@vibeorm/migrate 1.3.1 → 2.0.0-alpha.10

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 (73) 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/rls.d.ts +40 -0
  11. package/dist/ddl/rls.d.ts.map +1 -0
  12. package/dist/ddl/sqlite.d.ts +79 -0
  13. package/dist/ddl/sqlite.d.ts.map +1 -0
  14. package/dist/differ.d.ts +41 -0
  15. package/dist/differ.d.ts.map +1 -0
  16. package/dist/down.d.ts +57 -0
  17. package/dist/down.d.ts.map +1 -0
  18. package/dist/enum-values.d.ts +32 -0
  19. package/dist/enum-values.d.ts.map +1 -0
  20. package/dist/execute.d.ts +124 -0
  21. package/dist/execute.d.ts.map +1 -0
  22. package/dist/execution.d.ts +168 -0
  23. package/dist/execution.d.ts.map +1 -0
  24. package/dist/extensions.d.ts +74 -0
  25. package/dist/extensions.d.ts.map +1 -0
  26. package/dist/index.d.ts +32 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +5688 -0
  29. package/dist/index.js.map +37 -0
  30. package/dist/introspect/mysql.d.ts +31 -0
  31. package/dist/introspect/mysql.d.ts.map +1 -0
  32. package/dist/introspect/postgres-rls.d.ts +41 -0
  33. package/dist/introspect/postgres-rls.d.ts.map +1 -0
  34. package/dist/introspect/postgres.d.ts +12 -0
  35. package/dist/introspect/postgres.d.ts.map +1 -0
  36. package/dist/introspect/shared.d.ts +78 -0
  37. package/dist/introspect/shared.d.ts.map +1 -0
  38. package/dist/introspect/sqlite.d.ts +53 -0
  39. package/dist/introspect/sqlite.d.ts.map +1 -0
  40. package/dist/journal-table.d.ts +15 -0
  41. package/dist/journal-table.d.ts.map +1 -0
  42. package/dist/mysql-types.d.ts +66 -0
  43. package/dist/mysql-types.d.ts.map +1 -0
  44. package/dist/normalize.d.ts +143 -0
  45. package/dist/normalize.d.ts.map +1 -0
  46. package/dist/postgres-types.d.ts +95 -0
  47. package/dist/postgres-types.d.ts.map +1 -0
  48. package/dist/push.d.ts +51 -0
  49. package/dist/push.d.ts.map +1 -0
  50. package/dist/recovery.d.ts +41 -0
  51. package/dist/recovery.d.ts.map +1 -0
  52. package/dist/relations.d.ts +40 -0
  53. package/dist/relations.d.ts.map +1 -0
  54. package/dist/renames.d.ts +58 -0
  55. package/dist/renames.d.ts.map +1 -0
  56. package/dist/runner.d.ts +181 -0
  57. package/dist/runner.d.ts.map +1 -0
  58. package/dist/sqlite-types.d.ts +101 -0
  59. package/dist/sqlite-types.d.ts.map +1 -0
  60. package/dist/types.d.ts +329 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/package.json +34 -23
  63. package/src/cascade-actions.ts +0 -88
  64. package/src/ddl-builder.ts +0 -415
  65. package/src/index.ts +0 -41
  66. package/src/introspector.ts +0 -684
  67. package/src/migration-runner.ts +0 -127
  68. package/src/relation-utils.ts +0 -79
  69. package/src/schema-differ.ts +0 -865
  70. package/src/schema-printer.ts +0 -259
  71. package/src/snapshot.ts +0 -141
  72. package/src/sql-utils.ts +0 -45
  73. package/src/types.ts +0 -13
@@ -1,415 +0,0 @@
1
- /**
2
- * DDL Builder — Schema IR → DDL SQL
3
- *
4
- * Takes a parsed Schema IR and produces the complete set of DDL statements
5
- * to create the database from scratch (enums, tables, constraints, indexes, FKs).
6
- */
7
-
8
- import type { Schema, Model, Field, ScalarField, EnumField, RelationField, DefaultValue, Enum, EnumValue } from "@vibeorm/parser";
9
- import { PRISMA_TO_PG } from "@vibeorm/parser";
10
- import { buildFkActionClause } from "./cascade-actions.ts";
11
- import { detectImplicitM2MPairs } from "./relation-utils.ts";
12
-
13
- export type DDLResult = {
14
- /** The full DDL SQL wrapped in BEGIN/COMMIT */
15
- sql: string;
16
- /** Individual DDL statements (without BEGIN/COMMIT wrapper) */
17
- statements: string[];
18
- };
19
-
20
- // ─── Helpers ──────────────────────────────────────────────────────
21
-
22
- function q(params: { name: string }): string {
23
- return `"${params.name}"`;
24
- }
25
-
26
- function getDbName(params: { model: Model }): string {
27
- return params.model.dbName;
28
- }
29
-
30
- function getFieldDbName(params: { field: ScalarField | EnumField }): string {
31
- return params.field.dbName;
32
- }
33
-
34
- function resolveColumnType(params: { field: ScalarField }): string {
35
- const { field } = params;
36
-
37
- // Autoincrement uses SERIAL
38
- if (field.default?.kind === "autoincrement") {
39
- return field.prismaType === "BigInt" ? "BIGSERIAL" : "SERIAL";
40
- }
41
-
42
- // Native type override (e.g., @db.VarChar(255)) — lowercased to canonical PG form
43
- if (field.nativeType) {
44
- return field.nativeType.toLowerCase();
45
- }
46
-
47
- // List fields use PostgreSQL arrays
48
- if (field.isList) {
49
- return `${PRISMA_TO_PG[field.prismaType]}[]`;
50
- }
51
-
52
- return PRISMA_TO_PG[field.prismaType];
53
- }
54
-
55
- /** Resolve to the base column type (no SERIAL — used for FK reference columns) */
56
- function resolveBaseColumnType(params: { field: ScalarField }): string {
57
- const { field } = params;
58
- if (field.nativeType) return field.nativeType.toLowerCase();
59
- if (field.isList) return `${PRISMA_TO_PG[field.prismaType]}[]`;
60
- return PRISMA_TO_PG[field.prismaType];
61
- }
62
-
63
- function resolveDefault(params: { field: ScalarField | EnumField }): string | undefined {
64
- const { field } = params;
65
- const def = field.default;
66
- if (!def) return undefined;
67
-
68
- switch (def.kind) {
69
- case "autoincrement":
70
- // Handled by SERIAL type, no explicit DEFAULT needed
71
- return undefined;
72
- case "now":
73
- return "CURRENT_TIMESTAMP";
74
- case "uuid":
75
- return "gen_random_uuid()::TEXT";
76
- case "cuid":
77
- // No native PG equivalent, handled at application level
78
- return undefined;
79
- case "nanoid":
80
- // No native PG equivalent, handled at application level
81
- return undefined;
82
- case "ulid":
83
- // No native PG equivalent, handled at application level
84
- return undefined;
85
- case "dbgenerated":
86
- return def.value;
87
- case "literal": {
88
- const val = def.value;
89
- if (typeof val === "string") return `'${val.replace(/'/g, "''")}'`;
90
- if (typeof val === "boolean") return val ? "true" : "false";
91
- return String(val);
92
- }
93
- default:
94
- return undefined;
95
- }
96
- }
97
-
98
- // ─── Enum DDL ─────────────────────────────────────────────────────
99
-
100
- function buildEnumStatements(params: { enums: Enum[] }): string[] {
101
- const statements: string[] = [];
102
-
103
- for (const enumDef of params.enums) {
104
- const typeName = enumDef.dbName ?? enumDef.name;
105
- const values = enumDef.values
106
- .map((v) => `'${(v.dbName ?? v.name).replace(/'/g, "''")}'`)
107
- .join(", ");
108
-
109
- // Use DO block for safe creation (no IF NOT EXISTS for CREATE TYPE)
110
- statements.push(
111
- `DO $$ BEGIN\n` +
112
- ` CREATE TYPE ${q({ name: typeName })} AS ENUM (${values});\n` +
113
- `EXCEPTION WHEN duplicate_object THEN NULL;\n` +
114
- `END $$;`
115
- );
116
- }
117
-
118
- return statements;
119
- }
120
-
121
- // ─── Table DDL ────────────────────────────────────────────────────
122
-
123
- function buildTableStatement(params: { model: Model; enums: Enum[] }): string {
124
- const { model, enums } = params;
125
- const tableName = getDbName({ model });
126
- const columns: string[] = [];
127
- const enumNames = new Set(enums.map((e) => e.name));
128
-
129
- // Collect scalar + enum fields (not relations)
130
- for (const field of model.fields) {
131
- if (field.kind === "relation") continue;
132
-
133
- const colName = getFieldDbName({ field });
134
- let colType: string;
135
-
136
- if (field.kind === "enum") {
137
- const enumDef = enums.find((e) => e.name === field.enumName);
138
- const enumDbName = enumDef?.dbName ?? field.enumName;
139
- colType = q({ name: enumDbName });
140
- if (field.isList) colType += "[]";
141
- } else {
142
- colType = resolveColumnType({ field });
143
- }
144
-
145
- let colDef = ` ${q({ name: colName })} ${colType}`;
146
-
147
- // NOT NULL (unless optional or autoincrement serial which is implicitly NOT NULL)
148
- if (field.kind === "scalar" && field.isRequired && field.default?.kind !== "autoincrement") {
149
- colDef += " NOT NULL";
150
- } else if (field.kind === "enum" && field.isRequired) {
151
- colDef += " NOT NULL";
152
- }
153
-
154
- // DEFAULT
155
- const defaultExpr = resolveDefault({ field });
156
- if (defaultExpr) {
157
- colDef += ` DEFAULT ${defaultExpr}`;
158
- }
159
-
160
- // Inline UNIQUE for single-field unique
161
- if (field.isUnique) {
162
- colDef += " UNIQUE";
163
- }
164
-
165
- columns.push(colDef);
166
- }
167
-
168
- // Primary key constraint
169
- const pkFields = model.primaryKey.fields
170
- .map((f) => {
171
- const field = model.fields.find((fld) => fld.name === f);
172
- if (field && field.kind !== "relation") {
173
- return q({ name: getFieldDbName({ field }) });
174
- }
175
- return q({ name: f });
176
- })
177
- .join(", ");
178
-
179
- const pkName = `${tableName}_pkey`;
180
- columns.push(` CONSTRAINT ${q({ name: pkName })} PRIMARY KEY (${pkFields})`);
181
-
182
- return `CREATE TABLE ${q({ name: tableName })} (\n${columns.join(",\n")}\n);`;
183
- }
184
-
185
- // ─── Unique Index DDL ─────────────────────────────────────────────
186
-
187
- function buildUniqueIndexStatements(params: { model: Model }): string[] {
188
- const { model } = params;
189
- const statements: string[] = [];
190
- const tableName = getDbName({ model });
191
-
192
- for (const unique of model.uniqueConstraints) {
193
- // Skip single-field uniques (handled inline on column)
194
- if (unique.fields.length === 1) continue;
195
-
196
- const cols = unique.fields
197
- .map((f) => {
198
- const field = model.fields.find((fld) => fld.name === f);
199
- if (field && field.kind !== "relation") {
200
- return q({ name: getFieldDbName({ field }) });
201
- }
202
- return q({ name: f });
203
- })
204
- .join(", ");
205
-
206
- const idxName = unique.name ?? `${tableName}_${unique.fields.join("_")}_key`;
207
- statements.push(
208
- `CREATE UNIQUE INDEX ${q({ name: idxName })} ON ${q({ name: tableName })} (${cols});`
209
- );
210
- }
211
-
212
- return statements;
213
- }
214
-
215
- // ─── Regular Index DDL ────────────────────────────────────────────
216
-
217
- function buildIndexStatements(params: { model: Model }): string[] {
218
- const { model } = params;
219
- const statements: string[] = [];
220
- const tableName = getDbName({ model });
221
-
222
- for (const idx of model.indexes) {
223
- const cols = idx.fields
224
- .map((f) => {
225
- const field = model.fields.find((fld) => fld.name === f);
226
- if (field && field.kind !== "relation") {
227
- return q({ name: getFieldDbName({ field }) });
228
- }
229
- return q({ name: f });
230
- })
231
- .join(", ");
232
-
233
- const idxName = idx.name ?? `${tableName}_${idx.fields.join("_")}_idx`;
234
- statements.push(
235
- `CREATE INDEX ${q({ name: idxName })} ON ${q({ name: tableName })} (${cols});`
236
- );
237
- }
238
-
239
- return statements;
240
- }
241
-
242
- // ─── Foreign Key DDL ──────────────────────────────────────────────
243
-
244
- function buildForeignKeyStatements(params: { model: Model; allModels: Model[] }): string[] {
245
- const { model, allModels } = params;
246
- const statements: string[] = [];
247
- const tableName = getDbName({ model });
248
-
249
- for (const field of model.fields) {
250
- if (field.kind !== "relation") continue;
251
- if (!field.relation.isForeignKey) continue;
252
- if (field.relation.fields.length === 0) continue;
253
-
254
- const relatedModel = allModels.find((m) => m.name === field.relatedModel);
255
- if (!relatedModel) continue;
256
-
257
- const relatedTableName = getDbName({ model: relatedModel });
258
-
259
- const fkCols = field.relation.fields
260
- .map((f) => {
261
- const fld = model.fields.find((mf) => mf.name === f);
262
- if (fld && fld.kind !== "relation") return q({ name: getFieldDbName({ field: fld }) });
263
- return q({ name: f });
264
- })
265
- .join(", ");
266
-
267
- const refCols = field.relation.references
268
- .map((f) => {
269
- const fld = relatedModel.fields.find((mf) => mf.name === f);
270
- if (fld && fld.kind !== "relation") return q({ name: getFieldDbName({ field: fld }) });
271
- return q({ name: f });
272
- })
273
- .join(", ");
274
-
275
- const constraintName = `${tableName}_${field.relation.fields.join("_")}_fkey`;
276
- const actionClause = buildFkActionClause({ rel: field });
277
- statements.push(
278
- `ALTER TABLE ${q({ name: tableName })} ADD CONSTRAINT ${q({ name: constraintName })} ` +
279
- `FOREIGN KEY (${fkCols}) REFERENCES ${q({ name: relatedTableName })} (${refCols}) ` +
280
- `${actionClause};`
281
- );
282
- }
283
-
284
- return statements;
285
- }
286
-
287
- // ─── Implicit M:N Join Table ──────────────────────────────────────
288
-
289
- type ImplicitManyToMany = {
290
- modelA: string;
291
- modelB: string;
292
- tableA: string;
293
- tableB: string;
294
- pkFieldA: string;
295
- pkFieldB: string;
296
- pkTypeA: string;
297
- pkTypeB: string;
298
- };
299
-
300
- function detectImplicitManyToMany(params: { schema: Schema }): ImplicitManyToMany[] {
301
- const { schema } = params;
302
-
303
- // Detection lives in the shared helper (single source of truth with the
304
- // schema differ); here we only resolve the per-table DDL details (PK column
305
- // names + types) for each detected pair.
306
- return detectImplicitM2MPairs({ schema }).map((pair) => {
307
- const modelADef = schema.models.find((m) => m.name === pair.modelA)!;
308
- const modelBDef = schema.models.find((m) => m.name === pair.modelB)!;
309
-
310
- const pkFieldA = modelADef.primaryKey.fields[0]!;
311
- const pkFieldB = modelBDef.primaryKey.fields[0]!;
312
-
313
- const pkFieldDefA = modelADef.fields.find((f) => f.name === pkFieldA);
314
- const pkFieldDefB = modelBDef.fields.find((f) => f.name === pkFieldB);
315
-
316
- const pkTypeA = pkFieldDefA && pkFieldDefA.kind === "scalar"
317
- ? resolveBaseColumnType({ field: pkFieldDefA })
318
- : "INTEGER";
319
- const pkTypeB = pkFieldDefB && pkFieldDefB.kind === "scalar"
320
- ? resolveBaseColumnType({ field: pkFieldDefB })
321
- : "INTEGER";
322
-
323
- return {
324
- modelA: pair.modelA,
325
- modelB: pair.modelB,
326
- tableA: getDbName({ model: modelADef }),
327
- tableB: getDbName({ model: modelBDef }),
328
- pkFieldA,
329
- pkFieldB,
330
- pkTypeA,
331
- pkTypeB,
332
- };
333
- });
334
- }
335
-
336
- function buildJoinTableStatements(params: { joinTable: ImplicitManyToMany }): string[] {
337
- const { joinTable } = params;
338
- const tableName = `_${joinTable.modelA}To${joinTable.modelB}`;
339
- const statements: string[] = [];
340
-
341
- // Create join table
342
- statements.push(
343
- `CREATE TABLE ${q({ name: tableName })} (\n` +
344
- ` "A" ${joinTable.pkTypeA} NOT NULL,\n` +
345
- ` "B" ${joinTable.pkTypeB} NOT NULL\n` +
346
- `);`
347
- );
348
-
349
- // Composite unique index on (A, B)
350
- statements.push(
351
- `CREATE UNIQUE INDEX ${q({ name: `${tableName}_AB_unique` })} ON ${q({ name: tableName })} ("A", "B");`
352
- );
353
-
354
- // Index on B for reverse lookups
355
- statements.push(
356
- `CREATE INDEX ${q({ name: `${tableName}_B_index` })} ON ${q({ name: tableName })} ("B");`
357
- );
358
-
359
- // Foreign key A → modelA
360
- statements.push(
361
- `ALTER TABLE ${q({ name: tableName })} ADD CONSTRAINT ${q({ name: `${tableName}_A_fkey` })} ` +
362
- `FOREIGN KEY ("A") REFERENCES ${q({ name: joinTable.tableA })} ("${joinTable.pkFieldA}") ` +
363
- `ON DELETE CASCADE ON UPDATE CASCADE;`
364
- );
365
-
366
- // Foreign key B → modelB
367
- statements.push(
368
- `ALTER TABLE ${q({ name: tableName })} ADD CONSTRAINT ${q({ name: `${tableName}_B_fkey` })} ` +
369
- `FOREIGN KEY ("B") REFERENCES ${q({ name: joinTable.tableB })} ("${joinTable.pkFieldB}") ` +
370
- `ON DELETE CASCADE ON UPDATE CASCADE;`
371
- );
372
-
373
- return statements;
374
- }
375
-
376
- // ─── Main Entry Point ─────────────────────────────────────────────
377
-
378
- export function buildDDL(params: { schema: Schema }): DDLResult {
379
- const { schema } = params;
380
- const statements: string[] = [];
381
-
382
- // 1. Enums
383
- statements.push(...buildEnumStatements({ enums: schema.enums }));
384
-
385
- // 2. Tables
386
- for (const model of schema.models) {
387
- statements.push(buildTableStatement({ model, enums: schema.enums }));
388
- }
389
-
390
- // 3. Unique indexes (multi-column only; single-column handled inline)
391
- for (const model of schema.models) {
392
- statements.push(...buildUniqueIndexStatements({ model }));
393
- }
394
-
395
- // 4. Regular indexes
396
- for (const model of schema.models) {
397
- statements.push(...buildIndexStatements({ model }));
398
- }
399
-
400
- // 5. Foreign keys (after all tables exist)
401
- for (const model of schema.models) {
402
- statements.push(...buildForeignKeyStatements({ model, allModels: schema.models }));
403
- }
404
-
405
- // 6. Implicit M:N join tables
406
- const joinTables = detectImplicitManyToMany({ schema });
407
- for (const jt of joinTables) {
408
- statements.push(...buildJoinTableStatements({ joinTable: jt }));
409
- }
410
-
411
- // Wrap in transaction
412
- const sql = `BEGIN;\n\n${statements.join("\n\n")}\n\nCOMMIT;`;
413
-
414
- return { sql, statements };
415
- }
package/src/index.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * @vibeorm/migrate
3
- *
4
- * Core migration modules: DDL generation, database introspection,
5
- * schema diffing, snapshot management, migration execution, and schema printing.
6
- */
7
-
8
- export { buildDDL } from "./ddl-builder.ts";
9
- export type { DDLResult } from "./ddl-builder.ts";
10
-
11
- export { introspect } from "./introspector.ts";
12
-
13
- export { diffSchemas } from "./schema-differ.ts";
14
- export type { DiffOperation, DiffOperationType } from "./schema-differ.ts";
15
-
16
- export {
17
- saveSnapshot,
18
- loadLatestSnapshot,
19
- loadSnapshot,
20
- saveJournal,
21
- loadJournal,
22
- generateTimestamp,
23
- computeChecksum,
24
- } from "./snapshot.ts";
25
- export type { JournalEntry, Journal } from "./snapshot.ts";
26
-
27
- export {
28
- ensureMigrationTable,
29
- getAppliedMigrations,
30
- applyMigration,
31
- removeMigrationRecord,
32
- markMigrationApplied,
33
- rollbackMigration,
34
- } from "./migration-runner.ts";
35
- export type { AppliedMigration } from "./migration-runner.ts";
36
-
37
- export { printSchema } from "./schema-printer.ts";
38
-
39
- export { splitSqlStatements } from "./sql-utils.ts";
40
-
41
- export type { SqlExecutor } from "./types.ts";