@stacksjs/database 0.70.92 → 0.70.94
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.d.ts +11 -3
- package/dist/migrations.js +56 -4
- package/package.json +11 -11
package/dist/migrations.d.ts
CHANGED
|
@@ -30,9 +30,6 @@ export type { MigrationResult as MigrationResultType };
|
|
|
30
30
|
* directory clean and prevents future runs from re-discovering it.
|
|
31
31
|
*/
|
|
32
32
|
export declare function preprocessSqliteMigrations(): void;
|
|
33
|
-
/**
|
|
34
|
-
* Run database migrations
|
|
35
|
-
*/
|
|
36
33
|
export declare function runDatabaseMigration(): Promise<Result<string, Error>>;
|
|
37
34
|
/**
|
|
38
35
|
* Reset the database (drop all tables)
|
|
@@ -46,6 +43,13 @@ export declare function resetDatabase(): Promise<Result<string, Error>>;
|
|
|
46
43
|
*/
|
|
47
44
|
export declare function previewPendingMigrations(options?: GenerateMigrationsOptions): Promise<MigrationOperation[]>;
|
|
48
45
|
export declare function generateMigrations(options?: GenerateMigrationsOptions): Promise<Result<string, Error>>;
|
|
46
|
+
/**
|
|
47
|
+
* Group generated SQL by the migration filename style the runner already
|
|
48
|
+
* uses for hand-written files: `create-<table>-table`,
|
|
49
|
+
* `alter-<table>-<col>`, `create-<index>-index-in-<table>`, or
|
|
50
|
+
* `drop-<table>-table`. Anything we can't match falls back to `auto-misc`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function groupGeneratedStatements(sqlStatements: string[]): GeneratedGroup[];
|
|
49
53
|
/**
|
|
50
54
|
* Generate fresh migrations (full regeneration, ignoring previous state)
|
|
51
55
|
*/
|
|
@@ -66,6 +70,10 @@ export declare interface GenerateMigrationsOptions {
|
|
|
66
70
|
applyRenames?: boolean
|
|
67
71
|
fromDb?: boolean
|
|
68
72
|
}
|
|
73
|
+
declare interface GeneratedGroup {
|
|
74
|
+
label: string
|
|
75
|
+
statements: string[]
|
|
76
|
+
}
|
|
69
77
|
/**
|
|
70
78
|
* Migration result type for compatibility
|
|
71
79
|
*/
|
package/dist/migrations.js
CHANGED
|
@@ -247,7 +247,7 @@ async function ensureDatabaseExists() {
|
|
|
247
247
|
async function hideDisabledFeatureMigrations() {
|
|
248
248
|
const hidden = [];
|
|
249
249
|
try {
|
|
250
|
-
const { FEATURE_NAMES, migrationFeature } = await import("@stacksjs/buddy"), { feature: isFeatureEnabled } = await import("@stacksjs/config"), fs = await import("node:fs/promises"), migrationsDir = path.projectPath("database/migrations");
|
|
250
|
+
const { appModelClaimsTable, FEATURE_NAMES, migrationFeature, migrationTable } = await import("@stacksjs/buddy"), { feature: isFeatureEnabled } = await import("@stacksjs/config"), fs = await import("node:fs/promises"), migrationsDir = path.projectPath("database/migrations");
|
|
251
251
|
if (!existsSync(migrationsDir))
|
|
252
252
|
return hidden;
|
|
253
253
|
const disabledFeatures = new Set(FEATURE_NAMES.filter((f) => !isFeatureEnabled(f)));
|
|
@@ -258,6 +258,9 @@ async function hideDisabledFeatureMigrations() {
|
|
|
258
258
|
const owner = migrationFeature(file);
|
|
259
259
|
if (!owner || !disabledFeatures.has(owner))
|
|
260
260
|
continue;
|
|
261
|
+
const table = migrationTable(file);
|
|
262
|
+
if (table && appModelClaimsTable(table))
|
|
263
|
+
continue;
|
|
261
264
|
const original = join(migrationsDir, file), hiddenPath = `${original}.disabled`;
|
|
262
265
|
await fs.rename(original, hiddenPath);
|
|
263
266
|
hidden.push({ original, hidden: hiddenPath, feature: owner });
|
|
@@ -301,6 +304,50 @@ async function writeMigrateMarker(appliedCount) {
|
|
|
301
304
|
await fs.writeFile(file, body, "utf8");
|
|
302
305
|
} catch {}
|
|
303
306
|
}
|
|
307
|
+
function idempotentSql(sql) {
|
|
308
|
+
const stmts = sql.split(";").map((s) => s.trim()).filter(Boolean);
|
|
309
|
+
if (stmts.length === 0)
|
|
310
|
+
return sql;
|
|
311
|
+
const out = [];
|
|
312
|
+
for (const raw of stmts) {
|
|
313
|
+
const stmt = raw.replace(/\bADD\s+COLUMN\s+(?!IF\s+NOT\s+EXISTS\b)/gi, "ADD COLUMN IF NOT EXISTS "), m = /^ALTER\s+TABLE\s+("?\w+"?)\s+ADD\s+CONSTRAINT\s+("?\w+"?)/i.exec(stmt);
|
|
314
|
+
if (m) {
|
|
315
|
+
const drop = `ALTER TABLE ${m[1]} DROP CONSTRAINT IF EXISTS ${m[2]}`;
|
|
316
|
+
if ((out[out.length - 1] ?? "").toUpperCase() !== drop.toUpperCase())
|
|
317
|
+
out.push(drop);
|
|
318
|
+
}
|
|
319
|
+
out.push(stmt);
|
|
320
|
+
}
|
|
321
|
+
return `${out.join(`;
|
|
322
|
+
`)};
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
function makeMigrationsIdempotent() {
|
|
326
|
+
const migrationsDir = join(process.cwd(), "database", "migrations");
|
|
327
|
+
let files;
|
|
328
|
+
try {
|
|
329
|
+
files = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql"));
|
|
330
|
+
} catch {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
for (const f of files) {
|
|
334
|
+
const p = join(migrationsDir, f);
|
|
335
|
+
let sql;
|
|
336
|
+
try {
|
|
337
|
+
sql = readFileSync(p, "utf8");
|
|
338
|
+
} catch {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (!/\bADD\s+(?:COLUMN|CONSTRAINT)\b/i.test(sql))
|
|
342
|
+
continue;
|
|
343
|
+
const next = idempotentSql(sql);
|
|
344
|
+
if (next !== sql)
|
|
345
|
+
try {
|
|
346
|
+
writeFileSync(p, next);
|
|
347
|
+
log.debug(`[migration] made idempotent: ${f}`);
|
|
348
|
+
} catch {}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
304
351
|
export async function runDatabaseMigration() {
|
|
305
352
|
const startedAt = Date.now(), hidden = await hideDisabledFeatureMigrations();
|
|
306
353
|
let lockHandle = null;
|
|
@@ -312,6 +359,8 @@ export async function runDatabaseMigration() {
|
|
|
312
359
|
lockHandle = await acquireMigrationLock(dialect, lockDb);
|
|
313
360
|
if (dialect === "sqlite")
|
|
314
361
|
preprocessSqliteMigrations();
|
|
362
|
+
else if (dialect === "postgres")
|
|
363
|
+
makeMigrationsIdempotent();
|
|
315
364
|
const modelsDir = path.userModelsPath(), appliedBefore = await countAppliedMigrations();
|
|
316
365
|
log.debug(`[migration] Running migrations from: ${modelsDir}`);
|
|
317
366
|
await qbExecuteMigration(modelsDir);
|
|
@@ -486,12 +535,15 @@ ${readFileSync(join(migrationsDir, f), "utf8")}`;
|
|
|
486
535
|
}
|
|
487
536
|
return written;
|
|
488
537
|
}
|
|
489
|
-
function groupGeneratedStatements(sqlStatements) {
|
|
538
|
+
export function groupGeneratedStatements(sqlStatements) {
|
|
490
539
|
const groups = new Map, push = (label, stmt) => {
|
|
491
540
|
const list = groups.get(label) ?? [];
|
|
492
541
|
list.push(stmt);
|
|
493
542
|
groups.set(label, list);
|
|
494
|
-
}
|
|
543
|
+
}, createdTables = new Set(sqlStatements.flatMap((raw) => {
|
|
544
|
+
const match = raw.trim().match(/^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?/i);
|
|
545
|
+
return match?.[1] ? [match[1]] : [];
|
|
546
|
+
}));
|
|
495
547
|
for (const raw of sqlStatements) {
|
|
496
548
|
const stmt = raw.trim();
|
|
497
549
|
if (!stmt)
|
|
@@ -508,7 +560,7 @@ function groupGeneratedStatements(sqlStatements) {
|
|
|
508
560
|
}
|
|
509
561
|
const idx = stmt.match(/^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?\s+ON\s+["`]?(\w+)["`]?/i);
|
|
510
562
|
if (idx) {
|
|
511
|
-
push(`create-${idx[1]}-index-in-${idx[2]}`, stmt);
|
|
563
|
+
push(createdTables.has(idx[2]) ? `create-${idx[2]}-table` : `create-${idx[1]}-index-in-${idx[2]}`, stmt);
|
|
512
564
|
continue;
|
|
513
565
|
}
|
|
514
566
|
const drop = stmt.match(/^\s*DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?["`]?(\w+)["`]?/i);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.94",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -55,18 +55,18 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"bun-query-builder": "^0.1.
|
|
58
|
+
"bun-query-builder": "^0.1.51"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@stacksjs/cli": "0.70.
|
|
62
|
-
"@stacksjs/config": "0.70.
|
|
63
|
-
"@stacksjs/logging": "0.70.
|
|
64
|
-
"@stacksjs/router": "0.70.
|
|
61
|
+
"@stacksjs/cli": "0.70.94",
|
|
62
|
+
"@stacksjs/config": "0.70.94",
|
|
63
|
+
"@stacksjs/logging": "0.70.94",
|
|
64
|
+
"@stacksjs/router": "0.70.94",
|
|
65
65
|
"better-dx": "^0.2.16",
|
|
66
|
-
"@stacksjs/path": "0.70.
|
|
67
|
-
"@stacksjs/query-builder": "0.70.
|
|
68
|
-
"@stacksjs/storage": "0.70.
|
|
69
|
-
"@stacksjs/strings": "0.70.
|
|
70
|
-
"@stacksjs/utils": "0.70.
|
|
66
|
+
"@stacksjs/path": "0.70.94",
|
|
67
|
+
"@stacksjs/query-builder": "0.70.94",
|
|
68
|
+
"@stacksjs/storage": "0.70.94",
|
|
69
|
+
"@stacksjs/strings": "0.70.94",
|
|
70
|
+
"@stacksjs/utils": "0.70.94"
|
|
71
71
|
}
|
|
72
72
|
}
|