@antelopejs/interface-dms-builder 0.0.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.
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GetResourceStructure = exports.ListResources = exports.RemoveField = exports.ConfigureField = exports.AddField = exports.DeleteResource = exports.ConfigureResource = exports.CreateResource = void 0;
4
+ const interface_core_1 = require("@antelopejs/interface-core");
5
+ exports.CreateResource = (0, interface_core_1.InterfaceFunction)();
6
+ exports.ConfigureResource = (0, interface_core_1.InterfaceFunction)();
7
+ exports.DeleteResource = (0, interface_core_1.InterfaceFunction)();
8
+ exports.AddField = (0, interface_core_1.InterfaceFunction)();
9
+ exports.ConfigureField = (0, interface_core_1.InterfaceFunction)();
10
+ exports.RemoveField = (0, interface_core_1.InterfaceFunction)();
11
+ exports.ListResources = (0, interface_core_1.InterfaceFunction)();
12
+ exports.GetResourceStructure = (0, interface_core_1.InterfaceFunction)();
13
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AA4MlD,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAId,CAAC;AAcO,QAAA,iBAAiB,GAC5B,IAAA,kCAAiB,GAMd,CAAC;AAEO,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAEd,CAAC;AACO,QAAA,QAAQ,GACnB,IAAA,kCAAiB,GAMd,CAAC;AACO,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAMd,CAAC;AACO,QAAA,WAAW,GACtB,IAAA,kCAAiB,GAEd,CAAC;AACO,QAAA,aAAa,GACxB,IAAA,kCAAiB,GAAoC,CAAC;AAC3C,QAAA,oBAAoB,GAC/B,IAAA,kCAAiB,GAEd,CAAC"}
@@ -0,0 +1,59 @@
1
+ import type { BlockPath } from "./addressing";
2
+ export interface FileChange {
3
+ path: string;
4
+ kind: "create" | "modify" | "delete";
5
+ diff: string;
6
+ }
7
+ export interface ValidationIssue {
8
+ pointer: string;
9
+ message: string;
10
+ }
11
+ export interface TypecheckError {
12
+ file: string;
13
+ line: number;
14
+ message: string;
15
+ }
16
+ export interface OpWarning {
17
+ code: "index_clamped" | "datatype_fallback" | "data_not_dropped" | "route_changed";
18
+ message: string;
19
+ }
20
+ export type DuplicateScope = "page" | "siblings" | "category" | "resource" | "query";
21
+ export type BuilderError = {
22
+ code: "not_found";
23
+ ref: string;
24
+ } | {
25
+ code: "duplicate_name";
26
+ name: string;
27
+ scope: DuplicateScope;
28
+ } | {
29
+ code: "invalid_config";
30
+ issues: ValidationIssue[];
31
+ } | {
32
+ code: "opaque_target";
33
+ path: BlockPath;
34
+ } | {
35
+ code: "unsupported";
36
+ detail: string;
37
+ } | {
38
+ code: "referential_integrity";
39
+ blockedBy: string[];
40
+ } | {
41
+ code: "stale";
42
+ ref: string;
43
+ currentVersion: string;
44
+ } | {
45
+ code: "typecheck_failed";
46
+ diagnostics: TypecheckError[];
47
+ };
48
+ export type OpResult<T = void> = {
49
+ ok: true;
50
+ data: T;
51
+ changes: FileChange[];
52
+ warnings?: OpWarning[];
53
+ } | {
54
+ ok: false;
55
+ error: BuilderError;
56
+ };
57
+ export interface MutationOpts {
58
+ expectedVersion?: string;
59
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"results.js","sourceRoot":"","sources":["../src/results.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import type { BlockPath, CategoryRef, PageRef } from "./addressing";
2
+ import type { QueryStructure } from "./queries";
3
+ import type { ResourceRef } from "./resources";
4
+ export type PagePermission = Record<string, unknown>;
5
+ export interface PageMeta {
6
+ ref: PageRef;
7
+ id: string;
8
+ displayName: string;
9
+ icon?: string;
10
+ category: CategoryRef;
11
+ order?: number;
12
+ description?: string;
13
+ hidden?: boolean;
14
+ permission?: PagePermission;
15
+ filepath: string;
16
+ }
17
+ export interface EditablePageMeta {
18
+ displayName: string;
19
+ icon?: string;
20
+ category: CategoryRef;
21
+ order?: number;
22
+ description?: string;
23
+ hidden?: boolean;
24
+ permission?: PagePermission;
25
+ }
26
+ export interface BlockNode {
27
+ path: BlockPath;
28
+ name: string;
29
+ type: string | null;
30
+ editable: boolean;
31
+ opaqueReason?: string;
32
+ slot?: string;
33
+ meta?: Record<string, unknown>;
34
+ config?: Record<string, unknown>;
35
+ controller?: ResourceRef;
36
+ children?: BlockNode[];
37
+ }
38
+ export interface PageStructure {
39
+ page: PageMeta;
40
+ blocks: BlockNode[];
41
+ queries: QueryStructure[];
42
+ version: string;
43
+ }
44
+ export interface PageSummary {
45
+ ref: PageRef;
46
+ id: string;
47
+ displayName: string;
48
+ category: CategoryRef;
49
+ moduleId?: string;
50
+ filepath: string;
51
+ }
52
+ export interface CategorySummary {
53
+ ref: CategoryRef;
54
+ displayName: string;
55
+ parent?: CategoryRef;
56
+ }
57
+ export interface EditableCategoryMeta {
58
+ displayName: string;
59
+ parent?: CategoryRef;
60
+ icon?: string;
61
+ order?: number;
62
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=structure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.js","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ export interface DataTypeValue {
2
+ $dataType: string;
3
+ config?: Record<string, unknown>;
4
+ }
5
+ export interface ExprValue {
6
+ $expr: string;
7
+ }
8
+ export interface RefValue {
9
+ $ref: {
10
+ resource: string;
11
+ as?: "dataApi" | "table" | "model";
12
+ };
13
+ }
14
+ export interface BlockValue {
15
+ $block: {
16
+ type: string;
17
+ config?: Record<string, unknown>;
18
+ };
19
+ }
package/dist/values.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.js","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ # DMS Builder Documentation
2
+
3
+ ## Overview
4
+
5
+ The DMS Builder interface edits a DMS project's source tree programmatically. Where the DMS itself is authored by writing TypeScript — a `PageController` here, a `DataController` there — the builder exposes those same authoring moves as operations: create a page, add a block to it, generate a resource with its table and API, declare a query that counts rows.
6
+
7
+ Every operation reads or rewrites real source files. There is no separate database of pages, and no runtime registry to keep in sync: the builder parses the project, applies the edit with an AST rewrite, typechecks the result in memory, and writes only if it compiles. What you get back is the list of files it changed.
8
+
9
+ A *block* is the builder's name for one DMS component. Every type the catalog reports is a `dms-base` factory returning a `ComponentBuilder`, and `AddBlock` writes that factory call either as a static field of the page's `PageController` or as a `.child()` of another block — the same declaration you would otherwise have typed by hand.
10
+
11
+ Operations are `InterfaceFunction` exports, so you import one and call it like any async function; the call is routed to the module implementing the interface. Reads such as `ListPages` return their value directly. Everything that can fail — every mutation, and the two structure reads — returns an `OpResult`, a discriminated union you branch on rather than a value you wrap in `try`.
12
+
13
+ ```typescript
14
+ import type { OpResult } from "@antelopejs/interface-dms-builder";
15
+
16
+ // { ok: true; data: T; changes: FileChange[]; warnings?: OpWarning[] }
17
+ // { ok: false; error: BuilderError }
18
+ ```
19
+
20
+ ## Key Features
21
+
22
+ - **Source as the Single Source of Truth** — Operations parse and rewrite the project's own TypeScript, so hand-written code and generated code are the same code.
23
+ - **Typechecked Writes** — Each mutation is staged in memory, typechecked across the project, and rolled back on any diagnostic, so a rejected operation leaves nothing behind.
24
+ - **A Discoverable Catalog** — `GetCatalog` reports every block factory and DataType the builder can emit, each with a schema for its options, so a caller validates a configuration before writing it.
25
+ - **Stable String Addresses** — A page, a block, a category, a resource, a field and a query are each named by a plain string ref you can store and pass back into the next call.
26
+ - **Structural Read-Back** — Blocks, resource fields and queries are read back off disk by shape, so a hand-written declaration matching the grammar is as editable as a generated one, and one that drifts out of grammar is reported as opaque rather than silently rewritten.
27
+ - **Resource Generation** — A single call writes a `Table`, a `DataController` and a model into a wired folder, described in semantic field aspects rather than in decorators.
28
+ - **Derived Queries** — A query template compiles into a model method and a page route, giving a card on a page a number to fetch without hand-writing either half.
29
+ - **Optimistic Concurrency** — Structures carry a content-hash `version` you can pass back as `expectedVersion` to make the next write conditional.
30
+
31
+ ## Dependencies
32
+
33
+ This interface relies on the following AntelopeJS packages:
34
+
35
+ - [@antelopejs/interface-core](https://github.com/AntelopeJS/interface-core) - The `InterfaceFunction` call surface every operation is declared with.
36
+
37
+ The implementation lives in the dms-builder module, so the interface only works in a project that also loads `@antelopejs/dms`: the block factories and DataTypes the catalog reports are read from its [`@antelopejs/interface-dms/base`](https://github.com/AntelopeJS/dms) interface, and the pages and resources the builder writes import from it. Clearing a deleted resource's rows goes through [@antelopejs/interface-database](https://github.com/AntelopeJS/interface-database).
38
+
39
+ ## Quick Start
40
+
41
+ The example creates a page under the root `pages` category and puts a KPI card on it.
42
+
43
+ ```typescript
44
+ import {
45
+ AddBlock,
46
+ CreatePage,
47
+ } from "@antelopejs/interface-dms-builder";
48
+
49
+ const page = await CreatePage({
50
+ name: "revenue",
51
+ displayName: "Revenue",
52
+ category: "pages",
53
+ icon: "i-ph-chart-line",
54
+ });
55
+ if (!page.ok) {
56
+ throw new Error(`could not create the page: ${page.error.code}`);
57
+ }
58
+
59
+ const block = await AddBlock({
60
+ page: page.data.ref, // "/revenue"
61
+ name: "total",
62
+ type: "KpiCard",
63
+ config: {
64
+ title: "Total revenue",
65
+ icon: "i-ph-currency-eur",
66
+ },
67
+ });
68
+ if (!block.ok) {
69
+ throw new Error(`could not add the block: ${block.error.code}`);
70
+ }
71
+
72
+ console.log(block.data.path); // "/revenue#total"
73
+ for (const change of block.changes) {
74
+ console.log(change.kind, change.path);
75
+ }
76
+ ```
77
+
78
+ ## Documentation Sections
79
+
80
+ - [Addressing and Structure](./2.addressing-and-structure.md) - The refs that name a page, a block and a category, and the shapes the builder reads back
81
+ - [Catalog](./3.catalog.md) - The block types, DataTypes and reserved names the builder can report
82
+ - [Page Operations](./4.page-operations.md) - Create pages and categories, and edit a page's block tree
83
+ - [Resources](./5.resources.md) - Generate tables, DataAPI controllers and their fields
84
+ - [Queries](./6.queries.md) - Compile counts and aggregates into a model method and a page route
85
+ - [Value Encodings](./7.values.md) - Write configuration values that are not plain literals
86
+ - [Results and Errors](./8.results-and-errors.md) - Branch on `OpResult`, read diagnostics, and make a write conditional
@@ -0,0 +1,207 @@
1
+ # Addressing and Structure
2
+
3
+ ## Overview
4
+
5
+ Every builder operation names the thing it edits with a string ref. There are three of them — a `PageRef` for a page, a `BlockPath` for a block inside a page, and a `CategoryRef` for a category — and each is a plain string you can store, log and pass back into the next call.
6
+
7
+ Alongside the refs, this page describes the read shapes the builder hands back: the page metadata it can read and patch, the block tree it resolves from source, and the lightweight summaries the listing operations return. The operations that produce and consume these shapes live in [Page Operations](./4.page-operations.md).
8
+
9
+ ```typescript
10
+ import type {
11
+ BlockPath,
12
+ CategoryRef,
13
+ PageRef,
14
+ } from "@antelopejs/interface-dms-builder";
15
+
16
+ const page: PageRef = "/dashboard";
17
+ const block: BlockPath = "/dashboard#kpis/row/revenue";
18
+ const category: CategoryRef = "pages.reports";
19
+ ```
20
+
21
+ ## Address a Page
22
+
23
+ `PageRef` is a page's route string, built from its category's slug and the page's own name — a page named `revenue` under the root `pages` category is `/revenue`. `CreatePage` returns the ref it assigned, and every page operation takes it as its first argument.
24
+
25
+ ```typescript
26
+ import {
27
+ CreatePage,
28
+ GetPageStructure,
29
+ } from "@antelopejs/interface-dms-builder";
30
+
31
+ const created = await CreatePage({
32
+ name: "revenue",
33
+ displayName: "Revenue",
34
+ category: "pages",
35
+ });
36
+ if (created.ok) {
37
+ const structure = await GetPageStructure(created.data.ref); // "/revenue"
38
+ }
39
+ ```
40
+
41
+ ## Address a Block
42
+
43
+ `BlockPath` addresses one block inside a page: `<pageRef>#<name>` for a top-level block, then `/<childId>` for each nesting level below it.
44
+
45
+ | Path | What it addresses |
46
+ | --- | --- |
47
+ | `/dashboard#kpis` | The top-level block named `kpis` on `/dashboard` |
48
+ | `/dashboard#kpis/row` | The `.child("row", …)` of that block |
49
+ | `/dashboard#kpis/row/revenue` | The `.child("revenue", …)` of `row` |
50
+
51
+ The first segment after `#` is the static class member the block is declared as; every further segment is the id passed to `.child()`. `AddBlock` returns the path of the block it added, so you rarely build one by hand.
52
+
53
+ ## Address a Category
54
+
55
+ `CategoryRef` is a category's stable id. The three roots are `pages`, `modules` and `settings`; a category created under a parent is addressed by joining them with a dot, so a `reports` category under `pages` is `pages.reports`.
56
+
57
+ ```typescript
58
+ import { CreateCategory } from "@antelopejs/interface-dms-builder";
59
+
60
+ const category = await CreateCategory({
61
+ name: "reports",
62
+ displayName: "Reports",
63
+ parent: "pages",
64
+ });
65
+ // category.data.ref === "pages.reports"
66
+ ```
67
+
68
+ ## Read a Page's Metadata
69
+
70
+ `PageMeta` is what the builder knows about a page. It comes back inside `PageStructure`, which `GetPageStructure` returns.
71
+
72
+ | Field | Type | Description |
73
+ | --- | --- | --- |
74
+ | `ref` | `PageRef` | The page's route string. |
75
+ | `id` | `string` | The page id, the first argument of its `PageController`. |
76
+ | `displayName` | `string` | The label shown in the menu. |
77
+ | `icon` | `string` | Icon name, when the page declares one. |
78
+ | `category` | `CategoryRef` | The category the page belongs to. |
79
+ | `order` | `number` | Sort order within the category. |
80
+ | `description` | `string` | The page's description. |
81
+ | `hidden` | `boolean` | Whether the page is hidden from the menu. |
82
+ | `permission` | `PagePermission` | The permission guarding the page. |
83
+ | `filepath` | `string` | The file the page is declared in, resolved by the builder's source index rather than by the runtime registry. |
84
+
85
+ `hidden` and `permission` read back like any other field, as long as the page declares them literally. A `permission` built from a call or a reference — an `Action`, say — reads back as `undefined`: the builder reports only what it can also rewrite.
86
+
87
+ `PagePermission` mirrors the DMS `MenuOptions.permission`: a partial permission spec, whose fields are `id`, `title`, `icon`, `description`, `dependencies` and `defaultGranted`. A bare action id is *not* accepted — the DMS types the option as `Partial<Permission> | Action`, so a string would be written and then refused by the typecheck gate.
88
+
89
+ ```typescript
90
+ import type { PagePermission } from "@antelopejs/interface-dms-builder";
91
+
92
+ const permission: PagePermission = {
93
+ id: "reports.export",
94
+ title: "Export reports",
95
+ };
96
+ ```
97
+
98
+ The DMS also accepts an `Action` there. That is a live object rather than data, so it has no encoding in the builder: a page declaring one reads its permission back as `undefined`.
99
+
100
+ ## Patch a Page's Metadata
101
+
102
+ `EditablePageMeta` is the subset of `PageMeta` that `ConfigurePage` accepts as a patch: `displayName`, `icon`, `category`, `order`, `description`, `hidden` and `permission`. `ref`, `id` and `filepath` are not patchable — they follow from where the page lives.
103
+
104
+ Patching `category` moves the page, which changes its `ref`: the result carries the new one, and a `route_changed` warning says what the old route was. See [Page Operations](./4.page-operations.md).
105
+
106
+ ```typescript
107
+ import type { EditablePageMeta } from "@antelopejs/interface-dms-builder";
108
+
109
+ const patch: Partial<EditablePageMeta> = {
110
+ displayName: "Monthly revenue",
111
+ order: 10,
112
+ };
113
+ ```
114
+
115
+ ## Read the Block Tree
116
+
117
+ `BlockNode` is one block in a page's tree. The builder resolves each static member and each `.child()` call it can parse; anything it cannot parse still appears as a node, marked not editable.
118
+
119
+ | Field | Type | Description |
120
+ | --- | --- | --- |
121
+ | `path` | `BlockPath` | The block's address. |
122
+ | `name` | `string` | Static key for a top-level block, `.child()` id for a nested one. |
123
+ | `type` | `string \| null` | The factory name, or `null` when it does not resolve. |
124
+ | `editable` | `boolean` | `false` marks an opaque, non-canonical block. |
125
+ | `opaqueReason` | `string` | Why the block is opaque. Set when `editable === false`. |
126
+ | `slot` | `string` | The slot the block sits in, when its parent declares slots. |
127
+ | `config` | `Record<string, unknown>` | The block's options object. Present only when the block is editable. |
128
+ | `controller` | `ResourceRef` | For a controller-leading block such as `TableView`, the resource its leading argument resolves to, when it is a known generated resource. |
129
+ | `children` | `BlockNode[]` | Container blocks only, in `.child()` order. |
130
+
131
+ An opaque block is one the builder can see but not rewrite — a block behind an unresolved reference, or one produced by a computed expression rather than a direct factory call. Reading it is fine; `ConfigureBlock`, `AddBlock` under it and `MoveBlock` onto it fail with `opaque_target`, described in [Results and Errors](./8.results-and-errors.md).
132
+
133
+ ```typescript
134
+ import { GetPageStructure } from "@antelopejs/interface-dms-builder";
135
+
136
+ const result = await GetPageStructure("/dashboard");
137
+ if (result.ok) {
138
+ for (const block of result.data.blocks) {
139
+ if (!block.editable) {
140
+ console.log(`${block.path} is opaque: ${block.opaqueReason}`);
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Read a Page's Structure
147
+
148
+ `PageStructure` is the whole page as the builder sees it.
149
+
150
+ | Field | Type | Description |
151
+ | --- | --- | --- |
152
+ | `page` | `PageMeta` | The page's metadata. |
153
+ | `blocks` | `BlockNode[]` | The top-level blocks, each carrying its own children. |
154
+ | `queries` | `QueryStructure[]` | The query routes declared on the page, found by shape. See [Queries](./6.queries.md). |
155
+ | `version` | `string` | A content hash of the page file. |
156
+
157
+ `version` is what makes a write conditional: pass it back as `expectedVersion` on the next mutation and the operation fails with `stale` if the file changed in between. See [Results and Errors](./8.results-and-errors.md).
158
+
159
+ ## Read the Listings
160
+
161
+ `ListPages` and `ListCategories` return summaries rather than full structures, so you can survey a project without parsing every page.
162
+
163
+ `PageSummary`:
164
+
165
+ | Field | Type | Description |
166
+ | --- | --- | --- |
167
+ | `ref` | `PageRef` | The page's route string. |
168
+ | `id` | `string` | The page id. |
169
+ | `displayName` | `string` | The label shown in the menu. |
170
+ | `category` | `CategoryRef` | The category the page belongs to. |
171
+ | `moduleId` | `string` | The module the page comes from, for pages declared inside a module. |
172
+ | `filepath` | `string` | The file the page is declared in. |
173
+
174
+ `CategorySummary`:
175
+
176
+ | Field | Type | Description |
177
+ | --- | --- | --- |
178
+ | `ref` | `CategoryRef` | The category's id. |
179
+ | `displayName` | `string` | The label shown in the menu. |
180
+ | `parent` | `CategoryRef` | The parent category, absent for a root. |
181
+
182
+ ```typescript
183
+ import {
184
+ ListCategories,
185
+ ListPages,
186
+ } from "@antelopejs/interface-dms-builder";
187
+
188
+ const [pages, categories] = await Promise.all([
189
+ ListPages(),
190
+ ListCategories(),
191
+ ]);
192
+ const rootPages = pages.filter((page) => page.category === "pages");
193
+ ```
194
+
195
+ ## Patch a Category's Metadata
196
+
197
+ `EditableCategoryMeta` is what `ConfigureCategory` accepts: `displayName`, `parent`, `icon` and `order`. Re-parenting a category is refused — every page beneath it would change route at once; see [Page Operations](./4.page-operations.md).
198
+
199
+ ```typescript
200
+ import type { EditableCategoryMeta } from "@antelopejs/interface-dms-builder";
201
+
202
+ const patch: Partial<EditableCategoryMeta> = { displayName: "Reports", order: 2 };
203
+ ```
204
+
205
+ ## Next Steps
206
+
207
+ - [Catalog](./3.catalog.md) - Discover the block types and DataTypes you can emit
@@ -0,0 +1,176 @@
1
+ # Catalog
2
+
3
+ ## Overview
4
+
5
+ The catalog is the builder's answer to "what can I put on a page?". `GetCatalog` returns a `BlockCatalog`: every block factory the builder knows how to emit, every DataType a form field can use, and the field names a resource may not take. Each entry carries the import the emitter will add and a schema for the options the factory accepts, so you can validate a configuration before you write it.
6
+
7
+ Read the catalog before `AddBlock`: the `type` you pass is a catalog key, and `AddBlock` rejects a type that is not in it.
8
+
9
+ ## Read the Catalog
10
+
11
+ ```typescript
12
+ import { GetCatalog } from "@antelopejs/interface-dms-builder";
13
+
14
+ const catalog = await GetCatalog();
15
+ const kpi = catalog.blocks.find((block) => block.type === "KpiCard");
16
+ const containers = catalog.blocks.filter((block) => block.container);
17
+ const dataTypeIds = catalog.dataTypes.map((dataType) => dataType.id);
18
+ ```
19
+
20
+ `GetCatalog` is a read: it returns a `BlockCatalog` directly, not an `OpResult`.
21
+
22
+ | Field | Type | Description |
23
+ | --- | --- | --- |
24
+ | `blocks` | `BlockTypeDescriptor[]` | Every block factory the builder can emit and configure. |
25
+ | `dataTypes` | `DataTypeDescriptor[]` | Every DataType usable as a form field's `type`. |
26
+ | `reservedFieldNames` | `string[]` | Names a resource field may not take. |
27
+ | `generatedAt` | `string` | Cache key, invalidated when the module source changes. |
28
+
29
+ The catalog is cached. `RefreshSourceIndex` with no scope drops that cache; see [Page Operations](./4.page-operations.md).
30
+
31
+ ## Describe a Block Type
32
+
33
+ `BlockTypeDescriptor` is keyed on `type`, the factory's export name.
34
+
35
+ | Field | Type | Description |
36
+ | --- | --- | --- |
37
+ | `type` | `string` | The factory export name, e.g. `KpiCard`. This is the catalog key and the value of `AddBlockInput.type`. |
38
+ | `import` | `ImportRef` | The import the emitter adds for the factory. |
39
+ | `container` | `boolean` | Whether the block accepts `.child(...)`. |
40
+ | `slots` | `SlotDescriptor[]` | The named regions the block exposes, when it has any. |
41
+ | `config` | `ConfigSchema` | A description of the factory's options object. |
42
+ | `shapeSource` | `ShapeSource` | How the emit shape was determined. |
43
+ | `description` | `string` | A human-readable description of the block. |
44
+ | `controllerArg` | `boolean` | `true` when the factory takes a leading controller-class argument before its options object. |
45
+
46
+ Only a `container` block can hold children, so it is the check to make before passing a block's path as `AddBlockInput.parent`.
47
+
48
+ ## Place a Block in a Slot
49
+
50
+ A container block may declare named regions. `SlotDescriptor` describes one:
51
+
52
+ | Field | Type | Description |
53
+ | --- | --- | --- |
54
+ | `id` | `string` | The slot id, passed as `AddBlockInput.slot`. |
55
+ | `description` | `string` | What the slot is for. |
56
+
57
+ ```typescript
58
+ import { GetCatalog } from "@antelopejs/interface-dms-builder";
59
+
60
+ const catalog = await GetCatalog();
61
+ const descriptor = catalog.blocks.find((block) => block.type === "Tab");
62
+ const slotIds = descriptor?.slots?.map((slot) => slot.id) ?? [];
63
+ ```
64
+
65
+ ## Handle a Controller-Leading Block
66
+
67
+ Some factories take a controller class before their options — `TableView(controller, options)` is the common case. Those descriptors carry `controllerArg: true`, and their `config` describes the *second* argument, the options object.
68
+
69
+ A block type with `controllerArg: true` requires `AddBlockInput.controller`, naming the resource whose DataAPI class becomes the leading argument; passing `controller` for any other block type is rejected with `invalid_config`. Both rules are covered in [Page Operations](./4.page-operations.md).
70
+
71
+ ```typescript
72
+ import { GetCatalog } from "@antelopejs/interface-dms-builder";
73
+
74
+ const catalog = await GetCatalog();
75
+ const tableView = catalog.blocks.find((block) => block.type === "TableView");
76
+ if (tableView?.controllerArg) {
77
+ // AddBlock for this type must carry `controller: "<resource ref>"`.
78
+ }
79
+ ```
80
+
81
+ ## Read a Config Schema
82
+
83
+ `ConfigSchema` is a `Record<string, unknown>` holding a JSON-Schema-shaped description of a block's or DataType's options, generated from its TypeScript type. Keys are option names; each value describes that option — `type`, `enum` for a literal union, `properties` for a nested object, `items` for an array, `oneOf` for a union of object shapes.
84
+
85
+ On top of the standard keys, the schema carries DMS extensions in the `x-` namespace. The one you act on is `x-dataType`:
86
+
87
+ | Key | Meaning |
88
+ | --- | --- |
89
+ | `x-dataType: true` | The value is a DataType instance (`new <DataType>(...)`), such as a Form field's `type`. Pass a `DataTypeValue`, described in [Value Encodings](./7.values.md). |
90
+
91
+ An option whose value holds a nested block is marked the same way, with its own `x-` key. That is rare: most nesting goes through `.child`, which you express with `AddBlockInput.parent` rather than through config.
92
+
93
+ ```typescript
94
+ import { GetCatalog } from "@antelopejs/interface-dms-builder";
95
+
96
+ const catalog = await GetCatalog();
97
+ const form = catalog.blocks.find((block) => block.type === "Form");
98
+ const fieldsOption = form?.config.fields; // schema for the `fields` option
99
+ ```
100
+
101
+ ## Resolve an Import
102
+
103
+ `ImportRef` is the import the emitter adds when it writes a factory call, a DataType instantiation or a class reference.
104
+
105
+ | Field | Type | Description |
106
+ | --- | --- | --- |
107
+ | `name` | `string` | The imported symbol, e.g. `KpiCard` or `DefaultDataTypes.StringType`. |
108
+ | `module` | `string` | A package specifier, fixed and independent of the emitting file. |
109
+ | `targetFile` | `string` | An absolute path, resolved to a specifier relative to the file being written — used for a reference to a local class such as a generated resource. |
110
+
111
+ A descriptor carries one or the other: `module` for a symbol that comes from a package, `targetFile` for one that lives in the project's own source.
112
+
113
+ ## Know Where a Shape Came From
114
+
115
+ `ShapeSource` is `"inferred" | "annotated"`, and a `BlockTypeDescriptor` reports which applies to its `config`:
116
+
117
+ - **`inferred`** — the shape was read off the factory's TypeScript signature. It is as good as the types are: a well-typed options interface yields a precise schema, while a wide or generic option yields a loose one.
118
+ - **`annotated`** — the shape was declared for the builder rather than derived from the signature.
119
+
120
+ Use `shapeSource` when deciding how much to trust `config`. An empty or shallow `config` on an `inferred` descriptor means the type could not be described further, not that the factory takes no options.
121
+
122
+ ## Choose a DataType
123
+
124
+ `DataTypeDescriptor` describes a DataType a form field's `type` (or a table column's `type`) can use.
125
+
126
+ | Field | Type | Description |
127
+ | --- | --- | --- |
128
+ | `id` | `string` | The registered id, e.g. `string`, `number`, `select`. |
129
+ | `import` | `ImportRef` | The import the emitter adds for the DataType class. |
130
+ | `config` | `ConfigSchema` | A description of the DataType's options. |
131
+ | `description` | `string` | A human-readable description. |
132
+
133
+ The `id` is what you pass as `$dataType` when writing a value; see [Value Encodings](./7.values.md). A `$dataType` the catalog does not list is rejected with `invalid_config`.
134
+
135
+ ```typescript
136
+ import {
137
+ AddBlock,
138
+ GetCatalog,
139
+ } from "@antelopejs/interface-dms-builder";
140
+
141
+ const catalog = await GetCatalog();
142
+ const hasSelect = catalog.dataTypes.some((dataType) => dataType.id === "select");
143
+
144
+ await AddBlock({
145
+ page: "/customers",
146
+ name: "editor",
147
+ type: "Form",
148
+ config: {
149
+ fields: [
150
+ {
151
+ id: "name",
152
+ label: "Name",
153
+ type: { $dataType: "string" },
154
+ },
155
+ ],
156
+ },
157
+ });
158
+ ```
159
+
160
+ ## Avoid a Reserved Field Name
161
+
162
+ `reservedFieldNames` lists the names a resource field may not take: they collide with a generated DataController route or a Model member, and `CreateResource` or `AddField` rejects them with `invalid_config`. Reading them from the catalog saves you carrying your own copy of the list.
163
+
164
+ ```typescript
165
+ import { GetCatalog } from "@antelopejs/interface-dms-builder";
166
+
167
+ const catalog = await GetCatalog();
168
+ const reserved = new Set(catalog.reservedFieldNames);
169
+ const fieldName = reserved.has("status") ? "state" : "status";
170
+ ```
171
+
172
+ Resource fields are covered in [Resources](./5.resources.md).
173
+
174
+ ## Next Steps
175
+
176
+ - [Page Operations](./4.page-operations.md) - Create pages and categories, and edit a page's block tree