@vibeorm/migrate 1.2.0 → 1.3.0
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/package.json +2 -2
- package/src/introspector.ts +28 -3
- package/src/schema-differ.ts +23 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeorm/migrate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Migration, introspection, and schema diff toolkit for VibeORM",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"bun": ">=1.1.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@vibeorm/parser": "1.
|
|
40
|
+
"@vibeorm/parser": "1.3.0"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/introspector.ts
CHANGED
|
@@ -447,7 +447,7 @@ export async function introspect(params: { executor: SqlExecutor }): Promise<Sch
|
|
|
447
447
|
const isUnique = uniqueColumns.has(`${tableName}.${col.columnName}`);
|
|
448
448
|
const parsedDefault = parseDefault({ columnDefault: col.columnDefault, dataType: col.dataType });
|
|
449
449
|
|
|
450
|
-
//
|
|
450
|
+
// Branch 1: scalar enum column (data_type=USER-DEFINED, udt_name=EnumName).
|
|
451
451
|
if (col.dataType === "USER-DEFINED" && enumNames.has(col.udtName)) {
|
|
452
452
|
const enumField: EnumField = {
|
|
453
453
|
kind: "enum",
|
|
@@ -462,8 +462,33 @@ export async function introspect(params: { executor: SqlExecutor }): Promise<Sch
|
|
|
462
462
|
documentation: undefined,
|
|
463
463
|
};
|
|
464
464
|
fields.push(enumField);
|
|
465
|
-
}
|
|
466
|
-
|
|
465
|
+
}
|
|
466
|
+
// Branch 2: enum-array column. Postgres reports the underlying array type
|
|
467
|
+
// as `_EnumName` (the leading underscore is the array-type naming convention),
|
|
468
|
+
// and `data_type` is `ARRAY`. Without this branch the field would be parsed
|
|
469
|
+
// as `String[]` and the round-trip drift differ would always want to ALTER it.
|
|
470
|
+
// Bug 2 — see .ai/bug-report-2026-05-24.md.
|
|
471
|
+
else if (
|
|
472
|
+
col.dataType === "ARRAY" &&
|
|
473
|
+
col.udtName.startsWith("_") &&
|
|
474
|
+
enumNames.has(col.udtName.slice(1))
|
|
475
|
+
) {
|
|
476
|
+
const enumField: EnumField = {
|
|
477
|
+
kind: "enum",
|
|
478
|
+
name: col.columnName,
|
|
479
|
+
dbName: col.columnName,
|
|
480
|
+
enumName: col.udtName.slice(1),
|
|
481
|
+
isRequired: !col.isNullable,
|
|
482
|
+
isList: true,
|
|
483
|
+
isId,
|
|
484
|
+
isUnique,
|
|
485
|
+
default: parsedDefault,
|
|
486
|
+
documentation: undefined,
|
|
487
|
+
};
|
|
488
|
+
fields.push(enumField);
|
|
489
|
+
}
|
|
490
|
+
// Branch 3: scalar / scalar-array fallback.
|
|
491
|
+
else {
|
|
467
492
|
const pgType = col.dataType === "ARRAY" ? col.udtName.replace(/^_/, "") : col.udtName;
|
|
468
493
|
const prismaType = PG_TO_PRISMA[pgType] ?? PG_TO_PRISMA[col.dataType] ?? "String";
|
|
469
494
|
const isArray = col.dataType === "ARRAY";
|
package/src/schema-differ.ts
CHANGED
|
@@ -72,8 +72,11 @@ function getRelationFields(params: { model: Model }): RelationField[] {
|
|
|
72
72
|
function resolveColumnType(params: { field: ScalarField | EnumField; enums: Enum[] }): string {
|
|
73
73
|
const { field, enums } = params;
|
|
74
74
|
if (field.kind === "enum") {
|
|
75
|
+
// Enum lists must emit `EnumName[]`, not the scalar enum type. Mirrors
|
|
76
|
+
// `ddl-builder.ts:135-141`. Bug 2 — see .ai/bug-report-2026-05-24.md.
|
|
75
77
|
const enumDef = enums.find((e) => e.name === field.enumName);
|
|
76
|
-
|
|
78
|
+
const enumType = q({ name: enumDef?.dbName ?? field.enumName });
|
|
79
|
+
return field.isList ? `${enumType}[]` : enumType;
|
|
77
80
|
}
|
|
78
81
|
if (field.default?.kind === "autoincrement") {
|
|
79
82
|
return field.prismaType === "BigInt" ? "BIGSERIAL" : "SERIAL";
|
|
@@ -87,8 +90,10 @@ function resolveColumnType(params: { field: ScalarField | EnumField; enums: Enum
|
|
|
87
90
|
function resolveBaseColumnType(params: { field: ScalarField | EnumField; enums: Enum[] }): string {
|
|
88
91
|
const { field, enums } = params;
|
|
89
92
|
if (field.kind === "enum") {
|
|
93
|
+
// Same enum-list handling as resolveColumnType (Bug 2).
|
|
90
94
|
const enumDef = enums.find((e) => e.name === field.enumName);
|
|
91
|
-
|
|
95
|
+
const enumType = q({ name: enumDef?.dbName ?? field.enumName });
|
|
96
|
+
return field.isList ? `${enumType}[]` : enumType;
|
|
92
97
|
}
|
|
93
98
|
if (field.nativeType) return field.nativeType.toLowerCase();
|
|
94
99
|
if (field.isList) return `${PRISMA_TO_PG[field.prismaType]}[]`;
|
|
@@ -556,11 +561,25 @@ export function diffSchemas(params: { previous: Schema; current: Schema }): Diff
|
|
|
556
561
|
|
|
557
562
|
// Type change — reverse is setting back to the previous type
|
|
558
563
|
if (prevType !== currType) {
|
|
564
|
+
// Detect scalar -> array transition of same base type. PG can't implicit-cast,
|
|
565
|
+
// so we add a USING clause that wraps the existing value in a 1-element array
|
|
566
|
+
// (or NULL if the column is NULL). This heals databases broken by an earlier
|
|
567
|
+
// enum-array bug (Bug 2) where the column was created as the scalar type.
|
|
568
|
+
// We intentionally do NOT handle the reverse direction (array -> scalar) — that
|
|
569
|
+
// is lossy and the user must decide which element to keep.
|
|
570
|
+
const isScalarToArray =
|
|
571
|
+
currType.endsWith("[]") && !prevType.endsWith("[]") &&
|
|
572
|
+
currType.slice(0, -2) === prevType;
|
|
573
|
+
|
|
574
|
+
const usingClause = isScalarToArray
|
|
575
|
+
? ` USING CASE WHEN ${q({ name: colName })} IS NULL THEN NULL ELSE ARRAY[${q({ name: colName })}] END`
|
|
576
|
+
: "";
|
|
577
|
+
|
|
559
578
|
ops.push({
|
|
560
579
|
type: "alterColumnType",
|
|
561
|
-
sql: `ALTER TABLE ${q({ name: tableName })} ALTER COLUMN ${q({ name: colName })} SET DATA TYPE ${currType};`,
|
|
580
|
+
sql: `ALTER TABLE ${q({ name: tableName })} ALTER COLUMN ${q({ name: colName })} SET DATA TYPE ${currType}${usingClause};`,
|
|
562
581
|
downSql: `ALTER TABLE ${q({ name: tableName })} ALTER COLUMN ${q({ name: colName })} SET DATA TYPE ${prevType};`,
|
|
563
|
-
isDestructive:
|
|
582
|
+
isDestructive: !isScalarToArray, // scalar -> array is non-destructive (data preserved as 1-element arrays)
|
|
564
583
|
isReversible: true,
|
|
565
584
|
description: `Change type of "${fieldName}" in "${name}" from ${prevType} to ${currType}`,
|
|
566
585
|
});
|