okmodel 0.3.0 → 0.4.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 +182 -22
- package/dist/contracts/catalog/document.d.ts +6 -2
- 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/role/sql.d.ts +11 -4
- package/dist/dialects/pg/table.d.ts +17 -1
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +3 -2
- 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.js +2 -2
- package/dist/runtime/pg/pg.js +2 -2
- package/dist/runtime/pg/pglite.js +2 -2
- package/dist/runtime/pg/postgresjs.js +2 -2
- package/dist/runtime/traits/index.js +5 -4
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
- package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
- package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
- package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
- package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
- package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
- package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
- package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
- package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
- package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
- package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
- package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
- package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
- package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
- package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
- package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
- package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
- package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
- package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
- package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
- package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
- package/dist/tooling/cli.js +46 -17
- package/dist/tooling/migrate/apply.d.ts +59 -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 +12 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +67 -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 +72 -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/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/{xva8v7qk.js → awrsqrn7.js} +3 -3
- package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row estimates for `okm migrate plan` (D194).
|
|
3
|
+
*
|
|
4
|
+
* The numbers come from `pg_class.reltuples` on a selected target. They are
|
|
5
|
+
* printed next to the lock and then discarded. The linter never reads them,
|
|
6
|
+
* and `okm generate` never asks for them.
|
|
7
|
+
*/
|
|
8
|
+
import type { PlanStep } from "./plan.js";
|
|
9
|
+
/**
|
|
10
|
+
* Estimated rows above which an `ACCESS EXCLUSIVE` lock prints a note.
|
|
11
|
+
*
|
|
12
|
+
* The note is text on the lock line. It is not a lint finding.
|
|
13
|
+
*/
|
|
14
|
+
export declare const LARGE_TABLE_ROWS = 1000000;
|
|
15
|
+
/** What `pg_class` said about one table. */
|
|
16
|
+
export type RowEstimate = {
|
|
17
|
+
readonly kind: "rows";
|
|
18
|
+
readonly reltuples: number;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "unknown";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Reads `reltuples` for the named tables.
|
|
24
|
+
*
|
|
25
|
+
* The statements run in one read-only transaction. A table that is absent
|
|
26
|
+
* from the result is not entered in the map: the printer treats that as
|
|
27
|
+
* `new table`. `reltuples` of `-1` is {@link RowEstimate} `unknown`. This
|
|
28
|
+
* never scans a table.
|
|
29
|
+
*
|
|
30
|
+
* @param url - Target URL
|
|
31
|
+
* @param schema - Schema the plan qualifies, usually `public`
|
|
32
|
+
* @param tables - Table names the plan steps name
|
|
33
|
+
* @returns One entry per relation `pg_class` returned
|
|
34
|
+
*/
|
|
35
|
+
export declare function readRowEstimates(url: string, schema: string, tables: readonly string[]): Promise<ReadonlyMap<string, RowEstimate>>;
|
|
36
|
+
/**
|
|
37
|
+
* Lock line with estimates, the safe-rewrite label, and the large-table note.
|
|
38
|
+
*
|
|
39
|
+
* @param step - One plan step
|
|
40
|
+
* @param estimates - `reltuples` for tables that exist on the target
|
|
41
|
+
* @returns The text after `-- lock: `
|
|
42
|
+
*/
|
|
43
|
+
export declare function annotateLock(step: PlanStep, estimates: ReadonlyMap<string, RowEstimate>): string;
|
|
44
|
+
/**
|
|
45
|
+
* Compact phrase for one estimate.
|
|
46
|
+
*
|
|
47
|
+
* @param count - Rounded `reltuples`
|
|
48
|
+
* @returns `about 4.2M rows` and the same shape for thousands and billions
|
|
49
|
+
*/
|
|
50
|
+
export declare function aboutRows(count: number): string;
|
|
@@ -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,6 +1,7 @@
|
|
|
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 = {
|
|
@@ -17,3 +18,14 @@ export type StoredMigration = {
|
|
|
17
18
|
* @returns Migrations in apply order
|
|
18
19
|
*/
|
|
19
20
|
export declare function loadMigrations(directory: string): readonly StoredMigration[];
|
|
21
|
+
/**
|
|
22
|
+
* Loads the last migration's catalog, which is the head snapshot.
|
|
23
|
+
*
|
|
24
|
+
* @param directory - Migrations directory
|
|
25
|
+
* @returns The last file, or `undefined` when there are none
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadHeadSnapshot(directory: string): {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly catalogHash: string;
|
|
30
|
+
readonly catalog: ReturnType<typeof parseCatalog>;
|
|
31
|
+
} | 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,73 @@
|
|
|
1
|
+
import"../../shared/wqem4jed.js";
|
|
2
|
+
import"../../shared/8jb4ymh2.js";
|
|
3
|
+
import"../../shared/n442h54c.js";
|
|
4
|
+
import {
|
|
5
|
+
applyTarget2,
|
|
6
|
+
backfillTiming2
|
|
7
|
+
} from "../../shared/s4mb6a4b.js";
|
|
8
|
+
import {
|
|
9
|
+
loadMigrations,
|
|
10
|
+
loadHeadSnapshot,
|
|
11
|
+
readReference
|
|
12
|
+
} from "../../shared/7nk68fj8.js";
|
|
13
|
+
import {
|
|
14
|
+
assertTargetPolicy,
|
|
15
|
+
selectTarget,
|
|
16
|
+
loadBuiltSchema,
|
|
17
|
+
loadConfig
|
|
18
|
+
} from "../../shared/q6zsyh31.js";
|
|
19
|
+
import"../../shared/n5y9zyj1.js";
|
|
20
|
+
import"../../shared/41kv2xdm.js";
|
|
21
|
+
import {
|
|
22
|
+
OkmError2
|
|
23
|
+
} from "../../shared/52szzakm.js";
|
|
24
|
+
|
|
1
25
|
// src/tooling/migrate/config.ts
|
|
2
26
|
function defineConfig(config) {
|
|
3
27
|
return config;
|
|
4
28
|
}
|
|
29
|
+
// src/tooling/migrate/provision.ts
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
async function provision(target, cwd = process.cwd()) {
|
|
32
|
+
const config = await loadConfig(cwd);
|
|
33
|
+
const selected = selectTarget(config, target);
|
|
34
|
+
assertTargetPolicy(selected, "provision");
|
|
35
|
+
const directory = join(cwd, config.migrations ?? "migrations");
|
|
36
|
+
const migrations = loadMigrations(directory);
|
|
37
|
+
const head = loadHeadSnapshot(directory);
|
|
38
|
+
if (head === undefined) {
|
|
39
|
+
throw new OkmError2("invalid", "There is no head snapshot to provision.", {
|
|
40
|
+
fix: { summary: "Run okm generate before provision." }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const built = await loadBuiltSchema(cwd, config);
|
|
44
|
+
const report = await applyTarget2({
|
|
45
|
+
url: selected.url,
|
|
46
|
+
target: selected.name,
|
|
47
|
+
protected: selected.protected,
|
|
48
|
+
allowPooler: config.allowPooler === true,
|
|
49
|
+
...backfillTiming2(config.backfill),
|
|
50
|
+
migrations,
|
|
51
|
+
snapshot: {
|
|
52
|
+
catalog: head.catalog,
|
|
53
|
+
migrationId: head.id,
|
|
54
|
+
catalogHash: head.catalogHash,
|
|
55
|
+
reference: readReference(built.tables, built.casing, head.catalog),
|
|
56
|
+
only: true
|
|
57
|
+
},
|
|
58
|
+
...config.roles !== undefined ? { migrationRole: config.roles.migration } : {},
|
|
59
|
+
lintDirectory: directory
|
|
60
|
+
});
|
|
61
|
+
if (report.applied.length === 0)
|
|
62
|
+
return `target ${report.target}
|
|
63
|
+
nothing to apply
|
|
64
|
+
`;
|
|
65
|
+
return `target ${report.target}
|
|
66
|
+
${report.applied.map((id) => `applied ${id}`).join(`
|
|
67
|
+
`)}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
5
70
|
export {
|
|
6
|
-
defineConfig
|
|
71
|
+
defineConfig,
|
|
72
|
+
provision
|
|
7
73
|
};
|
|
@@ -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[];
|