@voltro/database 0.5.0 → 0.7.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/CHANGELOG.md +68 -1
- package/dist/index.d.ts +176 -11
- package/dist/index.js +669 -507
- package/dist/sql.d.ts +18 -0
- package/dist/sql.js +32 -23
- package/package.json +2 -2
package/dist/sql.d.ts
CHANGED
|
@@ -2003,6 +2003,24 @@ declare interface TableUnique {
|
|
|
2003
2003
|
};
|
|
2004
2004
|
}
|
|
2005
2005
|
|
|
2006
|
+
/**
|
|
2007
|
+
* Clear the `blocked` flag on every LOSSY operation in a plan.
|
|
2008
|
+
*
|
|
2009
|
+
* Called only after the destructive opt-in has been established, and it is not
|
|
2010
|
+
* cosmetic. `applyPlan` carries its OWN unconditional refusal on any blocked
|
|
2011
|
+
* operation, with no override parameter. So a plan that the boot gate has
|
|
2012
|
+
* decided may proceed, but which still arrives marked blocked, is refused a
|
|
2013
|
+
* second time by the applier — which meant `VOLTRO_DESTRUCTIVE_OK=1` could
|
|
2014
|
+
* never actually drop a table. The planner's fix hint names that exact
|
|
2015
|
+
* variable, so the documented escape hatch pointed at a wall.
|
|
2016
|
+
*
|
|
2017
|
+
* Only `lossy` ops are unblocked, and callers must have already established
|
|
2018
|
+
* that no other class is blocked. A rename-without-marker or a NOT NULL
|
|
2019
|
+
* without backfill stays blocked even in destructive-OK mode: those lose data
|
|
2020
|
+
* regardless of intent, so intent is not the question being asked.
|
|
2021
|
+
*/
|
|
2022
|
+
export declare const unblockLossy: (plan: MigrationPlan) => MigrationPlan;
|
|
2023
|
+
|
|
2006
2024
|
/**
|
|
2007
2025
|
* Unique-constraint metadata. Set by `.unique()` (or `.unique({ dedup })`).
|
|
2008
2026
|
*
|
package/dist/sql.js
CHANGED
|
@@ -1261,6 +1261,7 @@ BEGIN
|
|
|
1261
1261
|
JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)
|
|
1262
1262
|
WHERE con.contype = 'p' AND nsp.nspname = current_schema()
|
|
1263
1263
|
AND rel.relname IN ${t}
|
|
1264
|
+
ORDER BY rel.relname, array_position(con.conkey, att.attnum)
|
|
1264
1265
|
`), p = yield* a(l, (t) => e`
|
|
1265
1266
|
SELECT rel.relname AS table_name, con.conname AS constraint_name,
|
|
1266
1267
|
att.attname AS column_name, array_position(con.conkey, att.attnum) AS position
|
|
@@ -2337,7 +2338,14 @@ BEGIN
|
|
|
2337
2338
|
}), rr = () => {
|
|
2338
2339
|
let e = (process.env.VOLTRO_DB_IGNORE_TABLES ?? "").split(",").map((e) => e.trim()).filter((e) => e.length > 0);
|
|
2339
2340
|
return e.length > 0 ? { ignoreTables: e } : {};
|
|
2340
|
-
}, ir = (e
|
|
2341
|
+
}, ir = (e) => ({
|
|
2342
|
+
...e,
|
|
2343
|
+
operations: e.operations.map((e) => {
|
|
2344
|
+
if (!e.blocked || e.classification !== "lossy") return e;
|
|
2345
|
+
let { blocked: t, ...n } = e;
|
|
2346
|
+
return n;
|
|
2347
|
+
})
|
|
2348
|
+
}), ar = (e, t, n) => d.gen(function* () {
|
|
2341
2349
|
let r = Qn(e), i = t.filter((e) => !at(e.tableName));
|
|
2342
2350
|
if (process.env.VOLTRO_MIGRATE_FORCE !== "1") {
|
|
2343
2351
|
let t = M(P(i, r));
|
|
@@ -2363,6 +2371,7 @@ BEGIN
|
|
|
2363
2371
|
kind: "refused-blocked",
|
|
2364
2372
|
plan: s
|
|
2365
2373
|
};
|
|
2374
|
+
s = ir(s);
|
|
2366
2375
|
}
|
|
2367
2376
|
return s.operations.length === 0 ? {
|
|
2368
2377
|
kind: "up-to-date",
|
|
@@ -2377,10 +2386,10 @@ BEGIN
|
|
|
2377
2386
|
}),
|
|
2378
2387
|
plan: s
|
|
2379
2388
|
};
|
|
2380
|
-
}),
|
|
2389
|
+
}), or = (e, t, n) => process.env.VOLTRO_AUTO_MIGRATE === "0" ? d.succeed({
|
|
2381
2390
|
kind: "skipped",
|
|
2382
2391
|
reason: "VOLTRO_AUTO_MIGRATE=0"
|
|
2383
|
-
}) : n.environment === "prod" ? $n(e, t) :
|
|
2392
|
+
}) : n.environment === "prod" ? $n(e, t) : ar(e, t, n), sr = (e, t) => {
|
|
2384
2393
|
switch (e.kind) {
|
|
2385
2394
|
case "skipped": return `auto-migrate: skipped (${e.reason})`;
|
|
2386
2395
|
case "up-to-date": return `auto-migrate: schema up to date (${t}, fingerprint=${N(e.fingerprint)})`;
|
|
@@ -2394,7 +2403,7 @@ BEGIN
|
|
|
2394
2403
|
return n > 0 && s.push(`${n} table(s) exist in the live DB but aren't in your declared schema.`, "This is usually one of three things:", " (a) You're iterating on the schema — the table file got renamed, moved, or temporarily commented out.", " → Restore the declaration. Your data stays intact.", " (b) You want to KEEP the table but NOT manage it with Voltro (a leftover from another tool —", " a Strapi/Rails/legacy migration artifact, or an externally-owned table).", ` → Set \`VOLTRO_DB_IGNORE_TABLES=${t.filter((e) => e.op.kind === "drop-table").map((e) => "table" in e.op ? e.op.table : "").filter(Boolean).join(",")}\` (comma-separated).`, " The planner excludes those tables from the diff entirely — it never drops them, and they", " stop blocking your OTHER (additive) changes. This is the fix when a leftover freezes your schema.", " (c) You actually want to drop the table.", " → Add it to your declared set + chain `.dropped()` on it, OR", " write a file-based migration in `migrations/` that DROPs it, OR", " set `VOLTRO_DESTRUCTIVE_OK=1` for ONE `voltro dev` run (acknowledges data loss).", " Soft-drop: set `VOLTRO_SOFT_DROP=1` alongside DESTRUCTIVE_OK to RENAME instead of DROP", " (data survives as `<name>__dropped_<ts>` for ~7d; restorable via `voltro db restore-snapshot`)."), r > 0 && s.push(`${r} required column(s) need a backfill strategy before adding to a populated table.`, " → Annotate the column: `.backfill(sql`<expr>`)` or `.backfill(row => <fn>)` or `.default(<value>)`.", " Existing rows get the backfill value; the column then lands as NOT NULL."), i > 0 && s.push(`${i} column(s) are missing from your declared schema.`, " → If intentional: chain `.dropped()` on the column slot in the table declaration.", " → If a typo: restore the field. Data survives until you drop it."), a > 0 && s.push(`${a} column(s) look like renames (one column gone + a new one of same shape appeared).`, " → On the NEW column add `.renamedFrom('<oldName>')` so the planner emits RENAME instead of DROP+ADD."), s.push("", "See `voltro db plan` for the full machine-readable diff."), o + "\n" + s.join("\n");
|
|
2395
2404
|
}
|
|
2396
2405
|
}
|
|
2397
|
-
},
|
|
2406
|
+
}, cr = /* @__PURE__ */ new Set([
|
|
2398
2407
|
"node_modules",
|
|
2399
2408
|
"dist",
|
|
2400
2409
|
"build",
|
|
@@ -2403,11 +2412,11 @@ BEGIN
|
|
|
2403
2412
|
".next",
|
|
2404
2413
|
".git",
|
|
2405
2414
|
".framework"
|
|
2406
|
-
]),
|
|
2415
|
+
]), lr = async (e) => {
|
|
2407
2416
|
let t = [], n = async (e) => {
|
|
2408
2417
|
let r = await h.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
2409
2418
|
for (let i of r) {
|
|
2410
|
-
if (
|
|
2419
|
+
if (cr.has(i.name)) continue;
|
|
2411
2420
|
let r = g(e, i.name);
|
|
2412
2421
|
i.isDirectory() ? await n(r) : i.isFile() && c.test(i.name) && t.push(r);
|
|
2413
2422
|
}
|
|
@@ -2416,7 +2425,7 @@ BEGIN
|
|
|
2416
2425
|
let n = e.split("/").pop() ?? e, r = t.split("/").pop() ?? t;
|
|
2417
2426
|
return n.localeCompare(r);
|
|
2418
2427
|
});
|
|
2419
|
-
},
|
|
2428
|
+
}, ur = async (e) => {
|
|
2420
2429
|
let t = [];
|
|
2421
2430
|
for (let n of e) {
|
|
2422
2431
|
let e = (await import(ee(n).href)).default;
|
|
@@ -2430,11 +2439,11 @@ BEGIN
|
|
|
2430
2439
|
});
|
|
2431
2440
|
}
|
|
2432
2441
|
return t;
|
|
2433
|
-
},
|
|
2442
|
+
}, dr = (e) => e`
|
|
2434
2443
|
SELECT ${e("id")}
|
|
2435
2444
|
FROM ${e("_voltro_migration_plans")}
|
|
2436
2445
|
WHERE ${e("source")} = 'file'
|
|
2437
|
-
`.pipe(d.map((e) => e.map((e) => e.id)), d.catchAll(() => d.succeed([]))),
|
|
2446
|
+
`.pipe(d.map((e) => e.map((e) => e.id)), d.catchAll(() => d.succeed([]))), fr = (e, t, n, r) => d.gen(function* () {
|
|
2438
2447
|
let i = Date.now(), a = (/* @__PURE__ */ new Date()).toISOString(), o = {
|
|
2439
2448
|
sql: e,
|
|
2440
2449
|
log: {
|
|
@@ -2467,9 +2476,9 @@ BEGIN
|
|
|
2467
2476
|
${r}, ${n}, ${"file"},
|
|
2468
2477
|
${c}, ${a}, ${t.migration.description})
|
|
2469
2478
|
`, { durationMs: c };
|
|
2470
|
-
}),
|
|
2479
|
+
}), pr = (e, t) => d.gen(function* () {
|
|
2471
2480
|
let n = yield* d.tryPromise({
|
|
2472
|
-
try: () =>
|
|
2481
|
+
try: () => lr(g(t.projectRoot, "migrations")),
|
|
2473
2482
|
catch: (e) => e
|
|
2474
2483
|
});
|
|
2475
2484
|
if (n.length === 0) return {
|
|
@@ -2477,14 +2486,14 @@ BEGIN
|
|
|
2477
2486
|
skipped: []
|
|
2478
2487
|
};
|
|
2479
2488
|
let r = yield* d.tryPromise({
|
|
2480
|
-
try: () =>
|
|
2489
|
+
try: () => ur(n),
|
|
2481
2490
|
catch: (e) => e
|
|
2482
2491
|
});
|
|
2483
2492
|
if (r.length === 0) return {
|
|
2484
2493
|
applied: [],
|
|
2485
2494
|
skipped: []
|
|
2486
2495
|
};
|
|
2487
|
-
let i = new Set(yield*
|
|
2496
|
+
let i = new Set(yield* dr(e)), a = r.filter((e) => !i.has(e.migration.id)), o = r.filter((e) => i.has(e.migration.id)).map((e) => e.migration.id);
|
|
2488
2497
|
if (a.length === 0) return {
|
|
2489
2498
|
applied: [],
|
|
2490
2499
|
skipped: o
|
|
@@ -2498,7 +2507,7 @@ BEGIN
|
|
|
2498
2507
|
id: n.migration.id,
|
|
2499
2508
|
file: n.file
|
|
2500
2509
|
}));
|
|
2501
|
-
let { durationMs: r } = yield*
|
|
2510
|
+
let { durationMs: r } = yield* fr(e, n, t.env, t.appliedBy);
|
|
2502
2511
|
s.push({
|
|
2503
2512
|
id: n.migration.id,
|
|
2504
2513
|
durationMs: r
|
|
@@ -2515,18 +2524,18 @@ BEGIN
|
|
|
2515
2524
|
applied: s,
|
|
2516
2525
|
skipped: o
|
|
2517
2526
|
};
|
|
2518
|
-
}),
|
|
2527
|
+
}), mr = (e, t) => e`
|
|
2519
2528
|
SELECT ${e("id")}
|
|
2520
2529
|
FROM ${e("_voltro_migration_plans")}
|
|
2521
2530
|
WHERE ${e("id")} = ${t} AND ${e("source")} = 'file'
|
|
2522
2531
|
LIMIT 1
|
|
2523
|
-
`,
|
|
2524
|
-
if (!(yield*
|
|
2532
|
+
`, hr = (e, t) => d.gen(function* () {
|
|
2533
|
+
if (!(yield* mr(e, t.id))[0]) return yield* d.fail(/* @__PURE__ */ Error(`rollback: ${t.id} not found in _voltro_migration_plans (file source)`));
|
|
2525
2534
|
let n = yield* d.tryPromise({
|
|
2526
|
-
try: () =>
|
|
2535
|
+
try: () => lr(g(t.projectRoot, "migrations")),
|
|
2527
2536
|
catch: (e) => e
|
|
2528
2537
|
}), r = (yield* d.tryPromise({
|
|
2529
|
-
try: () =>
|
|
2538
|
+
try: () => ur(n),
|
|
2530
2539
|
catch: (e) => e
|
|
2531
2540
|
})).find((e) => e.migration.id === t.id);
|
|
2532
2541
|
if (!r) return yield* d.fail(/* @__PURE__ */ Error(`rollback: file for migration ${t.id} not found on disk under ${t.projectRoot}/migrations/`));
|
|
@@ -2559,7 +2568,7 @@ BEGIN
|
|
|
2559
2568
|
} finally {
|
|
2560
2569
|
yield* T(e).pipe(d.orDie);
|
|
2561
2570
|
}
|
|
2562
|
-
}),
|
|
2571
|
+
}), gr = (e) => d.gen(function* () {
|
|
2563
2572
|
let t = yield* p.SqlClient, n = yield* t`
|
|
2564
2573
|
SELECT id, fingerprint, appliedAt FROM _voltro_migration_plans
|
|
2565
2574
|
WHERE source = 'auto-diff'
|
|
@@ -2604,10 +2613,10 @@ BEGIN
|
|
|
2604
2613
|
snapshotFingerprint: e.fingerprint,
|
|
2605
2614
|
snapshotId: i
|
|
2606
2615
|
};
|
|
2607
|
-
}),
|
|
2616
|
+
}), _r = (e, ...t) => ({
|
|
2608
2617
|
_tag: "RawSqlFragment",
|
|
2609
2618
|
strings: [...e],
|
|
2610
2619
|
values: [...t]
|
|
2611
|
-
}),
|
|
2620
|
+
}), vr = (e) => typeof e == "object" && !!e && e._tag === "RawSqlFragment";
|
|
2612
2621
|
//#endregion
|
|
2613
|
-
export { c as FILE_MIGRATION_PATTERN, C as VOLTRO_MIGRATION_LOCK_KEY, ut as _internalCmp, w as acquireMigrationLock, Ke as applyNamespacedSchema, Yn as applyPlan, Be as applySchema, Lt as chunkTables, P as declaredSnapshot, ve as defaultClause, O as defaultJsonClause,
|
|
2622
|
+
export { c as FILE_MIGRATION_PATTERN, C as VOLTRO_MIGRATION_LOCK_KEY, ut as _internalCmp, w as acquireMigrationLock, Ke as applyNamespacedSchema, Yn as applyPlan, Be as applySchema, Lt as chunkTables, P as declaredSnapshot, ve as defaultClause, O as defaultJsonClause, sr as describeOutcome, Yt as emitDropColumnDdl, Xt as emitDropColumnDdlMysql, ze as emitFrameworkBootstrapSql, Le as emitNamespaceProvisionDdl, Re as emitNamespacedSchemaSql, j as emitSchemaSql, M as fingerprintSchema, rr as ignoreTablesFromEnv, qt as introspectSchema, a as isFileMigration, vr as isRawSqlFragment, Mt as mapPgType, o as migration, k as numericIdSql, jt as parseEnumCheck, At as planMigrations, qe as provisionTenantNamespace, T as releaseMigrationLock, sn as renderColumnMysql, rn as renderColumnPg, hr as rollbackFileBasedMigration, pr as runFileBasedMigrations, Je as runFrameworkBootstrap, Ge as runMigrate, or as runPlannedMigrations, N as shortFingerprint, _r as sql, D as sqlType, gr as squashMigrationPlans, ir as unblockLossy, ge as withMigrationLock };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Browser-safe schema DSL, query builder, and cross-dialect migration planner for Voltro — one schema, every SQL backend.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@effect/sql": "^0.51.1",
|
|
41
|
-
"@voltro/logger": "0.
|
|
41
|
+
"@voltro/logger": "0.7.0",
|
|
42
42
|
"typeid-js": "^1.2.0",
|
|
43
43
|
"ulidx": "^2.4.1"
|
|
44
44
|
},
|