okmodel 0.2.1 → 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 (148) hide show
  1. package/README.md +882 -48
  2. package/dist/contracts/catalog/document.d.ts +6 -2
  3. package/dist/contracts/catalog/enum.d.ts +31 -4
  4. package/dist/contracts/catalog/extension.d.ts +61 -0
  5. package/dist/contracts/catalog/privilege.d.ts +64 -0
  6. package/dist/contracts/catalog/routine.d.ts +56 -0
  7. package/dist/contracts/catalog/types.d.ts +164 -10
  8. package/dist/contracts/catalog/view.d.ts +73 -0
  9. package/dist/dialects/pg/column.d.ts +3 -1
  10. package/dist/dialects/pg/ddl.d.ts +65 -1
  11. package/dist/dialects/pg/enum.d.ts +10 -8
  12. package/dist/dialects/pg/ext/citext.d.ts +14 -0
  13. package/dist/dialects/pg/ext/citext.js +11 -0
  14. package/dist/dialects/pg/ext/index.d.ts +52 -0
  15. package/dist/dialects/pg/ext/index.js +6 -0
  16. package/dist/dialects/pg/ext/pg-trgm.d.ts +53 -0
  17. package/dist/dialects/pg/ext/pg-trgm.js +42 -0
  18. package/dist/dialects/pg/fn/index.d.ts +107 -0
  19. package/dist/dialects/pg/fn/index.js +175 -0
  20. package/dist/dialects/pg/fn/touch.d.ts +14 -0
  21. package/dist/dialects/pg/index.js +104 -19
  22. package/dist/dialects/pg/introspect.d.ts +10 -1
  23. package/dist/dialects/pg/operators.d.ts +8 -1
  24. package/dist/dialects/pg/role/check.d.ts +45 -0
  25. package/dist/dialects/pg/role/index.d.ts +50 -0
  26. package/dist/dialects/pg/role/sql.d.ts +51 -0
  27. package/dist/dialects/pg/table.d.ts +24 -1
  28. package/dist/dialects/pg/tenancy.d.ts +9 -0
  29. package/dist/dialects/pg/view/client.d.ts +30 -0
  30. package/dist/dialects/pg/view/depend.d.ts +35 -0
  31. package/dist/dialects/pg/view/index.d.ts +114 -0
  32. package/dist/dialects/pg/view/index.js +289 -0
  33. package/dist/dialects/pg/view/scratch.d.ts +22 -0
  34. package/dist/dialects/pg/where.d.ts +2 -2
  35. package/dist/runtime/catalog-cache.d.ts +17 -0
  36. package/dist/runtime/compat-words.d.ts +23 -0
  37. package/dist/runtime/drift-detail.d.ts +20 -4
  38. package/dist/runtime/drift.d.ts +4 -2
  39. package/dist/runtime/pg/bun.js +3 -3
  40. package/dist/runtime/pg/pg.js +3 -3
  41. package/dist/runtime/pg/pglite.js +3 -3
  42. package/dist/runtime/pg/postgresjs.js +3 -3
  43. package/dist/runtime/tenancy/index.js +6 -3
  44. package/dist/runtime/traits/index.d.ts +7 -2
  45. package/dist/runtime/traits/index.js +151 -13
  46. package/dist/runtime/types.d.ts +2 -0
  47. package/dist/runtime/validate/index.js +4 -4
  48. package/dist/shared/{xkz8g6by.js → 1g0drakv.js} +2 -2
  49. package/dist/shared/2321avmn.js +418 -0
  50. package/dist/shared/{7mht49yd.js → 3p4b1fg0.js} +57 -18
  51. package/dist/shared/41kv2xdm.js +318 -0
  52. package/dist/shared/{97g2jnrw.js → 4ef7mmqf.js} +3 -3
  53. package/dist/shared/{sacqha1y.js → 4m9fn52z.js} +2 -2
  54. package/dist/shared/5mtf6cj3.js +94 -0
  55. package/dist/shared/{1dp5knt7.js → 5vdm4sgp.js} +2 -2
  56. package/dist/shared/{a1gdp7j9.js → 6693j9rs.js} +1 -1
  57. package/dist/shared/{38crxbgn.js → 6p5k20sm.js} +3 -3
  58. package/dist/shared/7nk68fj8.js +751 -0
  59. package/dist/shared/84ptn35q.js +71 -0
  60. package/dist/shared/{d2whjz4k.js → 8jb4ymh2.js} +581 -28
  61. package/dist/shared/8ktwd3rh.js +97 -0
  62. package/dist/shared/94ez8kzh.js +19 -0
  63. package/dist/shared/{3a5ne750.js → 9m80mkac.js} +2 -2
  64. package/dist/shared/9x25fxhc.js +150 -0
  65. package/dist/shared/9yn6a2vd.js +11 -0
  66. package/dist/shared/ady1ms74.js +72 -0
  67. package/dist/shared/{4jzzghr6.js → awrsqrn7.js} +6 -6
  68. package/dist/shared/{4b9da9d7.js → axhk06q1.js} +19 -1
  69. package/dist/shared/{d04j53v8.js → c5g81ngb.js} +82 -14
  70. package/dist/shared/{n47rd821.js → c8aghh05.js} +1 -1
  71. package/dist/shared/{dd4f8mnx.js → c9z5g0fm.js} +4 -2
  72. package/dist/shared/{ps43134h.js → dyj86cmj.js} +4 -4
  73. package/dist/shared/{0qkxbm1f.js → fa9vt245.js} +1 -1
  74. package/dist/shared/{qa5dsc7z.js → fb00y8fp.js} +3 -3
  75. package/dist/shared/{qxa0yb9j.js → g7ez901n.js} +30 -1
  76. package/dist/shared/{py3nh7jy.js → gdh9mrtp.js} +2 -2
  77. package/dist/shared/{yw5c610b.js → gjthds9j.js} +2 -2
  78. package/dist/shared/hcmdtwgh.js +102 -0
  79. package/dist/shared/hqjsxnz7.js +13 -0
  80. package/dist/shared/htnaj77v.js +17 -0
  81. package/dist/shared/{v18jva5b.js → jgxzjzcz.js} +1 -1
  82. package/dist/shared/{x9v1qgeb.js → jsd07prz.js} +3 -3
  83. package/dist/shared/{g9f0zqce.js → kafaa5zv.js} +1 -1
  84. package/dist/shared/kj680nxv.js +19 -0
  85. package/dist/shared/{mxmcdsh5.js → kkzwp3d2.js} +9 -9
  86. package/dist/shared/{g1b5zzj9.js → n0dgs0pb.js} +1 -1
  87. package/dist/shared/n5y9zyj1.js +2431 -0
  88. package/dist/shared/{zt8y0wzb.js → n7mcc1gk.js} +1 -1
  89. package/dist/shared/{ge03bfca.js → nxb42q7h.js} +2 -2
  90. package/dist/shared/q6zsyh31.js +2192 -0
  91. package/dist/shared/{sfh4pk75.js → q795afwa.js} +26 -21
  92. package/dist/shared/{10tjenmd.js → qn0srwwq.js} +1 -1
  93. package/dist/shared/s4mb6a4b.js +545 -0
  94. package/dist/shared/{ytg3xf9p.js → sf8m6tfy.js} +3 -3
  95. package/dist/shared/{zd93g3rq.js → sr5ms7b3.js} +1 -1
  96. package/dist/shared/{xatfg0gn.js → tmbdywx0.js} +4 -19
  97. package/dist/shared/tny2t3sm.js +100 -0
  98. package/dist/shared/{e7a268fy.js → v7tpnhnz.js} +1 -1
  99. package/dist/shared/{ecdzej21.js → wqem4jed.js} +7 -7
  100. package/dist/shared/xghkt7a9.js +1144 -0
  101. package/dist/shared/y005b761.js +112 -0
  102. package/dist/shared/ycatkxmq.js +84 -0
  103. package/dist/shared/ygem90rn.js +900 -0
  104. package/dist/shared/{eaxwrgby.js → yjw02enp.js} +6 -3
  105. package/dist/shared/{mrq77sgw.js → zg24x0hm.js} +1 -1
  106. package/dist/shared/{cstgkzdx.js → zvbjc949.js} +1 -1
  107. package/dist/tooling/cli.js +66 -13
  108. package/dist/tooling/migrate/apply.d.ts +75 -5
  109. package/dist/tooling/migrate/backfill.d.ts +136 -0
  110. package/dist/tooling/migrate/check.d.ts +65 -0
  111. package/dist/tooling/migrate/classify.d.ts +108 -0
  112. package/dist/tooling/migrate/commands.d.ts +2 -1
  113. package/dist/tooling/migrate/config.d.ts +30 -0
  114. package/dist/tooling/migrate/doctor.d.ts +28 -0
  115. package/dist/tooling/migrate/drift.d.ts +35 -0
  116. package/dist/tooling/migrate/estimate.d.ts +50 -0
  117. package/dist/tooling/migrate/ext.d.ts +17 -0
  118. package/dist/tooling/migrate/extensions.d.ts +53 -0
  119. package/dist/tooling/migrate/files.d.ts +12 -0
  120. package/dist/tooling/migrate/index.d.ts +3 -2
  121. package/dist/tooling/migrate/index.js +67 -1
  122. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  123. package/dist/tooling/migrate/lint.d.ts +71 -0
  124. package/dist/tooling/migrate/managed.d.ts +17 -0
  125. package/dist/tooling/migrate/plan.d.ts +78 -10
  126. package/dist/tooling/migrate/project.d.ts +64 -5
  127. package/dist/tooling/migrate/provision.d.ts +18 -0
  128. package/dist/tooling/migrate/reference.d.ts +40 -0
  129. package/dist/tooling/migrate/runner.d.ts +108 -0
  130. package/dist/tooling/migrate/safe.d.ts +77 -0
  131. package/dist/tooling/migrate/seed.d.ts +20 -0
  132. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  133. package/dist/tooling/migrate/status.d.ts +25 -6
  134. package/dist/tooling/migrate/type-name.d.ts +19 -0
  135. package/dist/tooling/testing/factories.d.ts +117 -0
  136. package/dist/tooling/testing/facts.d.ts +72 -0
  137. package/dist/tooling/testing/index.d.ts +89 -0
  138. package/dist/tooling/testing/index.js +8 -0
  139. package/dist/tooling/testing/isolation.d.ts +30 -0
  140. package/dist/tooling/testing/random.d.ts +40 -0
  141. package/dist/tooling/testing/record.d.ts +38 -0
  142. package/package.json +26 -2
  143. package/dist/shared/5z4q2zda.js +0 -2073
  144. package/dist/shared/eqmtn1bj.js +0 -399
  145. package/dist/shared/rtnaqre0.js +0 -37
  146. package/dist/shared/zwmqxb0g.js +0 -79
  147. package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
  148. /package/dist/shared/{9k6vetvf.js → rrstp3kb.js} +0 -0
@@ -1,9 +1,12 @@
1
- import"./d2whjz4k.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 "./5z4q2zda.js";
7
+ } from "./q6zsyh31.js";
8
+ import"./n5y9zyj1.js";
9
+ import"./41kv2xdm.js";
7
10
 
8
11
  // src/tooling/migrate/dev.ts
9
12
  import { mkdirSync } from "node:fs";
@@ -16,7 +19,7 @@ async function devProject(cwd) {
16
19
  `;
17
20
  const directory = join(cwd, config.out ?? ".okm", "dev-db");
18
21
  mkdirSync(directory, { recursive: true });
19
- const { open } = await import("./py3nh7jy.js");
22
+ const { open } = await import("./gdh9mrtp.js");
20
23
  const pool = await open({ dataDir: directory });
21
24
  try {
22
25
  await pool.execute("select 1");
@@ -3,7 +3,7 @@ import {
3
3
  readHandle,
4
4
  readCall,
5
5
  attachHttp
6
- } from "./mxmcdsh5.js";
6
+ } from "./kkzwp3d2.js";
7
7
  import {
8
8
  fail
9
9
  } from "./xrfnq5k8.js";
@@ -4,7 +4,7 @@ import {
4
4
  nextTransactionDepth,
5
5
  EMPTY_NOTICES,
6
6
  resultFrom
7
- } from "./9k6vetvf.js";
7
+ } from "./rrstp3kb.js";
8
8
 
9
9
  // src/adapters/pg/session.ts
10
10
  function quoteIdent2(name) {
@@ -1,12 +1,17 @@
1
- import"../shared/d2whjz4k.js";
1
+ import"../shared/8jb4ymh2.js";
2
2
  import"../shared/n442h54c.js";
3
+ import"../shared/wqem4jed.js";
3
4
  import {
4
- formatPlan,
5
+ formatFindings,
6
+ hasError,
7
+ lintRefusal,
5
8
  buildProject,
6
9
  generateProject,
7
10
  planProject,
8
11
  checkProject
9
- } from "../shared/5z4q2zda.js";
12
+ } from "../shared/q6zsyh31.js";
13
+ import"../shared/n5y9zyj1.js";
14
+ import"../shared/41kv2xdm.js";
10
15
  import {
11
16
  OkmError2
12
17
  } from "../shared/52szzakm.js";
@@ -33,26 +38,31 @@ async function run(argv, io) {
33
38
  return;
34
39
  }
35
40
  if (command === "check") {
36
- await checkProject(cwd);
41
+ const findings = await checkProject(cwd, invoke(splitFlags(rest)));
42
+ const warnings = formatFindings(findings);
43
+ if (warnings.length > 0)
44
+ stdout(warnings);
37
45
  stdout(`ok
38
46
  `);
39
47
  return;
40
48
  }
41
49
  if (command === "generate") {
42
50
  const parsed = splitFlags(rest);
43
- const path = await generateProject(cwd, parsed.name ?? "migration", parsed.flags);
44
- stdout(path === undefined ? `no changes
45
- ` : `${path}
51
+ const written = await generateProject(cwd, parsed.name ?? "migration", parsed.flags);
52
+ stdout(written === undefined ? `no changes
53
+ ` : `${written.path}
46
54
  `);
55
+ if (written !== undefined)
56
+ stdout(formatFindings(written.findings));
47
57
  return;
48
58
  }
49
59
  if (command === "dev") {
50
- const { devProject } = await import("../shared/eaxwrgby.js");
60
+ const { devProject } = await import("../shared/yjw02enp.js");
51
61
  stdout(await devProject(cwd));
52
62
  return;
53
63
  }
54
64
  if (command === "push") {
55
- const { pushProject } = await import("../shared/eqmtn1bj.js");
65
+ const { pushProject } = await import("../shared/kj680nxv.js");
56
66
  stdout(await pushProject(cwd, invoke(splitFlags(rest))));
57
67
  return;
58
68
  }
@@ -63,19 +73,62 @@ async function run(argv, io) {
63
73
  if (parsed.name === undefined) {
64
74
  throw new OkmError2("invalid", "okm migrate plan needs a name.");
65
75
  }
66
- 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);
67
81
  return;
68
82
  }
69
83
  if (sub === "apply") {
70
- const { applyProject } = await import("../shared/eqmtn1bj.js");
71
- stdout(await applyProject(cwd, invoke(parsed)));
84
+ const { applyProject } = await import("../shared/kj680nxv.js");
85
+ stdout(await applyProject(cwd, invoke(parsed), stdout));
72
86
  return;
73
87
  }
74
88
  if (sub === "status") {
75
- const { statusProject } = await import("../shared/d04j53v8.js");
89
+ const { statusProject } = await import("../shared/c5g81ngb.js");
76
90
  stdout(await statusProject(cwd, invoke(parsed)));
77
91
  return;
78
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;
110
+ }
111
+ if (command === "doctor") {
112
+ const parsed = splitFlags(rest);
113
+ const { doctorProject } = await import("../shared/84ptn35q.js");
114
+ stdout(await doctorProject(cwd, parsed.name, invoke(parsed)));
115
+ return;
116
+ }
117
+ if (command === "ext") {
118
+ const sub = rest[0];
119
+ if (sub === "test" || sub === "scaffold") {
120
+ throw new OkmError2("invalid", `okm ext ${sub} is not available yet.`, {
121
+ fix: { summary: "okm ext list and okm ext check are the commands in this version." }
122
+ });
123
+ }
124
+ if (sub === "list" || sub === "check") {
125
+ const { extProject } = await import("../shared/tny2t3sm.js");
126
+ stdout(await extProject(cwd, sub, invoke(splitFlags(rest.slice(1)))));
127
+ return;
128
+ }
129
+ throw new OkmError2("invalid", `Unknown command ext ${sub ?? ""}.`, {
130
+ fix: { summary: "Use okm ext list or okm ext check." }
131
+ });
79
132
  }
80
133
  throw new OkmError2("invalid", `Unknown command ${command ?? ""}.`);
81
134
  }
@@ -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";
@@ -37,11 +42,56 @@ export type ApplyRequest = {
37
42
  readonly migrations: readonly StoredMigration[];
38
43
  /** Runs after the advisory lock is held and before the first step. */
39
44
  readonly onLocked?: () => void;
45
+ /**
46
+ * Role that runs the migration.
47
+ *
48
+ * When it already exists and is not `current_user`, the runner issues one
49
+ * `SET ROLE` before any statement. That statement is not a plan step.
50
+ */
51
+ readonly migrationRole?: string;
52
+ /**
53
+ * Schema the migration role may create in.
54
+ *
55
+ * When this run creates that role, the runner grants `USAGE` and `CREATE`
56
+ * on this schema before `SET ROLE`. The default is `public`.
57
+ */
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
+ };
40
88
  };
41
89
  /** Migrations that ran at least one step in this invocation. */
42
90
  export type ApplyReport = {
43
91
  readonly target: string;
44
92
  readonly applied: readonly string[];
93
+ /** Set when the runner issued `SET ROLE` at session start. */
94
+ readonly setRole?: string;
45
95
  };
46
96
  /**
47
97
  * Groups pending steps into the transactions apply will run.
@@ -58,8 +108,9 @@ export declare function applyUnits(migration: StoredMigration, done: ReadonlySet
58
108
  * Applies pending migrations on one target.
59
109
  *
60
110
  * The connection is reserved for the whole run so the advisory lock stays
61
- * session-scoped. Lock timeout retries that step. Any other failure stops
62
- * 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.
63
114
  *
64
115
  * @param request - Target, flags, and the migrations to consider
65
116
  * @returns The target and the migration ids that ran
@@ -68,11 +119,15 @@ export declare function applyTarget(request: ApplyRequest): Promise<ApplyReport>
68
119
  /**
69
120
  * Applies the migrations in the project to the selected target.
70
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
+ *
71
125
  * @param cwd - Project directory
72
126
  * @param flags - `--target`, protection, pooler, and timeouts
127
+ * @param write - Receives each backfill progress line, including the newline
73
128
  * @returns Text for stdout, including the target name
74
129
  */
75
- 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>;
76
131
  /**
77
132
  * Prototype sync. Blocked on a protected target.
78
133
  *
@@ -84,3 +139,18 @@ export declare function applyProject(cwd: string, flags: InvokeFlags): Promise<s
84
139
  * @returns Text for stdout
85
140
  */
86
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,7 +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`, and `migrate plan`, `apply`, and `status`.
26
+ * `generate`, `dev`, `push`, `seed`, `ext list`, `ext check`, `doctor`, and
27
+ * `migrate plan`, `apply`, `status`, and `check`.
27
28
  *
28
29
  * @param argv - Arguments after the program name
29
30
  * @param io - Working directory and stdout. Defaults to the process
@@ -9,6 +9,7 @@ export type TargetInput = string | {
9
9
  /** Refuses contract, push, and other non-expand work unless `--allow-protected`. */
10
10
  readonly protected?: boolean;
11
11
  };
12
+ import type { RolesInput } from "../../dialects/pg/role/index.js";
12
13
  /** Paths and targets `okm` reads. */
13
14
  export type MigrateConfig = {
14
15
  /** Module that exports the built schema. */
@@ -35,6 +36,35 @@ export type MigrateConfig = {
35
36
  readonly lock?: number;
36
37
  readonly statement?: number;
37
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
+ };
61
+ /**
62
+ * Migration role and application role.
63
+ *
64
+ * A name is external unless it is also listed in `managed`. The plan creates
65
+ * a managed role and never drops one.
66
+ */
67
+ readonly roles?: RolesInput;
38
68
  };
39
69
  /**
40
70
  * Returns the config unchanged.