@voltro/database 0.59.0 → 0.60.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 +58 -0
- package/dist/sql.js +43 -43
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,64 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.60.0] — 2026-08-31
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/plugin-sentry** — **A declared failure no longer reaches Sentry by default, and the source-map upload now injects debug ids.** Two separate defects, both found by the same deployment on its first day of real server-side events.
|
|
47
|
+
|
|
48
|
+
**1. The contract was being reported as an incident.** The rpc interceptor skipped only clean interrupts; everything else went to `captureException`. So a failure declared in a procedure's `error:` union — the thing the client gets typed and branches on — arrived as `level: error`, `handled: yes`. The first server-side issue a deployment ever received was a person clicking a team they are not a member of.
|
|
49
|
+
|
|
50
|
+
Effect separates a failure from a defect, this framework leans on that split deliberately (a store refusal was made typed so an app could branch on it; an unlookupable conflict key was deliberately left a defect, because it is a broken call rather than a condition in the data), and a descriptor carries it in `error:`. Reporting both as an incident discarded that one layer up.
|
|
51
|
+
|
|
52
|
+
`shouldCapture` now skips a cause that is failures-ONLY. A defect is reported as before, including a defect that travelled beside a failure — the rule is failures-only rather than "any failure present" precisely so one cannot hide the other. `captureFailures: true` restores the old behaviour; a predicate keeps the ones that are signal.
|
|
53
|
+
|
|
54
|
+
**The browser half moved with it**, or the option would have been half-wired: a rejected call is an rpc error on the client too, published to the client error bus and captured by the browser bridge. `initSentryBrowser` takes the same option and applies it to `rpc.*` events carrying a `_tag`. Route render failures and `reportClientError` calls are never filtered — nobody declared those.
|
|
55
|
+
|
|
56
|
+
**2. `sentry-cli sourcemaps upload` does NOT write debug ids.** `inject` is a separate subcommand; `upload` only uses ids that are already present, and falls back to matching on the artifact NAME when they are not. That fallback cannot work for a server bundle: the artifact is named from `--url-prefix` (`~/chunk- ABC.js`) while the frame carries the absolute path the node process loaded, and nothing rewrites either side.
|
|
57
|
+
|
|
58
|
+
Measured downstream: 4300 artifacts uploaded, release finalised, every frame still minified. Nothing was red — the exact shape this code's own header warns about, an upload that matched nothing looking like one that worked. The comment above the uploader asserted the injection happened, which made it a description standing where a check belonged.
|
|
59
|
+
|
|
60
|
+
`inject` now runs first, over the same directories, and `sourcemapDebugIds.test.ts` drives the real binary to assert an id lands in both the JS and the map. `--url-prefix` stays as the fallback for the browser bundle, whose frames really are URLs.
|
|
61
|
+
|
|
62
|
+
The boot line names the new setting (`sentry active … captureFailures=false`), because a default the framework picks for you is one nobody finds again.
|
|
63
|
+
|
|
64
|
+
**`voltro update` carries you across this** — codemod `0.60.0/01_declared_failures_are_not_incidents`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.60.0).
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- **@voltro/cli, @voltro/plugin-sentry** — **The web server process now initialises Sentry and reports its own errors.**
|
|
69
|
+
|
|
70
|
+
A framework app runs two server processes. `voltro serve` is the api, where `sentryPlugin()` initialises the SDK through the plugin lifecycle. `voltro start` is the web server — SSR, loaders, ISR, the revalidation legs — and it has no plugin lifecycle, so it had none.
|
|
71
|
+
|
|
72
|
+
That was not a cosmetic difference in boot output. A web pod legitimately logs less than an api pod, because it has no store, no rpc, no scheduler, no workflows and no plugins. What it also had was **no error reporting**: an SSR shell throw is caught, logged and answered with a 500, so no browser ever renders it and the client-side ErrorBoundary bridge cannot see it either. Both ends of the integration worked and the middle was dark — while the docs' "React render error ✅ auto" row, true of the client path, read as covering all of them.
|
|
73
|
+
|
|
74
|
+
Set `SENTRY_DSN` on the web deployment and the boot says `sentry active` with the same message and the same field names as the api half, from the same function — `initSentryServer` in `@voltro/plugin-sentry/server`, which the api plugin now calls too. One init, because a copy of `skipOpenTelemetrySetup`, the traces default, the overrides-first spread and the degrade-on-missing-SDK path would have drifted the moment either half gained a case.
|
|
75
|
+
|
|
76
|
+
Three deliberate asymmetries, each because the two processes are not the same thing:
|
|
77
|
+
|
|
78
|
+
- **`traces: false` on the web side.** The api contributes a span processor to the framework's tracer; the web server has no tracer at all, so tracing on would put `traces: true` in a boot line while nothing produces a span. - **No DSN is SILENT here.** On the api side `sentryPlugin()` is a declaration and an inert one contradicts it. There is no declaration here. - **`@voltro/plugin-sentry` must be installed in the WEB app.** With a DSN set and the package missing, the boot names the command rather than failing — monitoring must never be what stops a deploy.
|
|
79
|
+
|
|
80
|
+
Measured against a real production `voltro start`, both branches. Note that production `voltro start` loads the app's precompiled start bundle, so a web deployment picks this up when that bundle is rebuilt — the framework version alone is not enough.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **@voltro/workflow, @voltro/runtime, @voltro/database, @voltro/cli, @voltro/plugin-sentry** — **Six raw writes removed from production log streams — and the guard that was supposed to catch them rewritten, because it was green for two independent reasons.**
|
|
85
|
+
|
|
86
|
+
A pod tail showed `[voltro:workflow] shard-lock coordination: row-based (dialect=mariadb, mode=row)` sitting between JSON records. `@voltro/logger` is what makes a line JSON in a pod and pretty on a TTY; a `process.stderr.write` bypasses that decision at exactly the place nobody looks, because a dev terminal renders both the same.
|
|
87
|
+
|
|
88
|
+
Fixed at the source: the workflow cluster layer (2), `rpcServer`'s computed-cache warning, the subscription outbox's and the RYW store's `warn`/`onError` defaults — those two are not fallbacks, the callers pass nothing, so the default IS the production path — and the migration file discovery's skip notice, which lands in the migrate job's stream.
|
|
89
|
+
|
|
90
|
+
**The guard is the part worth reading.** `prodLogDiscipline.test.ts` existed for this exact class and reported clean, for two reasons that had to be fixed separately:
|
|
91
|
+
|
|
92
|
+
- Its file set was a hand-written list of five. A guard that opts files IN says nothing about any file added after it was written. - Its matcher was LINE-LOCAL, so `process.stderr.write(` on one line and the `` `[tag] `` on the next never matched — 4 of the 9 call sites in the repo are written that way, including one in a file that WAS on the list. The guard had been pointed straight at an offender and called it clean.
|
|
93
|
+
|
|
94
|
+
It is opt-OUT now: every server-side package's source is scanned, exceptions carry a reason, and a second test fails if an exception's call site disappears — an allowlist entry for code that is gone reads as a rule with a hole in it.
|
|
95
|
+
|
|
96
|
+
Verified against a real `voltro serve` under `NODE_ENV=production`: 35 records, zero framework lines that are not JSON.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
42
100
|
## [0.59.0] — 2026-08-30
|
|
43
101
|
|
|
44
102
|
### ⚠ BREAKING
|
package/dist/sql.js
CHANGED
|
@@ -3136,7 +3136,7 @@ BEGIN
|
|
|
3136
3136
|
return o > 0 && c.push(`${o} 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."), c.push("", "See `voltro db plan` for the full machine-readable diff."), s + "\n" + c.join("\n");
|
|
3137
3137
|
}
|
|
3138
3138
|
}
|
|
3139
|
-
}, na = /* @__PURE__ */ new Set([
|
|
3139
|
+
}, na = T({ scope: "voltro:migrate" }), ra = /* @__PURE__ */ new Set([
|
|
3140
3140
|
"node_modules",
|
|
3141
3141
|
"dist",
|
|
3142
3142
|
"build",
|
|
@@ -3145,11 +3145,11 @@ BEGIN
|
|
|
3145
3145
|
".next",
|
|
3146
3146
|
".git",
|
|
3147
3147
|
".framework"
|
|
3148
|
-
]),
|
|
3148
|
+
]), ia = async (e) => {
|
|
3149
3149
|
let t = [], n = async (e) => {
|
|
3150
3150
|
let r = await ee.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
3151
3151
|
for (let i of r) {
|
|
3152
|
-
if (
|
|
3152
|
+
if (ra.has(i.name)) continue;
|
|
3153
3153
|
let r = te(e, i.name);
|
|
3154
3154
|
i.isDirectory() ? await n(r) : i.isFile() && m.test(i.name) && t.push(r);
|
|
3155
3155
|
}
|
|
@@ -3158,12 +3158,12 @@ BEGIN
|
|
|
3158
3158
|
let n = e.split("/").pop() ?? e, r = t.split("/").pop() ?? t;
|
|
3159
3159
|
return n.localeCompare(r);
|
|
3160
3160
|
});
|
|
3161
|
-
},
|
|
3161
|
+
}, aa = async (e) => {
|
|
3162
3162
|
let t = [];
|
|
3163
3163
|
for (let n of e) {
|
|
3164
3164
|
let e = (await import(ne(n).href)).default;
|
|
3165
3165
|
if (!g(e)) {
|
|
3166
|
-
|
|
3166
|
+
na.warn("migration file has no default-exported `migration(...)` — skipped", { file: n });
|
|
3167
3167
|
continue;
|
|
3168
3168
|
}
|
|
3169
3169
|
t.push({
|
|
@@ -3172,11 +3172,11 @@ BEGIN
|
|
|
3172
3172
|
});
|
|
3173
3173
|
}
|
|
3174
3174
|
return t;
|
|
3175
|
-
},
|
|
3175
|
+
}, oa = (e) => e`
|
|
3176
3176
|
SELECT ${e("id")}
|
|
3177
3177
|
FROM ${e("_voltro_migration_plans")}
|
|
3178
3178
|
WHERE ${e("source")} = 'file'
|
|
3179
|
-
`.pipe(S.map((e) => e.map((e) => e.id)), S.catchAll(() => S.succeed([]))),
|
|
3179
|
+
`.pipe(S.map((e) => e.map((e) => e.id)), S.catchAll(() => S.succeed([]))), sa = class extends Error {
|
|
3180
3180
|
migrationId;
|
|
3181
3181
|
file;
|
|
3182
3182
|
cause;
|
|
@@ -3184,7 +3184,7 @@ BEGIN
|
|
|
3184
3184
|
constructor(e, t, n) {
|
|
3185
3185
|
super(`migration ${e}: up() SUCCEEDED but the ledger row could not be written.\n The change IS in the database. It is NOT recorded, so the next run would apply it AGAIN.\n Do not re-run ${t} until this is resolved.\n Cause: ${n instanceof Error ? n.message : String(n)}`), this.migrationId = e, this.file = t, this.cause = n, this.name = "FileMigrationLedgerError";
|
|
3186
3186
|
}
|
|
3187
|
-
},
|
|
3187
|
+
}, ca = (e, t, n, r) => S.gen(function* () {
|
|
3188
3188
|
let i = Date.now(), a = (/* @__PURE__ */ new Date()).toISOString(), o = {
|
|
3189
3189
|
sql: e,
|
|
3190
3190
|
log: {
|
|
@@ -3218,10 +3218,10 @@ BEGIN
|
|
|
3218
3218
|
}])},
|
|
3219
3219
|
${r}, ${n}, ${"file"},
|
|
3220
3220
|
${c}, ${l}, ${t.migration.description})
|
|
3221
|
-
`.pipe(S.mapError((e) => new
|
|
3222
|
-
}),
|
|
3221
|
+
`.pipe(S.mapError((e) => new sa(t.migration.id, t.file, e))), { durationMs: c };
|
|
3222
|
+
}), la = (e, t) => S.gen(function* () {
|
|
3223
3223
|
let n = yield* S.tryPromise({
|
|
3224
|
-
try: () =>
|
|
3224
|
+
try: () => ia(te(t, "migrations")),
|
|
3225
3225
|
catch: (e) => e
|
|
3226
3226
|
});
|
|
3227
3227
|
if (n.length === 0) return {
|
|
@@ -3229,20 +3229,20 @@ BEGIN
|
|
|
3229
3229
|
skipped: []
|
|
3230
3230
|
};
|
|
3231
3231
|
let r = yield* S.tryPromise({
|
|
3232
|
-
try: () =>
|
|
3232
|
+
try: () => aa(n),
|
|
3233
3233
|
catch: (e) => e
|
|
3234
3234
|
});
|
|
3235
3235
|
if (r.length === 0) return {
|
|
3236
3236
|
pending: [],
|
|
3237
3237
|
skipped: []
|
|
3238
3238
|
};
|
|
3239
|
-
let i = new Set(yield*
|
|
3239
|
+
let i = new Set(yield* oa(e));
|
|
3240
3240
|
return {
|
|
3241
3241
|
pending: r.filter((e) => !i.has(e.migration.id)),
|
|
3242
3242
|
skipped: r.filter((e) => i.has(e.migration.id)).map((e) => e.migration.id)
|
|
3243
3243
|
};
|
|
3244
|
-
}),
|
|
3245
|
-
let { pending: n, skipped: r } = yield*
|
|
3244
|
+
}), ua = (e, t) => la(e, t).pipe(S.map((e) => e.pending.map((e) => e.migration.id))), da = (e, t) => S.gen(function* () {
|
|
3245
|
+
let { pending: n, skipped: r } = yield* la(e, t.projectRoot);
|
|
3246
3246
|
if (n.length === 0) return {
|
|
3247
3247
|
applied: [],
|
|
3248
3248
|
skipped: r
|
|
@@ -3256,7 +3256,7 @@ BEGIN
|
|
|
3256
3256
|
id: r.migration.id,
|
|
3257
3257
|
file: r.file
|
|
3258
3258
|
}));
|
|
3259
|
-
let { durationMs: n } = yield*
|
|
3259
|
+
let { durationMs: n } = yield* ca(e, r, t.env, t.appliedBy);
|
|
3260
3260
|
i.push({
|
|
3261
3261
|
id: r.migration.id,
|
|
3262
3262
|
durationMs: n
|
|
@@ -3273,18 +3273,18 @@ BEGIN
|
|
|
3273
3273
|
applied: i,
|
|
3274
3274
|
skipped: r
|
|
3275
3275
|
};
|
|
3276
|
-
}),
|
|
3276
|
+
}), fa = (e, t) => e`
|
|
3277
3277
|
SELECT ${e("id")}
|
|
3278
3278
|
FROM ${e("_voltro_migration_plans")}
|
|
3279
3279
|
WHERE ${e("id")} = ${t} AND ${e("source")} = 'file'
|
|
3280
3280
|
LIMIT 1
|
|
3281
|
-
`,
|
|
3282
|
-
if (!(yield*
|
|
3281
|
+
`, pa = (e, t) => S.gen(function* () {
|
|
3282
|
+
if (!(yield* fa(e, t.id))[0]) return yield* S.fail(/* @__PURE__ */ Error(`rollback: ${t.id} not found in _voltro_migration_plans (file source)`));
|
|
3283
3283
|
let n = yield* S.tryPromise({
|
|
3284
|
-
try: () =>
|
|
3284
|
+
try: () => ia(te(t.projectRoot, "migrations")),
|
|
3285
3285
|
catch: (e) => e
|
|
3286
3286
|
}), r = (yield* S.tryPromise({
|
|
3287
|
-
try: () =>
|
|
3287
|
+
try: () => aa(n),
|
|
3288
3288
|
catch: (e) => e
|
|
3289
3289
|
})).find((e) => e.migration.id === t.id);
|
|
3290
3290
|
if (!r) return yield* S.fail(/* @__PURE__ */ Error(`rollback: file for migration ${t.id} not found on disk under ${t.projectRoot}/migrations/`));
|
|
@@ -3317,7 +3317,7 @@ BEGIN
|
|
|
3317
3317
|
} finally {
|
|
3318
3318
|
yield* O(e, { schema: t.lockSchema }).pipe(S.orDie);
|
|
3319
3319
|
}
|
|
3320
|
-
}),
|
|
3320
|
+
}), ma = (e) => S.gen(function* () {
|
|
3321
3321
|
let t = yield* E.SqlClient, n = yield* t`
|
|
3322
3322
|
SELECT id, fingerprint, appliedAt FROM _voltro_migration_plans
|
|
3323
3323
|
WHERE source = 'auto-diff'
|
|
@@ -3362,7 +3362,7 @@ BEGIN
|
|
|
3362
3362
|
snapshotFingerprint: e.fingerprint,
|
|
3363
3363
|
snapshotId: i
|
|
3364
3364
|
};
|
|
3365
|
-
}),
|
|
3365
|
+
}), ha = { safe: !0 }, ga = (e) => {
|
|
3366
3366
|
switch (e.kind) {
|
|
3367
3367
|
case "drop-column": return {
|
|
3368
3368
|
safe: !1,
|
|
@@ -3389,7 +3389,7 @@ BEGIN
|
|
|
3389
3389
|
reason: `old instances read/write "${e.table}"."${e.column}" as ${e.from}; the new ${e.to} type can reject their writes or fail to decode their reads`,
|
|
3390
3390
|
remedy: "add a new column of the target type + backfill + dual-write, cut code over, then drop the old column in a LATER deploy"
|
|
3391
3391
|
};
|
|
3392
|
-
case "alter-column-nullability": return e.toNullable ?
|
|
3392
|
+
case "alter-column-nullability": return e.toNullable ? ha : {
|
|
3393
3393
|
safe: !1,
|
|
3394
3394
|
reason: `old instances may INSERT "${e.table}" without (or with NULL in) "${e.column}", which the new NOT NULL rejects`,
|
|
3395
3395
|
remedy: `make code always write "${e.column}" and deploy that first; add NOT NULL in a LATER deploy (with a default to cover the gap)`
|
|
@@ -3419,12 +3419,12 @@ BEGIN
|
|
|
3419
3419
|
case "drop-unique":
|
|
3420
3420
|
case "drop-unique-composite":
|
|
3421
3421
|
case "drop-foreign-key":
|
|
3422
|
-
case "drop-check": return
|
|
3422
|
+
case "drop-check": return ha;
|
|
3423
3423
|
}
|
|
3424
|
-
},
|
|
3424
|
+
}, _a = (e) => {
|
|
3425
3425
|
let t = [];
|
|
3426
3426
|
for (let n of e) {
|
|
3427
|
-
let e =
|
|
3427
|
+
let e = ga(n.op);
|
|
3428
3428
|
e.safe || t.push({
|
|
3429
3429
|
op: n,
|
|
3430
3430
|
reason: e.reason,
|
|
@@ -3432,7 +3432,7 @@ BEGIN
|
|
|
3432
3432
|
});
|
|
3433
3433
|
}
|
|
3434
3434
|
return t;
|
|
3435
|
-
},
|
|
3435
|
+
}, va = (e) => {
|
|
3436
3436
|
if (!e.enabled || e.unsafeCount === 0) return {
|
|
3437
3437
|
refuse: !1,
|
|
3438
3438
|
warnForced: !1,
|
|
@@ -3448,18 +3448,18 @@ BEGIN
|
|
|
3448
3448
|
warnForced: !1,
|
|
3449
3449
|
message: `${t}. Old instances would break against the new schema mid-rollout. Split into expand → deploy → contract (see the ⚠ list above), deploy with no overlap (maintenance window / scale-to-zero), or pass --force to override.`
|
|
3450
3450
|
};
|
|
3451
|
-
},
|
|
3451
|
+
}, ya = (e) => {
|
|
3452
3452
|
let t = e.table;
|
|
3453
3453
|
if (typeof t == "string") return t;
|
|
3454
3454
|
let n = e.from;
|
|
3455
3455
|
return typeof n == "string" ? n : void 0;
|
|
3456
|
-
},
|
|
3456
|
+
}, ba = (e) => ({
|
|
3457
3457
|
kind: e.op.kind,
|
|
3458
|
-
table:
|
|
3458
|
+
table: ya(e.op),
|
|
3459
3459
|
classification: e.classification,
|
|
3460
3460
|
reason: e.reason,
|
|
3461
3461
|
blockedFix: e.blocked?.fix
|
|
3462
|
-
}),
|
|
3462
|
+
}), xa = (e) => e.operations.map(ba), Sa = async (e) => {
|
|
3463
3463
|
let t = e.log ?? (() => {}), n = {
|
|
3464
3464
|
branchId: e.branchId,
|
|
3465
3465
|
mechanism: e.mechanism,
|
|
@@ -3478,10 +3478,10 @@ BEGIN
|
|
|
3478
3478
|
applied: !1,
|
|
3479
3479
|
converged: !1,
|
|
3480
3480
|
residual: r,
|
|
3481
|
-
infidelity:
|
|
3481
|
+
infidelity: xa(o)
|
|
3482
3482
|
};
|
|
3483
3483
|
else {
|
|
3484
|
-
let i = await e.plan(), o =
|
|
3484
|
+
let i = await e.plan(), o = xa(i), s = o.filter((e) => e.classification === "lossy"), c = Zi(i), l = c.operations.filter((e) => e.blocked !== void 0).map(ba);
|
|
3485
3485
|
if (l.length > 0) a = {
|
|
3486
3486
|
...n,
|
|
3487
3487
|
outcome: "blocked",
|
|
@@ -3506,7 +3506,7 @@ BEGIN
|
|
|
3506
3506
|
};
|
|
3507
3507
|
else {
|
|
3508
3508
|
await e.apply(c), t(`branch ${e.branchId}: applied ${o.length} operation(s)`);
|
|
3509
|
-
let i =
|
|
3509
|
+
let i = xa(await e.replan());
|
|
3510
3510
|
a = {
|
|
3511
3511
|
...n,
|
|
3512
3512
|
outcome: i.length > 0 ? "diverged" : s.length > 0 ? "lossy" : "clean",
|
|
@@ -3544,7 +3544,7 @@ BEGIN
|
|
|
3544
3544
|
...a,
|
|
3545
3545
|
tornDown: o
|
|
3546
3546
|
};
|
|
3547
|
-
},
|
|
3547
|
+
}, Ca = (e) => {
|
|
3548
3548
|
switch (e.outcome) {
|
|
3549
3549
|
case "clean": return 0;
|
|
3550
3550
|
case "lossy": return 2;
|
|
@@ -3553,22 +3553,22 @@ BEGIN
|
|
|
3553
3553
|
case "infidelity": return 1;
|
|
3554
3554
|
case "failed": return 1;
|
|
3555
3555
|
}
|
|
3556
|
-
},
|
|
3556
|
+
}, wa = (e) => ` ${e.classification === "lossy" ? "✗" : "•"} ${e.kind}${e.table ? ` ${e.table}` : ""} [${e.classification}]${e.reason ? ` — ${e.reason}` : ""}${e.blockedFix ? `\n ! ${e.blockedFix}` : ""}`, Ta = (e) => {
|
|
3557
3557
|
let t = [];
|
|
3558
3558
|
if (t.push(`branch rehearsal · ${e.branchId} · mechanism ${e.mechanism}`), t.push(` branched ${e.branchedTables} table(s), replayed ${e.replayedForeignKeys} foreign key(s)`), e.outcome === "failed") t.push(` FAILED: ${e.error ?? "unknown error"}`);
|
|
3559
3559
|
else if (e.outcome === "infidelity") {
|
|
3560
3560
|
t.push(" BRANCH IS NOT A FAITHFUL COPY of the parent — the rehearsal proves nothing about your migration."), t.push(" The parent's own schema still differs from the branch by:");
|
|
3561
|
-
for (let n of e.infidelity) t.push(
|
|
3561
|
+
for (let n of e.infidelity) t.push(wa(n));
|
|
3562
3562
|
} else {
|
|
3563
3563
|
t.push(` plan: ${e.operations.length} operation(s), ${e.lossy.length} lossy, ${e.blocked.length} refused`);
|
|
3564
|
-
for (let n of e.operations) t.push(
|
|
3565
|
-
if (e.lossy.length > 0 && (t.push(""), t.push(` ⚠ ${e.lossy.length} operation(s) DESTROY DATA. They were executed on the branch (it is`), t.push(" disposable) so they are rehearsed, but production refuses them until you set"), t.push(" VOLTRO_DESTRUCTIVE_OK — naming the tables, not `1`.")), e.blocked.length > 0 && (t.push(""), t.push(` ${e.blocked.length} operation(s) the planner will not auto-apply anywhere — the plan was NOT executed.`)), e.applied && (t.push(""), t.push(e.converged ? " ✓ applied on the branch, and the re-plan is EMPTY (the migration converges)." : ` ✗ applied, but the re-plan STILL proposes ${e.residual.length} operation(s) — this migration does not converge:`), !e.converged)) for (let n of e.residual) t.push(
|
|
3564
|
+
for (let n of e.operations) t.push(wa(n));
|
|
3565
|
+
if (e.lossy.length > 0 && (t.push(""), t.push(` ⚠ ${e.lossy.length} operation(s) DESTROY DATA. They were executed on the branch (it is`), t.push(" disposable) so they are rehearsed, but production refuses them until you set"), t.push(" VOLTRO_DESTRUCTIVE_OK — naming the tables, not `1`.")), e.blocked.length > 0 && (t.push(""), t.push(` ${e.blocked.length} operation(s) the planner will not auto-apply anywhere — the plan was NOT executed.`)), e.applied && (t.push(""), t.push(e.converged ? " ✓ applied on the branch, and the re-plan is EMPTY (the migration converges)." : ` ✗ applied, but the re-plan STILL proposes ${e.residual.length} operation(s) — this migration does not converge:`), !e.converged)) for (let n of e.residual) t.push(wa(n));
|
|
3566
3566
|
}
|
|
3567
3567
|
return t.push(e.tornDown ? ` branch ${e.branchId} torn down.` : ` branch ${e.branchId} KEPT — drop it when you are done.`), t.join("\n");
|
|
3568
|
-
},
|
|
3568
|
+
}, Ea = (e, ...t) => ({
|
|
3569
3569
|
_tag: "RawSqlFragment",
|
|
3570
3570
|
strings: [...e],
|
|
3571
3571
|
values: [...t]
|
|
3572
|
-
}),
|
|
3572
|
+
}), Da = (e) => typeof e == "object" && !!e && e._tag === "RawSqlFragment";
|
|
3573
3573
|
//#endregion
|
|
3574
|
-
export { A as DEFAULT_CDC_CHANNEL, m as FILE_MIGRATION_PATTERN,
|
|
3574
|
+
export { A as DEFAULT_CDC_CHANNEL, m as FILE_MIGRATION_PATTERN, sa as FileMigrationLedgerError, qe as REACTIVE_TRIGGER_PREFIX, de as VOLTRO_MIGRATION_LOCK_KEY, je as acquireMigrationLock, W as appliedAtValue, It as applyNamespacedSchema, Bi as applyPlan, Et as applySchema, va as assessRollingDeployGate, Ca as branchRehearsalExitCode, Vn as chunkTables, ga as classifyRollingDeploySafety, I as declaredSnapshot, $e as defaultArrayClause, Qe as defaultClause, N as defaultJsonClause, ta as describeOutcome, Yi as destructiveScope, Xt as detectReactiveTriggerDrift, ni as emitAddIndexMysql, Lr as emitCreateTableMysql, _r as emitDropColumnDdl, vr as emitDropColumnDdlMysql, Tt as emitFrameworkBootstrapSql, Ct as emitNamespaceProvisionDdl, wt as emitNamespacedSchemaSql, St as emitSchemaSql, er as engineFromVersion, Wt as fingerprintSchema, _e as fnv1a64, Ta as formatBranchRehearsal, Zt as formatReactiveTriggerDrift, Ji as ignoreTablesFromEnv, $n as introspectSchema, ar as isAlreadySatisfied, g as isFileMigration, Da as isRawSqlFragment, Ot as isReRunSafeDdl, Fn as mapPgType, o as migration, ve as migrationLockKeyForSchema, ye as migrationLockNameForSchema, Qr as mysqlIndexPrefixFor, Ye as notifyFunctionName, P as numericIdSql, Pn as parseEnumCheck, ua as pendingFileMigrationIds, Nn as planMigrations, Lt as provisionTenantNamespace, Je as reactiveTriggerName, pt as reactiveTriggerRepairSql, Ui as recordUpToDate, Sa as rehearseMigrationOnBranch, ba as rehearsedOperation, O as releaseMigrationLock, Fr as renderColumnMysql, Ar as renderColumnPg, pe as resolveMigrationLockSchema, U as resolveMysqlEngine, pa as rollbackFileBasedMigration, _a as rollingDeployUnsafeOps, da as runFileBasedMigrations, Rt as runFrameworkBootstrap, Ft as runMigrate, ea as runPlannedMigrations, Gt as shortFingerprint, Kt as snapshotColumn, Ea as sql, M as sqlType, ma as squashMigrationPlans, Zi as unblockLossy, Me as withMigrationLock };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@effect/sql": "^0.52.0",
|
|
47
|
-
"@voltro/logger": "0.
|
|
47
|
+
"@voltro/logger": "0.60.0",
|
|
48
48
|
"typeid-js": "^1.2.0",
|
|
49
49
|
"ulidx": "^2.4.1"
|
|
50
50
|
},
|