@vibeorm/migrate 1.3.1 → 2.0.0-alpha.2

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 (61) hide show
  1. package/README.md +33 -66
  2. package/dist/ddl/drop-order.d.ts +25 -0
  3. package/dist/ddl/drop-order.d.ts.map +1 -0
  4. package/dist/ddl/mysql.d.ts +46 -0
  5. package/dist/ddl/mysql.d.ts.map +1 -0
  6. package/dist/ddl/postgres.d.ts +24 -0
  7. package/dist/ddl/postgres.d.ts.map +1 -0
  8. package/dist/ddl/render.d.ts +25 -0
  9. package/dist/ddl/render.d.ts.map +1 -0
  10. package/dist/ddl/sqlite.d.ts +62 -0
  11. package/dist/ddl/sqlite.d.ts.map +1 -0
  12. package/dist/differ.d.ts +33 -0
  13. package/dist/differ.d.ts.map +1 -0
  14. package/dist/down.d.ts +55 -0
  15. package/dist/down.d.ts.map +1 -0
  16. package/dist/enum-values.d.ts +19 -0
  17. package/dist/enum-values.d.ts.map +1 -0
  18. package/dist/execute.d.ts +41 -0
  19. package/dist/execute.d.ts.map +1 -0
  20. package/dist/extensions.d.ts +74 -0
  21. package/dist/extensions.d.ts.map +1 -0
  22. package/dist/index.d.ts +26 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +3858 -0
  25. package/dist/index.js.map +31 -0
  26. package/dist/introspect/mysql.d.ts +31 -0
  27. package/dist/introspect/mysql.d.ts.map +1 -0
  28. package/dist/introspect/postgres.d.ts +25 -0
  29. package/dist/introspect/postgres.d.ts.map +1 -0
  30. package/dist/introspect/shared.d.ts +76 -0
  31. package/dist/introspect/shared.d.ts.map +1 -0
  32. package/dist/introspect/sqlite.d.ts +53 -0
  33. package/dist/introspect/sqlite.d.ts.map +1 -0
  34. package/dist/mysql-types.d.ts +66 -0
  35. package/dist/mysql-types.d.ts.map +1 -0
  36. package/dist/normalize.d.ts +115 -0
  37. package/dist/normalize.d.ts.map +1 -0
  38. package/dist/postgres-types.d.ts +95 -0
  39. package/dist/postgres-types.d.ts.map +1 -0
  40. package/dist/push.d.ts +45 -0
  41. package/dist/push.d.ts.map +1 -0
  42. package/dist/relations.d.ts +40 -0
  43. package/dist/relations.d.ts.map +1 -0
  44. package/dist/runner.d.ts +120 -0
  45. package/dist/runner.d.ts.map +1 -0
  46. package/dist/sqlite-types.d.ts +87 -0
  47. package/dist/sqlite-types.d.ts.map +1 -0
  48. package/dist/types.d.ts +217 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/package.json +33 -23
  51. package/src/cascade-actions.ts +0 -88
  52. package/src/ddl-builder.ts +0 -415
  53. package/src/index.ts +0 -41
  54. package/src/introspector.ts +0 -684
  55. package/src/migration-runner.ts +0 -127
  56. package/src/relation-utils.ts +0 -79
  57. package/src/schema-differ.ts +0 -865
  58. package/src/schema-printer.ts +0 -259
  59. package/src/snapshot.ts +0 -141
  60. package/src/sql-utils.ts +0 -45
  61. package/src/types.ts +0 -13
package/dist/index.js ADDED
@@ -0,0 +1,3858 @@
1
+ // @bun
2
+ // packages/migrate/src/types.ts
3
+ function describeStep(params) {
4
+ const { step } = params;
5
+ switch (step.kind) {
6
+ case "createEnum":
7
+ return `create enum "${step.enumDef.dbName ?? step.enumDef.name}"`;
8
+ case "dropEnum":
9
+ return `drop enum "${step.enumName}"`;
10
+ case "addEnumValue":
11
+ return `add value "${step.value}" to enum "${step.enumName}"`;
12
+ case "createTable":
13
+ return `create table "${step.table}"`;
14
+ case "dropTable":
15
+ return `drop table "${step.table}"`;
16
+ case "addColumn":
17
+ return `add column "${step.table}"."${step.column.dbName ?? step.column.name}"`;
18
+ case "dropColumn":
19
+ return `drop column "${step.table}"."${step.column}"`;
20
+ case "alterColumn":
21
+ return `alter column "${step.table}"."${step.column}" (${step.changes.join(", ")})`;
22
+ case "createIndex":
23
+ return `create ${step.unique ? "unique " : ""}index "${step.name}" on "${step.table}"`;
24
+ case "dropIndex":
25
+ return `drop index "${step.name}"`;
26
+ case "addForeignKey":
27
+ return `add foreign key "${step.constraintName}" on "${step.table}"`;
28
+ case "dropForeignKey":
29
+ return `drop foreign key "${step.constraintName}" on "${step.table}"`;
30
+ case "createJoinTable":
31
+ return `create join table "${step.table}"`;
32
+ case "dropJoinTable":
33
+ return `drop join table "${step.table}"`;
34
+ case "addPrimaryKey":
35
+ return `add primary key on "${step.table}"`;
36
+ case "dropPrimaryKey":
37
+ return `drop primary key on "${step.table}"`;
38
+ case "dropView":
39
+ return `drop view "${step.table}"`;
40
+ }
41
+ }
42
+ // packages/migrate/src/differ.ts
43
+ import { isEnumType as isEnumType4, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
44
+
45
+ // packages/migrate/src/enum-values.ts
46
+ function resolveEnumValues(params) {
47
+ const { name, enums } = params;
48
+ const direct = enums.get(name);
49
+ if (direct !== undefined)
50
+ return direct.values;
51
+ for (const enumDef of enums.values()) {
52
+ if (enumDef.name === name)
53
+ return enumDef.values;
54
+ }
55
+ return;
56
+ }
57
+
58
+ // packages/migrate/src/normalize.ts
59
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
60
+
61
+ // packages/migrate/src/relations.ts
62
+ import { implicitJoinTable } from "@vibeorm/schema";
63
+ function detectImplicitM2MPairs(params) {
64
+ const { schema } = params;
65
+ const modelsByName = new Map(schema.models.map((model) => [model.name, model]));
66
+ const pairs = [];
67
+ const seen = new Set;
68
+ for (const model of schema.models) {
69
+ if (model.isView)
70
+ continue;
71
+ for (const relation of model.relations) {
72
+ if (!isFieldlessListSide({ relation }))
73
+ continue;
74
+ const target = modelsByName.get(relation.targetModel);
75
+ if (target === undefined || target.isView)
76
+ continue;
77
+ const counterpart = soleCounterpart({ relation, ownModel: model.name, target });
78
+ if (counterpart === undefined || !isFieldlessListSide({ relation: counterpart }))
79
+ continue;
80
+ if (soleCounterpart({ relation: counterpart, ownModel: target.name, target: model }) !== relation) {
81
+ continue;
82
+ }
83
+ const joinTable = implicitJoinTable({
84
+ modelA: model.name,
85
+ modelB: relation.targetModel,
86
+ ...relation.relationName === undefined ? {} : { relationName: relation.relationName }
87
+ });
88
+ if (seen.has(joinTable.tableName))
89
+ continue;
90
+ seen.add(joinTable.tableName);
91
+ pairs.push({
92
+ modelA: joinTable.modelA,
93
+ modelB: joinTable.modelB,
94
+ ...relation.relationName === undefined ? {} : { relationName: relation.relationName },
95
+ tableName: joinTable.tableName
96
+ });
97
+ }
98
+ }
99
+ return pairs;
100
+ }
101
+ function isFieldlessListSide(params) {
102
+ const { relation } = params;
103
+ return relation.isList && (relation.fields === undefined || relation.fields.length === 0);
104
+ }
105
+ function soleCounterpart(params) {
106
+ const { relation, ownModel, target } = params;
107
+ const candidates = target.relations.filter((candidate) => candidate !== relation && candidate.targetModel === ownModel && candidate.relationName === relation.relationName);
108
+ return candidates.length === 1 ? candidates[0] : undefined;
109
+ }
110
+
111
+ // packages/migrate/src/postgres-types.ts
112
+ import { VibeError, isEnumType } from "@vibeorm/schema";
113
+ import { postgresDialect } from "@vibeorm/sql";
114
+ var SCALAR_TO_PG = {
115
+ String: "text",
116
+ Boolean: "boolean",
117
+ Int: "integer",
118
+ BigInt: "bigint",
119
+ Float: "double precision",
120
+ Decimal: "numeric",
121
+ DateTime: "timestamptz",
122
+ Json: "jsonb",
123
+ Bytes: "bytea"
124
+ };
125
+ var CANONICAL_EQUIVALENTS = {
126
+ DateTime: ["timestamptz(3)", "timestamptz(6)"]
127
+ };
128
+ var NATIVE_ALIASES = {
129
+ charactervarying: "varchar",
130
+ character: "char",
131
+ bpchar: "char",
132
+ int: "integer",
133
+ int4: "integer",
134
+ int2: "smallint",
135
+ int8: "bigint",
136
+ serial: "integer",
137
+ bigserial: "bigint",
138
+ bool: "boolean",
139
+ float4: "real",
140
+ float8: "doubleprecision",
141
+ double: "doubleprecision",
142
+ doubleprecision: "doubleprecision",
143
+ decimal: "numeric",
144
+ timestampwithtimezone: "timestamptz",
145
+ timestampwithouttimezone: "timestamp",
146
+ timewithtimezone: "timetz",
147
+ timewithouttimezone: "time"
148
+ };
149
+ function normalizeNativeType(params) {
150
+ const compact = params.raw.toLowerCase().replace(/\s+/g, "");
151
+ const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(\[\])?$/);
152
+ if (match === null)
153
+ return compact;
154
+ const base = NATIVE_ALIASES[match[1]] ?? match[1];
155
+ return `${base}${match[2] ?? ""}${match[3] ?? ""}`;
156
+ }
157
+ function columnTypeKey(params) {
158
+ const { field } = params;
159
+ const listSuffix = field.isList ? "[]" : "";
160
+ if (field.isUnsupported === true) {
161
+ return `u:${normalizeNativeType({ raw: field.nativeType ?? "" })}`;
162
+ }
163
+ if (isEnumType(field.type)) {
164
+ return `e:${field.type.enum}${listSuffix}`;
165
+ }
166
+ if (field.nativeType !== undefined) {
167
+ const normalized = normalizeNativeType({ raw: field.nativeType });
168
+ const canonical = normalizeNativeType({ raw: SCALAR_TO_PG[field.type] });
169
+ const isCanonical = normalized === canonical || (CANONICAL_EQUIVALENTS[field.type]?.includes(normalized) ?? false);
170
+ if (!isCanonical)
171
+ return `n:${normalized}${listSuffix}`;
172
+ }
173
+ return `s:${field.type}${listSuffix}`;
174
+ }
175
+ function isScalarToArrayChange(params) {
176
+ const { from, to } = params;
177
+ if (from.isList || !to.isList)
178
+ return false;
179
+ const fromBase = columnTypeKey({ field: { ...from, isList: false } });
180
+ const toBase = columnTypeKey({ field: { ...to, isList: false } });
181
+ return fromBase === toBase;
182
+ }
183
+ function columnTypeSql(params) {
184
+ const { field } = params;
185
+ if (field.isUnsupported === true) {
186
+ if (field.nativeType === undefined) {
187
+ throw new VibeError({
188
+ code: "VIBE_MIGRATION",
189
+ message: `unsupported column "${field.name}" carries no native type to render`,
190
+ meta: { field: field.name }
191
+ });
192
+ }
193
+ return field.nativeType;
194
+ }
195
+ const listSuffix = field.isList ? "[]" : "";
196
+ if (isEnumType(field.type)) {
197
+ return `${postgresDialect.quoteIdent({ name: field.type.enum })}${listSuffix}`;
198
+ }
199
+ if (field.nativeType !== undefined) {
200
+ const normalized = normalizeNativeType({ raw: field.nativeType });
201
+ const canonical = normalizeNativeType({ raw: SCALAR_TO_PG[field.type] });
202
+ if (normalized !== canonical)
203
+ return `${field.nativeType.toLowerCase()}${listSuffix}`;
204
+ }
205
+ return `${SCALAR_TO_PG[field.type]}${listSuffix}`;
206
+ }
207
+ function isIdentityColumn(params) {
208
+ const { field } = params;
209
+ return field.default?.kind === "autoincrement" && !field.isList && !isEnumType(field.type) && (field.type === "Int" || field.type === "BigInt");
210
+ }
211
+ function defaultFingerprint(params) {
212
+ const { field } = params;
213
+ const def = field.default;
214
+ if (def === undefined)
215
+ return null;
216
+ switch (def.kind) {
217
+ case "autoincrement":
218
+ return "autoincrement";
219
+ case "now":
220
+ return "now";
221
+ case "uuid":
222
+ case "cuid":
223
+ case "nanoid":
224
+ case "ulid":
225
+ return null;
226
+ case "literal": {
227
+ const value = def.value;
228
+ if (typeof value === "boolean")
229
+ return `bool:${value}`;
230
+ if (typeof value === "number")
231
+ return `num:${value}`;
232
+ if (isEnumType(field.type))
233
+ return `enum:${value}`;
234
+ if (isNumericScalar({ type: field.type }) && NUMERIC_LITERAL.test(value)) {
235
+ return `num:${Number(value)}`;
236
+ }
237
+ return `str:${value}`;
238
+ }
239
+ case "enumValue":
240
+ return `enum:${def.value}`;
241
+ case "dbgenerated":
242
+ return `db:${def.sql.toLowerCase().replace(/\s+/g, "")}`;
243
+ }
244
+ }
245
+ function defaultSql(params) {
246
+ const def = params.field.default;
247
+ if (def === undefined)
248
+ return;
249
+ switch (def.kind) {
250
+ case "autoincrement":
251
+ case "uuid":
252
+ case "cuid":
253
+ case "nanoid":
254
+ case "ulid":
255
+ return;
256
+ case "now":
257
+ return "CURRENT_TIMESTAMP";
258
+ case "literal": {
259
+ const value = def.value;
260
+ if (typeof value === "boolean")
261
+ return value ? "true" : "false";
262
+ if (typeof value === "number")
263
+ return String(value);
264
+ return quotePgString({ value });
265
+ }
266
+ case "enumValue":
267
+ return quotePgString({ value: def.value });
268
+ case "dbgenerated":
269
+ return def.sql;
270
+ }
271
+ }
272
+ var NUMERIC_LITERAL = /^-?\d+(\.\d+)?$/;
273
+ function isNumericScalar(params) {
274
+ const { type } = params;
275
+ return type === "Int" || type === "BigInt" || type === "Float" || type === "Decimal";
276
+ }
277
+ var PG_CHAR_TO_CASCADE_ACTION = {
278
+ a: "NoAction",
279
+ r: "Restrict",
280
+ c: "Cascade",
281
+ n: "SetNull",
282
+ d: "SetDefault"
283
+ };
284
+ var CASCADE_ACTION_SQL = {
285
+ Cascade: "CASCADE",
286
+ Restrict: "RESTRICT",
287
+ NoAction: "NO ACTION",
288
+ SetNull: "SET NULL",
289
+ SetDefault: "SET DEFAULT"
290
+ };
291
+ function resolveOnDelete(params) {
292
+ if (params.explicit !== undefined)
293
+ return params.explicit;
294
+ return params.required ? "Restrict" : "SetNull";
295
+ }
296
+ function resolveOnUpdate(params) {
297
+ return params.explicit ?? "Cascade";
298
+ }
299
+ function quotePgString(params) {
300
+ return `'${params.value.replaceAll("'", "''")}'`;
301
+ }
302
+
303
+ // packages/migrate/src/sqlite-types.ts
304
+ import { VibeError as VibeError2, isEnumType as isEnumType2, unsupportedCapability } from "@vibeorm/schema";
305
+ var SCALAR_TO_SQLITE = {
306
+ String: "TEXT",
307
+ Boolean: "INTEGER",
308
+ Int: "INTEGER",
309
+ BigInt: "INTEGER",
310
+ Float: "REAL",
311
+ Decimal: "TEXT",
312
+ DateTime: "TEXT",
313
+ Json: "TEXT",
314
+ Bytes: "BLOB"
315
+ };
316
+ function refuseSqliteScalarList(params) {
317
+ throw unsupportedCapability({
318
+ dialect: "sqlite",
319
+ feature: `scalar list column "${params.field.name}"`,
320
+ hint: "sqlite has no array column type \u2014 model the list as a separate table or a Json column"
321
+ });
322
+ }
323
+ function sqliteStorageType(params) {
324
+ const { field } = params;
325
+ if (field.isUnsupported === true) {
326
+ if (field.nativeType === undefined) {
327
+ throw new VibeError2({
328
+ code: "VIBE_MIGRATION",
329
+ message: `unsupported column "${field.name}" carries no native type to render`,
330
+ meta: { field: field.name }
331
+ });
332
+ }
333
+ return field.nativeType;
334
+ }
335
+ if (field.isList)
336
+ refuseSqliteScalarList({ field });
337
+ if (isEnumType2(field.type))
338
+ return "TEXT";
339
+ return SCALAR_TO_SQLITE[field.type];
340
+ }
341
+ function sqliteColumnTypeKey(params) {
342
+ const { field, enums } = params;
343
+ if (field.isUnsupported === true) {
344
+ return `u:${(field.nativeType ?? "").toLowerCase().replace(/\s+/g, "")}`;
345
+ }
346
+ if (isEnumType2(field.type)) {
347
+ const values = resolveEnumValues({ name: field.type.enum, enums });
348
+ return values === undefined ? `e:?${field.type.enum}` : `e:${values.join("|")}`;
349
+ }
350
+ const listSuffix = field.isList ? "[]" : "";
351
+ return `t:${SCALAR_TO_SQLITE[field.type]}${listSuffix}`;
352
+ }
353
+ function sqliteDefaultFingerprint(params) {
354
+ const { field } = params;
355
+ const def = field.default;
356
+ if (def?.kind === "literal" && typeof def.value === "boolean") {
357
+ return `num:${def.value ? 1 : 0}`;
358
+ }
359
+ return defaultFingerprint({ field });
360
+ }
361
+ function sqliteDefaultSql(params) {
362
+ const def = params.field.default;
363
+ if (def === undefined)
364
+ return;
365
+ switch (def.kind) {
366
+ case "autoincrement":
367
+ case "uuid":
368
+ case "cuid":
369
+ case "nanoid":
370
+ case "ulid":
371
+ return;
372
+ case "now":
373
+ return "CURRENT_TIMESTAMP";
374
+ case "literal": {
375
+ const value = def.value;
376
+ if (typeof value === "boolean")
377
+ return value ? "1" : "0";
378
+ if (typeof value === "number")
379
+ return String(value);
380
+ return quotePgString({ value });
381
+ }
382
+ case "enumValue":
383
+ return quotePgString({ value: def.value });
384
+ case "dbgenerated":
385
+ return def.sql;
386
+ }
387
+ }
388
+ function sqliteEnumCheckName(params) {
389
+ return `${params.table}_${params.column}_enum_${params.enumName}`;
390
+ }
391
+ var ENUM_CHECK_PATTERN = /CONSTRAINT "((?:[^"]|"")+)_enum_((?:[^"]|"")+)" CHECK \("((?:[^"]|"")+)" IN \(([^)]*)\)\)/g;
392
+ function parseSqliteEnumChecks(params) {
393
+ const result = [];
394
+ for (const match of params.tableSql.matchAll(ENUM_CHECK_PATTERN)) {
395
+ const enumName = match[2].replaceAll('""', '"');
396
+ const column = match[3].replaceAll('""', '"');
397
+ const values = [];
398
+ for (const value of match[4].matchAll(/'((?:[^']|'')*)'/g)) {
399
+ values.push(value[1].replaceAll("''", "'"));
400
+ }
401
+ result.push({ column, enumName, values });
402
+ }
403
+ return result;
404
+ }
405
+
406
+ // packages/migrate/src/mysql-types.ts
407
+ import { VibeError as VibeError3, isEnumType as isEnumType3, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
408
+ var SCALAR_TO_MYSQL = {
409
+ String: "varchar(191)",
410
+ Boolean: "tinyint(1)",
411
+ Int: "int",
412
+ BigInt: "bigint",
413
+ Float: "double",
414
+ Decimal: "decimal(65,30)",
415
+ DateTime: "datetime(3)",
416
+ Json: "json",
417
+ Bytes: "longblob"
418
+ };
419
+ function refuseMysqlScalarList(params) {
420
+ throw unsupportedCapability2({
421
+ dialect: "mysql",
422
+ feature: `scalar list column "${params.field.name}"`,
423
+ hint: "mysql has no array column type \u2014 model the list as a separate table or a JSON column"
424
+ });
425
+ }
426
+ var MYSQL_NATIVE_ALIASES = {
427
+ integer: "int",
428
+ dec: "decimal",
429
+ numeric: "decimal",
430
+ bool: "tinyint(1)",
431
+ boolean: "tinyint(1)",
432
+ character: "char"
433
+ };
434
+ function normalizeMysqlType(params) {
435
+ const compact = params.raw.toLowerCase().replace(/\s+/g, "");
436
+ if (compact === "tinyint(1)" || compact === "bool" || compact === "boolean")
437
+ return "tinyint(1)";
438
+ const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(.*)$/);
439
+ if (match === null)
440
+ return compact;
441
+ const base = MYSQL_NATIVE_ALIASES[match[1]] ?? match[1];
442
+ let modifier = match[2] ?? "";
443
+ if (/^(tinyint|smallint|mediumint|int|bigint)$/.test(base))
444
+ modifier = "";
445
+ return `${base}${modifier}${match[3] ?? ""}`;
446
+ }
447
+ function mysqlColumnTypeKey(params) {
448
+ const { field, enums } = params;
449
+ if (field.isUnsupported === true) {
450
+ return `u:${normalizeMysqlType({ raw: field.nativeType ?? "" })}`;
451
+ }
452
+ if (isEnumType3(field.type)) {
453
+ const values = resolveEnumValues({ name: field.type.enum, enums });
454
+ return values === undefined ? `e:?${field.type.enum}` : `e:${values.join("|")}`;
455
+ }
456
+ const listSuffix = field.isList ? "[]" : "";
457
+ if (field.nativeType !== undefined) {
458
+ const normalized = normalizeMysqlType({ raw: field.nativeType });
459
+ const canonical = normalizeMysqlType({ raw: SCALAR_TO_MYSQL[field.type] });
460
+ if (normalized !== canonical)
461
+ return `n:${normalized}${listSuffix}`;
462
+ }
463
+ return `t:${normalizeMysqlType({ raw: SCALAR_TO_MYSQL[field.type] })}${listSuffix}`;
464
+ }
465
+ function mysqlDefaultFingerprint(params) {
466
+ return defaultFingerprint({ field: params.field });
467
+ }
468
+ function mysqlColumnTypeSql(params) {
469
+ const { field, enums } = params;
470
+ if (field.isUnsupported === true) {
471
+ if (field.nativeType === undefined) {
472
+ throw new VibeError3({
473
+ code: "VIBE_MIGRATION",
474
+ message: `unsupported column "${field.name}" carries no native type to render`,
475
+ meta: { field: field.name }
476
+ });
477
+ }
478
+ return field.nativeType;
479
+ }
480
+ if (field.isList)
481
+ refuseMysqlScalarList({ field });
482
+ if (isEnumType3(field.type)) {
483
+ const values = resolveEnumValues({ name: field.type.enum, enums });
484
+ if (values === undefined) {
485
+ throw new VibeError3({
486
+ code: "VIBE_MIGRATION",
487
+ message: `enum "${field.type.enum}" on column "${field.name}" is not declared in the schema`,
488
+ meta: { field: field.name, enum: field.type.enum }
489
+ });
490
+ }
491
+ return `ENUM(${values.map((value) => quotePgString({ value })).join(", ")})`;
492
+ }
493
+ if (field.nativeType !== undefined) {
494
+ const normalized = normalizeMysqlType({ raw: field.nativeType });
495
+ const canonical = normalizeMysqlType({ raw: SCALAR_TO_MYSQL[field.type] });
496
+ if (normalized !== canonical)
497
+ return field.nativeType.toLowerCase();
498
+ }
499
+ return SCALAR_TO_MYSQL[field.type];
500
+ }
501
+ function mysqlDefaultSql(params) {
502
+ const def = params.field.default;
503
+ if (def === undefined)
504
+ return;
505
+ switch (def.kind) {
506
+ case "autoincrement":
507
+ case "uuid":
508
+ case "cuid":
509
+ case "nanoid":
510
+ case "ulid":
511
+ return;
512
+ case "now":
513
+ return "CURRENT_TIMESTAMP(3)";
514
+ case "literal": {
515
+ const value = def.value;
516
+ if (typeof value === "boolean")
517
+ return value ? "1" : "0";
518
+ if (typeof value === "number")
519
+ return String(value);
520
+ return quotePgString({ value });
521
+ }
522
+ case "enumValue":
523
+ return quotePgString({ value: def.value });
524
+ case "dbgenerated":
525
+ return def.sql;
526
+ }
527
+ }
528
+ function parseMysqlEnumColumnType(params) {
529
+ const match = params.columnType.trim().match(/^enum\((.*)\)$/i);
530
+ if (match === null)
531
+ return null;
532
+ const values = [];
533
+ for (const value of match[1].matchAll(/'((?:[^']|'')*)'/g)) {
534
+ values.push(value[1].replaceAll("''", "'"));
535
+ }
536
+ return values;
537
+ }
538
+
539
+ // packages/migrate/src/normalize.ts
540
+ function normalizeSchema(params) {
541
+ const { schema } = params;
542
+ const dialect = params.dialect ?? "postgres";
543
+ const enums = new Map;
544
+ for (const enumDef of schema.enums) {
545
+ enums.set(enumDef.dbName ?? enumDef.name, enumDef);
546
+ }
547
+ const tables = new Map;
548
+ const views = new Map;
549
+ for (const model of schema.models) {
550
+ if (model.isView) {
551
+ views.set(model.dbName ?? model.name, model);
552
+ continue;
553
+ }
554
+ const table = normalizeModel({ model, schema, dialect, enums });
555
+ tables.set(table.table, table);
556
+ }
557
+ const joinTables = new Map;
558
+ for (const pair of detectImplicitM2MPairs({ schema })) {
559
+ joinTables.set(pair.tableName, normalizeJoinTable({ pair, schema }));
560
+ }
561
+ return { tables, joinTables, enums, views };
562
+ }
563
+ function columnTypeKeyFor(params) {
564
+ const { field, dialect, enums } = params;
565
+ switch (dialect) {
566
+ case "postgres":
567
+ return columnTypeKey({ field });
568
+ case "sqlite":
569
+ return sqliteColumnTypeKey({ field, enums });
570
+ case "mysql":
571
+ return mysqlColumnTypeKey({ field, enums });
572
+ }
573
+ }
574
+ function defaultFingerprintFor(params) {
575
+ const { field, dialect } = params;
576
+ switch (dialect) {
577
+ case "postgres":
578
+ return defaultFingerprint({ field });
579
+ case "sqlite":
580
+ return sqliteDefaultFingerprint({ field });
581
+ case "mysql":
582
+ return mysqlDefaultFingerprint({ field });
583
+ }
584
+ }
585
+ function normalizeModel(params) {
586
+ const { model, schema, dialect, enums } = params;
587
+ const table = model.dbName ?? model.name;
588
+ const columns = new Map;
589
+ for (const field of model.fields) {
590
+ const name = field.dbName ?? field.name;
591
+ columns.set(name, {
592
+ name,
593
+ field,
594
+ typeKey: columnTypeKeyFor({ field, dialect, enums }),
595
+ nullable: field.isOptional,
596
+ defaultKey: defaultFingerprintFor({ field, dialect })
597
+ });
598
+ }
599
+ const pk = normalizePk({ model });
600
+ const indexes = normalizeIndexes({ model, table });
601
+ const fks = normalizeFks({ model, table, schema });
602
+ return { model, table, columns, pk, indexes, fks };
603
+ }
604
+ function normalizePk(params) {
605
+ const { model } = params;
606
+ if (model.primaryKey !== undefined && model.primaryKey.fields.length > 0) {
607
+ return model.primaryKey.fields.map((name) => fieldDbName({ model, name }));
608
+ }
609
+ return model.fields.filter((field) => field.isId).map((field) => field.dbName ?? field.name);
610
+ }
611
+ function normalizeIndexes(params) {
612
+ const { model, table } = params;
613
+ const result = new Map;
614
+ const add = (index) => {
615
+ const unique = index.kind === "unique";
616
+ const kind = unique ? "btree" : index.kind;
617
+ const columns = index.fields.map((field) => {
618
+ const fieldIR = model.fields.find((candidate) => candidate.name === field.name);
619
+ const decimal = fieldIR !== undefined && fieldIR.type === "Decimal" && fieldIR.isList !== true;
620
+ return {
621
+ name: fieldDbName({ model, name: field.name }),
622
+ desc: field.sort === "desc",
623
+ ...decimal ? { decimal: true } : {}
624
+ };
625
+ });
626
+ const where = index.where;
627
+ const key = indexComparisonKey({ unique, kind, columns, where });
628
+ if (result.has(key))
629
+ return;
630
+ const name = index.name ?? `${table}_${columns.map((column) => column.name).join("_")}_${unique ? "key" : "idx"}`;
631
+ result.set(key, {
632
+ key,
633
+ name,
634
+ kind,
635
+ unique,
636
+ columns,
637
+ ...where === undefined ? {} : { where }
638
+ });
639
+ };
640
+ for (const index of model.indexes)
641
+ add(index);
642
+ for (const field of model.fields) {
643
+ if (!field.isUnique)
644
+ continue;
645
+ add({ kind: "unique", fields: [{ name: field.name }] });
646
+ }
647
+ return result;
648
+ }
649
+ function indexComparisonKey(params) {
650
+ const { unique, kind, columns, where } = params;
651
+ const cols = columns.map((column) => `${column.name}${column.desc ? " desc" : ""}`).join(",");
652
+ return `${unique ? "u" : "i"}:${kind}:${cols}:${normalizeWhere({ where })}`;
653
+ }
654
+ function normalizeWhere(params) {
655
+ const { where } = params;
656
+ if (where === undefined)
657
+ return "";
658
+ return where.toLowerCase().replace(/::[a-z_0-9 ]+/g, "").replace(/[()"'\s]/g, "");
659
+ }
660
+ function normalizeFks(params) {
661
+ const { model, table, schema } = params;
662
+ const result = new Map;
663
+ for (const relation of model.relations) {
664
+ if (relation.fields === undefined || relation.fields.length === 0)
665
+ continue;
666
+ const target = schema.models.find((candidate) => candidate.name === relation.targetModel);
667
+ if (target !== undefined && target.isView)
668
+ continue;
669
+ const refTable = target === undefined ? relation.targetModel : target.dbName ?? target.name;
670
+ const columns = relation.fields.map((name) => fieldDbName({ model, name }));
671
+ const refColumns = (relation.references ?? []).map((name) => target === undefined ? name : fieldDbName({ model: target, name }));
672
+ const required = relation.fields.every((name) => {
673
+ const field = model.fields.find((candidate) => candidate.name === name);
674
+ return field === undefined ? true : !field.isOptional;
675
+ });
676
+ const key = `${columns.join(",")}\u2192${refTable}.${refColumns.join(",")}`;
677
+ if (result.has(key))
678
+ continue;
679
+ result.set(key, {
680
+ key,
681
+ constraintName: `${table}_${columns.join("_")}_fkey`,
682
+ columns,
683
+ refTable,
684
+ refColumns,
685
+ onDelete: resolveOnDelete({ explicit: relation.onDelete, required }),
686
+ onUpdate: resolveOnUpdate({ explicit: relation.onUpdate })
687
+ });
688
+ }
689
+ return result;
690
+ }
691
+ function normalizeJoinTable(params) {
692
+ const { pair, schema } = params;
693
+ return {
694
+ table: pair.tableName,
695
+ a: joinTableEnd({ modelName: pair.modelA, schema, joinTable: pair.tableName }),
696
+ b: joinTableEnd({ modelName: pair.modelB, schema, joinTable: pair.tableName })
697
+ };
698
+ }
699
+ function joinTableEnd(params) {
700
+ const { modelName, schema, joinTable } = params;
701
+ const model = schema.models.find((candidate) => candidate.name === modelName);
702
+ if (model === undefined) {
703
+ throw new VibeError4({
704
+ code: "VIBE_MIGRATION",
705
+ message: `implicit many-to-many table "${joinTable}" references unknown model "${modelName}"`,
706
+ meta: { joinTable, model: modelName }
707
+ });
708
+ }
709
+ const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) => model.fields.find((field) => field.name === name)) : model.fields.filter((field) => field.isId);
710
+ const pkField = pkFields[0];
711
+ if (pkFields.length !== 1 || pkField === undefined) {
712
+ throw new VibeError4({
713
+ code: "VIBE_MIGRATION",
714
+ message: `implicit many-to-many relations require a single-column primary key on "${modelName}" (join table "${joinTable}")`,
715
+ meta: { joinTable, model: modelName }
716
+ });
717
+ }
718
+ return {
719
+ table: model.dbName ?? model.name,
720
+ column: pkField.dbName ?? pkField.name,
721
+ field: pkField
722
+ };
723
+ }
724
+ function fieldDbName(params) {
725
+ const { model, name } = params;
726
+ const field = model.fields.find((candidate) => candidate.name === name);
727
+ return field === undefined ? name : field.dbName ?? field.name;
728
+ }
729
+
730
+ // packages/migrate/src/differ.ts
731
+ function diffSchemas(params) {
732
+ const dialect = params.dialect ?? "postgres";
733
+ if (dialect !== "postgres")
734
+ refuseScalarListColumns({ schema: params.to, dialect });
735
+ const from = normalizeSchema({ schema: params.from, dialect });
736
+ const to = normalizeSchema({ schema: params.to, dialect });
737
+ const buckets = {
738
+ dropView: [],
739
+ createEnum: [],
740
+ addEnumValue: [],
741
+ dropForeignKey: [],
742
+ dropIndex: [],
743
+ dropJoinTable: [],
744
+ dropTable: [],
745
+ createTable: [],
746
+ addColumn: [],
747
+ alterColumn: [],
748
+ dropColumn: [],
749
+ dropPrimaryKey: [],
750
+ addPrimaryKey: [],
751
+ createIndex: [],
752
+ addForeignKey: [],
753
+ createJoinTable: [],
754
+ dropEnum: []
755
+ };
756
+ if (dialect === "postgres")
757
+ diffEnums({ from, to, buckets });
758
+ diffViews({ from, to, buckets });
759
+ diffJoinTables({ from, to, buckets });
760
+ diffTables({ from, to, buckets, dialect });
761
+ return [
762
+ ...buckets.dropView,
763
+ ...buckets.createEnum,
764
+ ...buckets.addEnumValue,
765
+ ...buckets.dropForeignKey,
766
+ ...buckets.dropIndex,
767
+ ...buckets.dropJoinTable,
768
+ ...buckets.dropTable,
769
+ ...buckets.createTable,
770
+ ...buckets.addColumn,
771
+ ...buckets.alterColumn,
772
+ ...buckets.dropColumn,
773
+ ...buckets.dropPrimaryKey,
774
+ ...buckets.addPrimaryKey,
775
+ ...buckets.createIndex,
776
+ ...buckets.addForeignKey,
777
+ ...buckets.createJoinTable,
778
+ ...buckets.dropEnum
779
+ ];
780
+ }
781
+ function refuseScalarListColumns(params) {
782
+ const { schema, dialect } = params;
783
+ for (const model of schema.models) {
784
+ if (model.isView)
785
+ continue;
786
+ for (const field of model.fields) {
787
+ if (!field.isList || field.isUnsupported === true)
788
+ continue;
789
+ throw unsupportedCapability3({
790
+ dialect,
791
+ feature: `scalar list column "${model.name}.${field.name}"`,
792
+ hint: `${dialect} has no array column type \u2014 model the list as a separate table or a ${dialect === "mysql" ? "JSON" : "Json"} column`
793
+ });
794
+ }
795
+ }
796
+ }
797
+ function diffEnums(params) {
798
+ const { from, to, buckets } = params;
799
+ for (const [name, enumDef] of to.enums) {
800
+ const previous = from.enums.get(name);
801
+ if (previous === undefined) {
802
+ buckets.createEnum.push({ kind: "createEnum", enumDef, destructive: false });
803
+ continue;
804
+ }
805
+ const previousValues = new Set(previous.values);
806
+ const currentValues = new Set(enumDef.values);
807
+ for (const value of enumDef.values) {
808
+ if (!previousValues.has(value)) {
809
+ buckets.addEnumValue.push({ kind: "addEnumValue", enumName: name, value, destructive: false });
810
+ }
811
+ }
812
+ for (const value of previous.values) {
813
+ if (!currentValues.has(value)) {
814
+ throw unsupportedCapability3({
815
+ dialect: "postgres",
816
+ feature: `removing value "${value}" from enum "${name}"`,
817
+ hint: "postgres cannot drop enum values \u2014 recreate the type manually (create new type, ALTER COLUMN ... USING, drop old type)"
818
+ });
819
+ }
820
+ }
821
+ }
822
+ for (const [name] of from.enums) {
823
+ if (!to.enums.has(name)) {
824
+ buckets.dropEnum.push({ kind: "dropEnum", enumName: name, destructive: true });
825
+ }
826
+ }
827
+ }
828
+ function diffViews(params) {
829
+ const { from, to, buckets } = params;
830
+ for (const [name] of from.views) {
831
+ if (!to.views.has(name)) {
832
+ buckets.dropView.push({ kind: "dropView", table: name, destructive: true });
833
+ }
834
+ }
835
+ }
836
+ function diffJoinTables(params) {
837
+ const { from, to, buckets } = params;
838
+ for (const [table, joinTable] of to.joinTables) {
839
+ if (!from.joinTables.has(table)) {
840
+ buckets.createJoinTable.push({
841
+ kind: "createJoinTable",
842
+ table,
843
+ a: joinTable.a,
844
+ b: joinTable.b,
845
+ destructive: false
846
+ });
847
+ }
848
+ }
849
+ for (const [table] of from.joinTables) {
850
+ if (!to.joinTables.has(table)) {
851
+ buckets.dropJoinTable.push({ kind: "dropJoinTable", table, destructive: true });
852
+ }
853
+ }
854
+ }
855
+ function diffTables(params) {
856
+ const { from, to, buckets, dialect } = params;
857
+ for (const [name, table] of to.tables) {
858
+ const previous = from.tables.get(name);
859
+ if (previous === undefined)
860
+ createWholeTable({ table, buckets });
861
+ else
862
+ diffOneTable({
863
+ previous,
864
+ current: table,
865
+ buckets,
866
+ dialect,
867
+ fromEnums: from.enums,
868
+ toEnums: to.enums
869
+ });
870
+ }
871
+ for (const [name] of from.tables) {
872
+ if (to.tables.has(name))
873
+ continue;
874
+ buckets.dropTable.push({ kind: "dropTable", table: name, destructive: true });
875
+ }
876
+ }
877
+ function createWholeTable(params) {
878
+ const { table, buckets } = params;
879
+ buckets.createTable.push({
880
+ kind: "createTable",
881
+ table: table.table,
882
+ columns: [...table.columns.values()].map((column) => column.field),
883
+ primaryKey: table.pk,
884
+ destructive: false
885
+ });
886
+ for (const index of table.indexes.values()) {
887
+ buckets.createIndex.push({
888
+ kind: "createIndex",
889
+ table: table.table,
890
+ name: index.name,
891
+ indexKind: index.kind,
892
+ unique: index.unique,
893
+ columns: index.columns,
894
+ ...index.where === undefined ? {} : { where: index.where },
895
+ destructive: false
896
+ });
897
+ }
898
+ for (const fk of table.fks.values()) {
899
+ buckets.addForeignKey.push({
900
+ kind: "addForeignKey",
901
+ table: table.table,
902
+ constraintName: fk.constraintName,
903
+ columns: fk.columns,
904
+ refTable: fk.refTable,
905
+ refColumns: fk.refColumns,
906
+ onDelete: fk.onDelete,
907
+ onUpdate: fk.onUpdate,
908
+ destructive: false
909
+ });
910
+ }
911
+ }
912
+ function diffOneTable(params) {
913
+ const { previous, current, buckets, dialect, fromEnums, toEnums } = params;
914
+ const table = current.table;
915
+ for (const [name, column] of current.columns) {
916
+ const before = previous.columns.get(name);
917
+ if (before === undefined) {
918
+ buckets.addColumn.push({ kind: "addColumn", table, column: column.field, destructive: false });
919
+ continue;
920
+ }
921
+ const changes = [];
922
+ if (before.typeKey !== column.typeKey)
923
+ changes.push("type");
924
+ if (before.nullable !== column.nullable)
925
+ changes.push("nullability");
926
+ if (before.defaultKey !== column.defaultKey)
927
+ changes.push("default");
928
+ if (changes.length === 0)
929
+ continue;
930
+ let narrowingType = changes.includes("type") && !isScalarToArrayChange({ from: before.field, to: column.field });
931
+ if (dialect !== "postgres" && changes.includes("type") && isEnumType4(before.field.type) && isEnumType4(column.field.type)) {
932
+ const fromValues = resolveEnumValues({ name: before.field.type.enum, enums: fromEnums });
933
+ const toValues = resolveEnumValues({ name: column.field.type.enum, enums: toEnums });
934
+ if (fromValues !== undefined && toValues !== undefined) {
935
+ const removed = fromValues.filter((value) => !toValues.includes(value));
936
+ if (removed.length > 0) {
937
+ throw unsupportedCapability3({
938
+ dialect,
939
+ feature: `removing value(s) ${removed.map((value) => `"${value}"`).join(", ")} from enum column "${table}"."${name}"`,
940
+ hint: "migrate the stored values away first, then recreate the column with the narrower enum"
941
+ });
942
+ }
943
+ narrowingType = false;
944
+ }
945
+ }
946
+ buckets.alterColumn.push({
947
+ kind: "alterColumn",
948
+ table,
949
+ column: name,
950
+ from: before.field,
951
+ to: column.field,
952
+ changes,
953
+ destructive: narrowingType
954
+ });
955
+ }
956
+ for (const [name] of previous.columns) {
957
+ if (!current.columns.has(name)) {
958
+ buckets.dropColumn.push({ kind: "dropColumn", table, column: name, destructive: true });
959
+ }
960
+ }
961
+ const pkBefore = previous.pk.join(",");
962
+ const pkAfter = current.pk.join(",");
963
+ if (pkBefore !== pkAfter) {
964
+ if (previous.pk.length > 0) {
965
+ buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, destructive: false });
966
+ }
967
+ if (current.pk.length > 0) {
968
+ buckets.addPrimaryKey.push({
969
+ kind: "addPrimaryKey",
970
+ table,
971
+ columns: current.pk,
972
+ destructive: false
973
+ });
974
+ }
975
+ }
976
+ for (const [key, index] of current.indexes) {
977
+ if (previous.indexes.has(key))
978
+ continue;
979
+ buckets.createIndex.push({
980
+ kind: "createIndex",
981
+ table,
982
+ name: index.name,
983
+ indexKind: index.kind,
984
+ unique: index.unique,
985
+ columns: index.columns,
986
+ ...index.where === undefined ? {} : { where: index.where },
987
+ destructive: false
988
+ });
989
+ }
990
+ for (const [key, index] of previous.indexes) {
991
+ if (current.indexes.has(key))
992
+ continue;
993
+ buckets.dropIndex.push({ kind: "dropIndex", name: index.name, table, destructive: false });
994
+ }
995
+ for (const [key, fk] of current.fks) {
996
+ const before = previous.fks.get(key);
997
+ if (before === undefined) {
998
+ buckets.addForeignKey.push({
999
+ kind: "addForeignKey",
1000
+ table,
1001
+ constraintName: fk.constraintName,
1002
+ columns: fk.columns,
1003
+ refTable: fk.refTable,
1004
+ refColumns: fk.refColumns,
1005
+ onDelete: fk.onDelete,
1006
+ onUpdate: fk.onUpdate,
1007
+ destructive: false
1008
+ });
1009
+ continue;
1010
+ }
1011
+ if (before.onDelete !== fk.onDelete || before.onUpdate !== fk.onUpdate) {
1012
+ buckets.dropForeignKey.push({
1013
+ kind: "dropForeignKey",
1014
+ table,
1015
+ constraintName: before.constraintName,
1016
+ destructive: false
1017
+ });
1018
+ buckets.addForeignKey.push({
1019
+ kind: "addForeignKey",
1020
+ table,
1021
+ constraintName: fk.constraintName,
1022
+ columns: fk.columns,
1023
+ refTable: fk.refTable,
1024
+ refColumns: fk.refColumns,
1025
+ onDelete: fk.onDelete,
1026
+ onUpdate: fk.onUpdate,
1027
+ destructive: false
1028
+ });
1029
+ }
1030
+ }
1031
+ for (const [key, fk] of previous.fks) {
1032
+ if (current.fks.has(key))
1033
+ continue;
1034
+ buckets.dropForeignKey.push({
1035
+ kind: "dropForeignKey",
1036
+ table,
1037
+ constraintName: fk.constraintName,
1038
+ destructive: false
1039
+ });
1040
+ }
1041
+ }
1042
+ // packages/migrate/src/ddl/postgres.ts
1043
+ import { VibeError as VibeError5 } from "@vibeorm/schema";
1044
+ import { postgresDialect as postgresDialect2 } from "@vibeorm/sql";
1045
+ function renderPostgresSteps(params) {
1046
+ return params.steps.flatMap((step) => renderStep({ step }));
1047
+ }
1048
+ function renderStep(params) {
1049
+ const { step } = params;
1050
+ switch (step.kind) {
1051
+ case "createEnum": {
1052
+ const name = step.enumDef.dbName ?? step.enumDef.name;
1053
+ const values = step.enumDef.values.map((value) => quotePgString({ value })).join(", ");
1054
+ return [`CREATE TYPE ${q(name)} AS ENUM (${values});`];
1055
+ }
1056
+ case "dropEnum":
1057
+ return [`DROP TYPE ${q(step.enumName)};`];
1058
+ case "addEnumValue":
1059
+ return [
1060
+ `ALTER TYPE ${q(step.enumName)} ADD VALUE IF NOT EXISTS ${quotePgString({ value: step.value })};`
1061
+ ];
1062
+ case "createTable": {
1063
+ const lines = step.columns.map((column) => ` ${columnDefSql({ field: column })}`);
1064
+ if (step.primaryKey.length > 0) {
1065
+ const pkCols = step.primaryKey.map((column) => q(column)).join(", ");
1066
+ lines.push(` CONSTRAINT ${q(`${step.table}_pkey`)} PRIMARY KEY (${pkCols})`);
1067
+ }
1068
+ return [`CREATE TABLE ${q(step.table)} (
1069
+ ${lines.join(`,
1070
+ `)}
1071
+ );`];
1072
+ }
1073
+ case "dropTable":
1074
+ case "dropJoinTable":
1075
+ return [`DROP TABLE ${q(step.table)} CASCADE;`];
1076
+ case "dropView":
1077
+ return [`DROP VIEW IF EXISTS ${q(step.table)};`];
1078
+ case "addColumn":
1079
+ return [`ALTER TABLE ${q(step.table)} ADD COLUMN ${columnDefSql({ field: step.column })};`];
1080
+ case "dropColumn":
1081
+ return [`ALTER TABLE ${q(step.table)} DROP COLUMN ${q(step.column)};`];
1082
+ case "alterColumn":
1083
+ return renderAlterColumn({ step });
1084
+ case "createIndex": {
1085
+ const unique = step.unique ? "UNIQUE " : "";
1086
+ const using = !step.unique && step.indexKind !== "btree" ? ` USING ${step.indexKind}` : "";
1087
+ const cols = step.columns.map((column) => `${q(column.name)}${column.desc ? " DESC" : ""}`).join(", ");
1088
+ const where = step.where === undefined ? "" : ` WHERE ${step.where}`;
1089
+ return [
1090
+ `CREATE ${unique}INDEX ${q(step.name)} ON ${q(step.table)}${using} (${cols})${where};`
1091
+ ];
1092
+ }
1093
+ case "dropIndex":
1094
+ return [`DROP INDEX ${q(step.name)};`];
1095
+ case "addForeignKey": {
1096
+ const cols = step.columns.map((column) => q(column)).join(", ");
1097
+ const refCols = step.refColumns.map((column) => q(column)).join(", ");
1098
+ return [
1099
+ `ALTER TABLE ${q(step.table)} ADD CONSTRAINT ${q(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${q(step.refTable)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]};`
1100
+ ];
1101
+ }
1102
+ case "dropForeignKey":
1103
+ return [`ALTER TABLE ${q(step.table)} DROP CONSTRAINT ${q(step.constraintName)};`];
1104
+ case "createJoinTable": {
1105
+ const aType = columnTypeSql({ field: { ...step.a.field, isList: false } });
1106
+ const bType = columnTypeSql({ field: { ...step.b.field, isList: false } });
1107
+ return [
1108
+ `CREATE TABLE ${q(step.table)} (
1109
+ "A" ${aType} NOT NULL,
1110
+ "B" ${bType} NOT NULL
1111
+ );`,
1112
+ `CREATE UNIQUE INDEX ${q(`${step.table}_AB_unique`)} ON ${q(step.table)} ("A", "B");`,
1113
+ `CREATE INDEX ${q(`${step.table}_B_index`)} ON ${q(step.table)} ("B");`,
1114
+ `ALTER TABLE ${q(step.table)} ADD CONSTRAINT ${q(`${step.table}_A_fkey`)} FOREIGN KEY ("A") REFERENCES ${q(step.a.table)} (${q(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
1115
+ `ALTER TABLE ${q(step.table)} ADD CONSTRAINT ${q(`${step.table}_B_fkey`)} FOREIGN KEY ("B") REFERENCES ${q(step.b.table)} (${q(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
1116
+ ];
1117
+ }
1118
+ case "addPrimaryKey": {
1119
+ const cols = step.columns.map((column) => q(column)).join(", ");
1120
+ return [
1121
+ `ALTER TABLE ${q(step.table)} ADD CONSTRAINT ${q(`${step.table}_pkey`)} PRIMARY KEY (${cols});`
1122
+ ];
1123
+ }
1124
+ case "dropPrimaryKey":
1125
+ return [`ALTER TABLE ${q(step.table)} DROP CONSTRAINT ${q(`${step.table}_pkey`)};`];
1126
+ }
1127
+ }
1128
+ function columnDefSql(params) {
1129
+ const { field } = params;
1130
+ const name = q(field.dbName ?? field.name);
1131
+ if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
1132
+ throw new VibeError5({
1133
+ code: "VIBE_MIGRATION",
1134
+ message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
1135
+ meta: { field: field.name }
1136
+ });
1137
+ }
1138
+ let def = `${name} ${columnTypeSql({ field })}`;
1139
+ if (isIdentityColumn({ field }))
1140
+ def += " GENERATED BY DEFAULT AS IDENTITY";
1141
+ if (!field.isOptional)
1142
+ def += " NOT NULL";
1143
+ const defaultExpr = defaultSql({ field });
1144
+ if (defaultExpr !== undefined)
1145
+ def += ` DEFAULT ${defaultExpr}`;
1146
+ return def;
1147
+ }
1148
+ function renderAlterColumn(params) {
1149
+ const { step } = params;
1150
+ const statements = [];
1151
+ const column = q(step.column);
1152
+ const alterPrefix = `ALTER TABLE ${q(step.table)} ALTER COLUMN ${column}`;
1153
+ if (step.changes.includes("type")) {
1154
+ const scalarToArray = step.to.isList && !step.from.isList ? ` USING CASE WHEN ${column} IS NULL THEN NULL ELSE ARRAY[${column}] END` : "";
1155
+ statements.push(`${alterPrefix} SET DATA TYPE ${columnTypeSql({ field: step.to })}${scalarToArray};`);
1156
+ }
1157
+ if (step.changes.includes("nullability")) {
1158
+ statements.push(`${alterPrefix} ${step.to.isOptional ? "DROP" : "SET"} NOT NULL;`);
1159
+ }
1160
+ if (step.changes.includes("default")) {
1161
+ if (step.to.default?.kind === "autoincrement") {
1162
+ throw new VibeError5({
1163
+ code: "VIBE_MIGRATION",
1164
+ message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
1165
+ meta: { table: step.table, column: step.column }
1166
+ });
1167
+ }
1168
+ const defaultExpr = defaultSql({ field: step.to });
1169
+ statements.push(defaultExpr === undefined ? `${alterPrefix} DROP DEFAULT;` : `${alterPrefix} SET DEFAULT ${defaultExpr};`);
1170
+ }
1171
+ return statements;
1172
+ }
1173
+ function q(name) {
1174
+ return postgresDialect2.quoteIdent({ name });
1175
+ }
1176
+
1177
+ // packages/migrate/src/ddl/sqlite.ts
1178
+ import { VibeError as VibeError6, isEnumType as isEnumType5, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
1179
+ import { sqliteDialect } from "@vibeorm/sql";
1180
+
1181
+ // packages/migrate/src/ddl/drop-order.ts
1182
+ function orderDropsChildrenFirst(params) {
1183
+ const { names, fromNorm } = params;
1184
+ if (fromNorm === undefined || names.length < 2)
1185
+ return [...names];
1186
+ const references = new Map;
1187
+ for (const name of names) {
1188
+ const table = fromNorm.tables.get(name);
1189
+ if (table !== undefined) {
1190
+ references.set(name, new Set([...table.fks.values()].map((fk) => fk.refTable)));
1191
+ continue;
1192
+ }
1193
+ const joinTable = fromNorm.joinTables.get(name);
1194
+ if (joinTable !== undefined) {
1195
+ references.set(name, new Set([joinTable.a.table, joinTable.b.table]));
1196
+ continue;
1197
+ }
1198
+ references.set(name, new Set);
1199
+ }
1200
+ const remaining = new Set(names);
1201
+ const result = [];
1202
+ while (remaining.size > 0) {
1203
+ let progressed = false;
1204
+ for (const name of names) {
1205
+ if (!remaining.has(name))
1206
+ continue;
1207
+ const stillReferenced = [...remaining].some((other) => other !== name && (references.get(other) ?? new Set).has(name));
1208
+ if (!stillReferenced) {
1209
+ result.push(name);
1210
+ remaining.delete(name);
1211
+ progressed = true;
1212
+ }
1213
+ }
1214
+ if (!progressed) {
1215
+ for (const name of names)
1216
+ if (remaining.has(name))
1217
+ result.push(name);
1218
+ break;
1219
+ }
1220
+ }
1221
+ return result;
1222
+ }
1223
+
1224
+ // packages/migrate/src/ddl/sqlite.ts
1225
+ function renderSqliteSteps(params) {
1226
+ const { steps } = params;
1227
+ const fromNorm = params.from === undefined ? undefined : normalizeSchema({ schema: params.from, dialect: "sqlite" });
1228
+ const toNorm = params.to === undefined ? undefined : normalizeSchema({ schema: params.to, dialect: "sqlite" });
1229
+ const enums = toNorm?.enums ?? new Map;
1230
+ const createdTables = new Set;
1231
+ for (const step of steps) {
1232
+ if (step.kind === "createTable" || step.kind === "createJoinTable") {
1233
+ createdTables.add(step.table);
1234
+ }
1235
+ }
1236
+ const foldedFks = new Map;
1237
+ const rebuildTables = [];
1238
+ const markRebuild = (table) => {
1239
+ if (!rebuildTables.includes(table))
1240
+ rebuildTables.push(table);
1241
+ };
1242
+ for (const step of steps) {
1243
+ switch (step.kind) {
1244
+ case "alterColumn":
1245
+ case "dropForeignKey":
1246
+ case "addPrimaryKey":
1247
+ case "dropPrimaryKey":
1248
+ markRebuild(step.table);
1249
+ break;
1250
+ case "addColumn":
1251
+ if (!isCheapAddColumn({ field: step.column }))
1252
+ markRebuild(step.table);
1253
+ break;
1254
+ case "dropColumn":
1255
+ if (!canCheapDropColumn({ table: step.table, column: step.column, fromNorm })) {
1256
+ markRebuild(step.table);
1257
+ }
1258
+ break;
1259
+ case "addForeignKey":
1260
+ if (createdTables.has(step.table)) {
1261
+ const list = foldedFks.get(step.table);
1262
+ if (list === undefined)
1263
+ foldedFks.set(step.table, [step]);
1264
+ else
1265
+ list.push(step);
1266
+ } else {
1267
+ markRebuild(step.table);
1268
+ }
1269
+ break;
1270
+ default:
1271
+ break;
1272
+ }
1273
+ }
1274
+ const rebuildSet = new Set(rebuildTables);
1275
+ const dropNames = steps.filter((step) => step.kind === "dropTable" || step.kind === "dropJoinTable").map((step) => step.table);
1276
+ const orderedDrops = orderDropsChildrenFirst({ names: dropNames, fromNorm });
1277
+ let dropCursor = 0;
1278
+ const statements = [];
1279
+ for (const step of steps) {
1280
+ switch (step.kind) {
1281
+ case "createEnum":
1282
+ case "dropEnum":
1283
+ case "addEnumValue":
1284
+ break;
1285
+ case "createTable":
1286
+ statements.push(createTableSql({
1287
+ table: step.table,
1288
+ constraintTable: step.table,
1289
+ columns: step.columns,
1290
+ primaryKey: step.primaryKey,
1291
+ fks: (foldedFks.get(step.table) ?? []).map((fk) => ({
1292
+ constraintName: fk.constraintName,
1293
+ columns: fk.columns,
1294
+ refTable: fk.refTable,
1295
+ refColumns: fk.refColumns,
1296
+ onDelete: CASCADE_ACTION_SQL[fk.onDelete],
1297
+ onUpdate: CASCADE_ACTION_SQL[fk.onUpdate]
1298
+ })),
1299
+ enums
1300
+ }));
1301
+ break;
1302
+ case "createJoinTable": {
1303
+ const aType = sqliteStorageType({ field: { ...step.a.field, isList: false } });
1304
+ const bType = sqliteStorageType({ field: { ...step.b.field, isList: false } });
1305
+ statements.push(`CREATE TABLE ${q2(step.table)} (
1306
+ "A" ${aType} NOT NULL,
1307
+ "B" ${bType} NOT NULL,
1308
+ CONSTRAINT ${q2(`${step.table}_A_fkey`)} FOREIGN KEY ("A") REFERENCES ${q2(step.a.table)} (${q2(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE,
1309
+ CONSTRAINT ${q2(`${step.table}_B_fkey`)} FOREIGN KEY ("B") REFERENCES ${q2(step.b.table)} (${q2(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE
1310
+ );`, `CREATE UNIQUE INDEX ${q2(`${step.table}_AB_unique`)} ON ${q2(step.table)} ("A", "B");`, `CREATE INDEX ${q2(`${step.table}_B_index`)} ON ${q2(step.table)} ("B");`);
1311
+ break;
1312
+ }
1313
+ case "addColumn":
1314
+ if (!rebuildSet.has(step.table)) {
1315
+ statements.push(`ALTER TABLE ${q2(step.table)} ADD COLUMN ${columnDefSql2({
1316
+ field: step.column,
1317
+ constraintTable: step.table,
1318
+ enums,
1319
+ inlinePkAutoincrement: false
1320
+ })};`);
1321
+ }
1322
+ break;
1323
+ case "dropColumn":
1324
+ if (!rebuildSet.has(step.table)) {
1325
+ statements.push(`ALTER TABLE ${q2(step.table)} DROP COLUMN ${q2(step.column)};`);
1326
+ }
1327
+ break;
1328
+ case "createIndex":
1329
+ if (!rebuildSet.has(step.table)) {
1330
+ statements.push(createIndexSql({
1331
+ table: step.table,
1332
+ name: step.name,
1333
+ unique: step.unique,
1334
+ kind: step.indexKind,
1335
+ columns: step.columns,
1336
+ where: step.where
1337
+ }));
1338
+ }
1339
+ break;
1340
+ case "dropIndex":
1341
+ statements.push(`DROP INDEX ${q2(step.name)};`);
1342
+ break;
1343
+ case "dropTable":
1344
+ case "dropJoinTable":
1345
+ statements.push(`DROP TABLE ${q2(orderedDrops[dropCursor])};`);
1346
+ dropCursor += 1;
1347
+ break;
1348
+ case "dropView":
1349
+ statements.push(`DROP VIEW IF EXISTS ${q2(step.table)};`);
1350
+ break;
1351
+ case "alterColumn":
1352
+ case "addForeignKey":
1353
+ case "dropForeignKey":
1354
+ case "addPrimaryKey":
1355
+ case "dropPrimaryKey":
1356
+ if (step.kind === "addForeignKey" && createdTables.has(step.table) || !rebuildSet.has(step.table)) {
1357
+ break;
1358
+ }
1359
+ requireSchemas({ step, fromNorm, toNorm });
1360
+ break;
1361
+ }
1362
+ }
1363
+ for (const table of rebuildTables) {
1364
+ const step = steps.find((candidate) => ("table" in candidate) && candidate.table === table);
1365
+ requireSchemas({ step, fromNorm, toNorm });
1366
+ statements.push(...renderTableRebuild({
1367
+ table,
1368
+ from: fromNorm,
1369
+ to: toNorm
1370
+ }));
1371
+ }
1372
+ return statements;
1373
+ }
1374
+ function renderTableRebuild(params) {
1375
+ const { table, from, to } = params;
1376
+ const source = from.tables.get(table);
1377
+ const target = to.tables.get(table);
1378
+ if (source === undefined || target === undefined) {
1379
+ throw new VibeError6({
1380
+ code: "VIBE_MIGRATION",
1381
+ message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
1382
+ meta: { table }
1383
+ });
1384
+ }
1385
+ const tmp = `${table}_new`;
1386
+ const statements = ["PRAGMA foreign_keys=OFF;", "BEGIN;"];
1387
+ statements.push(createTableSql({
1388
+ table: tmp,
1389
+ constraintTable: table,
1390
+ columns: [...target.columns.values()].map((column) => column.field),
1391
+ primaryKey: target.pk,
1392
+ fks: [...target.fks.values()].map((fk) => ({
1393
+ constraintName: fk.constraintName,
1394
+ columns: fk.columns,
1395
+ refTable: fk.refTable,
1396
+ refColumns: fk.refColumns,
1397
+ onDelete: CASCADE_ACTION_SQL[fk.onDelete],
1398
+ onUpdate: CASCADE_ACTION_SQL[fk.onUpdate]
1399
+ })),
1400
+ enums: to.enums
1401
+ }));
1402
+ const common = [...target.columns.keys()].filter((name) => source.columns.has(name));
1403
+ if (common.length > 0) {
1404
+ const cols = common.map((name) => q2(name)).join(", ");
1405
+ statements.push(`INSERT INTO ${q2(tmp)} (${cols}) SELECT ${cols} FROM ${q2(table)};`);
1406
+ }
1407
+ statements.push(`DROP TABLE ${q2(table)};`, `ALTER TABLE ${q2(tmp)} RENAME TO ${q2(table)};`);
1408
+ for (const index of target.indexes.values()) {
1409
+ statements.push(createIndexSql({
1410
+ table,
1411
+ name: index.name,
1412
+ unique: index.unique,
1413
+ kind: index.kind,
1414
+ columns: index.columns,
1415
+ where: index.where
1416
+ }));
1417
+ }
1418
+ statements.push("PRAGMA foreign_key_check;", "COMMIT;", "PRAGMA foreign_keys=ON;");
1419
+ return statements;
1420
+ }
1421
+ function isCheapAddColumn(params) {
1422
+ const { field } = params;
1423
+ const kind = field.default?.kind;
1424
+ if (kind === "autoincrement" || kind === "now" || kind === "dbgenerated")
1425
+ return false;
1426
+ const hasLiteralDefault = kind === "literal" || kind === "enumValue";
1427
+ if (!field.isOptional && !hasLiteralDefault)
1428
+ return false;
1429
+ return true;
1430
+ }
1431
+ function canCheapDropColumn(params) {
1432
+ const { table, column, fromNorm } = params;
1433
+ if (fromNorm === undefined)
1434
+ return false;
1435
+ const fromTable = fromNorm.tables.get(table);
1436
+ if (fromTable === undefined)
1437
+ return false;
1438
+ if (fromTable.pk.includes(column))
1439
+ return false;
1440
+ const normalized = fromTable.columns.get(column);
1441
+ if (normalized !== undefined && isEnumType5(normalized.field.type))
1442
+ return false;
1443
+ for (const index of fromTable.indexes.values()) {
1444
+ if (index.columns.some((indexColumn) => indexColumn.name === column))
1445
+ return false;
1446
+ if (index.where !== undefined && index.where.includes(column))
1447
+ return false;
1448
+ }
1449
+ for (const fk of fromTable.fks.values()) {
1450
+ if (fk.columns.includes(column))
1451
+ return false;
1452
+ }
1453
+ for (const other of fromNorm.tables.values()) {
1454
+ for (const fk of other.fks.values()) {
1455
+ if (fk.refTable === table && fk.refColumns.includes(column))
1456
+ return false;
1457
+ }
1458
+ }
1459
+ return true;
1460
+ }
1461
+ function createTableSql(params) {
1462
+ const { table, constraintTable, columns, primaryKey, fks, enums } = params;
1463
+ const autoincrementPk = autoincrementPkColumn({ columns, primaryKey });
1464
+ const lines = columns.map((field) => ` ${columnDefSql2({
1465
+ field,
1466
+ constraintTable,
1467
+ enums,
1468
+ inlinePkAutoincrement: (field.dbName ?? field.name) === autoincrementPk
1469
+ })}`);
1470
+ if (primaryKey.length > 0 && autoincrementPk === undefined) {
1471
+ lines.push(` PRIMARY KEY (${primaryKey.map((column) => q2(column)).join(", ")})`);
1472
+ }
1473
+ for (const fk of fks) {
1474
+ lines.push(` CONSTRAINT ${q2(fk.constraintName)} FOREIGN KEY (${fk.columns.map((column) => q2(column)).join(", ")}) REFERENCES ${q2(fk.refTable)} (${fk.refColumns.map((column) => q2(column)).join(", ")}) ON DELETE ${fk.onDelete} ON UPDATE ${fk.onUpdate}`);
1475
+ }
1476
+ return `CREATE TABLE ${q2(table)} (
1477
+ ${lines.join(`,
1478
+ `)}
1479
+ );`;
1480
+ }
1481
+ function autoincrementPkColumn(params) {
1482
+ const { columns, primaryKey } = params;
1483
+ if (primaryKey.length !== 1)
1484
+ return;
1485
+ const pkName = primaryKey[0];
1486
+ const field = columns.find((candidate) => (candidate.dbName ?? candidate.name) === pkName);
1487
+ if (field === undefined)
1488
+ return;
1489
+ const eligible = field.default?.kind === "autoincrement" && !field.isList && !isEnumType5(field.type) && (field.type === "Int" || field.type === "BigInt");
1490
+ return eligible ? pkName : undefined;
1491
+ }
1492
+ function columnDefSql2(params) {
1493
+ const { field, constraintTable, enums, inlinePkAutoincrement } = params;
1494
+ const name = field.dbName ?? field.name;
1495
+ if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
1496
+ throw new VibeError6({
1497
+ code: "VIBE_MIGRATION",
1498
+ message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
1499
+ meta: { field: field.name }
1500
+ });
1501
+ }
1502
+ if (inlinePkAutoincrement) {
1503
+ return `${q2(name)} INTEGER PRIMARY KEY AUTOINCREMENT`;
1504
+ }
1505
+ let def = `${q2(name)} ${sqliteStorageType({ field })}`;
1506
+ if (!field.isOptional)
1507
+ def += " NOT NULL";
1508
+ const defaultExpr = sqliteDefaultSql({ field });
1509
+ if (defaultExpr !== undefined)
1510
+ def += ` DEFAULT ${defaultExpr}`;
1511
+ if (isEnumType5(field.type) && field.isUnsupported !== true) {
1512
+ const enumName = field.type.enum;
1513
+ const values = resolveEnumValues({ name: enumName, enums });
1514
+ if (values === undefined) {
1515
+ throw new VibeError6({
1516
+ code: "VIBE_MIGRATION",
1517
+ message: `enum "${enumName}" on column "${field.name}" is not declared in the schema \u2014 sqlite rendering needs the target schema's enums for the CHECK constraint`,
1518
+ meta: { field: field.name, enum: enumName }
1519
+ });
1520
+ }
1521
+ const check = sqliteEnumCheckName({ table: constraintTable, column: name, enumName });
1522
+ def += ` CONSTRAINT ${q2(check)} CHECK (${q2(name)} IN (${values.map((value) => quotePgString({ value })).join(", ")}))`;
1523
+ }
1524
+ return def;
1525
+ }
1526
+ function createIndexSql(params) {
1527
+ const { table, name, unique, kind, columns, where } = params;
1528
+ if (!unique && kind !== "btree") {
1529
+ throw unsupportedCapability4({
1530
+ dialect: "sqlite",
1531
+ feature: `index kind "${kind}" (index "${name}")`,
1532
+ hint: "sqlite indexes are btree only; use an extension-specific virtual table for others"
1533
+ });
1534
+ }
1535
+ const cols = columns.map((column) => `${column.decimal === true ? `CAST(${q2(column.name)} AS REAL)` : q2(column.name)}${column.desc ? " DESC" : ""}`).join(", ");
1536
+ const whereClause = where === undefined ? "" : ` WHERE ${where}`;
1537
+ return `CREATE ${unique ? "UNIQUE " : ""}INDEX ${q2(name)} ON ${q2(table)} (${cols})${whereClause};`;
1538
+ }
1539
+ function requireSchemas(params) {
1540
+ const { step, fromNorm, toNorm } = params;
1541
+ if (fromNorm !== undefined && toNorm !== undefined)
1542
+ return;
1543
+ throw new VibeError6({
1544
+ code: "VIBE_MIGRATION",
1545
+ message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
1546
+ meta: { step: step.kind }
1547
+ });
1548
+ }
1549
+ function q2(name) {
1550
+ return sqliteDialect.quoteIdent({ name });
1551
+ }
1552
+
1553
+ // packages/migrate/src/ddl/mysql.ts
1554
+ import { VibeError as VibeError7, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
1555
+ import { mysqlDialect } from "@vibeorm/sql";
1556
+ function renderMysqlSteps(params) {
1557
+ const enums = new Map;
1558
+ for (const enumDef of params.to?.enums ?? []) {
1559
+ enums.set(enumDef.dbName ?? enumDef.name, enumDef);
1560
+ }
1561
+ const dropNames = params.steps.filter((step) => step.kind === "dropTable" || step.kind === "dropJoinTable").map((step) => step.table);
1562
+ const fromNorm = params.from === undefined ? undefined : normalizeSchema({ schema: params.from, dialect: "mysql" });
1563
+ const orderedDrops = orderDropsChildrenFirst({ names: dropNames, fromNorm });
1564
+ let dropCursor = 0;
1565
+ return params.steps.flatMap((step) => {
1566
+ if (step.kind === "dropTable" || step.kind === "dropJoinTable") {
1567
+ const table = orderedDrops[dropCursor];
1568
+ dropCursor += 1;
1569
+ return [`DROP TABLE ${q3(table)};`];
1570
+ }
1571
+ return renderStep2({ step, enums });
1572
+ });
1573
+ }
1574
+ function renderStep2(params) {
1575
+ const { step, enums } = params;
1576
+ switch (step.kind) {
1577
+ case "createEnum":
1578
+ case "dropEnum":
1579
+ case "addEnumValue":
1580
+ return [];
1581
+ case "createTable": {
1582
+ const lines = step.columns.map((column) => ` ${columnDefSql3({ field: column, enums })}`);
1583
+ if (step.primaryKey.length > 0) {
1584
+ lines.push(` PRIMARY KEY (${step.primaryKey.map((column) => q3(column)).join(", ")})`);
1585
+ }
1586
+ return [`CREATE TABLE ${q3(step.table)} (
1587
+ ${lines.join(`,
1588
+ `)}
1589
+ );`];
1590
+ }
1591
+ case "dropTable":
1592
+ case "dropJoinTable":
1593
+ return [`DROP TABLE ${q3(step.table)};`];
1594
+ case "dropView":
1595
+ return [`DROP VIEW IF EXISTS ${q3(step.table)};`];
1596
+ case "addColumn":
1597
+ return [
1598
+ `ALTER TABLE ${q3(step.table)} ADD COLUMN ${columnDefSql3({ field: step.column, enums })};`
1599
+ ];
1600
+ case "dropColumn":
1601
+ return [`ALTER TABLE ${q3(step.table)} DROP COLUMN ${q3(step.column)};`];
1602
+ case "alterColumn":
1603
+ return renderAlterColumn2({ step, enums });
1604
+ case "createIndex": {
1605
+ if (step.where !== undefined) {
1606
+ throw unsupportedCapability5({
1607
+ dialect: "mysql",
1608
+ feature: `partial index "${step.name}"`,
1609
+ hint: "mysql has no partial indexes \u2014 drop the WHERE predicate or gate the index per dialect"
1610
+ });
1611
+ }
1612
+ if (!step.unique && step.indexKind !== "btree") {
1613
+ throw unsupportedCapability5({
1614
+ dialect: "mysql",
1615
+ feature: `index kind "${step.indexKind}" (index "${step.name}")`,
1616
+ hint: "mysql secondary indexes are btree only"
1617
+ });
1618
+ }
1619
+ const cols = step.columns.map((column) => `${q3(column.name)}${column.desc ? " DESC" : ""}`).join(", ");
1620
+ return [
1621
+ `CREATE ${step.unique ? "UNIQUE " : ""}INDEX ${q3(step.name)} ON ${q3(step.table)} (${cols});`
1622
+ ];
1623
+ }
1624
+ case "dropIndex": {
1625
+ if (step.table === undefined) {
1626
+ throw new VibeError7({
1627
+ code: "VIBE_MIGRATION",
1628
+ message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
1629
+ meta: { index: step.name }
1630
+ });
1631
+ }
1632
+ return [`DROP INDEX ${q3(step.name)} ON ${q3(step.table)};`];
1633
+ }
1634
+ case "addForeignKey": {
1635
+ const cols = step.columns.map((column) => q3(column)).join(", ");
1636
+ const refCols = step.refColumns.map((column) => q3(column)).join(", ");
1637
+ return [
1638
+ `ALTER TABLE ${q3(step.table)} ADD CONSTRAINT ${q3(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${q3(step.refTable)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]};`
1639
+ ];
1640
+ }
1641
+ case "dropForeignKey":
1642
+ return [`ALTER TABLE ${q3(step.table)} DROP FOREIGN KEY ${q3(step.constraintName)};`];
1643
+ case "createJoinTable": {
1644
+ const aType = mysqlColumnTypeSql({ field: { ...step.a.field, isList: false }, enums });
1645
+ const bType = mysqlColumnTypeSql({ field: { ...step.b.field, isList: false }, enums });
1646
+ return [
1647
+ `CREATE TABLE ${q3(step.table)} (
1648
+ \`A\` ${aType} NOT NULL,
1649
+ \`B\` ${bType} NOT NULL
1650
+ );`,
1651
+ `CREATE UNIQUE INDEX ${q3(`${step.table}_AB_unique`)} ON ${q3(step.table)} (\`A\`, \`B\`);`,
1652
+ `CREATE INDEX ${q3(`${step.table}_B_index`)} ON ${q3(step.table)} (\`B\`);`,
1653
+ `ALTER TABLE ${q3(step.table)} ADD CONSTRAINT ${q3(`${step.table}_A_fkey`)} FOREIGN KEY (\`A\`) REFERENCES ${q3(step.a.table)} (${q3(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
1654
+ `ALTER TABLE ${q3(step.table)} ADD CONSTRAINT ${q3(`${step.table}_B_fkey`)} FOREIGN KEY (\`B\`) REFERENCES ${q3(step.b.table)} (${q3(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
1655
+ ];
1656
+ }
1657
+ case "addPrimaryKey":
1658
+ return [
1659
+ `ALTER TABLE ${q3(step.table)} ADD PRIMARY KEY (${step.columns.map((column) => q3(column)).join(", ")});`
1660
+ ];
1661
+ case "dropPrimaryKey":
1662
+ return [`ALTER TABLE ${q3(step.table)} DROP PRIMARY KEY;`];
1663
+ }
1664
+ }
1665
+ function columnDefSql3(params) {
1666
+ const { field, enums } = params;
1667
+ const name = q3(field.dbName ?? field.name);
1668
+ const autoincrement = field.default?.kind === "autoincrement";
1669
+ if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
1670
+ throw new VibeError7({
1671
+ code: "VIBE_MIGRATION",
1672
+ message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
1673
+ meta: { field: field.name }
1674
+ });
1675
+ }
1676
+ let def = `${name} ${mysqlColumnTypeSql({ field, enums })}`;
1677
+ if (!field.isOptional)
1678
+ def += " NOT NULL";
1679
+ if (autoincrement)
1680
+ def += " AUTO_INCREMENT";
1681
+ const defaultExpr = mysqlDefaultSql({ field });
1682
+ if (defaultExpr !== undefined)
1683
+ def += ` DEFAULT ${defaultExpr}`;
1684
+ return def;
1685
+ }
1686
+ function renderAlterColumn2(params) {
1687
+ const { step, enums } = params;
1688
+ if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
1689
+ throw new VibeError7({
1690
+ code: "VIBE_MIGRATION",
1691
+ message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
1692
+ meta: { table: step.table, column: step.column }
1693
+ });
1694
+ }
1695
+ if (step.changes.includes("type") || step.changes.includes("nullability")) {
1696
+ return [
1697
+ `ALTER TABLE ${q3(step.table)} MODIFY COLUMN ${columnDefSql3({ field: step.to, enums })};`
1698
+ ];
1699
+ }
1700
+ const defaultExpr = mysqlDefaultSql({ field: step.to });
1701
+ return [
1702
+ defaultExpr === undefined ? `ALTER TABLE ${q3(step.table)} ALTER COLUMN ${q3(step.column)} DROP DEFAULT;` : `ALTER TABLE ${q3(step.table)} ALTER COLUMN ${q3(step.column)} SET DEFAULT ${defaultExpr};`
1703
+ ];
1704
+ }
1705
+ function q3(name) {
1706
+ return mysqlDialect.quoteIdent({ name });
1707
+ }
1708
+
1709
+ // packages/migrate/src/ddl/render.ts
1710
+ function renderSteps(params) {
1711
+ const { steps, dialect, from, to } = params;
1712
+ switch (dialect) {
1713
+ case "postgres":
1714
+ return renderPostgresSteps({ steps });
1715
+ case "sqlite":
1716
+ return renderSqliteSteps({
1717
+ steps,
1718
+ ...from === undefined ? {} : { from },
1719
+ ...to === undefined ? {} : { to }
1720
+ });
1721
+ case "mysql":
1722
+ return renderMysqlSteps({
1723
+ steps,
1724
+ ...from === undefined ? {} : { from },
1725
+ ...to === undefined ? {} : { to }
1726
+ });
1727
+ }
1728
+ }
1729
+ // packages/migrate/src/runner.ts
1730
+ import { createHash } from "crypto";
1731
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
1732
+ import { DIALECT_CAPABILITIES, getSqlDialect } from "@vibeorm/sql";
1733
+
1734
+ // packages/migrate/src/extensions.ts
1735
+ import { extensionDeclarations } from "@vibeorm/schema";
1736
+ function collectExtensionArtifacts(params) {
1737
+ const { schema, dialect, extensions } = params;
1738
+ const setup = [];
1739
+ const objects = [];
1740
+ for (const extension of extensions) {
1741
+ const artifactsHook = extension.migrate?.artifacts;
1742
+ if (artifactsHook === undefined)
1743
+ continue;
1744
+ const declarations = extensionDeclarations({ schema, name: extension.name });
1745
+ if (Object.keys(declarations).length === 0 && extension.schema?.configOnly !== true)
1746
+ continue;
1747
+ for (const artifact of artifactsHook({
1748
+ schema,
1749
+ dialect,
1750
+ provider: schema.datasource.provider,
1751
+ options: extension.options,
1752
+ declarations
1753
+ })) {
1754
+ (artifact.owns.kind === "postgresExtension" ? setup : objects).push(artifact);
1755
+ }
1756
+ }
1757
+ return { setup, objects };
1758
+ }
1759
+ function ownedTableNames(params) {
1760
+ const exact = new Set;
1761
+ const prefixes = [];
1762
+ for (const artifact of params.artifacts) {
1763
+ if (artifact.owns.kind !== "table")
1764
+ continue;
1765
+ exact.add(artifact.owns.name);
1766
+ prefixes.push(...artifact.owns.auxiliaryPrefixes ?? []);
1767
+ }
1768
+ return { exact, prefixes };
1769
+ }
1770
+ function stripExtensionArtifacts(params) {
1771
+ const { schema, artifacts } = params;
1772
+ if (artifacts.length === 0)
1773
+ return schema;
1774
+ const tables = ownedTableNames({ artifacts });
1775
+ const ownedColumns = new Map;
1776
+ const ownedIndexes = new Map;
1777
+ for (const artifact of artifacts) {
1778
+ if (artifact.owns.kind === "column") {
1779
+ const set = ownedColumns.get(artifact.owns.table) ?? new Set;
1780
+ set.add(artifact.owns.column);
1781
+ ownedColumns.set(artifact.owns.table, set);
1782
+ } else if (artifact.owns.kind === "index") {
1783
+ const set = ownedIndexes.get(artifact.owns.table) ?? new Set;
1784
+ set.add(artifact.owns.name);
1785
+ ownedIndexes.set(artifact.owns.table, set);
1786
+ }
1787
+ }
1788
+ const models = [];
1789
+ for (const model of schema.models) {
1790
+ const tableName = model.dbName ?? model.name;
1791
+ if (tables.exact.has(tableName) || tables.prefixes.some((prefix) => tableName.startsWith(prefix))) {
1792
+ continue;
1793
+ }
1794
+ const columns = ownedColumns.get(tableName);
1795
+ const indexes = ownedIndexes.get(tableName);
1796
+ if (columns === undefined && indexes === undefined) {
1797
+ models.push(model);
1798
+ continue;
1799
+ }
1800
+ models.push({
1801
+ ...model,
1802
+ fields: model.fields.filter((field) => !(columns?.has(field.dbName ?? field.name) ?? false)),
1803
+ indexes: model.indexes.filter((index) => !(index.name !== undefined && (indexes?.has(index.name) ?? false)))
1804
+ });
1805
+ }
1806
+ return { ...schema, models };
1807
+ }
1808
+ function planMissingArtifacts(params) {
1809
+ const { introspected, artifacts, installed } = params;
1810
+ if (artifacts.length === 0)
1811
+ return [];
1812
+ const modelByTable = new Map;
1813
+ for (const model of introspected.models) {
1814
+ modelByTable.set(model.dbName ?? model.name, model);
1815
+ }
1816
+ return artifacts.filter((artifact) => {
1817
+ const owner = artifact.owns;
1818
+ switch (owner.kind) {
1819
+ case "postgresExtension":
1820
+ return installed?.pgExtensions === undefined ? true : !installed.pgExtensions.has(owner.name);
1821
+ case "trigger":
1822
+ return installed?.triggers === undefined ? true : !installed.triggers.has(owner.name);
1823
+ case "table":
1824
+ return !modelByTable.has(owner.name);
1825
+ case "column": {
1826
+ const model = modelByTable.get(owner.table);
1827
+ if (model === undefined)
1828
+ return true;
1829
+ return !model.fields.some((field) => (field.dbName ?? field.name) === owner.column);
1830
+ }
1831
+ case "index": {
1832
+ const model = modelByTable.get(owner.table);
1833
+ if (model === undefined)
1834
+ return true;
1835
+ return !model.indexes.some((index) => index.name === owner.name);
1836
+ }
1837
+ }
1838
+ });
1839
+ }
1840
+ async function probeInstalledExtensionState(params) {
1841
+ const { executor, dialect, artifacts } = params;
1842
+ const state = {};
1843
+ if (dialect === "postgres" && artifacts.some((artifact) => artifact.owns.kind === "postgresExtension")) {
1844
+ const rows = await executor({ text: "SELECT extname FROM pg_extension" });
1845
+ state.pgExtensions = new Set(rows.map((row) => String(row["extname"])));
1846
+ }
1847
+ if (dialect === "sqlite" && artifacts.some((artifact) => artifact.owns.kind === "trigger")) {
1848
+ const rows = await executor({
1849
+ text: "SELECT name FROM sqlite_master WHERE type = 'trigger'",
1850
+ values: []
1851
+ });
1852
+ state.triggers = new Set(rows.map((row) => String(row["name"])));
1853
+ }
1854
+ return state;
1855
+ }
1856
+ function artifactCreateSql(params) {
1857
+ return params.artifacts.flatMap((artifact) => [...artifact.create]);
1858
+ }
1859
+
1860
+ // packages/migrate/src/execute.ts
1861
+ import { VibeError as VibeError8 } from "@vibeorm/schema";
1862
+ var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
1863
+ var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
1864
+ function usesSelfManagedTransactions(params) {
1865
+ return params.sql.some((statement) => statement.startsWith(SELF_MANAGED_MARKER));
1866
+ }
1867
+ async function executeStatement(params) {
1868
+ const { executor, statement, dialect } = params;
1869
+ if (dialect === "sqlite" && statement.startsWith(FK_CHECK_PREFIX)) {
1870
+ const rows = await executor({ text: statement, values: [] });
1871
+ if (rows.length > 0) {
1872
+ const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
1873
+ throw new VibeError8({
1874
+ code: "VIBE_MIGRATION",
1875
+ message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
1876
+ meta: { violations: rows }
1877
+ });
1878
+ }
1879
+ return;
1880
+ }
1881
+ await executor({ text: statement });
1882
+ }
1883
+ async function cleanupSelfManagedFailure(params) {
1884
+ try {
1885
+ await params.executor({ text: "ROLLBACK;" });
1886
+ } catch {}
1887
+ try {
1888
+ await params.executor({ text: "PRAGMA foreign_keys=ON;" });
1889
+ } catch {}
1890
+ }
1891
+
1892
+ // packages/migrate/src/runner.ts
1893
+ var MIGRATIONS_TABLE_NAME = "_vibe_migrations";
1894
+ var PG_ADVISORY_LOCK_KEY = 1447645253;
1895
+ var MYSQL_LOCK_NAME = "vibeorm_migrate";
1896
+ var MYSQL_LOCK_TIMEOUT_SECONDS = 60;
1897
+ var RESOLVED_STATEMENT_INDEX = 2147483647;
1898
+ var CREATE_TABLE_SQL = {
1899
+ postgres: `CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE_NAME}" (
1900
+ "id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
1901
+ "name" text NOT NULL UNIQUE,
1902
+ "checksum" text NOT NULL,
1903
+ "applied_at" timestamptz(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
1904
+ "statement_index" integer NOT NULL DEFAULT 0,
1905
+ "error" text
1906
+ );`,
1907
+ sqlite: `CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE_NAME}" (
1908
+ "id" INTEGER PRIMARY KEY AUTOINCREMENT,
1909
+ "name" TEXT NOT NULL UNIQUE,
1910
+ "checksum" TEXT NOT NULL,
1911
+ "applied_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
1912
+ "statement_index" INTEGER NOT NULL DEFAULT 0,
1913
+ "error" TEXT
1914
+ );`,
1915
+ mysql: `CREATE TABLE IF NOT EXISTS \`${MIGRATIONS_TABLE_NAME}\` (
1916
+ \`id\` int NOT NULL AUTO_INCREMENT,
1917
+ \`name\` varchar(191) NOT NULL UNIQUE,
1918
+ \`checksum\` text NOT NULL,
1919
+ \`applied_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
1920
+ \`statement_index\` int NOT NULL DEFAULT 0,
1921
+ \`error\` text,
1922
+ PRIMARY KEY (\`id\`)
1923
+ );`
1924
+ };
1925
+ async function ensureMigrationsTable(params) {
1926
+ await params.executor({ text: CREATE_TABLE_SQL[params.dialect ?? "postgres"] });
1927
+ }
1928
+ function migrationChecksum(params) {
1929
+ return createHash("sha256").update(params.sql.join(`
1930
+ `)).digest("hex");
1931
+ }
1932
+ function generateMigration(params) {
1933
+ const { from, to, name } = params;
1934
+ const dialect = params.dialect ?? "postgres";
1935
+ const artifacts = collectExtensionArtifacts({
1936
+ schema: to,
1937
+ dialect,
1938
+ extensions: params.extensions ?? []
1939
+ });
1940
+ const allArtifacts = [...artifacts.setup, ...artifacts.objects];
1941
+ const strippedFrom = stripExtensionArtifacts({ schema: from, artifacts: allArtifacts });
1942
+ const steps = diffSchemas({ from: strippedFrom, to, dialect });
1943
+ const coreSql = renderSteps({ steps, dialect, from: strippedFrom, to });
1944
+ const missing = planMissingArtifacts({ introspected: from, artifacts: allArtifacts });
1945
+ const sql = [
1946
+ ...artifactCreateSql({
1947
+ artifacts: missing.filter((artifact) => artifact.owns.kind === "postgresExtension")
1948
+ }),
1949
+ ...coreSql,
1950
+ ...artifactCreateSql({
1951
+ artifacts: missing.filter((artifact) => artifact.owns.kind !== "postgresExtension")
1952
+ })
1953
+ ];
1954
+ return {
1955
+ name,
1956
+ sql,
1957
+ destructive: steps.some((step) => step.destructive)
1958
+ };
1959
+ }
1960
+ async function applyMigrations(params) {
1961
+ const { executor, migrations } = params;
1962
+ const dialect = params.dialect ?? "postgres";
1963
+ const transactional = DIALECT_CAPABILITIES[dialect].transactionalDdl;
1964
+ await ensureMigrationsTable({ executor, dialect });
1965
+ await acquireLock({ executor, dialect });
1966
+ try {
1967
+ const recorded = await recordedRows({ executor, dialect });
1968
+ const applied = [];
1969
+ const skipped = [];
1970
+ for (const migration of migrations) {
1971
+ const stored = recorded.get(migration.name);
1972
+ if (stored !== undefined) {
1973
+ if (stored.statementIndex < 0) {
1974
+ throw new VibeError9({
1975
+ code: "VIBE_MIGRATION",
1976
+ message: `migration "${migration.name}" is mid-rollback (${-stored.statementIndex - 1} down statement(s) done) \u2014 finish it with \`migrate down\` or repair with resolveMigration before applying`,
1977
+ meta: { migration: migration.name, statementIndex: stored.statementIndex }
1978
+ });
1979
+ }
1980
+ const complete = stored.checksum === "" || transactional || stored.statementIndex >= migration.sql.length;
1981
+ if (complete) {
1982
+ assertChecksumMatches({ migration, stored: stored.checksum });
1983
+ skipped.push(migration.name);
1984
+ continue;
1985
+ }
1986
+ assertResumable({ migration, stored });
1987
+ await runNonTransactional({
1988
+ executor,
1989
+ dialect,
1990
+ migration,
1991
+ start: stored.statementIndex,
1992
+ insertRecord: false
1993
+ });
1994
+ applied.push(migration.name);
1995
+ continue;
1996
+ }
1997
+ if (transactional) {
1998
+ await runTransactional({ executor, dialect, migration });
1999
+ } else {
2000
+ await runNonTransactional({ executor, dialect, migration, start: 0, insertRecord: true });
2001
+ }
2002
+ applied.push(migration.name);
2003
+ }
2004
+ return { applied, skipped };
2005
+ } finally {
2006
+ await releaseLock({ executor, dialect });
2007
+ }
2008
+ }
2009
+ async function migrationStatus(params) {
2010
+ const { executor, migrations } = params;
2011
+ const dialect = params.dialect ?? "postgres";
2012
+ const transactional = DIALECT_CAPABILITIES[dialect].transactionalDdl;
2013
+ await ensureMigrationsTable({ executor, dialect });
2014
+ const recorded = await recordedRows({ executor, dialect });
2015
+ const applied = [];
2016
+ const pending = [];
2017
+ const mismatched = [];
2018
+ const rollingBack = [];
2019
+ for (const migration of migrations) {
2020
+ const stored = recorded.get(migration.name);
2021
+ if (stored === undefined) {
2022
+ pending.push(migration.name);
2023
+ continue;
2024
+ }
2025
+ if (stored.statementIndex < 0) {
2026
+ rollingBack.push(migration.name);
2027
+ continue;
2028
+ }
2029
+ if (stored.checksum === "") {
2030
+ applied.push(migration.name);
2031
+ continue;
2032
+ }
2033
+ if (stored.checksum !== migrationChecksum({ sql: migration.sql })) {
2034
+ mismatched.push(migration.name);
2035
+ continue;
2036
+ }
2037
+ const complete = transactional || stored.statementIndex >= migration.sql.length;
2038
+ if (complete)
2039
+ applied.push(migration.name);
2040
+ else
2041
+ pending.push(migration.name);
2042
+ }
2043
+ return { applied, pending, mismatched, rollingBack };
2044
+ }
2045
+ async function resolveMigration(params) {
2046
+ const { executor, name } = params;
2047
+ const dialect = params.dialect ?? "postgres";
2048
+ await ensureMigrationsTable({ executor, dialect });
2049
+ if (params.as === "applied") {
2050
+ const insert = `INSERT INTO ${q4(dialect, MIGRATIONS_TABLE_NAME)} (${q4(dialect, "name")}, ${q4(dialect, "checksum")}, ${q4(dialect, "statement_index")}) VALUES (${ph(dialect, 1)}, ${ph(dialect, 2)}, ${RESOLVED_STATEMENT_INDEX})`;
2051
+ const upsert = dialect === "mysql" ? `${insert} ON DUPLICATE KEY UPDATE \`checksum\` = VALUES(\`checksum\`), \`statement_index\` = VALUES(\`statement_index\`), \`error\` = NULL` : `${insert} ON CONFLICT ("name") DO UPDATE SET "checksum" = EXCLUDED."checksum", "statement_index" = EXCLUDED."statement_index"`;
2052
+ await executor({ text: upsert, values: [name, params.checksum ?? ""] });
2053
+ return;
2054
+ }
2055
+ await executor({
2056
+ text: `DELETE FROM ${q4(dialect, MIGRATIONS_TABLE_NAME)} WHERE ${q4(dialect, "name")} = ${ph(dialect, 1)}`,
2057
+ values: [name]
2058
+ });
2059
+ }
2060
+ async function rollbackMigration(params) {
2061
+ const { executor, migration } = params;
2062
+ const dialect = params.dialect ?? "postgres";
2063
+ const transactional = DIALECT_CAPABILITIES[dialect].transactionalDdl;
2064
+ const downSql = migration.downSql;
2065
+ if (downSql === undefined) {
2066
+ throw new VibeError9({
2067
+ code: "VIBE_MIGRATION",
2068
+ message: `migration "${migration.name}" has no down.sql \u2014 it predates down-migration support; roll it back manually, then repair with resolveMigration (as: "rolled-back")`,
2069
+ meta: { migration: migration.name }
2070
+ });
2071
+ }
2072
+ await ensureMigrationsTable({ executor, dialect });
2073
+ await acquireLock({ executor, dialect });
2074
+ try {
2075
+ const recorded = await recordedRows({ executor, dialect });
2076
+ const stored = recorded.get(migration.name);
2077
+ if (stored === undefined) {
2078
+ throw new VibeError9({
2079
+ code: "VIBE_MIGRATION",
2080
+ message: `migration "${migration.name}" is not recorded as applied \u2014 nothing to roll back`,
2081
+ meta: { migration: migration.name }
2082
+ });
2083
+ }
2084
+ const resumingDown = stored.statementIndex < 0;
2085
+ assertChecksumMatches({ migration, stored: stored.checksum });
2086
+ if (!resumingDown) {
2087
+ const partialUp = !transactional && stored.checksum !== "" && stored.statementIndex < migration.sql.length;
2088
+ if (partialUp) {
2089
+ throw new VibeError9({
2090
+ code: "VIBE_MIGRATION",
2091
+ message: `migration "${migration.name}" is only partially applied (${stored.statementIndex} of ${migration.sql.length} statement(s)) \u2014 its down migration mirrors the FULL up; re-run applyMigrations to finish it (it resumes), or repair with resolveMigration`,
2092
+ meta: { migration: migration.name, statementIndex: stored.statementIndex }
2093
+ });
2094
+ }
2095
+ }
2096
+ if (transactional) {
2097
+ await runTransactionalDown({ executor, dialect, migration, downSql });
2098
+ } else {
2099
+ const start = resumingDown ? -stored.statementIndex - 1 : 0;
2100
+ await runNonTransactionalDown({ executor, dialect, migration, downSql, start });
2101
+ }
2102
+ } finally {
2103
+ await releaseLock({ executor, dialect });
2104
+ }
2105
+ }
2106
+ async function runTransactionalDown(params) {
2107
+ const { executor, dialect, migration, downSql } = params;
2108
+ const selfManaged = dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql });
2109
+ const wrap = !selfManaged;
2110
+ if (wrap)
2111
+ await executor({ text: "BEGIN" });
2112
+ try {
2113
+ for (const statement of downSql) {
2114
+ await executeStatement({ executor, statement, dialect });
2115
+ }
2116
+ await executor({
2117
+ text: `DELETE FROM ${q4(dialect, MIGRATIONS_TABLE_NAME)} WHERE ${q4(dialect, "name")} = ${ph(dialect, 1)}`,
2118
+ values: [migration.name]
2119
+ });
2120
+ if (wrap)
2121
+ await executor({ text: "COMMIT" });
2122
+ } catch (error) {
2123
+ if (wrap) {
2124
+ try {
2125
+ await executor({ text: "ROLLBACK" });
2126
+ } catch {}
2127
+ } else {
2128
+ await cleanupSelfManagedFailure({ executor });
2129
+ }
2130
+ throw new VibeError9({
2131
+ code: "VIBE_MIGRATION",
2132
+ message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
2133
+ meta: { migration: migration.name },
2134
+ cause: error
2135
+ });
2136
+ }
2137
+ }
2138
+ async function runNonTransactionalDown(params) {
2139
+ const { executor, dialect, migration, downSql, start } = params;
2140
+ const table = q4(dialect, MIGRATIONS_TABLE_NAME);
2141
+ const recordProgress = async (completed) => {
2142
+ await executor({
2143
+ text: `UPDATE ${table} SET ${q4(dialect, "statement_index")} = ${ph(dialect, 1)}, ${q4(dialect, "error")} = NULL WHERE ${q4(dialect, "name")} = ${ph(dialect, 2)}`,
2144
+ values: [-(completed + 1), migration.name]
2145
+ });
2146
+ };
2147
+ if (start === 0)
2148
+ await recordProgress(0);
2149
+ for (let index = start;index < downSql.length; index += 1) {
2150
+ try {
2151
+ await executeStatement({ executor, statement: downSql[index], dialect });
2152
+ } catch (error) {
2153
+ try {
2154
+ await executor({
2155
+ text: `UPDATE ${table} SET ${q4(dialect, "error")} = ${ph(dialect, 1)} WHERE ${q4(dialect, "name")} = ${ph(dialect, 2)}`,
2156
+ values: [error instanceof Error ? error.message : String(error), migration.name]
2157
+ });
2158
+ } catch {}
2159
+ throw new VibeError9({
2160
+ code: "VIBE_MIGRATION",
2161
+ message: `rollback of "${migration.name}" failed at down statement ${index + 1} of ${downSql.length}: ${error instanceof Error ? error.message : String(error)} \u2014 progress is recorded; re-running the rollback resumes from this statement`,
2162
+ meta: { migration: migration.name, statementIndex: index },
2163
+ cause: error
2164
+ });
2165
+ }
2166
+ await recordProgress(index + 1);
2167
+ }
2168
+ await executor({
2169
+ text: `DELETE FROM ${table} WHERE ${q4(dialect, "name")} = ${ph(dialect, 1)}`,
2170
+ values: [migration.name]
2171
+ });
2172
+ }
2173
+ async function runTransactional(params) {
2174
+ const { executor, dialect, migration } = params;
2175
+ const selfManaged = dialect === "sqlite" && usesSelfManagedTransactions({ sql: migration.sql });
2176
+ const wrap = !selfManaged;
2177
+ if (wrap)
2178
+ await executor({ text: "BEGIN" });
2179
+ try {
2180
+ let statementIndex = 0;
2181
+ for (const statement of migration.sql) {
2182
+ await executeStatement({ executor, statement, dialect });
2183
+ statementIndex += 1;
2184
+ }
2185
+ await executor({
2186
+ text: `INSERT INTO ${q4(dialect, MIGRATIONS_TABLE_NAME)} (${q4(dialect, "name")}, ${q4(dialect, "checksum")}, ${q4(dialect, "statement_index")}) VALUES (${ph(dialect, 1)}, ${ph(dialect, 2)}, ${ph(dialect, 3)})`,
2187
+ values: [migration.name, migrationChecksum({ sql: migration.sql }), statementIndex]
2188
+ });
2189
+ if (wrap)
2190
+ await executor({ text: "COMMIT" });
2191
+ } catch (error) {
2192
+ if (wrap) {
2193
+ try {
2194
+ await executor({ text: "ROLLBACK" });
2195
+ } catch {}
2196
+ } else {
2197
+ await cleanupSelfManagedFailure({ executor });
2198
+ }
2199
+ throw new VibeError9({
2200
+ code: "VIBE_MIGRATION",
2201
+ message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
2202
+ meta: { migration: migration.name },
2203
+ cause: error
2204
+ });
2205
+ }
2206
+ }
2207
+ async function runNonTransactional(params) {
2208
+ const { executor, dialect, migration, start, insertRecord } = params;
2209
+ const table = q4(dialect, MIGRATIONS_TABLE_NAME);
2210
+ if (insertRecord) {
2211
+ await executor({
2212
+ text: `INSERT INTO ${table} (${q4(dialect, "name")}, ${q4(dialect, "checksum")}, ${q4(dialect, "statement_index")}) VALUES (${ph(dialect, 1)}, ${ph(dialect, 2)}, 0)`,
2213
+ values: [migration.name, migrationChecksum({ sql: migration.sql })]
2214
+ });
2215
+ }
2216
+ for (let index = start;index < migration.sql.length; index += 1) {
2217
+ try {
2218
+ await executeStatement({ executor, statement: migration.sql[index], dialect });
2219
+ } catch (error) {
2220
+ try {
2221
+ await executor({
2222
+ text: `UPDATE ${table} SET ${q4(dialect, "error")} = ${ph(dialect, 1)} WHERE ${q4(dialect, "name")} = ${ph(dialect, 2)}`,
2223
+ values: [error instanceof Error ? error.message : String(error), migration.name]
2224
+ });
2225
+ } catch {}
2226
+ throw new VibeError9({
2227
+ code: "VIBE_MIGRATION",
2228
+ message: `migration "${migration.name}" failed at statement ${index + 1} of ${migration.sql.length}: ${error instanceof Error ? error.message : String(error)} \u2014 progress is recorded; re-running applyMigrations resumes from this statement`,
2229
+ meta: { migration: migration.name, statementIndex: index },
2230
+ cause: error
2231
+ });
2232
+ }
2233
+ await executor({
2234
+ text: `UPDATE ${table} SET ${q4(dialect, "statement_index")} = ${ph(dialect, 1)}, ${q4(dialect, "error")} = NULL WHERE ${q4(dialect, "name")} = ${ph(dialect, 2)}`,
2235
+ values: [index + 1, migration.name]
2236
+ });
2237
+ }
2238
+ }
2239
+ async function acquireLock(params) {
2240
+ const { executor, dialect } = params;
2241
+ if (dialect === "postgres") {
2242
+ await executor({ text: `SELECT pg_advisory_lock(${PG_ADVISORY_LOCK_KEY})` });
2243
+ return;
2244
+ }
2245
+ if (dialect === "mysql") {
2246
+ const rows = await executor({
2247
+ text: `SELECT GET_LOCK('${MYSQL_LOCK_NAME}', ${MYSQL_LOCK_TIMEOUT_SECONDS}) AS \`vibe_lock\``,
2248
+ values: []
2249
+ });
2250
+ const acquired = Number(rows[0]?.vibe_lock) === 1;
2251
+ if (!acquired) {
2252
+ throw new VibeError9({
2253
+ code: "VIBE_MIGRATION",
2254
+ message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
2255
+ meta: { lock: MYSQL_LOCK_NAME }
2256
+ });
2257
+ }
2258
+ }
2259
+ }
2260
+ async function releaseLock(params) {
2261
+ const { executor, dialect } = params;
2262
+ try {
2263
+ if (dialect === "postgres") {
2264
+ await executor({ text: `SELECT pg_advisory_unlock(${PG_ADVISORY_LOCK_KEY})` });
2265
+ } else if (dialect === "mysql") {
2266
+ await executor({ text: `SELECT RELEASE_LOCK('${MYSQL_LOCK_NAME}')` });
2267
+ }
2268
+ } catch {}
2269
+ }
2270
+ async function recordedRows(params) {
2271
+ const { executor, dialect } = params;
2272
+ const rows = await executor({
2273
+ text: `SELECT ${q4(dialect, "name")}, ${q4(dialect, "checksum")}, ${q4(dialect, "statement_index")} FROM ${q4(dialect, MIGRATIONS_TABLE_NAME)} ORDER BY ${q4(dialect, "id")}`,
2274
+ values: []
2275
+ });
2276
+ const result = new Map;
2277
+ for (const row of rows) {
2278
+ const rawIndex = Number(row.statement_index);
2279
+ result.set(row.name, {
2280
+ checksum: row.checksum,
2281
+ statementIndex: Number.isNaN(rawIndex) ? RESOLVED_STATEMENT_INDEX : rawIndex
2282
+ });
2283
+ }
2284
+ return result;
2285
+ }
2286
+ function assertChecksumMatches(params) {
2287
+ const { migration, stored } = params;
2288
+ if (stored === "")
2289
+ return;
2290
+ const actual = migrationChecksum({ sql: migration.sql });
2291
+ if (stored === actual)
2292
+ return;
2293
+ throw new VibeError9({
2294
+ code: "VIBE_MIGRATION",
2295
+ message: `checksum mismatch for migration "${migration.name}" \u2014 its SQL changed after it was applied (recorded ${stored.slice(0, 12)}\u2026, current ${actual.slice(0, 12)}\u2026)`,
2296
+ meta: { migration: migration.name, recorded: stored, current: actual }
2297
+ });
2298
+ }
2299
+ function assertResumable(params) {
2300
+ const { migration, stored } = params;
2301
+ const actual = migrationChecksum({ sql: migration.sql });
2302
+ if (stored.checksum === actual)
2303
+ return;
2304
+ throw new VibeError9({
2305
+ code: "VIBE_MIGRATION",
2306
+ message: `migration "${migration.name}" is partially applied (${stored.statementIndex} statement(s) done) and its SQL has CHANGED since \u2014 refusing to resume; restore the original SQL or repair with resolveMigration`,
2307
+ meta: {
2308
+ migration: migration.name,
2309
+ statementIndex: stored.statementIndex,
2310
+ recorded: stored.checksum,
2311
+ current: actual
2312
+ }
2313
+ });
2314
+ }
2315
+ function q4(dialect, name) {
2316
+ return getSqlDialect({ dialect }).quoteIdent({ name });
2317
+ }
2318
+ function ph(dialect, index) {
2319
+ return getSqlDialect({ dialect }).placeholder({ index });
2320
+ }
2321
+
2322
+ // packages/migrate/src/introspect/shared.ts
2323
+ function detectJoinTable(params) {
2324
+ const { table, columnNames, fkGroups, indexes } = params;
2325
+ if (!table.startsWith("_"))
2326
+ return null;
2327
+ const names = [...columnNames].sort();
2328
+ if (names.length !== 2 || names[0] !== "A" || names[1] !== "B")
2329
+ return null;
2330
+ const tableFks = fkGroups.filter((group) => group.table === table);
2331
+ if (tableFks.length !== 2)
2332
+ return null;
2333
+ const fkA = tableFks.find((group) => group.columns.length === 1 && group.columns[0] === "A");
2334
+ const fkB = tableFks.find((group) => group.columns.length === 1 && group.columns[0] === "B");
2335
+ if (fkA === undefined || fkB === undefined)
2336
+ return null;
2337
+ const hasAbUnique = indexes.some((index) => index.table === table && index.unique && index.columns.length === 2 && index.columns[0]?.name === "A" && index.columns[1]?.name === "B");
2338
+ if (!hasAbUnique)
2339
+ return null;
2340
+ const sorted = [fkA.refTable, fkB.refTable].sort();
2341
+ const defaultName = `_${sorted[0]}To${sorted[1]}`;
2342
+ return {
2343
+ tableA: fkA.refTable,
2344
+ tableB: fkB.refTable,
2345
+ ...table === defaultName ? {} : { relationName: table.slice(1) }
2346
+ };
2347
+ }
2348
+ function addFkRelations(params) {
2349
+ const { models, fkGroups, joinTables } = params;
2350
+ const modelsByName = new Map(models.map((model) => [model.name, model]));
2351
+ const relationsByModel = new Map;
2352
+ const takeName = nameAllocator({ models });
2353
+ const ambiguousPairs = detectAmbiguousPairs({ fkGroups, joinTables, modelsByName });
2354
+ for (const group of fkGroups) {
2355
+ if (joinTables.has(group.table))
2356
+ continue;
2357
+ const owner = modelsByName.get(group.table);
2358
+ const target = modelsByName.get(group.refTable);
2359
+ if (owner === undefined || target === undefined)
2360
+ continue;
2361
+ const relationName = ambiguousPairs.has(modelPairKey({ a: group.table, b: group.refTable })) ? `${group.table}_${group.columns.join("_")}To${group.refTable}` : undefined;
2362
+ const fkSide = {
2363
+ name: takeName({ model: owner.name, base: lowerFirst(group.refTable) }),
2364
+ model: owner.name,
2365
+ targetModel: group.refTable,
2366
+ type: "manyToOne",
2367
+ fields: group.columns,
2368
+ references: group.refColumns,
2369
+ onDelete: group.onDelete,
2370
+ onUpdate: group.onUpdate,
2371
+ isList: false,
2372
+ isOptional: !group.required,
2373
+ ...relationName === undefined ? {} : { relationName }
2374
+ };
2375
+ pushRelation({ relationsByModel, relation: fkSide });
2376
+ const listSide = {
2377
+ name: takeName({ model: target.name, base: lowerFirst(group.table) }),
2378
+ model: target.name,
2379
+ targetModel: group.table,
2380
+ type: "oneToMany",
2381
+ isList: true,
2382
+ isOptional: false,
2383
+ ...relationName === undefined ? {} : { relationName }
2384
+ };
2385
+ pushRelation({ relationsByModel, relation: listSide });
2386
+ }
2387
+ applyRelations({ models, relationsByModel });
2388
+ }
2389
+ function addJoinTableRelations(params) {
2390
+ const { models, joinTables } = params;
2391
+ const modelsByName = new Map(models.map((model) => [model.name, model]));
2392
+ const relationsByModel = new Map;
2393
+ const takeName = nameAllocator({ models });
2394
+ for (const [, joinTable] of joinTables) {
2395
+ const modelA = modelsByName.get(joinTable.tableA);
2396
+ const modelB = modelsByName.get(joinTable.tableB);
2397
+ if (modelA === undefined || modelB === undefined)
2398
+ continue;
2399
+ const shared = joinTable.relationName;
2400
+ pushRelation({
2401
+ relationsByModel,
2402
+ relation: {
2403
+ name: takeName({ model: modelA.name, base: lowerFirst(modelB.name) }),
2404
+ model: modelA.name,
2405
+ targetModel: modelB.name,
2406
+ type: "oneToMany",
2407
+ isList: true,
2408
+ isOptional: false,
2409
+ ...shared === undefined ? {} : { relationName: shared }
2410
+ }
2411
+ });
2412
+ pushRelation({
2413
+ relationsByModel,
2414
+ relation: {
2415
+ name: takeName({ model: modelB.name, base: lowerFirst(modelA.name) }),
2416
+ model: modelB.name,
2417
+ targetModel: modelA.name,
2418
+ type: "oneToMany",
2419
+ isList: true,
2420
+ isOptional: false,
2421
+ ...shared === undefined ? {} : { relationName: shared }
2422
+ }
2423
+ });
2424
+ }
2425
+ applyRelations({ models, relationsByModel });
2426
+ }
2427
+ function modelPairKey(params) {
2428
+ const { a, b } = params;
2429
+ return [a, b].sort().join("\x00");
2430
+ }
2431
+ function detectAmbiguousPairs(params) {
2432
+ const { fkGroups, joinTables, modelsByName } = params;
2433
+ const counts = new Map;
2434
+ const bump = (key) => {
2435
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2436
+ };
2437
+ for (const group of fkGroups) {
2438
+ if (joinTables.has(group.table))
2439
+ continue;
2440
+ if (!modelsByName.has(group.table) || !modelsByName.has(group.refTable))
2441
+ continue;
2442
+ bump(modelPairKey({ a: group.table, b: group.refTable }));
2443
+ }
2444
+ for (const [, joinTable] of joinTables) {
2445
+ if (joinTable.relationName !== undefined)
2446
+ continue;
2447
+ bump(modelPairKey({ a: joinTable.tableA, b: joinTable.tableB }));
2448
+ }
2449
+ return new Set([...counts.entries()].filter(([, count]) => count > 1).map(([key]) => key));
2450
+ }
2451
+ function pushRelation(params) {
2452
+ const { relationsByModel, relation } = params;
2453
+ const list = relationsByModel.get(relation.model);
2454
+ if (list === undefined)
2455
+ relationsByModel.set(relation.model, [relation]);
2456
+ else
2457
+ list.push(relation);
2458
+ }
2459
+ function applyRelations(params) {
2460
+ const { models, relationsByModel } = params;
2461
+ for (let index = 0;index < models.length; index += 1) {
2462
+ const model = models[index];
2463
+ const additions = relationsByModel.get(model.name);
2464
+ if (additions === undefined)
2465
+ continue;
2466
+ models[index] = { ...model, relations: [...model.relations, ...additions] };
2467
+ }
2468
+ }
2469
+ function nameAllocator(params) {
2470
+ const { models } = params;
2471
+ const used = new Map;
2472
+ for (const model of models) {
2473
+ used.set(model.name, new Set([
2474
+ ...model.fields.map((field) => field.name),
2475
+ ...model.relations.map((relation) => relation.name)
2476
+ ]));
2477
+ }
2478
+ return ({ model, base }) => {
2479
+ const names = used.get(model) ?? new Set;
2480
+ used.set(model, names);
2481
+ let candidate = base;
2482
+ let suffix = 2;
2483
+ while (names.has(candidate)) {
2484
+ candidate = `${base}_${suffix}`;
2485
+ suffix += 1;
2486
+ }
2487
+ names.add(candidate);
2488
+ return candidate;
2489
+ };
2490
+ }
2491
+ function lowerFirst(value) {
2492
+ return value.charAt(0).toLowerCase() + value.slice(1);
2493
+ }
2494
+
2495
+ // packages/migrate/src/introspect/postgres.ts
2496
+ var EXTENSION_OWNED_FILTER = `NOT EXISTS (
2497
+ SELECT 1
2498
+ FROM pg_catalog.pg_depend d
2499
+ JOIN pg_catalog.pg_class c ON c.oid = d.objid
2500
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2501
+ WHERE d.classid = 'pg_class'::regclass
2502
+ AND d.deptype = 'e'
2503
+ AND n.nspname = 'public'
2504
+ AND c.relname = table_name
2505
+ )`;
2506
+ var TABLES_QUERY = `
2507
+ SELECT table_name
2508
+ FROM information_schema.tables
2509
+ WHERE table_schema = 'public'
2510
+ AND table_type = 'BASE TABLE'
2511
+ AND table_name <> '${MIGRATIONS_TABLE_NAME}'
2512
+ AND ${EXTENSION_OWNED_FILTER}
2513
+ ORDER BY table_name;
2514
+ `;
2515
+ var VIEWS_QUERY = `
2516
+ SELECT table_name
2517
+ FROM information_schema.views
2518
+ WHERE table_schema = 'public'
2519
+ AND ${EXTENSION_OWNED_FILTER}
2520
+ ORDER BY table_name;
2521
+ `;
2522
+ var COLUMNS_QUERY = `
2523
+ SELECT
2524
+ table_name,
2525
+ column_name,
2526
+ data_type,
2527
+ udt_name,
2528
+ is_nullable,
2529
+ is_identity,
2530
+ column_default,
2531
+ datetime_precision,
2532
+ character_maximum_length,
2533
+ numeric_precision,
2534
+ numeric_scale,
2535
+ ordinal_position,
2536
+ (
2537
+ SELECT format_type(a.atttypid, a.atttypmod)
2538
+ FROM pg_attribute a
2539
+ JOIN pg_class c ON c.oid = a.attrelid
2540
+ JOIN pg_namespace n ON n.oid = c.relnamespace
2541
+ WHERE n.nspname = 'public'
2542
+ AND c.relname = columns.table_name
2543
+ AND a.attname = columns.column_name
2544
+ ) AS full_type
2545
+ FROM information_schema.columns AS columns
2546
+ WHERE table_schema = 'public'
2547
+ ORDER BY table_name, ordinal_position;
2548
+ `;
2549
+ var CONSTRAINTS_QUERY = `
2550
+ SELECT
2551
+ c.conname AS constraint_name,
2552
+ CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type,
2553
+ cl.relname AS table_name,
2554
+ att.attname AS column_name
2555
+ FROM pg_catalog.pg_constraint c
2556
+ JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2557
+ JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2558
+ JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
2559
+ JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum
2560
+ WHERE c.contype IN ('p', 'u')
2561
+ AND n.nspname = 'public'
2562
+ ORDER BY cl.relname, c.conname, k.ord;
2563
+ `;
2564
+ var FOREIGN_KEYS_QUERY = `
2565
+ SELECT
2566
+ c.conname AS constraint_name,
2567
+ cl.relname AS table_name,
2568
+ att.attname AS column_name,
2569
+ fcl.relname AS foreign_table_name,
2570
+ fatt.attname AS foreign_column_name,
2571
+ c.confdeltype AS on_delete_char,
2572
+ c.confupdtype AS on_update_char
2573
+ FROM pg_catalog.pg_constraint c
2574
+ JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2575
+ JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2576
+ JOIN pg_catalog.pg_class fcl ON fcl.oid = c.confrelid
2577
+ JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace
2578
+ JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true
2579
+ JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord
2580
+ JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum
2581
+ JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum
2582
+ WHERE c.contype = 'f'
2583
+ AND n.nspname = 'public'
2584
+ AND fn.nspname = 'public'
2585
+ ORDER BY cl.relname, c.conname, lk.ord;
2586
+ `;
2587
+ var ENUMS_QUERY = `
2588
+ SELECT
2589
+ t.typname AS type_name,
2590
+ e.enumlabel AS enum_value,
2591
+ e.enumsortorder AS sort_order
2592
+ FROM pg_type t
2593
+ JOIN pg_enum e ON t.oid = e.enumtypid
2594
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2595
+ WHERE n.nspname = 'public'
2596
+ ORDER BY t.typname, e.enumsortorder;
2597
+ `;
2598
+ var INDEXES_QUERY = `
2599
+ SELECT
2600
+ tablename AS table_name,
2601
+ indexname AS index_name,
2602
+ indexdef AS index_def
2603
+ FROM pg_indexes
2604
+ WHERE schemaname = 'public'
2605
+ ORDER BY tablename, indexname;
2606
+ `;
2607
+ async function introspectPostgres(params) {
2608
+ const { executor } = params;
2609
+ const [tableRows, viewRows, columnRows, constraintRows, fkRows, enumRows, indexRows] = await Promise.all([
2610
+ executor({ text: TABLES_QUERY }),
2611
+ executor({ text: VIEWS_QUERY }),
2612
+ executor({ text: COLUMNS_QUERY }),
2613
+ executor({ text: CONSTRAINTS_QUERY }),
2614
+ executor({ text: FOREIGN_KEYS_QUERY }),
2615
+ executor({ text: ENUMS_QUERY }),
2616
+ executor({ text: INDEXES_QUERY })
2617
+ ]);
2618
+ const tables = tableRows.map((row) => row.table_name);
2619
+ const views = viewRows.map((row) => row.table_name);
2620
+ const columns = columnRows.map((row) => ({
2621
+ table: row.table_name,
2622
+ name: row.column_name,
2623
+ dataType: row.data_type,
2624
+ udtName: row.udt_name,
2625
+ nullable: row.is_nullable === "YES",
2626
+ identity: row.is_identity === "YES",
2627
+ columnDefault: row.column_default ?? null,
2628
+ datetimePrecision: toNumberOrNull(row.datetime_precision),
2629
+ charMaxLength: toNumberOrNull(row.character_maximum_length),
2630
+ numericPrecision: toNumberOrNull(row.numeric_precision),
2631
+ numericScale: toNumberOrNull(row.numeric_scale),
2632
+ fullType: row.full_type ?? null
2633
+ }));
2634
+ const enums = [];
2635
+ {
2636
+ const valuesByName = new Map;
2637
+ for (const row of enumRows) {
2638
+ const name = row.type_name;
2639
+ const list = valuesByName.get(name);
2640
+ if (list === undefined)
2641
+ valuesByName.set(name, [row.enum_value]);
2642
+ else
2643
+ list.push(row.enum_value);
2644
+ }
2645
+ for (const [name, values] of valuesByName)
2646
+ enums.push({ name, values });
2647
+ }
2648
+ const enumNames = new Set(enums.map((entry) => entry.name));
2649
+ const pkByTable = new Map;
2650
+ const uniquesByConstraint = new Map;
2651
+ const constraintNames = new Set;
2652
+ for (const row of constraintRows) {
2653
+ const table = row.table_name;
2654
+ const constraintName = row.constraint_name;
2655
+ const columnName = row.column_name;
2656
+ constraintNames.add(constraintName);
2657
+ if (row.constraint_type === "PRIMARY KEY") {
2658
+ const list = pkByTable.get(table);
2659
+ if (list === undefined)
2660
+ pkByTable.set(table, [columnName]);
2661
+ else
2662
+ list.push(columnName);
2663
+ } else {
2664
+ const entry = uniquesByConstraint.get(constraintName);
2665
+ if (entry === undefined)
2666
+ uniquesByConstraint.set(constraintName, { table, columns: [columnName] });
2667
+ else
2668
+ entry.columns.push(columnName);
2669
+ }
2670
+ }
2671
+ const fkGroupsByName = new Map;
2672
+ for (const row of fkRows) {
2673
+ const constraintName = row.constraint_name;
2674
+ const existing = fkGroupsByName.get(constraintName);
2675
+ if (existing === undefined) {
2676
+ fkGroupsByName.set(constraintName, {
2677
+ constraintName,
2678
+ table: row.table_name,
2679
+ columns: [row.column_name],
2680
+ refTable: row.foreign_table_name,
2681
+ refColumns: [row.foreign_column_name],
2682
+ onDelete: PG_CHAR_TO_CASCADE_ACTION[row.on_delete_char ?? "a"] ?? "NoAction",
2683
+ onUpdate: PG_CHAR_TO_CASCADE_ACTION[row.on_update_char ?? "a"] ?? "NoAction"
2684
+ });
2685
+ } else {
2686
+ existing.columns.push(row.column_name);
2687
+ existing.refColumns.push(row.foreign_column_name);
2688
+ }
2689
+ }
2690
+ const fkGroups = [...fkGroupsByName.values()].map((group) => ({
2691
+ ...group,
2692
+ required: group.columns.every((columnName) => {
2693
+ const column = columns.find((c) => c.table === group.table && c.name === columnName);
2694
+ return column === undefined ? true : !column.nullable;
2695
+ })
2696
+ }));
2697
+ const indexes = [];
2698
+ for (const row of indexRows) {
2699
+ const parsed = parseIndexDef({
2700
+ table: row.table_name,
2701
+ name: row.index_name,
2702
+ def: row.index_def
2703
+ });
2704
+ if (parsed !== null)
2705
+ indexes.push(parsed);
2706
+ }
2707
+ const joinTables = new Map;
2708
+ for (const table of tables) {
2709
+ const detected = detectJoinTable({
2710
+ table,
2711
+ columnNames: columns.filter((column) => column.table === table).map((column) => column.name),
2712
+ fkGroups,
2713
+ indexes
2714
+ });
2715
+ if (detected !== null)
2716
+ joinTables.set(table, detected);
2717
+ }
2718
+ const models = [];
2719
+ for (const table of tables) {
2720
+ if (joinTables.has(table))
2721
+ continue;
2722
+ models.push(buildModel({
2723
+ table,
2724
+ isView: false,
2725
+ columns,
2726
+ pkByTable,
2727
+ uniquesByConstraint,
2728
+ constraintNames,
2729
+ indexes,
2730
+ enumNames
2731
+ }));
2732
+ }
2733
+ for (const view of views) {
2734
+ models.push(buildModel({
2735
+ table: view,
2736
+ isView: true,
2737
+ columns,
2738
+ pkByTable,
2739
+ uniquesByConstraint,
2740
+ constraintNames,
2741
+ indexes: [],
2742
+ enumNames
2743
+ }));
2744
+ }
2745
+ addFkRelations({ models, fkGroups, joinTables });
2746
+ addJoinTableRelations({ models, joinTables });
2747
+ return {
2748
+ datasource: { provider: "postgresql" },
2749
+ models,
2750
+ enums,
2751
+ extensions: []
2752
+ };
2753
+ }
2754
+ function buildModel(params) {
2755
+ const { table, isView, columns, pkByTable, uniquesByConstraint, constraintNames, indexes, enumNames } = params;
2756
+ const tableColumns = columns.filter((column) => column.table === table);
2757
+ const pk = pkByTable.get(table) ?? [];
2758
+ const uniqueSingles = new Set;
2759
+ const multiUniques = [];
2760
+ for (const [name, unique] of uniquesByConstraint) {
2761
+ if (unique.table !== table)
2762
+ continue;
2763
+ if (unique.columns.length === 1)
2764
+ uniqueSingles.add(unique.columns[0]);
2765
+ else
2766
+ multiUniques.push({ name, columns: unique.columns });
2767
+ }
2768
+ const tableIndexes = indexes.filter((index) => index.table === table && !constraintNames.has(index.name));
2769
+ for (const index of tableIndexes) {
2770
+ if (!index.unique)
2771
+ continue;
2772
+ if (index.columns.length === 1)
2773
+ uniqueSingles.add(index.columns[0].name);
2774
+ }
2775
+ const fields = tableColumns.map((column) => buildField({ column, pk, uniqueSingles, enumNames }));
2776
+ const modelIndexes = [];
2777
+ for (const unique of multiUniques) {
2778
+ modelIndexes.push({
2779
+ name: unique.name,
2780
+ kind: "unique",
2781
+ fields: unique.columns.map((name) => ({ name }))
2782
+ });
2783
+ }
2784
+ for (const index of tableIndexes) {
2785
+ if (index.unique && index.columns.length === 1)
2786
+ continue;
2787
+ modelIndexes.push({
2788
+ name: index.name,
2789
+ kind: index.unique ? "unique" : index.kind,
2790
+ fields: index.columns.map((column) => ({
2791
+ name: column.name,
2792
+ ...column.desc ? { sort: "desc" } : {}
2793
+ })),
2794
+ ...index.where === undefined ? {} : { where: index.where }
2795
+ });
2796
+ }
2797
+ return {
2798
+ name: table,
2799
+ fields,
2800
+ relations: [],
2801
+ indexes: modelIndexes,
2802
+ isView,
2803
+ ...pk.length > 1 ? { primaryKey: { fields: pk } } : {}
2804
+ };
2805
+ }
2806
+ function buildField(params) {
2807
+ const { column, pk, uniqueSingles, enumNames } = params;
2808
+ const mapped = reverseMapType({ column, enumNames });
2809
+ const isId = pk.length === 1 && pk[0] === column.name;
2810
+ const defaultValue = parseColumnDefault({ column, mapped });
2811
+ return {
2812
+ name: column.name,
2813
+ type: mapped.type,
2814
+ isList: mapped.isList,
2815
+ isOptional: column.nullable,
2816
+ isId,
2817
+ isUnique: uniqueSingles.has(column.name),
2818
+ isUpdatedAt: false,
2819
+ ...defaultValue === undefined ? {} : { default: defaultValue },
2820
+ ...mapped.nativeType === undefined ? {} : { nativeType: mapped.nativeType },
2821
+ ...mapped.isUnsupported === true ? { isUnsupported: true } : {}
2822
+ };
2823
+ }
2824
+ var CANONICAL_UDT = {
2825
+ text: "String",
2826
+ bool: "Boolean",
2827
+ int4: "Int",
2828
+ int8: "BigInt",
2829
+ float8: "Float",
2830
+ numeric: "Decimal",
2831
+ timestamptz: "DateTime",
2832
+ jsonb: "Json",
2833
+ bytea: "Bytes"
2834
+ };
2835
+ var NON_CANONICAL_UDT = {
2836
+ varchar: "String",
2837
+ bpchar: "String",
2838
+ uuid: "String",
2839
+ int2: "Int",
2840
+ float4: "Float",
2841
+ timestamp: "DateTime",
2842
+ date: "DateTime",
2843
+ json: "Json"
2844
+ };
2845
+ function reverseMapType(params) {
2846
+ const { column, enumNames } = params;
2847
+ const isList = column.dataType === "ARRAY";
2848
+ const udt = isList ? column.udtName.replace(/^_/, "") : column.udtName;
2849
+ if (enumNames.has(udt)) {
2850
+ return { type: { enum: udt }, isList };
2851
+ }
2852
+ const canonical = CANONICAL_UDT[udt];
2853
+ if (canonical !== undefined) {
2854
+ const nativeType = nonCanonicalModifier({ column, udt, scalar: canonical });
2855
+ return { type: canonical, isList, ...nativeType === undefined ? {} : { nativeType } };
2856
+ }
2857
+ const nonCanonical = NON_CANONICAL_UDT[udt];
2858
+ if (nonCanonical !== undefined) {
2859
+ return { type: nonCanonical, isList, nativeType: withModifier({ column, udt }) };
2860
+ }
2861
+ const raw = column.fullType ?? udt;
2862
+ return {
2863
+ type: "String",
2864
+ isList: false,
2865
+ nativeType: isList && !raw.endsWith("[]") ? `${raw}[]` : raw,
2866
+ isUnsupported: true
2867
+ };
2868
+ }
2869
+ function nonCanonicalModifier(params) {
2870
+ const { column, udt, scalar } = params;
2871
+ if (scalar === "DateTime" && udt === "timestamptz") {
2872
+ const precision = column.datetimePrecision;
2873
+ return precision === null || precision === 6 ? undefined : `timestamptz(${precision})`;
2874
+ }
2875
+ if (scalar === "Decimal" && udt === "numeric") {
2876
+ if (column.numericPrecision === null)
2877
+ return;
2878
+ const scale = column.numericScale ?? 0;
2879
+ return `numeric(${column.numericPrecision},${scale})`;
2880
+ }
2881
+ return;
2882
+ }
2883
+ function withModifier(params) {
2884
+ const { column, udt } = params;
2885
+ if ((udt === "varchar" || udt === "bpchar") && column.charMaxLength !== null) {
2886
+ return `${udt === "bpchar" ? "char" : "varchar"}(${column.charMaxLength})`;
2887
+ }
2888
+ if (udt === "bpchar")
2889
+ return "char";
2890
+ if (udt === "timestamp" && column.datetimePrecision !== null && column.datetimePrecision !== 6) {
2891
+ return `timestamp(${column.datetimePrecision})`;
2892
+ }
2893
+ return udt;
2894
+ }
2895
+ function parseColumnDefault(params) {
2896
+ const { column, mapped } = params;
2897
+ if (column.identity)
2898
+ return { kind: "autoincrement" };
2899
+ const raw = column.columnDefault;
2900
+ if (raw === null)
2901
+ return;
2902
+ if (raw.startsWith("NULL::"))
2903
+ return;
2904
+ if (raw.includes("nextval("))
2905
+ return { kind: "autoincrement" };
2906
+ if (raw === "now()" || raw.startsWith("CURRENT_TIMESTAMP"))
2907
+ return { kind: "now" };
2908
+ if (raw === "true" || raw === "false")
2909
+ return { kind: "literal", value: raw === "true" };
2910
+ if (/^-?\d+(\.\d+)?$/.test(raw))
2911
+ return { kind: "literal", value: Number(raw) };
2912
+ const quoted = raw.match(/^'((?:[^']|'')*)'(::.*)?$/);
2913
+ if (quoted !== null) {
2914
+ const value = quoted[1].replaceAll("''", "'");
2915
+ if (typeof mapped.type === "object")
2916
+ return { kind: "enumValue", value };
2917
+ if (isNumericScalar({ type: mapped.type }) && /^-?\d+(\.\d+)?$/.test(value)) {
2918
+ return { kind: "literal", value: Number(value) };
2919
+ }
2920
+ const cast = quoted[2];
2921
+ if (cast !== undefined && cast !== "" && mapped.isList) {
2922
+ return { kind: "dbgenerated", sql: raw };
2923
+ }
2924
+ return { kind: "literal", value };
2925
+ }
2926
+ return { kind: "dbgenerated", sql: raw };
2927
+ }
2928
+ function parseIndexDef(params) {
2929
+ const { table, name, def } = params;
2930
+ const head = def.match(/^CREATE (UNIQUE )?INDEX .*? USING (\w+) \(/);
2931
+ if (head === null)
2932
+ return null;
2933
+ const start = (head.index ?? 0) + head[0].length;
2934
+ let depth = 1;
2935
+ let end = -1;
2936
+ for (let position = start;position < def.length; position += 1) {
2937
+ const char = def[position];
2938
+ if (char === "(")
2939
+ depth += 1;
2940
+ else if (char === ")") {
2941
+ depth -= 1;
2942
+ if (depth === 0) {
2943
+ end = position;
2944
+ break;
2945
+ }
2946
+ }
2947
+ }
2948
+ if (end === -1)
2949
+ return null;
2950
+ const columns = splitTopLevel({ text: def.slice(start, end) }).map((entry) => {
2951
+ const trimmed = entry.trim();
2952
+ const desc = / DESC$/i.test(trimmed);
2953
+ const nameOnly = trimmed.replace(/ (DESC|ASC)( NULLS (FIRST|LAST))?$/i, "");
2954
+ return { name: nameOnly.replaceAll('"', ""), desc };
2955
+ });
2956
+ const tail = def.slice(end + 1).trim();
2957
+ const whereMatch = tail.match(/^WHERE (.*)$/);
2958
+ const whereRaw = whereMatch?.[1];
2959
+ return {
2960
+ table,
2961
+ name,
2962
+ unique: head[1] !== undefined,
2963
+ kind: head[2].toLowerCase(),
2964
+ columns,
2965
+ ...whereRaw === undefined ? {} : { where: whereRaw }
2966
+ };
2967
+ }
2968
+ function splitTopLevel(params) {
2969
+ const { text } = params;
2970
+ const parts = [];
2971
+ let depth = 0;
2972
+ let current = "";
2973
+ for (const char of text) {
2974
+ if (char === "(")
2975
+ depth += 1;
2976
+ if (char === ")")
2977
+ depth -= 1;
2978
+ if (char === "," && depth === 0) {
2979
+ parts.push(current);
2980
+ current = "";
2981
+ continue;
2982
+ }
2983
+ current += char;
2984
+ }
2985
+ if (current.trim().length > 0)
2986
+ parts.push(current);
2987
+ return parts;
2988
+ }
2989
+ function toNumberOrNull(value) {
2990
+ if (value === null || value === undefined)
2991
+ return null;
2992
+ const parsed = Number(value);
2993
+ return Number.isNaN(parsed) ? null : parsed;
2994
+ }
2995
+ // packages/migrate/src/introspect/sqlite.ts
2996
+ import { sqliteDialect as sqliteDialect2 } from "@vibeorm/sql";
2997
+ var SQLITE_ACTION_TO_CASCADE = {
2998
+ "NO ACTION": "NoAction",
2999
+ RESTRICT: "Restrict",
3000
+ CASCADE: "Cascade",
3001
+ "SET NULL": "SetNull",
3002
+ "SET DEFAULT": "SetDefault"
3003
+ };
3004
+ async function introspectSqlite(params) {
3005
+ const { executor } = params;
3006
+ const masterRows = await executor({
3007
+ text: `SELECT name, type, sql FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' AND name <> '${MIGRATIONS_TABLE_NAME}' ORDER BY name`,
3008
+ values: []
3009
+ });
3010
+ const indexSqlRows = await executor({
3011
+ text: `SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index' ORDER BY name`,
3012
+ values: []
3013
+ });
3014
+ const entries = masterRows.map((row) => ({
3015
+ name: row.name,
3016
+ type: row.type,
3017
+ sql: row.sql ?? null
3018
+ }));
3019
+ const tables = entries.filter((entry) => entry.type === "table");
3020
+ const views = entries.filter((entry) => entry.type === "view");
3021
+ const indexSqlByName = new Map;
3022
+ for (const row of indexSqlRows) {
3023
+ indexSqlByName.set(row.name, row.sql ?? null);
3024
+ }
3025
+ const columnsByTable = new Map;
3026
+ const indexes = [];
3027
+ const uniqueSinglesByTable = new Map;
3028
+ const modelIndexesByTable = new Map;
3029
+ const fkGroups = [];
3030
+ const enumsByColumn = new Map;
3031
+ const enumRegistry = new Map;
3032
+ const autoincrementTables = new Set;
3033
+ for (const table of tables) {
3034
+ const columns = await readColumns({ executor, table: table.name });
3035
+ columnsByTable.set(table.name, columns);
3036
+ for (const check of parseSqliteEnumChecks({ tableSql: table.sql ?? "" })) {
3037
+ enumsByColumn.set(`${table.name}.${check.column}`, {
3038
+ enumName: check.enumName,
3039
+ values: check.values
3040
+ });
3041
+ if (!enumRegistry.has(check.enumName)) {
3042
+ enumRegistry.set(check.enumName, { name: check.enumName, values: check.values });
3043
+ }
3044
+ }
3045
+ if (/PRIMARY KEY AUTOINCREMENT/i.test(table.sql ?? "")) {
3046
+ autoincrementTables.add(table.name);
3047
+ }
3048
+ const { tableIndexes, uniqueSingles, modelIndexes } = await readIndexes({
3049
+ executor,
3050
+ table: table.name,
3051
+ indexSqlByName
3052
+ });
3053
+ indexes.push(...tableIndexes);
3054
+ uniqueSinglesByTable.set(table.name, uniqueSingles);
3055
+ modelIndexesByTable.set(table.name, modelIndexes);
3056
+ fkGroups.push(...await readForeignKeys({ executor, table: table.name, columns }));
3057
+ }
3058
+ const joinTables = new Map;
3059
+ for (const table of tables) {
3060
+ const detected = detectJoinTable({
3061
+ table: table.name,
3062
+ columnNames: (columnsByTable.get(table.name) ?? []).map((column) => column.name),
3063
+ fkGroups,
3064
+ indexes
3065
+ });
3066
+ if (detected !== null)
3067
+ joinTables.set(table.name, detected);
3068
+ }
3069
+ const models = [];
3070
+ for (const table of tables) {
3071
+ if (joinTables.has(table.name))
3072
+ continue;
3073
+ models.push(buildModel2({
3074
+ table: table.name,
3075
+ isView: false,
3076
+ columns: columnsByTable.get(table.name) ?? [],
3077
+ uniqueSingles: uniqueSinglesByTable.get(table.name) ?? new Set,
3078
+ modelIndexes: modelIndexesByTable.get(table.name) ?? [],
3079
+ enumsByColumn,
3080
+ autoincrement: autoincrementTables.has(table.name)
3081
+ }));
3082
+ }
3083
+ for (const view of views) {
3084
+ models.push(buildModel2({
3085
+ table: view.name,
3086
+ isView: true,
3087
+ columns: await readColumns({ executor, table: view.name }),
3088
+ uniqueSingles: new Set,
3089
+ modelIndexes: [],
3090
+ enumsByColumn,
3091
+ autoincrement: false
3092
+ }));
3093
+ }
3094
+ addFkRelations({ models, fkGroups, joinTables });
3095
+ addJoinTableRelations({ models, joinTables });
3096
+ return {
3097
+ datasource: { provider: "sqlite" },
3098
+ models,
3099
+ enums: [...enumRegistry.values()],
3100
+ extensions: []
3101
+ };
3102
+ }
3103
+ async function readColumns(params) {
3104
+ const rows = await params.executor({
3105
+ text: `PRAGMA table_info(${q5(params.table)})`,
3106
+ values: []
3107
+ });
3108
+ return rows.map((row) => ({
3109
+ name: row.name,
3110
+ declaredType: row.type ?? "",
3111
+ notNull: Number(row.notnull) === 1,
3112
+ defaultValue: row.dflt_value === null || row.dflt_value === undefined ? null : String(row.dflt_value),
3113
+ pkPosition: Number(row.pk)
3114
+ }));
3115
+ }
3116
+ async function readIndexes(params) {
3117
+ const { executor, table, indexSqlByName } = params;
3118
+ const listRows = await executor({ text: `PRAGMA index_list(${q5(table)})`, values: [] });
3119
+ const tableIndexes = [];
3120
+ const uniqueSingles = new Set;
3121
+ const modelIndexes = [];
3122
+ for (const listRow of listRows) {
3123
+ const origin = listRow.origin;
3124
+ if (origin === "pk")
3125
+ continue;
3126
+ const name = listRow.name;
3127
+ const unique = Number(listRow.unique) === 1;
3128
+ const partial = Number(listRow.partial) === 1;
3129
+ const infoRows = await executor({ text: `PRAGMA index_xinfo(${q5(name)})`, values: [] });
3130
+ const keyRows = infoRows.filter((row) => Number(row.key) === 1).sort((a, b) => Number(a.seqno) - Number(b.seqno));
3131
+ let columns;
3132
+ if (keyRows.some((row) => row.name === null || row.name === undefined)) {
3133
+ const recovered = parseIndexColumnsFromSql({ sql: indexSqlByName.get(name) ?? null });
3134
+ if (recovered === null)
3135
+ continue;
3136
+ columns = recovered;
3137
+ } else {
3138
+ columns = keyRows.map((row) => ({
3139
+ name: row.name,
3140
+ desc: Number(row.desc) === 1
3141
+ }));
3142
+ }
3143
+ let where;
3144
+ if (partial) {
3145
+ const sql = indexSqlByName.get(name);
3146
+ const match = sql?.match(/\sWHERE\s+(.*)$/is);
3147
+ where = match?.[1]?.trim();
3148
+ }
3149
+ tableIndexes.push({
3150
+ table,
3151
+ name,
3152
+ unique,
3153
+ kind: "btree",
3154
+ columns,
3155
+ ...where === undefined ? {} : { where }
3156
+ });
3157
+ if (unique && columns.length === 1 && where === undefined) {
3158
+ uniqueSingles.add(columns[0].name);
3159
+ continue;
3160
+ }
3161
+ modelIndexes.push({
3162
+ name,
3163
+ kind: unique ? "unique" : "btree",
3164
+ fields: columns.map((column) => ({
3165
+ name: column.name,
3166
+ ...column.desc ? { sort: "desc" } : {}
3167
+ })),
3168
+ ...where === undefined ? {} : { where }
3169
+ });
3170
+ }
3171
+ return { tableIndexes, uniqueSingles, modelIndexes };
3172
+ }
3173
+ var PLAIN_INDEX_COLUMN = /^"((?:[^"]|"")+)"(?:\s+(ASC|DESC))?$/i;
3174
+ var CAST_REAL_INDEX_COLUMN = /^CAST\s*\(\s*"((?:[^"]|"")+)"\s+AS\s+REAL\s*\)(?:\s+(ASC|DESC))?$/i;
3175
+ function parseIndexColumnsFromSql(params) {
3176
+ const { sql } = params;
3177
+ if (sql === null)
3178
+ return null;
3179
+ const onMatch = /\sON\s+"(?:[^"]|"")+"\s*\(/i.exec(sql);
3180
+ if (onMatch === null)
3181
+ return null;
3182
+ const open = onMatch.index + onMatch[0].length - 1;
3183
+ let listDepth = 0;
3184
+ let close = -1;
3185
+ for (let index = open;index < sql.length; index++) {
3186
+ const char = sql[index];
3187
+ if (char === "(")
3188
+ listDepth += 1;
3189
+ else if (char === ")") {
3190
+ listDepth -= 1;
3191
+ if (listDepth === 0) {
3192
+ close = index;
3193
+ break;
3194
+ }
3195
+ }
3196
+ }
3197
+ if (close === -1)
3198
+ return null;
3199
+ const list = sql.slice(open + 1, close);
3200
+ const terms = [];
3201
+ let depth = 0;
3202
+ let start = 0;
3203
+ for (let index = 0;index < list.length; index++) {
3204
+ const char = list[index];
3205
+ if (char === "(")
3206
+ depth += 1;
3207
+ else if (char === ")")
3208
+ depth -= 1;
3209
+ else if (char === "," && depth === 0) {
3210
+ terms.push(list.slice(start, index));
3211
+ start = index + 1;
3212
+ }
3213
+ }
3214
+ terms.push(list.slice(start));
3215
+ const columns = [];
3216
+ for (const term of terms) {
3217
+ const trimmed = term.trim();
3218
+ const match = PLAIN_INDEX_COLUMN.exec(trimmed) ?? CAST_REAL_INDEX_COLUMN.exec(trimmed);
3219
+ if (match === null)
3220
+ return null;
3221
+ columns.push({
3222
+ name: match[1].replaceAll('""', '"'),
3223
+ desc: (match[2] ?? "").toUpperCase() === "DESC"
3224
+ });
3225
+ }
3226
+ return columns;
3227
+ }
3228
+ async function readForeignKeys(params) {
3229
+ const { executor, table, columns } = params;
3230
+ const rows = await executor({ text: `PRAGMA foreign_key_list(${q5(table)})`, values: [] });
3231
+ const groups = new Map;
3232
+ const ordered = [...rows].sort((a, b) => Number(a.id) - Number(b.id) || Number(a.seq) - Number(b.seq));
3233
+ for (const row of ordered) {
3234
+ const id = Number(row.id);
3235
+ const from = row.from;
3236
+ const to = row.to ?? "rowid";
3237
+ const existing = groups.get(id);
3238
+ if (existing === undefined) {
3239
+ groups.set(id, {
3240
+ columns: [from],
3241
+ refTable: row.table,
3242
+ refColumns: [to],
3243
+ onDelete: SQLITE_ACTION_TO_CASCADE[row.on_delete ?? "NO ACTION"] ?? "NoAction",
3244
+ onUpdate: SQLITE_ACTION_TO_CASCADE[row.on_update ?? "NO ACTION"] ?? "NoAction"
3245
+ });
3246
+ } else {
3247
+ existing.columns.push(from);
3248
+ existing.refColumns.push(to);
3249
+ }
3250
+ }
3251
+ return [...groups.values()].map((group) => ({
3252
+ constraintName: `${table}_${group.columns.join("_")}_fkey`,
3253
+ table,
3254
+ columns: group.columns,
3255
+ refTable: group.refTable,
3256
+ refColumns: group.refColumns,
3257
+ onDelete: group.onDelete,
3258
+ onUpdate: group.onUpdate,
3259
+ required: group.columns.every((columnName) => {
3260
+ const column = columns.find((candidate) => candidate.name === columnName);
3261
+ return column === undefined ? true : column.notNull;
3262
+ })
3263
+ }));
3264
+ }
3265
+ function buildModel2(params) {
3266
+ const { table, isView, columns, uniqueSingles, modelIndexes, enumsByColumn, autoincrement } = params;
3267
+ const pk = columns.filter((column) => column.pkPosition > 0).sort((a, b) => a.pkPosition - b.pkPosition).map((column) => column.name);
3268
+ const fields = columns.map((column) => {
3269
+ const enumRef = enumsByColumn.get(`${table}.${column.name}`);
3270
+ const mapped = reverseMapType2({ column, enumRef });
3271
+ const isId = pk.length === 1 && pk[0] === column.name;
3272
+ const isAutoincrement = autoincrement && isId && column.declaredType.toUpperCase() === "INTEGER";
3273
+ const defaultValue = isAutoincrement ? { kind: "autoincrement" } : parseColumnDefault2({ column, mapped });
3274
+ return {
3275
+ name: column.name,
3276
+ type: mapped.type,
3277
+ isList: false,
3278
+ isOptional: column.pkPosition > 0 ? false : !column.notNull,
3279
+ isId,
3280
+ isUnique: uniqueSingles.has(column.name),
3281
+ isUpdatedAt: false,
3282
+ ...defaultValue === undefined ? {} : { default: defaultValue },
3283
+ ...mapped.nativeType === undefined ? {} : { nativeType: mapped.nativeType },
3284
+ ...mapped.isUnsupported === true ? { isUnsupported: true } : {}
3285
+ };
3286
+ });
3287
+ return {
3288
+ name: table,
3289
+ fields,
3290
+ relations: [],
3291
+ indexes: [...modelIndexes],
3292
+ isView,
3293
+ ...pk.length > 1 ? { primaryKey: { fields: pk } } : {}
3294
+ };
3295
+ }
3296
+ function reverseMapType2(params) {
3297
+ const { column, enumRef } = params;
3298
+ const declared = column.declaredType.trim().toUpperCase();
3299
+ if (enumRef !== undefined && declared === "TEXT") {
3300
+ return { type: { enum: enumRef.enumName } };
3301
+ }
3302
+ switch (declared) {
3303
+ case "TEXT":
3304
+ return { type: "String" };
3305
+ case "INTEGER":
3306
+ return { type: "Int" };
3307
+ case "REAL":
3308
+ return { type: "Float" };
3309
+ case "BLOB":
3310
+ return { type: "Bytes" };
3311
+ default:
3312
+ return { type: "String", nativeType: column.declaredType, isUnsupported: true };
3313
+ }
3314
+ }
3315
+ function parseColumnDefault2(params) {
3316
+ const { column, mapped } = params;
3317
+ const raw = column.defaultValue;
3318
+ if (raw === null)
3319
+ return;
3320
+ if (raw === "CURRENT_TIMESTAMP")
3321
+ return { kind: "now" };
3322
+ if (/^-?\d+(\.\d+)?$/.test(raw))
3323
+ return { kind: "literal", value: Number(raw) };
3324
+ const quoted = raw.match(/^'((?:[^']|'')*)'$/);
3325
+ if (quoted !== null) {
3326
+ const value = quoted[1].replaceAll("''", "'");
3327
+ if (typeof mapped.type === "object")
3328
+ return { kind: "enumValue", value };
3329
+ if ((mapped.type === "Int" || mapped.type === "Float") && /^-?\d+(\.\d+)?$/.test(value)) {
3330
+ return { kind: "literal", value: Number(value) };
3331
+ }
3332
+ return { kind: "literal", value };
3333
+ }
3334
+ return { kind: "dbgenerated", sql: raw };
3335
+ }
3336
+ function q5(name) {
3337
+ return sqliteDialect2.quoteIdent({ name });
3338
+ }
3339
+ // packages/migrate/src/introspect/mysql.ts
3340
+ var TABLES_QUERY2 = `
3341
+ SELECT table_name
3342
+ FROM information_schema.tables
3343
+ WHERE table_schema = DATABASE()
3344
+ AND table_type = 'BASE TABLE'
3345
+ AND table_name <> '${MIGRATIONS_TABLE_NAME}'
3346
+ ORDER BY table_name;
3347
+ `;
3348
+ var VIEWS_QUERY2 = `
3349
+ SELECT table_name
3350
+ FROM information_schema.views
3351
+ WHERE table_schema = DATABASE()
3352
+ ORDER BY table_name;
3353
+ `;
3354
+ var COLUMNS_QUERY2 = `
3355
+ SELECT
3356
+ table_name,
3357
+ column_name,
3358
+ column_type,
3359
+ is_nullable,
3360
+ column_default,
3361
+ extra,
3362
+ ordinal_position
3363
+ FROM information_schema.columns
3364
+ WHERE table_schema = DATABASE()
3365
+ ORDER BY table_name, ordinal_position;
3366
+ `;
3367
+ var STATISTICS_QUERY = `
3368
+ SELECT
3369
+ table_name,
3370
+ index_name,
3371
+ non_unique,
3372
+ seq_in_index,
3373
+ column_name,
3374
+ collation
3375
+ FROM information_schema.statistics
3376
+ WHERE table_schema = DATABASE()
3377
+ ORDER BY table_name, index_name, seq_in_index;
3378
+ `;
3379
+ var FOREIGN_KEYS_QUERY2 = `
3380
+ SELECT
3381
+ constraint_name,
3382
+ table_name,
3383
+ column_name,
3384
+ ordinal_position,
3385
+ referenced_table_name,
3386
+ referenced_column_name
3387
+ FROM information_schema.key_column_usage
3388
+ WHERE table_schema = DATABASE()
3389
+ AND referenced_table_name IS NOT NULL
3390
+ ORDER BY table_name, constraint_name, ordinal_position;
3391
+ `;
3392
+ var REFERENTIAL_QUERY = `
3393
+ SELECT constraint_name, update_rule, delete_rule
3394
+ FROM information_schema.referential_constraints
3395
+ WHERE constraint_schema = DATABASE();
3396
+ `;
3397
+ var MYSQL_RULE_TO_CASCADE = {
3398
+ "NO ACTION": "NoAction",
3399
+ RESTRICT: "Restrict",
3400
+ CASCADE: "Cascade",
3401
+ "SET NULL": "SetNull",
3402
+ "SET DEFAULT": "SetDefault"
3403
+ };
3404
+ function catalogField(params) {
3405
+ const value = params.row[params.name];
3406
+ return value !== undefined ? value : params.row[params.name.toUpperCase()];
3407
+ }
3408
+ async function introspectMysql(params) {
3409
+ const { executor } = params;
3410
+ const [tableRows, viewRows, columnRows, statisticRows, fkRows, refRows] = await Promise.all([
3411
+ executor({ text: TABLES_QUERY2 }),
3412
+ executor({ text: VIEWS_QUERY2 }),
3413
+ executor({ text: COLUMNS_QUERY2 }),
3414
+ executor({ text: STATISTICS_QUERY }),
3415
+ executor({ text: FOREIGN_KEYS_QUERY2 }),
3416
+ executor({ text: REFERENTIAL_QUERY })
3417
+ ]);
3418
+ const tables = tableRows.map((row) => catalogField({ row, name: "table_name" }));
3419
+ const views = viewRows.map((row) => catalogField({ row, name: "table_name" }));
3420
+ const columns = columnRows.map((row) => ({
3421
+ table: catalogField({ row, name: "table_name" }),
3422
+ name: catalogField({ row, name: "column_name" }),
3423
+ columnType: catalogField({ row, name: "column_type" }),
3424
+ nullable: catalogField({ row, name: "is_nullable" }) === "YES",
3425
+ columnDefault: catalogField({ row, name: "column_default" }) ?? null,
3426
+ extra: (catalogField({ row, name: "extra" }) ?? "").toLowerCase()
3427
+ }));
3428
+ const rulesByConstraint = new Map;
3429
+ for (const row of refRows) {
3430
+ rulesByConstraint.set(catalogField({ row, name: "constraint_name" }), {
3431
+ onDelete: MYSQL_RULE_TO_CASCADE[(catalogField({ row, name: "delete_rule" }) ?? "NO ACTION").toUpperCase()] ?? "NoAction",
3432
+ onUpdate: MYSQL_RULE_TO_CASCADE[(catalogField({ row, name: "update_rule" }) ?? "NO ACTION").toUpperCase()] ?? "NoAction"
3433
+ });
3434
+ }
3435
+ const groupsByKey = new Map;
3436
+ for (const row of fkRows) {
3437
+ const constraintName = catalogField({ row, name: "constraint_name" });
3438
+ const table = catalogField({ row, name: "table_name" });
3439
+ const key = `${table}\x00${constraintName}`;
3440
+ const existing = groupsByKey.get(key);
3441
+ if (existing === undefined) {
3442
+ groupsByKey.set(key, {
3443
+ constraintName,
3444
+ table,
3445
+ columns: [catalogField({ row, name: "column_name" })],
3446
+ refTable: catalogField({ row, name: "referenced_table_name" }),
3447
+ refColumns: [catalogField({ row, name: "referenced_column_name" })]
3448
+ });
3449
+ } else {
3450
+ existing.columns.push(catalogField({ row, name: "column_name" }));
3451
+ existing.refColumns.push(catalogField({ row, name: "referenced_column_name" }));
3452
+ }
3453
+ }
3454
+ const fkGroups = [...groupsByKey.values()].map((group) => {
3455
+ const rules = rulesByConstraint.get(group.constraintName);
3456
+ return {
3457
+ ...group,
3458
+ onDelete: rules?.onDelete ?? "NoAction",
3459
+ onUpdate: rules?.onUpdate ?? "NoAction",
3460
+ required: group.columns.every((columnName) => {
3461
+ const column = columns.find((candidate) => candidate.table === group.table && candidate.name === columnName);
3462
+ return column === undefined ? true : !column.nullable;
3463
+ })
3464
+ };
3465
+ });
3466
+ const fkNamesByTable = new Map;
3467
+ for (const group of fkGroups) {
3468
+ const set = fkNamesByTable.get(group.table) ?? new Set;
3469
+ set.add(group.constraintName);
3470
+ fkNamesByTable.set(group.table, set);
3471
+ }
3472
+ const pkByTable = new Map;
3473
+ const indexes = [];
3474
+ {
3475
+ const byKey = new Map;
3476
+ for (const row of statisticRows) {
3477
+ const table = catalogField({ row, name: "table_name" });
3478
+ const name = catalogField({ row, name: "index_name" });
3479
+ const columnName = catalogField({ row, name: "column_name" });
3480
+ if (name === "PRIMARY") {
3481
+ const list = pkByTable.get(table);
3482
+ if (list === undefined)
3483
+ pkByTable.set(table, [columnName]);
3484
+ else
3485
+ list.push(columnName);
3486
+ continue;
3487
+ }
3488
+ if (fkNamesByTable.get(table)?.has(name) === true)
3489
+ continue;
3490
+ const key = `${table}\x00${name}`;
3491
+ const entry = byKey.get(key);
3492
+ const column = {
3493
+ name: columnName,
3494
+ desc: catalogField({ row, name: "collation" }) === "D"
3495
+ };
3496
+ if (entry === undefined) {
3497
+ byKey.set(key, {
3498
+ table,
3499
+ name,
3500
+ unique: Number(catalogField({ row, name: "non_unique" })) === 0,
3501
+ columns: [column]
3502
+ });
3503
+ } else {
3504
+ entry.columns.push(column);
3505
+ }
3506
+ }
3507
+ for (const entry of byKey.values()) {
3508
+ indexes.push({ ...entry, kind: "btree" });
3509
+ }
3510
+ }
3511
+ const enums = [];
3512
+ const joinTables = new Map;
3513
+ for (const table of tables) {
3514
+ const detected = detectJoinTable({
3515
+ table,
3516
+ columnNames: columns.filter((column) => column.table === table).map((column) => column.name),
3517
+ fkGroups,
3518
+ indexes
3519
+ });
3520
+ if (detected !== null)
3521
+ joinTables.set(table, detected);
3522
+ }
3523
+ const models = [];
3524
+ for (const table of tables) {
3525
+ if (joinTables.has(table))
3526
+ continue;
3527
+ models.push(buildModel3({ table, isView: false, columns, pkByTable, indexes, enums }));
3528
+ }
3529
+ for (const view of views) {
3530
+ models.push(buildModel3({ table: view, isView: true, columns, pkByTable: new Map, indexes: [], enums }));
3531
+ }
3532
+ addFkRelations({ models, fkGroups, joinTables });
3533
+ addJoinTableRelations({ models, joinTables });
3534
+ return {
3535
+ datasource: { provider: "mysql" },
3536
+ models,
3537
+ enums,
3538
+ extensions: []
3539
+ };
3540
+ }
3541
+ function buildModel3(params) {
3542
+ const { table, isView, columns, pkByTable, indexes, enums } = params;
3543
+ const tableColumns = columns.filter((column) => column.table === table);
3544
+ const pk = pkByTable.get(table) ?? [];
3545
+ const tableIndexes = indexes.filter((index) => index.table === table);
3546
+ const uniqueSingles = new Set;
3547
+ for (const index of tableIndexes) {
3548
+ if (index.unique && index.columns.length === 1) {
3549
+ uniqueSingles.add(index.columns[0].name);
3550
+ }
3551
+ }
3552
+ const fields = tableColumns.map((column) => {
3553
+ const mapped = reverseMapType3({ table, column, enums });
3554
+ const isId = pk.length === 1 && pk[0] === column.name;
3555
+ const defaultValue = parseColumnDefault3({ column, mapped });
3556
+ return {
3557
+ name: column.name,
3558
+ type: mapped.type,
3559
+ isList: false,
3560
+ isOptional: column.nullable,
3561
+ isId,
3562
+ isUnique: uniqueSingles.has(column.name),
3563
+ isUpdatedAt: false,
3564
+ ...defaultValue === undefined ? {} : { default: defaultValue },
3565
+ ...mapped.nativeType === undefined ? {} : { nativeType: mapped.nativeType },
3566
+ ...mapped.isUnsupported === true ? { isUnsupported: true } : {}
3567
+ };
3568
+ });
3569
+ const modelIndexes = [];
3570
+ for (const index of tableIndexes) {
3571
+ if (index.unique && index.columns.length === 1)
3572
+ continue;
3573
+ modelIndexes.push({
3574
+ name: index.name,
3575
+ kind: index.unique ? "unique" : "btree",
3576
+ fields: index.columns.map((column) => ({
3577
+ name: column.name,
3578
+ ...column.desc ? { sort: "desc" } : {}
3579
+ }))
3580
+ });
3581
+ }
3582
+ return {
3583
+ name: table,
3584
+ fields,
3585
+ relations: [],
3586
+ indexes: modelIndexes,
3587
+ isView,
3588
+ ...pk.length > 1 ? { primaryKey: { fields: pk } } : {}
3589
+ };
3590
+ }
3591
+ var CANONICAL_MYSQL = {
3592
+ "varchar(191)": "String",
3593
+ "tinyint(1)": "Boolean",
3594
+ int: "Int",
3595
+ bigint: "BigInt",
3596
+ double: "Float",
3597
+ "decimal(65,30)": "Decimal",
3598
+ "datetime(3)": "DateTime",
3599
+ json: "Json",
3600
+ longblob: "Bytes"
3601
+ };
3602
+ var BASE_TO_SCALAR = {
3603
+ varchar: "String",
3604
+ char: "String",
3605
+ text: "String",
3606
+ tinytext: "String",
3607
+ mediumtext: "String",
3608
+ longtext: "String",
3609
+ tinyint: "Int",
3610
+ smallint: "Int",
3611
+ mediumint: "Int",
3612
+ int: "Int",
3613
+ bigint: "BigInt",
3614
+ double: "Float",
3615
+ float: "Float",
3616
+ real: "Float",
3617
+ decimal: "Decimal",
3618
+ datetime: "DateTime",
3619
+ timestamp: "DateTime",
3620
+ date: "DateTime",
3621
+ json: "Json",
3622
+ blob: "Bytes",
3623
+ tinyblob: "Bytes",
3624
+ mediumblob: "Bytes",
3625
+ longblob: "Bytes",
3626
+ binary: "Bytes",
3627
+ varbinary: "Bytes"
3628
+ };
3629
+ function reverseMapType3(params) {
3630
+ const { table, column, enums } = params;
3631
+ const enumValues = parseMysqlEnumColumnType({ columnType: column.columnType });
3632
+ if (enumValues !== null) {
3633
+ const name = `${table}_${column.name}`;
3634
+ if (!enums.some((entry) => entry.name === name)) {
3635
+ enums.push({ name, values: enumValues });
3636
+ }
3637
+ return { type: { enum: name } };
3638
+ }
3639
+ const normalized = normalizeMysqlType({ raw: column.columnType });
3640
+ const canonical = CANONICAL_MYSQL[normalized];
3641
+ if (canonical !== undefined)
3642
+ return { type: canonical };
3643
+ const base = normalized.match(/^([a-z_0-9]+)/)?.[1] ?? normalized;
3644
+ const scalar = BASE_TO_SCALAR[base];
3645
+ if (scalar !== undefined) {
3646
+ return { type: scalar, nativeType: column.columnType };
3647
+ }
3648
+ return { type: "String", nativeType: column.columnType, isUnsupported: true };
3649
+ }
3650
+ function parseColumnDefault3(params) {
3651
+ const { column, mapped } = params;
3652
+ if (column.extra.includes("auto_increment"))
3653
+ return { kind: "autoincrement" };
3654
+ const raw = column.columnDefault;
3655
+ if (raw === null)
3656
+ return;
3657
+ if (/^current_timestamp(\(\d+\))?$/i.test(raw))
3658
+ return { kind: "now" };
3659
+ if (column.extra.includes("default_generated"))
3660
+ return { kind: "dbgenerated", sql: raw };
3661
+ if (typeof mapped.type === "object")
3662
+ return { kind: "enumValue", value: raw };
3663
+ if (mapped.type === "Boolean") {
3664
+ if (raw === "1" || raw === "0")
3665
+ return { kind: "literal", value: raw === "1" };
3666
+ return { kind: "dbgenerated", sql: raw };
3667
+ }
3668
+ if (isNumericScalar2({ type: mapped.type }) && /^-?\d+(\.\d+)?$/.test(raw)) {
3669
+ return { kind: "literal", value: Number(raw) };
3670
+ }
3671
+ return { kind: "literal", value: raw };
3672
+ }
3673
+ function isNumericScalar2(params) {
3674
+ const { type } = params;
3675
+ return type === "Int" || type === "BigInt" || type === "Float" || type === "Decimal";
3676
+ }
3677
+ // packages/migrate/src/down.ts
3678
+ function buildRollbackTarget(params) {
3679
+ const { from, to, dialect } = params;
3680
+ const skipped = [];
3681
+ const toEnumsByKey = new Map;
3682
+ for (const enumDef of to.enums) {
3683
+ toEnumsByKey.set(enumDef.dbName ?? enumDef.name, enumDef);
3684
+ }
3685
+ let patched = false;
3686
+ const enums = from.enums.map((enumDef) => {
3687
+ const key = enumDef.dbName ?? enumDef.name;
3688
+ const after = toEnumsByKey.get(key);
3689
+ if (after === undefined)
3690
+ return enumDef;
3691
+ const before = new Set(enumDef.values);
3692
+ const added = after.values.filter((value) => !before.has(value));
3693
+ if (added.length === 0)
3694
+ return enumDef;
3695
+ for (const value of added) {
3696
+ skipped.push(`enum "${key}": value "${value}" added by the up migration is kept \u2014 ${dialect} cannot drop enum values`);
3697
+ }
3698
+ patched = true;
3699
+ return { ...enumDef, values: [...enumDef.values, ...added] };
3700
+ });
3701
+ return { target: patched ? { ...from, enums } : from, skipped };
3702
+ }
3703
+ function forceReAddedColumnsNullable(params) {
3704
+ return params.steps.map((step) => {
3705
+ if (step.kind !== "addColumn" || step.column.isOptional)
3706
+ return step;
3707
+ return { ...step, column: { ...step.column, isOptional: true } };
3708
+ });
3709
+ }
3710
+ function generateDownMigration(params) {
3711
+ const { from, to, name } = params;
3712
+ const dialect = params.dialect ?? "postgres";
3713
+ const artifacts = collectExtensionArtifacts({
3714
+ schema: to,
3715
+ dialect,
3716
+ extensions: params.extensions ?? []
3717
+ });
3718
+ const allArtifacts = [...artifacts.setup, ...artifacts.objects];
3719
+ const strippedFrom = stripExtensionArtifacts({ schema: from, artifacts: allArtifacts });
3720
+ const { target, skipped } = buildRollbackTarget({ from: strippedFrom, to, dialect });
3721
+ const toViewNames = new Set(to.models.filter((model) => model.isView).map((model) => model.dbName ?? model.name));
3722
+ for (const model of target.models) {
3723
+ if (!model.isView)
3724
+ continue;
3725
+ const name2 = model.dbName ?? model.name;
3726
+ if (!toViewNames.has(name2)) {
3727
+ skipped.push(`view "${name2}" dropped by the up migration is not re-created \u2014 its definition is not stored in the schema; re-create it manually`);
3728
+ }
3729
+ }
3730
+ const steps = forceReAddedColumnsNullable({
3731
+ steps: diffSchemas({ from: to, to: target, dialect })
3732
+ });
3733
+ const coreSql = renderSteps({ steps, dialect, from: to, to: target });
3734
+ const created = planMissingArtifacts({ introspected: from, artifacts: allArtifacts });
3735
+ const artifactDropSql = [];
3736
+ for (const artifact of [...created].reverse()) {
3737
+ if (artifact.owns.kind === "postgresExtension") {
3738
+ skipped.push(`extension "${artifact.owns.name}" stays installed \u2014 other objects may depend on it (drop it manually if intended)`);
3739
+ continue;
3740
+ }
3741
+ artifactDropSql.push(...artifact.drop);
3742
+ }
3743
+ return {
3744
+ name,
3745
+ sql: [...artifactDropSql, ...coreSql],
3746
+ destructive: steps.some((step) => step.destructive),
3747
+ skipped
3748
+ };
3749
+ }
3750
+ // packages/migrate/src/push.ts
3751
+ import { VibeError as VibeError10, assertValidSchemaIR, providerToDialect } from "@vibeorm/schema";
3752
+ import { DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2 } from "@vibeorm/sql";
3753
+ async function push(params) {
3754
+ const { executor, schema } = params;
3755
+ assertValidSchemaIR({ schema });
3756
+ const dialect = providerToDialect({ provider: schema.datasource.provider });
3757
+ const artifacts = collectExtensionArtifacts({
3758
+ schema,
3759
+ dialect,
3760
+ extensions: params.extensions ?? []
3761
+ });
3762
+ const allArtifacts = [...artifacts.setup, ...artifacts.objects];
3763
+ const introspected = await introspectDialect({ executor, dialect });
3764
+ const from = stripExtensionArtifacts({ schema: introspected, artifacts: allArtifacts });
3765
+ const steps = diffSchemas({ from, to: schema, dialect });
3766
+ const destructive = steps.filter((step) => step.destructive);
3767
+ if (destructive.length > 0 && params.acceptDataLoss !== true) {
3768
+ throw new VibeError10({
3769
+ code: "VIBE_MIGRATION",
3770
+ message: `push refused: ${destructive.length} destructive step(s) would lose data \u2014 pass acceptDataLoss: true to proceed:
3771
+ ${destructive.map((step) => ` - ${describeStep({ step })}`).join(`
3772
+ `)}`,
3773
+ meta: { destructive: destructive.map((step) => describeStep({ step })) }
3774
+ });
3775
+ }
3776
+ const installed = await probeInstalledExtensionState({
3777
+ executor,
3778
+ dialect,
3779
+ artifacts: allArtifacts
3780
+ });
3781
+ const missing = planMissingArtifacts({ introspected, artifacts: allArtifacts, installed });
3782
+ const setupSql = artifactCreateSql({
3783
+ artifacts: missing.filter((artifact) => artifact.owns.kind === "postgresExtension")
3784
+ });
3785
+ const objectSql = artifactCreateSql({
3786
+ artifacts: missing.filter((artifact) => artifact.owns.kind !== "postgresExtension")
3787
+ });
3788
+ const coreSql = renderSteps({ steps, dialect, from, to: schema });
3789
+ const sql = [...setupSql, ...coreSql, ...objectSql];
3790
+ if (sql.length === 0)
3791
+ return { steps, applied: [] };
3792
+ const wrap = DIALECT_CAPABILITIES2[dialect].transactionalDdl && !usesSelfManagedTransactions({ sql });
3793
+ if (wrap)
3794
+ await executor({ text: "BEGIN" });
3795
+ try {
3796
+ for (const statement of sql) {
3797
+ await executeStatement({ executor, statement, dialect });
3798
+ }
3799
+ if (wrap)
3800
+ await executor({ text: "COMMIT" });
3801
+ } catch (error) {
3802
+ if (wrap) {
3803
+ try {
3804
+ await executor({ text: "ROLLBACK" });
3805
+ } catch {}
3806
+ } else if (dialect === "sqlite") {
3807
+ await cleanupSelfManagedFailure({ executor });
3808
+ }
3809
+ throw new VibeError10({
3810
+ code: "VIBE_MIGRATION",
3811
+ message: `push failed while applying schema changes: ${error instanceof Error ? error.message : String(error)}`,
3812
+ meta: { statements: sql },
3813
+ cause: error
3814
+ });
3815
+ }
3816
+ return { steps, applied: sql };
3817
+ }
3818
+ async function pull(params) {
3819
+ return introspectDialect({ executor: params.executor, dialect: params.dialect ?? "postgres" });
3820
+ }
3821
+ async function introspectDialect(params) {
3822
+ const { executor, dialect } = params;
3823
+ switch (dialect) {
3824
+ case "postgres":
3825
+ return introspectPostgres({ executor });
3826
+ case "sqlite":
3827
+ return introspectSqlite({ executor });
3828
+ case "mysql":
3829
+ return introspectMysql({ executor });
3830
+ }
3831
+ }
3832
+ export {
3833
+ stripExtensionArtifacts,
3834
+ rollbackMigration,
3835
+ resolveMigration,
3836
+ renderSteps,
3837
+ push,
3838
+ pull,
3839
+ probeInstalledExtensionState,
3840
+ planMissingArtifacts,
3841
+ migrationStatus,
3842
+ migrationChecksum,
3843
+ introspectSqlite,
3844
+ introspectPostgres,
3845
+ introspectMysql,
3846
+ generateMigration,
3847
+ generateDownMigration,
3848
+ ensureMigrationsTable,
3849
+ diffSchemas,
3850
+ detectImplicitM2MPairs,
3851
+ describeStep,
3852
+ collectExtensionArtifacts,
3853
+ artifactCreateSql,
3854
+ applyMigrations,
3855
+ MIGRATIONS_TABLE_NAME
3856
+ };
3857
+
3858
+ //# debugId=5088DA7EA679FA1464756E2164756E21