@pylonts/dsl 1.0.6 → 1.1.2
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/README.md +2 -1
- package/dist/action.d.ts +7 -0
- package/dist/action.js +3 -0
- package/dist/asset.d.ts +48 -0
- package/dist/asset.js +31 -0
- package/dist/bases.d.ts +6 -2
- package/dist/bases.js +10 -6
- package/dist/check-inheritance.js +1 -4
- package/dist/component.d.ts +20 -0
- package/dist/component.js +1 -0
- package/dist/convert.d.ts +9 -0
- package/dist/convert.js +3 -0
- package/dist/curd.d.ts +62 -0
- package/dist/curd.js +31 -0
- package/dist/db-config.d.ts +8 -0
- package/dist/db-config.js +1 -0
- package/dist/db.d.ts +56 -0
- package/dist/db.js +100 -0
- package/dist/dictionary.d.ts +26 -5
- package/dist/dictionary.js +21 -8
- package/dist/dsl.d.ts +10 -49
- package/dist/dsl.js +12 -103
- package/dist/dto.d.ts +12 -9
- package/dist/dto.js +25 -34
- package/dist/enum-driver.d.ts +1 -1
- package/dist/enum-driver.js +1 -4
- package/dist/event.d.ts +8 -0
- package/dist/event.js +3 -0
- package/dist/flow.d.ts +1 -1
- package/dist/flow.js +3 -8
- package/dist/import-base.d.ts +15 -0
- package/dist/import-base.js +1 -0
- package/dist/index.d.ts +31 -17
- package/dist/index.js +31 -33
- package/dist/mermaid-driver.d.ts +2 -2
- package/dist/mermaid-driver.js +4 -7
- package/dist/mock.d.ts +12 -0
- package/dist/mock.js +1 -0
- package/dist/mysql-driver.d.ts +5 -1
- package/dist/mysql-driver.js +25 -10
- package/dist/navigation.d.ts +22 -0
- package/dist/navigation.js +15 -0
- package/dist/page-def.d.ts +40 -0
- package/dist/page-def.js +38 -0
- package/dist/page-flow.d.ts +5 -3
- package/dist/page-flow.js +109 -18
- package/dist/page.d.ts +37 -15
- package/dist/page.js +23 -13
- package/dist/pattern.js +2 -6
- package/dist/patterns/retry.d.ts +1 -1
- package/dist/patterns/retry.js +2 -6
- package/dist/popup.d.ts +18 -0
- package/dist/popup.js +8 -0
- package/dist/project.d.ts +25 -13
- package/dist/project.js +41 -6
- package/dist/prototype.d.ts +1 -1
- package/dist/prototype.js +1 -4
- package/dist/provider.d.ts +54 -0
- package/dist/provider.js +18 -0
- package/dist/ref.d.ts +14 -0
- package/dist/ref.js +6 -0
- package/dist/route.d.ts +8 -0
- package/dist/route.js +3 -0
- package/dist/typebox-driver.d.ts +3 -3
- package/dist/typebox-driver.js +24 -26
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -4
- package/docs/curd.md +111 -0
- package/docs/dictionary.md +42 -31
- package/docs/driver.md +42 -42
- package/docs/dto.md +6 -6
- package/docs/enum.md +24 -24
- package/docs/project.md +2 -2
- package/docs/table.md +53 -16
- package/package.json +5 -3
- package/src/action.ts +11 -0
- package/src/asset.ts +63 -0
- package/src/bases.ts +30 -20
- package/src/component.ts +22 -0
- package/src/convert.ts +13 -0
- package/src/curd.ts +94 -0
- package/src/db-config.ts +8 -0
- package/src/db.ts +153 -0
- package/src/dictionary.ts +45 -19
- package/src/dsl.ts +13 -106
- package/src/dto.ts +25 -12
- package/src/enum-driver.ts +42 -42
- package/src/event.ts +12 -0
- package/src/flow.ts +103 -103
- package/src/import-base.ts +15 -0
- package/src/index.ts +31 -17
- package/src/mermaid-driver.ts +5 -4
- package/src/mock.ts +12 -0
- package/src/mysql-driver.ts +25 -6
- package/src/navigation.ts +29 -0
- package/src/page-def.ts +80 -0
- package/src/page-flow.ts +117 -15
- package/src/page.ts +57 -20
- package/src/patterns/retry.ts +54 -54
- package/src/popup.ts +25 -0
- package/src/project.ts +57 -14
- package/src/prototype.ts +29 -29
- package/src/provider.ts +73 -0
- package/src/ref.ts +19 -0
- package/src/route.ts +12 -0
- package/src/typebox-driver.ts +192 -187
- package/src/utils.ts +11 -6
- package/src/check-inheritance.ts +0 -86
package/dist/mysql-driver.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildCreateTableSql = buildCreateTableSql;
|
|
4
1
|
// MySQL driver: converts a TableSchema into a CREATE TABLE statement.
|
|
2
|
+
/** Default value constant for created_at columns. */
|
|
3
|
+
export const CURRENT_TIMESTAMP = 'CURRENT_TIMESTAMP';
|
|
4
|
+
/** Default value constant for updated_at columns (with auto-update). */
|
|
5
|
+
export const CURRENT_TIMESTAMP_ON_UPDATE = 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP';
|
|
5
6
|
function columnType(field) {
|
|
6
7
|
switch (field.type) {
|
|
7
8
|
case 'string':
|
|
@@ -31,14 +32,28 @@ function columnType(field) {
|
|
|
31
32
|
return 'JSON';
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
function renderDefault(field) {
|
|
36
|
+
if (field.default === undefined)
|
|
37
|
+
return '';
|
|
38
|
+
// MySQL keyword expressions (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
39
|
+
// NULL, etc.) — render bare, no quotes.
|
|
40
|
+
if (/^[A-Z]/.test(field.default))
|
|
41
|
+
return ` DEFAULT ${field.default}`;
|
|
42
|
+
// Numeric columns take a bare literal, not a quoted one.
|
|
43
|
+
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
|
|
44
|
+
return ` DEFAULT ${field.default}`;
|
|
45
|
+
}
|
|
46
|
+
return ` DEFAULT '${field.default}'`;
|
|
47
|
+
}
|
|
34
48
|
function columnDef(field, autoIncrement) {
|
|
35
49
|
const parts = [field.name, columnType(field)];
|
|
36
50
|
if (field.optional === false)
|
|
37
51
|
parts.push('NOT NULL');
|
|
38
|
-
|
|
39
|
-
parts.push(`DEFAULT '${field.default}'`);
|
|
52
|
+
parts.push(renderDefault(field));
|
|
40
53
|
if (field === autoIncrement)
|
|
41
54
|
parts.push('AUTO_INCREMENT');
|
|
55
|
+
if (field.description)
|
|
56
|
+
parts.push(`COMMENT '${field.description.replace(/'/g, "\\'")}'`);
|
|
42
57
|
return parts.join(' ');
|
|
43
58
|
}
|
|
44
59
|
function primaryKeyClause(schema) {
|
|
@@ -48,13 +63,13 @@ function primaryKeyClause(schema) {
|
|
|
48
63
|
return `PRIMARY KEY (${fields.map((f) => f.name).join(', ')})`;
|
|
49
64
|
}
|
|
50
65
|
function indexClause(index) {
|
|
51
|
-
const fields = Array.isArray(index.
|
|
66
|
+
const fields = Array.isArray(index.columns) ? index.columns : [index.columns];
|
|
52
67
|
const kind = index.unique ? 'UNIQUE KEY' : 'KEY';
|
|
53
68
|
const name = index.name ?? fields.map((f) => f.name).join('_');
|
|
54
69
|
return `${kind} ${name} (${fields.map((f) => f.name).join(', ')})`;
|
|
55
70
|
}
|
|
56
71
|
function foreignKeyClause(name, fk) {
|
|
57
|
-
const fields = Array.isArray(fk.
|
|
72
|
+
const fields = Array.isArray(fk.columns) ? fk.columns : [fk.columns];
|
|
58
73
|
const refs = Array.isArray(fk.references) ? fk.references : [fk.references];
|
|
59
74
|
const refTable = refs[0].schema?.name;
|
|
60
75
|
if (!refTable)
|
|
@@ -63,8 +78,8 @@ function foreignKeyClause(name, fk) {
|
|
|
63
78
|
const refCols = refs.map((r) => r.name).join(', ');
|
|
64
79
|
return `CONSTRAINT \`${name}\` FOREIGN KEY (${fkCols}) REFERENCES \`${refTable}\` (${refCols})`;
|
|
65
80
|
}
|
|
66
|
-
function buildCreateTableSql(schema, options = {}) {
|
|
67
|
-
const lines = Object.values(schema.
|
|
81
|
+
export function buildCreateTableSql(schema, options = {}) {
|
|
82
|
+
const lines = Object.values(schema.columns).map((field) => columnDef(field, schema.autoIncrement));
|
|
68
83
|
const pk = primaryKeyClause(schema);
|
|
69
84
|
if (pk)
|
|
70
85
|
lines.push(pk);
|
|
@@ -75,5 +90,5 @@ function buildCreateTableSql(schema, options = {}) {
|
|
|
75
90
|
lines.push(foreignKeyClause(name, fk));
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
|
-
return `CREATE TABLE \`${schema.name}\` (\n ${lines.join(',\n ')}\n);`;
|
|
93
|
+
return `CREATE TABLE IF NOT EXISTS \`${schema.name}\` (\n ${lines.join(',\n ')}\n);`;
|
|
79
94
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RouteDataSchema } from './route.js';
|
|
2
|
+
import type { ActionSchema } from './action.js';
|
|
3
|
+
import { PageSchema } from './page.js';
|
|
4
|
+
/** Navigation primitives: front-end routing actions that are not provider calls. */
|
|
5
|
+
export interface NavigationAction extends ActionSchema {
|
|
6
|
+
type: 'navigation';
|
|
7
|
+
method: 'back' | 'push' | 'popup' | 'home';
|
|
8
|
+
/** Target page for push navigation. */
|
|
9
|
+
target?: PageSchema;
|
|
10
|
+
/** Route params type for push navigation. Auto-derived from target.params if omitted. */
|
|
11
|
+
params?: RouteDataSchema;
|
|
12
|
+
}
|
|
13
|
+
export declare const navigation: {
|
|
14
|
+
back(description?: string): NavigationAction;
|
|
15
|
+
push(options: {
|
|
16
|
+
target: PageSchema;
|
|
17
|
+
params?: RouteDataSchema;
|
|
18
|
+
description?: string;
|
|
19
|
+
}): NavigationAction;
|
|
20
|
+
popup(description?: string): NavigationAction;
|
|
21
|
+
home(description?: string): NavigationAction;
|
|
22
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const navigation = {
|
|
2
|
+
back(description) {
|
|
3
|
+
return { name: 'back', type: 'navigation', method: 'back', description };
|
|
4
|
+
},
|
|
5
|
+
push(options) {
|
|
6
|
+
const { target, params, description } = options;
|
|
7
|
+
return { name: 'push', type: 'navigation', method: 'push', target, params: params ?? target.params, description };
|
|
8
|
+
},
|
|
9
|
+
popup(description) {
|
|
10
|
+
return { name: 'popup', type: 'navigation', method: 'popup', description };
|
|
11
|
+
},
|
|
12
|
+
home(description) {
|
|
13
|
+
return { name: 'home', type: 'navigation', method: 'home', description };
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ImportableSchemaBase, CollectionSchemaBase, SchemaBase } from './dsl.js';
|
|
2
|
+
import { DtoField } from './dto.js';
|
|
3
|
+
import type { DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
|
|
4
|
+
import type { ComponentSchema } from './component.js';
|
|
5
|
+
import type { ActionSchema } from './action.js';
|
|
6
|
+
export type { RouteDataSchema } from './route.js';
|
|
7
|
+
export { defineRouteData } from './route.js';
|
|
8
|
+
/** Page lifecycle events that trigger data loading. */
|
|
9
|
+
export interface EventSchema extends SchemaBase {
|
|
10
|
+
type: 'onLoad' | 'onShow' | 'onHide' | 'onPullDownRefresh' | 'onReachBottom';
|
|
11
|
+
/** Actions that fire when this event occurs. */
|
|
12
|
+
actions?: ActionSchema[];
|
|
13
|
+
}
|
|
14
|
+
export declare const events: {
|
|
15
|
+
onLoad(actions?: ActionSchema[], description?: string): EventSchema;
|
|
16
|
+
onShow(actions?: ActionSchema[], description?: string): EventSchema;
|
|
17
|
+
onHide(actions?: ActionSchema[], description?: string): EventSchema;
|
|
18
|
+
onPullDownRefresh(actions?: ActionSchema[], description?: string): EventSchema;
|
|
19
|
+
onReachBottom(actions?: ActionSchema[], description?: string): EventSchema;
|
|
20
|
+
};
|
|
21
|
+
/** Page data: a named collection of fields that defines the page's data shape. */
|
|
22
|
+
export interface PageDataSchema extends CollectionSchemaBase {
|
|
23
|
+
type: 'pageData';
|
|
24
|
+
fields: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField>;
|
|
25
|
+
}
|
|
26
|
+
export declare function definePageData(fields: PageDataSchema['fields']): PageDataSchema;
|
|
27
|
+
/** Page skeleton definition. */
|
|
28
|
+
export interface PageDef extends ImportableSchemaBase {
|
|
29
|
+
data: PageDataSchema;
|
|
30
|
+
/** Lifecycle events that trigger data loading. */
|
|
31
|
+
events: EventSchema[];
|
|
32
|
+
/** Page actions (provider calls, navigation, etc.). */
|
|
33
|
+
actions: ActionSchema[];
|
|
34
|
+
/** UI component declarations that make up the page skeleton. */
|
|
35
|
+
components: ComponentSchema[];
|
|
36
|
+
/** Whether the driver should generate page-level loading / error / empty state wrappers. */
|
|
37
|
+
handleStates?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Define a page skeleton. */
|
|
40
|
+
export declare function definePageDef(name: string, schema: Omit<PageDef, 'name'>): PageDef;
|
package/dist/page-def.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DtoField } from './dto.js';
|
|
2
|
+
export { defineRouteData } from './route.js';
|
|
3
|
+
export const events = {
|
|
4
|
+
onLoad(actions, description) {
|
|
5
|
+
return { name: 'onLoad', type: 'onLoad', actions, description };
|
|
6
|
+
},
|
|
7
|
+
onShow(actions, description) {
|
|
8
|
+
return { name: 'onShow', type: 'onShow', actions, description };
|
|
9
|
+
},
|
|
10
|
+
onHide(actions, description) {
|
|
11
|
+
return { name: 'onHide', type: 'onHide', actions, description };
|
|
12
|
+
},
|
|
13
|
+
onPullDownRefresh(actions, description) {
|
|
14
|
+
return { name: 'onPullDownRefresh', type: 'onPullDownRefresh', actions, description };
|
|
15
|
+
},
|
|
16
|
+
onReachBottom(actions, description) {
|
|
17
|
+
return { name: 'onReachBottom', type: 'onReachBottom', actions, description };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export function definePageData(fields) {
|
|
21
|
+
// Page data has no meaningful identity of its own; the page name already
|
|
22
|
+
// scopes it, so the schema name defaults to 'data'.
|
|
23
|
+
const data = { name: 'data', type: 'pageData', fields };
|
|
24
|
+
// Write back field names (same convention as buildMessage) so defineRef
|
|
25
|
+
// produces refs with a real field name instead of ''.
|
|
26
|
+
for (const key of Object.keys(data.fields)) {
|
|
27
|
+
const df = data.fields[key];
|
|
28
|
+
if (df instanceof DtoField) {
|
|
29
|
+
df.name = key;
|
|
30
|
+
df.schema = data;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
/** Define a page skeleton. */
|
|
36
|
+
export function definePageDef(name, schema) {
|
|
37
|
+
return { name, ...schema };
|
|
38
|
+
}
|
package/dist/page-flow.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl';
|
|
2
|
-
import { ActionSchema
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { ActionSchema } from './action.js';
|
|
3
|
+
import { Page } from './page.js';
|
|
3
4
|
export interface PageEdge extends SchemaBase {
|
|
4
5
|
/** Trigger action; undefined = default path (success/normal). */
|
|
5
6
|
when?: ActionSchema;
|
|
@@ -9,13 +10,14 @@ export interface PageEdge extends SchemaBase {
|
|
|
9
10
|
export interface PageFlow extends SchemaBase {
|
|
10
11
|
/** Entry page. */
|
|
11
12
|
start: Page;
|
|
12
|
-
/** All pages
|
|
13
|
+
/** All pages explicitly declared by the caller. */
|
|
13
14
|
pages: Page[];
|
|
14
15
|
edges: PageEdge[];
|
|
15
16
|
}
|
|
16
17
|
export declare function pageEdge(start: Page, end: Page, when?: ActionSchema, description?: string): PageEdge;
|
|
17
18
|
export declare function definePageFlow(name: string, schema: {
|
|
18
19
|
start: Page;
|
|
20
|
+
pages: Page[];
|
|
19
21
|
edges: PageEdge[];
|
|
20
22
|
description?: string;
|
|
21
23
|
}): PageFlow;
|
package/dist/page-flow.js
CHANGED
|
@@ -1,25 +1,116 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.pageEdge = pageEdge;
|
|
4
|
-
exports.definePageFlow = definePageFlow;
|
|
5
|
-
function pageEdge(start, end, when, description) {
|
|
1
|
+
import { getRegisteredPages, clearRegisteredPages } from './page.js';
|
|
2
|
+
export function pageEdge(start, end, when, description) {
|
|
6
3
|
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
7
4
|
return { name: `${start.name}->${end.name}`, start, end, when, description };
|
|
8
5
|
}
|
|
9
|
-
function definePageFlow(name, schema) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
export function definePageFlow(name, schema) {
|
|
7
|
+
const { start, pages, edges: explicitEdges, description } = schema;
|
|
8
|
+
// Resolve the back action from any page's actions list (PageSchema at runtime)
|
|
9
|
+
const backAction = findBackAction(pages);
|
|
10
|
+
// Auto-generate back edges: for every explicit edge A→B (not itself a back),
|
|
11
|
+
// if B has no explicit back edge and no existing edge B→A, add B→A with back.
|
|
12
|
+
const hasExplicitBack = new Set();
|
|
13
|
+
for (const e of explicitEdges) {
|
|
14
|
+
if (e.when && e.when.name === 'back')
|
|
15
|
+
hasExplicitBack.add(e.start);
|
|
16
|
+
}
|
|
17
|
+
const hasReverse = new Set();
|
|
18
|
+
for (const e of explicitEdges) {
|
|
19
|
+
hasReverse.add(`${e.start.name}->${e.end.name}`);
|
|
20
|
+
}
|
|
21
|
+
const generatedBackEdges = [];
|
|
22
|
+
if (backAction) {
|
|
23
|
+
for (const e of explicitEdges) {
|
|
24
|
+
if (e.when && e.when.name === 'back')
|
|
25
|
+
continue;
|
|
26
|
+
if (hasExplicitBack.has(e.end))
|
|
27
|
+
continue;
|
|
28
|
+
if (hasReverse.has(`${e.end.name}->${e.start.name}`))
|
|
29
|
+
continue;
|
|
30
|
+
generatedBackEdges.push({
|
|
31
|
+
name: `${e.end.name}->${e.start.name}`,
|
|
32
|
+
start: e.end,
|
|
33
|
+
end: e.start,
|
|
34
|
+
when: backAction,
|
|
35
|
+
description: 'auto-generated back edge',
|
|
36
|
+
});
|
|
37
|
+
hasExplicitBack.add(e.end);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const edges = [...explicitEdges, ...generatedBackEdges];
|
|
41
|
+
// Validate: every registered page must be in the flow's pages
|
|
42
|
+
const registered = getRegisteredPages();
|
|
43
|
+
const orphaned = [];
|
|
44
|
+
for (const rp of registered) {
|
|
45
|
+
// Compare by object identity — caller passes the same reference
|
|
46
|
+
if (!pages.includes(rp)) {
|
|
47
|
+
orphaned.push(rp.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (orphaned.length > 0) {
|
|
51
|
+
throw new Error(`[definePageFlow "${name}"] orphaned pages (defined but not in flow): ${orphaned.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
clearRegisteredPages();
|
|
54
|
+
// Validate: start must be in pages
|
|
55
|
+
if (!pages.includes(start)) {
|
|
56
|
+
throw new Error(`[definePageFlow "${name}"] start page "${start.name}" not in pages`);
|
|
57
|
+
}
|
|
58
|
+
// Validate: every page appears in at least one edge
|
|
59
|
+
const inEdge = new Set();
|
|
60
|
+
for (const e of edges) {
|
|
61
|
+
inEdge.add(e.start);
|
|
62
|
+
inEdge.add(e.end);
|
|
63
|
+
}
|
|
64
|
+
const isolated = [];
|
|
65
|
+
for (const p of pages) {
|
|
66
|
+
if (!inEdge.has(p))
|
|
67
|
+
isolated.push(p.name);
|
|
68
|
+
}
|
|
69
|
+
if (isolated.length > 0) {
|
|
70
|
+
throw new Error(`[definePageFlow "${name}"] isolated pages (no edges): ${isolated.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
// Validate: all pages reachable from start
|
|
73
|
+
const adj = new Map();
|
|
74
|
+
for (const p of pages)
|
|
75
|
+
adj.set(p, []);
|
|
76
|
+
for (const e of edges) {
|
|
77
|
+
const neighbors = adj.get(e.start);
|
|
78
|
+
if (neighbors)
|
|
79
|
+
neighbors.push(e.end);
|
|
80
|
+
}
|
|
81
|
+
const visited = new Set();
|
|
82
|
+
const stack = [start];
|
|
83
|
+
while (stack.length > 0) {
|
|
84
|
+
const cur = stack.pop();
|
|
85
|
+
if (visited.has(cur))
|
|
86
|
+
continue;
|
|
87
|
+
visited.add(cur);
|
|
88
|
+
for (const next of adj.get(cur) ?? []) {
|
|
89
|
+
if (!visited.has(next))
|
|
90
|
+
stack.push(next);
|
|
18
91
|
}
|
|
19
92
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
93
|
+
const unreachable = [];
|
|
94
|
+
for (const p of pages) {
|
|
95
|
+
if (!visited.has(p))
|
|
96
|
+
unreachable.push(p.name);
|
|
97
|
+
}
|
|
98
|
+
if (unreachable.length > 0) {
|
|
99
|
+
throw new Error(`[definePageFlow "${name}"] unreachable pages: ${unreachable.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
return { name, description, start, pages, edges };
|
|
102
|
+
}
|
|
103
|
+
/** Walk all pages' pageDef.actions (PageSchema runtime shape) to find a NavigationAction with method 'back'. */
|
|
104
|
+
function findBackAction(pages) {
|
|
105
|
+
for (const p of pages) {
|
|
106
|
+
const pageDef = p.pageDef;
|
|
107
|
+
if (!pageDef?.actions)
|
|
108
|
+
continue;
|
|
109
|
+
for (const a of pageDef.actions) {
|
|
110
|
+
const na = a;
|
|
111
|
+
if (na.type === 'navigation' && na.method === 'back')
|
|
112
|
+
return na;
|
|
113
|
+
}
|
|
23
114
|
}
|
|
24
|
-
return
|
|
115
|
+
return undefined;
|
|
25
116
|
}
|
package/dist/page.d.ts
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import { FrontAppSchema } from './project.js';
|
|
3
|
+
import type { PageDef } from './page-def.js';
|
|
4
|
+
import type { RouteDataSchema } from './route.js';
|
|
5
|
+
export declare function getRegisteredPages(): ReadonlySet<PageSchema>;
|
|
6
|
+
export declare function clearRegisteredPages(): void;
|
|
7
|
+
/** Standalone page definition. A page is a shared value object: it belongs to
|
|
8
|
+
* exactly one frontend app. Actions live in the page skeleton (PageDef.actions),
|
|
9
|
+
* not on the page itself — the page only carries what the flow/topology needs. */
|
|
5
10
|
export interface PageSchema extends SchemaBase {
|
|
11
|
+
/** Short display name for topology diagrams. */
|
|
12
|
+
label: string;
|
|
6
13
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
7
|
-
app:
|
|
8
|
-
/**
|
|
9
|
-
|
|
14
|
+
app: FrontAppSchema;
|
|
15
|
+
/** Full page skeleton definition (optional). */
|
|
16
|
+
pageDef?: PageDef;
|
|
17
|
+
/** Route params this page expects (e.g. detail page expects productId). */
|
|
18
|
+
params?: RouteDataSchema;
|
|
10
19
|
}
|
|
11
|
-
/** An action a user can perform on a page (e.g. submit, approve, reject). */
|
|
12
|
-
export interface ActionSchema extends SchemaBase {
|
|
13
|
-
}
|
|
14
|
-
export declare function defineAction(name: string, description?: string): ActionSchema;
|
|
15
20
|
export declare function definePage(schema: {
|
|
16
21
|
name: string;
|
|
22
|
+
label: string;
|
|
17
23
|
description?: string;
|
|
18
|
-
app:
|
|
19
|
-
|
|
24
|
+
app: FrontAppSchema;
|
|
25
|
+
pageDef?: PageDef;
|
|
26
|
+
params?: RouteDataSchema;
|
|
20
27
|
}): PageSchema;
|
|
28
|
+
/** Page with bottom tab navigation. tabs collects the child pages reachable via tab switch. */
|
|
29
|
+
export interface TabPageSchema extends PageSchema {
|
|
30
|
+
tabs: PageSchema[];
|
|
31
|
+
}
|
|
32
|
+
export declare function defineTabPage(schema: {
|
|
33
|
+
name: string;
|
|
34
|
+
label: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
app: FrontAppSchema;
|
|
37
|
+
tabs: PageSchema[];
|
|
38
|
+
pageDef?: PageDef;
|
|
39
|
+
params?: RouteDataSchema;
|
|
40
|
+
}): TabPageSchema;
|
|
21
41
|
/** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
|
|
22
42
|
export interface Page extends SchemaBase {
|
|
43
|
+
/** Short display name for topology diagrams. */
|
|
44
|
+
label?: string;
|
|
23
45
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
24
|
-
app:
|
|
46
|
+
app: FrontAppSchema;
|
|
25
47
|
}
|
|
26
|
-
export declare function page(app:
|
|
48
|
+
export declare function page(app: FrontAppSchema, name: string, description?: string, label?: string): Page;
|
package/dist/page.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
function
|
|
13
|
-
|
|
1
|
+
// Page definitions: standalone page schemas and the page node type used by
|
|
2
|
+
// page-driven flows. Kept separate from page-flow.ts (the flow graph itself).
|
|
3
|
+
// Module-level registry: every definePage/defineTabPage call registers its
|
|
4
|
+
// result so definePageFlow can check that no page is left out.
|
|
5
|
+
const _pageRegistry = new Set();
|
|
6
|
+
export function getRegisteredPages() {
|
|
7
|
+
return _pageRegistry;
|
|
8
|
+
}
|
|
9
|
+
export function clearRegisteredPages() {
|
|
10
|
+
_pageRegistry.clear();
|
|
11
|
+
}
|
|
12
|
+
export function definePage(schema) {
|
|
13
|
+
const p = { ...schema };
|
|
14
|
+
_pageRegistry.add(p);
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
export function defineTabPage(schema) {
|
|
18
|
+
const p = { ...schema };
|
|
19
|
+
_pageRegistry.add(p);
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
export function page(app, name, description, label) {
|
|
23
|
+
return { name, label, app, description };
|
|
14
24
|
}
|
package/dist/pattern.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Pattern core: minimal definitions to bootstrap the Flow × Pattern DSL.
|
|
3
2
|
// A Pattern is a reusable solution ("how to guarantee success") declared as
|
|
4
3
|
// pure data. Concrete usage fills its params and action injection points.
|
|
5
|
-
|
|
6
|
-
exports.definePattern = definePattern;
|
|
7
|
-
exports.ref = ref;
|
|
8
|
-
function definePattern(def) {
|
|
4
|
+
export function definePattern(def) {
|
|
9
5
|
return def;
|
|
10
6
|
}
|
|
11
|
-
function ref(name, args) {
|
|
7
|
+
export function ref(name, args) {
|
|
12
8
|
return { ref: name, args };
|
|
13
9
|
}
|
package/dist/patterns/retry.d.ts
CHANGED
package/dist/patterns/retry.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.retryPattern = void 0;
|
|
4
|
-
exports.renderRetry = renderRetry;
|
|
5
1
|
// Retry pattern: blind mechanical retry for read-only operations.
|
|
6
2
|
// Valid because a read-only action is idempotent by nature: clicking a query
|
|
7
3
|
// button any number of times never changes the result, so retrying the same
|
|
8
4
|
// call is always safe. No idempotency key, no query-and-resume, no compensate.
|
|
9
|
-
|
|
5
|
+
export const retryPattern = {
|
|
10
6
|
name: 'retry',
|
|
11
7
|
params: {
|
|
12
8
|
max: { type: 'int', min: 1, default: 3 },
|
|
13
9
|
backoffMs: { type: 'int', min: 0, default: 0 },
|
|
14
10
|
},
|
|
15
11
|
};
|
|
16
|
-
function renderRetry(args) {
|
|
12
|
+
export function renderRetry(args) {
|
|
17
13
|
if (args.max !== undefined && args.max < 1)
|
|
18
14
|
throw new Error('retry: max must be >= 1');
|
|
19
15
|
if (!args.action.call)
|
package/dist/popup.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ActionSchema } from './action.js';
|
|
2
|
+
import type { RefSchema } from './ref.js';
|
|
3
|
+
/** Display a toast notification. React: toast/message UI. Mini-program: wx.showToast. */
|
|
4
|
+
export interface ToastAction extends ActionSchema {
|
|
5
|
+
type: 'toast';
|
|
6
|
+
message: string | RefSchema;
|
|
7
|
+
icon?: 'success' | 'error' | 'loading' | 'none';
|
|
8
|
+
}
|
|
9
|
+
/** Display an alert dialog. React: modal. Mini-program: wx.showModal. */
|
|
10
|
+
export interface AlertAction extends ActionSchema {
|
|
11
|
+
type: 'alert';
|
|
12
|
+
title: string | RefSchema;
|
|
13
|
+
content: string | RefSchema;
|
|
14
|
+
}
|
|
15
|
+
export declare const popup: {
|
|
16
|
+
toast(message: string | RefSchema, icon?: 'success' | 'error' | 'loading' | 'none'): ToastAction;
|
|
17
|
+
alert(title: string | RefSchema, content: string | RefSchema): AlertAction;
|
|
18
|
+
};
|
package/dist/popup.js
ADDED
package/dist/project.d.ts
CHANGED
|
@@ -1,41 +1,53 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl';
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { TableSchema } from './db.js';
|
|
2
3
|
/** Frontend form factor. Closed enum, extend when new form factors appear. */
|
|
3
4
|
export type FrontType = 'admin' | 'wxmini';
|
|
4
5
|
/** A frontend application (e.g. admin console, wechat mini program). */
|
|
5
|
-
export interface
|
|
6
|
+
export interface FrontAppSchema extends SchemaBase {
|
|
6
7
|
type: FrontType;
|
|
7
8
|
/** Source directory relative to project root, e.g. 'web-admin/'. */
|
|
8
9
|
dir: string;
|
|
10
|
+
/**
|
|
11
|
+
* Tenant table for this app. When set, all tables with a foreign key
|
|
12
|
+
* pointing to this table get automatic tenant scoping: the tenant PK
|
|
13
|
+
* value is injected from `user.id` into all curd operations.
|
|
14
|
+
*/
|
|
15
|
+
tenant?: TableSchema;
|
|
9
16
|
}
|
|
10
|
-
/** A backend API service. apps references shared
|
|
11
|
-
export interface
|
|
17
|
+
/** A backend API service. apps references shared FrontAppSchema instances. */
|
|
18
|
+
export interface ProjectApiSchema extends SchemaBase {
|
|
12
19
|
/** Source directory relative to project root, e.g. 'api/'. */
|
|
13
20
|
dir: string;
|
|
14
21
|
/** Frontends this API serves. Direct instance references (see defineProject). */
|
|
15
|
-
apps:
|
|
22
|
+
apps: FrontAppSchema[];
|
|
16
23
|
/** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
|
|
17
24
|
contextPath?: string;
|
|
25
|
+
/** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
|
|
26
|
+
baseUrl?: string;
|
|
18
27
|
}
|
|
19
28
|
/** A third-party system (e.g. wechat pay, unionpay). Owns its own
|
|
20
29
|
* implementation dir and contract (controller_types), just like an API,
|
|
21
30
|
* but is not part of this repo's served surface. */
|
|
22
|
-
export interface
|
|
31
|
+
export interface ThirdApiSchema extends SchemaBase {
|
|
23
32
|
/** Source directory relative to project root, e.g. 'wechat/'. */
|
|
24
33
|
dir: string;
|
|
25
34
|
}
|
|
26
35
|
export interface ProjectSchema extends SchemaBase {
|
|
27
|
-
apps:
|
|
28
|
-
apis:
|
|
29
|
-
thirdApis:
|
|
36
|
+
apps: FrontAppSchema[];
|
|
37
|
+
apis: ProjectApiSchema[];
|
|
38
|
+
thirdApis: ThirdApiSchema[];
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
|
-
* Defines the project topology.
|
|
41
|
+
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
33
42
|
* api.apps references the same instances from project.apps, so an app served
|
|
34
43
|
* by multiple APIs is defined once and referenced many times.
|
|
44
|
+
*
|
|
45
|
+
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
46
|
+
* integrity (same style as defineTable/defineCurd).
|
|
35
47
|
*/
|
|
36
48
|
export declare function defineProject(name: string, schema: {
|
|
37
49
|
description?: string;
|
|
38
|
-
apps:
|
|
39
|
-
apis:
|
|
40
|
-
thirdApis?:
|
|
50
|
+
apps: FrontAppSchema[];
|
|
51
|
+
apis: ProjectApiSchema[];
|
|
52
|
+
thirdApis?: ThirdApiSchema[];
|
|
41
53
|
}): ProjectSchema;
|
package/dist/project.js
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defineProject = defineProject;
|
|
4
1
|
/**
|
|
5
|
-
* Defines the project topology.
|
|
2
|
+
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
6
3
|
* api.apps references the same instances from project.apps, so an app served
|
|
7
4
|
* by multiple APIs is defined once and referenced many times.
|
|
5
|
+
*
|
|
6
|
+
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
7
|
+
* integrity (same style as defineTable/defineCurd).
|
|
8
8
|
*/
|
|
9
|
-
function defineProject(name, schema) {
|
|
10
|
-
|
|
9
|
+
export function defineProject(name, schema) {
|
|
10
|
+
const project = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
|
|
11
|
+
const appNames = new Set();
|
|
12
|
+
for (const app of project.apps) {
|
|
13
|
+
if (!app.name)
|
|
14
|
+
throw new Error(`project ${name}: app name is required`);
|
|
15
|
+
if (appNames.has(app.name))
|
|
16
|
+
throw new Error(`project ${name}: duplicate app name '${app.name}'`);
|
|
17
|
+
appNames.add(app.name);
|
|
18
|
+
if (app.type !== 'admin' && app.type !== 'wxmini') {
|
|
19
|
+
throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
|
|
20
|
+
}
|
|
21
|
+
if (!app.dir)
|
|
22
|
+
throw new Error(`project ${name}: app '${app.name}' dir is required`);
|
|
23
|
+
}
|
|
24
|
+
const apiNames = new Set();
|
|
25
|
+
for (const api of project.apis) {
|
|
26
|
+
if (!api.name)
|
|
27
|
+
throw new Error(`project ${name}: api name is required`);
|
|
28
|
+
if (apiNames.has(api.name))
|
|
29
|
+
throw new Error(`project ${name}: duplicate api name '${api.name}'`);
|
|
30
|
+
apiNames.add(api.name);
|
|
31
|
+
if (!api.dir)
|
|
32
|
+
throw new Error(`project ${name}: api '${api.name}' dir is required`);
|
|
33
|
+
for (const ref of api.apps) {
|
|
34
|
+
if (!project.apps.includes(ref)) {
|
|
35
|
+
throw new Error(`project ${name}: api '${api.name}' references app '${ref.name}' that is not a shared instance in project.apps (define once and reference it)`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const third of project.thirdApis) {
|
|
40
|
+
if (!third.name)
|
|
41
|
+
throw new Error(`project ${name}: thirdApi name is required`);
|
|
42
|
+
if (!third.dir)
|
|
43
|
+
throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
|
|
44
|
+
}
|
|
45
|
+
return project;
|
|
11
46
|
}
|