@voltro/database 0.84.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 +1215 -0
- package/THIRD-PARTY-NOTICES.md +5 -89
- 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 +122 -64
- package/dist/index.js +81 -2080
- 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-B28cPjEp.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 +58 -29
- package/dist/sql.js +41 -6661
- 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.d.ts +9 -10
- package/dist/wire.js +6 -2
- package/dist/writeAttribution.js +16 -0
- package/dist/writeRecorder.js +37 -0
- package/package.json +4 -6
- package/dist/replaceInProgress-DUHl5JEz.js +0 -3557
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
//#region src/migrations/fileBased.ts
|
|
3
|
+
var t = ["before-schema", "after-schema"], n = (e) => {
|
|
4
|
+
if (!e.id || e.id.length === 0) throw Error("migration: `id` is required + must be non-empty");
|
|
5
|
+
if (!e.up || !e.down) throw Error(`migration ${e.id}: both \`up\` and \`down\` must be functions`);
|
|
6
|
+
if (!t.includes(e.phase)) throw Error(`migration ${e.id}: \`phase\` is required and must be one of ${t.map((e) => `'${e}'`).join(" | ")} — 'before-schema' runs before the declarative apply (a shape change the planner must not compute against), 'after-schema' runs after it (a body that needs the new shape, such as a back-fill into a table this deploy creates)`);
|
|
7
|
+
return e;
|
|
8
|
+
}, r = Symbol.for("voltro.migration.irreversible"), i = class extends Error {
|
|
9
|
+
migrationId;
|
|
10
|
+
reason;
|
|
11
|
+
_tag = "IrreversibleMigrationError";
|
|
12
|
+
constructor(e, t) {
|
|
13
|
+
super(`migration ${e} cannot be rolled back: ${t}`), this.migrationId = e, this.reason = t, this.name = "IrreversibleMigrationError";
|
|
14
|
+
}
|
|
15
|
+
}, a = (t) => {
|
|
16
|
+
if (!t || t.trim().length === 0) throw Error("irreversible(): a reason is required");
|
|
17
|
+
return Object.assign(() => e.fail(new i("<unknown>", t)), { [r]: t });
|
|
18
|
+
}, o = (e) => {
|
|
19
|
+
let t = e[r];
|
|
20
|
+
return typeof t == "string" ? t : void 0;
|
|
21
|
+
}, s = class {
|
|
22
|
+
reason;
|
|
23
|
+
_tag = "MigrationSkipped";
|
|
24
|
+
constructor(e) {
|
|
25
|
+
this.reason = e;
|
|
26
|
+
}
|
|
27
|
+
}, c = class extends Error {
|
|
28
|
+
migrationId;
|
|
29
|
+
failed;
|
|
30
|
+
_tag = "MigrationExpectationFailed";
|
|
31
|
+
constructor(e, t) {
|
|
32
|
+
super(`migration ${e}: ${t.length} post-condition(s) did not hold after up():\n` + t.map((e) => ` - ${e}`).join("\n") + "\n The migration is NOT recorded; the next run applies it again."), this.migrationId = e, this.failed = t, this.name = "MigrationExpectationFailed";
|
|
33
|
+
}
|
|
34
|
+
}, l = class extends Error {
|
|
35
|
+
migrationId;
|
|
36
|
+
dialect;
|
|
37
|
+
_tag = "TransactionBrokenByBodyError";
|
|
38
|
+
constructor(e, t) {
|
|
39
|
+
super(`migration ${e} is transactional, and its body COMMITTED the transaction from inside — on ${t} a DDL statement commits everything before it. The work is in the database and the migration is recorded; it will not run again. What did not hold is the all-or-nothing guarantee. Either mark it \`transactional: false\` (per-statement, resumable through your own idempotency) or move the schema change into \`ctx.schema\` in a migration of its own and keep this one to data.`), this.migrationId = e, this.dialect = t, this.name = "TransactionBrokenByBodyError";
|
|
40
|
+
}
|
|
41
|
+
}, u = class extends Error {
|
|
42
|
+
migrationId;
|
|
43
|
+
operation;
|
|
44
|
+
_tag = "MigrationNeedsSqlClient";
|
|
45
|
+
constructor(e, t, n) {
|
|
46
|
+
super(`migration ${e}: ${t} — ${n}`), this.migrationId = e, this.operation = t, this.name = "MigrationNeedsSqlClient";
|
|
47
|
+
}
|
|
48
|
+
}, d = class extends Error {
|
|
49
|
+
table;
|
|
50
|
+
report;
|
|
51
|
+
_tag = "RewriteUnstableError";
|
|
52
|
+
constructor(e, t) {
|
|
53
|
+
super(`rewrite of ${e} did not stabilise after ${t.passes} pass(es): ${t.rewritten} rewritten, ${t.contended} contended on the last pass. Check for concurrent writes and guard values that cannot round-trip exactly before raising maxPasses.`), this.table = e, this.report = t, this.name = "RewriteUnstableError";
|
|
54
|
+
}
|
|
55
|
+
}, f = /^\d{8}_\d{6}_[a-z0-9_]+\.ts$/, p = (e) => typeof e == "object" && !!e && "id" in e && "up" in e && "down" in e && typeof e.id == "string" && typeof e.up == "function" && typeof e.down == "function";
|
|
56
|
+
//#endregion
|
|
57
|
+
export { f as FILE_MIGRATION_PATTERN, i as IrreversibleMigrationError, t as MIGRATION_PHASES, c as MigrationExpectationFailed, u as MigrationNeedsSqlClient, s as MigrationSkipped, d as RewriteUnstableError, l as TransactionBrokenByBodyError, a as irreversible, o as irreversibleReason, p as isFileMigration, n as migration };
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { materializeFields as e } from "../columns.js";
|
|
2
|
+
import { and as t, eq as n, gt as r, isNull as i } from "../predicate.js";
|
|
3
|
+
import { getTable as a } from "../tableRegistry.js";
|
|
4
|
+
import { decodeSqliteDecimal as o } from "../sqliteDecimal.js";
|
|
5
|
+
import { sqliteDecimalStorageShape as s } from "../sqliteDecimalStorage.js";
|
|
6
|
+
import { decodeRowsFromSchema as c } from "../rowDecoder.js";
|
|
7
|
+
import { compileSelect as l } from "../sqlCompiler.js";
|
|
8
|
+
import { MigrationNeedsSqlClient as u, MigrationSkipped as d, RewriteUnstableError as f } from "./fileBased.js";
|
|
9
|
+
import { liveIdStorage as p } from "./columnEquivalence.js";
|
|
10
|
+
import { planMigrations as m } from "./planner.js";
|
|
11
|
+
import { introspectSchema as h } from "./introspect.js";
|
|
12
|
+
import { runMigrationOp as g, runSchemaOp as _ } from "./applier.js";
|
|
13
|
+
import { unblockLossy as v } from "./boot.js";
|
|
14
|
+
import { Effect as y } from "effect";
|
|
15
|
+
//#region src/migrations/fileContext.ts
|
|
16
|
+
var b = (t) => typeof t.__definition == "function" ? e({ c: t }).c : t, x = (e) => {
|
|
17
|
+
if (typeof e != "string") return e;
|
|
18
|
+
let t = e.trim().replace(/::[a-zA-Z0-9_ "]+$/, "").trim();
|
|
19
|
+
if (/^current_timestamp(\(\d*\))?$/i.test(t) || /^now\(\)$/i.test(t)) return "now";
|
|
20
|
+
let n = /^'(.*)'$/s.exec(t);
|
|
21
|
+
return n === null ? e : n[1].replace(/''/g, "'");
|
|
22
|
+
}, S = (e, t = /* @__PURE__ */ new Map()) => {
|
|
23
|
+
let n = {};
|
|
24
|
+
for (let r of e.columns) {
|
|
25
|
+
let e = x(r.defaultValue), i = s(r), a = i && e != null ? o(e, i) : e, c = r.references, l = c === void 0 ? void 0 : t.get(c.table);
|
|
26
|
+
n[r.name] = {
|
|
27
|
+
type: r.type,
|
|
28
|
+
...r.type === "id" && p(r) === "numeric" ? { idScheme: { kind: "numeric" } } : {},
|
|
29
|
+
nullable: r.nullable,
|
|
30
|
+
unique: r.unique,
|
|
31
|
+
hasDefault: r.hasDefault,
|
|
32
|
+
...r.hasDefault ? { defaultValue: a } : {},
|
|
33
|
+
...r.maxLength === void 0 ? {} : { maxLength: r.maxLength },
|
|
34
|
+
...r.precision === void 0 ? {} : { precision: r.precision },
|
|
35
|
+
...r.scale === void 0 ? {} : { scale: r.scale },
|
|
36
|
+
...r.sqliteDecimalEncoding === void 0 ? {} : { sqliteDecimalEncoding: r.sqliteDecimalEncoding },
|
|
37
|
+
...r.enumValues === void 0 ? {} : { enumValues: r.enumValues },
|
|
38
|
+
...r.enumName === void 0 ? {} : { enumName: r.enumName },
|
|
39
|
+
...r.arrayElement === void 0 ? {} : { arrayElement: r.arrayElement },
|
|
40
|
+
...r.oneOf === void 0 ? {} : { oneOf: r.oneOf },
|
|
41
|
+
...r.referenceStorage === void 0 ? {} : { referenceStorage: r.referenceStorage },
|
|
42
|
+
...r.vectorDim === void 0 ? {} : { vectorDim: r.vectorDim },
|
|
43
|
+
...r.vectorPrecision === void 0 ? {} : { vectorPrecision: r.vectorPrecision },
|
|
44
|
+
...r.generatedAs === void 0 ? {} : { generatedAs: r.generatedAs },
|
|
45
|
+
...r.spatial === void 0 ? {} : { spatial: r.spatial },
|
|
46
|
+
...c === void 0 ? {} : {
|
|
47
|
+
references: () => l === void 0 ? {
|
|
48
|
+
tableName: c.table,
|
|
49
|
+
fields: {}
|
|
50
|
+
} : S(l),
|
|
51
|
+
referenceColumn: c.column,
|
|
52
|
+
...c.onDelete === void 0 ? {} : { onDelete: c.onDelete },
|
|
53
|
+
...c.onUpdate === void 0 ? {} : { refOnUpdate: c.onUpdate },
|
|
54
|
+
fkAutoIndex: !1
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
let r = e.indexes.filter((t) => t.name !== `${e.name}_pkey`);
|
|
59
|
+
for (let e of r) e.unique && e.columns.length === 1 && n[e.columns[0]] !== void 0 && (n[e.columns[0]] = {
|
|
60
|
+
...n[e.columns[0]],
|
|
61
|
+
unique: !0
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
tableName: e.name,
|
|
65
|
+
fields: n,
|
|
66
|
+
appliedUniques: r.filter((e) => e.unique && e.columns.length > 1).map((e) => ({
|
|
67
|
+
name: e.name,
|
|
68
|
+
fields: e.columns
|
|
69
|
+
})),
|
|
70
|
+
appliedIndexes: r.filter((e) => !e.unique).map((e) => ({
|
|
71
|
+
name: e.name,
|
|
72
|
+
fields: e.columns,
|
|
73
|
+
unique: !1
|
|
74
|
+
})),
|
|
75
|
+
...e.primaryKey === void 0 ? {} : { appliedPrimaryKey: e.primaryKey }
|
|
76
|
+
};
|
|
77
|
+
}, C = (e) => {
|
|
78
|
+
let t = new Map(e.tables.map((e) => [e.name, e]));
|
|
79
|
+
return e.tables.map((e) => S(e, t));
|
|
80
|
+
}, w = (e, t = "safe") => ({
|
|
81
|
+
op: e,
|
|
82
|
+
classification: t,
|
|
83
|
+
reason: "file migration schema operation"
|
|
84
|
+
}), T = "this context was built over an in-memory store without a `sql` client, and this needs a live connection. Run the migration against a database (the runner supplies the client), or keep a database-free test to `each`, `rewrite`, `expect`, `skipUnless`, `store`, `log`, `schema.tableExists`, `schema.columnExists` and a Promise body under `transactional`.", E = {
|
|
85
|
+
snapshot: "the in-memory store holds rows, not a catalog — it has no column types, defaults, indexes or constraints to report. `schema.tableExists` and `schema.columnExists` answer from the rows it holds; a body that needs the whole shape needs a database.",
|
|
86
|
+
indexExists: "the in-memory store has no indexes — every read of it is a scan — so there is no honest answer to give, and `false` would claim an index is absent from a store that could never have had one. Run this migration against a database, or guard on `schema.tableExists` / `schema.columnExists`, which the store can answer.",
|
|
87
|
+
foreignKeyExists: "the in-memory store has no constraints, so there is no honest answer to give, and `false` would claim a foreign key is absent from a store that could never have had one. Run this migration against a database, or guard on `schema.tableExists` / `schema.columnExists`, which the store can answer.",
|
|
88
|
+
evolve: T,
|
|
89
|
+
createTable: T,
|
|
90
|
+
dropTable: T,
|
|
91
|
+
renameTable: T,
|
|
92
|
+
addColumn: T,
|
|
93
|
+
alterColumn: T,
|
|
94
|
+
addIndex: T,
|
|
95
|
+
addForeignKey: T,
|
|
96
|
+
dropForeignKey: T,
|
|
97
|
+
dropIndex: T,
|
|
98
|
+
dropColumn: T,
|
|
99
|
+
renameColumn: T,
|
|
100
|
+
truncate: T
|
|
101
|
+
}, D = "this context was built over a store that cannot report the tables it holds (`DataStore.storedTables` is not implemented), so the question has no honest answer here. Use the in-memory store `@voltro/runtime` ships, or run the migration against a database.", O = (e, t) => {
|
|
102
|
+
let n = () => {
|
|
103
|
+
let e = t.storedTables;
|
|
104
|
+
if (e !== void 0) return new Map(e.call(t).map((e) => [e.table, e]));
|
|
105
|
+
}, r = (t, n) => y.fail(new u(e, t, n));
|
|
106
|
+
return {
|
|
107
|
+
tableExists: (e) => y.suspend(() => {
|
|
108
|
+
let t = n();
|
|
109
|
+
return t === void 0 ? r("schema.tableExists()", D) : y.succeed(t.has(e));
|
|
110
|
+
}),
|
|
111
|
+
columnExists: (e, t) => y.suspend(() => {
|
|
112
|
+
let i = n();
|
|
113
|
+
if (i === void 0) return r("schema.columnExists()", D);
|
|
114
|
+
let a = i.get(e);
|
|
115
|
+
return a === void 0 ? y.succeed(!1) : a.rowCount === 0 ? r(`schema.columnExists(${JSON.stringify(e)}, ${JSON.stringify(t)})`, `${e} is in this store and holds no rows, and a column exists in a store without a catalog only as a key on a row — so "no such column" and "no row has said yet" are the same observation, and answering either would be a guess. Seed ${e} with a row carrying the columns this migration guards on, or run the migration against a database.`) : y.succeed(a.columns.includes(t));
|
|
116
|
+
})
|
|
117
|
+
};
|
|
118
|
+
}, k = (e, t) => {
|
|
119
|
+
let n = { ...O(e, t) };
|
|
120
|
+
for (let [t, r] of Object.entries(E)) n[t] = () => y.fail(new u(e, `schema.${t}()`, r));
|
|
121
|
+
return n;
|
|
122
|
+
}, A = (e) => typeof e == "function" && typeof e.onDialectOrElse == "function", j = (e) => y.isEffect(e) ? e : y.tryPromise({
|
|
123
|
+
try: () => e,
|
|
124
|
+
catch: (e) => e
|
|
125
|
+
}), M = (e, t, n) => t.run === void 0 ? Promise.reject(new u(e, "transactional() with an Effect body", "an Effect returned from `transactional` runs ON the transaction's connection, and this store has none. Return a Promise body that uses the transaction's `store`, or run the migration against a database.")) : t.run(n), N = (e, t) => {
|
|
126
|
+
let n = e.run;
|
|
127
|
+
if (n === void 0) return t;
|
|
128
|
+
let r = {};
|
|
129
|
+
for (let [i, a] of Object.entries(t)) {
|
|
130
|
+
let t = a;
|
|
131
|
+
r[i] = (...r) => y.tryPromise({
|
|
132
|
+
try: () => n.call(e, t(...r)),
|
|
133
|
+
catch: (e) => e
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return r;
|
|
137
|
+
}, P = (e, t, n) => {
|
|
138
|
+
let r = () => h(e), i = (e, t) => e.tables.find((e) => e.name === t), a = (e, t, n) => i(e, t)?.columns.find((e) => e.name === n), o = (e) => y.succeed(e), s = (e) => new Map(e.tables.map((e) => [e.name, e])), c = (e, t) => {
|
|
139
|
+
let n = i(e, t);
|
|
140
|
+
if (n === void 0) throw Error(`schema: table ${t} does not exist`);
|
|
141
|
+
return n;
|
|
142
|
+
}, l = (n) => y.gen(function* () {
|
|
143
|
+
let i = () => r().pipe(y.map((e) => ({ tables: e.tables.filter((e) => e.name === n.tableName) }))), a = v(m({
|
|
144
|
+
declared: [n],
|
|
145
|
+
live: yield* i(),
|
|
146
|
+
dialect: t
|
|
147
|
+
})), o = a.operations.filter((e) => e.blocked !== void 0);
|
|
148
|
+
if (o.length > 0) return yield* y.fail(/* @__PURE__ */ Error(`evolve(${n.tableName}): ${o.length} operation(s) cannot be planned:\n` + o.map((e) => ` - ${e.op.kind}: ${e.blocked?.fix ?? e.reason}`).join("\n")));
|
|
149
|
+
for (let t of a.operations) yield* g(e, t, [n]);
|
|
150
|
+
let s = m({
|
|
151
|
+
declared: [n],
|
|
152
|
+
live: yield* i(),
|
|
153
|
+
dialect: t
|
|
154
|
+
});
|
|
155
|
+
return s.operations.length > 0 ? yield* y.fail(/* @__PURE__ */ Error(`evolve(${n.tableName}): applied ${a.operations.length} operation(s) and the re-plan still finds ${s.operations.length}: ${s.operations.map((e) => e.op.kind).join(", ")} — the migration did not converge`)) : { applied: a.operations.length };
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
snapshot: r,
|
|
159
|
+
evolve: l,
|
|
160
|
+
createTable: (e) => y.gen(function* () {
|
|
161
|
+
return (yield* r()).tables.some((t) => t.name === e.tableName) ? yield* o("already-present") : (yield* l(e), "applied");
|
|
162
|
+
}),
|
|
163
|
+
dropTable: (t) => y.gen(function* () {
|
|
164
|
+
return (yield* r()).tables.some((e) => e.name === t) ? (yield* g(e, w({
|
|
165
|
+
kind: "drop-table",
|
|
166
|
+
table: t
|
|
167
|
+
}, "lossy"), []), "applied") : yield* o("already-absent");
|
|
168
|
+
}),
|
|
169
|
+
renameTable: (t, n) => y.gen(function* () {
|
|
170
|
+
let a = yield* r(), s = i(a, t) !== void 0, l = i(a, n) !== void 0;
|
|
171
|
+
return !s && l ? yield* o("already-present") : s ? l ? yield* y.fail(/* @__PURE__ */ Error(`renameTable: ${n} already exists beside ${t}`)) : (yield* g(e, w({
|
|
172
|
+
kind: "rename-table",
|
|
173
|
+
from: t,
|
|
174
|
+
to: n
|
|
175
|
+
}), [S(c(a, t))]), "applied") : yield* y.fail(/* @__PURE__ */ Error(`renameTable: ${t} does not exist`));
|
|
176
|
+
}),
|
|
177
|
+
addColumn: (t, n, i) => y.gen(function* () {
|
|
178
|
+
let a = yield* r(), u = c(a, t);
|
|
179
|
+
if (u.columns.some((e) => e.name === n)) return yield* o("already-present");
|
|
180
|
+
let d = b(i);
|
|
181
|
+
if (!d.nullable && !d.hasDefault && d.backfill === void 0) {
|
|
182
|
+
let r = yield* e`SELECT COUNT(*) AS n FROM ${e(t)}`;
|
|
183
|
+
if (Number(r[0]?.n ?? 0) > 0) return yield* y.fail(/* @__PURE__ */ Error(`addColumn(${t}.${n}): a NOT NULL column without a default cannot be added to a table with rows on any dialect. Add it nullable or with a default, fill it (rewrite), then alterColumn(${JSON.stringify(t)}, ${JSON.stringify(n)}, { nullable: false }).`));
|
|
184
|
+
}
|
|
185
|
+
let f = S(u, s(a));
|
|
186
|
+
return yield* l({
|
|
187
|
+
...f,
|
|
188
|
+
fields: {
|
|
189
|
+
...f.fields,
|
|
190
|
+
[n]: d
|
|
191
|
+
}
|
|
192
|
+
}), "applied";
|
|
193
|
+
}),
|
|
194
|
+
alterColumn: (e, t, n) => y.gen(function* () {
|
|
195
|
+
let i = yield* r(), a = c(i, e), u = a.columns.find((e) => e.name === t);
|
|
196
|
+
if (u === void 0) return yield* y.fail(/* @__PURE__ */ Error(`alterColumn: ${e}.${t} does not exist`));
|
|
197
|
+
let d = n.default === void 0 || (n.default === null ? !u.hasDefault : u.hasDefault && String(u.defaultValue) === String(n.default));
|
|
198
|
+
if ((n.nullable === void 0 || n.nullable === u.nullable) && d) return yield* o("already-present");
|
|
199
|
+
let f = {
|
|
200
|
+
...u,
|
|
201
|
+
...n.nullable === void 0 ? {} : { nullable: n.nullable },
|
|
202
|
+
...n.default === void 0 ? {} : n.default === null ? {
|
|
203
|
+
hasDefault: !1,
|
|
204
|
+
defaultValue: void 0
|
|
205
|
+
} : {
|
|
206
|
+
hasDefault: !0,
|
|
207
|
+
defaultValue: n.default
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
return yield* l(S({
|
|
211
|
+
...a,
|
|
212
|
+
columns: a.columns.map((e) => e.name === t ? f : e)
|
|
213
|
+
}, s(i))), "applied";
|
|
214
|
+
}),
|
|
215
|
+
addIndex: (e, t, n = {}) => y.gen(function* () {
|
|
216
|
+
let i = yield* r(), a = c(i, e), u = n.unique === !0;
|
|
217
|
+
if (u && t.length === 1) {
|
|
218
|
+
let n = a.columns.find((e) => e.name === t[0]);
|
|
219
|
+
return n === void 0 ? yield* y.fail(/* @__PURE__ */ Error(`addIndex: ${e}.${t[0]} does not exist`)) : n.unique ? yield* o("already-present") : (yield* l(S({
|
|
220
|
+
...a,
|
|
221
|
+
columns: a.columns.map((e) => e.name === n.name ? {
|
|
222
|
+
...e,
|
|
223
|
+
unique: !0
|
|
224
|
+
} : e)
|
|
225
|
+
}, s(i))), "applied");
|
|
226
|
+
}
|
|
227
|
+
let d = n.name ?? `${e}_${t.join("_")}_idx`;
|
|
228
|
+
if (a.indexes.some((e) => e.name === d)) return yield* o("already-present");
|
|
229
|
+
let f = {
|
|
230
|
+
name: d,
|
|
231
|
+
table: e,
|
|
232
|
+
columns: t,
|
|
233
|
+
unique: u
|
|
234
|
+
};
|
|
235
|
+
return yield* l(S({
|
|
236
|
+
...a,
|
|
237
|
+
indexes: [...a.indexes, f]
|
|
238
|
+
}, s(i))), "applied";
|
|
239
|
+
}),
|
|
240
|
+
addForeignKey: (e, n, a, u = {}) => y.gen(function* () {
|
|
241
|
+
let d = yield* r(), f = c(d, e), p = f.columns.find((e) => e.name === n);
|
|
242
|
+
if (p === void 0) return yield* y.fail(/* @__PURE__ */ Error(`addForeignKey: ${e}.${n} does not exist`));
|
|
243
|
+
if (p.references !== void 0) return yield* o("already-present");
|
|
244
|
+
if (i(d, a.table) === void 0) return yield* y.fail(/* @__PURE__ */ Error(`addForeignKey: target table ${a.table} does not exist`));
|
|
245
|
+
let m = a.column ?? "id";
|
|
246
|
+
if ((t === "mysql" || t === "mariadb" || t === "mssql") && p.type === "text" && p.maxLength === void 0) return yield* y.fail(/* @__PURE__ */ Error(`addForeignKey(${e}.${n}): an unbounded text column cannot carry a FOREIGN KEY on ${t} — the referencing column must match the key's width. Add the column as \`reference(() => target)\` (addColumn), or give it \`text().maxLength(n)\` matching the key first.`));
|
|
247
|
+
let h = {
|
|
248
|
+
...p,
|
|
249
|
+
type: "reference",
|
|
250
|
+
references: {
|
|
251
|
+
table: a.table,
|
|
252
|
+
column: m,
|
|
253
|
+
onDelete: u.onDelete ?? "noAction",
|
|
254
|
+
onUpdate: u.onUpdate ?? "noAction"
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return yield* l(S({
|
|
258
|
+
...f,
|
|
259
|
+
columns: f.columns.map((e) => e.name === n ? h : e)
|
|
260
|
+
}, s(d))), "applied";
|
|
261
|
+
}),
|
|
262
|
+
truncate: (t) => y.gen(function* () {
|
|
263
|
+
return (yield* r()).tables.some((e) => e.name === t) ? (yield* e`DELETE FROM ${e(t)}`, "applied") : yield* o("already-absent");
|
|
264
|
+
}),
|
|
265
|
+
tableExists: (e) => r().pipe(y.map((t) => i(t, e) !== void 0)),
|
|
266
|
+
columnExists: (e, t) => r().pipe(y.map((n) => a(n, e, t) !== void 0)),
|
|
267
|
+
indexExists: (e, t) => r().pipe(y.map((n) => i(n, e)?.indexes.some((e) => e.name === t) ?? !1)),
|
|
268
|
+
foreignKeyExists: (e, t) => r().pipe(y.map((n) => a(n, e, t)?.references !== void 0)),
|
|
269
|
+
dropForeignKey: (t, n) => y.gen(function* () {
|
|
270
|
+
let i = yield* r(), s = a(i, t, n);
|
|
271
|
+
return s?.references === void 0 ? yield* o("already-absent") : (yield* _(e, {
|
|
272
|
+
kind: "drop-foreign-key",
|
|
273
|
+
table: t,
|
|
274
|
+
column: n,
|
|
275
|
+
...s.references.constraintName === void 0 ? {} : { name: s.references.constraintName }
|
|
276
|
+
}, i), "applied");
|
|
277
|
+
}),
|
|
278
|
+
dropIndex: (t, n) => y.gen(function* () {
|
|
279
|
+
let a = yield* r();
|
|
280
|
+
return i(a, t)?.indexes.some((e) => e.name === n) ?? !1 ? (yield* _(e, {
|
|
281
|
+
kind: "drop-index",
|
|
282
|
+
table: t,
|
|
283
|
+
index: n
|
|
284
|
+
}, a), "applied") : yield* o("already-absent");
|
|
285
|
+
}),
|
|
286
|
+
dropColumn: (t, n) => y.gen(function* () {
|
|
287
|
+
let s = yield* r(), c = a(s, t, n);
|
|
288
|
+
if (c === void 0) return yield* o("already-absent");
|
|
289
|
+
c.references !== void 0 && (yield* _(e, {
|
|
290
|
+
kind: "drop-foreign-key",
|
|
291
|
+
table: t,
|
|
292
|
+
column: n,
|
|
293
|
+
...c.references.constraintName === void 0 ? {} : { name: c.references.constraintName }
|
|
294
|
+
}, s), s = yield* r());
|
|
295
|
+
for (let r of i(s, t)?.indexes ?? []) r.columns.includes(n) && r.name !== `${t}_pkey` && (yield* _(e, {
|
|
296
|
+
kind: "drop-index",
|
|
297
|
+
table: t,
|
|
298
|
+
index: r.name
|
|
299
|
+
}, s));
|
|
300
|
+
return i(s, t)?.indexes.some((e) => e.columns.includes(n) && e.name !== `${t}_pkey`) && (s = yield* r()), yield* _(e, {
|
|
301
|
+
kind: "drop-column",
|
|
302
|
+
table: t,
|
|
303
|
+
column: n
|
|
304
|
+
}, s), "applied";
|
|
305
|
+
}),
|
|
306
|
+
renameColumn: (t, n, i) => y.gen(function* () {
|
|
307
|
+
let s = yield* r(), c = a(s, t, n) !== void 0, l = a(s, t, i) !== void 0;
|
|
308
|
+
return !c && l ? yield* o("already-present") : c ? l ? yield* y.fail(/* @__PURE__ */ Error(`renameColumn: ${t}.${i} already exists beside ${n} — a rename cannot merge two columns`)) : (yield* _(e, {
|
|
309
|
+
kind: "rename-column",
|
|
310
|
+
table: t,
|
|
311
|
+
from: n,
|
|
312
|
+
to: i
|
|
313
|
+
}, s), "applied") : yield* y.fail(/* @__PURE__ */ Error(`renameColumn: ${t}.${n} does not exist`));
|
|
314
|
+
})
|
|
315
|
+
};
|
|
316
|
+
}, F = (e, n, i, a) => {
|
|
317
|
+
let o;
|
|
318
|
+
return (async function* () {
|
|
319
|
+
for (;;) {
|
|
320
|
+
let s = [o === void 0 ? void 0 : r("id", o), a].filter((e) => e !== void 0), c = await e.query({
|
|
321
|
+
table: n,
|
|
322
|
+
predicate: s.length === 0 ? void 0 : s.length === 1 ? s[0] : t(...s),
|
|
323
|
+
order: [{
|
|
324
|
+
column: "id",
|
|
325
|
+
direction: "asc"
|
|
326
|
+
}],
|
|
327
|
+
take: i,
|
|
328
|
+
skip: void 0,
|
|
329
|
+
projection: void 0
|
|
330
|
+
});
|
|
331
|
+
if (c.length === 0) return;
|
|
332
|
+
let l = c[c.length - 1].id;
|
|
333
|
+
if (l == null) throw Error(`each/rewrite: ${n} rows carry no \`id\` — paging needs one`);
|
|
334
|
+
if (o !== void 0 && !(String(l) > String(o))) throw Error(`each/rewrite: paging ${n} did not advance past ${String(o)}`);
|
|
335
|
+
if (o = l, yield c, c.length < i) return;
|
|
336
|
+
}
|
|
337
|
+
})();
|
|
338
|
+
}, I = (e) => e > 0 ? y.sleep(`${e} millis`) : y.void, L = (e) => (t, n, r = {}) => y.gen(function* () {
|
|
339
|
+
let i = r.batchSize ?? 1e3, a = 0, o = 0, s = F(e, t, i);
|
|
340
|
+
for (;;) {
|
|
341
|
+
let e = yield* y.tryPromise({
|
|
342
|
+
try: () => s.next(),
|
|
343
|
+
catch: (e) => e
|
|
344
|
+
});
|
|
345
|
+
if (e.done) break;
|
|
346
|
+
o++;
|
|
347
|
+
for (let t of e.value) yield* j(n(t)), a++;
|
|
348
|
+
yield* I(r.sleepMs ?? 0);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
scanned: a,
|
|
352
|
+
pages: o
|
|
353
|
+
};
|
|
354
|
+
}), R = (e, t) => t == null ? i(e) : n(e, t), z = (e) => typeof e != "object" || !e || e instanceof Date, B = (e, t) => y.gen(function* () {
|
|
355
|
+
if (e === void 0) return [];
|
|
356
|
+
let n = (yield* h(e)).tables.find((e) => e.name === t)?.columns ?? [], r = new Set(n.map((e) => e.name)), i = a(t)?.fields;
|
|
357
|
+
return n.filter((e) => e.type === "timestamp" || e.type === "date" || i?.[e.name]?.type === "timestamp" || i?.[e.name]?.type === "date").map((e, t) => {
|
|
358
|
+
let n = `__voltro_rewrite_guard_${t}`;
|
|
359
|
+
for (; r.has(n);) n += "_";
|
|
360
|
+
return r.add(n), {
|
|
361
|
+
column: e.name,
|
|
362
|
+
alias: n
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}), V = (e, r, i, a) => (o, s, u = {}) => y.gen(function* () {
|
|
366
|
+
let d = u.batchSize ?? 1e3, p = u.maxPasses ?? 10, m = u.guard ?? [], h = yield* B(i, o), g = /* @__PURE__ */ new WeakMap(), _ = h.length === 0 || i === void 0 ? r : { query: async (t) => {
|
|
367
|
+
let n = l(t, i), s = h.map(({ column: e, alias: t }) => i`${i.onDialectOrElse({
|
|
368
|
+
mysql: () => i`CAST(${i(e)} AS CHAR)`,
|
|
369
|
+
mssql: () => i`CONVERT(VARCHAR(40), ${i(e)}, 127)`,
|
|
370
|
+
orElse: () => i`CAST(${i(e)} AS TEXT)`
|
|
371
|
+
})} AS ${i(t)}`);
|
|
372
|
+
return (await M(e, r, i`SELECT *, ${i.join(", ", !1)(s)} FROM (${n}) AS ${i("__voltro_rewrite_rows")} ORDER BY ${i("id")} ASC`)).map((e) => {
|
|
373
|
+
let t = { ...e }, n = /* @__PURE__ */ new Map();
|
|
374
|
+
for (let { column: e, alias: r } of h) n.set(e, t[r]), delete t[r];
|
|
375
|
+
let r = c([t], o, a)[0];
|
|
376
|
+
return g.set(r, n), r;
|
|
377
|
+
});
|
|
378
|
+
} }, v = {
|
|
379
|
+
passes: 0,
|
|
380
|
+
scanned: 0,
|
|
381
|
+
rewritten: 0,
|
|
382
|
+
contended: 0
|
|
383
|
+
}, b = v;
|
|
384
|
+
for (let e = 1; e <= p; e++) {
|
|
385
|
+
let i = 0, a = 0, c = 0, l = F(_, o, d);
|
|
386
|
+
for (;;) {
|
|
387
|
+
let e = yield* y.tryPromise({
|
|
388
|
+
try: () => l.next(),
|
|
389
|
+
catch: (e) => e
|
|
390
|
+
});
|
|
391
|
+
if (e.done) break;
|
|
392
|
+
for (let l of e.value) {
|
|
393
|
+
i++;
|
|
394
|
+
let e = yield* y.tryPromise({
|
|
395
|
+
try: async () => s(l),
|
|
396
|
+
catch: (e) => e
|
|
397
|
+
});
|
|
398
|
+
if (e === void 0) continue;
|
|
399
|
+
let u = Object.entries(e).filter(([e, t]) => !Object.is(l[e], t));
|
|
400
|
+
if (u.length === 0) continue;
|
|
401
|
+
let d = [n("id", l.id)], f = (e) => g.get(l)?.has(e) ? g.get(l).get(e) : l[e];
|
|
402
|
+
for (let [e] of u) z(l[e]) && d.push(R(e, f(e)));
|
|
403
|
+
for (let e of m) d.push(R(e, f(e)));
|
|
404
|
+
(yield* y.tryPromise({
|
|
405
|
+
try: () => r.updateMany(o, Object.fromEntries(u), { where: t(...d) }),
|
|
406
|
+
catch: (e) => e
|
|
407
|
+
})) === 1 ? a++ : c++;
|
|
408
|
+
}
|
|
409
|
+
yield* I(u.sleepMs ?? 0);
|
|
410
|
+
}
|
|
411
|
+
if (b = {
|
|
412
|
+
passes: e,
|
|
413
|
+
scanned: i,
|
|
414
|
+
rewritten: a,
|
|
415
|
+
contended: c
|
|
416
|
+
}, v = {
|
|
417
|
+
passes: e,
|
|
418
|
+
scanned: v.scanned + i,
|
|
419
|
+
rewritten: v.rewritten + a,
|
|
420
|
+
contended: v.contended + c
|
|
421
|
+
}, a === 0 && c === 0) return v;
|
|
422
|
+
}
|
|
423
|
+
return yield* y.fail(new f(o, b));
|
|
424
|
+
}), H = (e) => {
|
|
425
|
+
let { migrationId: t } = e, n = `migrations:file:${t}`;
|
|
426
|
+
if (e.sql !== void 0 && !A(e.sql)) throw new u(t, "makeFileMigrationContext()", `\`sql\` is not an effect/sql client (got ${typeof e.sql}). Pass the client the store executes on, or omit \`sql\` to drive the body over an in-memory store.`);
|
|
427
|
+
if (e.sql === void 0 && e.store.run !== void 0) throw new u(t, "makeFileMigrationContext()", "a SQL-backed store was given without `sql`. Its migration context runs `raw()`, `schema` and the guarded rewrite on the client the store executes on; pass that client, or use an in-memory store for a database-free test.");
|
|
428
|
+
let r = e.sql, i = [], a = (e) => {
|
|
429
|
+
if (typeof e != "string" || e.trim().length < 12) throw Error(`raw(reason): say why this migration needs raw SQL — the reason is what a reviewer and \`voltro doctor\` read (got ${JSON.stringify(e)})`);
|
|
430
|
+
if (r === void 0) throw new u(t, `raw(${JSON.stringify(e)})`, T);
|
|
431
|
+
return r;
|
|
432
|
+
};
|
|
433
|
+
return {
|
|
434
|
+
ctx: {
|
|
435
|
+
...((o) => ({
|
|
436
|
+
raw: a,
|
|
437
|
+
store: o,
|
|
438
|
+
dialect: e.dialect,
|
|
439
|
+
appliedAt: e.appliedAt,
|
|
440
|
+
log: {
|
|
441
|
+
info: (e, t) => y.logInfo(e).pipe(y.annotateLogs({
|
|
442
|
+
scope: n,
|
|
443
|
+
...t ?? {}
|
|
444
|
+
})),
|
|
445
|
+
warn: (e, t) => y.logWarning(e).pipe(y.annotateLogs({
|
|
446
|
+
scope: n,
|
|
447
|
+
...t ?? {}
|
|
448
|
+
}))
|
|
449
|
+
},
|
|
450
|
+
schema: r === void 0 ? k(t, o) : N(o, P(r, e.dialect, o)),
|
|
451
|
+
each: L(o),
|
|
452
|
+
rewrite: V(t, o, r, e.dialect),
|
|
453
|
+
expect: (e, t) => {
|
|
454
|
+
i.push({
|
|
455
|
+
description: e,
|
|
456
|
+
check: t
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
skipUnless: (e, t) => y.gen(function* () {
|
|
460
|
+
if (!(typeof e == "boolean" ? e : yield* j(e))) return yield* y.fail(new d(t));
|
|
461
|
+
})
|
|
462
|
+
}))(e.store),
|
|
463
|
+
transactional: (n) => y.tryPromise({
|
|
464
|
+
try: () => e.store.transactional(async (e) => {
|
|
465
|
+
let r = n({
|
|
466
|
+
store: e,
|
|
467
|
+
raw: a
|
|
468
|
+
});
|
|
469
|
+
return y.isEffect(r) ? M(t, e, r) : r;
|
|
470
|
+
}),
|
|
471
|
+
catch: (e) => e
|
|
472
|
+
})
|
|
473
|
+
},
|
|
474
|
+
expectations: i
|
|
475
|
+
};
|
|
476
|
+
}, U = (e) => y.gen(function* () {
|
|
477
|
+
let t = [];
|
|
478
|
+
for (let { description: n, check: r } of e) (yield* j(r())) || t.push(n);
|
|
479
|
+
return t;
|
|
480
|
+
});
|
|
481
|
+
//#endregion
|
|
482
|
+
export { U as failedExpectations, H as makeFileMigrationContext, C as materialiseSnapshot };
|