@pylonts/dsl 1.0.2 → 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.
@@ -17,24 +17,37 @@ function renderString(s: string): string {
17
17
  return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
18
18
  }
19
19
 
20
- function renderBasic(field: Field, pattern: string | undefined, resolver: EnumResolver | undefined): string {
20
+ function renderDefault(v: unknown): string {
21
+ if (typeof v === 'string') return renderString(v);
22
+ return JSON.stringify(v);
23
+ }
24
+
25
+ function renderBasic(
26
+ field: Field,
27
+ pattern: string | undefined,
28
+ defaultValue: unknown,
29
+ resolver: EnumResolver | undefined,
30
+ ): string {
21
31
  if (pattern !== undefined && field.type !== 'string') {
22
32
  throw new Error(`pattern is only supported on string fields, got ${field.type} (${field.name})`);
23
33
  }
34
+ const def = defaultValue !== undefined ? `default: ${renderDefault(defaultValue)}` : undefined;
24
35
  switch (field.type) {
25
36
  case 'string': {
26
37
  const opts: string[] = [];
27
38
  if (field.minLength !== undefined) opts.push(`minLength: ${field.minLength}`);
28
39
  if (field.maxLength !== undefined) opts.push(`maxLength: ${field.maxLength}`);
29
40
  if (pattern !== undefined) opts.push(`pattern: ${renderString(pattern)}`);
41
+ if (def !== undefined) opts.push(def);
30
42
  return opts.length > 0 ? `Type.String({ ${opts.join(', ')} })` : 'Type.String()';
31
43
  }
32
44
  case 'text':
33
- return 'Type.String()';
45
+ return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
34
46
  case 'integer': {
35
47
  const opts: string[] = [];
36
48
  if (field.min !== undefined) opts.push(`minimum: ${field.min}`);
37
49
  if (field.max !== undefined) opts.push(`maximum: ${field.max}`);
50
+ if (def !== undefined) opts.push(def);
38
51
  return opts.length > 0 ? `Type.Integer({ ${opts.join(', ')} })` : 'Type.Integer()';
39
52
  }
40
53
  case 'bigint':
@@ -44,14 +57,23 @@ function renderBasic(field: Field, pattern: string | undefined, resolver: EnumRe
44
57
  case 'datetime':
45
58
  // Transmitted as string over HTTP: bigint/decimal keep full precision,
46
59
  // date/time serialize to string.
47
- return 'Type.String()';
60
+ return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
48
61
  case 'boolean':
49
- return 'Type.Boolean()';
62
+ return def !== undefined ? `Type.Boolean({ ${def} })` : 'Type.Boolean()';
50
63
  case 'json':
51
- return 'Type.Unknown()';
64
+ return def !== undefined ? `Type.Unknown({ ${def} })` : 'Type.Unknown()';
52
65
  case 'enum': {
53
66
  const ref = resolver?.(field.enum.jsName);
54
67
  if (!ref) throw new Error(`enum field ${field.name}: no import ref for ${field.enum.jsName} — pass an EnumResolver`);
68
+ if (def !== undefined) {
69
+ const member = field.enum.values.find((v) => v.value === defaultValue);
70
+ if (!member) {
71
+ throw new Error(
72
+ `enum field ${field.name}: default ${renderDefault(defaultValue)} is not a member of ${field.enum.jsName}`,
73
+ );
74
+ }
75
+ return `Type.Enum(${ref.name}, { default: ${ref.name}.${member.symbol} })`;
76
+ }
55
77
  return `Type.Enum(${ref.name})`;
56
78
  }
57
79
  default:
@@ -79,7 +101,10 @@ function renderValue(f: DtoField, indent: number, resolver: EnumResolver | undef
79
101
  return renderObject(f.properties(), indent + 1, resolver);
80
102
  }
81
103
  // DtoField only wraps a database Field; array/object defs live in the subclasses.
82
- return renderBasic(f.field as Field, f.pattern, resolver);
104
+ // DTO-level default wins over the DB field default; the DB default (string)
105
+ // is used as a fallback so from() picks carry it into the API contract.
106
+ const defaultValue = f.default !== undefined ? f.default : f.field.default;
107
+ return renderBasic(f.field as Field, f.pattern, defaultValue, resolver);
83
108
  }
84
109
 
85
110
  function collectEnumImports(
@@ -102,14 +127,39 @@ function collectEnumImports(
102
127
  }
103
128
  }
104
129
 
130
+ /** Collect all imports needed to render a DTO: include() bases + enum references. */
131
+ export function collectDtoImports(
132
+ schema: DtoMessage,
133
+ resolver: EnumResolver | undefined,
134
+ out: Map<string, ImportRef>,
135
+ ): void {
136
+ for (const base of schema.bases ?? []) out.set(`${base.from}#${base.name}`, base);
137
+ for (const f of Object.values(schema.fields)) collectEnumImports(f, resolver, out);
138
+ }
139
+
140
+ /** Render one DTO export (const + type) — no file header, for file-level generation. */
141
+ export function renderDtoExport(schema: DtoMessage, resolver: EnumResolver | undefined): string {
142
+ const object = renderObject(schema.fields, 1, resolver);
143
+ const bases = schema.bases ?? [];
144
+ const body =
145
+ bases.length > 0
146
+ ? `Type.Intersect([${bases.map(renderBase).join(', ')}, ${object}])`
147
+ : object;
148
+ return `export const ${schema.name} = ${body};`;
149
+ }
150
+
151
+ /** Render the Static type export for a DTO. */
152
+ export function renderDtoTypeExport(name: string): string {
153
+ return `export type ${name} = Static<typeof ${name}>;`;
154
+ }
155
+
105
156
  export function renderDtoMessage(
106
157
  schema: DtoMessage,
107
158
  options: { resolver?: EnumResolver; source?: string } = {},
108
159
  ): string {
109
160
  const { resolver, source } = options;
110
161
  const imports = new Map<string, ImportRef>();
111
- for (const base of schema.bases ?? []) imports.set(`${base.from}#${base.name}`, base);
112
- for (const f of Object.values(schema.fields)) collectEnumImports(f, resolver, imports);
162
+ collectDtoImports(schema, resolver, imports);
113
163
 
114
164
  const header = [
115
165
  '// AUTO-GENERATED by typebox-driver — DO NOT EDIT',
@@ -118,24 +168,17 @@ export function renderDtoMessage(
118
168
  ...[...imports.values()].map((r) => `import { ${r.name} } from '${r.from}';`),
119
169
  ];
120
170
 
121
- const object = renderObject(schema.fields, 1, resolver);
122
- const bases = schema.bases ?? [];
123
- const body =
124
- bases.length > 0
125
- ? `Type.Intersect([${bases.map(renderBase).join(', ')}, ${object}])`
126
- : object;
127
-
128
171
  return [
129
172
  ...header,
130
173
  '',
131
- `export const ${schema.name} = ${body};`,
132
- `export type ${schema.name} = Static<typeof ${schema.name}>;`,
174
+ renderDtoExport(schema, resolver),
175
+ renderDtoTypeExport(schema.name),
133
176
  '',
134
177
  ].join('\n');
135
178
  }
136
179
 
137
180
  function renderBase(base: ImportRef): string {
138
- return base.args !== undefined && base.args.length > 0
139
- ? `${base.name}(${base.args.join(', ')})`
140
- : base.name;
181
+ if (base.args === undefined || base.args.length === 0) return base.name;
182
+ const args = base.args.map((a) => (typeof a === 'string' ? a : a.name));
183
+ return `${base.name}(${args.join(', ')})`;
141
184
  }
package/src/utils.ts ADDED
@@ -0,0 +1,6 @@
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
+ }