@stacksjs/database 0.70.91 → 0.70.93

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.
@@ -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)
@@ -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);
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.91",
5
+ "version": "0.70.93",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,15 +58,15 @@
58
58
  "bun-query-builder": "^0.1.50"
59
59
  },
60
60
  "devDependencies": {
61
- "@stacksjs/cli": "0.70.91",
62
- "@stacksjs/config": "0.70.91",
63
- "@stacksjs/logging": "0.70.91",
64
- "@stacksjs/router": "0.70.91",
61
+ "@stacksjs/cli": "0.70.93",
62
+ "@stacksjs/config": "0.70.93",
63
+ "@stacksjs/logging": "0.70.93",
64
+ "@stacksjs/router": "0.70.93",
65
65
  "better-dx": "^0.2.16",
66
- "@stacksjs/path": "0.70.91",
67
- "@stacksjs/query-builder": "0.70.91",
68
- "@stacksjs/storage": "0.70.91",
69
- "@stacksjs/strings": "0.70.91",
70
- "@stacksjs/utils": "0.70.91"
66
+ "@stacksjs/path": "0.70.93",
67
+ "@stacksjs/query-builder": "0.70.93",
68
+ "@stacksjs/storage": "0.70.93",
69
+ "@stacksjs/strings": "0.70.93",
70
+ "@stacksjs/utils": "0.70.93"
71
71
  }
72
72
  }