Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/12325-retire-api-view-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@object-ui/core': minor
'@object-ui/react': minor
'@object-ui/plugin-gantt': minor
'@object-ui/plugin-list': minor
'@object-ui/plugin-calendar': patch
'@object-ui/plugin-map': patch
'@object-ui/types': patch
'@object-ui/console': patch
---

The view-level `provider: 'api'` data source is no longer read anywhere in objectui (objectui#12325). `@objectstack/spec` retires it (objectstack#23082, ADR-0049): a view binds its object (`provider: 'object'`) or static rows (`provider: 'value'`), and a metadata-declared API call is the page element's `dataSource` binding.

Breaking, declared as `minor` per the version-alignment rule:

- **`resolveDataSource`** (`@object-ui/core`) has no `api` arm. A block that still says `provider: 'api'` is an unknown provider and gets an unknown provider's answer (the fallback DataSource, else `null`); no `ApiDataSource` is built from its `read` / `write`. `ResolveDataSourceOptions` loses `fetch` and `defaultHeaders`, which only that arm read.
- **`ViewDataProvider`** (`@object-ui/core`) has no `api` arm: such a config resolves with `Unknown data provider: api`. `ViewDataConfig` and `ResolvedData.provider` drop `'api'`, and `DataFetcher.fetchUrl`, which only that arm called, is removed.
- **`useViewData`** (`@object-ui/react`) no longer keys on or threads anything for an `api` block.
- **`ObjectGantt`** reads no `api` block: its `read` URL is never requested, and the chart answers it like any unknown provider. `ObjectCalendar` and `ObjectMap` drop their `api` branches, which only warned "not yet implemented".
- **`ListView`** no longer stands down for a gantt view with an `api` block: it fetches the bound object like any other list.

Unchanged: `ApiDataSource` stays a class for code that constructs it from an `ApiDataSourceConfig`, so the console's approvals inbox reads exactly as before.
5 changes: 3 additions & 2 deletions apps/console/src/services/approvalRequestsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* `ListView` for a list, `RecordDetailView` for one request. Both take ONE
* `dataSource`. `RecordDetailView` also sends its side reads through it
* (`sys_user`, comments, activities, history). So this source routes. Reads of
* `sys_approval_request` go to the approvals routes through the existing
* `provider: 'api'` door (`ApiDataSource`). Every other resource, and every
* `sys_approval_request` go to the approvals routes through an `ApiDataSource`
* built here in code (not a view's `provider: 'api'` block, which is retired —
* objectui#12325). Every other resource, and every
* other method, goes to the host adapter unchanged. The rows come back exactly
* as the approvals service serves them, so `viewer` (list and get) and
* `decision_progress` (get only, by contract) are ordinary fields a view binds.
Expand Down
37 changes: 8 additions & 29 deletions content/docs/plugins/plugin-gantt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The `@object-ui/plugin-gantt` plugin provides Gantt chart visualization for Obje

## Features

- **ObjectQL Integration**: Works seamlessly with object/api/value data providers
- **ObjectQL Integration**: Works seamlessly with object/value data providers
- **Automatic Field Mapping**: Maps database fields to Gantt tasks
- **Task Timeline**: Visual bars showing task duration
- **Progress Tracking**: Display task completion percentage (0-100%)
Expand Down Expand Up @@ -332,35 +332,14 @@ const valueProviderGantt: ObjectGanttBlockNode = {
};
```

### API Provider
### No API provider

```tsx
import type { ObjectGanttBlockNode } from '@object-ui/types';

const apiProviderGantt: ObjectGanttBlockNode = {
type: 'object-gantt',
properties: {
data: {
provider: 'api',
// the api member is `read` / `write` HTTP requests — there is no
// top-level `endpoint` key, and nothing reads one
read: { url: '/api/project/tasks', method: 'GET' }
},
gantt: {
startDateField: 'startDate',
endDateField: 'endDate',
titleField: 'taskName',
progressField: 'percentComplete'
}
}
};
```

When the view renders inside a `SchemaRendererProvider` that supplies an
`apiFetch` (the console host wires an authenticated fetch there), api-provider
requests carry the same credentials — `Authorization`, tenant, and locale
headers — as native platform requests. Without it, requests fall back to the
bare global fetch and rely on same-origin cookies alone.
The view-level `{ provider: 'api', read, write }` data source is retired in
`@objectstack/spec` (objectstack#23082). A gantt reads its object
(`provider: 'object'`) or inline rows (`provider: 'value'`); a
metadata-declared API call is the page element's `dataSource` binding. A
gantt that still carries an `api` block is not read through it: its `read` /
`write` URLs are never requested.

## Event Handling

Expand Down
10 changes: 5 additions & 5 deletions content/docs/plugins/plugin-map.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,13 @@ const staticLocations = {
};
```

### API Provider — not implemented
### No API provider

`data.provider: 'api'` has no fetch implementation in `ObjectMap`. A schema that
reaches this branch logs `API provider not yet implemented for ObjectMap`, sets
the record set to empty and renders a map with no markers; `endpoint` and
`method` have no read point anywhere in the package. Without a `DataSource` it
The view-level `data.provider: 'api'` is retired in `@objectstack/spec`
(objectstack#23082), and `ObjectMap` has no branch for it: a schema that still
carries one plots no markers and requests nothing. Without a `DataSource` it
fails one step earlier, with `DataSource required for object/api providers`.
A metadata-declared API call is the page element's `dataSource` binding.

Read from the database with the **Object Provider** above, or pass records you
already hold with the **Value Provider**.
Expand Down
14 changes: 9 additions & 5 deletions packages/core/src/adapters/ApiDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* A DataSource adapter for the `provider: 'api'` ViewData mode.
* Makes raw HTTP requests using the HttpRequest configs from ViewData.
* A DataSource adapter over raw HTTP, built in code from an
* `ApiDataSourceConfig` (e.g. the console's approvals inbox). It is not a
* `ViewData` mode: the view-level `provider: 'api'` is retired in
* `@objectstack/spec` (objectstack#23082), and `resolveDataSource` builds no
* `ApiDataSource` (objectui#12325).
*/

import type {
Expand Down Expand Up @@ -126,8 +129,9 @@ function mergeHeaders(
/**
* ApiDataSource — a DataSource adapter for raw HTTP APIs.
*
* Used when `ViewData.provider === 'api'`. The read and write HttpRequest
* configs define the endpoints; all CRUD methods map onto HTTP verbs.
* Constructed in code — never resolved from a `ViewData` block (see the file
* header). The read and write HttpRequest configs define the endpoints; all
* CRUD methods map onto HTTP verbs.
*
* Read operations use the `read` config, write operations use the `write` config.
* Both fall back to each other when one is not provided.
Expand Down Expand Up @@ -172,7 +176,7 @@ export class ApiDataSource<T = any> implements DataSource<T> {
if (!base) {
throw new Error(
'ApiDataSource: No HTTP configuration provided for this operation. ' +
'Ensure the ViewData has read/write configs.',
'Ensure the ApiDataSourceConfig has read/write configs.',
);
}

Expand Down
15 changes: 9 additions & 6 deletions packages/core/src/adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ them from `@object-ui/core`.

| Export | Kind | Role |
| --- | --- | --- |
| `ApiDataSource` | class | `provider: 'api'` — raw HTTP against the `HttpRequest` configs carried in `ViewData` |
| `ApiDataSource` | class | raw HTTP against the `HttpRequest` configs it is constructed with — built in code, never from a `ViewData` block |
| `ValueDataSource` | class | `provider: 'value'` — an in-memory array; no network at all |
| `resolveDataSource` | function | builds the adapter a `ViewData` config asks for (`object` / `api` / `value`) |
| `resolveDataSource` | function | builds the adapter a `ViewData` config asks for (`object` / `value`) |
| `runBatchTransaction` / `emulateBatchTransaction` | functions | ordered cross-object save — the adapter's own `batchTransaction` when it has one, a sequential non-atomic emulation when it does not |

Backend-specific adapters live in their own packages rather than here; today
that is `@object-ui/data-objectstack`.

### `ApiDataSource`

For `provider: 'api'`. The endpoint comes from the `HttpRequest` configs, not
Built in code from an `ApiDataSourceConfig` — the console's approvals inbox is
one such caller. It is not a `ViewData` mode: the view-level `provider: 'api'`
is retired in `@objectstack/spec` (objectstack#23082), so `resolveDataSource`
never builds one, and a metadata-declared API call is the page element's
`dataSource` binding instead. The endpoint comes from the `HttpRequest` configs, not
from the `resource` argument — `find`/`findOne` use `read`, and
`create`/`update`/`delete` use `write` (falling back to `read` when `write` is
absent). `QueryParams` are flattened onto the query string.
Expand Down Expand Up @@ -273,17 +277,16 @@ import type { DataSource } from '@object-ui/types';
declare const contextDataSource: DataSource;

const dataSource = resolveDataSource(
{ provider: 'api', read: { url: '/api/users' } },
{ provider: 'value', items: [{ id: '1', name: 'Ada' }] },
contextDataSource, // used for `provider: 'object'`, and as the fallback
);
```

| `viewData.provider` | Result |
| --- | --- |
| `'object'` | the `fallback` — the `DataSource` from context, typically `ObjectStackAdapter` |
| `'api'` | a new `ApiDataSource` built from `read` / `write` |
| `'value'` | a new `ValueDataSource` over `items` |
| unknown, or no `viewData` | the `fallback`, else `null` |
| unknown (the retired `'api'` included), or no `viewData` | the `fallback`, else `null` |

### `runBatchTransaction` / `emulateBatchTransaction`

Expand Down
44 changes: 19 additions & 25 deletions packages/core/src/adapters/__tests__/resolveDataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,37 +62,31 @@ describe('resolveDataSource', () => {
});

// -----------------------------------------------------------------------
// provider: 'api'
// provider: 'api' — retired (objectstack#23082, objectui#12325)
// -----------------------------------------------------------------------

it('should create ApiDataSource for provider: "api"', () => {
const viewData: ViewData = {
provider: 'api',
read: { url: '/api/contacts' },
};
const ds = resolveDataSource(viewData);
expect(ds).toBeInstanceOf(ApiDataSource);
});

it('should create ApiDataSource with read and write configs', () => {
const viewData: ViewData = {
// These cases used to pin that an `api` block built an `ApiDataSource` from
// its `read` / `write`. The view-level `api` data source is retired in the
// spec, so the arm is gone: the block is an unknown provider and gets an
// unknown provider's answer. The `ApiDataSource` class itself stays, for
// code that constructs it (ApiDataSource.test.ts pins that).

it('builds no ApiDataSource for a retired provider: "api" block; it falls back like an unknown provider', () => {
const retired = {
provider: 'api',
read: { url: '/api/contacts', method: 'GET' },
write: { url: '/api/contacts', method: 'POST' },
};
const ds = resolveDataSource(viewData);
expect(ds).toBeInstanceOf(ApiDataSource);
} as unknown as ViewData;
const withFallback = resolveDataSource(retired, mockFallback);
expect(withFallback).toBe(mockFallback);
expect(withFallback).not.toBeInstanceOf(ApiDataSource);
expect(resolveDataSource(retired)).toBeNull();
});

it('should pass adapter options to ApiDataSource', () => {
const viewData: ViewData = {
provider: 'api',
read: { url: '/api/contacts' },
};
const ds = resolveDataSource(viewData, null, {
defaultHeaders: { Authorization: 'Bearer test' },
});
expect(ds).toBeInstanceOf(ApiDataSource);
it('answers a retired provider: "api" block exactly as it answers any unknown provider', () => {
const retired = { provider: 'api', read: { url: '/api/contacts' } } as unknown as ViewData;
const unknown = { provider: 'nonexistent' } as unknown as ViewData;
expect(resolveDataSource(retired, mockFallback)).toBe(resolveDataSource(unknown, mockFallback));
expect(resolveDataSource(retired)).toBe(resolveDataSource(unknown));
});

// -----------------------------------------------------------------------
Expand Down
26 changes: 9 additions & 17 deletions packages/core/src/adapters/resolveDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@
*/

import type { DataSource, ViewData } from '@object-ui/types';
import { ApiDataSource, type ApiDataSourceConfig } from './ApiDataSource.js';
import { ValueDataSource } from './ValueDataSource.js';

export interface ResolveDataSourceOptions {
/** Custom fetch implementation passed to ApiDataSource */
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
/** Default headers for API requests */
defaultHeaders?: Record<string, string>;
/** Custom ID field for ValueDataSource */
idField?: string;
}
Expand All @@ -25,9 +20,16 @@ export interface ResolveDataSourceOptions {
* Resolve a ViewData configuration into a concrete DataSource instance.
*
* - `provider: 'object'` → returns `fallback` (the context DataSource — typically ObjectStackAdapter)
* - `provider: 'api'` → returns a new `ApiDataSource`
* - `provider: 'value'` → returns a new `ValueDataSource`
*
* There is no `provider: 'api'` arm. The view-level `api` data source is
* retired in `@objectstack/spec` (objectstack#23082, ADR-0049): a view binds
* its object or static rows, and a metadata-declared API call is the page
* element's `dataSource` binding. A view block that still says `api` is an
* unknown provider here and gets the same answer as one, so no request is
* ever built from its `read` / `write` (objectui#12325). Code that needs raw
* HTTP constructs `ApiDataSource` itself, from an `ApiDataSourceConfig`.
*
* @param viewData - The ViewData configuration from the schema
* @param fallback - The default DataSource from context (for `provider: 'object'`)
* @param options - Additional options for adapter construction
Expand All @@ -36,7 +38,7 @@ export interface ResolveDataSourceOptions {
* @example
* ```ts
* const ds = resolveDataSource(
* { provider: 'api', read: { url: '/api/users' } },
* { provider: 'value', items: [{ id: '1', name: 'Ada' }] },
* contextDataSource,
* );
* const result = await ds.find('users');
Expand All @@ -56,16 +58,6 @@ export function resolveDataSource<T = any>(
// Delegate to the context DataSource (ObjectStackAdapter, etc.)
return fallback ?? null;

case 'api': {
const config: ApiDataSourceConfig = {
read: viewData.read,
write: viewData.write,
fetch: options?.fetch,
defaultHeaders: options?.defaultHeaders,
};
return new ApiDataSource<T>(config);
}

case 'value':
return new ValueDataSource<T>({
items: (viewData.items ?? []) as T[],
Expand Down
Loading
Loading