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.
Files changed (104) hide show
  1. package/README.md +182 -22
  2. package/dist/contracts/catalog/document.d.ts +6 -2
  3. package/dist/dialects/pg/ext/citext.js +1 -1
  4. package/dist/dialects/pg/ext/index.js +1 -1
  5. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  6. package/dist/dialects/pg/index.js +3 -3
  7. package/dist/dialects/pg/role/sql.d.ts +11 -4
  8. package/dist/dialects/pg/table.d.ts +17 -1
  9. package/dist/dialects/pg/view/index.js +1 -1
  10. package/dist/dialects/pg/view/scratch.d.ts +3 -2
  11. package/dist/runtime/catalog-cache.d.ts +17 -0
  12. package/dist/runtime/compat-words.d.ts +23 -0
  13. package/dist/runtime/drift-detail.d.ts +20 -4
  14. package/dist/runtime/drift.d.ts +4 -2
  15. package/dist/runtime/pg/bun.js +2 -2
  16. package/dist/runtime/pg/pg.js +2 -2
  17. package/dist/runtime/pg/pglite.js +2 -2
  18. package/dist/runtime/pg/postgresjs.js +2 -2
  19. package/dist/runtime/traits/index.js +5 -4
  20. package/dist/runtime/types.d.ts +2 -0
  21. package/dist/runtime/validate/index.js +2 -2
  22. package/dist/shared/2321avmn.js +418 -0
  23. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  24. package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
  25. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  26. package/dist/shared/5mtf6cj3.js +94 -0
  27. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  28. package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
  29. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  30. package/dist/shared/7nk68fj8.js +751 -0
  31. package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
  32. package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
  33. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  34. package/dist/shared/9yn6a2vd.js +11 -0
  35. package/dist/shared/ady1ms74.js +72 -0
  36. package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
  37. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  38. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  39. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  40. package/dist/shared/hqjsxnz7.js +13 -0
  41. package/dist/shared/htnaj77v.js +17 -0
  42. package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
  43. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  44. package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
  45. package/dist/shared/kj680nxv.js +19 -0
  46. package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
  47. package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
  48. package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
  49. package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
  50. package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
  51. package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
  52. package/dist/shared/s4mb6a4b.js +545 -0
  53. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  54. package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
  55. package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
  56. package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
  57. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  58. package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
  59. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  60. package/dist/shared/ygem90rn.js +900 -0
  61. package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
  62. package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
  63. package/dist/tooling/cli.js +46 -17
  64. package/dist/tooling/migrate/apply.d.ts +59 -5
  65. package/dist/tooling/migrate/backfill.d.ts +136 -0
  66. package/dist/tooling/migrate/check.d.ts +65 -0
  67. package/dist/tooling/migrate/classify.d.ts +108 -0
  68. package/dist/tooling/migrate/commands.d.ts +2 -2
  69. package/dist/tooling/migrate/config.d.ts +22 -0
  70. package/dist/tooling/migrate/doctor.d.ts +3 -0
  71. package/dist/tooling/migrate/drift.d.ts +18 -2
  72. package/dist/tooling/migrate/estimate.d.ts +50 -0
  73. package/dist/tooling/migrate/extensions.d.ts +2 -0
  74. package/dist/tooling/migrate/files.d.ts +12 -0
  75. package/dist/tooling/migrate/index.d.ts +3 -2
  76. package/dist/tooling/migrate/index.js +67 -1
  77. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  78. package/dist/tooling/migrate/lint.d.ts +71 -0
  79. package/dist/tooling/migrate/managed.d.ts +1 -1
  80. package/dist/tooling/migrate/plan.d.ts +64 -9
  81. package/dist/tooling/migrate/project.d.ts +42 -7
  82. package/dist/tooling/migrate/provision.d.ts +18 -0
  83. package/dist/tooling/migrate/reference.d.ts +40 -0
  84. package/dist/tooling/migrate/runner.d.ts +108 -0
  85. package/dist/tooling/migrate/safe.d.ts +77 -0
  86. package/dist/tooling/migrate/seed.d.ts +20 -0
  87. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  88. package/dist/tooling/migrate/status.d.ts +25 -6
  89. package/dist/tooling/testing/factories.d.ts +117 -0
  90. package/dist/tooling/testing/facts.d.ts +72 -0
  91. package/dist/tooling/testing/index.d.ts +89 -0
  92. package/dist/tooling/testing/index.js +8 -0
  93. package/dist/tooling/testing/isolation.d.ts +30 -0
  94. package/dist/tooling/testing/random.d.ts +40 -0
  95. package/dist/tooling/testing/record.d.ts +38 -0
  96. package/package.json +6 -2
  97. package/dist/shared/dbjej1tw.js +0 -456
  98. package/dist/shared/g4f5kcx9.js +0 -37
  99. package/dist/shared/qbxzvwwc.js +0 -88
  100. package/dist/shared/zwmqxb0g.js +0 -79
  101. package/dist/shared/{xva8v7qk.js → awrsqrn7.js} +3 -3
  102. package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  103. package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
  104. /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` and its types. The CLI imports planning and apply helpers
5
- * from their modules, so those names are not exported.
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 `okm_history`.
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). Those data steps are plain statements here. Batching them is P52.
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
- /** Expand, contract, or a step that is neither. */
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; P16B runs it as written. `transactional: false` marks a
20
- * step that cannot share a transaction with a later use (`ALTER TYPE … ADD
21
- * VALUE`, spec 19.2).
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 text is SQL plus class comments, never TypeScript.
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
- * @returns The text `okm generate` writes and `okm migrate plan` prints
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<string | undefined>;
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
- * @returns The plan
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<MigrationPlan>;
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, a database
46
- * that already has `okm_meta` is compared with the schema. A difference is
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<void>;
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[];