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
@@ -1,10 +1,12 @@
1
- import"./t616ynfn.js";
1
+ import"./8jb4ymh2.js";
2
2
  import"./n442h54c.js";
3
+ import"./wqem4jed.js";
3
4
  import {
4
5
  listTargets,
5
6
  loadConfig
6
- } from "./5nmdenb6.js";
7
- import"./2ssqrv66.js";
7
+ } from "./q6zsyh31.js";
8
+ import"./n5y9zyj1.js";
9
+ import"./41kv2xdm.js";
8
10
 
9
11
  // src/tooling/migrate/dev.ts
10
12
  import { mkdirSync } from "node:fs";
@@ -17,7 +19,7 @@ async function devProject(cwd) {
17
19
  `;
18
20
  const directory = join(cwd, config.out ?? ".okm", "dev-db");
19
21
  mkdirSync(directory, { recursive: true });
20
- const { open } = await import("./kgnc8grt.js");
22
+ const { open } = await import("./gdh9mrtp.js");
21
23
  const pool = await open({ dataDir: directory });
22
24
  try {
23
25
  await pool.execute("select 1");
@@ -3,7 +3,7 @@ import {
3
3
  readHandle,
4
4
  readCall,
5
5
  attachHttp
6
- } from "./k34e0xf2.js";
6
+ } from "./kkzwp3d2.js";
7
7
  import {
8
8
  fail
9
9
  } from "./xrfnq5k8.js";
@@ -1,14 +1,17 @@
1
- import"../shared/t616ynfn.js";
1
+ import"../shared/8jb4ymh2.js";
2
2
  import"../shared/n442h54c.js";
3
+ import"../shared/wqem4jed.js";
3
4
  import {
5
+ formatFindings,
6
+ hasError,
7
+ lintRefusal,
4
8
  buildProject,
5
9
  generateProject,
6
10
  planProject,
7
11
  checkProject
8
- } from "../shared/5nmdenb6.js";
9
- import {
10
- formatPlan
11
- } from "../shared/2ssqrv66.js";
12
+ } from "../shared/q6zsyh31.js";
13
+ import"../shared/n5y9zyj1.js";
14
+ import"../shared/41kv2xdm.js";
12
15
  import {
13
16
  OkmError2
14
17
  } from "../shared/52szzakm.js";
@@ -35,26 +38,31 @@ async function run(argv, io) {
35
38
  return;
36
39
  }
37
40
  if (command === "check") {
38
- await checkProject(cwd, invoke(splitFlags(rest)));
41
+ const findings = await checkProject(cwd, invoke(splitFlags(rest)));
42
+ const warnings = formatFindings(findings);
43
+ if (warnings.length > 0)
44
+ stdout(warnings);
39
45
  stdout(`ok
40
46
  `);
41
47
  return;
42
48
  }
43
49
  if (command === "generate") {
44
50
  const parsed = splitFlags(rest);
45
- const path = await generateProject(cwd, parsed.name ?? "migration", parsed.flags);
46
- stdout(path === undefined ? `no changes
47
- ` : `${path}
51
+ const written = await generateProject(cwd, parsed.name ?? "migration", parsed.flags);
52
+ stdout(written === undefined ? `no changes
53
+ ` : `${written.path}
48
54
  `);
55
+ if (written !== undefined)
56
+ stdout(formatFindings(written.findings));
49
57
  return;
50
58
  }
51
59
  if (command === "dev") {
52
- const { devProject } = await import("../shared/1tfdfvhh.js");
60
+ const { devProject } = await import("../shared/yjw02enp.js");
53
61
  stdout(await devProject(cwd));
54
62
  return;
55
63
  }
56
64
  if (command === "push") {
57
- const { pushProject } = await import("../shared/dbjej1tw.js");
65
+ const { pushProject } = await import("../shared/kj680nxv.js");
58
66
  stdout(await pushProject(cwd, invoke(splitFlags(rest))));
59
67
  return;
60
68
  }
@@ -65,23 +73,44 @@ async function run(argv, io) {
65
73
  if (parsed.name === undefined) {
66
74
  throw new OkmError2("invalid", "okm migrate plan needs a name.");
67
75
  }
68
- stdout(formatPlan(await planProject(cwd, parsed.name, parsed.flags)));
76
+ const planned = await planProject(cwd, parsed.name, parsed.flags, invoke(parsed));
77
+ stdout(planned.text);
78
+ stdout(formatFindings(planned.findings));
79
+ if (hasError(planned.findings))
80
+ throw lintRefusal(planned.findings);
69
81
  return;
70
82
  }
71
83
  if (sub === "apply") {
72
- const { applyProject } = await import("../shared/dbjej1tw.js");
73
- stdout(await applyProject(cwd, invoke(parsed)));
84
+ const { applyProject } = await import("../shared/kj680nxv.js");
85
+ stdout(await applyProject(cwd, invoke(parsed), stdout));
74
86
  return;
75
87
  }
76
88
  if (sub === "status") {
77
- const { statusProject } = await import("../shared/scwxrpx2.js");
89
+ const { statusProject } = await import("../shared/c5g81ngb.js");
78
90
  stdout(await statusProject(cwd, invoke(parsed)));
79
91
  return;
80
92
  }
93
+ if (sub === "check") {
94
+ const { checkMigrations } = await import("../shared/2321avmn.js");
95
+ const provision = parsed.flags.includes("--provision") || rest.slice(1).includes("--provision");
96
+ stdout(await checkMigrations(cwd, invoke(parsed), { provision }));
97
+ return;
98
+ }
99
+ }
100
+ if (command === "seed") {
101
+ const parsed = splitFlags(rest);
102
+ if (parsed.name === undefined) {
103
+ throw new OkmError2("invalid", "okm seed needs a file.", {
104
+ fix: { summary: "Pass okm seed <file>." }
105
+ });
106
+ }
107
+ const { seedProject } = await import("../shared/ady1ms74.js");
108
+ stdout(await seedProject(cwd, parsed.name, invoke(parsed)));
109
+ return;
81
110
  }
82
111
  if (command === "doctor") {
83
112
  const parsed = splitFlags(rest);
84
- const { doctorProject } = await import("../shared/ne02ng5c.js");
113
+ const { doctorProject } = await import("../shared/84ptn35q.js");
85
114
  stdout(await doctorProject(cwd, parsed.name, invoke(parsed)));
86
115
  return;
87
116
  }
@@ -93,7 +122,7 @@ async function run(argv, io) {
93
122
  });
94
123
  }
95
124
  if (sub === "list" || sub === "check") {
96
- const { extProject } = await import("../shared/meck53jv.js");
125
+ const { extProject } = await import("../shared/tny2t3sm.js");
97
126
  stdout(await extProject(cwd, sub, invoke(splitFlags(rest.slice(1)))));
98
127
  return;
99
128
  }
@@ -3,9 +3,14 @@
3
3
  *
4
4
  * A run holds a session advisory lock. Transactional steps of one migration
5
5
  * share a transaction. Steps Postgres cannot run there (`CREATE INDEX
6
- * CONCURRENTLY`, `ADD VALUE`, `VACUUM`) are their own steps. Each finished
7
- * step is a history row. A later apply skips those rows.
6
+ * CONCURRENTLY`, `ADD VALUE`, `VACUUM`) and backfill steps are their own
7
+ * units. {@link runTarget} runs those units. Each finished step is a history
8
+ * row. A later apply skips those rows. A backfill records each committed
9
+ * batch in `okm_backfill` and resumes from that boundary.
8
10
  */
11
+ import type { Catalog } from "../../contracts/catalog/types.js";
12
+ import { type BackfillBatch } from "./runner.js";
13
+ import { type ReferenceTable } from "./reference.js";
9
14
  import { type PlanStep } from "./plan.js";
10
15
  import type { StoredMigration } from "./files.js";
11
16
  import { type InvokeFlags } from "./policy.js";
@@ -51,6 +56,35 @@ export type ApplyRequest = {
51
56
  * on this schema before `SET ROLE`. The default is `public`.
52
57
  */
53
58
  readonly schema?: string;
59
+ /** Milliseconds to wait after each committed backfill batch. */
60
+ readonly backfillPauseMs?: number;
61
+ /** `statement_timeout` for one backfill batch. Falls back to {@link ApplyRequest.statementTimeoutMs}. */
62
+ readonly backfillStatementTimeoutMs?: number;
63
+ /** One line after each committed backfill batch. */
64
+ readonly onProgress?: (line: string) => void;
65
+ /** Runs after each committed backfill batch, before the pause. */
66
+ readonly afterBatch?: (batch: BackfillBatch) => void | Promise<void>;
67
+ /**
68
+ * Migrations directory to lint before any DDL.
69
+ *
70
+ * Only a migration with a step missing from `okm_history` is checked.
71
+ * `push` omits this: its steps are not files.
72
+ */
73
+ readonly lintDirectory?: string;
74
+ /**
75
+ * Head snapshot.
76
+ *
77
+ * Set by `okm migrate apply` and `provision()`. An empty target installs
78
+ * this catalog instead of replaying files. `only` refuses a target that
79
+ * is not empty (OKM1851) and does not fall through to replay.
80
+ */
81
+ readonly snapshot?: {
82
+ readonly catalog: Catalog;
83
+ readonly migrationId: string;
84
+ readonly catalogHash: string;
85
+ readonly reference: readonly ReferenceTable[];
86
+ readonly only?: boolean;
87
+ };
54
88
  };
55
89
  /** Migrations that ran at least one step in this invocation. */
56
90
  export type ApplyReport = {
@@ -74,8 +108,9 @@ export declare function applyUnits(migration: StoredMigration, done: ReadonlySet
74
108
  * Applies pending migrations on one target.
75
109
  *
76
110
  * The connection is reserved for the whole run so the advisory lock stays
77
- * session-scoped. Lock timeout retries that step. Any other failure stops
78
- * at that step.
111
+ * session-scoped. Lock timeout retries that step, including `NOT VALID`,
112
+ * `VALIDATE CONSTRAINT`, `SET NOT NULL`, `ADD CONSTRAINT … USING INDEX`, and
113
+ * a concurrent index create or drop. Any other failure stops at that step.
79
114
  *
80
115
  * @param request - Target, flags, and the migrations to consider
81
116
  * @returns The target and the migration ids that ran
@@ -84,11 +119,15 @@ export declare function applyTarget(request: ApplyRequest): Promise<ApplyReport>
84
119
  /**
85
120
  * Applies the migrations in the project to the selected target.
86
121
  *
122
+ * Pending migrations are linted after `okm_history` is read and before any
123
+ * DDL. A migration the target already applied is left as it ran.
124
+ *
87
125
  * @param cwd - Project directory
88
126
  * @param flags - `--target`, protection, pooler, and timeouts
127
+ * @param write - Receives each backfill progress line, including the newline
89
128
  * @returns Text for stdout, including the target name
90
129
  */
91
- export declare function applyProject(cwd: string, flags: InvokeFlags): Promise<string>;
130
+ export declare function applyProject(cwd: string, flags: InvokeFlags, write?: (text: string) => void): Promise<string>;
92
131
  /**
93
132
  * Prototype sync. Blocked on a protected target.
94
133
  *
@@ -100,3 +139,18 @@ export declare function applyProject(cwd: string, flags: InvokeFlags): Promise<s
100
139
  * @returns Text for stdout
101
140
  */
102
141
  export declare function pushProject(cwd: string, flags: InvokeFlags): Promise<string>;
142
+ /**
143
+ * Pause and statement timeout `defineConfig({ backfill })` sets for a run.
144
+ *
145
+ * Omitted fields stay unset so the runner uses its built-in defaults.
146
+ *
147
+ * @param backfill - Config block, when the project set one
148
+ * @returns The fields {@link applyTarget} reads
149
+ */
150
+ export declare function backfillTiming(backfill: {
151
+ readonly pauseMs?: number;
152
+ readonly statementTimeoutMs?: number;
153
+ } | undefined): {
154
+ readonly backfillPauseMs?: number;
155
+ readonly backfillStatementTimeoutMs?: number;
156
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Backfill step header and the per-batch predicate (D195).
3
+ *
4
+ * The migration file keeps one `UPDATE`. `-- backfill` names the table, the
5
+ * primary key, and the batch size. `$1` is the exclusive lower bound and `$2`
6
+ * is the inclusive upper bound. Null means that side is open. The runner
7
+ * fills those bounds. This module does not run the batches.
8
+ */
9
+ import type { Catalog } from "../../contracts/catalog/types.js";
10
+ /**
11
+ * Rows in one batch when the plan and the config omit `batchSize`.
12
+ *
13
+ * A step header `batch=` replaces this for that step.
14
+ */
15
+ export declare const DEFAULT_BACKFILL_BATCH_SIZE = 1000;
16
+ /**
17
+ * Milliseconds to wait after a committed batch when the config omits `pauseMs`.
18
+ *
19
+ * Zero runs the next batch immediately. Set a pause to rate-limit a large table.
20
+ */
21
+ export declare const DEFAULT_BACKFILL_PAUSE_MS = 0;
22
+ /**
23
+ * `statement_timeout` for one batch when the config omits `statementTimeoutMs`.
24
+ *
25
+ * This is separate from the apply timeout on DDL. A timed-out batch rolls
26
+ * back and leaves the previous checkpoint.
27
+ */
28
+ export declare const DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS = 30000;
29
+ /** One primary-key column, quoted, with the catalog type used as a cast. */
30
+ export type BackfillColumn = {
31
+ /** Quoted identifier. */
32
+ readonly quoted: string;
33
+ /** Catalog `dataType`, safe to place after `::`. */
34
+ readonly dataType: string;
35
+ };
36
+ /**
37
+ * What `-- backfill` records.
38
+ *
39
+ * `key` is quoted identifiers in primary-key order. Types live in the
40
+ * statement's casts, not in the header.
41
+ */
42
+ export type BackfillSpec = {
43
+ /** Quoted schema-qualified table, as in the statement. */
44
+ readonly table: string;
45
+ /** Quoted key columns. */
46
+ readonly key: readonly string[];
47
+ /** Rows per batch. The header value, which overrides config. */
48
+ readonly batch: number;
49
+ };
50
+ /**
51
+ * Batch size the planner writes into a new step.
52
+ *
53
+ * @param requested - `defineConfig({ backfill: { batchSize } })`, when set
54
+ * @returns A positive integer. The built-in default is 1000
55
+ */
56
+ /**
57
+ * Field for {@link resolveBatchSize} when the config may omit it.
58
+ *
59
+ * Exact optional properties reject `batchSize: undefined`.
60
+ *
61
+ * @param batchSize - Configured size, when set
62
+ * @returns `{ batchSize }` or an empty object
63
+ */
64
+ export declare function batchSizeField(batchSize: number | undefined): {
65
+ readonly batchSize: number;
66
+ } | Record<string, never>;
67
+ /**
68
+ * Batch size the planner writes into a new step.
69
+ *
70
+ * @param requested - `defineConfig({ backfill: { batchSize } })`, when set
71
+ * @returns A positive integer. The built-in default is 1000
72
+ */
73
+ export declare function resolveBatchSize(requested: number | undefined): number;
74
+ /**
75
+ * Prints the header comment.
76
+ *
77
+ * @param spec - Table, key, and batch size
78
+ * @returns One line, without a trailing newline
79
+ */
80
+ export declare function formatBackfillHeader(spec: BackfillSpec): string;
81
+ /**
82
+ * Reads a `-- backfill` line.
83
+ *
84
+ * @param line - One comment line
85
+ * @returns The spec, or `undefined` when the line is not a backfill header
86
+ */
87
+ export declare function parseBackfillHeader(line: string): BackfillSpec | undefined;
88
+ /**
89
+ * Primary key of `table`, or OKM1546 when the table has none.
90
+ *
91
+ * A backfill walks this key. It does not count rows and it does not scan.
92
+ *
93
+ * @param catalogs - Before and after catalogs. The first key found wins
94
+ * @param table - Unqualified table name
95
+ * @returns Columns in key order, with cast types
96
+ */
97
+ export declare function primaryKeyColumns(catalogs: readonly Catalog[], table: string): readonly BackfillColumn[];
98
+ /**
99
+ * Predicate that limits an idempotent `UPDATE` to one key range.
100
+ *
101
+ * `$1` is exclusive and `$2` is inclusive. Either null leaves that side open.
102
+ * One column compares as itself. A composite key compares as a row, with the
103
+ * bound stored as a JSON array of text.
104
+ *
105
+ * @param columns - Primary key in order
106
+ * @returns SQL fragment with no leading `and`
107
+ */
108
+ export declare function keyRangePredicate(columns: readonly BackfillColumn[]): string;
109
+ /**
110
+ * How many batches `rows` take at `batch` size.
111
+ *
112
+ * Zero rows is zero batches. The count is `ceil(rows / batch)`.
113
+ *
114
+ * @param rows - Estimated row count
115
+ * @param batch - Batch size from the step header
116
+ * @returns The batch count a plan line prints
117
+ */
118
+ export declare function aboutBatchCount(rows: number, batch: number): number;
119
+ /**
120
+ * Keyset query that returns the last key of the next batch.
121
+ *
122
+ * `$1` is the previous boundary, null for the first batch. `$2` is
123
+ * `batch - 1`. No row means this batch is the last one.
124
+ *
125
+ * @param table - Quoted schema-qualified table
126
+ * @param columns - Primary key, with cast types
127
+ * @returns One statement
128
+ */
129
+ export declare function boundaryQuery(table: string, columns: readonly BackfillColumn[]): string;
130
+ /**
131
+ * Cast spelling safe to interpolate after `::`.
132
+ *
133
+ * @param dataType - Catalog or `format_type` spelling
134
+ * @returns The same spelling
135
+ */
136
+ export declare function checkedCast(dataType: string): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `okm migrate check` proves a migration history is sound (D196).
3
+ *
4
+ * The command replays every file into a scratch schema through
5
+ * {@link applyTarget}, then plans the introspected schema back to that
6
+ * file's catalog. It also checks the previous catalog, the head, and the
7
+ * linter. Nothing here changes apply.
8
+ */
9
+ import type { Catalog } from "../../contracts/catalog/types.js";
10
+ import { type MigrationClass } from "./classify.js";
11
+ import { type MigrationPlan } from "./plan.js";
12
+ import { type InvokeFlags } from "./policy.js";
13
+ /** Prefix of the scratch schema. The random suffix makes the name unique. */
14
+ export declare const CHECK_SCHEMA_PREFIX = "okm_check_";
15
+ /**
16
+ * Runs `okm migrate check` for the project in `cwd`.
17
+ *
18
+ * A protected target is refused before any statement. The scratch schema is
19
+ * dropped before this returns, including when a check fails. `--allow-protected`
20
+ * does not lift the refusal.
21
+ *
22
+ * @param cwd - Project directory
23
+ * @param flags - `--target` and pooler. Protection is ignored
24
+ * @param options - `--provision` also compares a snapshot install with a full replay
25
+ * @returns One summary line, ending in a newline
26
+ */
27
+ export declare function checkMigrations(cwd: string, flags: InvokeFlags, options?: {
28
+ readonly provision?: boolean;
29
+ }): Promise<string>;
30
+ /**
31
+ * Objects in `before` that `after` no longer satisfies.
32
+ *
33
+ * A table, column, constraint, or type that disappeared is a gap. A column
34
+ * whose type changed is a gap. Nullability tightened without a default is a
35
+ * gap. An added object is not.
36
+ *
37
+ * @param before - Previous release catalog
38
+ * @param after - Catalog the migration stores
39
+ * @returns One sentence per gap, in catalog order
40
+ */
41
+ export declare function previousCatalogGaps(before: Catalog, after: Catalog): readonly string[];
42
+ /**
43
+ * Class of a migration, from step kinds.
44
+ *
45
+ * A step with no kind is planned from `before` to `after` instead. The
46
+ * `-- class:` comment is not read.
47
+ *
48
+ * @param plan - Parsed migration file
49
+ * @param before - Catalog before the migration
50
+ * @param after - Catalog the file stores
51
+ * @returns The strictest recomputed class
52
+ */
53
+ export declare function recomputedMigrationClass(plan: MigrationPlan, before: Catalog, after: Catalog): MigrationClass;
54
+ /**
55
+ * Whether a previous-catalog gap is allowed.
56
+ *
57
+ * The recomputed class must be `contract`. A file whose steps are classified
58
+ * `expand`, including a hand-edited `-- class:` line, does not allow the gap.
59
+ *
60
+ * @param plan - Parsed migration file
61
+ * @param before - Catalog before the migration
62
+ * @param after - Catalog the file stores
63
+ * @returns `true` when the gap may stand
64
+ */
65
+ export declare function migrationAllowsPreviousGaps(plan: MigrationPlan, before: Catalog, after: Catalog): boolean;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Expand, contract, or raw SQL.
3
+ *
4
+ * `unclassified` is only for a statement the planner did not classify.
5
+ * The protected-target policy treats that the same as `contract`.
6
+ */
7
+ export type MigrationClass = "expand" | "contract" | "unclassified";
8
+ /**
9
+ * One operation the planner can emit.
10
+ *
11
+ * A new kind belongs in {@link STEP_CLASS}. The classification test lists
12
+ * every key, so a kind without a class fails that test.
13
+ */
14
+ export declare const STEP_CLASS: {
15
+ readonly "create-table": "expand";
16
+ readonly "drop-table": "contract";
17
+ readonly "add-column": "expand";
18
+ readonly "add-column-required": "contract";
19
+ readonly "drop-column": "contract";
20
+ readonly "set-column-type": "contract";
21
+ readonly "set-not-null": "contract";
22
+ readonly "drop-not-null": "expand";
23
+ readonly "set-default": "expand";
24
+ readonly "drop-default": "contract";
25
+ readonly "add-identity": "expand";
26
+ readonly "drop-identity": "contract";
27
+ readonly "set-identity-always": "contract";
28
+ readonly "set-identity-by-default": "expand";
29
+ readonly "add-constraint": "expand";
30
+ readonly "validate-constraint": "expand";
31
+ readonly "drop-constraint": "contract";
32
+ readonly "drop-not-null-check": "expand";
33
+ readonly "rename-constraint": "contract";
34
+ readonly "widen-check": "expand";
35
+ readonly "narrow-check": "contract";
36
+ readonly "create-index": "expand";
37
+ readonly "drop-index": "contract";
38
+ readonly "rename-index": "contract";
39
+ readonly "create-sequence": "expand";
40
+ readonly "drop-sequence": "contract";
41
+ readonly "create-enum": "expand";
42
+ readonly "recreate-enum": "contract";
43
+ readonly "add-enum-value": "expand";
44
+ readonly "rename-enum": "contract";
45
+ readonly "drop-enum": "contract";
46
+ readonly "set-enum-column": "contract";
47
+ readonly "create-domain": "expand";
48
+ readonly "drop-domain": "contract";
49
+ readonly "add-domain-check": "expand";
50
+ readonly "validate-domain-check": "expand";
51
+ readonly "drop-domain-check": "contract";
52
+ readonly "rename-domain-check": "expand";
53
+ readonly "create-extension": "expand";
54
+ readonly "drop-extension": "contract";
55
+ readonly "update-extension": "expand";
56
+ readonly "move-extension": "contract";
57
+ readonly "create-function": "expand";
58
+ readonly "drop-function": "contract";
59
+ readonly "replace-function": "expand";
60
+ readonly "create-trigger": "expand";
61
+ readonly "drop-trigger": "contract";
62
+ readonly "create-view": "expand";
63
+ readonly "drop-view": "contract";
64
+ readonly "replace-view": "expand";
65
+ readonly "create-matview": "expand";
66
+ readonly "drop-matview": "contract";
67
+ readonly "refresh-matview": "expand";
68
+ readonly "create-role": "expand";
69
+ readonly "alter-role": "contract";
70
+ readonly grant: "expand";
71
+ readonly revoke: "contract";
72
+ readonly "grant-default": "expand";
73
+ readonly "revoke-default": "contract";
74
+ readonly "rename-table": "contract";
75
+ readonly "rename-column": "contract";
76
+ readonly "rename-sequence": "contract";
77
+ readonly "backfill-expand": "expand";
78
+ readonly "backfill-contract": "contract";
79
+ readonly "raw-sql": "unclassified";
80
+ };
81
+ /** A planner operation. */
82
+ export type StepKind = keyof typeof STEP_CLASS;
83
+ /** Every kind, in table order. */
84
+ export declare const STEP_KINDS: readonly StepKind[];
85
+ /**
86
+ * Class for one kind.
87
+ *
88
+ * @param kind - Planner operation
89
+ * @returns The class stored on the step
90
+ */
91
+ export declare function classOf(kind: StepKind): MigrationClass;
92
+ /**
93
+ * Strictest class in a plan.
94
+ *
95
+ * `contract` wins over `unclassified`, which wins over `expand`. An empty
96
+ * list is `expand`.
97
+ *
98
+ * @param classes - One class per step
99
+ * @returns The class of the plan header
100
+ */
101
+ export declare function strictestClass(classes: readonly MigrationClass[]): MigrationClass;
102
+ /**
103
+ * Reports whether a plan comment names a kind.
104
+ *
105
+ * @param value - Text after `-- kind: `
106
+ * @returns `true` when the text is a {@link StepKind}
107
+ */
108
+ export declare function isStepKind(value: string): value is StepKind;
@@ -23,8 +23,8 @@ export declare function formatFailure(error: OkmError): string;
23
23
  * Runs one `okm` invocation.
24
24
  *
25
25
  * `--version` stays in the bin. This function handles `build`, `check`,
26
- * `generate`, `dev`, `push`, `ext list`, `ext check`, `doctor`, and `migrate plan`,
27
- * `apply`, and `status`.
26
+ * `generate`, `dev`, `push`, `seed`, `ext list`, `ext check`, `doctor`, and
27
+ * `migrate plan`, `apply`, `status`, and `check`.
28
28
  *
29
29
  * @param argv - Arguments after the program name
30
30
  * @param io - Working directory and stdout. Defaults to the process
@@ -36,6 +36,28 @@ export type MigrateConfig = {
36
36
  readonly lock?: number;
37
37
  readonly statement?: number;
38
38
  };
39
+ /**
40
+ * First migration id that `okm migrate check` lints.
41
+ *
42
+ * Files before this id in apply order are the adoption baseline: a project
43
+ * that turns the linter on later does not fail on old unreasoned drops.
44
+ * The id itself is linted. `okm migrate apply` still lints every pending
45
+ * migration.
46
+ */
47
+ readonly lintFrom?: string;
48
+ /**
49
+ * Batched backfill (D195).
50
+ *
51
+ * `batchSize` is what a new plan writes as `batch=`. A step header overrides
52
+ * it at apply time. `pauseMs` waits between committed batches. `statementTimeoutMs`
53
+ * is `statement_timeout` for each batch. Omitted fields use the built-in
54
+ * defaults: 1000 rows, no pause, and 30 seconds.
55
+ */
56
+ readonly backfill?: {
57
+ readonly batchSize?: number;
58
+ readonly pauseMs?: number;
59
+ readonly statementTimeoutMs?: number;
60
+ };
39
61
  /**
40
62
  * Migration role and application role.
41
63
  *
@@ -11,6 +11,9 @@ import { type InvokeFlags } from "./policy.js";
11
11
  /**
12
12
  * Explains one code, or lists the triggers on each table.
13
13
  *
14
+ * A role check connects as the read-only `check` class. A protected target
15
+ * is allowed. The command does not change the database.
16
+ *
14
17
  * @param cwd - Project directory
15
18
  * @param code - Spec code, when the caller passed one
16
19
  * @returns Text for stdout, including the trailing newline
@@ -4,10 +4,12 @@
4
4
  * A database that has never been pushed has no `okm_meta` and is left alone.
5
5
  * After push, the introspected catalog is planned back to the schema. Each
6
6
  * view is reprinted on this server first, so a `pg_get_viewdef` spelling is
7
- * not drift. Any remaining statement is OKM1520. `okm_meta` and `okm_history`
8
- * are omitted by the planner.
7
+ * not drift. Any remaining statement is OKM1520. `okm_meta`, `okm_history`,
8
+ * and `okm_backfill` are omitted by the planner.
9
9
  */
10
+ import { type Sql } from "postgres";
10
11
  import type { Catalog } from "../../contracts/catalog/types.js";
12
+ import { type CatalogQuery, type IntrospectOptions } from "../../dialects/pg/introspect.js";
11
13
  import type { RolesInput } from "../../dialects/pg/role/index.js";
12
14
  /**
13
15
  * Refuses when the connected database differs from the schema.
@@ -17,3 +19,17 @@ import type { RolesInput } from "../../dialects/pg/role/index.js";
17
19
  * @param roles - `defineConfig({ roles })`, when set
18
20
  */
19
21
  export declare function assertAuthorDrift(url: string, author: Catalog, roles?: RolesInput): Promise<void>;
22
+ /**
23
+ * Turns a postgres.js handle into the query shape introspection uses.
24
+ *
25
+ * @param sql - One connection
26
+ * @returns A {@link CatalogQuery}
27
+ */
28
+ export declare function catalogQuery(sql: Sql): CatalogQuery;
29
+ /**
30
+ * Role names introspection should read, when the config names them.
31
+ *
32
+ * @param roles - `defineConfig({ roles })`, when set
33
+ * @returns The introspect option, or `undefined` when roles are not configured
34
+ */
35
+ export declare function managedRoleOptions(roles: RolesInput | undefined): IntrospectOptions | undefined;