@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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and appending the history table. Nothing here updates or deletes
|
|
3
|
+
* a row.
|
|
4
|
+
*/
|
|
5
|
+
import { hostname, userInfo } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { HISTORY_COLUMNS, HISTORY_TABLE, historyDdl, ident, literal } from "./ddl.js";
|
|
8
|
+
import { replicates } from "./topology.js";
|
|
9
|
+
import { deriveState } from "./rows.js";
|
|
10
|
+
/** Settings every history write carries: synchronous, and never deduplicated away on a Replicated engine. */
|
|
11
|
+
const WRITE_SETTINGS = { async_insert: "0", insert_deduplicate: "0" };
|
|
12
|
+
/** Who is applying: the CI actor or the user, at this host. */
|
|
13
|
+
export function appliedBy(env = process.env) {
|
|
14
|
+
let user = env.GITHUB_ACTOR ?? env.GITLAB_USER_LOGIN ?? env.USER;
|
|
15
|
+
if (!user) {
|
|
16
|
+
try {
|
|
17
|
+
user = userInfo().username;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
user = "unknown";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return `${user}@${hostname()}`;
|
|
24
|
+
}
|
|
25
|
+
const columnNames = HISTORY_COLUMNS.map(([name]) => name);
|
|
26
|
+
/** A row as a server returns it (strings or numbers), as a {@link HistoryRow}. */
|
|
27
|
+
export function fromRaw(r) {
|
|
28
|
+
const s = (k) => {
|
|
29
|
+
const v = r[k];
|
|
30
|
+
return typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : "";
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
seq: Number(r.seq),
|
|
34
|
+
run_id: s("run_id"),
|
|
35
|
+
kind: s("kind") === "statement" || s("kind") === "step" ? s("kind") : "migration",
|
|
36
|
+
migration_id: s("migration_id"),
|
|
37
|
+
parent: s("parent"),
|
|
38
|
+
checksum: s("checksum"),
|
|
39
|
+
statement_index: Number(r.statement_index),
|
|
40
|
+
statement_sha: s("statement_sha"),
|
|
41
|
+
status: (["started", "succeeded", "failed"].includes(s("status")) ? s("status") : "started"),
|
|
42
|
+
error: s("error"),
|
|
43
|
+
applied_by: s("applied_by"),
|
|
44
|
+
plan_digest: s("plan_digest"),
|
|
45
|
+
silences: s("silences") || "[]",
|
|
46
|
+
note: s("note"),
|
|
47
|
+
recorded_at: s("recorded_at"),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export class HistoryStore {
|
|
51
|
+
port;
|
|
52
|
+
location;
|
|
53
|
+
constructor(port, location) {
|
|
54
|
+
this.port = port;
|
|
55
|
+
this.location = location;
|
|
56
|
+
}
|
|
57
|
+
get table() {
|
|
58
|
+
return `${ident(this.location.database)}.${HISTORY_TABLE}`;
|
|
59
|
+
}
|
|
60
|
+
/** Whether the history table is there. A history that was never made has no rows. */
|
|
61
|
+
async exists() {
|
|
62
|
+
const rows = await this.port.query(`SELECT count() AS n FROM system.tables WHERE database = ${literal(this.location.database)} AND name = ${literal(HISTORY_TABLE)}`);
|
|
63
|
+
return Number(rows[0]?.n ?? 0) > 0;
|
|
64
|
+
}
|
|
65
|
+
/** Creates the database and table when they are not there. */
|
|
66
|
+
async ensure() {
|
|
67
|
+
for (const sql of historyDdl(this.location))
|
|
68
|
+
await this.port.query(sql);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Every row. On a cluster or a Replicated database the replica first
|
|
72
|
+
* catches up with the others (`SYSTEM SYNC REPLICA`), so a run reads what
|
|
73
|
+
* a run on another host wrote; on Cloud the read is sequentially
|
|
74
|
+
* consistent. `sync: false` skips that, for a status read that need not
|
|
75
|
+
* wait.
|
|
76
|
+
*/
|
|
77
|
+
async rows(options = {}) {
|
|
78
|
+
if (!(await this.exists()))
|
|
79
|
+
return [];
|
|
80
|
+
const { topology } = this.location;
|
|
81
|
+
if (options.sync !== false && replicates(topology))
|
|
82
|
+
await this.port.query(`SYSTEM SYNC REPLICA ${this.table}`);
|
|
83
|
+
const settings = topology.kind === "cloud" && options.sync !== false ? { settings: { select_sequential_consistency: "1" } } : {};
|
|
84
|
+
const select = columnNames.map((c) => (c === "recorded_at" ? "toString(recorded_at) AS recorded_at" : c)).join(", ");
|
|
85
|
+
const raw = await this.port.query(`SELECT ${select} FROM ${this.table} ORDER BY seq`, settings);
|
|
86
|
+
return raw.map(fromRaw);
|
|
87
|
+
}
|
|
88
|
+
async state(options = {}) {
|
|
89
|
+
return deriveState(await this.rows(options));
|
|
90
|
+
}
|
|
91
|
+
/** Appends rows, as written; `recorded_at` is the server's. */
|
|
92
|
+
async append(rows) {
|
|
93
|
+
if (rows.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
const body = rows
|
|
96
|
+
.map((r) => JSON.stringify({ ...r, recorded_at: undefined }))
|
|
97
|
+
.join("\n");
|
|
98
|
+
await this.port.query(`INSERT INTO ${this.table} FORMAT JSONEachRow\n${body}`, { settings: WRITE_SETTINGS });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export const newRunContext = (planDigest, env = process.env) => ({
|
|
102
|
+
runId: randomUUID(),
|
|
103
|
+
appliedBy: appliedBy(env),
|
|
104
|
+
planDigest,
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Writes one run's rows, each with the next `seq`. Made under the lock from
|
|
108
|
+
* the history's highest seq, so seqs never collide across runs.
|
|
109
|
+
*/
|
|
110
|
+
export class HistoryRecorder {
|
|
111
|
+
store;
|
|
112
|
+
run;
|
|
113
|
+
next;
|
|
114
|
+
constructor(store, run, maxSeq) {
|
|
115
|
+
this.store = store;
|
|
116
|
+
this.run = run;
|
|
117
|
+
this.next = maxSeq + 1;
|
|
118
|
+
}
|
|
119
|
+
async record(row) {
|
|
120
|
+
const full = {
|
|
121
|
+
error: "",
|
|
122
|
+
note: "",
|
|
123
|
+
silences: "[]",
|
|
124
|
+
...row,
|
|
125
|
+
seq: this.next++,
|
|
126
|
+
run_id: this.run.runId,
|
|
127
|
+
applied_by: this.run.appliedBy,
|
|
128
|
+
plan_digest: this.run.planDigest,
|
|
129
|
+
};
|
|
130
|
+
await this.store.append([full]);
|
|
131
|
+
return full;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/history/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the history table lives, as far as its DDL cares: a single node, a
|
|
3
|
+
* cluster (`ON CLUSTER`), a `Replicated` database, or ClickHouse Cloud.
|
|
4
|
+
*
|
|
5
|
+
* The same four kinds as chant's ClickHouse topology, with the same string
|
|
6
|
+
* form (`single`, `cluster:<name>`, `replicated`, `replicated:<cluster>`,
|
|
7
|
+
* `cloud`). Each environment names one in `yodel.config.ts` (#9, see
|
|
8
|
+
* `../config/settings.ts` for the fallbacks); the default is a single node.
|
|
9
|
+
*/
|
|
10
|
+
export type HistoryTopology = {
|
|
11
|
+
kind: "single";
|
|
12
|
+
} | {
|
|
13
|
+
kind: "cluster";
|
|
14
|
+
/** The cluster's name in `remote_servers`, or a macro such as `{cluster}`. */
|
|
15
|
+
cluster: string;
|
|
16
|
+
/** The history table's Keeper path. Default {@link defaultReplicaPath}. */
|
|
17
|
+
replicaPath?: string;
|
|
18
|
+
/** Its replica name. Default {@link DEFAULT_HISTORY_REPLICA_NAME}. */
|
|
19
|
+
replicaName?: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "replicated";
|
|
22
|
+
/** A cluster to create the history's `Replicated` database on, when yodel creates it. */
|
|
23
|
+
cluster?: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "cloud";
|
|
26
|
+
};
|
|
27
|
+
export declare const SINGLE: HistoryTopology;
|
|
28
|
+
/**
|
|
29
|
+
* A cluster's history table is one copy for the whole cluster, not one per
|
|
30
|
+
* shard: every runner, on any host, must read the same rows. So the Keeper
|
|
31
|
+
* path names no `{shard}`, and the replica name carries the shard as well as
|
|
32
|
+
* the replica, so replicas of different shards do not collide.
|
|
33
|
+
*/
|
|
34
|
+
export declare const defaultReplicaPath: (database: string, table: string) => string;
|
|
35
|
+
export declare const DEFAULT_HISTORY_REPLICA_NAME = "{shard}-{replica}";
|
|
36
|
+
/** A topology from its string form. */
|
|
37
|
+
export declare function parseHistoryTopology(value: string): HistoryTopology;
|
|
38
|
+
/** The string form of a topology, for messages. */
|
|
39
|
+
export declare function describeTopology(t: HistoryTopology): string;
|
|
40
|
+
/** Whether the history's tables replicate through Keeper, so a read on one host must first catch up with the others. */
|
|
41
|
+
export declare const replicates: (t: HistoryTopology) => boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the history table lives, as far as its DDL cares: a single node, a
|
|
3
|
+
* cluster (`ON CLUSTER`), a `Replicated` database, or ClickHouse Cloud.
|
|
4
|
+
*
|
|
5
|
+
* The same four kinds as chant's ClickHouse topology, with the same string
|
|
6
|
+
* form (`single`, `cluster:<name>`, `replicated`, `replicated:<cluster>`,
|
|
7
|
+
* `cloud`). Each environment names one in `yodel.config.ts` (#9, see
|
|
8
|
+
* `../config/settings.ts` for the fallbacks); the default is a single node.
|
|
9
|
+
*/
|
|
10
|
+
import { YodelError } from "../project.js";
|
|
11
|
+
export const SINGLE = { kind: "single" };
|
|
12
|
+
/**
|
|
13
|
+
* A cluster's history table is one copy for the whole cluster, not one per
|
|
14
|
+
* shard: every runner, on any host, must read the same rows. So the Keeper
|
|
15
|
+
* path names no `{shard}`, and the replica name carries the shard as well as
|
|
16
|
+
* the replica, so replicas of different shards do not collide.
|
|
17
|
+
*/
|
|
18
|
+
export const defaultReplicaPath = (database, table) => `/clickhouse/yodeler/${database}/${table}`;
|
|
19
|
+
export const DEFAULT_HISTORY_REPLICA_NAME = "{shard}-{replica}";
|
|
20
|
+
/** A topology from its string form. */
|
|
21
|
+
export function parseHistoryTopology(value) {
|
|
22
|
+
const at = value.indexOf(":");
|
|
23
|
+
const kind = (at < 0 ? value : value.slice(0, at)).trim();
|
|
24
|
+
const arg = at < 0 ? undefined : value.slice(at + 1).trim();
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case "single":
|
|
27
|
+
case "cloud":
|
|
28
|
+
if (arg !== undefined)
|
|
29
|
+
throw new YodelError(`topology ${kind} takes no argument: ${JSON.stringify(value)}`);
|
|
30
|
+
return { kind };
|
|
31
|
+
case "cluster":
|
|
32
|
+
if (!arg)
|
|
33
|
+
throw new YodelError(`topology cluster needs the cluster's name: cluster:<name>, got ${JSON.stringify(value)}`);
|
|
34
|
+
return { kind, cluster: arg };
|
|
35
|
+
case "replicated":
|
|
36
|
+
return arg ? { kind, cluster: arg } : { kind };
|
|
37
|
+
default:
|
|
38
|
+
throw new YodelError(`unknown topology ${JSON.stringify(value)}: expected single, cluster:<name>, replicated or cloud`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** The string form of a topology, for messages. */
|
|
42
|
+
export function describeTopology(t) {
|
|
43
|
+
switch (t.kind) {
|
|
44
|
+
case "cluster":
|
|
45
|
+
return `cluster:${t.cluster}`;
|
|
46
|
+
case "replicated":
|
|
47
|
+
return t.cluster ? `replicated:${t.cluster}` : "replicated";
|
|
48
|
+
default:
|
|
49
|
+
return t.kind;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Whether the history's tables replicate through Keeper, so a read on one host must first catch up with the others. */
|
|
53
|
+
export const replicates = (t) => t.kind === "cluster" || t.kind === "replicated";
|
|
54
|
+
//# sourceMappingURL=topology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.js","sourceRoot":"","sources":["../../src/history/topology.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAoB3C,MAAM,CAAC,MAAM,MAAM,GAAoB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAU,EAAE,CAAC,uBAAuB,QAAQ,IAAI,KAAK,EAAE,CAAC;AAC1H,MAAM,CAAC,MAAM,4BAA4B,GAAG,mBAAmB,CAAC;AAEhE,uCAAuC;AACvC,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1D,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ,CAAC;QACd,KAAK,OAAO;YACV,IAAI,GAAG,KAAK,SAAS;gBAAE,MAAM,IAAI,UAAU,CAAC,YAAY,IAAI,uBAAuB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5G,OAAO,EAAE,IAAI,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,UAAU,CAAC,kEAAkE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC1H,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QAChC,KAAK,YAAY;YACf,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACjD;YACE,MAAM,IAAI,UAAU,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5H,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,gBAAgB,CAAC,CAAkB;IACjD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC;QAChC,KAAK,YAAY;YACf,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;QAC9D;YACE,OAAO,CAAC,CAAC,IAAI,CAAC;IAClB,CAAC;AACH,CAAC;AAED,wHAAwH;AACxH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAkB,EAAW,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package root, for a project's `yodel.config.ts`:
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { defineConfig } from "@intentius/sql-yodeler";
|
|
6
|
+
* export default defineConfig({ environments: { prod: { topology: "cluster:main" } } });
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export { defineConfig } from "./config/define.js";
|
|
10
|
+
export type { EnvironmentConfig, PolicyConfig, PolicyMigration, PolicyPlan, PolicyRule, PolicyStep, Topology, TopologySetting, YodelConfig } from "./config/define.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package root, for a project's `yodel.config.ts`:
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { defineConfig } from "@intentius/sql-yodeler";
|
|
6
|
+
* export default defineConfig({ environments: { prod: { topology: "cluster:main" } } });
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export { defineConfig } from "./config/define.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel init --from <env>`: adopt a live database in one go.
|
|
3
|
+
*
|
|
4
|
+
* 1. Refuse, before anything is written, when the project already declares
|
|
5
|
+
* a schema (unless `force`), already has migrations, or the
|
|
6
|
+
* environment's history already records any; and when the environment
|
|
7
|
+
* names no `databases` (ClickHouse) or `schemas` (Postgres), or names
|
|
8
|
+
* the history's own.
|
|
9
|
+
* 2. Read the live database with chant's import (`chant import --from`'s
|
|
10
|
+
* library functions) and write the declarations into the source
|
|
11
|
+
* directory.
|
|
12
|
+
* 3. Build them and plan the build against the same database. The plan
|
|
13
|
+
* must be empty: the declarations are the database. If it is not, the
|
|
14
|
+
* import does not round-trip, and everything written is taken back.
|
|
15
|
+
* 4. Write the baseline migration: the first migration (parent `null`),
|
|
16
|
+
* whose statements create everything, from an empty schema to the build.
|
|
17
|
+
* 5. Record it applied in the history without running it (`./baseline.ts`),
|
|
18
|
+
* on ClickHouse in `<history database>.history`, on Postgres in
|
|
19
|
+
* `<history schema>.history` (#13), under the apply lock.
|
|
20
|
+
*
|
|
21
|
+
* Any failure in 2 to 5 takes back every file written. The live database
|
|
22
|
+
* is only read; the one write to the server is the history row, and the
|
|
23
|
+
* history database (schema) and table when they were not there.
|
|
24
|
+
*/
|
|
25
|
+
import { type Environment, type Project } from "../project.js";
|
|
26
|
+
import type { HistoryRow } from "../history/index.js";
|
|
27
|
+
export interface AdoptOptions {
|
|
28
|
+
/** Overwrite declaration files the source directory already has. */
|
|
29
|
+
force?: boolean;
|
|
30
|
+
/** The baseline migration's name. Default {@link BASELINE_NAME}. */
|
|
31
|
+
name?: string;
|
|
32
|
+
now?: Date;
|
|
33
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
34
|
+
log?: (line: string) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface Adopted {
|
|
37
|
+
environment: string;
|
|
38
|
+
dialect: Project["dialect"];
|
|
39
|
+
/** The objects read, as `<kind> <name>`. */
|
|
40
|
+
objects: string[];
|
|
41
|
+
/** The declaration files written, relative to the project. */
|
|
42
|
+
schemaFiles: string[];
|
|
43
|
+
/** The baseline migration's directory, relative to the project. */
|
|
44
|
+
migrationPath: string;
|
|
45
|
+
baseline: {
|
|
46
|
+
id: string;
|
|
47
|
+
checksum: string;
|
|
48
|
+
statements: number;
|
|
49
|
+
};
|
|
50
|
+
/** The baseline's history row, recorded applied; init records it on both dialects. */
|
|
51
|
+
recorded: {
|
|
52
|
+
database: string;
|
|
53
|
+
row: HistoryRow;
|
|
54
|
+
lock: {
|
|
55
|
+
kind: string;
|
|
56
|
+
where: string;
|
|
57
|
+
notice?: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
warnings: string[];
|
|
61
|
+
}
|
|
62
|
+
/** Where the declarations go: `sourceDir` when the config names one, else `src`. */
|
|
63
|
+
export declare const schemaDir: (project: Project) => string;
|
|
64
|
+
/** The refusals that need no server: a schema already declared, migrations already there, a ClickHouse scope that would take in the history. */
|
|
65
|
+
export declare function preflight(project: Project, env: Environment, options?: {
|
|
66
|
+
force?: boolean;
|
|
67
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
68
|
+
}): void;
|
|
69
|
+
/** Adopts `env`'s live database into the project; see the module comment. */
|
|
70
|
+
export declare function adopt(project: Project, env: Environment, options?: AdoptOptions): Promise<Adopted>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yodel init --from <env>`: adopt a live database in one go.
|
|
3
|
+
*
|
|
4
|
+
* 1. Refuse, before anything is written, when the project already declares
|
|
5
|
+
* a schema (unless `force`), already has migrations, or the
|
|
6
|
+
* environment's history already records any; and when the environment
|
|
7
|
+
* names no `databases` (ClickHouse) or `schemas` (Postgres), or names
|
|
8
|
+
* the history's own.
|
|
9
|
+
* 2. Read the live database with chant's import (`chant import --from`'s
|
|
10
|
+
* library functions) and write the declarations into the source
|
|
11
|
+
* directory.
|
|
12
|
+
* 3. Build them and plan the build against the same database. The plan
|
|
13
|
+
* must be empty: the declarations are the database. If it is not, the
|
|
14
|
+
* import does not round-trip, and everything written is taken back.
|
|
15
|
+
* 4. Write the baseline migration: the first migration (parent `null`),
|
|
16
|
+
* whose statements create everything, from an empty schema to the build.
|
|
17
|
+
* 5. Record it applied in the history without running it (`./baseline.ts`),
|
|
18
|
+
* on ClickHouse in `<history database>.history`, on Postgres in
|
|
19
|
+
* `<history schema>.history` (#13), under the apply lock.
|
|
20
|
+
*
|
|
21
|
+
* Any failure in 2 to 5 takes back every file written. The live database
|
|
22
|
+
* is only read; the one write to the server is the history row, and the
|
|
23
|
+
* history database (schema) and table when they were not there.
|
|
24
|
+
*/
|
|
25
|
+
import { join, relative } from "node:path";
|
|
26
|
+
import { loadProject, YodelError } from "../project.js";
|
|
27
|
+
import { buildSchema } from "../chant/build.js";
|
|
28
|
+
import { planEnvironment, renderPlan } from "../chant/plan.js";
|
|
29
|
+
import { createMigration, MIGRATIONS_DIR, writeMigration } from "../migrations/index.js";
|
|
30
|
+
import { environmentSettings } from "../config/settings.js";
|
|
31
|
+
import { closeVersioned, hasMigrations, openVersioned, REFUSED } from "../versioned/index.js";
|
|
32
|
+
import { BASELINE_NAME, checkHistoryEmpty, recordBaseline } from "./baseline.js";
|
|
33
|
+
import { declarationFiles, Journal } from "./files.js";
|
|
34
|
+
import { describeImported, importLive } from "./live.js";
|
|
35
|
+
/** Where the declarations go: `sourceDir` when the config names one, else `src`. */
|
|
36
|
+
export const schemaDir = (project) => join(project.dir, project.config.sourceDir ?? "src");
|
|
37
|
+
/** The refusals that need no server: a schema already declared, migrations already there, a ClickHouse scope that would take in the history. */
|
|
38
|
+
export function preflight(project, env, options = {}) {
|
|
39
|
+
if (hasMigrations(project)) {
|
|
40
|
+
throw new YodelError(`refused: ${join(project.dir, MIGRATIONS_DIR)} already holds migrations; init writes the first one (the baseline), so it adopts a database only in a project with none. Nothing was written.`, REFUSED);
|
|
41
|
+
}
|
|
42
|
+
const dir = schemaDir(project);
|
|
43
|
+
const existing = declarationFiles(dir);
|
|
44
|
+
if (existing.length > 0 && !options.force) {
|
|
45
|
+
throw new YodelError(`refused: ${relative(project.dir, dir) || "."} already declares a schema (${existing.slice(0, 3).join(", ")}${existing.length > 3 ? ", ..." : ""}); ` +
|
|
46
|
+
`init writes the declarations from the live database there. Pass --force to overwrite (files the import does not write are kept, and are built with it). Nothing was written.`, REFUSED);
|
|
47
|
+
}
|
|
48
|
+
const { historyDatabase } = environmentSettings(project, env.name, options.processEnv);
|
|
49
|
+
const [key, what] = project.dialect === "postgres" ? ["schemas", "schema"] : ["databases", "database"];
|
|
50
|
+
const scope = project.dialect === "postgres" ? env.profile.schemas : env.profile.databases;
|
|
51
|
+
if (!scope || scope.length === 0) {
|
|
52
|
+
throw new YodelError(`refused: sql.profiles.${env.name} names no ${key}, so the import would read every ${what} on the server, the history's (${historyDatabase}) included. ` +
|
|
53
|
+
`List the ${key} this project owns in sql.profiles.${env.name}.${key}. Nothing was written.`, REFUSED);
|
|
54
|
+
}
|
|
55
|
+
if (scope.includes(historyDatabase)) {
|
|
56
|
+
throw new YodelError(`refused: sql.profiles.${env.name}.${key} includes ${historyDatabase}, the history's ${what} (yodel.config.ts environments.${env.name}.history.database, else YODEL_HISTORY_DATABASE); the history is yodel's, never part of the declared schema. Nothing was written.`, REFUSED);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a plan has nothing to do: no change to a declared object, and no
|
|
61
|
+
* undeclared object it would drop, but for the objects the import left out
|
|
62
|
+
* as another tool's (`leftOut`), which an ApplyOp never drops since they
|
|
63
|
+
* carry no chant marker.
|
|
64
|
+
*/
|
|
65
|
+
const planIsEmpty = (plan, leftOut = []) => plan.diff.changes.every((c) => leftOut.includes(c.object)) && plan.refused.length === 0;
|
|
66
|
+
/** Adopts `env`'s live database into the project; see the module comment. */
|
|
67
|
+
export async function adopt(project, env, options = {}) {
|
|
68
|
+
const processEnv = options.processEnv ?? process.env;
|
|
69
|
+
const log = options.log ?? (() => undefined);
|
|
70
|
+
preflight(project, env, { ...(options.force !== undefined ? { force: options.force } : {}), processEnv });
|
|
71
|
+
const venv = await openVersioned(project, env, processEnv);
|
|
72
|
+
try {
|
|
73
|
+
await checkHistoryEmpty(venv);
|
|
74
|
+
return await adoptWith(project, env, venv, options, processEnv, log);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await closeVersioned(venv);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function adoptWith(project, env, venv, options, processEnv, log) {
|
|
81
|
+
const live = await importLive(project, env, processEnv);
|
|
82
|
+
if (live.ir.resources.length === 0) {
|
|
83
|
+
throw new YodelError(`refused: ${env.name} holds no objects in its scope (sql.profiles.${env.name}); there is nothing to adopt. Declare a schema and run yodel new instead. Nothing was written.`, REFUSED);
|
|
84
|
+
}
|
|
85
|
+
const objects = describeImported(live.ir);
|
|
86
|
+
log(`read ${String(objects.length)} object(s) from ${env.name}`);
|
|
87
|
+
const journal = new Journal();
|
|
88
|
+
try {
|
|
89
|
+
const dir = schemaDir(project);
|
|
90
|
+
journal.mkdir(dir);
|
|
91
|
+
const schemaFiles = [];
|
|
92
|
+
for (const f of live.files) {
|
|
93
|
+
const path = join(dir, f.path);
|
|
94
|
+
journal.write(path, f.content);
|
|
95
|
+
schemaFiles.push(relative(project.dir, path));
|
|
96
|
+
}
|
|
97
|
+
log(`wrote ${schemaFiles.join(", ")}`);
|
|
98
|
+
// The source directory may have been made just now; load the project again so it is built from there.
|
|
99
|
+
const adopted = await loadProject(project.dir);
|
|
100
|
+
const build = await buildSchema(adopted, { env: processEnv });
|
|
101
|
+
let made;
|
|
102
|
+
try {
|
|
103
|
+
const plan = await planEnvironment(adopted, env, build, { env: processEnv });
|
|
104
|
+
if (!planIsEmpty(plan, live.leftOut)) {
|
|
105
|
+
throw new YodelError(`the declarations chant's import wrote do not plan clean against ${env.name}, so they are not yet the database; nothing was kept. ` +
|
|
106
|
+
`This is an object chant cannot round-trip; please report it with the plan:\n${await renderPlan(plan, `Plan for ${env.name} after import`)}`);
|
|
107
|
+
}
|
|
108
|
+
log(`planned against ${env.name}: no change`);
|
|
109
|
+
made = createMigration({ name: options.name ?? BASELINE_NAME, build: build.output, ...(options.now ? { now: options.now } : {}) });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
build.dispose();
|
|
113
|
+
}
|
|
114
|
+
if (made.id === undefined)
|
|
115
|
+
throw new YodelError("the build declares nothing, so there is no baseline to write");
|
|
116
|
+
const migrations = join(project.dir, MIGRATIONS_DIR);
|
|
117
|
+
journal.mkdir(migrations);
|
|
118
|
+
const path = writeMigration(migrations, made);
|
|
119
|
+
journal.created(path);
|
|
120
|
+
const migration = { id: made.id, dir: path, parent: null, checksum: made.document.checksum, document: made.document, files: made.files };
|
|
121
|
+
const statements = made.statements.steps.length;
|
|
122
|
+
log(`wrote ${relative(project.dir, path)} (${String(statements)} statement${statements === 1 ? "" : "s"})`);
|
|
123
|
+
const base = {
|
|
124
|
+
environment: env.name,
|
|
125
|
+
dialect: project.dialect,
|
|
126
|
+
objects,
|
|
127
|
+
schemaFiles,
|
|
128
|
+
migrationPath: relative(project.dir, path),
|
|
129
|
+
baseline: { id: made.id, checksum: made.document.checksum, statements },
|
|
130
|
+
warnings: live.warnings,
|
|
131
|
+
};
|
|
132
|
+
const { row, lock } = await recordBaseline(venv, migration, processEnv);
|
|
133
|
+
if (lock.notice)
|
|
134
|
+
log(`lock: ${lock.notice}`);
|
|
135
|
+
log(`recorded ${made.id} applied in ${venv.location.database}.history as a baseline; none of its statements was run`);
|
|
136
|
+
return { ...base, recorded: { database: venv.location.database, row, lock } };
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
journal.undo();
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=adopt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/init/adopt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAkC,MAAM,eAAe,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAqB,MAAM,uBAAuB,CAAC;AAEjH,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AA2BzD,oFAAoF;AACpF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC;AAE5G,gJAAgJ;AAChJ,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,GAAgB,EAAE,UAA+D,EAAE;IAC7H,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,UAAU,CAClB,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,gJAAgJ,EAC7L,OAAO,CACR,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC1C,MAAM,IAAI,UAAU,CAClB,YAAY,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,GAAG,+BAA+B,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK;YACnJ,8KAA8K,EAChL,OAAO,CACR,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,eAAe,EAAE,GAAG,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACvF,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,QAAQ,CAAW,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,UAAU,CAAW,CAAC;IAC7H,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;IAC3F,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,UAAU,CAClB,yBAAyB,GAAG,CAAC,IAAI,aAAa,GAAG,oCAAoC,IAAI,kCAAkC,eAAe,cAAc;YACtJ,YAAY,GAAG,sCAAsC,GAAG,CAAC,IAAI,IAAI,GAAG,wBAAwB,EAC9F,OAAO,CACR,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAClB,yBAAyB,GAAG,CAAC,IAAI,IAAI,GAAG,aAAa,eAAe,mBAAmB,IAAI,kCAAkC,GAAG,CAAC,IAAI,kIAAkI,EACvQ,OAAO,CACR,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,WAAW,GAAG,CAAC,IAAU,EAAE,UAA6B,EAAE,EAAW,EAAE,CAC3E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAgB,EAAE,GAAgB,EAAE,UAAwB,EAAE;IACxF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAE1G,MAAM,IAAI,GAAiB,MAAM,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IACzE,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,MAAM,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IACvE,CAAC;YAAS,CAAC;QACT,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,OAAgB,EAAE,GAAgB,EAAE,IAAkB,EAAE,OAAqB,EAAE,UAA6B,EAAE,GAA2B;IAChK,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,UAAU,CAAC,YAAY,GAAG,CAAC,IAAI,gDAAgD,GAAG,CAAC,IAAI,gGAAgG,EAAE,OAAO,CAAC,CAAC;IAC9M,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,GAAG,CAAC,QAAQ,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,MAAM,WAAW,GAAa,EAAE,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;YAC/B,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAChD,CAAC;QACD,GAAG,CAAC,SAAS,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEvC,sGAAsG;QACtG,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9D,IAAI,IAAwC,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;YAC7E,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,UAAU,CAClB,mEAAmE,GAAG,CAAC,IAAI,wDAAwD;oBACjI,+EAA+E,MAAM,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,CAAC,IAAI,eAAe,CAAC,EAAE,CAC/I,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,mBAAmB,GAAG,CAAC,IAAI,aAAa,CAAC,CAAC;YAC9C,IAAI,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACrI,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS;YAAE,MAAM,IAAI,UAAU,CAAC,8DAA8D,CAAC,CAAC;QAEhH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,SAAS,GAAc,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACpJ,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;QAChD,GAAG,CAAC,SAAS,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,aAAa,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAE5G,MAAM,IAAI,GAAG;YACX,WAAW,EAAE,GAAG,CAAC,IAAI;YACrB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO;YACP,WAAW;YACX,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC;YAC1C,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE;YACvE,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,MAAM;YAAE,GAAG,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7C,GAAG,CAAC,YAAY,IAAI,CAAC,EAAE,eAAe,IAAI,CAAC,QAAQ,CAAC,QAAQ,wDAAwD,CAAC,CAAC;QACtH,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;IAChF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The baseline: the first migration of an adopted database, holding the
|
|
3
|
+
* schema the database already had, recorded applied without running it.
|
|
4
|
+
*
|
|
5
|
+
* ## How the history records it
|
|
6
|
+
*
|
|
7
|
+
* One migration row, `status = 'succeeded'`, `note` starting `baseline`
|
|
8
|
+
* ({@link BASELINE_NOTE}), the migration's checksum, `parent = ''`, and no
|
|
9
|
+
* statement rows: none of its statements was sent, since the objects they
|
|
10
|
+
* create are the ones it was read from. `plan_digest` is empty, as nothing
|
|
11
|
+
* was approved.
|
|
12
|
+
*
|
|
13
|
+
* The rest of yodel needs nothing new to read it. `deriveState` makes the
|
|
14
|
+
* migration's state its latest migration row, `succeeded`, so `isApplied`
|
|
15
|
+
* holds; `compareHistory` lists it applied, checks its checksum like any
|
|
16
|
+
* other's, and never puts it among the pending, so `yodel apply` never sends
|
|
17
|
+
* its statements; and statement rows are only read for a migration that is
|
|
18
|
+
* part way. A status other than `succeeded` (say `adopted`) would read as
|
|
19
|
+
* part way, and apply would run it, so the status stays `succeeded` and the
|
|
20
|
+
* note is what says it was adopted.
|
|
21
|
+
*/
|
|
22
|
+
import { YodelError } from "../project.js";
|
|
23
|
+
import type { Migration } from "../migrations/index.js";
|
|
24
|
+
import { type HistoryRow } from "../history/index.js";
|
|
25
|
+
import { type VersionedEnv } from "../versioned/index.js";
|
|
26
|
+
/** The baseline migration's name: its id is `<YYYYMMDDTHHMM>-baseline`. */
|
|
27
|
+
export declare const BASELINE_NAME = "baseline";
|
|
28
|
+
/** The start of a baseline row's `note`. */
|
|
29
|
+
export declare const BASELINE_NOTE = "baseline";
|
|
30
|
+
/** The note a baseline row carries. */
|
|
31
|
+
export declare const baselineNote: (environment: string) => string;
|
|
32
|
+
/** Whether a history row records a baseline. */
|
|
33
|
+
export declare const isBaselineRow: (row: Pick<HistoryRow, "kind" | "note">) => boolean;
|
|
34
|
+
/** The one row that records `m` applied as a baseline. */
|
|
35
|
+
export declare function baselineRow(m: Pick<Migration, "id" | "parent" | "checksum">, input: {
|
|
36
|
+
seq: number;
|
|
37
|
+
runId: string;
|
|
38
|
+
appliedBy: string;
|
|
39
|
+
environment: string;
|
|
40
|
+
}): HistoryRow;
|
|
41
|
+
/** Why the history cannot take a baseline, or `undefined`: a baseline is the first thing a history records. */
|
|
42
|
+
export declare function historyRefusal(state: {
|
|
43
|
+
migrations: readonly {
|
|
44
|
+
id: string;
|
|
45
|
+
}[];
|
|
46
|
+
}, location: string): YodelError | undefined;
|
|
47
|
+
/** Reads the history without the lock, for a refusal before anything is written. */
|
|
48
|
+
export declare function checkHistoryEmpty(venv: VersionedEnv): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Records `m` applied as the baseline, under the apply lock: makes the
|
|
51
|
+
* history when it is not there, checks again that it records nothing, and
|
|
52
|
+
* appends the one row.
|
|
53
|
+
*/
|
|
54
|
+
export declare function recordBaseline(venv: VersionedEnv, m: Migration, processEnv?: NodeJS.ProcessEnv): Promise<{
|
|
55
|
+
row: HistoryRow;
|
|
56
|
+
lock: {
|
|
57
|
+
kind: string;
|
|
58
|
+
where: string;
|
|
59
|
+
notice?: string;
|
|
60
|
+
};
|
|
61
|
+
}>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The baseline: the first migration of an adopted database, holding the
|
|
3
|
+
* schema the database already had, recorded applied without running it.
|
|
4
|
+
*
|
|
5
|
+
* ## How the history records it
|
|
6
|
+
*
|
|
7
|
+
* One migration row, `status = 'succeeded'`, `note` starting `baseline`
|
|
8
|
+
* ({@link BASELINE_NOTE}), the migration's checksum, `parent = ''`, and no
|
|
9
|
+
* statement rows: none of its statements was sent, since the objects they
|
|
10
|
+
* create are the ones it was read from. `plan_digest` is empty, as nothing
|
|
11
|
+
* was approved.
|
|
12
|
+
*
|
|
13
|
+
* The rest of yodel needs nothing new to read it. `deriveState` makes the
|
|
14
|
+
* migration's state its latest migration row, `succeeded`, so `isApplied`
|
|
15
|
+
* holds; `compareHistory` lists it applied, checks its checksum like any
|
|
16
|
+
* other's, and never puts it among the pending, so `yodel apply` never sends
|
|
17
|
+
* its statements; and statement rows are only read for a migration that is
|
|
18
|
+
* part way. A status other than `succeeded` (say `adopted`) would read as
|
|
19
|
+
* part way, and apply would run it, so the status stays `succeeded` and the
|
|
20
|
+
* note is what says it was adopted.
|
|
21
|
+
*/
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { YodelError } from "../project.js";
|
|
24
|
+
import { appliedBy, MIGRATION_ROW, withLock } from "../history/index.js";
|
|
25
|
+
import { REFUSED } from "../versioned/index.js";
|
|
26
|
+
/** The baseline migration's name: its id is `<YYYYMMDDTHHMM>-baseline`. */
|
|
27
|
+
export const BASELINE_NAME = "baseline";
|
|
28
|
+
/** The start of a baseline row's `note`. */
|
|
29
|
+
export const BASELINE_NOTE = "baseline";
|
|
30
|
+
/** The note a baseline row carries. */
|
|
31
|
+
export const baselineNote = (environment) => `${BASELINE_NOTE}: adopted from the live database by yodel init --from ${environment}; its statements were not run`;
|
|
32
|
+
/** Whether a history row records a baseline. */
|
|
33
|
+
export const isBaselineRow = (row) => row.kind === "migration" && (row.note === BASELINE_NOTE || row.note.startsWith(`${BASELINE_NOTE}:`));
|
|
34
|
+
/** The one row that records `m` applied as a baseline. */
|
|
35
|
+
export function baselineRow(m, input) {
|
|
36
|
+
if (m.parent !== null)
|
|
37
|
+
throw new YodelError(`${m.id} follows ${m.parent}; only a first migration can be a baseline`);
|
|
38
|
+
return {
|
|
39
|
+
seq: input.seq,
|
|
40
|
+
run_id: input.runId,
|
|
41
|
+
kind: "migration",
|
|
42
|
+
migration_id: m.id,
|
|
43
|
+
parent: "",
|
|
44
|
+
checksum: m.checksum,
|
|
45
|
+
statement_index: MIGRATION_ROW,
|
|
46
|
+
statement_sha: "",
|
|
47
|
+
status: "succeeded",
|
|
48
|
+
error: "",
|
|
49
|
+
applied_by: input.appliedBy,
|
|
50
|
+
plan_digest: "",
|
|
51
|
+
silences: "[]",
|
|
52
|
+
note: baselineNote(input.environment),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Why the history cannot take a baseline, or `undefined`: a baseline is the first thing a history records. */
|
|
56
|
+
export function historyRefusal(state, location) {
|
|
57
|
+
if (state.migrations.length === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
const ids = state.migrations.map((m) => m.id);
|
|
60
|
+
return new YodelError(`refused: the history (${location}) already records ${ids.length === 1 ? "a migration" : `${String(ids.length)} migrations`} (${ids.slice(0, 3).join(", ")}${ids.length > 3 ? ", ..." : ""}); ` +
|
|
61
|
+
`this environment is already under versioned migrations, so there is nothing to adopt. Nothing was written.`, REFUSED);
|
|
62
|
+
}
|
|
63
|
+
/** Reads the history without the lock, for a refusal before anything is written. */
|
|
64
|
+
export async function checkHistoryEmpty(venv) {
|
|
65
|
+
const refusal = historyRefusal(await venv.store.state(), `${venv.location.database}.history`);
|
|
66
|
+
if (refusal)
|
|
67
|
+
throw refusal;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Records `m` applied as the baseline, under the apply lock: makes the
|
|
71
|
+
* history when it is not there, checks again that it records nothing, and
|
|
72
|
+
* appends the one row.
|
|
73
|
+
*/
|
|
74
|
+
export async function recordBaseline(venv, m, processEnv = process.env) {
|
|
75
|
+
const by = appliedBy(processEnv);
|
|
76
|
+
const lock = await venv.driver.acquireLock(by);
|
|
77
|
+
return withLock(lock, venv.lockTtlMs, async () => {
|
|
78
|
+
await venv.store.ensure();
|
|
79
|
+
const state = await venv.store.state();
|
|
80
|
+
const refusal = historyRefusal(state, `${venv.location.database}.history`);
|
|
81
|
+
if (refusal)
|
|
82
|
+
throw refusal;
|
|
83
|
+
const row = baselineRow(m, { seq: state.maxSeq + 1, runId: randomUUID(), appliedBy: by, environment: venv.environment });
|
|
84
|
+
await venv.store.append([row]);
|
|
85
|
+
return { row, lock: { kind: lock.kind, where: lock.where, ...(lock.notice ? { notice: lock.notice } : {}) } };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=baseline.js.map
|