@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 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?: SchemaBase;
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 TableSchema extends SchemaBase {
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 = { name, ...schema };
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
- /** Generic type arguments for the base schema (same-file DTO export names), e.g. PageResult(AdvertRow) */
14
- args?: string[];
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
- constructor(field: Field | DtoArrayFieldDef | DtoObjectFieldDef, extra?: DtoExtras);
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
- op(value: Operator): this;
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 type DtoDirection = 'input' | 'output' | 'query' | 'pk';
60
- export interface DtoMessage extends SchemaBase {
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?: ImportRef[];
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[]): DtoMessage;
82
+ include(...refs: ImportRef[]): this;
68
83
  }
69
- export declare function dtoField(field: Field, extra?: DtoExtras): DtoField;
84
+ export declare function dtoField(field: Field): DtoField;
70
85
  export declare function dtoArrayField(def: {
71
- items: DtoField;
72
- } & Omit<BaseField, 'name'>, extra?: DtoExtras): DtoArrayField;
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'>, extra?: DtoExtras): DtoObjectField;
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
- constructor(field, extra = {}) {
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
- op(value) {
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
- function dtoField(field, extra = {}) {
64
- return new DtoField(field, extra);
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, extra = {}) {
67
- return new DtoArrayField({ name: '', type: 'array', jsType: 'array', ...def }, extra);
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, extra = {}) {
70
- return new DtoObjectField({ name: '', type: 'object', jsType: 'object', ...def }, extra);
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
- return buildMessage(name, 'input', fields, description);
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, 'output', fields, description);
152
+ return buildMessage(name, DtoDirection.Output, fields, description);
110
153
  }
111
154
  function buildQuery(name, fields, description) {
112
- return buildMessage(name, 'query', fields, description);
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
- return buildMessage(name, 'pk', fields, description);
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
- out[field.name] = dtoField(field);
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
@@ -1,5 +1,6 @@
1
1
  export * from './dsl';
2
2
  export * from './dto';
3
+ export * from './utils';
3
4
  export * from './project';
4
5
  export * from './prototype';
5
6
  export * from './dictionary';
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);
@@ -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.push(p);
52
- byApp.set(p.app.name, list);
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) {
@@ -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;
@@ -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 renderBasic(field, pattern, resolver) {
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
- return renderBasic(f.field, f.pattern, resolver);
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
- for (const base of schema.bases ?? [])
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
- `export const ${schema.name} = ${body};`,
114
- `export type ${schema.name} = Static<typeof ${schema.name}>;`,
148
+ renderDtoExport(schema, resolver),
149
+ renderDtoTypeExport(schema.name),
115
150
  '',
116
151
  ].join('\n');
117
152
  }
118
153
  function renderBase(base) {
119
- return base.args !== undefined && base.args.length > 0
120
- ? `${base.name}(${base.args.join(', ')})`
121
- : base.name;
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
  }
@@ -0,0 +1,2 @@
1
+ /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
2
+ export declare function toCamelCase(name: string): string;
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
+ }