You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 62ce45d
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec): a page element dataSource can name an API endpoint (provider: 'api') (#23079)
Fixes#23016
Clause-②: yes (widening: a new accepted shape)
## What
A page element's `dataSource` gains a second shape beside the object
binding, per the maintainer's ruling in the card body: the **API
binding** `{ provider: 'api', read: { url, method? }, path? }`, exported
as `ElementApiDataSourceSchema` (type `ElementApiDataSource`, pinned
isomorphic). `PageComponentSchema.dataSource` is now
`z.union([ElementDataSourceSchema, ElementApiDataSourceSchema])`.
- **Same-origin only, refused at parse, every class named.** `read.url`
must open with a single `/`. Refused at `dataSource.read.url`
(`invalid_format`), with a message that names the class, the rule and
the fix, and never echoes the value: `scheme` (an absolute URL of any
scheme: `https:`, `http:`, `HTTPS:`, `javascript:`, `data:`, `mailto:`,
`localhost:3000/…`), `protocol-relative` (`//host`), `backslash` (a
slash then a backslash, or a leading backslash: the URL parser reads
both as `//host`), `whitespace-or-control` (tab, LF, CR anywhere; a
space at either end, which the parser strips, so a space before `//host`
is `//host`; a mid-path space) and `relative` (`./home`, `../home`,
`api/v1/x`, `?q`, `#f`, the empty string). The first four are the
browser-equivalent spellings objectui's `classifyNavigationTarget`
refuses for a navigation. Accepted: `/api/v1/cloud/home`, with a query
string, and `/`.
- **The rule is a `.regex()`, not a refinement**, so it projects into
the published JSON Schema as `pattern`. A refinement would be dropped
from `json-schema/` (`dropped-refinements.baseline.json`), and a
validator would accept a URL the parse refuses. A pin holds the
published `pattern` to the same verdict as the parse on all 23 refused
values and 3 accepted ones.
- **No credentials in metadata.** The read block declares `url` and
`method` and nothing else (the view-level `HttpRequestSchema` carries
`headers` / `params` / `body`; this one does not). `headers`,
`credentials`, `auth`, `authorization`, `token` and `apiKey` are refused
with a prescription that names the reuse of the console's signed-in
fetch; `params` and `body` are refused with their own fix.
- **`method` admits `GET` alone**, the only method the renderer sends
for a read (measured below).
- **`path`** is a dotted path into the `{ success, data }` envelope
(`data.environments`): segments of letters, digits, `_`, `-`, joined by
single dots; a JSONPath (`$.data`), brackets, slashes or empty segments
are refused at `dataSource.path`.
- The API arm is closed (`strictObject`); `object`, `write` and a
top-level `url` / `method` on it are refused with guidance.
## The other halves of the acceptance list (not this repository)
- **Acceptance 1, `objectui validate` accepts a page using the `api`
shape:** objectui's half. objectui's block schemas import
`ElementDataSourceSchema` for their object-bound `dataSource`
(`packages/types/src/zod/objectql.zod.ts`). That is why the object arm
keeps that name and meaning. The generic blocks that read the API
binding, and their validation, are objectstack-ai/objectui#12197,
re-scoped by the same ruling.
- **Acceptance 3, a cloud page binds `GET /api/v1/cloud/home` with no
cloud-specific React:** cloud's half, objectstack-ai/cloud#2777 part B.
- **Acceptance 2, the docs:** here. The generated page reference
(`content/docs/references/ui/page.mdx`) now lists `ElementApiDataSource`
beside `ElementDataSource`, and so does `PageComponent.dataSource[option
2]`. The hand-written `content/docs/ui/pages.mdx` gains an "Element data
sources" section with both shapes in one type-checked (`os:check`)
example and the API binding's rules.
## Measured calls (the dispatch's mechanism assumptions)
1. **A plain `z.union`; no `provider: 'object'` on the object arm.** zod
4.6.5's `$ZodDiscriminatedUnion` constructor throws `Invalid
discriminated union option` for an option whose shape lacks the
discriminator key (read in `zod/v4/core/schemas.js`). So a discriminated
union would have needed a `provider: 'object'` key on the object
binding: a second spelling of the same binding, and a change to the def
objectui imports. With the union, the object arm's accept set and
published def are byte-identical (`authorable-surface/` and
`json-schema.manifest/` diffs are additions only).
**The cost, measured:** a refused object binding parsed through
`PageComponentSchema` now arrives as one `invalid_union` at
`dataSource`, whose object branch carries the old issue.
`formatZodIssue` and `zodIssuesToFields` add one `dataSource: Invalid
input` entry, then render the branch at the old path
(`dataSource.filter`, `dataSource.object`). An API-binding refusal
arrives directly at its path (`dataSource.read.url`). Two
`dropped-refinements.baseline.json` sites moved path
(`in.dataSource.filter.element` became
`in.dataSource.options[0].filter.element`, on `ui/PageComponent` and
`ui/PageRegion`); no new gap.
2. **`./relative` is refused.** Measured at objectui `7578323a544e`: the
`apiBase` promotion of a `url` action (`ActionRunner.navigateTo`)
prefixes only root-relative `/(api|_auth|_account)/…` paths. A `./x`
read is a `fetch` resolved against the console route the page is mounted
on, on the SPA host. `classifyNavigationTarget` admits `./` for a
navigation, which is a different question.
3. **`method`: `GET` only.** Measured at objectui `7578323a544e`:
`ApiDataSource.find`, `findOne` and `aggregate` each pass `method:
'GET'` explicitly, which overrides `read.method` (`options.method ??
base.method`), so no other value would ever be sent.
4. **No renderer reads the API binding yet.** Measured at objectui
`7578323a544e`: `useElementDataSource` takes `schema.dataSource` only
through `isElementDataSourceConfig` (a plain object with a non-empty
string `object`), so an API binding reaches no block. `SchemaRenderer`
strips `dataSource` from the props spread, so the binding cannot shadow
an injected adapter either. The reader is objectstack-ai/objectui#12197.
The schema's describe carries `[PLANNED — not yet rendered]`.
5. **Liveness: no ledger row is possible.** The page ledger's walk stops
at `page/regions` and `page/slots`, recorded undrilled containers. No
component-level key has a row, and `childShape()` does not descend a
`z.union`. The planned record therefore went into `liveness/page.json`'s
`_note`, with carrier objectstack-ai/objectui#12197, and
`check:liveness` is green. The checkable records are the
`ui/ElementApiDataSource:*` authorable-surface rows and the describe
marker.
## Readers of the schema: each one updated or unaffected
- `PageComponentSchema.dataSource`: **updated** (the union).
- Types: `ElementDataSource` / `ElementDataSourceParsed` are
**unaffected** (object arm unchanged). `ElementApiDataSource` is new.
`PageComponent['dataSource']` is now a union, so a typed read of
`.object` must narrow. The one in-tree typed reader (`page.test.ts`) was
updated. Reverse check against the rebuilt `dist/ui/index.d.mts`: `read:
{ method: 'POST' }` fails with `TS2322: Type '"POST"' is not assignable
to type '"GET"'`, the valid object and API components compile, and
removing the bad line returns exit 0.
- `visibleWhen`'s describe (the `data` binding): **unaffected**. It
already records `data` as renderer behaviour, not contract. The API
arm's own describe states what an API-bound element reads as `data`,
marked planned.
- `@objectstack/lint`: `page-walk.ts`, `validate-component-props.ts`
(`DATA_SOURCE_BOUND_ELEMENT_TYPES` owe `dataSource.object`),
`validate-action-name-refs.ts`, `validate-preset-comparands.ts` and
`validate-page-field-bindings.ts` are **unaffected**. All read untyped
(`isRec` / `strName`). An API binding has no `object`, so each falls
back exactly as for no binding. For `element:record_picker` /
`element:number` / `element:repeater` the existing warning stays
correct, because those elements read only the object binding.
- `@objectstack/cli` `i18n-extract.ts`: **unaffected** (through lint's
page walk, same fallback).
- `packages/spec/scripts/check-yaml-examples.ts`: **unaffected** (the
same rule as `validate-component-props`).
- Conversions `element-flat-data-binding-to-data-source` and
`page-component-filter-record-to-rule-array`: **unaffected**. The first
moves only tombstoned flat keys, which the parse refuses (and a v17 row
cannot carry an API binding). The second rewrites `dataSource.filter`,
which the API arm has none of.
- `@objectstack/metadata-protocol`: **unaffected**. No `src` reader; its
tests assert conversion TODO paths, not zod paths.
- `@objectstack/sdui-parser`: **unaffected**, with zero `dataSource`
references.
- `@objectstack/objectql` (its test reads the stored item untyped) and
`examples/app-showcase` (authors object bindings; a test reads `as
any`): **unaffected**, tests run below.
## Verification
Readings at head `b6c1d0790` (this branch with `origin/main` `466380132`
merged in, then the references index regenerated on the merged tree)
unless marked otherwise. Commands under `scripts/pm/os-verify-lock.sh`,
exit codes captured before any pipe.
- **Gate families.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 115 families, the same list
at the pre-merge head and at `b6c1d0790`. All 115 were run at
`b6c1d0790`: 114 exit 0 and 1 exit 1. `--ran` reconciliation: `115
derived, 115 run, 0 NOT-MEASURED, 0 UNRUN`.
- The red one is `pnpm check:platform-checklist`: 8 `ABSENT SYMBOL` /
floor problems in
`docs/qa/platform-checklist/areas/{access-security,attachments-storage,search}.json`,
which cite symbols in `metadata-protocol/src/protocol.ts` and
`service-storage`. This diff touches none of its inputs.
- Control leg: the same command on a clean checkout at the base
`df7a137923` exits 1 with the same 8 problems.
- `checklist-status.yml` keeps this gate out of per-PR CI.
- **Spec build and generated artifacts.** `pnpm --filter
@objectstack/spec build` exits 0. `pnpm --filter @objectstack/spec
check:generated` exits 0, with all 14 artifacts current,
`check:test-typecheck` among them.
- **Spec tests at `b6c1d0790`.** 7 files, 315 passed in the `local`
project: `page-element-api-data-source`, `page`,
`type-alias-convention.pin`, `alias-integrity`,
`union-author-message-pins`,
`page-component-filter-record-to-rule-array` and
`filter-rule-array-guidance`. `element-flat-binding-retirement` passed
56 in the `repo` project.
- **Before the merge, at `22930dae3` (the same diff, older base):**
- `pnpm --filter @objectstack/spec test`: 655 files, 19699 passed.
- `@objectstack/lint` test: 137 files, 6569 passed.
- `@objectstack/metadata-protocol` test: 227 files passed, 3 skipped;
28190 tests passed.
- `@objectstack/cli` `vitest run --project unit`: 282 of 283 files
passed. The 2 failures were 5 s timeouts in
`test/hook-timeout-override-refusal.test.ts`, which spawns vitest, while
the gate battery shared the cores. Run alone afterwards: 4/4 passed.
- `@objectstack/example-showcase` test: 33 files, 409 passed.
- `@objectstack/objectql`
`src/registry-cross-package-item-classes.test.ts`: 14 passed.
- The consumer suites were not re-run after the merge. The incoming
commits touch none of the reader files listed above and not
`page.zod.ts`; their one `dataSource` mention is a code comment.
- **Reverse type check against the rebuilt declarations.** A scratch
program compiled against `packages/spec/dist/ui/index.d.mts`. The bad
line, `read: { method: 'POST' }`, gives exit 2 with `TS2322: Type
'"POST"' is not assignable to type '"GET"'`. Removing it gives exit 0,
with an object-bound and an API-bound `PageComponent` still compiling.
- **Control bytes.** `grep -naP` for raw control bytes over every
changed file finds none: the regexes spell `\x00`-`\x20` and `\x7F` as
escapes. `pnpm check:nul-bytes` exits 0.
## Acceptance notes
Observations recorded here and not filed (no public-door measurement, or
zero producers):
- **`@objectstack/lint` `page-walk.ts` inherits the page's `object` for
an API-bound node.** An API binding has no `object`, so the walk falls
through to `properties.object` and then the page object. Every reader of
`objectName` (field bindings, preset comparands, i18n section keys)
would then judge an API-bound block's field-shaped props against the
page's object. No API-bound block exists today. The first field-bearing
generic block (objectstack-ai/objectui#12197) is where this needs an
answer. Carrier: none in this repository yet.
- **objectui's `url`-action `apiBase` promotion hard-codes
`/(api|_auth|_account)/`, while the REST `basePath` is configurable
(default `/api`).** A renderer for this binding that reuses the rule
would not promote a deployment with another base path. Carrier:
objectstack-ai/objectui#12197.
- **The view-level `ViewDataSchema` `api` arm (`HttpRequestSchema`)
still accepts a cross-origin `url` and a `headers` map.** Its `method`
admits `POST`/`PUT`/`PATCH`/`DELETE`, which `ApiDataSource`'s reads
never send. The ruling scoped same-origin and no-credentials to the
page-element binding only. This is a read-only inference: the producers
measured in this repository (`content/docs/ui/views.mdx`,
`skills/objectstack-ui/rules/list-views.md`) write `GET` and a
same-origin path. Carrier: none.
- **When objectstack-ai/objectui#12197 lands,** the `[PLANNED — not yet
rendered]` marker leaves `ElementApiDataSourceSchema`'s describe, the
`liveness/page.json` `_note` sentence is rewritten, and the "Not
rendered yet" bullet in `content/docs/ui/pages.mdx` goes.
- `skills/objectstack-ui/references/_index.md` changed as a pure
`gen:skill-refs` regeneration. `check-governed-merges.mjs --test` over
the final file list answers NOT governed after the generated-artifact
lift.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01GZSPSQ5ArPjSJdxxuqwGoZ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec): a page element's `dataSource` can name an API endpoint — `{ provider: 'api', read: { url }, path? }`
6
+
7
+
Clause-②: yes (widening: a new accepted shape)
8
+
9
+
-**What is new.**`PageComponentSchema.dataSource` takes a second shape beside the object binding: the API binding, exported as `ElementApiDataSourceSchema` (type `ElementApiDataSource`). It reads one endpoint on the deployment's own origin and binds its answer, so a product page composes from a summary that is not an object — `{ provider: 'api', read: { url: '/api/v1/cloud/home' }, path: 'data.environments' }`. `path` is an optional dotted path into the `{ success, data }` response; omitted, the element binds the whole response.
10
+
-**Same-origin only, refused at parse.**`read.url` must open with a single `/`. Five classes are refused with the reason and the fix, at `dataSource.read.url`: an absolute URL of any scheme (`https:`, `http:`, `mailto:`, `javascript:`, `data:`, …), a protocol-relative `//host`, a backslash the browser reads as a slash (`/` then a backslash, or a leading backslash), a space or ASCII control character (a tab, line feed or carriage return anywhere is deleted by the URL parser, so `/`, a tab, `/host` is `//host`), and a relative path such as `./home` or `api/v1/x`, which resolves against the page, never the API. The rule is a `pattern` in the published JSON Schema too, so a validator reading `json-schema/` refuses what the parse refuses.
11
+
-**No credentials in metadata.** The console reads the endpoint with its own signed-in fetch. The read block declares `url` and `method` only; `headers`, `credentials`, `auth`, `authorization`, `token` and `apiKey` are refused with that prescription, and so are `params` and `body`.
12
+
-**Reads only.**`read.method` is optional and accepts `GET` alone, the method the console's API adapter sends for every read. There is no `write` block.
13
+
-**The object binding is unchanged.**`ElementDataSourceSchema` accepts exactly what it did and gains no `provider` key; the two shapes meet as a `z.union` at `PageComponentSchema.dataSource`. One visible difference: a refused object binding parsed through `PageComponentSchema` now arrives as one `invalid_union` issue at `dataSource`, whose object branch carries the same issue as before. `os validate` and the API error envelope print one `Invalid input` line for the union at `dataSource`, then the object branch's issue at the same path as before (for example `dataSource.filter`).
14
+
-**Not rendered yet.** No console block reads an API binding today: the generic stat, usage-bar and checklist blocks that will are a console release still to come. Until then the binding parses and no block reads it; its schema description says so (`[PLANNED — not yet rendered]`).
description: "Page schemas of the ObjectStack UI Protocol: ElementDataSource, InterfacePageConfig and 7 more — each property with its type, default and a TypeScript example."
4
+
description: "Page schemas of the ObjectStack UI Protocol: ElementApiDataSource, ElementDataSource and 8 more — each property with its type, default and a TypeScript example."
5
5
---
6
6
7
7
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -13,13 +13,35 @@ description: "Page schemas of the ObjectStack UI Protocol: ElementDataSource, In
const result =ElementDataSourceSchema.parse(data);
20
+
const result =ElementApiDataSourceSchema.parse(data);
21
21
```
22
22
23
+
---
24
+
25
+
## ElementApiDataSource
26
+
27
+
[PLANNED — not yet rendered] API binding of a page element: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the selected value, which the element reads as `data` in its expression holes and its `visibleWhen`. The console's generic stat, usage-bar and checklist blocks are the planned readers; until they ship, the binding parses and no block reads it.
28
+
29
+
### Properties
30
+
31
+
| Property | Type | Required | Description |
32
+
| :--- | :--- | :--- | :--- |
33
+
|**provider**|`'api'`| ✅ | Binds the element to one same-origin API endpoint instead of an object |
34
+
|**read**|`{ url: string; method?: 'GET' }`| ✅ | The read request — the same-origin endpoint the element binds |
35
+
|**path**|`string`| optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response |
36
+
37
+
### Nested Shape: `ElementApiDataSource.read`
38
+
39
+
| Property | Type | Required | Description |
40
+
| :--- | :--- | :--- | :--- |
41
+
|**url**|`string`| ✅ | Same-origin API path the element reads — it opens with a single `/`, e.g. `/api/v1/cloud/home`. An absolute URL of any scheme, a protocol-relative `//host`, a backslash read as a slash, a space or control character, and a relative path are refused at parse. No credential is written here: the console reads the endpoint with its own signed-in fetch. |
42
+
|**method**|`'GET'`| optional | HTTP method of the read — `GET`, the only method a page element's read is sent with |
|**visibleWhen**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }`| optional | Visibility predicate (CEL) — component rendered only when TRUE. Contract-bound roots: `record`, `current_user` (ADR-0068 aliases `user` / `ctx.user` — one object, three spellings), and page state as `page.<var>`. The shipping renderer additionally mounts `features`, `os.user` and binds `data` to the data-source ADAPTER here — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only). ⚠️ `data` is surface-dependent: on a `page:tabs` item `visibleWhen` it is the record ROW instead. e.g. "page.selectedProjectId != ''" |
|**dataSource**|`{ object: string; view?: string; filter?: object[]; sort?: object[]; … }`| optional | Per-element data binding for multi-object pages|
295
+
|**dataSource**|`{ object: string; view?: string; filter?: object[]; sort?: object[]; … } \| { provider: 'api'; read: object; path?: string }`| optional | Per-element data binding: an object binding `{ object, view?, filter?, sort?, limit? }` that overrides the page object, or an API binding `{ provider: 'api', read: { url }, path? }` that reads one same-origin endpoint|
274
296
|**responsive**|`never`| optional |[REMOVED]`page.components[].responsive` was removed in @objectstack/spec 17 (ADR-0049 D2) — no renderer ever read it, so per-breakpoint layout overrides (columns/order/visibility) parsed, validated, and then did nothing. Delete the key. For breakpoint behaviour that IS applied, use the sibling `responsiveStyles` (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render, e.g. `responsiveStyles: { xsmall: { display: 'none' } }` to hide a component on the narrowest screens. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
[PLANNED — not yet rendered] API binding of a page element: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the selected value, which the element reads as `data` in its expression holes and its `visibleWhen`. The console's generic stat, usage-bar and checklist blocks are the planned readers; until they ship, the binding parses and no block reads it.
321
+
322
+
| Property | Type | Required | Description |
323
+
| :--- | :--- | :--- | :--- |
324
+
|**provider**|`'api'`| ✅ | Binds the element to one same-origin API endpoint instead of an object |
325
+
|**read**|`{ url: string; method?: 'GET' }`| ✅ | The read request — the same-origin endpoint the element binds |
326
+
|**path**|`string`| optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response |
327
+
296
328
### Nested Shape: `PageComponent.aria`
297
329
298
330
| Property | Type | Required | Description |
@@ -399,7 +431,7 @@ Print declaration: paper size, orientation, margins, the running header and foot
|**visibleWhen**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }`| optional | Visibility predicate (CEL) — component rendered only when TRUE. Contract-bound roots: `record`, `current_user` (ADR-0068 aliases `user` / `ctx.user` — one object, three spellings), and page state as `page.<var>`. The shipping renderer additionally mounts `features`, `os.user` and binds `data` to the data-source ADAPTER here — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only). ⚠️ `data` is surface-dependent: on a `page:tabs` item `visibleWhen` it is the record ROW instead. e.g. "page.selectedProjectId != ''" |
|**dataSource**|`{ object: string; view?: string; filter?: object[]; sort?: object[]; … }`| optional | Per-element data binding for multi-object pages|
434
+
|**dataSource**|`{ object: string; view?: string; filter?: object[]; sort?: object[]; … } \| { provider: 'api'; read: object; path?: string }`| optional | Per-element data binding: an object binding `{ object, view?, filter?, sort?, limit? }` that overrides the page object, or an API binding `{ provider: 'api', read: { url }, path? }` that reads one same-origin endpoint|
403
435
|**responsive**|`never`| optional |[REMOVED]`page.components[].responsive` was removed in @objectstack/spec 17 (ADR-0049 D2) — no renderer ever read it, so per-breakpoint layout overrides (columns/order/visibility) parsed, validated, and then did nothing. Delete the key. For breakpoint behaviour that IS applied, use the sibling `responsiveStyles` (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render, e.g. `responsiveStyles: { xsmall: { display: 'none' } }` to hide a component on the narrowest screens. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
Copy file name to clipboardExpand all lines: content/docs/ui/pages.mdx
+39-1Lines changed: 39 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -186,7 +186,45 @@ The `type` field is a union of the standard `PageComponentType` enum and any cus
186
186
-**AI:**`ai:suggestion` — `ai:chat_window` was retired in v17.x: AI chat is not a page element, and the floating chat overlay the console mounts on every page is the supported entry point (set the app's `defaultAgent` to choose which platform agent answers there). No renderer for it ever shipped, and an authored `ai:chat_window` node is refused at the schema door — `definePage()`, `os validate`, `os build` — with that prescription at the node's path instead of drawing an unknown-type panel in front of a user
187
187
-**Elements:**`element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:record_picker`, `element:text_input` (`element:filter` and `element:form` were retired in v17.x — no renderer ever shipped for either, and an authored node of either type is refused at the schema door — `definePage()`, `os validate`, `os build` — with that prescription at the node's path, bare node included. List surfaces own their filtering via a view's `userFilters` quick-filter bar or the list toolbar's filter builder; for forms use the object-bound `object-form` block, which is rendered and designer-publishable)
188
188
189
-
Components may also carry `dataSource` (per-element object binding for multi-object pages), `responsiveStyles` (per-breakpoint scoped CSS, ADR-0065), and `aria` configuration. Custom string types are also accepted for project-specific widgets. (The former `responsive` layout block was retired in v17.x — no renderer ever applied it; see the upgrade guide.)
189
+
Components may also carry `dataSource` (a per-element data binding — see below), `responsiveStyles` (per-breakpoint scoped CSS, ADR-0065), and `aria` configuration. Custom string types are also accepted for project-specific widgets. (The former `responsive` layout block was retired in v17.x — no renderer ever applied it; see the upgrade guide.)
190
+
191
+
### Element data sources
192
+
193
+
A component's `dataSource` takes one of two shapes. The **object** binding queries an object, overriding the page's own `object`, so one page can show several objects. The **API** binding reads one endpoint on the deployment's own origin and binds its answer, so a page can compose from a summary that is not an object:
// API binding: `read.url` is a same-origin API path; `path` selects the part
211
+
// of the `{ success, data }` response the element binds.
212
+
const environments:PageComponent= {
213
+
type: 'stat',
214
+
dataSource: {
215
+
provider: 'api',
216
+
read: { url: '/api/v1/cloud/home' },
217
+
path: 'data.environments',
218
+
},
219
+
};
220
+
```
221
+
222
+
The API binding's rules:
223
+
224
+
-**Same-origin only.**`read.url` must open with a single `/`. An absolute URL of any scheme (`https:`, `mailto:`, `javascript:`, …), a protocol-relative `//host`, a backslash the browser reads as a slash, a space or control character, and a relative path such as `./home` are all refused when the page is parsed (`definePage()`, `os validate`, `os build`), with the reason.
225
+
-**No credentials in metadata.** The console reads the endpoint with its own signed-in fetch, so the read block has no `headers`, and a credential key such as `headers`, `credentials` or `token` is refused.
226
+
-**Reads only.**`read.method` is optional and accepts `GET` alone; there is no `write` block.
227
+
-**Not rendered yet.** The console's generic blocks that read an API binding (a stat, a usage bar, a checklist) have not shipped: until they do, the binding parses and no block reads it.
0 commit comments