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
20 changes: 20 additions & 0 deletions .changeset/12364-provider-api-prose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@object-ui/types': patch
'@object-ui/plugin-grid': patch
'@object-ui/plugin-map': patch
'@object-ui/plugin-calendar': patch
'@object-ui/plugin-gantt': patch
'@object-ui/react': patch
'@object-ui/auth': patch
'@object-ui/app-shell': patch
'@object-ui/core': patch
---

Published text no longer teaches the retired view-level `provider: 'api'` data source (objectui#12364). A view binds `provider: 'object'` or `provider: 'value'`; a metadata-declared API call is the page element's `dataSource` binding. Text only: no schema accepts or refuses anything new, and no renderer behaves differently.

- **`@object-ui/types`:** the `object-tree` `data` description drops the `api` provider from the providers it names.
- **`@object-ui/plugin-grid` / `@object-ui/plugin-map`:** the `data` input descriptions drop the `api` provider.
- **`@object-ui/plugin-calendar` / `@object-ui/plugin-gantt` / `@object-ui/plugin-map`:** the error thrown when no `DataSource` is available now reads `DataSource required for the object provider` (was `DataSource required for object/api providers`). It fires in exactly the same conditions as before.
- **`@object-ui/react` / `@object-ui/auth`:** the README and docblocks describe `SchemaRendererProvider`'s `apiFetch` as what it is now: the host's authenticated fetch the renderers use for their own reads of the host's API (chart metadata, dataset dimension labels, record-permission probes). It is not a channel for a view's data source.
- **`@object-ui/app-shell`:** comment wording only in `ConsoleShell`, `useConsoleActionRuntime` and `ObjectView` (these comments ship in the built JS): no behaviour change.
- **`@object-ui/core`:** docblock wording only on `normalizeListViewSchema` and `resolveRecordSourceObjectName` (these ship in `dist/*.d.ts` and the built JS): no behaviour change.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* objectui#12032 — approval requests through the standard `provider: 'api'`
* objectui#12032 — approval requests through a code-built `ApiDataSource`
* door, so `ListView` reads them with `viewer` as a field and the scopes stay
* server-side.
*
Expand Down
2 changes: 1 addition & 1 deletion content/docs/plugins/plugin-calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ const valueProviderCalendar: ObjectCalendarSchema = {

⚠️ Since **objectui#8348** a calendar authored with a record-source *config
object* under `data` — `{ provider: 'value', items: [...] }`,
`{ provider: 'object', object: … }`, `{ provider: 'api', … }` — is not honoured
`{ provider: 'object', object: … }` — is not honoured
here. `object-calendar`'s published `data` row is an **array of pre-fetched
records**, so the config object is refused by kind: `os validate` and the save
gate reject it, and as of that card the renderer does too.
Expand Down
3 changes: 2 additions & 1 deletion content/docs/plugins/plugin-map.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ const staticLocations = {
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`.
fails one step earlier, with the error an object-bound map gets:
`DataSource required for the object provider`.
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const FAILED_TO_RENDER = 'failed to render';
* draw the primitive, so this string is what would catch them sliding back to
* the bare keyword.
*/
const DATASOURCE_REQUIRED = 'DataSource required for object/api providers';
const DATASOURCE_REQUIRED = 'DataSource required for the object provider';

const ALL_DIAGNOSTICS = [UNKNOWN_COMPONENT, FAILED_TO_RENDER, DATASOURCE_REQUIRED];

Expand Down
12 changes: 7 additions & 5 deletions packages/app-shell/src/console/ConsoleShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,13 @@ export function ConnectedShell({ children }: { children: ReactNode }) {
);
}

// Authenticated fetch for `provider: 'api'` view data sources (#2725): custom
// endpoints get the same Authorization / X-Tenant-ID / Accept-Language headers
// as the native adapter channel, so a cookie whose HMAC signature rotated
// (e.g. dev-server restart) no longer strands them on 401. `sameOriginOnly`
// keeps the bearer token off third-party hosts a view's URL may point at, and
// The host's authenticated fetch, handed to the renderers as `apiFetch`
// (#2725): their own reads of the host's API (chart metadata, dataset
// dimension labels, record-permission probes) get the same Authorization /
// X-Tenant-ID / Accept-Language headers as the native adapter channel, so a
// cookie whose HMAC signature rotated (e.g. dev-server restart) no longer
// strands them on 401. `sameOriginOnly` keeps the bearer token off any
// third-party host a request may name, and
// withSettleSignal keeps these requests visible to the ADR-0054 C5 idle probe.
const apiProviderFetch = withSettleSignal(createAuthenticatedFetch({ sameOriginOnly: true }));

Expand Down
2 changes: 1 addition & 1 deletion packages/app-shell/src/hooks/useConsoleActionRuntime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons
// ruling): an action's `target` comes from author-supplied metadata and may
// name an off-origin host. Cross-origin requests pass through to the bare
// global fetch — no Authorization, X-Tenant-ID, or Accept-Language — matching
// the `provider: 'api'` data-source lane (ConsoleShell). Same-origin actions
// the renderers' `apiFetch` lane (ConsoleShell). Same-origin actions
// are unaffected. An off-origin integration that legitimately needs the
// platform bearer declares itself explicitly or proxies same-origin.
const authFetch = useMemo(() => createAuthenticatedFetch({ sameOriginOnly: true }), []);
Expand Down
2 changes: 1 addition & 1 deletion packages/app-shell/src/views/ObjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3507,7 +3507,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
// The census answers each in `ObjectView.relayRungCensus-7559.test.ts`.
//
// ViewData source override (spec `data` key): a view authored with
// `data: {provider:'api', read, write}` must survive this explicit
// `data: {provider:'value', items}` must survive this explicit
// picklist, or ObjectGantt falls back to provider:'object'.
data: (viewDef as any).data ?? (listSchema as any).data,
// Propagate new spec properties (P0/P1/P2)
Expand Down
8 changes: 4 additions & 4 deletions packages/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ import { createAuthenticatedFetch } from '@object-ui/auth';

const authedFetch = createAuthenticatedFetch();

// For fetches whose target URL comes from view metadata (`provider: 'api'`
// data sources), restrict credential injection to the current page's origin
// so the platform token never leaks to third-party hosts:
const apiProviderFetch = createAuthenticatedFetch({ sameOriginOnly: true });
// For fetches whose target URL may come from metadata (an API-call
// action's target), restrict credential injection to the current page's
// origin so the platform token never leaks to third-party hosts:
const sameOriginFetch = createAuthenticatedFetch({ sameOriginOnly: true });
```

### getSessionOwnerChangeCount / subscribeSessionOwnerChange
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/src/__tests__/createAuthenticatedFetch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ describe('createAuthenticatedFetch', () => {
expect(calls[0].headers.get('Accept-Language')).toBe('ja');
});

// ── sameOriginOnly (#2725) — provider:'api' fetches must not leak the
// bearer token to third-party hosts a view's metadata URL may name ──
// ── sameOriginOnly (#2725) — fetches must not leak the bearer token to
// third-party hosts a metadata URL (an action's target) may name ──

it('sameOriginOnly: still injects the token on same-origin API calls', async () => {
vi.spyOn(TokenStorage, 'get').mockReturnValue('tok123');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* auth lane — `createBearerFetch` inside `createAuthClient`, used by
* sign-in / get-session / the auth endpoints;
* data lane — `createAuthenticatedFetch`, used by the adapter, by
* `provider: 'api'` view data sources, and by EVERY metadata
* the renderers' `apiFetch`, and by EVERY metadata
* `type: 'api'` action (`useConsoleActionRuntime`'s `apiHandler`).
*
* Only the auth lane read the `set-auth-token` response header better-auth's
Expand Down
9 changes: 5 additions & 4 deletions packages/auth/src/createAuthenticatedFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ export interface CreateAuthenticatedFetchOptions {
* current page are passed through to the bare global fetch — no
* Authorization, X-Tenant-ID, or Accept-Language headers are attached.
*
* Use this for fetches whose target URL comes from view metadata
* (`provider: 'api'` data sources): those may point at third-party hosts
* that must never see the platform bearer token.
* Use this for fetches whose target URL may come from metadata (a
* `type: 'api'` action's target) or that a host hands to the renderers as
* `apiFetch`: those may point at third-party hosts that must never see the
* platform bearer token.
*/
sameOriginOnly?: boolean;
}
Expand Down Expand Up @@ -155,7 +156,7 @@ export function createAuthenticatedFetch(
//
// The console injects the same localStorage bearer from two places: the
// AUTH lane (sign-in / get-session / the auth endpoints) and THIS one — the
// adapter, `provider: 'api'` data sources, and every metadata `type: 'api'`
// adapter, the renderers' `apiFetch`, and every metadata `type: 'api'`
// action (`useConsoleActionRuntime`'s `apiHandler`). better-auth's
// server-side bearer plugin hands a rotated session token back in
// `set-auth-token` on whichever lane the call arrived over. Only the auth
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/utils/__tests__/normalize-list-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ describe('normalizeListViewSchema (#2890)', () => {
});

it('KEEPS `data` — unlike every other fold here, and deliberately', () => {
// `data` is not a legacy alias with one meaning: it has four providers,
// `api`/`value` are read live in ListView, and the whole block is
// `data` is not a legacy alias with one meaning: it has more providers
// than `object`, `value` is read live in ListView, and the whole block is
// forwarded to child views whose own `getDataConfig` reads it BEFORE
// `objectName`. Deleting it for one provider would rewrite what a child
// resolves. See the note on `normalizeListViewSchema`.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/utils/normalize-list-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,8 @@ function readListViewKind(value: unknown): string | undefined {
* · an already-present `objectName` WINS — this fold only fills a gap, it
* never re-points a binding that already resolves;
* · `data` is NOT deleted. Every other fold deletes because the legacy key
* has one meaning and one home; `data` has four providers, `api`/`value`
* are read live in `ListView`, and the whole block is FORWARDED to child
* has one meaning and one home; `data` has more providers than
* `object`, `value` is read live in `ListView`, and the whole block is FORWARDED to child
* views (the gantt branch), whose own `getDataConfig` reads `data` before
* `objectName`. Deleting it for one provider would rewrite what a child
* resolves. Nothing in `ListView` reads `data.provider === 'object'`, so
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/utils/record-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import type { ViewData } from '@object-ui/types';
* The ladder's second rung wraps inline rows as `{ provider: 'value', items }`,
* which names no object at all. So for the object-NAME question the three-rung
* ladder reduces to two rungs — the resolved config's object when it names one,
* else the schema's own `objectName`, which is what a `value`/`api`-backed block
* else the schema's own `objectName`, which is what a `value`-backed block
* still needs for metadata reads, i18n field labels and permission verdicts.
* Callers pass the ALREADY-RESOLVED config (their
* {@link resolveRecordSourceConfig} output, direct or through a local wrapper),
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-calendar/src/ObjectCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ export const ObjectCalendar: React.FC<ObjectCalendarComponentProps> = ({
// inline set has no metadata to build and behind the
// `objectSchemaReady` gate deliberately scoped to that same arm.
// Falling through here would therefore throw
// `DataSource required for object/api providers` on a calendar that
// `DataSource required for the object provider` on a calendar that
// needs no DataSource at all. So the adapter is resolved for the
// inline provider ONLY, and the same three keys are lowered onto the same query shape.
//
Expand Down Expand Up @@ -892,7 +892,7 @@ export const ObjectCalendar: React.FC<ObjectCalendarComponentProps> = ({
}

if (!dataSource || typeof dataSource.find !== 'function') {
throw new Error('DataSource required for object/api providers');
throw new Error('DataSource required for the object provider');
}

if (dataProvider === 'object') {
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-charts/src/ObjectChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,8 @@ export const ObjectChart = (props: ObjectChartProps) => {
// Resolve the category dimension's option colors (P3). Best-effort: any
// failure leaves categoryColors null and the chart keeps the theme palette.
//
// Both metadata reads ride the host's AUTHENTICATED fetch (#4114) — the same
// channel `provider: 'api'` view sources use — falling back to the global one
// Both metadata reads ride the host's AUTHENTICATED fetch (#4114) — the
// `SchemaRendererProvider`'s `apiFetch` — falling back to the global one
// only when no host supplies it. A bearer-token session carries its
// credential in the `Authorization` header, not a cookie, so
// `credentials: 'include'` alone left these two reads unauthenticated in a
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-detail/src/useRecordEditable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* user cannot act.
*
* The probe rides the host's AUTHENTICATED fetch (`SchemaRendererProvider`'s
* `apiFetch`, the same channel `provider: 'api'` view sources use), not the bare
* `apiFetch`), not the bare
* global one. A bearer-token session carries its credential in the
* `Authorization` header, not a cookie, so `credentials: 'include'` alone made
* every probe 401 on a perfectly valid admin session — the verdict then always
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-gantt/src/ObjectGantt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ describe('ObjectGantt', () => {
expect(screen.getByTestId('gv-field-1-1').textContent).toBe('Tags=a, b');
});

it('falls back to quickFilters labels + ISO date sniffing when there is no object schema (api provider)', async () => {
// The api provider returns records but no object schema, so tooltip fields
it('falls back to quickFilters labels + ISO date sniffing when there is no object schema (inline value rows)', async () => {
// Inline `value` rows come with no object schema, so tooltip fields
// have no defs: machine values and raw ISO strings used to leak through.
const schema: any = {
type: 'gantt',
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-gantt/src/ObjectGantt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ export const ObjectGantt: React.FC<ObjectGanttProps> = ({
// resolved; what it is not is a reason to rebuild this callback.
const adapter = effectiveDataSourceRef.current;
if (!adapter || typeof adapter.find !== 'function') {
throw new Error('DataSource required for object/api providers');
throw new Error('DataSource required for the object provider');
}

// 'object' → the context adapter (resolved above).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* and a bar handed back ending on a day's midnight is written as the day
* before it, the day it runs through. Two
* adapters have no types to give: one with no `getObjectSchema`, and one whose
* schema carries no fields (the `api` provider's adapter answers
* schema carries no fields (a code-built `ApiDataSource` answers
* `{ fields: {} }`). A chart with a business `timeZone` takes the same split
* through the shim, as the schema-declared pin beside this one measures.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-grid/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ const GRID_QUERY_INPUTS: ComponentInput[] = [
{ name: 'sort', type: 'array', description: 'Initial sort order, `[{ field, order }]`. The only sort spelling this block reads — the retired single-sort `defaultSort` is refused by the protocol and ignored by the renderer.' },
{ name: 'pagination', type: 'object', description: 'Pagination config, `{ pageSize, pageSizeOptions, … }`. Presence enables paging with the object\'s settings, and an explicit off wins — the deprecated flat `showPagination: false` turns paging off even beside this object, because this object declares no off switch of its own. Prefer it over the deprecated flat `pageSize` / `showPagination` pair.' },
{ name: 'searchableFields', type: 'array', of: 'string', description: 'Fields the toolbar search box queries. A non-empty list is what enables search — prefer it over the deprecated boolean `showSearch`, which cannot say WHICH fields to search.' },
{ name: 'data', type: 'object', description: 'Data source configuration — a `ViewData` object discriminated by `provider`: `{ provider: "object", object }` (what an omitted `data` falls back to, using `objectName`), `{ provider: "api", read, write }`, `{ provider: "value", items: [...] }` for inline rows that bypass the object query, or `{ provider: "schema", schemaId }`. The canonical spelling — the deprecated `staticData` is the array-only shortcut for the `value` provider, so inline rows go under `items` here rather than in a bare array.' },
{ name: 'data', type: 'object', description: 'Data source configuration — a `ViewData` object discriminated by `provider`: `{ provider: "object", object }` (what an omitted `data` falls back to, using `objectName`), `{ provider: "value", items: [...] }` for inline rows that bypass the object query, or `{ provider: "schema", schemaId }`. The canonical spelling — the deprecated `staticData` is the array-only shortcut for the `value` provider, so inline rows go under `items` here rather than in a bare array.' },
// ── presentation ──────────────────────────────────────────────────────────
{ name: 'rowHeight', type: 'enum', enum: ['compact', 'short', 'medium', 'tall', 'extra_tall'], description: 'Row density. An unrecognised value falls back to `compact` rather than erroring.' },
{ name: 'frozenColumns', type: 'number', description: 'How many leading columns stay pinned while the grid scrolls horizontally.' },
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-list/src/ListViewBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const ListViewBlock = elementDataSourceBlock(React.forwardRef<ListViewHandle, Li
errorTitle="This list view’s data source could not be resolved"
// A list that names its object in neither place has nothing to fetch, and
// drew the "Nothing here yet" empty state, which reads as an empty query
// (objectui#11605). Inline `data` (rows, or a `value` / `api` provider) is
// (objectui#11605). Inline `data` (rows, or a `value` provider) is
// the list's one other record source, so its presence opts out.
requiresObject={props.schema?.data == null}
>
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-map/src/ObjectMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
// shared query. This effect's `find` sits INSIDE the
// `dataProvider === 'object'` arm, behind an `$expand` projection an
// inline set has no metadata to build. Falling through here would
// therefore throw `DataSource required for object/api providers` on a
// therefore throw `DataSource required for the object provider` on a
// map that needs no DataSource at all. So the adapter is resolved for
// the inline provider ONLY, and the same three keys are lowered onto the same query shape.
//
Expand Down Expand Up @@ -1014,7 +1014,7 @@ export const ObjectMap: React.FC<ObjectMapProps> = ({
}

if (!dataSource || typeof dataSource.find !== 'function') {
throw new Error('DataSource required for object/api providers');
throw new Error('DataSource required for the object provider');
}

if (dataProvider === 'object') {
Expand Down
Loading
Loading