@intentius/sql-yodeler 0.0.0-stage → 0.1.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 +110 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +51 -0
- package/dist/chant/apply.js +104 -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 +44 -0
- package/dist/chant/drift.js +91 -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/cli.d.ts +4 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +17 -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 +306 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/drift.d.ts +3 -0
- package/dist/commands/drift.js +38 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +193 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/new.d.ts +3 -0
- package/dist/commands/new.js +168 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +171 -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 +3 -0
- package/dist/commands/repair.js +117 -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 +50 -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 +102 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +40 -0
- package/dist/config/settings.js +88 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +18 -0
- package/dist/env-args.js +62 -0
- package/dist/env-args.js.map +1 -0
- package/dist/history/client.d.ts +17 -0
- package/dist/history/client.js +19 -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 +100 -0
- package/dist/history/lock.js +224 -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 +138 -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 +28 -0
- package/dist/init/live.js +45 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +81 -0
- package/dist/lint/check.js +208 -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/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 +73 -0
- package/dist/migrations/create.js +119 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +225 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +83 -0
- package/dist/migrations/document.js +82 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +20 -0
- package/dist/migrations/index.js +21 -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 +50 -0
- package/dist/pr/forge.js +105 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/model.d.ts +91 -0
- package/dist/pr/model.js +113 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +27 -0
- package/dist/pr/render.js +246 -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 +36 -0
- package/dist/replay/index.js +54 -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/steps/backfill.d.ts +128 -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 +162 -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/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +34 -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 +41 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +152 -0
- package/dist/versioned/engine.js +277 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +289 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +37 -0
- package/dist/versioned/index.js +54 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +62 -0
- package/dist/versioned/op.js +153 -0
- package/dist/versioned/op.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 +69 -0
- package/dist/versioned/postgres/driver.js +113 -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/report.d.ts +22 -0
- package/dist/versioned/report.js +81 -0
- package/dist/versioned/report.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 +51 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History rows and the state they add up to. Pure: rows in, state out.
|
|
3
|
+
*
|
|
4
|
+
* Rows are only ever appended. The state of a migration is its migration
|
|
5
|
+
* row with the highest `seq`; the state of one of its statements is that
|
|
6
|
+
* statement's row with the highest `seq`. A migration whose latest row is
|
|
7
|
+
* `succeeded` is applied. One whose latest row is `failed` (a statement
|
|
8
|
+
* failed) or `started` (the runner died) is part way: it resumes at the
|
|
9
|
+
* first statement whose latest row is not `succeeded`.
|
|
10
|
+
*/
|
|
11
|
+
export type HistoryStatus = "started" | "succeeded" | "failed";
|
|
12
|
+
/** `step`: an Op step (#12), recorded as a statement is, its `note` the step's summary (receipts skipped and run). */
|
|
13
|
+
export type HistoryKind = "migration" | "statement" | "step";
|
|
14
|
+
/** The statement index of a migration's own rows. */
|
|
15
|
+
export declare const MIGRATION_ROW = -1;
|
|
16
|
+
/** One row of the history table, as written and as read back. */
|
|
17
|
+
export interface HistoryRow {
|
|
18
|
+
seq: number;
|
|
19
|
+
/** The apply run that wrote it. */
|
|
20
|
+
run_id: string;
|
|
21
|
+
kind: HistoryKind;
|
|
22
|
+
migration_id: string;
|
|
23
|
+
/** The migration's parent id, `""` for the first. */
|
|
24
|
+
parent: string;
|
|
25
|
+
/** The migration's checksum when the row was written. */
|
|
26
|
+
checksum: string;
|
|
27
|
+
/** {@link MIGRATION_ROW} on a migration row; else the statement's (or Op step's) index in the migration's `steps`. */
|
|
28
|
+
statement_index: number;
|
|
29
|
+
/** `sha256:<hex>` of the statement's SQL; `""` on a migration row. */
|
|
30
|
+
statement_sha: string;
|
|
31
|
+
status: HistoryStatus;
|
|
32
|
+
/** The server's message, on a `failed` row. */
|
|
33
|
+
error: string;
|
|
34
|
+
/** Who ran it: the CI actor or the user, and the host. */
|
|
35
|
+
applied_by: string;
|
|
36
|
+
/** The plan digest the run was approved under. */
|
|
37
|
+
plan_digest: string;
|
|
38
|
+
/** The silences (`-- yodel:allow <RULE> <reason>`) in force, as JSON text; `"[]"` when none. */
|
|
39
|
+
silences: string;
|
|
40
|
+
/** Free text, e.g. a repair's reason (#15). */
|
|
41
|
+
note: string;
|
|
42
|
+
/** The server's time, `YYYY-MM-DD hh:mm:ss.ffffff` UTC; absent on a row not yet written. */
|
|
43
|
+
recorded_at?: string;
|
|
44
|
+
}
|
|
45
|
+
/** The hash a statement row records of the SQL it sent. */
|
|
46
|
+
export declare const statementSha: (sql: string) => string;
|
|
47
|
+
export interface StatementRecord {
|
|
48
|
+
index: number;
|
|
49
|
+
status: HistoryStatus;
|
|
50
|
+
sha: string;
|
|
51
|
+
error: string;
|
|
52
|
+
seq: number;
|
|
53
|
+
}
|
|
54
|
+
/** What the history says about one migration. */
|
|
55
|
+
export interface MigrationRecord {
|
|
56
|
+
id: string;
|
|
57
|
+
parent: string | null;
|
|
58
|
+
/** The checksum on its latest migration row. */
|
|
59
|
+
checksum: string;
|
|
60
|
+
status: HistoryStatus;
|
|
61
|
+
error: string;
|
|
62
|
+
/** Who applied it; until it is applied, who wrote its latest migration row. A repair does not change it. */
|
|
63
|
+
appliedBy: string;
|
|
64
|
+
planDigest: string;
|
|
65
|
+
/** When it was applied (the row that first recorded it succeeded); until then, its latest migration row's time. A repair does not change it. */
|
|
66
|
+
recordedAt: string;
|
|
67
|
+
/** The repairs recorded since it was applied (`yodel repair`: a later succeeded migration row), oldest first. */
|
|
68
|
+
repairs: Repair[];
|
|
69
|
+
/** The seq of its latest migration row. */
|
|
70
|
+
seq: number;
|
|
71
|
+
/** The seq of the first row that recorded it `succeeded`; orders applied migrations by when they were applied. */
|
|
72
|
+
appliedSeq: number | undefined;
|
|
73
|
+
/** Each statement's latest row, by index. */
|
|
74
|
+
statements: Map<number, StatementRecord>;
|
|
75
|
+
}
|
|
76
|
+
/** A `yodel repair` of an applied migration: the new checksum, who recorded it, when and why. */
|
|
77
|
+
export interface Repair {
|
|
78
|
+
checksum: string;
|
|
79
|
+
by: string;
|
|
80
|
+
at: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
seq: number;
|
|
83
|
+
}
|
|
84
|
+
export interface HistoryState {
|
|
85
|
+
/** Every migration the history names, in the order each was first recorded. */
|
|
86
|
+
migrations: MigrationRecord[];
|
|
87
|
+
byId: Map<string, MigrationRecord>;
|
|
88
|
+
/** The highest seq in the history, 0 when it is empty. */
|
|
89
|
+
maxSeq: number;
|
|
90
|
+
}
|
|
91
|
+
/** The state the rows add up to. Rows may come in any order. */
|
|
92
|
+
export declare function deriveState(rows: readonly HistoryRow[]): HistoryState;
|
|
93
|
+
/** Whether the history records the migration applied. */
|
|
94
|
+
export declare const isApplied: (m: MigrationRecord | undefined) => boolean;
|
|
95
|
+
/** Whether the statement at `index` has already run: its latest row is `succeeded`. Such a statement is never sent again. */
|
|
96
|
+
export declare const statementDone: (m: MigrationRecord | undefined, index: number) => boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History rows and the state they add up to. Pure: rows in, state out.
|
|
3
|
+
*
|
|
4
|
+
* Rows are only ever appended. The state of a migration is its migration
|
|
5
|
+
* row with the highest `seq`; the state of one of its statements is that
|
|
6
|
+
* statement's row with the highest `seq`. A migration whose latest row is
|
|
7
|
+
* `succeeded` is applied. One whose latest row is `failed` (a statement
|
|
8
|
+
* failed) or `started` (the runner died) is part way: it resumes at the
|
|
9
|
+
* first statement whose latest row is not `succeeded`.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
/** The statement index of a migration's own rows. */
|
|
13
|
+
export const MIGRATION_ROW = -1;
|
|
14
|
+
/** The hash a statement row records of the SQL it sent. */
|
|
15
|
+
export const statementSha = (sql) => `sha256:${createHash("sha256").update(sql, "utf8").digest("hex")}`;
|
|
16
|
+
/** The state the rows add up to. Rows may come in any order. */
|
|
17
|
+
export function deriveState(rows) {
|
|
18
|
+
const sorted = [...rows].sort((a, b) => a.seq - b.seq);
|
|
19
|
+
const byId = new Map();
|
|
20
|
+
const order = [];
|
|
21
|
+
let maxSeq = 0;
|
|
22
|
+
for (const r of sorted) {
|
|
23
|
+
maxSeq = Math.max(maxSeq, r.seq);
|
|
24
|
+
let m = byId.get(r.migration_id);
|
|
25
|
+
if (!m) {
|
|
26
|
+
m = {
|
|
27
|
+
id: r.migration_id,
|
|
28
|
+
parent: r.parent === "" ? null : r.parent,
|
|
29
|
+
checksum: r.checksum,
|
|
30
|
+
status: "started",
|
|
31
|
+
error: "",
|
|
32
|
+
appliedBy: r.applied_by,
|
|
33
|
+
planDigest: r.plan_digest,
|
|
34
|
+
recordedAt: r.recorded_at ?? "",
|
|
35
|
+
seq: r.seq,
|
|
36
|
+
appliedSeq: undefined,
|
|
37
|
+
repairs: [],
|
|
38
|
+
statements: new Map(),
|
|
39
|
+
};
|
|
40
|
+
byId.set(r.migration_id, m);
|
|
41
|
+
order.push(m);
|
|
42
|
+
}
|
|
43
|
+
if (r.kind === "migration" && r.status === "succeeded" && m.status === "succeeded" && m.appliedSeq !== undefined) {
|
|
44
|
+
// Already applied: a repair. It records a new checksum; the migration was applied when and by whom it was.
|
|
45
|
+
m.parent = r.parent === "" ? null : r.parent;
|
|
46
|
+
m.checksum = r.checksum;
|
|
47
|
+
m.planDigest = r.plan_digest;
|
|
48
|
+
m.seq = r.seq;
|
|
49
|
+
m.repairs.push({ checksum: r.checksum, by: r.applied_by, at: r.recorded_at ?? "", reason: r.note, seq: r.seq });
|
|
50
|
+
}
|
|
51
|
+
else if (r.kind === "migration") {
|
|
52
|
+
m.parent = r.parent === "" ? null : r.parent;
|
|
53
|
+
m.checksum = r.checksum;
|
|
54
|
+
m.status = r.status;
|
|
55
|
+
m.error = r.error;
|
|
56
|
+
m.appliedBy = r.applied_by;
|
|
57
|
+
m.planDigest = r.plan_digest;
|
|
58
|
+
m.recordedAt = r.recorded_at ?? "";
|
|
59
|
+
m.seq = r.seq;
|
|
60
|
+
if (r.status === "succeeded" && m.appliedSeq === undefined)
|
|
61
|
+
m.appliedSeq = r.seq;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
m.statements.set(r.statement_index, { index: r.statement_index, status: r.status, sha: r.statement_sha, error: r.error, seq: r.seq });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { migrations: order, byId, maxSeq };
|
|
68
|
+
}
|
|
69
|
+
/** Whether the history records the migration applied. */
|
|
70
|
+
export const isApplied = (m) => m?.status === "succeeded";
|
|
71
|
+
/** Whether the statement at `index` has already run: its latest row is `succeeded`. Such a statement is never sent again. */
|
|
72
|
+
export const statementDone = (m, index) => m?.statements.get(index)?.status === "succeeded";
|
|
73
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/history/rows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and appending the history table. Nothing here updates or deletes
|
|
3
|
+
* a row.
|
|
4
|
+
*/
|
|
5
|
+
import type { ClickHousePort } from "./client.js";
|
|
6
|
+
import { type HistoryTopology } from "./topology.js";
|
|
7
|
+
import { type HistoryRow, type HistoryState } from "./rows.js";
|
|
8
|
+
export interface HistoryLocation {
|
|
9
|
+
database: string;
|
|
10
|
+
topology: HistoryTopology;
|
|
11
|
+
}
|
|
12
|
+
/** Who is applying: the CI actor or the user, at this host. */
|
|
13
|
+
export declare function appliedBy(env?: NodeJS.ProcessEnv): string;
|
|
14
|
+
type RawRow = Record<string, unknown>;
|
|
15
|
+
/** A row as a server returns it (strings or numbers), as a {@link HistoryRow}. */
|
|
16
|
+
export declare function fromRaw(r: RawRow): HistoryRow;
|
|
17
|
+
/** What the versioned verbs need of a history, on either dialect: this ClickHouse store, or `./postgres/store.ts`. */
|
|
18
|
+
export interface HistoryStoreLike {
|
|
19
|
+
exists(): Promise<boolean>;
|
|
20
|
+
ensure(): Promise<void>;
|
|
21
|
+
rows(options?: {
|
|
22
|
+
sync?: boolean;
|
|
23
|
+
}): Promise<HistoryRow[]>;
|
|
24
|
+
state(options?: {
|
|
25
|
+
sync?: boolean;
|
|
26
|
+
}): Promise<HistoryState>;
|
|
27
|
+
append(rows: readonly HistoryRow[]): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare class HistoryStore implements HistoryStoreLike {
|
|
30
|
+
readonly port: ClickHousePort;
|
|
31
|
+
readonly location: HistoryLocation;
|
|
32
|
+
constructor(port: ClickHousePort, location: HistoryLocation);
|
|
33
|
+
get table(): string;
|
|
34
|
+
/** Whether the history table is there. A history that was never made has no rows. */
|
|
35
|
+
exists(): Promise<boolean>;
|
|
36
|
+
/** Creates the database and table when they are not there. */
|
|
37
|
+
ensure(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Every row. On a cluster or a Replicated database the replica first
|
|
40
|
+
* catches up with the others (`SYSTEM SYNC REPLICA`), so a run reads what
|
|
41
|
+
* a run on another host wrote; on Cloud the read is sequentially
|
|
42
|
+
* consistent. `sync: false` skips that, for a status read that need not
|
|
43
|
+
* wait.
|
|
44
|
+
*/
|
|
45
|
+
rows(options?: {
|
|
46
|
+
sync?: boolean;
|
|
47
|
+
}): Promise<HistoryRow[]>;
|
|
48
|
+
state(options?: {
|
|
49
|
+
sync?: boolean;
|
|
50
|
+
}): Promise<HistoryState>;
|
|
51
|
+
/** Appends rows, as written; `recorded_at` is the server's. */
|
|
52
|
+
append(rows: readonly HistoryRow[]): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/** The fields a run sets on every row it writes. */
|
|
55
|
+
export interface RunContext {
|
|
56
|
+
runId: string;
|
|
57
|
+
appliedBy: string;
|
|
58
|
+
planDigest: string;
|
|
59
|
+
}
|
|
60
|
+
export declare const newRunContext: (planDigest: string, env?: NodeJS.ProcessEnv) => RunContext;
|
|
61
|
+
/**
|
|
62
|
+
* Writes one run's rows, each with the next `seq`. Made under the lock from
|
|
63
|
+
* the history's highest seq, so seqs never collide across runs.
|
|
64
|
+
*/
|
|
65
|
+
export declare class HistoryRecorder {
|
|
66
|
+
readonly store: HistoryStoreLike;
|
|
67
|
+
readonly run: RunContext;
|
|
68
|
+
private next;
|
|
69
|
+
constructor(store: HistoryStoreLike, run: RunContext, maxSeq: number);
|
|
70
|
+
record(row: Omit<HistoryRow, "seq" | "run_id" | "applied_by" | "plan_digest" | "recorded_at" | "error" | "note" | "silences"> & Partial<Pick<HistoryRow, "error" | "note" | "silences">>): Promise<HistoryRow>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
@@ -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":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,UAAU,EAAwB,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,WAAW,EAAsC,MAAM,WAAW,CAAC;AAO5E,6GAA6G;AAC7G,MAAM,cAAc,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC;AAEtE,+DAA+D;AAC/D,MAAM,UAAU,SAAS,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC5D,IAAI,IAAI,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,iBAAiB,IAAI,GAAG,CAAC,IAAI,CAAC;IACjE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,EAAE,CAAC,QAAQ,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,IAAI,IAAI,QAAQ,EAAE,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;AAI1D,kFAAkF;AAClF,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,MAAM,CAAC,GAAG,CAAC,CAAS,EAAU,EAAE;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACf,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtG,CAAC,CAAC;IACF,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;QAClB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC;QACnB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,MAAM,CAA0B,CAAC,CAAC,CAAC,WAAW;QAC3G,YAAY,EAAE,CAAC,CAAC,cAAc,CAAC;QAC/B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC;QACnB,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC;QAC1C,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC;QACjC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAyB;QACpH,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;QACjB,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC;QAC3B,WAAW,EAAE,CAAC,CAAC,aAAa,CAAC;QAC7B,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI;QAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,WAAW,EAAE,CAAC,CAAC,aAAa,CAAC;KAC9B,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,YAAY;IAEZ;IACA;IAFX,YACW,IAAoB,EACpB,QAAyB;QADzB,SAAI,GAAJ,IAAI,CAAgB;QACpB,aAAQ,GAAR,QAAQ,CAAiB;IACjC,CAAC;IAEJ,IAAI,KAAK;QACP,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC;IAC7D,CAAC;IAED,qFAAqF;IACrF,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAChC,2DAA2D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,OAAO,CAAC,aAAa,CAAC,EAAE,CAClI,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,MAAM;QACV,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1E,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,UAA8B,EAAE;QACzC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACnC,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/G,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,6BAA6B,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjI,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAS,UAAU,MAAM,SAAS,IAAI,CAAC,KAAK,eAAe,EAAE,QAAQ,CAAC,CAAC;QACxG,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,UAA8B,EAAE;QAC1C,OAAO,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,MAAM,CAAC,IAA2B;QACtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,IAAI,GAAG,IAAI;aACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;aAC5D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,wBAAwB,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/G,CAAC;CACF;AASD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAE,MAAyB,OAAO,CAAC,GAAG,EAAc,EAAE,CAAC,CAAC;IACtG,KAAK,EAAE,UAAU,EAAE;IACnB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC;IACzB,UAAU;CACX,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,OAAO,eAAe;IAIf;IACA;IAJH,IAAI,CAAS;IAErB,YACW,KAAuB,EACvB,GAAe,EACxB,MAAc;QAFL,UAAK,GAAL,KAAK,CAAkB;QACvB,QAAG,GAAH,GAAG,CAAY;QAGxB,IAAI,CAAC,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAiL;QAC5L,MAAM,IAAI,GAAe;YACvB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,GAAG,GAAG;YACN,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE;YAChB,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK;YACtB,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS;YAC9B,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU;SACjC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -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, 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>;
|