@pylonts/dsl 1.1.4 → 1.1.6
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/controller.d.ts +27 -0
- package/dist/controller.js +11 -0
- package/dist/convert.d.ts +18 -6
- package/dist/convert.js +12 -2
- package/dist/curd.d.ts +0 -2
- package/dist/curd.js +7 -0
- package/dist/dao.d.ts +111 -2
- package/dist/dao.js +28 -2
- package/dist/db.js +3 -0
- package/dist/dsl.d.ts +24 -1
- package/dist/dsl.js +16 -0
- package/dist/dto.d.ts +6 -2
- package/dist/dto.js +20 -9
- package/dist/endpoint.d.ts +15 -0
- package/dist/endpoint.js +3 -0
- package/dist/exception.d.ts +14 -0
- package/dist/exception.js +9 -0
- package/dist/field-rule.d.ts +20 -0
- package/dist/field-rule.js +19 -0
- package/dist/flow.d.ts +24 -2
- package/dist/flow.js +16 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +9 -0
- package/dist/mermaid-driver.js +32 -3
- package/dist/method.d.ts +11 -0
- package/dist/method.js +3 -0
- package/dist/mysql-driver.js +8 -1
- package/dist/provider.d.ts +6 -11
- package/dist/provider.js +2 -2
- package/dist/service.d.ts +16 -6
- package/dist/service.js +13 -2
- package/dist/third-service.d.ts +75 -0
- package/dist/third-service.js +96 -0
- package/dist/typebox-driver.d.ts +6 -0
- package/dist/typebox-driver.js +76 -14
- package/dist/utils.d.ts +25 -10
- package/dist/utils.js +28 -11
- package/docs/curd.md +110 -110
- package/docs/dto.md +9 -2
- package/docs/table.md +135 -135
- package/docs/third-service.md +122 -0
- package/package.json +4 -1
- package/src/action.ts +10 -10
- package/src/asset.ts +62 -62
- package/src/bases.ts +29 -29
- package/src/component.ts +21 -21
- package/src/controller.ts +40 -0
- package/src/convert.ts +34 -7
- package/src/curd.ts +7 -2
- package/src/dao.ts +162 -3
- package/src/db.ts +5 -0
- package/src/dsl.ts +49 -1
- package/src/dto.ts +25 -9
- package/src/endpoint.ts +18 -0
- package/src/event.ts +12 -12
- package/src/exception.ts +28 -0
- package/src/field-rule.ts +47 -0
- package/src/flow.ts +143 -103
- package/src/index.ts +11 -1
- package/src/mermaid-driver.ts +31 -3
- package/src/method.ts +20 -0
- package/src/mock.ts +12 -12
- package/src/mysql-driver.ts +8 -2
- package/src/navigation.ts +28 -28
- package/src/page-def.ts +79 -79
- package/src/page-flow.ts +153 -153
- package/src/page.ts +76 -76
- package/src/popup.ts +25 -25
- package/src/project.ts +97 -97
- package/src/provider.ts +7 -12
- package/src/ref.ts +18 -18
- package/src/route.ts +11 -11
- package/src/service.ts +28 -6
- package/src/third-service.ts +186 -0
- package/src/typebox-driver.ts +264 -192
- package/src/utils.ts +54 -17
- package/dist/check-inheritance.d.ts +0 -9
- package/dist/check-inheritance.js +0 -58
package/src/dto.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseField, CollectionSchemaBase, Field, Operator, SchemaBase } from './dsl.js';
|
|
2
2
|
import { TableSchema } from './db.js';
|
|
3
3
|
import type { ImportBase } from './import-base.js';
|
|
4
|
-
import {
|
|
4
|
+
import type { ThirdMethodSchema } from './third-service.js';
|
|
5
|
+
import { toCamelCase } from '@pylonts/core';
|
|
5
6
|
|
|
6
7
|
// Interface (DTO) field definitions.
|
|
7
8
|
// Naming convention: all DTO types and builders use the Dto prefix.
|
|
@@ -227,22 +228,37 @@ export function buildPk(name: string, fields: Record<string, DtoField>, descript
|
|
|
227
228
|
for (const field of Object.values(message.fields)) {
|
|
228
229
|
if (field.optional !== undefined) continue;
|
|
229
230
|
const f = field.field as Field;
|
|
230
|
-
const table = f.schema as TableSchema
|
|
231
|
+
const table = f.schema?.type === 'table' ? (f.schema as TableSchema) : undefined;
|
|
231
232
|
field.optional = table !== undefined && table.isPk(f) ? false : true;
|
|
232
233
|
}
|
|
233
234
|
return message;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
/**
|
|
237
|
-
export
|
|
237
|
+
/** Field-collection source a DTO can project from: a DB table or a third-party method message. */
|
|
238
|
+
export type DtoFieldSource = TableSchema | ThirdMethodSchema;
|
|
239
|
+
|
|
240
|
+
// Type guard instead of a plain discriminant check: TableSchema.type is declared
|
|
241
|
+
// as a class property, so TS widens it to string and cannot narrow the union.
|
|
242
|
+
function isThirdMethod(source: DtoFieldSource): source is ThirdMethodSchema {
|
|
243
|
+
return source.type === 'thirdMethod';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function ownsField(source: DtoFieldSource, field: Field): boolean {
|
|
247
|
+
if (isThirdMethod(source)) return Object.values(source.fields).includes(field);
|
|
248
|
+
return Object.values(source.columns).includes(field);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Project fields from a field-collection source (table or third-party method
|
|
252
|
+
* message) and wrap them as DTO fields (aligned with dto.from). */
|
|
253
|
+
export function from(source: DtoFieldSource, fields: Field[]): Record<string, DtoField> {
|
|
238
254
|
const out: Record<string, DtoField> = {};
|
|
239
255
|
for (const field of fields) {
|
|
240
|
-
if (field
|
|
241
|
-
throw new Error(`dto.from(${
|
|
256
|
+
if (!ownsField(source, field)) {
|
|
257
|
+
throw new Error(`dto.from(${source.name}): field ${field.name} does not belong to this ${source.type}`);
|
|
242
258
|
}
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
out[toCamelCase(field.name)] = dtoField(field);
|
|
259
|
+
// DB columns map to camelCase interface names (mer_id → merId); wire-format
|
|
260
|
+
// names are protocol names themselves and stay untouched.
|
|
261
|
+
out[isThirdMethod(source) ? field.name : toCamelCase(field.name)] = dtoField(field);
|
|
246
262
|
}
|
|
247
263
|
return out;
|
|
248
264
|
}
|
package/src/endpoint.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoMessage } from './dto.js';
|
|
3
|
+
|
|
4
|
+
/** Shared API call signature: one request DTO in, one response shape out.
|
|
5
|
+
* Referenced by both backend controller methods and frontend page providers —
|
|
6
|
+
* both sides must use the exact same instance, so drift is impossible. */
|
|
7
|
+
export interface EndpointSchema extends SchemaBase {
|
|
8
|
+
type: 'endpoint';
|
|
9
|
+
args: DtoMessage;
|
|
10
|
+
results: DtoMessage | number | boolean | string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function defineEndpoint(
|
|
14
|
+
name: string,
|
|
15
|
+
options: { args: DtoMessage; results: DtoMessage | number | boolean | string; description?: string },
|
|
16
|
+
): EndpointSchema {
|
|
17
|
+
return { type: 'endpoint', name, args: options.args, results: options.results, description: options.description };
|
|
18
|
+
}
|
package/src/event.ts
CHANGED
|
@@ -1,12 +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
|
-
}
|
|
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/exception.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { ImportBase } from './import-base.js';
|
|
3
|
+
|
|
4
|
+
// Exception declarations (logic-flatten-spec §5.3): each method declares the
|
|
5
|
+
// exceptions it throws, with retryability. The runtime class is referenced by
|
|
6
|
+
// import location — never loaded by the DSL.
|
|
7
|
+
|
|
8
|
+
/** Describes an exception a method can throw. */
|
|
9
|
+
export interface ExceptionSchema extends SchemaBase, Omit<ImportBase, 'type'> {
|
|
10
|
+
type: 'exception';
|
|
11
|
+
/** Whether the caller may safely retry after catching this exception. */
|
|
12
|
+
retryable: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function defineException(options: {
|
|
16
|
+
name: string;
|
|
17
|
+
from: string;
|
|
18
|
+
retryable: boolean;
|
|
19
|
+
description?: string;
|
|
20
|
+
}): ExceptionSchema {
|
|
21
|
+
return {
|
|
22
|
+
type: 'exception',
|
|
23
|
+
name: options.name,
|
|
24
|
+
from: options.from,
|
|
25
|
+
retryable: options.retryable,
|
|
26
|
+
description: options.description,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
|
|
3
|
+
// Field rules: the third semantic dimension of a Field. A rule is a named
|
|
4
|
+
// transformation between two ends — scale (fen↔yuan), encrypt/decrypt,
|
|
5
|
+
// mask/unmask. The rule owns both ends; a binding says which field plays
|
|
6
|
+
// which end. Rules are unique per semantic name (checked at definition).
|
|
7
|
+
|
|
8
|
+
/** One end of a field rule (e.g. 'fen' / 'yuan', 'plain' / 'cipher', 'raw' / 'masked'). */
|
|
9
|
+
export interface FieldRuleEnd {
|
|
10
|
+
/** End name — written back by defineFieldRule from the ends map key. */
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A semantic transformation rule between two field representations. */
|
|
16
|
+
export interface FieldRuleSchema extends SchemaBase {
|
|
17
|
+
type: 'fieldRule';
|
|
18
|
+
/** Rule name — the semantic uniqueness key. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** The two ends of the rule, keyed by end name. */
|
|
21
|
+
ends: Record<string, FieldRuleEnd>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One rule per semantic name. Defining the same name twice throws. */
|
|
25
|
+
const ruleRegistry = new Map<string, FieldRuleSchema>();
|
|
26
|
+
|
|
27
|
+
export function defineFieldRule(options: {
|
|
28
|
+
name: string;
|
|
29
|
+
ends: Record<string, Omit<FieldRuleEnd, 'name'>>;
|
|
30
|
+
description?: string;
|
|
31
|
+
}): FieldRuleSchema {
|
|
32
|
+
if (ruleRegistry.has(options.name)) {
|
|
33
|
+
throw new Error(`fieldRule '${options.name}' is already defined — one rule per semantic`);
|
|
34
|
+
}
|
|
35
|
+
const ends: Record<string, FieldRuleEnd> = {};
|
|
36
|
+
for (const key of Object.keys(options.ends)) {
|
|
37
|
+
ends[key] = { name: key, description: options.ends[key].description };
|
|
38
|
+
}
|
|
39
|
+
const rule: FieldRuleSchema = {
|
|
40
|
+
type: 'fieldRule',
|
|
41
|
+
name: options.name,
|
|
42
|
+
description: options.description,
|
|
43
|
+
ends,
|
|
44
|
+
};
|
|
45
|
+
ruleRegistry.set(options.name, rule);
|
|
46
|
+
return rule;
|
|
47
|
+
}
|
package/src/flow.ts
CHANGED
|
@@ -1,104 +1,144 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for (const
|
|
92
|
-
if (!
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { MethodSchema } from './method.js';
|
|
3
|
+
import type { ConvertSchema } from './convert.js';
|
|
4
|
+
import type { DaoMethodSchema } from './dao.js';
|
|
5
|
+
import type { ServiceMethodSchema } from './service.js';
|
|
6
|
+
import type { ThirdServiceMethodSchema } from './third-service.js';
|
|
7
|
+
import type { UtilsMethodSchema } from './utils.js';
|
|
8
|
+
|
|
9
|
+
// Flow model: nodes and edges are separate entities. A FlowNode is a plain
|
|
10
|
+
// named step; a FlowEdge references its start/end node objects directly (no
|
|
11
|
+
// ids). Because a node object may appear in many edges, the graph can share
|
|
12
|
+
// nodes, merge branches, and form cycles (e.g. poll-and-retry loops).
|
|
13
|
+
|
|
14
|
+
/** A method a flow node can invoke: contract references, or a pure descriptor
|
|
15
|
+
* (MethodSchema) for the period before the contract file exists. */
|
|
16
|
+
export type FlowMethodRef =
|
|
17
|
+
| MethodSchema
|
|
18
|
+
| ConvertSchema
|
|
19
|
+
| DaoMethodSchema
|
|
20
|
+
| ServiceMethodSchema
|
|
21
|
+
| ThirdServiceMethodSchema
|
|
22
|
+
| UtilsMethodSchema;
|
|
23
|
+
|
|
24
|
+
export interface FlowNode extends SchemaBase {
|
|
25
|
+
/** Optional sub-flow. When present, entering this node runs the sub-flow;
|
|
26
|
+
* after the sub-flow reaches any terminal node, the outer flow continues
|
|
27
|
+
* via this node's outgoing edges. Sub-flows nest recursively. */
|
|
28
|
+
flow?: FlowSchema;
|
|
29
|
+
/** Methods this node invokes — contract references or pure descriptors
|
|
30
|
+
* (rendered under the node label). */
|
|
31
|
+
methods?: FlowMethodRef[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FlowEdge extends SchemaBase {
|
|
35
|
+
/** Trigger condition; undefined = default path (success/normal). */
|
|
36
|
+
when?: string;
|
|
37
|
+
start: FlowNode;
|
|
38
|
+
end: FlowNode;
|
|
39
|
+
/** Exception path (rendered dashed); defaults to normal. */
|
|
40
|
+
exception?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface FlowSchema extends SchemaBase {
|
|
44
|
+
/** Entry node. */
|
|
45
|
+
start: FlowNode;
|
|
46
|
+
/** All nodes, collected from edges (deduplicated by object identity). */
|
|
47
|
+
nodes: FlowNode[];
|
|
48
|
+
/** Independent edges; a node may be start of many edges, so cycles are expressible. */
|
|
49
|
+
edges: FlowEdge[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function node(
|
|
53
|
+
name: string,
|
|
54
|
+
options: { flow?: FlowSchema; description?: string; methods?: FlowMethodRef[] } = {},
|
|
55
|
+
): FlowNode {
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
flow: options.flow,
|
|
59
|
+
description: options.description,
|
|
60
|
+
methods: options.methods,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function edge(
|
|
65
|
+
start: FlowNode,
|
|
66
|
+
end: FlowNode,
|
|
67
|
+
options: { when?: string; description?: string; exception?: boolean } = {},
|
|
68
|
+
): FlowEdge {
|
|
69
|
+
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
70
|
+
return {
|
|
71
|
+
name: `${start.name}->${end.name}`,
|
|
72
|
+
start,
|
|
73
|
+
end,
|
|
74
|
+
when: options.when,
|
|
75
|
+
description: options.description,
|
|
76
|
+
exception: options.exception,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function defineFlow(
|
|
81
|
+
name: string,
|
|
82
|
+
schema: {
|
|
83
|
+
start: FlowNode;
|
|
84
|
+
edges: FlowEdge[];
|
|
85
|
+
description?: string;
|
|
86
|
+
},
|
|
87
|
+
): FlowSchema {
|
|
88
|
+
const seen = new Set<FlowNode>();
|
|
89
|
+
const nodes: FlowNode[] = [];
|
|
90
|
+
for (const e of schema.edges) {
|
|
91
|
+
for (const n of [e.start, e.end]) {
|
|
92
|
+
if (!seen.has(n)) {
|
|
93
|
+
seen.add(n);
|
|
94
|
+
nodes.push(n);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!seen.has(schema.start)) {
|
|
99
|
+
seen.add(schema.start);
|
|
100
|
+
nodes.push(schema.start);
|
|
101
|
+
}
|
|
102
|
+
const flow: FlowSchema = { name, description: schema.description, start: schema.start, nodes, edges: schema.edges };
|
|
103
|
+
validate(flow);
|
|
104
|
+
return flow;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Nodes that never appear as an edge start are terminals. Reverse BFS from all
|
|
108
|
+
// terminals marks every node that can reach a terminal; unmarked nodes sit on
|
|
109
|
+
// a path that never ends (e.g. a cycle without an exit) — reject them at
|
|
110
|
+
// definition time.
|
|
111
|
+
function validate(schema: FlowSchema): void {
|
|
112
|
+
const starts = new Set<FlowNode>();
|
|
113
|
+
for (const e of schema.edges) starts.add(e.start);
|
|
114
|
+
|
|
115
|
+
const reverse = new Map<FlowNode, FlowNode[]>();
|
|
116
|
+
for (const n of schema.nodes) reverse.set(n, []);
|
|
117
|
+
for (const e of schema.edges) {
|
|
118
|
+
reverse.get(e.end)!.push(e.start);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const reached = new Set<FlowNode>();
|
|
122
|
+
const queue: FlowNode[] = [];
|
|
123
|
+
for (const n of schema.nodes) {
|
|
124
|
+
if (!starts.has(n)) {
|
|
125
|
+
reached.add(n);
|
|
126
|
+
queue.push(n);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
while (queue.length > 0) {
|
|
130
|
+
const cur = queue.shift()!;
|
|
131
|
+
for (const prev of reverse.get(cur)!) {
|
|
132
|
+
if (!reached.has(prev)) {
|
|
133
|
+
reached.add(prev);
|
|
134
|
+
queue.push(prev);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const n of schema.nodes) {
|
|
140
|
+
if (!reached.has(n)) {
|
|
141
|
+
throw new Error(`flow ${schema.name}: node "${n.name}" cannot reach a terminal`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
104
144
|
}
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,12 @@ export * from './convert.js';
|
|
|
22
22
|
export * from './ref.js';
|
|
23
23
|
export * from './route.js';
|
|
24
24
|
export * from './service.js';
|
|
25
|
+
export * from './controller.js';
|
|
26
|
+
export * from './endpoint.js';
|
|
25
27
|
export * from './dao.js';
|
|
28
|
+
export * from './third-service.js';
|
|
29
|
+
export * from './field-rule.js';
|
|
30
|
+
export * from './exception.js';
|
|
26
31
|
export * from './page.js';
|
|
27
32
|
export * from './curd.js';
|
|
28
33
|
export * from './page-flow.js';
|
|
@@ -30,4 +35,9 @@ export * from './provider.js';
|
|
|
30
35
|
export * from './page-def.js';
|
|
31
36
|
export * from './mermaid-driver.js';
|
|
32
37
|
export * from './navigation.js';
|
|
33
|
-
export * from './popup.js';
|
|
38
|
+
export * from './popup.js';
|
|
39
|
+
export * from './method.js';
|
|
40
|
+
|
|
41
|
+
// Naming conversions moved to @pylonts/core; re-exported for compatibility
|
|
42
|
+
// with packages that import them from @pylonts/dsl.
|
|
43
|
+
export { toCamelCase, toPascalCase, toKebabCase } from '@pylonts/core';
|
package/src/mermaid-driver.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowEdge, FlowNode, FlowSchema } from './flow.js';
|
|
2
|
+
import type { FlowMethodRef } from './flow.js';
|
|
2
3
|
import { Page } from './page.js';
|
|
3
4
|
import { PageEdge, PageFlow } from './page-flow.js';
|
|
4
5
|
|
|
@@ -24,14 +25,19 @@ export function renderFlowMermaid(schema: FlowSchema): string {
|
|
|
24
25
|
|
|
25
26
|
function renderFlow(schema: FlowSchema, prefix: string, lines: string[], ids: Map<FlowNode, string>): void {
|
|
26
27
|
schema.nodes.forEach((n, i) => ids.set(n, `${prefix}${i}`));
|
|
28
|
+
const outgoing = new Map<FlowNode, FlowEdge[]>();
|
|
29
|
+
for (const n of schema.nodes) outgoing.set(n, []);
|
|
30
|
+
for (const e of schema.edges) outgoing.get(e.start)!.push(e);
|
|
31
|
+
|
|
27
32
|
for (const n of schema.nodes) {
|
|
28
33
|
const id = ids.get(n)!;
|
|
29
34
|
if (n.flow) {
|
|
30
|
-
lines.push(` subgraph ${id}["${escapeLabel(n
|
|
35
|
+
lines.push(` subgraph ${id}["${escapeLabel(renderNodeLabel(n))}"]`);
|
|
31
36
|
renderFlow(n.flow, `${id}_`, lines, ids);
|
|
32
37
|
lines.push(' end');
|
|
33
38
|
} else {
|
|
34
|
-
|
|
39
|
+
const shape = renderShape(n, schema, outgoing.get(n)!);
|
|
40
|
+
lines.push(` ${id}${shape.open}${escapeLabel(renderNodeLabel(n))}${shape.close}`);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
for (const e of schema.edges) {
|
|
@@ -39,9 +45,31 @@ function renderFlow(schema: FlowSchema, prefix: string, lines: string[], ids: Ma
|
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
/** Node label: name on the first line, method references on the second. */
|
|
49
|
+
function renderNodeLabel(n: FlowNode): string {
|
|
50
|
+
if (n.methods === undefined || n.methods.length === 0) return n.name;
|
|
51
|
+
return `${n.name}\n${n.methods.map((m: FlowMethodRef) => renderMethodRef(m)).join(' | ')}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Display form: owner.name for descriptors and container methods, bare name for converts. */
|
|
55
|
+
function renderMethodRef(m: FlowMethodRef): string {
|
|
56
|
+
if ('owner' in m) return `${m.owner}.${m.name}`;
|
|
57
|
+
if ('schema' in m) return `${m.schema.name}.${m.name}`;
|
|
58
|
+
return m.name;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Shape derived from topology: start and terminals rounded, branching nodes diamond, else rect.
|
|
62
|
+
* Exception edges do not count as branches. */
|
|
63
|
+
function renderShape(n: FlowNode, schema: FlowSchema, outgoing: FlowEdge[]): { open: string; close: string } {
|
|
64
|
+
if (n === schema.start || outgoing.length === 0) return { open: '(["', close: '"])' };
|
|
65
|
+
const normal = outgoing.filter((e) => e.exception !== true).length;
|
|
66
|
+
return normal >= 2 ? { open: '{"', close: '"}' } : { open: '["', close: '"]' };
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
function renderEdge(e: FlowEdge, ids: Map<FlowNode, string>): string {
|
|
70
|
+
const arrow = e.exception === true ? '-.->' : '-->';
|
|
43
71
|
const label = e.when ? `|"${escapeLabel(e.when)}"|` : '';
|
|
44
|
-
return ` ${ids.get(e.start)}
|
|
72
|
+
return ` ${ids.get(e.start)} ${arrow}${label} ${ids.get(e.end)}`;
|
|
45
73
|
}
|
|
46
74
|
|
|
47
75
|
// Page-driven flow renderer: groups pages by their app into swimlane
|
package/src/method.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
|
|
3
|
+
// A pure descriptor of one method invocation (e.g. payDao.insert).
|
|
4
|
+
// No args/results — the invocation stays natural language for now.
|
|
5
|
+
// SequenceSchema is the ordered list of such invocations.
|
|
6
|
+
|
|
7
|
+
export interface MethodSchema extends SchemaBase {
|
|
8
|
+
/** Owning schema name, e.g. 'payDao'. */
|
|
9
|
+
owner: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SequenceSchema = MethodSchema[];
|
|
13
|
+
|
|
14
|
+
export function defineMethod(options: {
|
|
15
|
+
name: string;
|
|
16
|
+
owner: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}): MethodSchema {
|
|
19
|
+
return { name: options.name, owner: options.owner, description: options.description };
|
|
20
|
+
}
|
package/src/mock.ts
CHANGED
|
@@ -1,12 +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
|
-
}
|
|
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
|
+
}
|
package/src/mysql-driver.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Field } from './dsl.js';
|
|
1
|
+
import { Field, rateScale } from './dsl.js';
|
|
2
2
|
import { ForeignKey, Index, TableSchema } from './db.js';
|
|
3
3
|
|
|
4
4
|
// MySQL driver: converts a TableSchema into a CREATE TABLE statement.
|
|
@@ -21,6 +21,8 @@ function columnType(field: Field): string {
|
|
|
21
21
|
return 'BIGINT';
|
|
22
22
|
case 'decimal':
|
|
23
23
|
return `DECIMAL(${field.precision}, ${field.scale})`;
|
|
24
|
+
case 'rate':
|
|
25
|
+
return `DECIMAL(5, ${rateScale(field.unit)})`;
|
|
24
26
|
case 'boolean':
|
|
25
27
|
return 'TINYINT(1)';
|
|
26
28
|
case 'date':
|
|
@@ -34,6 +36,10 @@ function columnType(field: Field): string {
|
|
|
34
36
|
return field.enum.valueType === 'integer' ? 'TINYINT' : 'VARCHAR(20)';
|
|
35
37
|
case 'json':
|
|
36
38
|
return 'JSON';
|
|
39
|
+
case 'array':
|
|
40
|
+
case 'object':
|
|
41
|
+
// Nested fields are wire-format only (third-party messages); table columns cannot nest.
|
|
42
|
+
throw new Error(`field ${field.name} (${field.type}): nested fields are not supported on table columns`);
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -43,7 +49,7 @@ function renderDefault(field: Field): string {
|
|
|
43
49
|
// NULL, etc.) — render bare, no quotes.
|
|
44
50
|
if (/^[A-Z]/.test(field.default)) return ` DEFAULT ${field.default}`;
|
|
45
51
|
// Numeric columns take a bare literal, not a quoted one.
|
|
46
|
-
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
|
|
52
|
+
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'rate' || field.type === 'boolean') {
|
|
47
53
|
return ` DEFAULT ${field.default}`;
|
|
48
54
|
}
|
|
49
55
|
return ` DEFAULT '${field.default}'`;
|
package/src/navigation.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type { RouteDataSchema } from './route.js';
|
|
2
|
-
import type { ActionSchema } from './action.js';
|
|
3
|
-
import { PageSchema } from './page.js';
|
|
4
|
-
|
|
5
|
-
/** Navigation primitives: front-end routing actions that are not provider calls. */
|
|
6
|
-
export interface NavigationAction extends ActionSchema {
|
|
7
|
-
type: 'navigation';
|
|
8
|
-
method: 'back' | 'push' | 'popup' | 'home';
|
|
9
|
-
/** Target page for push navigation. */
|
|
10
|
-
target?: PageSchema;
|
|
11
|
-
/** Route params type for push navigation. Auto-derived from target.params if omitted. */
|
|
12
|
-
params?: RouteDataSchema;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const navigation = {
|
|
16
|
-
back(description?: string): NavigationAction {
|
|
17
|
-
return { name: 'back', type: 'navigation', method: 'back', description };
|
|
18
|
-
},
|
|
19
|
-
push(options: { target: PageSchema; params?: RouteDataSchema; description?: string }): NavigationAction {
|
|
20
|
-
const { target, params, description } = options;
|
|
21
|
-
return { name: 'push', type: 'navigation', method: 'push', target, params: params ?? target.params, description };
|
|
22
|
-
},
|
|
23
|
-
popup(description?: string): NavigationAction {
|
|
24
|
-
return { name: 'popup', type: 'navigation', method: 'popup', description };
|
|
25
|
-
},
|
|
26
|
-
home(description?: string): NavigationAction {
|
|
27
|
-
return { name: 'home', type: 'navigation', method: 'home', description };
|
|
28
|
-
},
|
|
1
|
+
import type { RouteDataSchema } from './route.js';
|
|
2
|
+
import type { ActionSchema } from './action.js';
|
|
3
|
+
import { PageSchema } from './page.js';
|
|
4
|
+
|
|
5
|
+
/** Navigation primitives: front-end routing actions that are not provider calls. */
|
|
6
|
+
export interface NavigationAction extends ActionSchema {
|
|
7
|
+
type: 'navigation';
|
|
8
|
+
method: 'back' | 'push' | 'popup' | 'home';
|
|
9
|
+
/** Target page for push navigation. */
|
|
10
|
+
target?: PageSchema;
|
|
11
|
+
/** Route params type for push navigation. Auto-derived from target.params if omitted. */
|
|
12
|
+
params?: RouteDataSchema;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const navigation = {
|
|
16
|
+
back(description?: string): NavigationAction {
|
|
17
|
+
return { name: 'back', type: 'navigation', method: 'back', description };
|
|
18
|
+
},
|
|
19
|
+
push(options: { target: PageSchema; params?: RouteDataSchema; description?: string }): NavigationAction {
|
|
20
|
+
const { target, params, description } = options;
|
|
21
|
+
return { name: 'push', type: 'navigation', method: 'push', target, params: params ?? target.params, description };
|
|
22
|
+
},
|
|
23
|
+
popup(description?: string): NavigationAction {
|
|
24
|
+
return { name: 'popup', type: 'navigation', method: 'popup', description };
|
|
25
|
+
},
|
|
26
|
+
home(description?: string): NavigationAction {
|
|
27
|
+
return { name: 'home', type: 'navigation', method: 'home', description };
|
|
28
|
+
},
|
|
29
29
|
};
|