@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,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One environment's settings, from `yodel.config.ts`, chant's profile and
|
|
3
|
+
* the process environment, first one set wins:
|
|
4
|
+
*
|
|
5
|
+
* | setting | 1. yodel.config.ts | 2. chant.config.ts | 3. env var | default |
|
|
6
|
+
* |------------------|----------------------------------------|-----------------------------------|-------------------------|----------|
|
|
7
|
+
* | topology | `environments.<env>.topology` | `sql.profiles.<env>.topology` | `YODEL_TOPOLOGY` | `single` |
|
|
8
|
+
* | history database | `environments.<env>.history.database` | | `YODEL_HISTORY_DATABASE`| `yodeler`|
|
|
9
|
+
* | lock TTL | `environments.<env>.lockTtl` (seconds) | | `YODEL_LOCK_TTL` | 900 |
|
|
10
|
+
*/
|
|
11
|
+
import { type Topology } from "@intentius/chant-lexicon-sql/clickhouse/topology";
|
|
12
|
+
import type { HistoryTopology } from "../history/topology.js";
|
|
13
|
+
import { YodelError, type Project } from "../project.js";
|
|
14
|
+
export interface EnvironmentSettings {
|
|
15
|
+
/** What every statement is rendered for when it is sent. */
|
|
16
|
+
topology: Topology;
|
|
17
|
+
/** Where {@link topology} came from, for messages: `yodel.config.ts`, `sql.profiles.<env>.topology`, `YODEL_TOPOLOGY` or `default`. */
|
|
18
|
+
topologySource: string;
|
|
19
|
+
/**
|
|
20
|
+
* The history's topology: {@link topology} without a cluster's Keeper
|
|
21
|
+
* path and replica name, which are for the migrations' tables. The
|
|
22
|
+
* history keeps its own path, one copy for the whole cluster.
|
|
23
|
+
*/
|
|
24
|
+
historyTopology: HistoryTopology;
|
|
25
|
+
historyDatabase: string;
|
|
26
|
+
lockTtlMs: number;
|
|
27
|
+
}
|
|
28
|
+
/** The topology the history's DDL is written for. */
|
|
29
|
+
export declare function historyTopologyOf(t: Topology): HistoryTopology;
|
|
30
|
+
/** The settings of environment `name`; see the module comment for the order. */
|
|
31
|
+
export declare function environmentSettings(project: Pick<Project, "config" | "yodel">, name: string, env?: NodeJS.ProcessEnv): EnvironmentSettings;
|
|
32
|
+
/** `cluster:main (yodel.config.ts)`, for a report's header. */
|
|
33
|
+
export declare const describeSettingsTopology: (s: Pick<EnvironmentSettings, "topology" | "topologySource">) => string;
|
|
34
|
+
/**
|
|
35
|
+
* The declarative path (`yodel plan`, `yodel apply` without migrations) runs
|
|
36
|
+
* chant's planner and applier, which render for `sql.profiles.<env>.topology`
|
|
37
|
+
* and do not read `yodel.config.ts`. A topology set only in yodel.config.ts
|
|
38
|
+
* would be ignored there, so it is refused unless chant's profile says the same.
|
|
39
|
+
*/
|
|
40
|
+
export declare function declarativeTopologyRefusal(project: Pick<Project, "config" | "yodel">, name: string): YodelError | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One environment's settings, from `yodel.config.ts`, chant's profile and
|
|
3
|
+
* the process environment, first one set wins:
|
|
4
|
+
*
|
|
5
|
+
* | setting | 1. yodel.config.ts | 2. chant.config.ts | 3. env var | default |
|
|
6
|
+
* |------------------|----------------------------------------|-----------------------------------|-------------------------|----------|
|
|
7
|
+
* | topology | `environments.<env>.topology` | `sql.profiles.<env>.topology` | `YODEL_TOPOLOGY` | `single` |
|
|
8
|
+
* | history database | `environments.<env>.history.database` | | `YODEL_HISTORY_DATABASE`| `yodeler`|
|
|
9
|
+
* | lock TTL | `environments.<env>.lockTtl` (seconds) | | `YODEL_LOCK_TTL` | 900 |
|
|
10
|
+
*/
|
|
11
|
+
import { toTopology, topologyLabel } from "@intentius/chant-lexicon-sql/clickhouse/topology";
|
|
12
|
+
import { DEFAULT_HISTORY_DATABASE } from "../history/ddl.js";
|
|
13
|
+
import { DEFAULT_LOCK_TTL_MS } from "../history/lock.js";
|
|
14
|
+
import { YodelError } from "../project.js";
|
|
15
|
+
import { CONFIG_FILE } from "./load.js";
|
|
16
|
+
function parse(value, where) {
|
|
17
|
+
try {
|
|
18
|
+
return toTopology(value);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
throw new YodelError(`${where}: ${e instanceof Error ? e.message : String(e)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The topology the history's DDL is written for. */
|
|
25
|
+
export function historyTopologyOf(t) {
|
|
26
|
+
return t.kind === "cluster" ? { kind: "cluster", cluster: t.cluster } : t;
|
|
27
|
+
}
|
|
28
|
+
/** The settings of environment `name`; see the module comment for the order. */
|
|
29
|
+
export function environmentSettings(project, name, env = process.env) {
|
|
30
|
+
const own = project.yodel?.config.environments?.[name];
|
|
31
|
+
const profile = project.config.sql?.profiles?.[name];
|
|
32
|
+
let topology;
|
|
33
|
+
let topologySource;
|
|
34
|
+
if (own?.topology !== undefined) {
|
|
35
|
+
topology = parse(own.topology, `${CONFIG_FILE}: environments.${name}.topology`);
|
|
36
|
+
topologySource = CONFIG_FILE;
|
|
37
|
+
}
|
|
38
|
+
else if (profile?.topology !== undefined) {
|
|
39
|
+
topology = parse(profile.topology, `sql.profiles.${name}.topology`);
|
|
40
|
+
topologySource = `sql.profiles.${name}.topology`;
|
|
41
|
+
}
|
|
42
|
+
else if (env.YODEL_TOPOLOGY) {
|
|
43
|
+
topology = parse(env.YODEL_TOPOLOGY, "YODEL_TOPOLOGY");
|
|
44
|
+
topologySource = "YODEL_TOPOLOGY";
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
topology = { kind: "single" };
|
|
48
|
+
topologySource = "default";
|
|
49
|
+
}
|
|
50
|
+
let lockTtlMs = DEFAULT_LOCK_TTL_MS;
|
|
51
|
+
if (own?.lockTtl !== undefined)
|
|
52
|
+
lockTtlMs = own.lockTtl * 1000;
|
|
53
|
+
else if (env.YODEL_LOCK_TTL) {
|
|
54
|
+
const ttl = Number(env.YODEL_LOCK_TTL);
|
|
55
|
+
if (!(Number.isFinite(ttl) && ttl > 0))
|
|
56
|
+
throw new YodelError(`YODEL_LOCK_TTL must be a number of seconds, got ${JSON.stringify(env.YODEL_LOCK_TTL)}`);
|
|
57
|
+
lockTtlMs = ttl * 1000;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
topology,
|
|
61
|
+
topologySource,
|
|
62
|
+
historyTopology: historyTopologyOf(topology),
|
|
63
|
+
historyDatabase: own?.history?.database ?? (env.YODEL_HISTORY_DATABASE || DEFAULT_HISTORY_DATABASE),
|
|
64
|
+
lockTtlMs,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** `cluster:main (yodel.config.ts)`, for a report's header. */
|
|
68
|
+
export const describeSettingsTopology = (s) => `${topologyLabel(s.topology)} (${s.topologySource})`;
|
|
69
|
+
/**
|
|
70
|
+
* The declarative path (`yodel plan`, `yodel apply` without migrations) runs
|
|
71
|
+
* chant's planner and applier, which render for `sql.profiles.<env>.topology`
|
|
72
|
+
* and do not read `yodel.config.ts`. A topology set only in yodel.config.ts
|
|
73
|
+
* would be ignored there, so it is refused unless chant's profile says the same.
|
|
74
|
+
*/
|
|
75
|
+
export function declarativeTopologyRefusal(project, name) {
|
|
76
|
+
const own = project.yodel?.config.environments?.[name]?.topology;
|
|
77
|
+
if (own === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const profile = project.config.sql?.profiles?.[name]?.topology;
|
|
80
|
+
const ours = parse(own, `${CONFIG_FILE}: environments.${name}.topology`);
|
|
81
|
+
const chants = profile === undefined ? undefined : parse(profile, `sql.profiles.${name}.topology`);
|
|
82
|
+
if (chants && JSON.stringify(sorted(chants)) === JSON.stringify(sorted(ours)))
|
|
83
|
+
return undefined;
|
|
84
|
+
return new YodelError(`${CONFIG_FILE} sets environments.${name}.topology to ${topologyLabel(ours)}, but the declarative path plans and applies through chant, which reads sql.profiles.${name}.topology ` +
|
|
85
|
+
`(${chants ? topologyLabel(chants) : "unset"}). Set sql.profiles.${name}.topology to the same in chant.config.ts, or leave the topology to chant.config.ts.`);
|
|
86
|
+
}
|
|
87
|
+
const sorted = (t) => Object.entries(t).sort(([a], [b]) => a.localeCompare(b));
|
|
88
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/config/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Io } from "./command.js";
|
|
2
|
+
/** The arguments `yodel <verb> <env>` takes: the environment, `--json`, `--dir`, and any extra string options. */
|
|
3
|
+
export interface EnvArgs {
|
|
4
|
+
env: string;
|
|
5
|
+
json: boolean;
|
|
6
|
+
/** The project directory, or a directory inside it. Default: the working directory. */
|
|
7
|
+
dir: string;
|
|
8
|
+
options: Record<string, string | undefined>;
|
|
9
|
+
}
|
|
10
|
+
/** True when the arguments ask for the verb's help. */
|
|
11
|
+
export declare const wantsHelp: (args: readonly string[]) => boolean;
|
|
12
|
+
export declare function parseEnvArgs(verb: string, args: string[], extra?: readonly string[]): EnvArgs;
|
|
13
|
+
export declare function usage(verb: string, extra?: readonly string[]): string;
|
|
14
|
+
/**
|
|
15
|
+
* Runs a verb: prints `help` for `--help`, else runs `body`, turning a
|
|
16
|
+
* {@link YodelError} (or any error) into a message on stderr and its exit code.
|
|
17
|
+
*/
|
|
18
|
+
export declare function guarded(verb: string, io: Io, args: readonly string[], help: string, body: () => Promise<number>): Promise<number>;
|
package/dist/env-args.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { YodelError } from "./project.js";
|
|
3
|
+
/** True when the arguments ask for the verb's help. */
|
|
4
|
+
export const wantsHelp = (args) => args.includes("--help") || args.includes("-h");
|
|
5
|
+
export function parseEnvArgs(verb, args, extra = []) {
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = parseArgs({
|
|
9
|
+
args,
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
strict: true,
|
|
12
|
+
options: {
|
|
13
|
+
json: { type: "boolean" },
|
|
14
|
+
dir: { type: "string", short: "C" },
|
|
15
|
+
...Object.fromEntries(extra.map((name) => [name, { type: "string" }])),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
throw new YodelError(`${e instanceof Error ? e.message : String(e)}\n${usage(verb, extra)}`);
|
|
21
|
+
}
|
|
22
|
+
const { values, positionals } = parsed;
|
|
23
|
+
const [env, ...rest] = positionals;
|
|
24
|
+
if (env === undefined || rest.length > 0)
|
|
25
|
+
throw new YodelError(usage(verb, extra));
|
|
26
|
+
const options = {};
|
|
27
|
+
for (const name of extra) {
|
|
28
|
+
const v = values[name];
|
|
29
|
+
options[name] = typeof v === "string" ? v : undefined;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
env,
|
|
33
|
+
json: values.json === true,
|
|
34
|
+
dir: typeof values.dir === "string" ? values.dir : process.cwd(),
|
|
35
|
+
options,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function usage(verb, extra = []) {
|
|
39
|
+
return `usage: yodel ${verb} <env> [--json] [--dir <project>]${extra.map((e) => ` [--${e} <${e}>]`).join("")}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Runs a verb: prints `help` for `--help`, else runs `body`, turning a
|
|
43
|
+
* {@link YodelError} (or any error) into a message on stderr and its exit code.
|
|
44
|
+
*/
|
|
45
|
+
export async function guarded(verb, io, args, help, body) {
|
|
46
|
+
if (wantsHelp(args)) {
|
|
47
|
+
io.out(help);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return await body();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof YodelError) {
|
|
55
|
+
io.err(`yodel ${verb}: ${e.message}\n`);
|
|
56
|
+
return e.exitCode;
|
|
57
|
+
}
|
|
58
|
+
io.err(`yodel ${verb}: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}\n`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=env-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-args.js","sourceRoot":"","sources":["../src/env-args.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one thing the history needs from a ClickHouse server: run a statement,
|
|
3
|
+
* get rows back. Over chant's own HTTP client in use; an in-memory fake in
|
|
4
|
+
* the unit tests.
|
|
5
|
+
*/
|
|
6
|
+
import type { ClickHouseEndpoint } from "@intentius/chant-lexicon-sql/clickhouse/http";
|
|
7
|
+
export interface QueryOptions {
|
|
8
|
+
/** Server settings for this query alone. `database` sets the default database. */
|
|
9
|
+
settings?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface ClickHousePort {
|
|
12
|
+
query<Row = Record<string, unknown>>(sql: string, options?: QueryOptions): Promise<Row[]>;
|
|
13
|
+
}
|
|
14
|
+
/** A port over chant's ClickHouse HTTP client. */
|
|
15
|
+
export declare function httpPort(endpoint: ClickHouseEndpoint): Promise<ClickHousePort>;
|
|
16
|
+
/** The server's message for an error from a port, first line. */
|
|
17
|
+
export declare function serverMessage(e: unknown): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one thing the history needs from a ClickHouse server: run a statement,
|
|
3
|
+
* get rows back. Over chant's own HTTP client in use; an in-memory fake in
|
|
4
|
+
* the unit tests.
|
|
5
|
+
*/
|
|
6
|
+
/** A port over chant's ClickHouse HTTP client. */
|
|
7
|
+
export async function httpPort(endpoint) {
|
|
8
|
+
const { clickhouseQuery } = await import("@intentius/chant-lexicon-sql/clickhouse/http");
|
|
9
|
+
return {
|
|
10
|
+
query: (sql, options = {}) => clickhouseQuery(endpoint, sql, options.settings ? { settings: options.settings } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** The server's message for an error from a port, first line. */
|
|
14
|
+
export function serverMessage(e) {
|
|
15
|
+
if (e && typeof e === "object" && "serverMessage" in e && typeof e.serverMessage === "string")
|
|
16
|
+
return e.serverMessage.trim();
|
|
17
|
+
return e instanceof Error ? e.message : String(e);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/history/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAA4B;IACzD,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,8CAA8C,CAAC,CAAC;IACzF,OAAO;QACL,KAAK,EAAE,CAAM,GAAW,EAAE,UAAwB,EAAE,EAAE,EAAE,CACtD,eAAe,CAAM,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9F,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAAC,CAAU;IACtC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,eAAe,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAC7H,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The history's DDL, per topology. Written out here rather than rendered by
|
|
3
|
+
* chant's topology renderer, so the history does not wait on a chant release
|
|
4
|
+
* and the four forms are plain to read and test:
|
|
5
|
+
*
|
|
6
|
+
* | topology | database | history table |
|
|
7
|
+
* |------------|--------------------------------------------|-------------------------------------------------|
|
|
8
|
+
* | single | `CREATE DATABASE` | `MergeTree` |
|
|
9
|
+
* | cluster | `CREATE DATABASE ... ON CLUSTER c` | `ReplicatedMergeTree('<path>', '<replica>')` `ON CLUSTER c` |
|
|
10
|
+
* | replicated | `ENGINE = Replicated(...)`, `ON CLUSTER` when the topology names one | `ReplicatedMergeTree` with no arguments (the database supplies them) |
|
|
11
|
+
* | cloud | `CREATE DATABASE` | `MergeTree` (Cloud makes it `SharedMergeTree`) |
|
|
12
|
+
*
|
|
13
|
+
* The lock table is a `KeeperMap` (see `./lock.ts`); on a cluster it is
|
|
14
|
+
* created `ON CLUSTER`, every host's table naming the same Keeper path, so
|
|
15
|
+
* there is one lock for the cluster.
|
|
16
|
+
*/
|
|
17
|
+
import { type HistoryTopology } from "./topology.js";
|
|
18
|
+
/** The database the history lives in when the environment names none. */
|
|
19
|
+
export declare const DEFAULT_HISTORY_DATABASE = "yodeler";
|
|
20
|
+
export declare const HISTORY_TABLE = "history";
|
|
21
|
+
export declare const LOCK_TABLE = "lock";
|
|
22
|
+
/** A ClickHouse identifier, back-quoted. */
|
|
23
|
+
export declare const ident: (name: string) => string;
|
|
24
|
+
/** A ClickHouse string literal. */
|
|
25
|
+
export declare const literal: (value: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* The history table's columns. One row per event, never updated or deleted:
|
|
28
|
+
*
|
|
29
|
+
* - a migration row (`kind = 'migration'`, `statement_index = -1`) when a
|
|
30
|
+
* migration starts, succeeds or fails;
|
|
31
|
+
* - a statement row (`kind = 'statement'`, `statement_index` its index in
|
|
32
|
+
* the migration's `steps`) when a statement starts, succeeds or fails;
|
|
33
|
+
* - a step row (`kind = 'step'`, the same) when an Op step (a rebuild, a
|
|
34
|
+
* backfill) starts, succeeds or fails, its `note` the step's summary as
|
|
35
|
+
* JSON (the receipts it skipped and the work it ran).
|
|
36
|
+
*
|
|
37
|
+
* The state of a migration or a statement is its row with the highest
|
|
38
|
+
* `seq`. `seq` is taken under the lock, one more than the highest in the
|
|
39
|
+
* table, so it orders rows across runs without trusting anyone's clock;
|
|
40
|
+
* `recorded_at` is the server's time, for people.
|
|
41
|
+
*/
|
|
42
|
+
export declare const HISTORY_COLUMNS: readonly (readonly [name: string, type: string])[];
|
|
43
|
+
export interface HistoryDdlOptions {
|
|
44
|
+
database: string;
|
|
45
|
+
topology: HistoryTopology;
|
|
46
|
+
}
|
|
47
|
+
/** `CREATE DATABASE IF NOT EXISTS` for the history's database. */
|
|
48
|
+
export declare function databaseDdl({ database, topology }: HistoryDdlOptions): string;
|
|
49
|
+
/** The history table's engine clause for a topology. */
|
|
50
|
+
export declare function historyEngine({ database, topology }: HistoryDdlOptions): string;
|
|
51
|
+
/** `CREATE TABLE IF NOT EXISTS <database>.history`. */
|
|
52
|
+
export declare function historyTableDdl(options: HistoryDdlOptions): string;
|
|
53
|
+
/** The Keeper path of an environment's lock, under the server's `keeper_map_path_prefix`. */
|
|
54
|
+
export declare const lockKeeperPath: (database: string) => string;
|
|
55
|
+
/** `CREATE TABLE IF NOT EXISTS <database>.lock`, a KeeperMap. */
|
|
56
|
+
export declare function lockTableDdl(options: HistoryDdlOptions): string;
|
|
57
|
+
/** Every statement that makes the history ready, in order: the database, then the table. */
|
|
58
|
+
export declare const historyDdl: (options: HistoryDdlOptions) => string[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The history's DDL, per topology. Written out here rather than rendered by
|
|
3
|
+
* chant's topology renderer, so the history does not wait on a chant release
|
|
4
|
+
* and the four forms are plain to read and test:
|
|
5
|
+
*
|
|
6
|
+
* | topology | database | history table |
|
|
7
|
+
* |------------|--------------------------------------------|-------------------------------------------------|
|
|
8
|
+
* | single | `CREATE DATABASE` | `MergeTree` |
|
|
9
|
+
* | cluster | `CREATE DATABASE ... ON CLUSTER c` | `ReplicatedMergeTree('<path>', '<replica>')` `ON CLUSTER c` |
|
|
10
|
+
* | replicated | `ENGINE = Replicated(...)`, `ON CLUSTER` when the topology names one | `ReplicatedMergeTree` with no arguments (the database supplies them) |
|
|
11
|
+
* | cloud | `CREATE DATABASE` | `MergeTree` (Cloud makes it `SharedMergeTree`) |
|
|
12
|
+
*
|
|
13
|
+
* The lock table is a `KeeperMap` (see `./lock.ts`); on a cluster it is
|
|
14
|
+
* created `ON CLUSTER`, every host's table naming the same Keeper path, so
|
|
15
|
+
* there is one lock for the cluster.
|
|
16
|
+
*/
|
|
17
|
+
import { defaultReplicaPath, DEFAULT_HISTORY_REPLICA_NAME } from "./topology.js";
|
|
18
|
+
/** The database the history lives in when the environment names none. */
|
|
19
|
+
export const DEFAULT_HISTORY_DATABASE = "yodeler";
|
|
20
|
+
export const HISTORY_TABLE = "history";
|
|
21
|
+
export const LOCK_TABLE = "lock";
|
|
22
|
+
/** A ClickHouse identifier, back-quoted. */
|
|
23
|
+
export const ident = (name) => `\`${name.replace(/\\/g, "\\\\").replace(/`/g, "\\`")}\``;
|
|
24
|
+
/** A ClickHouse string literal. */
|
|
25
|
+
export const literal = (value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
26
|
+
const onCluster = (t) => (t.kind === "cluster" ? ` ON CLUSTER ${ident(t.cluster)}` : "");
|
|
27
|
+
/**
|
|
28
|
+
* The history table's columns. One row per event, never updated or deleted:
|
|
29
|
+
*
|
|
30
|
+
* - a migration row (`kind = 'migration'`, `statement_index = -1`) when a
|
|
31
|
+
* migration starts, succeeds or fails;
|
|
32
|
+
* - a statement row (`kind = 'statement'`, `statement_index` its index in
|
|
33
|
+
* the migration's `steps`) when a statement starts, succeeds or fails;
|
|
34
|
+
* - a step row (`kind = 'step'`, the same) when an Op step (a rebuild, a
|
|
35
|
+
* backfill) starts, succeeds or fails, its `note` the step's summary as
|
|
36
|
+
* JSON (the receipts it skipped and the work it ran).
|
|
37
|
+
*
|
|
38
|
+
* The state of a migration or a statement is its row with the highest
|
|
39
|
+
* `seq`. `seq` is taken under the lock, one more than the highest in the
|
|
40
|
+
* table, so it orders rows across runs without trusting anyone's clock;
|
|
41
|
+
* `recorded_at` is the server's time, for people.
|
|
42
|
+
*/
|
|
43
|
+
export const HISTORY_COLUMNS = [
|
|
44
|
+
["seq", "UInt64"],
|
|
45
|
+
["run_id", "String"],
|
|
46
|
+
["kind", "LowCardinality(String)"],
|
|
47
|
+
["migration_id", "String"],
|
|
48
|
+
["parent", "String"],
|
|
49
|
+
["checksum", "String"],
|
|
50
|
+
["statement_index", "Int32"],
|
|
51
|
+
["statement_sha", "String"],
|
|
52
|
+
["status", "LowCardinality(String)"],
|
|
53
|
+
["error", "String"],
|
|
54
|
+
["applied_by", "String"],
|
|
55
|
+
["plan_digest", "String"],
|
|
56
|
+
["silences", "String DEFAULT '[]'"],
|
|
57
|
+
["note", "String"],
|
|
58
|
+
["recorded_at", "DateTime64(6, 'UTC') DEFAULT now64(6)"],
|
|
59
|
+
];
|
|
60
|
+
/** `CREATE DATABASE IF NOT EXISTS` for the history's database. */
|
|
61
|
+
export function databaseDdl({ database, topology }) {
|
|
62
|
+
if (topology.kind === "replicated") {
|
|
63
|
+
const cluster = topology.cluster ? ` ON CLUSTER ${ident(topology.cluster)}` : "";
|
|
64
|
+
return `CREATE DATABASE IF NOT EXISTS ${ident(database)}${cluster} ENGINE = Replicated(${literal(`/clickhouse/databases/${database}`)}, '{shard}', '{replica}')`;
|
|
65
|
+
}
|
|
66
|
+
return `CREATE DATABASE IF NOT EXISTS ${ident(database)}${onCluster(topology)}`;
|
|
67
|
+
}
|
|
68
|
+
/** The history table's engine clause for a topology. */
|
|
69
|
+
export function historyEngine({ database, topology }) {
|
|
70
|
+
switch (topology.kind) {
|
|
71
|
+
case "cluster": {
|
|
72
|
+
const path = topology.replicaPath ?? defaultReplicaPath(database, HISTORY_TABLE);
|
|
73
|
+
const replica = topology.replicaName ?? DEFAULT_HISTORY_REPLICA_NAME;
|
|
74
|
+
return `ReplicatedMergeTree(${literal(path)}, ${literal(replica)})`;
|
|
75
|
+
}
|
|
76
|
+
case "replicated":
|
|
77
|
+
return "ReplicatedMergeTree";
|
|
78
|
+
case "single":
|
|
79
|
+
case "cloud":
|
|
80
|
+
return "MergeTree";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** `CREATE TABLE IF NOT EXISTS <database>.history`. */
|
|
84
|
+
export function historyTableDdl(options) {
|
|
85
|
+
const columns = HISTORY_COLUMNS.map(([name, type]) => ` ${name} ${type}`).join(",\n");
|
|
86
|
+
return (`CREATE TABLE IF NOT EXISTS ${ident(options.database)}.${HISTORY_TABLE}${onCluster(options.topology)}\n(\n${columns}\n)\n` +
|
|
87
|
+
`ENGINE = ${historyEngine(options)}\nORDER BY (migration_id, statement_index, seq)\n` +
|
|
88
|
+
`COMMENT 'yodel migration history: append-only, one row per migration and per statement event'`);
|
|
89
|
+
}
|
|
90
|
+
/** The Keeper path of an environment's lock, under the server's `keeper_map_path_prefix`. */
|
|
91
|
+
export const lockKeeperPath = (database) => `/yodeler/${database}/lock`;
|
|
92
|
+
/** `CREATE TABLE IF NOT EXISTS <database>.lock`, a KeeperMap. */
|
|
93
|
+
export function lockTableDdl(options) {
|
|
94
|
+
return (`CREATE TABLE IF NOT EXISTS ${ident(options.database)}.${LOCK_TABLE}${onCluster(options.topology)}\n` +
|
|
95
|
+
`(\n name String,\n owner String,\n acquired_at DateTime64(3, 'UTC'),\n expires_at DateTime64(3, 'UTC')\n)\n` +
|
|
96
|
+
`ENGINE = KeeperMap(${literal(lockKeeperPath(options.database))})\nPRIMARY KEY name`);
|
|
97
|
+
}
|
|
98
|
+
/** Every statement that makes the history ready, in order: the database, then the table. */
|
|
99
|
+
export const historyDdl = (options) => [databaseDdl(options), historyTableDdl(options)];
|
|
100
|
+
//# sourceMappingURL=ddl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ddl.js","sourceRoot":"","sources":["../../src/history/ddl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration history (#8): an append-only ClickHouse table in the
|
|
3
|
+
* database the environment names (default `yodeler`), and the apply lock.
|
|
4
|
+
*
|
|
5
|
+
* historyDdl({ database, topology }) CREATE DATABASE / TABLE for single, cluster, replicated, cloud
|
|
6
|
+
* lockTableDdl({ database, topology }) the KeeperMap lock table
|
|
7
|
+
* new HistoryStore(port, location) exists(), ensure(), rows(), state(), append(rows)
|
|
8
|
+
* new HistoryRecorder(store, run, maxSeq) record(row): appends with the next seq
|
|
9
|
+
* deriveState(rows) the latest row per migration and per statement
|
|
10
|
+
* isApplied(record), statementDone(record, i)
|
|
11
|
+
* acquireLock(port, options), withLock(lock, ttl, body)
|
|
12
|
+
* parseHistoryTopology("cluster:main")
|
|
13
|
+
*
|
|
14
|
+
* Rows are never updated or deleted; a state is always the row with the
|
|
15
|
+
* highest seq for its key. #10 (`init --from` records a baseline), #12 (Op
|
|
16
|
+
* steps record their receipts' progress), #13 (the Postgres twin), #15
|
|
17
|
+
* (`repair` appends a migration row with the new checksum and a `note`) and
|
|
18
|
+
* #16 (the digest) build on this.
|
|
19
|
+
*/
|
|
20
|
+
export * from "./client.js";
|
|
21
|
+
export * from "./ddl.js";
|
|
22
|
+
export * from "./lock.js";
|
|
23
|
+
export * from "./rows.js";
|
|
24
|
+
export * from "./store.js";
|
|
25
|
+
export * from "./topology.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration history (#8): an append-only ClickHouse table in the
|
|
3
|
+
* database the environment names (default `yodeler`), and the apply lock.
|
|
4
|
+
*
|
|
5
|
+
* historyDdl({ database, topology }) CREATE DATABASE / TABLE for single, cluster, replicated, cloud
|
|
6
|
+
* lockTableDdl({ database, topology }) the KeeperMap lock table
|
|
7
|
+
* new HistoryStore(port, location) exists(), ensure(), rows(), state(), append(rows)
|
|
8
|
+
* new HistoryRecorder(store, run, maxSeq) record(row): appends with the next seq
|
|
9
|
+
* deriveState(rows) the latest row per migration and per statement
|
|
10
|
+
* isApplied(record), statementDone(record, i)
|
|
11
|
+
* acquireLock(port, options), withLock(lock, ttl, body)
|
|
12
|
+
* parseHistoryTopology("cluster:main")
|
|
13
|
+
*
|
|
14
|
+
* Rows are never updated or deleted; a state is always the row with the
|
|
15
|
+
* highest seq for its key. #10 (`init --from` records a baseline), #12 (Op
|
|
16
|
+
* steps record their receipts' progress), #13 (the Postgres twin), #15
|
|
17
|
+
* (`repair` appends a migration row with the new checksum and a `note`) and
|
|
18
|
+
* #16 (the digest) build on this.
|
|
19
|
+
*/
|
|
20
|
+
export * from "./client.js";
|
|
21
|
+
export * from "./ddl.js";
|
|
22
|
+
export * from "./lock.js";
|
|
23
|
+
export * from "./rows.js";
|
|
24
|
+
export * from "./store.js";
|
|
25
|
+
export * from "./topology.js";
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/history/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One apply at a time per environment.
|
|
3
|
+
*
|
|
4
|
+
* ## Keeper: a KeeperMap row
|
|
5
|
+
*
|
|
6
|
+
* The lock is a row in `<history database>.lock`, a `KeeperMap` table, so
|
|
7
|
+
* it lives in Keeper. Inserted with `keeper_map_strict_mode = 1`, an insert
|
|
8
|
+
* of a key that is there already fails ("Node exists") instead of
|
|
9
|
+
* overwriting it: create-if-absent, which is a lock. Every runner, on any
|
|
10
|
+
* host, sees the same row, because every host's `lock` table names the same
|
|
11
|
+
* Keeper path. It was picked over the other routes ClickHouse SQL offers:
|
|
12
|
+
*
|
|
13
|
+
* - an insert into a Replicated table that deduplicates: the losing insert is
|
|
14
|
+
* dropped silently, inside a time window, so the loser cannot tell it lost
|
|
15
|
+
* without reading back, and a later insert is not refused at all;
|
|
16
|
+
* - `INSERT INTO system.zookeeper`: no `DELETE`, so a lock could never be
|
|
17
|
+
* released or expired from SQL.
|
|
18
|
+
*
|
|
19
|
+
* The row names its owner and an expiry. A runner that holds it extends the
|
|
20
|
+
* expiry while it works (a heartbeat every third of the TTL) and deletes the
|
|
21
|
+
* row when done. A runner that finds an expired row deletes that row (by its
|
|
22
|
+
* owner, so it never deletes a newer one) and inserts its own; two runners
|
|
23
|
+
* racing for an expired lock both delete, and only one insert succeeds. A
|
|
24
|
+
* heartbeat that finds the row gone or someone else's stops the apply
|
|
25
|
+
* before its next statement.
|
|
26
|
+
*
|
|
27
|
+
* KeeperMap needs Keeper and the server setting `keeper_map_path_prefix`.
|
|
28
|
+
*
|
|
29
|
+
* ## No Keeper: a local lock file
|
|
30
|
+
*
|
|
31
|
+
* A single node with no Keeper (or no `keeper_map_path_prefix`) gets a lock
|
|
32
|
+
* file under the system temp directory instead, created exclusively, with
|
|
33
|
+
* the same owner and expiry. It serializes applies from one machine only, and
|
|
34
|
+
* the run says so: cross-runner locking needs Keeper. A cluster, a
|
|
35
|
+
* Replicated database or Cloud never fall back: they refuse without the
|
|
36
|
+
* KeeperMap lock.
|
|
37
|
+
*
|
|
38
|
+
* ## Clearing a lock by hand
|
|
39
|
+
*
|
|
40
|
+
* A dead runner's lock expires after its TTL (`YODEL_LOCK_TTL`, seconds,
|
|
41
|
+
* default 900). To clear it sooner, when you know no apply is running:
|
|
42
|
+
*
|
|
43
|
+
* Keeper: ALTER TABLE <history database>.lock DELETE WHERE name = 'apply'
|
|
44
|
+
* local: rm the lock file the refusal names
|
|
45
|
+
*/
|
|
46
|
+
import { YodelError } from "../project.js";
|
|
47
|
+
import { type ClickHousePort } from "./client.js";
|
|
48
|
+
import { type HistoryTopology } from "./topology.js";
|
|
49
|
+
/** Exit code: another apply holds the lock. */
|
|
50
|
+
export declare const LOCKED = 5;
|
|
51
|
+
/** The lock row's key: one apply lock per history database. */
|
|
52
|
+
export declare const LOCK_NAME = "apply";
|
|
53
|
+
/** Default lock TTL. */
|
|
54
|
+
export declare const DEFAULT_LOCK_TTL_MS: number;
|
|
55
|
+
export interface Lock {
|
|
56
|
+
/** `advisory`: a Postgres session advisory lock (`./postgres/lock.ts`). */
|
|
57
|
+
kind: "keeper" | "local" | "advisory";
|
|
58
|
+
owner: string;
|
|
59
|
+
/** Where it is: the KeeperMap table, or the lock file. */
|
|
60
|
+
where: string;
|
|
61
|
+
/** Set when the lock is local: why, and that cross-runner locking needs Keeper. */
|
|
62
|
+
notice?: string;
|
|
63
|
+
/** Extends the expiry. Throws a {@link LockLostError} when the lock is no longer this owner's. */
|
|
64
|
+
heartbeat(): Promise<void>;
|
|
65
|
+
/** Deletes the lock, when it is still this owner's. */
|
|
66
|
+
release(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export declare class LockHeldError extends YodelError {
|
|
69
|
+
readonly holder: string;
|
|
70
|
+
readonly expiresAt: string;
|
|
71
|
+
readonly where: string;
|
|
72
|
+
readonly clear: string;
|
|
73
|
+
constructor(holder: string, expiresAt: string, where: string, clear: string);
|
|
74
|
+
}
|
|
75
|
+
export declare class LockLostError extends YodelError {
|
|
76
|
+
constructor(where: string);
|
|
77
|
+
}
|
|
78
|
+
export interface LockOptions {
|
|
79
|
+
database: string;
|
|
80
|
+
topology: HistoryTopology;
|
|
81
|
+
/** Who holds it, for the refusal another runner sees. */
|
|
82
|
+
holder: string;
|
|
83
|
+
ttlMs?: number;
|
|
84
|
+
/** Identifies the server, for the local lock file's name. */
|
|
85
|
+
serverKey: string;
|
|
86
|
+
/** Where local lock files go. Default: `<tmpdir>/yodel-locks`. */
|
|
87
|
+
localDir?: string;
|
|
88
|
+
}
|
|
89
|
+
/** A lock owner: who, plus a random part so two runs by the same user never share one. */
|
|
90
|
+
export declare const lockOwner: (holder: string) => string;
|
|
91
|
+
/** Acquires the environment's apply lock, or throws {@link LockHeldError}. */
|
|
92
|
+
export declare function acquireLock(port: ClickHousePort, options: LockOptions): Promise<Lock>;
|
|
93
|
+
/** The local lock's file for a server and history database. */
|
|
94
|
+
export declare function localLockPath(options: Pick<LockOptions, "serverKey" | "database" | "localDir">): string;
|
|
95
|
+
/**
|
|
96
|
+
* Runs `body` holding the lock: a heartbeat every third of the TTL while it
|
|
97
|
+
* runs, the lock released after, whatever happens. `beat` lets the body
|
|
98
|
+
* extend the lock (and learn it was lost) between statements.
|
|
99
|
+
*/
|
|
100
|
+
export declare function withLock<T>(lock: Lock, ttlMs: number, body: (beat: () => Promise<void>) => Promise<T>): Promise<T>;
|