@pylonts/dsl 1.0.5 → 1.1.1

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 (78) hide show
  1. package/README.md +2 -1
  2. package/dist/asset.d.ts +48 -0
  3. package/dist/asset.js +31 -0
  4. package/dist/bases.d.ts +6 -2
  5. package/dist/bases.js +10 -6
  6. package/dist/check-inheritance.js +1 -4
  7. package/dist/curd.d.ts +60 -0
  8. package/dist/curd.js +31 -0
  9. package/dist/db-config.d.ts +8 -0
  10. package/dist/db-config.js +1 -0
  11. package/dist/db.d.ts +52 -0
  12. package/dist/db.js +91 -0
  13. package/dist/dictionary.d.ts +26 -5
  14. package/dist/dictionary.js +21 -8
  15. package/dist/dsl.d.ts +5 -49
  16. package/dist/dsl.js +12 -103
  17. package/dist/dto.d.ts +14 -9
  18. package/dist/dto.js +28 -38
  19. package/dist/enum-driver.d.ts +1 -1
  20. package/dist/enum-driver.js +1 -4
  21. package/dist/flow.d.ts +1 -1
  22. package/dist/flow.js +3 -8
  23. package/dist/import-base.d.ts +15 -0
  24. package/dist/import-base.js +1 -0
  25. package/dist/index.d.ts +21 -17
  26. package/dist/index.js +21 -33
  27. package/dist/mermaid-driver.d.ts +2 -2
  28. package/dist/mermaid-driver.js +2 -6
  29. package/dist/mock.d.ts +30 -0
  30. package/dist/mock.js +18 -0
  31. package/dist/mysql-driver.d.ts +1 -1
  32. package/dist/mysql-driver.js +20 -10
  33. package/dist/page-flow.d.ts +2 -2
  34. package/dist/page-flow.js +2 -6
  35. package/dist/page.d.ts +6 -6
  36. package/dist/page.js +3 -8
  37. package/dist/pattern.js +2 -6
  38. package/dist/patterns/retry.d.ts +1 -1
  39. package/dist/patterns/retry.js +2 -6
  40. package/dist/project.d.ts +18 -13
  41. package/dist/project.js +41 -6
  42. package/dist/prototype.d.ts +1 -1
  43. package/dist/prototype.js +1 -4
  44. package/dist/typebox-driver.d.ts +3 -3
  45. package/dist/typebox-driver.js +28 -24
  46. package/dist/utils.d.ts +2 -0
  47. package/dist/utils.js +5 -4
  48. package/docs/curd.md +111 -0
  49. package/docs/dictionary.md +42 -31
  50. package/docs/driver.md +42 -42
  51. package/docs/dto.md +66 -66
  52. package/docs/enum.md +24 -24
  53. package/docs/project.md +2 -2
  54. package/docs/table.md +50 -15
  55. package/package.json +6 -4
  56. package/src/asset.ts +63 -0
  57. package/src/bases.ts +12 -2
  58. package/src/curd.ts +92 -0
  59. package/src/db-config.ts +8 -0
  60. package/src/db.ts +142 -0
  61. package/src/dictionary.ts +45 -19
  62. package/src/dsl.ts +182 -281
  63. package/src/dto.ts +247 -234
  64. package/src/enum-driver.ts +1 -1
  65. package/src/flow.ts +1 -1
  66. package/src/import-base.ts +15 -0
  67. package/src/index.ts +21 -17
  68. package/src/mermaid-driver.ts +3 -3
  69. package/src/mock.ts +45 -0
  70. package/src/mysql-driver.ts +19 -6
  71. package/src/page-flow.ts +3 -3
  72. package/src/page.ts +7 -7
  73. package/src/patterns/retry.ts +1 -1
  74. package/src/project.ts +90 -54
  75. package/src/prototype.ts +1 -1
  76. package/src/typebox-driver.ts +192 -183
  77. package/src/utils.ts +5 -0
  78. package/src/check-inheritance.ts +0 -86
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildCreateTableSql = buildCreateTableSql;
4
1
  // MySQL driver: converts a TableSchema into a CREATE TABLE statement.
5
2
  function columnType(field) {
6
3
  switch (field.type) {
@@ -31,14 +28,27 @@ function columnType(field) {
31
28
  return 'JSON';
32
29
  }
33
30
  }
31
+ function renderDefault(field) {
32
+ if (field.default === undefined)
33
+ return '';
34
+ // CURRENT_TIMESTAMP is a MySQL function keyword, not a string literal.
35
+ if (field.default === 'CURRENT_TIMESTAMP')
36
+ return ' DEFAULT CURRENT_TIMESTAMP';
37
+ // Numeric columns take a bare literal, not a quoted one.
38
+ if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
39
+ return ` DEFAULT ${field.default}`;
40
+ }
41
+ return ` DEFAULT '${field.default}'`;
42
+ }
34
43
  function columnDef(field, autoIncrement) {
35
44
  const parts = [field.name, columnType(field)];
36
45
  if (field.optional === false)
37
46
  parts.push('NOT NULL');
38
- if (field.default !== undefined)
39
- parts.push(`DEFAULT '${field.default}'`);
47
+ parts.push(renderDefault(field));
40
48
  if (field === autoIncrement)
41
49
  parts.push('AUTO_INCREMENT');
50
+ if (field.description)
51
+ parts.push(`COMMENT '${field.description.replace(/'/g, "\\'")}'`);
42
52
  return parts.join(' ');
43
53
  }
44
54
  function primaryKeyClause(schema) {
@@ -48,13 +58,13 @@ function primaryKeyClause(schema) {
48
58
  return `PRIMARY KEY (${fields.map((f) => f.name).join(', ')})`;
49
59
  }
50
60
  function indexClause(index) {
51
- const fields = Array.isArray(index.fields) ? index.fields : [index.fields];
61
+ const fields = Array.isArray(index.columns) ? index.columns : [index.columns];
52
62
  const kind = index.unique ? 'UNIQUE KEY' : 'KEY';
53
63
  const name = index.name ?? fields.map((f) => f.name).join('_');
54
64
  return `${kind} ${name} (${fields.map((f) => f.name).join(', ')})`;
55
65
  }
56
66
  function foreignKeyClause(name, fk) {
57
- const fields = Array.isArray(fk.fields) ? fk.fields : [fk.fields];
67
+ const fields = Array.isArray(fk.columns) ? fk.columns : [fk.columns];
58
68
  const refs = Array.isArray(fk.references) ? fk.references : [fk.references];
59
69
  const refTable = refs[0].schema?.name;
60
70
  if (!refTable)
@@ -63,8 +73,8 @@ function foreignKeyClause(name, fk) {
63
73
  const refCols = refs.map((r) => r.name).join(', ');
64
74
  return `CONSTRAINT \`${name}\` FOREIGN KEY (${fkCols}) REFERENCES \`${refTable}\` (${refCols})`;
65
75
  }
66
- function buildCreateTableSql(schema, options = {}) {
67
- const lines = Object.values(schema.fields).map((field) => columnDef(field, schema.autoIncrement));
76
+ export function buildCreateTableSql(schema, options = {}) {
77
+ const lines = Object.values(schema.columns).map((field) => columnDef(field, schema.autoIncrement));
68
78
  const pk = primaryKeyClause(schema);
69
79
  if (pk)
70
80
  lines.push(pk);
@@ -75,5 +85,5 @@ function buildCreateTableSql(schema, options = {}) {
75
85
  lines.push(foreignKeyClause(name, fk));
76
86
  }
77
87
  }
78
- return `CREATE TABLE \`${schema.name}\` (\n ${lines.join(',\n ')}\n);`;
88
+ return `CREATE TABLE IF NOT EXISTS \`${schema.name}\` (\n ${lines.join(',\n ')}\n);`;
79
89
  }
@@ -1,5 +1,5 @@
1
- import { SchemaBase } from './dsl';
2
- import { ActionSchema, Page } from './page';
1
+ import { SchemaBase } from './dsl.js';
2
+ import { ActionSchema, Page } from './page.js';
3
3
  export interface PageEdge extends SchemaBase {
4
4
  /** Trigger action; undefined = default path (success/normal). */
5
5
  when?: ActionSchema;
package/dist/page-flow.js CHANGED
@@ -1,12 +1,8 @@
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) {
1
+ export function pageEdge(start, end, when, description) {
6
2
  // Auto name for uniformity with SchemaBase; `when` stays the branch marker.
7
3
  return { name: `${start.name}->${end.name}`, start, end, when, description };
8
4
  }
9
- function definePageFlow(name, schema) {
5
+ export function definePageFlow(name, schema) {
10
6
  const seen = new Set();
11
7
  const pages = [];
12
8
  for (const e of schema.edges) {
package/dist/page.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { SchemaBase } from './dsl';
2
- import { FrontApp } from './project';
1
+ import { SchemaBase } from './dsl.js';
2
+ import { FrontAppSchema } from './project.js';
3
3
  /** Standalone page definition. A page is a shared value object: it lists
4
4
  * the actions a user can perform, and belongs to exactly one frontend app. */
5
5
  export interface PageSchema extends SchemaBase {
6
6
  /** The frontend app this page belongs to (shared instance from project.config). */
7
- app: FrontApp;
7
+ app: FrontAppSchema;
8
8
  /** Actions a user can perform on this page (e.g. submit, approve, reject). */
9
9
  actions: ActionSchema[];
10
10
  }
@@ -15,12 +15,12 @@ export declare function defineAction(name: string, description?: string): Action
15
15
  export declare function definePage(schema: {
16
16
  name: string;
17
17
  description?: string;
18
- app: FrontApp;
18
+ app: FrontAppSchema;
19
19
  actions: ActionSchema[];
20
20
  }): PageSchema;
21
21
  /** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
22
22
  export interface Page extends SchemaBase {
23
23
  /** The frontend app this page belongs to (shared instance from project.config). */
24
- app: FrontApp;
24
+ app: FrontAppSchema;
25
25
  }
26
- export declare function page(app: FrontApp, name: string, description?: string): Page;
26
+ export declare function page(app: FrontAppSchema, name: string, description?: string): Page;
package/dist/page.js CHANGED
@@ -1,14 +1,9 @@
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) {
1
+ export function defineAction(name, description) {
7
2
  return { name, description };
8
3
  }
9
- function definePage(schema) {
4
+ export function definePage(schema) {
10
5
  return { ...schema };
11
6
  }
12
- function page(app, name, description) {
7
+ export function page(app, name, description) {
13
8
  return { name, app, description };
14
9
  }
package/dist/pattern.js CHANGED
@@ -1,13 +1,9 @@
1
- "use strict";
2
1
  // Pattern core: minimal definitions to bootstrap the Flow × Pattern DSL.
3
2
  // A Pattern is a reusable solution ("how to guarantee success") declared as
4
3
  // pure data. Concrete usage fills its params and action injection points.
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.definePattern = definePattern;
7
- exports.ref = ref;
8
- function definePattern(def) {
4
+ export function definePattern(def) {
9
5
  return def;
10
6
  }
11
- function ref(name, args) {
7
+ export function ref(name, args) {
12
8
  return { ref: name, args };
13
9
  }
@@ -1,4 +1,4 @@
1
- import { PatternDef } from '../pattern';
1
+ import { PatternDef } from '../pattern.js';
2
2
  export declare const retryPattern: PatternDef;
3
3
  export interface RetryAction {
4
4
  /** Function to call, e.g. 'queryOrderList' */
@@ -1,19 +1,15 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.retryPattern = void 0;
4
- exports.renderRetry = renderRetry;
5
1
  // Retry pattern: blind mechanical retry for read-only operations.
6
2
  // Valid because a read-only action is idempotent by nature: clicking a query
7
3
  // button any number of times never changes the result, so retrying the same
8
4
  // call is always safe. No idempotency key, no query-and-resume, no compensate.
9
- exports.retryPattern = {
5
+ export const retryPattern = {
10
6
  name: 'retry',
11
7
  params: {
12
8
  max: { type: 'int', min: 1, default: 3 },
13
9
  backoffMs: { type: 'int', min: 0, default: 0 },
14
10
  },
15
11
  };
16
- function renderRetry(args) {
12
+ export function renderRetry(args) {
17
13
  if (args.max !== undefined && args.max < 1)
18
14
  throw new Error('retry: max must be >= 1');
19
15
  if (!args.action.call)
package/dist/project.d.ts CHANGED
@@ -1,41 +1,46 @@
1
- import { SchemaBase } from './dsl';
1
+ import { SchemaBase } from './dsl.js';
2
2
  /** Frontend form factor. Closed enum, extend when new form factors appear. */
3
3
  export type FrontType = 'admin' | 'wxmini';
4
4
  /** A frontend application (e.g. admin console, wechat mini program). */
5
- export interface FrontApp extends SchemaBase {
5
+ export interface FrontAppSchema extends SchemaBase {
6
6
  type: FrontType;
7
7
  /** Source directory relative to project root, e.g. 'web-admin/'. */
8
8
  dir: string;
9
9
  }
10
- /** A backend API service. apps references shared FrontApp instances. */
11
- export interface ProjectApi extends SchemaBase {
10
+ /** A backend API service. apps references shared FrontAppSchema instances. */
11
+ export interface ProjectApiSchema extends SchemaBase {
12
12
  /** Source directory relative to project root, e.g. 'api/'. */
13
13
  dir: string;
14
14
  /** Frontends this API serves. Direct instance references (see defineProject). */
15
- apps: FrontApp[];
15
+ apps: FrontAppSchema[];
16
16
  /** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
17
17
  contextPath?: string;
18
+ /** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
19
+ baseUrl?: string;
18
20
  }
19
21
  /** A third-party system (e.g. wechat pay, unionpay). Owns its own
20
22
  * implementation dir and contract (controller_types), just like an API,
21
23
  * but is not part of this repo's served surface. */
22
- export interface ThirdApi extends SchemaBase {
24
+ export interface ThirdApiSchema extends SchemaBase {
23
25
  /** Source directory relative to project root, e.g. 'wechat/'. */
24
26
  dir: string;
25
27
  }
26
28
  export interface ProjectSchema extends SchemaBase {
27
- apps: FrontApp[];
28
- apis: ProjectApi[];
29
- thirdApis: ThirdApi[];
29
+ apps: FrontAppSchema[];
30
+ apis: ProjectApiSchema[];
31
+ thirdApis: ThirdApiSchema[];
30
32
  }
31
33
  /**
32
- * Defines the project topology. FrontApp instances are shared value objects:
34
+ * Defines the project topology. FrontAppSchema instances are shared value objects:
33
35
  * api.apps references the same instances from project.apps, so an app served
34
36
  * by multiple APIs is defined once and referenced many times.
37
+ *
38
+ * Runtime-validates app type whitelist, unique names and api.apps reference
39
+ * integrity (same style as defineTable/defineCurd).
35
40
  */
36
41
  export declare function defineProject(name: string, schema: {
37
42
  description?: string;
38
- apps: FrontApp[];
39
- apis: ProjectApi[];
40
- thirdApis?: ThirdApi[];
43
+ apps: FrontAppSchema[];
44
+ apis: ProjectApiSchema[];
45
+ thirdApis?: ThirdApiSchema[];
41
46
  }): ProjectSchema;
package/dist/project.js CHANGED
@@ -1,11 +1,46 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defineProject = defineProject;
4
1
  /**
5
- * Defines the project topology. FrontApp instances are shared value objects:
2
+ * Defines the project topology. FrontAppSchema instances are shared value objects:
6
3
  * api.apps references the same instances from project.apps, so an app served
7
4
  * by multiple APIs is defined once and referenced many times.
5
+ *
6
+ * Runtime-validates app type whitelist, unique names and api.apps reference
7
+ * integrity (same style as defineTable/defineCurd).
8
8
  */
9
- function defineProject(name, schema) {
10
- return { name, ...schema, thirdApis: schema.thirdApis ?? [] };
9
+ export function defineProject(name, schema) {
10
+ const project = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
11
+ const appNames = new Set();
12
+ for (const app of project.apps) {
13
+ if (!app.name)
14
+ throw new Error(`project ${name}: app name is required`);
15
+ if (appNames.has(app.name))
16
+ throw new Error(`project ${name}: duplicate app name '${app.name}'`);
17
+ appNames.add(app.name);
18
+ if (app.type !== 'admin' && app.type !== 'wxmini') {
19
+ throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
20
+ }
21
+ if (!app.dir)
22
+ throw new Error(`project ${name}: app '${app.name}' dir is required`);
23
+ }
24
+ const apiNames = new Set();
25
+ for (const api of project.apis) {
26
+ if (!api.name)
27
+ throw new Error(`project ${name}: api name is required`);
28
+ if (apiNames.has(api.name))
29
+ throw new Error(`project ${name}: duplicate api name '${api.name}'`);
30
+ apiNames.add(api.name);
31
+ if (!api.dir)
32
+ throw new Error(`project ${name}: api '${api.name}' dir is required`);
33
+ for (const ref of api.apps) {
34
+ if (!project.apps.includes(ref)) {
35
+ throw new Error(`project ${name}: api '${api.name}' references app '${ref.name}' that is not a shared instance in project.apps (define once and reference it)`);
36
+ }
37
+ }
38
+ }
39
+ for (const third of project.thirdApis) {
40
+ if (!third.name)
41
+ throw new Error(`project ${name}: thirdApi name is required`);
42
+ if (!third.dir)
43
+ throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
44
+ }
45
+ return project;
11
46
  }
@@ -1,4 +1,4 @@
1
- import { SchemaBase } from './dsl';
1
+ import { SchemaBase } from './dsl.js';
2
2
  /** Display metadata for a prototype field. */
3
3
  export interface PrototypeFieldMeta {
4
4
  label: string;
package/dist/prototype.js CHANGED
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.definePrototype = definePrototype;
4
1
  /**
5
2
  * Defines a page prototype. The field keys become the names referenced by
6
3
  * later DTO/table definitions; here they only carry label/description.
7
4
  */
8
- function definePrototype(name, schema) {
5
+ export function definePrototype(name, schema) {
9
6
  return { name, ...schema };
10
7
  }
@@ -1,7 +1,7 @@
1
- import { DtoMessage, ImportRef } from './dto';
2
- export type EnumResolver = (enumName: string) => ImportRef | undefined;
1
+ import { DtoMessage, ImportBase } from './dto.js';
2
+ export type EnumResolver = (enumName: string) => ImportBase | undefined;
3
3
  /** Collect all imports needed to render a DTO: include() bases + enum references. */
4
- export declare function collectDtoImports(schema: DtoMessage, resolver: EnumResolver | undefined, out: Map<string, ImportRef>): void;
4
+ export declare function collectDtoImports(schema: DtoMessage, resolver: EnumResolver | undefined, out: Map<string, ImportBase>): void;
5
5
  /** Render one DTO export (const + type) — no file header, for file-level generation. */
6
6
  export declare function renderDtoExport(schema: DtoMessage, resolver: EnumResolver | undefined): string;
7
7
  /** Render the Static type export for a DTO. */
@@ -1,10 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.collectDtoImports = collectDtoImports;
4
- exports.renderDtoExport = renderDtoExport;
5
- exports.renderDtoTypeExport = renderDtoTypeExport;
6
- exports.renderDtoMessage = renderDtoMessage;
7
- const dto_1 = require("./dto");
8
1
  function renderString(s) {
9
2
  return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
10
3
  }
@@ -83,25 +76,36 @@ function renderField(f, indent, resolver) {
83
76
  return f.isOptional() ? `Type.Optional(${base})` : base;
84
77
  }
85
78
  function renderValue(f, indent, resolver) {
86
- if (f instanceof dto_1.DtoArrayField) {
87
- return `Type.Array(${renderField(f.items(), indent + 1, resolver)})`;
79
+ if (f.field.type === 'array') {
80
+ const items = f.field.items;
81
+ // Referenced DTO element — render by name (same-file export), not expanded.
82
+ if (isDtoMessage(items))
83
+ return `Type.Array(${items.name})`;
84
+ return `Type.Array(${renderField(items, indent + 1, resolver)})`;
88
85
  }
89
- if (f instanceof dto_1.DtoObjectField) {
90
- return renderObject(f.properties(), indent + 1, resolver);
86
+ if (f.field.type === 'object') {
87
+ return renderObject(f.field.properties, indent + 1, resolver);
91
88
  }
92
89
  // DtoField only wraps a database Field; array/object defs live in the subclasses.
93
- // DTO-level default wins over the DB field default; the DB default (string)
94
- // is used as a fallback so from() picks carry it into the API contract.
95
- const defaultValue = f.default !== undefined ? f.default : f.field.default;
96
- return renderBasic(f.field, f.pattern, defaultValue, resolver);
90
+ // Only DTO-level defaults (setDefault) are emitted as TypeBox default
91
+ // annotations; DB field defaults are not carried into the API contract.
92
+ return renderBasic(f.field, f.pattern, f.default, resolver);
93
+ }
94
+ /** Structural check — DtoMessage instances may come from a different module copy, so instanceof is unreliable. */
95
+ function isDtoMessage(v) {
96
+ if (typeof v !== 'object' || v === null)
97
+ return false;
98
+ return v.type === 'dto';
97
99
  }
98
100
  function collectEnumImports(f, resolver, out) {
99
- if (f instanceof dto_1.DtoArrayField) {
100
- collectEnumImports(f.items(), resolver, out);
101
+ if (f.field.type === 'array') {
102
+ const items = f.field.items;
103
+ if (!isDtoMessage(items))
104
+ collectEnumImports(items, resolver, out);
101
105
  return;
102
106
  }
103
- if (f instanceof dto_1.DtoObjectField) {
104
- for (const child of Object.values(f.properties()))
107
+ if (f.field.type === 'object') {
108
+ for (const child of Object.values(f.field.properties))
105
109
  collectEnumImports(child, resolver, out);
106
110
  return;
107
111
  }
@@ -113,14 +117,14 @@ function collectEnumImports(f, resolver, out) {
113
117
  }
114
118
  }
115
119
  /** Collect all imports needed to render a DTO: include() bases + enum references. */
116
- function collectDtoImports(schema, resolver, out) {
120
+ export function collectDtoImports(schema, resolver, out) {
117
121
  for (const base of schema.bases ?? [])
118
122
  out.set(`${base.from}#${base.name}`, base);
119
123
  for (const f of Object.values(schema.fields))
120
124
  collectEnumImports(f, resolver, out);
121
125
  }
122
126
  /** Render one DTO export (const + type) — no file header, for file-level generation. */
123
- function renderDtoExport(schema, resolver) {
127
+ export function renderDtoExport(schema, resolver) {
124
128
  const object = renderObject(schema.fields, 1, resolver);
125
129
  const bases = schema.bases ?? [];
126
130
  const body = bases.length > 0
@@ -129,10 +133,10 @@ function renderDtoExport(schema, resolver) {
129
133
  return `export const ${schema.name} = ${body};`;
130
134
  }
131
135
  /** Render the Static type export for a DTO. */
132
- function renderDtoTypeExport(name) {
136
+ export function renderDtoTypeExport(name) {
133
137
  return `export type ${name} = Static<typeof ${name}>;`;
134
138
  }
135
- function renderDtoMessage(schema, options = {}) {
139
+ export function renderDtoMessage(schema, options = {}) {
136
140
  const { resolver, source } = options;
137
141
  const imports = new Map();
138
142
  collectDtoImports(schema, resolver, imports);
@@ -140,7 +144,7 @@ function renderDtoMessage(schema, options = {}) {
140
144
  '// AUTO-GENERATED by typebox-driver — DO NOT EDIT',
141
145
  ...(source !== undefined ? [`// Source: ${source}`] : []),
142
146
  "import { Type, Static } from '@sinclair/typebox';",
143
- ...[...imports.values()].map((r) => `import { ${r.name} } from '${r.from}';`),
147
+ ...[...imports.values()].map((r) => `import${r.type ? ' type' : ''} { ${r.name} } from '${r.from}';`),
144
148
  ];
145
149
  return [
146
150
  ...header,
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
2
2
  export declare function toCamelCase(name: string): string;
3
+ /** snake_case → PascalCase: mer_id → MerId */
4
+ export declare function toPascalCase(snake: string): string;
package/dist/utils.js CHANGED
@@ -1,8 +1,9 @@
1
- "use strict";
2
1
  // ── naming conversions ──
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.toCamelCase = toCamelCase;
5
2
  /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
6
- function toCamelCase(name) {
3
+ export function toCamelCase(name) {
7
4
  return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
8
5
  }
6
+ /** snake_case → PascalCase: mer_id → MerId */
7
+ export function toPascalCase(snake) {
8
+ return snake.replace(/(^|_)([a-z])/g, (_m, _p, c) => c.toUpperCase());
9
+ }
package/docs/curd.md ADDED
@@ -0,0 +1,111 @@
1
+ # 管理端 CRUD 页面标准(CurdSchema)
2
+
3
+ CurdSchema 是**管理端专用**(`FrontAppSchema.type === 'admin'`)的 CRUD 页面标准:绑定一张实体表 + 一个管理端 app,描述列表页与新增/编辑/详情动作页生成所需的全部页面语义。一条 CurdSchema = 列表页(+ 动作页)的生成规格。
4
+
5
+ 页面定义文件按实体组织:`{project}/pages/{entity}.curd.ts`(与 `schema/*.table.ts` 平级)。
6
+
7
+ **CurdSchema 只依赖 table schema(`Field` 实例),不挂钩 DTO(`DtoMessage`)**——DTO 由生成器按标准从 `columns` 推导。
8
+
9
+ ## 定义
10
+
11
+ ```ts
12
+ import { defineCurd } from '@pylonts/dsl';
13
+
14
+ export const orderCurd = defineCurd('order-curd', {
15
+ description: '订单管理',
16
+ app: webAdmin, // 所属管理端(project.config.ts 的 FrontAppSchema 共享实例)
17
+ table: order, // 绑定实体表(共享实例)
18
+ title: '订单管理',
19
+ section: '订单管理', // 必填:sidebar 分组名
20
+ actions: [defineAction('EXPORT', '导出订单')], // 额外操作按钮
21
+ actionPages: {
22
+ add: { mode: 'modal', columns: [order.columns.order_no, order.columns.mer_id] },
23
+ update: { mode: 'modal', columns: [order.columns.id, order.columns.order_no] },
24
+ detail: { mode: 'route', columns: [order.columns.id, order.columns.order_no, order.columns.amount] },
25
+ },
26
+ list: {
27
+ columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 可跨表
28
+ keyword: { columns: [order.columns.order_no] }, // 模糊搜索(本表字段)
29
+ orderBy: { column: order.columns.id, direction: 'desc' },
30
+ searchFields: [{ field: order.columns.mer_id }, { field: order.columns.order_no, op: 'like' }],
31
+ columnTitles: { order_no: '订单号', name: '商户名称' }, // Field.name → 文案
32
+ },
33
+ });
34
+ ```
35
+
36
+ ## 字段
37
+
38
+ | 字段 | 类型 | 说明 |
39
+ |---|---|---|
40
+ | `app` | `FrontAppSchema` | 所属管理端(共享实例,`type` 必须为 `'admin'`) |
41
+ | `table` | `TableSchema` | 绑定实体表(共享实例) |
42
+ | `title` | `string` | 列表页中文标题 |
43
+ | `section` | `string` | **必填**:sidebar 分组名(`gen-cli curd` 据此生成路由注册的 `section` 字段) |
44
+ | `actions?` | `ActionSchema[]` | 页面额外可执行动作(标准 CRUD 之外,如导出、审核) |
45
+ | `actionPages?` | `{ add? / update? / detail? }` | 动作页:`{ mode: 'modal' \| 'route'; columns: Field[] }` |
46
+ | `list` | `CurdListConfig` | 列表页配置(必填) |
47
+
48
+ ### ActionPage
49
+
50
+ | 字段 | 类型 | 说明 |
51
+ |---|---|---|
52
+ | `mode` | `'modal' \| 'route'` | 弹窗或独立路由 |
53
+ | `columns` | `Field[]` | 该页面渲染的字段,**必填非空**——前端要显示的字段必须全部显式列出 |
54
+
55
+ ### CurdListConfig
56
+
57
+ | 字段 | 类型 | 说明 |
58
+ |---|---|---|
59
+ | `columns` | `Field[]` | 列表列,**必填非空**——前端要显示的字段必须全部显式列出;可含跨表字段(见下) |
60
+ | `keyword?` | `{ columns: Field[] }` | 模糊搜索,columns 必须是**本表字段实例** |
61
+ | `orderBy` | `{ column: Field; direction: 'asc' \| 'desc' }` | 默认排序,**必填**,column 与 direction 都必填;column 必须是**本表字段实例** |
62
+ | `searchFields?` | `{ field: Field; op?: Operator }[]` | 搜索条件字段,op 默认 `'eq'`,可选 `eq/gt/gte/lt/lte/like/ne` |
63
+ | `columnTitles?` | `Record<string, string>` | 列标题覆盖:`Field.name` → 中文文案 |
64
+
65
+ ## 跨表字段
66
+
67
+ `columns` / `searchFields` 里的 `Field` 实例可指向**本表或其他表**的列——列表列与搜索条件因此可以显示关联表字段(如订单列表显示商户名称):
68
+
69
+ ```ts
70
+ list: {
71
+ columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 跨表:字段指向 merchant.name
72
+ }
73
+ ```
74
+
75
+ ## 默认与校验
76
+
77
+ - `list.columns` / `actionPages.*.columns` **必填非空**(不允许省略、不允许空数组)——前端显示什么必须显式定义
78
+ - `list.orderBy` **必填**,`column` 与 `direction` 都必填(规格:默认主键 desc 由定义方显式写出)
79
+ - 运行时校验(`defineCurd`,仿 `defineTable` 强校验风格):
80
+ - `app.type` 必须为 `'admin'`,否则抛错
81
+ - 所有 `columns` 非空,否则抛错
82
+ - `list.keyword.columns` / `list.orderBy.column` 必须属于 `table`,否则抛错
83
+ - `list.columns` / `list.searchFields` 允许跨表,**不校验归属**
84
+ - 生成时校验(curd 生成器,`DtoSchemaGen.add` / `update`):
85
+ - 表配置 `autoIncrement` 或 `generator`(主键由服务端生成)时,`actionPages.add.columns` **不允许包含主键字段**,否则抛错——AddRequest 不携带服务端生成的主键
86
+ - `actionPages.update.columns` **必须包含主键字段**,否则抛错——UpdateRequest 靠主键定位记录
87
+
88
+ ## DTO 推导(生成器约定)
89
+
90
+ DTO 由 curd 生成器从 `CurdSchema` 按标准命名推导,页面语义不持有 DTO 实例:
91
+
92
+ | DTO | 命名 | 字段来源 |
93
+ |---|---|---|
94
+ | Row | `{Pascal}Row` | `list.columns` |
95
+ | AddRequest | `{Pascal}AddRequest` | `actionPages.add.columns` |
96
+ | UpdateRequest | `{Pascal}UpdateRequest` | `actionPages.update.columns` |
97
+ | DetailRequest | `{Pascal}DetailRequest` | 主键 |
98
+ | DetailResponse | `{Pascal}DetailResponse` | `actionPages.detail.columns` |
99
+
100
+ ## 与旧 PageConfig 的差异
101
+
102
+ | PageConfig(旧方案,已废弃) | CurdSchema |
103
+ |---|---|
104
+ | `module: string` | 由 `app` 推导(后端模块 == app 1:1) |
105
+ | `schema: 'bd'` 字符串 | `table: TableSchema` 实例(类型安全) |
106
+ | `operations: { label, action }` | `actions: ActionSchema[]` |
107
+ | `detail.mode` 单例 | `actionPages.detail.mode` |
108
+ | `forms.add / forms.update` | `actionPages.add / actionPages.update` |
109
+ | `keyword` / `orderBy` / `columnTitles` | `list.keyword` / `list.orderBy` / `list.columnTitles`(列改字段实例引用) |
110
+ | `naming` | 去掉(DTO 命名是生成器约定,非页面语义) |
111
+ | DTO 引用(`request` / `fields` / `DtoFields`) | 去掉(DTO 由生成器推导,页面只依赖 table) |
@@ -1,31 +1,42 @@
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
- - 未收录短语的实体保留全名作词干(不臆造缩写),评审时再裁决收录。
1
+ # 短语词典 (Dictionary)
2
+
3
+ 词典是与团队达成共识的基础知识库:**某词代表什么**(语义/定义层面),不是物理形式。短语定了,字段命名、外键命名就都有依据——全项目只说同一种话。
4
+
5
+ - 是基础知识库,很少变更。
6
+ - **能引用就引用**:魔法字符串只在首次出现时使用,之后一律引用词典条目。
7
+
8
+ ## 规范位置:schema/_dictionary.ts
9
+
10
+ **所有短语统一定义在 `schema/_dictionary.ts`**,一个文件一处定义;`*.table.ts` 从该文件 import 短语,禁止在表文件里内联定义短语。
11
+
12
+ ## 两种短语,两种命名规则
13
+
14
+ 短语分两类,参与不同的字段命名校验:
15
+
16
+ | 类型 | 定义函数 | 含义 | 命名规则 | 例子 |
17
+ |---|---|---|---|---|
18
+ | `entity` | `defineEntityPhrase` | 实体缩写 | 字段名**首段**(实体领先) | `mer_id`、`bd_rate` |
19
+ | `business` | `defineBusinessPhrase` | 实体的属性 | 字段名**末段**(属性收尾) | `bd_rate`、`acquiring_rate` |
20
+
21
+ ## 口径:name 即短语
22
+
23
+ 两个定义函数返回的条目**就是短语本身**,不是"全名 + 缩写"两套——`name` 即短语词干(列名前缀),`label`/`description` 解释语义。**变量名与 name 一致**(小写)。短语要短(mer / bd / amt 三字母左右),**不要用长语**:引用商户实体的字段叫 `mer_id`,不叫 `merchant_id`。
24
+
25
+ ## 定义
26
+
27
+ ```ts
28
+ // schema/_dictionary.ts
29
+ import { defineEntityPhrase, defineBusinessPhrase } from '@pylonts/dsl';
30
+
31
+ const bd = defineEntityPhrase({ name: 'bd', label: 'BD推广员', description: '线下拓展商户、辅助入驻的推广人员' });
32
+ const mer = defineEntityPhrase({ name: 'mer', label: '商户', description: '入驻平台的商户' });
33
+ const amt = defineBusinessPhrase({ name: 'amt', label: '金额', description: '交易金额,单位分' });
34
+ const rate = defineBusinessPhrase({ name: 'rate', label: '费率', description: '结算费率' });
35
+ ```
36
+
37
+ ## 使用
38
+
39
+ - **表链接实体**:`TableSchema.phrase` 引用**实体短语**条目,声明本表归属哪个实体(见 [table.md](./table.md) 的外键检查链)。关联表等多实体场景不需要。
40
+ - 业务短语供字段命名/文档使用,跨团队对齐。
41
+ - 未收录短语的实体保留全名作词干(不臆造缩写),评审时再裁决收录。
42
+ - 字段命名校验按类型区分:实体短语必须首段、业务短语必须末段(见 [field-check.md](../../lint/docs/field-check.md))。