ormbridge 0.3.0 → 0.5.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 (58) hide show
  1. package/README.md +239 -37
  2. package/dist/cliRunner.js +14 -1
  3. package/dist/cliRunner.js.map +1 -1
  4. package/dist/config.d.ts +3 -0
  5. package/dist/config.js +9 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/emitters/drizzle.d.ts +19 -0
  8. package/dist/emitters/drizzle.js +1337 -0
  9. package/dist/emitters/drizzle.js.map +1 -0
  10. package/dist/emitters/jsonSchema.d.ts +20 -0
  11. package/dist/emitters/jsonSchema.js +503 -0
  12. package/dist/emitters/jsonSchema.js.map +1 -0
  13. package/dist/emitters/sqlDdl.d.ts +29 -0
  14. package/dist/emitters/sqlDdl.js +1741 -0
  15. package/dist/emitters/sqlDdl.js.map +1 -0
  16. package/dist/emitters/sqlalchemy.d.ts +30 -0
  17. package/dist/emitters/sqlalchemy.js +1725 -0
  18. package/dist/emitters/sqlalchemy.js.map +1 -0
  19. package/dist/emitters/typeorm.js +53 -1
  20. package/dist/emitters/typeorm.js.map +1 -1
  21. package/dist/emitters/zod.d.ts +40 -0
  22. package/dist/emitters/zod.js +498 -0
  23. package/dist/emitters/zod.js.map +1 -0
  24. package/dist/formats.d.ts +11 -1
  25. package/dist/formats.js +84 -0
  26. package/dist/formats.js.map +1 -1
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/io.js +82 -8
  31. package/dist/io.js.map +1 -1
  32. package/dist/parsers/drizzle.d.ts +12 -0
  33. package/dist/parsers/drizzle.js +2040 -0
  34. package/dist/parsers/drizzle.js.map +1 -0
  35. package/dist/parsers/jsonSchema.d.ts +21 -0
  36. package/dist/parsers/jsonSchema.js +1432 -0
  37. package/dist/parsers/jsonSchema.js.map +1 -0
  38. package/dist/parsers/pythonSyntax.d.ts +7 -1
  39. package/dist/parsers/pythonSyntax.js +47 -8
  40. package/dist/parsers/pythonSyntax.js.map +1 -1
  41. package/dist/parsers/sqlDdl.d.ts +18 -0
  42. package/dist/parsers/sqlDdl.js +2302 -0
  43. package/dist/parsers/sqlDdl.js.map +1 -0
  44. package/dist/parsers/sqlSyntax.d.ts +79 -0
  45. package/dist/parsers/sqlSyntax.js +698 -0
  46. package/dist/parsers/sqlSyntax.js.map +1 -0
  47. package/dist/parsers/sqlTypes.d.ts +53 -0
  48. package/dist/parsers/sqlTypes.js +742 -0
  49. package/dist/parsers/sqlTypes.js.map +1 -0
  50. package/dist/parsers/sqlalchemy.d.ts +12 -0
  51. package/dist/parsers/sqlalchemy.js +3191 -0
  52. package/dist/parsers/sqlalchemy.js.map +1 -0
  53. package/dist/parsers/typeorm.js +22 -7
  54. package/dist/parsers/typeorm.js.map +1 -1
  55. package/dist/prismaOnlyConstructs.d.ts +2 -2
  56. package/dist/prismaOnlyConstructs.js +5 -5
  57. package/dist/prismaOnlyConstructs.js.map +1 -1
  58. package/package.json +2 -1
@@ -0,0 +1,1337 @@
1
+ import { findModel } from '../ir.js';
2
+ import { limitIdentifier, toCamelCase, toPascalCase, toSnakeCase, } from '../naming.js';
3
+ import { prismaOnlyWarnings } from '../prismaOnlyConstructs.js';
4
+ import { expandManyToMany, normalizeSchema, } from '../transforms.js';
5
+ /** The generated Drizzle schema file. */
6
+ export const DRIZZLE_SCHEMA_FILE = 'schema.ts';
7
+ /** The generated drizzle-kit configuration, so `npx drizzle-kit generate` works next to the schema. */
8
+ export const DRIZZLE_CONFIG_FILE = 'drizzle.config.ts';
9
+ const DIALECTS = {
10
+ pg: {
11
+ module: 'drizzle-orm/pg-core',
12
+ tableFunction: 'pgTable',
13
+ anyColumnType: 'AnyPgColumn',
14
+ kitDialect: 'postgresql',
15
+ },
16
+ mysql: {
17
+ module: 'drizzle-orm/mysql-core',
18
+ tableFunction: 'mysqlTable',
19
+ anyColumnType: 'AnyMySqlColumn',
20
+ kitDialect: 'mysql',
21
+ },
22
+ sqlite: {
23
+ module: 'drizzle-orm/sqlite-core',
24
+ tableFunction: 'sqliteTable',
25
+ anyColumnType: 'AnySQLiteColumn',
26
+ kitDialect: 'sqlite',
27
+ },
28
+ };
29
+ const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
30
+ const MAX_LINE_WIDTH = 80;
31
+ const MAX_INDEX_NAME_LENGTH = 63;
32
+ /** Names the generated file declares or imports, plus words that cannot be variable names. */
33
+ const RESERVED_NAMES = [
34
+ 'AnyMySqlColumn',
35
+ 'AnyPgColumn',
36
+ 'AnySQLiteColumn',
37
+ 'Buffer',
38
+ 'Date',
39
+ 'JSON',
40
+ 'Object',
41
+ 'String',
42
+ 'bigint',
43
+ 'bigserial',
44
+ 'blob',
45
+ 'boolean',
46
+ 'bytea',
47
+ 'char',
48
+ 'cidr',
49
+ 'crypto',
50
+ 'customType',
51
+ 'date',
52
+ 'datetime',
53
+ 'decimal',
54
+ 'defineConfig',
55
+ 'double',
56
+ 'doublePrecision',
57
+ 'hstore',
58
+ 'inet',
59
+ 'index',
60
+ 'int',
61
+ 'int4range',
62
+ 'int8range',
63
+ 'integer',
64
+ 'interval',
65
+ 'json',
66
+ 'jsonb',
67
+ 'many',
68
+ 'mysqlEnum',
69
+ 'mysqlTable',
70
+ 'numeric',
71
+ 'numrange',
72
+ 'one',
73
+ 'pgEnum',
74
+ 'pgTable',
75
+ 'primaryKey',
76
+ 'real',
77
+ 'relations',
78
+ 'serial',
79
+ 'sql',
80
+ 'sqliteTable',
81
+ 'table',
82
+ 'text',
83
+ 'time',
84
+ 'timestamp',
85
+ 'tstzrange',
86
+ 'daterange',
87
+ 'unique',
88
+ 'uniqueIndex',
89
+ 'uuid',
90
+ 'varchar',
91
+ // JavaScript reserved words.
92
+ 'await',
93
+ 'break',
94
+ 'case',
95
+ 'catch',
96
+ 'class',
97
+ 'const',
98
+ 'continue',
99
+ 'debugger',
100
+ 'default',
101
+ 'delete',
102
+ 'do',
103
+ 'else',
104
+ 'enum',
105
+ 'export',
106
+ 'extends',
107
+ 'false',
108
+ 'finally',
109
+ 'for',
110
+ 'function',
111
+ 'if',
112
+ 'implements',
113
+ 'import',
114
+ 'in',
115
+ 'instanceof',
116
+ 'interface',
117
+ 'let',
118
+ 'new',
119
+ 'null',
120
+ 'package',
121
+ 'private',
122
+ 'protected',
123
+ 'public',
124
+ 'return',
125
+ 'static',
126
+ 'super',
127
+ 'switch',
128
+ 'this',
129
+ 'throw',
130
+ 'true',
131
+ 'try',
132
+ 'typeof',
133
+ 'var',
134
+ 'void',
135
+ 'while',
136
+ 'with',
137
+ 'yield',
138
+ ];
139
+ const REFERENTIAL_ACTIONS = {
140
+ cascade: 'cascade',
141
+ setNull: 'set null',
142
+ restrict: 'restrict',
143
+ // The database default; Drizzle only needs the option for the other actions.
144
+ noAction: undefined,
145
+ setDefault: 'set default',
146
+ };
147
+ const POSTGRES_RANGE_TYPES = {
148
+ int: 'int4range',
149
+ bigInt: 'int8range',
150
+ decimal: 'numrange',
151
+ date: 'daterange',
152
+ dateTime: 'tstzrange',
153
+ };
154
+ // ---------------------------------------------------------------------------
155
+ // Entry point
156
+ // ---------------------------------------------------------------------------
157
+ export function emitDrizzle(schema, options) {
158
+ // Drizzle has no many-to-many field: the join table is an ordinary table.
159
+ const expanded = expandManyToMany(schema);
160
+ const prepared = options.naming === 'normalize' ? normalizeSchema(expanded) : expanded;
161
+ const dialect = dialectOf(options.provider);
162
+ const context = {
163
+ schema: prepared,
164
+ options,
165
+ dialect,
166
+ // Drizzle writes referential actions on update, so that Prisma-only note does not apply.
167
+ warnings: prismaOnlyWarnings(prepared).filter((warning) => !warning.includes('the onUpdate action')),
168
+ imports: new Set(),
169
+ typeImports: new Set(),
170
+ coreImports: new Set(),
171
+ helpers: new Set(),
172
+ tables: new Map(),
173
+ enums: new Map(),
174
+ reserved: new Set(RESERVED_NAMES),
175
+ indexNames: new Set(),
176
+ ambiguousPairs: new Set(),
177
+ inverseKeys: new Map(),
178
+ };
179
+ warnAboutProvider(context);
180
+ allocateEnums(context);
181
+ allocateTables(context);
182
+ allocateRelationKeys(context);
183
+ const ordered = orderTables(context);
184
+ const blocks = [];
185
+ for (const info of context.enums.values()) {
186
+ blocks.push(renderEnum(context, info));
187
+ }
188
+ for (const info of ordered) {
189
+ blocks.push(renderTable(context, info));
190
+ }
191
+ // Relations come last, after every table has settled which of its relations could be written.
192
+ for (const info of ordered) {
193
+ const relationsBlock = renderRelations(context, info);
194
+ if (relationsBlock !== undefined) {
195
+ blocks.push(relationsBlock);
196
+ }
197
+ }
198
+ const helperBlocks = [...context.helpers]
199
+ .sort()
200
+ .map((name) => helperDeclaration(name));
201
+ const importLines = buildImports(context);
202
+ const body = [...helperBlocks, ...blocks];
203
+ const text = body.length === 0
204
+ ? 'export {};\n'
205
+ : `${[importLines.join('\n'), ...body].join('\n\n')}\n`;
206
+ return {
207
+ files: {
208
+ [DRIZZLE_SCHEMA_FILE]: text,
209
+ [DRIZZLE_CONFIG_FILE]: renderConfig(dialect),
210
+ },
211
+ warnings: context.warnings,
212
+ };
213
+ }
214
+ function dialectOf(provider) {
215
+ switch (provider) {
216
+ case 'mysql':
217
+ return 'mysql';
218
+ case 'sqlite':
219
+ return 'sqlite';
220
+ default:
221
+ // CockroachDB speaks the PostgreSQL wire protocol and uses the same drizzle-kit dialect.
222
+ return 'pg';
223
+ }
224
+ }
225
+ function warnAboutProvider(context) {
226
+ const provider = context.options.provider;
227
+ if (provider === 'sqlserver') {
228
+ context.warnings.push('Provider "sqlserver": Drizzle ORM has no SQL Server dialect; PostgreSQL tables (pg-core) were written.');
229
+ }
230
+ else if (provider === 'mongodb') {
231
+ context.warnings.push('Provider "mongodb": Drizzle ORM is relational (it has no MongoDB dialect); PostgreSQL tables (pg-core) were written.');
232
+ }
233
+ }
234
+ // ---------------------------------------------------------------------------
235
+ // Small helpers
236
+ // ---------------------------------------------------------------------------
237
+ function quote(value) {
238
+ const escaped = value
239
+ .replace(/\\/g, '\\\\')
240
+ .replace(/\n/g, '\\n')
241
+ .replace(/\r/g, '\\r');
242
+ if (value.includes("'") && !value.includes('"')) {
243
+ return `"${escaped}"`;
244
+ }
245
+ return `'${escaped.replace(/'/g, "\\'")}'`;
246
+ }
247
+ /** Writes a JSON value as a JavaScript literal with single-quoted strings. */
248
+ function literalOf(value) {
249
+ if (value === null) {
250
+ return 'null';
251
+ }
252
+ if (typeof value === 'string') {
253
+ return quote(value);
254
+ }
255
+ if (typeof value === 'number' || typeof value === 'boolean') {
256
+ return String(value);
257
+ }
258
+ if (Array.isArray(value)) {
259
+ return `[${value.map((item) => literalOf(item)).join(', ')}]`;
260
+ }
261
+ if (typeof value === 'object') {
262
+ const entries = Object.entries(value).map(([key, item]) => `${IDENTIFIER_PATTERN.test(key) ? key : quote(key)}: ${literalOf(item)}`);
263
+ return entries.length === 0 ? '{}' : `{ ${entries.join(', ')} }`;
264
+ }
265
+ return 'undefined';
266
+ }
267
+ function uniqueName(baseName, used) {
268
+ let candidate = baseName;
269
+ let suffix = 2;
270
+ while (used.has(candidate)) {
271
+ candidate = `${baseName}${suffix}`;
272
+ suffix += 1;
273
+ }
274
+ used.add(candidate);
275
+ return candidate;
276
+ }
277
+ /** camelCase key for a name of the source; invalid TypeScript identifiers are repaired with a warning. */
278
+ function keyOf(context, owner, raw) {
279
+ const camel = toCamelCase(raw);
280
+ if (IDENTIFIER_PATTERN.test(camel)) {
281
+ return camel;
282
+ }
283
+ const replaced = camel.replace(/[^A-Za-z0-9_$]/g, '_');
284
+ const safe = /^[0-9]/.test(replaced) ? `_${replaced}` : replaced;
285
+ const result = safe === '' ? '_' : safe;
286
+ context.warnings.push(`${owner}: "${raw}" cannot be turned into a valid TypeScript identifier; it was written as "${result}".`);
287
+ return result;
288
+ }
289
+ function use(context, name) {
290
+ context.imports.add(name);
291
+ return name;
292
+ }
293
+ function useCore(context, name) {
294
+ context.coreImports.add(name);
295
+ return name;
296
+ }
297
+ function pluralize(word) {
298
+ if (/(s|x|z|ch|sh)$/i.test(word)) {
299
+ return `${word}es`;
300
+ }
301
+ if (/[^aeiou]y$/i.test(word)) {
302
+ return `${word.slice(0, -1)}ies`;
303
+ }
304
+ return `${word}s`;
305
+ }
306
+ function compareText(first, second) {
307
+ return first.localeCompare(second);
308
+ }
309
+ function isManyToMany(relation) {
310
+ return relation.kind === 'manyToMany';
311
+ }
312
+ function isKeyedRelation(model, relation) {
313
+ return (relation.isPrimaryKey === true ||
314
+ (model.compositePrimaryKey?.includes(relation.name) ?? false));
315
+ }
316
+ function pairKey(first, second) {
317
+ return first <= second
318
+ ? `${first}\u0000${second}`
319
+ : `${second}\u0000${first}`;
320
+ }
321
+ function tableOf(context, modelName) {
322
+ return context.tables.get(modelName);
323
+ }
324
+ // ---------------------------------------------------------------------------
325
+ // Names
326
+ // ---------------------------------------------------------------------------
327
+ function allocateEnums(context) {
328
+ for (const definition of context.schema.enums) {
329
+ if (definition.values.length === 0) {
330
+ context.warnings.push(`enum ${definition.name}: an enum without values cannot be written; fields that use it became plain strings.`);
331
+ continue;
332
+ }
333
+ const base = keyOf(context, `enum ${definition.name}`, definition.name);
334
+ const dbName = definition.dbName ?? definition.name;
335
+ context.enums.set(definition.name, {
336
+ definition,
337
+ valuesName: uniqueName(`${base}Values`, context.reserved),
338
+ enumName: uniqueName(`${base}Enum`, context.reserved),
339
+ typeName: uniqueName(toPascalCase(definition.name) || 'Enum', context.reserved),
340
+ dbName: context.options.naming === 'normalize' ? toSnakeCase(dbName) : dbName,
341
+ });
342
+ }
343
+ }
344
+ function allocateTables(context) {
345
+ for (const model of context.schema.models) {
346
+ const base = keyOf(context, model.name, model.name);
347
+ const variable = uniqueName(base, context.reserved);
348
+ const typeName = uniqueName(toPascalCase(model.name) || 'Model', context.reserved);
349
+ const info = {
350
+ model,
351
+ variable,
352
+ relationsVariable: uniqueName(`${variable}Relations`, context.reserved),
353
+ typeName,
354
+ insertTypeName: uniqueName(`New${typeName}`, context.reserved),
355
+ fieldKeys: new Map(),
356
+ columnKeys: new Map(),
357
+ relationKeys: new Map(),
358
+ used: new Set(),
359
+ position: 0,
360
+ };
361
+ for (const field of model.fields) {
362
+ info.fieldKeys.set(field.name, uniqueName(keyOf(context, `${model.name}.${field.name}`, field.name), info.used));
363
+ }
364
+ for (const relation of model.relations) {
365
+ if (isManyToMany(relation)) {
366
+ continue;
367
+ }
368
+ info.columnKeys.set(relation.name, uniqueName(keyOf(context, `${model.name}.${relation.name}`, relation.columnName), info.used));
369
+ }
370
+ context.tables.set(model.name, info);
371
+ }
372
+ }
373
+ function defaultInverseName(model, relation) {
374
+ const owner = toCamelCase(model.name);
375
+ if (model.isJoinTable === true) {
376
+ // The join table's own name says what the rows are; the relation names only tell the sides apart.
377
+ const sameTarget = model.relations.filter((candidate) => candidate.targetModel === relation.targetModel).length;
378
+ return sameTarget > 1 ? `${owner}${toPascalCase(relation.name)}` : owner;
379
+ }
380
+ if (relation.relatedName !== undefined) {
381
+ return toCamelCase(relation.relatedName);
382
+ }
383
+ return relation.kind === 'oneToOne' ? owner : pluralize(owner);
384
+ }
385
+ /** Allocates the keys of forward relations and of the inverse side on the target table. */
386
+ function allocateRelationKeys(context) {
387
+ const counts = new Map();
388
+ for (const model of context.schema.models) {
389
+ for (const relation of model.relations) {
390
+ if (isManyToMany(relation)) {
391
+ continue;
392
+ }
393
+ const key = pairKey(model.name, relation.targetModel);
394
+ counts.set(key, (counts.get(key) ?? 0) + 1);
395
+ }
396
+ }
397
+ for (const [key, count] of counts) {
398
+ const [first, second] = key.split('\u0000');
399
+ if (count > 1 || first === second) {
400
+ context.ambiguousPairs.add(key);
401
+ }
402
+ }
403
+ for (const model of context.schema.models) {
404
+ const info = tableOf(context, model.name);
405
+ if (info === undefined) {
406
+ continue;
407
+ }
408
+ for (const relation of model.relations) {
409
+ if (isManyToMany(relation)) {
410
+ continue;
411
+ }
412
+ info.relationKeys.set(relation.name, uniqueName(keyOf(context, `${model.name}.${relation.name}`, relation.name), info.used));
413
+ }
414
+ }
415
+ }
416
+ /** Key of the inverse side of a relation on its target table (allocated lazily, once). */
417
+ function inverseKeyOf(context, inverseKeys, model, relation, target) {
418
+ const cacheKey = `${model.name}.${relation.name}`;
419
+ const cached = inverseKeys.get(cacheKey);
420
+ if (cached !== undefined) {
421
+ return cached;
422
+ }
423
+ const raw = defaultInverseName(model, relation);
424
+ const safe = IDENTIFIER_PATTERN.test(raw)
425
+ ? raw
426
+ : keyOf(context, cacheKey, raw);
427
+ const allocated = uniqueName(safe, target.used);
428
+ inverseKeys.set(cacheKey, allocated);
429
+ return allocated;
430
+ }
431
+ // ---------------------------------------------------------------------------
432
+ // Ordering
433
+ // ---------------------------------------------------------------------------
434
+ /**
435
+ * Orders the tables so that a table comes after the tables its foreign keys point at. A cycle (or a
436
+ * self reference) cannot be ordered; those references are annotated with `AnyPgColumn` and friends,
437
+ * which keeps TypeScript from reporting an implicitly typed circular initializer.
438
+ */
439
+ function orderTables(context) {
440
+ const ordered = [];
441
+ const done = new Set();
442
+ const visiting = new Set();
443
+ const visit = (model) => {
444
+ if (done.has(model.name) || visiting.has(model.name)) {
445
+ return;
446
+ }
447
+ visiting.add(model.name);
448
+ for (const relation of model.relations) {
449
+ if (isManyToMany(relation)) {
450
+ continue;
451
+ }
452
+ const target = findModel(context.schema, relation.targetModel);
453
+ if (target !== undefined) {
454
+ visit(target);
455
+ }
456
+ }
457
+ visiting.delete(model.name);
458
+ done.add(model.name);
459
+ const info = tableOf(context, model.name);
460
+ if (info !== undefined) {
461
+ info.position = ordered.length;
462
+ ordered.push(info);
463
+ }
464
+ };
465
+ for (const model of context.schema.models) {
466
+ visit(model);
467
+ }
468
+ return ordered;
469
+ }
470
+ // ---------------------------------------------------------------------------
471
+ // Enums
472
+ // ---------------------------------------------------------------------------
473
+ function enumOf(context, field) {
474
+ return field.enumName === undefined
475
+ ? undefined
476
+ : context.enums.get(field.enumName);
477
+ }
478
+ function renderEnum(context, info) {
479
+ const values = info.definition.values
480
+ .map((value) => quote(value.dbValue))
481
+ .join(', ');
482
+ const lines = [];
483
+ const labelled = info.definition.values.filter((value) => value.label !== undefined);
484
+ if (labelled.length > 0) {
485
+ lines.push(`// ${info.definition.name}: ${info.definition.values
486
+ .map((value) => `${value.dbValue}${value.label === undefined ? '' : ` = ${value.label}`}`)
487
+ .join(', ')}`);
488
+ }
489
+ const declaration = `export const ${info.valuesName} = [${values}] as const;`;
490
+ lines.push(declaration.length <= MAX_LINE_WIDTH
491
+ ? declaration
492
+ : [
493
+ `export const ${info.valuesName} = [`,
494
+ ...info.definition.values.map((value) => ` ${quote(value.dbValue)},`),
495
+ '] as const;',
496
+ ].join('\n'), `export type ${info.typeName} = (typeof ${info.valuesName})[number];`);
497
+ if (context.dialect === 'pg') {
498
+ lines.push(`export const ${info.enumName} = ${use(context, 'pgEnum')}(${quote(info.dbName)}, ${info.valuesName});`);
499
+ }
500
+ return lines.join('\n');
501
+ }
502
+ /** The stored value an enum default refers to, by member name or stored value. */
503
+ function enumDefaultValue(info, raw) {
504
+ const value = info.definition.values.find((candidate) => candidate.name === raw) ??
505
+ info.definition.values.find((candidate) => candidate.dbValue === raw);
506
+ return value?.dbValue;
507
+ }
508
+ // ---------------------------------------------------------------------------
509
+ // Column builders
510
+ // ---------------------------------------------------------------------------
511
+ function call(name, args) {
512
+ return `${name}(${args.join(', ')})`;
513
+ }
514
+ function configObject(entries) {
515
+ return entries.length === 0 ? undefined : `{ ${entries.join(', ')} }`;
516
+ }
517
+ function builderCall(context, name, columnName, entries = []) {
518
+ const config = configObject(entries);
519
+ return call(use(context, name), config === undefined ? [quote(columnName)] : [quote(columnName), config]);
520
+ }
521
+ /** Registers a `customType` helper and returns its name. */
522
+ function helperType(context, name) {
523
+ use(context, 'customType');
524
+ context.helpers.add(name);
525
+ return name;
526
+ }
527
+ function helperDeclaration(name) {
528
+ switch (name) {
529
+ case 'bytea':
530
+ return [
531
+ `const bytea = customType<{ data: Buffer; driverData: Buffer }>({`,
532
+ ` dataType() {`,
533
+ ` return 'bytea';`,
534
+ ` },`,
535
+ `});`,
536
+ ].join('\n');
537
+ case 'blob':
538
+ return [
539
+ `const blob = customType<{ data: Buffer; driverData: Buffer }>({`,
540
+ ` dataType() {`,
541
+ ` return 'blob';`,
542
+ ` },`,
543
+ `});`,
544
+ ].join('\n');
545
+ case 'hstore':
546
+ return [
547
+ `const hstore = customType<{ data: string; driverData: string }>({`,
548
+ ` dataType() {`,
549
+ ` return 'hstore';`,
550
+ ` },`,
551
+ `});`,
552
+ ].join('\n');
553
+ default:
554
+ return [
555
+ `const ${name} = customType<{ data: string; driverData: string }>({`,
556
+ ` dataType() {`,
557
+ ` return '${name}';`,
558
+ ` },`,
559
+ `});`,
560
+ ].join('\n');
561
+ }
562
+ }
563
+ /** The element type of a field as TypeScript sees it, for `.$type<...>()` on JSON-backed arrays. */
564
+ function tsTypeOf(context, field) {
565
+ const enumInfo = enumOf(context, field);
566
+ if (enumInfo !== undefined) {
567
+ return enumInfo.typeName;
568
+ }
569
+ switch (field.type) {
570
+ case 'int':
571
+ case 'bigInt':
572
+ case 'float':
573
+ return 'number';
574
+ case 'boolean':
575
+ return 'boolean';
576
+ case 'dateTime':
577
+ return 'Date';
578
+ case 'json':
579
+ return 'unknown';
580
+ default:
581
+ return 'string';
582
+ }
583
+ }
584
+ /** The builder call of a column's type (no constraints or defaults). */
585
+ function columnBuilder(context, label, field, columnName) {
586
+ const depth = field.arrayDepth ?? 0;
587
+ const base = scalarBuilder(context, label, { ...field, arrayDepth: undefined }, columnName);
588
+ if (depth === 0) {
589
+ return { builder: base };
590
+ }
591
+ if (context.dialect === 'pg') {
592
+ return { builder: `${base}${'.array()'.repeat(depth)}` };
593
+ }
594
+ const jsonType = context.dialect === 'mysql' ? 'json' : 'text';
595
+ context.warnings.push(`${label}: ${context.dialect === 'mysql' ? 'MySQL' : 'SQLite'} has no array column type; the array was written as ${jsonType === 'json' ? 'json' : 'a JSON text column'}.`);
596
+ const element = tsTypeOf(context, field);
597
+ const typeText = `${/[\s|]/.test(element) ? `(${element})` : element}${'[]'.repeat(depth)}`;
598
+ return {
599
+ builder: `${jsonType === 'json'
600
+ ? builderCall(context, 'json', columnName)
601
+ : builderCall(context, 'text', columnName, [`mode: 'json'`])}.$type<${typeText}>()`,
602
+ };
603
+ }
604
+ function scalarBuilder(context, label, field, columnName) {
605
+ const dialect = context.dialect;
606
+ const enumInfo = enumOf(context, field);
607
+ if (field.enumName !== undefined && enumInfo === undefined) {
608
+ context.warnings.push(`${label}: enum "${field.enumName}" does not exist in the schema; the column was written as a plain string.`);
609
+ }
610
+ if (enumInfo !== undefined) {
611
+ if (dialect === 'pg') {
612
+ return call(enumInfo.enumName, [quote(columnName)]);
613
+ }
614
+ if (dialect === 'mysql') {
615
+ return call(use(context, 'mysqlEnum'), [
616
+ quote(columnName),
617
+ enumInfo.valuesName,
618
+ ]);
619
+ }
620
+ return builderCall(context, 'text', columnName, [
621
+ `enum: ${enumInfo.valuesName}`,
622
+ ]);
623
+ }
624
+ switch (field.type) {
625
+ case 'string':
626
+ case 'unsupported': {
627
+ if (dialect === 'sqlite') {
628
+ return builderCall(context, 'text', columnName);
629
+ }
630
+ if (field.maxLength !== undefined) {
631
+ return builderCall(context, 'varchar', columnName, [
632
+ `length: ${field.maxLength}`,
633
+ ]);
634
+ }
635
+ // MySQL cannot index or key an unbounded text column and varchar requires a length.
636
+ return dialect === 'mysql'
637
+ ? builderCall(context, 'varchar', columnName, ['length: 255'])
638
+ : builderCall(context, 'text', columnName);
639
+ }
640
+ case 'text':
641
+ return builderCall(context, 'text', columnName);
642
+ case 'uuid':
643
+ if (dialect === 'pg') {
644
+ return builderCall(context, 'uuid', columnName);
645
+ }
646
+ return dialect === 'mysql'
647
+ ? builderCall(context, 'char', columnName, ['length: 36'])
648
+ : builderCall(context, 'text', columnName);
649
+ case 'int':
650
+ return dialect === 'mysql'
651
+ ? builderCall(context, 'int', columnName)
652
+ : builderCall(context, 'integer', columnName);
653
+ case 'bigInt':
654
+ return dialect === 'sqlite'
655
+ ? builderCall(context, 'integer', columnName)
656
+ : builderCall(context, 'bigint', columnName, [`mode: 'number'`]);
657
+ case 'float':
658
+ if (dialect === 'pg') {
659
+ return builderCall(context, 'doublePrecision', columnName);
660
+ }
661
+ return dialect === 'mysql'
662
+ ? builderCall(context, 'double', columnName)
663
+ : builderCall(context, 'real', columnName);
664
+ case 'decimal': {
665
+ const entries = [];
666
+ if (field.maxDigits !== undefined) {
667
+ entries.push(`precision: ${field.maxDigits}`);
668
+ }
669
+ if (field.decimalPlaces !== undefined) {
670
+ entries.push(`scale: ${field.decimalPlaces}`);
671
+ }
672
+ if (dialect === 'sqlite') {
673
+ return builderCall(context, 'numeric', columnName);
674
+ }
675
+ return builderCall(context, dialect === 'pg' ? 'numeric' : 'decimal', columnName, entries);
676
+ }
677
+ case 'boolean':
678
+ return dialect === 'sqlite'
679
+ ? builderCall(context, 'integer', columnName, [`mode: 'boolean'`])
680
+ : builderCall(context, 'boolean', columnName);
681
+ case 'dateTime':
682
+ if (dialect === 'pg') {
683
+ return builderCall(context, 'timestamp', columnName, [
684
+ 'withTimezone: true',
685
+ ]);
686
+ }
687
+ return dialect === 'mysql'
688
+ ? builderCall(context, 'datetime', columnName)
689
+ : builderCall(context, 'integer', columnName, [`mode: 'timestamp'`]);
690
+ case 'date':
691
+ return dialect === 'sqlite'
692
+ ? builderCall(context, 'text', columnName)
693
+ : builderCall(context, 'date', columnName, [`mode: 'string'`]);
694
+ case 'time':
695
+ return dialect === 'sqlite'
696
+ ? builderCall(context, 'text', columnName)
697
+ : builderCall(context, 'time', columnName);
698
+ case 'json':
699
+ if (dialect === 'pg') {
700
+ return builderCall(context, 'jsonb', columnName);
701
+ }
702
+ return dialect === 'mysql'
703
+ ? builderCall(context, 'json', columnName)
704
+ : builderCall(context, 'text', columnName, [`mode: 'json'`]);
705
+ case 'bytes':
706
+ if (dialect === 'pg') {
707
+ return call(helperType(context, 'bytea'), [quote(columnName)]);
708
+ }
709
+ return dialect === 'mysql'
710
+ ? call(helperType(context, 'blob'), [quote(columnName)])
711
+ : builderCall(context, 'blob', columnName, [`mode: 'buffer'`]);
712
+ case 'duration':
713
+ if (dialect === 'pg') {
714
+ return builderCall(context, 'interval', columnName);
715
+ }
716
+ context.warnings.push(`${label}: ${dialect === 'mysql' ? 'MySQL' : 'SQLite'} has no interval type; the duration was written as an integer (microseconds).`);
717
+ return dialect === 'mysql'
718
+ ? builderCall(context, 'bigint', columnName, [`mode: 'number'`])
719
+ : builderCall(context, 'integer', columnName);
720
+ case 'ipAddress':
721
+ if (dialect === 'pg') {
722
+ return builderCall(context, 'inet', columnName);
723
+ }
724
+ return dialect === 'mysql'
725
+ ? builderCall(context, 'varchar', columnName, ['length: 45'])
726
+ : builderCall(context, 'text', columnName);
727
+ case 'hstore':
728
+ if (dialect === 'pg') {
729
+ return call(helperType(context, 'hstore'), [quote(columnName)]);
730
+ }
731
+ context.warnings.push(`${label}: hstore exists only on PostgreSQL; the field was written as JSON.`);
732
+ return `${dialect === 'mysql'
733
+ ? builderCall(context, 'json', columnName)
734
+ : builderCall(context, 'text', columnName, [`mode: 'json'`])}.$type<Record<string, string | null>>()`;
735
+ case 'range':
736
+ if (dialect === 'pg') {
737
+ const rangeType = POSTGRES_RANGE_TYPES[field.rangeOf ?? 'int'] ?? 'int4range';
738
+ return call(helperType(context, rangeType), [quote(columnName)]);
739
+ }
740
+ context.warnings.push(`${label}: range columns exist only on PostgreSQL; the field was written as text.`);
741
+ return dialect === 'mysql'
742
+ ? builderCall(context, 'varchar', columnName, ['length: 255'])
743
+ : builderCall(context, 'text', columnName);
744
+ default:
745
+ context.warnings.push(`${label}: unknown field type "${String(field.type)}"; it was written as text.`);
746
+ return builderCall(context, 'text', columnName);
747
+ }
748
+ }
749
+ // ---------------------------------------------------------------------------
750
+ // Defaults
751
+ // ---------------------------------------------------------------------------
752
+ function sqlTemplate(context, expression) {
753
+ useCore(context, 'sql');
754
+ return `sql\`${expression.replace(/\\/g, '\\\\').replace(/`/g, '\\`')}\``;
755
+ }
756
+ function parseJson(text) {
757
+ try {
758
+ return { ok: true, value: JSON.parse(text) };
759
+ }
760
+ catch {
761
+ return { ok: false };
762
+ }
763
+ }
764
+ /** The `.default(...)` / `.defaultNow()` call for a field, or undefined when there is none. */
765
+ function defaultSegment(context, label, field) {
766
+ const value = field.default;
767
+ if (value === undefined) {
768
+ return undefined;
769
+ }
770
+ const dialect = context.dialect;
771
+ const enumInfo = enumOf(context, field);
772
+ switch (value.kind) {
773
+ case 'autoIncrement':
774
+ context.warnings.push(`${label}: an auto-increment default is only supported on a single-column integer primary key; it was dropped.`);
775
+ return undefined;
776
+ case 'now':
777
+ return nowDefault(context, label, field);
778
+ case 'uuid':
779
+ if (field.type !== 'uuid') {
780
+ context.warnings.push(`${label}: a UUID default on a ${field.type} column cannot be written; it was dropped.`);
781
+ return undefined;
782
+ }
783
+ if (dialect === 'pg') {
784
+ return '.defaultRandom()';
785
+ }
786
+ if (dialect === 'mysql') {
787
+ return `.default(${sqlTemplate(context, '(UUID())')})`;
788
+ }
789
+ context.warnings.push(`${label}: SQLite has no database-side UUID default; the UUID is generated by Drizzle ($defaultFn) when a row is inserted through it.`);
790
+ return '.$defaultFn(() => crypto.randomUUID())';
791
+ case 'enumValue': {
792
+ const stored = enumInfo === undefined
793
+ ? undefined
794
+ : enumDefaultValue(enumInfo, value.value);
795
+ if (stored === undefined) {
796
+ context.warnings.push(`${label}: the enum default "${value.value}" does not match a member of the enum; it was written as a string.`);
797
+ }
798
+ return `.default(${quote(stored ?? value.value)})`;
799
+ }
800
+ case 'literal':
801
+ return literalDefault(context, label, field, value.value, enumInfo);
802
+ default:
803
+ // clientGenerated and dbExpression defaults are reported by prismaOnlyWarnings.
804
+ return undefined;
805
+ }
806
+ }
807
+ function nowDefault(context, label, field) {
808
+ const dialect = context.dialect;
809
+ switch (field.type) {
810
+ case 'dateTime':
811
+ if (dialect === 'sqlite') {
812
+ return `.default(${sqlTemplate(context, '(unixepoch())')})`;
813
+ }
814
+ // MySQL's datetime builder has no defaultNow().
815
+ return dialect === 'mysql'
816
+ ? `.default(${sqlTemplate(context, 'CURRENT_TIMESTAMP')})`
817
+ : '.defaultNow()';
818
+ case 'date':
819
+ return `.default(${sqlTemplate(context, dialect === 'pg' ? 'CURRENT_DATE' : '(CURRENT_DATE)')})`;
820
+ case 'time':
821
+ return `.default(${sqlTemplate(context, dialect === 'pg' ? 'CURRENT_TIME' : '(CURRENT_TIME)')})`;
822
+ default:
823
+ context.warnings.push(`${label}: a "now" default on a ${field.type} column cannot be written; it was dropped.`);
824
+ return undefined;
825
+ }
826
+ }
827
+ function literalDefault(context, label, field, value, enumInfo) {
828
+ if ((field.arrayDepth ?? 0) > 0) {
829
+ const parsed = typeof value === 'string' ? parseJson(value) : { ok: false };
830
+ if (parsed.ok && Array.isArray(parsed.value)) {
831
+ return `.default(${literalOf(parsed.value)})`;
832
+ }
833
+ context.warnings.push(`${label}: the array default ${String(value)} cannot be written; it was dropped.`);
834
+ return undefined;
835
+ }
836
+ if (enumInfo !== undefined) {
837
+ const stored = enumDefaultValue(enumInfo, String(value));
838
+ return `.default(${quote(stored ?? String(value))})`;
839
+ }
840
+ const dropped = (reason) => {
841
+ context.warnings.push(`${label}: ${reason}; the default was dropped.`);
842
+ return undefined;
843
+ };
844
+ switch (field.type) {
845
+ case 'int':
846
+ case 'bigInt':
847
+ case 'float':
848
+ case 'duration': {
849
+ if (field.type === 'duration' && context.dialect === 'pg') {
850
+ return `.default(${quote(String(value))})`;
851
+ }
852
+ const numeric = typeof value === 'number' ? value : Number(value);
853
+ return typeof value === 'boolean' || Number.isNaN(numeric)
854
+ ? dropped(`the value ${String(value)} is not a number`)
855
+ : `.default(${numeric})`;
856
+ }
857
+ case 'decimal':
858
+ return `.default(${quote(String(value))})`;
859
+ case 'boolean': {
860
+ if (typeof value === 'boolean') {
861
+ return `.default(${value})`;
862
+ }
863
+ const text = String(value).toLowerCase();
864
+ if (text === 'true' || text === '1') {
865
+ return '.default(true)';
866
+ }
867
+ return text === 'false' || text === '0'
868
+ ? '.default(false)'
869
+ : dropped(`the value ${String(value)} is not a boolean`);
870
+ }
871
+ case 'dateTime': {
872
+ const time = Date.parse(String(value));
873
+ return Number.isNaN(time)
874
+ ? dropped(`the value ${String(value)} is not a date`)
875
+ : `.default(new Date(${quote(String(value))}))`;
876
+ }
877
+ case 'json': {
878
+ if (typeof value !== 'string') {
879
+ return `.default(${literalOf(value)})`;
880
+ }
881
+ const parsed = parseJson(value);
882
+ return parsed.ok
883
+ ? `.default(${literalOf(parsed.value)})`
884
+ : dropped(`the value ${value} is not JSON`);
885
+ }
886
+ case 'bytes':
887
+ return dropped('a literal default on a binary column cannot be represented');
888
+ case 'hstore':
889
+ if (context.dialect === 'pg' && value === '{}') {
890
+ return `.default(${sqlTemplate(context, "''")})`;
891
+ }
892
+ if (context.dialect !== 'pg') {
893
+ const parsed = parseJson(String(value));
894
+ return parsed.ok
895
+ ? `.default(${literalOf(parsed.value)})`
896
+ : dropped(`the value ${String(value)} is not JSON`);
897
+ }
898
+ return dropped('only an empty hstore default can be written');
899
+ case 'range':
900
+ return dropped('a range default cannot be represented');
901
+ default:
902
+ return `.default(${quote(String(value))})`;
903
+ }
904
+ }
905
+ // ---------------------------------------------------------------------------
906
+ // References
907
+ // ---------------------------------------------------------------------------
908
+ /** Resolves the column a relation points at: `toField`, otherwise the target's primary key. */
909
+ function referencedColumn(context, target, toField, depth = 0) {
910
+ const info = tableOf(context, target.name);
911
+ if (info === undefined || depth > 5) {
912
+ return undefined;
913
+ }
914
+ const explicit = toField === undefined
915
+ ? undefined
916
+ : target.fields.find((candidate) => candidate.name === toField);
917
+ const column = explicit ??
918
+ target.fields.find((candidate) => candidate.isPrimaryKey);
919
+ if (column !== undefined) {
920
+ const key = info.fieldKeys.get(column.name);
921
+ return key === undefined ? undefined : { table: info, key, field: column };
922
+ }
923
+ const primaryRelation = target.relations.find((relation) => relation.isPrimaryKey === true);
924
+ if (primaryRelation === undefined) {
925
+ return undefined;
926
+ }
927
+ const chained = findModel(context.schema, primaryRelation.targetModel);
928
+ const chainedColumn = chained === undefined
929
+ ? undefined
930
+ : referencedColumn(context, chained, primaryRelation.toField, depth + 1);
931
+ const key = info.columnKeys.get(primaryRelation.name);
932
+ return chainedColumn === undefined || key === undefined
933
+ ? undefined
934
+ : { table: info, key, field: chainedColumn.field };
935
+ }
936
+ function actionOf(context, label, kind, action) {
937
+ if (action === 'setDefault' && context.dialect === 'mysql') {
938
+ context.warnings.push(`${label}: MySQL (InnoDB) does not support ${kind === 'onDelete' ? 'ON DELETE' : 'ON UPDATE'} SET DEFAULT; the action was left at the default (NO ACTION).`);
939
+ return undefined;
940
+ }
941
+ return REFERENTIAL_ACTIONS[action];
942
+ }
943
+ function referencesSegment(context, label, from, relation, column) {
944
+ const entries = [];
945
+ const onDelete = actionOf(context, label, 'onDelete', relation.onDelete);
946
+ if (onDelete !== undefined) {
947
+ entries.push(`onDelete: ${quote(onDelete)}`);
948
+ }
949
+ if (relation.onUpdate !== undefined) {
950
+ const onUpdate = actionOf(context, label, 'onUpdate', relation.onUpdate);
951
+ if (onUpdate !== undefined) {
952
+ entries.push(`onUpdate: ${quote(onUpdate)}`);
953
+ }
954
+ }
955
+ const later = column.table.position >= from.position;
956
+ const annotation = later
957
+ ? `(): ${typeImport(context, DIALECTS[context.dialect].anyColumnType)} =>`
958
+ : '() =>';
959
+ const target = `${column.table.variable}.${column.key}`;
960
+ const config = configObject(entries);
961
+ return `.references(${annotation} ${target}${config === undefined ? '' : `, ${config}`})`;
962
+ }
963
+ function typeImport(context, name) {
964
+ context.typeImports.add(name);
965
+ return name;
966
+ }
967
+ // ---------------------------------------------------------------------------
968
+ // Tables
969
+ // ---------------------------------------------------------------------------
970
+ /** Writes `key: chain,` on one line, or with the chain broken after the builder when it is too wide. */
971
+ function formatMember(key, chain, indent = ' ') {
972
+ const inline = `${indent}${key}: ${chain.join('')},`;
973
+ if (inline.length <= MAX_LINE_WIDTH || chain.length <= 1) {
974
+ return [inline];
975
+ }
976
+ const rest = chain.slice(1);
977
+ return [
978
+ `${indent}${key}: ${chain[0] ?? ''}`,
979
+ ...rest.map((segment, position) => `${indent} ${segment}${position === rest.length - 1 ? ',' : ''}`),
980
+ ];
981
+ }
982
+ function isIntegerKey(field) {
983
+ return field.type === 'int' || field.type === 'bigInt';
984
+ }
985
+ function fieldChain(context, model, field) {
986
+ const label = `${model.name}.${field.name}`;
987
+ const inCompositeKey = model.compositePrimaryKey?.includes(field.name) ?? false;
988
+ if (field.generated !== undefined) {
989
+ context.warnings.push(`${label}: the generated column expression ${field.generated.expression} is Python, not SQL, and cannot be written for Drizzle; the property was written as a regular column.`);
990
+ }
991
+ const isAutoKey = field.isPrimaryKey &&
992
+ !inCompositeKey &&
993
+ field.default?.kind === 'autoIncrement' &&
994
+ isIntegerKey(field);
995
+ if (isAutoKey) {
996
+ return autoIncrementChain(context, field);
997
+ }
998
+ const chain = [
999
+ columnBuilder(context, label, field, field.columnName).builder,
1000
+ ];
1001
+ if (field.isPrimaryKey && !inCompositeKey) {
1002
+ chain.push('.primaryKey()');
1003
+ }
1004
+ else if (!field.isNullable || inCompositeKey) {
1005
+ chain.push('.notNull()');
1006
+ }
1007
+ if (field.isUnique && !field.isPrimaryKey) {
1008
+ chain.push(field.uniqueName === undefined
1009
+ ? '.unique()'
1010
+ : `.unique(${quote(field.uniqueName)})`);
1011
+ }
1012
+ const defaultText = defaultSegment(context, label, field);
1013
+ if (defaultText !== undefined) {
1014
+ chain.push(defaultText);
1015
+ }
1016
+ if (field.isAutoUpdated) {
1017
+ const updater = onUpdateSegment(context, label, field);
1018
+ if (updater !== undefined) {
1019
+ chain.push(updater);
1020
+ }
1021
+ }
1022
+ return chain;
1023
+ }
1024
+ function onUpdateSegment(context, label, field) {
1025
+ if (field.type === 'dateTime') {
1026
+ return '.$onUpdate(() => new Date())';
1027
+ }
1028
+ if (field.type === 'date') {
1029
+ return '.$onUpdate(() => new Date().toISOString().slice(0, 10))';
1030
+ }
1031
+ context.warnings.push(`${label}: only date and date-time columns can be refreshed on update ($onUpdate); the auto-update flag was dropped.`);
1032
+ return undefined;
1033
+ }
1034
+ function autoIncrementChain(context, field) {
1035
+ const name = field.columnName;
1036
+ switch (context.dialect) {
1037
+ case 'pg':
1038
+ return [
1039
+ field.type === 'bigInt'
1040
+ ? builderCall(context, 'bigserial', name, [`mode: 'number'`])
1041
+ : builderCall(context, 'serial', name),
1042
+ '.primaryKey()',
1043
+ ];
1044
+ case 'mysql':
1045
+ return [
1046
+ field.type === 'bigInt'
1047
+ ? builderCall(context, 'bigint', name, [`mode: 'number'`])
1048
+ : builderCall(context, 'int', name),
1049
+ '.primaryKey()',
1050
+ '.autoincrement()',
1051
+ ];
1052
+ default:
1053
+ // SQLite's INTEGER PRIMARY KEY is already 64 bits wide.
1054
+ return [
1055
+ builderCall(context, 'integer', name),
1056
+ `.primaryKey({ autoIncrement: true })`,
1057
+ ];
1058
+ }
1059
+ }
1060
+ function relationColumnChain(context, from, relation, column) {
1061
+ const model = from.model;
1062
+ const label = `${model.name}.${relation.name}`;
1063
+ const keyed = isKeyedRelation(model, relation);
1064
+ const inCompositeKey = model.compositePrimaryKey?.includes(relation.name) ?? false;
1065
+ const asField = {
1066
+ ...column.field,
1067
+ name: relation.name,
1068
+ columnName: relation.columnName,
1069
+ isPrimaryKey: false,
1070
+ isUnique: false,
1071
+ isNullable: relation.isNullable,
1072
+ isAutoUpdated: false,
1073
+ default: undefined,
1074
+ generated: undefined,
1075
+ };
1076
+ const chain = [
1077
+ columnBuilder(context, label, asField, relation.columnName).builder,
1078
+ ];
1079
+ if (keyed && !inCompositeKey) {
1080
+ chain.push('.primaryKey()');
1081
+ }
1082
+ else if (!relation.isNullable || inCompositeKey) {
1083
+ chain.push('.notNull()');
1084
+ }
1085
+ if (relation.kind === 'oneToOne' && !keyed) {
1086
+ chain.push('.unique()');
1087
+ }
1088
+ if (relation.onDelete === 'setNull' && !relation.isNullable) {
1089
+ context.warnings.push(`${label}: onDelete SET NULL on a required relation will fail at the database level; review the relation.`);
1090
+ }
1091
+ chain.push(referencesSegment(context, label, from, relation, column));
1092
+ return chain;
1093
+ }
1094
+ /** Resolves an index or key entry (a field or relation name of the IR) to a column key. */
1095
+ function columnKeyOf(info, name) {
1096
+ const model = info.model;
1097
+ const relation = model.relations.find((candidate) => !isManyToMany(candidate) && candidate.name === name) ??
1098
+ model.relations.find((candidate) => !isManyToMany(candidate) && candidate.columnName === name);
1099
+ if (relation !== undefined) {
1100
+ return info.columnKeys.get(relation.name);
1101
+ }
1102
+ const field = model.fields.find((candidate) => candidate.name === name) ??
1103
+ model.fields.find((candidate) => candidate.columnName === name);
1104
+ return field === undefined ? undefined : info.fieldKeys.get(field.name);
1105
+ }
1106
+ function indexName(context, model, index, columns) {
1107
+ if (index.name !== undefined) {
1108
+ context.indexNames.add(index.name);
1109
+ return index.name;
1110
+ }
1111
+ const suffix = index.isUnique ? 'key' : 'idx';
1112
+ const base = limitIdentifier(`${model.tableName}_${columns.join('_')}_${suffix}`, MAX_INDEX_NAME_LENGTH);
1113
+ return uniqueName(base, context.indexNames);
1114
+ }
1115
+ function extraEntries(context, info) {
1116
+ const model = info.model;
1117
+ const entries = [];
1118
+ const composite = model.compositePrimaryKey;
1119
+ if (composite !== undefined && composite.length > 0) {
1120
+ const columns = [];
1121
+ for (const name of composite) {
1122
+ const key = columnKeyOf(info, name);
1123
+ if (key === undefined) {
1124
+ context.warnings.push(`${model.name}: the composite primary key refers to "${name}", which is not a field of the model; it was left out of the key.`);
1125
+ }
1126
+ else {
1127
+ columns.push(`table.${key}`);
1128
+ }
1129
+ }
1130
+ if (columns.length > 0) {
1131
+ const nameEntry = model.primaryKeyName === undefined
1132
+ ? ''
1133
+ : `, name: ${quote(model.primaryKeyName)}`;
1134
+ entries.push(`${use(context, 'primaryKey')}({ columns: [${columns.join(', ')}]${nameEntry} })`);
1135
+ }
1136
+ }
1137
+ for (const index of model.indexes) {
1138
+ const keys = [];
1139
+ let complete = true;
1140
+ for (const name of index.fields) {
1141
+ const key = columnKeyOf(info, name);
1142
+ if (key === undefined) {
1143
+ complete = false;
1144
+ context.warnings.push(`${model.name}: an index references "${name}", which is not a field of the model; the index was skipped.`);
1145
+ break;
1146
+ }
1147
+ keys.push(key);
1148
+ }
1149
+ if (!complete || keys.length === 0) {
1150
+ continue;
1151
+ }
1152
+ const columnNames = index.fields.map((name) => {
1153
+ const field = model.fields.find((candidate) => candidate.name === name);
1154
+ const relation = model.relations.find((candidate) => candidate.name === name);
1155
+ return field?.columnName ?? relation?.columnName ?? name;
1156
+ });
1157
+ const name = indexName(context, model, index, columnNames);
1158
+ entries.push(`${use(context, index.isUnique ? 'uniqueIndex' : 'index')}(${quote(name)}).on(${keys
1159
+ .map((key) => `table.${key}`)
1160
+ .join(', ')})`);
1161
+ }
1162
+ return entries;
1163
+ }
1164
+ function renderTable(context, info) {
1165
+ const model = info.model;
1166
+ const members = [];
1167
+ for (const field of model.fields) {
1168
+ members.push(...formatMember(info.fieldKeys.get(field.name) ?? field.name, fieldChain(context, model, field)));
1169
+ }
1170
+ for (const relation of model.relations) {
1171
+ if (isManyToMany(relation)) {
1172
+ continue;
1173
+ }
1174
+ const target = findModel(context.schema, relation.targetModel);
1175
+ if (target === undefined) {
1176
+ context.warnings.push(`${model.name}.${relation.name}: target model "${relation.targetModel}" does not exist in the schema; the relation was skipped.`);
1177
+ info.columnKeys.delete(relation.name);
1178
+ info.relationKeys.delete(relation.name);
1179
+ continue;
1180
+ }
1181
+ const column = referencedColumn(context, target, relation.toField);
1182
+ if (column === undefined) {
1183
+ context.warnings.push(`${model.name}.${relation.name}: target model "${target.name}" has no single-column primary key to reference; the relation was skipped.`);
1184
+ info.columnKeys.delete(relation.name);
1185
+ info.relationKeys.delete(relation.name);
1186
+ continue;
1187
+ }
1188
+ members.push(...formatMember(info.columnKeys.get(relation.name) ?? relation.columnName, relationColumnChain(context, info, relation, column)));
1189
+ }
1190
+ const extras = extraEntries(context, info);
1191
+ const tableFunction = use(context, DIALECTS[context.dialect].tableFunction);
1192
+ const lines = [];
1193
+ if (extras.length === 0) {
1194
+ lines.push(`export const ${info.variable} = ${tableFunction}(${quote(model.tableName)}, {`, ...members, '});');
1195
+ }
1196
+ else {
1197
+ // The three-argument call is laid out the way Prettier would.
1198
+ lines.push(`export const ${info.variable} = ${tableFunction}(`, ` ${quote(model.tableName)},`, ' {', ...members.map((line) => ` ${line}`), ' },', ' (table) => [', ...extras.map((entry) => ` ${entry},`), ' ]', ');');
1199
+ }
1200
+ lines.push('', `export type ${info.typeName} = typeof ${info.variable}.$inferSelect;`, `export type ${info.insertTypeName} = typeof ${info.variable}.$inferInsert;`);
1201
+ return lines.join('\n');
1202
+ }
1203
+ // ---------------------------------------------------------------------------
1204
+ // Relations
1205
+ // ---------------------------------------------------------------------------
1206
+ function relationNameOf(model, relation) {
1207
+ return `${model.name}_${relation.name}`;
1208
+ }
1209
+ function isAmbiguous(context, model, relation) {
1210
+ return context.ambiguousPairs.has(pairKey(model.name, relation.targetModel));
1211
+ }
1212
+ function renderRelations(context, info) {
1213
+ const entries = [];
1214
+ // Allocate in a stable order: this table's forward relations first, then inverse sides.
1215
+ for (const model of context.schema.models) {
1216
+ for (const relation of model.relations) {
1217
+ if (isManyToMany(relation)) {
1218
+ continue;
1219
+ }
1220
+ if (model.name === info.model.name) {
1221
+ const forward = forwardEntry(context, info, relation);
1222
+ if (forward !== undefined) {
1223
+ entries.push(forward);
1224
+ }
1225
+ }
1226
+ }
1227
+ }
1228
+ for (const model of context.schema.models) {
1229
+ for (const relation of model.relations) {
1230
+ if (isManyToMany(relation) || relation.targetModel !== info.model.name) {
1231
+ continue;
1232
+ }
1233
+ const owner = tableOf(context, model.name);
1234
+ if (owner === undefined || !owner.relationKeys.has(relation.name)) {
1235
+ continue;
1236
+ }
1237
+ entries.push(inverseEntry(context, info, owner, relation));
1238
+ }
1239
+ }
1240
+ if (entries.length === 0) {
1241
+ return undefined;
1242
+ }
1243
+ const helpers = ['one', 'many'].filter((helper) => entries.some((entry) => entry.helper === helper));
1244
+ const lines = [
1245
+ `export const ${info.relationsVariable} = ${useCore(context, 'relations')}(${info.variable}, ({ ${helpers.join(', ')} }) => ({`,
1246
+ ];
1247
+ for (const entry of entries) {
1248
+ const [first, ...rest] = entry.text;
1249
+ lines.push(` ${entry.key}: ${first ?? ''}`);
1250
+ lines.push(...rest);
1251
+ lines[lines.length - 1] = `${lines[lines.length - 1] ?? ''},`;
1252
+ }
1253
+ lines.push('}));');
1254
+ return lines.join('\n');
1255
+ }
1256
+ function forwardEntry(context, info, relation) {
1257
+ const key = info.relationKeys.get(relation.name);
1258
+ const columnKey = info.columnKeys.get(relation.name);
1259
+ const target = findModel(context.schema, relation.targetModel);
1260
+ const targetTable = tableOf(context, relation.targetModel);
1261
+ if (key === undefined ||
1262
+ columnKey === undefined ||
1263
+ target === undefined ||
1264
+ targetTable === undefined) {
1265
+ return undefined;
1266
+ }
1267
+ const column = referencedColumn(context, target, relation.toField);
1268
+ if (column === undefined) {
1269
+ return undefined;
1270
+ }
1271
+ const lines = [
1272
+ `one(${targetTable.variable}, {`,
1273
+ ` fields: [${info.variable}.${columnKey}],`,
1274
+ ` references: [${column.table.variable}.${column.key}],`,
1275
+ ];
1276
+ if (isAmbiguous(context, info.model, relation)) {
1277
+ lines.push(` relationName: ${quote(relationNameOf(info.model, relation))},`);
1278
+ }
1279
+ lines.push(' })');
1280
+ return { key, helper: 'one', text: lines };
1281
+ }
1282
+ function inverseEntry(context, target, owner, relation) {
1283
+ const key = inverseKeyOf(context, context.inverseKeys, owner.model, relation, target);
1284
+ const helper = relation.kind === 'oneToOne' ? 'one' : 'many';
1285
+ const name = isAmbiguous(context, owner.model, relation)
1286
+ ? relationNameOf(owner.model, relation)
1287
+ : undefined;
1288
+ return {
1289
+ key,
1290
+ helper,
1291
+ text: [
1292
+ name === undefined
1293
+ ? `${helper}(${owner.variable})`
1294
+ : `${helper}(${owner.variable}, { relationName: ${quote(name)} })`,
1295
+ ],
1296
+ };
1297
+ }
1298
+ // ---------------------------------------------------------------------------
1299
+ // Imports and configuration
1300
+ // ---------------------------------------------------------------------------
1301
+ function importLine(specifiers, module) {
1302
+ const inline = `import { ${specifiers.join(', ')} } from '${module}';`;
1303
+ return inline.length <= MAX_LINE_WIDTH
1304
+ ? inline
1305
+ : [
1306
+ 'import {',
1307
+ ...specifiers.map((name) => ` ${name},`),
1308
+ `} from '${module}';`,
1309
+ ].join('\n');
1310
+ }
1311
+ function buildImports(context) {
1312
+ const lines = [];
1313
+ if (context.coreImports.size > 0) {
1314
+ lines.push(importLine([...context.coreImports].sort(compareText), 'drizzle-orm'));
1315
+ }
1316
+ const specifiers = [
1317
+ ...[...context.imports],
1318
+ ...[...context.typeImports].map((name) => `type ${name}`),
1319
+ ].sort((first, second) => compareText(first.replace(/^type /, ''), second.replace(/^type /, '')));
1320
+ if (specifiers.length > 0) {
1321
+ lines.push(importLine(specifiers, DIALECTS[context.dialect].module));
1322
+ }
1323
+ return lines;
1324
+ }
1325
+ function renderConfig(dialect) {
1326
+ return [
1327
+ `import { defineConfig } from 'drizzle-kit';`,
1328
+ '',
1329
+ 'export default defineConfig({',
1330
+ ` dialect: '${DIALECTS[dialect].kitDialect}',`,
1331
+ ` schema: './${DRIZZLE_SCHEMA_FILE}',`,
1332
+ ` out: './drizzle',`,
1333
+ '});',
1334
+ '',
1335
+ ].join('\n');
1336
+ }
1337
+ //# sourceMappingURL=drizzle.js.map