okmodel 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +182 -22
  2. package/dist/contracts/catalog/document.d.ts +6 -2
  3. package/dist/dialects/pg/ext/citext.js +1 -1
  4. package/dist/dialects/pg/ext/index.js +1 -1
  5. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  6. package/dist/dialects/pg/index.js +3 -3
  7. package/dist/dialects/pg/role/sql.d.ts +11 -4
  8. package/dist/dialects/pg/table.d.ts +17 -1
  9. package/dist/dialects/pg/view/index.js +1 -1
  10. package/dist/dialects/pg/view/scratch.d.ts +3 -2
  11. package/dist/runtime/catalog-cache.d.ts +17 -0
  12. package/dist/runtime/compat-words.d.ts +23 -0
  13. package/dist/runtime/drift-detail.d.ts +20 -4
  14. package/dist/runtime/drift.d.ts +4 -2
  15. package/dist/runtime/pg/bun.js +2 -2
  16. package/dist/runtime/pg/pg.js +2 -2
  17. package/dist/runtime/pg/pglite.js +2 -2
  18. package/dist/runtime/pg/postgresjs.js +2 -2
  19. package/dist/runtime/traits/index.js +5 -4
  20. package/dist/runtime/types.d.ts +2 -0
  21. package/dist/runtime/validate/index.js +2 -2
  22. package/dist/shared/2321avmn.js +418 -0
  23. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  24. package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
  25. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  26. package/dist/shared/5mtf6cj3.js +94 -0
  27. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  28. package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
  29. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  30. package/dist/shared/7nk68fj8.js +751 -0
  31. package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
  32. package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
  33. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  34. package/dist/shared/9yn6a2vd.js +11 -0
  35. package/dist/shared/ady1ms74.js +72 -0
  36. package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
  37. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  38. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  39. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  40. package/dist/shared/hqjsxnz7.js +13 -0
  41. package/dist/shared/htnaj77v.js +17 -0
  42. package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
  43. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  44. package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
  45. package/dist/shared/kj680nxv.js +19 -0
  46. package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
  47. package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
  48. package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
  49. package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
  50. package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
  51. package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
  52. package/dist/shared/s4mb6a4b.js +545 -0
  53. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  54. package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
  55. package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
  56. package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
  57. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  58. package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
  59. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  60. package/dist/shared/ygem90rn.js +900 -0
  61. package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
  62. package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
  63. package/dist/tooling/cli.js +46 -17
  64. package/dist/tooling/migrate/apply.d.ts +59 -5
  65. package/dist/tooling/migrate/backfill.d.ts +136 -0
  66. package/dist/tooling/migrate/check.d.ts +65 -0
  67. package/dist/tooling/migrate/classify.d.ts +108 -0
  68. package/dist/tooling/migrate/commands.d.ts +2 -2
  69. package/dist/tooling/migrate/config.d.ts +22 -0
  70. package/dist/tooling/migrate/doctor.d.ts +3 -0
  71. package/dist/tooling/migrate/drift.d.ts +18 -2
  72. package/dist/tooling/migrate/estimate.d.ts +50 -0
  73. package/dist/tooling/migrate/extensions.d.ts +2 -0
  74. package/dist/tooling/migrate/files.d.ts +12 -0
  75. package/dist/tooling/migrate/index.d.ts +3 -2
  76. package/dist/tooling/migrate/index.js +67 -1
  77. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  78. package/dist/tooling/migrate/lint.d.ts +71 -0
  79. package/dist/tooling/migrate/managed.d.ts +1 -1
  80. package/dist/tooling/migrate/plan.d.ts +64 -9
  81. package/dist/tooling/migrate/project.d.ts +42 -7
  82. package/dist/tooling/migrate/provision.d.ts +18 -0
  83. package/dist/tooling/migrate/reference.d.ts +40 -0
  84. package/dist/tooling/migrate/runner.d.ts +108 -0
  85. package/dist/tooling/migrate/safe.d.ts +77 -0
  86. package/dist/tooling/migrate/seed.d.ts +20 -0
  87. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  88. package/dist/tooling/migrate/status.d.ts +25 -6
  89. package/dist/tooling/testing/factories.d.ts +117 -0
  90. package/dist/tooling/testing/facts.d.ts +72 -0
  91. package/dist/tooling/testing/index.d.ts +89 -0
  92. package/dist/tooling/testing/index.js +8 -0
  93. package/dist/tooling/testing/isolation.d.ts +30 -0
  94. package/dist/tooling/testing/random.d.ts +40 -0
  95. package/dist/tooling/testing/record.d.ts +38 -0
  96. package/package.json +6 -2
  97. package/dist/shared/dbjej1tw.js +0 -456
  98. package/dist/shared/g4f5kcx9.js +0 -37
  99. package/dist/shared/qbxzvwwc.js +0 -88
  100. package/dist/shared/zwmqxb0g.js +0 -79
  101. package/dist/shared/{xva8v7qk.js → awrsqrn7.js} +3 -3
  102. package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  103. package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
  104. /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Seeded generators for factories.
3
+ *
4
+ * Mulberry32, a fixed word list, and a counter. No runtime dependency.
5
+ * The same seed replays the same sequence (D199).
6
+ */
7
+ /** A seeded sequence and the values factories ask for. */
8
+ export type Rng = {
9
+ /** Next unit interval, `[0, 1)`. */
10
+ next(): number;
11
+ /** Integer in `[0, bound)`. */
12
+ int(bound: number): number;
13
+ /** A version-4 UUID. The bits come from this sequence. */
14
+ uuid(): string;
15
+ /** `local@example.com`, unique for this sequence. */
16
+ email(): string;
17
+ /** A first and last name from the fixed lists. */
18
+ name(): string;
19
+ /** `count` words from the fixed list. */
20
+ words(count: number): string;
21
+ /** A boolean. */
22
+ boolean(): boolean;
23
+ /** A number in `[0, 1)`. */
24
+ float(): number;
25
+ /** An ISO date `YYYY-MM-DD` in 2020. */
26
+ date(): string;
27
+ /** One value from `list`. */
28
+ pick<T>(list: readonly T[]): T;
29
+ /** The next counter value, starting at 1. Text defaults append it. */
30
+ counter(): number;
31
+ };
32
+ /**
33
+ * Builds the sequence for `seed`.
34
+ *
35
+ * Seed `1` is the default. `0` is shifted so the first step is not a fixed point.
36
+ *
37
+ * @param seed - Integer seed
38
+ * @returns The sequence
39
+ */
40
+ export declare function rng(seed: number): Rng;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A pool that records every statement sent to the driver.
3
+ *
4
+ * `expectQueries` counts this log. Transaction control is left out (D199).
5
+ * The runtime logger records errors only, and `hookm` records notices and
6
+ * transaction phases, not statement text, so the count wraps the pool.
7
+ */
8
+ import type { DriverPool, WireValue } from "../../contracts/driver.js";
9
+ /** One statement that reached the driver. */
10
+ export type Recorded = {
11
+ readonly text: string;
12
+ readonly params: readonly WireValue[];
13
+ };
14
+ /** A recording pool and its log. */
15
+ export type Recording = {
16
+ readonly pool: DriverPool;
17
+ /** Every statement since the pool was wrapped, in the order sent. */
18
+ readonly log: Recorded[];
19
+ };
20
+ /**
21
+ * Reports whether `text` only frames a transaction.
22
+ *
23
+ * Not counted: `BEGIN`, `START TRANSACTION`, `COMMIT`, `END`, `ROLLBACK`,
24
+ * `ROLLBACK TO`, `SAVEPOINT`, `RELEASE SAVEPOINT`, `SET TRANSACTION`,
25
+ * `SET LOCAL`, and `SET SESSION CHARACTERISTICS`. A data statement is counted,
26
+ * including one that runs inside a transaction.
27
+ *
28
+ * @param text - Statement text
29
+ * @returns Whether the statement is transaction control
30
+ */
31
+ export declare function isTransactionControl(text: string): boolean;
32
+ /**
33
+ * Wraps a pool so each statement is logged.
34
+ *
35
+ * @param pool - The real pool
36
+ * @returns The wrapped pool and its log
37
+ */
38
+ export declare function record(pool: DriverPool): Recording;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okmodel",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "okmodel is a catalog-first TypeScript ORM, PostgreSQL first.",
5
5
  "keywords": [
6
6
  "migrations",
@@ -82,6 +82,10 @@
82
82
  "types": "./dist/runtime/tenancy/index.d.ts",
83
83
  "import": "./dist/runtime/tenancy/index.js"
84
84
  },
85
+ "./testing": {
86
+ "types": "./dist/tooling/testing/index.d.ts",
87
+ "import": "./dist/tooling/testing/index.js"
88
+ },
85
89
  "./validate": {
86
90
  "types": "./dist/runtime/validate/index.d.ts",
87
91
  "import": "./dist/runtime/validate/index.js"
@@ -108,7 +112,7 @@
108
112
  }
109
113
  },
110
114
  "scripts": {
111
- "build": "rm -rf dist && bun build ./src/contracts/index.ts ./src/contracts/internal.ts ./src/dialects/pg/index.ts ./src/runtime/pg/postgresjs.ts ./src/runtime/pg/pglite.ts ./src/runtime/pg/pg.ts ./src/runtime/pg/bun.ts ./src/runtime/ids/index.ts ./src/runtime/safety/index.ts ./src/runtime/traits/index.ts ./src/runtime/tenancy/index.ts ./src/runtime/validate/index.ts ./src/dialects/pg/ext/index.ts ./src/dialects/pg/fn/index.ts ./src/dialects/pg/view/index.ts ./src/dialects/pg/ext/citext.ts ./src/dialects/pg/ext/pg-trgm.ts ./src/tooling/migrate/index.ts ./src/tooling/cli.ts --outdir ./dist --root ./src --format esm --target node --splitting --chunk-naming=shared/[hash].js --external postgres --external @electric-sql/pglite --external pg --external bun && bunx tsc -p tsconfig.build.json && bun ./scripts/write-bin-wrappers.ts",
115
+ "build": "rm -rf dist && bun build ./src/contracts/index.ts ./src/contracts/internal.ts ./src/dialects/pg/index.ts ./src/runtime/pg/postgresjs.ts ./src/runtime/pg/pglite.ts ./src/runtime/pg/pg.ts ./src/runtime/pg/bun.ts ./src/runtime/ids/index.ts ./src/runtime/safety/index.ts ./src/runtime/traits/index.ts ./src/runtime/tenancy/index.ts ./src/runtime/validate/index.ts ./src/dialects/pg/ext/index.ts ./src/dialects/pg/fn/index.ts ./src/dialects/pg/view/index.ts ./src/dialects/pg/ext/citext.ts ./src/dialects/pg/ext/pg-trgm.ts ./src/tooling/migrate/index.ts ./src/tooling/testing/index.ts ./src/tooling/cli.ts --outdir ./dist --root ./src --format esm --target node --splitting --chunk-naming=shared/[hash].js --external postgres --external @electric-sql/pglite --external pg --external bun && bunx tsc -p tsconfig.build.json && bun ./scripts/write-bin-wrappers.ts",
112
116
  "driver-bench": "bun ./scripts/driver-bench.ts",
113
117
  "typecheck": "bunx tsc --noEmit -p tsconfig.json",
114
118
  "editor-check": "bun ./scripts/editor-check.ts",
@@ -1,456 +0,0 @@
1
- import {
2
- open2
3
- } from "./xw1d69vd.js";
4
- import {
5
- catalogHash2
6
- } from "./t616ynfn.js";
7
- import"./n442h54c.js";
8
- import {
9
- loadMigrations
10
- } from "./g4f5kcx9.js";
11
- import {
12
- assertCreateRole,
13
- currentUser,
14
- roleExists
15
- } from "./hcmdtwgh.js";
16
- import {
17
- assertTargetPolicy,
18
- assertDirectConnection,
19
- selectTarget,
20
- projectHead,
21
- loadConfig
22
- } from "./5nmdenb6.js";
23
- import {
24
- createdRoleName,
25
- changesRole,
26
- assertExtensionsAvailable,
27
- planMigration
28
- } from "./2ssqrv66.js";
29
- import {
30
- quoteIdent
31
- } from "./wekze34d.js";
32
- import {
33
- OkmError2
34
- } from "./52szzakm.js";
35
-
36
- // src/tooling/migrate/apply.ts
37
- import { join } from "node:path";
38
-
39
- // src/tooling/migrate/engine.ts
40
- var IDENT = String.raw`"(?:[^"]|"")+"|[A-Za-z_][\w$]*`;
41
- var DEFAULT_UUIDV7 = /\bdefault\s+uuidv7\s*\(\s*\)/i;
42
- var TABLE = new RegExp(String.raw`^\s*(?:create\s+table(?:\s+if\s+not\s+exists)?|alter\s+table(?:\s+if\s+exists)?(?:\s+only)?)\s+(?:(?:${IDENT})\.)?(${IDENT})`, "i");
43
- var COLUMN = new RegExp(String.raw`(${IDENT})\s+(?:uuid\b|set\s+default\b)`, "i");
44
- function findUuidV7Default(migrations) {
45
- for (const migration of migrations) {
46
- for (let stepIndex = 0;stepIndex < migration.steps.length; stepIndex += 1) {
47
- const sql = migration.steps[stepIndex]?.sql ?? "";
48
- if (!DEFAULT_UUIDV7.test(sql))
49
- continue;
50
- return { migrationId: migration.id, stepIndex, column: columnOf(sql) };
51
- }
52
- }
53
- return;
54
- }
55
- async function assertUuidV7Available(connection, migrations) {
56
- const found = findUuidV7Default(migrations);
57
- if (found === undefined)
58
- return;
59
- const result = await connection.execute("select current_setting('server_version_num'), to_regprocedure('uuidv7()')::text");
60
- const row = result.rows[0];
61
- const major = Math.floor(Number(row?.[0] ?? "0") / 1e4);
62
- const defined = row?.[1] !== null && row?.[1] !== undefined;
63
- if (major >= 18 || defined)
64
- return;
65
- const where = found.column === undefined ? "A column" : `Column ${found.column}`;
66
- throw new OkmError2("OKM1812", `${where} has a uuidv7() default (migration ${found.migrationId}, step ${String(found.stepIndex)}), and the server is PostgreSQL ${String(major)}. uuidv7() arrives in PostgreSQL 18. Nothing was changed.`, {
67
- fix: {
68
- summary: 'Either run PostgreSQL 18 and declare schema({ requires: { postgres: ">=18" } }), or use t.id({ default: "uuidv4" }) (or schema({ defaults: { id: "uuidv4" } })), which uses gen_random_uuid() and works on PostgreSQL 13 and newer. Then delete the migration that was not applied and generate it again.'
69
- }
70
- });
71
- }
72
- function columnOf(sql) {
73
- const table = TABLE.exec(sql)?.[1];
74
- if (table === undefined)
75
- return;
76
- const line = sql.split(`
77
- `).find((text) => DEFAULT_UUIDV7.test(text));
78
- const column = line === undefined ? undefined : COLUMN.exec(line)?.[1];
79
- if (column === undefined)
80
- return;
81
- return `${unquote(table)}.${unquote(column)}`;
82
- }
83
- function unquote(name) {
84
- return name.startsWith('"') ? name.slice(1, -1).replaceAll('""', '"') : name;
85
- }
86
-
87
- // src/tooling/migrate/apply.ts
88
- var DEFAULT_LOCK_MS = 5000;
89
- var DEFAULT_STATEMENT_MS = 30000;
90
- var DEFAULT_RETRIES = 3;
91
- var BACKOFF_MS = 50;
92
- var META = `create table if not exists okm_meta (
93
- id text primary key,
94
- catalog_hash text not null,
95
- migration_id text not null
96
- )`;
97
- var HISTORY = `create table if not exists okm_history (
98
- migration_id text not null,
99
- step_index integer not null,
100
- class text not null,
101
- catalog_hash text not null,
102
- applied_at timestamptz not null default now(),
103
- primary key (migration_id, step_index)
104
- )`;
105
- function applyUnits(migration, done) {
106
- const pending = [];
107
- for (let index = 0;index < migration.steps.length; index += 1) {
108
- if (done.has(`${migration.id}:${String(index)}`))
109
- continue;
110
- const step = migration.steps[index];
111
- if (step !== undefined)
112
- pending.push({ index, step });
113
- }
114
- const units = [];
115
- let batch = [];
116
- const flush = () => {
117
- if (batch.length === 0)
118
- return;
119
- const last = batch.at(-1);
120
- units.push({
121
- migrationId: migration.id,
122
- catalogHash: migration.catalogHash,
123
- finishes: last !== undefined && last.index === migration.steps.length - 1,
124
- transactional: true,
125
- steps: batch
126
- });
127
- batch = [];
128
- };
129
- for (const item of pending) {
130
- if (item.step.transactional && !outsideTransaction(item.step.sql)) {
131
- batch.push(item);
132
- continue;
133
- }
134
- flush();
135
- units.push({
136
- migrationId: migration.id,
137
- catalogHash: migration.catalogHash,
138
- finishes: item.index === migration.steps.length - 1,
139
- transactional: false,
140
- steps: [item]
141
- });
142
- }
143
- flush();
144
- return units;
145
- }
146
- async function applyTarget(request) {
147
- assertDirectConnection(request.url, request.allowPooler === true);
148
- const pool = open2({
149
- url: request.url,
150
- max: 1,
151
- ...request.searchPath !== undefined ? { searchPath: request.searchPath } : {}
152
- });
153
- if (pool.reserve === undefined) {
154
- await pool.close();
155
- throw new OkmError2("invalid", "The driver cannot reserve a connection.", {
156
- fix: { summary: "Apply needs a driver with interactive transactions." }
157
- });
158
- }
159
- const connection = await pool.reserve();
160
- let locked = false;
161
- try {
162
- await setTimeouts(connection, request);
163
- await lockTarget(connection, request.target);
164
- locked = true;
165
- request.onLocked?.();
166
- const session = {};
167
- await prepareMigrationRole(connection, request, session);
168
- await assertExtensionsAvailable(connection, request.migrations);
169
- await assertUuidV7Available(connection, request.migrations);
170
- await connection.execute(META);
171
- await connection.execute(HISTORY);
172
- const done = await readDone(connection);
173
- const applied = [];
174
- for (const migration of request.migrations) {
175
- const units = applyUnits(migration, done);
176
- if (units.length === 0)
177
- continue;
178
- for (const unit of units) {
179
- assertUnitPolicy(request, unit);
180
- await runUnit(connection, unit, request, session);
181
- for (const item of unit.steps)
182
- done.add(`${unit.migrationId}:${String(item.index)}`);
183
- }
184
- applied.push(migration.id);
185
- }
186
- return {
187
- target: request.target,
188
- applied,
189
- ...session.setRole !== undefined ? { setRole: session.setRole } : {}
190
- };
191
- } finally {
192
- if (locked)
193
- await connection.execute("select pg_advisory_unlock(hashtext($1))", [
194
- lockKey(request.target)
195
- ]);
196
- await connection.release();
197
- await pool.close();
198
- }
199
- }
200
- async function applyProject(cwd, flags) {
201
- const config = await loadConfig(cwd);
202
- const target = selectTarget(config, flags.target);
203
- const migrations = loadMigrations(joinMigrations(cwd, config.migrations));
204
- if (migrations.length === 0)
205
- return `target ${target.name}
206
- nothing to apply
207
- `;
208
- const report = await applyTarget({
209
- url: target.url,
210
- target: target.name,
211
- protected: target.protected,
212
- allowProtected: flags.allowProtected,
213
- allowPooler: flags.allowPooler || config.allowPooler === true,
214
- ...flags.lockTimeoutMs !== undefined ? { lockTimeoutMs: flags.lockTimeoutMs } : config.timeouts?.lock !== undefined ? { lockTimeoutMs: config.timeouts.lock } : {},
215
- ...flags.statementTimeoutMs !== undefined ? { statementTimeoutMs: flags.statementTimeoutMs } : config.timeouts?.statement !== undefined ? { statementTimeoutMs: config.timeouts.statement } : {},
216
- migrations,
217
- ...config.roles !== undefined ? { migrationRole: config.roles.migration } : {}
218
- });
219
- return formatReport(report);
220
- }
221
- async function pushProject(cwd, flags) {
222
- const config = await loadConfig(cwd);
223
- const target = selectTarget(config, flags.target);
224
- assertTargetPolicy(target, "push", flags.allowProtected);
225
- const head = await projectHead(cwd);
226
- const plan = planMigration({
227
- before: head.previous,
228
- after: head.catalog,
229
- renames: head.renames,
230
- name: "push"
231
- });
232
- if (plan.steps.length === 0)
233
- return `target ${target.name}
234
- no changes
235
- `;
236
- const report = await applyTarget({
237
- url: target.url,
238
- target: target.name,
239
- protected: target.protected,
240
- allowProtected: flags.allowProtected,
241
- allowPooler: flags.allowPooler || config.allowPooler === true,
242
- migrations: [
243
- {
244
- id: "push",
245
- catalogHash: catalogHash2(head.catalog),
246
- steps: plan.steps
247
- }
248
- ],
249
- ...config.roles !== undefined ? { migrationRole: config.roles.migration } : {}
250
- });
251
- return formatReport(report);
252
- }
253
- function formatReport(report) {
254
- const role = report.setRole !== undefined ? `set role ${report.setRole}
255
- ` : "";
256
- if (report.applied.length === 0)
257
- return `target ${report.target}
258
- ${role}nothing to apply
259
- `;
260
- return `target ${report.target}
261
- ${role}${report.applied.map((id) => `applied ${id}`).join(`
262
- `)}
263
- `;
264
- }
265
- function joinMigrations(cwd, migrations) {
266
- return join(cwd, migrations ?? "migrations");
267
- }
268
- function assertUnitPolicy(request, unit) {
269
- let operation = "expand";
270
- for (const item of unit.steps) {
271
- if (item.step.action === "backfill") {
272
- operation = "backfill";
273
- break;
274
- }
275
- if (item.step.class === "contract" || item.step.class === "unclassified")
276
- operation = item.step.class;
277
- }
278
- assertTargetPolicy({ name: request.target, protected: request.protected }, operation, request.allowProtected === true);
279
- }
280
- async function setTimeouts(connection, request) {
281
- const lock = request.lockTimeoutMs ?? DEFAULT_LOCK_MS;
282
- const statement = request.statementTimeoutMs ?? DEFAULT_STATEMENT_MS;
283
- await connection.execute("select set_config('lock_timeout', $1, false)", [String(lock)]);
284
- await connection.execute("select set_config('statement_timeout', $1, false)", [
285
- String(statement)
286
- ]);
287
- }
288
- async function lockTarget(connection, target) {
289
- const result = await connection.execute("select pg_try_advisory_lock(hashtext($1))", [
290
- lockKey(target)
291
- ]);
292
- if (!wireTrue(result.rows[0]?.[0])) {
293
- throw new OkmError2("OKM1522", `Another apply holds the lock for ${target}.`, {
294
- kind: "conflict",
295
- fix: {
296
- summary: "Wait for that apply to finish. Do not start a second apply against the same target."
297
- }
298
- });
299
- }
300
- }
301
- function lockKey(target) {
302
- return `okm:${target}`;
303
- }
304
- async function readDone(connection) {
305
- const result = await connection.execute("select migration_id, step_index::text from okm_history");
306
- const done = new Set;
307
- for (const row of result.rows) {
308
- const id = row[0];
309
- const step = row[1];
310
- if (id !== null && step !== null)
311
- done.add(`${id}:${step}`);
312
- }
313
- return done;
314
- }
315
- async function prepareMigrationRole(connection, request, session) {
316
- const role = request.migrationRole;
317
- const changes = request.migrations.some((migration) => migration.steps.some((step) => changesRole(step.sql)));
318
- if (role === undefined) {
319
- if (changes)
320
- await assertCreateRole(connection, await currentUser(connection));
321
- return;
322
- }
323
- const current = await currentUser(connection);
324
- const exists = await roleExists(connection, role);
325
- if (changes)
326
- await assertCreateRole(connection, exists && current !== role ? role : current);
327
- if (exists && current !== role)
328
- await assumeRole(connection, role, session);
329
- }
330
- async function grantSchema(connection, schema, role) {
331
- await connection.execute(`grant usage, create on schema ${quoteIdent(schema)} to ${quoteIdent(role)}`);
332
- }
333
- async function assumeRole(connection, role, session) {
334
- if (session.setRole === role)
335
- return;
336
- await connection.execute(`set role ${quoteIdent(role)}`);
337
- session.setRole = role;
338
- }
339
- async function runUnit(connection, unit, request, session) {
340
- const retries = request.retries ?? DEFAULT_RETRIES;
341
- for (let attempt = 0;attempt <= retries; attempt += 1) {
342
- let began = false;
343
- let current = unit.steps[0]?.index ?? 0;
344
- try {
345
- if (unit.transactional) {
346
- await connection.execute("begin");
347
- began = true;
348
- }
349
- for (const item of unit.steps) {
350
- current = item.index;
351
- await rebuildInvalidIndex(connection, item.step.sql);
352
- const created = createdRoleName(item.step.sql);
353
- const existed = created !== undefined && await roleExists(connection, created);
354
- if (created === undefined || !existed)
355
- await connection.execute(item.step.sql);
356
- if (created !== undefined && created === request.migrationRole) {
357
- if (!existed) {
358
- await grantSchema(connection, request.schema ?? "public", created);
359
- await connection.execute(`grant select, insert, update on okm_meta, okm_history to ${quoteIdent(created)}`);
360
- }
361
- await assumeRole(connection, created, session);
362
- }
363
- if (!unit.transactional) {
364
- await connection.execute("begin");
365
- began = true;
366
- await writeStep(connection, unit, item);
367
- if (unit.finishes)
368
- await writeMeta(connection, unit);
369
- await connection.execute("commit");
370
- began = false;
371
- }
372
- }
373
- if (unit.transactional) {
374
- for (const item of unit.steps)
375
- await writeStep(connection, unit, item);
376
- if (unit.finishes)
377
- await writeMeta(connection, unit);
378
- await connection.execute("commit");
379
- began = false;
380
- }
381
- return;
382
- } catch (error) {
383
- if (began)
384
- await connection.execute("rollback").catch(() => {
385
- return;
386
- });
387
- if (!isLockTimeout(error) || attempt === retries)
388
- throw failStep(unit, current, error);
389
- await delay(BACKOFF_MS * 2 ** attempt);
390
- }
391
- }
392
- }
393
- async function writeStep(connection, unit, item) {
394
- await connection.execute("insert into okm_history (migration_id, step_index, class, catalog_hash) values ($1, $2, $3, $4)", [unit.migrationId, String(item.index), item.step.class, unit.catalogHash]);
395
- }
396
- async function writeMeta(connection, unit) {
397
- await connection.execute(`insert into okm_meta (id, catalog_hash, migration_id) values ('head', $1, $2)
398
- on conflict (id) do update set catalog_hash = excluded.catalog_hash, migration_id = excluded.migration_id`, [unit.catalogHash, unit.migrationId]);
399
- }
400
- async function rebuildInvalidIndex(connection, sql) {
401
- const name = concurrentIndexName(sql);
402
- if (name === undefined)
403
- return;
404
- const found = await connection.execute(`select i.indisvalid
405
- from pg_class c
406
- join pg_namespace n on n.oid = c.relnamespace
407
- join pg_index i on i.indexrelid = c.oid
408
- where c.relname = $1 and n.nspname = current_schema()`, [name]);
409
- const valid = found.rows[0]?.[0];
410
- if (valid === undefined || wireTrue(valid))
411
- return;
412
- await connection.execute(`drop index concurrently if exists ${quoteIdent(name)}`);
413
- }
414
- function concurrentIndexName(sql) {
415
- const match = /^create\s+(?:unique\s+)?index\s+concurrently\s+(?:if\s+not\s+exists\s+)?(?:"([^"]+)"|([A-Za-z_][\w$]*))/i.exec(sql.trim());
416
- return match?.[1] ?? match?.[2];
417
- }
418
- function outsideTransaction(sql) {
419
- const text = sql.trim().toLowerCase();
420
- if (text.startsWith("vacuum"))
421
- return true;
422
- if (/^alter\s+type\b/.test(text) && /\badd\s+value\b/.test(text))
423
- return true;
424
- if (/^create\s+(?:unique\s+)?index\s+concurrently\b/.test(text))
425
- return true;
426
- return false;
427
- }
428
- function failStep(unit, index, error) {
429
- if (error instanceof OkmError2)
430
- return error;
431
- const message = error instanceof Error ? error.message : "The step failed.";
432
- return new OkmError2("invalid", `Migration ${unit.migrationId} failed at step ${String(index)}: ${message}`, {
433
- cause: error,
434
- fix: { summary: "Fix the step and run apply again. Finished steps are not repeated." }
435
- });
436
- }
437
- function isLockTimeout(error) {
438
- if (typeof error !== "object" || error === null)
439
- return false;
440
- if ("sqlstate" in error && error.sqlstate === "55P03")
441
- return true;
442
- const message = error instanceof Error ? error.message : "";
443
- return message.includes("lock timeout");
444
- }
445
- function wireTrue(value) {
446
- return value === "t" || value === "true";
447
- }
448
- function delay(ms) {
449
- return new Promise((resolve) => {
450
- setTimeout(resolve, ms);
451
- });
452
- }
453
- export {
454
- applyProject,
455
- pushProject
456
- };
@@ -1,37 +0,0 @@
1
- import {
2
- catalogHash2,
3
- parseCatalog2
4
- } from "./t616ynfn.js";
5
- import {
6
- parsePlan
7
- } from "./2ssqrv66.js";
8
- import {
9
- OkmError2
10
- } from "./52szzakm.js";
11
-
12
- // src/tooling/migrate/files.ts
13
- import { existsSync, readdirSync, readFileSync } from "node:fs";
14
- import { join } from "node:path";
15
- function loadMigrations(directory) {
16
- if (!existsSync(directory))
17
- return [];
18
- const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
19
- const migrations = [];
20
- for (const file of names) {
21
- const id = file.slice(0, -".sql".length);
22
- const catalogPath = join(directory, `${id}.catalog.json`);
23
- if (!existsSync(catalogPath)) {
24
- throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
25
- fix: {
26
- summary: "Generate the migration again so the SQL and the catalog are written together."
27
- }
28
- });
29
- }
30
- const plan = parsePlan(readFileSync(join(directory, file), "utf8"));
31
- const hash = catalogHash2(parseCatalog2(readFileSync(catalogPath, "utf8")));
32
- migrations.push({ id, catalogHash: hash, steps: plan.steps });
33
- }
34
- return migrations;
35
- }
36
-
37
- export { loadMigrations };
@@ -1,88 +0,0 @@
1
- import {
2
- assertNamespace2,
3
- normaliseEdges,
4
- assertProvenance
5
- } from "./t4tbsy9h.js";
6
- import {
7
- assertIdentifier2,
8
- assertStoredText
9
- } from "./nfzc5z0w.js";
10
- import {
11
- catalogError2
12
- } from "./52szzakm.js";
13
-
14
- // src/contracts/catalog/enum.ts
15
- function enumType(input) {
16
- assertNamespace2(input.namespace);
17
- assertIdentifier2(input.name, "type");
18
- assertProvenance(input.provenance);
19
- if (input.labels.length === 0) {
20
- catalogError2("OKM1020", `Enum ${input.name} must list at least one label.`);
21
- }
22
- const labels = [];
23
- const seen = new Set;
24
- for (const label of input.labels) {
25
- assertStoredText(label, "enum label");
26
- if (label.length === 0) {
27
- catalogError2("OKM1020", `Enum ${input.name} has an empty label.`);
28
- }
29
- if (seen.has(label)) {
30
- catalogError2("OKM1020", `Enum ${input.name} repeats label ${label}.`);
31
- }
32
- seen.add(label);
33
- labels.push(label);
34
- }
35
- const identity = {
36
- kind: "type",
37
- namespace: input.namespace,
38
- name: input.name
39
- };
40
- return {
41
- kind: "type",
42
- identity,
43
- owner: input.owner ?? "managed",
44
- definition: { labels },
45
- dependencies: normaliseEdges(input.dependencies ?? []),
46
- provenance: input.provenance
47
- };
48
- }
49
- function domainType(input) {
50
- assertNamespace2(input.namespace);
51
- assertIdentifier2(input.name, "type");
52
- assertProvenance(input.provenance);
53
- if (input.base.length === 0) {
54
- catalogError2("OKM1020", `Domain ${input.name} needs a base type.`);
55
- }
56
- assertStoredText(input.check, "domain check");
57
- if (input.check.length === 0) {
58
- catalogError2("OKM1020", `Domain ${input.name} needs a check.`);
59
- }
60
- const identity = {
61
- kind: "type",
62
- namespace: input.namespace,
63
- name: input.name
64
- };
65
- const definition = { base: input.base, check: input.check };
66
- return {
67
- kind: "type",
68
- identity,
69
- owner: input.owner ?? "managed",
70
- definition,
71
- dependencies: normaliseEdges(input.dependencies ?? []),
72
- provenance: input.provenance
73
- };
74
- }
75
- function isDomain(definition) {
76
- return "base" in definition;
77
- }
78
- function sameEnumLabels(left, right) {
79
- if (left.length !== right.length)
80
- return false;
81
- for (let index = 0;index < left.length; index += 1) {
82
- if (left[index] !== right[index])
83
- return false;
84
- }
85
- return true;
86
- }
87
-
88
- export { enumType, domainType, isDomain, sameEnumLabels };