@pylonts/dsl 1.0.1 → 1.0.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.
@@ -1,25 +1,8 @@
1
1
  import { SchemaBase } from './dsl';
2
- /** A vocabulary entry: name is written back from the dictionary key. */
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
- /**
21
- * Merges business phrase packages (one file per industry) into a single flat
22
- * map. Package metadata (name/description) is dropped; duplicate keys across
23
- * packages throw.
24
- */
25
- export declare function mergeBusinessDictionaries(packages: DictionarySchema[]): Record<string, DictionaryEntry>;
7
+ /** Creates a phrase entry. */
8
+ export declare function definePhrase(extra: DictionaryEntry): DictionaryEntry;
@@ -1,45 +1,7 @@
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
- /**
30
- * Merges business phrase packages (one file per industry) into a single flat
31
- * map. Package metadata (name/description) is dropped; duplicate keys across
32
- * packages throw.
33
- */
34
- function mergeBusinessDictionaries(packages) {
35
- const merged = {};
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;
4
+ /** Creates a phrase entry. */
5
+ function definePhrase(extra) {
6
+ return extra;
45
7
  }
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,4 @@
1
+ import { FlowSchema } from './flow';
2
+ import { PageFlow } from './page-flow';
3
+ export declare function renderFlowMermaid(schema: FlowSchema): string;
4
+ export declare function renderPageFlowMermaid(schema: PageFlow): string;
@@ -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;
@@ -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
@@ -7,5 +7,5 @@ exports.defineProject = defineProject;
7
7
  * by multiple APIs is defined once and referenced many times.
8
8
  */
9
9
  function defineProject(name, schema) {
10
- return { name, ...schema };
10
+ return { name, ...schema, thirdApis: schema.thirdApis ?? [] };
11
11
  }
@@ -0,0 +1,20 @@
1
+ # 短语词典 (Dictionary)
2
+
3
+ 词典是与团队达成共识的基础知识库:**某词代表什么**(语义/定义层面),不是物理形式。短语定了,字段命名、外键命名就都有依据——全项目只说同一种话。
4
+
5
+ - 是基础知识库,很少变更。
6
+ - **能引用就引用**:魔法字符串只在首次出现时使用,之后一律引用词典条目。
7
+
8
+ ## 定义
9
+
10
+ ```ts
11
+ import { definePhrase } from '@pylonts/dsl';
12
+
13
+ const BD = definePhrase({ label: 'BD推广员', description: '线下拓展商户、辅助入驻的推广人员' });
14
+ const Amt = definePhrase({ label: '金额', description: '交易金额,单位分' });
15
+ ```
16
+
17
+ ## 使用
18
+
19
+ - **表链接实体**:`TableSchema.phrase` 引用实体条目,声明本表归属哪个实体(见 [table.md](./table.md) 的外键检查链)。关联表等多实体场景不需要。
20
+ - 业务短语供字段命名/文档使用,跨团队对齐。
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,54 @@
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.merchant_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.merchant_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()`,保证精度与序列化语义。
package/docs/enum.md ADDED
@@ -0,0 +1,24 @@
1
+ # 定义枚举(可跨表复用)
2
+
3
+ 枚举定义与字段分离:`defineEnum` 产生共享定义(纯值对象),`enumField` 引用它。同一枚举可被多张表 / 多个 DTO 复用,只生成一次。
4
+
5
+ ```ts
6
+ import { defineEnum, enumField } from '@pylonts/dsl';
7
+
8
+ // 共享定义(_common.ts 等公共文件)
9
+ export const AcquiringType = defineEnum('AcquiringType', 'string', [
10
+ { symbol: 'WECHAT', value: 'wechat', label: '微信' },
11
+ { symbol: 'UNIONPAY', value: 'unionpay', label: '银联商务' },
12
+ ]);
13
+
14
+ // 字段引用(每表独立实例)
15
+ buildTable('merchant', { fields: { acquiring_type: enumField({ enum: AcquiringType }) }, ... });
16
+ buildTable('order', { fields: { acquiring_type: enumField({ enum: AcquiringType }) }, ... });
17
+ ```
18
+
19
+ - 字段实例每表独立(列名、可选性随表),枚举定义全局共享。
20
+ - 枚举由 enum-driver 生成独立文件;typebox-driver 只渲染 `Type.Enum(名称)` + import 引用,不内联。
21
+
22
+ ## 生成枚举源码
23
+
24
+ 见 [driver.md](./driver.md)。
@@ -0,0 +1 @@
1
+ 连接 MySQL 时,必须配置:`supportBigNumbers: true, bigNumberStrings: true`