@pylonts/dsl 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dsl.d.ts +28 -15
- package/dist/dsl.js +44 -1
- package/dist/dto.d.ts +36 -21
- package/dist/dto.js +92 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mermaid-driver.js +7 -3
- package/dist/typebox-driver.d.ts +6 -0
- package/dist/typebox-driver.js +56 -20
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +8 -0
- package/docs/dto.md +17 -10
- package/package.json +1 -1
- package/src/dsl.ts +50 -19
- package/src/dto.ts +93 -48
- package/src/index.ts +1 -0
- package/src/mermaid-driver.ts +6 -3
- package/src/typebox-driver.ts +183 -140
- package/src/utils.ts +6 -0
package/dist/dsl.d.ts
CHANGED
|
@@ -3,17 +3,23 @@ export interface SchemaBase {
|
|
|
3
3
|
name: string;
|
|
4
4
|
description?: string;
|
|
5
5
|
}
|
|
6
|
+
/** Field collection schemas (DB table vs DTO message); `type` is the discriminator */
|
|
7
|
+
export interface CollectionSchemaBase extends SchemaBase {
|
|
8
|
+
type: string;
|
|
9
|
+
}
|
|
6
10
|
export interface BaseField {
|
|
7
11
|
name: string;
|
|
8
12
|
/** 显示名称(中文标签) */
|
|
9
13
|
label?: string;
|
|
10
14
|
/** 字段描述 */
|
|
11
15
|
description?: string;
|
|
16
|
+
/** 业务语义码(如 'merchant_name'),驱动 mock 生成等下游消费 */
|
|
17
|
+
semantic?: string;
|
|
12
18
|
optional?: boolean;
|
|
13
19
|
readOnly?: boolean;
|
|
14
20
|
default?: string;
|
|
15
21
|
/** 所属 schema(db 或 dto) */
|
|
16
|
-
schema?:
|
|
22
|
+
schema?: CollectionSchemaBase;
|
|
17
23
|
}
|
|
18
24
|
interface StringField extends BaseField {
|
|
19
25
|
type: 'string';
|
|
@@ -90,7 +96,23 @@ export type ForeignKey = {
|
|
|
90
96
|
fields: Field | Field[];
|
|
91
97
|
references: Field | Field[];
|
|
92
98
|
};
|
|
93
|
-
export interface
|
|
99
|
+
export interface TableSchemaOptions {
|
|
100
|
+
description?: string;
|
|
101
|
+
paginated?: boolean;
|
|
102
|
+
actor?: boolean;
|
|
103
|
+
generator?: string;
|
|
104
|
+
autoIncrement?: Field;
|
|
105
|
+
primaryKey?: Field | Field[];
|
|
106
|
+
indexes?: Index[];
|
|
107
|
+
foreignKeys?: Record<string, ForeignKey>;
|
|
108
|
+
/** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
|
|
109
|
+
phrase?: DictionaryEntry;
|
|
110
|
+
fields: Record<string, Field>;
|
|
111
|
+
}
|
|
112
|
+
export declare class TableSchema implements CollectionSchemaBase {
|
|
113
|
+
type: string;
|
|
114
|
+
name: string;
|
|
115
|
+
description?: string;
|
|
94
116
|
/** 分页 */
|
|
95
117
|
paginated?: boolean;
|
|
96
118
|
/** 系统操作者(如小程序为 C 端用户,管理端为运营) */
|
|
@@ -106,6 +128,9 @@ export interface TableSchema extends SchemaBase {
|
|
|
106
128
|
/** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
|
|
107
129
|
phrase?: DictionaryEntry;
|
|
108
130
|
fields: Record<string, Field>;
|
|
131
|
+
constructor(name: string, options: TableSchemaOptions);
|
|
132
|
+
/** True when the field is part of this table's primary key */
|
|
133
|
+
isPk(fieldRef: Field): boolean;
|
|
109
134
|
}
|
|
110
135
|
type FieldExtras<T extends Field> = Omit<T, 'name' | 'type' | 'jsType'>;
|
|
111
136
|
export declare function stringField(extra?: FieldExtras<StringField>): StringField;
|
|
@@ -119,17 +144,5 @@ export declare function timeField(extra?: FieldExtras<TimeField>): TimeField;
|
|
|
119
144
|
export declare function datetimeField(extra?: FieldExtras<DateTimeField>): DateTimeField;
|
|
120
145
|
export declare function jsonField(extra?: FieldExtras<JsonField>): JsonField;
|
|
121
146
|
export declare function enumField(extra: Omit<EnumField, 'name' | 'type' | 'jsType'>): EnumField;
|
|
122
|
-
export declare function defineTable(name: string, schema:
|
|
123
|
-
description?: string;
|
|
124
|
-
paginated?: boolean;
|
|
125
|
-
actor?: boolean;
|
|
126
|
-
generator?: string;
|
|
127
|
-
autoIncrement?: Field;
|
|
128
|
-
primaryKey?: Field | Field[];
|
|
129
|
-
indexes?: Index[];
|
|
130
|
-
foreignKeys?: Record<string, ForeignKey>;
|
|
131
|
-
/** 引用的实体短语(词典条目) */
|
|
132
|
-
phrase?: DictionaryEntry;
|
|
133
|
-
fields: Record<string, Field>;
|
|
134
|
-
}): TableSchema;
|
|
147
|
+
export declare function defineTable(name: string, schema: TableSchemaOptions): TableSchema;
|
|
135
148
|
export {};
|
package/dist/dsl.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// DSL field type definitions.
|
|
3
3
|
// Shape: { type: <type name>, <extension fields> }
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.TableSchema = void 0;
|
|
5
6
|
exports.defineEnum = defineEnum;
|
|
6
7
|
exports.stringField = stringField;
|
|
7
8
|
exports.textField = textField;
|
|
@@ -18,6 +19,48 @@ exports.defineTable = defineTable;
|
|
|
18
19
|
function defineEnum(jsName, valueType, values) {
|
|
19
20
|
return { jsName, valueType, values };
|
|
20
21
|
}
|
|
22
|
+
class TableSchema {
|
|
23
|
+
type = 'table';
|
|
24
|
+
name;
|
|
25
|
+
description;
|
|
26
|
+
/** 分页 */
|
|
27
|
+
paginated;
|
|
28
|
+
/** 系统操作者(如小程序为 C 端用户,管理端为运营) */
|
|
29
|
+
actor;
|
|
30
|
+
/** id 生成器 */
|
|
31
|
+
generator;
|
|
32
|
+
/** 自增主键字段 */
|
|
33
|
+
autoIncrement;
|
|
34
|
+
primaryKey;
|
|
35
|
+
indexes;
|
|
36
|
+
/** 外键,引用其他表的字段 */
|
|
37
|
+
foreignKeys;
|
|
38
|
+
/** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
|
|
39
|
+
phrase;
|
|
40
|
+
fields;
|
|
41
|
+
constructor(name, options) {
|
|
42
|
+
this.name = name;
|
|
43
|
+
this.description = options.description;
|
|
44
|
+
this.paginated = options.paginated;
|
|
45
|
+
this.actor = options.actor;
|
|
46
|
+
this.generator = options.generator;
|
|
47
|
+
this.autoIncrement = options.autoIncrement;
|
|
48
|
+
this.primaryKey = options.primaryKey;
|
|
49
|
+
this.indexes = options.indexes;
|
|
50
|
+
this.foreignKeys = options.foreignKeys;
|
|
51
|
+
this.phrase = options.phrase;
|
|
52
|
+
this.fields = options.fields;
|
|
53
|
+
}
|
|
54
|
+
/** True when the field is part of this table's primary key */
|
|
55
|
+
isPk(fieldRef) {
|
|
56
|
+
if (this.primaryKey === undefined)
|
|
57
|
+
return false;
|
|
58
|
+
return Array.isArray(this.primaryKey)
|
|
59
|
+
? this.primaryKey.includes(fieldRef)
|
|
60
|
+
: this.primaryKey === fieldRef;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.TableSchema = TableSchema;
|
|
21
64
|
function stringField(extra = {}) {
|
|
22
65
|
return { name: '', type: 'string', jsType: 'string', ...extra };
|
|
23
66
|
}
|
|
@@ -53,7 +96,7 @@ function enumField(extra) {
|
|
|
53
96
|
return { name: '', type: 'enum', jsType, ...extra };
|
|
54
97
|
}
|
|
55
98
|
function defineTable(name, schema) {
|
|
56
|
-
const table =
|
|
99
|
+
const table = new TableSchema(name, schema);
|
|
57
100
|
for (const key of Object.keys(table.fields)) {
|
|
58
101
|
const field = table.fields[key];
|
|
59
102
|
if (field.schema && field.schema !== table) {
|
package/dist/dto.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseField, Field, SchemaBase, TableSchema } from './dsl';
|
|
1
|
+
import { BaseField, CollectionSchemaBase, Field, SchemaBase, TableSchema } from './dsl';
|
|
2
2
|
export type Operator = 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ne';
|
|
3
3
|
/**
|
|
4
4
|
* Reference to an existing TypeBox base schema by its import location.
|
|
@@ -10,16 +10,14 @@ export interface ImportRef {
|
|
|
10
10
|
from: string;
|
|
11
11
|
/** Named export, e.g. 'PageRequest' */
|
|
12
12
|
name: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Generic type arguments for the base schema (e.g. PageResult(OrderRow)).
|
|
15
|
+
* Two forms, both local DTOs:
|
|
16
|
+
* string — the DTO export name
|
|
17
|
+
* DtoMessage — the DTO instance itself; the driver resolves it to its name
|
|
18
|
+
*/
|
|
19
|
+
args?: (string | DtoMessage)[];
|
|
15
20
|
}
|
|
16
|
-
export type DtoExtras = {
|
|
17
|
-
pattern?: string;
|
|
18
|
-
/** 联合判断是否可选,定义后优先级高于 field.optional */
|
|
19
|
-
optional?: boolean;
|
|
20
|
-
/** 查询比较操作符(query 方向字段) */
|
|
21
|
-
operator?: Operator;
|
|
22
|
-
};
|
|
23
21
|
export type DtoArrayFieldDef = BaseField & {
|
|
24
22
|
type: 'array';
|
|
25
23
|
jsType: 'array';
|
|
@@ -30,21 +28,29 @@ export type DtoObjectFieldDef = BaseField & {
|
|
|
30
28
|
jsType: 'object';
|
|
31
29
|
properties: Record<string, DtoField>;
|
|
32
30
|
};
|
|
33
|
-
export declare class DtoField {
|
|
31
|
+
export declare class DtoField implements SchemaBase {
|
|
34
32
|
/** DTO 语义字段名(接口字段名),与 field.name(数据库列名)含义不同。
|
|
35
33
|
* 构造时未知,由 buildMessage 从 map key 反写。 */
|
|
36
34
|
name: string;
|
|
35
|
+
/** 字段描述 */
|
|
36
|
+
description?: string;
|
|
37
37
|
/** 所属 DTO 容器(buildMessage 反写) */
|
|
38
38
|
schema?: DtoMessage;
|
|
39
39
|
field: Field | DtoArrayFieldDef | DtoObjectFieldDef;
|
|
40
40
|
pattern?: string;
|
|
41
41
|
optional?: boolean;
|
|
42
42
|
operator?: Operator;
|
|
43
|
-
|
|
43
|
+
/** TypeBox default annotation (API contract level); falls back to field.default (DB default) */
|
|
44
|
+
default?: unknown;
|
|
45
|
+
constructor(field: Field | DtoArrayFieldDef | DtoObjectFieldDef);
|
|
44
46
|
setPattern(value: string): this;
|
|
47
|
+
setDescription(value: string): this;
|
|
48
|
+
getDescription(): string | undefined;
|
|
45
49
|
setOptional(value: boolean): this;
|
|
50
|
+
/** Set a default value — emitted as a TypeBox schema default annotation */
|
|
51
|
+
setDefault(value: unknown): this;
|
|
46
52
|
/** 查询比较操作符(query 方向字段)。Rule B: 查询字段恒为可选 */
|
|
47
|
-
|
|
53
|
+
setOperator(value: Operator): this;
|
|
48
54
|
/** optional 优先于 field.optional */
|
|
49
55
|
isOptional(): boolean;
|
|
50
56
|
}
|
|
@@ -56,23 +62,32 @@ export declare class DtoObjectField extends DtoField {
|
|
|
56
62
|
field: DtoObjectFieldDef;
|
|
57
63
|
properties(): Record<string, DtoField>;
|
|
58
64
|
}
|
|
59
|
-
export
|
|
60
|
-
|
|
65
|
+
export declare enum DtoDirection {
|
|
66
|
+
Input = "input",
|
|
67
|
+
Output = "output",
|
|
68
|
+
Query = "query",
|
|
69
|
+
Pk = "pk"
|
|
70
|
+
}
|
|
71
|
+
export declare class DtoMessage implements CollectionSchemaBase {
|
|
72
|
+
type: string;
|
|
73
|
+
name: string;
|
|
74
|
+
description?: string;
|
|
61
75
|
/** 方向:输入或输出 */
|
|
62
76
|
direction: DtoDirection;
|
|
63
77
|
fields: Record<string, DtoField>;
|
|
64
78
|
/** TypeBox base schemas to intersect with at generation time (e.g. PageRequest) */
|
|
65
|
-
bases
|
|
79
|
+
bases: ImportRef[];
|
|
80
|
+
constructor(name: string, direction: DtoDirection, fields: Record<string, DtoField>, description?: string);
|
|
66
81
|
/** 引用已存在的 TypeBox base schema,例如 include({ from: '@pylonts/core', name: 'PageRequest' }) */
|
|
67
|
-
include(...refs: ImportRef[]):
|
|
82
|
+
include(...refs: ImportRef[]): this;
|
|
68
83
|
}
|
|
69
|
-
export declare function dtoField(field: Field
|
|
84
|
+
export declare function dtoField(field: Field): DtoField;
|
|
70
85
|
export declare function dtoArrayField(def: {
|
|
71
|
-
items: DtoField;
|
|
72
|
-
} & Omit<BaseField, 'name'
|
|
86
|
+
items: DtoField | DtoMessage;
|
|
87
|
+
} & Omit<BaseField, 'name'>): DtoArrayField;
|
|
73
88
|
export declare function dtoObjectField(def: {
|
|
74
89
|
properties: Record<string, DtoField>;
|
|
75
|
-
} & Omit<BaseField, 'name'
|
|
90
|
+
} & Omit<BaseField, 'name'>): DtoObjectField;
|
|
76
91
|
export declare function buildInput(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
|
|
77
92
|
export declare function buildOutput(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
|
|
78
93
|
export declare function buildQuery(name: string, fields: Record<string, DtoField>, description?: string): DtoMessage;
|
package/dist/dto.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DtoObjectField = exports.DtoArrayField = exports.DtoField = void 0;
|
|
3
|
+
exports.DtoMessage = exports.DtoDirection = exports.DtoObjectField = exports.DtoArrayField = exports.DtoField = void 0;
|
|
4
4
|
exports.dtoField = dtoField;
|
|
5
5
|
exports.dtoArrayField = dtoArrayField;
|
|
6
6
|
exports.dtoObjectField = dtoObjectField;
|
|
@@ -9,35 +9,48 @@ exports.buildOutput = buildOutput;
|
|
|
9
9
|
exports.buildQuery = buildQuery;
|
|
10
10
|
exports.buildPk = buildPk;
|
|
11
11
|
exports.from = from;
|
|
12
|
+
const utils_1 = require("./utils");
|
|
12
13
|
class DtoField {
|
|
13
14
|
/** DTO 语义字段名(接口字段名),与 field.name(数据库列名)含义不同。
|
|
14
15
|
* 构造时未知,由 buildMessage 从 map key 反写。 */
|
|
15
16
|
name;
|
|
17
|
+
/** 字段描述 */
|
|
18
|
+
description;
|
|
16
19
|
/** 所属 DTO 容器(buildMessage 反写) */
|
|
17
20
|
schema;
|
|
18
21
|
field;
|
|
19
22
|
pattern;
|
|
20
23
|
optional;
|
|
21
24
|
operator;
|
|
22
|
-
|
|
25
|
+
/** TypeBox default annotation (API contract level); falls back to field.default (DB default) */
|
|
26
|
+
default;
|
|
27
|
+
constructor(field) {
|
|
23
28
|
this.name = '';
|
|
24
29
|
this.field = field;
|
|
25
|
-
this.pattern = extra.pattern;
|
|
26
|
-
this.optional = extra.optional;
|
|
27
|
-
this.operator = extra.operator;
|
|
28
30
|
}
|
|
29
31
|
setPattern(value) {
|
|
30
32
|
this.pattern = value;
|
|
31
33
|
return this;
|
|
32
34
|
}
|
|
35
|
+
setDescription(value) {
|
|
36
|
+
this.description = value;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
getDescription() {
|
|
40
|
+
return this.description;
|
|
41
|
+
}
|
|
33
42
|
setOptional(value) {
|
|
34
43
|
this.optional = value;
|
|
35
44
|
return this;
|
|
36
45
|
}
|
|
46
|
+
/** Set a default value — emitted as a TypeBox schema default annotation */
|
|
47
|
+
setDefault(value) {
|
|
48
|
+
this.default = value;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
37
51
|
/** 查询比较操作符(query 方向字段)。Rule B: 查询字段恒为可选 */
|
|
38
|
-
|
|
52
|
+
setOperator(value) {
|
|
39
53
|
this.operator = value;
|
|
40
|
-
this.optional = true;
|
|
41
54
|
return this;
|
|
42
55
|
}
|
|
43
56
|
/** optional 优先于 field.optional */
|
|
@@ -60,32 +73,50 @@ class DtoObjectField extends DtoField {
|
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
exports.DtoObjectField = DtoObjectField;
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
var DtoDirection;
|
|
77
|
+
(function (DtoDirection) {
|
|
78
|
+
DtoDirection["Input"] = "input";
|
|
79
|
+
DtoDirection["Output"] = "output";
|
|
80
|
+
DtoDirection["Query"] = "query";
|
|
81
|
+
DtoDirection["Pk"] = "pk";
|
|
82
|
+
})(DtoDirection || (exports.DtoDirection = DtoDirection = {}));
|
|
83
|
+
class DtoMessage {
|
|
84
|
+
type = 'dto';
|
|
85
|
+
name;
|
|
86
|
+
description;
|
|
87
|
+
/** 方向:输入或输出 */
|
|
88
|
+
direction;
|
|
89
|
+
fields;
|
|
90
|
+
/** TypeBox base schemas to intersect with at generation time (e.g. PageRequest) */
|
|
91
|
+
bases = [];
|
|
92
|
+
constructor(name, direction, fields, description) {
|
|
93
|
+
this.name = name;
|
|
94
|
+
this.direction = direction;
|
|
95
|
+
this.fields = fields;
|
|
96
|
+
this.description = description;
|
|
97
|
+
}
|
|
98
|
+
/** 引用已存在的 TypeBox base schema,例如 include({ from: '@pylonts/core', name: 'PageRequest' }) */
|
|
99
|
+
include(...refs) {
|
|
100
|
+
this.bases.push(...refs);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.DtoMessage = DtoMessage;
|
|
105
|
+
function dtoField(field) {
|
|
106
|
+
return new DtoField(field);
|
|
65
107
|
}
|
|
66
|
-
function dtoArrayField(def
|
|
67
|
-
|
|
108
|
+
function dtoArrayField(def) {
|
|
109
|
+
// Reuse an existing DTO as the array element: expand its fields into an object.
|
|
110
|
+
const items = def.items instanceof DtoMessage
|
|
111
|
+
? new DtoObjectField({ name: '', type: 'object', jsType: 'object', properties: def.items.fields })
|
|
112
|
+
: def.items;
|
|
113
|
+
return new DtoArrayField({ name: '', type: 'array', jsType: 'array', ...def, items });
|
|
68
114
|
}
|
|
69
|
-
function dtoObjectField(def
|
|
70
|
-
return new DtoObjectField({ name: '', type: 'object', jsType: 'object', ...def }
|
|
115
|
+
function dtoObjectField(def) {
|
|
116
|
+
return new DtoObjectField({ name: '', type: 'object', jsType: 'object', ...def });
|
|
71
117
|
}
|
|
72
118
|
function buildMessage(name, direction, fields, description) {
|
|
73
|
-
const message =
|
|
74
|
-
name,
|
|
75
|
-
direction,
|
|
76
|
-
description,
|
|
77
|
-
fields,
|
|
78
|
-
bases: [],
|
|
79
|
-
include(...refs) {
|
|
80
|
-
this.bases.push(...refs);
|
|
81
|
-
return this;
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
if (direction === 'query') {
|
|
85
|
-
// Rule B: query/search fields are always optional.
|
|
86
|
-
for (const field of Object.values(message.fields))
|
|
87
|
-
field.optional = true;
|
|
88
|
-
}
|
|
119
|
+
const message = new DtoMessage(name, direction, fields, description);
|
|
89
120
|
// Write back the DTO field name from the map key (safe: DtoField instances
|
|
90
121
|
// are created per DTO, never shared).
|
|
91
122
|
for (const key of Object.keys(message.fields)) {
|
|
@@ -103,16 +134,41 @@ function buildMessage(name, direction, fields, description) {
|
|
|
103
134
|
return message;
|
|
104
135
|
}
|
|
105
136
|
function buildInput(name, fields, description) {
|
|
106
|
-
|
|
137
|
+
const message = buildMessage(name, DtoDirection.Input, fields, description);
|
|
138
|
+
// Rule A — set optionality from the DB column rule (skips fields the author
|
|
139
|
+
// already set): nullable / default / auto-increment → optional, else required.
|
|
140
|
+
for (const field of Object.values(message.fields)) {
|
|
141
|
+
if (field.optional !== undefined)
|
|
142
|
+
continue;
|
|
143
|
+
const f = field.field;
|
|
144
|
+
if (f.schema?.type !== 'table')
|
|
145
|
+
continue;
|
|
146
|
+
const table = f.schema;
|
|
147
|
+
field.optional = f.optional !== false || f.default !== undefined || table.autoIncrement === f;
|
|
148
|
+
}
|
|
149
|
+
return message;
|
|
107
150
|
}
|
|
108
151
|
function buildOutput(name, fields, description) {
|
|
109
|
-
return buildMessage(name,
|
|
152
|
+
return buildMessage(name, DtoDirection.Output, fields, description);
|
|
110
153
|
}
|
|
111
154
|
function buildQuery(name, fields, description) {
|
|
112
|
-
|
|
155
|
+
const message = buildMessage(name, DtoDirection.Query, fields, description);
|
|
156
|
+
// Rule B: query/search fields are always optional.
|
|
157
|
+
for (const field of Object.values(message.fields))
|
|
158
|
+
field.optional = true;
|
|
159
|
+
return message;
|
|
113
160
|
}
|
|
114
161
|
function buildPk(name, fields, description) {
|
|
115
|
-
|
|
162
|
+
const message = buildMessage(name, DtoDirection.Pk, fields, description);
|
|
163
|
+
// Rule P: PK locator fields are required, other fields are optional.
|
|
164
|
+
for (const field of Object.values(message.fields)) {
|
|
165
|
+
if (field.optional !== undefined)
|
|
166
|
+
continue;
|
|
167
|
+
const f = field.field;
|
|
168
|
+
const table = f.schema;
|
|
169
|
+
field.optional = table !== undefined && table.isPk(f) ? false : true;
|
|
170
|
+
}
|
|
171
|
+
return message;
|
|
116
172
|
}
|
|
117
173
|
/** Pick columns from a built table and wrap them as DTO fields (aligned with dto.from). */
|
|
118
174
|
function from(table, fields) {
|
|
@@ -121,7 +177,9 @@ function from(table, fields) {
|
|
|
121
177
|
if (field.schema !== table) {
|
|
122
178
|
throw new Error(`dto.from(${table.name}): field ${field.name} does not belong to this table`);
|
|
123
179
|
}
|
|
124
|
-
|
|
180
|
+
// DTO field name is camelCase (mer_id → merId); the underlying field.name
|
|
181
|
+
// stays snake_case (DB column).
|
|
182
|
+
out[(0, utils_1.toCamelCase)(field.name)] = dtoField(field);
|
|
125
183
|
}
|
|
126
184
|
return out;
|
|
127
185
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./dsl"), exports);
|
|
18
18
|
__exportStar(require("./dto"), exports);
|
|
19
|
+
__exportStar(require("./utils"), exports);
|
|
19
20
|
__exportStar(require("./project"), exports);
|
|
20
21
|
__exportStar(require("./prototype"), exports);
|
|
21
22
|
__exportStar(require("./dictionary"), exports);
|
package/dist/mermaid-driver.js
CHANGED
|
@@ -47,9 +47,13 @@ function renderPageFlowMermaid(schema) {
|
|
|
47
47
|
const ids = new Map();
|
|
48
48
|
const byApp = new Map();
|
|
49
49
|
for (const p of schema.pages) {
|
|
50
|
-
const list = byApp.get(p.app.name)
|
|
51
|
-
list
|
|
52
|
-
|
|
50
|
+
const list = byApp.get(p.app.name);
|
|
51
|
+
if (!list) {
|
|
52
|
+
byApp.set(p.app.name, [p]);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
list.push(p);
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
let appIdx = 0;
|
|
55
59
|
for (const [appName, pages] of byApp) {
|
package/dist/typebox-driver.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { DtoMessage, ImportRef } from './dto';
|
|
2
2
|
export type EnumResolver = (enumName: string) => ImportRef | undefined;
|
|
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;
|
|
5
|
+
/** Render one DTO export (const + type) — no file header, for file-level generation. */
|
|
6
|
+
export declare function renderDtoExport(schema: DtoMessage, resolver: EnumResolver | undefined): string;
|
|
7
|
+
/** Render the Static type export for a DTO. */
|
|
8
|
+
export declare function renderDtoTypeExport(name: string): string;
|
|
3
9
|
export declare function renderDtoMessage(schema: DtoMessage, options?: {
|
|
4
10
|
resolver?: EnumResolver;
|
|
5
11
|
source?: string;
|
package/dist/typebox-driver.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectDtoImports = collectDtoImports;
|
|
4
|
+
exports.renderDtoExport = renderDtoExport;
|
|
5
|
+
exports.renderDtoTypeExport = renderDtoTypeExport;
|
|
3
6
|
exports.renderDtoMessage = renderDtoMessage;
|
|
4
7
|
const dto_1 = require("./dto");
|
|
5
8
|
function renderString(s) {
|
|
6
9
|
return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
7
10
|
}
|
|
8
|
-
function
|
|
11
|
+
function renderDefault(v) {
|
|
12
|
+
if (typeof v === 'string')
|
|
13
|
+
return renderString(v);
|
|
14
|
+
return JSON.stringify(v);
|
|
15
|
+
}
|
|
16
|
+
function renderBasic(field, pattern, defaultValue, resolver) {
|
|
9
17
|
if (pattern !== undefined && field.type !== 'string') {
|
|
10
18
|
throw new Error(`pattern is only supported on string fields, got ${field.type} (${field.name})`);
|
|
11
19
|
}
|
|
20
|
+
const def = defaultValue !== undefined ? `default: ${renderDefault(defaultValue)}` : undefined;
|
|
12
21
|
switch (field.type) {
|
|
13
22
|
case 'string': {
|
|
14
23
|
const opts = [];
|
|
@@ -18,16 +27,20 @@ function renderBasic(field, pattern, resolver) {
|
|
|
18
27
|
opts.push(`maxLength: ${field.maxLength}`);
|
|
19
28
|
if (pattern !== undefined)
|
|
20
29
|
opts.push(`pattern: ${renderString(pattern)}`);
|
|
30
|
+
if (def !== undefined)
|
|
31
|
+
opts.push(def);
|
|
21
32
|
return opts.length > 0 ? `Type.String({ ${opts.join(', ')} })` : 'Type.String()';
|
|
22
33
|
}
|
|
23
34
|
case 'text':
|
|
24
|
-
return 'Type.String()';
|
|
35
|
+
return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
|
|
25
36
|
case 'integer': {
|
|
26
37
|
const opts = [];
|
|
27
38
|
if (field.min !== undefined)
|
|
28
39
|
opts.push(`minimum: ${field.min}`);
|
|
29
40
|
if (field.max !== undefined)
|
|
30
41
|
opts.push(`maximum: ${field.max}`);
|
|
42
|
+
if (def !== undefined)
|
|
43
|
+
opts.push(def);
|
|
31
44
|
return opts.length > 0 ? `Type.Integer({ ${opts.join(', ')} })` : 'Type.Integer()';
|
|
32
45
|
}
|
|
33
46
|
case 'bigint':
|
|
@@ -37,15 +50,22 @@ function renderBasic(field, pattern, resolver) {
|
|
|
37
50
|
case 'datetime':
|
|
38
51
|
// Transmitted as string over HTTP: bigint/decimal keep full precision,
|
|
39
52
|
// date/time serialize to string.
|
|
40
|
-
return 'Type.String()';
|
|
53
|
+
return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
|
|
41
54
|
case 'boolean':
|
|
42
|
-
return 'Type.Boolean()';
|
|
55
|
+
return def !== undefined ? `Type.Boolean({ ${def} })` : 'Type.Boolean()';
|
|
43
56
|
case 'json':
|
|
44
|
-
return 'Type.Unknown()';
|
|
57
|
+
return def !== undefined ? `Type.Unknown({ ${def} })` : 'Type.Unknown()';
|
|
45
58
|
case 'enum': {
|
|
46
59
|
const ref = resolver?.(field.enum.jsName);
|
|
47
60
|
if (!ref)
|
|
48
61
|
throw new Error(`enum field ${field.name}: no import ref for ${field.enum.jsName} — pass an EnumResolver`);
|
|
62
|
+
if (def !== undefined) {
|
|
63
|
+
const member = field.enum.values.find((v) => v.value === defaultValue);
|
|
64
|
+
if (!member) {
|
|
65
|
+
throw new Error(`enum field ${field.name}: default ${renderDefault(defaultValue)} is not a member of ${field.enum.jsName}`);
|
|
66
|
+
}
|
|
67
|
+
return `Type.Enum(${ref.name}, { default: ${ref.name}.${member.symbol} })`;
|
|
68
|
+
}
|
|
49
69
|
return `Type.Enum(${ref.name})`;
|
|
50
70
|
}
|
|
51
71
|
default:
|
|
@@ -70,7 +90,10 @@ function renderValue(f, indent, resolver) {
|
|
|
70
90
|
return renderObject(f.properties(), indent + 1, resolver);
|
|
71
91
|
}
|
|
72
92
|
// DtoField only wraps a database Field; array/object defs live in the subclasses.
|
|
73
|
-
|
|
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);
|
|
74
97
|
}
|
|
75
98
|
function collectEnumImports(f, resolver, out) {
|
|
76
99
|
if (f instanceof dto_1.DtoArrayField) {
|
|
@@ -89,34 +112,47 @@ function collectEnumImports(f, resolver, out) {
|
|
|
89
112
|
out.set(`${ref.from}#${ref.name}`, ref);
|
|
90
113
|
}
|
|
91
114
|
}
|
|
115
|
+
/** Collect all imports needed to render a DTO: include() bases + enum references. */
|
|
116
|
+
function collectDtoImports(schema, resolver, out) {
|
|
117
|
+
for (const base of schema.bases ?? [])
|
|
118
|
+
out.set(`${base.from}#${base.name}`, base);
|
|
119
|
+
for (const f of Object.values(schema.fields))
|
|
120
|
+
collectEnumImports(f, resolver, out);
|
|
121
|
+
}
|
|
122
|
+
/** Render one DTO export (const + type) — no file header, for file-level generation. */
|
|
123
|
+
function renderDtoExport(schema, resolver) {
|
|
124
|
+
const object = renderObject(schema.fields, 1, resolver);
|
|
125
|
+
const bases = schema.bases ?? [];
|
|
126
|
+
const body = bases.length > 0
|
|
127
|
+
? `Type.Intersect([${bases.map(renderBase).join(', ')}, ${object}])`
|
|
128
|
+
: object;
|
|
129
|
+
return `export const ${schema.name} = ${body};`;
|
|
130
|
+
}
|
|
131
|
+
/** Render the Static type export for a DTO. */
|
|
132
|
+
function renderDtoTypeExport(name) {
|
|
133
|
+
return `export type ${name} = Static<typeof ${name}>;`;
|
|
134
|
+
}
|
|
92
135
|
function renderDtoMessage(schema, options = {}) {
|
|
93
136
|
const { resolver, source } = options;
|
|
94
137
|
const imports = new Map();
|
|
95
|
-
|
|
96
|
-
imports.set(`${base.from}#${base.name}`, base);
|
|
97
|
-
for (const f of Object.values(schema.fields))
|
|
98
|
-
collectEnumImports(f, resolver, imports);
|
|
138
|
+
collectDtoImports(schema, resolver, imports);
|
|
99
139
|
const header = [
|
|
100
140
|
'// AUTO-GENERATED by typebox-driver — DO NOT EDIT',
|
|
101
141
|
...(source !== undefined ? [`// Source: ${source}`] : []),
|
|
102
142
|
"import { Type, Static } from '@sinclair/typebox';",
|
|
103
143
|
...[...imports.values()].map((r) => `import { ${r.name} } from '${r.from}';`),
|
|
104
144
|
];
|
|
105
|
-
const object = renderObject(schema.fields, 1, resolver);
|
|
106
|
-
const bases = schema.bases ?? [];
|
|
107
|
-
const body = bases.length > 0
|
|
108
|
-
? `Type.Intersect([${bases.map(renderBase).join(', ')}, ${object}])`
|
|
109
|
-
: object;
|
|
110
145
|
return [
|
|
111
146
|
...header,
|
|
112
147
|
'',
|
|
113
|
-
|
|
114
|
-
|
|
148
|
+
renderDtoExport(schema, resolver),
|
|
149
|
+
renderDtoTypeExport(schema.name),
|
|
115
150
|
'',
|
|
116
151
|
].join('\n');
|
|
117
152
|
}
|
|
118
153
|
function renderBase(base) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
if (base.args === undefined || base.args.length === 0)
|
|
155
|
+
return base.name;
|
|
156
|
+
const args = base.args.map((a) => (typeof a === 'string' ? a : a.name));
|
|
157
|
+
return `${base.name}(${args.join(', ')})`;
|
|
122
158
|
}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ── naming conversions ──
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.toCamelCase = toCamelCase;
|
|
5
|
+
/** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
|
|
6
|
+
function toCamelCase(name) {
|
|
7
|
+
return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
8
|
+
}
|