okmodel 0.4.0 → 0.5.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 (64) hide show
  1. package/README.md +86 -6
  2. package/dist/contracts/catalog/document.d.ts +20 -0
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/introspect.d.ts +6 -0
  5. package/dist/dialects/pg/role/sql.d.ts +10 -0
  6. package/dist/dialects/pg/schema.d.ts +15 -6
  7. package/dist/dialects/pg/view/index.d.ts +20 -6
  8. package/dist/dialects/pg/view/scratch.d.ts +2 -2
  9. package/dist/runtime/pg/bun.d.ts +12 -7
  10. package/dist/runtime/pg/bun.js +4 -1
  11. package/dist/runtime/pg/pg.d.ts +12 -6
  12. package/dist/runtime/pg/pg.js +4 -1
  13. package/dist/runtime/pg/pglite.d.ts +11 -3
  14. package/dist/runtime/pg/pglite.js +4 -1
  15. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  16. package/dist/runtime/pg/postgresjs.js +4 -1
  17. package/dist/runtime/plan.d.ts +3 -1
  18. package/dist/runtime/tenancy/index.js +1 -1
  19. package/dist/runtime/topology.d.ts +191 -0
  20. package/dist/runtime/traits/index.js +6 -6
  21. package/dist/runtime/types.d.ts +57 -2
  22. package/dist/runtime/validate/index.js +1 -1
  23. package/dist/shared/{nxb42q7h.js → 47xzb9fc.js} +2 -2
  24. package/dist/shared/{6693j9rs.js → 5fqx9yh4.js} +6 -2
  25. package/dist/shared/{8jb4ymh2.js → 8bwbwy8h.js} +12 -1
  26. package/dist/shared/{41kv2xdm.js → 8xsbagad.js} +1 -1
  27. package/dist/shared/{sr5ms7b3.js → 922qbgnw.js} +3 -3
  28. package/dist/shared/{tny2t3sm.js → 990a291j.js} +4 -4
  29. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  30. package/dist/shared/{n5y9zyj1.js → ayz64vzj.js} +13 -12
  31. package/dist/shared/{kkzwp3d2.js → bt9sfq0z.js} +16 -14
  32. package/dist/shared/{c5g81ngb.js → bzsjtd7c.js} +7 -5
  33. package/dist/shared/{radd3z4g.js → ckjbyrt9.js} +1 -1
  34. package/dist/shared/{zg24x0hm.js → dxjp14rk.js} +10 -2
  35. package/dist/shared/{xghkt7a9.js → fancm5pt.js} +6 -233
  36. package/dist/shared/{yjw02enp.js → fyqkk1m0.js} +4 -4
  37. package/dist/shared/{ygem90rn.js → g516zdsb.js} +8 -3
  38. package/dist/shared/{v7tpnhnz.js → h4nqvjkp.js} +5 -3
  39. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  40. package/dist/shared/{ady1ms74.js → jdgqqx31.js} +5 -5
  41. package/dist/shared/{kafaa5zv.js → k7cc9nzk.js} +24 -7
  42. package/dist/shared/{7nk68fj8.js → kdkxh3kb.js} +17 -10
  43. package/dist/shared/nkc9sfqb.js +1161 -0
  44. package/dist/shared/{awrsqrn7.js → p7pfx624.js} +2 -2
  45. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  46. package/dist/shared/{84ptn35q.js → qy9rdq0g.js} +6 -5
  47. package/dist/shared/{qn0srwwq.js → r6pj4fq2.js} +5 -3
  48. package/dist/shared/{2321avmn.js → r73pt594.js} +21 -13
  49. package/dist/shared/{kj680nxv.js → rasvvzgz.js} +8 -6
  50. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  51. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  52. package/dist/shared/wxn4b7pp.js +252 -0
  53. package/dist/shared/{q6zsyh31.js → xdpj5kkm.js} +19 -8
  54. package/dist/shared/{s4mb6a4b.js → xhh1srw3.js} +54 -10
  55. package/dist/shared/{jgxzjzcz.js → xsq7v6wp.js} +2 -2
  56. package/dist/shared/{hqjsxnz7.js → ykv7wh5b.js} +5 -4
  57. package/dist/shared/{q795afwa.js → z2rnngj4.js} +3 -3
  58. package/dist/tooling/cli.js +12 -12
  59. package/dist/tooling/migrate/apply.d.ts +2 -0
  60. package/dist/tooling/migrate/files.d.ts +7 -0
  61. package/dist/tooling/migrate/index.js +8 -6
  62. package/dist/tooling/testing/facts.d.ts +3 -0
  63. package/dist/tooling/testing/index.js +3 -3
  64. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  open2
3
3
  } from "./wqem4jed.js";
4
- import"./8jb4ymh2.js";
4
+ import"./8bwbwy8h.js";
5
5
  import"./n442h54c.js";
6
6
  import {
7
7
  CURRENT,
@@ -15,14 +15,16 @@ import {
15
15
  loadMigrations,
16
16
  expandProvisioned,
17
17
  provisionHistoryIsKnown
18
- } from "./7nk68fj8.js";
18
+ } from "./kdkxh3kb.js";
19
+ import"./szm0cksa.js";
20
+ import"./fancm5pt.js";
19
21
  import {
20
22
  assertTargetPolicy,
21
23
  listTargets,
22
24
  loadConfig
23
- } from "./q6zsyh31.js";
24
- import"./n5y9zyj1.js";
25
- import"./41kv2xdm.js";
25
+ } from "./xdpj5kkm.js";
26
+ import"./ayz64vzj.js";
27
+ import"./8xsbagad.js";
26
28
  import {
27
29
  OkmError2
28
30
  } from "./52szzakm.js";
@@ -14,7 +14,7 @@ import {
14
14
  isRecord,
15
15
  registerFailFix,
16
16
  fail
17
- } from "./xrfnq5k8.js";
17
+ } from "./qgvt8nx1.js";
18
18
  import {
19
19
  safetyInstalled,
20
20
  runSafety
@@ -3,10 +3,10 @@ import {
3
3
  readHandle,
4
4
  readCall,
5
5
  attachHttp
6
- } from "./kkzwp3d2.js";
6
+ } from "./bt9sfq0z.js";
7
7
  import {
8
8
  fail
9
- } from "./xrfnq5k8.js";
9
+ } from "./qgvt8nx1.js";
10
10
  import {
11
11
  OkmError2,
12
12
  throwNamed2
@@ -27,6 +27,14 @@ function build(session, table, options, mods, view) {
27
27
  }
28
28
  function plan(session, table, options, mods, view) {
29
29
  const { lock, wait, ...rest } = options;
30
+ const forced = session.pool.forcedRoute;
31
+ if (rest.route === "replica" || forced === "replica") {
32
+ throw new OkmError2("OKM1840", "A locking read needs the primary.", {
33
+ fix: {
34
+ summary: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
35
+ }
36
+ });
37
+ }
30
38
  if (session.tx === undefined) {
31
39
  throw new OkmError2("OKM1830", "A row lock needs a transaction. find was called outside tx().", {
32
40
  fix: { summary: "Move the find into tx(): the lock lasts until the transaction ends." }
@@ -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 };
@@ -1,12 +1,12 @@
1
- import"./8jb4ymh2.js";
1
+ import"./8bwbwy8h.js";
2
2
  import"./n442h54c.js";
3
3
  import"./wqem4jed.js";
4
4
  import {
5
5
  listTargets,
6
6
  loadConfig
7
- } from "./q6zsyh31.js";
8
- import"./n5y9zyj1.js";
9
- import"./41kv2xdm.js";
7
+ } from "./xdpj5kkm.js";
8
+ import"./ayz64vzj.js";
9
+ import"./8xsbagad.js";
10
10
 
11
11
  // src/tooling/migrate/dev.ts
12
12
  import { mkdirSync } from "node:fs";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  renderCatalog
3
- } from "./41kv2xdm.js";
3
+ } from "./8xsbagad.js";
4
4
  import {
5
5
  createClient
6
- } from "./kkzwp3d2.js";
6
+ } from "./bt9sfq0z.js";
7
7
  import {
8
8
  OkmError2
9
9
  } from "./52szzakm.js";
@@ -12,10 +12,15 @@ import {
12
12
  var tenantProbe = "00000000-0000-4000-8000-000000000001";
13
13
  function readFacts(schema) {
14
14
  const foreign = foreignKeys(schema.catalog);
15
+ const tables = new Set;
16
+ for (const object of schema.catalog.objects) {
17
+ if (object.kind === "table")
18
+ tables.add(object.identity.name);
19
+ }
15
20
  const facts = [];
16
21
  for (const name of Object.keys(schema.model)) {
17
22
  const model = schema.model[name];
18
- if (model === undefined)
23
+ if (model === undefined || !tables.has(model.sql))
19
24
  continue;
20
25
  const keys = foreign.get(model.sql) ?? [];
21
26
  facts.push({
@@ -3,7 +3,7 @@ import {
3
3
  readHandle,
4
4
  readCall,
5
5
  attachHttp
6
- } from "./kkzwp3d2.js";
6
+ } from "./bt9sfq0z.js";
7
7
  import {
8
8
  selectedColumns,
9
9
  parseOrder,
@@ -15,7 +15,7 @@ import {
15
15
  isRecord,
16
16
  registerFailFix,
17
17
  fail
18
- } from "./xrfnq5k8.js";
18
+ } from "./qgvt8nx1.js";
19
19
  import {
20
20
  OkmError2,
21
21
  throwNamed2
@@ -30,6 +30,7 @@ var OPTIONS = [
30
30
  "limit",
31
31
  "include",
32
32
  "after",
33
+ "route",
33
34
  "signal",
34
35
  "timeout"
35
36
  ];
@@ -69,7 +70,8 @@ function plan(session, table, options, mods, view) {
69
70
  limit: size + 1,
70
71
  include: record.include,
71
72
  signal: record.signal,
72
- timeout: record.timeout
73
+ timeout: record.timeout,
74
+ route: record.route
73
75
  }, undefined, session.scope, session.schema.model[table], session.schema, view),
74
76
  ...after !== undefined ? { after } : {}
75
77
  };
@@ -17,7 +17,7 @@ import {
17
17
  isRecord,
18
18
  registerFailFix,
19
19
  fail
20
- } from "./xrfnq5k8.js";
20
+ } from "./qgvt8nx1.js";
21
21
  import {
22
22
  throwNamed2
23
23
  } from "./52szzakm.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  open2
3
3
  } from "./wqem4jed.js";
4
- import"./8jb4ymh2.js";
4
+ import"./8bwbwy8h.js";
5
5
  import {
6
6
  createdOf,
7
7
  testing2
8
- } from "./ygem90rn.js";
8
+ } from "./g516zdsb.js";
9
9
  import"./n442h54c.js";
10
10
  import {
11
11
  assertTargetPolicy,
@@ -13,9 +13,9 @@ import {
13
13
  selectTarget,
14
14
  loadConfig,
15
15
  loadSchema
16
- } from "./q6zsyh31.js";
17
- import"./n5y9zyj1.js";
18
- import"./41kv2xdm.js";
16
+ } from "./xdpj5kkm.js";
17
+ import"./ayz64vzj.js";
18
+ import"./8xsbagad.js";
19
19
  import {
20
20
  OkmError2
21
21
  } from "./52szzakm.js";
@@ -5,12 +5,12 @@ import {
5
5
  openClient,
6
6
  withHttp,
7
7
  settleCall
8
- } from "./kkzwp3d2.js";
8
+ } from "./bt9sfq0z.js";
9
9
  import {
10
10
  rejectKeys,
11
11
  isRecord,
12
12
  fail
13
- } from "./xrfnq5k8.js";
13
+ } from "./qgvt8nx1.js";
14
14
  import {
15
15
  OkmError2
16
16
  } from "./52szzakm.js";
@@ -312,8 +312,16 @@ function held(session, frame, after, depth) {
312
312
  };
313
313
  const pool = depth === 0 ? {
314
314
  capabilities: base.capabilities,
315
- execute: (text, params, options) => guarded(() => frame.conn.execute(text, params, stoppable(frame, options)), false),
316
- batch: (statements, options) => guarded(() => frame.conn.batch(statements, stoppable(frame, options)), true),
315
+ execute: (text, params, options) => {
316
+ if (options?.route === "replica")
317
+ replicaInside();
318
+ return guarded(() => frame.conn.execute(text, params, stoppable(frame, options)), false);
319
+ },
320
+ batch: (statements, options) => {
321
+ if (options?.route === "replica")
322
+ replicaInside();
323
+ return guarded(() => frame.conn.batch(statements, stoppable(frame, options)), true);
324
+ },
317
325
  stats: () => base.stats(),
318
326
  close: () => Promise.resolve()
319
327
  } : base;
@@ -364,13 +372,22 @@ function lockKey(key) {
364
372
  return ["select pg_advisory_xact_lock($1::bigint)", String(key)];
365
373
  }
366
374
  function callOf(options) {
367
- if (options?.signal === undefined && options?.timeout === undefined)
375
+ if (options?.signal === undefined && options?.timeout === undefined && options?.route === undefined) {
368
376
  return;
377
+ }
369
378
  return {
370
- ...options.signal !== undefined ? { signal: options.signal } : {},
371
- ...options.timeout !== undefined ? { timeout: options.timeout } : {}
379
+ ...options?.signal !== undefined ? { signal: options.signal } : {},
380
+ ...options?.timeout !== undefined ? { timeout: options.timeout } : {},
381
+ ...options?.route !== undefined ? { route: options.route } : {}
372
382
  };
373
383
  }
384
+ function replicaInside() {
385
+ throw new OkmError2("OKM1840", "This operation needs the primary.", {
386
+ fix: {
387
+ summary: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
388
+ }
389
+ });
390
+ }
374
391
  export {
375
392
  tx
376
393
  };
@@ -1,17 +1,19 @@
1
1
  import {
2
2
  serializeCatalog2,
3
3
  catalogHash2,
4
+ startupCatalog2,
4
5
  parseCatalog2
5
- } from "./8jb4ymh2.js";
6
+ } from "./8bwbwy8h.js";
6
7
  import {
7
8
  roleExists
8
- } from "./hcmdtwgh.js";
9
+ } from "./szm0cksa.js";
9
10
  import {
10
11
  assertTargetPolicy
11
- } from "./q6zsyh31.js";
12
+ } from "./xdpj5kkm.js";
12
13
  import {
13
14
  canonicalTypeName,
14
15
  createdRoleName,
16
+ createRoleOnce,
15
17
  DEFAULT_BACKFILL_PAUSE_MS,
16
18
  DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS,
17
19
  boundaryQuery,
@@ -19,11 +21,11 @@ import {
19
21
  omitManagedObjects,
20
22
  planMigration,
21
23
  parsePlan
22
- } from "./n5y9zyj1.js";
24
+ } from "./ayz64vzj.js";
23
25
  import {
24
26
  quoteIdent,
25
27
  qualify
26
- } from "./41kv2xdm.js";
28
+ } from "./8xsbagad.js";
27
29
  import {
28
30
  alignExtensionVersions
29
31
  } from "./8ktwd3rh.js";
@@ -98,8 +100,10 @@ async function runUnit(connection, unit, run, session) {
98
100
  await rebuildInvalidIndex(connection, item.step.sql);
99
101
  const created = createdRoleName(item.step.sql);
100
102
  const existed = created !== undefined && await roleExists(connection, created);
101
- if (created === undefined || !existed)
103
+ if (created === undefined)
102
104
  await connection.execute(item.step.sql);
105
+ else if (!existed)
106
+ await connection.execute(createRoleOnce(item.step.sql));
103
107
  if (created !== undefined && created === run.migrationRole) {
104
108
  if (!existed) {
105
109
  await grantSchema(connection, run.schema ?? "public", created);
@@ -377,8 +381,10 @@ function loadMigrations(directory) {
377
381
  });
378
382
  }
379
383
  const plan = parsePlan(readFileSync(join(directory, file), "utf8"));
380
- const hash = catalogHash2(parseCatalog2(readFileSync(catalogPath, "utf8")));
381
- migrations.push({ id, catalogHash: hash, steps: plan.steps });
384
+ const stored = parseCatalog2(readFileSync(catalogPath, "utf8"));
385
+ const startup = startupCatalog2(stored);
386
+ const hash = catalogHash2(startup);
387
+ migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
382
388
  }
383
389
  return migrations;
384
390
  }
@@ -601,11 +607,12 @@ async function installSnapshot(connection, head, schema, reference, session, mig
601
607
  await assumeRole(connection, created, session);
602
608
  continue;
603
609
  }
610
+ const sql = created === undefined ? step.sql : createRoleOnce(step.sql);
604
611
  if (step.transactional && !statementOutsideTransaction(step.sql)) {
605
- batch.push(step.sql);
612
+ batch.push(sql);
606
613
  } else {
607
614
  await flush();
608
- await connection.execute(step.sql);
615
+ await connection.execute(sql);
609
616
  }
610
617
  if (created !== undefined && created === migrationRole) {
611
618
  await flush();