zenstack-kit 0.1.12 → 0.1.14
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/dist/migrations/prisma/diff.d.ts +8 -1
- package/dist/migrations/prisma/diff.d.ts.map +1 -1
- package/dist/migrations/prisma/diff.js +71 -1
- package/dist/schema/pull.d.ts.map +1 -1
- package/dist/schema/pull.js +60 -4
- package/dist/schema/snapshot.d.ts +7 -0
- package/dist/schema/snapshot.d.ts.map +1 -1
- package/dist/schema/snapshot.js +31 -7
- package/dist/sql/compiler.d.ts +20 -1
- package/dist/sql/compiler.d.ts.map +1 -1
- package/dist/sql/compiler.js +72 -11
- package/package.json +1 -1
|
@@ -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;
|
|
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 +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;
|
|
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"}
|
package/dist/schema/pull.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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), ...
|
|
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;
|
|
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;AAuVD,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBxF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,kBAAkB,CAMzE"}
|
package/dist/schema/snapshot.js
CHANGED
|
@@ -65,9 +65,20 @@ function getDefaultValue(field) {
|
|
|
65
65
|
if (funcName === "now") {
|
|
66
66
|
return { hasDefault: true, default: "now()" };
|
|
67
67
|
}
|
|
68
|
-
//
|
|
68
|
+
// cuid(), uuid(), nanoid() are handled at the application level by ZenStack/Prisma,
|
|
69
|
+
// not at the database level. We mark hasDefault as true so the column isn't
|
|
70
|
+
// required in INSERT statements (the ORM will provide the value).
|
|
71
|
+
if (funcName === "cuid" || funcName === "uuid" || funcName === "nanoid") {
|
|
72
|
+
return { hasDefault: true };
|
|
73
|
+
}
|
|
74
|
+
// Return function name for other functions (dbgenerated, etc.)
|
|
69
75
|
return { hasDefault: true, default: `${funcName}()` };
|
|
70
76
|
}
|
|
77
|
+
// Handle enum default values like @default(USER) - these are ReferenceExpr
|
|
78
|
+
if (expr.$type === "ReferenceExpr") {
|
|
79
|
+
const enumValue = expr.target.$refText;
|
|
80
|
+
return { hasDefault: true, default: enumValue };
|
|
81
|
+
}
|
|
71
82
|
return { hasDefault: true };
|
|
72
83
|
}
|
|
73
84
|
function getTableName(model) {
|
|
@@ -124,12 +135,12 @@ function buildForeignKeyName(tableName, columns, _referencedTable, _referencedCo
|
|
|
124
135
|
function getFieldType(field) {
|
|
125
136
|
const ref = field.type.reference?.ref;
|
|
126
137
|
if (ref && isDataModel(ref)) {
|
|
127
|
-
return { type: ref.name, isRelation: true };
|
|
138
|
+
return { type: ref.name, isRelation: true, isEnum: false };
|
|
128
139
|
}
|
|
129
140
|
if (ref && isEnum(ref)) {
|
|
130
|
-
return { type: ref.name, isRelation: false };
|
|
141
|
+
return { type: ref.name, isRelation: false, isEnum: true };
|
|
131
142
|
}
|
|
132
|
-
return { type: field.type.type ?? "String", isRelation: false };
|
|
143
|
+
return { type: field.type.type ?? "String", isRelation: false, isEnum: false };
|
|
133
144
|
}
|
|
134
145
|
function getRelationFieldNames(field) {
|
|
135
146
|
const relationAttr = getAttribute(field, "@relation");
|
|
@@ -164,14 +175,16 @@ function parseModel(model) {
|
|
|
164
175
|
}
|
|
165
176
|
const defaultInfo = getDefaultValue(field);
|
|
166
177
|
const columnName = getColumnName(field);
|
|
167
|
-
|
|
178
|
+
// For enum types, store the enum name directly; for other types, map to SQL type
|
|
179
|
+
const columnType = typeInfo.isEnum ? typeInfo.type : mapFieldTypeToSQL(typeInfo.type);
|
|
168
180
|
columns.push({
|
|
169
181
|
name: columnName,
|
|
170
|
-
type:
|
|
182
|
+
type: columnType,
|
|
171
183
|
notNull: !field.type.optional,
|
|
172
184
|
isArray: field.type.array ?? false,
|
|
173
185
|
default: defaultInfo.default,
|
|
174
186
|
isAutoincrement: defaultInfo.isAutoincrement,
|
|
187
|
+
isEnum: typeInfo.isEnum || undefined,
|
|
175
188
|
});
|
|
176
189
|
}
|
|
177
190
|
const modelIdAttr = getAttribute(model, "@@id");
|
|
@@ -257,6 +270,13 @@ function parseModel(model) {
|
|
|
257
270
|
foreignKeys: foreignKeys.sort((a, b) => a.name.localeCompare(b.name)),
|
|
258
271
|
};
|
|
259
272
|
}
|
|
273
|
+
function parseEnum(enumDecl) {
|
|
274
|
+
const values = enumDecl.fields.map((field) => field.name);
|
|
275
|
+
return {
|
|
276
|
+
name: enumDecl.name,
|
|
277
|
+
values,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
260
280
|
export async function generateSchemaSnapshot(schemaPath) {
|
|
261
281
|
const loadResult = await loadDocument(schemaPath);
|
|
262
282
|
if (!loadResult.success) {
|
|
@@ -267,7 +287,11 @@ export async function generateSchemaSnapshot(schemaPath) {
|
|
|
267
287
|
const tables = dataModels
|
|
268
288
|
.map((model) => parseModel(model))
|
|
269
289
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
270
|
-
|
|
290
|
+
const enumDecls = loadResult.model.declarations.filter(isEnum);
|
|
291
|
+
const enums = enumDecls
|
|
292
|
+
.map((enumDecl) => parseEnum(enumDecl))
|
|
293
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
294
|
+
return { tables, enums };
|
|
271
295
|
}
|
|
272
296
|
export function createSnapshot(schema) {
|
|
273
297
|
return {
|
package/dist/sql/compiler.d.ts
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/sql/compiler.ts"],"names":[],"mappings":"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"}
|
package/dist/sql/compiler.js
CHANGED
|
@@ -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 =
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|