okmodel 0.4.0 → 0.5.1

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 (102) hide show
  1. package/README.md +95 -12
  2. package/dist/contracts/catalog/document.d.ts +20 -0
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/filter-parse.d.ts +8 -2
  5. package/dist/dialects/pg/fn/index.js +8 -0
  6. package/dist/dialects/pg/index.js +8 -2
  7. package/dist/dialects/pg/introspect.d.ts +6 -0
  8. package/dist/dialects/pg/ops/or.d.ts +1 -1
  9. package/dist/dialects/pg/role/sql.d.ts +10 -0
  10. package/dist/dialects/pg/schema.d.ts +15 -6
  11. package/dist/dialects/pg/temporal.d.ts +5 -76
  12. package/dist/dialects/pg/view/index.d.ts +20 -6
  13. package/dist/dialects/pg/view/index.js +1 -1
  14. package/dist/dialects/pg/view/scratch.d.ts +2 -2
  15. package/dist/runtime/client.d.ts +6 -1
  16. package/dist/runtime/conflict.d.ts +2 -1
  17. package/dist/runtime/pg/bun.d.ts +12 -7
  18. package/dist/runtime/pg/bun.js +7 -4
  19. package/dist/runtime/pg/pg.d.ts +12 -6
  20. package/dist/runtime/pg/pg.js +7 -4
  21. package/dist/runtime/pg/pglite.d.ts +11 -3
  22. package/dist/runtime/pg/pglite.js +7 -4
  23. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  24. package/dist/runtime/pg/postgresjs.js +7 -4
  25. package/dist/runtime/plan.d.ts +16 -1
  26. package/dist/runtime/tenancy/index.js +1 -1
  27. package/dist/runtime/topology.d.ts +191 -0
  28. package/dist/runtime/traits/index.js +7 -7
  29. package/dist/runtime/types.d.ts +57 -2
  30. package/dist/runtime/validate/index.js +3 -3
  31. package/dist/shared/{radd3z4g.js → 0t22wwh1.js} +5 -9
  32. package/dist/shared/{ady1ms74.js → 2pqx26h8.js} +25 -10
  33. package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
  34. package/dist/shared/{jns2wx4t.js → 32zqc1dw.js} +18 -7
  35. package/dist/shared/{nxb42q7h.js → 47x8x9ze.js} +2 -2
  36. package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
  37. package/dist/shared/{s6gqfn5j.js → 51mvgj8w.js} +27 -3
  38. package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
  39. package/dist/shared/{3p4b1fg0.js → 5enne4ma.js} +1 -1
  40. package/dist/shared/{6693j9rs.js → 5fqx9yh4.js} +6 -2
  41. package/dist/shared/{c5g81ngb.js → 6mnp99hx.js} +11 -8
  42. package/dist/shared/{hqjsxnz7.js → 6qd27mx0.js} +5 -4
  43. package/dist/shared/{v7tpnhnz.js → 6rk2gen5.js} +5 -3
  44. package/dist/shared/{2321avmn.js → 7zwwyh0v.js} +46 -16
  45. package/dist/shared/{fa9vt245.js → 87jvvhnt.js} +2 -2
  46. package/dist/shared/{8jb4ymh2.js → 8bwbwy8h.js} +12 -1
  47. package/dist/shared/{c8aghh05.js → 8rsy3hxz.js} +7 -1
  48. package/dist/shared/{41kv2xdm.js → 8xsbagad.js} +1 -1
  49. package/dist/shared/{6p5k20sm.js → 96qcj21c.js} +3 -3
  50. package/dist/shared/{jgxzjzcz.js → aj2qxyhj.js} +2 -2
  51. package/dist/shared/{5vdm4sgp.js → caw8b9d8.js} +4 -4
  52. package/dist/shared/{s4mb6a4b.js → cctw17hj.js} +74 -15
  53. package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
  54. package/dist/shared/{axhk06q1.js → cr74e96e.js} +1 -1
  55. package/dist/shared/{b066g80h.js → efmy2gcg.js} +2 -2
  56. package/dist/shared/{gdh9mrtp.js → f5cw5q5r.js} +2 -2
  57. package/dist/shared/{xghkt7a9.js → fancm5pt.js} +6 -233
  58. package/dist/shared/{n5y9zyj1.js → ga3sfy93.js} +369 -14
  59. package/dist/shared/{xrfnq5k8.js → gbtcc2d7.js} +55 -5
  60. package/dist/shared/{7nk68fj8.js → gde73es6.js} +20 -15
  61. package/dist/shared/{kkzwp3d2.js → hf22e0wt.js} +55 -19
  62. package/dist/shared/{tny2t3sm.js → janx09m5.js} +8 -7
  63. package/dist/shared/{4ef7mmqf.js → jwtxdj06.js} +5 -5
  64. package/dist/shared/{sf8m6tfy.js → k8v5v5wj.js} +3 -3
  65. package/dist/shared/{qn0srwwq.js → kfjp0gck.js} +5 -3
  66. package/dist/shared/{wqem4jed.js → krxx77g0.js} +7 -7
  67. package/dist/shared/{edwqqryd.js → nkaxvt9q.js} +1 -1
  68. package/dist/shared/{jsd07prz.js → ntneex2a.js} +1 -1
  69. package/dist/shared/nxdp2ze3.js +1197 -0
  70. package/dist/shared/{tbszx8mz.js → pf0d8f77.js} +1 -1
  71. package/dist/shared/{84ptn35q.js → ps7a6h14.js} +10 -9
  72. package/dist/shared/{kj680nxv.js → q58y17hy.js} +9 -7
  73. package/dist/shared/{awrsqrn7.js → qm7cdtvr.js} +22 -9
  74. package/dist/shared/{q6zsyh31.js → qv2ydck9.js} +156 -205
  75. package/dist/shared/{kafaa5zv.js → r2sp8hrc.js} +28 -8
  76. package/dist/shared/{fb00y8fp.js → r8x5qhd7.js} +5 -5
  77. package/dist/shared/{yjw02enp.js → sdc7r2ww.js} +8 -7
  78. package/dist/shared/sycnx8gt.js +289 -0
  79. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  80. package/dist/shared/{zg24x0hm.js → tm5ab8we.js} +10 -2
  81. package/dist/shared/{ygem90rn.js → v36c5g6p.js} +85 -21
  82. package/dist/shared/wq9n6pwr.js +139 -0
  83. package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
  84. package/dist/shared/{q795afwa.js → z2rnngj4.js} +3 -3
  85. package/dist/shared/{sr5ms7b3.js → zqrnhwsq.js} +3 -3
  86. package/dist/tooling/cli.js +70 -23
  87. package/dist/tooling/migrate/apply.d.ts +2 -0
  88. package/dist/tooling/migrate/commands.d.ts +1 -2
  89. package/dist/tooling/migrate/drift.d.ts +1 -1
  90. package/dist/tooling/migrate/estimate.d.ts +14 -1
  91. package/dist/tooling/migrate/files.d.ts +7 -0
  92. package/dist/tooling/migrate/index.js +12 -9
  93. package/dist/tooling/migrate/lint.d.ts +3 -3
  94. package/dist/tooling/migrate/plan.d.ts +7 -3
  95. package/dist/tooling/migrate/policy.d.ts +11 -0
  96. package/dist/tooling/migrate/sql-lex.d.ts +22 -0
  97. package/dist/tooling/testing/factories.d.ts +10 -0
  98. package/dist/tooling/testing/facts.d.ts +3 -0
  99. package/dist/tooling/testing/index.js +3 -3
  100. package/dist/tooling/testing/isolation.d.ts +4 -3
  101. package/package.json +6 -5
  102. package/dist/shared/0v1g424p.js +0 -26
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  creationOrder2
3
- } from "./8jb4ymh2.js";
3
+ } from "./8bwbwy8h.js";
4
4
  import {
5
5
  isDomain
6
6
  } from "./g7ez901n.js";
@@ -3,14 +3,14 @@ import {
3
3
  noticeBuffer,
4
4
  WireSession,
5
5
  cursorStream
6
- } from "./zvbjc949.js";
6
+ } from "./4az561fn.js";
7
7
  import {
8
8
  NODE_POSTGRES_CAPABILITIES,
9
9
  driverErrors,
10
10
  rejectClosed,
11
11
  mapFailure,
12
12
  runCall
13
- } from "./rrstp3kb.js";
13
+ } from "./cgbamx3e.js";
14
14
 
15
15
  // src/adapters/pg/nodepostgres.ts
16
16
  import net from "node:net";
@@ -53,7 +53,7 @@ function open4(config) {
53
53
  counters.busy = Math.max(0, counters.busy - 1);
54
54
  }, inflight);
55
55
  const held = { pool, acquireMs, counters, notices, named, isClosed };
56
- const checkout = () => closed ? rejectClosed() : import("./fb00y8fp.js").then((mod) => mod.checkout(held));
56
+ const checkout = () => closed ? rejectClosed() : import("./r8x5qhd7.js").then((mod) => mod.checkout(held));
57
57
  return {
58
58
  capabilities: NODE_POSTGRES_CAPABILITIES,
59
59
  execute(text, params, options) {
@@ -4,12 +4,12 @@ import {
4
4
  import {
5
5
  withHttp,
6
6
  settleCall
7
- } from "./kkzwp3d2.js";
7
+ } from "./hf22e0wt.js";
8
8
  import {
9
9
  rejectKeys,
10
10
  isRecord,
11
11
  fail
12
- } from "./xrfnq5k8.js";
12
+ } from "./gbtcc2d7.js";
13
13
  import {
14
14
  OkmError2
15
15
  } from "./52szzakm.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  PgSession
3
- } from "./wqem4jed.js";
3
+ } from "./krxx77g0.js";
4
4
  import {
5
5
  isConnectionLost
6
- } from "./tbszx8mz.js";
6
+ } from "./pf0d8f77.js";
7
7
  import {
8
8
  driverErrors,
9
9
  rejectClosed,
@@ -11,7 +11,7 @@ import {
11
11
  runCall,
12
12
  rowsFrom,
13
13
  resultFrom
14
- } from "./rrstp3kb.js";
14
+ } from "./cgbamx3e.js";
15
15
 
16
16
  // src/adapters/pg/postgres-extra.ts
17
17
  var RESET_ALL = "RESET ALL";
@@ -33,7 +33,7 @@ async function checkout(pool) {
33
33
  return {
34
34
  execute: (text, params, options) => guard(() => runCall(pool.isClosed(), options, (watch) => session.query(text, params, watch))),
35
35
  batch: (statements, options) => guard(() => runCall(pool.isClosed(), options, async (watch) => {
36
- const { runAtomicBatch } = await import("./b066g80h.js");
36
+ const { runAtomicBatch } = await import("./efmy2gcg.js");
37
37
  return runAtomicBatch(session, statements, watch);
38
38
  })),
39
39
  cancel() {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  open2
3
- } from "./wqem4jed.js";
3
+ } from "./krxx77g0.js";
4
4
  import {
5
- catalogHash2
6
- } from "./8jb4ymh2.js";
5
+ startupHash2
6
+ } from "./8bwbwy8h.js";
7
7
  import {
8
8
  runTarget,
9
9
  assumeRole,
@@ -15,29 +15,30 @@ import {
15
15
  provisionMark,
16
16
  expandProvisioned,
17
17
  installSnapshot
18
- } from "./7nk68fj8.js";
18
+ } from "./gde73es6.js";
19
19
  import {
20
20
  assertCreateRole,
21
21
  currentUser,
22
22
  roleExists
23
- } from "./hcmdtwgh.js";
23
+ } from "./szm0cksa.js";
24
24
  import {
25
25
  lintMigrationDirectory,
26
26
  hasError,
27
27
  lintRefusal,
28
- assertTargetPolicy,
29
- assertDirectConnection,
30
- selectTarget,
31
28
  projectHead,
32
29
  loadBuiltSchema,
33
30
  loadConfig
34
- } from "./q6zsyh31.js";
31
+ } from "./qv2ydck9.js";
35
32
  import {
36
33
  changesRole,
37
34
  batchSizeField,
38
35
  assertExtensionsAvailable,
39
- planMigration
40
- } from "./n5y9zyj1.js";
36
+ planMigration,
37
+ assertTargetPolicy,
38
+ assertDirectConnection,
39
+ assertStableBackend,
40
+ selectTarget
41
+ } from "./ga3sfy93.js";
41
42
  import {
42
43
  OkmError2
43
44
  } from "./52szzakm.js";
@@ -177,6 +178,8 @@ async function applyTarget2(request) {
177
178
  const connection = await pool.reserve();
178
179
  let locked = false;
179
180
  try {
181
+ await assertStableBackend(() => backendPid(connection), request.allowPooler === true);
182
+ await assertStandardStrings(connection);
180
183
  await setTimeouts(connection, request);
181
184
  await lockTarget(connection, request.target);
182
185
  locked = true;
@@ -202,6 +205,7 @@ async function applyTarget2(request) {
202
205
  await connection.execute(META);
203
206
  await connection.execute(HISTORY);
204
207
  await connection.execute(BACKFILL);
208
+ const restamped = await restampLegacy(connection, request);
205
209
  const history = await readHistory(connection);
206
210
  const done = expandProvisioned(request.migrations, history.done, history.ids);
207
211
  const units = request.migrations.flatMap((migration) => applyUnits(migration, done));
@@ -221,7 +225,7 @@ async function applyTarget2(request) {
221
225
  if (request.snapshot !== undefined) {
222
226
  await insertReference(connection, request);
223
227
  }
224
- return report;
228
+ return restamped.length === 0 ? report : { ...report, restamped };
225
229
  } finally {
226
230
  if (locked)
227
231
  await connection.execute("select pg_advisory_unlock(hashtext($1))", [
@@ -306,7 +310,7 @@ no changes
306
310
  migrations: [
307
311
  {
308
312
  id: "push",
309
- catalogHash: catalogHash2(head.catalog),
313
+ catalogHash: startupHash2(head.catalog),
310
314
  steps: plan.steps
311
315
  }
312
316
  ],
@@ -323,18 +327,73 @@ function backfillTiming2(backfill) {
323
327
  function formatReport(report) {
324
328
  const role = report.setRole !== undefined ? `set role ${report.setRole}
325
329
  ` : "";
330
+ const restamped = (report.restamped ?? []).map((id) => `restamped ${id}
331
+ `).join("");
326
332
  if (report.applied.length === 0)
327
333
  return `target ${report.target}
328
- ${role}nothing to apply
334
+ ${role}${restamped}nothing to apply
329
335
  `;
330
336
  return `target ${report.target}
331
- ${role}${report.applied.map((id) => `applied ${id}`).join(`
337
+ ${role}${restamped}${report.applied.map((id) => `applied ${id}`).join(`
332
338
  `)}
333
339
  `;
334
340
  }
341
+ async function restampLegacy(connection, request) {
342
+ const pairs = [];
343
+ for (const migration of request.migrations) {
344
+ if (migration.legacyHash === undefined)
345
+ continue;
346
+ pairs.push({
347
+ id: migration.id,
348
+ mark: provisionMark(migration.id),
349
+ legacy: migration.legacyHash,
350
+ startup: migration.catalogHash
351
+ });
352
+ }
353
+ if (pairs.length === 0)
354
+ return [];
355
+ await connection.execute("begin");
356
+ try {
357
+ const result = await connection.execute(RESTAMP, [JSON.stringify(pairs)]);
358
+ const restamped = result.rows.flatMap((row) => typeof row[0] === "string" ? [row[0]] : []);
359
+ if (restamped.length > 0) {
360
+ assertTargetPolicy({ name: request.target, protected: request.protected }, "history-repair", request.allowProtected === true);
361
+ }
362
+ await connection.execute("commit");
363
+ return restamped;
364
+ } catch (error) {
365
+ await connection.execute("rollback");
366
+ throw error;
367
+ }
368
+ }
369
+ var RESTAMP = `with pairs as (
370
+ select * from json_to_recordset($1::json) as p(id text, mark text, legacy text, startup text)
371
+ ), history as (
372
+ update okm_history h set catalog_hash = p.startup from pairs p
373
+ where h.migration_id in (p.id, p.mark) and h.catalog_hash = p.legacy
374
+ returning p.id, h.migration_id
375
+ ), meta as (
376
+ update okm_meta m set catalog_hash = p.startup from pairs p
377
+ where m.id = 'head' and m.migration_id in (p.id, p.mark) and m.catalog_hash = p.legacy
378
+ and m.migration_id in (select migration_id from history)
379
+ returning m.id
380
+ )
381
+ select distinct id from history order by id`;
335
382
  function joinMigrations(cwd, migrations) {
336
383
  return join(cwd, migrations ?? "migrations");
337
384
  }
385
+ async function backendPid(connection) {
386
+ const result = await connection.execute("select pg_backend_pid()::text");
387
+ const cell = result.rows[0]?.[0];
388
+ return cell === null || cell === undefined ? "" : String(cell);
389
+ }
390
+ async function assertStandardStrings(connection) {
391
+ const result = await connection.execute("show standard_conforming_strings");
392
+ const value = result.rows[0]?.[0];
393
+ if (value === "on")
394
+ return;
395
+ throw new OkmError2("invalid", `standard_conforming_strings is ${value === null || value === undefined ? "off" : String(value)}. Apply refuses to run.`, { fix: { summary: "Turn standard_conforming_strings on for this database." } });
396
+ }
338
397
  async function setTimeouts(connection, request) {
339
398
  const lock = request.lockTimeoutMs ?? DEFAULT_LOCK_MS;
340
399
  const statement = request.statementTimeoutMs ?? DEFAULT_STATEMENT_MS;
@@ -41,7 +41,7 @@ function hasCapability2(flags, name) {
41
41
  // src/adapters/failure.ts
42
42
  var loading;
43
43
  function driverErrors() {
44
- loading ??= import("./7p2wbz7f.js");
44
+ loading ??= import("./x72jb7ad.js");
45
45
  return loading;
46
46
  }
47
47
  function rejectClosed(message = "The pool is closed.") {
@@ -83,7 +83,7 @@ function runCall(closed, options, fn) {
83
83
  if (options === undefined || !needsWatch(options))
84
84
  return fn(undefined);
85
85
  const watched = options;
86
- return import("./k3ted12y.js").then(({ openWatch }) => {
86
+ return import("./54tqkxja.js").then(({ openWatch }) => {
87
87
  const watch = openWatch(watched);
88
88
  return fn(watch).finally(() => {
89
89
  watch.finish();
@@ -11,7 +11,7 @@ import {
11
11
  quote,
12
12
  isRecord,
13
13
  fail
14
- } from "./xrfnq5k8.js";
14
+ } from "./gbtcc2d7.js";
15
15
  import {
16
16
  isOperator,
17
17
  operatorName,
@@ -2,10 +2,10 @@ import {
2
2
  DriverError2,
3
3
  outcomeUnknown2,
4
4
  mapDriverError2
5
- } from "./kyxb62mc.js";
5
+ } from "./31vm914h.js";
6
6
  import {
7
7
  isConnectionLost
8
- } from "./tbszx8mz.js";
8
+ } from "./pf0d8f77.js";
9
9
 
10
10
  // src/adapters/pg/batch.ts
11
11
  var savepointIds = 0;
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  open3
3
- } from "./fa9vt245.js";
3
+ } from "./87jvvhnt.js";
4
4
  import {
5
5
  PGLITE_CAPABILITIES,
6
6
  readCapability2,
7
7
  hasCapability2
8
- } from "./rrstp3kb.js";
8
+ } from "./cgbamx3e.js";
9
9
  export {
10
10
  PGLITE_CAPABILITIES as capabilities,
11
11
  hasCapability2 as hasCapability,
@@ -2,14 +2,10 @@ import {
2
2
  roleObject,
3
3
  grantObject,
4
4
  defaultPrivilegeObject
5
- } from "./8jb4ymh2.js";
5
+ } from "./8bwbwy8h.js";
6
6
  import {
7
- planMigration
8
- } from "./n5y9zyj1.js";
9
- import {
10
- quoteIdent,
11
- createObjectSql
12
- } from "./41kv2xdm.js";
7
+ quoteIdent
8
+ } from "./8xsbagad.js";
13
9
  import {
14
10
  functionObject,
15
11
  triggerObject
@@ -40,12 +36,6 @@ import {
40
36
  import {
41
37
  referentialAction
42
38
  } from "./4e4c6vq2.js";
43
- import {
44
- OkmError2
45
- } from "./52szzakm.js";
46
-
47
- // src/tooling/migrate/drift.ts
48
- import postgres from "postgres";
49
39
 
50
40
  // src/dialects/pg/view/depend.ts
51
41
  function cellText(value) {
@@ -446,8 +436,9 @@ var RELATION_GRANTS = `select c.relname as name,
446
436
  select 1 from pg_depend d
447
437
  where d.classid = 'pg_class'::regclass and d.objid = c.oid and d.deptype = 'e'
448
438
  )`;
439
+ var FUNCTION_ARG_TYPES = "replace(oidvectortypes(p.proargtypes), ' ', '')";
449
440
  var FUNCTION_GRANTS = `select p.proname as name,
450
- replace(pg_get_function_identity_arguments(p.oid), ' ', '') as args,
441
+ ${FUNCTION_ARG_TYPES} as args,
451
442
  r.rolname as role, a.privilege_type as privilege
452
443
  from pg_proc p
453
444
  join pg_namespace n on n.oid = p.pronamespace
@@ -923,222 +914,4 @@ function flag(row, key) {
923
914
  return value === true || value === "true" || value === "t";
924
915
  }
925
916
 
926
- // src/dialects/pg/view/scratch.ts
927
- var SCRATCH = "okm_view_seal";
928
- var VIEW_SHAPE = `
929
- select c.relname as name, c.relkind as kind, pg_get_viewdef(c.oid, true) as query,
930
- (
931
- select coalesce(string_agg(
932
- a.attname || '|' || format_type(a.atttypid, a.atttypmod),
933
- E'\\n' order by a.attnum
934
- ), '')
935
- from pg_attribute a
936
- where a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
937
- ) as columns
938
- from pg_class c
939
- join pg_namespace n on n.oid = c.relnamespace
940
- where n.nspname = $1 and c.relkind in ('v', 'm')
941
- `;
942
- async function sealViews(runner, source) {
943
- const views = source.objects.filter((object) => object.kind === "view" || object.kind === "materializedView");
944
- if (views.length === 0)
945
- return source;
946
- await dropScratch(runner);
947
- await runner.query(`create schema ${quoteIdent(SCRATCH)}`);
948
- try {
949
- await runner.query(`set search_path to ${quoteIdent(SCRATCH)}`);
950
- for (const object of source.objects) {
951
- if (object.kind !== "type")
952
- continue;
953
- const statement = createObjectSql(object, SCRATCH);
954
- if (statement !== undefined)
955
- await runner.query(statement);
956
- }
957
- for (const object of source.objects) {
958
- if (object.kind !== "table")
959
- continue;
960
- await runner.query(stubTable(object, source));
961
- }
962
- await createViews(runner, views);
963
- const shapes = await runner.query(VIEW_SHAPE, [SCRATCH]);
964
- const deps = await runner.query(VIEW_DEPENDENCIES, [SCRATCH]);
965
- const edges = viewDependencyEdges(deps, PUBLIC, source.objects);
966
- const byName = new Map(shapes.map((row) => [cellText(row.name), row]));
967
- const next = source.objects.map((object) => {
968
- if (object.kind !== "view" && object.kind !== "materializedView")
969
- return object;
970
- const row = byName.get(object.identity.name);
971
- if (row === undefined) {
972
- throw new OkmError2("OKM1020", `View ${object.identity.name} was not created in the scratch schema.`, {
973
- fix: { summary: "The query has to run against the tables in this schema." }
974
- });
975
- }
976
- const columns = readColumns(object, cellText(row.columns));
977
- const query = normaliseViewQuery(cellText(row.query));
978
- const dependencies = edges.get(object.identity.name) ?? [];
979
- if (object.kind === "view") {
980
- return viewObject({
981
- namespace: object.identity.namespace,
982
- name: object.identity.name,
983
- columns,
984
- query,
985
- owner: object.owner,
986
- provenance: object.provenance,
987
- dependencies
988
- });
989
- }
990
- return materializedViewObject({
991
- namespace: object.identity.namespace,
992
- name: object.identity.name,
993
- columns,
994
- query,
995
- owner: object.owner,
996
- provenance: object.provenance,
997
- dependencies,
998
- ...object.definition.refresh !== undefined ? { refresh: object.definition.refresh } : {}
999
- });
1000
- });
1001
- return catalog(next);
1002
- } finally {
1003
- await runner.query("set search_path to public");
1004
- await dropScratch(runner);
1005
- }
1006
- }
1007
- async function createViews(runner, views) {
1008
- const pending = [...views];
1009
- let last = "";
1010
- for (let pass = 0;pass < views.length && pending.length > 0; pass += 1) {
1011
- const failed = [];
1012
- let created = 0;
1013
- for (const object of pending) {
1014
- const sql = object.kind === "materializedView" ? `create materialized view ${quoteIdent(object.identity.name)} as ${object.definition.query} with no data` : `create view ${quoteIdent(object.identity.name)} as ${object.definition.query}`;
1015
- try {
1016
- await runner.query(sql);
1017
- created += 1;
1018
- } catch (error) {
1019
- last = error instanceof Error ? error.message : String(error);
1020
- failed.push(object);
1021
- }
1022
- }
1023
- if (created === 0) {
1024
- throw new OkmError2("OKM1020", `View ${failed[0]?.identity.name ?? ""} could not be created. ${last}`, {
1025
- fix: { summary: "The query has to run against the tables in this schema." }
1026
- });
1027
- }
1028
- pending.length = 0;
1029
- pending.push(...failed);
1030
- }
1031
- }
1032
- function stubTable(object, source) {
1033
- const columns = source.objects.filter((item) => item.kind === "column" && item.identity.parent.name === object.identity.name);
1034
- const lines = columns.map((column) => `${quoteIdent(column.identity.name)} ${column.definition.dataType}`);
1035
- return `create table ${quoteIdent(object.identity.name)} (${lines.join(", ")})`;
1036
- }
1037
- function readColumns(object, printed) {
1038
- const live = printed.length === 0 ? [] : printed.split(`
1039
- `);
1040
- const declared = object.definition.columns;
1041
- if (live.length !== declared.length) {
1042
- throw mismatch(object.identity.name, declared.map((column) => column.name).join(", "), printed);
1043
- }
1044
- return declared.map((column, index) => {
1045
- const line = live[index] ?? "";
1046
- const splitAt = line.indexOf("|");
1047
- const name = splitAt < 0 ? line : line.slice(0, splitAt);
1048
- const type = splitAt < 0 ? "" : line.slice(splitAt + 1);
1049
- if (name !== column.name || type !== column.dataType) {
1050
- throw mismatch(object.identity.name, `${column.name}:${column.dataType}`, live[index] ?? "");
1051
- }
1052
- return column;
1053
- });
1054
- }
1055
- function mismatch(view, declared, live) {
1056
- return new OkmError2("OKM1020", `View ${view} declares ${declared} and the database has ${live}.`, { fix: { summary: "Declare the columns the query returns, in that order, with those types." } });
1057
- }
1058
- async function dropScratch(runner) {
1059
- const present = await runner.query(`select 1 as ok from pg_namespace where nspname = $1`, [
1060
- SCRATCH
1061
- ]);
1062
- if (present.length === 0)
1063
- return;
1064
- const rows = await runner.query(`
1065
- select c.relname as name, c.relkind as kind
1066
- from pg_class c
1067
- join pg_namespace n on n.oid = c.relnamespace
1068
- where n.nspname = $1 and c.relkind in ('v', 'm', 'r')
1069
- `, [SCRATCH]);
1070
- const views = rows.filter((row) => cellText(row.kind) === "v" || cellText(row.kind) === "m");
1071
- for (let pass = 0;pass < Math.max(views.length, 1); pass += 1) {
1072
- for (const row of views) {
1073
- const kind = cellText(row.kind) === "m" ? "materialized view" : "view";
1074
- try {
1075
- await runner.query(`drop ${kind} if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
1076
- } catch {}
1077
- }
1078
- }
1079
- for (const row of rows) {
1080
- if (cellText(row.kind) !== "r")
1081
- continue;
1082
- await runner.query(`drop table if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
1083
- }
1084
- await runner.query(`drop schema if exists ${quoteIdent(SCRATCH)} cascade`);
1085
- }
1086
-
1087
- // src/tooling/migrate/drift.ts
1088
- async function assertAuthorDrift2(url, author, roles) {
1089
- const sql = postgres(url, {
1090
- max: 1,
1091
- connect_timeout: 5,
1092
- idle_timeout: 1,
1093
- onnotice: () => {}
1094
- });
1095
- try {
1096
- const present = await sql`
1097
- select to_regclass('public.okm_meta')::text as reg
1098
- `;
1099
- if (present[0]?.reg == null)
1100
- return;
1101
- const runner = catalogQuery2(sql);
1102
- const authorOnServer = await sealViews(runner, author);
1103
- const live = await introspectSchema(runner, "public", "public", managedRoleOptions2(roles));
1104
- const plan = planMigration({ before: live, after: authorOnServer, name: "check" });
1105
- if (plan.steps.length === 0)
1106
- return;
1107
- throw new OkmError2("OKM1520", `The database differs from the schema.
1108
- ${plan.steps.map((step) => step.sql).join(`
1109
- `)}`, {
1110
- fix: {
1111
- summary: "Change the schema or the database so okm check reports no difference."
1112
- }
1113
- });
1114
- } finally {
1115
- await sql.end({ timeout: 5 });
1116
- }
1117
- }
1118
- function catalogQuery2(sql) {
1119
- return {
1120
- async query(text, params) {
1121
- const rows = await sql.unsafe(text, params === undefined ? undefined : [...params]);
1122
- return rows.map((row) => {
1123
- const copy = {};
1124
- for (const [key, value] of Object.entries(row))
1125
- copy[key] = value;
1126
- return copy;
1127
- });
1128
- }
1129
- };
1130
- }
1131
- function managedRoleOptions2(roles) {
1132
- const names = managedRoleNames(roles);
1133
- return names === undefined ? undefined : { managedRoles: names };
1134
- }
1135
- function managedRoleNames(roles) {
1136
- if (roles === undefined)
1137
- return;
1138
- const names = new Set([roles.migration, roles.app]);
1139
- for (const role of roles.managed ?? [])
1140
- names.add(role.name);
1141
- return [...names];
1142
- }
1143
-
1144
- export { introspectSchema, sealViews, assertAuthorDrift2, catalogQuery2, managedRoleOptions2 };
917
+ export { cellText, VIEW_DEPENDENCIES, viewDependencyEdges, PUBLIC, introspectSchema, FUNCTION_ARG_TYPES };