@intentius/sql-yodeler 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +112 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +29 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +59 -0
- package/dist/chant/apply.js +116 -0
- package/dist/chant/apply.js.map +1 -0
- package/dist/chant/build.d.ts +34 -0
- package/dist/chant/build.js +48 -0
- package/dist/chant/build.js.map +1 -0
- package/dist/chant/drift.d.ts +112 -0
- package/dist/chant/drift.js +238 -0
- package/dist/chant/drift.js.map +1 -0
- package/dist/chant/plan.d.ts +45 -0
- package/dist/chant/plan.js +61 -0
- package/dist/chant/plan.js.map +1 -0
- package/dist/chant/process.d.ts +41 -0
- package/dist/chant/process.js +87 -0
- package/dist/chant/process.js.map +1 -0
- package/dist/chant/runnable.d.ts +19 -0
- package/dist/chant/runnable.js +47 -0
- package/dist/chant/runnable.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +23 -0
- package/dist/command.js +13 -0
- package/dist/command.js.map +1 -0
- package/dist/commands/apply.d.ts +3 -0
- package/dist/commands/apply.js +426 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/approve.d.ts +5 -0
- package/dist/commands/approve.js +111 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/drift.d.ts +14 -0
- package/dist/commands/drift.js +267 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +28 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +139 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +202 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +50 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.js +292 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/override.d.ts +3 -0
- package/dist/commands/override.js +118 -0
- package/dist/commands/override.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +183 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/rebase.d.ts +3 -0
- package/dist/commands/rebase.js +167 -0
- package/dist/commands/rebase.js.map +1 -0
- package/dist/commands/repair.d.ts +14 -0
- package/dist/commands/repair.js +125 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/define.d.ts +148 -0
- package/dist/config/define.js +20 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +162 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +53 -0
- package/dist/config/settings.js +116 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +24 -0
- package/dist/env-args.js +92 -0
- package/dist/env-args.js.map +1 -0
- package/dist/exit.d.ts +20 -0
- package/dist/exit.js +28 -0
- package/dist/exit.js.map +1 -0
- package/dist/history/client.d.ts +25 -0
- package/dist/history/client.js +42 -0
- package/dist/history/client.js.map +1 -0
- package/dist/history/ddl.d.ts +58 -0
- package/dist/history/ddl.js +100 -0
- package/dist/history/ddl.js.map +1 -0
- package/dist/history/index.d.ts +25 -0
- package/dist/history/index.js +26 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/lock.d.ts +104 -0
- package/dist/history/lock.js +230 -0
- package/dist/history/lock.js.map +1 -0
- package/dist/history/postgres/ddl.d.ts +22 -0
- package/dist/history/postgres/ddl.js +47 -0
- package/dist/history/postgres/ddl.js.map +1 -0
- package/dist/history/postgres/index.d.ts +12 -0
- package/dist/history/postgres/index.js +13 -0
- package/dist/history/postgres/index.js.map +1 -0
- package/dist/history/postgres/lock.d.ts +48 -0
- package/dist/history/postgres/lock.js +95 -0
- package/dist/history/postgres/lock.js.map +1 -0
- package/dist/history/postgres/store.d.ts +39 -0
- package/dist/history/postgres/store.js +68 -0
- package/dist/history/postgres/store.js.map +1 -0
- package/dist/history/rows.d.ts +96 -0
- package/dist/history/rows.js +73 -0
- package/dist/history/rows.js.map +1 -0
- package/dist/history/store.d.ts +72 -0
- package/dist/history/store.js +134 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/topology.d.ts +41 -0
- package/dist/history/topology.js +54 -0
- package/dist/history/topology.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/init/adopt.d.ts +70 -0
- package/dist/init/adopt.js +143 -0
- package/dist/init/adopt.js.map +1 -0
- package/dist/init/baseline.d.ts +61 -0
- package/dist/init/baseline.js +88 -0
- package/dist/init/baseline.js.map +1 -0
- package/dist/init/files.d.ts +23 -0
- package/dist/init/files.js +77 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/index.d.ts +16 -0
- package/dist/init/index.js +17 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/live.d.ts +39 -0
- package/dist/init/live.js +68 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +88 -0
- package/dist/lint/check.js +227 -0
- package/dist/lint/check.js.map +1 -0
- package/dist/lint/config.d.ts +21 -0
- package/dist/lint/config.js +43 -0
- package/dist/lint/config.js.map +1 -0
- package/dist/lint/index.d.ts +31 -0
- package/dist/lint/index.js +43 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/project-rules.d.ts +19 -0
- package/dist/lint/project-rules.js +32 -0
- package/dist/lint/project-rules.js.map +1 -0
- package/dist/lint/report.d.ts +7 -0
- package/dist/lint/report.js +48 -0
- package/dist/lint/report.js.map +1 -0
- package/dist/lint/rules.d.ts +53 -0
- package/dist/lint/rules.js +42 -0
- package/dist/lint/rules.js.map +1 -0
- package/dist/lint/silences.d.ts +49 -0
- package/dist/lint/silences.js +91 -0
- package/dist/lint/silences.js.map +1 -0
- package/dist/lint/statements.d.ts +38 -0
- package/dist/lint/statements.js +120 -0
- package/dist/lint/statements.js.map +1 -0
- package/dist/mcp/server.d.ts +40 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +72 -0
- package/dist/mcp/tools.js +178 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/migrations/checks.d.ts +28 -0
- package/dist/migrations/checks.js +97 -0
- package/dist/migrations/checks.js.map +1 -0
- package/dist/migrations/checksum.d.ts +56 -0
- package/dist/migrations/checksum.js +72 -0
- package/dist/migrations/checksum.js.map +1 -0
- package/dist/migrations/create.d.ts +75 -0
- package/dist/migrations/create.js +120 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +229 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +106 -0
- package/dist/migrations/document.js +83 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +21 -0
- package/dist/migrations/index.js +22 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/pr/comment.d.ts +32 -0
- package/dist/pr/comment.js +99 -0
- package/dist/pr/comment.js.map +1 -0
- package/dist/pr/forge.d.ts +54 -0
- package/dist/pr/forge.js +119 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/issue.d.ts +68 -0
- package/dist/pr/issue.js +107 -0
- package/dist/pr/issue.js.map +1 -0
- package/dist/pr/model.d.ts +99 -0
- package/dist/pr/model.js +137 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +29 -0
- package/dist/pr/render.js +279 -0
- package/dist/pr/render.js.map +1 -0
- package/dist/project.d.ts +62 -0
- package/dist/project.js +92 -0
- package/dist/project.js.map +1 -0
- package/dist/rebase/index.d.ts +121 -0
- package/dist/rebase/index.js +390 -0
- package/dist/rebase/index.js.map +1 -0
- package/dist/rebase/repair.d.ts +41 -0
- package/dist/rebase/repair.js +68 -0
- package/dist/rebase/repair.js.map +1 -0
- package/dist/replay/clickhouse.d.ts +33 -0
- package/dist/replay/clickhouse.js +106 -0
- package/dist/replay/clickhouse.js.map +1 -0
- package/dist/replay/index.d.ts +39 -0
- package/dist/replay/index.js +57 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay/postgres.d.ts +31 -0
- package/dist/replay/postgres.js +140 -0
- package/dist/replay/postgres.js.map +1 -0
- package/dist/replay/replay.d.ts +114 -0
- package/dist/replay/replay.js +98 -0
- package/dist/replay/replay.js.map +1 -0
- package/dist/replay/report.d.ts +18 -0
- package/dist/replay/report.js +72 -0
- package/dist/replay/report.js.map +1 -0
- package/dist/replay/shared.d.ts +28 -0
- package/dist/replay/shared.js +77 -0
- package/dist/replay/shared.js.map +1 -0
- package/dist/schemas.d.ts +34 -0
- package/dist/schemas.js +38 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.js +87 -0
- package/dist/server.js.map +1 -0
- package/dist/steps/backfill.d.ts +132 -0
- package/dist/steps/backfill.js +307 -0
- package/dist/steps/backfill.js.map +1 -0
- package/dist/steps/index.d.ts +52 -0
- package/dist/steps/index.js +52 -0
- package/dist/steps/index.js.map +1 -0
- package/dist/steps/kinds.d.ts +101 -0
- package/dist/steps/kinds.js +143 -0
- package/dist/steps/kinds.js.map +1 -0
- package/dist/steps/postgres-migration.d.ts +95 -0
- package/dist/steps/postgres-migration.js +149 -0
- package/dist/steps/postgres-migration.js.map +1 -0
- package/dist/steps/rebuild.d.ts +97 -0
- package/dist/steps/rebuild.js +155 -0
- package/dist/steps/rebuild.js.map +1 -0
- package/dist/steps/run.d.ts +45 -0
- package/dist/steps/run.js +72 -0
- package/dist/steps/run.js.map +1 -0
- package/dist/topology/render.d.ts +17 -0
- package/dist/topology/render.js +17 -0
- package/dist/topology/render.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/versioned/approval.d.ts +35 -0
- package/dist/versioned/approval.js +53 -0
- package/dist/versioned/approval.js.map +1 -0
- package/dist/versioned/checks.d.ts +29 -0
- package/dist/versioned/checks.js +54 -0
- package/dist/versioned/checks.js.map +1 -0
- package/dist/versioned/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +42 -0
- package/dist/versioned/clickhouse.js.map +1 -0
- package/dist/versioned/digest.d.ts +77 -0
- package/dist/versioned/digest.js +69 -0
- package/dist/versioned/digest.js.map +1 -0
- package/dist/versioned/driver.d.ts +51 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +206 -0
- package/dist/versioned/engine.js +370 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +294 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +46 -0
- package/dist/versioned/index.js +77 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +72 -0
- package/dist/versioned/op.js +185 -0
- package/dist/versioned/op.js.map +1 -0
- package/dist/versioned/policy.d.ts +67 -0
- package/dist/versioned/policy.js +161 -0
- package/dist/versioned/policy.js.map +1 -0
- package/dist/versioned/postgres/catalog.d.ts +33 -0
- package/dist/versioned/postgres/catalog.js +34 -0
- package/dist/versioned/postgres/catalog.js.map +1 -0
- package/dist/versioned/postgres/driver.d.ts +71 -0
- package/dist/versioned/postgres/driver.js +140 -0
- package/dist/versioned/postgres/driver.js.map +1 -0
- package/dist/versioned/postgres/executor.d.ts +104 -0
- package/dist/versioned/postgres/executor.js +172 -0
- package/dist/versioned/postgres/executor.js.map +1 -0
- package/dist/versioned/postgres/index.d.ts +11 -0
- package/dist/versioned/postgres/index.js +12 -0
- package/dist/versioned/postgres/index.js.map +1 -0
- package/dist/versioned/postgres/waits.d.ts +46 -0
- package/dist/versioned/postgres/waits.js +78 -0
- package/dist/versioned/postgres/waits.js.map +1 -0
- package/dist/versioned/report.d.ts +25 -0
- package/dist/versioned/report.js +84 -0
- package/dist/versioned/report.js.map +1 -0
- package/dist/versioned/stand-down.d.ts +30 -0
- package/dist/versioned/stand-down.js +56 -0
- package/dist/versioned/stand-down.js.map +1 -0
- package/dist/versioned/state.d.ts +108 -0
- package/dist/versioned/state.js +153 -0
- package/dist/versioned/state.js.map +1 -0
- package/package.json +61 -4
- package/schemas/apply.schema.json +269 -0
- package/schemas/common.schema.json +213 -0
- package/schemas/drift.schema.json +36 -0
- package/schemas/init.schema.json +31 -0
- package/schemas/lint-rules.schema.json +54 -0
- package/schemas/lint.schema.json +290 -0
- package/schemas/mcp.schema.json +70 -0
- package/schemas/new.schema.json +71 -0
- package/schemas/plan.schema.json +371 -0
- package/schemas/rebase.schema.json +54 -0
- package/schemas/repair.schema.json +20 -0
- package/schemas/status.schema.json +310 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-migration checks (#52): what `checks` in `migration.json` may hold,
|
|
3
|
+
* and whether a query's rows pass one. Pure; `../versioned/checks.ts` runs
|
|
4
|
+
* them against a server.
|
|
5
|
+
*
|
|
6
|
+
* A check is part of `migration.json`, so of the migration's checksum and of
|
|
7
|
+
* the plan digest: editing, adding or removing one moves the digest, and an
|
|
8
|
+
* approval given before does not carry over.
|
|
9
|
+
*/
|
|
10
|
+
const NAME = /^[a-z0-9][a-z0-9_-]*$/;
|
|
11
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12
|
+
/** What is wrong with a `checks` value read from `migration.json`, or `undefined` when it is well formed. */
|
|
13
|
+
export function checksProblem(value) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!Array.isArray(value))
|
|
17
|
+
return "checks is not an array";
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
for (const [i, c] of value.entries()) {
|
|
20
|
+
const at = `checks[${String(i)}]`;
|
|
21
|
+
if (!isRecord(c))
|
|
22
|
+
return `${at} is not an object`;
|
|
23
|
+
if (typeof c.name !== "string" || !NAME.test(c.name))
|
|
24
|
+
return `${at}.name must be lowercase letters, digits, - and _, got ${JSON.stringify(c.name)}`;
|
|
25
|
+
if (seen.has(c.name))
|
|
26
|
+
return `two checks are named ${c.name}`;
|
|
27
|
+
seen.add(c.name);
|
|
28
|
+
if (typeof c.sql !== "string" || c.sql.trim() === "")
|
|
29
|
+
return `${at} (${c.name}) has no sql`;
|
|
30
|
+
const e = c.expect;
|
|
31
|
+
if (e === undefined || e === "empty" || e === "true")
|
|
32
|
+
continue;
|
|
33
|
+
if (!isRecord(e))
|
|
34
|
+
return `${at} (${c.name}): expect must be "empty", "true" or { "min"?: n, "max"?: n }, got ${JSON.stringify(e)}`;
|
|
35
|
+
const extra = Object.keys(e).filter((k) => k !== "min" && k !== "max");
|
|
36
|
+
if (extra.length > 0 || (e.min === undefined && e.max === undefined))
|
|
37
|
+
return `${at} (${c.name}): expect { "min"?: n, "max"?: n } takes min, max or both, got ${JSON.stringify(e)}`;
|
|
38
|
+
for (const k of ["min", "max"]) {
|
|
39
|
+
if (e[k] !== undefined && (typeof e[k] !== "number" || !Number.isFinite(e[k])))
|
|
40
|
+
return `${at} (${c.name}): expect.${k} must be a number, got ${JSON.stringify(e[k])}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/** What a check expects, in words. */
|
|
46
|
+
export function describeExpect(check) {
|
|
47
|
+
const e = check.expect ?? "empty";
|
|
48
|
+
if (e === "empty")
|
|
49
|
+
return "returns no rows";
|
|
50
|
+
if (e === "true")
|
|
51
|
+
return "returns true";
|
|
52
|
+
if (e.min !== undefined && e.max !== undefined)
|
|
53
|
+
return `returns a number from ${String(e.min)} to ${String(e.max)}`;
|
|
54
|
+
return e.min !== undefined ? `returns a number of at least ${String(e.min)}` : `returns a number of at most ${String(e.max ?? 0)}`;
|
|
55
|
+
}
|
|
56
|
+
const firstValue = (row) => (row ? Object.values(row)[0] : undefined);
|
|
57
|
+
const asNumber = (v) => {
|
|
58
|
+
if (typeof v === "number")
|
|
59
|
+
return v;
|
|
60
|
+
if (typeof v === "bigint")
|
|
61
|
+
return Number(v);
|
|
62
|
+
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)))
|
|
63
|
+
return Number(v);
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
const shown = (v) => (v === undefined ? "nothing" : typeof v === "bigint" ? v.toString() : JSON.stringify(v));
|
|
67
|
+
/** Whether `rows`, what the check's query returned, pass it. */
|
|
68
|
+
export function judgeCheck(check, rows) {
|
|
69
|
+
const e = check.expect ?? "empty";
|
|
70
|
+
const n = rows.length;
|
|
71
|
+
const count = `${String(n)} row${n === 1 ? "" : "s"}`;
|
|
72
|
+
if (e === "empty") {
|
|
73
|
+
return { name: check.name, passed: n === 0, detail: n === 0 ? "no rows" : `${count}, the first ${JSON.stringify(rows[0])}` };
|
|
74
|
+
}
|
|
75
|
+
if (n !== 1)
|
|
76
|
+
return { name: check.name, passed: false, detail: `${count}, not one` };
|
|
77
|
+
const v = firstValue(rows[0]);
|
|
78
|
+
if (e === "true") {
|
|
79
|
+
const passed = v === true || v === 1 || v === "1" || v === "true" || v === "t";
|
|
80
|
+
return { name: check.name, passed, detail: shown(v) };
|
|
81
|
+
}
|
|
82
|
+
const x = asNumber(v);
|
|
83
|
+
if (x === undefined)
|
|
84
|
+
return { name: check.name, passed: false, detail: `${shown(v)}, not a number` };
|
|
85
|
+
const passed = (e.min === undefined || x >= e.min) && (e.max === undefined || x <= e.max);
|
|
86
|
+
return { name: check.name, passed, detail: String(x) };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A `--check` argument: `<sql>`, or `<name>: <sql>`; the name defaults to
|
|
90
|
+
* `check-<n>`, n counting from 1. The check expects no rows.
|
|
91
|
+
*/
|
|
92
|
+
export function parseCheckArg(arg, n) {
|
|
93
|
+
const named = /^([a-z0-9][a-z0-9_-]*):\s+([\s\S]+)$/.exec(arg.trim());
|
|
94
|
+
const check = named ? { name: named[1] ?? "", sql: (named[2] ?? "").trim() } : { name: `check-${String(n)}`, sql: arg.trim() };
|
|
95
|
+
return check;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/migrations/checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A migration's checksum: covers that migration's own files and nothing
|
|
3
|
+
* else, so editing, adding or removing another migration never changes it.
|
|
4
|
+
*
|
|
5
|
+
* Exactly:
|
|
6
|
+
*
|
|
7
|
+
* sql = migration.sql, with CRLF and lone CR turned into LF
|
|
8
|
+
* json = migration.json parsed, its top-level "checksum" removed, then
|
|
9
|
+
* written as JSON with every object's keys sorted and no whitespace
|
|
10
|
+
* checksum = "sha256:" + lowercase hex of SHA-256 over the UTF-8 bytes of
|
|
11
|
+
* "yodel-migration-checksum-v1\n"
|
|
12
|
+
* + "migration.sql " + byteLength(sql) + "\n" + sql
|
|
13
|
+
* + "migration.json " + byteLength(json) + "\n" + json
|
|
14
|
+
* + for each other file in the migration's directory, by path:
|
|
15
|
+
* path + " " + byteLength(text) + "\n" + text
|
|
16
|
+
*
|
|
17
|
+
* The other files are a migration's own besides the two, such as a backfill
|
|
18
|
+
* step's module (`backfill.ts`, see `../steps/backfill.ts`): every regular
|
|
19
|
+
* file under its directory, at any depth, except `migration.sql`,
|
|
20
|
+
* `migration.json` and anything whose name starts with `.`. `path` is
|
|
21
|
+
* relative to the directory with `/` separators; paths are taken in code
|
|
22
|
+
* point order, and each file's text has its line endings normalized as
|
|
23
|
+
* migration.sql's are. A migration with no other files hashes exactly as it
|
|
24
|
+
* did before they counted, so no existing checksum changes.
|
|
25
|
+
*
|
|
26
|
+
* The lengths frame each file, so no text can move from one file into the
|
|
27
|
+
* other and keep the hash. Because migration.json is hashed as parsed JSON,
|
|
28
|
+
* its whitespace, key order and line endings do not count; every value in it
|
|
29
|
+
* does, the parent id and the recorded schema included. Whitespace in
|
|
30
|
+
* migration.sql counts, except line endings.
|
|
31
|
+
*/
|
|
32
|
+
import { type MigrationDocument, type MigrationFiles } from "./document.js";
|
|
33
|
+
export declare const CHECKSUM_PREFIX = "sha256:";
|
|
34
|
+
/** The canonical JSON of a migration document, without its checksum: what the checksum hashes for `migration.json`. */
|
|
35
|
+
export declare function canonicalDocument(doc: MigrationDocument | Record<string, unknown>): string;
|
|
36
|
+
/**
|
|
37
|
+
* The checksum of a migration's files. `json` may be the file's text or the
|
|
38
|
+
* document already parsed; whatever `checksum` it holds is ignored. `extra`
|
|
39
|
+
* is the migration's other files, by path (see above).
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeChecksum(files: {
|
|
42
|
+
sql: string;
|
|
43
|
+
json: string | MigrationDocument | Record<string, unknown>;
|
|
44
|
+
extra?: Readonly<Record<string, string>> | undefined;
|
|
45
|
+
}): string;
|
|
46
|
+
export interface ChecksumCheck {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
/** The checksum `migration.json` records. */
|
|
49
|
+
recorded: string;
|
|
50
|
+
/** The checksum of the files as they are. */
|
|
51
|
+
actual: string;
|
|
52
|
+
}
|
|
53
|
+
/** Whether a migration's files still hash to the checksum its `migration.json` records. */
|
|
54
|
+
export declare function verifyChecksum(m: {
|
|
55
|
+
files: MigrationFiles;
|
|
56
|
+
} | MigrationFiles): ChecksumCheck;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A migration's checksum: covers that migration's own files and nothing
|
|
3
|
+
* else, so editing, adding or removing another migration never changes it.
|
|
4
|
+
*
|
|
5
|
+
* Exactly:
|
|
6
|
+
*
|
|
7
|
+
* sql = migration.sql, with CRLF and lone CR turned into LF
|
|
8
|
+
* json = migration.json parsed, its top-level "checksum" removed, then
|
|
9
|
+
* written as JSON with every object's keys sorted and no whitespace
|
|
10
|
+
* checksum = "sha256:" + lowercase hex of SHA-256 over the UTF-8 bytes of
|
|
11
|
+
* "yodel-migration-checksum-v1\n"
|
|
12
|
+
* + "migration.sql " + byteLength(sql) + "\n" + sql
|
|
13
|
+
* + "migration.json " + byteLength(json) + "\n" + json
|
|
14
|
+
* + for each other file in the migration's directory, by path:
|
|
15
|
+
* path + " " + byteLength(text) + "\n" + text
|
|
16
|
+
*
|
|
17
|
+
* The other files are a migration's own besides the two, such as a backfill
|
|
18
|
+
* step's module (`backfill.ts`, see `../steps/backfill.ts`): every regular
|
|
19
|
+
* file under its directory, at any depth, except `migration.sql`,
|
|
20
|
+
* `migration.json` and anything whose name starts with `.`. `path` is
|
|
21
|
+
* relative to the directory with `/` separators; paths are taken in code
|
|
22
|
+
* point order, and each file's text has its line endings normalized as
|
|
23
|
+
* migration.sql's are. A migration with no other files hashes exactly as it
|
|
24
|
+
* did before they counted, so no existing checksum changes.
|
|
25
|
+
*
|
|
26
|
+
* The lengths frame each file, so no text can move from one file into the
|
|
27
|
+
* other and keep the hash. Because migration.json is hashed as parsed JSON,
|
|
28
|
+
* its whitespace, key order and line endings do not count; every value in it
|
|
29
|
+
* does, the parent id and the recorded schema included. Whitespace in
|
|
30
|
+
* migration.sql counts, except line endings.
|
|
31
|
+
*/
|
|
32
|
+
import { createHash } from "node:crypto";
|
|
33
|
+
import { MIGRATION_JSON, MIGRATION_SQL, normalizeLineEndings, sortKeys } from "./document.js";
|
|
34
|
+
export const CHECKSUM_PREFIX = "sha256:";
|
|
35
|
+
const DOMAIN = "yodel-migration-checksum-v1\n";
|
|
36
|
+
/** The canonical JSON of a migration document, without its checksum: what the checksum hashes for `migration.json`. */
|
|
37
|
+
export function canonicalDocument(doc) {
|
|
38
|
+
const rest = { ...doc };
|
|
39
|
+
delete rest.checksum;
|
|
40
|
+
return JSON.stringify(sortKeys(rest));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The checksum of a migration's files. `json` may be the file's text or the
|
|
44
|
+
* document already parsed; whatever `checksum` it holds is ignored. `extra`
|
|
45
|
+
* is the migration's other files, by path (see above).
|
|
46
|
+
*/
|
|
47
|
+
export function computeChecksum(files) {
|
|
48
|
+
const sql = normalizeLineEndings(files.sql);
|
|
49
|
+
const doc = typeof files.json === "string" ? JSON.parse(files.json) : files.json;
|
|
50
|
+
const json = canonicalDocument(doc);
|
|
51
|
+
const hash = createHash("sha256");
|
|
52
|
+
hash.update(DOMAIN, "utf8");
|
|
53
|
+
hash.update(`${MIGRATION_SQL} ${String(Buffer.byteLength(sql, "utf8"))}\n`, "utf8");
|
|
54
|
+
hash.update(sql, "utf8");
|
|
55
|
+
hash.update(`${MIGRATION_JSON} ${String(Buffer.byteLength(json, "utf8"))}\n`, "utf8");
|
|
56
|
+
hash.update(json, "utf8");
|
|
57
|
+
for (const path of Object.keys(files.extra ?? {}).sort()) {
|
|
58
|
+
const text = normalizeLineEndings(files.extra?.[path] ?? "");
|
|
59
|
+
hash.update(`${path} ${String(Buffer.byteLength(text, "utf8"))}\n`, "utf8");
|
|
60
|
+
hash.update(text, "utf8");
|
|
61
|
+
}
|
|
62
|
+
return `${CHECKSUM_PREFIX}${hash.digest("hex")}`;
|
|
63
|
+
}
|
|
64
|
+
/** Whether a migration's files still hash to the checksum its `migration.json` records. */
|
|
65
|
+
export function verifyChecksum(m) {
|
|
66
|
+
const files = "files" in m ? m.files : m;
|
|
67
|
+
const doc = JSON.parse(files.json);
|
|
68
|
+
const recorded = typeof doc.checksum === "string" ? doc.checksum : "";
|
|
69
|
+
const actual = computeChecksum(files);
|
|
70
|
+
return { ok: recorded === actual, recorded, actual };
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=checksum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checksum.js","sourceRoot":"","sources":["../../src/migrations/checksum.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Making the next migration, offline: the newest migration's recorded
|
|
3
|
+
* schema (or an empty schema, for the first) diffed against the current
|
|
4
|
+
* build with chant's `diffStatements`. No server is read. The statements
|
|
5
|
+
* are written for a single node and the migration records no topology: it
|
|
6
|
+
* is rendered for each environment's topology when it is applied (#9). Pure: nothing
|
|
7
|
+
* here touches the disk; `yodel new` writes what it returns with
|
|
8
|
+
* `writeMigration`.
|
|
9
|
+
*/
|
|
10
|
+
import { type DiffStatementsOptions, type SchemaStatements } from "@intentius/chant-lexicon-sql/migration-statements";
|
|
11
|
+
import type { BuildOutput } from "../chant/build.js";
|
|
12
|
+
import { type MigrationCheck, type MigrationDocument, type MigrationFiles, type MigrationStep } from "./document.js";
|
|
13
|
+
import { type Migration } from "./directory.js";
|
|
14
|
+
export interface CreateMigrationOptions {
|
|
15
|
+
/** The migration's name, the part of its id after the timestamp. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** The current `chant build` output: the schema after the migration. */
|
|
18
|
+
build: BuildOutput;
|
|
19
|
+
/** The newest migration, which the new one follows; `undefined` for the first. */
|
|
20
|
+
previous?: Migration | undefined;
|
|
21
|
+
/** When it is made. Default: now. Its UTC minute is the id's timestamp. */
|
|
22
|
+
now?: Date;
|
|
23
|
+
/** The ownership marker each `CREATE` carries. Default: chant's, `managed-by=chant`. */
|
|
24
|
+
marker?: DiffStatementsOptions["marker"];
|
|
25
|
+
/** ClickHouse: the database a bare name means. Default: chant's, `default`. */
|
|
26
|
+
defaultDatabase?: string;
|
|
27
|
+
/** Postgres: the schema a bare name means. Default: chant's, `public`. */
|
|
28
|
+
defaultSchema?: string;
|
|
29
|
+
/**
|
|
30
|
+
* A backfill step to end the migration with (#12): the module's text,
|
|
31
|
+
* written into the migration's directory as `file` (default `backfill.ts`)
|
|
32
|
+
* and counted in its checksum. With one, a migration is made even when the
|
|
33
|
+
* build has no schema change.
|
|
34
|
+
*/
|
|
35
|
+
backfill?: {
|
|
36
|
+
text: string;
|
|
37
|
+
file?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Pre-migration checks (#52), written to `migration.json`. */
|
|
40
|
+
checks?: MigrationCheck[];
|
|
41
|
+
}
|
|
42
|
+
export interface CreatedMigration {
|
|
43
|
+
id: string;
|
|
44
|
+
document: MigrationDocument;
|
|
45
|
+
files: MigrationFiles;
|
|
46
|
+
statements: SchemaStatements;
|
|
47
|
+
}
|
|
48
|
+
/** What {@link createMigration} returns when the build has no change from the recorded schema. */
|
|
49
|
+
export interface NoChange {
|
|
50
|
+
id: undefined;
|
|
51
|
+
statements: SchemaStatements;
|
|
52
|
+
}
|
|
53
|
+
/** `YYYYMMDDTHHMM` in UTC. */
|
|
54
|
+
export declare function migrationTimestamp(now: Date): string;
|
|
55
|
+
/** `<YYYYMMDDTHHMM>-<name>`, or an error for a name that is not lowercase words joined by `-` or `_`. */
|
|
56
|
+
export declare function migrationId(name: string, now: Date): string;
|
|
57
|
+
/**
|
|
58
|
+
* The schema before the first migration: a build of the same dialect with no
|
|
59
|
+
* objects. `diffStatements` takes two build outputs and has no form for
|
|
60
|
+
* "nothing", so this is one. A Postgres build's `postgresMajor` is kept, so
|
|
61
|
+
* both sides classify for the same major.
|
|
62
|
+
*/
|
|
63
|
+
export declare function emptyBuild(like: BuildOutput): BuildOutput;
|
|
64
|
+
/** The steps that are not statements: each Op or manual step, which a person has to act on. */
|
|
65
|
+
export declare const refusedSteps: (steps: readonly MigrationStep[]) => Exclude<MigrationStep, {
|
|
66
|
+
kind: "statement";
|
|
67
|
+
}>[];
|
|
68
|
+
/** `migration.sql`: a header naming the migration, its parent, any Op or manual step and the diff's hints, then chant's rendered statements. */
|
|
69
|
+
export declare function renderMigrationSql(doc: Pick<MigrationDocument, "id" | "parent" | "hints">, statements: SchemaStatements): string;
|
|
70
|
+
/**
|
|
71
|
+
* The next migration, or a {@link NoChange} when the build declares the
|
|
72
|
+
* schema the previous migration recorded. Throws a YodelError when the
|
|
73
|
+
* recorded schema and the build are different dialects.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createMigration(options: CreateMigrationOptions): CreatedMigration | NoChange;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Making the next migration, offline: the newest migration's recorded
|
|
3
|
+
* schema (or an empty schema, for the first) diffed against the current
|
|
4
|
+
* build with chant's `diffStatements`. No server is read. The statements
|
|
5
|
+
* are written for a single node and the migration records no topology: it
|
|
6
|
+
* is rendered for each environment's topology when it is applied (#9). Pure: nothing
|
|
7
|
+
* here touches the disk; `yodel new` writes what it returns with
|
|
8
|
+
* `writeMigration`.
|
|
9
|
+
*/
|
|
10
|
+
import { diffStatements, renderStatements } from "@intentius/chant-lexicon-sql/migration-statements";
|
|
11
|
+
import { YodelError } from "../project.js";
|
|
12
|
+
import { computeChecksum } from "./checksum.js";
|
|
13
|
+
import { MIGRATION_FORMAT, serializeDocument } from "./document.js";
|
|
14
|
+
import { MIGRATION_NAME } from "./directory.js";
|
|
15
|
+
import { asBackfill, BACKFILL_FILE } from "../steps/kinds.js";
|
|
16
|
+
import { backfillStep } from "../steps/backfill.js";
|
|
17
|
+
/** `YYYYMMDDTHHMM` in UTC. */
|
|
18
|
+
export function migrationTimestamp(now) {
|
|
19
|
+
const iso = now.toISOString(); // 2026-10-09T14:12:34.567Z
|
|
20
|
+
return `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}T${iso.slice(11, 13)}${iso.slice(14, 16)}`;
|
|
21
|
+
}
|
|
22
|
+
/** `<YYYYMMDDTHHMM>-<name>`, or an error for a name that is not lowercase words joined by `-` or `_`. */
|
|
23
|
+
export function migrationId(name, now) {
|
|
24
|
+
if (!MIGRATION_NAME.test(name)) {
|
|
25
|
+
throw new YodelError(`migration name "${name}": use lowercase letters and digits, words joined by - or _ (e.g. add-country)`);
|
|
26
|
+
}
|
|
27
|
+
return `${migrationTimestamp(now)}-${name}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The schema before the first migration: a build of the same dialect with no
|
|
31
|
+
* objects. `diffStatements` takes two build outputs and has no form for
|
|
32
|
+
* "nothing", so this is one. A Postgres build's `postgresMajor` is kept, so
|
|
33
|
+
* both sides classify for the same major.
|
|
34
|
+
*/
|
|
35
|
+
export function emptyBuild(like) {
|
|
36
|
+
return {
|
|
37
|
+
dialect: like.dialect,
|
|
38
|
+
objects: [],
|
|
39
|
+
...(typeof like.postgresMajor === "number" ? { postgresMajor: like.postgresMajor } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The steps that are not statements: each Op or manual step, which a person has to act on. */
|
|
43
|
+
export const refusedSteps = (steps) => steps.filter((s) => s.kind !== "statement");
|
|
44
|
+
function describeRefused(s) {
|
|
45
|
+
const backfill = asBackfill(s);
|
|
46
|
+
if (backfill)
|
|
47
|
+
return `a backfill: the Op ${backfill.options.file} exports, its effect() batches resumable from their receipts`;
|
|
48
|
+
const rules = s.changes.map((c) => c.rule).join(", ");
|
|
49
|
+
return s.kind === "op"
|
|
50
|
+
? `${s.op} for ${s.object} (${s.name}), ${rules}: not SQL; migration.json holds its declaration`
|
|
51
|
+
: `a manual step for ${s.object} (${s.name}), ${rules}: no statement and no Op makes it`;
|
|
52
|
+
}
|
|
53
|
+
/** `migration.sql`: a header naming the migration, its parent, any Op or manual step and the diff's hints, then chant's rendered statements. */
|
|
54
|
+
export function renderMigrationSql(doc, statements) {
|
|
55
|
+
const head = [`-- yodel migration ${doc.id}`, `-- parent: ${doc.parent ?? "none (the first migration)"}`];
|
|
56
|
+
const refused = refusedSteps(statements.steps);
|
|
57
|
+
if (refused.length > 0) {
|
|
58
|
+
head.push(`-- This migration contains ${refused.some((s) => s.kind === "op") ? "an Op step" : "a manual step"}, not only SQL:`);
|
|
59
|
+
for (const s of refused)
|
|
60
|
+
head.push(`-- ${describeRefused(s)}`);
|
|
61
|
+
}
|
|
62
|
+
for (const h of doc.hints)
|
|
63
|
+
head.push(`-- hint: ${h.replace(/\r?\n/g, " ")}`);
|
|
64
|
+
const body = statements.steps
|
|
65
|
+
.map((s) => {
|
|
66
|
+
const backfill = asBackfill(s);
|
|
67
|
+
return backfill ? `-- ${backfill.object} (${backfill.name}): made by the Op ${backfill.options.file} exports, not a statement\n` : renderStatements({ ...statements, steps: [s] });
|
|
68
|
+
})
|
|
69
|
+
.join("\n")
|
|
70
|
+
.replace(/\n+$/, "");
|
|
71
|
+
return `${head.join("\n")}\n\n${body}\n`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The next migration, or a {@link NoChange} when the build declares the
|
|
75
|
+
* schema the previous migration recorded. Throws a YodelError when the
|
|
76
|
+
* recorded schema and the build are different dialects.
|
|
77
|
+
*/
|
|
78
|
+
export function createMigration(options) {
|
|
79
|
+
const now = options.now ?? new Date();
|
|
80
|
+
const id = migrationId(options.name, now);
|
|
81
|
+
const before = options.previous?.document.schema ?? emptyBuild(options.build);
|
|
82
|
+
let statements;
|
|
83
|
+
try {
|
|
84
|
+
statements = diffStatements(before, options.build, {
|
|
85
|
+
...(options.marker ? { marker: options.marker } : {}),
|
|
86
|
+
...(options.defaultDatabase !== undefined ? { defaultDatabase: options.defaultDatabase } : {}),
|
|
87
|
+
...(options.defaultSchema !== undefined ? { defaultSchema: options.defaultSchema } : {}),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
throw new YodelError(e instanceof Error ? e.message : String(e));
|
|
92
|
+
}
|
|
93
|
+
if (options.backfill) {
|
|
94
|
+
const file = options.backfill.file ?? BACKFILL_FILE;
|
|
95
|
+
statements = { ...statements, refused: true, steps: [...statements.steps, backfillStep(options.name, file)] };
|
|
96
|
+
}
|
|
97
|
+
if (statements.steps.length === 0)
|
|
98
|
+
return { id: undefined, statements };
|
|
99
|
+
const unsigned = {
|
|
100
|
+
format: MIGRATION_FORMAT,
|
|
101
|
+
id,
|
|
102
|
+
parent: options.previous?.id ?? null,
|
|
103
|
+
checksum: "",
|
|
104
|
+
createdAt: `${now.toISOString().slice(0, 19)}Z`,
|
|
105
|
+
dialect: statements.dialect,
|
|
106
|
+
...(statements.defaultDatabase !== undefined ? { defaultDatabase: statements.defaultDatabase } : {}),
|
|
107
|
+
...(statements.defaultSchema !== undefined ? { defaultSchema: statements.defaultSchema } : {}),
|
|
108
|
+
...(statements.major !== undefined ? { major: statements.major } : {}),
|
|
109
|
+
refused: statements.refused,
|
|
110
|
+
hints: statements.hints,
|
|
111
|
+
...(options.checks && options.checks.length > 0 ? { checks: options.checks } : {}),
|
|
112
|
+
steps: statements.steps,
|
|
113
|
+
schema: options.build,
|
|
114
|
+
};
|
|
115
|
+
const sql = renderMigrationSql(unsigned, statements);
|
|
116
|
+
const extra = options.backfill ? { [options.backfill.file ?? BACKFILL_FILE]: options.backfill.text } : undefined;
|
|
117
|
+
const document = { ...unsigned, checksum: computeChecksum({ sql, json: unsigned, extra }) };
|
|
118
|
+
return { id, document, files: { sql, json: serializeDocument(document), ...(extra ? { extra } : {}) }, statements };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/migrations/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing a project's `migrations/` directory.
|
|
3
|
+
*
|
|
4
|
+
* Order is the chain of parent ids, as in Alembic and pgroll: the first
|
|
5
|
+
* migration has `parent: null`, every other names the one it follows, and
|
|
6
|
+
* the list runs from the first to the newest. The ids' timestamps never
|
|
7
|
+
* decide the order. A directory is well formed when the parent links make
|
|
8
|
+
* one unbroken chain:
|
|
9
|
+
*
|
|
10
|
+
* - every directory under `migrations/` is named like an id and holds a
|
|
11
|
+
* readable `migration.sql` and `migration.json` whose `id` is that name
|
|
12
|
+
* (plain files directly under `migrations/`, such as a README, are
|
|
13
|
+
* ignored; files inside a migration's directory are its own and count in
|
|
14
|
+
* its checksum);
|
|
15
|
+
* - every parent names a migration in the directory;
|
|
16
|
+
* - no two migrations have the same parent, `null` included. Two branches
|
|
17
|
+
* that each added a migration give one parent two children: a fork. It is
|
|
18
|
+
* reported naming both, never ordered by timestamp or by name;
|
|
19
|
+
* - every migration is reachable from the first (no cycles).
|
|
20
|
+
*
|
|
21
|
+
* {@link scanMigrations} reports every problem and never throws on one, for
|
|
22
|
+
* `yodel lint`. {@link readMigrations} returns the ordered chain or throws a
|
|
23
|
+
* {@link MigrationDirectoryError} listing the problems, for the verbs that
|
|
24
|
+
* act on the chain (`new`, `apply`, `rebase`).
|
|
25
|
+
*/
|
|
26
|
+
import { YodelError } from "../project.js";
|
|
27
|
+
import { type ChecksumCheck } from "./checksum.js";
|
|
28
|
+
import { type MigrationDocument, type MigrationFiles } from "./document.js";
|
|
29
|
+
/** Where a project keeps its migrations, relative to the project directory. */
|
|
30
|
+
export declare const MIGRATIONS_DIR = "migrations";
|
|
31
|
+
/** `<YYYYMMDDTHHMM>-<name>`; a name is lowercase letters and digits, words joined by `-` or `_`. */
|
|
32
|
+
export declare const MIGRATION_ID: RegExp;
|
|
33
|
+
export declare const MIGRATION_NAME: RegExp;
|
|
34
|
+
/** One migration as read from disk. */
|
|
35
|
+
export interface Migration {
|
|
36
|
+
id: string;
|
|
37
|
+
/** Its directory, absolute. */
|
|
38
|
+
dir: string;
|
|
39
|
+
parent: string | null;
|
|
40
|
+
/** The checksum `migration.json` records (not recomputed; see {@link verifyChecksum}). */
|
|
41
|
+
checksum: string;
|
|
42
|
+
document: MigrationDocument;
|
|
43
|
+
/** The files' text exactly as read. */
|
|
44
|
+
files: MigrationFiles;
|
|
45
|
+
}
|
|
46
|
+
export type MigrationProblem = {
|
|
47
|
+
kind: "unreadable";
|
|
48
|
+
path: string;
|
|
49
|
+
message: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "id-mismatch";
|
|
52
|
+
id: string;
|
|
53
|
+
recorded: string;
|
|
54
|
+
message: string;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "missing-parent";
|
|
57
|
+
id: string;
|
|
58
|
+
parent: string;
|
|
59
|
+
message: string;
|
|
60
|
+
} | {
|
|
61
|
+
kind: "fork";
|
|
62
|
+
parent: string | null;
|
|
63
|
+
children: string[];
|
|
64
|
+
message: string;
|
|
65
|
+
} | {
|
|
66
|
+
kind: "unreachable";
|
|
67
|
+
ids: string[];
|
|
68
|
+
message: string;
|
|
69
|
+
} | {
|
|
70
|
+
kind: "checksum";
|
|
71
|
+
id: string;
|
|
72
|
+
check: ChecksumCheck;
|
|
73
|
+
message: string;
|
|
74
|
+
};
|
|
75
|
+
export interface MigrationScan {
|
|
76
|
+
/** The migrations directory, absolute. */
|
|
77
|
+
dir: string;
|
|
78
|
+
/** Every migration that could be read, sorted by id. */
|
|
79
|
+
migrations: Migration[];
|
|
80
|
+
/** From the first migration to the newest, when there is no structural problem; else `undefined`. */
|
|
81
|
+
ordered: Migration[] | undefined;
|
|
82
|
+
/** Every problem found. Checksum mismatches are listed here but never stop {@link MigrationScan.ordered} being computed. */
|
|
83
|
+
problems: MigrationProblem[];
|
|
84
|
+
}
|
|
85
|
+
/** A migrations directory that is not one unbroken chain, or (when asked) whose checksums do not match. */
|
|
86
|
+
export declare class MigrationDirectoryError extends YodelError {
|
|
87
|
+
readonly dir: string;
|
|
88
|
+
readonly problems: MigrationProblem[];
|
|
89
|
+
constructor(dir: string, problems: MigrationProblem[]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A migration's files besides migration.sql and migration.json, by path
|
|
93
|
+
* relative to its directory (`/` separators): every regular file at any
|
|
94
|
+
* depth, except those two and anything whose name starts with `.`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readExtraFiles(at: string): Record<string, string>;
|
|
97
|
+
/** Every migration in `dir` and every problem with the directory. A missing `dir` is an empty directory. */
|
|
98
|
+
export declare function scanMigrations(dir: string): MigrationScan;
|
|
99
|
+
/**
|
|
100
|
+
* The migrations in `dir`, from the first to the newest. Throws a
|
|
101
|
+
* {@link MigrationDirectoryError} on any structural problem (unreadable,
|
|
102
|
+
* id mismatch, missing parent, fork, cycle), and, with `verify`, on a
|
|
103
|
+
* checksum mismatch too. A missing `dir` has no migrations.
|
|
104
|
+
*/
|
|
105
|
+
export declare function readMigrations(dir: string, options?: {
|
|
106
|
+
verify?: boolean;
|
|
107
|
+
}): Migration[];
|
|
108
|
+
/** The newest migration of an ordered list (the end of the chain), or `undefined` for none. */
|
|
109
|
+
export declare const newestMigration: (ordered: readonly Migration[]) => Migration | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Writes a migration's directory, `<dir>/<id>/`, creating `dir` as needed.
|
|
112
|
+
* The files go into a hidden directory first and are renamed into place, so
|
|
113
|
+
* a reader never sees half a migration. An existing `<id>` is an error.
|
|
114
|
+
* Returns the directory written.
|
|
115
|
+
*/
|
|
116
|
+
export declare function writeMigration(dir: string, migration: {
|
|
117
|
+
id: string;
|
|
118
|
+
files: MigrationFiles;
|
|
119
|
+
}): string;
|