@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.
Files changed (108) hide show
  1. package/README.md +2 -1
  2. package/dist/action.d.ts +7 -0
  3. package/dist/action.js +3 -0
  4. package/dist/asset.d.ts +48 -0
  5. package/dist/asset.js +31 -0
  6. package/dist/bases.d.ts +6 -2
  7. package/dist/bases.js +10 -6
  8. package/dist/check-inheritance.js +1 -4
  9. package/dist/component.d.ts +20 -0
  10. package/dist/component.js +1 -0
  11. package/dist/convert.d.ts +9 -0
  12. package/dist/convert.js +3 -0
  13. package/dist/curd.d.ts +62 -0
  14. package/dist/curd.js +31 -0
  15. package/dist/db-config.d.ts +8 -0
  16. package/dist/db-config.js +1 -0
  17. package/dist/db.d.ts +56 -0
  18. package/dist/db.js +100 -0
  19. package/dist/dictionary.d.ts +26 -5
  20. package/dist/dictionary.js +21 -8
  21. package/dist/dsl.d.ts +10 -49
  22. package/dist/dsl.js +12 -103
  23. package/dist/dto.d.ts +12 -9
  24. package/dist/dto.js +25 -34
  25. package/dist/enum-driver.d.ts +1 -1
  26. package/dist/enum-driver.js +1 -4
  27. package/dist/event.d.ts +8 -0
  28. package/dist/event.js +3 -0
  29. package/dist/flow.d.ts +1 -1
  30. package/dist/flow.js +3 -8
  31. package/dist/import-base.d.ts +15 -0
  32. package/dist/import-base.js +1 -0
  33. package/dist/index.d.ts +31 -17
  34. package/dist/index.js +31 -33
  35. package/dist/mermaid-driver.d.ts +2 -2
  36. package/dist/mermaid-driver.js +4 -7
  37. package/dist/mock.d.ts +12 -0
  38. package/dist/mock.js +1 -0
  39. package/dist/mysql-driver.d.ts +5 -1
  40. package/dist/mysql-driver.js +25 -10
  41. package/dist/navigation.d.ts +22 -0
  42. package/dist/navigation.js +15 -0
  43. package/dist/page-def.d.ts +40 -0
  44. package/dist/page-def.js +38 -0
  45. package/dist/page-flow.d.ts +5 -3
  46. package/dist/page-flow.js +109 -18
  47. package/dist/page.d.ts +37 -15
  48. package/dist/page.js +23 -13
  49. package/dist/pattern.js +2 -6
  50. package/dist/patterns/retry.d.ts +1 -1
  51. package/dist/patterns/retry.js +2 -6
  52. package/dist/popup.d.ts +18 -0
  53. package/dist/popup.js +8 -0
  54. package/dist/project.d.ts +25 -13
  55. package/dist/project.js +41 -6
  56. package/dist/prototype.d.ts +1 -1
  57. package/dist/prototype.js +1 -4
  58. package/dist/provider.d.ts +54 -0
  59. package/dist/provider.js +18 -0
  60. package/dist/ref.d.ts +14 -0
  61. package/dist/ref.js +6 -0
  62. package/dist/route.d.ts +8 -0
  63. package/dist/route.js +3 -0
  64. package/dist/typebox-driver.d.ts +3 -3
  65. package/dist/typebox-driver.js +24 -26
  66. package/dist/utils.d.ts +2 -0
  67. package/dist/utils.js +5 -4
  68. package/docs/curd.md +111 -0
  69. package/docs/dictionary.md +42 -31
  70. package/docs/driver.md +42 -42
  71. package/docs/dto.md +6 -6
  72. package/docs/enum.md +24 -24
  73. package/docs/project.md +2 -2
  74. package/docs/table.md +53 -16
  75. package/package.json +5 -3
  76. package/src/action.ts +11 -0
  77. package/src/asset.ts +63 -0
  78. package/src/bases.ts +30 -20
  79. package/src/component.ts +22 -0
  80. package/src/convert.ts +13 -0
  81. package/src/curd.ts +94 -0
  82. package/src/db-config.ts +8 -0
  83. package/src/db.ts +153 -0
  84. package/src/dictionary.ts +45 -19
  85. package/src/dsl.ts +13 -106
  86. package/src/dto.ts +25 -12
  87. package/src/enum-driver.ts +42 -42
  88. package/src/event.ts +12 -0
  89. package/src/flow.ts +103 -103
  90. package/src/import-base.ts +15 -0
  91. package/src/index.ts +31 -17
  92. package/src/mermaid-driver.ts +5 -4
  93. package/src/mock.ts +12 -0
  94. package/src/mysql-driver.ts +25 -6
  95. package/src/navigation.ts +29 -0
  96. package/src/page-def.ts +80 -0
  97. package/src/page-flow.ts +117 -15
  98. package/src/page.ts +57 -20
  99. package/src/patterns/retry.ts +54 -54
  100. package/src/popup.ts +25 -0
  101. package/src/project.ts +57 -14
  102. package/src/prototype.ts +29 -29
  103. package/src/provider.ts +73 -0
  104. package/src/ref.ts +19 -0
  105. package/src/route.ts +12 -0
  106. package/src/typebox-driver.ts +192 -187
  107. package/src/utils.ts +11 -6
  108. package/src/check-inheritance.ts +0 -86
package/src/dsl.ts CHANGED
@@ -1,13 +1,23 @@
1
1
  // DSL field type definitions.
2
2
  // Shape: { type: <type name>, <extension fields> }
3
3
 
4
- import type { DictionaryEntry } from './dictionary';
4
+ import type { DictionaryEntry } from './dictionary.js';
5
+ import type { ImportBase } from './import-base.js';
6
+ import type { MockDescriptor } from './mock.js';
7
+
8
+ /** Field query comparison operators (search field semantics). */
9
+ export type Operator = 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ne';
5
10
 
6
11
  export interface SchemaBase {
7
12
  name: string;
8
13
  description?: string;
9
14
  }
10
15
 
16
+ /** Schema base for cross-file entities. importRef locates the generating module; inline schemas omit it. */
17
+ export interface ImportableSchemaBase extends SchemaBase {
18
+ importRef?: ImportBase;
19
+ }
20
+
11
21
  /** Field collection schemas (DB table vs DTO message); `type` is the discriminator */
12
22
  export interface CollectionSchemaBase extends SchemaBase {
13
23
  type: string;
@@ -19,13 +29,13 @@ export interface BaseField {
19
29
  label?: string;
20
30
  /** 字段描述 */
21
31
  description?: string;
22
- /** 业务语义码(如 'merchant_name'),驱动 mock 生成等下游消费 */
23
- semantic?: string;
24
32
  optional?: boolean;
25
33
  readOnly?: boolean;
26
34
  default?: string;
27
35
  /** 所属 schema(db 或 dto) */
28
36
  schema?: CollectionSchemaBase;
37
+ /** Pure-data mock descriptor for test data generation */
38
+ mock?: MockDescriptor;
29
39
  }
30
40
 
31
41
  interface StringField extends BaseField {
@@ -128,74 +138,6 @@ export type Field =
128
138
  | EnumField
129
139
  | JsonField;
130
140
 
131
- export type Index = {
132
- name?: string;
133
- fields: Field | Field[];
134
- unique?: boolean;
135
- };
136
-
137
- export type ForeignKey = {
138
- fields: Field | Field[];
139
- references: Field | Field[];
140
- };
141
-
142
- export interface TableSchemaOptions {
143
- description?: string;
144
- paginated?: boolean;
145
- actor?: boolean;
146
- generator?: string;
147
- autoIncrement?: Field;
148
- primaryKey?: Field | Field[];
149
- indexes?: Index[];
150
- foreignKeys?: Record<string, ForeignKey>;
151
- /** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
152
- phrase?: DictionaryEntry;
153
- fields: Record<string, Field>;
154
- }
155
-
156
- export class TableSchema implements CollectionSchemaBase {
157
- type = 'table';
158
- name: string;
159
- description?: string;
160
- /** 分页 */
161
- paginated?: boolean;
162
- /** 系统操作者(如小程序为 C 端用户,管理端为运营) */
163
- actor?: boolean;
164
- /** id 生成器 */
165
- generator?: string;
166
- /** 自增主键字段 */
167
- autoIncrement?: Field;
168
- primaryKey?: Field | Field[];
169
- indexes?: Index[];
170
- /** 外键,引用其他表的字段 */
171
- foreignKeys?: Record<string, ForeignKey>;
172
- /** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
173
- phrase?: DictionaryEntry;
174
- fields: Record<string, Field>;
175
-
176
- constructor(name: string, options: TableSchemaOptions) {
177
- this.name = name;
178
- this.description = options.description;
179
- this.paginated = options.paginated;
180
- this.actor = options.actor;
181
- this.generator = options.generator;
182
- this.autoIncrement = options.autoIncrement;
183
- this.primaryKey = options.primaryKey;
184
- this.indexes = options.indexes;
185
- this.foreignKeys = options.foreignKeys;
186
- this.phrase = options.phrase;
187
- this.fields = options.fields;
188
- }
189
-
190
- /** True when the field is part of this table's primary key */
191
- isPk(fieldRef: Field): boolean {
192
- if (this.primaryKey === undefined) return false;
193
- return Array.isArray(this.primaryKey)
194
- ? this.primaryKey.includes(fieldRef)
195
- : this.primaryKey === fieldRef;
196
- }
197
- }
198
-
199
141
  // Field builders: type and jsType are fixed, pass extra properties only.
200
142
  // The field name is written back from the map key later (see defineTable).
201
143
 
@@ -244,39 +186,4 @@ export function jsonField(extra: FieldExtras<JsonField> = {}): JsonField {
244
186
  export function enumField(extra: Omit<EnumField, 'name' | 'type' | 'jsType'>): EnumField {
245
187
  const jsType = extra.enum.valueType === 'integer' ? 'number' : 'string';
246
188
  return { name: '', type: 'enum', jsType, ...extra };
247
- }
248
-
249
- export function defineTable(name: string, schema: TableSchemaOptions): TableSchema {
250
- const table = new TableSchema(name, schema);
251
- for (const key of Object.keys(table.fields)) {
252
- const field = table.fields[key];
253
- if (field.schema && field.schema !== table) {
254
- throw new Error(
255
- `field ${key}: belongs to table ${field.schema.name}, cannot reuse in table ${table.name}`,
256
- );
257
- }
258
- }
259
- for (const key of Object.keys(table.fields)) {
260
- table.fields[key].name = key;
261
- table.fields[key].schema = table;
262
- }
263
- for (const [fkName, fk] of Object.entries(table.foreignKeys ?? {})) {
264
- const refs = Array.isArray(fk.references) ? fk.references : [fk.references];
265
- const fields = Array.isArray(fk.fields) ? fk.fields : [fk.fields];
266
- for (let i = 0; i < refs.length; i++) {
267
- const ref = refs[i];
268
- if (!ref.schema) throw new Error(`foreign key ${fkName}: references field has no schema`);
269
- if (ref.schema === table) throw new Error(`foreign key ${fkName}: cannot reference own table ${table.name}`);
270
- const phrase = (ref.schema as TableSchema).phrase;
271
- if (!phrase) throw new Error(`foreign key ${fkName}: referenced table ${ref.schema.name} has no phrase, cannot check field naming`);
272
- const expected = `${phrase.name}_${ref.name}`;
273
- const fkField = fields[i];
274
- if (fkField.name !== expected) {
275
- throw new Error(
276
- `foreign key ${fkName}: field must be named ${expected} (phrase ${phrase.name} + ${ref.name}), got ${fkField.name}`,
277
- );
278
- }
279
- }
280
- }
281
- return table;
282
189
  }
package/src/dto.ts CHANGED
@@ -1,22 +1,27 @@
1
- import { BaseField, CollectionSchemaBase, Field, SchemaBase, TableSchema } from './dsl';
2
- import { toCamelCase } from './utils';
1
+ import { BaseField, CollectionSchemaBase, Field, Operator, SchemaBase } from './dsl.js';
2
+ import { TableSchema } from './db.js';
3
+ import type { ImportBase } from './import-base.js';
4
+ import { toCamelCase } from './utils.js';
3
5
 
4
6
  // Interface (DTO) field definitions.
5
7
  // Naming convention: all DTO types and builders use the Dto prefix.
6
8
  // A DtoField stores the database Field and the API-only extras separately.
7
9
 
8
- export type Operator = 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ne';
10
+ /** Re-export Operator lives on the DSL level (see dsl.ts). */
11
+ export type { Operator } from './dsl.js';
12
+
13
+ /** Re-export — ImportBase lives on its own module (see import-base.ts). */
14
+ export type { ImportBase } from './import-base.js';
15
+
16
+ /** Re-export — MockDescriptor lives on its own module (see mock.ts). */
17
+ export type { MockDescriptor } from './mock.js';
9
18
 
10
19
  /**
11
20
  * Reference to an existing TypeBox base schema by its import location.
12
21
  * Serializable metadata: the driver renders `import { name } from 'from'`
13
22
  * and `Type.Intersect([name, ...])` — the runtime schema is never loaded by the DSL.
14
23
  */
15
- export interface ImportRef {
16
- /** Module specifier, e.g. '@pylonts/core' */
17
- from: string;
18
- /** Named export, e.g. 'PageRequest' */
19
- name: string;
24
+ export interface ImportRef extends ImportBase {
20
25
  /**
21
26
  * Generic type arguments for the base schema (e.g. PageResult(OrderRow)).
22
27
  * Two forms, both local DTOs:
@@ -45,8 +50,8 @@ export class DtoField implements SchemaBase {
45
50
  name: string;
46
51
  /** 字段描述 */
47
52
  description?: string;
48
- /** 所属 DTO 容器(buildMessage 反写) */
49
- schema?: DtoMessage;
53
+ /** 所属容器(buildMessage / defineRouteData / definePageData 反写) */
54
+ schema?: CollectionSchemaBase;
50
55
  field: Field | DtoArrayFieldDef | DtoObjectFieldDef;
51
56
  pattern?: string;
52
57
  optional?: boolean;
@@ -170,6 +175,13 @@ function buildMessage(name: string, direction: DtoDirection, fields: Record<stri
170
175
  // are created per DTO, never shared).
171
176
  for (const key of Object.keys(message.fields)) {
172
177
  const df = message.fields[key];
178
+ if (!(df instanceof DtoField)) {
179
+ const got = df === null || df === undefined ? String(df) : `${(df as object).constructor.name ?? typeof df}`;
180
+ throw new Error(
181
+ `[dto] DTO "${name}" field "${key}" must be a DtoField (created with dtoField()/dtoArrayField()/dtoObjectField()), got ${got}. ` +
182
+ `Did you pass enumField(...) directly? Use dtoField(enumField({...})) instead.`
183
+ );
184
+ }
173
185
  df.name = key;
174
186
  df.schema = message;
175
187
  // Custom (inline) fields are owned by this DTO: write back name + schema.
@@ -186,13 +198,14 @@ function buildMessage(name: string, direction: DtoDirection, fields: Record<stri
186
198
  export function buildInput(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage {
187
199
  const message = buildMessage(name, DtoDirection.Input, fields, description);
188
200
  // Rule A — set optionality from the DB column rule (skips fields the author
189
- // already set): nullable / default / auto-increment → optional, else required.
201
+ // already set): nullable / default → optional, else required.
202
+ // PK columns are always required.
190
203
  for (const field of Object.values(message.fields)) {
191
204
  if (field.optional !== undefined) continue;
192
205
  const f = field.field as Field;
193
206
  if (f.schema?.type !== 'table') continue;
194
207
  const table = f.schema as TableSchema;
195
- field.optional = f.optional !== false || f.default !== undefined || table.autoIncrement === f;
208
+ field.optional = table.isPk(f) ? false : f.optional !== false || f.default !== undefined;
196
209
  }
197
210
  return message;
198
211
  }
@@ -1,43 +1,43 @@
1
- import { EnumDef } from './dsl';
2
-
3
- // Enum driver: renders an EnumDef into a standalone TypeScript enum file.
4
- // Shape matches the generated-enum product consumed by the TypeBox driver (Type.Enum):
5
- //
6
- // export enum UserStatus {
7
- // ACTIVE = 'ACTIVE',
8
- // DISABLED = 'DISABLED',
9
- // }
10
- //
11
- // export const USER_STATUS_LABEL: Record<UserStatus, string> = {
12
- // [UserStatus.ACTIVE]: '启用',
13
- // [UserStatus.DISABLED]: '禁用',
14
- // };
15
-
16
- function renderString(s: string): string {
17
- return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
18
- }
19
-
20
- function renderValue(value: string | number): string {
21
- return typeof value === 'number' ? String(value) : renderString(value);
22
- }
23
-
24
- /** 'UserStatus' → 'USER_STATUS_LABEL' (matches the label map naming convention) */
25
- function labelName(jsName: string): string {
26
- return `${jsName.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase()}_LABEL`;
27
- }
28
-
29
- export function renderEnum(def: EnumDef): string {
30
- const name = def.jsName;
31
- const members = def.values.map((v) => ` ${v.symbol} = ${renderValue(v.value)},`);
32
- const labels = def.values.map((v) => ` [${name}.${v.symbol}]: ${renderString(v.label)},`);
33
- return [
34
- `export enum ${name} {`,
35
- ...members,
36
- '}',
37
- '',
38
- `export const ${labelName(name)}: Record<${name}, string> = {`,
39
- ...labels,
40
- '};',
41
- '',
42
- ].join('\n');
1
+ import { EnumDef } from './dsl.js';
2
+
3
+ // Enum driver: renders an EnumDef into a standalone TypeScript enum file.
4
+ // Shape matches the generated-enum product consumed by the TypeBox driver (Type.Enum):
5
+ //
6
+ // export enum UserStatus {
7
+ // ACTIVE = 'ACTIVE',
8
+ // DISABLED = 'DISABLED',
9
+ // }
10
+ //
11
+ // export const USER_STATUS_LABEL: Record<UserStatus, string> = {
12
+ // [UserStatus.ACTIVE]: '启用',
13
+ // [UserStatus.DISABLED]: '禁用',
14
+ // };
15
+
16
+ function renderString(s: string): string {
17
+ return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
18
+ }
19
+
20
+ function renderValue(value: string | number): string {
21
+ return typeof value === 'number' ? String(value) : renderString(value);
22
+ }
23
+
24
+ /** 'UserStatus' → 'USER_STATUS_LABEL' (matches the label map naming convention) */
25
+ function labelName(jsName: string): string {
26
+ return `${jsName.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase()}_LABEL`;
27
+ }
28
+
29
+ export function renderEnum(def: EnumDef): string {
30
+ const name = def.jsName;
31
+ const members = def.values.map((v) => ` ${v.symbol} = ${renderValue(v.value)},`);
32
+ const labels = def.values.map((v) => ` [${name}.${v.symbol}]: ${renderString(v.label)},`);
33
+ return [
34
+ `export enum ${name} {`,
35
+ ...members,
36
+ '}',
37
+ '',
38
+ `export const ${labelName(name)}: Record<${name}, string> = {`,
39
+ ...labels,
40
+ '};',
41
+ '',
42
+ ].join('\n');
43
43
  }
package/src/event.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { CollectionSchemaBase } from './dsl.js';
2
+ import type { DtoField } from './dto.js';
3
+
4
+ /** Data carried by a component event (e.g. e.detail from a Tab onChange). */
5
+ export interface EventDataSchema extends CollectionSchemaBase {
6
+ type: 'event';
7
+ fields: Record<string, DtoField>;
8
+ }
9
+
10
+ export function defineEventData(name: string, fields: Record<string, DtoField>): EventDataSchema {
11
+ return { name, type: 'event', fields };
12
+ }
package/src/flow.ts CHANGED
@@ -1,104 +1,104 @@
1
- import { SchemaBase } from './dsl';
2
-
3
- // Flow model: nodes and edges are separate entities. A FlowNode is a plain
4
- // named step; a FlowEdge references its start/end node objects directly (no
5
- // ids). Because a node object may appear in many edges, the graph can share
6
- // nodes, merge branches, and form cycles (e.g. poll-and-retry loops).
7
-
8
- export interface FlowNode extends SchemaBase {
9
- /** Optional sub-flow. When present, entering this node runs the sub-flow;
10
- * after the sub-flow reaches any terminal node, the outer flow continues
11
- * via this node's outgoing edges. Sub-flows nest recursively. */
12
- flow?: FlowSchema;
13
- }
14
-
15
- export interface FlowEdge extends SchemaBase {
16
- /** Trigger condition; undefined = default path (success/normal). */
17
- when?: string;
18
- start: FlowNode;
19
- end: FlowNode;
20
- }
21
-
22
- export interface FlowSchema extends SchemaBase {
23
- /** Entry node. */
24
- start: FlowNode;
25
- /** All nodes, collected from edges (deduplicated by object identity). */
26
- nodes: FlowNode[];
27
- /** Independent edges; a node may be start of many edges, so cycles are expressible. */
28
- edges: FlowEdge[];
29
- }
30
-
31
- export function node(name: string, flow?: FlowSchema, description?: string): FlowNode {
32
- return { name, flow, description };
33
- }
34
-
35
- export function edge(start: FlowNode, end: FlowNode, when?: string, description?: string): FlowEdge {
36
- // Auto name for uniformity with SchemaBase; `when` stays the branch marker.
37
- return { name: `${start.name}->${end.name}`, start, end, when, description };
38
- }
39
-
40
- export function defineFlow(
41
- name: string,
42
- schema: {
43
- start: FlowNode;
44
- edges: FlowEdge[];
45
- description?: string;
46
- },
47
- ): FlowSchema {
48
- const seen = new Set<FlowNode>();
49
- const nodes: FlowNode[] = [];
50
- for (const e of schema.edges) {
51
- for (const n of [e.start, e.end]) {
52
- if (!seen.has(n)) {
53
- seen.add(n);
54
- nodes.push(n);
55
- }
56
- }
57
- }
58
- if (!seen.has(schema.start)) {
59
- seen.add(schema.start);
60
- nodes.push(schema.start);
61
- }
62
- const flow: FlowSchema = { name, description: schema.description, start: schema.start, nodes, edges: schema.edges };
63
- validate(flow);
64
- return flow;
65
- }
66
-
67
- // Nodes that never appear as an edge start are terminals. Reverse BFS from all
68
- // terminals marks every node that can reach a terminal; unmarked nodes sit on
69
- // a path that never ends (e.g. a cycle without an exit) — reject them at
70
- // definition time.
71
- function validate(schema: FlowSchema): void {
72
- const starts = new Set<FlowNode>();
73
- for (const e of schema.edges) starts.add(e.start);
74
-
75
- const reverse = new Map<FlowNode, FlowNode[]>();
76
- for (const n of schema.nodes) reverse.set(n, []);
77
- for (const e of schema.edges) {
78
- reverse.get(e.end)!.push(e.start);
79
- }
80
-
81
- const reached = new Set<FlowNode>();
82
- const queue: FlowNode[] = [];
83
- for (const n of schema.nodes) {
84
- if (!starts.has(n)) {
85
- reached.add(n);
86
- queue.push(n);
87
- }
88
- }
89
- while (queue.length > 0) {
90
- const cur = queue.shift()!;
91
- for (const prev of reverse.get(cur)!) {
92
- if (!reached.has(prev)) {
93
- reached.add(prev);
94
- queue.push(prev);
95
- }
96
- }
97
- }
98
-
99
- for (const n of schema.nodes) {
100
- if (!reached.has(n)) {
101
- throw new Error(`flow ${schema.name}: node "${n.name}" cannot reach a terminal`);
102
- }
103
- }
1
+ import { SchemaBase } from './dsl.js';
2
+
3
+ // Flow model: nodes and edges are separate entities. A FlowNode is a plain
4
+ // named step; a FlowEdge references its start/end node objects directly (no
5
+ // ids). Because a node object may appear in many edges, the graph can share
6
+ // nodes, merge branches, and form cycles (e.g. poll-and-retry loops).
7
+
8
+ export interface FlowNode extends SchemaBase {
9
+ /** Optional sub-flow. When present, entering this node runs the sub-flow;
10
+ * after the sub-flow reaches any terminal node, the outer flow continues
11
+ * via this node's outgoing edges. Sub-flows nest recursively. */
12
+ flow?: FlowSchema;
13
+ }
14
+
15
+ export interface FlowEdge extends SchemaBase {
16
+ /** Trigger condition; undefined = default path (success/normal). */
17
+ when?: string;
18
+ start: FlowNode;
19
+ end: FlowNode;
20
+ }
21
+
22
+ export interface FlowSchema extends SchemaBase {
23
+ /** Entry node. */
24
+ start: FlowNode;
25
+ /** All nodes, collected from edges (deduplicated by object identity). */
26
+ nodes: FlowNode[];
27
+ /** Independent edges; a node may be start of many edges, so cycles are expressible. */
28
+ edges: FlowEdge[];
29
+ }
30
+
31
+ export function node(name: string, flow?: FlowSchema, description?: string): FlowNode {
32
+ return { name, flow, description };
33
+ }
34
+
35
+ export function edge(start: FlowNode, end: FlowNode, when?: string, description?: string): FlowEdge {
36
+ // Auto name for uniformity with SchemaBase; `when` stays the branch marker.
37
+ return { name: `${start.name}->${end.name}`, start, end, when, description };
38
+ }
39
+
40
+ export function defineFlow(
41
+ name: string,
42
+ schema: {
43
+ start: FlowNode;
44
+ edges: FlowEdge[];
45
+ description?: string;
46
+ },
47
+ ): FlowSchema {
48
+ const seen = new Set<FlowNode>();
49
+ const nodes: FlowNode[] = [];
50
+ for (const e of schema.edges) {
51
+ for (const n of [e.start, e.end]) {
52
+ if (!seen.has(n)) {
53
+ seen.add(n);
54
+ nodes.push(n);
55
+ }
56
+ }
57
+ }
58
+ if (!seen.has(schema.start)) {
59
+ seen.add(schema.start);
60
+ nodes.push(schema.start);
61
+ }
62
+ const flow: FlowSchema = { name, description: schema.description, start: schema.start, nodes, edges: schema.edges };
63
+ validate(flow);
64
+ return flow;
65
+ }
66
+
67
+ // Nodes that never appear as an edge start are terminals. Reverse BFS from all
68
+ // terminals marks every node that can reach a terminal; unmarked nodes sit on
69
+ // a path that never ends (e.g. a cycle without an exit) — reject them at
70
+ // definition time.
71
+ function validate(schema: FlowSchema): void {
72
+ const starts = new Set<FlowNode>();
73
+ for (const e of schema.edges) starts.add(e.start);
74
+
75
+ const reverse = new Map<FlowNode, FlowNode[]>();
76
+ for (const n of schema.nodes) reverse.set(n, []);
77
+ for (const e of schema.edges) {
78
+ reverse.get(e.end)!.push(e.start);
79
+ }
80
+
81
+ const reached = new Set<FlowNode>();
82
+ const queue: FlowNode[] = [];
83
+ for (const n of schema.nodes) {
84
+ if (!starts.has(n)) {
85
+ reached.add(n);
86
+ queue.push(n);
87
+ }
88
+ }
89
+ while (queue.length > 0) {
90
+ const cur = queue.shift()!;
91
+ for (const prev of reverse.get(cur)!) {
92
+ if (!reached.has(prev)) {
93
+ reached.add(prev);
94
+ queue.push(prev);
95
+ }
96
+ }
97
+ }
98
+
99
+ for (const n of schema.nodes) {
100
+ if (!reached.has(n)) {
101
+ throw new Error(`flow ${schema.name}: node "${n.name}" cannot reach a terminal`);
102
+ }
103
+ }
104
104
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reference to a named export from another module, by its import location.
3
+ * Serializable metadata: the driver renders `import { name } from 'from'`.
4
+ * Used for enum types and plain imports that need no generic arguments.
5
+ */
6
+ export interface ImportBase {
7
+ /** Module specifier, e.g. '@pylonts/core' */
8
+ from: string;
9
+ /** Named export, e.g. 'PageRequest' */
10
+ name: string;
11
+ /** Type-only import (`import type`); defaults to a value import. */
12
+ type?: boolean;
13
+ /** Default import (`import X from '...'`) instead of named (`import { X }`). */
14
+ default?: boolean;
15
+ }
package/src/index.ts CHANGED
@@ -1,17 +1,31 @@
1
- export * from './dsl';
2
- export * from './dto';
3
- export * from './bases';
4
- export * from './utils';
5
- export * from './project';
6
- export * from './prototype';
7
- export * from './dictionary';
8
- export * from './mysql-driver';
9
- export * from './enum-driver';
10
- export * from './typebox-driver';
11
- export * from './check-inheritance';
12
- export * from './pattern';
13
- export * from './patterns/retry';
14
- export * from './flow';
15
- export * from './page';
16
- export * from './page-flow';
17
- export * from './mermaid-driver';
1
+ export * from './dsl.js';
2
+ export * from './db.js';
3
+ export * from './mock.js';
4
+ export * from './asset.js';
5
+ export * from './dto.js';
6
+ export * from './db-config.js';
7
+ export * from './bases.js';
8
+ export * from './utils.js';
9
+ export * from './project.js';
10
+ export * from './prototype.js';
11
+ export * from './dictionary.js';
12
+ export * from './mysql-driver.js';
13
+ export * from './enum-driver.js';
14
+ export * from './typebox-driver.js';
15
+ export * from './pattern.js';
16
+ export * from './patterns/retry.js';
17
+ export * from './flow.js';
18
+ export * from './action.js';
19
+ export * from './event.js';
20
+ export * from './component.js';
21
+ export * from './convert.js';
22
+ export * from './ref.js';
23
+ export * from './route.js';
24
+ export * from './page.js';
25
+ export * from './curd.js';
26
+ export * from './page-flow.js';
27
+ export * from './provider.js';
28
+ export * from './page-def.js';
29
+ export * from './mermaid-driver.js';
30
+ export * from './navigation.js';
31
+ export * from './popup.js';
@@ -1,6 +1,6 @@
1
- import { FlowEdge, FlowNode, FlowSchema } from './flow';
2
- import { Page } from './page';
3
- import { PageEdge, PageFlow } from './page-flow';
1
+ import { FlowEdge, FlowNode, FlowSchema } from './flow.js';
2
+ import { Page } from './page.js';
3
+ import { PageEdge, PageFlow } from './page-flow.js';
4
4
 
5
5
  // Mermaid driver: converts a FlowSchema into a Mermaid flowchart (TD).
6
6
  // Node ids are hierarchical (n0, n0_0, n0_0_0, ...) so nested sub-flows stay
@@ -66,7 +66,8 @@ export function renderPageFlowMermaid(schema: PageFlow): string {
66
66
  lines.push(` subgraph app${appIdx}["${escapeLabel(appName)}"]`);
67
67
  pages.forEach((p, i) => ids.set(p, `app${appIdx}_p${i}`));
68
68
  for (const p of pages) {
69
- lines.push(` ${ids.get(p)}["${escapeLabel(p.name)}"]`);
69
+ const display = p.label ?? p.name;
70
+ lines.push(` ${ids.get(p)}["${escapeLabel(display)}"]`);
70
71
  }
71
72
  appIdx++;
72
73
  lines.push(' end');
package/src/mock.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pure-data mock descriptor. Stored on DTO fields, consumed by pylonts lint mock
3
+ * or MockRegistry.resolve. No function references — serializable and gen-readable.
4
+ *
5
+ * @example { fn: 'abc', count: 20 }
6
+ * @example { fn: 'amt', min: 10, max: 500 }
7
+ * @example { fn: 'phone' }
8
+ */
9
+ export interface MockDescriptor {
10
+ fn: string;
11
+ [args: string]: unknown;
12
+ }