@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.
- package/LICENSE +190 -0
- package/README.md +12 -0
- package/dist/addressing.d.ts +3 -0
- package/dist/addressing.js +3 -0
- package/dist/addressing.js.map +1 -0
- package/dist/catalog.d.ts +87 -0
- package/dist/catalog.js +3 -0
- package/dist/catalog.js.map +1 -0
- package/dist/drafts.d.ts +37 -0
- package/dist/drafts.js +7 -0
- package/dist/drafts.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/operations.d.ts +69 -0
- package/dist/operations.js +20 -0
- package/dist/operations.js.map +1 -0
- package/dist/queries.d.ts +55 -0
- package/dist/queries.js +9 -0
- package/dist/queries.js.map +1 -0
- package/dist/resources.d.ts +97 -0
- package/dist/resources.js +13 -0
- package/dist/resources.js.map +1 -0
- package/dist/results.d.ts +59 -0
- package/dist/results.js +3 -0
- package/dist/results.js.map +1 -0
- package/dist/structure.d.ts +62 -0
- package/dist/structure.js +3 -0
- package/dist/structure.js.map +1 -0
- package/dist/values.d.ts +19 -0
- package/dist/values.js +3 -0
- package/dist/values.js.map +1 -0
- package/docs/1.introduction.md +86 -0
- package/docs/2.addressing-and-structure.md +207 -0
- package/docs/3.catalog.md +176 -0
- package/docs/4.page-operations.md +341 -0
- package/docs/5.resources.md +276 -0
- package/docs/6.queries.md +229 -0
- package/docs/7.values.md +130 -0
- package/docs/8.results-and-errors.md +201 -0
- package/docs/9.drafts-and-preview.md +140 -0
- package/package.json +67 -0
|
@@ -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
|
+
}
|
package/dist/results.js
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"structure.js","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":""}
|
package/dist/values.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|