@voltro/database 0.85.0 → 0.86.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 +666 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/actorSnapshot.js +24 -0
- package/dist/arrayCodec.js +32 -0
- package/dist/branchExecute.js +92 -0
- package/dist/branching.js +106 -0
- package/dist/bulkInsertLimits.js +25 -0
- package/dist/cdcOffsetsTables.js +19 -0
- package/dist/changeEvent.js +41 -0
- package/dist/changeIdentity.js +65 -0
- package/dist/changeListenerCeiling.js +6 -0
- package/dist/columnReference.js +17 -0
- package/dist/columns.js +326 -0
- package/dist/committedChanges.js +12 -0
- package/dist/connectionIdentity.js +36 -0
- package/dist/coreTables.js +16 -0
- package/dist/coreTablesRegistry.js +10 -0
- package/dist/dataTransfers.js +22 -0
- package/dist/dbCause.js +222 -0
- package/dist/dbMetrics.js +65 -0
- package/dist/decimalComparison.js +27 -0
- package/dist/derivedRowId.js +5 -0
- package/dist/dialect.js +13 -0
- package/dist/enumRenames.js +14 -0
- package/dist/expiresMixin.js +10 -0
- package/dist/fieldEncryption.js +98 -0
- package/dist/frameworkTables.js +130 -0
- package/dist/ginIndex.js +10 -0
- package/dist/hybridSearch.js +32 -0
- package/dist/idGenerator.js +57 -0
- package/dist/identifierLimits.js +32 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.js +81 -2183
- package/dist/indexAudit.js +61 -0
- package/dist/indexStorage.js +58 -0
- package/dist/joinCompiler.js +160 -0
- package/dist/jsonEagerCompiler.js +436 -0
- package/dist/jsonPatch.js +78 -0
- package/dist/jsonPatchAssignments.js +13 -0
- package/dist/jsonPath.js +31 -0
- package/dist/localFirstMixin.js +12 -0
- package/dist/migrate.js +624 -0
- package/dist/migration.js +18 -0
- package/dist/migrations/appliedAtValue.js +8 -0
- package/dist/migrations/applier.js +1085 -0
- package/dist/migrations/boot.js +196 -0
- package/dist/migrations/branchRehearsal.js +122 -0
- package/dist/migrations/columnEquivalence.js +85 -0
- package/dist/migrations/ddlError.js +63 -0
- package/dist/migrations/declaredSchema.js +162 -0
- package/dist/migrations/deployLedger.js +19 -0
- package/dist/migrations/fileBased.js +57 -0
- package/dist/migrations/fileContext.js +482 -0
- package/dist/migrations/fileDiscovery.js +318 -0
- package/dist/migrations/fileSkipProbes.js +117 -0
- package/dist/migrations/fingerprint.js +46 -0
- package/dist/migrations/fkCycles.js +13 -0
- package/dist/migrations/frameworkLiveTables.js +25 -0
- package/dist/migrations/idempotentDdl.js +37 -0
- package/dist/migrations/indexPredicate.js +50 -0
- package/dist/migrations/introspect.js +799 -0
- package/dist/migrations/ledgerRows.js +4 -0
- package/dist/migrations/lock.js +91 -0
- package/dist/migrations/migrationTableExists.js +9 -0
- package/dist/migrations/mssqlColumnDependencies.js +76 -0
- package/dist/migrations/mysqlEngine.js +11 -0
- package/dist/migrations/namespaceCharset.js +51 -0
- package/dist/migrations/planner.js +775 -0
- package/dist/migrations/postgresEnumCheck.js +19 -0
- package/dist/migrations/reactiveTriggerDrift.js +58 -0
- package/dist/migrations/reactiveTriggerName.js +7 -0
- package/dist/migrations/renameMssqlCheck.js +21 -0
- package/dist/migrations/restoreSnapshots.js +206 -0
- package/dist/migrations/resumeLedger.js +86 -0
- package/dist/migrations/rollingDeploy.js +106 -0
- package/dist/migrations/schemaOwner.js +46 -0
- package/dist/migrations/schemaOwnerTable.js +4 -0
- package/dist/migrations/softDropSnapshot.js +20 -0
- package/dist/migrations/sqlLimit.js +8 -0
- package/dist/migrations/sqliteConstraintIdentity.js +97 -0
- package/dist/migrations/sqliteDecimalBoot.js +22 -0
- package/dist/migrations/sqliteDecimalPublication.js +257 -0
- package/dist/migrations/sqliteDecimalReview.js +30 -0
- package/dist/migrations/sqliteDecimalReviewStore.js +188 -0
- package/dist/migrations/sqliteDecimalReviewWitness.js +38 -0
- package/dist/migrations/sqliteDecimalSource.js +69 -0
- package/dist/migrations/sqliteDecimalStage.js +135 -0
- package/dist/migrations/sqliteForeignKeys.js +38 -0
- package/dist/migrations/sqliteMigrationBatch.js +37 -0
- package/dist/migrations/sqliteRetainedSource.js +106 -0
- package/dist/migrations/sqliteRowIdentity.js +63 -0
- package/dist/migrations/sqliteSchemaEpoch.js +12 -0
- package/dist/migrations/sqliteSnapshotGuard.js +10 -0
- package/dist/migrations/sqliteTableContents.js +34 -0
- package/dist/migrations/sqliteTableCopy.js +25 -0
- package/dist/migrations/sqliteTableSchema.js +19 -0
- package/dist/migrations/sqliteWitnessProtection.js +8 -0
- package/dist/migrations/squash.js +81 -0
- package/dist/migrations/storeCharset.js +66 -0
- package/dist/migrations/transactionCause.js +10 -0
- package/dist/migrations/undeclaredCharset.js +40 -0
- package/dist/migrations/uniqueActiveLowering.js +39 -0
- package/dist/mixins.js +22 -0
- package/dist/mssqlDecimalDistinct.js +51 -0
- package/dist/mssqlJsonValueCheck.js +42 -0
- package/dist/mssqlTextStorage.js +9 -0
- package/dist/namespace.js +21 -0
- package/dist/{wire-CG2j7J3f.js → optimisticVersion.js} +2 -2
- package/dist/pendingAttribution.js +79 -0
- package/dist/pluginRef.js +17 -0
- package/dist/predicate.js +266 -0
- package/dist/predicateCoercion.js +13 -0
- package/dist/projectedColumn.js +37 -0
- package/dist/queryBuilder.js +382 -0
- package/dist/rawSql.js +8 -0
- package/dist/relations.js +69 -0
- package/dist/relationsRegistry.js +38 -0
- package/dist/replaceInProgress.js +53 -0
- package/dist/requiredColumns.js +26 -0
- package/dist/residency.js +52 -0
- package/dist/retention.js +62 -0
- package/dist/rowDecoder.js +123 -0
- package/dist/rowKey.js +8 -0
- package/dist/rowSchema.js +31 -0
- package/dist/scopeKey.js +8 -0
- package/dist/seed.js +33 -0
- package/dist/snowflake.js +26 -0
- package/dist/sql.d.ts +27 -6
- package/dist/sql.js +41 -6905
- package/dist/sqlCompiler.js +343 -0
- package/dist/sqlError.js +8 -0
- package/dist/sqlPoolCapacity.js +12 -0
- package/dist/sqliteDecimal.js +22 -0
- package/dist/sqliteDecimalAggregateSql.js +61 -0
- package/dist/sqliteDecimalColumnSql.js +108 -0
- package/dist/sqliteDecimalConversion.js +15 -0
- package/dist/sqliteDecimalCte.js +154 -0
- package/dist/sqliteDecimalEncodeSql.js +32 -0
- package/dist/sqliteDecimalOutputs.js +74 -0
- package/dist/sqliteDecimalPredicate.js +66 -0
- package/dist/sqliteDecimalQuery.js +183 -0
- package/dist/sqliteDecimalQueryPlan.js +37 -0
- package/dist/sqliteDecimalResultSql.js +44 -0
- package/dist/sqliteDecimalRowQuery.js +63 -0
- package/dist/sqliteDecimalSetQuery.js +50 -0
- package/dist/sqliteDecimalSource.js +63 -0
- package/dist/sqliteDecimalStorage.js +29 -0
- package/dist/sqliteDecimalSubquery.js +19 -0
- package/dist/sqliteDecimalWindowQuery.js +196 -0
- package/dist/sqliteValueCoercion.js +4 -0
- package/dist/stampId.js +21 -0
- package/dist/storeErrors.js +9 -0
- package/dist/storeRefusal.js +20 -0
- package/dist/stream.js +36 -0
- package/dist/subjectGraph.js +175 -0
- package/dist/table.js +394 -0
- package/dist/tableRegistry.js +16 -0
- package/dist/tenantSelection.js +27 -0
- package/dist/transactionOutcome.js +97 -0
- package/dist/transientContention.js +36 -0
- package/dist/typedStore.js +16 -0
- package/dist/upsertUpdate.js +20 -0
- package/dist/vectorEmbedding.js +31 -0
- package/dist/view.js +39 -0
- package/dist/wire.js +6 -2
- package/dist/writeAttribution.js +16 -0
- package/dist/writeRecorder.js +37 -0
- package/package.json +3 -3
- package/dist/replaceInProgress-DC88Pdiy.js +0 -3735
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { agedSoftDropSnapshots as e, describeAgedSoftDropSnapshots as t, plannableLiveTables as n } from "./frameworkLiveTables.js";
|
|
2
|
+
import { dialectOf as r } from "./resumeLedger.js";
|
|
3
|
+
import { ensureFullTextIndexes as i } from "../migrate.js";
|
|
4
|
+
import { fingerprintSchema as a, shortFingerprint as o } from "./fingerprint.js";
|
|
5
|
+
import { declaredSnapshot as s } from "./declaredSchema.js";
|
|
6
|
+
import { detectReactiveTriggerDrift as c, formatReactiveTriggerDrift as l } from "./reactiveTriggerDrift.js";
|
|
7
|
+
import { planMigrations as u } from "./planner.js";
|
|
8
|
+
import { introspectSchema as d } from "./introspect.js";
|
|
9
|
+
import { limitClause as f } from "./sqlLimit.js";
|
|
10
|
+
import { appliedAtValue as p } from "./appliedAtValue.js";
|
|
11
|
+
import { applyPlan as m, hasInterruptedMigration as h } from "./applier.js";
|
|
12
|
+
import { verifySqliteDecimalStorage as g } from "./sqliteDecimalBoot.js";
|
|
13
|
+
import { describesSchema as _ } from "./ledgerRows.js";
|
|
14
|
+
import { convergeUndeclaredCharset as v, describeUndeclaredCharsetWork as y, isUndeclaredCharsetWorkEmpty as b, readUndeclaredCharsetColumns as x, undeclaredCharsetWork as S } from "./undeclaredCharset.js";
|
|
15
|
+
import { Effect as C } from "effect";
|
|
16
|
+
//#region src/migrations/boot.ts
|
|
17
|
+
var w = (e) => e.operations.filter((e) => e.blocked).map((e) => ` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}: ${e.reason ?? "<no reason>"}\n fix: ${e.blocked?.fix ?? "<no fix>"}`).join("\n"), T = (e) => e`
|
|
18
|
+
SELECT fingerprint
|
|
19
|
+
FROM _voltro_migration_plans
|
|
20
|
+
WHERE ${_(e)}
|
|
21
|
+
ORDER BY ${e("appliedAt")} DESC
|
|
22
|
+
${f(e, 1)}
|
|
23
|
+
`.pipe(C.map((e) => e.length > 0 ? e[0].fingerprint : void 0), C.catch((t) => e`SELECT COUNT(*) AS n FROM _voltro_migration_plans`.pipe(C.matchEffect({
|
|
24
|
+
onFailure: () => C.succeed(void 0),
|
|
25
|
+
onSuccess: () => C.die(/* @__PURE__ */ Error(`reading the newest applied plan from _voltro_migration_plans failed while the table exists — this is a query defect, not a missing ledger: ${String(t)}`))
|
|
26
|
+
})))), E = (e, t, n) => C.gen(function* () {
|
|
27
|
+
if ((yield* T(e)) === t.toFingerprint) return "already-current";
|
|
28
|
+
let r = `plan_${Date.now().toString(36)}`, i = (/* @__PURE__ */ new Date()).toISOString(), a = yield* p(e, i);
|
|
29
|
+
return yield* e`
|
|
30
|
+
INSERT INTO ${e("_voltro_migration_plans")}
|
|
31
|
+
(${e("id")}, ${e("fingerprint")}, ${e("liveFingerprint")}, ${e("operations")},
|
|
32
|
+
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
33
|
+
${e("durationMs")}, ${e("appliedAt")}, ${e("notes")})
|
|
34
|
+
VALUES
|
|
35
|
+
(${r}, ${t.toFingerprint}, ${t.fromFingerprint}, ${JSON.stringify([])},
|
|
36
|
+
${n.appliedBy}, ${n.environment}, ${n.source},
|
|
37
|
+
${0}, ${a}, ${"no DDL — the declared schema already matched the database"})
|
|
38
|
+
`, "recorded";
|
|
39
|
+
}).pipe(C.catch(() => C.succeed("already-current"))), D = (e, t, n) => C.gen(function* () {
|
|
40
|
+
let i = r(e), o = a(s(t, i, { charset: n.charset })), c = yield* T(e);
|
|
41
|
+
return c === o ? {
|
|
42
|
+
kind: "up-to-date",
|
|
43
|
+
fingerprint: o
|
|
44
|
+
} : {
|
|
45
|
+
kind: "prod-mismatch",
|
|
46
|
+
expected: o,
|
|
47
|
+
...c === void 0 ? {} : { actual: c }
|
|
48
|
+
};
|
|
49
|
+
}), O = [
|
|
50
|
+
"pgeagertest_",
|
|
51
|
+
"mig_e2e_",
|
|
52
|
+
"mig_test_",
|
|
53
|
+
"mysqleagertest_",
|
|
54
|
+
"mssqleagertest_",
|
|
55
|
+
"sqliteeagertest_"
|
|
56
|
+
], k = (e) => O.some((t) => e.startsWith(t)), A = (e, t) => C.gen(function* () {
|
|
57
|
+
if (process.env.VOLTRO_TEST_FIXTURE_GC === "off") return [];
|
|
58
|
+
let n = t.filter((e) => k(e.name)).map((e) => e.name);
|
|
59
|
+
if (n.length === 0) return [];
|
|
60
|
+
for (let t of n) yield* e.unsafe(`DROP TABLE IF EXISTS "${t}" CASCADE`).pipe(C.catch(() => C.void));
|
|
61
|
+
return n;
|
|
62
|
+
}), j = () => {
|
|
63
|
+
let e = (process.env.VOLTRO_DB_IGNORE_TABLES ?? "").split(",").map((e) => e.trim()).filter((e) => e.length > 0);
|
|
64
|
+
return e.length > 0 ? { ignoreTables: e } : {};
|
|
65
|
+
}, M = (e) => {
|
|
66
|
+
if (e === void 0) return;
|
|
67
|
+
let t = e.trim();
|
|
68
|
+
if (t === "") return;
|
|
69
|
+
if (t === "1" || t.toLowerCase() === "true") return { kind: "all" };
|
|
70
|
+
let n = t.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
|
|
71
|
+
return n.length > 0 ? {
|
|
72
|
+
kind: "tables",
|
|
73
|
+
tables: new Set(n)
|
|
74
|
+
} : void 0;
|
|
75
|
+
}, N = (e) => typeof e.table == "string" ? e.table : void 0, P = (e, t = { kind: "all" }) => ({
|
|
76
|
+
...e,
|
|
77
|
+
operations: e.operations.map((e) => {
|
|
78
|
+
if (!e.blocked || e.classification !== "lossy") return e;
|
|
79
|
+
if (t?.kind === "tables") {
|
|
80
|
+
let n = N(e.op);
|
|
81
|
+
if (n === void 0 || !t.tables.has(n)) return e;
|
|
82
|
+
}
|
|
83
|
+
let { blocked: n, ...r } = e;
|
|
84
|
+
return r;
|
|
85
|
+
})
|
|
86
|
+
}), F = (e, t, n) => t === "postgres" ? c(e, n).pipe(C.flatMap((e) => {
|
|
87
|
+
let t = l(e);
|
|
88
|
+
return t === void 0 ? C.void : C.logWarning(`auto-migrate: ${t}`).pipe(C.annotateLogs({ scope: "voltro:migrate" }));
|
|
89
|
+
}), C.catch(() => C.void)) : C.void, I = (i, o, c) => C.gen(function* () {
|
|
90
|
+
let l = r(i), f = o;
|
|
91
|
+
if (c.charset === "utf8mb4") {
|
|
92
|
+
let e = new Set(o.map((e) => e.tableName)), t = S(yield* x(i), e, c.charset);
|
|
93
|
+
if (!b(t)) {
|
|
94
|
+
let t = yield* v(i, {
|
|
95
|
+
declaredNames: e,
|
|
96
|
+
charset: c.charset
|
|
97
|
+
});
|
|
98
|
+
for (let e of y(t)) yield* C.logInfo(`auto-migrate: store charset — ${e}`).pipe(C.annotateLogs({ scope: "voltro:migrate" }));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let p = yield* h(i);
|
|
102
|
+
if (!p && process.env.VOLTRO_MIGRATE_FORCE !== "1") {
|
|
103
|
+
let e = a(s(f, l, { charset: c.charset }));
|
|
104
|
+
if ((yield* T(i)) === e) return yield* F(i, l, f), {
|
|
105
|
+
kind: "up-to-date",
|
|
106
|
+
fingerprint: e
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
let g = yield* d(i), _ = yield* A(i, g.tables);
|
|
110
|
+
_.length > 0 && (yield* C.logInfo(`auto-migrate: reaped ${_.length} test-fixture leftover(s)`).pipe(C.annotateLogs({
|
|
111
|
+
scope: "voltro:migrate",
|
|
112
|
+
count: _.length
|
|
113
|
+
})), g = yield* d(i));
|
|
114
|
+
{
|
|
115
|
+
let n = c.softDropRetentionDays ?? 7, r = e(g, { retentionDays: n });
|
|
116
|
+
r.length > 0 && (yield* C.logWarning(`auto-migrate: ${t(r, n)}`).pipe(C.annotateLogs({
|
|
117
|
+
scope: "voltro:migrate",
|
|
118
|
+
snapshots: r.map((e) => e.name)
|
|
119
|
+
})));
|
|
120
|
+
}
|
|
121
|
+
let w = new Set(o.map((e) => e.tableName)), D = (e) => n(e, w), O = { tables: D(g.tables) }, k = u({
|
|
122
|
+
declared: f,
|
|
123
|
+
live: O,
|
|
124
|
+
dialect: l,
|
|
125
|
+
charset: c.charset,
|
|
126
|
+
...j()
|
|
127
|
+
});
|
|
128
|
+
if (k.summary.blocked > 0) {
|
|
129
|
+
let e = k.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy"), t = M(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
130
|
+
if (t === void 0 || !e || (k = P(k, t), k.operations.some((e) => e.blocked))) return {
|
|
131
|
+
kind: "refused-blocked",
|
|
132
|
+
plan: k
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return k.operations.length === 0 && !p ? (yield* E(i, k, {
|
|
136
|
+
appliedBy: c.appliedBy,
|
|
137
|
+
environment: c.environment === "prod" ? "dev" : c.environment,
|
|
138
|
+
source: "auto-diff"
|
|
139
|
+
}), {
|
|
140
|
+
kind: "up-to-date",
|
|
141
|
+
fingerprint: k.toFingerprint
|
|
142
|
+
}) : {
|
|
143
|
+
kind: "applied",
|
|
144
|
+
applied: yield* m(i, k, {
|
|
145
|
+
...c.beforeExecute === void 0 ? {} : { beforeExecute: c.beforeExecute },
|
|
146
|
+
declared: o,
|
|
147
|
+
charset: c.charset,
|
|
148
|
+
appliedBy: c.appliedBy,
|
|
149
|
+
environment: c.environment === "prod" ? "dev" : c.environment,
|
|
150
|
+
source: "auto-diff",
|
|
151
|
+
replan: (e) => C.gen(function* () {
|
|
152
|
+
let t = yield* d(e);
|
|
153
|
+
return u({
|
|
154
|
+
declared: f,
|
|
155
|
+
live: { tables: D(t.tables) },
|
|
156
|
+
dialect: l,
|
|
157
|
+
charset: c.charset,
|
|
158
|
+
...j()
|
|
159
|
+
});
|
|
160
|
+
})
|
|
161
|
+
}),
|
|
162
|
+
plan: k
|
|
163
|
+
};
|
|
164
|
+
}), L = (e, t, n) => process.env.VOLTRO_AUTO_MIGRATE === "0" ? g(e, t).pipe(C.as({
|
|
165
|
+
kind: "skipped",
|
|
166
|
+
reason: "VOLTRO_AUTO_MIGRATE=0"
|
|
167
|
+
})) : n.environment === "prod" ? D(e, t, n).pipe(C.tap((n) => n.kind === "up-to-date" ? g(e, t) : C.void)) : I(e, t, n).pipe(C.tap((n) => n.kind === "applied" || n.kind === "up-to-date" ? g(e, t) : C.void), C.tap((n) => n.kind === "applied" || n.kind === "up-to-date" ? i(t, r(e)) : C.void)), R = (e, t) => {
|
|
168
|
+
switch (e.kind) {
|
|
169
|
+
case "skipped": return `auto-migrate: skipped (${e.reason})`;
|
|
170
|
+
case "up-to-date": return `auto-migrate: schema up to date (${t}, fingerprint=${o(e.fingerprint)})`;
|
|
171
|
+
case "applied": {
|
|
172
|
+
let t = e.plan.summary;
|
|
173
|
+
return `auto-migrate: applied ${e.applied.appliedOps} op(s) in ${e.applied.durationMs}ms [safe=${t.safe} needs-default=${t.needsDefault} needs-backfill=${t.needsBackfill} rename=${t.needsRenameAnnotation} lossy=${t.lossy}] fingerprint=${o(e.applied.fingerprint)}`;
|
|
174
|
+
}
|
|
175
|
+
case "prod-mismatch": return `auto-migrate: SCHEMA FINGERPRINT MISMATCH — declared=${o(e.expected)}` + (e.actual ? ` live=${o(e.actual)}` : " live=<no _voltro_migration_plans row>") + ". Run `voltro db apply --plan plan.json` from the deploy pipeline before serving.";
|
|
176
|
+
case "refused-blocked": {
|
|
177
|
+
let t = e.plan.operations.filter((e) => e.blocked), n = t.filter((e) => e.op.kind === "drop-table").length, r = t.filter((e) => e.op.kind === "drop-table").map((e) => "table" in e.op ? e.op.table : "").filter(Boolean), i = t.filter((e) => e.op.kind === "add-column").length, a = t.filter((e) => e.op.kind === "drop-column").length, o = t.filter((e) => e.classification === "needs-rename-annotation").length, s = `auto-migrate: REFUSED — ${e.plan.summary.blocked} blocked operation(s):\n` + w(e.plan), c = [""];
|
|
178
|
+
if (n > 0 && c.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=${r.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.", ` → Set \`VOLTRO_DESTRUCTIVE_OK=${r.join(",")}\` for ONE run — it acknowledges`, " the data loss for THOSE tables only; every other lossy op in this plan stays blocked.", " (`VOLTRO_DESTRUCTIVE_OK=1` acknowledges all of them, which is rarely what you mean.)", " There is deliberately no `dropped()` marker for a TABLE, unlike for a column: a", " dropped column leaves a slot worth documenting in the declaration, a dropped table", " leaves nothing — the marker would be a dead entry you must remember to delete.", " 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`)."), i > 0 && c.push(`${i} 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."), a > 0) {
|
|
179
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
|
|
180
|
+
for (let r of e.plan.operations) {
|
|
181
|
+
let e = "table" in r.op ? r.op.table : void 0;
|
|
182
|
+
if (e === void 0) continue;
|
|
183
|
+
let i = r.op.kind === "drop-column" ? r.op.column : r.op.kind === "add-column" ? r.op.column.name : void 0;
|
|
184
|
+
if (i === void 0) continue;
|
|
185
|
+
let a = r.op.kind === "drop-column" ? t : r.op.kind === "add-column" ? n : void 0;
|
|
186
|
+
a !== void 0 && a.set(e, [...a.get(e) ?? [], i]);
|
|
187
|
+
}
|
|
188
|
+
let r = [...t.entries()].filter(([e]) => (n.get(e)?.length ?? 0) > 0).map(([e, t]) => `${e}: ${t.join(", ")} gone, ${n.get(e).join(", ")} new`);
|
|
189
|
+
c.push(`${a} column(s) are missing from your declared schema.`), r.length > 0 && c.push(" → DID YOU RENAME ONE? This plan drops AND adds columns on the same table:", ...r.map((e) => ` ${e}`), " If so, add `.renamedFrom('<oldName>')` to the NEW column — the planner then emits a", " RENAME and the data moves with it. Dropping and re-adding loses every value."), c.push(" → If intentional: give the field the `dropped()` marker as its VALUE —", " `<column>: dropped()` (import it from `@voltro/database`). It is not a method", " chained onto the column; the slot stays, documenting the hole.", " → If a typo: restore the field. Data survives until you drop it.");
|
|
190
|
+
}
|
|
191
|
+
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");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
export { R as describeOutcome, M as destructiveScope, T as fetchLastFingerprint, j as ignoreTablesFromEnv, E as recordUpToDate, L as runPlannedMigrations, P as unblockLossy };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { unblockLossy as e } from "./boot.js";
|
|
2
|
+
//#region src/migrations/branchRehearsal.ts
|
|
3
|
+
var t = (e) => {
|
|
4
|
+
let t = e.table;
|
|
5
|
+
if (typeof t == "string") return t;
|
|
6
|
+
let n = e.from;
|
|
7
|
+
return typeof n == "string" ? n : void 0;
|
|
8
|
+
}, n = (e) => ({
|
|
9
|
+
kind: e.op.kind,
|
|
10
|
+
table: t(e.op),
|
|
11
|
+
classification: e.classification,
|
|
12
|
+
reason: e.reason,
|
|
13
|
+
blockedFix: e.blocked?.fix
|
|
14
|
+
}), r = (e) => e.operations.map(n), i = async (t) => {
|
|
15
|
+
let i = t.log ?? (() => {}), a = {
|
|
16
|
+
branchId: t.branchId,
|
|
17
|
+
mechanism: t.mechanism,
|
|
18
|
+
branchedTables: t.branchedTables,
|
|
19
|
+
replayedForeignKeys: t.replayedForeignKeys
|
|
20
|
+
}, o = [], s = !1, c;
|
|
21
|
+
try {
|
|
22
|
+
await t.provision(), s = !0, i(`branch ${t.branchId} provisioned (${t.mechanism}, ${t.branchedTables} table(s), ${t.replayedForeignKeys} foreign key(s))`);
|
|
23
|
+
let l = await t.fidelityPlan();
|
|
24
|
+
if (l.operations.length > 0) c = {
|
|
25
|
+
...a,
|
|
26
|
+
outcome: "infidelity",
|
|
27
|
+
operations: o,
|
|
28
|
+
lossy: o,
|
|
29
|
+
blocked: o,
|
|
30
|
+
applied: !1,
|
|
31
|
+
converged: !1,
|
|
32
|
+
residual: o,
|
|
33
|
+
infidelity: r(l)
|
|
34
|
+
};
|
|
35
|
+
else {
|
|
36
|
+
let s = await t.plan(), l = r(s), u = l.filter((e) => e.classification === "lossy"), d = e(s), f = d.operations.filter((e) => e.blocked !== void 0).map(n);
|
|
37
|
+
if (f.length > 0) c = {
|
|
38
|
+
...a,
|
|
39
|
+
outcome: "blocked",
|
|
40
|
+
operations: l,
|
|
41
|
+
lossy: u,
|
|
42
|
+
blocked: f,
|
|
43
|
+
applied: !1,
|
|
44
|
+
converged: !1,
|
|
45
|
+
residual: o,
|
|
46
|
+
infidelity: o
|
|
47
|
+
};
|
|
48
|
+
else if (l.length === 0) c = {
|
|
49
|
+
...a,
|
|
50
|
+
outcome: "clean",
|
|
51
|
+
operations: l,
|
|
52
|
+
lossy: u,
|
|
53
|
+
blocked: f,
|
|
54
|
+
applied: !1,
|
|
55
|
+
converged: !0,
|
|
56
|
+
residual: o,
|
|
57
|
+
infidelity: o
|
|
58
|
+
};
|
|
59
|
+
else {
|
|
60
|
+
await t.apply(d), i(`branch ${t.branchId}: applied ${l.length} operation(s)`);
|
|
61
|
+
let e = r(await t.replan());
|
|
62
|
+
c = {
|
|
63
|
+
...a,
|
|
64
|
+
outcome: e.length > 0 ? "diverged" : u.length > 0 ? "lossy" : "clean",
|
|
65
|
+
operations: l,
|
|
66
|
+
lossy: u,
|
|
67
|
+
blocked: f,
|
|
68
|
+
applied: !0,
|
|
69
|
+
converged: e.length === 0,
|
|
70
|
+
residual: e,
|
|
71
|
+
infidelity: o
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
c = {
|
|
77
|
+
...a,
|
|
78
|
+
outcome: "failed",
|
|
79
|
+
operations: o,
|
|
80
|
+
lossy: o,
|
|
81
|
+
blocked: o,
|
|
82
|
+
applied: !1,
|
|
83
|
+
converged: !1,
|
|
84
|
+
residual: o,
|
|
85
|
+
infidelity: o,
|
|
86
|
+
error: e instanceof Error ? e.message : String(e)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let l = !1;
|
|
90
|
+
if (s && t.keep !== !0) try {
|
|
91
|
+
await t.teardown(), l = !0;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
i(`branch ${t.branchId}: TEARDOWN FAILED — drop it by hand: ${e instanceof Error ? e.message : String(e)}`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
...c,
|
|
97
|
+
tornDown: l
|
|
98
|
+
};
|
|
99
|
+
}, a = (e) => {
|
|
100
|
+
switch (e.outcome) {
|
|
101
|
+
case "clean": return 0;
|
|
102
|
+
case "lossy": return 2;
|
|
103
|
+
case "blocked": return 2;
|
|
104
|
+
case "diverged": return 1;
|
|
105
|
+
case "infidelity": return 1;
|
|
106
|
+
case "failed": return 1;
|
|
107
|
+
}
|
|
108
|
+
}, o = (e) => ` ${e.classification === "lossy" ? "✗" : "•"} ${e.kind}${e.table ? ` ${e.table}` : ""} [${e.classification}]${e.reason ? ` — ${e.reason}` : ""}${e.blockedFix ? `\n ! ${e.blockedFix}` : ""}`, s = (e) => {
|
|
109
|
+
let t = [];
|
|
110
|
+
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"}`);
|
|
111
|
+
else if (e.outcome === "infidelity") {
|
|
112
|
+
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:");
|
|
113
|
+
for (let n of e.infidelity) t.push(o(n));
|
|
114
|
+
} else {
|
|
115
|
+
t.push(` plan: ${e.operations.length} operation(s), ${e.lossy.length} lossy, ${e.blocked.length} refused`);
|
|
116
|
+
for (let n of e.operations) t.push(o(n));
|
|
117
|
+
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(o(n));
|
|
118
|
+
}
|
|
119
|
+
return t.push(e.tornDown ? ` branch ${e.branchId} torn down.` : ` branch ${e.branchId} KEPT — drop it when you are done.`), t.join("\n");
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
export { a as branchRehearsalExitCode, s as formatBranchRehearsal, i as rehearseMigrationOnBranch, n as rehearsedOperation };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { indexStorageShape as e } from "../indexStorage.js";
|
|
2
|
+
import { mssqlTextStorageLength as t } from "../mssqlTextStorage.js";
|
|
3
|
+
import { sqlType as n } from "../migrate.js";
|
|
4
|
+
import { normalizeIndexPredicate as r } from "./indexPredicate.js";
|
|
5
|
+
//#region src/migrations/columnEquivalence.ts
|
|
6
|
+
var i = (e, t) => t.name === "id" && e.primaryKey?.length === 1 && e.primaryKey[0] === "id", a = /* @__PURE__ */ new Set(["integer", "bigint"]), o = (e) => a.has(e) ? "numeric" : "text", s = (e) => e.idScheme?.kind === "numeric" ? "numeric" : "text", c = (e) => e.idStorage ?? (b(e) ? "numeric" : "text"), l = (e) => e.idScheme ? s(e) : c(e), u = (e) => e.type === "text" || e.type === "id" && l(e) === "text", d = (e, t) => t && (e.type === "text" || e.type === "id") ? {
|
|
7
|
+
...e,
|
|
8
|
+
type: "id",
|
|
9
|
+
unique: !0,
|
|
10
|
+
idStorage: l(e)
|
|
11
|
+
} : e.type === "id" ? {
|
|
12
|
+
...e,
|
|
13
|
+
idStorage: l(e)
|
|
14
|
+
} : e, f = (e, t, n) => {
|
|
15
|
+
let r = d(e, n), i = d(t, !1);
|
|
16
|
+
return n && r.type === "id" && i.type === "id" && r.idStorage === i.idStorage;
|
|
17
|
+
}, p = (e) => e.type !== "raw", m = (e) => ({
|
|
18
|
+
kind: e.kind,
|
|
19
|
+
geomKind: e.geomKind.toUpperCase(),
|
|
20
|
+
srid: e.srid
|
|
21
|
+
}), h = (e, t) => {
|
|
22
|
+
if (e === void 0 || t === void 0) return e === t;
|
|
23
|
+
let n = m(e), r = m(t);
|
|
24
|
+
return n.kind === r.kind && n.geomKind === r.geomKind && n.srid === r.srid;
|
|
25
|
+
}, g = (t) => {
|
|
26
|
+
let { kind: n, kindOptions: i, ...a } = t, o = {
|
|
27
|
+
...a,
|
|
28
|
+
...e(n, i),
|
|
29
|
+
...t.where === void 0 ? {} : { where: r(t.where) }
|
|
30
|
+
};
|
|
31
|
+
return t.expression ? {
|
|
32
|
+
...o,
|
|
33
|
+
columns: []
|
|
34
|
+
} : o;
|
|
35
|
+
}, _ = (e) => {
|
|
36
|
+
if (typeof e != "object" || !e) return JSON.stringify(e);
|
|
37
|
+
if (Array.isArray(e)) return `[${e.map(_).join(",")}]`;
|
|
38
|
+
let t = e;
|
|
39
|
+
return `{${Object.keys(t).sort().map((e) => `${JSON.stringify(e)}:${_(t[e])}`).join(",")}}`;
|
|
40
|
+
}, v = (e) => {
|
|
41
|
+
if (!e.hasDefault || e.defaultValue === void 0 || e.defaultValue === null) return null;
|
|
42
|
+
let t = e.defaultValue;
|
|
43
|
+
if (typeof t == "boolean") return t ? "__BOOL_TRUE__" : "__BOOL_FALSE__";
|
|
44
|
+
if (typeof t == "number") return String(t);
|
|
45
|
+
if (typeof t != "string") return _(t);
|
|
46
|
+
let n = t.trim(), r = n.toLowerCase().replace(/\s+/g, "");
|
|
47
|
+
if (r === "now" || r === "now()" || /^current_timestamp(\(\d*\))?$/.test(r) || r === "sysutcdatetime()" || r === "sysdatetime()" || r === "getutcdate()" || r === "getdate()") return "__NOW__";
|
|
48
|
+
let i = n.replace(/::[a-zA-Z0-9_ "]+$/, "").trim(), a = /^'(.*)'$/.test(i) ? i.slice(1, -1).replace(/''/g, "'") : i, o = /^cast\(\s*(?:_\w+\s*)?\\?'(.*?)\\?'\s+as\s+json\s*\)$/i.exec(a);
|
|
49
|
+
if (o) try {
|
|
50
|
+
return _(JSON.parse(o[1]));
|
|
51
|
+
} catch {}
|
|
52
|
+
let s = /^convert\(\s*(?:_\w+\s*)?\\?'(.*?)\\?'\s+using\s+[\w]+\s*\)$/i.exec(a);
|
|
53
|
+
if (s) try {
|
|
54
|
+
return _(JSON.parse(s[1]));
|
|
55
|
+
} catch {}
|
|
56
|
+
if (a.startsWith("{") || a.startsWith("[")) try {
|
|
57
|
+
return _(JSON.parse(a));
|
|
58
|
+
} catch {}
|
|
59
|
+
return /^-?\d+(\.\d+)?$/.test(a) ? String(Number(a)) : a;
|
|
60
|
+
}, y = (e) => {
|
|
61
|
+
let t = v(e);
|
|
62
|
+
return t === "1" || t === "true" ? "__BOOL_TRUE__" : t === "0" || t === "false" ? "__BOOL_FALSE__" : t;
|
|
63
|
+
}, b = (e) => typeof e.defaultValue == "string" && /^nextval\(.+::regclass\)$/i.test(e.defaultValue.trim()), x = (e) => {
|
|
64
|
+
if (!e) return null;
|
|
65
|
+
let t = (e) => e === "restrict" ? "noAction" : e;
|
|
66
|
+
return {
|
|
67
|
+
table: e.table,
|
|
68
|
+
column: e.column,
|
|
69
|
+
onDelete: t(e.onDelete),
|
|
70
|
+
onUpdate: t(e.onUpdate)
|
|
71
|
+
};
|
|
72
|
+
}, S = (e, r, i) => {
|
|
73
|
+
if (r === "mssql") {
|
|
74
|
+
let n = t(e, i);
|
|
75
|
+
if (n !== void 0) return n;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
let t = /^N?VARCHAR\((\d+)\)$/i.exec(n(e, r).trim());
|
|
79
|
+
return t ? Number(t[1]) : void 0;
|
|
80
|
+
} catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
export { d as canonicalIdColumn, _ as canonicalJson, y as comparableDefault, g as comparableIndex, m as comparableSpatial, s as declaredIdStorage, p as hasComparableType, o as idStorageOfType, b as isPgSequenceDefault, i as isSolePrimaryKeyId, u as isTextStorage, c as liveIdStorage, x as normRef, v as normalizeDefault, S as renderedTextWidth, l as resolvedIdStorage, f as sameIdSpelling, h as sameSpatial };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/migrations/ddlError.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"sqlMessage",
|
|
4
|
+
"code",
|
|
5
|
+
"errno",
|
|
6
|
+
"sqlState",
|
|
7
|
+
"number",
|
|
8
|
+
"state",
|
|
9
|
+
"class",
|
|
10
|
+
"lineNumber",
|
|
11
|
+
"procName",
|
|
12
|
+
"serverName",
|
|
13
|
+
"detail",
|
|
14
|
+
"hint",
|
|
15
|
+
"table",
|
|
16
|
+
"column",
|
|
17
|
+
"constraint",
|
|
18
|
+
"position",
|
|
19
|
+
"routine"
|
|
20
|
+
], t = (t) => {
|
|
21
|
+
let n = t.cause;
|
|
22
|
+
for (let t = 0; t < 6 && typeof n == "object" && n; t++) {
|
|
23
|
+
let t = n;
|
|
24
|
+
if (e.some((e) => t[e] !== void 0 && t[e] !== null && t[e] !== "")) return t;
|
|
25
|
+
n = t.cause;
|
|
26
|
+
}
|
|
27
|
+
return t.cause ?? {};
|
|
28
|
+
}, n = (e, n, i, a = 400) => {
|
|
29
|
+
let o = i, s = t(i), c = (e) => {
|
|
30
|
+
let t = s[e];
|
|
31
|
+
return t == null || t === "" ? void 0 : String(t);
|
|
32
|
+
}, l = [
|
|
33
|
+
"",
|
|
34
|
+
`═══ ${e} ═══`,
|
|
35
|
+
`statement: ${n.replace(/\s+/g, " ").slice(0, a)}${n.length > a ? " …" : ""}`,
|
|
36
|
+
`db.message: ${c("sqlMessage") ?? c("message") ?? o.message ?? "(none)"}`
|
|
37
|
+
];
|
|
38
|
+
for (let e of [
|
|
39
|
+
"code",
|
|
40
|
+
"errno",
|
|
41
|
+
"sqlState",
|
|
42
|
+
"number",
|
|
43
|
+
"state",
|
|
44
|
+
"class",
|
|
45
|
+
"lineNumber",
|
|
46
|
+
"procName",
|
|
47
|
+
"serverName",
|
|
48
|
+
"detail",
|
|
49
|
+
"hint",
|
|
50
|
+
"table",
|
|
51
|
+
"column",
|
|
52
|
+
"constraint",
|
|
53
|
+
"position",
|
|
54
|
+
"routine"
|
|
55
|
+
]) {
|
|
56
|
+
let t = c(e);
|
|
57
|
+
t !== void 0 && l.push(`db.${e}: ${t}`);
|
|
58
|
+
}
|
|
59
|
+
let u = r(c("code"));
|
|
60
|
+
return u !== void 0 && l.push("", u), l.push(""), l.join("\n");
|
|
61
|
+
}, r = (e) => e === "3F000" ? "remedy: the connection's search_path names a schema that does not exist. Voltro sets it from DB_SCHEMA — create the schema (`CREATE SCHEMA \"<name>\"`) or unset DB_SCHEMA to use the connection default. Voltro creates TABLES, never the schema itself: that is a namespace decision, and inventing one from a typo is how a migration lands somewhere nobody is looking." : void 0;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { n as formatDdlError };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { validateIndexName as e } from "../identifierLimits.js";
|
|
2
|
+
import { isSqliteFamily as t } from "../dialect.js";
|
|
3
|
+
import { encodeSqliteDecimalDefault as n, sqliteDecimalStorageShape as r } from "../sqliteDecimalStorage.js";
|
|
4
|
+
import { isStagingTableName as i } from "./frameworkLiveTables.js";
|
|
5
|
+
import { indexKindForDialect as a, indexStorageShape as o, resolveHnswOptions as s } from "../indexStorage.js";
|
|
6
|
+
import { lowerUniqueActive as c } from "./uniqueActiveLowering.js";
|
|
7
|
+
import { renderedTextWidth as l } from "./columnEquivalence.js";
|
|
8
|
+
import { stampDeclaredCharset as u } from "./storeCharset.js";
|
|
9
|
+
//#region src/migrations/declaredSchema.ts
|
|
10
|
+
var d = (e, i, a) => {
|
|
11
|
+
let o = i.references ? {
|
|
12
|
+
table: i.references().tableName,
|
|
13
|
+
column: i.referenceColumn ?? "id",
|
|
14
|
+
...i.onDelete ? { onDelete: i.onDelete } : {},
|
|
15
|
+
...i.refOnUpdate ? { onUpdate: i.refOnUpdate } : {},
|
|
16
|
+
...i.orphanPolicy ? { orphanPolicy: i.orphanPolicy } : {}
|
|
17
|
+
} : void 0, s = a === void 0 || t(a) ? r(i) : void 0, c = s ? n(i.defaultValue, s) : i.defaultValue;
|
|
18
|
+
return {
|
|
19
|
+
name: e,
|
|
20
|
+
type: i.type,
|
|
21
|
+
nullable: i.generatedAs ? !0 : i.nullable,
|
|
22
|
+
unique: i.unique,
|
|
23
|
+
hasDefault: i.hasDefault,
|
|
24
|
+
...i.hasDefault && c !== void 0 ? { defaultValue: c } : {},
|
|
25
|
+
...o ? { references: o } : {},
|
|
26
|
+
...i.type === "reference" ? { referenceStorage: i.referenceStorage ?? "text" } : {},
|
|
27
|
+
...i.oneOf && i.oneOf.length > 0 ? { oneOf: i.oneOf } : {},
|
|
28
|
+
...i.idScheme ? { idScheme: { kind: i.idScheme.kind } } : {},
|
|
29
|
+
...i.rawDdl === void 0 ? {} : { rawDdl: i.rawDdl },
|
|
30
|
+
...i.type === "array" && a !== void 0 && a !== "postgres" ? { storageType: a === "mysql" || a === "mariadb" ? "json" : "text" } : {},
|
|
31
|
+
...i.type === "enum" && (a === "mssql" || a === "sqlite" || a === "turso") ? { storageType: "text" } : {},
|
|
32
|
+
...i.type === "enum" && i.enumValues !== void 0 && i.enumValues.length > 0 && (a === "mssql" || a === "sqlite" || a === "turso") ? { oneOf: i.enumValues } : {},
|
|
33
|
+
...i.maxLength === void 0 ? {} : { maxLength: i.maxLength },
|
|
34
|
+
...i.precision === void 0 ? {} : { precision: i.precision },
|
|
35
|
+
...i.scale === void 0 ? {} : { scale: i.scale },
|
|
36
|
+
...(a === void 0 || t(a)) && i.sqliteDecimalEncoding !== void 0 ? { sqliteDecimalEncoding: i.sqliteDecimalEncoding } : {},
|
|
37
|
+
...i.generatedAs ? { generatedAs: {
|
|
38
|
+
expr: i.generatedAs.expr,
|
|
39
|
+
stored: i.generatedAs.stored
|
|
40
|
+
} } : {},
|
|
41
|
+
...i.narrowedFrom ? { narrowedFrom: {
|
|
42
|
+
from: i.narrowedFrom.from,
|
|
43
|
+
...i.narrowedFrom.using === void 0 ? {} : { using: i.narrowedFrom.using }
|
|
44
|
+
} } : {},
|
|
45
|
+
...i.sensitive ? { sensitive: i.sensitive } : i.encrypted && !i.safe ? { sensitive: { class: "secret" } } : {},
|
|
46
|
+
...i.safe ? { safe: !0 } : {},
|
|
47
|
+
...i.vectorDim === void 0 ? {} : { vectorDim: i.vectorDim },
|
|
48
|
+
...i.vectorPrecision === void 0 ? {} : { vectorPrecision: i.vectorPrecision },
|
|
49
|
+
...i.arrayElement === void 0 ? {} : { arrayElement: i.arrayElement },
|
|
50
|
+
...i.enumName !== void 0 && (a === void 0 || a === "postgres") ? { enumName: i.enumName } : {},
|
|
51
|
+
...i.enumValues === void 0 ? {} : { enumValues: i.enumValues },
|
|
52
|
+
...i.spatial === void 0 ? {} : { spatial: i.spatial }
|
|
53
|
+
};
|
|
54
|
+
}, f = (t, n) => {
|
|
55
|
+
let r = [], i = /* @__PURE__ */ new Set(), c = (e) => {
|
|
56
|
+
i.has(e.name) || (i.add(e.name), r.push(e));
|
|
57
|
+
}, l = t.appliedPrimaryKey ?? [];
|
|
58
|
+
l.length > 0 ? c({
|
|
59
|
+
name: `${t.tableName}_pkey`,
|
|
60
|
+
table: t.tableName,
|
|
61
|
+
columns: [...l],
|
|
62
|
+
unique: !0
|
|
63
|
+
}) : t.fields.id && c({
|
|
64
|
+
name: `${t.tableName}_pkey`,
|
|
65
|
+
table: t.tableName,
|
|
66
|
+
columns: ["id"],
|
|
67
|
+
unique: !0
|
|
68
|
+
});
|
|
69
|
+
let u = t.appliedIndexes ?? [];
|
|
70
|
+
for (let e of u) {
|
|
71
|
+
let r = a(e.kind, n);
|
|
72
|
+
if (r === null) continue;
|
|
73
|
+
let i = e.fields[0], l = typeof i == "string" ? t.fields[i]?.vectorPrecision : void 0, u = r === "hnsw" ? { hnsw: s(e.kindOptions, l) } : r === "gin" ? e.kindOptions : void 0, d = e.fields.map((e) => typeof e == "string" ? e : "jsonPath" in e ? `(json:${e.jsonPath.column}->${e.jsonPath.path.join(".")}${e.jsonPath.numeric ? "::num" : ""})` : `(${e.expr})`), f = e.fields.some((e) => typeof e != "string");
|
|
74
|
+
c({
|
|
75
|
+
name: e.name,
|
|
76
|
+
table: t.tableName,
|
|
77
|
+
columns: d,
|
|
78
|
+
unique: e.unique ?? !1,
|
|
79
|
+
...o(r, u),
|
|
80
|
+
...e.where === void 0 ? {} : { where: e.where },
|
|
81
|
+
...f ? { expression: !0 } : {}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
let d = t.appliedUniques ?? [];
|
|
85
|
+
for (let e of d) e.fields.length < 2 || c({
|
|
86
|
+
name: e.name,
|
|
87
|
+
table: t.tableName,
|
|
88
|
+
columns: e.fields,
|
|
89
|
+
unique: !0
|
|
90
|
+
});
|
|
91
|
+
let f = /* @__PURE__ */ new Set();
|
|
92
|
+
for (let e of u) e.fields.every((e) => typeof e == "string") && f.add(e.fields.join("\0"));
|
|
93
|
+
for (let [n, r] of Object.entries(t.fields)) {
|
|
94
|
+
let i = r;
|
|
95
|
+
if (i.dropped || !i.references || i.fkAutoIndex === !1 || f.has(n)) continue;
|
|
96
|
+
let a = `${t.tableName}_${n}_idx`;
|
|
97
|
+
e(t.tableName, a, !0, [n]), c({
|
|
98
|
+
name: a,
|
|
99
|
+
table: t.tableName,
|
|
100
|
+
columns: [n],
|
|
101
|
+
unique: !1
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return r;
|
|
105
|
+
}, p = (e, t) => {
|
|
106
|
+
let n = new Set((e.appliedUniques ?? []).filter((e) => e.fields.length === 1).map((e) => e.fields[0])), r = [];
|
|
107
|
+
for (let [i, a] of Object.entries(e.fields)) {
|
|
108
|
+
if (a.dropped) continue;
|
|
109
|
+
let e = d(i, a, t);
|
|
110
|
+
r.push(n.has(i) ? {
|
|
111
|
+
...e,
|
|
112
|
+
unique: !0
|
|
113
|
+
} : e);
|
|
114
|
+
}
|
|
115
|
+
let i = e.appliedPrimaryKey ?? [], a = e.previousTableName;
|
|
116
|
+
return {
|
|
117
|
+
name: e.tableName,
|
|
118
|
+
columns: r,
|
|
119
|
+
indexes: f(e, t),
|
|
120
|
+
...i.length > 0 ? { primaryKey: [...i] } : {},
|
|
121
|
+
...a === void 0 ? {} : { renamedFrom: a }
|
|
122
|
+
};
|
|
123
|
+
}, m = (e) => {
|
|
124
|
+
let t = /* @__PURE__ */ new Map();
|
|
125
|
+
for (let n of e) for (let e of n.indexes) {
|
|
126
|
+
let r = t.get(e.name);
|
|
127
|
+
if (r !== void 0 && r !== n.name) throw Error(`duplicate index name '${e.name}' across tables '${r}' and '${n.name}'. Index names must be unique per SCHEMA, not just per table — the DB creates only one, so the other never applies and 'db plan' re-emits it forever. Give each an explicit, table-prefixed name (e.g. '${n.name}_${e.name}').`);
|
|
128
|
+
t.set(e.name, n.name);
|
|
129
|
+
}
|
|
130
|
+
}, h = (e, t, n = {}) => {
|
|
131
|
+
if (n.charset !== void 0 && t !== "mysql" && t !== "mariadb") throw Error(`store charset '${n.charset}' is declared for a ${t ?? "dialect-less"} store — a character set is a MySQL / MariaDB setting. Remove \`charset\` from \`store\` in app.config.ts, or declare \`store: { dialect: 'mysql' | 'mariadb', charset }\`.`);
|
|
132
|
+
let r = e.filter((e) => !i(e.tableName)).map((e) => p(c(e, t), t));
|
|
133
|
+
if (t === "mysql" || t === "mariadb") {
|
|
134
|
+
for (let e of r) for (let n of e.indexes) if (n.where !== void 0) throw Error(`Partial index '${n.name}' on '${e.name}' is not supported by ${t}. Use uniqueActive for portable conditional uniqueness.`);
|
|
135
|
+
}
|
|
136
|
+
m(r);
|
|
137
|
+
let a = t === void 0 ? r : r.map((e) => g(e, t));
|
|
138
|
+
return { tables: n.charset === void 0 ? a : u(a, n.charset) };
|
|
139
|
+
}, g = (e, n) => {
|
|
140
|
+
let r = new Set(e.indexes.flatMap((e) => e.columns));
|
|
141
|
+
return {
|
|
142
|
+
...e,
|
|
143
|
+
columns: e.columns.map((e) => {
|
|
144
|
+
if (n === "mssql" && !e.generatedAs && (e.type === "json" || e.type === "array") && (e = {
|
|
145
|
+
...e,
|
|
146
|
+
jsonValidation: e.type === "json" ? "value" : "container"
|
|
147
|
+
}), t(n)) {
|
|
148
|
+
if (e.maxLength === void 0) return e;
|
|
149
|
+
let { maxLength: t, ...n } = e;
|
|
150
|
+
return n;
|
|
151
|
+
}
|
|
152
|
+
if (e.maxLength !== void 0) return e;
|
|
153
|
+
let i = l(e, n, r.has(e.name) || e.unique);
|
|
154
|
+
return i === void 0 ? e : {
|
|
155
|
+
...e,
|
|
156
|
+
maxLength: i
|
|
157
|
+
};
|
|
158
|
+
})
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { h as declaredSnapshot, d as snapshotColumn };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { id as e, text as t } from "../columns.js";
|
|
2
|
+
import { table as n } from "../table.js";
|
|
3
|
+
import { Effect as r } from "effect";
|
|
4
|
+
//#region src/migrations/deployLedger.ts
|
|
5
|
+
var i = "_voltro_deploys", a = n(i, {
|
|
6
|
+
id: e({ prefix: "dpl" }),
|
|
7
|
+
status: t(),
|
|
8
|
+
dialect: t(),
|
|
9
|
+
appliedBy: t(),
|
|
10
|
+
startedAt: t(),
|
|
11
|
+
baseline: t(),
|
|
12
|
+
report: t().nullable()
|
|
13
|
+
}), o = (e, t) => e`INSERT INTO ${e(i)} (${e("id")}, ${e("status")}, ${e("dialect")}, ${e("appliedBy")}, ${e("startedAt")}, ${e("baseline")}, ${e("report")})
|
|
14
|
+
VALUES (${t.id}, ${"running"}, ${t.dialect}, ${t.appliedBy}, ${(/* @__PURE__ */ new Date()).toISOString()}, ${t.baseline}, ${null})`.pipe(r.asVoid), s = (e, t) => e`DELETE FROM ${e(i)} WHERE ${e("id")} = ${t}`.pipe(r.asVoid), c = (e, t, n) => e`UPDATE ${e(i)} SET ${e("status")} = ${"failed"}, ${e("report")} = ${n} WHERE ${e("id")} = ${t}`.pipe(r.asVoid), l = (e) => e`SELECT ${e("id")}, ${e("status")}, ${e("dialect")}, ${e("appliedBy")}, ${e("startedAt")}, ${e("baseline")}, ${e("report")} FROM ${e(i)}`.pipe(r.map((e) => e.map((e) => ({
|
|
15
|
+
...e,
|
|
16
|
+
startedAt: String(e.startedAt)
|
|
17
|
+
}))), r.catch(() => r.succeed([])));
|
|
18
|
+
//#endregion
|
|
19
|
+
export { i as DEPLOY_LEDGER_TABLE, a as _voltroDeploysTable, s as closeDeploy, c as failDeploy, o as openDeploy, l as unfinishedDeploys };
|