@antelopejs/interface-dms-builder 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/6.queries.md CHANGED
@@ -1,8 +1,12 @@
1
- # Queries
1
+ ---
2
+ title: "Queries"
3
+ description: "The operations that add, configure, read back and remove a query, a page route that answers a count, an aggregate or a series of points computed from a resource."
4
+ mode: reference
5
+ ---
2
6
 
3
- ## Count rows without writing a route
7
+ ## Overview
4
8
 
5
- A query gives a page data to display: a count, a total or a series of points. With an existing `/books` page and a `book` resource, this example creates a route that counts the books:
9
+ A query gives a page a count, a total or a series of points to display. This example adds a query to an existing `/books` page that counts the rows of the `book` resource:
6
10
 
7
11
  ```typescript
8
12
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -19,21 +23,17 @@ if (result.ok) {
19
23
  }
20
24
  ```
21
25
 
22
- After the backend reloads, a signed-in user with access to `/books` can request that URL through the frontend. It returns `{ "value": N }`, where `N` is the number of rows. The operation writes a model method and a page route; it does not add a visible block. Use the returned `route` as the block's `fetchUrl`.
26
+ `AddQuery` writes a model method and a page route. It adds no visible block. After the backend reloads, a signed-in user with access to `/books` can request the route through the frontend, and the route answers `{ "value": N }`, where `N` is the number of rows. A block reads the answer through the returned `route`, as its `fetchUrl`. An editor that previews before it saves uses [PreviewQuery and page drafts](./9.drafts-and-preview.md) instead of a call to `AddQuery` on each change.
23
27
 
24
- Choose an unused query name. For an editor that must preview before saving, use [PreviewQuery and page drafts](./9.drafts-and-preview.md) instead of calling `AddQuery` on each change.
28
+ The route carries the page's own permission, through a parameter decorator on that route alone. A user who can open the page can read what the query derives, and no other route on the controller changes.
25
29
 
26
- ## Query reference
30
+ A query is addressed by a `QueryRef`, lives on a page, and reads from a resource. A template decides what a query can compute. You pick one by id and fill in its parameters, and the builder emits the database chain.
27
31
 
28
- The route is guarded with the page's own permission, as a parameter decorator on that route alone: whoever may open the page may read what it derives, and no other route on the controller is affected.
32
+ `GetPageStructure` reports the queries it finds on a page in `PageStructure.queries`, whether the builder wrote them or a person did.
29
33
 
30
- A query is addressed by a `QueryRef`, lives on a page, and reads from a resource. Templates decide what a query can compute; you pick one by id, fill in its parameters, and the builder emits the chain.
34
+ ## Address a query
31
35
 
32
- Queries read back structurally: `GetPageStructure` reports the queries it finds on a page in `PageStructure.queries`, whether the builder wrote them or a human did.
33
-
34
- ## Address a Query
35
-
36
- `QueryRef` is `<pageRef>@<name>`, e.g. `/books@inStockCount`. The page part says where the route lives; the resource the query reads is recorded in the query itself, not in its ref.
36
+ `QueryRef` is `<pageRef>@<name>`, for example `/books@inStockCount`. The page part says where the route lives. The query itself records the resource it reads, and the ref does not.
37
37
 
38
38
  ```typescript
39
39
  import type { QueryRef } from "@antelopejs/interface-dms-builder";
@@ -41,9 +41,9 @@ import type { QueryRef } from "@antelopejs/interface-dms-builder";
41
41
  const query: QueryRef = "/books@inStockCount";
42
42
  ```
43
43
 
44
- ## List the Templates
44
+ ## List the templates
45
45
 
46
- `ListQueryTemplates(resourceType?)` returns the templates currently registered. It is a plain read, and the registry is live, so call it rather than hard-coding ids.
46
+ `ListQueryTemplates(resourceType?)` returns the templates registered now. It is a plain read of a live registry, so the ids it returns are the ones to use.
47
47
 
48
48
  ```typescript
49
49
  import { ListQueryTemplates } from "@antelopejs/interface-dms-builder";
@@ -54,32 +54,29 @@ for (const template of templates) {
54
54
  }
55
55
  ```
56
56
 
57
- `QueryTemplateDescriptor`:
57
+ `QueryTemplateDescriptor` has these fields:
58
58
 
59
59
  | Field | Type | Description |
60
60
  | --- | --- | --- |
61
- | `id` | `string` | The template id, e.g. `count` or `aggregate`. This is `AddQueryInput.template`. |
61
+ | `id` | `string` | The template id, such as `count` or `aggregate`. `AddQueryInput.template` takes it. |
62
62
  | `resourceType` | `"database-table"` | The kind of resource the template reads. |
63
63
  | `title` | `string` | A human-readable title. |
64
- | `description` | `string` | What the template computes. |
65
- | `output` | `QueryOutputKind` | The shape the query's route responds with. |
64
+ | `description` | `string` | What the template computes. Optional. |
65
+ | `output` | `QueryOutputKind` | The shape the query's route answers with. |
66
66
  | `params` | `ConfigSchema` | The template's parameter schema, in the same vocabulary as the block catalog. |
67
67
 
68
- `QueryOutputKind` says what the route responds with:
68
+ `QueryOutputKind` names what the route answers with:
69
69
 
70
70
  | Kind | Response | Templates |
71
71
  | --- | --- | --- |
72
- | `"scalar"` | `{ value }` — one number | `count`, `aggregate` |
73
- | `"series"` | `{ series }` — one point per group, each `{ x, y }` | `series` |
72
+ | `"scalar"` | `{ value }`, one number | `count`, `aggregate` |
73
+ | `"series"` | `{ series }`, one `{ x, y }` point per group | `series` |
74
74
 
75
- A series point carries its group as `x` and what was measured as `y`. A bucketed
76
- group is a number that sorts — `2026`, `202601`, `20260114` — rather than a label:
77
- the group is also what the points are ordered by, and a label belongs to a
78
- response that knows the reader's locale.
75
+ A series point carries its group as `x` and the measure as `y`. A bucketed group is a number that sorts, such as `2026`, `202601` or `20260114`, and not a label. The points are ordered by the group, and a label would need the reader's locale, which the route does not know.
79
76
 
80
- Read `params` the way you read a block's `config` schema, described in [Catalog](./3.catalog.md) — it tells you which keys `AddQueryInput.params` accepts, and a key the template does not declare fails with `invalid_config`.
77
+ `params` reads like a block's `config` schema, described in [Catalog](./3.catalog.md). It lists the keys that `AddQueryInput.params` accepts. A key the template does not declare fails with `invalid_config`, and so does an unknown template id.
81
78
 
82
- ## Add a Query
79
+ ## Add a query
83
80
 
84
81
  ```typescript
85
82
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -98,7 +95,7 @@ if (result.ok) {
98
95
  }
99
96
  ```
100
97
 
101
- `AddQueryInput`:
98
+ `AddQueryInput` has these options:
102
99
 
103
100
  | Option | Type | Description |
104
101
  | --- | --- | --- |
@@ -107,30 +104,30 @@ if (result.ok) {
107
104
  | `template` | `string` | A `QueryTemplateDescriptor.id`. Required. |
108
105
  | `params` | `Record<string, QueryParamValue>` | The template's parameters. |
109
106
  | `endpoint` | `string` | The route path relative to the page, served at `/dms/pages<pageRef><endpoint>`. Defaults to `/stats/<kebab-name>`. |
110
- | `response` | `QueryResponseShape` | How the answer is arranged for the block reading it. Defaults to what the calculation computes. |
107
+ | `response` | `QueryArrangement` | How the answer is arranged for the block that reads it. Defaults to what the template computes. |
111
108
  | `compare` | `boolean` | Also answer the preceding period, so a card can show a variation. |
112
109
 
113
- The result carries both addresses: `query` is the `QueryRef` to configure or remove it by, `route` is the URL a block fetches from: the endpoint under the page, at `/dms/pages<pageRef><endpoint>` (`pageRoutePath(pageRef, endpoint)` from `@antelopejs/interface-dms/page`). The DMS serves the routes of a page controller under its own `/dms/pages` prefix, since the page's own path is the page.
110
+ The result carries two addresses. `query` is the `QueryRef` that configures or removes the query. `route` is the URL a block fetches from, the endpoint under the page at `/dms/pages<pageRef><endpoint>`, which `pageRoutePath(pageRef, endpoint)` from `@antelopejs/interface-dms/page` also builds. The DMS serves the routes of a page controller under its own `/dms/pages` prefix, because the page's own path is the page.
114
111
 
115
- A few rules the operation enforces:
112
+ The operation enforces these rules:
116
113
 
117
- - `name` must be a camelCase identifier, and must not collide with a member the page or the model already has. A collision fails with `duplicate_name` in the `query` scope.
118
- - `endpoint` must start with `/`, and must not collide with another route on the page. Any path is allowed otherwise, because read-back identifies a query by its shape rather than by its URL.
119
- - When a model method of that name already exists compiling a *different* chain, the model method is emitted under a free suffixed name, so the queries already sharing the original keep their behavior.
114
+ - `name` must be a camelCase identifier, or the operation fails with `invalid_config`. A name that matches a member of the page or of the model fails with `duplicate_name` in the `query` scope.
115
+ - `endpoint` must start with `/`, or the operation fails with `invalid_config`. An endpoint that another route on the page already uses fails with `duplicate_name`. Any other path is allowed, because read-back identifies a query by its shape and not by its URL.
116
+ - A model method of that name may already exist and compile a different chain. The builder then emits the model method under a free suffixed name, so the queries that share the original keep their behavior.
120
117
 
121
- ## Filter the Rows
118
+ ## Filter the rows
122
119
 
123
- `QueryFilter` is one conjunct of a template's `where`. Filters combine with AND.
120
+ `QueryFilter` is one condition of a template's `where`. Filters combine with AND.
124
121
 
125
122
  | Field | Type | Description |
126
123
  | --- | --- | --- |
127
124
  | `field` | `string` | A field of the resource. It must exist and must not be opaque. |
128
125
  | `op` | `FilterOp` | The comparison. |
129
- | `value` | `QueryParamValue` | A plain scalar baked into the chain, or a `QueryParamBinding`. |
126
+ | `value` | `QueryParamValue` | A plain scalar written into the chain, or a `QueryParamBinding`. A scalar must match the field's type. |
130
127
 
131
- `FilterOp` is `"eq" | "ne" | "gt" | "ge" | "lt" | "le"`. The ordering operators (`gt`, `ge`, `lt`, `le`) are rejected on `boolean` and `select` fields.
128
+ `FilterOp` is `"eq" | "ne" | "gt" | "ge" | "lt" | "le"`. The ordering operators (`gt`, `ge`, `lt` and `le`) fail with `invalid_config` on `boolean` and `select` fields.
132
129
 
133
- `QueryParamValue` is `unknown`, and a query's values are data: an `ExprValue` anywhere in a query's params is refused with `invalid_config`, since a raw expression would put uncheckable code inside a model body. The other sentinels are described in [Value Encodings](./7.values.md).
130
+ `QueryParamValue` is a `ConfigValue`, which is plain data. An `ExprValue` anywhere in a query's params fails with `invalid_config`, because a raw expression would put code the builder cannot check inside a model body. [Value encodings](./7.values.md) describes the other sentinels.
134
131
 
135
132
  ```typescript
136
133
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -147,9 +144,9 @@ await AddQuery("/books", {
147
144
  });
148
145
  ```
149
146
 
150
- ## Bind a Value at Request Time
147
+ ## Bind a value at request time
151
148
 
152
- `QueryParamBinding` replaces a baked-in filter value with one supplied per request. It becomes a parameter of the model method and a declared parameter on the route.
149
+ `QueryParamBinding` replaces a fixed filter value with a value each request supplies. It becomes a parameter of the model method and a declared `@Parameter` on the route.
153
150
 
154
151
  ```typescript
155
152
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -172,12 +169,12 @@ await AddQuery("/books", {
172
169
  | `$param.name` | `string` | A camelCase identifier. The route exposes the value under this name. |
173
170
  | `$param.in` | `"query" \| "param"` | Where the route reads the value from. Defaults to `"query"`. |
174
171
 
175
- `name` and `in` describe the route, not the model: the route reads the value from that source, coerces it, and passes it positionally to the model method, whose own parameter is named from the template.
172
+ `name` and `in` describe the route, not the model. The route reads the value from that source, converts it with `Number(...)`, `new Date(...)` or a comparison with `"true"` for the field's type, and passes it by position to the model method. The model method names its own parameter from the template.
176
173
 
177
- Two rules to plan for:
174
+ Two rules apply:
178
175
 
179
- - `in: "param"` reads a path segment, so the endpoint must declare a matching `:name`. The default `/stats/…` endpoint has none, so a path-bound value forces you to supply an endpoint that does. A mismatch fails with `invalid_config` rather than emitting a route whose value never binds.
180
- - Two filters may bind the same parameter name, but only when they agree on its type and source.
176
+ - `in: "param"` reads a path segment, so the endpoint must declare a matching `:name`. The default `/stats/...` endpoint has no segment, so a path-bound value needs an endpoint that declares one. A mismatch fails with `invalid_config`, and the builder emits no route.
177
+ - Two filters can bind the same parameter name only when they agree on its type and its source.
181
178
 
182
179
  ```typescript
183
180
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -199,9 +196,9 @@ await AddQuery("/books", {
199
196
  });
200
197
  ```
201
198
 
202
- ## Measure per Group
199
+ ## Measure per group
203
200
 
204
- The `series` template answers one point per group rather than one number, which is what a chart, a ranking or any other multi-point block reads. Group by a field's own value, or by a period of a date field.
201
+ The `series` template answers one point per group instead of one number. A chart, a ranking and the other blocks that show several points read it. A series groups by a field's own value, or by a period of a date field.
205
202
 
206
203
  ```typescript
207
204
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -226,44 +223,44 @@ await AddQuery("/dashboard", {
226
223
  | Option | Type | Description |
227
224
  | --- | --- | --- |
228
225
  | `op` | `"count" \| "sum" \| "avg" \| "min" \| "max"` | What to measure in each group. Defaults to `count`. |
229
- | `field` | `string` | The numeric field the measure reads. Required for every op but `count`. |
230
- | `groupBy` | `string` | The field the rows are grouped by. Required. |
231
- | `bucket` | `"day" \| "week" \| "month" \| "quarter" \| "year"` | Groups a date field by period instead of by exact value. A week runs Monday to Sunday. Only for a date field. |
232
- | `timezone` | `string` | IANA zone the period is computed in. |
233
- | `orderBy` | `"group" \| "measure"` | Order the points by their group (a timeline) or by what was measured (a ranking). Defaults to `group`. |
226
+ | `field` | `string` | The numeric field the measure reads. Required for every op except `count`. |
227
+ | `groupBy` | `string` | The field that groups the rows. Required. Without a `bucket`, it must be a text or numeric field. |
228
+ | `bucket` | `"day" \| "week" \| "month" \| "quarter" \| "year"` | Groups a date field by period instead of by exact value. A week runs from Monday to Sunday. Only valid on a date field. |
229
+ | `timezone` | `string` | The IANA zone the period is computed in. |
230
+ | `orderBy` | `"group" \| "measure"` | Orders the points by their group, for a timeline, or by the measure, for a ranking. Defaults to `group`. |
234
231
  | `direction` | `"asc" \| "desc"` | Defaults to `asc`. |
235
- | `limit` | `number` | Keep only the first N points after ordering — a top N. At most 1000. |
232
+ | `limit` | `number` | Keeps only the first N points after ordering, for a top N. A whole number from 1 to 1000. |
236
233
  | `where` | `QueryFilter[]` | As for every other template. |
237
234
 
238
- Three rules worth planning for:
235
+ These rules apply to a series:
239
236
 
240
- - **A series is always ordered.** Databases return groups in no particular order — MongoDB scrambles them — so an ordering is emitted whether or not you ask for one. Omitting `orderBy` gives a timeline: the group, ascending.
241
- - **Omitting `timezone` buckets in UTC.** A row near a period boundary may belong to a different bucket in the reader's local time. Pass the zone the reader uses for reporting.
242
- - **A top N is an ordering plus a limit**, not a separate template: `orderBy: "measure"`, `direction: "desc"`, `limit: 5`.
237
+ - **A series is always ordered.** A database returns groups in its own order, and MongoDB returns them unordered, so the builder always emits an ordering. Without `orderBy` and `direction`, the points come as a timeline, by group, ascending.
238
+ - **Without `timezone`, periods are computed in UTC.** A row near a period boundary can then fall in a different bucket than in the reader's local time.
239
+ - **A top N is an ordering plus a limit,** such as `orderBy: "measure"`, `direction: "desc"` and `limit: 5`. It has no template of its own.
243
240
 
244
- There is no week bucket. Neither database adapter exposes an ISO week, and deriving one from the day of the year puts the first days of January in the previous year's week — a wrong week being worse than a missing one.
241
+ A week runs from Monday to Sunday, like an ISO week, and a week that straddles New Year stays one week. `AddQuery` refuses `bucket: "week"` with `invalid_config` when the installed `@antelopejs/interface-dms` cannot name a week for a reader. Bucket by day or month then, or update it.
245
242
 
246
- A series reads back like any other query: `GetPageStructure` reports its `template` and the parameters that produced it, including the ordering the engine applies when you omit one. Editing and removing it work as they do for a scalar query — the model method goes with the route.
243
+ A series reads back like any other query. `GetPageStructure` reports its `template` and the parameters that produced it, including the ordering the engine applies when you omit one. Configuring and removing it work as for a scalar query, and the model method goes with the route.
247
244
 
248
- A chain edited past what the grammar accepts is the exception, and the rule is the same as everywhere else: it reads as `opaque`, keeps its `endpoint`, `resource`, `modelMethod` and `output`, and the builder will list and remove it but never rewrite it.
245
+ A chain edited past what the templates accept reads as `opaque`, as everywhere else. It keeps its `endpoint`, `resource`, `modelMethod` and `output`. The builder lists and removes it, but never rewrites it.
249
246
 
250
- ## Arrange the Answer
247
+ ## Arrange the answer
251
248
 
252
- One calculation feeds several kinds of block, so `response` says how its answer is dressed rather than making you describe it more than once.
249
+ One calculation feeds several kinds of block. `response` says how the route arranges its answer, so you describe the calculation only once.
253
250
 
254
251
  | Shape | Answer | Read by |
255
252
  | --- | --- | --- |
256
- | `series` | `{ series }` — the points | A bare chart |
257
- | `card` | `{ value, series, previousValue?, delta?, comparisonSeries? }` | A chart card |
258
- | `value` | `{ value, previousValue?, delta?, sparkline? }` | A KPI card |
259
- | `items` | `{ items }` — the points as ranked entries | A ranked list |
253
+ | `series` | `{ series }`, the points | A bare chart |
254
+ | `card` | `{ value?, series, previousValue?, delta?, comparisonSeries? }` | A chart card |
255
+ | `value` | `{ value?, previousValue?, delta?, sparkline? }` | A KPI card |
256
+ | `items` | `{ items }`, the points as ranked entries | A ranked list |
260
257
 
261
- The arrangements that need more than a property name are built by helpers `@antelopejs/interface-dms/base` publishes, which the generated route imports. That keeps the route to one expression, and is why a query whose answer is arranged still reads back as the parameters that produced it.
258
+ The `card`, `value` and `items` shapes come from the `chartCardData`, `kpiCardData` and `topListData` helpers that `@antelopejs/interface-dms/base` publishes. The generated route imports them. The route stays one expression, so a query with an arranged answer still reads back as the parameters that produced it.
262
259
 
263
- Two rules worth planning for:
260
+ Two rules apply:
264
261
 
265
- - **The headline figure is taken from the points.** Counts and sums add up, a min or max is the min or max of the group figures — but an average is the average of the group averages, which is not the average over all the rows whenever the groups are uneven.
266
- - **`compare` needs a period the route supplies.** It re-asks the same model method with the `compareFrom` / `compareTo` bounds the request carried. A calculation whose dates are baked in has nothing to shift, so it is not compared and no second call is emitted.
262
+ - **The headline figure comes from the points.** Counts and sums add up, and a min or a max is the min or the max of the group figures. An average is the average of the group averages. When the groups differ in size, that is not the average over all the rows.
263
+ - **`compare` needs a period that the route supplies.** The route calls the same model method again with the `compareFrom` and `compareTo` bounds of the request. The query must bind exactly two date values. A calculation with fixed dates has nothing to shift, so the builder emits no second call.
267
264
 
268
265
  ```typescript
269
266
  import { AddQuery } from "@antelopejs/interface-dms-builder";
@@ -285,13 +282,13 @@ await AddQuery("/dashboard", {
285
282
  response: "card",
286
283
  compare: true,
287
284
  });
288
- // GET /dms/pages/dashboard/stats/revenue?from=…&to=…&compareFrom=…&compareTo=…
285
+ // GET /dms/pages/dashboard/stats/revenue?from=...&to=...&compareFrom=...&compareTo=...
289
286
  // { value, series, previousValue, delta, comparisonSeries }
290
287
  ```
291
288
 
292
- ## Configure a Query
289
+ ## Configure a query
293
290
 
294
- `ConfigureQuery(query, patch, opts?)` recompiles the query from the merged patch and replaces both emitted members. Fields you leave out keep their current value, read back off disk.
291
+ `ConfigureQuery(query, patch, opts?)` compiles the query again from the merged patch and replaces the route and the model method. A field you leave out keeps the value read back from the file.
295
292
 
296
293
  ```typescript
297
294
  import { ConfigureQuery } from "@antelopejs/interface-dms-builder";
@@ -301,13 +298,13 @@ await ConfigureQuery("/books@inStockCount", {
301
298
  });
302
299
  ```
303
300
 
304
- The patch is a `Partial<AddQueryInput>`, so `resource`, `template`, `params` and `endpoint` are all patchable. The query keeps the name in its `QueryRef`: a `name` in the patch is not applied.
301
+ The patch is a `Partial<AddQueryInput>`. The operation applies its `resource`, `template`, `params` and `endpoint`. The query keeps the name in its `QueryRef`, so a `name` in the patch has no effect. `response` and `compare` in the patch also have no effect, and the route the operation writes has neither an arrangement nor a comparison.
305
302
 
306
- A query that no longer reads back as an editable template fails with `opaque_target`. That is deliberate — an opaque chain means a human owns it now, and regenerating it from a template it no longer matches would revert their edit. Remove it and add a new one if you need to.
303
+ A query that no longer reads back as an editable template fails with `opaque_target`. An opaque chain belongs to a person now, and a chain generated again from a template it no longer matches would undo their edit. To change such a query, remove it and add a new one.
307
304
 
308
- ## Remove a Query
305
+ ## Remove a query
309
306
 
310
- `RemoveQuery(query, opts?)` deletes the query's route from the page, and drops the model method it called once nothing else calls it — a method shared with another query stays. It works on an opaque query too.
307
+ `RemoveQuery(query, opts?)` deletes the query's route from the page. It deletes the model method too once nothing else calls it, and keeps a method that another query shares. It works on an opaque query as well.
311
308
 
312
309
  ```typescript
313
310
  import { RemoveQuery } from "@antelopejs/interface-dms-builder";
@@ -315,31 +312,33 @@ import { RemoveQuery } from "@antelopejs/interface-dms-builder";
315
312
  await RemoveQuery("/books@cheaperThan");
316
313
  ```
317
314
 
318
- ## Read a Query Back
315
+ ## Read a query back
319
316
 
320
- `QueryStructure` is a query as found on disk. Read-back is purely structural: a route reads as a query when its shape and the chain it calls both parse, so a hand-written route matching the grammar is as editable as a generated one, and any edit that keeps the chain in-grammar is simply re-read.
317
+ `QueryStructure` is a query as found on disk. Read-back is structural. A route reads as a query when its shape and the chain it calls both parse. A hand-written route that matches the grammar is as editable as a generated one, and an edit that keeps the chain inside the grammar reads back again.
321
318
 
322
319
  | Field | Type | Description |
323
320
  | --- | --- | --- |
324
321
  | `name` | `string` | The query name. Always present. |
325
- | `endpoint` | `string` | The route path relative to the page's slug, as `AddQueryInput.endpoint` takes it. Always present. |
326
- | `resource` | `ResourceRef` | The resource the route's model parameter points at. |
327
- | `modelMethod` | `string` | The model method the route calls. Sharing is driven by the query's *name*: a query looks for its own name and numbered forks of it, so two differently-named queries never share a method however identical their chains. |
328
- | `output` | `QueryOutputKind` | The output shape, read from the property the route's response carries — so a query whose chain no longer parses still reports what it answers. |
322
+ | `endpoint` | `string` | The route path relative to the page's slug, in the form `AddQueryInput.endpoint` takes. Always present. |
323
+ | `resource` | `ResourceRef` | The resource that the route's model parameter points at. |
324
+ | `modelMethod` | `string` | The model method the route calls. A query looks for a method with its own name or a numbered fork of it, so two queries with different names never share a method, even when their chains match. |
325
+ | `output` | `QueryOutputKind` | The output shape, read from the property the route's response carries. A query whose chain no longer parses still reports what it answers. |
326
+ | `response` | `QueryArrangement` | How the answer is arranged, when the route wraps the calculation in a helper. |
327
+ | `compare` | `boolean` | `true` when the route also answers the preceding period. |
329
328
  | `template` | `string` | The template the chain was compiled from. |
330
- | `params` | `Record<string, unknown>` | The parameters that produced the chain. |
331
- | `routeParams` | `{ name: string; in: "query" \| "param" \| "header" }[]` | The route's declared parameters — the source of truth for a bound value's `name` and `in`. `header` appears only on hand-written routes. |
332
- | `opaque` | `boolean` | `true` when the query could not be reversed to an editable template. |
333
- | `opaqueReason` | `"unparseable_route" \| "unparseable_chain"` | Why it is opaque. Set when `opaque === true`. |
329
+ | `params` | `Record<string, QueryParamValue>` | The parameters that produced the chain. |
330
+ | `routeParams` | `{ name: string; in: "query" \| "param" \| "header" }[]` | The route's declared parameters. They are what a bound value's `name` and `in` are read from. `header` appears only on hand-written routes. |
331
+ | `opaque` | `boolean` | `true` when the query does not read back as an editable template. |
332
+ | `opaqueReason` | `"unparseable_route" \| "unparseable_chain"` | Why the query is opaque. Set when `opaque` is `true`. |
334
333
 
335
- The two opaque reasons say which half failed:
334
+ Each opaque reason names the half that failed:
336
335
 
337
- - **`unparseable_route`** — the GET wrapper is not the recognized `return { value: await model.m(...) }` shape.
338
- - **`unparseable_chain`** — the model method it calls is not a chain any registered template accepts: hand-written, edited out of grammar, or emitted by a newer builder.
336
+ - **`unparseable_route`.** The GET method is not the `return { value: await model.m(...) }` shape the builder recognizes.
337
+ - **`unparseable_chain`.** The model method it calls is not a chain that any registered template accepts. It was hand-written, edited outside the grammar, or emitted by a newer builder.
339
338
 
340
- A query that no longer parses still reports its `endpoint`, `resource` and `modelMethod`; only `template` and `params` go missing. The builder can therefore always see and remove what it cannot edit.
339
+ A query that no longer parses still reports its `endpoint`, `resource` and `modelMethod`. Only `template` and `params` are missing. The builder can always see and remove what it cannot edit.
341
340
 
342
- ```typescript
341
+ ```typescript fragment
343
342
  import { GetPageStructure } from "@antelopejs/interface-dms-builder";
344
343
  import { pageRoutePath } from "@antelopejs/interface-dms/page";
345
344
 
@@ -350,6 +349,6 @@ if (result.ok) {
350
349
  }
351
350
  ```
352
351
 
353
- ## Next Steps
352
+ ## Next steps
354
353
 
355
- - [Value Encodings](./7.values.md) - Write configuration values that are not plain literals
354
+ - [Value encodings](./7.values.md) shows how to write configuration values that are not plain literals.
package/docs/7.values.md CHANGED
@@ -1,10 +1,16 @@
1
- # Value Encodings
1
+ ---
2
+ title: "Value encodings"
3
+ description: "How the builder writes a configuration value to source: plain JSON as a literal, and the $dataType, $ref, $expr and $block sentinels for values that must reach the source as TypeScript."
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- Configuration values are plain JSON by default. `AddBlockInput.config`, a `ConfigureBlock` patch, a page's `permission` — all of it is written to source exactly as you pass it, as a literal.
9
+ Configuration values are plain JSON by default. The builder writes `AddBlockInput.config`, a `ConfigureBlock` patch and a page's `permission` to source exactly as you pass them, as literals.
6
10
 
7
- Some options need TypeScript values rather than JSON literals. A form field needs a DataType instance, a relation needs a controller class, and a chart card needs a nested chart. The structured encodings below let the builder write those values and their imports. Use a raw `$expr` only as an escape hatch; drafts and query parameters reject it.
11
+ The type of such a value is `ConfigValue`. It is a string, a number, a boolean, `null`, `undefined`, an array of `ConfigValue` items, or a `ConfigObject`, an object whose members are `ConfigValue` items. The compiler refuses a function or a class instance. `undefined` stands for an absent member. The builder skips it when it writes, and a `ConfigureBlock` patch reads it as "remove this key". The four encodings below are `ConfigObject` values.
12
+
13
+ Some options need TypeScript values instead of JSON literals. A form field needs a DataType instance, a relation needs a controller class, and a chart card needs a nested chart. The structured encodings below let the builder write those values and their imports. A raw `$expr` is the fallback for anything else, and page drafts and query parameters refuse it.
8
14
 
9
15
  ```typescript
10
16
  import type {
@@ -15,26 +21,26 @@ import type {
15
21
  } from "@antelopejs/interface-dms-builder";
16
22
  ```
17
23
 
18
- ## Choose an Encoding
24
+ ## Choose an encoding
19
25
 
20
26
  | You are writing | Encoding |
21
27
  | --- | --- |
22
28
  | A string, number, boolean, array or object | The value itself, as JSON. |
23
29
  | A value the catalog marks `x-dataType` | `DataTypeValue` |
24
- | A reference to a builder-generated resource class | `RefValue` |
25
- | A nested component in an option marked `x-component` | `BlockValue` |
30
+ | A reference to a class of a builder-generated resource | `RefValue` |
31
+ | A nested component in an option the catalog marks `x-component` | `BlockValue` |
26
32
  | Anything else that must reach the source as code | `ExprValue` |
27
33
 
28
- The catalog is what tells you which option needs which: an option marked `x-dataType` in a block's `ConfigSchema` takes a `DataTypeValue`, and nothing else. See [Catalog](./3.catalog.md).
34
+ The catalog says which option needs which encoding. An option marked `x-dataType` in a block's `ConfigSchema` takes a `DataTypeValue` and nothing else. See [Catalog](./3.catalog.md).
29
35
 
30
- ## Write a DataType
36
+ ## Write a `DataTypeValue`
31
37
 
32
- `DataTypeValue` emits `new <DataType>(<config>)` and adds the import for you.
38
+ `DataTypeValue` emits `new <DataType>(<config>)` and adds the import.
33
39
 
34
40
  | Field | Type | Description |
35
41
  | --- | --- | --- |
36
- | `$dataType` | `string` | A `DataTypeDescriptor.id` from the catalog, e.g. `"string"` or `"select"`. |
37
- | `config` | `Record<string, unknown>` | The DataType's options. Omit for a no-arg `new <DataType>()`. |
42
+ | `$dataType` | `string` | A `DataTypeDescriptor.id` from the catalog, such as `"string"` or `"select"`. |
43
+ | `config` | `ConfigObject` | The DataType's options. Omit it for `new <DataType>()` with no argument. |
38
44
 
39
45
  ```typescript
40
46
  import { AddBlock } from "@antelopejs/interface-dms-builder";
@@ -56,17 +62,17 @@ await AddBlock({
56
62
  // emits: type: new DefaultDataTypes.StringType({ placeholder: "Name" })
57
63
  ```
58
64
 
59
- The same encoding is what a resource field's `dataType` takes, described in [Resources](./5.resources.md).
65
+ A resource field's `dataType` takes the same encoding, as [Resources](./5.resources.md) describes.
60
66
 
61
- A `$dataType` id the catalog does not list fails with `invalid_config`, and the message points you at `GetCatalog` for the valid ids.
67
+ A `$dataType` id that the catalog does not list fails with `invalid_config`. The message names `GetCatalog` as the source of the valid ids.
62
68
 
63
- ## Reference a Generated Class
69
+ ## Reference a generated class
64
70
 
65
- `RefValue` emits the bare class identifier of a builder-generated resource, with its import added. It is the structured, validated alternative to `ExprValue` for a class reference — a `RelationType`'s `dataApiController`, for instance.
71
+ `RefValue` emits the bare class name of a builder-generated resource and adds its import. It is the checked alternative to `ExprValue` for a class reference, such as the `dataApiController` of a `RelationType`.
66
72
 
67
73
  | Field | Type | Description |
68
74
  | --- | --- | --- |
69
- | `$ref.resource` | `string` | A `ResourceRef` — the resource's name, e.g. `"product"`. |
75
+ | `$ref.resource` | `string` | A `ResourceRef`, the resource's name, such as `"product"`. |
70
76
  | `$ref.as` | `"dataApi" \| "table" \| "model"` | Which of the resource's classes to reference. Defaults to `"dataApi"`. |
71
77
 
72
78
  ```typescript
@@ -83,17 +89,17 @@ await AddField("order-line", {
83
89
  // emits: dataApiController: productDataAPI (imported from the resource's data-api.ts)
84
90
  ```
85
91
 
86
- `as` selects among the three classes a resource generates: `"dataApi"` for the `DataController`, `"table"` for the `Table`, `"model"` for the `BasicDataModel`. An unknown resource fails with `invalid_config`, and the message points you at `ListResources`.
92
+ `as` picks one of the three classes a resource generates. `"dataApi"` is the `DataController`, `"table"` is the `Table` and `"model"` is the `BasicDataModel`. An unknown resource fails with `invalid_config`, and the message names `ListResources` as the source of the valid refs.
87
93
 
88
- `AddBlockInput.controller` is the same reference in a dedicated field: a controller-leading block type takes a `ResourceRef` there rather than a `RefValue` in its config. See [Page Operations](./4.page-operations.md).
94
+ `AddBlockInput.controller` holds the same reference in a field of its own. A block type that leads with a controller takes a `ResourceRef` there, not a `RefValue` in its config. See [Page operations](./4.page-operations.md).
89
95
 
90
- ## Write a Raw Expression
96
+ ## Write a raw expression
91
97
 
92
- `ExprValue` is emitted verbatim. Reach for it only when the builder cannot model the value structurally.
98
+ The builder emits an `ExprValue` as written. It is for a value the builder cannot model as structure.
93
99
 
94
100
  | Field | Type | Description |
95
101
  | --- | --- | --- |
96
- | `$expr` | `string` | TypeScript source, spliced into the emitted file as written. |
102
+ | `$expr` | `string` | TypeScript source, inserted into the emitted file as written. |
97
103
 
98
104
  ```typescript
99
105
  import { ConfigureBlock } from "@antelopejs/interface-dms-builder";
@@ -103,16 +109,16 @@ await ConfigureBlock("/revenue#total", {
103
109
  });
104
110
  ```
105
111
 
106
- Two constraints come with it:
112
+ Two constraints apply:
107
113
 
108
- - The referenced symbol must already be in scope in the target file. `$expr` adds no import.
109
- - `$expr` is refused inside a query's parameters — a query's values are data, and a raw expression would put uncheckable code inside a model body. See [Queries](./6.queries.md).
114
+ - The symbol the expression names must already be in scope in the target file. `$expr` adds no import.
115
+ - A query's parameters refuse `$expr`, because a query's values are data and a raw expression would put code the builder cannot check inside a model body. See [Queries](./6.queries.md).
110
116
 
111
- Consumers that run the builder in a restricted mode may refuse `$expr` outright, since arbitrary code defeats the structured guarantees the rest of the surface provides. `RefValue` is allowed in that mode precisely because it is validated; prefer it whenever the value you need is a generated class.
117
+ A consumer that runs the builder in a restricted mode can refuse `$expr` entirely, because arbitrary code removes the checks that the structured encodings give. `RefValue` and `BlockValue` stay allowed in that mode, because the builder checks them. For a generated class, `RefValue` is the encoding to use.
112
118
 
113
- ## Nest a Block in a Config Value
119
+ ## Nest a block in a config value
114
120
 
115
- A few options hold a component rather than data — a `ChartCard`'s `chart`, marked `x-component` in the catalog. `BlockValue` is how you write one: the builder emits the nested factory call and adds its import.
121
+ A few options hold a component instead of data, such as the `chart` of a `ChartCard`, which the catalog marks `x-component`. `BlockValue` writes such a component. The builder emits the nested factory call and adds its import.
116
122
 
117
123
  ```typescript
118
124
  import type { BlockValue } from "@antelopejs/interface-dms-builder";
@@ -120,13 +126,13 @@ import type { BlockValue } from "@antelopejs/interface-dms-builder";
120
126
  const chart: BlockValue = {
121
127
  $block: { type: "ChartArea", config: { xaxisType: "datetime" } },
122
128
  };
123
- // → chart: ChartArea({ xaxisType: "datetime" })
129
+ // emits: chart: ChartArea({ xaxisType: "datetime" })
124
130
  ```
125
131
 
126
- `type` is a catalog key, like `AddBlockInput.type`; an unknown one is rejected with `invalid_config`. Read-back is symmetrical: a nested factory call whose callee is a known block type comes back as a `$block` sentinel rather than as `$expr`, so the value stays editable.
132
+ `type` is a catalog key, like `AddBlockInput.type`. In a page draft, an unknown type fails with `invalid_config`. `AddBlock` and `ConfigureBlock` do not turn it into a result, and the call rejects with an `unknown block type` error. Read-back mirrors the write. A nested factory call whose callee is a known block type reads back as a `$block` sentinel, not as `$expr`, so the value stays editable.
127
133
 
128
- Most nesting does *not* go through config — it goes through `.child`, which you express with `AddBlockInput.parent` or with `BlockDraft.children`. Reach for `$block` only where the catalog marks an option `x-component`.
134
+ Most nesting does not go through config. It goes through `.child`, which `AddBlockInput.parent` or `BlockDraft.children` express. `$block` is only for an option that the catalog marks `x-component`.
129
135
 
130
- ## Next Steps
136
+ ## Next steps
131
137
 
132
- - [Results and Errors](./8.results-and-errors.md) - Branch on an operation's result and read its diagnostics
138
+ - [Results and errors](./8.results-and-errors.md) shows how to branch on an operation's result and read its diagnostics.