zenstack-kit 0.1.1 → 0.1.3

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 (52) hide show
  1. package/dist/cli/app.d.ts.map +1 -1
  2. package/dist/cli/app.js +9 -28
  3. package/dist/cli/commands.d.ts +3 -1
  4. package/dist/cli/commands.d.ts.map +1 -1
  5. package/dist/cli/commands.js +34 -1
  6. package/dist/cli/index.js +0 -0
  7. package/dist/cli/prompts.d.ts +9 -0
  8. package/dist/cli/prompts.d.ts.map +1 -1
  9. package/dist/cli/prompts.js +57 -1
  10. package/dist/migrations/prisma.d.ts +10 -0
  11. package/dist/migrations/prisma.d.ts.map +1 -1
  12. package/dist/migrations/prisma.js +85 -3
  13. package/package.json +1 -5
  14. package/dist/cli.d.ts +0 -12
  15. package/dist/cli.d.ts.map +0 -1
  16. package/dist/cli.js +0 -240
  17. package/dist/config-loader.d.ts +0 -6
  18. package/dist/config-loader.d.ts.map +0 -1
  19. package/dist/config-loader.js +0 -36
  20. package/dist/config.d.ts +0 -62
  21. package/dist/config.d.ts.map +0 -1
  22. package/dist/config.js +0 -44
  23. package/dist/init-prompts.d.ts +0 -13
  24. package/dist/init-prompts.d.ts.map +0 -1
  25. package/dist/init-prompts.js +0 -64
  26. package/dist/introspect.d.ts +0 -54
  27. package/dist/introspect.d.ts.map +0 -1
  28. package/dist/introspect.js +0 -75
  29. package/dist/kysely-adapter.d.ts +0 -49
  30. package/dist/kysely-adapter.d.ts.map +0 -1
  31. package/dist/kysely-adapter.js +0 -74
  32. package/dist/migrate-apply.d.ts +0 -18
  33. package/dist/migrate-apply.d.ts.map +0 -1
  34. package/dist/migrate-apply.js +0 -61
  35. package/dist/migrations.d.ts +0 -161
  36. package/dist/migrations.d.ts.map +0 -1
  37. package/dist/migrations.js +0 -620
  38. package/dist/prisma-migrations.d.ts +0 -160
  39. package/dist/prisma-migrations.d.ts.map +0 -1
  40. package/dist/prisma-migrations.js +0 -789
  41. package/dist/prompts.d.ts +0 -10
  42. package/dist/prompts.d.ts.map +0 -1
  43. package/dist/prompts.js +0 -41
  44. package/dist/pull.d.ts +0 -23
  45. package/dist/pull.d.ts.map +0 -1
  46. package/dist/pull.js +0 -424
  47. package/dist/schema-snapshot.d.ts +0 -45
  48. package/dist/schema-snapshot.d.ts.map +0 -1
  49. package/dist/schema-snapshot.js +0 -265
  50. package/dist/sql-compiler.d.ts +0 -74
  51. package/dist/sql-compiler.d.ts.map +0 -1
  52. package/dist/sql-compiler.js +0 -243
@@ -1,620 +0,0 @@
1
- /**
2
- * Migration generation and management
3
- *
4
- * Creates and manages database migrations based on ZenStack schema changes
5
- * using AST-based diffs and Kysely schema builder operations.
6
- */
7
- import * as fs from "fs/promises";
8
- import * as path from "path";
9
- import { createSnapshot, generateSchemaSnapshot, } from "./schema-snapshot.js";
10
- function getSnapshotPaths(outputPath, snapshotPath) {
11
- if (snapshotPath) {
12
- return {
13
- metaDir: path.dirname(snapshotPath),
14
- snapshotPath,
15
- };
16
- }
17
- const metaDir = path.join(outputPath, "meta");
18
- return {
19
- metaDir,
20
- snapshotPath: path.join(metaDir, "_snapshot.json"),
21
- };
22
- }
23
- async function readSnapshot(snapshotPath) {
24
- try {
25
- const content = await fs.readFile(snapshotPath, "utf-8");
26
- const snapshot = JSON.parse(content);
27
- if (!snapshot || snapshot.version !== 2 || !snapshot.schema) {
28
- throw new Error("Snapshot format is invalid");
29
- }
30
- return snapshot;
31
- }
32
- catch (error) {
33
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
34
- return null;
35
- }
36
- throw error;
37
- }
38
- }
39
- async function writeSnapshot(snapshotPath, schema) {
40
- const snapshot = createSnapshot(schema);
41
- await fs.mkdir(path.dirname(snapshotPath), { recursive: true });
42
- await fs.writeFile(snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
43
- }
44
- function buildColumnBuilder(field) {
45
- const parts = [];
46
- if (field.notNull) {
47
- parts.push("notNull()");
48
- }
49
- if (field.default !== undefined) {
50
- parts.push(`defaultTo(${formatLiteral(field.default)})`);
51
- }
52
- if (parts.length === 0) {
53
- return null;
54
- }
55
- return `(col) => col.${parts.join(".")}`;
56
- }
57
- function formatLiteral(value) {
58
- if (typeof value === "string") {
59
- return JSON.stringify(value);
60
- }
61
- return String(value);
62
- }
63
- function buildCreateTable(model) {
64
- const tableName = model.name;
65
- const columns = model.columns.map((field) => {
66
- const columnName = field.name;
67
- const columnType = field.type;
68
- const builder = buildColumnBuilder(field);
69
- if (builder) {
70
- return `.addColumn('${columnName}', '${columnType}', ${builder})`;
71
- }
72
- return `.addColumn('${columnName}', '${columnType}')`;
73
- });
74
- const constraints = [];
75
- if (model.primaryKey) {
76
- constraints.push(`.addPrimaryKeyConstraint('${model.primaryKey.name}', ${JSON.stringify(model.primaryKey.columns)})`);
77
- }
78
- for (const unique of model.uniqueConstraints) {
79
- constraints.push(`.addUniqueConstraint('${unique.name}', ${JSON.stringify(unique.columns)})`);
80
- }
81
- for (const foreignKey of model.foreignKeys) {
82
- constraints.push(`.addForeignKeyConstraint('${foreignKey.name}', ${JSON.stringify(foreignKey.columns)}, '${foreignKey.referencedTable}', ${JSON.stringify(foreignKey.referencedColumns)})`);
83
- }
84
- const statements = [
85
- `await db.schema.createTable('${tableName}')`,
86
- ...columns,
87
- ...constraints,
88
- ".execute();",
89
- ];
90
- for (const index of model.indexes) {
91
- statements.push(buildCreateIndex(tableName, index.name, index.columns));
92
- }
93
- return statements.join("\n\n");
94
- }
95
- function buildDropTable(model) {
96
- const tableName = model.name;
97
- return `await db.schema.dropTable('${tableName}').ifExists().execute();`;
98
- }
99
- function buildAddColumn(model, field) {
100
- const tableName = model.name;
101
- const columnName = field.name;
102
- const columnType = field.type;
103
- const builder = buildColumnBuilder(field);
104
- if (builder) {
105
- return [
106
- `await db.schema.alterTable('${tableName}')`,
107
- `.addColumn('${columnName}', '${columnType}', ${builder})`,
108
- ".execute();",
109
- ].join("\n");
110
- }
111
- return [
112
- `await db.schema.alterTable('${tableName}')`,
113
- `.addColumn('${columnName}', '${columnType}')`,
114
- ".execute();",
115
- ].join("\n");
116
- }
117
- function buildDropColumn(model, field) {
118
- const tableName = model.name;
119
- const columnName = field.name;
120
- return `await db.schema.alterTable('${tableName}').dropColumn('${columnName}').execute();`;
121
- }
122
- function buildAddPrimaryKeyConstraint(tableName, name, columns) {
123
- return [
124
- `await db.schema.alterTable('${tableName}')`,
125
- `.addPrimaryKeyConstraint('${name}', ${JSON.stringify(columns)})`,
126
- ".execute();",
127
- ].join("\n");
128
- }
129
- function buildAddUniqueConstraint(tableName, name, columns) {
130
- return [
131
- `await db.schema.alterTable('${tableName}')`,
132
- `.addUniqueConstraint('${name}', ${JSON.stringify(columns)})`,
133
- ".execute();",
134
- ].join("\n");
135
- }
136
- function buildDropConstraint(tableName, name) {
137
- return `await db.schema.alterTable('${tableName}').dropConstraint('${name}').execute();`;
138
- }
139
- function buildCreateIndex(tableName, name, columns) {
140
- const statement = [
141
- `await db.schema.createIndex('${name}')`,
142
- `.on('${tableName}')`,
143
- ...columns.map((column) => `.column('${column}')`),
144
- ".execute();",
145
- ];
146
- return statement.join("\n");
147
- }
148
- function buildDropIndex(name) {
149
- return `await db.schema.dropIndex('${name}').execute();`;
150
- }
151
- function buildAddForeignKeyConstraint(tableName, name, columns, referencedTable, referencedColumns) {
152
- return [
153
- `await db.schema.alterTable('${tableName}')`,
154
- `.addForeignKeyConstraint('${name}', ${JSON.stringify(columns)}, '${referencedTable}', ${JSON.stringify(referencedColumns)})`,
155
- ".execute();",
156
- ].join("\n");
157
- }
158
- function buildAlterColumnChanges(change) {
159
- const upStatements = [];
160
- const downStatements = [];
161
- const tableName = change.tableName;
162
- const columnName = change.columnName;
163
- if (change.changes.typeChanged || change.changes.listChanged) {
164
- const upType = change.current.type;
165
- const downType = change.previous.type;
166
- upStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setDataType('${upType}')).execute();`);
167
- downStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setDataType('${downType}')).execute();`);
168
- }
169
- if (change.changes.requiredChanged) {
170
- if (change.current.notNull) {
171
- upStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setNotNull()).execute();`);
172
- downStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.dropNotNull()).execute();`);
173
- }
174
- else {
175
- upStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.dropNotNull()).execute();`);
176
- downStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setNotNull()).execute();`);
177
- }
178
- }
179
- if (change.changes.defaultChanged) {
180
- if (change.current.default !== undefined) {
181
- upStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setDefault(${formatLiteral(change.current.default)})).execute();`);
182
- }
183
- else {
184
- upStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.dropDefault()).execute();`);
185
- }
186
- if (change.previous.default !== undefined) {
187
- downStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.setDefault(${formatLiteral(change.previous.default)})).execute();`);
188
- }
189
- else {
190
- downStatements.push(`await db.schema.alterTable('${tableName}').alterColumn('${columnName}', (ac) => ac.dropDefault()).execute();`);
191
- }
192
- }
193
- return { up: upStatements, down: downStatements };
194
- }
195
- function diffSchemas(previous, current) {
196
- const previousModels = new Map();
197
- const currentModels = new Map();
198
- previous?.tables.forEach((model) => previousModels.set(model.name, model));
199
- current.tables.forEach((model) => currentModels.set(model.name, model));
200
- const addedModels = [];
201
- const removedModels = [];
202
- const addedFields = [];
203
- const removedFields = [];
204
- const alteredFields = [];
205
- const addedUniqueConstraints = [];
206
- const removedUniqueConstraints = [];
207
- const addedIndexes = [];
208
- const removedIndexes = [];
209
- const addedForeignKeys = [];
210
- const removedForeignKeys = [];
211
- const primaryKeyChanges = [];
212
- const renamedTables = [];
213
- const renamedColumns = [];
214
- for (const [tableName, model] of currentModels.entries()) {
215
- if (!previousModels.has(tableName)) {
216
- addedModels.push(model);
217
- }
218
- }
219
- for (const [tableName, model] of previousModels.entries()) {
220
- if (!currentModels.has(tableName)) {
221
- removedModels.push(model);
222
- }
223
- }
224
- for (const [tableName, currentModel] of currentModels.entries()) {
225
- const previousModel = previousModels.get(tableName);
226
- if (!previousModel) {
227
- continue;
228
- }
229
- const modelDiff = diffModelChanges(previousModel, currentModel, tableName);
230
- addedFields.push(...modelDiff.addedFields);
231
- removedFields.push(...modelDiff.removedFields);
232
- alteredFields.push(...modelDiff.alteredFields);
233
- addedUniqueConstraints.push(...modelDiff.addedUniqueConstraints);
234
- removedUniqueConstraints.push(...modelDiff.removedUniqueConstraints);
235
- addedIndexes.push(...modelDiff.addedIndexes);
236
- removedIndexes.push(...modelDiff.removedIndexes);
237
- addedForeignKeys.push(...modelDiff.addedForeignKeys);
238
- removedForeignKeys.push(...modelDiff.removedForeignKeys);
239
- primaryKeyChanges.push(...modelDiff.primaryKeyChanges);
240
- }
241
- return {
242
- addedModels,
243
- removedModels,
244
- addedFields,
245
- removedFields,
246
- alteredFields,
247
- renamedTables,
248
- renamedColumns,
249
- addedUniqueConstraints,
250
- removedUniqueConstraints,
251
- addedIndexes,
252
- removedIndexes,
253
- addedForeignKeys,
254
- removedForeignKeys,
255
- primaryKeyChanges,
256
- };
257
- }
258
- function diffModelChanges(previousModel, currentModel, tableName) {
259
- const addedFields = [];
260
- const removedFields = [];
261
- const alteredFields = [];
262
- const addedUniqueConstraints = [];
263
- const removedUniqueConstraints = [];
264
- const addedIndexes = [];
265
- const removedIndexes = [];
266
- const addedForeignKeys = [];
267
- const removedForeignKeys = [];
268
- const primaryKeyChanges = [];
269
- const previousFields = new Map();
270
- const currentFields = new Map();
271
- previousModel.columns.forEach((field) => previousFields.set(field.name, field));
272
- currentModel.columns.forEach((field) => currentFields.set(field.name, field));
273
- for (const [columnName, field] of currentFields.entries()) {
274
- if (!previousFields.has(columnName)) {
275
- addedFields.push({ model: currentModel, tableName, field, columnName });
276
- }
277
- }
278
- for (const [columnName, field] of previousFields.entries()) {
279
- if (!currentFields.has(columnName)) {
280
- removedFields.push({ model: previousModel, tableName, field, columnName });
281
- }
282
- }
283
- for (const [columnName, currentField] of currentFields.entries()) {
284
- const previousField = previousFields.get(columnName);
285
- if (!previousField) {
286
- continue;
287
- }
288
- const typeChanged = previousField.type !== currentField.type;
289
- const requiredChanged = previousField.notNull !== currentField.notNull;
290
- const defaultChanged = previousField.default !== currentField.default;
291
- const listChanged = previousField.isArray !== currentField.isArray;
292
- if (typeChanged || requiredChanged || defaultChanged || listChanged) {
293
- alteredFields.push({
294
- model: currentModel,
295
- tableName,
296
- columnName,
297
- previous: previousField,
298
- current: currentField,
299
- changes: {
300
- typeChanged,
301
- requiredChanged,
302
- defaultChanged,
303
- listChanged,
304
- },
305
- });
306
- }
307
- }
308
- const previousPk = previousModel.primaryKey;
309
- const currentPk = currentModel.primaryKey;
310
- const pkEqual = (previousPk?.name ?? "") === (currentPk?.name ?? "") &&
311
- JSON.stringify(previousPk?.columns ?? []) === JSON.stringify(currentPk?.columns ?? []);
312
- if (!pkEqual) {
313
- primaryKeyChanges.push({
314
- tableName,
315
- previous: previousPk,
316
- current: currentPk,
317
- });
318
- }
319
- const previousUniqueMap = new Map(previousModel.uniqueConstraints.map((constraint) => [constraint.name, constraint]));
320
- const currentUniqueMap = new Map(currentModel.uniqueConstraints.map((constraint) => [constraint.name, constraint]));
321
- for (const [name, constraint] of currentUniqueMap.entries()) {
322
- if (!previousUniqueMap.has(name)) {
323
- addedUniqueConstraints.push({ tableName, constraint });
324
- }
325
- }
326
- for (const [name, constraint] of previousUniqueMap.entries()) {
327
- if (!currentUniqueMap.has(name)) {
328
- removedUniqueConstraints.push({ tableName, constraint });
329
- }
330
- }
331
- const previousIndexMap = new Map(previousModel.indexes.map((index) => [index.name, index]));
332
- const currentIndexMap = new Map(currentModel.indexes.map((index) => [index.name, index]));
333
- for (const [name, index] of currentIndexMap.entries()) {
334
- if (!previousIndexMap.has(name)) {
335
- addedIndexes.push({ tableName, index });
336
- }
337
- }
338
- for (const [name, index] of previousIndexMap.entries()) {
339
- if (!currentIndexMap.has(name)) {
340
- removedIndexes.push({ tableName, index });
341
- }
342
- }
343
- const previousFkMap = new Map(previousModel.foreignKeys.map((foreignKey) => [foreignKey.name, foreignKey]));
344
- const currentFkMap = new Map(currentModel.foreignKeys.map((foreignKey) => [foreignKey.name, foreignKey]));
345
- for (const [name, foreignKey] of currentFkMap.entries()) {
346
- if (!previousFkMap.has(name)) {
347
- addedForeignKeys.push({ tableName, foreignKey });
348
- }
349
- }
350
- for (const [name, foreignKey] of previousFkMap.entries()) {
351
- if (!currentFkMap.has(name)) {
352
- removedForeignKeys.push({ tableName, foreignKey });
353
- }
354
- }
355
- return {
356
- addedFields,
357
- removedFields,
358
- alteredFields,
359
- addedUniqueConstraints,
360
- removedUniqueConstraints,
361
- addedIndexes,
362
- removedIndexes,
363
- addedForeignKeys,
364
- removedForeignKeys,
365
- primaryKeyChanges,
366
- };
367
- }
368
- function applyRenameMappings(diff, renameTables = [], renameColumns = []) {
369
- const removedModels = [...diff.removedModels];
370
- const addedModels = [...diff.addedModels];
371
- const removedFields = [...diff.removedFields];
372
- const addedFields = [...diff.addedFields];
373
- const alteredFields = [...diff.alteredFields];
374
- const addedUniqueConstraints = [...diff.addedUniqueConstraints];
375
- const removedUniqueConstraints = [...diff.removedUniqueConstraints];
376
- const addedIndexes = [...diff.addedIndexes];
377
- const removedIndexes = [...diff.removedIndexes];
378
- const addedForeignKeys = [...diff.addedForeignKeys];
379
- const removedForeignKeys = [...diff.removedForeignKeys];
380
- const primaryKeyChanges = [...diff.primaryKeyChanges];
381
- const renamedTables = [];
382
- const renamedColumns = [];
383
- const renamedTableMap = new Map();
384
- renameTables.forEach((mapping) => {
385
- const fromIndex = removedModels.findIndex((model) => model.name === mapping.from);
386
- const toIndex = addedModels.findIndex((model) => model.name === mapping.to);
387
- if (fromIndex === -1 || toIndex === -1) {
388
- return;
389
- }
390
- const previousModel = removedModels[fromIndex];
391
- const currentModel = addedModels[toIndex];
392
- removedModels.splice(fromIndex, 1);
393
- addedModels.splice(toIndex, 1);
394
- renamedTables.push({ from: mapping.from, to: mapping.to });
395
- renamedTableMap.set(mapping.from, mapping.to);
396
- const modelDiff = diffModelChanges(previousModel, currentModel, mapping.to);
397
- addedFields.push(...modelDiff.addedFields);
398
- removedFields.push(...modelDiff.removedFields);
399
- alteredFields.push(...modelDiff.alteredFields);
400
- addedUniqueConstraints.push(...modelDiff.addedUniqueConstraints);
401
- removedUniqueConstraints.push(...modelDiff.removedUniqueConstraints);
402
- addedIndexes.push(...modelDiff.addedIndexes);
403
- removedIndexes.push(...modelDiff.removedIndexes);
404
- addedForeignKeys.push(...modelDiff.addedForeignKeys);
405
- removedForeignKeys.push(...modelDiff.removedForeignKeys);
406
- primaryKeyChanges.push(...modelDiff.primaryKeyChanges);
407
- });
408
- if (renamedTableMap.size > 0) {
409
- removedFields.forEach((entry) => {
410
- const mapped = renamedTableMap.get(entry.tableName);
411
- if (mapped) {
412
- entry.tableName = mapped;
413
- }
414
- });
415
- }
416
- const remapTableName = (tableName) => renamedTableMap.get(tableName) ?? tableName;
417
- const remapTableEntries = (items) => items.map((item) => ({ ...item, tableName: remapTableName(item.tableName) }));
418
- renameColumns.forEach((mapping) => {
419
- const fromIndex = removedFields.findIndex((entry) => entry.tableName === mapping.table && entry.columnName === mapping.from);
420
- const toIndex = addedFields.findIndex((entry) => entry.tableName === mapping.table && entry.columnName === mapping.to);
421
- if (fromIndex === -1 || toIndex === -1) {
422
- return;
423
- }
424
- removedFields.splice(fromIndex, 1);
425
- addedFields.splice(toIndex, 1);
426
- renamedColumns.push({ tableName: mapping.table, from: mapping.from, to: mapping.to });
427
- });
428
- return {
429
- ...diff,
430
- removedModels,
431
- addedModels,
432
- removedFields,
433
- addedFields,
434
- alteredFields,
435
- renamedTables,
436
- renamedColumns,
437
- addedUniqueConstraints: remapTableEntries(addedUniqueConstraints),
438
- removedUniqueConstraints: remapTableEntries(removedUniqueConstraints),
439
- addedIndexes: remapTableEntries(addedIndexes),
440
- removedIndexes: remapTableEntries(removedIndexes),
441
- addedForeignKeys: remapTableEntries(addedForeignKeys),
442
- removedForeignKeys: remapTableEntries(removedForeignKeys),
443
- primaryKeyChanges: remapTableEntries(primaryKeyChanges),
444
- };
445
- }
446
- function buildMigrationPlan(diff) {
447
- const upStatements = [];
448
- const downStatements = [];
449
- diff.renamedTables.forEach((rename) => {
450
- upStatements.push(`await db.schema.alterTable('${rename.from}').renameTo('${rename.to}').execute();`);
451
- downStatements.unshift(`await db.schema.alterTable('${rename.to}').renameTo('${rename.from}').execute();`);
452
- });
453
- diff.renamedColumns.forEach((rename) => {
454
- upStatements.push(`await db.schema.alterTable('${rename.tableName}').renameColumn('${rename.from}', '${rename.to}').execute();`);
455
- downStatements.unshift(`await db.schema.alterTable('${rename.tableName}').renameColumn('${rename.to}', '${rename.from}').execute();`);
456
- });
457
- diff.addedModels.forEach((model) => {
458
- upStatements.push(buildCreateTable(model));
459
- downStatements.unshift(buildDropTable(model));
460
- });
461
- diff.removedModels.forEach((model) => {
462
- upStatements.push(buildDropTable(model));
463
- downStatements.unshift(buildCreateTable(model));
464
- });
465
- diff.primaryKeyChanges.forEach((change) => {
466
- if (change.previous) {
467
- upStatements.push(buildDropConstraint(change.tableName, change.previous.name));
468
- downStatements.unshift(buildAddPrimaryKeyConstraint(change.tableName, change.previous.name, change.previous.columns));
469
- }
470
- });
471
- diff.removedForeignKeys.forEach(({ tableName, foreignKey }) => {
472
- upStatements.push(buildDropConstraint(tableName, foreignKey.name));
473
- downStatements.unshift(buildAddForeignKeyConstraint(tableName, foreignKey.name, foreignKey.columns, foreignKey.referencedTable, foreignKey.referencedColumns));
474
- });
475
- diff.removedUniqueConstraints.forEach(({ tableName, constraint }) => {
476
- upStatements.push(buildDropConstraint(tableName, constraint.name));
477
- downStatements.unshift(buildAddUniqueConstraint(tableName, constraint.name, constraint.columns));
478
- });
479
- diff.removedIndexes.forEach(({ tableName, index }) => {
480
- upStatements.push(buildDropIndex(index.name));
481
- downStatements.unshift(buildCreateIndex(tableName, index.name, index.columns));
482
- });
483
- diff.addedFields.forEach(({ model, field }) => {
484
- upStatements.push(buildAddColumn(model, field));
485
- downStatements.unshift(buildDropColumn(model, field));
486
- });
487
- diff.removedFields.forEach(({ model, field }) => {
488
- upStatements.push(buildDropColumn(model, field));
489
- downStatements.unshift(buildAddColumn(model, field));
490
- });
491
- diff.alteredFields.forEach((change) => {
492
- const alterations = buildAlterColumnChanges(change);
493
- upStatements.push(...alterations.up);
494
- downStatements.unshift(...alterations.down);
495
- });
496
- diff.primaryKeyChanges.forEach((change) => {
497
- if (change.current) {
498
- upStatements.push(buildAddPrimaryKeyConstraint(change.tableName, change.current.name, change.current.columns));
499
- downStatements.unshift(buildDropConstraint(change.tableName, change.current.name));
500
- }
501
- });
502
- diff.addedUniqueConstraints.forEach(({ tableName, constraint }) => {
503
- upStatements.push(buildAddUniqueConstraint(tableName, constraint.name, constraint.columns));
504
- downStatements.unshift(buildDropConstraint(tableName, constraint.name));
505
- });
506
- diff.addedIndexes.forEach(({ tableName, index }) => {
507
- upStatements.push(buildCreateIndex(tableName, index.name, index.columns));
508
- downStatements.unshift(buildDropIndex(index.name));
509
- });
510
- diff.addedForeignKeys.forEach(({ tableName, foreignKey }) => {
511
- upStatements.push(buildAddForeignKeyConstraint(tableName, foreignKey.name, foreignKey.columns, foreignKey.referencedTable, foreignKey.referencedColumns));
512
- downStatements.unshift(buildDropConstraint(tableName, foreignKey.name));
513
- });
514
- return { upStatements, downStatements };
515
- }
516
- function generateTimestamp() {
517
- const now = new Date();
518
- return [
519
- now.getFullYear(),
520
- String(now.getMonth() + 1).padStart(2, "0"),
521
- String(now.getDate()).padStart(2, "0"),
522
- String(now.getHours()).padStart(2, "0"),
523
- String(now.getMinutes()).padStart(2, "0"),
524
- String(now.getSeconds()).padStart(2, "0"),
525
- ].join("");
526
- }
527
- function formatStatements(statements) {
528
- if (statements.length === 0) {
529
- return " // No schema changes detected";
530
- }
531
- return statements.map((statement) => indentLines(statement, 2)).join("\n\n");
532
- }
533
- function indentLines(text, spaces) {
534
- const indent = " ".repeat(spaces);
535
- return text
536
- .split("\n")
537
- .map((line) => `${indent}${line}`)
538
- .join("\n");
539
- }
540
- export async function getSchemaDiff(options) {
541
- const currentSchema = await generateSchemaSnapshot(options.schemaPath);
542
- const { snapshotPath } = getSnapshotPaths(options.outputPath, options.snapshotPath);
543
- const previousSnapshot = await readSnapshot(snapshotPath);
544
- return applyRenameMappings(diffSchemas(previousSnapshot?.schema ?? null, currentSchema), options.renameTables, options.renameColumns);
545
- }
546
- export async function hasSchemaChanges(options) {
547
- const diff = await getSchemaDiff(options);
548
- return (diff.addedModels.length > 0 ||
549
- diff.removedModels.length > 0 ||
550
- diff.addedFields.length > 0 ||
551
- diff.removedFields.length > 0 ||
552
- diff.alteredFields.length > 0 ||
553
- diff.addedUniqueConstraints.length > 0 ||
554
- diff.removedUniqueConstraints.length > 0 ||
555
- diff.addedIndexes.length > 0 ||
556
- diff.removedIndexes.length > 0 ||
557
- diff.addedForeignKeys.length > 0 ||
558
- diff.removedForeignKeys.length > 0 ||
559
- diff.primaryKeyChanges.length > 0 ||
560
- diff.renamedTables.length > 0 ||
561
- diff.renamedColumns.length > 0);
562
- }
563
- /**
564
- * Initialize a snapshot from the current schema without generating a migration.
565
- * Use this to baseline an existing database before starting to track migrations.
566
- */
567
- export async function initSnapshot(options) {
568
- const currentSchema = await generateSchemaSnapshot(options.schemaPath);
569
- const { snapshotPath } = getSnapshotPaths(options.outputPath, options.snapshotPath);
570
- await writeSnapshot(snapshotPath, currentSchema);
571
- return {
572
- snapshotPath,
573
- tableCount: currentSchema.tables.length,
574
- };
575
- }
576
- /**
577
- * Create a migration file from schema changes
578
- */
579
- export async function createMigration(options) {
580
- if (!options.name) {
581
- throw new Error("Migration name is required");
582
- }
583
- const currentSchema = await generateSchemaSnapshot(options.schemaPath);
584
- const { snapshotPath } = getSnapshotPaths(options.outputPath, options.snapshotPath);
585
- const previousSnapshot = await readSnapshot(snapshotPath);
586
- const diff = applyRenameMappings(diffSchemas(previousSnapshot?.schema ?? null, currentSchema), options.renameTables, options.renameColumns);
587
- const plan = buildMigrationPlan(diff);
588
- if (plan.upStatements.length === 0) {
589
- return null;
590
- }
591
- const timestamp = Date.now();
592
- const timestampStr = generateTimestamp();
593
- const safeName = options.name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
594
- const filename = `${timestampStr}_${safeName}.ts`;
595
- const upContent = formatStatements(plan.upStatements);
596
- const downContent = formatStatements(plan.downStatements);
597
- const content = `// Migration: ${options.name}
598
- // Generated at: ${new Date(timestamp).toISOString()}
599
-
600
- import type { Kysely } from "kysely";
601
-
602
- export async function up(db: Kysely<any>): Promise<void> {
603
- ${upContent}
604
- }
605
-
606
- export async function down(db: Kysely<any>): Promise<void> {
607
- ${downContent}
608
- }
609
- `;
610
- await fs.mkdir(options.outputPath, { recursive: true });
611
- const outputFile = path.join(options.outputPath, filename);
612
- await fs.writeFile(outputFile, content, "utf-8");
613
- await writeSnapshot(snapshotPath, currentSchema);
614
- return {
615
- filename,
616
- up: plan.upStatements.join("\n\n"),
617
- down: plan.downStatements.join("\n\n"),
618
- timestamp,
619
- };
620
- }