@antelopejs/interface-dms-builder 0.3.0 → 0.4.0

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.
@@ -1,14 +1,18 @@
1
- # Results and Errors
1
+ ---
2
+ title: "Results and errors"
3
+ description: "The OpResult envelope every builder operation returns: the staged file changes, the warnings, the BuilderError codes with their payloads, and the expectedVersion check."
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- Every operation that can fail returns an `OpResult`. It is a discriminated union: `ok: true` carries the operation's data and the file changes it staged, `ok: false` carries a single `BuilderError`. Nothing throws for an expected failure — an unknown ref, a duplicate name, a config the engine cannot validate all come back as values.
9
+ Every operation that can fail returns an `OpResult`. It is a discriminated union. `ok: true` carries the operation's data and the file changes it staged, and `ok: false` carries a single `BuilderError`. An expected failure comes back as a value and does not throw. An unknown ref, a duplicate name and a configuration the engine cannot validate are examples. The one known exception is an unknown block type inside a `$block` value passed to `AddBlock` or `ConfigureBlock`, which rejects the call, as [Value encodings](./7.values.md) describes.
6
10
 
7
- Mutations are transactional. The engine applies the edit in memory, typechecks the project, and only writes when the typecheck passes; a failure rolls the transaction back and returns `typecheck_failed`, so a rejected operation leaves no partial edit on disk.
11
+ Mutations of source files are transactional. The engine applies the edit in memory, typechecks the project, and writes only when the typecheck passes. A failure rolls the transaction back and returns `typecheck_failed`, so a rejected operation leaves no partial edit on disk.
8
12
 
9
- ## Branch on a Result
13
+ ## Branch on a result
10
14
 
11
- ```typescript
15
+ ```typescript fragment
12
16
  import { GetPageStructure } from "@antelopejs/interface-dms-builder";
13
17
 
14
18
  const result = await GetPageStructure("/dashboard");
@@ -20,28 +24,28 @@ if (!result.ok) {
20
24
  const { blocks, version } = result.data;
21
25
  ```
22
26
 
23
- `OpResult<T>` is:
27
+ `OpResult<T>` has two variants:
24
28
 
25
29
  | Variant | Fields |
26
30
  | --- | --- |
27
- | success | `ok: true`, `data: T`, `changes: FileChange[]`, `warnings?: OpWarning[]` |
28
- | failure | `ok: false`, `error: BuilderError` |
31
+ | Success | `ok: true`, `data: T`, `changes: FileChange[]`, `warnings?: OpWarning[]` |
32
+ | Failure | `ok: false`, `error: BuilderError` |
29
33
 
30
- `OpResult` defaults to `OpResult<void>`, which is what the operations that return nothing but a status use — `ConfigurePage`, `RemoveBlock`, `RemoveField` and the rest.
34
+ `OpResult` defaults to `OpResult<void>`. The operations that return only a status use it, such as `ConfigureCategory`, `RemoveBlock` and `RemoveField`.
31
35
 
32
- Reads that use the envelope — `GetPageStructure`, `GetResourceStructure` — return an empty `changes` array on success. The listing operations (`ListPages`, `ListCategories`, `ListResources`, `ListQueryTemplates`, `GetCatalog`) do not use the envelope at all: they return their value directly, and `RefreshSourceIndex` returns `void`.
36
+ The reads that use the envelope, such as `GetPageStructure` and `GetResourceStructure`, return an empty `changes` array on success. The listing operations (`ListPages`, `ListCategories`, `ListResources`, `ListQueryTemplates`, `ListDataSources` and `GetCatalog`) do not use the envelope. They return their value directly, and `RefreshSourceIndex` returns `void`.
33
37
 
34
- ## Read the Staged Changes
38
+ ## Read the staged changes
35
39
 
36
- `changes` is what the operation wrote, one entry per file.
40
+ `changes` lists what the operation wrote, one entry per file.
37
41
 
38
42
  | Field | Type | Description |
39
43
  | --- | --- | --- |
40
44
  | `path` | `string` | The file's absolute path. |
41
45
  | `kind` | `"create" \| "modify" \| "delete"` | What happened to the file. |
42
- | `diff` | `string` | The change as unified diff hunks (`@@ -a,b +c,d @@`, then lines prefixed with a space, `-` or `+`), with no file headers. Empty for a deletion. |
46
+ | `diff` | `string` | The change as unified diff hunks (`@@ -a,b +c,d @@`, then lines that start with a space, `-` or `+`), with no file headers. Empty for a deleted file or folder. |
43
47
 
44
- A single operation usually touches more than one file — creating a page writes the page file and rewrites the barrel that exports it; creating a resource writes three files and rewrites a barrel.
48
+ One operation often changes more than one file. Creating a page writes the page file and rewrites the barrel that exports it. Creating a resource writes three files and rewrites a barrel.
45
49
 
46
50
  ```typescript
47
51
  import { CreateResource } from "@antelopejs/interface-dms-builder";
@@ -57,16 +61,28 @@ if (result.ok) {
57
61
  }
58
62
  ```
59
63
 
60
- ## Handle Warnings
64
+ ## Handle warnings
61
65
 
62
- `warnings` is present only when an otherwise successful operation has something non-fatal to report. It is optional: check for it rather than assuming an array.
66
+ `warnings` is present only when a successful operation has something non-fatal to report. The field is optional, so it can be absent instead of an empty array.
63
67
 
64
68
  | Code | Raised when |
65
69
  | --- | --- |
66
- | `index_clamped` | An `AddBlock` or `MoveBlock` index fell outside the sibling range and was clamped into it. |
67
- | `datatype_fallback` | A field's DataType is not one the builder maps, so it is handled as a string. |
68
- | `data_not_dropped` | `DeleteResource` removed the source but could not clear the resource's table data. |
69
- | `display_not_proposed` | `MapResources` mapped a relation whose columns left `display` out, and neither a profile nor an analysis was there to propose a badge from, so its text columns were written plain. |
70
+ | `index_clamped` | An `AddBlock` or `MoveBlock` index fell outside the sibling range, and the engine moved it to the nearest end. |
71
+ | `datatype_fallback` | A field's DataType is not one the builder maps, so a resource stores it as a string, or a query filter compares it as a string. |
72
+ | `data_not_dropped` | `DeleteResource` removed the source files but could not delete the rows of the resource's table. |
73
+ | `data_kept` | `DeleteResource` deleted a resource that mirrors an external database. Its rows belong to that database and stay in place. |
74
+ | `route_changed` | `ConfigurePage` moved a page to another category, so its route changed and links to the old route break. |
75
+ | `query_opaque` | `SetPageBlocks` left alone a query on the page that a person took over by hand. |
76
+ | `query_kept` | `SetPageBlocks` kept a query that the draft dropped, because a block still reads it. |
77
+ | `page_not_written` | `MapResources` wrote a mapped relation's tables, but not its table view page. |
78
+ | `relation_missing` | `GetMapping` found a relation that the module maps but the database no longer has, or that the role can no longer see. |
79
+ | `module_not_loaded` | `MapResources` wrote the mapping, but the module it wrote into did not load again. The module was unloaded and did not start within the wait, or the runtime does not report it active. The message names the module, and the backend log holds the error. |
80
+ | `setting_dropped` | `ConfigureTableView` or `MapResources` dropped a TableView setting that no longer holds, such as a default display the table no longer offers. |
81
+ | `relation_skipped` | `MapResources` left out a relation of the batch because one of its choices was refused, and wrote the others. |
82
+ | `display_dropped` | `MapResources` wrote a column plain because the display sent for it, or the tabs sent from it, do not suit the column. |
83
+ | `field_flag_set` | `MapResources` or `ConfigureTableView` marked a column filterable or sortable, because a view, footer figure, tab or grouping needs it. |
84
+ | `label_changed` | `MapResources` changed a relation's label to one the generated code can read, or dropped it when no column offers it. |
85
+ | `display_not_proposed` | `MapResources` mapped a relation whose columns left `display` out, and no profile or analysis could propose a badge, so its text columns were written plain. |
70
86
 
71
87
  ```typescript
72
88
  import { AddBlock } from "@antelopejs/interface-dms-builder";
@@ -85,20 +101,20 @@ if (result.ok) {
85
101
  }
86
102
  ```
87
103
 
88
- ## Handle Errors
104
+ ## Handle errors
89
105
 
90
- `BuilderError` is a union discriminated by `code`. Each variant carries exactly what you need to act on it.
106
+ `BuilderError` is a union discriminated by `code`. Each variant carries the data needed to act on it.
91
107
 
92
108
  | Code | Payload | Meaning |
93
109
  | --- | --- | --- |
94
110
  | `not_found` | `ref: string` | The page, category, block, resource, field or query does not exist. |
95
111
  | `duplicate_name` | `name: string`, `scope: DuplicateScope` | The name is already taken within that scope. |
96
112
  | `invalid_config` | `issues: ValidationIssue[]` | The request's configuration did not validate. |
97
- | `opaque_target` | `path: BlockPath` | The target exists but the builder cannot rewrite it. |
98
- | `unsupported` | `detail: string` | The operation is valid but out of scope for the engine, and `detail` says why. |
113
+ | `opaque_target` | `path: BlockPath` | The target exists, but the builder cannot rewrite it. For a field or a query, `path` holds the `FieldPath` or the `QueryRef`. |
114
+ | `unsupported` | `detail: string` | The engine does not handle the request, and `detail` says why. |
99
115
  | `referential_integrity` | `blockedBy: string[]` | Something still depends on what you asked to delete. |
100
116
  | `stale` | `ref: string`, `currentVersion: string` | The file changed since the version you passed as `expectedVersion`. |
101
- | `typecheck_failed` | `diagnostics: TypecheckError[]` | The edit would not compile, and was rolled back. |
117
+ | `typecheck_failed` | `diagnostics: TypecheckError[]` | The edit would not compile, and the engine rolled it back. |
102
118
 
103
119
  ```typescript
104
120
  import { AddBlock } from "@antelopejs/interface-dms-builder";
@@ -130,9 +146,9 @@ if (!result.ok) {
130
146
  }
131
147
  ```
132
148
 
133
- ### Duplicate Scopes
149
+ ### Duplicate scopes
134
150
 
135
- `DuplicateScope` names the namespace the collision happened in, which tells you what to rename:
151
+ `DuplicateScope` names the namespace the collision happened in, which tells you what to rename.
136
152
 
137
153
  | Scope | The name collided with |
138
154
  | --- | --- |
@@ -140,20 +156,21 @@ if (!result.ok) {
140
156
  | `siblings` | Another child of the same container block. |
141
157
  | `category` | Another category. |
142
158
  | `resource` | Another resource, or another field on the same resource. |
143
- | `query` | A member of the page controller, a route path already served by the page, or a member of the resource's model. |
159
+ | `query` | A member of the page controller, a route path the page already serves, or a member of the resource's model. For a route path, `name` holds the endpoint. |
160
+ | `module` | A module. The type declares this scope, and no operation returns it now. |
144
161
 
145
- ### Validation Issues
162
+ ### Validation issues
146
163
 
147
- `ValidationIssue` addresses a failure by JSON pointer into the request:
164
+ `ValidationIssue` addresses a failure by a JSON pointer into the request.
148
165
 
149
166
  | Field | Type | Description |
150
167
  | --- | --- | --- |
151
- | `pointer` | `string` | A JSON pointer into the offending input, e.g. `/where/0/op`. Empty when the failure is not tied to one key. |
152
- | `message` | `string` | What is wrong, and often what to call to find the valid values. |
168
+ | `pointer` | `string` | A JSON pointer into the input that failed, such as `/where/0/op`. Empty when the failure is not tied to one key. |
169
+ | `message` | `string` | What is wrong. It often names the operation that lists the valid values. |
153
170
 
154
- ### Typecheck Errors
171
+ ### Typecheck errors
155
172
 
156
- `TypecheckError` is one TypeScript diagnostic from the in-memory typecheck gate:
173
+ `TypecheckError` is one TypeScript diagnostic from the in-memory typecheck.
157
174
 
158
175
  | Field | Type | Description |
159
176
  | --- | --- | --- |
@@ -161,21 +178,21 @@ if (!result.ok) {
161
178
  | `line` | `number` | The line number. |
162
179
  | `message` | `string` | The diagnostic message. |
163
180
 
164
- Diagnostics are collected across the project, not just the file the operation edited, so a change that breaks a consumer elsewhere is caught before anything is written.
181
+ The engine collects diagnostics across the whole project, outside `node_modules`, and not only in the file the operation edited. A change that breaks code elsewhere fails before anything is written.
165
182
 
166
- ## Make a Write Conditional
183
+ ## Make a write conditional
167
184
 
168
- `MutationOpts` is the trailing options argument of version-checked mutations, not of every mutation.
185
+ `MutationOpts` is the trailing options argument of the mutations that check a version. Not every mutation takes it.
169
186
 
170
187
  | Option | Type | Description |
171
188
  | --- | --- | --- |
172
- | `expectedVersion` | `string` | The version the consumer last read. A mismatch fails with `stale`. |
189
+ | `expectedVersion` | `string` | The version the caller last read. A mismatch fails with `stale`. |
173
190
 
174
- The operations that edit something that already exists take it: `ConfigurePage`, `DeletePage`, `AddBlock`, `ConfigureBlock`, `MoveBlock`, `RemoveBlock`, `DeleteResource`, `AddField`, `ConfigureField`, `RemoveField`, `AddQuery`, `ConfigureQuery` and `RemoveQuery`. The three creations — `CreatePage`, `CreateCategory` and `CreateResource` — take no options argument, and neither do `ConfigureCategory` and `DeleteCategory`.
191
+ The operations that edit something that already exists take it: `ConfigurePage`, `DeletePage`, `AddBlock`, `ConfigureBlock`, `MoveBlock`, `RemoveBlock`, `SetPageBlocks`, `ConfigureResource`, `DeleteResource`, `AddField`, `ConfigureField`, `RemoveField`, `AddQuery`, `ConfigureQuery`, `RemoveQuery`, `ConfigureTableView` and `ComposePage`. The three creations, `CreatePage`, `CreateCategory` and `CreateResource`, take no options argument. Neither do `ConfigureCategory` and `DeleteCategory`.
175
192
 
176
- The version comes from the structure you read: `PageStructure.version` for a page operation, `ResourceStructure.version` for a resource one. Both are content hashes — of the page file, and of a resource's two generated files.
193
+ The version comes from the structure you read. A page operation takes `PageStructure.version`, and a resource operation takes `ResourceStructure.version`. Both are content hashes, of the page file and of the resource's two generated files.
177
194
 
178
- ```typescript
195
+ ```typescript fragment
179
196
  import {
180
197
  ConfigureBlock,
181
198
  GetPageStructure,
@@ -192,11 +209,11 @@ const write = await ConfigureBlock(
192
209
  { expectedVersion: read.data.version },
193
210
  );
194
211
  if (!write.ok && write.error.code === "stale") {
195
- // `write.error.currentVersion` is what the file hashes to now:
196
- // re-read the structure, re-decide, and retry.
212
+ // `write.error.currentVersion` is the file's hash now.
213
+ // Read the structure again, decide again, and retry.
197
214
  }
198
215
  ```
199
216
 
200
- Omitting `expectedVersion` writes unconditionally. Pass it whenever a human or another process may have touched the file between your read and your write.
217
+ Without `expectedVersion`, the write is unconditional. Pass it when a person or another process can change the file between your read and your write.
201
218
 
202
- Two operations extend `MutationOpts` with options of their own: `ConfigureBlockOpts` adds `replace` and `meta`, described in [Page Operations](./4.page-operations.md), and `DeleteResourceOpts` adds `keepData`, described in [Resources](./5.resources.md).
219
+ Two operations extend `MutationOpts` with options of their own. `ConfigureBlockOpts` adds `replace` and `meta`, described in [Page operations](./4.page-operations.md). `DeleteResourceOpts` adds `keepData`, described in [Resources](./5.resources.md).
@@ -1,12 +1,16 @@
1
- # Drafts and Preview
1
+ ---
2
+ title: "Drafts and preview"
3
+ description: "SetPageBlocks writes a page's whole block tree and its queries in one transaction, PreviewLayout serializes a draft without writing it, and PreviewQuery runs an unsaved query against the real table."
4
+ mode: reference
5
+ ---
2
6
 
3
- Use `PreviewLayout` to inspect a page draft without writing files, then `SetPageBlocks` to save its complete block tree in one transaction. A successful preview does not mean the draft compiles: the write performs that check.
7
+ `PreviewLayout` serializes a page draft without writing files. `SetPageBlocks` saves the draft's complete block tree in one transaction. A successful preview does not mean that the draft compiles, because only the write runs the typecheck.
4
8
 
5
9
  > **Warning:** `SetPageBlocks` replaces the whole block tree. Blocks you omit are removed. For one small change to an existing page, use `ConfigureBlock` instead.
6
10
 
7
11
  ## Preview one card
8
12
 
9
- With an existing `/dashboard` page, this read-only example returns a serialized KPI card. An editor can render `preview.data.components.total`; no source file changes.
13
+ With an existing `/dashboard` page, this read-only example returns a serialized KPI card. An editor can render `preview.data.components.total`. No source file changes.
10
14
 
11
15
  ```typescript
12
16
  import { PreviewLayout } from "@antelopejs/interface-dms-builder";
@@ -22,26 +26,26 @@ if (preview.ok) {
22
26
  }
23
27
  ```
24
28
 
25
- The per-block operations each save immediately. A visual editor usually holds edits locally and calls `SetPageBlocks` once, so the block tree and its queries either pass one typecheck together or remain unchanged.
29
+ Each per-block operation saves at once. A visual editor usually holds its edits locally and calls `SetPageBlocks` once. The block tree and its queries then pass one typecheck together, or the page stays unchanged.
26
30
 
27
- ## Describe a Draft
31
+ ## Describe a draft
28
32
 
29
33
  `PageDraft` is a page as you want it written.
30
34
 
31
35
  | Field | Type | Description |
32
36
  | --- | --- | --- |
33
- | `page` | `Partial<EditablePageMeta>` | Metadata to patch alongside the blocks. Omit to leave it alone. |
34
- | `blocks` | `BlockDraft[]` | The top-level blocks, in the order they should appear. |
35
- | `queries` | `AddQueryInput[]` | The queries the page should serve, written with the blocks. Omit to leave the page's queries alone. |
37
+ | `page` | `Partial<EditablePageMeta>` | Metadata to patch alongside the blocks. Omit it to leave the metadata alone. |
38
+ | `blocks` | `BlockDraft[]` | The top-level blocks, in page order. |
39
+ | `queries` | `AddQueryInput[]` | The queries the page serves, written with the blocks. Omit it to leave the page's queries alone. |
36
40
 
37
- A block and the data it reads are one edit, so they are one write: the blocks, the model methods and the routes land in a single transaction with a single typecheck, or none of them land.
41
+ A block and the data it reads are one edit, so they are one write. The blocks, the model methods and the routes land in a single transaction with a single typecheck, or none of them land.
38
42
 
39
- Omitting `queries` is not the same as passing an empty array. Omitted leaves whatever the page already serves untouched — which is what a caller written before queries joined the draft keeps doing. An empty array says the page should serve none, and removes the generated ones.
43
+ An omitted `queries` and an empty array differ. An omitted `queries` leaves the queries the page already serves untouched. An empty array says that the page serves none, and removes the generated ones. Two queries in a draft with the same name fail with `invalid_config`.
40
44
 
41
- Two things a draft never destroys silently:
45
+ A draft never removes or rewrites these two kinds of query:
42
46
 
43
- - **A query edited past the grammar** is left exactly as it is, whether the draft still lists it or not. The engine reports `query_opaque` and moves on, because a chain it cannot read is one a human owns.
44
- - **A query the draft forgot** is kept when a block on the page still reads its URL, reported as `query_kept`. A draft that dropped a source a block depends on is a draft that would break that block, and keeping it is the recoverable side of the mistake.
47
+ - **A query edited past the grammar.** The write leaves it exactly as it is, whether the draft still lists it or not, and reports a `query_opaque` warning. The builder cannot read the chain, so a person owns it.
48
+ - **A query the draft left out while a block still reads it.** The write keeps the query when a block on the page still names its URL, and reports a `query_kept` warning. Removing the query would break that block.
45
49
 
46
50
  ```typescript
47
51
  import { SetPageBlocks } from "@antelopejs/interface-dms-builder";
@@ -74,20 +78,21 @@ await SetPageBlocks("/shop/board", {
74
78
 
75
79
  | Field | Type | Description |
76
80
  | --- | --- | --- |
77
- | `name` | `string` | Static key (top-level) or `.child()` id (nested). Must be a valid identifier. |
78
- | `type` | `string` | A `BlockTypeDescriptor.type` from the catalog. Ignored when `preserve` is set. |
79
- | `config` | `Record<string, unknown>` | The factory's options. |
81
+ | `name` | `string` | The static key (top level) or the `.child()` id (nested). Must be a valid identifier. |
82
+ | `type` | `string` | A `BlockTypeDescriptor.type` from the catalog. Required unless `preserve` is set. On a preserved block, a `type` that differs from the one on disk is refused. |
83
+ | `config` | `ConfigObject` | The factory's options. Leave out locked options, because the server keeps their source. A preserved block takes no `config`. |
84
+ | `lockedOptions` | `Record<string, string>` | Read-only UI hints copied from `GetPageStructure`. The server derives option locks from its own source. |
80
85
  | `slot` | `string` | The slot the block sits in. |
81
- | `meta` | `Record<string, unknown>` | Child metadata emitted with `slot` — `{ colSpan: 2 }` on a grid child. |
86
+ | `meta` | `ConfigObject` | Child metadata emitted with `slot`, such as `{ colSpan: 2 }` on a grid child. |
82
87
  | `controller` | `ResourceRef` | Required for a controller-leading type such as `TableView`. |
83
88
  | `children` | `BlockDraft[]` | Nested blocks, in `.child()` order. |
84
89
  | `preserve` | `boolean` | Write the block back exactly as it stands on disk. |
85
90
 
86
- ## Carry an Opaque Block Through a Write
91
+ ## Carry an opaque block through a write
87
92
 
88
- A whole-tree write has to answer for blocks the builder can read but not rewrite: a `CustomComponent`, a block behind a computed expression, anything `GetPageStructure` reported with `editable: false`. `preserve` is that answer — the block is matched by name among its siblings and its source text is written back untouched.
93
+ A whole-tree write has to keep the blocks that the builder can read but not rewrite. These include a `CustomComponent`, a block behind a computed expression, and any block that `GetPageStructure` reported with `editable: false`. Set `preserve` on such a block. The write matches the block by name among its siblings and writes its source text back untouched. A locked block without `preserve` is refused.
89
94
 
90
- To change only the page title, preserve every top-level block, including its children. This avoids reconstructing editable blocks from only part of their configuration:
95
+ To change only the page title, preserve every top-level block. A preserved block keeps its children, so you do not rebuild editable blocks from part of their configuration:
91
96
 
92
97
  ```typescript
93
98
  import {
@@ -111,11 +116,13 @@ if (structure.ok) {
111
116
  }
112
117
  ```
113
118
 
114
- When you do edit a block, supply its full draft, including its `children`, `slot`, `meta` and `controller` where applicable. Do not reduce a block tree to names and configuration objects: omitted children are deleted.
119
+ When you edit a block, supply its full draft, including its `children`, `slot`, `meta` and `controller` where they apply. Do not reduce a block tree to names and configuration objects, because the write deletes omitted children.
120
+
121
+ The children of a preserved block are read-only. If a preserved block lists `children`, each child must also be preserved, with the names, slots and metadata it has on disk. A block with locked options keeps its type, its controller and the placement of its children, and the draft cannot copy it to another path.
115
122
 
116
123
  A `preserve` block whose name matches nothing on disk is rejected with `invalid_config`.
117
124
 
118
- ## Write a Whole Page
125
+ ## Write a whole page
119
126
 
120
127
  ```typescript
121
128
  import { GetPageStructure, SetPageBlocks } from "@antelopejs/interface-dms-builder";
@@ -154,32 +161,35 @@ const result = await SetPageBlocks(
154
161
  if (!result.ok) throw new Error(result.error.code);
155
162
  ```
156
163
 
157
- Read the version immediately before preparing the draft. If the write returns `stale`, read the page again and review the other editor's changes before retrying.
164
+ Read the version immediately before you prepare the draft. If the write returns `stale`, read the page again and review the other editor's changes before you retry.
158
165
 
159
- On success the result carries the staged `changes` and the page's new `version`, ready to be passed as the next `expectedVersion`.
166
+ On success, the result carries the staged `changes` and the page's new `version`. Pass that `version` as the next `expectedVersion`.
160
167
 
161
- Blocks absent from the draft are removed. Static members that are not blocks are left where they are, and named imports nothing references any more are dropped, so a page shrinks cleanly rather than accumulating dead imports.
168
+ The write removes the blocks that the draft leaves out. It leaves static members that are not blocks where they are. It drops a named import that the file used before the write and no longer uses. An import that was already unused before the write stays.
162
169
 
163
- `$expr` is refused anywhere in a draft: a whole-tree write stays structured, and arbitrary code would defeat the guarantee. `$dataType`, `$ref` and `$block` are all accepted — see [Value Encodings](./7.values.md).
170
+ `$expr` is refused anywhere in a draft, including `page` and a block's `meta`. A whole-tree write stays structured, and arbitrary code would break that guarantee. `$dataType`, `$ref` and `$block` are accepted. See [Value encodings](./7.values.md).
164
171
 
165
- ## Read the Failures
172
+ ## Read the failures
166
173
 
167
- `SetPageBlocks` validates the whole draft before touching anything, so one call reports every problem at once, each addressed by a JSON pointer into the draft:
174
+ `SetPageBlocks` validates the whole draft before it touches anything. One call reports every problem at once as an `invalid_config` issue, each addressed by a JSON pointer into the draft:
168
175
 
169
176
  | Pointer | Cause |
170
177
  | --- | --- |
171
178
  | `/blocks/1/name` | Not a valid identifier, or a duplicate among its siblings. |
172
- | `/blocks/2/type` | Unknown block type, or a child a container does not accept. |
173
- | `/blocks/0/controller` | A controller-leading type without one, or a controller on a type that takes none. |
179
+ | `/blocks/2/type` | An unknown block type. |
180
+ | `/blocks/2/children` | Children on a block type that takes none. |
181
+ | `/blocks/2/children/0/type` | A child type that the container does not accept. |
182
+ | `/blocks/0/controller` | A controller-leading type without a controller, or a controller on a type that takes none. |
174
183
  | `/blocks/3/config/title` | `$expr` inside a config value. |
184
+ | `/blocks/4` | A `preserve` block with no match on disk, a locked block without `preserve`, or a change to a preserved block. |
175
185
 
176
- Beyond `invalid_config`, the write fails the same way the per-block operations do: `stale` when `expectedVersion` no longer matches, `typecheck_failed` when the emitted source does not compile — and then nothing is written. See [Results and Errors](./8.results-and-errors.md).
186
+ Beyond `invalid_config`, the write fails the same way as the per-block operations. It returns `stale` when `expectedVersion` no longer matches, and `typecheck_failed` when the emitted source does not compile. In both cases nothing is written. See [Results and errors](./8.results-and-errors.md).
177
187
 
178
- ## Preview a Draft
188
+ ## Preview a draft
179
189
 
180
- `PreviewLayout` serializes a draft the way the runtime would, by calling the same `dms-base` factories the emitted source would call. Nothing is typechecked and nothing reaches disk, so it is cheap enough to run on every edit.
190
+ `PreviewLayout` serializes a draft the way the runtime would, through the block factories of `@antelopejs/interface-dms/base` that the emitted source calls. It runs no typecheck and writes nothing to disk, so an editor can run it on every edit.
181
191
 
182
- ```typescript
192
+ ```typescript fragment
183
193
  const preview = await PreviewLayout("/dashboard", draft);
184
194
  if (preview.ok) {
185
195
  const grid = preview.data.components.kpis; // ComponentPreview
@@ -190,14 +200,22 @@ if (preview.ok) {
190
200
 
191
201
  | Field | Type | Description |
192
202
  | --- | --- | --- |
193
- | `components` | `Record<string, ComponentPreview>` | The serialized tree, keyed by top-level block name — the shape the DMS frontend renders. |
194
- | `degraded` | `BlockPath[]` | Blocks the preview could not build faithfully. |
203
+ | `components` | `Record<string, ComponentPreview>` | The serialized tree, keyed by top-level block name. The DMS frontend renders this shape. |
204
+ | `degraded` | `BlockPath[]` | Blocks that the preview could not build faithfully. |
205
+
206
+ A degraded block appears in `components` as a placeholder that names the reason. A block lands in `degraded` in these cases:
195
207
 
196
- A block lands in `degraded` when the preview cannot reach what the running page holds: a controller-leading block such as `TableView` (the DataAPI class lives in the app, not in the builder), a `$ref`, an `$expr`, a `preserve` block (which the draft does not describe), or a factory that threw. Render the last known-good layout for those rather than trusting the preview.
208
+ - It is a `preserve` block, or a block with locked options, which the draft does not describe.
209
+ - Its type is unknown.
210
+ - It is a controller-leading block, such as `TableView`, with no `controller`, or with a resource whose DataAPI class the running app has not loaded. A table created a moment ago is not loaded until the app reloads.
211
+ - Its config holds an `$expr`, or a `$ref` to a resource whose DataAPI class the running app has not loaded.
212
+ - Its factory threw.
197
213
 
198
- ## Preview a Query
214
+ For a degraded block, render the last known-good layout rather than trust the preview.
199
215
 
200
- `PreviewLayout` says what the page will look like; `PreviewQuery` says what its blocks will be showing. It runs a query the caller has not saved and answers what its route would, so a source can be configured against real data before anything is written.
216
+ ## Preview a query
217
+
218
+ `PreviewLayout` shows what the page looks like. `PreviewQuery` shows the data its blocks display. It runs a query that is not saved and answers what its route would, so an editor can configure a source against real data before anything is written.
201
219
 
202
220
  ```typescript
203
221
  import { PreviewQuery } from "@antelopejs/interface-dms-builder";
@@ -225,17 +243,35 @@ if (result.ok) {
225
243
 
226
244
  | Field | Type | Description |
227
245
  | --- | --- | --- |
228
- | `query` | `AddQueryInput` | What to run. Validated the same way `AddQuery` validates it. |
229
- | `args` | `Record<string, unknown>` | Values the route would receive, by the name it exposes them under. A bound value with nothing supplied is refused rather than compared against nothing. |
230
- | `tenant` | `string` | Whose rows to read, for a resource in a per-tenant schema. |
246
+ | `query` | `AddQueryInput` | The query to run. The template validates its parameters as it does for `AddQuery`. |
247
+ | `args` | `ConfigObject` | The values the route would receive, under the names the route exposes. |
248
+ | `tenant` | `string` | Whose rows to read, for a resource in a per-tenant schema. A resource in any other schema ignores it. |
249
+
250
+ `QueryPreview` holds the route's own response body and how it is arranged:
251
+
252
+ | Field | Type | Description |
253
+ | --- | --- | --- |
254
+ | `output` | `QueryOutputKind` | `"scalar"` or `"series"`, as `GetPageStructure` reports the saved query. |
255
+ | `response` | `QueryArrangement` | Set only when a DMS helper arranged the body, the same condition under which read-back reports it. |
256
+ | `body` | `QueryResponseBody` | What the route would return. |
257
+ | `truncated` | `boolean` | `true` when the preview stopped at 500 points. |
258
+
259
+ The body depends on the calculation and on `response`:
260
+
261
+ - A scalar calculation answers `{ value }`, whatever `response` asks for.
262
+ - A grouped calculation with no `response`, or with `response: "series"`, answers `{ series }` and leaves `response` unset.
263
+ - `response: "card"`, `"value"` and `"items"` answer the body that `chartCardData`, `kpiCardData` and `topListData` from `@antelopejs/interface-dms/base/query-response` build. The generated route calls the same helper, so `"card"` answers a `ChartCardData`, headline figure included, and a block can take it directly.
231
264
 
232
- What comes back is the route's own response body, not a rendering of it: `body` is built by the same helper the generated route would call, and `output` and `response` report it the way `GetPageStructure` reports the saved query. Ask for `response: "card"` and the preview answers a `ChartCardData` — the headline figure included — so a block can be handed it directly. `response` is set only when a helper arranged the body, which is the same condition read-back reports it under; a plain `{ value }` or `{ series }` leaves it absent. An arrangement the installed DMS does not publish is refused as `unsupported` rather than answered as bare points, because saving that query would fail its typecheck on the very same helper.
265
+ A query is filters, a grouping and a measure. It has no way to express a write, so a preview only reads. It stops at 500 points and sets `truncated`, so a chart that lost its tail says so.
233
266
 
234
- Read-only by construction: a query is filters, a grouping and a measure, and there is no way to spell a write in one. It stops at 500 points and sets `truncated`, because a chart that quietly lost its tail is worse than one that reports it. A database that refuses comes back as a failed `OpResult`, not as a rejection.
267
+ `PreviewQuery` answers every failure as a failed `OpResult`, never as a rejected promise:
235
268
 
236
- ## Next Steps
269
+ - `not_found` for an unknown resource.
270
+ - `invalid_config` for an unknown template or invalid parameters.
271
+ - `unsupported` when the resource's schema is not registered, when a bound value has no argument in `args`, or when the database refuses the query.
237
272
 
238
- - [A Chart Without Code](./10.a-chart-without-code.md) - The journey these calls add up to
273
+ ## Next steps
239
274
 
240
- - [Results and Errors](./8.results-and-errors.md) - Branch on `OpResult` and handle `stale`
241
- - [Catalog](./3.catalog.md) - The block types, their schemas and their defaults
275
+ - [A chart without code](./10.a-chart-without-code.md) follows these calls from the choice of a measure to the reopened page.
276
+ - [Results and errors](./8.results-and-errors.md) shows how to branch on `OpResult` and handle `stale`.
277
+ - [Catalog](./3.catalog.md) lists the block types, their schemas and their defaults.
@@ -2,6 +2,10 @@
2
2
  "extends": "../tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": "..",
5
+ "module": "esnext",
6
+ "moduleResolution": "bundler",
7
+ "noUnusedLocals": false,
8
+ "noUnusedParameters": false,
5
9
  "composite": false,
6
10
  "incremental": false,
7
11
  "noEmit": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-dms-builder",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "AntelopeJS interface for the DMS builder: create and edit pages, blocks, resources and queries in a DMS project from another module.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -50,7 +50,7 @@
50
50
  "prepack": "pnpm run build"
51
51
  },
52
52
  "devDependencies": {
53
- "@antelopejs/interface-core": ">=0.2.4 <1.0.0",
53
+ "@antelopejs/interface-core": ">=0.4.0 <1.0.0",
54
54
  "release-it": "^19.2.4",
55
55
  "release-it-changelogen": "^0.1.0",
56
56
  "rimraf": "^6.1.3",
Binary file