@cosmicdrift/kumiko-framework 0.163.1 → 0.163.2
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.163.
|
|
3
|
+
"version": "0.163.2",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"./package.json": "./package.json"
|
|
183
183
|
},
|
|
184
184
|
"dependencies": {
|
|
185
|
-
"@cosmicdrift/kumiko-types": "0.163.
|
|
185
|
+
"@cosmicdrift/kumiko-types": "0.163.2",
|
|
186
186
|
"bullmq": "^5.76.7",
|
|
187
187
|
"bun-types": "^1.3.13",
|
|
188
188
|
"hono": "^4.12.18",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
"zod": "^4.4.3"
|
|
199
199
|
},
|
|
200
200
|
"devDependencies": {
|
|
201
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.163.
|
|
201
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.163.2",
|
|
202
202
|
"bun-types": "^1.3.13",
|
|
203
203
|
"pino-pretty": "^13.1.3"
|
|
204
204
|
},
|
|
@@ -72,6 +72,54 @@ CREATE INDEX IF NOT EXISTS "read_accounts_tenant_id_idx" ON "read_accounts" ("te
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// Regression: publicstatus#0007_fix-secrets-table-columns adds three
|
|
76
|
+
// columns in ONE statement (comma-separated ADD COLUMN clauses) — the
|
|
77
|
+
// replay used to only pick up the first, reporting "metadata" and
|
|
78
|
+
// "last_rotated_at" as missing even though the migration creates them.
|
|
79
|
+
test("multiple ADD COLUMN clauses in a single ALTER TABLE statement all extend the table", () => {
|
|
80
|
+
const dir = tmpMigrationsDir();
|
|
81
|
+
try {
|
|
82
|
+
write(dir, "0001_init.sql", `CREATE TABLE IF NOT EXISTS "read_a" ("id" uuid PRIMARY KEY);`);
|
|
83
|
+
write(
|
|
84
|
+
dir,
|
|
85
|
+
"0002_add-cols.sql",
|
|
86
|
+
`ALTER TABLE "read_a"
|
|
87
|
+
ADD COLUMN IF NOT EXISTS "envelope" jsonb NOT NULL,
|
|
88
|
+
ADD COLUMN IF NOT EXISTS "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
89
|
+
ADD COLUMN IF NOT EXISTS "last_rotated_at" timestamp with time zone DEFAULT now() NOT NULL;`,
|
|
90
|
+
);
|
|
91
|
+
const replayed = replayMigrationsDir(dir);
|
|
92
|
+
expect([...(replayed.get("read_a")?.columns ?? [])].sort()).toEqual([
|
|
93
|
+
"envelope",
|
|
94
|
+
"id",
|
|
95
|
+
"last_rotated_at",
|
|
96
|
+
"metadata",
|
|
97
|
+
]);
|
|
98
|
+
} finally {
|
|
99
|
+
rmSync(dir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("mixed ADD + DROP COLUMN clauses in one statement apply in order", () => {
|
|
104
|
+
const dir = tmpMigrationsDir();
|
|
105
|
+
try {
|
|
106
|
+
write(
|
|
107
|
+
dir,
|
|
108
|
+
"0001_init.sql",
|
|
109
|
+
`CREATE TABLE IF NOT EXISTS "read_a" ("id" uuid PRIMARY KEY, "legacy" text);`,
|
|
110
|
+
);
|
|
111
|
+
write(
|
|
112
|
+
dir,
|
|
113
|
+
"0002_migrate-cols.sql",
|
|
114
|
+
`ALTER TABLE "read_a" DROP COLUMN IF EXISTS "legacy", ADD COLUMN IF NOT EXISTS "title" text;`,
|
|
115
|
+
);
|
|
116
|
+
const replayed = replayMigrationsDir(dir);
|
|
117
|
+
expect([...(replayed.get("read_a")?.columns ?? [])].sort()).toEqual(["id", "title"]);
|
|
118
|
+
} finally {
|
|
119
|
+
rmSync(dir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
75
123
|
test("DROP COLUMN IF EXISTS (hand-edited) still removes the column", () => {
|
|
76
124
|
const dir = tmpMigrationsDir();
|
|
77
125
|
try {
|
|
@@ -54,37 +54,40 @@ function applyStatement(schema: Map<string, { columns: Set<string> }>, statement
|
|
|
54
54
|
const create = statement.match(
|
|
55
55
|
/^CREATE TABLE\s+(?:IF NOT EXISTS\s+)?"([^"]+)"\s*\(([\s\S]*)\);?\s*$/i,
|
|
56
56
|
);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
const addColumnTable = addColumn?.[1];
|
|
64
|
-
const addColumnName = addColumn?.[2];
|
|
57
|
+
if (create?.[1] !== undefined && create[2] !== undefined) {
|
|
58
|
+
schema.set(create[1], { columns: parseColumnNames(create[2]) });
|
|
59
|
+
// skip: CREATE TABLE fully handled above, no other clause can also match
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
65
62
|
|
|
66
63
|
const dropTable = statement.match(/^DROP TABLE\s+(?:IF EXISTS\s+)?"([^"]+)"/i);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const dropColumnTable = dropColumn?.[1];
|
|
73
|
-
const dropColumnName = dropColumn?.[2];
|
|
64
|
+
if (dropTable?.[1] !== undefined) {
|
|
65
|
+
schema.delete(dropTable[1]);
|
|
66
|
+
// skip: DROP TABLE fully handled above, no other clause can also match
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
74
69
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
// A single ALTER TABLE statement can carry multiple comma-separated
|
|
71
|
+
// ADD/DROP COLUMN clauses (e.g. migration 0007_fix-secrets-table-columns'
|
|
72
|
+
// three-column fix in one statement) — matchAll over the whole body
|
|
73
|
+
// instead of matching only the first clause, in statement order so an
|
|
74
|
+
// add-then-drop of the same column (unusual, but not impossible) resolves
|
|
75
|
+
// correctly.
|
|
76
|
+
const alterTable = statement.match(/^ALTER TABLE\s+"([^"]+)"\s+([\s\S]*?);?\s*$/i);
|
|
77
|
+
const alterTableName = alterTable?.[1];
|
|
78
|
+
const alterBody = alterTable?.[2];
|
|
79
|
+
if (alterTableName !== undefined && alterBody !== undefined) {
|
|
80
|
+
const table = schema.get(alterTableName) ?? { columns: new Set<string>() };
|
|
81
|
+
schema.set(alterTableName, table);
|
|
82
|
+
const clauseRe = /(ADD|DROP)\s+COLUMN\s+(?:IF (?:NOT )?EXISTS\s+)?"([^"]+)"/gi;
|
|
83
|
+
for (const [, verb, name] of alterBody.matchAll(clauseRe)) {
|
|
84
|
+
if (verb === undefined || name === undefined) continue;
|
|
85
|
+
if (verb.toUpperCase() === "ADD") table.columns.add(name);
|
|
86
|
+
else table.columns.delete(name);
|
|
87
|
+
}
|
|
85
88
|
}
|
|
86
|
-
// else: CREATE INDEX
|
|
87
|
-
//
|
|
89
|
+
// else: CREATE INDEX and everything else don't change the table/column
|
|
90
|
+
// shape this replay tracks.
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// Reads `<migrationsDir>/*.sql` in sequence order and replays every
|