ormbridge 0.0.0-stage → 0.3.0

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +459 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +20 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliRunner.d.ts +20 -0
  7. package/dist/cliRunner.js +363 -0
  8. package/dist/cliRunner.js.map +1 -0
  9. package/dist/config.d.ts +44 -0
  10. package/dist/config.js +220 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/convert.d.ts +20 -0
  13. package/dist/convert.js +61 -0
  14. package/dist/convert.js.map +1 -0
  15. package/dist/emitters/django.d.ts +4 -0
  16. package/dist/emitters/django.js +501 -0
  17. package/dist/emitters/django.js.map +1 -0
  18. package/dist/emitters/doctrine.d.ts +20 -0
  19. package/dist/emitters/doctrine.js +1134 -0
  20. package/dist/emitters/doctrine.js.map +1 -0
  21. package/dist/emitters/gorm.d.ts +38 -0
  22. package/dist/emitters/gorm.js +1507 -0
  23. package/dist/emitters/gorm.js.map +1 -0
  24. package/dist/emitters/graphene.d.ts +4 -0
  25. package/dist/emitters/graphene.js +431 -0
  26. package/dist/emitters/graphene.js.map +1 -0
  27. package/dist/emitters/laravel.d.ts +33 -0
  28. package/dist/emitters/laravel.js +1695 -0
  29. package/dist/emitters/laravel.js.map +1 -0
  30. package/dist/emitters/prisma.d.ts +21 -0
  31. package/dist/emitters/prisma.js +1058 -0
  32. package/dist/emitters/prisma.js.map +1 -0
  33. package/dist/emitters/typeorm.d.ts +9 -0
  34. package/dist/emitters/typeorm.js +838 -0
  35. package/dist/emitters/typeorm.js.map +1 -0
  36. package/dist/emitters/typescriptInterfaces.d.ts +15 -0
  37. package/dist/emitters/typescriptInterfaces.js +350 -0
  38. package/dist/emitters/typescriptInterfaces.js.map +1 -0
  39. package/dist/formats.d.ts +82 -0
  40. package/dist/formats.js +238 -0
  41. package/dist/formats.js.map +1 -0
  42. package/dist/index.d.ts +37 -0
  43. package/dist/index.js +20 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/io.d.ts +48 -0
  46. package/dist/io.js +343 -0
  47. package/dist/io.js.map +1 -0
  48. package/dist/ir.d.ts +197 -0
  49. package/dist/ir.js +10 -0
  50. package/dist/ir.js.map +1 -0
  51. package/dist/naming.d.ts +11 -0
  52. package/dist/naming.js +88 -0
  53. package/dist/naming.js.map +1 -0
  54. package/dist/parsers/django.d.ts +14 -0
  55. package/dist/parsers/django.js +1161 -0
  56. package/dist/parsers/django.js.map +1 -0
  57. package/dist/parsers/doctrine.d.ts +12 -0
  58. package/dist/parsers/doctrine.js +1984 -0
  59. package/dist/parsers/doctrine.js.map +1 -0
  60. package/dist/parsers/goSyntax.d.ts +105 -0
  61. package/dist/parsers/goSyntax.js +435 -0
  62. package/dist/parsers/goSyntax.js.map +1 -0
  63. package/dist/parsers/gorm.d.ts +16 -0
  64. package/dist/parsers/gorm.js +1857 -0
  65. package/dist/parsers/gorm.js.map +1 -0
  66. package/dist/parsers/laravel.d.ts +17 -0
  67. package/dist/parsers/laravel.js +2824 -0
  68. package/dist/parsers/laravel.js.map +1 -0
  69. package/dist/parsers/phpSyntax.d.ts +124 -0
  70. package/dist/parsers/phpSyntax.js +429 -0
  71. package/dist/parsers/phpSyntax.js.map +1 -0
  72. package/dist/parsers/prisma.d.ts +12 -0
  73. package/dist/parsers/prisma.js +964 -0
  74. package/dist/parsers/prisma.js.map +1 -0
  75. package/dist/parsers/pythonSyntax.d.ts +38 -0
  76. package/dist/parsers/pythonSyntax.js +120 -0
  77. package/dist/parsers/pythonSyntax.js.map +1 -0
  78. package/dist/parsers/typeorm.d.ts +12 -0
  79. package/dist/parsers/typeorm.js +1614 -0
  80. package/dist/parsers/typeorm.js.map +1 -0
  81. package/dist/parsers/typescriptSyntax.d.ts +57 -0
  82. package/dist/parsers/typescriptSyntax.js +200 -0
  83. package/dist/parsers/typescriptSyntax.js.map +1 -0
  84. package/dist/prismaOnlyConstructs.d.ts +7 -0
  85. package/dist/prismaOnlyConstructs.js +76 -0
  86. package/dist/prismaOnlyConstructs.js.map +1 -0
  87. package/dist/result.d.ts +20 -0
  88. package/dist/result.js +18 -0
  89. package/dist/result.js.map +1 -0
  90. package/dist/transforms.d.ts +13 -0
  91. package/dist/transforms.js +142 -0
  92. package/dist/transforms.js.map +1 -0
  93. package/package.json +83 -4
@@ -0,0 +1,838 @@
1
+ import { prismaOnlyWarnings } from '../prismaOnlyConstructs.js';
2
+ import { findModel } from '../ir.js';
3
+ import { toCamelCase, toSnakeCase } from '../naming.js';
4
+ const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
5
+ const CREATED_FIELD_PATTERN = /^(created|created_?at|date_?created|createdon)$/i;
6
+ const MAX_LINE_WIDTH = 80;
7
+ const ON_DELETE_NAMES = {
8
+ cascade: 'CASCADE',
9
+ setNull: 'SET NULL',
10
+ restrict: 'RESTRICT',
11
+ noAction: 'NO ACTION',
12
+ setDefault: 'SET DEFAULT',
13
+ };
14
+ export function emitTypeorm(schema, options) {
15
+ const context = {
16
+ schema,
17
+ options,
18
+ dialect: dialectOf(options.provider),
19
+ warnings: prismaOnlyWarnings(schema),
20
+ imports: new Set(),
21
+ names: new Map(),
22
+ inverseNames: new Map(),
23
+ };
24
+ if (options.provider === 'mongodb') {
25
+ context.warnings.push('Provider "mongodb": TypeORM entities for MongoDB need different decorators; SQL column types (PostgreSQL style) were used.');
26
+ }
27
+ allocateNames(context);
28
+ const blocks = [];
29
+ for (const enumDefinition of schema.enums) {
30
+ blocks.push(emitEnum(context, enumDefinition));
31
+ }
32
+ const inverseMembers = new Map();
33
+ const classMembers = new Map();
34
+ for (const model of schema.models) {
35
+ classMembers.set(model.name, buildForwardMembers(context, model, inverseMembers));
36
+ }
37
+ for (const model of schema.models) {
38
+ const members = [
39
+ ...(classMembers.get(model.name) ?? []),
40
+ ...(inverseMembers.get(model.name) ?? []),
41
+ ];
42
+ blocks.push(emitClass(context, model, members));
43
+ }
44
+ if (blocks.length === 0) {
45
+ return { text: '', warnings: context.warnings };
46
+ }
47
+ const importLine = buildImportLine(context.imports);
48
+ return {
49
+ text: `${[importLine, ...blocks].join('\n\n')}\n`,
50
+ warnings: context.warnings,
51
+ };
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Imports and small formatting helpers
55
+ // ---------------------------------------------------------------------------
56
+ function buildImportLine(imports) {
57
+ const names = [...imports].sort((first, second) => first.localeCompare(second));
58
+ const specifiers = names.map((name) => name === 'Relation' ? 'type Relation' : name);
59
+ return `import { ${specifiers.join(', ')} } from 'typeorm';`;
60
+ }
61
+ function use(context, name) {
62
+ context.imports.add(name);
63
+ return name;
64
+ }
65
+ function quote(value) {
66
+ const escaped = value.replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
67
+ if (value.includes("'") && !value.includes('"')) {
68
+ return `"${escaped}"`;
69
+ }
70
+ return `'${escaped.replace(/'/g, "\\'")}'`;
71
+ }
72
+ /**
73
+ * Formats a decorator. Leading arguments are written as-is; options become one
74
+ * object literal that wraps onto several lines when the decorator is too wide.
75
+ */
76
+ function decorator(name, args, entries, indent = ' ') {
77
+ const parts = [...args];
78
+ if (entries.length > 0) {
79
+ parts.push(`{ ${entries.join(', ')} }`);
80
+ }
81
+ const inline = `${indent}@${name}(${parts.join(', ')})`;
82
+ if (entries.length === 0 || inline.length <= MAX_LINE_WIDTH) {
83
+ return [inline];
84
+ }
85
+ const lead = args.length > 0 ? `${args.join(', ')}, ` : '';
86
+ return [
87
+ `${indent}@${name}(${lead}{`,
88
+ ...entries.map((entry) => `${indent} ${entry},`),
89
+ `${indent}})`,
90
+ ];
91
+ }
92
+ function identifier(context, owner, raw) {
93
+ if (IDENTIFIER_PATTERN.test(raw)) {
94
+ return raw;
95
+ }
96
+ const replaced = raw.replace(/[^A-Za-z0-9_$]/g, '_');
97
+ const safe = /^[0-9]/.test(replaced) ? `_${replaced}` : replaced;
98
+ const result = safe === '' ? '_' : safe;
99
+ context.warnings.push(`${owner}: "${raw}" is not a valid TypeScript identifier; it was written as "${result}".`);
100
+ return result;
101
+ }
102
+ function uniqueName(baseName, used) {
103
+ let candidate = baseName;
104
+ let suffix = 2;
105
+ while (used.has(candidate)) {
106
+ candidate = `${baseName}${suffix}`;
107
+ suffix += 1;
108
+ }
109
+ used.add(candidate);
110
+ return candidate;
111
+ }
112
+ function dialectOf(provider) {
113
+ switch (provider) {
114
+ case 'mysql':
115
+ return 'mysql';
116
+ case 'sqlite':
117
+ return 'sqlite';
118
+ case 'sqlserver':
119
+ return 'sqlserver';
120
+ default:
121
+ return 'postgres';
122
+ }
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Names
126
+ // ---------------------------------------------------------------------------
127
+ function propertyName(context, rawName) {
128
+ return context.options.camelFields ? toCamelCase(rawName) : rawName;
129
+ }
130
+ function isKeyedRelation(model, relation) {
131
+ return (relation.isPrimaryKey === true ||
132
+ (model.compositePrimaryKey?.includes(relation.name) ?? false));
133
+ }
134
+ function allocateNames(context) {
135
+ for (const model of context.schema.models) {
136
+ const names = {
137
+ fields: new Map(),
138
+ relations: new Map(),
139
+ scalars: new Map(),
140
+ used: new Set(),
141
+ };
142
+ for (const field of model.fields) {
143
+ const safe = identifier(context, `${model.name}.${field.name}`, propertyName(context, field.name));
144
+ names.fields.set(field.name, uniqueName(safe, names.used));
145
+ }
146
+ for (const relation of model.relations) {
147
+ const safe = identifier(context, `${model.name}.${relation.name}`, propertyName(context, relation.name));
148
+ const relationProp = uniqueName(safe, names.used);
149
+ names.relations.set(relation.name, relationProp);
150
+ if (relation.kind !== 'manyToMany' && isKeyedRelation(model, relation)) {
151
+ const columnProp = propertyName(context, relation.columnName);
152
+ const base = columnProp !== relationProp ? columnProp : `${relationProp}Id`;
153
+ names.scalars.set(relation.name, uniqueName(identifier(context, `${model.name}.${relation.name}`, base), names.used));
154
+ }
155
+ }
156
+ context.names.set(model.name, names);
157
+ }
158
+ for (const model of context.schema.models) {
159
+ for (const relation of model.relations) {
160
+ const target = findModel(context.schema, relation.targetModel);
161
+ const targetNames = context.names.get(relation.targetModel);
162
+ if (target === undefined || targetNames === undefined) {
163
+ continue;
164
+ }
165
+ const defaultName = relation.kind === 'oneToOne'
166
+ ? toSnakeCase(model.name)
167
+ : `${toSnakeCase(model.name)}_set`;
168
+ const safe = identifier(context, `${model.name}.${relation.name}`, propertyName(context, relation.relatedName ?? defaultName));
169
+ context.inverseNames.set(`${model.name}.${relation.name}`, uniqueName(safe, targetNames.used));
170
+ }
171
+ }
172
+ }
173
+ function namesOf(context, model) {
174
+ return (context.names.get(model.name) ?? {
175
+ fields: new Map(),
176
+ relations: new Map(),
177
+ scalars: new Map(),
178
+ used: new Set(),
179
+ });
180
+ }
181
+ // ---------------------------------------------------------------------------
182
+ // Enums
183
+ // ---------------------------------------------------------------------------
184
+ function emitEnum(context, enumDefinition) {
185
+ const lines = [`export enum ${enumDefinition.name} {`];
186
+ for (const value of enumDefinition.values) {
187
+ lines.push(` ${enumMemberName(context, enumDefinition, value)} = ${quote(value.dbValue)},`);
188
+ }
189
+ lines.push('}');
190
+ return lines.join('\n');
191
+ }
192
+ function enumMemberName(context, enumDefinition, value) {
193
+ return identifier(context, `enum ${enumDefinition.name}`, value.name);
194
+ }
195
+ /** Finds the enum member a default refers to, by member name or stored value. */
196
+ function enumReference(context, enumDefinition, raw) {
197
+ const value = enumDefinition.values.find((candidate) => candidate.name === raw) ??
198
+ enumDefinition.values.find((candidate) => candidate.dbValue === raw);
199
+ if (value === undefined) {
200
+ return undefined;
201
+ }
202
+ const memberName = identifier({ ...context, warnings: [] }, '', value.name);
203
+ return `${enumDefinition.name}.${memberName}`;
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // Column types
207
+ // ---------------------------------------------------------------------------
208
+ const POSTGRES_RANGE_TYPES = {
209
+ int: 'int4range',
210
+ bigInt: 'int8range',
211
+ decimal: 'numrange',
212
+ date: 'daterange',
213
+ dateTime: 'tstzrange',
214
+ };
215
+ /** Column type of a field, including array columns built on top of the element type. */
216
+ function columnSpecOf(context, label, field) {
217
+ const depth = field.arrayDepth ?? 0;
218
+ if (depth === 0) {
219
+ return scalarColumnSpec(context, label, field);
220
+ }
221
+ const element = scalarColumnSpec(context, label, {
222
+ ...field,
223
+ arrayDepth: undefined,
224
+ });
225
+ const tsType = `${element.tsType.includes(' ') ? `(${element.tsType})` : element.tsType}${'[]'.repeat(depth)}`;
226
+ if (context.dialect === 'postgres') {
227
+ return {
228
+ typeEntries: [...element.typeEntries, 'array: true'],
229
+ tsType,
230
+ dbType: element.dbType,
231
+ };
232
+ }
233
+ const jsonType = context.dialect === 'mysql' ? 'json' : 'simple-json';
234
+ context.warnings.push(`${label}: ${context.dialect} has no array column type; the array was written as ${jsonType}.`);
235
+ return {
236
+ typeEntries: [`type: '${jsonType}'`],
237
+ tsType,
238
+ dbType: jsonType,
239
+ };
240
+ }
241
+ function scalarColumnSpec(context, label, field) {
242
+ const dialect = context.dialect;
243
+ if (field.enumName !== undefined) {
244
+ const enumDefinition = context.schema.enums.find((candidate) => candidate.name === field.enumName);
245
+ if (enumDefinition === undefined) {
246
+ context.warnings.push(`${label}: enum "${field.enumName}" does not exist in the schema; the column is written as a plain string.`);
247
+ }
248
+ else if (dialect === 'sqlserver') {
249
+ context.warnings.push(`${label}: SQL Server has no enum column type; it was written as varchar and the TypeScript enum "${enumDefinition.name}" is only used for typing.`);
250
+ return {
251
+ typeEntries: [`type: 'varchar'`],
252
+ tsType: enumDefinition.name,
253
+ dbType: 'varchar',
254
+ };
255
+ }
256
+ else {
257
+ const enumType = dialect === 'sqlite' ? 'simple-enum' : 'enum';
258
+ return {
259
+ typeEntries: [
260
+ `type: ${quote(enumType)}`,
261
+ `enum: ${enumDefinition.name}`,
262
+ ],
263
+ tsType: enumDefinition.name,
264
+ dbType: enumType,
265
+ };
266
+ }
267
+ }
268
+ switch (field.type) {
269
+ case 'string': {
270
+ const entries = [`type: 'varchar'`];
271
+ if (field.maxLength !== undefined) {
272
+ entries.push(`length: ${field.maxLength}`);
273
+ }
274
+ return { typeEntries: entries, tsType: 'string', dbType: 'varchar' };
275
+ }
276
+ case 'text':
277
+ return {
278
+ typeEntries: [`type: 'text'`],
279
+ tsType: 'string',
280
+ dbType: 'text',
281
+ };
282
+ case 'uuid':
283
+ if (dialect === 'mysql' || dialect === 'sqlite') {
284
+ return {
285
+ typeEntries: [`type: 'varchar'`, 'length: 36'],
286
+ tsType: 'string',
287
+ dbType: 'varchar',
288
+ };
289
+ }
290
+ return dialect === 'sqlserver'
291
+ ? {
292
+ typeEntries: [`type: 'uniqueidentifier'`],
293
+ tsType: 'string',
294
+ dbType: 'uniqueidentifier',
295
+ }
296
+ : { typeEntries: [`type: 'uuid'`], tsType: 'string', dbType: 'uuid' };
297
+ case 'int':
298
+ return { typeEntries: [`type: 'int'`], tsType: 'number', dbType: 'int' };
299
+ case 'bigInt':
300
+ return {
301
+ typeEntries: [`type: 'bigint'`],
302
+ tsType: 'string',
303
+ dbType: 'bigint',
304
+ };
305
+ case 'float':
306
+ return {
307
+ typeEntries: [`type: 'float'`],
308
+ tsType: 'number',
309
+ dbType: 'float',
310
+ };
311
+ case 'decimal': {
312
+ const entries = [`type: 'decimal'`];
313
+ if (field.maxDigits !== undefined && field.decimalPlaces !== undefined) {
314
+ entries.push(`precision: ${field.maxDigits}`, `scale: ${field.decimalPlaces}`);
315
+ }
316
+ return { typeEntries: entries, tsType: 'string', dbType: 'decimal' };
317
+ }
318
+ case 'boolean': {
319
+ const booleanType = dialect === 'sqlserver' ? 'bit' : 'boolean';
320
+ return {
321
+ typeEntries: [`type: '${booleanType}'`],
322
+ tsType: 'boolean',
323
+ dbType: booleanType,
324
+ };
325
+ }
326
+ case 'dateTime': {
327
+ const dateTimeType = dialect === 'postgres'
328
+ ? 'timestamptz'
329
+ : dialect === 'sqlserver'
330
+ ? 'datetimeoffset'
331
+ : 'datetime';
332
+ return {
333
+ typeEntries: [`type: '${dateTimeType}'`],
334
+ tsType: 'Date',
335
+ dbType: dateTimeType,
336
+ };
337
+ }
338
+ case 'date':
339
+ return {
340
+ typeEntries: [`type: 'date'`],
341
+ tsType: 'string',
342
+ dbType: 'date',
343
+ };
344
+ case 'time':
345
+ return {
346
+ typeEntries: [`type: 'time'`],
347
+ tsType: 'string',
348
+ dbType: 'time',
349
+ };
350
+ case 'json': {
351
+ const jsonType = dialect === 'postgres'
352
+ ? 'jsonb'
353
+ : dialect === 'mysql'
354
+ ? 'json'
355
+ : 'simple-json';
356
+ return {
357
+ typeEntries: [`type: '${jsonType}'`],
358
+ tsType: 'unknown',
359
+ dbType: jsonType,
360
+ };
361
+ }
362
+ case 'bytes': {
363
+ const bytesType = dialect === 'postgres'
364
+ ? 'bytea'
365
+ : dialect === 'sqlserver'
366
+ ? 'varbinary'
367
+ : 'blob';
368
+ return {
369
+ typeEntries: [`type: '${bytesType}'`],
370
+ tsType: 'Buffer',
371
+ dbType: bytesType,
372
+ };
373
+ }
374
+ case 'duration':
375
+ if (dialect === 'postgres') {
376
+ return {
377
+ typeEntries: [`type: 'interval'`],
378
+ tsType: 'string',
379
+ dbType: 'interval',
380
+ };
381
+ }
382
+ context.warnings.push(`${label}: ${dialect} has no interval type; the duration was written as bigint (microseconds).`);
383
+ return {
384
+ typeEntries: [`type: 'bigint'`],
385
+ tsType: 'string',
386
+ dbType: 'bigint',
387
+ };
388
+ case 'ipAddress':
389
+ if (dialect === 'postgres') {
390
+ return {
391
+ typeEntries: [`type: 'inet'`],
392
+ tsType: 'string',
393
+ dbType: 'inet',
394
+ };
395
+ }
396
+ return {
397
+ typeEntries: [`type: 'varchar'`, 'length: 45'],
398
+ tsType: 'string',
399
+ dbType: 'varchar',
400
+ };
401
+ case 'hstore':
402
+ if (dialect === 'postgres') {
403
+ return {
404
+ typeEntries: [`type: 'hstore'`],
405
+ tsType: 'Record<string, string | null>',
406
+ dbType: 'hstore',
407
+ };
408
+ }
409
+ context.warnings.push(`${label}: hstore exists only on PostgreSQL; the field was written as ${dialect === 'mysql' ? 'json' : 'simple-json'}.`);
410
+ return {
411
+ typeEntries: [
412
+ `type: '${dialect === 'mysql' ? 'json' : 'simple-json'}'`,
413
+ ],
414
+ tsType: 'Record<string, string | null>',
415
+ dbType: dialect === 'mysql' ? 'json' : 'simple-json',
416
+ };
417
+ case 'range':
418
+ if (dialect === 'postgres') {
419
+ const rangeType = POSTGRES_RANGE_TYPES[field.rangeOf ?? 'int'] ?? 'int4range';
420
+ return {
421
+ typeEntries: [`type: '${rangeType}'`],
422
+ tsType: 'string',
423
+ dbType: rangeType,
424
+ };
425
+ }
426
+ context.warnings.push(`${label}: range columns exist only on PostgreSQL; the field was written as varchar.`);
427
+ return {
428
+ typeEntries: [`type: 'varchar'`],
429
+ tsType: 'string',
430
+ dbType: 'varchar',
431
+ };
432
+ default:
433
+ context.warnings.push(`${label}: unknown field type "${String(field.type)}"; it was written as varchar.`);
434
+ return {
435
+ typeEntries: [`type: 'varchar'`],
436
+ tsType: 'string',
437
+ dbType: 'varchar',
438
+ };
439
+ }
440
+ }
441
+ /** Returns the `default:` option text for a field, or undefined when there is none. */
442
+ function defaultEntryOf(context, label, field) {
443
+ const defaultValue = field.default;
444
+ if (defaultValue === undefined) {
445
+ return undefined;
446
+ }
447
+ const enumDefinition = field.enumName === undefined
448
+ ? undefined
449
+ : context.schema.enums.find((candidate) => candidate.name === field.enumName);
450
+ switch (defaultValue.kind) {
451
+ case 'autoIncrement':
452
+ context.warnings.push(`${label}: an auto-increment default is only supported on primary keys; it was dropped.`);
453
+ return undefined;
454
+ case 'now':
455
+ return `default: () => 'CURRENT_TIMESTAMP'`;
456
+ case 'uuid': {
457
+ const expression = context.dialect === 'postgres'
458
+ ? 'gen_random_uuid()'
459
+ : context.dialect === 'mysql'
460
+ ? '(UUID())'
461
+ : context.dialect === 'sqlserver'
462
+ ? 'NEWID()'
463
+ : undefined;
464
+ if (expression === undefined) {
465
+ context.warnings.push(`${label}: SQLite has no database-side UUID default; the default was dropped.`);
466
+ return undefined;
467
+ }
468
+ return `default: () => ${quote(expression)}`;
469
+ }
470
+ case 'enumValue': {
471
+ const reference = enumDefinition === undefined
472
+ ? undefined
473
+ : enumReference(context, enumDefinition, defaultValue.value);
474
+ if (reference === undefined) {
475
+ context.warnings.push(`${label}: the enum default "${defaultValue.value}" does not match a member of the enum; it was written as a string.`);
476
+ return `default: ${quote(defaultValue.value)}`;
477
+ }
478
+ return `default: ${reference}`;
479
+ }
480
+ case 'literal': {
481
+ if (typeof defaultValue.value === 'string') {
482
+ if (enumDefinition !== undefined) {
483
+ const reference = enumReference(context, enumDefinition, defaultValue.value);
484
+ if (reference !== undefined) {
485
+ return `default: ${reference}`;
486
+ }
487
+ }
488
+ if ((field.arrayDepth ?? 0) > 0) {
489
+ if (defaultValue.value !== '[]') {
490
+ return undefined;
491
+ }
492
+ return context.dialect === 'postgres'
493
+ ? `default: () => ${quote(quote('{}'))}`
494
+ : `default: () => ${quote(quote('[]'))}`;
495
+ }
496
+ if (field.type === 'hstore' && defaultValue.value === '{}') {
497
+ return context.dialect === 'postgres'
498
+ ? `default: () => ${quote(quote(''))}`
499
+ : `default: () => ${quote(quote('{}'))}`;
500
+ }
501
+ if (field.type === 'json') {
502
+ return `default: () => ${quote(quote(defaultValue.value))}`;
503
+ }
504
+ if (field.type === 'bytes') {
505
+ context.warnings.push(`${label}: a literal default on a binary column cannot be represented; it was dropped.`);
506
+ return undefined;
507
+ }
508
+ return `default: ${quote(defaultValue.value)}`;
509
+ }
510
+ return `default: ${String(defaultValue.value)}`;
511
+ }
512
+ default:
513
+ return undefined;
514
+ }
515
+ }
516
+ // ---------------------------------------------------------------------------
517
+ // Members
518
+ // ---------------------------------------------------------------------------
519
+ function isCreatedTimestamp(field) {
520
+ return (field.type === 'dateTime' &&
521
+ !field.isNullable &&
522
+ !field.isAutoUpdated &&
523
+ field.default !== undefined &&
524
+ field.default.kind === 'now' &&
525
+ CREATED_FIELD_PATTERN.test(field.name));
526
+ }
527
+ function isUpdatedTimestamp(field) {
528
+ return field.isAutoUpdated && field.type === 'dateTime' && !field.isNullable;
529
+ }
530
+ function nameEntry(columnName, propName) {
531
+ return columnName === propName ? undefined : `name: ${quote(columnName)}`;
532
+ }
533
+ function compact(entries) {
534
+ return entries.filter((entry) => entry !== undefined);
535
+ }
536
+ function buildFieldMember(context, model, field, propName) {
537
+ const label = `${model.name}.${field.name}`;
538
+ const spec = columnSpecOf(context, label, field);
539
+ if (field.generated !== undefined) {
540
+ context.warnings.push(`${label}: the generated column expression ${field.generated.expression} is Python, not SQL, and has no TypeORM equivalent; ` +
541
+ `the property was written as a regular column.`);
542
+ }
543
+ const nameOption = nameEntry(field.columnName, propName);
544
+ const inCompositeKey = model.compositePrimaryKey?.includes(field.name) ?? false;
545
+ const nullableSuffix = field.isNullable ? ' | null' : '';
546
+ const property = ` ${propName}!: ${spec.tsType}${nullableSuffix};`;
547
+ if (field.isAutoUpdated && !isUpdatedTimestamp(field)) {
548
+ context.warnings.push(`${label}: an auto-updated timestamp must be a non-null date-time column to become @UpdateDateColumn; it was written as a plain column.`);
549
+ }
550
+ if (field.isPrimaryKey || inCompositeKey) {
551
+ const generated = field.default;
552
+ if (field.isPrimaryKey &&
553
+ !inCompositeKey &&
554
+ generated !== undefined &&
555
+ generated.kind === 'autoIncrement' &&
556
+ (field.type === 'int' || field.type === 'bigInt')) {
557
+ // SQLite only allows AUTOINCREMENT on an INTEGER PRIMARY KEY (which is already 64 bits
558
+ // wide and read as a number), so a bigint key there is written as a plain integer.
559
+ const sqliteInteger = context.dialect === 'sqlite' && field.type === 'bigInt';
560
+ const options = compact([
561
+ nameOption,
562
+ field.type === 'bigInt' && !sqliteInteger
563
+ ? `type: 'bigint'`
564
+ : undefined,
565
+ ]);
566
+ return {
567
+ lines: [
568
+ ...decorator(use(context, 'PrimaryGeneratedColumn'), [`'increment'`], options),
569
+ sqliteInteger ? ` ${propName}!: number;` : property,
570
+ ],
571
+ };
572
+ }
573
+ if (field.isPrimaryKey &&
574
+ !inCompositeKey &&
575
+ generated !== undefined &&
576
+ generated.kind === 'uuid' &&
577
+ field.type === 'uuid' &&
578
+ context.dialect !== 'sqlite') {
579
+ return {
580
+ lines: [
581
+ ...decorator(use(context, 'PrimaryGeneratedColumn'), [`'uuid'`], compact([nameOption])),
582
+ property,
583
+ ],
584
+ };
585
+ }
586
+ const entries = compact([
587
+ nameOption,
588
+ ...spec.typeEntries,
589
+ defaultEntryOf(context, label, field),
590
+ ]);
591
+ return {
592
+ lines: [
593
+ ...decorator(use(context, 'PrimaryColumn'), [], entries),
594
+ property,
595
+ ],
596
+ };
597
+ }
598
+ if (isUpdatedTimestamp(field)) {
599
+ return {
600
+ lines: [
601
+ ...decorator(use(context, 'UpdateDateColumn'), [], compact([nameOption, ...spec.typeEntries])),
602
+ property,
603
+ ],
604
+ };
605
+ }
606
+ if (isCreatedTimestamp(field)) {
607
+ return {
608
+ lines: [
609
+ ...decorator(use(context, 'CreateDateColumn'), [], compact([nameOption, ...spec.typeEntries])),
610
+ property,
611
+ ],
612
+ };
613
+ }
614
+ const entries = compact([
615
+ nameOption,
616
+ ...spec.typeEntries,
617
+ field.isNullable ? 'nullable: true' : undefined,
618
+ field.isUnique ? 'unique: true' : undefined,
619
+ defaultEntryOf(context, label, field),
620
+ ]);
621
+ return {
622
+ lines: [...decorator(use(context, 'Column'), [], entries), property],
623
+ };
624
+ }
625
+ /** Resolves the target column of a relation: an explicit to_field, otherwise the primary key. */
626
+ function referencedKey(context, label, target, toField, depth = 0) {
627
+ if (depth > 5) {
628
+ return undefined;
629
+ }
630
+ const targetNames = namesOf(context, target);
631
+ const explicit = toField === undefined
632
+ ? undefined
633
+ : target.fields.find((field) => field.name === toField);
634
+ const column = explicit ?? target.fields.find((field) => field.isPrimaryKey);
635
+ if (column !== undefined) {
636
+ return {
637
+ property: targetNames.fields.get(column.name) ?? column.name,
638
+ spec: columnSpecOf(context, label, column),
639
+ };
640
+ }
641
+ const primaryRelation = target.relations.find((relation) => relation.isPrimaryKey === true);
642
+ if (primaryRelation !== undefined) {
643
+ const chained = findModel(context.schema, primaryRelation.targetModel);
644
+ const chainedKey = chained === undefined
645
+ ? undefined
646
+ : referencedKey(context, label, chained, primaryRelation.toField, depth + 1);
647
+ if (chainedKey !== undefined) {
648
+ return {
649
+ property: targetNames.scalars.get(primaryRelation.name) ?? chainedKey.property,
650
+ spec: chainedKey.spec,
651
+ };
652
+ }
653
+ }
654
+ return undefined;
655
+ }
656
+ /** Column name of the first primary key property of a model, for join tables. */
657
+ function primaryKeyProperty(context, model) {
658
+ const primary = model.fields.find((field) => field.isPrimaryKey);
659
+ return primary === undefined
660
+ ? undefined
661
+ : namesOf(context, model).fields.get(primary.name);
662
+ }
663
+ function buildForwardMembers(context, model, inverseMembers) {
664
+ const members = [];
665
+ const names = namesOf(context, model);
666
+ for (const field of model.fields) {
667
+ members.push(buildFieldMember(context, model, field, names.fields.get(field.name) ?? field.name));
668
+ }
669
+ for (const relation of model.relations) {
670
+ const target = findModel(context.schema, relation.targetModel);
671
+ if (target === undefined) {
672
+ context.warnings.push(`${model.name}.${relation.name}: target model "${relation.targetModel}" does not exist in the schema; the relation was skipped.`);
673
+ continue;
674
+ }
675
+ if (relation.kind === 'manyToMany') {
676
+ addManyToMany(context, model, relation, target, members, inverseMembers);
677
+ }
678
+ else {
679
+ addSingleRelation(context, model, relation, target, members, inverseMembers);
680
+ }
681
+ }
682
+ return members;
683
+ }
684
+ function addInverse(inverseMembers, targetName, member) {
685
+ const existing = inverseMembers.get(targetName) ?? [];
686
+ existing.push(member);
687
+ inverseMembers.set(targetName, existing);
688
+ }
689
+ function addSingleRelation(context, model, relation, target, members, inverseMembers) {
690
+ const label = `${model.name}.${relation.name}`;
691
+ const names = namesOf(context, model);
692
+ const relationProp = names.relations.get(relation.name) ?? relation.name;
693
+ const inverseProp = context.inverseNames.get(label) ?? toSnakeCase(model.name);
694
+ const keyed = isKeyedRelation(model, relation);
695
+ const needsKey = keyed || relation.toField !== undefined;
696
+ const key = needsKey
697
+ ? referencedKey(context, label, target, relation.toField)
698
+ : undefined;
699
+ if (needsKey && key === undefined) {
700
+ context.warnings.push(`${label}: target model "${target.name}" has no single-column primary key to reference; the relation was skipped.`);
701
+ return;
702
+ }
703
+ if (relation.onDelete === 'setNull' && !relation.isNullable) {
704
+ context.warnings.push(`${label}: onDelete SET NULL on a required relation will fail at the database level; review the relation.`);
705
+ }
706
+ const scalarProp = names.scalars.get(relation.name);
707
+ if (keyed && key !== undefined && scalarProp !== undefined) {
708
+ members.push({
709
+ lines: [
710
+ ...decorator(use(context, 'PrimaryColumn'), [], compact([
711
+ `name: ${quote(relation.columnName)}`,
712
+ ...key.spec.typeEntries,
713
+ ])),
714
+ ` ${scalarProp}!: ${key.spec.tsType};`,
715
+ ],
716
+ });
717
+ }
718
+ const decoratorName = relation.kind === 'oneToOne' ? 'OneToOne' : 'ManyToOne';
719
+ const options = [
720
+ `onDelete: ${quote(ON_DELETE_NAMES[relation.onDelete])}`,
721
+ `nullable: ${relation.isNullable ? 'true' : 'false'}`,
722
+ ];
723
+ const joinEntries = compact([
724
+ `name: ${quote(relation.columnName)}`,
725
+ key !== undefined && relation.toField !== undefined
726
+ ? `referencedColumnName: ${quote(key.property)}`
727
+ : undefined,
728
+ ]);
729
+ const relationType = `${use(context, 'Relation')}<${target.name}>${relation.isNullable ? ' | null' : ''}`;
730
+ members.push({
731
+ lines: [
732
+ ...decorator(use(context, decoratorName), [`() => ${target.name}`, `(entity) => entity.${inverseProp}`], options),
733
+ ...decorator(use(context, 'JoinColumn'), [], joinEntries),
734
+ ` ${relationProp}!: ${relationType};`,
735
+ ],
736
+ });
737
+ if (relation.kind === 'oneToOne') {
738
+ addInverse(inverseMembers, target.name, {
739
+ lines: [
740
+ ...decorator(use(context, 'OneToOne'), [`() => ${model.name}`, `(entity) => entity.${relationProp}`], []),
741
+ ` ${inverseProp}!: ${use(context, 'Relation')}<${model.name}> | null;`,
742
+ ],
743
+ });
744
+ }
745
+ else {
746
+ addInverse(inverseMembers, target.name, {
747
+ lines: [
748
+ ...decorator(use(context, 'OneToMany'), [`() => ${model.name}`, `(entity) => entity.${relationProp}`], []),
749
+ ` ${inverseProp}!: ${model.name}[];`,
750
+ ],
751
+ });
752
+ }
753
+ }
754
+ function addManyToMany(context, model, relation, target, members, inverseMembers) {
755
+ const label = `${model.name}.${relation.name}`;
756
+ const names = namesOf(context, model);
757
+ const relationProp = names.relations.get(relation.name) ?? relation.name;
758
+ const inverseProp = context.inverseNames.get(label) ?? `${toSnakeCase(model.name)}_set`;
759
+ const ownerSnake = toSnakeCase(model.name);
760
+ const targetSnake = toSnakeCase(target.name);
761
+ const isSelfReference = model.name === target.name;
762
+ const ownerColumn = `${isSelfReference ? 'from_' : ''}${ownerSnake}_id`;
763
+ const targetColumn = `${isSelfReference ? 'to_' : ''}${targetSnake}_id`;
764
+ const ownerKey = primaryKeyProperty(context, model);
765
+ const targetKey = primaryKeyProperty(context, target);
766
+ const joinColumn = compact([
767
+ `name: ${quote(ownerColumn)}`,
768
+ ownerKey === undefined
769
+ ? undefined
770
+ : `referencedColumnName: ${quote(ownerKey)}`,
771
+ ]);
772
+ const inverseJoinColumn = compact([
773
+ `name: ${quote(targetColumn)}`,
774
+ targetKey === undefined
775
+ ? undefined
776
+ : `referencedColumnName: ${quote(targetKey)}`,
777
+ ]);
778
+ members.push({
779
+ lines: [
780
+ ...decorator(use(context, 'ManyToMany'), [`() => ${target.name}`, `(entity) => entity.${inverseProp}`], []),
781
+ ...decorator(use(context, 'JoinTable'), [], [
782
+ `name: ${quote(`${model.tableName}_${relation.name}`)}`,
783
+ `joinColumn: { ${joinColumn.join(', ')} }`,
784
+ `inverseJoinColumn: { ${inverseJoinColumn.join(', ')} }`,
785
+ ]),
786
+ ` ${relationProp}!: ${target.name}[];`,
787
+ ],
788
+ });
789
+ addInverse(inverseMembers, target.name, {
790
+ lines: [
791
+ ...decorator(use(context, 'ManyToMany'), [`() => ${model.name}`, `(entity) => entity.${relationProp}`], []),
792
+ ` ${inverseProp}!: ${model.name}[];`,
793
+ ],
794
+ });
795
+ }
796
+ // ---------------------------------------------------------------------------
797
+ // Classes
798
+ // ---------------------------------------------------------------------------
799
+ function resolveIndexProperty(context, model, fieldName) {
800
+ const names = namesOf(context, model);
801
+ const relation = model.relations.find((candidate) => candidate.name === fieldName) ??
802
+ model.relations.find((candidate) => candidate.kind !== 'manyToMany' && candidate.columnName === fieldName);
803
+ if (relation !== undefined) {
804
+ return names.relations.get(relation.name) ?? fieldName;
805
+ }
806
+ const field = model.fields.find((candidate) => candidate.name === fieldName) ??
807
+ model.fields.find((candidate) => candidate.columnName === fieldName);
808
+ if (field !== undefined) {
809
+ return names.fields.get(field.name) ?? fieldName;
810
+ }
811
+ context.warnings.push(`${model.name}: an index references "${fieldName}", which is not a field of the model; it was written as-is.`);
812
+ return propertyName(context, fieldName);
813
+ }
814
+ function indexDecorator(context, model, index) {
815
+ const properties = `[${index.fields
816
+ .map((fieldName) => quote(resolveIndexProperty(context, model, fieldName)))
817
+ .join(', ')}]`;
818
+ const args = index.name === undefined ? [properties] : [quote(index.name), properties];
819
+ return decorator(use(context, index.isUnique ? 'Unique' : 'Index'), args, [], '');
820
+ }
821
+ function emitClass(context, model, members) {
822
+ const hasPrimaryKey = model.fields.some((field) => field.isPrimaryKey) ||
823
+ model.relations.some((relation) => relation.isPrimaryKey === true) ||
824
+ (model.compositePrimaryKey !== undefined &&
825
+ model.compositePrimaryKey.length > 0);
826
+ if (!hasPrimaryKey) {
827
+ context.warnings.push(`${model.name}: the model has no primary key; TypeORM requires at least one primary column.`);
828
+ }
829
+ const header = [
830
+ ...decorator(use(context, 'Entity'), [quote(model.tableName)], [], ''),
831
+ ];
832
+ for (const index of model.indexes) {
833
+ header.push(...indexDecorator(context, model, index));
834
+ }
835
+ const body = members.flatMap((member, position) => position === 0 ? member.lines : ['', ...member.lines]);
836
+ return [...header, `export class ${model.name} {`, ...body, '}'].join('\n');
837
+ }
838
+ //# sourceMappingURL=typeorm.js.map