okmodel 0.3.0 → 0.5.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/README.md +263 -23
- package/dist/contracts/catalog/document.d.ts +26 -2
- package/dist/contracts/driver.d.ts +7 -0
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/index.js +3 -3
- package/dist/dialects/pg/introspect.d.ts +6 -0
- package/dist/dialects/pg/role/sql.d.ts +21 -4
- package/dist/dialects/pg/schema.d.ts +15 -6
- package/dist/dialects/pg/table.d.ts +17 -1
- package/dist/dialects/pg/view/index.d.ts +20 -6
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +2 -1
- package/dist/runtime/catalog-cache.d.ts +17 -0
- package/dist/runtime/compat-words.d.ts +23 -0
- package/dist/runtime/drift-detail.d.ts +20 -4
- package/dist/runtime/drift.d.ts +4 -2
- package/dist/runtime/pg/bun.d.ts +12 -7
- package/dist/runtime/pg/bun.js +5 -2
- package/dist/runtime/pg/pg.d.ts +12 -6
- package/dist/runtime/pg/pg.js +5 -2
- package/dist/runtime/pg/pglite.d.ts +11 -3
- package/dist/runtime/pg/pglite.js +5 -2
- package/dist/runtime/pg/postgresjs.d.ts +12 -6
- package/dist/runtime/pg/postgresjs.js +5 -2
- package/dist/runtime/plan.d.ts +3 -1
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/topology.d.ts +191 -0
- package/dist/runtime/traits/index.js +7 -6
- package/dist/runtime/types.d.ts +59 -2
- package/dist/runtime/validate/index.js +2 -2
- package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
- package/dist/shared/{ty3v3hfk.js → 47xzb9fc.js} +2 -2
- package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
- package/dist/shared/{qybh48nd.js → 5fqx9yh4.js} +6 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
- package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
- package/dist/shared/{t616ynfn.js → 8bwbwy8h.js} +71 -13
- package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
- package/dist/shared/{wekze34d.js → 8xsbagad.js} +74 -2
- package/dist/shared/{bryqk9k9.js → 922qbgnw.js} +3 -3
- package/dist/shared/{meck53jv.js → 990a291j.js} +6 -5
- package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/{2ssqrv66.js → ayz64vzj.js} +1037 -181
- package/dist/shared/{k34e0xf2.js → bt9sfq0z.js} +16 -14
- package/dist/shared/{scwxrpx2.js → bzsjtd7c.js} +84 -15
- package/dist/shared/{m9tv7xn7.js → ckjbyrt9.js} +4 -4
- package/dist/shared/{ydaz1sv3.js → dxjp14rk.js} +10 -2
- package/dist/shared/{49ypnfwt.js → fancm5pt.js} +24 -229
- package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
- package/dist/shared/{1tfdfvhh.js → fyqkk1m0.js} +6 -4
- package/dist/shared/g516zdsb.js +905 -0
- package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
- package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
- package/dist/shared/{p24x2334.js → h4nqvjkp.js} +5 -3
- package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/jdgqqx31.js +72 -0
- package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
- package/dist/shared/{m78s7898.js → k7cc9nzk.js} +24 -7
- package/dist/shared/kdkxh3kb.js +758 -0
- package/dist/shared/nkc9sfqb.js +1161 -0
- package/dist/shared/{xva8v7qk.js → p7pfx624.js} +5 -5
- package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
- package/dist/shared/{ne02ng5c.js → qy9rdq0g.js} +9 -5
- package/dist/shared/{3ekj9x92.js → r6pj4fq2.js} +5 -3
- package/dist/shared/r73pt594.js +426 -0
- package/dist/shared/rasvvzgz.js +21 -0
- package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
- package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
- package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
- package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
- package/dist/shared/wxn4b7pp.js +252 -0
- package/dist/shared/xdpj5kkm.js +2203 -0
- package/dist/shared/xhh1srw3.js +589 -0
- package/dist/shared/{mt5wswjz.js → xsq7v6wp.js} +2 -2
- package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
- package/dist/shared/ykv7wh5b.js +14 -0
- package/dist/shared/{fptpmvxp.js → z2rnngj4.js} +26 -21
- package/dist/tooling/cli.js +46 -17
- package/dist/tooling/migrate/apply.d.ts +61 -5
- package/dist/tooling/migrate/backfill.d.ts +136 -0
- package/dist/tooling/migrate/check.d.ts +65 -0
- package/dist/tooling/migrate/classify.d.ts +108 -0
- package/dist/tooling/migrate/commands.d.ts +2 -2
- package/dist/tooling/migrate/config.d.ts +22 -0
- package/dist/tooling/migrate/doctor.d.ts +3 -0
- package/dist/tooling/migrate/drift.d.ts +18 -2
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/extensions.d.ts +2 -0
- package/dist/tooling/migrate/files.d.ts +19 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +69 -1
- package/dist/tooling/migrate/lint-rules.d.ts +51 -0
- package/dist/tooling/migrate/lint.d.ts +71 -0
- package/dist/tooling/migrate/managed.d.ts +1 -1
- package/dist/tooling/migrate/plan.d.ts +64 -9
- package/dist/tooling/migrate/project.d.ts +42 -7
- package/dist/tooling/migrate/provision.d.ts +18 -0
- package/dist/tooling/migrate/reference.d.ts +40 -0
- package/dist/tooling/migrate/runner.d.ts +108 -0
- package/dist/tooling/migrate/safe.d.ts +77 -0
- package/dist/tooling/migrate/seed.d.ts +20 -0
- package/dist/tooling/migrate/snapshot.d.ts +78 -0
- package/dist/tooling/migrate/status.d.ts +25 -6
- package/dist/tooling/testing/factories.d.ts +117 -0
- package/dist/tooling/testing/facts.d.ts +75 -0
- package/dist/tooling/testing/index.d.ts +89 -0
- package/dist/tooling/testing/index.js +8 -0
- package/dist/tooling/testing/isolation.d.ts +30 -0
- package/dist/tooling/testing/random.d.ts +40 -0
- package/dist/tooling/testing/record.d.ts +38 -0
- package/package.json +6 -2
- package/dist/shared/5nmdenb6.js +0 -1229
- package/dist/shared/dbjej1tw.js +0 -456
- package/dist/shared/g4f5kcx9.js +0 -37
- package/dist/shared/qbxzvwwc.js +0 -88
- package/dist/shared/zwmqxb0g.js +0 -79
- /package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
- /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { CatalogObject } from "../../contracts/catalog/types.js";
|
|
10
10
|
import type { DriverConnection } from "../../contracts/driver.js";
|
|
11
|
+
import { type StepKind } from "./classify.js";
|
|
11
12
|
/** One extension alter. The planner stores it as a plan step. */
|
|
12
13
|
export type ExtensionStep = {
|
|
13
14
|
readonly sql: string;
|
|
15
|
+
readonly kind: StepKind;
|
|
14
16
|
readonly class: "expand" | "contract";
|
|
15
17
|
readonly action: "ddl";
|
|
16
18
|
readonly lock: string;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Migration files on disk: SQL from {@link formatPlan} plus the catalog hash.
|
|
3
3
|
*/
|
|
4
|
+
import { parseCatalog } from "../../contracts/catalog/document.js";
|
|
4
5
|
import { type PlanStep } from "./plan.js";
|
|
5
6
|
/** One migration the applier can run and resume. */
|
|
6
7
|
export type StoredMigration = {
|
|
7
8
|
readonly id: string;
|
|
8
9
|
readonly catalogHash: string;
|
|
10
|
+
/**
|
|
11
|
+
* The hash written before D208, which counted roles and grants.
|
|
12
|
+
*
|
|
13
|
+
* Set only when the catalog has them. Apply moves a stored copy of it to
|
|
14
|
+
* {@link StoredMigration.catalogHash}.
|
|
15
|
+
*/
|
|
16
|
+
readonly legacyHash?: string;
|
|
9
17
|
readonly steps: readonly PlanStep[];
|
|
10
18
|
};
|
|
11
19
|
/**
|
|
@@ -17,3 +25,14 @@ export type StoredMigration = {
|
|
|
17
25
|
* @returns Migrations in apply order
|
|
18
26
|
*/
|
|
19
27
|
export declare function loadMigrations(directory: string): readonly StoredMigration[];
|
|
28
|
+
/**
|
|
29
|
+
* Loads the last migration's catalog, which is the head snapshot.
|
|
30
|
+
*
|
|
31
|
+
* @param directory - Migrations directory
|
|
32
|
+
* @returns The last file, or `undefined` when there are none
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadHeadSnapshot(directory: string): {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly catalogHash: string;
|
|
37
|
+
readonly catalog: ReturnType<typeof parseCatalog>;
|
|
38
|
+
} | undefined;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public entry for `okmodel/migrate`.
|
|
3
3
|
*
|
|
4
|
-
* `defineConfig
|
|
5
|
-
*
|
|
4
|
+
* `defineConfig`, its types, and `provision` for one configured target.
|
|
5
|
+
* The CLI imports planning and apply helpers from their modules.
|
|
6
6
|
*/
|
|
7
7
|
export { defineConfig, type MigrateConfig, type TargetInput } from "./config.js";
|
|
8
|
+
export { provision } from "./provision.js";
|
|
@@ -1,7 +1,75 @@
|
|
|
1
|
+
import"../../shared/wqem4jed.js";
|
|
2
|
+
import"../../shared/8bwbwy8h.js";
|
|
3
|
+
import"../../shared/n442h54c.js";
|
|
4
|
+
import {
|
|
5
|
+
applyTarget2,
|
|
6
|
+
backfillTiming2
|
|
7
|
+
} from "../../shared/xhh1srw3.js";
|
|
8
|
+
import {
|
|
9
|
+
loadMigrations,
|
|
10
|
+
loadHeadSnapshot,
|
|
11
|
+
readReference
|
|
12
|
+
} from "../../shared/kdkxh3kb.js";
|
|
13
|
+
import"../../shared/szm0cksa.js";
|
|
14
|
+
import"../../shared/fancm5pt.js";
|
|
15
|
+
import {
|
|
16
|
+
assertTargetPolicy,
|
|
17
|
+
selectTarget,
|
|
18
|
+
loadBuiltSchema,
|
|
19
|
+
loadConfig
|
|
20
|
+
} from "../../shared/xdpj5kkm.js";
|
|
21
|
+
import"../../shared/ayz64vzj.js";
|
|
22
|
+
import"../../shared/8xsbagad.js";
|
|
23
|
+
import {
|
|
24
|
+
OkmError2
|
|
25
|
+
} from "../../shared/52szzakm.js";
|
|
26
|
+
|
|
1
27
|
// src/tooling/migrate/config.ts
|
|
2
28
|
function defineConfig(config) {
|
|
3
29
|
return config;
|
|
4
30
|
}
|
|
31
|
+
// src/tooling/migrate/provision.ts
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
async function provision(target, cwd = process.cwd()) {
|
|
34
|
+
const config = await loadConfig(cwd);
|
|
35
|
+
const selected = selectTarget(config, target);
|
|
36
|
+
assertTargetPolicy(selected, "provision");
|
|
37
|
+
const directory = join(cwd, config.migrations ?? "migrations");
|
|
38
|
+
const migrations = loadMigrations(directory);
|
|
39
|
+
const head = loadHeadSnapshot(directory);
|
|
40
|
+
if (head === undefined) {
|
|
41
|
+
throw new OkmError2("invalid", "There is no head snapshot to provision.", {
|
|
42
|
+
fix: { summary: "Run okm generate before provision." }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
46
|
+
const report = await applyTarget2({
|
|
47
|
+
url: selected.url,
|
|
48
|
+
target: selected.name,
|
|
49
|
+
protected: selected.protected,
|
|
50
|
+
allowPooler: config.allowPooler === true,
|
|
51
|
+
...backfillTiming2(config.backfill),
|
|
52
|
+
migrations,
|
|
53
|
+
snapshot: {
|
|
54
|
+
catalog: head.catalog,
|
|
55
|
+
migrationId: head.id,
|
|
56
|
+
catalogHash: head.catalogHash,
|
|
57
|
+
reference: readReference(built.tables, built.casing, head.catalog),
|
|
58
|
+
only: true
|
|
59
|
+
},
|
|
60
|
+
...config.roles !== undefined ? { migrationRole: config.roles.migration } : {},
|
|
61
|
+
lintDirectory: directory
|
|
62
|
+
});
|
|
63
|
+
if (report.applied.length === 0)
|
|
64
|
+
return `target ${report.target}
|
|
65
|
+
nothing to apply
|
|
66
|
+
`;
|
|
67
|
+
return `target ${report.target}
|
|
68
|
+
${report.applied.map((id) => `applied ${id}`).join(`
|
|
69
|
+
`)}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
5
72
|
export {
|
|
6
|
-
defineConfig
|
|
73
|
+
defineConfig,
|
|
74
|
+
provision
|
|
7
75
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lint rules for one plan step or one catalog column.
|
|
3
|
+
*
|
|
4
|
+
* A rule reads the statement and the catalogs. It does not connect.
|
|
5
|
+
*/
|
|
6
|
+
import type { Catalog, ColumnObject } from "../../contracts/catalog/types.js";
|
|
7
|
+
import type { PlanStep } from "./plan.js";
|
|
8
|
+
/** Destructive, compatibility, data, locking, or a schema type preference. */
|
|
9
|
+
export type LintCategory = "destructive" | "backward-incompatible" | "data-dependent" | "locking" | "type-preference";
|
|
10
|
+
/** An error blocks plan, check, and apply. A warning is printed and does not. */
|
|
11
|
+
export type LintSeverity = "error" | "warning";
|
|
12
|
+
/** What a step rule can see. Catalogs are the plan's before and after. */
|
|
13
|
+
export type StepInput = {
|
|
14
|
+
readonly step: PlanStep;
|
|
15
|
+
readonly before: Catalog;
|
|
16
|
+
readonly after: Catalog;
|
|
17
|
+
readonly existingTables: ReadonlySet<string>;
|
|
18
|
+
/** Steps before this one. A safe `SET NOT NULL` is recognised from them. */
|
|
19
|
+
readonly earlier: readonly PlanStep[];
|
|
20
|
+
/** Steps after this one. A unique index built for a constraint is recognised from them. */
|
|
21
|
+
readonly later: readonly PlanStep[];
|
|
22
|
+
};
|
|
23
|
+
/** One step rule. `check` returns a reason for each hit, or nothing. */
|
|
24
|
+
export type StepRule = {
|
|
25
|
+
readonly code: string;
|
|
26
|
+
readonly category: Exclude<LintCategory, "type-preference">;
|
|
27
|
+
readonly severity: LintSeverity;
|
|
28
|
+
/** One line for the codes table. */
|
|
29
|
+
readonly doc: string;
|
|
30
|
+
readonly check: (input: StepInput) => readonly string[];
|
|
31
|
+
};
|
|
32
|
+
/** One schema rule. `okm check` runs these on the author catalog. */
|
|
33
|
+
export type ColumnRule = {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
readonly category: "type-preference";
|
|
36
|
+
readonly severity: "warning";
|
|
37
|
+
/** One line for the codes table. */
|
|
38
|
+
readonly doc: string;
|
|
39
|
+
readonly check: (column: ColumnObject) => string | undefined;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Step rules, lowest free code in each category.
|
|
43
|
+
*
|
|
44
|
+
* OKM1534 to OKM1537 are errors when the statement is not the safe form.
|
|
45
|
+
* OKM1538 stays a warning: a rewriting type change has no safe form here.
|
|
46
|
+
*/
|
|
47
|
+
export declare const STEP_RULES: readonly StepRule[];
|
|
48
|
+
/**
|
|
49
|
+
* Type preferences. Warnings, read from the schema catalog by `okm check`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const COLUMN_RULES: readonly ColumnRule[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs the migration linter.
|
|
3
|
+
*
|
|
4
|
+
* The linter reads a plan and the two catalogs. It does not connect.
|
|
5
|
+
* An override is a `-- okm-allow` line on the statement. It silences only
|
|
6
|
+
* the code it names, and only with a reason.
|
|
7
|
+
*/
|
|
8
|
+
import type { Catalog } from "../../contracts/catalog/types.js";
|
|
9
|
+
import { OkmError } from "../../contracts/error.js";
|
|
10
|
+
import { type LintSeverity } from "./lint-rules.js";
|
|
11
|
+
import { type MigrationPlan } from "./plan.js";
|
|
12
|
+
/** One finding. `place` is `step N` or `table.column`, optionally prefixed with the file. */
|
|
13
|
+
export type Finding = {
|
|
14
|
+
readonly severity: LintSeverity;
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly place: string;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
readonly fix: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Lints every step. Type preferences are {@link lintCatalog}, not here.
|
|
22
|
+
*
|
|
23
|
+
* @param plan - Parsed or planned steps, including `-- okm-allow` lines
|
|
24
|
+
* @param before - Catalog the statements start from
|
|
25
|
+
* @param after - Catalog the statements end on
|
|
26
|
+
* @returns Findings in step order, then code order
|
|
27
|
+
*/
|
|
28
|
+
export declare function lintPlan(plan: MigrationPlan, before: Catalog, after: Catalog): readonly Finding[];
|
|
29
|
+
/**
|
|
30
|
+
* Lints type preferences on the author catalog.
|
|
31
|
+
*
|
|
32
|
+
* @param source - Schema catalog `okm check` loaded
|
|
33
|
+
* @returns One warning per column that prefers another type
|
|
34
|
+
*/
|
|
35
|
+
export declare function lintCatalog(source: Catalog): readonly Finding[];
|
|
36
|
+
/**
|
|
37
|
+
* Lints each SQL file against the previous file's catalog.
|
|
38
|
+
*
|
|
39
|
+
* The walk starts at the first file, against an empty catalog, so a later
|
|
40
|
+
* file sees the catalog the file before it left. The sibling `.catalog.json`
|
|
41
|
+
* is the catalog after that file. When `pending` is passed, only those
|
|
42
|
+
* migration ids contribute findings. The earlier files are still read.
|
|
43
|
+
*
|
|
44
|
+
* @param directory - Migrations directory. Missing means there are none
|
|
45
|
+
* @param pending - Migration ids still to run. Omit to lint every file
|
|
46
|
+
* @returns Findings, each place prefixed with the migration id
|
|
47
|
+
*/
|
|
48
|
+
export declare function lintMigrationDirectory(directory: string, pending?: ReadonlySet<string>): readonly Finding[];
|
|
49
|
+
/**
|
|
50
|
+
* Prints findings one per line, in a form CI can grep.
|
|
51
|
+
*
|
|
52
|
+
* @param findings - Findings from {@link lintPlan} or {@link lintCatalog}
|
|
53
|
+
* @returns The text, or `""` when there are none. A non-empty result ends in a newline
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatFindings(findings: readonly Finding[]): string;
|
|
56
|
+
/**
|
|
57
|
+
* Reports whether any finding is an error.
|
|
58
|
+
*
|
|
59
|
+
* @param findings - Findings to scan
|
|
60
|
+
* @returns `true` when plan, check, or apply must fail
|
|
61
|
+
*/
|
|
62
|
+
export declare function hasError(findings: readonly Finding[]): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* OKM1510 for an unresolved error finding.
|
|
65
|
+
*
|
|
66
|
+
* The message is the finding lines, so the failure text stays greppable.
|
|
67
|
+
*
|
|
68
|
+
* @param findings - Findings that include at least one error
|
|
69
|
+
* @returns The error apply, plan, and check throw before continuing
|
|
70
|
+
*/
|
|
71
|
+
export declare function lintRefusal(findings: readonly Finding[]): OkmError;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tables OKModel owns: `okm_meta` and `
|
|
2
|
+
* Tables OKModel owns: `okm_meta`, `okm_history`, and `okm_backfill`.
|
|
3
3
|
*
|
|
4
4
|
* Apply creates them. They are not part of the author schema. Plan, push,
|
|
5
5
|
* check, and doctor omit them on both sides, so a diff neither drops them
|
|
@@ -6,26 +6,56 @@
|
|
|
6
6
|
* OKM1530. The planner never prompts.
|
|
7
7
|
*
|
|
8
8
|
* Picklist and enum removals are expand `UPDATE`s plus a contract swap
|
|
9
|
-
* (D131).
|
|
9
|
+
* (D131). Each data step is a backfill: one idempotent `UPDATE` per batch,
|
|
10
|
+
* with `-- backfill` naming the primary key (D195).
|
|
10
11
|
*/
|
|
11
12
|
import type { Catalog } from "../../contracts/catalog/types.js";
|
|
12
13
|
import type { DeclaredRename } from "../../dialects/pg/declarations.js";
|
|
14
|
+
import { type BackfillSpec } from "./backfill.js";
|
|
15
|
+
import { type MigrationClass, type StepKind } from "./classify.js";
|
|
13
16
|
import { type Replacement } from "./values.js";
|
|
14
|
-
|
|
15
|
-
export type MigrationClass = "expand" | "contract" | "unclassified";
|
|
17
|
+
export type { MigrationClass, StepKind };
|
|
16
18
|
/**
|
|
17
19
|
* One statement in a plan.
|
|
18
20
|
*
|
|
19
|
-
* `backfill` is data
|
|
20
|
-
*
|
|
21
|
-
* VALUE`,
|
|
21
|
+
* `backfill` is data. A step with {@link PlanStep.backfill} is one idempotent
|
|
22
|
+
* `UPDATE` per batch, outside the migration transaction. `transactional: false`
|
|
23
|
+
* also marks a step that cannot share a transaction (`ALTER TYPE … ADD VALUE`,
|
|
24
|
+
* a concurrent index).
|
|
22
25
|
*/
|
|
23
26
|
export type PlanStep = {
|
|
24
27
|
readonly sql: string;
|
|
25
28
|
readonly class: MigrationClass;
|
|
29
|
+
/**
|
|
30
|
+
* Planner operation. Absent on a step built by hand.
|
|
31
|
+
*
|
|
32
|
+
* The class comes from this kind. A statement with no kind and no class
|
|
33
|
+
* comment is `raw-sql`.
|
|
34
|
+
*/
|
|
35
|
+
readonly kind?: StepKind;
|
|
26
36
|
readonly action: "ddl" | "backfill";
|
|
27
37
|
readonly lock: string;
|
|
38
|
+
/**
|
|
39
|
+
* Tables the statement names.
|
|
40
|
+
*
|
|
41
|
+
* Used to look up `pg_class.reltuples` when a plan is printed against a
|
|
42
|
+
* reachable target. {@link formatPlan} does not write this into a file.
|
|
43
|
+
*/
|
|
44
|
+
readonly tables?: readonly string[];
|
|
45
|
+
/**
|
|
46
|
+
* Set when the step was emitted by a safe rewrite (D193).
|
|
47
|
+
*
|
|
48
|
+
* Display only. A migration file does not record it.
|
|
49
|
+
*/
|
|
50
|
+
readonly safeRewrite?: true;
|
|
28
51
|
readonly transactional: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Set when the statement is a batched backfill.
|
|
54
|
+
*
|
|
55
|
+
* The header is `table`, `key`, and `batch`. The statement receives `$1`
|
|
56
|
+
* and `$2`. Absent on a data step written without that header.
|
|
57
|
+
*/
|
|
58
|
+
readonly backfill?: BackfillSpec;
|
|
29
59
|
/**
|
|
30
60
|
* Set on an extension upgrade the planner could not check.
|
|
31
61
|
*
|
|
@@ -39,6 +69,18 @@ export type PlanStep = {
|
|
|
39
69
|
* The statement is expand. The flag says the body or the settings changed.
|
|
40
70
|
*/
|
|
41
71
|
readonly behavior?: "change";
|
|
72
|
+
/**
|
|
73
|
+
* `-- okm-allow` lines directly above the statement.
|
|
74
|
+
*
|
|
75
|
+
* A reason silences that code only. An empty reason, or a code the
|
|
76
|
+
* statement did not trigger, is OKM1510 and silences nothing.
|
|
77
|
+
*/
|
|
78
|
+
readonly allows?: readonly StepAllow[];
|
|
79
|
+
};
|
|
80
|
+
/** One `-- okm-allow CODE: reason` line above a statement. */
|
|
81
|
+
export type StepAllow = {
|
|
82
|
+
readonly code: string;
|
|
83
|
+
readonly reason: string;
|
|
42
84
|
};
|
|
43
85
|
/** A named plan and the class of its strictest step. */
|
|
44
86
|
export type MigrationPlan = {
|
|
@@ -55,6 +97,13 @@ export type PlanRequest = {
|
|
|
55
97
|
readonly schema?: string;
|
|
56
98
|
/** Concrete schema written into the SQL. Identities stay logical. */
|
|
57
99
|
readonly name?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Rows per backfill batch written into the step header.
|
|
102
|
+
*
|
|
103
|
+
* Omitted uses the built-in default of 1000. A later apply reads `batch=`
|
|
104
|
+
* from the file, which overrides `defineConfig({ backfill })`.
|
|
105
|
+
*/
|
|
106
|
+
readonly batchSize?: number;
|
|
58
107
|
};
|
|
59
108
|
/**
|
|
60
109
|
* Builds a plan from catalog `before` to catalog `after`.
|
|
@@ -64,12 +113,18 @@ export type PlanRequest = {
|
|
|
64
113
|
*/
|
|
65
114
|
export declare function planMigration(input: PlanRequest): MigrationPlan;
|
|
66
115
|
/**
|
|
67
|
-
* Prints a plan. The
|
|
116
|
+
* Prints a plan. The header is the strictest class. Each step prints its
|
|
117
|
+
* class and lock. The text is SQL plus those comments, never TypeScript.
|
|
118
|
+
*
|
|
119
|
+
* `okm generate` calls this with no `lockText`, so a file stays free of row
|
|
120
|
+
* estimates. `okm migrate plan` passes `lockText` only after a reachable
|
|
121
|
+
* target has answered.
|
|
68
122
|
*
|
|
69
123
|
* @param plan - Plan from {@link planMigration}
|
|
70
|
-
* @
|
|
124
|
+
* @param lockText - Replaces the lock comment when a target supplied estimates
|
|
125
|
+
* @returns The text `okm generate` writes. `okm migrate plan` uses the same text offline
|
|
71
126
|
*/
|
|
72
|
-
export declare function formatPlan(plan: MigrationPlan): string;
|
|
127
|
+
export declare function formatPlan(plan: MigrationPlan, lockText?: (step: PlanStep) => string): string;
|
|
73
128
|
/**
|
|
74
129
|
* Reads a plan written by {@link formatPlan}.
|
|
75
130
|
*
|
|
@@ -11,6 +11,7 @@ import type { Catalog } from "../../contracts/catalog/types.js";
|
|
|
11
11
|
import { type MigrateConfig } from "./config.js";
|
|
12
12
|
import { type InvokeFlags } from "./policy.js";
|
|
13
13
|
import { type MigrationPlan } from "./plan.js";
|
|
14
|
+
import { type Finding } from "./lint.js";
|
|
14
15
|
type Built = BuiltSchema<readonly AnyTable[]>;
|
|
15
16
|
/**
|
|
16
17
|
* Validates the schema and writes `.okm/`.
|
|
@@ -27,29 +28,47 @@ export declare function buildProject(cwd: string): Promise<string>;
|
|
|
27
28
|
* @param cwd - Project directory
|
|
28
29
|
* @param name - Migration name
|
|
29
30
|
* @param flags - `--replace` values
|
|
30
|
-
* @returns The SQL path, or `undefined` when nothing changed
|
|
31
|
+
* @returns The SQL path and lint findings, or `undefined` when nothing changed
|
|
31
32
|
*/
|
|
32
|
-
export declare function generateProject(cwd: string, name: string, flags: readonly string[]): Promise<
|
|
33
|
+
export declare function generateProject(cwd: string, name: string, flags: readonly string[]): Promise<{
|
|
34
|
+
readonly path: string;
|
|
35
|
+
readonly findings: readonly Finding[];
|
|
36
|
+
} | undefined>;
|
|
33
37
|
/**
|
|
34
38
|
* Plans and returns the text. Nothing is written.
|
|
35
39
|
*
|
|
40
|
+
* When one target is configured, or `--target` names one, and that database
|
|
41
|
+
* answers, the printed lock lines include `pg_class` row estimates. No
|
|
42
|
+
* target, several targets without `--target`, or a target that cannot be
|
|
43
|
+
* reached leaves the text identical to {@link formatPlan}.
|
|
44
|
+
*
|
|
36
45
|
* @param cwd - Project directory
|
|
37
46
|
* @param name - Plan name
|
|
38
47
|
* @param flags - `--replace` values
|
|
39
|
-
* @
|
|
48
|
+
* @param invoke - Target and protection flags, when the command passed them
|
|
49
|
+
* @returns The plan, its lint findings, and the text to print
|
|
40
50
|
*/
|
|
41
|
-
export declare function planProject(cwd: string, name: string, flags: readonly string[]): Promise<
|
|
51
|
+
export declare function planProject(cwd: string, name: string, flags: readonly string[], invoke?: InvokeFlags): Promise<{
|
|
52
|
+
readonly plan: MigrationPlan;
|
|
53
|
+
readonly findings: readonly Finding[];
|
|
54
|
+
readonly text: string;
|
|
55
|
+
}>;
|
|
42
56
|
/**
|
|
43
57
|
* Validates the schema, then reports stale renames and unlisted table files.
|
|
44
58
|
*
|
|
45
|
-
* When exactly one target is configured, or `--target` names one,
|
|
46
|
-
*
|
|
59
|
+
* When exactly one target is configured, or `--target` names one, the
|
|
60
|
+
* read-only `check` and `drift` classes are allowed and a database that
|
|
61
|
+
* already has `okm_meta` is compared with the schema. A difference is
|
|
47
62
|
* OKM1520. Several targets and no `--target` skip that comparison.
|
|
48
63
|
*
|
|
64
|
+
* Lint findings are computed from the plan and the catalog before any
|
|
65
|
+
* connection. An error finding is OKM1510. Warnings are returned.
|
|
66
|
+
*
|
|
49
67
|
* @param cwd - Project directory
|
|
50
68
|
* @param flags - `--target`, when the caller passed one
|
|
69
|
+
* @returns Warning findings. Errors throw
|
|
51
70
|
*/
|
|
52
|
-
export declare function checkProject(cwd: string, flags?: InvokeFlags): Promise<
|
|
71
|
+
export declare function checkProject(cwd: string, flags?: InvokeFlags): Promise<readonly Finding[]>;
|
|
53
72
|
/**
|
|
54
73
|
* Table files whose names do not appear in the schema module.
|
|
55
74
|
*
|
|
@@ -83,6 +102,14 @@ export declare function openProject(cwd: string): Promise<{
|
|
|
83
102
|
readonly previous: Catalog;
|
|
84
103
|
readonly renames: readonly DeclaredRename[];
|
|
85
104
|
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Loads the schema named by `config`.
|
|
107
|
+
*
|
|
108
|
+
* @param cwd - Project directory
|
|
109
|
+
* @param config - Project config
|
|
110
|
+
* @returns The built schema, with roles attached when the config declares them
|
|
111
|
+
*/
|
|
112
|
+
export declare function loadBuiltSchema(cwd: string, config: MigrateConfig): Promise<Built>;
|
|
86
113
|
/**
|
|
87
114
|
* Loads `okmodel.config.ts`.
|
|
88
115
|
*
|
|
@@ -90,4 +117,12 @@ export declare function openProject(cwd: string): Promise<{
|
|
|
90
117
|
* @returns The config object
|
|
91
118
|
*/
|
|
92
119
|
export declare function loadConfig(cwd: string): Promise<MigrateConfig>;
|
|
120
|
+
/**
|
|
121
|
+
* Loads the module named by `spec` and returns the schema it exports.
|
|
122
|
+
*
|
|
123
|
+
* @param cwd - Project directory
|
|
124
|
+
* @param spec - Path from `defineConfig({ schema })`, relative to `cwd`
|
|
125
|
+
* @returns The built schema
|
|
126
|
+
*/
|
|
127
|
+
export declare function loadSchema(cwd: string, spec: string): Promise<Built>;
|
|
93
128
|
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `provision(target)` for one configured target (D198).
|
|
3
|
+
*
|
|
4
|
+
* Same rules as `okm migrate apply` on an empty target, including
|
|
5
|
+
* {@link assertTargetPolicy}. A target that is not empty is OKM1851.
|
|
6
|
+
* Tenant registry creation is M5.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Provisions one configured target from the head snapshot.
|
|
10
|
+
*
|
|
11
|
+
* The target must be empty. Reference rows are inserted if missing. History
|
|
12
|
+
* records `provisioned@<migration id>` and no migration step.
|
|
13
|
+
*
|
|
14
|
+
* @param target - Configured target name. `default` is the `database` target
|
|
15
|
+
* @param cwd - Project directory. Defaults to the process working directory
|
|
16
|
+
* @returns The same text `okm migrate apply` prints for that run
|
|
17
|
+
*/
|
|
18
|
+
export declare function provision(target: string, cwd?: string): Promise<string>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference rows declared on a table (D198).
|
|
3
|
+
*
|
|
4
|
+
* The rows live on the schema, not as a catalog diff. Apply inserts a row
|
|
5
|
+
* when its key is missing and does not update or delete. The statements are
|
|
6
|
+
* not migration steps, so OKM1542 does not see them.
|
|
7
|
+
*/
|
|
8
|
+
import type { Catalog } from "../../contracts/catalog/types.js";
|
|
9
|
+
import { type AnyTable } from "../../dialects/pg/table.js";
|
|
10
|
+
/** One value a reference row may store. */
|
|
11
|
+
export type ReferenceValue = string | number | boolean | null;
|
|
12
|
+
/** One table's reference declaration, in SQL names. */
|
|
13
|
+
export type ReferenceTable = {
|
|
14
|
+
readonly table: string;
|
|
15
|
+
readonly key: readonly string[];
|
|
16
|
+
readonly rows: readonly (readonly (readonly [string, ReferenceValue])[])[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Reads `reference` from the schema and checks it against the head catalog.
|
|
20
|
+
*
|
|
21
|
+
* A key must be a primary key, a unique constraint, or a unique index, in
|
|
22
|
+
* that column order. The catalog is not given a copy of the rows.
|
|
23
|
+
*
|
|
24
|
+
* @param tables - Tables the schema built
|
|
25
|
+
* @param casing - Schema casing. Absent means names are used as written
|
|
26
|
+
* @param head - Head snapshot the rows will be inserted into
|
|
27
|
+
* @returns One entry per table that declares rows
|
|
28
|
+
*/
|
|
29
|
+
export declare function readReference(tables: readonly AnyTable[], casing: "snake" | undefined, head: Catalog): readonly ReferenceTable[];
|
|
30
|
+
/**
|
|
31
|
+
* Insert-if-missing statements for one schema.
|
|
32
|
+
*
|
|
33
|
+
* Each row is its own `INSERT … ON CONFLICT DO NOTHING`. There is no
|
|
34
|
+
* `UPDATE` and no `DELETE`.
|
|
35
|
+
*
|
|
36
|
+
* @param tables - Declarations from {@link readReference}
|
|
37
|
+
* @param schema - Schema the statements write
|
|
38
|
+
* @returns SQL in declaration order
|
|
39
|
+
*/
|
|
40
|
+
export declare function referenceInserts(tables: readonly ReferenceTable[], schema: string): readonly string[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-target apply runner (D195).
|
|
3
|
+
*
|
|
4
|
+
* `okm migrate apply` calls {@link runTarget} with the units of one target.
|
|
5
|
+
* Bounded concurrency, several targets, canaries, and class flags are M5.
|
|
6
|
+
* A backfill step is one transaction per batch, with a checkpoint in
|
|
7
|
+
* `okm_backfill` after each commit.
|
|
8
|
+
*/
|
|
9
|
+
import type { DriverConnection } from "../../contracts/driver.js";
|
|
10
|
+
import type { PlanStep } from "./plan.js";
|
|
11
|
+
/** One step with its index in the migration file. */
|
|
12
|
+
export type RunnableStep = {
|
|
13
|
+
readonly index: number;
|
|
14
|
+
readonly step: PlanStep;
|
|
15
|
+
};
|
|
16
|
+
/** Steps that commit together, or one step that cannot. */
|
|
17
|
+
export type RunnableUnit = {
|
|
18
|
+
readonly migrationId: string;
|
|
19
|
+
readonly catalogHash: string;
|
|
20
|
+
/** True when this unit is the last step of its migration. */
|
|
21
|
+
readonly finishes: boolean;
|
|
22
|
+
readonly transactional: boolean;
|
|
23
|
+
readonly steps: readonly RunnableStep[];
|
|
24
|
+
};
|
|
25
|
+
/** What one committed backfill batch recorded. */
|
|
26
|
+
export type BackfillBatch = {
|
|
27
|
+
readonly migrationId: string;
|
|
28
|
+
readonly stepIndex: number;
|
|
29
|
+
readonly batch: number;
|
|
30
|
+
readonly rows: number;
|
|
31
|
+
readonly lastKey: string | null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Protection and timeouts the runner reads on every batch.
|
|
35
|
+
*
|
|
36
|
+
* `protected` is read again at each batch, so a caller can change it between
|
|
37
|
+
* batches.
|
|
38
|
+
*/
|
|
39
|
+
export type RunPolicy = {
|
|
40
|
+
readonly target: string;
|
|
41
|
+
readonly protected: boolean;
|
|
42
|
+
readonly allowProtected?: boolean;
|
|
43
|
+
};
|
|
44
|
+
/** One target's units, retries, and checkpoints. */
|
|
45
|
+
export type TargetRun = {
|
|
46
|
+
readonly policy: RunPolicy;
|
|
47
|
+
readonly units: readonly RunnableUnit[];
|
|
48
|
+
readonly retries: number;
|
|
49
|
+
readonly lockTimeoutMs: number;
|
|
50
|
+
readonly statementTimeoutMs: number;
|
|
51
|
+
readonly backfillPauseMs?: number;
|
|
52
|
+
readonly backfillStatementTimeoutMs?: number;
|
|
53
|
+
readonly migrationRole?: string;
|
|
54
|
+
readonly schema?: string;
|
|
55
|
+
readonly onProgress?: (line: string) => void;
|
|
56
|
+
readonly afterBatch?: (batch: BackfillBatch) => void | Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
/** What {@link runTarget} finished on this target. */
|
|
59
|
+
export type TargetRunReport = {
|
|
60
|
+
readonly target: string;
|
|
61
|
+
readonly applied: readonly string[];
|
|
62
|
+
readonly setRole?: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Runs one target's units in order.
|
|
66
|
+
*
|
|
67
|
+
* A finished step is skipped by the caller before it is placed in `units`.
|
|
68
|
+
* Lock timeout retries that step. A backfill retries the current batch only
|
|
69
|
+
* and resumes from `okm_backfill`.
|
|
70
|
+
*
|
|
71
|
+
* @param connection - Reserved connection. The caller holds the advisory lock
|
|
72
|
+
* @param run - Units, retries, and the protection flag
|
|
73
|
+
* @param session - Role assumed for the rest of the connection
|
|
74
|
+
* @returns The target and the migration ids that ran at least one unit
|
|
75
|
+
*/
|
|
76
|
+
export declare function runTarget(connection: DriverConnection, run: TargetRun, session: {
|
|
77
|
+
setRole?: string;
|
|
78
|
+
}): Promise<TargetRunReport>;
|
|
79
|
+
/**
|
|
80
|
+
* Assumes `role` for the rest of this connection.
|
|
81
|
+
*
|
|
82
|
+
* A second call with the same role does nothing.
|
|
83
|
+
*
|
|
84
|
+
* @param connection - Reserved connection
|
|
85
|
+
* @param role - Migration role
|
|
86
|
+
* @param session - Remembers the role already assumed
|
|
87
|
+
*/
|
|
88
|
+
export declare function assumeRole(connection: DriverConnection, role: string, session: {
|
|
89
|
+
setRole?: string;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Lets a new migration role create objects in `schema`.
|
|
93
|
+
*
|
|
94
|
+
* @param connection - Reserved connection, before `SET ROLE`
|
|
95
|
+
* @param schema - Schema the migration writes
|
|
96
|
+
* @param role - Role that was just created
|
|
97
|
+
*/
|
|
98
|
+
export declare function grantSchema(connection: DriverConnection, schema: string, role: string): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Reports whether Postgres refuses to run `sql` inside a transaction.
|
|
101
|
+
*
|
|
102
|
+
* `VACUUM`, `ALTER TYPE … ADD VALUE`, and concurrent index create or drop
|
|
103
|
+
* each need their own unit.
|
|
104
|
+
*
|
|
105
|
+
* @param sql - One statement
|
|
106
|
+
* @returns `true` when the statement cannot share a transaction
|
|
107
|
+
*/
|
|
108
|
+
export declare function statementOutsideTransaction(sql: string): boolean;
|