@pylonts/dsl 1.1.5 → 1.1.11

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 (100) hide show
  1. package/README.md +4 -0
  2. package/dist/action.d.ts +32 -0
  3. package/dist/action.js +14 -0
  4. package/dist/aggregate.d.ts +38 -0
  5. package/dist/aggregate.js +46 -0
  6. package/dist/business-flow.d.ts +9 -0
  7. package/dist/business-flow.js +72 -0
  8. package/dist/controller.d.ts +35 -0
  9. package/dist/controller.js +17 -0
  10. package/dist/convert.d.ts +37 -7
  11. package/dist/convert.js +23 -2
  12. package/dist/curd.d.ts +7 -12
  13. package/dist/curd.js +10 -1
  14. package/dist/dao.d.ts +140 -3
  15. package/dist/dao.js +307 -2
  16. package/dist/db.d.ts +6 -0
  17. package/dist/db.js +13 -0
  18. package/dist/domain-event.d.ts +48 -0
  19. package/dist/domain-event.js +24 -0
  20. package/dist/dsl.d.ts +32 -2
  21. package/dist/dsl.js +13 -0
  22. package/dist/dto.d.ts +8 -2
  23. package/dist/dto.js +21 -9
  24. package/dist/endpoint.d.ts +15 -0
  25. package/dist/endpoint.js +3 -0
  26. package/dist/entity.d.ts +29 -0
  27. package/dist/entity.js +13 -0
  28. package/dist/exception.d.ts +20 -0
  29. package/dist/exception.js +33 -0
  30. package/dist/expr.d.ts +45 -0
  31. package/dist/expr.js +32 -0
  32. package/dist/field-rule.d.ts +20 -0
  33. package/dist/field-rule.js +19 -0
  34. package/dist/filter.d.ts +45 -0
  35. package/dist/filter.js +21 -0
  36. package/dist/flow-script.d.ts +108 -0
  37. package/dist/flow-script.js +505 -0
  38. package/dist/flow.d.ts +309 -10
  39. package/dist/flow.js +819 -22
  40. package/dist/index.d.ts +12 -1
  41. package/dist/index.js +14 -1
  42. package/dist/mermaid-driver.js +278 -9
  43. package/dist/method.d.ts +11 -0
  44. package/dist/method.js +3 -0
  45. package/dist/mysql-driver.js +7 -0
  46. package/dist/project.d.ts +5 -4
  47. package/dist/project.js +14 -2
  48. package/dist/provider.d.ts +6 -11
  49. package/dist/provider.js +2 -2
  50. package/dist/repository.d.ts +26 -0
  51. package/dist/repository.js +8 -0
  52. package/dist/service.d.ts +30 -8
  53. package/dist/service.js +62 -2
  54. package/dist/third-service.d.ts +80 -0
  55. package/dist/third-service.js +97 -0
  56. package/dist/typebox-driver.d.ts +6 -0
  57. package/dist/typebox-driver.js +77 -12
  58. package/dist/utils.d.ts +32 -10
  59. package/dist/utils.js +32 -11
  60. package/docs/aggregate.md +110 -0
  61. package/docs/dao-generation.md +478 -0
  62. package/docs/ddd-principles.md +75 -0
  63. package/docs/domain-event.md +137 -0
  64. package/docs/dto.md +73 -66
  65. package/docs/keyword-matcher.md +182 -0
  66. package/docs/third-service.md +122 -0
  67. package/docs/token.md +327 -0
  68. package/docs/trans-reentrant.md +85 -0
  69. package/package.json +27 -5
  70. package/src/action.ts +51 -10
  71. package/src/aggregate.ts +104 -0
  72. package/src/business-flow.ts +80 -0
  73. package/src/controller.ts +54 -0
  74. package/src/convert.ts +78 -15
  75. package/src/curd.ts +104 -93
  76. package/src/dao.ts +486 -13
  77. package/src/db.ts +199 -181
  78. package/src/domain-event.ts +74 -0
  79. package/src/dsl.ts +48 -2
  80. package/src/dto.ts +266 -247
  81. package/src/entity.ts +43 -0
  82. package/src/exception.ts +53 -0
  83. package/src/expr.ts +65 -0
  84. package/src/field-rule.ts +47 -0
  85. package/src/filter.ts +70 -0
  86. package/src/flow-script.ts +696 -0
  87. package/src/flow.ts +1226 -103
  88. package/src/index.ts +47 -33
  89. package/src/mermaid-driver.ts +339 -84
  90. package/src/method.ts +20 -0
  91. package/src/mysql-driver.ts +7 -0
  92. package/src/project.ts +114 -97
  93. package/src/repository.ts +35 -0
  94. package/src/service.ts +107 -20
  95. package/src/third-service.ts +192 -0
  96. package/src/typebox-driver.ts +86 -11
  97. package/src/utils.ts +74 -26
  98. package/dist/check-inheritance.d.ts +0 -9
  99. package/dist/check-inheritance.js +0 -58
  100. package/src/provider.ts +0 -73
@@ -1,5 +1,6 @@
1
1
  import { DtoArrayField, DtoField, DtoMessage, DtoObjectField, ImportBase, ImportRef } from './dto.js';
2
- import { Field } from './dsl.js';
2
+ import { EnumField, Field } from './dsl.js';
3
+ import type { ThirdMethodSchema } from './third-service.js';
3
4
 
4
5
  // TypeBox driver: renders a DtoMessage into TypeBox TypeScript source.
5
6
  // Shape matches the codegen product consumed by fastify v5 TypeBoxTypeProvider:
@@ -27,6 +28,7 @@ function renderBasic(
27
28
  pattern: string | undefined,
28
29
  defaultValue: unknown,
29
30
  resolver: EnumResolver | undefined,
31
+ indent = 0,
30
32
  ): string {
31
33
  if (pattern !== undefined && field.type !== 'string') {
32
34
  throw new Error(`pattern is only supported on string fields, got ${field.type} (${field.name})`);
@@ -77,15 +79,39 @@ function renderBasic(
77
79
  }
78
80
  return `Type.Enum(${ref.name})`;
79
81
  }
82
+ case 'aggregate':
83
+ // Aggregate query outputs: count/sum(int) are numbers, everything else
84
+ // arrives as a precision string.
85
+ return field.jsType === 'number' ? 'Type.Number()' : 'Type.String()';
86
+ case 'array':
87
+ return `Type.Array(${renderFieldValue(field.items, indent, resolver)})`;
88
+ case 'object':
89
+ return renderFieldObject(field.properties, indent + 1, resolver);
80
90
  default:
81
91
  // Field union is exhaustive; this branch is unreachable at runtime.
82
92
  throw new Error(`unsupported field type: ${String((field as Field).type)}`);
83
93
  }
84
94
  }
85
95
 
86
- function renderObject(fields: Record<string, DtoField>, indent: number, resolver: EnumResolver | undefined): string {
96
+ /** Render a plain Field value (wire-format nested fields), wrapping optional. */
97
+ function renderFieldValue(field: Field, indent: number, resolver: EnumResolver | undefined): string {
98
+ const base = renderBasic(field, undefined, undefined, resolver, indent);
99
+ return field.optional ? `Type.Optional(${base})` : base;
100
+ }
101
+
102
+ /** Render a plain Field object (wire-format nested object). */
103
+ function renderFieldObject(properties: Record<string, Field>, indent: number, resolver: EnumResolver | undefined): string {
104
+ const pad = ' '.repeat(indent);
105
+ const entries = Object.entries(properties).map(([name, f]) => `${pad}${name}: ${renderFieldValue(f, indent, resolver)}`);
106
+ return `Type.Object({\n${entries.join(',\n')}\n${' '.repeat(indent - 1)}})`;
107
+ }
108
+
109
+ function renderObject(fields: Record<string, DtoField | Field>, indent: number, resolver: EnumResolver | undefined): string {
87
110
  const pad = ' '.repeat(indent);
88
- const entries = Object.entries(fields).map(([name, f]) => `${pad}${name}: ${renderField(f, indent, resolver)}`);
111
+ const entries = Object.entries(fields).map(([name, f]) => {
112
+ const rendered = isDtoField(f) ? renderField(f, indent, resolver) : renderFieldValue(f, indent, resolver);
113
+ return `${pad}${name}: ${rendered}`;
114
+ });
89
115
  return `Type.Object({\n${entries.join(',\n')}\n${' '.repeat(indent - 1)}})`;
90
116
  }
91
117
 
@@ -99,7 +125,8 @@ function renderValue(f: DtoField, indent: number, resolver: EnumResolver | undef
99
125
  const items = f.field.items;
100
126
  // Referenced DTO element — render by name (same-file export), not expanded.
101
127
  if (isDtoMessage(items)) return `Type.Array(${items.name})`;
102
- return `Type.Array(${renderField(items, indent + 1, resolver)})`;
128
+ if (isDtoField(items)) return `Type.Array(${renderField(items, indent + 1, resolver)})`;
129
+ return `Type.Array(${renderFieldValue(items, indent, resolver)})`;
103
130
  }
104
131
  if (f.field.type === 'object') {
105
132
  return renderObject(f.field.properties, indent + 1, resolver);
@@ -107,7 +134,7 @@ function renderValue(f: DtoField, indent: number, resolver: EnumResolver | undef
107
134
  // DtoField only wraps a database Field; array/object defs live in the subclasses.
108
135
  // Only DTO-level defaults (setDefault) are emitted as TypeBox default
109
136
  // annotations; DB field defaults are not carried into the API contract.
110
- return renderBasic(f.field as Field, f.pattern, f.default, resolver);
137
+ return renderBasic(f.field as Field, f.pattern, f.default, resolver, indent);
111
138
  }
112
139
 
113
140
  /** Structural check — DtoMessage instances may come from a different module copy, so instanceof is unreliable. */
@@ -116,6 +143,12 @@ function isDtoMessage(v: unknown): v is DtoMessage {
116
143
  return (v as Record<string, unknown>).type === 'dto';
117
144
  }
118
145
 
146
+ /** Structural check — a DtoField wraps a Field in a .field property and has no .type of its own. */
147
+ function isDtoField(v: unknown): v is DtoField {
148
+ if (typeof v !== 'object' || v === null) return false;
149
+ return 'field' in v && !('type' in v);
150
+ }
151
+
119
152
  function collectEnumImports(
120
153
  f: DtoField,
121
154
  resolver: EnumResolver | undefined,
@@ -123,18 +156,45 @@ function collectEnumImports(
123
156
  ): void {
124
157
  if (f.field.type === 'array') {
125
158
  const items = f.field.items;
126
- if (!isDtoMessage(items)) collectEnumImports(items, resolver, out);
159
+ if (isDtoMessage(items)) return;
160
+ if (isDtoField(items)) {
161
+ collectEnumImports(items, resolver, out);
162
+ return;
163
+ }
164
+ collectFieldEnumImports(items, resolver, out);
127
165
  return;
128
166
  }
129
167
  if (f.field.type === 'object') {
130
- for (const child of Object.values(f.field.properties)) collectEnumImports(child, resolver, out);
168
+ for (const child of Object.values(f.field.properties)) {
169
+ if (isDtoField(child)) collectEnumImports(child, resolver, out);
170
+ else collectFieldEnumImports(child, resolver, out);
171
+ }
131
172
  return;
132
173
  }
133
- if (f.field.type === 'enum') {
134
- const ref = resolver?.(f.field.enum.jsName);
135
- if (!ref) throw new Error(`enum field ${f.field.name}: no import ref for ${f.field.enum.jsName} — pass an EnumResolver`);
136
- out.set(`${ref.from}#${ref.name}`, ref);
174
+ if (f.field.type === 'enum') collectEnumRef(f.field, resolver, out);
175
+ }
176
+
177
+ /** Enum import collection over a plain Field (wire-format nested fields). */
178
+ function collectFieldEnumImports(
179
+ field: Field,
180
+ resolver: EnumResolver | undefined,
181
+ out: Map<string, ImportBase>,
182
+ ): void {
183
+ if (field.type === 'array') {
184
+ collectFieldEnumImports(field.items, resolver, out);
185
+ return;
137
186
  }
187
+ if (field.type === 'object') {
188
+ for (const child of Object.values(field.properties)) collectFieldEnumImports(child, resolver, out);
189
+ return;
190
+ }
191
+ if (field.type === 'enum') collectEnumRef(field, resolver, out);
192
+ }
193
+
194
+ function collectEnumRef(field: EnumField, resolver: EnumResolver | undefined, out: Map<string, ImportBase>): void {
195
+ const ref = resolver?.(field.enum.jsName);
196
+ if (!ref) throw new Error(`enum field ${field.name}: no import ref for ${field.enum.jsName} — pass an EnumResolver`);
197
+ out.set(`${ref.from}#${ref.name}`, ref);
138
198
  }
139
199
 
140
200
  /** Collect all imports needed to render a DTO: include() bases + enum references. */
@@ -191,4 +251,19 @@ function renderBase(base: ImportRef): string {
191
251
  if (base.args === undefined || base.args.length === 0) return base.name;
192
252
  const args = base.args.map((a) => (typeof a === 'string' ? a : a.name));
193
253
  return `${base.name}(${args.join(', ')})`;
254
+ }
255
+
256
+ /** Render one third-party method message export (const only — pair with
257
+ * renderDtoTypeExport for the Static type). */
258
+ export function renderThirdMethodExport(schema: ThirdMethodSchema, resolver: EnumResolver | undefined): string {
259
+ return `export const ${schema.name} = ${renderFieldObject(schema.fields, 1, resolver)};`;
260
+ }
261
+
262
+ /** Collect all imports needed to render a third-party method message: enum references. */
263
+ export function collectThirdMethodImports(
264
+ schema: ThirdMethodSchema,
265
+ resolver: EnumResolver | undefined,
266
+ out: Map<string, ImportBase>,
267
+ ): void {
268
+ for (const f of Object.values(schema.fields)) collectFieldEnumImports(f, resolver, out);
194
269
  }
package/src/utils.ts CHANGED
@@ -1,27 +1,75 @@
1
- // ── naming conversions ──
2
-
3
- /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
4
- export function toCamelCase(name: string): string {
5
- return name.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
6
- }
7
-
8
- /** snake_case PascalCase: mer_id MerId */
9
- export function toPascalCase(snake: string): string {
10
- return snake.replace(/(^|_)([a-z])/g, (_m, _p, c: string) => c.toUpperCase());
11
- }
12
-
13
- // ── schema ──
14
-
15
- import { SchemaBase } from './dsl.js';
16
- import { FrontAppSchema } from './project.js';
17
-
18
- /** A utility module bound to exactly one frontend app. */
19
- export interface UtilsSchema extends SchemaBase {
20
- type: 'utils';
21
- /** The frontend app this utility module belongs to (shared instance from project.config). */
22
- app: FrontAppSchema;
23
- }
24
-
25
- export function defineUtils(name: string, app: FrontAppSchema, description?: string): UtilsSchema {
26
- return { name, type: 'utils', app, description };
1
+ import type { Field, SchemaBase } from './dsl.js';
2
+ import type { FrontAppSchema, ProjectApiSchema } from './project.js';
3
+
4
+ // Base utility modules business-agnostic helpers with full method
5
+ // signatures (e.g. DateTimeUtils.format). Called at the service layer;
6
+ // their args/results are own wire types, independent of business schemas.
7
+
8
+ /** A utility method with a full signature. */
9
+ export interface UtilsMethodSchema extends SchemaBase {
10
+ type: 'utilsMethod';
11
+ /** The utility module this method belongs to. */
12
+ schema: UtilsSchema;
13
+ /** Input fields. */
14
+ args: Record<string, Field>;
15
+ /** Output field. */
16
+ result: Field;
17
+ }
18
+
19
+ /** Method input for defineUtils: type/schema/name are set by the builder. */
20
+ export type UtilsMethodDef = Omit<UtilsMethodSchema, 'type' | 'schema' | 'name'>;
21
+
22
+ /** A base utility module (e.g. DateTimeUtils). */
23
+ export interface UtilsSchema extends SchemaBase {
24
+ type: 'utils';
25
+ /** Backend binding the api module this utils belongs to (shared instance
26
+ * from project.config.ts apis). With `app` it serves that frontend
27
+ * ({api}/{app}/utils/); alone it is the api's public module
28
+ * ({api}/common/utils/). Unset = not backend-side. */
29
+ api?: ProjectApiSchema;
30
+ /** Optional binding — the frontend app this utils serves (shared instance
31
+ * from project.config.ts apps). Empty means a shared public module. */
32
+ app?: FrontAppSchema;
33
+ /** Methods keyed by name — the map key is written back as the method name. */
34
+ methods: Record<string, UtilsMethodSchema>;
35
+ }
36
+
37
+ export function defineUtils(options: {
38
+ name: string;
39
+ api?: ProjectApiSchema;
40
+ app?: FrontAppSchema;
41
+ methods: Record<string, UtilsMethodDef>;
42
+ description?: string;
43
+ }): UtilsSchema {
44
+ if (options.api !== undefined && options.app !== undefined && !options.api.apps.includes(options.app)) {
45
+ throw new Error(`utils ${options.name}: api '${options.api.name}' does not serve app '${options.app.name}'`);
46
+ }
47
+ const schema: UtilsSchema = {
48
+ type: 'utils',
49
+ name: options.name,
50
+ description: options.description,
51
+ api: options.api,
52
+ app: options.app,
53
+ methods: {},
54
+ };
55
+ for (const key of Object.keys(options.methods)) {
56
+ const method = options.methods[key] as UtilsMethodDef;
57
+ const methodSchema: UtilsMethodSchema = {
58
+ type: 'utilsMethod',
59
+ name: key,
60
+ description: method.description,
61
+ schema,
62
+ args: method.args,
63
+ result: method.result,
64
+ };
65
+ for (const argKey of Object.keys(methodSchema.args)) {
66
+ const field = methodSchema.args[argKey] as Field;
67
+ field.name = argKey;
68
+ field.schema = methodSchema;
69
+ }
70
+ methodSchema.result.name = key;
71
+ methodSchema.result.schema = methodSchema;
72
+ schema.methods[key] = methodSchema;
73
+ }
74
+ return schema;
27
75
  }
@@ -1,9 +0,0 @@
1
- export interface InheritanceIssue {
2
- file: string;
3
- container: string;
4
- field: string;
5
- /** e.g. ["t_order.order_no"] or ["t_order.id", "t_merchant.id"] when several inferred tables share the name */
6
- candidates: string[];
7
- }
8
- /** Check one loaded DSL module (its exports) for inheritance gaps. */
9
- export declare function checkInheritance(mod: Record<string, unknown>, file: string): InheritanceIssue[];
@@ -1,58 +0,0 @@
1
- /** snake_case → camelCase: order_no → orderNo; names without underscores are unchanged */
2
- function toCamelCase(name) {
3
- return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
4
- }
5
- function isDtoMessage(v) {
6
- if (typeof v !== 'object' || v === null)
7
- return false;
8
- const o = v;
9
- return o.type === 'dto' && typeof o.name === 'string' && typeof o.fields === 'object' && o.fields !== null;
10
- }
11
- function collectFields(mod) {
12
- const out = [];
13
- for (const [name, v] of Object.entries(mod)) {
14
- if (isDtoMessage(v)) {
15
- const container = v;
16
- for (const [fname, f] of Object.entries(container.fields)) {
17
- out.push({ name: fname, field: f.field, container: name });
18
- }
19
- }
20
- }
21
- return out;
22
- }
23
- /** Check one loaded DSL module (its exports) for inheritance gaps. */
24
- export function checkInheritance(mod, file) {
25
- const fields = collectFields(mod);
26
- if (fields.length === 0)
27
- return [];
28
- // Infer the tables this file is about from the referenced fields' schema identity.
29
- const tables = new Map(); // table -> camelCol -> origCol
30
- for (const f of fields) {
31
- const schema = f.field.schema;
32
- if (schema?.type !== 'table')
33
- continue;
34
- const table = schema;
35
- if (!tables.has(table.name))
36
- tables.set(table.name, new Map());
37
- tables.get(table.name).set(toCamelCase(f.name), f.name);
38
- }
39
- if (tables.size === 0)
40
- return [];
41
- const issues = [];
42
- for (const f of fields) {
43
- if (f.field.schema?.type === 'table')
44
- continue;
45
- if (f.field.semantic !== undefined || f.field.type === 'enum')
46
- continue;
47
- const candidates = [];
48
- for (const [t, cols] of tables) {
49
- const orig = cols.get(f.name);
50
- if (orig !== undefined)
51
- candidates.push(`${t}.${orig}`);
52
- }
53
- if (candidates.length > 0) {
54
- issues.push({ file, container: f.container, field: f.name, candidates });
55
- }
56
- }
57
- return issues;
58
- }
package/src/provider.ts DELETED
@@ -1,73 +0,0 @@
1
- import type { ImportBase } from './import-base.js';
2
- import type { ImportableSchemaBase } from './dsl.js';
3
- import type { DtoField, DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
4
- import type { ActionSchema } from './action.js';
5
- import type { RefSchema } from './ref.js';
6
- import type { ConvertSchema } from './convert.js';
7
-
8
- // ProviderSchema: an API function signature (args DTO + results DTO).
9
-
10
- /** Parameter data source for a call argument. */
11
- export type DataRef =
12
- | { type: 'route'; key: string }
13
- | { type: 'data'; key: string }
14
- | { type: 'value'; value: unknown };
15
-
16
- /** Create a route-parameter reference. */
17
- export function route(key: string): DataRef {
18
- return { type: 'route', key };
19
- }
20
-
21
- /** Create a page-data reference. */
22
- export function data(key: string): DataRef {
23
- return { type: 'data', key };
24
- }
25
-
26
- export interface CallAction extends ActionSchema {
27
- type: 'call';
28
- func: ProviderSchema;
29
- args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>;
30
- }
31
-
32
-
33
- export function call(func: ProviderSchema, args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>): CallAction {
34
- return { name: func.name, type: 'call', func, args };
35
- }
36
-
37
- /** Provider function signature. The generated API client exposes one function
38
- * per endpoint; ProviderSchema gives that function a name and types. */
39
- export interface ProviderSchema extends ImportableSchemaBase {
40
- isAsync: boolean;
41
- /** Input DTO (buildInput / buildQuery / buildPk result). */
42
- args: DtoMessage;
43
- /** Output DTO (buildOutput result) or primitive. */
44
- results: DtoMessage | number | boolean | string;
45
- }
46
-
47
- /** Define a provider function. */
48
- export function defineProvider(
49
- name: string,
50
- schema: {
51
- isAsync: boolean;
52
- args: DtoMessage;
53
- results: DtoMessage | number | boolean | string;
54
- description?: string;
55
- importRef?: ImportBase;
56
- },
57
- ): ProviderSchema {
58
- return { name, ...schema };
59
- }
60
-
61
- /** Assign a call's result to a page data field.
62
- * React: setState({ [field]: await ... }). Mini-program: this.setData({ [field]: ... }). */
63
- export interface SetDataAction extends ActionSchema {
64
- type: 'setData';
65
- call: CallAction;
66
- field: DtoField;
67
- /** Optional field-level transform before assignment. */
68
- convert?: ConvertSchema;
69
- }
70
-
71
- export function setData(call: CallAction, field: DtoField, convert?: ConvertSchema): SetDataAction {
72
- return { name: 'setData', type: 'setData', call, field, convert };
73
- }