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
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `okm doctor`.
3
+ *
4
+ * A code prints the registry entry. With no code, the project is loaded and
5
+ * each table's triggers are listed. When `roles` is set, the command also
6
+ * connects and checks that those roles exist, that a managed role can be
7
+ * created, and that the application role can reach each managed object.
8
+ */
9
+ import type { CatalogObject } from "../../contracts/catalog/types.js";
10
+ import { type InvokeFlags } from "./policy.js";
11
+ /**
12
+ * Explains one code, or lists the triggers on each table.
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
+ *
17
+ * @param cwd - Project directory
18
+ * @param code - Spec code, when the caller passed one
19
+ * @returns Text for stdout, including the trailing newline
20
+ */
21
+ export declare function doctorProject(cwd: string, code: string | undefined, flags?: InvokeFlags): Promise<string>;
22
+ /**
23
+ * One line per table that has a trigger.
24
+ *
25
+ * @param objects - Catalog objects
26
+ * @returns The listing, or `no triggers` when none are present
27
+ */
28
+ export declare function formatTriggers(objects: readonly CatalogObject[]): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Live drift for `okm check`.
3
+ *
4
+ * A database that has never been pushed has no `okm_meta` and is left alone.
5
+ * After push, the introspected catalog is planned back to the schema. Each
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`, `okm_history`,
8
+ * and `okm_backfill` are omitted by the planner.
9
+ */
10
+ import { type Sql } from "postgres";
11
+ import type { Catalog } from "../../contracts/catalog/types.js";
12
+ import { type CatalogQuery, type IntrospectOptions } from "../../dialects/pg/introspect.js";
13
+ import type { RolesInput } from "../../dialects/pg/role/index.js";
14
+ /**
15
+ * Refuses when the connected database differs from the schema.
16
+ *
17
+ * @param url - Target URL
18
+ * @param author - Schema catalog, including roles when the config names them
19
+ * @param roles - `defineConfig({ roles })`, when set
20
+ */
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;
@@ -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;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `okm ext list` and `okm ext check`.
3
+ *
4
+ * Both talk to the selected target. List prints every extension the server
5
+ * can install, and the installed version when one is present. Check compares
6
+ * the schema's declarations with what is installed.
7
+ */
8
+ import { type InvokeFlags } from "./policy.js";
9
+ /**
10
+ * Runs `list` or `check` against the selected target.
11
+ *
12
+ * @param cwd - Project directory
13
+ * @param command - `list` or `check`
14
+ * @param flags - Target and protection flags
15
+ * @returns Text for stdout, including the trailing newline
16
+ */
17
+ export declare function extProject(cwd: string, command: "list" | "check", flags: InvokeFlags): Promise<string>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Extension alters and the apply preflight.
3
+ *
4
+ * Create and drop SQL come from the dialect. An upgrade is `ALTER EXTENSION
5
+ * … UPDATE`, marked `path-unverified` until apply checks
6
+ * `pg_extension_update_paths`. A move is `SET SCHEMA` and only when the
7
+ * declaration is relocatable.
8
+ */
9
+ import type { CatalogObject } from "../../contracts/catalog/types.js";
10
+ import type { DriverConnection } from "../../contracts/driver.js";
11
+ import { type StepKind } from "./classify.js";
12
+ /** One extension alter. The planner stores it as a plan step. */
13
+ export type ExtensionStep = {
14
+ readonly sql: string;
15
+ readonly kind: StepKind;
16
+ readonly class: "expand" | "contract";
17
+ readonly action: "ddl";
18
+ readonly lock: string;
19
+ readonly transactional: boolean;
20
+ readonly path?: "unverified";
21
+ };
22
+ /** Migrations the preflight reads. Only the statement text is used. */
23
+ type MigrationSql = {
24
+ readonly steps: readonly {
25
+ readonly sql: string;
26
+ }[];
27
+ };
28
+ /**
29
+ * Upgrade and move steps for extensions whose definition changed.
30
+ *
31
+ * Exact downgrades and moves of a non-relocatable extension are OKM1814.
32
+ * Dropping an extension that an object in the next catalog still uses is
33
+ * OKM1814. `DROP` is never `CASCADE`.
34
+ *
35
+ * @param beforeBy - Previous objects, keyed by identity
36
+ * @param afterBy - Next objects, keyed by identity
37
+ * @param dropKeys - Identities the plan drops
38
+ * @returns Alter steps. Creates and drops stay in the main planner
39
+ */
40
+ export declare function extensionAlterSteps(beforeBy: ReadonlyMap<string, CatalogObject>, afterBy: ReadonlyMap<string, CatalogObject>, dropKeys: ReadonlySet<string>): ExtensionStep[];
41
+ /**
42
+ * Refuses a migration that names an extension the server cannot install,
43
+ * or an upgrade with no path.
44
+ *
45
+ * The server is asked only when a step creates or upgrades an extension.
46
+ * The check runs before any statement.
47
+ *
48
+ * @param connection - The reserved apply connection
49
+ * @param migrations - Migrations in apply order
50
+ * @throws OkmError OKM1811 when the extension is not available, OKM1814 when the update path is missing
51
+ */
52
+ export declare function assertExtensionsAvailable(connection: DriverConnection, migrations: readonly MigrationSql[]): Promise<void>;
53
+ export {};
@@ -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;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tables OKModel owns: `okm_meta`, `okm_history`, and `okm_backfill`.
3
+ *
4
+ * Apply creates them. They are not part of the author schema. Plan, push,
5
+ * check, and doctor omit them on both sides, so a diff neither drops them
6
+ * nor creates them.
7
+ */
8
+ import type { Catalog } from "../../contracts/catalog/types.js";
9
+ /**
10
+ * Removes the migration history tables and the objects that belong to them.
11
+ *
12
+ * A catalog that has neither is returned as itself.
13
+ *
14
+ * @param source - Catalog about to be diffed or diagnosed
15
+ * @returns The catalog without OKModel's own tables
16
+ */
17
+ export declare function omitManagedObjects(source: Catalog): Catalog;
@@ -6,26 +6,81 @@
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;
59
+ /**
60
+ * Set on an extension upgrade the planner could not check.
61
+ *
62
+ * Apply reads `pg_extension_update_paths` before any statement and refuses
63
+ * a missing path. Offline `okm migrate plan` leaves this unverified.
64
+ */
65
+ readonly path?: "unverified";
66
+ /**
67
+ * Set on `CREATE OR REPLACE` of a function whose signature did not change.
68
+ *
69
+ * The statement is expand. The flag says the body or the settings changed.
70
+ */
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;
29
84
  };
30
85
  /** A named plan and the class of its strictest step. */
31
86
  export type MigrationPlan = {
@@ -42,6 +97,13 @@ export type PlanRequest = {
42
97
  readonly schema?: string;
43
98
  /** Concrete schema written into the SQL. Identities stay logical. */
44
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;
45
107
  };
46
108
  /**
47
109
  * Builds a plan from catalog `before` to catalog `after`.
@@ -49,14 +111,20 @@ export type PlanRequest = {
49
111
  * @param request - Catalogs, declared renames, and `--replace` flags
50
112
  * @returns Steps in apply order, with one class for the plan
51
113
  */
52
- export declare function planMigration(request: PlanRequest): MigrationPlan;
114
+ export declare function planMigration(input: PlanRequest): MigrationPlan;
53
115
  /**
54
- * 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.
55
122
  *
56
123
  * @param plan - Plan from {@link planMigration}
57
- * @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
58
126
  */
59
- export declare function formatPlan(plan: MigrationPlan): string;
127
+ export declare function formatPlan(plan: MigrationPlan, lockText?: (step: PlanStep) => string): string;
60
128
  /**
61
129
  * Reads a plan written by {@link formatPlan}.
62
130
  *
@@ -5,9 +5,14 @@
5
5
  * `.catalog`, which runs that validation. Generate writes SQL only.
6
6
  */
7
7
  import { type DeclaredRename } from "../../dialects/pg/declarations.js";
8
+ import type { BuiltSchema } from "../../dialects/pg/schema.js";
9
+ import type { AnyTable } from "../../dialects/pg/table.js";
8
10
  import type { Catalog } from "../../contracts/catalog/types.js";
9
11
  import { type MigrateConfig } from "./config.js";
12
+ import { type InvokeFlags } from "./policy.js";
10
13
  import { type MigrationPlan } from "./plan.js";
14
+ import { type Finding } from "./lint.js";
15
+ type Built = BuiltSchema<readonly AnyTable[]>;
11
16
  /**
12
17
  * Validates the schema and writes `.okm/`.
13
18
  *
@@ -23,24 +28,47 @@ export declare function buildProject(cwd: string): Promise<string>;
23
28
  * @param cwd - Project directory
24
29
  * @param name - Migration name
25
30
  * @param flags - `--replace` values
26
- * @returns The SQL path, or `undefined` when nothing changed
31
+ * @returns The SQL path and lint findings, or `undefined` when nothing changed
27
32
  */
28
- 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>;
29
37
  /**
30
38
  * Plans and returns the text. Nothing is written.
31
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
+ *
32
45
  * @param cwd - Project directory
33
46
  * @param name - Plan name
34
47
  * @param flags - `--replace` values
35
- * @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
36
50
  */
37
- 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
+ }>;
38
56
  /**
39
57
  * Validates the schema, then reports stale renames and unlisted table files.
40
58
  *
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
62
+ * OKM1520. Several targets and no `--target` skip that comparison.
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
+ *
41
67
  * @param cwd - Project directory
68
+ * @param flags - `--target`, when the caller passed one
69
+ * @returns Warning findings. Errors throw
42
70
  */
43
- export declare function checkProject(cwd: string): Promise<void>;
71
+ export declare function checkProject(cwd: string, flags?: InvokeFlags): Promise<readonly Finding[]>;
44
72
  /**
45
73
  * Table files whose names do not appear in the schema module.
46
74
  *
@@ -60,6 +88,28 @@ export declare function projectHead(cwd: string): Promise<{
60
88
  readonly catalog: Catalog;
61
89
  readonly renames: readonly DeclaredRename[];
62
90
  }>;
91
+ /**
92
+ * Loads the config, the schema, and the previous catalog snapshot.
93
+ *
94
+ * A `renamedFrom` that the previous catalog does not contain is OKM1020.
95
+ *
96
+ * @param cwd - Project directory
97
+ * @returns The loaded project
98
+ */
99
+ export declare function openProject(cwd: string): Promise<{
100
+ readonly config: MigrateConfig;
101
+ readonly built: Built;
102
+ readonly previous: Catalog;
103
+ readonly renames: readonly DeclaredRename[];
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>;
63
113
  /**
64
114
  * Loads `okmodel.config.ts`.
65
115
  *
@@ -67,3 +117,12 @@ export declare function projectHead(cwd: string): Promise<{
67
117
  * @returns The config object
68
118
  */
69
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>;
128
+ export {};