@antelopejs/interface-dms-builder 0.2.2 → 0.2.3
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/dist/analysis.d.ts +124 -0
- package/dist/analysis.js +8 -0
- package/dist/analysis.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mapping.d.ts +49 -0
- package/dist/mapping.js +6 -0
- package/dist/mapping.js.map +1 -0
- package/dist/operations.d.ts +2 -0
- package/dist/operations.js.map +1 -1
- package/dist/project.d.ts +40 -0
- package/dist/project.js +9 -0
- package/dist/project.js.map +1 -0
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js.map +1 -1
- package/dist/results.d.ts +2 -2
- package/dist/structure.d.ts +1 -0
- package/docs/1.introduction.md +24 -35
- package/docs/10.a-chart-without-code.md +72 -45
- package/docs/11.external-databases.md +150 -0
- package/docs/2.addressing-and-structure.md +3 -2
- package/docs/6.queries.md +28 -6
- package/docs/7.values.md +8 -6
- package/docs/9.drafts-and-preview.md +51 -17
- package/docs/images/editor-selected.png +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ResourceRef } from "./resources";
|
|
2
|
+
import type { OpResult } from "./results";
|
|
3
|
+
export type ProposedKind = "fact" | "default";
|
|
4
|
+
export interface Proposed<T> {
|
|
5
|
+
value: T;
|
|
6
|
+
rule: string;
|
|
7
|
+
kind: ProposedKind;
|
|
8
|
+
}
|
|
9
|
+
export interface ProposedDeclaration extends Proposed<string> {
|
|
10
|
+
lossy?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProposedRelationColumn extends Proposed<string> {
|
|
13
|
+
implied: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface ProposedRange {
|
|
16
|
+
min: number | string;
|
|
17
|
+
max: number | string;
|
|
18
|
+
}
|
|
19
|
+
export interface ProposedCategory {
|
|
20
|
+
value: string | number | boolean;
|
|
21
|
+
rows: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ProposedColumnProfile {
|
|
24
|
+
traits: string[];
|
|
25
|
+
nulls?: number;
|
|
26
|
+
distinct?: number;
|
|
27
|
+
range?: ProposedRange;
|
|
28
|
+
categories?: ProposedCategory[];
|
|
29
|
+
}
|
|
30
|
+
export interface ProposedField {
|
|
31
|
+
column: string;
|
|
32
|
+
native: string;
|
|
33
|
+
accepts: string[];
|
|
34
|
+
declared: ProposedDeclaration;
|
|
35
|
+
label: Proposed<string>;
|
|
36
|
+
listable: Proposed<boolean>;
|
|
37
|
+
filterable: Proposed<boolean>;
|
|
38
|
+
sortable: Proposed<boolean>;
|
|
39
|
+
searchable: Proposed<boolean>;
|
|
40
|
+
relation?: ProposedRelationColumn;
|
|
41
|
+
profile?: ProposedColumnProfile;
|
|
42
|
+
}
|
|
43
|
+
export interface ProposedLeftOut {
|
|
44
|
+
column: string;
|
|
45
|
+
reason: string;
|
|
46
|
+
rule: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ProposedWrites {
|
|
49
|
+
insert: boolean;
|
|
50
|
+
update: boolean;
|
|
51
|
+
delete: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface ProposedResource {
|
|
54
|
+
relation: string;
|
|
55
|
+
tableView: Proposed<boolean>;
|
|
56
|
+
writable: Proposed<boolean>;
|
|
57
|
+
label: Proposed<string | null>;
|
|
58
|
+
title: Proposed<string>;
|
|
59
|
+
fields: ProposedField[];
|
|
60
|
+
leftOut: ProposedLeftOut[];
|
|
61
|
+
writes: ProposedWrites;
|
|
62
|
+
}
|
|
63
|
+
export interface ProposedExclusion {
|
|
64
|
+
relation: string;
|
|
65
|
+
reason: string;
|
|
66
|
+
rule: string;
|
|
67
|
+
}
|
|
68
|
+
export type OpenItemKind = "schemas" | "purpose" | "label" | "reference";
|
|
69
|
+
export interface OpenItemOption {
|
|
70
|
+
value: unknown;
|
|
71
|
+
title: string;
|
|
72
|
+
}
|
|
73
|
+
export interface OpenItem {
|
|
74
|
+
id: string;
|
|
75
|
+
kind: OpenItemKind;
|
|
76
|
+
relation?: string;
|
|
77
|
+
column?: string;
|
|
78
|
+
question: string;
|
|
79
|
+
options: OpenItemOption[];
|
|
80
|
+
proposed: unknown;
|
|
81
|
+
resource?: ProposedResource;
|
|
82
|
+
}
|
|
83
|
+
export interface ProposalDatabase {
|
|
84
|
+
engine: string;
|
|
85
|
+
role: string;
|
|
86
|
+
writes: "none" | "some" | "all";
|
|
87
|
+
}
|
|
88
|
+
export interface MappingProposal {
|
|
89
|
+
connection: string;
|
|
90
|
+
module: string;
|
|
91
|
+
database: ProposalDatabase;
|
|
92
|
+
resources: ProposedResource[];
|
|
93
|
+
excluded: ProposedExclusion[];
|
|
94
|
+
open: OpenItem[];
|
|
95
|
+
}
|
|
96
|
+
export interface AnalyseDatabaseInput {
|
|
97
|
+
connection: string;
|
|
98
|
+
module: string;
|
|
99
|
+
schemas?: string[];
|
|
100
|
+
}
|
|
101
|
+
export interface AnalysisProgress {
|
|
102
|
+
relations: number;
|
|
103
|
+
described: number;
|
|
104
|
+
toProfile: number;
|
|
105
|
+
profiled: number;
|
|
106
|
+
timeouts: number;
|
|
107
|
+
}
|
|
108
|
+
export type AnalysisStatus = "running" | "done" | "failed";
|
|
109
|
+
export interface AnalysisState {
|
|
110
|
+
id: string;
|
|
111
|
+
connection: string;
|
|
112
|
+
status: AnalysisStatus;
|
|
113
|
+
progress: AnalysisProgress;
|
|
114
|
+
proposal?: MappingProposal;
|
|
115
|
+
error?: string;
|
|
116
|
+
}
|
|
117
|
+
export declare const AnalyseDatabase: (input: AnalyseDatabaseInput) => Promise<OpResult<AnalysisState>>;
|
|
118
|
+
export declare const GetAnalysis: (id: string) => Promise<OpResult<AnalysisState>>;
|
|
119
|
+
export interface MappedRelation {
|
|
120
|
+
ref: ResourceRef;
|
|
121
|
+
resource: ProposedResource;
|
|
122
|
+
included: string[];
|
|
123
|
+
}
|
|
124
|
+
export declare const GetMapping: (connection: string, module: string) => Promise<OpResult<MappedRelation[]>>;
|
package/dist/analysis.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetMapping = exports.GetAnalysis = exports.AnalyseDatabase = void 0;
|
|
4
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
5
|
+
exports.AnalyseDatabase = (0, interface_core_1.InterfaceFunction)();
|
|
6
|
+
exports.GetAnalysis = (0, interface_core_1.InterfaceFunction)();
|
|
7
|
+
exports.GetMapping = (0, interface_core_1.InterfaceFunction)();
|
|
8
|
+
//# sourceMappingURL=analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AAoLlD,QAAA,eAAe,GAC1B,IAAA,kCAAiB,GAEd,CAAC;AAMO,QAAA,WAAW,GACtB,IAAA,kCAAiB,GAAoD,CAAC;AAc3D,QAAA,UAAU,GACrB,IAAA,kCAAiB,GAEd,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export * from "./addressing";
|
|
2
|
+
export * from "./analysis";
|
|
2
3
|
export * from "./catalog";
|
|
3
4
|
export * from "./drafts";
|
|
5
|
+
export * from "./mapping";
|
|
4
6
|
export * from "./operations";
|
|
7
|
+
export * from "./project";
|
|
5
8
|
export * from "./queries";
|
|
6
9
|
export * from "./resources";
|
|
7
10
|
export * from "./results";
|
package/dist/index.js
CHANGED
|
@@ -15,9 +15,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./addressing"), exports);
|
|
18
|
+
__exportStar(require("./analysis"), exports);
|
|
18
19
|
__exportStar(require("./catalog"), exports);
|
|
19
20
|
__exportStar(require("./drafts"), exports);
|
|
21
|
+
__exportStar(require("./mapping"), exports);
|
|
20
22
|
__exportStar(require("./operations"), exports);
|
|
23
|
+
__exportStar(require("./project"), exports);
|
|
21
24
|
__exportStar(require("./queries"), exports);
|
|
22
25
|
__exportStar(require("./resources"), exports);
|
|
23
26
|
__exportStar(require("./results"), exports);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,4CAA0B;AAC1B,2CAAyB;AACzB,+CAA6B;AAC7B,4CAA0B;AAC1B,8CAA4B;AAC5B,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,6CAA2B;AAC3B,4CAA0B;AAC1B,2CAAyB;AACzB,4CAA0B;AAC1B,+CAA6B;AAC7B,4CAA0B;AAC1B,4CAA0B;AAC1B,8CAA4B;AAC5B,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CategoryRef, PageRef } from "./addressing";
|
|
2
|
+
import type { ResourceRef } from "./resources";
|
|
3
|
+
import type { OpResult } from "./results";
|
|
4
|
+
export interface MappedField {
|
|
5
|
+
column: string;
|
|
6
|
+
declared?: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
listable?: boolean;
|
|
9
|
+
filterable?: boolean;
|
|
10
|
+
sortable?: boolean;
|
|
11
|
+
searchable?: boolean;
|
|
12
|
+
relation?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MappedResource {
|
|
15
|
+
relation: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
tableView: boolean;
|
|
20
|
+
writable: boolean;
|
|
21
|
+
fields: MappedField[];
|
|
22
|
+
}
|
|
23
|
+
export interface ExcludedRelation {
|
|
24
|
+
relation: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
}
|
|
27
|
+
export interface MapResourcesInput {
|
|
28
|
+
module: string;
|
|
29
|
+
connection: string;
|
|
30
|
+
resources: MappedResource[];
|
|
31
|
+
excluded?: ExcludedRelation[];
|
|
32
|
+
}
|
|
33
|
+
export interface LeftOutColumn {
|
|
34
|
+
relation: string;
|
|
35
|
+
column: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
export interface MappedResult {
|
|
39
|
+
relation: string;
|
|
40
|
+
ref: ResourceRef;
|
|
41
|
+
page?: PageRef;
|
|
42
|
+
files: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface MapResourcesResult {
|
|
45
|
+
resources: MappedResult[];
|
|
46
|
+
leftOut: LeftOutColumn[];
|
|
47
|
+
category?: CategoryRef;
|
|
48
|
+
}
|
|
49
|
+
export declare const MapResources: (input: MapResourcesInput) => Promise<OpResult<MapResourcesResult>>;
|
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MapResources = void 0;
|
|
4
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
5
|
+
exports.MapResources = (0, interface_core_1.InterfaceFunction)();
|
|
6
|
+
//# sourceMappingURL=mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping.js","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AAmGlD,QAAA,YAAY,GACvB,IAAA,kCAAiB,GAEd,CAAC"}
|
package/dist/operations.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface CreatePageInput {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
permission?: PagePermission;
|
|
17
17
|
dir?: string;
|
|
18
|
+
module?: string;
|
|
18
19
|
}
|
|
19
20
|
export interface CreateCategoryInput {
|
|
20
21
|
name: string;
|
|
@@ -22,6 +23,7 @@ export interface CreateCategoryInput {
|
|
|
22
23
|
parent?: CategoryRef;
|
|
23
24
|
icon?: string;
|
|
24
25
|
order?: number;
|
|
26
|
+
module?: string;
|
|
25
27
|
}
|
|
26
28
|
export interface AddBlockInput {
|
|
27
29
|
page: PageRef;
|
package/dist/operations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AAoGlD,QAAA,SAAS,GAAG,IAAA,kCAAiB,GAAgC,CAAC;AAC9D,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAAoC,CAAC;AAC3C,QAAA,UAAU,GAAG,IAAA,kCAAiB,GAA+B,CAAC;AAS9D,QAAA,eAAe,GAC1B,IAAA,kCAAiB,GAEd,CAAC;AACO,QAAA,gBAAgB,GAC3B,IAAA,kCAAiB,GAAsD,CAAC;AAM7D,QAAA,kBAAkB,GAC7B,IAAA,kCAAiB,GAA2C,CAAC;AAElD,QAAA,UAAU,GACrB,IAAA,kCAAiB,GAId,CAAC;AAOO,QAAA,aAAa,GACxB,IAAA,kCAAiB,GAMd,CAAC;AACO,QAAA,UAAU,GACrB,IAAA,kCAAiB,GAA4D,CAAC;AAEnE,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAEd,CAAC;AACO,QAAA,iBAAiB,GAC5B,IAAA,kCAAiB,GAKd,CAAC;AACO,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAA2C,CAAC;AAElD,QAAA,QAAQ,GACnB,IAAA,kCAAiB,GAKd,CAAC;AACO,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAMd,CAAC;AACO,QAAA,SAAS,GACpB,IAAA,kCAAiB,GAEd,CAAC;AACO,QAAA,WAAW,GACtB,IAAA,kCAAiB,GAEd,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { OpResult } from "./results";
|
|
2
|
+
export interface ImportOverrideInput {
|
|
3
|
+
interface: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ModuleEntryInput {
|
|
8
|
+
source?: Record<string, unknown>;
|
|
9
|
+
config?: Record<string, unknown>;
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
importOverrides?: ImportOverrideInput[];
|
|
12
|
+
exportPriority?: Record<string, number>;
|
|
13
|
+
}
|
|
14
|
+
export interface AddModuleStep {
|
|
15
|
+
op: "AddModule";
|
|
16
|
+
id: string;
|
|
17
|
+
entry: ModuleEntryInput;
|
|
18
|
+
}
|
|
19
|
+
export interface ConfigureModuleStep {
|
|
20
|
+
op: "ConfigureModule";
|
|
21
|
+
id: string;
|
|
22
|
+
entry: ModuleEntryInput;
|
|
23
|
+
}
|
|
24
|
+
export interface CreateLocalModuleStep {
|
|
25
|
+
op: "CreateLocalModule";
|
|
26
|
+
id: string;
|
|
27
|
+
path: string;
|
|
28
|
+
entry?: ModuleEntryInput;
|
|
29
|
+
}
|
|
30
|
+
export type ProjectConfigStep = AddModuleStep | ConfigureModuleStep | CreateLocalModuleStep;
|
|
31
|
+
export interface ProjectConfigOpts {
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ProjectConfigResult {
|
|
35
|
+
files: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare const ConfigureProject: (steps: ProjectConfigStep[], opts?: ProjectConfigOpts | undefined) => Promise<OpResult<ProjectConfigResult>>;
|
|
38
|
+
export declare const AddModule: (id: string, entry: ModuleEntryInput, opts?: ProjectConfigOpts | undefined) => Promise<OpResult<ProjectConfigResult>>;
|
|
39
|
+
export declare const ConfigureModule: (id: string, entry: ModuleEntryInput, opts?: ProjectConfigOpts | undefined) => Promise<OpResult<ProjectConfigResult>>;
|
|
40
|
+
export declare const CreateLocalModule: (id: string, path: string, entry?: ModuleEntryInput | undefined, opts?: ProjectConfigOpts | undefined) => Promise<OpResult<ProjectConfigResult>>;
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CreateLocalModule = exports.ConfigureModule = exports.AddModule = exports.ConfigureProject = void 0;
|
|
4
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
5
|
+
exports.ConfigureProject = (0, interface_core_1.InterfaceFunction)();
|
|
6
|
+
exports.AddModule = (0, interface_core_1.InterfaceFunction)();
|
|
7
|
+
exports.ConfigureModule = (0, interface_core_1.InterfaceFunction)();
|
|
8
|
+
exports.CreateLocalModule = (0, interface_core_1.InterfaceFunction)();
|
|
9
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AA8ElD,QAAA,gBAAgB,GAC3B,IAAA,kCAAiB,GAKd,CAAC;AAGO,QAAA,SAAS,GACpB,IAAA,kCAAiB,GAMd,CAAC;AAGO,QAAA,eAAe,GAC1B,IAAA,kCAAiB,GAMd,CAAC;AAGO,QAAA,iBAAiB,GAC5B,IAAA,kCAAiB,GAOd,CAAC"}
|
package/dist/resources.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export interface CreateResourceInput {
|
|
|
48
48
|
seeds?: Record<string, unknown>[];
|
|
49
49
|
location?: ResourceLocation;
|
|
50
50
|
routes?: ResourceRoute[];
|
|
51
|
+
module?: string;
|
|
51
52
|
}
|
|
52
53
|
export interface ResourceFieldStructure extends Partial<FieldAspects> {
|
|
53
54
|
name: string;
|
|
@@ -75,6 +76,7 @@ export interface ResourceSummary {
|
|
|
75
76
|
database: string;
|
|
76
77
|
dataApi: string;
|
|
77
78
|
};
|
|
79
|
+
sourceModule?: string;
|
|
78
80
|
}
|
|
79
81
|
export interface DeleteResourceOpts extends MutationOpts {
|
|
80
82
|
keepData?: boolean;
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":";;;AAAA,+DAA+D;AAwNlD,QAAA,cAAc,GACzB,IAAA,kCAAiB,GAId,CAAC;AAcO,QAAA,iBAAiB,GAC5B,IAAA,kCAAiB,GAMd,CAAC;AAOO,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"}
|
package/dist/results.d.ts
CHANGED
|
@@ -14,10 +14,10 @@ export interface TypecheckError {
|
|
|
14
14
|
message: string;
|
|
15
15
|
}
|
|
16
16
|
export interface OpWarning {
|
|
17
|
-
code: "query_opaque" | "query_kept" | "index_clamped" | "datatype_fallback" | "data_not_dropped" | "route_changed";
|
|
17
|
+
code: "query_opaque" | "query_kept" | "index_clamped" | "datatype_fallback" | "data_not_dropped" | "route_changed" | "data_kept" | "page_not_written" | "relation_missing";
|
|
18
18
|
message: string;
|
|
19
19
|
}
|
|
20
|
-
export type DuplicateScope = "page" | "siblings" | "category" | "resource" | "query";
|
|
20
|
+
export type DuplicateScope = "page" | "siblings" | "category" | "resource" | "query" | "module";
|
|
21
21
|
export type BuilderError = {
|
|
22
22
|
code: "not_found";
|
|
23
23
|
ref: string;
|
package/dist/structure.d.ts
CHANGED
package/docs/1.introduction.md
CHANGED
|
@@ -1,44 +1,14 @@
|
|
|
1
1
|
# DMS Builder Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `@antelopejs/interface-dms-builder` to create and edit DMS pages from a backend module or a tool. The operations write ordinary TypeScript into your project; there is no separate database of page definitions.
|
|
4
4
|
|
|
5
|
-
|
|
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).
|
|
5
|
+
If you want to edit a page with the mouse instead, start with the [visual builder guide](https://dms.antelopejs.com/docs/modules/builder/introduction).
|
|
38
6
|
|
|
39
7
|
## Quick Start
|
|
40
8
|
|
|
41
|
-
|
|
9
|
+
This example creates a **Revenue** page with a **Total revenue** card. Run it once from a backend module after the builder is available, in a development project that loads both `@antelopejs/dms` and `@antelopejs/dms-builder`. Installing the interface package alone does not provide the implementation.
|
|
10
|
+
|
|
11
|
+
The project must already have a `pages` category. Use `ListCategories()` to find yours, or [create a category](./4.page-operations.md) first. Choose an unused page name: running a creation twice returns `duplicate_name`.
|
|
42
12
|
|
|
43
13
|
```typescript
|
|
44
14
|
import {
|
|
@@ -75,6 +45,25 @@ for (const change of block.changes) {
|
|
|
75
45
|
}
|
|
76
46
|
```
|
|
77
47
|
|
|
48
|
+
After the backend reloads, open `/revenue` as a tenant owner. You should see the page and its card; the card has no revenue data yet. [Queries](./6.queries.md) connects a card to a resource when you are ready for real numbers.
|
|
49
|
+
|
|
50
|
+
Each call saves independently. If `CreatePage` succeeds but `AddBlock` fails, the empty page remains; fix the block configuration and retry that call, not the page creation.
|
|
51
|
+
|
|
52
|
+
## How an edit reaches your files
|
|
53
|
+
|
|
54
|
+
A *block* is one DMS component, such as a card or a table. Adding one writes a factory call into a `PageController` class, either as a static field or as a child of another block. The builder parses the source, stages the edit in memory and typechecks the project before writing it. A type error anywhere in the project can therefore block the edit.
|
|
55
|
+
|
|
56
|
+
Operations are `InterfaceFunction` exports from `@antelopejs/interface-core`. Expected failures return values you can inspect:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Success: { ok: true, data, changes, warnings? }
|
|
60
|
+
// Failure: { ok: false, error }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Listing operations such as `ListPages` return their value directly. See [Results and errors](./8.results-and-errors.md) for the full contract. When you read before writing, pass the structure's `version` as `expectedVersion` so another editor's work is not overwritten.
|
|
64
|
+
|
|
65
|
+
Hand-written declarations remain editable when the builder recognizes their shape. Otherwise, it marks them *opaque*: you can inspect them without asking the builder to rewrite code it does not understand. Use [Drafts and preview](./9.drafts-and-preview.md) when an editor needs to save a whole block tree in one transaction.
|
|
66
|
+
|
|
78
67
|
## Documentation Sections
|
|
79
68
|
|
|
80
69
|
- [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
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
# A Chart Without Code
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Build an editor that lets someone choose a table, a measure and a grouping, then
|
|
4
|
+
save a chart without writing its data route by hand. This walkthrough follows the
|
|
5
|
+
interface calls behind the visual builder; it is not a standalone script.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
experience knows which call belongs where.
|
|
7
|
+

|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
You need an existing `/shop/board` page and an `order` resource with numeric
|
|
10
|
+
`amount`, date `createdAt` and `status` fields. Run these calls inside a project
|
|
11
|
+
that loads the builder. Use a disposable page for the save example: its draft
|
|
12
|
+
replaces the page's existing blocks.
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
fetchUrl: ui(z.string().optional(), {
|
|
16
|
-
label: "Data source",
|
|
17
|
-
group: "data",
|
|
18
|
-
widget: "dataSource",
|
|
19
|
-
responseShape: "card",
|
|
20
|
-
periodOption: "periodScope",
|
|
21
|
-
});
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
An editor reads that from the catalog and offers only calculations whose answer
|
|
25
|
-
the block can display — a series cannot be wired into a slot that renders one
|
|
26
|
-
number — and knows which of the block's own options carries the period, so
|
|
27
|
-
binding a source writes both in one patch.
|
|
14
|
+
The result is a revenue chart grouped by month. Start with all paid orders;
|
|
15
|
+
add period controls and comparison only after this first chart works.
|
|
28
16
|
|
|
29
17
|
## Show the data before writing anything
|
|
30
18
|
|
|
@@ -50,13 +38,19 @@ const preview = await PreviewQuery({
|
|
|
50
38
|
response: "card",
|
|
51
39
|
},
|
|
52
40
|
});
|
|
41
|
+
if (preview.ok) {
|
|
42
|
+
console.log(preview.data.body); // Chart-card data from the current rows
|
|
43
|
+
console.log(preview.data.truncated);
|
|
44
|
+
} else {
|
|
45
|
+
console.error(preview.error);
|
|
46
|
+
}
|
|
53
47
|
```
|
|
54
48
|
|
|
55
|
-
Nothing has been written
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
Nothing has been written yet. On success, `preview.data.body` has the response
|
|
50
|
+
shape the chart card reads, including its headline figure. The saved route uses
|
|
51
|
+
the same calculation, but its values can change as rows change. Check `truncated`
|
|
52
|
+
before treating a limited preview as a complete result. For a per-tenant resource,
|
|
53
|
+
also pass the tenant id in `PreviewQuery`'s `tenant` option.
|
|
60
54
|
|
|
61
55
|
## Save the block and its data together
|
|
62
56
|
|
|
@@ -64,9 +58,12 @@ The query travels in the page draft, so one save writes the block, the model
|
|
|
64
58
|
method and the route in a single transaction:
|
|
65
59
|
|
|
66
60
|
```typescript
|
|
67
|
-
import { SetPageBlocks } from "@antelopejs/interface-dms-builder";
|
|
61
|
+
import { GetPageStructure, SetPageBlocks } from "@antelopejs/interface-dms-builder";
|
|
68
62
|
|
|
69
|
-
await
|
|
63
|
+
const current = await GetPageStructure("/shop/board");
|
|
64
|
+
if (!current.ok) throw new Error(current.error.code);
|
|
65
|
+
|
|
66
|
+
const saved = await SetPageBlocks("/shop/board", {
|
|
70
67
|
blocks: [
|
|
71
68
|
{
|
|
72
69
|
name: "revenueCard",
|
|
@@ -74,7 +71,6 @@ await SetPageBlocks("/shop/board", {
|
|
|
74
71
|
config: {
|
|
75
72
|
title: "Revenue",
|
|
76
73
|
fetchUrl: "/dms/pages/shop/board/stats/revenue-chart",
|
|
77
|
-
periodScope: "page",
|
|
78
74
|
chart: {
|
|
79
75
|
$block: { type: "ChartArea", config: { xaxisType: "category" } },
|
|
80
76
|
},
|
|
@@ -92,21 +88,18 @@ await SetPageBlocks("/shop/board", {
|
|
|
92
88
|
groupBy: "createdAt",
|
|
93
89
|
bucket: "month",
|
|
94
90
|
timezone: "Europe/Brussels",
|
|
95
|
-
where: [
|
|
96
|
-
{ field: "status", op: "eq", value: "paid" },
|
|
97
|
-
{ field: "createdAt", op: "ge", value: { $param: { name: "from" } } },
|
|
98
|
-
{ field: "createdAt", op: "le", value: { $param: { name: "to" } } },
|
|
99
|
-
],
|
|
91
|
+
where: [{ field: "status", op: "eq", value: "paid" }],
|
|
100
92
|
},
|
|
101
93
|
response: "card",
|
|
102
|
-
compare: true,
|
|
103
94
|
},
|
|
104
95
|
],
|
|
105
|
-
});
|
|
96
|
+
}, { expectedVersion: current.data.version });
|
|
97
|
+
if (!saved.ok) throw new Error(saved.error.code);
|
|
106
98
|
```
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
After the backend reloads, open `/shop/board` as a tenant owner. The card reads
|
|
101
|
+
`/dms/pages/shop/board/stats/revenue-chart` and shows the saved calculation.
|
|
102
|
+
Either the blocks and query save together or neither changes.
|
|
110
103
|
|
|
111
104
|
Two details the engine will hold you to. The block and the query cannot share a
|
|
112
105
|
name: both become members of the page class, so a query named after the block it
|
|
@@ -115,7 +108,18 @@ what it is and the query for what it answers. And a `ChartCard` requires the
|
|
|
115
108
|
chart it wraps, so a config without `chart` fails the save on `typecheck_failed`
|
|
116
109
|
rather than on a validation message.
|
|
117
110
|
|
|
118
|
-
##
|
|
111
|
+
## Reference: add a period and comparison
|
|
112
|
+
|
|
113
|
+
The first example intentionally has no period filter. To make the chart follow a
|
|
114
|
+
page period, add `periodScope: "page"` to the card, bind `from` and `to` in the
|
|
115
|
+
query's date filters, and supply the page's period selector. Set `compare: true`
|
|
116
|
+
only when requests also supply `compareFrom` and `compareTo`. Pass the same filters
|
|
117
|
+
and arguments to `PreviewQuery` to preview that period, rather than all paid orders.
|
|
118
|
+
See [request-time bindings](./6.queries.md#bind-a-value-at-request-time).
|
|
119
|
+
|
|
120
|
+
With those period filters added, the generated code has the following shape.
|
|
121
|
+
These fragments illustrate the model method and route; they are not extra files
|
|
122
|
+
you need to write for the walkthrough.
|
|
119
123
|
|
|
120
124
|
A method on the resource's model:
|
|
121
125
|
|
|
@@ -171,11 +175,15 @@ the measure's own word, and a reader who never asked for one would be shown "sum
|
|
|
171
175
|
produced them, so the editor reopens filled in rather than empty:
|
|
172
176
|
|
|
173
177
|
```typescript
|
|
178
|
+
import { GetPageStructure } from "@antelopejs/interface-dms-builder";
|
|
179
|
+
|
|
174
180
|
const structure = await GetPageStructure("/shop/board");
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
if (structure.ok) {
|
|
182
|
+
const source = structure.data.queries.find((q) => q.name === "revenueChart");
|
|
183
|
+
console.log(source?.template); // "series"
|
|
184
|
+
console.log(source?.response); // "card"
|
|
185
|
+
console.log(source?.params); // Saved parameters, including explicit ordering
|
|
186
|
+
}
|
|
179
187
|
```
|
|
180
188
|
|
|
181
189
|
That read-back is what makes the journey repeatable rather than one-way. It is
|
|
@@ -187,9 +195,28 @@ rewrite it.
|
|
|
187
195
|
|
|
188
196
|
| Guarantee | Where it comes from |
|
|
189
197
|
| --- | --- |
|
|
190
|
-
|
|
|
198
|
+
| Preview and saved queries use the same calculation | One `QueryPlan`, rendered by the emitter and run by the executor; rows and preview limits can differ |
|
|
191
199
|
| The block and its data save together | `PageDraft.queries`, written in the page's transaction |
|
|
192
200
|
| Reopening shows what was configured | The chain reads back into its parameters |
|
|
193
201
|
| A developer's edit survives | An unreadable chain is opaque, and opaque is never rewritten |
|
|
194
202
|
| The data is not public | The route carries the page's permission |
|
|
195
203
|
| A tenant reads its own rows | The model provider is chosen from the resource's schema |
|
|
204
|
+
|
|
205
|
+
## Reference: what the block asks for
|
|
206
|
+
|
|
207
|
+
A block declares the response shape it can read beside its factory. The editor
|
|
208
|
+
uses this catalog metadata to offer compatible calculations and find the option
|
|
209
|
+
that controls the period:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
fetchUrl: ui(z.string().optional(), {
|
|
213
|
+
label: "Data source",
|
|
214
|
+
group: "data",
|
|
215
|
+
widget: "dataSource",
|
|
216
|
+
responseShape: "card",
|
|
217
|
+
periodOption: "periodScope",
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This is a schema fragment for a block author, not configuration to pass to
|
|
222
|
+
`AddBlock`. An editor consumer reads it from [the catalog](./3.catalog.md).
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# External Databases
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A DMS project can show pages over a database it does not own: a client's PostgreSQL, read and written live through a postgres module in external mode. The builder takes such a database in four moves, each an operation:
|
|
6
|
+
|
|
7
|
+
1. **Wire it** into `antelope.config.ts` with `ConfigureProject`: the provider module, a local module for its tables, and the builder's catalog connection.
|
|
8
|
+
2. **Analyse it** with `AnalyseDatabase`, which reads the catalog and proposes a mapping by fixed rules. It writes nothing.
|
|
9
|
+
3. **Decide** the proposal: a person in the builder's *Database* workspace, or an assistant through these operations.
|
|
10
|
+
4. **Map it** with `MapResources`, which writes table classes, controllers and TableView pages into the module bound to the database.
|
|
11
|
+
|
|
12
|
+
The builder reaches the database through `@antelopejs/interface-database-catalog`, a connection declared on its own module entry. It never connects with credentials of its own.
|
|
13
|
+
|
|
14
|
+
## Wire the Project
|
|
15
|
+
|
|
16
|
+
`ConfigureProject(steps, opts?)` applies its steps to `antelope.config.ts` as one edit. Every step is validated before anything is written, and a refused step writes nothing. With `{ dryRun: true }` it answers the changes it would make, as `OpResult.changes`, so a caller can show the diff before writing.
|
|
17
|
+
|
|
18
|
+
| Step | Writes |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `AddModule` | a module entry, refusing an id that exists |
|
|
21
|
+
| `ConfigureModule` | merges config, `importOverrides` (pins and connection ids), `exportPriority` and env variable names into an entry |
|
|
22
|
+
| `CreateLocalModule` | scaffolds a local module and adds its entry |
|
|
23
|
+
|
|
24
|
+
`env` takes variable names, never values: each is added to `envOverrides`, and to `.env.example` when the project has one.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { ConfigureProject } from "@antelopejs/interface-dms-builder";
|
|
28
|
+
|
|
29
|
+
const catalog = "@antelopejs/interface-database-catalog";
|
|
30
|
+
|
|
31
|
+
const result = await ConfigureProject([
|
|
32
|
+
{
|
|
33
|
+
op: "AddModule",
|
|
34
|
+
id: "postgres-erp",
|
|
35
|
+
entry: {
|
|
36
|
+
source: { type: "package", package: "@antelopejs/postgres", version: "^0.2.0" },
|
|
37
|
+
config: { mode: "external", statementTimeoutMs: 30000 },
|
|
38
|
+
env: { url: "ERP_DATABASE_URL" },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
op: "CreateLocalModule",
|
|
43
|
+
id: "erp-tables",
|
|
44
|
+
path: "modules/erp-tables",
|
|
45
|
+
entry: {
|
|
46
|
+
importOverrides: [{ interface: "@antelopejs/interface-database", source: "postgres-erp" }],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
op: "ConfigureModule",
|
|
51
|
+
id: "mongodb",
|
|
52
|
+
entry: { exportPriority: { "@antelopejs/interface-database": 1 } },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
op: "ConfigureModule",
|
|
56
|
+
id: "dms-builder",
|
|
57
|
+
entry: { importOverrides: [{ interface: catalog, source: "postgres-erp", id: "erp" }] },
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The connection exists once the project restarts with the variable set.
|
|
63
|
+
|
|
64
|
+
## Analyse a Database
|
|
65
|
+
|
|
66
|
+
`AnalyseDatabase({ connection, module, schemas? })` starts a job and answers it at once: profiling a database takes a while. `GetAnalysis(id)` answers the job's `progress` while it runs, then its `proposal`, or its `error`.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { AnalyseDatabase, GetAnalysis } from "@antelopejs/interface-dms-builder";
|
|
70
|
+
|
|
71
|
+
const started = await AnalyseDatabase({ connection: "erp", module: "erp-tables" });
|
|
72
|
+
if (started.ok) {
|
|
73
|
+
const state = await GetAnalysis(started.data.id);
|
|
74
|
+
// state.data.status: "running" | "done" | "failed"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- One job runs per connection. Starting another while one runs answers the running one.
|
|
79
|
+
- A finished job is kept until the next one on its connection, or a restart.
|
|
80
|
+
- Relations the module has mapped, or recorded as excluded, are not proposed again.
|
|
81
|
+
- Above 200 relations to profile, the proposal holds a single `schemas` question instead, and the analysis runs again with `schemas`.
|
|
82
|
+
|
|
83
|
+
## Read a Proposal
|
|
84
|
+
|
|
85
|
+
A `MappingProposal` is a `MapResources` input in which every choice says where it came from:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface Proposed<T> {
|
|
89
|
+
value: T;
|
|
90
|
+
rule: string; // "R6", "L1", "F5"…
|
|
91
|
+
kind: "fact" | "default";
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A **fact** follows from the catalog with no judgement. A **default** is a judgement to check.
|
|
96
|
+
|
|
97
|
+
| Rule | When | Then |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| R1 | the catalog says unmappable | excluded, with its reason |
|
|
100
|
+
| R2 | no key | stats only: table class and model, no page |
|
|
101
|
+
| R3 | no rows | excluded |
|
|
102
|
+
| R4 | a framework's or a migration tool's table | excluded |
|
|
103
|
+
| R5 | keyed by its two foreign keys | stats only |
|
|
104
|
+
| R6 | keyed, with rows and a label | TableView |
|
|
105
|
+
| R7 | keyed, no label, referenced | TableView labelled by its key, and a `label` question |
|
|
106
|
+
| R8 | keyed, no label, not referenced, with a time axis | stats only |
|
|
107
|
+
| R9 | anything else | a `purpose` question, nothing proposed |
|
|
108
|
+
| L1, L2 | the label by name, then by order; titles from short comments, else from names | |
|
|
109
|
+
| F1–F5 | columns no type reads and secrets left out; the first declaration; declared foreign keys and references found whole as relation columns | |
|
|
110
|
+
| W1 | every TableView | read-only |
|
|
111
|
+
|
|
112
|
+
Each field also carries the declarations its column `accepts`, and its profile's aggregates: traits, nulls, distinct values, range and categories. Never a row.
|
|
113
|
+
|
|
114
|
+
What the rules could not decide is in `open`, each item with its options and what the proposal holds meanwhile. A `purpose` item carries the `resource` it would map, should the answer be to map it.
|
|
115
|
+
|
|
116
|
+
## Map Relations
|
|
117
|
+
|
|
118
|
+
`MapResources(input)` reads the descriptions of the relations through the builder's connection, checks every choice against them, and writes in one typechecked transaction. Then it creates the connection's category `pages.<connection>` and the TableView pages.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { MapResources } from "@antelopejs/interface-dms-builder";
|
|
122
|
+
|
|
123
|
+
const result = await MapResources({
|
|
124
|
+
module: "erp-tables",
|
|
125
|
+
connection: "erp",
|
|
126
|
+
resources: [
|
|
127
|
+
{
|
|
128
|
+
relation: "erp.customers",
|
|
129
|
+
label: "name",
|
|
130
|
+
title: "Customers",
|
|
131
|
+
tableView: true,
|
|
132
|
+
writable: false,
|
|
133
|
+
fields: [{ column: "name", searchable: true }, { column: "segment", filterable: true }],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
excluded: [{ relation: "erp.audit_log", reason: "a log" }],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **Names are the database's own.** `_id` is declared only where the relation has a key, and nothing creates or alters a table.
|
|
141
|
+
- **Relation columns** point at a relation of the batch, or one the module mapped before, by its single-column key.
|
|
142
|
+
- **Mapping again** a relation already mapped rewrites it from the new choices. Its page is created, kept or removed as `tableView` says.
|
|
143
|
+
- **`excluded`** is recorded in the module's `package.json`, so the next analysis does not propose those relations. Mapping one later removes it from the record.
|
|
144
|
+
- **In a mapped module**, `CreateResource` and `AddField` are refused, since its tables are the database's. `DeleteResource` keeps the client's rows and warns `data_kept`.
|
|
145
|
+
|
|
146
|
+
## Next Steps
|
|
147
|
+
|
|
148
|
+
- [Resources](./5.resources.md): the resources a mapping writes are ordinary ones, read the same way.
|
|
149
|
+
- [Queries](./6.queries.md): stats over a mapped relation name it by its ref.
|
|
150
|
+
- [Results and Errors](./8.results-and-errors.md): branch on what every operation answers.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
Every builder operation
|
|
5
|
+
Every builder operation identifies its target with a string ref. This page covers `PageRef` for a page, `BlockPath` for a block inside it, and `CategoryRef` for a menu category. [Resources](./5.resources.md) and [Queries](./6.queries.md) define their own refs.
|
|
6
6
|
|
|
7
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
8
|
|
|
@@ -102,7 +102,7 @@ The DMS also accepts an `Action` there. That is a live object rather than data,
|
|
|
102
102
|
|
|
103
103
|
## Patch a Page's Metadata
|
|
104
104
|
|
|
105
|
-
`EditablePageMeta` is the subset of `PageMeta` that `ConfigurePage` accepts as a patch: `displayName`, `icon`, `category`, `order`, `description`, `hidden` and `
|
|
105
|
+
`EditablePageMeta` is the subset of `PageMeta` that `ConfigurePage` accepts as a patch: `displayName`, `icon`, `category`, `order`, `description`, `hidden`, `permission` and `live`. `ref`, `id` and `filepath` are not patchable — they follow from where the page lives.
|
|
106
106
|
|
|
107
107
|
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).
|
|
108
108
|
|
|
@@ -127,6 +127,7 @@ const patch: Partial<EditablePageMeta> = {
|
|
|
127
127
|
| `editable` | `boolean` | `false` marks an opaque, non-canonical block. |
|
|
128
128
|
| `opaqueReason` | `string` | Why the block is opaque. Set when `editable === false`. |
|
|
129
129
|
| `slot` | `string` | The slot the block sits in, when its parent declares slots. |
|
|
130
|
+
| `meta` | `Record<string, unknown>` | Child metadata beyond `slot`, such as `{ colSpan: 2 }`. Keep it when constructing a draft from the tree. |
|
|
130
131
|
| `config` | `Record<string, unknown>` | The block's options object. Present only when the block is editable. |
|
|
131
132
|
| `controller` | `ResourceRef` | For a controller-leading block such as `TableView`, the resource its leading argument resolves to, when it is a known generated resource. |
|
|
132
133
|
| `children` | `BlockNode[]` | Container blocks only, in `.child()` order. |
|
package/docs/6.queries.md
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# Queries
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Count rows without writing a route
|
|
4
4
|
|
|
5
|
-
A query
|
|
5
|
+
A query gives a page data to display: a count, a total or a series of points. With an existing `/books` page and a `book` resource, this example creates a route that counts the books:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { AddQuery } from "@antelopejs/interface-dms-builder";
|
|
9
|
+
|
|
10
|
+
const result = await AddQuery("/books", {
|
|
11
|
+
name: "bookCount",
|
|
12
|
+
resource: "book",
|
|
13
|
+
template: "count",
|
|
14
|
+
});
|
|
15
|
+
if (result.ok) {
|
|
16
|
+
console.log(result.data.route); // "/dms/pages/books/stats/book-count"
|
|
17
|
+
} else {
|
|
18
|
+
console.error(result.error);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
After the backend reloads, a signed-in user with access to `/books` can request that URL through the frontend. It returns `{ "value": N }`, where `N` is the number of rows. The operation writes a model method and a page route; it does not add a visible block. Use the returned `route` as the block's `fetchUrl`.
|
|
23
|
+
|
|
24
|
+
Choose an unused query name. For an editor that must preview before saving, use [PreviewQuery and page drafts](./9.drafts-and-preview.md) instead of calling `AddQuery` on each change.
|
|
25
|
+
|
|
26
|
+
## Query reference
|
|
6
27
|
|
|
7
28
|
The route is guarded with the page's own permission, as a parameter decorator on that route alone: whoever may open the page may read what it derives, and no other route on the controller is affected.
|
|
8
29
|
|
|
@@ -198,7 +219,7 @@ await AddQuery("/dashboard", {
|
|
|
198
219
|
where: [{ field: "status", op: "eq", value: "paid" }],
|
|
199
220
|
},
|
|
200
221
|
});
|
|
201
|
-
// GET /dashboard/stats/monthly-revenue
|
|
222
|
+
// GET /dms/pages/dashboard/stats/monthly-revenue
|
|
202
223
|
// { series: [{ x: 202601, y: 1250 }, { x: 202602, y: 980 }] }
|
|
203
224
|
```
|
|
204
225
|
|
|
@@ -217,7 +238,7 @@ await AddQuery("/dashboard", {
|
|
|
217
238
|
Three rules worth planning for:
|
|
218
239
|
|
|
219
240
|
- **A series is always ordered.** Databases return groups in no particular order — MongoDB scrambles them — so an ordering is emitted whether or not you ask for one. Omitting `orderBy` gives a timeline: the group, ascending.
|
|
220
|
-
- **Omitting `timezone` buckets in UTC
|
|
241
|
+
- **Omitting `timezone` buckets in UTC.** A row near a period boundary may belong to a different bucket in the reader's local time. Pass the zone the reader uses for reporting.
|
|
221
242
|
- **A top N is an ordering plus a limit**, not a separate template: `orderBy: "measure"`, `direction: "desc"`, `limit: 5`.
|
|
222
243
|
|
|
223
244
|
There is no week bucket. Neither database adapter exposes an ISO week, and deriving one from the day of the year puts the first days of January in the previous year's week — a wrong week being worse than a missing one.
|
|
@@ -264,7 +285,7 @@ await AddQuery("/dashboard", {
|
|
|
264
285
|
response: "card",
|
|
265
286
|
compare: true,
|
|
266
287
|
});
|
|
267
|
-
// GET /dashboard/stats/revenue?from=…&to=…&compareFrom=…&compareTo=…
|
|
288
|
+
// GET /dms/pages/dashboard/stats/revenue?from=…&to=…&compareFrom=…&compareTo=…
|
|
268
289
|
// { value, series, previousValue, delta, comparisonSeries }
|
|
269
290
|
```
|
|
270
291
|
|
|
@@ -320,11 +341,12 @@ A query that no longer parses still reports its `endpoint`, `resource` and `mode
|
|
|
320
341
|
|
|
321
342
|
```typescript
|
|
322
343
|
import { GetPageStructure } from "@antelopejs/interface-dms-builder";
|
|
344
|
+
import { pageRoutePath } from "@antelopejs/interface-dms/page";
|
|
323
345
|
|
|
324
346
|
const result = await GetPageStructure("/books");
|
|
325
347
|
if (result.ok) {
|
|
326
348
|
const editable = result.data.queries.filter((query) => !query.opaque);
|
|
327
|
-
const urls = result.data.queries.map((query) =>
|
|
349
|
+
const urls = result.data.queries.map((query) => pageRoutePath("/books", query.endpoint));
|
|
328
350
|
}
|
|
329
351
|
```
|
|
330
352
|
|
package/docs/7.values.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
Configuration values are plain JSON by default. `AddBlockInput.config`, a `ConfigureBlock` patch, a page's `permission` — all of it is written to source exactly as you pass it, as a literal.
|
|
6
6
|
|
|
7
|
-
Some values
|
|
7
|
+
Some options need TypeScript values rather than JSON literals. A form field needs a DataType instance, a relation needs a controller class, and a chart card needs a nested chart. The structured encodings below let the builder write those values and their imports. Use a raw `$expr` only as an escape hatch; drafts and query parameters reject it.
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
10
|
import type {
|
|
11
|
+
BlockValue,
|
|
11
12
|
DataTypeValue,
|
|
12
13
|
ExprValue,
|
|
13
14
|
RefValue,
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
| A string, number, boolean, array or object | The value itself, as JSON. |
|
|
22
23
|
| A value the catalog marks `x-dataType` | `DataTypeValue` |
|
|
23
24
|
| A reference to a builder-generated resource class | `RefValue` |
|
|
25
|
+
| A nested component in an option marked `x-component` | `BlockValue` |
|
|
24
26
|
| Anything else that must reach the source as code | `ExprValue` |
|
|
25
27
|
|
|
26
28
|
The catalog is what tells you which option needs which: an option marked `x-dataType` in a block's `ConfigSchema` takes a `DataTypeValue`, and nothing else. See [Catalog](./3.catalog.md).
|
|
@@ -103,15 +105,11 @@ await ConfigureBlock("/revenue#total", {
|
|
|
103
105
|
|
|
104
106
|
Two constraints come with it:
|
|
105
107
|
|
|
106
|
-
- The referenced symbol must already be
|
|
108
|
+
- The referenced symbol must already be in scope in the target file. `$expr` adds no import.
|
|
107
109
|
- `$expr` is refused inside a query's parameters — a query's values are data, and a raw expression would put uncheckable code inside a model body. See [Queries](./6.queries.md).
|
|
108
110
|
|
|
109
111
|
Consumers that run the builder in a restricted mode may refuse `$expr` outright, since arbitrary code defeats the structured guarantees the rest of the surface provides. `RefValue` is allowed in that mode precisely because it is validated; prefer it whenever the value you need is a generated class.
|
|
110
112
|
|
|
111
|
-
## Next Steps
|
|
112
|
-
|
|
113
|
-
- [Results and Errors](./8.results-and-errors.md) - Branch on an operation's result and read its diagnostics
|
|
114
|
-
|
|
115
113
|
## Nest a Block in a Config Value
|
|
116
114
|
|
|
117
115
|
A few options hold a component rather than data — a `ChartCard`'s `chart`, marked `x-component` in the catalog. `BlockValue` is how you write one: the builder emits the nested factory call and adds its import.
|
|
@@ -128,3 +126,7 @@ const chart: BlockValue = {
|
|
|
128
126
|
`type` is a catalog key, like `AddBlockInput.type`; an unknown one is rejected with `invalid_config`. Read-back is symmetrical: a nested factory call whose callee is a known block type comes back as a `$block` sentinel rather than as `$expr`, so the value stays editable.
|
|
129
127
|
|
|
130
128
|
Most nesting does *not* go through config — it goes through `.child`, which you express with `AddBlockInput.parent` or with `BlockDraft.children`. Reach for `$block` only where the catalog marks an option `x-component`.
|
|
129
|
+
|
|
130
|
+
## Next Steps
|
|
131
|
+
|
|
132
|
+
- [Results and Errors](./8.results-and-errors.md) - Branch on an operation's result and read its diagnostics
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
# Drafts and Preview
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **Warning:** `SetPageBlocks` replaces the whole block tree. Blocks you omit are removed. For one small change to an existing page, use `ConfigureBlock` instead.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Preview one card
|
|
8
|
+
|
|
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.
|
|
8
10
|
|
|
9
11
|
```typescript
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import { PreviewLayout } from "@antelopejs/interface-dms-builder";
|
|
13
|
+
|
|
14
|
+
const preview = await PreviewLayout("/dashboard", {
|
|
15
|
+
blocks: [{ name: "total", type: "KpiCard", config: { title: "Total revenue" } }],
|
|
16
|
+
});
|
|
17
|
+
if (preview.ok) {
|
|
18
|
+
console.log(preview.data.components.total);
|
|
19
|
+
console.log(preview.data.degraded);
|
|
20
|
+
} else {
|
|
21
|
+
console.error(preview.error);
|
|
22
|
+
}
|
|
14
23
|
```
|
|
15
24
|
|
|
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.
|
|
26
|
+
|
|
16
27
|
## Describe a Draft
|
|
17
28
|
|
|
18
29
|
`PageDraft` is a page as you want it written.
|
|
@@ -40,7 +51,11 @@ await SetPageBlocks("/shop/board", {
|
|
|
40
51
|
{
|
|
41
52
|
name: "revenueChart",
|
|
42
53
|
type: "ChartCard",
|
|
43
|
-
config: {
|
|
54
|
+
config: {
|
|
55
|
+
title: "Revenue",
|
|
56
|
+
fetchUrl: "/dms/pages/shop/board/stats/revenue",
|
|
57
|
+
chart: { $block: { type: "ChartArea", config: { xaxisType: "category" } } },
|
|
58
|
+
},
|
|
44
59
|
},
|
|
45
60
|
],
|
|
46
61
|
queries: [
|
|
@@ -72,7 +87,7 @@ await SetPageBlocks("/shop/board", {
|
|
|
72
87
|
|
|
73
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.
|
|
74
89
|
|
|
75
|
-
|
|
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:
|
|
76
91
|
|
|
77
92
|
```typescript
|
|
78
93
|
import {
|
|
@@ -82,22 +97,32 @@ import {
|
|
|
82
97
|
|
|
83
98
|
const structure = await GetPageStructure("/dashboard");
|
|
84
99
|
if (structure.ok) {
|
|
85
|
-
const blocks = structure.data.blocks.map((block) =>
|
|
86
|
-
block.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
const blocks = structure.data.blocks.map((block) => ({
|
|
101
|
+
name: block.name,
|
|
102
|
+
preserve: true,
|
|
103
|
+
}));
|
|
104
|
+
const result = await SetPageBlocks("/dashboard", {
|
|
105
|
+
page: { displayName: "Revenue" },
|
|
106
|
+
blocks,
|
|
107
|
+
}, {
|
|
91
108
|
expectedVersion: structure.data.version,
|
|
92
109
|
});
|
|
110
|
+
if (!result.ok) console.error(result.error);
|
|
93
111
|
}
|
|
94
112
|
```
|
|
95
113
|
|
|
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.
|
|
115
|
+
|
|
96
116
|
A `preserve` block whose name matches nothing on disk is rejected with `invalid_config`.
|
|
97
117
|
|
|
98
118
|
## Write a Whole Page
|
|
99
119
|
|
|
100
120
|
```typescript
|
|
121
|
+
import { GetPageStructure, SetPageBlocks } from "@antelopejs/interface-dms-builder";
|
|
122
|
+
|
|
123
|
+
const structure = await GetPageStructure("/dashboard");
|
|
124
|
+
if (!structure.ok) throw new Error(structure.error.code);
|
|
125
|
+
|
|
101
126
|
const result = await SetPageBlocks(
|
|
102
127
|
"/dashboard",
|
|
103
128
|
{
|
|
@@ -124,10 +149,13 @@ const result = await SetPageBlocks(
|
|
|
124
149
|
},
|
|
125
150
|
],
|
|
126
151
|
},
|
|
127
|
-
{ expectedVersion:
|
|
152
|
+
{ expectedVersion: structure.data.version },
|
|
128
153
|
);
|
|
154
|
+
if (!result.ok) throw new Error(result.error.code);
|
|
129
155
|
```
|
|
130
156
|
|
|
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.
|
|
158
|
+
|
|
131
159
|
On success the result carries the staged `changes` and the page's new `version`, ready to be passed as the next `expectedVersion`.
|
|
132
160
|
|
|
133
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.
|
|
@@ -179,7 +207,13 @@ const result = await PreviewQuery({
|
|
|
179
207
|
name: "revenue",
|
|
180
208
|
resource: "order",
|
|
181
209
|
template: "series",
|
|
182
|
-
params: {
|
|
210
|
+
params: {
|
|
211
|
+
op: "sum", field: "amount", groupBy: "createdAt", bucket: "month",
|
|
212
|
+
where: [
|
|
213
|
+
{ field: "createdAt", op: "ge", value: { $param: { name: "from" } } },
|
|
214
|
+
{ field: "createdAt", op: "lt", value: { $param: { name: "to" } } },
|
|
215
|
+
],
|
|
216
|
+
},
|
|
183
217
|
},
|
|
184
218
|
args: { from: "2026-01-01T00:00:00.000Z", to: "2026-09-01T00:00:00.000Z" },
|
|
185
219
|
});
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-dms-builder",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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",
|