zenstack-kit 0.1.11 → 0.1.13

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.
package/README.md CHANGED
@@ -100,6 +100,16 @@ zenstack-kit migrate apply
100
100
 
101
101
  Migrations are tracked in the `_prisma_migrations` table, making them compatible with `prisma migrate deploy`.
102
102
 
103
+ ### Migration log and checksums
104
+
105
+ zenstack-kit maintains `meta/_migration_log` alongside your migrations. It stores a SHA256 checksum for each `migration.sql` so integrity checks work even when you never apply migrations locally.
106
+
107
+ Flow:
108
+ - On `migrate create`, a checksum entry is appended to the log.
109
+ - On `migrate apply`, applied migrations are verified against the database and log.
110
+ - For pending migrations, the log is auto-updated from disk by default (so edits or empty migrations do not require manual rehash).
111
+ - Use `--strict` (or `ZENSTACK_MIGRATION_STRICT=1`) to disable auto-rehash and fail on any pending mismatch (recommended for CI).
112
+
103
113
  ## CLI Commands
104
114
 
105
115
  Run `zenstack-kit` without arguments to launch the interactive menu, or run commands directly. For CI or non-TTY environments, pass `--no-ui` to bypass Ink.
@@ -142,6 +152,8 @@ Options:
142
152
  - `-s, --schema <path>` - Path to ZenStack schema
143
153
  - `-m, --migrations <path>` - Migrations directory
144
154
  - `--dialect <dialect>` - Database dialect (`sqlite`, `postgres`, `mysql`)
155
+ - `--empty` - Create an empty migration (no schema diff)
156
+ - `--update-snapshot` - Update snapshot when used with `--empty`
145
157
  - `-c, --config <path>` - Path to zenstack-kit config file
146
158
 
147
159
  ### `zenstack-kit migrate apply`
@@ -158,13 +170,15 @@ Options:
158
170
  - `--url <url>` - Database connection URL (overrides config)
159
171
  - `--table <name>` - Migrations table name (default: `_prisma_migrations`)
160
172
  - `--db-schema <name>` - Database schema for migrations table (PostgreSQL only, default: `public`)
173
+ - `--migration <name>` - Apply a single migration (must be the next pending one)
161
174
  - `--preview` - Preview pending migrations without applying
162
175
  - `--mark-applied` - Mark pending migrations as applied without running SQL
176
+ - `--strict` - Enforce pending migration log checksums (no auto-rehash)
163
177
  - `-c, --config <path>` - Path to zenstack-kit config file
164
178
 
165
179
  ### `zenstack-kit migrate rehash`
166
180
 
167
- Rebuild the migration log checksums from the `migration.sql` files (useful after manual edits).
181
+ Rebuild the migration log checksums from the `migration.sql` files (useful after manual edits or when strict mode fails).
168
182
 
169
183
  ```bash
170
184
  zenstack-kit migrate rehash
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;GASG;AAgYH,wBAAgB,MAAM,SA4BrB"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/cli/app.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;GASG;AA4YH,wBAAgB,MAAM,SA4BrB"}
package/dist/cli/app.js CHANGED
@@ -89,6 +89,15 @@ function parseArgs() {
89
89
  else if (arg === "--mark-applied") {
90
90
  options.markApplied = true;
91
91
  }
92
+ else if (arg === "--strict") {
93
+ options.strict = true;
94
+ }
95
+ else if (arg === "--empty") {
96
+ options.empty = true;
97
+ }
98
+ else if (arg === "--update-snapshot") {
99
+ options.updateSnapshot = true;
100
+ }
92
101
  else if (arg === "--force" || arg === "-f") {
93
102
  options.force = true;
94
103
  }
@@ -116,7 +125,7 @@ function Status({ type, message }) {
116
125
  }
117
126
  // Help display component
118
127
  function HelpDisplay() {
119
- return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "zenstack-kit" }), _jsx(Text, { dimColor: true, children: "Database tooling for ZenStack schemas" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Commands:" }), commands.filter(c => c.value !== "exit").map((cmd) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: cmd.label }) }), _jsx(Text, { dimColor: true, children: cmd.description })] }, cmd.value))), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "-s, --schema <path> Path to ZenStack schema" }), _jsx(Text, { dimColor: true, children: "-m, --migrations <path> Migrations directory" }), _jsx(Text, { dimColor: true, children: "-n, --name <name> Migration name" }), _jsx(Text, { dimColor: true, children: "--dialect <dialect> Database dialect (sqlite, postgres, mysql)" }), _jsx(Text, { dimColor: true, children: "--url <url> Database connection URL" }), _jsx(Text, { dimColor: true, children: "--migration <name> Target a single migration (rehash only)" }), _jsx(Text, { dimColor: true, children: "--no-ui Disable Ink UI (useful for CI/non-TTY)" }), _jsx(Text, { dimColor: true, children: "--create-initial Create initial migration (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--baseline Create baseline only (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--preview Preview pending migrations without applying" }), _jsx(Text, { dimColor: true, children: "--mark-applied Mark pending migrations as applied without running SQL" }), _jsx(Text, { dimColor: true, children: "-f, --force Force operation without confirmation" }), _jsx(Text, { dimColor: true, children: "-c, --config <path> Path to zenstack-kit config file" })] })] }));
128
+ return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "zenstack-kit" }), _jsx(Text, { dimColor: true, children: "Database tooling for ZenStack schemas" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Commands:" }), commands.filter(c => c.value !== "exit").map((cmd) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: cmd.label }) }), _jsx(Text, { dimColor: true, children: cmd.description })] }, cmd.value))), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "-s, --schema <path> Path to ZenStack schema" }), _jsx(Text, { dimColor: true, children: "-m, --migrations <path> Migrations directory" }), _jsx(Text, { dimColor: true, children: "-n, --name <name> Migration name" }), _jsx(Text, { dimColor: true, children: "--dialect <dialect> Database dialect (sqlite, postgres, mysql)" }), _jsx(Text, { dimColor: true, children: "--url <url> Database connection URL" }), _jsx(Text, { dimColor: true, children: "--migration <name> Target a single migration (apply/rehash)" }), _jsx(Text, { dimColor: true, children: "--no-ui Disable Ink UI (useful for CI/non-TTY)" }), _jsx(Text, { dimColor: true, children: "--create-initial Create initial migration (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--baseline Create baseline only (skip prompt)" }), _jsx(Text, { dimColor: true, children: "--empty Create an empty migration (no schema diff)" }), _jsx(Text, { dimColor: true, children: "--update-snapshot Update snapshot when used with --empty" }), _jsx(Text, { dimColor: true, children: "--preview Preview pending migrations without applying" }), _jsx(Text, { dimColor: true, children: "--mark-applied Mark pending migrations as applied without running SQL" }), _jsx(Text, { dimColor: true, children: "--strict Enforce pending migration log checksums (no auto-rehash)" }), _jsx(Text, { dimColor: true, children: "-f, --force Force operation without confirmation" }), _jsx(Text, { dimColor: true, children: "-c, --config <path> Path to zenstack-kit config file" })] })] }));
120
129
  }
121
130
  function CliApp({ initialCommand, options }) {
122
131
  const { exit } = useApp();
@@ -255,12 +264,15 @@ function printHelpText() {
255
264
  " -n, --name <name> Migration name",
256
265
  " --dialect <dialect> Database dialect (sqlite, postgres, mysql)",
257
266
  " --url <url> Database connection URL",
258
- " --migration <name> Target a single migration (rehash only)",
267
+ " --migration <name> Target a single migration (apply/rehash)",
259
268
  " --no-ui Disable Ink UI (useful for CI/non-TTY)",
260
269
  " --create-initial Create initial migration (skip prompt)",
261
270
  " --baseline Create baseline only (skip prompt)",
271
+ " --empty Create an empty migration (no schema diff)",
272
+ " --update-snapshot Update snapshot when used with --empty",
262
273
  " --preview Preview pending migrations without applying",
263
274
  " --mark-applied Mark pending migrations as applied without running SQL",
275
+ " --strict Enforce pending migration log checksums (no auto-rehash)",
264
276
  " -f, --force Force operation without confirmation",
265
277
  " -c, --config <path> Path to zenstack-kit config file",
266
278
  ];
@@ -24,6 +24,9 @@ export interface CommandOptions {
24
24
  markApplied?: boolean;
25
25
  force?: boolean;
26
26
  config?: string;
27
+ empty?: boolean;
28
+ updateSnapshot?: boolean;
29
+ strict?: boolean;
27
30
  }
28
31
  export interface CommandContext {
29
32
  cwd: string;
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ import * as os from "os";
10
10
  import { execFileSync } from "child_process";
11
11
  import { loadConfig } from "../config/loader.js";
12
12
  import { pullSchema } from "../schema/pull.js";
13
- import { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, getMigrationLogPath, calculateChecksum, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
13
+ import { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, getMigrationLogPath, calculateChecksum, initializeSnapshot, createInitialMigration, detectPotentialRenames, } from "../migrations/prisma.js";
14
14
  export class CommandError extends Error {
15
15
  constructor(message) {
16
16
  super(message);
@@ -64,17 +64,22 @@ export async function runMigrateGenerate(ctx) {
64
64
  if (!snapshotExists) {
65
65
  throw new CommandError("No snapshot found. Run 'zenstack-kit init' first.");
66
66
  }
67
+ const isEmpty = Boolean(ctx.options.empty);
67
68
  ctx.log("info", "Generating migration...");
68
- const hasChanges = await hasPrismaSchemaChanges({
69
- schemaPath,
70
- outputPath,
71
- });
72
- if (!hasChanges) {
73
- ctx.log("warning", "No schema changes detected");
74
- return;
69
+ if (!isEmpty) {
70
+ const hasChanges = await hasPrismaSchemaChanges({
71
+ schemaPath,
72
+ outputPath,
73
+ });
74
+ if (!hasChanges) {
75
+ ctx.log("warning", "No schema changes detected");
76
+ return;
77
+ }
75
78
  }
76
79
  // Detect potential renames and prompt user to disambiguate
77
- const potentialRenames = await detectPotentialRenames({ schemaPath, outputPath });
80
+ const potentialRenames = isEmpty
81
+ ? { tables: [], columns: [] }
82
+ : await detectPotentialRenames({ schemaPath, outputPath });
78
83
  const renameTables = [];
79
84
  const renameColumns = [];
80
85
  // Prompt for table renames
@@ -112,20 +117,30 @@ export async function runMigrateGenerate(ctx) {
112
117
  return;
113
118
  }
114
119
  }
115
- const migration = await createPrismaMigration({
116
- name,
117
- schemaPath,
118
- outputPath,
119
- dialect,
120
- renameTables: renameTables.length > 0 ? renameTables : undefined,
121
- renameColumns: renameColumns.length > 0 ? renameColumns : undefined,
122
- });
120
+ const migration = isEmpty
121
+ ? await createEmptyMigration({
122
+ name,
123
+ schemaPath,
124
+ outputPath,
125
+ updateSnapshot: ctx.options.updateSnapshot,
126
+ })
127
+ : await createPrismaMigration({
128
+ name,
129
+ schemaPath,
130
+ outputPath,
131
+ dialect,
132
+ renameTables: renameTables.length > 0 ? renameTables : undefined,
133
+ renameColumns: renameColumns.length > 0 ? renameColumns : undefined,
134
+ });
123
135
  if (!migration) {
124
136
  ctx.log("warning", "No schema changes detected");
125
137
  return;
126
138
  }
127
139
  ctx.log("success", `Migration created: ${migration.folderName}/migration.sql`);
128
140
  ctx.log("info", `Path: ${migration.folderPath}`);
141
+ if (isEmpty && ctx.options.updateSnapshot) {
142
+ ctx.log("info", "Snapshot updated to current schema");
143
+ }
129
144
  ctx.log("info", "Next: run 'zenstack-kit migrate apply' (or --preview to review SQL).");
130
145
  }
131
146
  /**
@@ -191,6 +206,10 @@ export async function runMigrateApply(ctx) {
191
206
  else {
192
207
  ctx.log("info", "Applying migrations...");
193
208
  }
209
+ const strictEnv = process.env.ZENSTACK_MIGRATION_STRICT;
210
+ const strict = ctx.options.strict === true ||
211
+ strictEnv === "1" ||
212
+ (strictEnv ? strictEnv.toLowerCase() === "true" : false);
194
213
  const result = await applyPrismaMigrations({
195
214
  migrationsFolder: outputPath,
196
215
  dialect,
@@ -199,6 +218,8 @@ export async function runMigrateApply(ctx) {
199
218
  migrationsTable,
200
219
  migrationsSchema,
201
220
  markApplied: ctx.options.markApplied,
221
+ strict,
222
+ targetMigration: ctx.options.migration,
202
223
  });
203
224
  // Handle coherence errors
204
225
  if (result.coherenceErrors && result.coherenceErrors.length > 0) {
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { introspectSchema, type SchemaInfo, type ModelInfo, type FieldInfo } fro
10
10
  export { createMigration, getSchemaDiff, hasSchemaChanges, initSnapshot, type MigrationOptions, type Migration, type InitSnapshotOptions, type InitSnapshotResult, } from "./migrations/diff.js";
11
11
  export { applyMigrations, type ApplyMigrationsOptions } from "./migrations/apply.js";
12
12
  export { setPromptProvider, type PromptProvider } from "./cli/prompt-provider.js";
13
- export { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, type PrismaMigrationOptions, type PrismaMigration, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type CreateInitialMigrationOptions, type MigrationLogEntry, type PotentialTableRename, type PotentialColumnRename, type PotentialRenames, } from "./migrations/prisma.js";
13
+ export { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, type PrismaMigrationOptions, type PrismaMigration, type CreateEmptyMigrationOptions, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type CreateInitialMigrationOptions, type MigrationLogEntry, type PotentialTableRename, type PotentialColumnRename, type PotentialRenames, } from "./migrations/prisma.js";
14
14
  export { migrate, type MigrateOptions, type MigrateResult } from "./migrate.js";
15
15
  export { defineConfig, type ZenStackKitConfig } from "./config/index.js";
16
16
  export { type RenameChoice } from "./cli/prompts.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGhF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGhF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { createMigration, getSchemaDiff, hasSchemaChanges, initSnapshot, } from
12
12
  export { applyMigrations } from "./migrations/apply.js";
13
13
  export { setPromptProvider } from "./cli/prompt-provider.js";
14
14
  // Prisma-compatible migrations (default)
15
- export { createPrismaMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, } from "./migrations/prisma.js";
15
+ export { createPrismaMigration, createEmptyMigration, applyPrismaMigrations, previewPrismaMigrations, hasPrismaSchemaChanges, createInitialMigration, initializeSnapshot, hasSnapshot, scanMigrationFolders, readMigrationLog, writeMigrationLog, appendToMigrationLog, getMigrationLogPath, calculateChecksum, detectPotentialRenames, } from "./migrations/prisma.js";
16
16
  // High-level programmatic API
17
17
  export { migrate } from "./migrate.js";
18
18
  // CLI utilities
package/dist/migrate.d.ts CHANGED
@@ -61,6 +61,15 @@ export interface MigrateOptions {
61
61
  * @default false
62
62
  */
63
63
  preview?: boolean;
64
+ /**
65
+ * Apply a single migration by name (must be the next pending one).
66
+ */
67
+ migration?: string;
68
+ /**
69
+ * Enforce pending migration log checksums (no auto-rehash).
70
+ * @default false
71
+ */
72
+ strict?: boolean;
64
73
  /**
65
74
  * Current working directory for config resolution.
66
75
  * @default process.cwd()
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,2BAA2B,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACjD,CAAC,6BAA6B,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAsFlF"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,2BAA2B,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACjD,CAAC,6BAA6B,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwFlF"}
package/dist/migrate.js CHANGED
@@ -119,6 +119,8 @@ export async function migrate(options = {}) {
119
119
  databasePath,
120
120
  migrationsTable,
121
121
  migrationsSchema,
122
+ strict: options.strict,
123
+ targetMigration: options.migration,
122
124
  });
123
125
  if (result.failed) {
124
126
  throw new Error(`Migration failed: ${result.failed.migrationName} - ${result.failed.error}`);
@@ -14,6 +14,10 @@ export interface ApplyPrismaMigrationsOptions {
14
14
  migrationsSchema?: string;
15
15
  /** Mark migrations as applied without executing SQL */
16
16
  markApplied?: boolean;
17
+ /** Enforce checksum/log consistency for pending migrations (no auto-rehash) */
18
+ strict?: boolean;
19
+ /** Apply a single migration by name */
20
+ targetMigration?: string;
17
21
  }
18
22
  export interface ApplyPrismaMigrationsResult {
19
23
  applied: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/apply.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,4BAA4B;IAC3C,6BAA6B;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,eAAe,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAC5G,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,uBAAuB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AA2QD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CA0HtC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CA0DxC"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/apply.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,4BAA4B;IAC3C,6BAA6B;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,eAAe,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAC5G,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,uBAAuB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AA2QD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CAoLtC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CA0DxC"}
@@ -3,7 +3,7 @@ import * as path from "path";
3
3
  import * as crypto from "crypto";
4
4
  import { sql } from "kysely";
5
5
  import { createKyselyAdapter } from "../../sql/kysely-adapter.js";
6
- import { calculateChecksum, readMigrationLog } from "./log.js";
6
+ import { calculateChecksum, readMigrationLog, writeMigrationLog } from "./log.js";
7
7
  /**
8
8
  * Ensure _prisma_migrations table exists
9
9
  */
@@ -276,7 +276,34 @@ export async function applyPrismaMigrations(options) {
276
276
  applied: [],
277
277
  alreadyApplied: [],
278
278
  };
279
- for (const folderName of migrationFoldersWithSql) {
279
+ const logIndex = new Map(migrationLog.map((entry, index) => [entry.name, index]));
280
+ let logUpdated = false;
281
+ let migrationFoldersToApply = migrationFoldersWithSql;
282
+ if (options.targetMigration) {
283
+ const target = options.targetMigration;
284
+ const pendingMigrations = migrationFoldersWithSql.filter((name) => !appliedMigrations.has(name));
285
+ if (appliedMigrations.has(target)) {
286
+ result.alreadyApplied.push(target);
287
+ return result;
288
+ }
289
+ if (!migrationFoldersWithSql.includes(target)) {
290
+ result.failed = {
291
+ migrationName: target,
292
+ error: `Migration ${target} not found in migrations folder.`,
293
+ };
294
+ return result;
295
+ }
296
+ if (pendingMigrations[0] !== target) {
297
+ result.failed = {
298
+ migrationName: target,
299
+ error: `Migration ${target} is not the next pending migration.\n` +
300
+ `Apply pending migrations in order or omit --migration.`,
301
+ };
302
+ return result;
303
+ }
304
+ migrationFoldersToApply = [target];
305
+ }
306
+ for (const folderName of migrationFoldersToApply) {
280
307
  if (appliedMigrations.has(folderName)) {
281
308
  result.alreadyApplied.push(folderName);
282
309
  continue;
@@ -290,18 +317,35 @@ export async function applyPrismaMigrations(options) {
290
317
  continue; // Skip if no migration.sql
291
318
  }
292
319
  const checksum = calculateChecksum(sqlContent);
293
- // Verify checksum against migration log (migrationLog already read above)
294
- const logEntry = migrationLog.find((m) => m.name === folderName);
295
- if (logEntry && logEntry.checksum !== checksum) {
296
- result.failed = {
297
- migrationName: folderName,
298
- error: `Checksum mismatch for migration ${folderName}.\n` +
299
- `Expected: ${logEntry.checksum}\n` +
300
- `Found: ${checksum}\n` +
301
- `The migration file may have been modified after generation.\n` +
302
- `If you intended this, run 'zenstack-kit migrate rehash' to rebuild log checksums.`,
303
- };
304
- break;
320
+ // Verify or update checksum against migration log (pending migrations only)
321
+ const logEntryIndex = logIndex.get(folderName);
322
+ if (logEntryIndex === undefined) {
323
+ if (options.strict) {
324
+ result.failed = {
325
+ migrationName: folderName,
326
+ error: `Migration ${folderName} is missing from the migration log.\n` +
327
+ `Run 'zenstack-kit migrate rehash' to rebuild log checksums or disable strict mode.`,
328
+ };
329
+ break;
330
+ }
331
+ migrationLog.push({ name: folderName, checksum });
332
+ logIndex.set(folderName, migrationLog.length - 1);
333
+ logUpdated = true;
334
+ }
335
+ else if (migrationLog[logEntryIndex].checksum !== checksum) {
336
+ if (options.strict) {
337
+ result.failed = {
338
+ migrationName: folderName,
339
+ error: `Checksum mismatch for migration ${folderName}.\n` +
340
+ `Expected: ${migrationLog[logEntryIndex].checksum}\n` +
341
+ `Found: ${checksum}\n` +
342
+ `The migration file may have been modified after generation.\n` +
343
+ `If you intended this, run 'zenstack-kit migrate rehash' to rebuild log checksums.`,
344
+ };
345
+ break;
346
+ }
347
+ migrationLog[logEntryIndex] = { name: folderName, checksum };
348
+ logUpdated = true;
305
349
  }
306
350
  const startTime = Date.now();
307
351
  try {
@@ -327,6 +371,11 @@ export async function applyPrismaMigrations(options) {
327
371
  break; // Stop on first failure
328
372
  }
329
373
  }
374
+ if (logUpdated) {
375
+ migrationLog.sort((a, b) => a.name.localeCompare(b.name));
376
+ await writeMigrationLog(options.migrationsFolder, migrationLog);
377
+ logUpdated = false;
378
+ }
330
379
  return result;
331
380
  }
332
381
  finally {
@@ -40,10 +40,24 @@ export interface CreateInitialMigrationOptions {
40
40
  /** Database dialect for SQL generation */
41
41
  dialect: KyselyDialect;
42
42
  }
43
+ export interface CreateEmptyMigrationOptions {
44
+ /** Migration name */
45
+ name: string;
46
+ /** Path to ZenStack schema file */
47
+ schemaPath: string;
48
+ /** Output directory for migration files */
49
+ outputPath: string;
50
+ /** Update snapshot to current schema */
51
+ updateSnapshot?: boolean;
52
+ }
43
53
  /**
44
54
  * Generate timestamp string for migration folder name
45
55
  */
46
56
  export declare function generateTimestamp(): string;
57
+ /**
58
+ * Create a Prisma-compatible empty migration
59
+ */
60
+ export declare function createEmptyMigration(options: CreateEmptyMigrationOptions): Promise<PrismaMigration>;
47
61
  /**
48
62
  * Create a Prisma-compatible migration
49
63
  */
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,WAAW,sBAAsB;IACrC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiDjC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBnB"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,WAAW,sBAAsB;IACrC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,CAgC1B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiDjC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBnB"}
@@ -18,6 +18,37 @@ export function generateTimestamp() {
18
18
  String(now.getSeconds()).padStart(2, "0"),
19
19
  ].join("");
20
20
  }
21
+ /**
22
+ * Create a Prisma-compatible empty migration
23
+ */
24
+ export async function createEmptyMigration(options) {
25
+ const timestamp = Date.now();
26
+ const timestampStr = generateTimestamp();
27
+ const safeName = options.name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
28
+ const folderName = `${timestampStr}_${safeName}`;
29
+ const folderPath = path.join(options.outputPath, folderName);
30
+ const sqlContent = [
31
+ `-- Migration: ${options.name}`,
32
+ `-- Generated at: ${new Date(timestamp).toISOString()}`,
33
+ "",
34
+ "",
35
+ ].join("\n");
36
+ await fs.mkdir(folderPath, { recursive: true });
37
+ await fs.writeFile(path.join(folderPath, "migration.sql"), sqlContent, "utf-8");
38
+ if (options.updateSnapshot) {
39
+ const currentSchema = await generateSchemaSnapshot(options.schemaPath);
40
+ const { snapshotPath } = getSnapshotPaths(options.outputPath);
41
+ await writeSnapshot(snapshotPath, currentSchema);
42
+ }
43
+ const checksum = calculateChecksum(sqlContent);
44
+ await appendToMigrationLog(options.outputPath, { name: folderName, checksum });
45
+ return {
46
+ folderName,
47
+ folderPath,
48
+ sql: sqlContent,
49
+ timestamp,
50
+ };
51
+ }
21
52
  /**
22
53
  * Create a Prisma-compatible migration
23
54
  */
@@ -1,5 +1,5 @@
1
1
  import type { KyselyDialect } from "../../sql/kysely-adapter.js";
2
- import type { SchemaSnapshot, SchemaTable, SchemaColumn } from "../../schema/snapshot.js";
2
+ import type { SchemaSnapshot, SchemaTable, SchemaColumn, SchemaEnum } from "../../schema/snapshot.js";
3
3
  export declare function diffSchemas(previous: SchemaSnapshot | null, current: SchemaSnapshot): {
4
4
  addedModels: SchemaTable[];
5
5
  removedModels: SchemaTable[];
@@ -83,6 +83,13 @@ export declare function diffSchemas(previous: SchemaSnapshot | null, current: Sc
83
83
  from: string;
84
84
  to: string;
85
85
  }>;
86
+ addedEnums: SchemaEnum[];
87
+ removedEnums: SchemaEnum[];
88
+ alteredEnums: {
89
+ enumName: string;
90
+ addedValues: string[];
91
+ removedValues: string[];
92
+ }[];
86
93
  };
87
94
  type PrismaDiff = ReturnType<typeof diffSchemas>;
88
95
  export declare function applyRenameMappings(diff: PrismaDiff, renameTables?: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAyK1F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc;;;;mBAsB5C,MAAM;gBAAU,YAAY;;;mBAC1B,MAAM;gBAAU,YAAY;;;mBAEvD,MAAM;oBACL,MAAM;kBACR,YAAY;iBACb,YAAY;;;mBAGV,MAAM;oBACL;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAGpC,MAAM;oBACL;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAGpC,MAAM;eACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAG/B,MAAM;eACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAG/B,MAAM;oBACL;YACV,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC;YACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;SAC7B;;;mBAGU,MAAM;oBACL;YACV,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC;YACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;SAC7B;;;mBAGU,MAAM;mBACN;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;kBACpC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;mBAiCxB,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;oBAClC,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;EAE/E;AAED,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAkGjD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,YAAY,GAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAM,EACtD,aAAa,GAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAM,GACrE,UAAU,CA8FZ;AA2CD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,EACpC,OAAO,EAAE,aAAa,GACrB;IAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAkNlC"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/migrations/prisma/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AA4KtG,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc;;;;mBAsB5C,MAAM;gBAAU,YAAY;;;mBAC1B,MAAM;gBAAU,YAAY;;;mBAEvD,MAAM;oBACL,MAAM;kBACR,YAAY;iBACb,YAAY;;;mBAGV,MAAM;oBACL;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAGpC,MAAM;oBACL;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAGpC,MAAM;eACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAG/B,MAAM;eACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;;mBAG/B,MAAM;oBACL;YACV,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC;YACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;SAC7B;;;mBAGU,MAAM;oBACL;YACV,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC;YACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;SAC7B;;;mBAGU,MAAM;mBACN;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;kBACpC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,CAAA;SAAE;;mBA4ExB,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;oBAClC,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;;;;kBA/ClE,MAAM;qBACH,MAAM,EAAE;uBACN,MAAM,EAAE;;EAkD1B;AAED,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAkGjD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,YAAY,GAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAM,EACtD,aAAa,GAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAM,GACrE,UAAU,CA8FZ;AA2CD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,EACpC,OAAO,EAAE,aAAa,GACrB;IAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAuPlC"}
@@ -1,4 +1,4 @@
1
- import { compileCreateTable, compileDropTable, compileAddColumn, compileDropColumn, compileRenameTable, compileRenameColumn, compileCreateIndex, compileDropIndex, compileAddUniqueConstraint, compileDropConstraint, compileAddForeignKeyConstraint, compileAddPrimaryKeyConstraint, compileAlterColumn, } from "../../sql/compiler.js";
1
+ import { compileCreateTable, compileDropTable, compileAddColumn, compileDropColumn, compileRenameTable, compileRenameColumn, compileCreateIndex, compileDropIndex, compileAddUniqueConstraint, compileDropConstraint, compileAddForeignKeyConstraint, compileAddPrimaryKeyConstraint, compileAlterColumn, compileCreateEnum, compileDropEnum, compileAddEnumValue, } from "../../sql/compiler.js";
2
2
  function diffTableChanges(previousModel, currentModel, tableName) {
3
3
  const addedFields = [];
4
4
  const removedFields = [];
@@ -140,6 +140,37 @@ export function diffSchemas(previous, current) {
140
140
  removedForeignKeys.push(...modelDiff.removedForeignKeys);
141
141
  primaryKeyChanges.push(...modelDiff.primaryKeyChanges);
142
142
  }
143
+ // Diff enums
144
+ const previousEnums = new Map();
145
+ const currentEnums = new Map();
146
+ (previous?.enums ?? []).forEach((e) => previousEnums.set(e.name, e));
147
+ (current.enums ?? []).forEach((e) => currentEnums.set(e.name, e));
148
+ const addedEnums = [];
149
+ const removedEnums = [];
150
+ const alteredEnums = [];
151
+ for (const [enumName, enumDef] of currentEnums.entries()) {
152
+ if (!previousEnums.has(enumName)) {
153
+ addedEnums.push(enumDef);
154
+ }
155
+ }
156
+ for (const [enumName, enumDef] of previousEnums.entries()) {
157
+ if (!currentEnums.has(enumName)) {
158
+ removedEnums.push(enumDef);
159
+ }
160
+ }
161
+ // Check for altered enums (added/removed values)
162
+ for (const [enumName, currentEnum] of currentEnums.entries()) {
163
+ const previousEnum = previousEnums.get(enumName);
164
+ if (!previousEnum)
165
+ continue;
166
+ const prevValues = new Set(previousEnum.values);
167
+ const currValues = new Set(currentEnum.values);
168
+ const addedValues = currentEnum.values.filter((v) => !prevValues.has(v));
169
+ const removedValues = previousEnum.values.filter((v) => !currValues.has(v));
170
+ if (addedValues.length > 0 || removedValues.length > 0) {
171
+ alteredEnums.push({ enumName, addedValues, removedValues });
172
+ }
173
+ }
143
174
  return {
144
175
  addedModels,
145
176
  removedModels,
@@ -155,6 +186,9 @@ export function diffSchemas(previous, current) {
155
186
  primaryKeyChanges,
156
187
  renamedTables: [],
157
188
  renamedColumns: [],
189
+ addedEnums,
190
+ removedEnums,
191
+ alteredEnums,
158
192
  };
159
193
  }
160
194
  function columnsSignature(columns) {
@@ -329,6 +363,32 @@ export function buildSqlStatements(diff, dialect) {
329
363
  const up = [];
330
364
  const down = [];
331
365
  const compileOpts = { dialect };
366
+ // Create enums FIRST (before tables that use them)
367
+ for (const enumDef of diff.addedEnums) {
368
+ const sql = compileCreateEnum(enumDef, compileOpts);
369
+ if (sql) {
370
+ up.push(sql);
371
+ const dropSql = compileDropEnum(enumDef.name, compileOpts);
372
+ if (dropSql)
373
+ down.unshift(dropSql);
374
+ }
375
+ }
376
+ // Add new values to existing enums
377
+ for (const altered of diff.alteredEnums) {
378
+ for (const value of altered.addedValues) {
379
+ const sql = compileAddEnumValue(altered.enumName, value, compileOpts);
380
+ if (sql) {
381
+ up.push(sql);
382
+ // Note: PostgreSQL doesn't support removing enum values easily,
383
+ // so we don't add a down migration for added values
384
+ }
385
+ }
386
+ // Note: Removing enum values in PostgreSQL requires recreating the type
387
+ // which is complex and potentially data-losing. We skip this for now.
388
+ if (altered.removedValues.length > 0 && dialect === "postgres") {
389
+ up.push(`-- WARNING: Removing enum values (${altered.removedValues.join(", ")}) from "${altered.enumName}" requires manual migration`);
390
+ }
391
+ }
332
392
  // Table renames
333
393
  for (const rename of diff.renamedTables) {
334
394
  up.push(compileRenameTable(rename.from, rename.to, compileOpts));
@@ -438,5 +498,15 @@ export function buildSqlStatements(diff, dialect) {
438
498
  up.push(compileAddForeignKeyConstraint(tableName, foreignKey.name, foreignKey.columns, foreignKey.referencedTable, foreignKey.referencedColumns, compileOpts));
439
499
  down.unshift(compileDropConstraint(tableName, foreignKey.name, compileOpts));
440
500
  }
501
+ // Drop enums LAST (after tables that use them are dropped)
502
+ for (const enumDef of diff.removedEnums) {
503
+ const sql = compileDropEnum(enumDef.name, compileOpts);
504
+ if (sql) {
505
+ up.push(sql);
506
+ const createSql = compileCreateEnum(enumDef, compileOpts);
507
+ if (createSql)
508
+ down.unshift(createSql);
509
+ }
510
+ }
441
511
  return { up, down };
442
512
  }
@@ -1,4 +1,4 @@
1
- export { createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, type PrismaMigrationOptions, type PrismaMigration, type CreateInitialMigrationOptions, } from "./prisma/create.js";
1
+ export { createEmptyMigration, createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, type PrismaMigrationOptions, type PrismaMigration, type CreateInitialMigrationOptions, type CreateEmptyMigrationOptions, } from "./prisma/create.js";
2
2
  export { applyPrismaMigrations, previewPrismaMigrations, type ApplyPrismaMigrationsOptions, type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult, type MigrationCoherenceError, type MigrationCoherenceResult, } from "./prisma/apply.js";
3
3
  export { readMigrationLog, writeMigrationLog, appendToMigrationLog, scanMigrationFolders, getMigrationLogPath, calculateChecksum, type MigrationLogEntry, } from "./prisma/log.js";
4
4
  export { initializeSnapshot, hasSnapshot, getSnapshotPaths, writeSnapshot, } from "./prisma/snapshot.js";
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,6BAA6B,GACnC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/migrations/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,GACjC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
@@ -1,4 +1,4 @@
1
- export { createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, } from "./prisma/create.js";
1
+ export { createEmptyMigration, createPrismaMigration, createInitialMigration, hasPrismaSchemaChanges, } from "./prisma/create.js";
2
2
  export { applyPrismaMigrations, previewPrismaMigrations, } from "./prisma/apply.js";
3
3
  export { readMigrationLog, writeMigrationLog, appendToMigrationLog, scanMigrationFolders, getMigrationLogPath, calculateChecksum, } from "./prisma/log.js";
4
4
  export { initializeSnapshot, hasSnapshot, getSnapshotPaths, writeSnapshot, } from "./prisma/snapshot.js";
@@ -46,7 +46,7 @@ async function parseZModelFile(schemaPath) {
46
46
  }
47
47
  models.push({
48
48
  name: modelName,
49
- tableName: modelName.toLowerCase(),
49
+ tableName: modelName,
50
50
  fields,
51
51
  });
52
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AA8lBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA4C1E"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/schema/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAsqBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAkD1E"}
@@ -91,7 +91,9 @@ function buildDatasourceBlock(dialect) {
91
91
  ].join("\n");
92
92
  }
93
93
  function buildModelBlock(options) {
94
- const { table, foreignKeys, indexes, primaryKeys, allTables, columnDefaults } = options;
94
+ const { table, foreignKeys, indexes, primaryKeys, allTables, columnDefaults, enums = [] } = options;
95
+ // Build a map of enum names for quick lookup
96
+ const enumNames = new Set(enums.map((e) => e.name));
95
97
  const modelName = toPascalCase(table.name) || "Model";
96
98
  const fieldLines = [];
97
99
  // Get primary key columns for this table
@@ -170,7 +172,18 @@ function buildModelBlock(options) {
170
172
  for (const column of sortedColumns) {
171
173
  const fieldName = toCamelCase(column.name) || column.name;
172
174
  const mapped = fieldName !== column.name;
173
- const { type, isArray } = normalizeType(column.dataType);
175
+ // Check if the column type is an enum (PostgreSQL stores udt_name for enum types)
176
+ const rawDataType = column.dataType.replace(/\[\]$/, ""); // Remove array suffix
177
+ const isEnumType = enumNames.has(rawDataType);
178
+ const isArray = column.dataType.endsWith("[]");
179
+ let type;
180
+ if (isEnumType) {
181
+ type = toPascalCase(rawDataType);
182
+ }
183
+ else {
184
+ const normalized = normalizeType(column.dataType);
185
+ type = normalized.type;
186
+ }
174
187
  const optional = column.isNullable ? "?" : "";
175
188
  const modifiers = [];
176
189
  const isPkColumn = pkColumns.has(column.name);
@@ -483,6 +496,44 @@ async function extractColumnDefaults(db, dialect, tableNames) {
483
496
  }
484
497
  return defaultsByTable;
485
498
  }
499
+ async function extractEnums(db, dialect) {
500
+ const enums = [];
501
+ if (dialect === "postgres") {
502
+ // Query PostgreSQL system catalogs for enum types
503
+ const result = await sql `
504
+ SELECT
505
+ t.typname as enum_name,
506
+ e.enumlabel as enum_value
507
+ FROM pg_type t
508
+ JOIN pg_enum e ON t.oid = e.enumtypid
509
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
510
+ WHERE n.nspname = 'public'
511
+ ORDER BY t.typname, e.enumsortorder
512
+ `.execute(db);
513
+ // Group by enum name
514
+ const enumMap = new Map();
515
+ for (const row of result.rows) {
516
+ if (!enumMap.has(row.enum_name)) {
517
+ enumMap.set(row.enum_name, []);
518
+ }
519
+ enumMap.get(row.enum_name).push(row.enum_value);
520
+ }
521
+ for (const [name, values] of enumMap) {
522
+ enums.push({ name, values });
523
+ }
524
+ }
525
+ // MySQL and SQLite don't have standalone enum types
526
+ // MySQL uses inline ENUM definitions in column types
527
+ return enums;
528
+ }
529
+ function buildEnumBlock(enumInfo) {
530
+ const lines = [`enum ${toPascalCase(enumInfo.name)} {`];
531
+ for (const value of enumInfo.values) {
532
+ lines.push(` ${value}`);
533
+ }
534
+ lines.push("}");
535
+ return lines.join("\n");
536
+ }
486
537
  export async function pullSchema(options) {
487
538
  const { db, destroy } = await createKyselyAdapter({
488
539
  dialect: options.dialect,
@@ -498,15 +549,20 @@ export async function pullSchema(options) {
498
549
  const indexes = await extractIndexes(db, options.dialect, tableNames);
499
550
  const primaryKeys = await extractPrimaryKeys(db, options.dialect, tableNames);
500
551
  const columnDefaultsByTable = await extractColumnDefaults(db, options.dialect, tableNames);
501
- const blocks = filtered.map((table) => buildModelBlock({
552
+ const enums = await extractEnums(db, options.dialect);
553
+ // Build enum blocks
554
+ const enumBlocks = enums.map((e) => buildEnumBlock(e));
555
+ // Build model blocks
556
+ const modelBlocks = filtered.map((table) => buildModelBlock({
502
557
  table,
503
558
  foreignKeys,
504
559
  indexes,
505
560
  primaryKeys,
506
561
  allTables,
507
562
  columnDefaults: columnDefaultsByTable.get(table.name) ?? new Map(),
563
+ enums, // Pass enums for type mapping
508
564
  }));
509
- const schema = [buildDatasourceBlock(options.dialect), ...blocks].join("\n\n");
565
+ const schema = [buildDatasourceBlock(options.dialect), ...enumBlocks, ...modelBlocks].join("\n\n");
510
566
  if (options.writeFile !== false) {
511
567
  await fs.mkdir(path.dirname(options.outputPath), { recursive: true });
512
568
  await fs.writeFile(options.outputPath, schema.trimEnd() + "\n", "utf-8");
@@ -10,6 +10,8 @@ export interface SchemaColumn {
10
10
  isArray: boolean;
11
11
  default?: string | number | boolean;
12
12
  isAutoincrement?: boolean;
13
+ /** If true, type refers to an enum name rather than a SQL type */
14
+ isEnum?: boolean;
13
15
  }
14
16
  export interface SchemaConstraint {
15
17
  name: string;
@@ -33,8 +35,13 @@ export interface SchemaTable {
33
35
  indexes: SchemaIndex[];
34
36
  foreignKeys: SchemaForeignKey[];
35
37
  }
38
+ export interface SchemaEnum {
39
+ name: string;
40
+ values: string[];
41
+ }
36
42
  export interface SchemaSnapshot {
37
43
  tables: SchemaTable[];
44
+ enums: SchemaEnum[];
38
45
  }
39
46
  export interface SchemaSnapshotFile {
40
47
  version: 2;
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/schema/snapshot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;CACxB;AAiUD,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAaxF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,kBAAkB,CAMzE"}
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/schema/snapshot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;CACxB;AAiVD,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBxF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,kBAAkB,CAMzE"}
@@ -68,12 +68,17 @@ function getDefaultValue(field) {
68
68
  // Return function name for other functions
69
69
  return { hasDefault: true, default: `${funcName}()` };
70
70
  }
71
+ // Handle enum default values like @default(USER) - these are ReferenceExpr
72
+ if (expr.$type === "ReferenceExpr") {
73
+ const enumValue = expr.target.$refText;
74
+ return { hasDefault: true, default: enumValue };
75
+ }
71
76
  return { hasDefault: true };
72
77
  }
73
78
  function getTableName(model) {
74
79
  const mapAttr = getAttribute(model, "@@map");
75
80
  const mapped = getAttributeStringArg(mapAttr, ["name", "map"]);
76
- return mapped ?? model.name.toLowerCase();
81
+ return mapped ?? model.name;
77
82
  }
78
83
  function getColumnName(field) {
79
84
  const mapAttr = getAttribute(field, "@map");
@@ -124,12 +129,12 @@ function buildForeignKeyName(tableName, columns, _referencedTable, _referencedCo
124
129
  function getFieldType(field) {
125
130
  const ref = field.type.reference?.ref;
126
131
  if (ref && isDataModel(ref)) {
127
- return { type: ref.name, isRelation: true };
132
+ return { type: ref.name, isRelation: true, isEnum: false };
128
133
  }
129
134
  if (ref && isEnum(ref)) {
130
- return { type: ref.name, isRelation: false };
135
+ return { type: ref.name, isRelation: false, isEnum: true };
131
136
  }
132
- return { type: field.type.type ?? "String", isRelation: false };
137
+ return { type: field.type.type ?? "String", isRelation: false, isEnum: false };
133
138
  }
134
139
  function getRelationFieldNames(field) {
135
140
  const relationAttr = getAttribute(field, "@relation");
@@ -164,14 +169,16 @@ function parseModel(model) {
164
169
  }
165
170
  const defaultInfo = getDefaultValue(field);
166
171
  const columnName = getColumnName(field);
167
- const sqlType = mapFieldTypeToSQL(typeInfo.type);
172
+ // For enum types, store the enum name directly; for other types, map to SQL type
173
+ const columnType = typeInfo.isEnum ? typeInfo.type : mapFieldTypeToSQL(typeInfo.type);
168
174
  columns.push({
169
175
  name: columnName,
170
- type: sqlType,
176
+ type: columnType,
171
177
  notNull: !field.type.optional,
172
178
  isArray: field.type.array ?? false,
173
179
  default: defaultInfo.default,
174
180
  isAutoincrement: defaultInfo.isAutoincrement,
181
+ isEnum: typeInfo.isEnum || undefined,
175
182
  });
176
183
  }
177
184
  const modelIdAttr = getAttribute(model, "@@id");
@@ -257,6 +264,13 @@ function parseModel(model) {
257
264
  foreignKeys: foreignKeys.sort((a, b) => a.name.localeCompare(b.name)),
258
265
  };
259
266
  }
267
+ function parseEnum(enumDecl) {
268
+ const values = enumDecl.fields.map((field) => field.name);
269
+ return {
270
+ name: enumDecl.name,
271
+ values,
272
+ };
273
+ }
260
274
  export async function generateSchemaSnapshot(schemaPath) {
261
275
  const loadResult = await loadDocument(schemaPath);
262
276
  if (!loadResult.success) {
@@ -267,7 +281,11 @@ export async function generateSchemaSnapshot(schemaPath) {
267
281
  const tables = dataModels
268
282
  .map((model) => parseModel(model))
269
283
  .sort((a, b) => a.name.localeCompare(b.name));
270
- return { tables };
284
+ const enumDecls = loadResult.model.declarations.filter(isEnum);
285
+ const enums = enumDecls
286
+ .map((enumDecl) => parseEnum(enumDecl))
287
+ .sort((a, b) => a.name.localeCompare(b.name));
288
+ return { tables, enums };
271
289
  }
272
290
  export function createSnapshot(schema) {
273
291
  return {
@@ -5,7 +5,7 @@
5
5
  * without requiring a database connection.
6
6
  */
7
7
  import type { KyselyDialect } from "./kysely-adapter.js";
8
- import type { SchemaTable, SchemaColumn } from "../schema/snapshot.js";
8
+ import type { SchemaTable, SchemaColumn, SchemaEnum } from "../schema/snapshot.js";
9
9
  export interface SqlMigration {
10
10
  up: string[];
11
11
  down: string[];
@@ -71,4 +71,23 @@ export declare function compileAlterColumn(tableName: string, columnName: string
71
71
  setDefault?: string | number | boolean;
72
72
  dropDefault?: boolean;
73
73
  }, options: CompileSqlOptions): string[];
74
+ /**
75
+ * Compile a CREATE TYPE ... AS ENUM statement for PostgreSQL
76
+ * For MySQL and SQLite, enums are handled differently (inline or as text)
77
+ */
78
+ export declare function compileCreateEnum(enumDef: SchemaEnum, options: CompileSqlOptions): string | null;
79
+ /**
80
+ * Compile a DROP TYPE statement for PostgreSQL
81
+ */
82
+ export declare function compileDropEnum(enumName: string, options: CompileSqlOptions): string | null;
83
+ /**
84
+ * Compile an ALTER TYPE ... ADD VALUE statement for PostgreSQL
85
+ * Adds a new value to an existing enum type
86
+ */
87
+ export declare function compileAddEnumValue(enumName: string, value: string, options: CompileSqlOptions): string | null;
88
+ /**
89
+ * Map column type considering enum types
90
+ * For enum columns, returns the enum type name (PostgreSQL) or text (other dialects)
91
+ */
92
+ export declare function mapColumnTypeWithEnum(column: SchemaColumn, dialect: KyselyDialect): string;
74
93
  //# sourceMappingURL=compiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/sql/compiler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAoCvE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CA8CR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAsBR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EAAE,EAC3B,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,EACD,OAAO,EAAE,iBAAiB,GACzB,MAAM,EAAE,CAqDV"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/sql/compiler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAoCnF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAgDR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAwBR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EAAE,EAC3B,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,EACD,OAAO,EAAE,iBAAiB,GACzB,MAAM,EAAE,CAqDV;AAmED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CAQf;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CAMf;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CAOf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,GACrB,MAAM,CAgBR"}
@@ -47,10 +47,12 @@ export function compileCreateTable(model, options) {
47
47
  const db = createCompilerDb(options.dialect);
48
48
  let builder = db.schema.createTable(model.name);
49
49
  for (const column of model.columns) {
50
- const columnType = mapColumnType(column.type, options.dialect, {
51
- isArray: column.isArray,
52
- isAutoincrement: column.isAutoincrement,
53
- });
50
+ const columnType = column.isEnum
51
+ ? mapColumnTypeWithEnum(column, options.dialect)
52
+ : mapColumnType(column.type, options.dialect, {
53
+ isArray: column.isArray,
54
+ isAutoincrement: column.isAutoincrement,
55
+ });
54
56
  builder = builder.addColumn(column.name, sql.raw(columnType), (cb) => {
55
57
  // For SERIAL types in PostgreSQL, NOT NULL is implicit and we don't need defaults
56
58
  const isSerialType = column.isAutoincrement && options.dialect === "postgres";
@@ -58,7 +60,7 @@ export function compileCreateTable(model, options) {
58
60
  cb = cb.notNull();
59
61
  }
60
62
  if (column.default !== undefined && !isSerialType) {
61
- cb = cb.defaultTo(sql.raw(formatDefault(column.default, options.dialect)));
63
+ cb = cb.defaultTo(sql.raw(formatDefault(column.default, options.dialect, column)));
62
64
  }
63
65
  return cb;
64
66
  });
@@ -89,10 +91,12 @@ export function compileDropTable(tableName, options) {
89
91
  */
90
92
  export function compileAddColumn(tableName, column, options) {
91
93
  const db = createCompilerDb(options.dialect);
92
- const columnType = mapColumnType(column.type, options.dialect, {
93
- isArray: column.isArray,
94
- isAutoincrement: column.isAutoincrement,
95
- });
94
+ const columnType = column.isEnum
95
+ ? mapColumnTypeWithEnum(column, options.dialect)
96
+ : mapColumnType(column.type, options.dialect, {
97
+ isArray: column.isArray,
98
+ isAutoincrement: column.isAutoincrement,
99
+ });
96
100
  return (db.schema
97
101
  .alterTable(tableName)
98
102
  .addColumn(column.name, sql.raw(columnType), (cb) => {
@@ -101,7 +105,7 @@ export function compileAddColumn(tableName, column, options) {
101
105
  cb = cb.notNull();
102
106
  }
103
107
  if (column.default !== undefined && !isSerialType) {
104
- cb = cb.defaultTo(sql.raw(formatDefault(column.default, options.dialect)));
108
+ cb = cb.defaultTo(sql.raw(formatDefault(column.default, options.dialect, column)));
105
109
  }
106
110
  return cb;
107
111
  })
@@ -251,12 +255,17 @@ function mapColumnType(type, dialect, options) {
251
255
  /**
252
256
  * Format a default value for SQL
253
257
  */
254
- function formatDefault(value, dialect) {
258
+ function formatDefault(value, dialect, column) {
255
259
  if (typeof value === "string") {
256
260
  // Check if it's a function call like now() or autoincrement()
257
261
  if (/^\w+\([^)]*\)$/.test(value)) {
258
262
  return value;
259
263
  }
264
+ // For enum columns in PostgreSQL, we need to cast the default value
265
+ if (column?.isEnum && dialect === "postgres") {
266
+ const escapedValue = value.replace(/'/g, "''");
267
+ return `'${escapedValue}'::"${column.type}"`;
268
+ }
260
269
  // Escape string values
261
270
  return `'${value.replace(/'/g, "''")}'`;
262
271
  }
@@ -268,3 +277,55 @@ function formatDefault(value, dialect) {
268
277
  }
269
278
  return String(value);
270
279
  }
280
+ /**
281
+ * Compile a CREATE TYPE ... AS ENUM statement for PostgreSQL
282
+ * For MySQL and SQLite, enums are handled differently (inline or as text)
283
+ */
284
+ export function compileCreateEnum(enumDef, options) {
285
+ if (options.dialect !== "postgres") {
286
+ // MySQL and SQLite don't have standalone enum types
287
+ return null;
288
+ }
289
+ const values = enumDef.values.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
290
+ return `CREATE TYPE "${enumDef.name}" AS ENUM (${values});`;
291
+ }
292
+ /**
293
+ * Compile a DROP TYPE statement for PostgreSQL
294
+ */
295
+ export function compileDropEnum(enumName, options) {
296
+ if (options.dialect !== "postgres") {
297
+ return null;
298
+ }
299
+ return `DROP TYPE IF EXISTS "${enumName}";`;
300
+ }
301
+ /**
302
+ * Compile an ALTER TYPE ... ADD VALUE statement for PostgreSQL
303
+ * Adds a new value to an existing enum type
304
+ */
305
+ export function compileAddEnumValue(enumName, value, options) {
306
+ if (options.dialect !== "postgres") {
307
+ return null;
308
+ }
309
+ const escapedValue = value.replace(/'/g, "''");
310
+ return `ALTER TYPE "${enumName}" ADD VALUE '${escapedValue}';`;
311
+ }
312
+ /**
313
+ * Map column type considering enum types
314
+ * For enum columns, returns the enum type name (PostgreSQL) or text (other dialects)
315
+ */
316
+ export function mapColumnTypeWithEnum(column, dialect) {
317
+ if (column.isEnum) {
318
+ if (dialect === "postgres") {
319
+ // Use the native enum type for PostgreSQL
320
+ const baseType = `"${column.type}"`;
321
+ return column.isArray ? `${baseType}[]` : baseType;
322
+ }
323
+ // For MySQL and SQLite, fall back to text
324
+ return column.isArray ? "text[]" : "text";
325
+ }
326
+ // Use existing type mapping for non-enum columns
327
+ return mapColumnType(column.type, dialect, {
328
+ isArray: column.isArray,
329
+ isAutoincrement: column.isAutoincrement,
330
+ });
331
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zenstack-kit",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Drizzle-kit like CLI tooling for ZenStack schemas with Kysely support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -73,5 +73,10 @@
73
73
  "pg": "^8.13.1",
74
74
  "typescript": "^5.9.3",
75
75
  "vitest": "^4.0.16"
76
+ },
77
+ "pnpm": {
78
+ "onlyBuiltDependencies": [
79
+ "better-sqlite3"
80
+ ]
76
81
  }
77
82
  }