@pylonts/dsl 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/controller.d.ts +27 -0
  2. package/dist/controller.js +11 -0
  3. package/dist/convert.d.ts +18 -6
  4. package/dist/convert.js +12 -2
  5. package/dist/curd.d.ts +0 -2
  6. package/dist/curd.js +7 -0
  7. package/dist/dao.d.ts +111 -2
  8. package/dist/dao.js +28 -2
  9. package/dist/db.js +3 -0
  10. package/dist/dsl.d.ts +24 -1
  11. package/dist/dsl.js +16 -0
  12. package/dist/dto.d.ts +6 -2
  13. package/dist/dto.js +20 -9
  14. package/dist/endpoint.d.ts +15 -0
  15. package/dist/endpoint.js +3 -0
  16. package/dist/exception.d.ts +14 -0
  17. package/dist/exception.js +9 -0
  18. package/dist/field-rule.d.ts +20 -0
  19. package/dist/field-rule.js +19 -0
  20. package/dist/flow.d.ts +24 -2
  21. package/dist/flow.js +16 -4
  22. package/dist/index.d.ts +7 -0
  23. package/dist/index.js +9 -0
  24. package/dist/mermaid-driver.js +32 -3
  25. package/dist/method.d.ts +11 -0
  26. package/dist/method.js +3 -0
  27. package/dist/mysql-driver.js +8 -1
  28. package/dist/provider.d.ts +6 -11
  29. package/dist/provider.js +2 -2
  30. package/dist/service.d.ts +16 -6
  31. package/dist/service.js +13 -2
  32. package/dist/third-service.d.ts +75 -0
  33. package/dist/third-service.js +96 -0
  34. package/dist/typebox-driver.d.ts +6 -0
  35. package/dist/typebox-driver.js +76 -14
  36. package/dist/utils.d.ts +25 -10
  37. package/dist/utils.js +28 -11
  38. package/docs/curd.md +110 -110
  39. package/docs/dto.md +9 -2
  40. package/docs/table.md +135 -135
  41. package/docs/third-service.md +122 -0
  42. package/package.json +4 -1
  43. package/src/action.ts +10 -10
  44. package/src/asset.ts +62 -62
  45. package/src/bases.ts +29 -29
  46. package/src/component.ts +21 -21
  47. package/src/controller.ts +40 -0
  48. package/src/convert.ts +34 -7
  49. package/src/curd.ts +7 -2
  50. package/src/dao.ts +162 -3
  51. package/src/db.ts +5 -0
  52. package/src/dsl.ts +49 -1
  53. package/src/dto.ts +25 -9
  54. package/src/endpoint.ts +18 -0
  55. package/src/event.ts +12 -12
  56. package/src/exception.ts +28 -0
  57. package/src/field-rule.ts +47 -0
  58. package/src/flow.ts +143 -103
  59. package/src/index.ts +11 -1
  60. package/src/mermaid-driver.ts +31 -3
  61. package/src/method.ts +20 -0
  62. package/src/mock.ts +12 -12
  63. package/src/mysql-driver.ts +8 -2
  64. package/src/navigation.ts +28 -28
  65. package/src/page-def.ts +79 -79
  66. package/src/page-flow.ts +153 -153
  67. package/src/page.ts +76 -76
  68. package/src/popup.ts +25 -25
  69. package/src/project.ts +97 -97
  70. package/src/provider.ts +7 -12
  71. package/src/ref.ts +18 -18
  72. package/src/route.ts +11 -11
  73. package/src/service.ts +28 -6
  74. package/src/third-service.ts +186 -0
  75. package/src/typebox-driver.ts +264 -192
  76. package/src/utils.ts +54 -17
  77. package/dist/check-inheritance.d.ts +0 -9
  78. package/dist/check-inheritance.js +0 -58
@@ -0,0 +1,27 @@
1
+ import { SchemaBase } from './dsl.js';
2
+ import { FrontAppSchema } from './project.js';
3
+ import type { EndpointSchema } from './endpoint.js';
4
+ /** A backend RPC controller. Strong constraints:
5
+ * - a backend module maps 1:1 to a frontend app (they are peers);
6
+ * - a controller serves exactly one frontend app — no cross-module calls. */
7
+ export interface ControllerSchema extends SchemaBase {
8
+ type: 'controller';
9
+ /** The frontend app this controller serves (shared instance from project.config). */
10
+ app: FrontAppSchema;
11
+ /** RPC methods exposed by this controller. */
12
+ methods: ControllerMethodSchema[];
13
+ }
14
+ export declare function defineController(options: {
15
+ name: string;
16
+ app: FrontAppSchema;
17
+ /** Method declarations: type/schema are injected by this builder. */
18
+ methods: Array<Omit<ControllerMethodSchema, 'type' | 'schema'>>;
19
+ description?: string;
20
+ }): ControllerSchema;
21
+ /** An RPC method exposed by a controller. */
22
+ export interface ControllerMethodSchema extends SchemaBase {
23
+ type: 'method';
24
+ schema: ControllerSchema;
25
+ /** Shared API signature — same instance the page-side provider references. */
26
+ signature: EndpointSchema;
27
+ }
@@ -0,0 +1,11 @@
1
+ export function defineController(options) {
2
+ const schema = {
3
+ type: 'controller',
4
+ name: options.name,
5
+ description: options.description,
6
+ app: options.app,
7
+ methods: [],
8
+ };
9
+ schema.methods = options.methods.map((method) => ({ type: 'method', schema, ...method }));
10
+ return schema;
11
+ }
package/dist/convert.d.ts CHANGED
@@ -1,12 +1,24 @@
1
1
  import type { SchemaBase } from './dsl.js';
2
+ import type { DtoMessage } from './dto.js';
3
+ import type { TableSchema } from './db.js';
4
+ import type { ThirdMethodSchema } from './third-service.js';
2
5
  import type { FrontAppSchema } from './project.js';
3
- /** Declares post-call result page data field mapping.
4
- * Driver generates per-item transform (e.g. .map()) before setData. */
6
+ /** A source/target collection of a convert dto, entity or third-party message. */
7
+ export type ConvertSourceSchema = DtoMessage | TableSchema | ThirdMethodSchema;
8
+ /** Declares a multi-source → single-target schema integration. */
5
9
  export interface ConvertSchema extends SchemaBase {
6
10
  type: 'convert';
7
- /** The frontend app this convert belongs to (shared instance from project.config). */
11
+ /** The app (module) this convert belongs to its artifact lands in modules/{app}/convert/. */
8
12
  app: FrontAppSchema;
9
- /** { targetField: sourceField } renames or copies fields from call result. */
10
- fields: Record<string, string>;
13
+ /** Source schemasone or more, mixed dimensions. */
14
+ sources: ConvertSourceSchema[];
15
+ /** Target schema — the single integrated collection. */
16
+ target: ConvertSourceSchema;
11
17
  }
12
- export declare function defineConvert(name: string, app: FrontAppSchema, fields: Record<string, string>): ConvertSchema;
18
+ export declare function defineConvert(options: {
19
+ name: string;
20
+ app: FrontAppSchema;
21
+ sources: ConvertSourceSchema[];
22
+ target: ConvertSourceSchema;
23
+ description?: string;
24
+ }): ConvertSchema;
package/dist/convert.js CHANGED
@@ -1,3 +1,13 @@
1
- export function defineConvert(name, app, fields) {
2
- return { name, type: 'convert', app, fields };
1
+ export function defineConvert(options) {
2
+ if (options.sources.length === 0) {
3
+ throw new Error(`convert '${options.name}': sources must not be empty`);
4
+ }
5
+ return {
6
+ type: 'convert',
7
+ name: options.name,
8
+ description: options.description,
9
+ app: options.app,
10
+ sources: options.sources,
11
+ target: options.target,
12
+ };
3
13
  }
package/dist/curd.d.ts CHANGED
@@ -7,8 +7,6 @@ export type ActionPageMode = 'modal' | 'route';
7
7
  /** One CRUD action page (add / update / detail). */
8
8
  export interface ActionPage {
9
9
  mode: ActionPageMode;
10
- /** For add/update: when true, render as modal on list page; when false/undefined, render as standalone route page. */
11
- modal?: boolean;
12
10
  /** Fields rendered on this page. Required, non-empty — every field the
13
11
  * frontend shows must be listed explicitly. */
14
12
  columns: Field[];
package/dist/curd.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { toKebabCase } from '@pylonts/core';
1
2
  function assertColumns(curd, pageName, columns) {
2
3
  if (columns.length === 0) {
3
4
  throw new Error(`curd ${curd.name}: ${pageName}.columns must be non-empty`);
@@ -17,6 +18,12 @@ export function defineCurd(name, schema) {
17
18
  if (curd.app.type !== 'admin') {
18
19
  throw new Error(`curd ${name}: app ${curd.app.name} must be type 'admin' (got '${curd.app.type}')`);
19
20
  }
21
+ // The name is the admin route path — it must be the kebab-case table name
22
+ // so paths cannot drift from the table they serve.
23
+ const expectedName = toKebabCase(curd.table.name);
24
+ if (name !== expectedName) {
25
+ throw new Error(`curd name must be '${expectedName}' (kebab-case of table '${curd.table.name}'); got '${name}'`);
26
+ }
20
27
  if (!curd.section) {
21
28
  throw new Error(`curd ${name}: section is required (sidebar menu group, e.g. '商户管理')`);
22
29
  }
package/dist/dao.d.ts CHANGED
@@ -1,9 +1,118 @@
1
- import { SchemaBase } from './dsl.js';
1
+ import { SchemaBase, Field, Operator } from './dsl.js';
2
2
  import { FrontAppSchema } from './project.js';
3
+ import { TableSchema } from './db.js';
3
4
  /** A data-access layer bound to exactly one frontend app. */
4
5
  export interface DaoSchema extends SchemaBase {
5
6
  type: 'dao';
6
7
  /** The frontend app this DAO belongs to (shared instance from project.config). */
7
8
  app: FrontAppSchema;
9
+ /** The table this DAO operates on (single-table atomicity). */
10
+ table: TableSchema;
11
+ /** Methods keyed by name — the map key is written back as the method name. */
12
+ methods: Record<string, DaoMethodSchema>;
13
+ }
14
+ /** Method input for defineDao: name is written back from the methods map key. */
15
+ export type DaoMethodDef = Omit<FindSchema, 'schema' | 'name'> | Omit<GetSchema, 'schema' | 'name'> | Omit<InsertSchema, 'schema' | 'name'> | Omit<UpdateSchema, 'schema' | 'name'> | Omit<DeleteSchema, 'schema' | 'name'> | Omit<AggregateSchema, 'schema' | 'name'>;
16
+ export declare function defineDao(options: {
17
+ name: string;
18
+ app: FrontAppSchema;
19
+ table: TableSchema;
20
+ methods: Record<string, DaoMethodDef>;
21
+ description?: string;
22
+ }): DaoSchema;
23
+ /** One AND-combined criterion: a column plus its comparison operator. */
24
+ export interface QueryField {
25
+ /** Column to compare. */
26
+ field: Field;
27
+ /** Comparison operator; defaults to 'eq'. */
28
+ op?: Operator;
29
+ }
30
+ /** Query criteria for find methods. */
31
+ export interface QuerySchema extends SchemaBase {
32
+ type: 'query';
33
+ /** Criteria; all fields are AND-combined. */
34
+ fields: QueryField[];
35
+ }
36
+ /** Sort specification for find results. */
37
+ export interface OrderBySchema {
38
+ /** Column to sort by. */
39
+ column: Field;
40
+ /** Sort direction. */
41
+ sort: 'asc' | 'desc';
42
+ }
43
+ /** Query rows by criteria; returns a list of row objects. */
44
+ export interface FindSchema extends SchemaBase {
45
+ type: 'find';
46
+ schema: DaoSchema;
47
+ /** Query criteria; omit = all rows. */
48
+ args?: QuerySchema;
49
+ /** Pagination marker: 'page' (page/pageSize) or 'limit' (position/limit).
50
+ * Only presence matters — parameter shapes are a generator convention. */
51
+ mode?: 'page' | 'limit';
52
+ /** Result sort; omit = no explicit order. */
53
+ orderBy?: OrderBySchema | OrderBySchema[];
54
+ /** Row type of the result list. */
55
+ results: EntitySchema;
56
+ }
57
+ /** Fetch a single row by primary key. Composite PK is not supported. */
58
+ export interface GetSchema extends SchemaBase {
59
+ type: 'get';
60
+ schema: DaoSchema;
61
+ /** Single PK scalar value. */
62
+ args: Field;
63
+ /** Row type. Get may miss the row: the generated signature returns Entity | null. */
64
+ results: EntitySchema;
65
+ }
66
+ /** Insert one row; returns number (insert id). */
67
+ export interface InsertSchema extends SchemaBase {
68
+ type: 'insert';
69
+ schema: DaoSchema;
70
+ /** Row object. */
71
+ args: EntitySchema;
72
+ }
73
+ /** Update one row; returns number (affected rows). The where key is derived
74
+ * from the PK columns inside args. */
75
+ export interface UpdateSchema extends SchemaBase {
76
+ type: 'update';
77
+ schema: DaoSchema;
78
+ /** Row object containing the PK columns plus the columns to set. */
79
+ args: EntitySchema;
80
+ /** Additional criteria beyond the derived PK (AND-combined). */
81
+ where?: QuerySchema;
82
+ }
83
+ /** Delete one row by primary key; returns number (affected rows).
84
+ * Composite PK is not supported. */
85
+ export interface DeleteSchema extends SchemaBase {
86
+ type: 'delete';
87
+ schema: DaoSchema;
88
+ /** Single PK scalar value. */
89
+ args: Field;
90
+ }
91
+ export type DaoMethodSchema = FindSchema | GetSchema | InsertSchema | UpdateSchema | DeleteSchema | AggregateSchema;
92
+ /** Aggregate expression result for aggregate queries. */
93
+ export interface ComputeExpr {
94
+ fn: 'sum' | 'avg' | 'count';
95
+ /** Column the function applies to; absent for count(*). */
96
+ field?: Field;
97
+ }
98
+ /** Aggregate expressions: Compute.sum(col) / Compute.avg(col) / Compute.count(). */
99
+ export declare const Compute: {
100
+ sum(field: Field): ComputeExpr;
101
+ avg(field: Field): ComputeExpr;
102
+ count(): ComputeExpr;
103
+ };
104
+ /** Aggregate query (count/sum/avg): returns computed scalar values. */
105
+ export interface AggregateSchema extends SchemaBase {
106
+ type: 'aggregate';
107
+ schema: DaoSchema;
108
+ /** Criteria, same shape as find. */
109
+ args?: QuerySchema;
110
+ /** Computed results: key = result field name. */
111
+ results: Record<string, ComputeExpr>;
112
+ }
113
+ /** A database entity backed by a table. */
114
+ export interface EntitySchema extends SchemaBase {
115
+ type: 'entity';
116
+ /** The table columns of this entity. */
117
+ columns: Field[];
8
118
  }
9
- export declare function defineDao(name: string, app: FrontAppSchema, description?: string): DaoSchema;
package/dist/dao.js CHANGED
@@ -1,3 +1,29 @@
1
- export function defineDao(name, app, description) {
2
- return { name, type: 'dao', app, description };
1
+ export function defineDao(options) {
2
+ const schema = {
3
+ type: 'dao',
4
+ name: options.name,
5
+ description: options.description,
6
+ app: options.app,
7
+ table: options.table,
8
+ methods: {},
9
+ };
10
+ for (const key of Object.keys(options.methods)) {
11
+ const method = options.methods[key];
12
+ // Spread of a union loses discriminant correlation; the cast is safe
13
+ // (the builder only adds name and the back-reference field).
14
+ schema.methods[key] = { ...method, name: key, schema };
15
+ }
16
+ return schema;
3
17
  }
18
+ /** Aggregate expressions: Compute.sum(col) / Compute.avg(col) / Compute.count(). */
19
+ export const Compute = {
20
+ sum(field) {
21
+ return { fn: 'sum', field };
22
+ },
23
+ avg(field) {
24
+ return { fn: 'avg', field };
25
+ },
26
+ count() {
27
+ return { fn: 'count' };
28
+ },
29
+ };
package/dist/db.js CHANGED
@@ -66,6 +66,9 @@ export function defineTable(name, schema) {
66
66
  if (field.schema && field.schema !== table) {
67
67
  throw new Error(`field ${key}: belongs to table ${field.schema.name}, cannot reuse in table ${table.name}`);
68
68
  }
69
+ if (field.type === 'array' || field.type === 'object') {
70
+ throw new Error(`table '${name}': column '${key}' cannot be a nested ${field.type} field — wire-format nesting is not a table column`);
71
+ }
69
72
  }
70
73
  for (const key of Object.keys(table.columns)) {
71
74
  table.columns[key].name = key;
package/dist/dsl.d.ts CHANGED
@@ -54,6 +54,13 @@ interface DecimalField extends BaseField {
54
54
  precision: number;
55
55
  scale: number;
56
56
  }
57
+ type RateUnit = 'pct' | 'pm' | 'bp';
58
+ export declare function rateScale(unit: RateUnit): number;
59
+ interface RateField extends BaseField {
60
+ type: 'rate';
61
+ jsType: 'string';
62
+ unit: RateUnit;
63
+ }
57
64
  interface BooleanField extends BaseField {
58
65
  type: 'boolean';
59
66
  jsType: 'boolean';
@@ -93,17 +100,33 @@ interface JsonField extends BaseField {
93
100
  type: 'json';
94
101
  jsType: 'object';
95
102
  }
96
- export type Field = StringField | TextField | IntField | BigintField | DecimalField | BooleanField | DateField | TimeField | DateTimeField | EnumField | JsonField;
103
+ /** Recursive array field wire-format nesting (third-party messages), not a table column. */
104
+ interface ArrayField extends BaseField {
105
+ type: 'array';
106
+ jsType: 'array';
107
+ /** Element type: any Field, including nested array/object. */
108
+ items: Field;
109
+ }
110
+ /** Recursive object field — wire-format nesting (third-party messages), not a table column. */
111
+ interface ObjectField extends BaseField {
112
+ type: 'object';
113
+ jsType: 'object';
114
+ properties: Record<string, Field>;
115
+ }
116
+ export type Field = StringField | TextField | IntField | BigintField | DecimalField | RateField | BooleanField | DateField | TimeField | DateTimeField | EnumField | JsonField | ArrayField | ObjectField;
97
117
  type FieldExtras<T extends Field> = Omit<T, 'name' | 'type' | 'jsType'>;
98
118
  export declare function stringField(extra?: FieldExtras<StringField>): StringField;
99
119
  export declare function textField(extra?: FieldExtras<TextField>): TextField;
100
120
  export declare function intField(extra?: FieldExtras<IntField>): IntField;
101
121
  export declare function bigintField(extra?: FieldExtras<BigintField>): BigintField;
102
122
  export declare function decimalField(extra: FieldExtras<DecimalField>): DecimalField;
123
+ export declare function rateField(unit: RateUnit, extra?: Omit<FieldExtras<RateField>, 'unit'>): RateField;
103
124
  export declare function booleanField(extra?: FieldExtras<BooleanField>): BooleanField;
104
125
  export declare function dateField(extra?: FieldExtras<DateField>): DateField;
105
126
  export declare function timeField(extra?: FieldExtras<TimeField>): TimeField;
106
127
  export declare function datetimeField(extra?: FieldExtras<DateTimeField>): DateTimeField;
107
128
  export declare function jsonField(extra?: FieldExtras<JsonField>): JsonField;
129
+ export declare function arrayField(extra: FieldExtras<ArrayField>): ArrayField;
130
+ export declare function objectField(extra: FieldExtras<ObjectField>): ObjectField;
108
131
  export declare function enumField(extra: Omit<EnumField, 'name' | 'type' | 'jsType'>): EnumField;
109
132
  export {};
package/dist/dsl.js CHANGED
@@ -1,5 +1,12 @@
1
1
  // DSL field type definitions.
2
2
  // Shape: { type: <type name>, <extension fields> }
3
+ export function rateScale(unit) {
4
+ switch (unit) {
5
+ case 'pct': return 2;
6
+ case 'pm': return 3;
7
+ case 'bp': return 4;
8
+ }
9
+ }
3
10
  export function defineEnum(jsName, valueType, values) {
4
11
  return { jsName, valueType, values };
5
12
  }
@@ -18,6 +25,9 @@ export function bigintField(extra = {}) {
18
25
  export function decimalField(extra) {
19
26
  return { name: '', type: 'decimal', jsType: 'string', ...extra };
20
27
  }
28
+ export function rateField(unit, extra = {}) {
29
+ return { name: '', type: 'rate', jsType: 'string', unit, ...extra };
30
+ }
21
31
  export function booleanField(extra = {}) {
22
32
  return { name: '', type: 'boolean', jsType: 'boolean', ...extra };
23
33
  }
@@ -33,6 +43,12 @@ export function datetimeField(extra = {}) {
33
43
  export function jsonField(extra = {}) {
34
44
  return { name: '', type: 'json', jsType: 'object', ...extra };
35
45
  }
46
+ export function arrayField(extra) {
47
+ return { name: '', type: 'array', jsType: 'array', ...extra };
48
+ }
49
+ export function objectField(extra) {
50
+ return { name: '', type: 'object', jsType: 'object', ...extra };
51
+ }
36
52
  export function enumField(extra) {
37
53
  const jsType = extra.enum.valueType === 'integer' ? 'number' : 'string';
38
54
  return { name: '', type: 'enum', jsType, ...extra };
package/dist/dto.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BaseField, CollectionSchemaBase, Field, Operator, SchemaBase } from './dsl.js';
2
2
  import { TableSchema } from './db.js';
3
3
  import type { ImportBase } from './import-base.js';
4
+ import type { ThirdMethodSchema } from './third-service.js';
4
5
  /** Re-export — Operator lives on the DSL level (see dsl.ts). */
5
6
  export type { Operator } from './dsl.js';
6
7
  /** Re-export — ImportBase lives on its own module (see import-base.ts). */
@@ -99,5 +100,8 @@ export declare function buildInput(name: string, fields: Record<string, DtoField
99
100
  export declare function buildOutput(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
100
101
  export declare function buildQuery(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
101
102
  export declare function buildPk(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
102
- /** Pick columns from a built table and wrap them as DTO fields (aligned with dto.from). */
103
- export declare function from(table: TableSchema, fields: Field[]): Record<string, DtoField>;
103
+ /** Field-collection source a DTO can project from: a DB table or a third-party method message. */
104
+ export type DtoFieldSource = TableSchema | ThirdMethodSchema;
105
+ /** Project fields from a field-collection source (table or third-party method
106
+ * message) and wrap them as DTO fields (aligned with dto.from). */
107
+ export declare function from(source: DtoFieldSource, fields: Field[]): Record<string, DtoField>;
package/dist/dto.js CHANGED
@@ -1,4 +1,4 @@
1
- import { toCamelCase } from './utils.js';
1
+ import { toCamelCase } from '@pylonts/core';
2
2
  export class DtoField {
3
3
  /** DTO 语义字段名(接口字段名),与 field.name(数据库列名)含义不同。
4
4
  * 构造时未知,由 buildMessage 从 map key 反写。 */
@@ -159,21 +159,32 @@ export function buildPk(name, fields, description) {
159
159
  if (field.optional !== undefined)
160
160
  continue;
161
161
  const f = field.field;
162
- const table = f.schema;
162
+ const table = f.schema?.type === 'table' ? f.schema : undefined;
163
163
  field.optional = table !== undefined && table.isPk(f) ? false : true;
164
164
  }
165
165
  return message;
166
166
  }
167
- /** Pick columns from a built table and wrap them as DTO fields (aligned with dto.from). */
168
- export function from(table, fields) {
167
+ // Type guard instead of a plain discriminant check: TableSchema.type is declared
168
+ // as a class property, so TS widens it to string and cannot narrow the union.
169
+ function isThirdMethod(source) {
170
+ return source.type === 'thirdMethod';
171
+ }
172
+ function ownsField(source, field) {
173
+ if (isThirdMethod(source))
174
+ return Object.values(source.fields).includes(field);
175
+ return Object.values(source.columns).includes(field);
176
+ }
177
+ /** Project fields from a field-collection source (table or third-party method
178
+ * message) and wrap them as DTO fields (aligned with dto.from). */
179
+ export function from(source, fields) {
169
180
  const out = {};
170
181
  for (const field of fields) {
171
- if (field.schema !== table) {
172
- throw new Error(`dto.from(${table.name}): field ${field.name} does not belong to this table`);
182
+ if (!ownsField(source, field)) {
183
+ throw new Error(`dto.from(${source.name}): field ${field.name} does not belong to this ${source.type}`);
173
184
  }
174
- // DTO field name is camelCase (mer_id → merId); the underlying field.name
175
- // stays snake_case (DB column).
176
- out[toCamelCase(field.name)] = dtoField(field);
185
+ // DB columns map to camelCase interface names (mer_id → merId); wire-format
186
+ // names are protocol names themselves and stay untouched.
187
+ out[isThirdMethod(source) ? field.name : toCamelCase(field.name)] = dtoField(field);
177
188
  }
178
189
  return out;
179
190
  }
@@ -0,0 +1,15 @@
1
+ import { SchemaBase } from './dsl.js';
2
+ import type { DtoMessage } from './dto.js';
3
+ /** Shared API call signature: one request DTO in, one response shape out.
4
+ * Referenced by both backend controller methods and frontend page providers —
5
+ * both sides must use the exact same instance, so drift is impossible. */
6
+ export interface EndpointSchema extends SchemaBase {
7
+ type: 'endpoint';
8
+ args: DtoMessage;
9
+ results: DtoMessage | number | boolean | string;
10
+ }
11
+ export declare function defineEndpoint(name: string, options: {
12
+ args: DtoMessage;
13
+ results: DtoMessage | number | boolean | string;
14
+ description?: string;
15
+ }): EndpointSchema;
@@ -0,0 +1,3 @@
1
+ export function defineEndpoint(name, options) {
2
+ return { type: 'endpoint', name, args: options.args, results: options.results, description: options.description };
3
+ }
@@ -0,0 +1,14 @@
1
+ import type { SchemaBase } from './dsl.js';
2
+ import type { ImportBase } from './import-base.js';
3
+ /** Describes an exception a method can throw. */
4
+ export interface ExceptionSchema extends SchemaBase, Omit<ImportBase, 'type'> {
5
+ type: 'exception';
6
+ /** Whether the caller may safely retry after catching this exception. */
7
+ retryable: boolean;
8
+ }
9
+ export declare function defineException(options: {
10
+ name: string;
11
+ from: string;
12
+ retryable: boolean;
13
+ description?: string;
14
+ }): ExceptionSchema;
@@ -0,0 +1,9 @@
1
+ export function defineException(options) {
2
+ return {
3
+ type: 'exception',
4
+ name: options.name,
5
+ from: options.from,
6
+ retryable: options.retryable,
7
+ description: options.description,
8
+ };
9
+ }
@@ -0,0 +1,20 @@
1
+ import type { SchemaBase } from './dsl.js';
2
+ /** One end of a field rule (e.g. 'fen' / 'yuan', 'plain' / 'cipher', 'raw' / 'masked'). */
3
+ export interface FieldRuleEnd {
4
+ /** End name — written back by defineFieldRule from the ends map key. */
5
+ name: string;
6
+ description?: string;
7
+ }
8
+ /** A semantic transformation rule between two field representations. */
9
+ export interface FieldRuleSchema extends SchemaBase {
10
+ type: 'fieldRule';
11
+ /** Rule name — the semantic uniqueness key. */
12
+ name: string;
13
+ /** The two ends of the rule, keyed by end name. */
14
+ ends: Record<string, FieldRuleEnd>;
15
+ }
16
+ export declare function defineFieldRule(options: {
17
+ name: string;
18
+ ends: Record<string, Omit<FieldRuleEnd, 'name'>>;
19
+ description?: string;
20
+ }): FieldRuleSchema;
@@ -0,0 +1,19 @@
1
+ /** One rule per semantic name. Defining the same name twice throws. */
2
+ const ruleRegistry = new Map();
3
+ export function defineFieldRule(options) {
4
+ if (ruleRegistry.has(options.name)) {
5
+ throw new Error(`fieldRule '${options.name}' is already defined — one rule per semantic`);
6
+ }
7
+ const ends = {};
8
+ for (const key of Object.keys(options.ends)) {
9
+ ends[key] = { name: key, description: options.ends[key].description };
10
+ }
11
+ const rule = {
12
+ type: 'fieldRule',
13
+ name: options.name,
14
+ description: options.description,
15
+ ends,
16
+ };
17
+ ruleRegistry.set(options.name, rule);
18
+ return rule;
19
+ }
package/dist/flow.d.ts CHANGED
@@ -1,15 +1,29 @@
1
1
  import { SchemaBase } from './dsl.js';
2
+ import type { MethodSchema } from './method.js';
3
+ import type { ConvertSchema } from './convert.js';
4
+ import type { DaoMethodSchema } from './dao.js';
5
+ import type { ServiceMethodSchema } from './service.js';
6
+ import type { ThirdServiceMethodSchema } from './third-service.js';
7
+ import type { UtilsMethodSchema } from './utils.js';
8
+ /** A method a flow node can invoke: contract references, or a pure descriptor
9
+ * (MethodSchema) for the period before the contract file exists. */
10
+ export type FlowMethodRef = MethodSchema | ConvertSchema | DaoMethodSchema | ServiceMethodSchema | ThirdServiceMethodSchema | UtilsMethodSchema;
2
11
  export interface FlowNode extends SchemaBase {
3
12
  /** Optional sub-flow. When present, entering this node runs the sub-flow;
4
13
  * after the sub-flow reaches any terminal node, the outer flow continues
5
14
  * via this node's outgoing edges. Sub-flows nest recursively. */
6
15
  flow?: FlowSchema;
16
+ /** Methods this node invokes — contract references or pure descriptors
17
+ * (rendered under the node label). */
18
+ methods?: FlowMethodRef[];
7
19
  }
8
20
  export interface FlowEdge extends SchemaBase {
9
21
  /** Trigger condition; undefined = default path (success/normal). */
10
22
  when?: string;
11
23
  start: FlowNode;
12
24
  end: FlowNode;
25
+ /** Exception path (rendered dashed); defaults to normal. */
26
+ exception?: boolean;
13
27
  }
14
28
  export interface FlowSchema extends SchemaBase {
15
29
  /** Entry node. */
@@ -19,8 +33,16 @@ export interface FlowSchema extends SchemaBase {
19
33
  /** Independent edges; a node may be start of many edges, so cycles are expressible. */
20
34
  edges: FlowEdge[];
21
35
  }
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;
36
+ export declare function node(name: string, options?: {
37
+ flow?: FlowSchema;
38
+ description?: string;
39
+ methods?: FlowMethodRef[];
40
+ }): FlowNode;
41
+ export declare function edge(start: FlowNode, end: FlowNode, options?: {
42
+ when?: string;
43
+ description?: string;
44
+ exception?: boolean;
45
+ }): FlowEdge;
24
46
  export declare function defineFlow(name: string, schema: {
25
47
  start: FlowNode;
26
48
  edges: FlowEdge[];
package/dist/flow.js CHANGED
@@ -1,9 +1,21 @@
1
- export function node(name, flow, description) {
2
- return { name, flow, description };
1
+ export function node(name, options = {}) {
2
+ return {
3
+ name,
4
+ flow: options.flow,
5
+ description: options.description,
6
+ methods: options.methods,
7
+ };
3
8
  }
4
- export function edge(start, end, when, description) {
9
+ export function edge(start, end, options = {}) {
5
10
  // Auto name for uniformity with SchemaBase; `when` stays the branch marker.
6
- return { name: `${start.name}->${end.name}`, start, end, when, description };
11
+ return {
12
+ name: `${start.name}->${end.name}`,
13
+ start,
14
+ end,
15
+ when: options.when,
16
+ description: options.description,
17
+ exception: options.exception,
18
+ };
7
19
  }
8
20
  export function defineFlow(name, schema) {
9
21
  const seen = new Set();
package/dist/index.d.ts CHANGED
@@ -22,7 +22,12 @@ export * from './convert.js';
22
22
  export * from './ref.js';
23
23
  export * from './route.js';
24
24
  export * from './service.js';
25
+ export * from './controller.js';
26
+ export * from './endpoint.js';
25
27
  export * from './dao.js';
28
+ export * from './third-service.js';
29
+ export * from './field-rule.js';
30
+ export * from './exception.js';
26
31
  export * from './page.js';
27
32
  export * from './curd.js';
28
33
  export * from './page-flow.js';
@@ -31,3 +36,5 @@ export * from './page-def.js';
31
36
  export * from './mermaid-driver.js';
32
37
  export * from './navigation.js';
33
38
  export * from './popup.js';
39
+ export * from './method.js';
40
+ export { toCamelCase, toPascalCase, toKebabCase } from '@pylonts/core';
package/dist/index.js CHANGED
@@ -22,7 +22,12 @@ export * from './convert.js';
22
22
  export * from './ref.js';
23
23
  export * from './route.js';
24
24
  export * from './service.js';
25
+ export * from './controller.js';
26
+ export * from './endpoint.js';
25
27
  export * from './dao.js';
28
+ export * from './third-service.js';
29
+ export * from './field-rule.js';
30
+ export * from './exception.js';
26
31
  export * from './page.js';
27
32
  export * from './curd.js';
28
33
  export * from './page-flow.js';
@@ -31,3 +36,7 @@ export * from './page-def.js';
31
36
  export * from './mermaid-driver.js';
32
37
  export * from './navigation.js';
33
38
  export * from './popup.js';
39
+ export * from './method.js';
40
+ // Naming conversions moved to @pylonts/core; re-exported for compatibility
41
+ // with packages that import them from @pylonts/dsl.
42
+ export { toCamelCase, toPascalCase, toKebabCase } from '@pylonts/core';