@pylonts/dsl 1.0.1 → 1.0.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/dictionary.d.ts +8 -20
- package/dist/dictionary.js +6 -39
- package/dist/dsl.d.ts +2 -0
- package/dist/flow.d.ts +28 -0
- package/dist/flow.js +68 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mermaid-driver.d.ts +4 -0
- package/dist/mermaid-driver.js +72 -0
- package/dist/page-flow.d.ts +21 -0
- package/dist/page-flow.js +25 -0
- package/dist/page.d.ts +26 -0
- package/dist/page.js +14 -0
- package/dist/pattern.d.ts +15 -0
- package/dist/pattern.js +13 -0
- package/dist/patterns/retry.d.ts +16 -0
- package/dist/patterns/retry.js +40 -0
- package/dist/project.d.ts +11 -0
- package/dist/project.js +1 -1
- package/docs/dictionary.md +31 -0
- package/docs/driver.md +42 -0
- package/docs/dto.md +60 -0
- package/docs/enum.md +24 -0
- package/docs/mysql-connection.md +1 -0
- package/docs/project.md +24 -0
- package/docs/prototype.md +21 -0
- package/docs/table.md +99 -0
- package/package.json +3 -2
- package/src/dictionary.ts +11 -54
- package/src/dsl.ts +2 -0
- package/src/flow.ts +104 -0
- package/src/index.ts +7 -1
- package/src/mermaid-driver.ts +81 -0
- package/src/page-flow.ts +52 -0
- package/src/page.ts +40 -0
- package/src/pattern.ts +27 -0
- package/src/patterns/retry.ts +55 -0
- package/src/project.ts +13 -1
- package/src/typebox-driver.ts +140 -140
package/dist/dictionary.d.ts
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { SchemaBase } from './dsl';
|
|
2
|
-
/** A vocabulary entry
|
|
2
|
+
/** A vocabulary entry. */
|
|
3
3
|
export interface DictionaryEntry extends SchemaBase {
|
|
4
|
+
/** Display label (Chinese) for the term. */
|
|
5
|
+
label?: string;
|
|
4
6
|
}
|
|
5
|
-
export interface DictionarySchema extends SchemaBase {
|
|
6
|
-
entries: Record<string, DictionaryEntry>;
|
|
7
|
-
}
|
|
8
|
-
/** Creates a phrase entry. name is filled in by defineDictionary. */
|
|
9
|
-
export declare function definePhrase(extra?: Omit<DictionaryEntry, 'name'>): DictionaryEntry;
|
|
10
|
-
/** Defines the entity phrase dictionary: standard words aligned with entities. */
|
|
11
|
-
export declare function defineEntityDictionary(schema: {
|
|
12
|
-
description?: string;
|
|
13
|
-
entries: Record<string, DictionaryEntry>;
|
|
14
|
-
}): DictionarySchema;
|
|
15
|
-
/**
|
|
16
|
-
* Defines a business phrase package (one file per industry): entries are the
|
|
17
|
-
* top-level map keys, no wrapping container.
|
|
18
|
-
*/
|
|
19
|
-
export declare function defineBusinessDictionary(entries: Record<string, DictionaryEntry>): DictionarySchema;
|
|
20
7
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
8
|
+
* Explains a phrase — the returned entry IS the phrase. `name` is the phrase
|
|
9
|
+
* itself (column-name stem, e.g. mer / bd / amt), not a full entity name;
|
|
10
|
+
* label/description explain what it means. Convention: short phrase, not long
|
|
11
|
+
* name.
|
|
24
12
|
*/
|
|
25
|
-
export declare function
|
|
13
|
+
export declare function definePhrase(extra: DictionaryEntry): DictionaryEntry;
|
package/dist/dictionary.js
CHANGED
|
@@ -1,45 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.definePhrase = definePhrase;
|
|
4
|
-
exports.defineEntityDictionary = defineEntityDictionary;
|
|
5
|
-
exports.defineBusinessDictionary = defineBusinessDictionary;
|
|
6
|
-
exports.mergeBusinessDictionaries = mergeBusinessDictionaries;
|
|
7
|
-
/** Creates a phrase entry. name is filled in by defineDictionary. */
|
|
8
|
-
function definePhrase(extra = {}) {
|
|
9
|
-
return { name: '', ...extra };
|
|
10
|
-
}
|
|
11
|
-
function defineDictionary(schema) {
|
|
12
|
-
const dictionary = { ...schema };
|
|
13
|
-
for (const key of Object.keys(dictionary.entries)) {
|
|
14
|
-
dictionary.entries[key].name = key;
|
|
15
|
-
}
|
|
16
|
-
return dictionary;
|
|
17
|
-
}
|
|
18
|
-
/** Defines the entity phrase dictionary: standard words aligned with entities. */
|
|
19
|
-
function defineEntityDictionary(schema) {
|
|
20
|
-
return defineDictionary({ name: 'entity', ...schema });
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Defines a business phrase package (one file per industry): entries are the
|
|
24
|
-
* top-level map keys, no wrapping container.
|
|
25
|
-
*/
|
|
26
|
-
function defineBusinessDictionary(entries) {
|
|
27
|
-
return defineDictionary({ name: 'business', entries });
|
|
28
|
-
}
|
|
29
4
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
5
|
+
* Explains a phrase — the returned entry IS the phrase. `name` is the phrase
|
|
6
|
+
* itself (column-name stem, e.g. mer / bd / amt), not a full entity name;
|
|
7
|
+
* label/description explain what it means. Convention: short phrase, not long
|
|
8
|
+
* name.
|
|
33
9
|
*/
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
for (const pkg of packages) {
|
|
37
|
-
for (const key of Object.keys(pkg.entries)) {
|
|
38
|
-
if (merged[key]) {
|
|
39
|
-
throw new Error(`business phrase ${key} is defined in multiple packages`);
|
|
40
|
-
}
|
|
41
|
-
merged[key] = pkg.entries[key];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return merged;
|
|
10
|
+
function definePhrase(extra) {
|
|
11
|
+
return extra;
|
|
45
12
|
}
|
package/dist/dsl.d.ts
CHANGED
|
@@ -128,6 +128,8 @@ export declare function defineTable(name: string, schema: {
|
|
|
128
128
|
primaryKey?: Field | Field[];
|
|
129
129
|
indexes?: Index[];
|
|
130
130
|
foreignKeys?: Record<string, ForeignKey>;
|
|
131
|
+
/** 引用的实体短语(词典条目) */
|
|
132
|
+
phrase?: DictionaryEntry;
|
|
131
133
|
fields: Record<string, Field>;
|
|
132
134
|
}): TableSchema;
|
|
133
135
|
export {};
|
package/dist/flow.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl';
|
|
2
|
+
export interface FlowNode extends SchemaBase {
|
|
3
|
+
/** Optional sub-flow. When present, entering this node runs the sub-flow;
|
|
4
|
+
* after the sub-flow reaches any terminal node, the outer flow continues
|
|
5
|
+
* via this node's outgoing edges. Sub-flows nest recursively. */
|
|
6
|
+
flow?: FlowSchema;
|
|
7
|
+
}
|
|
8
|
+
export interface FlowEdge extends SchemaBase {
|
|
9
|
+
/** Trigger condition; undefined = default path (success/normal). */
|
|
10
|
+
when?: string;
|
|
11
|
+
start: FlowNode;
|
|
12
|
+
end: FlowNode;
|
|
13
|
+
}
|
|
14
|
+
export interface FlowSchema extends SchemaBase {
|
|
15
|
+
/** Entry node. */
|
|
16
|
+
start: FlowNode;
|
|
17
|
+
/** All nodes, collected from edges (deduplicated by object identity). */
|
|
18
|
+
nodes: FlowNode[];
|
|
19
|
+
/** Independent edges; a node may be start of many edges, so cycles are expressible. */
|
|
20
|
+
edges: FlowEdge[];
|
|
21
|
+
}
|
|
22
|
+
export declare function node(name: string, flow?: FlowSchema, description?: string): FlowNode;
|
|
23
|
+
export declare function edge(start: FlowNode, end: FlowNode, when?: string, description?: string): FlowEdge;
|
|
24
|
+
export declare function defineFlow(name: string, schema: {
|
|
25
|
+
start: FlowNode;
|
|
26
|
+
edges: FlowEdge[];
|
|
27
|
+
description?: string;
|
|
28
|
+
}): FlowSchema;
|
package/dist/flow.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.node = node;
|
|
4
|
+
exports.edge = edge;
|
|
5
|
+
exports.defineFlow = defineFlow;
|
|
6
|
+
function node(name, flow, description) {
|
|
7
|
+
return { name, flow, description };
|
|
8
|
+
}
|
|
9
|
+
function edge(start, end, when, description) {
|
|
10
|
+
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
11
|
+
return { name: `${start.name}->${end.name}`, start, end, when, description };
|
|
12
|
+
}
|
|
13
|
+
function defineFlow(name, schema) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const nodes = [];
|
|
16
|
+
for (const e of schema.edges) {
|
|
17
|
+
for (const n of [e.start, e.end]) {
|
|
18
|
+
if (!seen.has(n)) {
|
|
19
|
+
seen.add(n);
|
|
20
|
+
nodes.push(n);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!seen.has(schema.start)) {
|
|
25
|
+
seen.add(schema.start);
|
|
26
|
+
nodes.push(schema.start);
|
|
27
|
+
}
|
|
28
|
+
const flow = { name, description: schema.description, start: schema.start, nodes, edges: schema.edges };
|
|
29
|
+
validate(flow);
|
|
30
|
+
return flow;
|
|
31
|
+
}
|
|
32
|
+
// Nodes that never appear as an edge start are terminals. Reverse BFS from all
|
|
33
|
+
// terminals marks every node that can reach a terminal; unmarked nodes sit on
|
|
34
|
+
// a path that never ends (e.g. a cycle without an exit) — reject them at
|
|
35
|
+
// definition time.
|
|
36
|
+
function validate(schema) {
|
|
37
|
+
const starts = new Set();
|
|
38
|
+
for (const e of schema.edges)
|
|
39
|
+
starts.add(e.start);
|
|
40
|
+
const reverse = new Map();
|
|
41
|
+
for (const n of schema.nodes)
|
|
42
|
+
reverse.set(n, []);
|
|
43
|
+
for (const e of schema.edges) {
|
|
44
|
+
reverse.get(e.end).push(e.start);
|
|
45
|
+
}
|
|
46
|
+
const reached = new Set();
|
|
47
|
+
const queue = [];
|
|
48
|
+
for (const n of schema.nodes) {
|
|
49
|
+
if (!starts.has(n)) {
|
|
50
|
+
reached.add(n);
|
|
51
|
+
queue.push(n);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
while (queue.length > 0) {
|
|
55
|
+
const cur = queue.shift();
|
|
56
|
+
for (const prev of reverse.get(cur)) {
|
|
57
|
+
if (!reached.has(prev)) {
|
|
58
|
+
reached.add(prev);
|
|
59
|
+
queue.push(prev);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const n of schema.nodes) {
|
|
64
|
+
if (!reached.has(n)) {
|
|
65
|
+
throw new Error(`flow ${schema.name}: node "${n.name}" cannot reach a terminal`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,9 @@ export * from './dictionary';
|
|
|
6
6
|
export * from './mysql-driver';
|
|
7
7
|
export * from './enum-driver';
|
|
8
8
|
export * from './typebox-driver';
|
|
9
|
+
export * from './pattern';
|
|
10
|
+
export * from './patterns/retry';
|
|
11
|
+
export * from './flow';
|
|
12
|
+
export * from './page';
|
|
13
|
+
export * from './page-flow';
|
|
14
|
+
export * from './mermaid-driver';
|
package/dist/index.js
CHANGED
|
@@ -22,3 +22,9 @@ __exportStar(require("./dictionary"), exports);
|
|
|
22
22
|
__exportStar(require("./mysql-driver"), exports);
|
|
23
23
|
__exportStar(require("./enum-driver"), exports);
|
|
24
24
|
__exportStar(require("./typebox-driver"), exports);
|
|
25
|
+
__exportStar(require("./pattern"), exports);
|
|
26
|
+
__exportStar(require("./patterns/retry"), exports);
|
|
27
|
+
__exportStar(require("./flow"), exports);
|
|
28
|
+
__exportStar(require("./page"), exports);
|
|
29
|
+
__exportStar(require("./page-flow"), exports);
|
|
30
|
+
__exportStar(require("./mermaid-driver"), exports);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderFlowMermaid = renderFlowMermaid;
|
|
4
|
+
exports.renderPageFlowMermaid = renderPageFlowMermaid;
|
|
5
|
+
// Mermaid driver: converts a FlowSchema into a Mermaid flowchart (TD).
|
|
6
|
+
// Node ids are hierarchical (n0, n0_0, n0_0_0, ...) so nested sub-flows stay
|
|
7
|
+
// globally unique. A node with a sub-flow renders as a subgraph block whose
|
|
8
|
+
// internals are rendered recursively. ok edges render as -->, conditional
|
|
9
|
+
// edges render as -->|"WHEN"|.
|
|
10
|
+
function escapeLabel(s) {
|
|
11
|
+
return s.replace(/"/g, '\\"').replace(/\n/g, '<br/>');
|
|
12
|
+
}
|
|
13
|
+
function renderFlowMermaid(schema) {
|
|
14
|
+
const lines = ['flowchart TD'];
|
|
15
|
+
const ids = new Map();
|
|
16
|
+
renderFlow(schema, 'n', lines, ids);
|
|
17
|
+
lines.push('');
|
|
18
|
+
lines.push(' classDef start fill:#e6f4ea,stroke:#333,stroke-width:1px;');
|
|
19
|
+
lines.push(` class ${ids.get(schema.start)} start;`);
|
|
20
|
+
return lines.join('\n');
|
|
21
|
+
}
|
|
22
|
+
function renderFlow(schema, prefix, lines, ids) {
|
|
23
|
+
schema.nodes.forEach((n, i) => ids.set(n, `${prefix}${i}`));
|
|
24
|
+
for (const n of schema.nodes) {
|
|
25
|
+
const id = ids.get(n);
|
|
26
|
+
if (n.flow) {
|
|
27
|
+
lines.push(` subgraph ${id}["${escapeLabel(n.name)}"]`);
|
|
28
|
+
renderFlow(n.flow, `${id}_`, lines, ids);
|
|
29
|
+
lines.push(' end');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
lines.push(` ${id}["${escapeLabel(n.name)}"]`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const e of schema.edges) {
|
|
36
|
+
lines.push(renderEdge(e, ids));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function renderEdge(e, ids) {
|
|
40
|
+
const label = e.when ? `|"${escapeLabel(e.when)}"|` : '';
|
|
41
|
+
return ` ${ids.get(e.start)} -->${label} ${ids.get(e.end)}`;
|
|
42
|
+
}
|
|
43
|
+
// Page-driven flow renderer: groups pages by their app into swimlane
|
|
44
|
+
// subgraphs, then renders edges across the whole flow.
|
|
45
|
+
function renderPageFlowMermaid(schema) {
|
|
46
|
+
const lines = ['flowchart TD'];
|
|
47
|
+
const ids = new Map();
|
|
48
|
+
const byApp = new Map();
|
|
49
|
+
for (const p of schema.pages) {
|
|
50
|
+
const list = byApp.get(p.app.name) ?? [];
|
|
51
|
+
list.push(p);
|
|
52
|
+
byApp.set(p.app.name, list);
|
|
53
|
+
}
|
|
54
|
+
let appIdx = 0;
|
|
55
|
+
for (const [appName, pages] of byApp) {
|
|
56
|
+
lines.push(` subgraph app${appIdx}["${escapeLabel(appName)}"]`);
|
|
57
|
+
pages.forEach((p, i) => ids.set(p, `app${appIdx}_p${i}`));
|
|
58
|
+
for (const p of pages) {
|
|
59
|
+
lines.push(` ${ids.get(p)}["${escapeLabel(p.name)}"]`);
|
|
60
|
+
}
|
|
61
|
+
appIdx++;
|
|
62
|
+
lines.push(' end');
|
|
63
|
+
}
|
|
64
|
+
for (const e of schema.edges) {
|
|
65
|
+
const label = e.when ? `|"${escapeLabel(e.when.name)}"|` : '';
|
|
66
|
+
lines.push(` ${ids.get(e.start)} -->${label} ${ids.get(e.end)}`);
|
|
67
|
+
}
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(' classDef start fill:#e6f4ea,stroke:#333,stroke-width:1px;');
|
|
70
|
+
lines.push(` class ${ids.get(schema.start)} start;`);
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl';
|
|
2
|
+
import { ActionSchema, Page } from './page';
|
|
3
|
+
export interface PageEdge extends SchemaBase {
|
|
4
|
+
/** Trigger action; undefined = default path (success/normal). */
|
|
5
|
+
when?: ActionSchema;
|
|
6
|
+
start: Page;
|
|
7
|
+
end: Page;
|
|
8
|
+
}
|
|
9
|
+
export interface PageFlow extends SchemaBase {
|
|
10
|
+
/** Entry page. */
|
|
11
|
+
start: Page;
|
|
12
|
+
/** All pages, collected from edges (deduplicated by object identity). */
|
|
13
|
+
pages: Page[];
|
|
14
|
+
edges: PageEdge[];
|
|
15
|
+
}
|
|
16
|
+
export declare function pageEdge(start: Page, end: Page, when?: ActionSchema, description?: string): PageEdge;
|
|
17
|
+
export declare function definePageFlow(name: string, schema: {
|
|
18
|
+
start: Page;
|
|
19
|
+
edges: PageEdge[];
|
|
20
|
+
description?: string;
|
|
21
|
+
}): PageFlow;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pageEdge = pageEdge;
|
|
4
|
+
exports.definePageFlow = definePageFlow;
|
|
5
|
+
function pageEdge(start, end, when, description) {
|
|
6
|
+
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
7
|
+
return { name: `${start.name}->${end.name}`, start, end, when, description };
|
|
8
|
+
}
|
|
9
|
+
function definePageFlow(name, schema) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const pages = [];
|
|
12
|
+
for (const e of schema.edges) {
|
|
13
|
+
for (const p of [e.start, e.end]) {
|
|
14
|
+
if (!seen.has(p)) {
|
|
15
|
+
seen.add(p);
|
|
16
|
+
pages.push(p);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!seen.has(schema.start)) {
|
|
21
|
+
seen.add(schema.start);
|
|
22
|
+
pages.push(schema.start);
|
|
23
|
+
}
|
|
24
|
+
return { name, description: schema.description, start: schema.start, pages, edges: schema.edges };
|
|
25
|
+
}
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl';
|
|
2
|
+
import { FrontApp } from './project';
|
|
3
|
+
/** Standalone page definition. A page is a shared value object: it lists
|
|
4
|
+
* the actions a user can perform, and belongs to exactly one frontend app. */
|
|
5
|
+
export interface PageSchema extends SchemaBase {
|
|
6
|
+
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
7
|
+
app: FrontApp;
|
|
8
|
+
/** Actions a user can perform on this page (e.g. submit, approve, reject). */
|
|
9
|
+
actions: ActionSchema[];
|
|
10
|
+
}
|
|
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
|
+
export declare function definePage(schema: {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
app: FrontApp;
|
|
19
|
+
actions: ActionSchema[];
|
|
20
|
+
}): PageSchema;
|
|
21
|
+
/** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
|
|
22
|
+
export interface Page extends SchemaBase {
|
|
23
|
+
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
24
|
+
app: FrontApp;
|
|
25
|
+
}
|
|
26
|
+
export declare function page(app: FrontApp, name: string, description?: string): Page;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defineAction = defineAction;
|
|
4
|
+
exports.definePage = definePage;
|
|
5
|
+
exports.page = page;
|
|
6
|
+
function defineAction(name, description) {
|
|
7
|
+
return { name, description };
|
|
8
|
+
}
|
|
9
|
+
function definePage(schema) {
|
|
10
|
+
return { ...schema };
|
|
11
|
+
}
|
|
12
|
+
function page(app, name, description) {
|
|
13
|
+
return { name, app, description };
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface PatternParamDef {
|
|
2
|
+
type: 'int' | 'string';
|
|
3
|
+
min?: number;
|
|
4
|
+
default?: number | string;
|
|
5
|
+
}
|
|
6
|
+
export interface PatternDef {
|
|
7
|
+
name: string;
|
|
8
|
+
params: Record<string, PatternParamDef>;
|
|
9
|
+
}
|
|
10
|
+
export interface PatternRef {
|
|
11
|
+
ref: string;
|
|
12
|
+
args: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export declare function definePattern(def: PatternDef): PatternDef;
|
|
15
|
+
export declare function ref(name: string, args: Record<string, unknown>): PatternRef;
|
package/dist/pattern.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pattern core: minimal definitions to bootstrap the Flow × Pattern DSL.
|
|
3
|
+
// A Pattern is a reusable solution ("how to guarantee success") declared as
|
|
4
|
+
// pure data. Concrete usage fills its params and action injection points.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.definePattern = definePattern;
|
|
7
|
+
exports.ref = ref;
|
|
8
|
+
function definePattern(def) {
|
|
9
|
+
return def;
|
|
10
|
+
}
|
|
11
|
+
function ref(name, args) {
|
|
12
|
+
return { ref: name, args };
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PatternDef } from '../pattern';
|
|
2
|
+
export declare const retryPattern: PatternDef;
|
|
3
|
+
export interface RetryAction {
|
|
4
|
+
/** Function to call, e.g. 'queryOrderList' */
|
|
5
|
+
call: string;
|
|
6
|
+
/** Type name of the single argument passed through, e.g. 'OrderQueryParams' */
|
|
7
|
+
params?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RetryRefArgs {
|
|
10
|
+
max?: number;
|
|
11
|
+
backoffMs?: number;
|
|
12
|
+
action: RetryAction;
|
|
13
|
+
/** Generated function name; defaults to '<call>WithRetry' */
|
|
14
|
+
fnName?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function renderRetry(args: RetryRefArgs): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retryPattern = void 0;
|
|
4
|
+
exports.renderRetry = renderRetry;
|
|
5
|
+
// Retry pattern: blind mechanical retry for read-only operations.
|
|
6
|
+
// Valid because a read-only action is idempotent by nature: clicking a query
|
|
7
|
+
// button any number of times never changes the result, so retrying the same
|
|
8
|
+
// call is always safe. No idempotency key, no query-and-resume, no compensate.
|
|
9
|
+
exports.retryPattern = {
|
|
10
|
+
name: 'retry',
|
|
11
|
+
params: {
|
|
12
|
+
max: { type: 'int', min: 1, default: 3 },
|
|
13
|
+
backoffMs: { type: 'int', min: 0, default: 0 },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
function renderRetry(args) {
|
|
17
|
+
if (args.max !== undefined && args.max < 1)
|
|
18
|
+
throw new Error('retry: max must be >= 1');
|
|
19
|
+
if (!args.action.call)
|
|
20
|
+
throw new Error('retry: action.call is required');
|
|
21
|
+
const max = args.max ?? 3;
|
|
22
|
+
const backoffMs = args.backoffMs ?? 0;
|
|
23
|
+
const call = args.action.call;
|
|
24
|
+
const paramType = args.action.params ?? 'unknown';
|
|
25
|
+
const fnName = args.fnName ?? `${call}WithRetry`;
|
|
26
|
+
const retryLine = backoffMs > 0 ? ` await sleep(${backoffMs});` : '';
|
|
27
|
+
return [
|
|
28
|
+
`export async function ${fnName}(params: ${paramType}) {`,
|
|
29
|
+
` for (let attempt = 1; ; attempt++) {`,
|
|
30
|
+
` try {`,
|
|
31
|
+
` return await ${call}(params);`,
|
|
32
|
+
` } catch (err) {`,
|
|
33
|
+
` if (attempt >= ${max}) throw err;`,
|
|
34
|
+
retryLine,
|
|
35
|
+
` }`,
|
|
36
|
+
` }`,
|
|
37
|
+
`}`,
|
|
38
|
+
'',
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
package/dist/project.d.ts
CHANGED
|
@@ -13,10 +13,20 @@ export interface ProjectApi extends SchemaBase {
|
|
|
13
13
|
dir: string;
|
|
14
14
|
/** Frontends this API serves. Direct instance references (see defineProject). */
|
|
15
15
|
apps: FrontApp[];
|
|
16
|
+
/** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
|
|
17
|
+
contextPath?: string;
|
|
18
|
+
}
|
|
19
|
+
/** A third-party system (e.g. wechat pay, unionpay). Owns its own
|
|
20
|
+
* implementation dir and contract (controller_types), just like an API,
|
|
21
|
+
* but is not part of this repo's served surface. */
|
|
22
|
+
export interface ThirdApi extends SchemaBase {
|
|
23
|
+
/** Source directory relative to project root, e.g. 'wechat/'. */
|
|
24
|
+
dir: string;
|
|
16
25
|
}
|
|
17
26
|
export interface ProjectSchema extends SchemaBase {
|
|
18
27
|
apps: FrontApp[];
|
|
19
28
|
apis: ProjectApi[];
|
|
29
|
+
thirdApis: ThirdApi[];
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
32
|
* Defines the project topology. FrontApp instances are shared value objects:
|
|
@@ -27,4 +37,5 @@ export declare function defineProject(name: string, schema: {
|
|
|
27
37
|
description?: string;
|
|
28
38
|
apps: FrontApp[];
|
|
29
39
|
apis: ProjectApi[];
|
|
40
|
+
thirdApis?: ThirdApi[];
|
|
30
41
|
}): ProjectSchema;
|
package/dist/project.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 短语词典 (Dictionary)
|
|
2
|
+
|
|
3
|
+
词典是与团队达成共识的基础知识库:**某词代表什么**(语义/定义层面),不是物理形式。短语定了,字段命名、外键命名就都有依据——全项目只说同一种话。
|
|
4
|
+
|
|
5
|
+
- 是基础知识库,很少变更。
|
|
6
|
+
- **能引用就引用**:魔法字符串只在首次出现时使用,之后一律引用词典条目。
|
|
7
|
+
|
|
8
|
+
## 规范位置:schema/_dictionary.ts
|
|
9
|
+
|
|
10
|
+
**所有短语统一定义在 `schema/_dictionary.ts`**,一个文件一处定义;`*.table.ts` 从该文件 import 短语,禁止在表文件里内联定义短语。
|
|
11
|
+
|
|
12
|
+
## 口径:definePhrase 解释短语,name 即短语
|
|
13
|
+
|
|
14
|
+
`definePhrase` 返回的条目**就是短语本身**,不是"全名 + 缩写"两套——`name` 即短语词干(列名前缀),`label`/`description` 解释语义。**变量名与 name 一致**(小写)。短语要短(mer / bd / amt 三字母左右),**不要用长语**:引用商户实体的字段叫 `mer_id`,不叫 `merchant_id`。
|
|
15
|
+
|
|
16
|
+
## 定义
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// schema/_dictionary.ts
|
|
20
|
+
import { definePhrase } from '@pylonts/dsl';
|
|
21
|
+
|
|
22
|
+
const bd = definePhrase({ name: 'bd', label: 'BD推广员', description: '线下拓展商户、辅助入驻的推广人员' });
|
|
23
|
+
const amt = definePhrase({ name: 'amt', label: '金额', description: '交易金额,单位分' });
|
|
24
|
+
const mer = definePhrase({ name: 'mer', label: '商户', description: '入驻平台的商户' });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 使用
|
|
28
|
+
|
|
29
|
+
- **表链接实体**:`TableSchema.phrase` 引用实体条目,声明本表归属哪个实体(见 [table.md](./table.md) 的外键检查链)。关联表等多实体场景不需要。
|
|
30
|
+
- 业务短语供字段命名/文档使用,跨团队对齐。
|
|
31
|
+
- 未收录短语的实体保留全名作词干(不臆造缩写),评审时再裁决收录。
|
package/docs/driver.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Driver 模式与产物生成
|
|
2
|
+
|
|
3
|
+
DSL 定义元数据,driver 翻译成目标语言产物。产物与定义解耦,同一份定义可生成不同目标:
|
|
4
|
+
|
|
5
|
+
| 定义 | Driver | 产物 | 消费方 |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `TableSchema` | mysql-driver | `CREATE TABLE` | MySQL |
|
|
8
|
+
| `EnumDef` | enum-driver | `export enum Xxx { … }` + `XXX_LABEL` | 业务代码 |
|
|
9
|
+
| `DtoMessage` | typebox-driver | `Type.Object({…})` + `Static` 推导 | fastify v5 参数校验 |
|
|
10
|
+
|
|
11
|
+
## 生成 SQL
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { buildCreateTableSql } from '@pylonts/dsl';
|
|
15
|
+
|
|
16
|
+
buildCreateTableSql(order); // "CREATE TABLE `order` (\n ..."
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
默认不生成外键约束;需要时:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
buildCreateTableSql(order, { generateForeignKeys: true });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 生成枚举源码
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { renderEnum } from '@pylonts/dsl';
|
|
29
|
+
|
|
30
|
+
renderEnum(AcquiringType); // TS enum 源码
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 生成 TypeBox 源码
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { renderDtoMessage } from '@pylonts/dsl';
|
|
37
|
+
|
|
38
|
+
renderDtoMessage(orderPageQuery, {
|
|
39
|
+
source: 'dto_schema/order/order.dsl.dto.ts',
|
|
40
|
+
resolver: (name) => ({ from: '@mall/enums/user', name }), // 枚举引用解析
|
|
41
|
+
});
|
|
42
|
+
```
|
package/docs/dto.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# 定义 DTO(四种方向)
|
|
2
|
+
|
|
3
|
+
DTO 描述接口出入参。方向决定语义与可选性规则:
|
|
4
|
+
|
|
5
|
+
| 构建器 | 方向 | 用途 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `buildInput` | input | 新增/修改请求体 |
|
|
8
|
+
| `buildOutput` | output | 响应体 |
|
|
9
|
+
| `buildQuery` | query | 分页 + 过滤查询(字段恒为可选) |
|
|
10
|
+
| `buildPk` | pk | 按主键取详情 |
|
|
11
|
+
|
|
12
|
+
## 从表提取字段
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { buildInput, buildQuery, dtoField, from } from '@pylonts/dsl';
|
|
16
|
+
|
|
17
|
+
// 输入:新增订单
|
|
18
|
+
buildInput('OrderAddRequest', { ...from(order, [order.fields.mer_id, order.fields.amount]) });
|
|
19
|
+
|
|
20
|
+
// 输出:订单行
|
|
21
|
+
buildOutput('OrderRow', from(order, [order.fields.id, order.fields.order_no]));
|
|
22
|
+
|
|
23
|
+
// 查询:分页 + 过滤(query 字段恒为可选,.op() 声明比较操作符)
|
|
24
|
+
buildQuery('OrderPageQuery', {
|
|
25
|
+
keyword: dtoField(stringField({ maxLength: 32 })).op('like'),
|
|
26
|
+
...from(order, [order.fields.mer_id]),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 主键:按 id 取详情
|
|
30
|
+
buildPk('OrderDetailRequest', from(order, [order.fields.id]));
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`from(table, fields)` 提取表字段包装为 DTO 字段,字段实例与表共享,`name/schema` 保持指向表。
|
|
34
|
+
|
|
35
|
+
## 独立字段
|
|
36
|
+
|
|
37
|
+
不来自表的内联字段直接用 `dtoField(...)` 包装任意字段构建器,可加 `pattern`、`optional`、`operator`。
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
dtoField(stringField({ maxLength: 32 })).op('like')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 继承基础 schema
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
buildQuery('OrderPageQuery', { ... })
|
|
47
|
+
.include({ from: '@pylonts/core', name: 'PageRequest' }); // 渲染 Type.Intersect([PageRequest, ...])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 关键语义
|
|
51
|
+
|
|
52
|
+
- **字段两层名**:`DtoField.name` 是接口字段名(DTO map key 反写);`field.name` 是数据库列名(表反写)。
|
|
53
|
+
- **可选性优先级**:DTO 层 `optional` 优先于字段层;query 方向所有字段强制可选。
|
|
54
|
+
- **HTTP 传 string**:bigint / decimal / date / time 在接口层渲染为 `Type.String()`,保证精度与序列化语义。
|
|
55
|
+
|
|
56
|
+
## 文件组织
|
|
57
|
+
|
|
58
|
+
- `dto_schema/{module}/*.dto.ts`:DTO 源文件,一个文件可导出多个 DtoMessage;`module` = `project.config.ts` 的 `apps.name`,另加 `common/` 放跨 app 共享 DTO。
|
|
59
|
+
- `gen-cli dto` 按 module 逐个扫描:`dto_schema/{module}/*.dto.ts` → 生成 `dto/{module}/{Name}.dto.ts`。
|
|
60
|
+
- 枚举字段引用 `enums/` 下生成的枚举源文件,DTO 文件通过 `../../enums/{JsName}.enum` 导入。
|