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
@@ -2,7 +2,7 @@ import {
2
2
  creationOrder2,
3
3
  renameColumn2,
4
4
  renameTable2
5
- } from "./8jb4ymh2.js";
5
+ } from "./8bwbwy8h.js";
6
6
  import {
7
7
  quoteIdent,
8
8
  ownedByView,
@@ -15,7 +15,7 @@ import {
15
15
  identitySequence,
16
16
  createTableSql,
17
17
  qualify
18
- } from "./41kv2xdm.js";
18
+ } from "./8xsbagad.js";
19
19
  import {
20
20
  isExactVersion,
21
21
  compareExtensionVersions,
@@ -122,7 +122,7 @@ function privilegeSql(before, after, schema, recreated = new Set) {
122
122
  revoke.push({ sql: grantStatement(object, schema, true), kind: "revoke" });
123
123
  }
124
124
  if (object.kind === "defaultPrivilege") {
125
- revoke.push({ sql: defaultStatement(object, true), kind: "revoke-default" });
125
+ revoke.push({ sql: defaultStatement(object, schema, true), kind: "revoke-default" });
126
126
  }
127
127
  }
128
128
  for (const [key, object] of afterBy) {
@@ -151,7 +151,7 @@ function privilegeSql(before, after, schema, recreated = new Set) {
151
151
  if (previous !== undefined)
152
152
  continue;
153
153
  if (object.kind === "defaultPrivilege") {
154
- defaults.push({ sql: defaultStatement(object, false), kind: "grant-default" });
154
+ defaults.push({ sql: defaultStatement(object, schema, false), kind: "grant-default" });
155
155
  }
156
156
  }
157
157
  return { revoke, prepare: [...roles, ...defaults], grant };
@@ -163,6 +163,9 @@ function createdRoleName(sql) {
163
163
  return quoted.replaceAll('""', '"');
164
164
  return match?.[2];
165
165
  }
166
+ function createRoleOnce(sql) {
167
+ return `do $okm$ begin ${sql}; exception when duplicate_object or unique_violation then null; end $okm$`;
168
+ }
166
169
  function changesRole(sql) {
167
170
  return /^\s*(create|alter)\s+role\b/i.test(sql);
168
171
  }
@@ -195,13 +198,12 @@ function grantStatement(object, schema, revoke) {
195
198
  const direction = revoke ? "from" : "to";
196
199
  return `${verb} ${privilege} on ${grantTarget(object.identity.object, schema)} ${direction} ${quoteIdent(object.identity.role)}`;
197
200
  }
198
- function defaultStatement(object, revoke) {
201
+ function defaultStatement(object, schema, revoke) {
199
202
  const privilege = object.identity.privilege.toLowerCase();
200
203
  assertPrivilege(privilege);
201
204
  const verb = revoke ? "revoke" : "grant";
202
205
  const direction = revoke ? "from" : "to";
203
- const namespace = object.identity.namespace.form === "static" ? object.identity.namespace.name : "public";
204
- return `alter default privileges for role ${quoteIdent(object.identity.forRole)} in schema ${quoteIdent(namespace)} ${verb} ${privilege} on ${objectKindSql(object.identity.objectKind)} ${direction} ${quoteIdent(object.identity.grantee)}`;
206
+ return `alter default privileges for role ${quoteIdent(object.identity.forRole)} in schema ${quoteIdent(schema)} ${verb} ${privilege} on ${objectKindSql(object.identity.objectKind)} ${direction} ${quoteIdent(object.identity.grantee)}`;
205
207
  }
206
208
  function grantTargetKey(object) {
207
209
  if (object.kind === "table" || object.kind === "view" || object.kind === "materializedView" || object.kind === "sequence") {
@@ -222,14 +224,13 @@ function grantTargetKey(object) {
222
224
  });
223
225
  }
224
226
  function grantTarget(object, schema) {
225
- const name = object.namespace.form === "static" ? object.namespace.name : schema;
226
227
  if (object.kind === "sequence")
227
- return `sequence ${qualify(name, object.name)}`;
228
+ return `sequence ${qualify(schema, object.name)}`;
228
229
  if (object.kind === "function")
229
- return functionTarget(object.name, name);
230
+ return functionTarget(object.name, schema);
230
231
  if (object.kind === "namespace")
231
232
  return `schema ${quoteIdent(object.name)}`;
232
- return `table ${qualify(name, object.name)}`;
233
+ return `table ${qualify(schema, object.name)}`;
233
234
  }
234
235
  function functionTarget(name, schema) {
235
236
  const open = name.lastIndexOf("(");
@@ -975,6 +976,189 @@ function functionCalls(expression) {
975
976
  return names;
976
977
  }
977
978
 
979
+ // src/tooling/migrate/sql-lex.ts
980
+ function sqlStatements(sql) {
981
+ return scan(sql).statements;
982
+ }
983
+ function sqlQuoteOpen(sql) {
984
+ return scan(sql).open;
985
+ }
986
+ function scan(sql) {
987
+ const statements = [];
988
+ const parts = [];
989
+ let index = 0;
990
+ let open = false;
991
+ const flush = () => {
992
+ const text = parts.join("").trim();
993
+ parts.length = 0;
994
+ if (text.length > 0)
995
+ statements.push(text);
996
+ };
997
+ while (index < sql.length) {
998
+ if (sql.startsWith("--", index)) {
999
+ parts.push(" ");
1000
+ index = lineCommentEnd(sql, index + 2);
1001
+ continue;
1002
+ }
1003
+ if (sql.startsWith("/*", index)) {
1004
+ parts.push(" ");
1005
+ const block = blockComment(sql, index + 2);
1006
+ index = block.index;
1007
+ if (block.open) {
1008
+ open = true;
1009
+ break;
1010
+ }
1011
+ continue;
1012
+ }
1013
+ const dollar = dollarAt(sql, index);
1014
+ if (dollar !== undefined) {
1015
+ const closed = takeDollar(sql, dollar);
1016
+ parts.push(sql.slice(index, closed.index));
1017
+ index = closed.index;
1018
+ if (closed.open) {
1019
+ open = true;
1020
+ break;
1021
+ }
1022
+ continue;
1023
+ }
1024
+ if (isEscapeString(sql, index)) {
1025
+ const quote = takeQuoted(sql, index + 2, true);
1026
+ parts.push(sql.slice(index, quote.index));
1027
+ index = quote.index;
1028
+ if (quote.open) {
1029
+ open = true;
1030
+ break;
1031
+ }
1032
+ continue;
1033
+ }
1034
+ const char = sql[index];
1035
+ if (char === "'") {
1036
+ const quote = takeQuoted(sql, index + 1, false);
1037
+ parts.push(sql.slice(index, quote.index));
1038
+ index = quote.index;
1039
+ if (quote.open) {
1040
+ open = true;
1041
+ break;
1042
+ }
1043
+ continue;
1044
+ }
1045
+ if (char === '"') {
1046
+ const quote = takeIdent(sql, index + 1);
1047
+ parts.push(sql.slice(index, quote.index));
1048
+ index = quote.index;
1049
+ if (quote.open) {
1050
+ open = true;
1051
+ break;
1052
+ }
1053
+ continue;
1054
+ }
1055
+ if (char === ";") {
1056
+ flush();
1057
+ index += 1;
1058
+ continue;
1059
+ }
1060
+ parts.push(char ?? "");
1061
+ index += 1;
1062
+ }
1063
+ flush();
1064
+ return { statements, open };
1065
+ }
1066
+ function lineCommentEnd(sql, index) {
1067
+ while (index < sql.length && sql.charCodeAt(index) !== 10)
1068
+ index += 1;
1069
+ return index;
1070
+ }
1071
+ function blockComment(sql, index) {
1072
+ let depth = 1;
1073
+ while (index < sql.length) {
1074
+ if (sql.startsWith("/*", index)) {
1075
+ depth += 1;
1076
+ index += 2;
1077
+ continue;
1078
+ }
1079
+ if (sql.startsWith("*/", index)) {
1080
+ depth -= 1;
1081
+ index += 2;
1082
+ if (depth === 0)
1083
+ return { index, open: false };
1084
+ continue;
1085
+ }
1086
+ index += 1;
1087
+ }
1088
+ return { index, open: true };
1089
+ }
1090
+ function dollarAt(sql, index) {
1091
+ if (sql.charCodeAt(index) !== 36)
1092
+ return;
1093
+ const next = index + 1;
1094
+ if (sql.charCodeAt(next) === 36)
1095
+ return { tag: "", end: next + 1 };
1096
+ if (!isIdentStart(sql.charCodeAt(next)))
1097
+ return;
1098
+ let cursor = next + 1;
1099
+ while (isIdentCont(sql.charCodeAt(cursor)))
1100
+ cursor += 1;
1101
+ if (sql.charCodeAt(cursor) !== 36)
1102
+ return;
1103
+ return { tag: sql.slice(next, cursor), end: cursor + 1 };
1104
+ }
1105
+ function takeDollar(sql, dollar) {
1106
+ const closer = `$${dollar.tag}$`;
1107
+ const found = sql.indexOf(closer, dollar.end);
1108
+ if (found === -1)
1109
+ return { index: sql.length, open: true };
1110
+ return { index: found + closer.length, open: false };
1111
+ }
1112
+ function takeQuoted(sql, index, escape) {
1113
+ while (index < sql.length) {
1114
+ const char = sql[index];
1115
+ if (escape && char === "\\") {
1116
+ if (index + 1 >= sql.length)
1117
+ return { index: sql.length, open: true };
1118
+ index += 2;
1119
+ continue;
1120
+ }
1121
+ if (char === "'") {
1122
+ if (sql[index + 1] === "'") {
1123
+ index += 2;
1124
+ continue;
1125
+ }
1126
+ return { index: index + 1, open: false };
1127
+ }
1128
+ index += 1;
1129
+ }
1130
+ return { index, open: true };
1131
+ }
1132
+ function takeIdent(sql, index) {
1133
+ while (index < sql.length) {
1134
+ if (sql[index] === '"') {
1135
+ if (sql[index + 1] === '"') {
1136
+ index += 2;
1137
+ continue;
1138
+ }
1139
+ return { index: index + 1, open: false };
1140
+ }
1141
+ index += 1;
1142
+ }
1143
+ return { index, open: true };
1144
+ }
1145
+ function isEscapeString(sql, index) {
1146
+ const code = sql.charCodeAt(index);
1147
+ if (code !== 69 && code !== 101)
1148
+ return false;
1149
+ if (sql.charCodeAt(index + 1) !== 39)
1150
+ return false;
1151
+ if (index > 0 && isIdentCont(sql.charCodeAt(index - 1)))
1152
+ return false;
1153
+ return true;
1154
+ }
1155
+ function isIdentStart(code) {
1156
+ return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
1157
+ }
1158
+ function isIdentCont(code) {
1159
+ return isIdentStart(code) || code >= 48 && code <= 57;
1160
+ }
1161
+
978
1162
  // src/tooling/migrate/values.ts
979
1163
  function parseReplace(flag) {
980
1164
  const eq = flag.indexOf("=");
@@ -1376,9 +1560,16 @@ function parsePlan(text) {
1376
1560
  let behavior;
1377
1561
  const allows = [];
1378
1562
  const sql = [];
1379
- while (index < lines.length && (lines[index] ?? "") !== "") {
1563
+ let quoteOpen = false;
1564
+ while (index < lines.length && (quoteOpen || (lines[index] ?? "") !== "")) {
1380
1565
  const line = lines[index] ?? "";
1381
1566
  index += 1;
1567
+ if (quoteOpen) {
1568
+ sql.push(line);
1569
+ quoteOpen = sqlQuoteOpen(sql.join(`
1570
+ `));
1571
+ continue;
1572
+ }
1382
1573
  if (line.startsWith("-- class: ")) {
1383
1574
  stepClass = readClass(line.slice("-- class: ".length));
1384
1575
  sawClass = true;
@@ -1400,8 +1591,11 @@ function parsePlan(text) {
1400
1591
  behavior = "change";
1401
1592
  else if (line.startsWith("-- okm-allow"))
1402
1593
  allows.push(readAllow(line));
1403
- else if (!line.startsWith("--"))
1594
+ else if (!line.startsWith("--")) {
1404
1595
  sql.push(line);
1596
+ quoteOpen = sqlQuoteOpen(sql.join(`
1597
+ `));
1598
+ }
1405
1599
  }
1406
1600
  const statement = sql.join(`
1407
1601
  `).replace(/;\s*$/, "");
@@ -2428,4 +2622,165 @@ function order(value) {
2428
2622
  return value;
2429
2623
  }
2430
2624
 
2431
- export { canonicalTypeName, createdRoleName, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames };
2625
+ // src/tooling/migrate/policy.ts
2626
+ var READ = new Set([
2627
+ "plan",
2628
+ "status",
2629
+ "check",
2630
+ "drift",
2631
+ "verify",
2632
+ "pull",
2633
+ "catalog-export",
2634
+ "inspect"
2635
+ ]);
2636
+ var ALLOWED = new Set(["expand", "reference", "provision"]);
2637
+ var FORWARD_ONLY = new Set(["drop", "rollback"]);
2638
+ function assertTargetPolicy(target, operation, allowProtected = false) {
2639
+ if (FORWARD_ONLY.has(operation)) {
2640
+ throw new OkmError2("invalid", "There is no down migration.", {
2641
+ fix: { summary: "Recovery is a new forward migration." }
2642
+ });
2643
+ }
2644
+ if (!target.protected || allowProtected)
2645
+ return;
2646
+ if (READ.has(operation) || ALLOWED.has(operation))
2647
+ return;
2648
+ throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
2649
+ kind: "forbidden",
2650
+ fix: {
2651
+ summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
2652
+ }
2653
+ });
2654
+ }
2655
+ function assertTargetAlias(targets) {
2656
+ const groups = new Map;
2657
+ for (const target of targets) {
2658
+ const key = `${target.host}
2659
+ ${target.port}
2660
+ ${target.database}`;
2661
+ const group = groups.get(key);
2662
+ if (group === undefined)
2663
+ groups.set(key, [target]);
2664
+ else
2665
+ group.push(target);
2666
+ }
2667
+ for (const group of groups.values()) {
2668
+ const protectedTarget = group.find((target) => target.protected);
2669
+ const open = group.find((target) => !target.protected);
2670
+ if (protectedTarget !== undefined && open !== undefined) {
2671
+ throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
2672
+ fix: {
2673
+ summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
2674
+ }
2675
+ });
2676
+ }
2677
+ }
2678
+ }
2679
+ function assertDirectConnection(url, allowPooler) {
2680
+ if (allowPooler)
2681
+ return;
2682
+ const endpoint = endpointOf(url);
2683
+ const parsed = new URL(url);
2684
+ const host = endpoint.host;
2685
+ if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || endpoint.port === "6432" || parsed.searchParams.get("pgbouncer") === "true") {
2686
+ throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
2687
+ }
2688
+ }
2689
+ async function assertStableBackend(readPid, allowPooler) {
2690
+ if (allowPooler)
2691
+ return;
2692
+ const first = await readPid();
2693
+ const second = await readPid();
2694
+ if (first === second)
2695
+ return;
2696
+ throw new OkmError2("OKM1854", `The backend pid changed from ${first} to ${second}. A pooler is handing out a new session between statements.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
2697
+ }
2698
+ function listTargets(config) {
2699
+ if (config.database !== undefined && config.targets !== undefined) {
2700
+ throw new OkmError2("invalid", "Set database or targets, not both.", {
2701
+ fix: { summary: "database is the one target named default. Use targets for more than one." }
2702
+ });
2703
+ }
2704
+ const entries = [];
2705
+ if (config.database !== undefined)
2706
+ entries.push(["default", config.database]);
2707
+ if (config.targets !== undefined) {
2708
+ for (const name of Object.keys(config.targets).sort()) {
2709
+ const input = config.targets[name];
2710
+ if (input !== undefined)
2711
+ entries.push([name, input]);
2712
+ }
2713
+ }
2714
+ return entries.map(([name, input]) => resolveTarget(name, input));
2715
+ }
2716
+ function selectTarget(config, name) {
2717
+ const targets = listTargets(config);
2718
+ if (targets.length === 0) {
2719
+ throw new OkmError2("OKM1845", "No target is configured.", {
2720
+ fix: { summary: "Set database or targets in okmodel.config.ts." }
2721
+ });
2722
+ }
2723
+ if (name === undefined) {
2724
+ if (targets.length === 1) {
2725
+ const only = targets[0];
2726
+ if (only !== undefined)
2727
+ return only;
2728
+ }
2729
+ throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
2730
+ fix: { summary: "Pass --target <name>. The command does not guess." }
2731
+ });
2732
+ }
2733
+ const found = targets.find((target) => target.name === name);
2734
+ if (found === undefined) {
2735
+ throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
2736
+ fix: { summary: "Pass a name from targets, or database for the target named default." }
2737
+ });
2738
+ }
2739
+ return found;
2740
+ }
2741
+ function resolveTarget(name, input) {
2742
+ if (typeof input !== "string" && isReplicaTarget(input)) {
2743
+ throw new OkmError2("OKM1845", `Target ${name} is a replica. Migrations run on the primary only.`, {
2744
+ fix: {
2745
+ summary: "Set database or targets to the primary URL. A replica is not a migration target."
2746
+ }
2747
+ });
2748
+ }
2749
+ const url = typeof input === "string" ? input : input.url;
2750
+ const protectedTarget = typeof input === "string" ? false : input.protected === true;
2751
+ if (url === undefined || url.length === 0) {
2752
+ throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
2753
+ fix: { summary: "Set the target url. A target is not inferred from the environment." }
2754
+ });
2755
+ }
2756
+ const endpoint = endpointOf(url);
2757
+ return {
2758
+ name,
2759
+ url,
2760
+ protected: protectedTarget,
2761
+ host: endpoint.host,
2762
+ port: endpoint.port,
2763
+ database: endpoint.database
2764
+ };
2765
+ }
2766
+ function isReplicaTarget(input) {
2767
+ return "primary" in input || "replicas" in input || "weight" in input || "pool" in input;
2768
+ }
2769
+ function endpointOf(url) {
2770
+ let parsed;
2771
+ try {
2772
+ parsed = new URL(url);
2773
+ } catch {
2774
+ throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
2775
+ fix: { summary: "Pass a postgres:// URL for the target." }
2776
+ });
2777
+ }
2778
+ const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
2779
+ return {
2780
+ host: parsed.hostname.toLowerCase(),
2781
+ port: parsed.port.length > 0 ? parsed.port : "5432",
2782
+ database
2783
+ };
2784
+ }
2785
+
2786
+ export { canonicalTypeName, createdRoleName, createRoleOnce, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, sqlStatements, parseReplace, planMigration, formatPlan, parsePlan, staleRenames, assertTargetPolicy, assertTargetAlias, assertDirectConnection, assertStableBackend, listTargets, selectTarget };
@@ -42,12 +42,21 @@ var FIND_OPTIONS = [
42
42
  "limit",
43
43
  "orderBy",
44
44
  "select",
45
+ "route",
45
46
  "signal",
46
47
  "timeout",
47
48
  "where"
48
49
  ];
49
- var ONE_OPTIONS = ["include", "orderBy", "select", "signal", "timeout", "where"];
50
- var FILTER_OPTIONS = ["signal", "timeout", "where"];
50
+ var ONE_OPTIONS = [
51
+ "include",
52
+ "orderBy",
53
+ "route",
54
+ "select",
55
+ "signal",
56
+ "timeout",
57
+ "where"
58
+ ];
59
+ var FILTER_OPTIONS = ["route", "signal", "timeout", "where"];
51
60
  function readOptionNames(op) {
52
61
  if (op === "find")
53
62
  return FIND_OPTIONS;
@@ -524,7 +533,9 @@ function emitIn(column, ref, name, value, sink) {
524
533
  sink.text(")");
525
534
  }
526
535
  function emitOr(schema, table, value, sink, alias, depth) {
527
- if (!Array.isArray(value) || value.length === 0) {
536
+ if (!Array.isArray(value))
537
+ orFail(false);
538
+ if (value.length === 0) {
528
539
  sink.text("false");
529
540
  sink.mark(":0");
530
541
  return;
@@ -550,7 +561,7 @@ function emitOr(schema, table, value, sink, alias, depth) {
550
561
  emitPredicate(schema, table, key, item, sink, alias, depth);
551
562
  }
552
563
  if (!wrote)
553
- sink.text("true");
564
+ orFail(true);
554
565
  sink.text(")");
555
566
  }
556
567
  sink.text(")");
@@ -835,6 +846,45 @@ function list(names) {
835
846
  function isRecord(value) {
836
847
  return typeof value === "object" && value !== null && !Array.isArray(value) && !isOperator(value);
837
848
  }
849
+ function effectivePredicate(where) {
850
+ if (where === undefined)
851
+ return false;
852
+ if (isOperator(where)) {
853
+ const name = operatorName(where);
854
+ const value = operatorValue(where);
855
+ if (name === "or") {
856
+ if (!Array.isArray(value))
857
+ orFail(false);
858
+ if (value.length === 0)
859
+ return true;
860
+ for (const branch of value) {
861
+ if (!effectivePredicate(branch))
862
+ orFail(true);
863
+ }
864
+ return true;
865
+ }
866
+ if (name === "and") {
867
+ return Array.isArray(value) && value.some((part) => effectivePredicate(part));
868
+ }
869
+ if (name === "not")
870
+ return effectivePredicate(value);
871
+ return true;
872
+ }
873
+ if (!isRecord(where))
874
+ return true;
875
+ for (const key of Object.keys(where)) {
876
+ if (where[key] !== undefined)
877
+ return true;
878
+ }
879
+ return false;
880
+ }
881
+ function orFail(empty) {
882
+ throw new OkmError2("OKM1121", empty ? "or() branch is empty." : "Pass an array: or([a, b]).", {
883
+ fix: {
884
+ summary: empty ? "Give every or() branch a predicate. An empty branch matches every row." : "Pass an array: or([a, b])."
885
+ }
886
+ });
887
+ }
838
888
  var FAIL_FIX = {
839
889
  OKM1121: "Use eq for an object value, or has, none, or every for a relation.",
840
890
  OKM1101: "Pass limit, or call .all(reason)."
@@ -866,4 +916,4 @@ function hasMany(include) {
866
916
  return Object.keys(include).length > 0;
867
917
  }
868
918
 
869
- export { withRowFilters, installArchiveInspect, archiveRules, readOptionNames, installOperatorSql, readNeedsOperatorSql, emitRelationFrom, bindCall, compileCall, decodeResult, logicalIntent, appliedRules, emitJoin, emitWhere, emitOrder, emitPrimaryOrder, selectedColumns, parseOrder, rejectKeys, readLimit, takeObject, projectExpr, jsonExpr, decodeRow, cell, indexes, quote, list, isRecord, registerFailFix, fail };
919
+ export { withRowFilters, installArchiveInspect, archiveRules, readOptionNames, installOperatorSql, readNeedsOperatorSql, emitRelationFrom, bindCall, compileCall, decodeResult, logicalIntent, appliedRules, emitJoin, emitWhere, emitOrder, emitPrimaryOrder, selectedColumns, parseOrder, rejectKeys, readLimit, takeObject, projectExpr, jsonExpr, decodeRow, cell, indexes, quote, list, isRecord, effectivePredicate, registerFailFix, fail };
@@ -1,29 +1,29 @@
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
- import {
10
- assertTargetPolicy
11
- } from "./q6zsyh31.js";
9
+ } from "./szm0cksa.js";
12
10
  import {
13
11
  canonicalTypeName,
14
12
  createdRoleName,
13
+ createRoleOnce,
15
14
  DEFAULT_BACKFILL_PAUSE_MS,
16
15
  DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS,
17
16
  boundaryQuery,
18
17
  checkedCast,
19
18
  omitManagedObjects,
20
19
  planMigration,
21
- parsePlan
22
- } from "./n5y9zyj1.js";
20
+ parsePlan,
21
+ assertTargetPolicy
22
+ } from "./ga3sfy93.js";
23
23
  import {
24
24
  quoteIdent,
25
25
  qualify
26
- } from "./41kv2xdm.js";
26
+ } from "./8xsbagad.js";
27
27
  import {
28
28
  alignExtensionVersions
29
29
  } from "./8ktwd3rh.js";
@@ -33,7 +33,7 @@ import {
33
33
  } from "./g7ez901n.js";
34
34
  import {
35
35
  snakeCase
36
- } from "./jsd07prz.js";
36
+ } from "./ntneex2a.js";
37
37
  import {
38
38
  quoteLiteral
39
39
  } from "./94ez8kzh.js";
@@ -98,8 +98,10 @@ async function runUnit(connection, unit, run, session) {
98
98
  await rebuildInvalidIndex(connection, item.step.sql);
99
99
  const created = createdRoleName(item.step.sql);
100
100
  const existed = created !== undefined && await roleExists(connection, created);
101
- if (created === undefined || !existed)
101
+ if (created === undefined)
102
102
  await connection.execute(item.step.sql);
103
+ else if (!existed)
104
+ await connection.execute(createRoleOnce(item.step.sql));
103
105
  if (created !== undefined && created === run.migrationRole) {
104
106
  if (!existed) {
105
107
  await grantSchema(connection, run.schema ?? "public", created);
@@ -305,7 +307,7 @@ function failStep(unit, index, error) {
305
307
  if (error instanceof OkmError2)
306
308
  return error;
307
309
  const message = error instanceof Error ? error.message : "The step failed.";
308
- return new OkmError2("invalid", `Migration ${unit.migrationId} failed at step ${String(index)}: ${message}`, {
310
+ return new OkmError2("invalid", `Migration ${unit.migrationId} failed at step ${String(index + 1)}: ${message}`, {
309
311
  cause: error,
310
312
  fix: { summary: "Fix the step and run apply again. Finished steps are not repeated." }
311
313
  });
@@ -377,8 +379,10 @@ function loadMigrations(directory) {
377
379
  });
378
380
  }
379
381
  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 });
382
+ const stored = parseCatalog2(readFileSync(catalogPath, "utf8"));
383
+ const startup = startupCatalog2(stored);
384
+ const hash = catalogHash2(startup);
385
+ migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
382
386
  }
383
387
  return migrations;
384
388
  }
@@ -601,11 +605,12 @@ async function installSnapshot(connection, head, schema, reference, session, mig
601
605
  await assumeRole(connection, created, session);
602
606
  continue;
603
607
  }
608
+ const sql = created === undefined ? step.sql : createRoleOnce(step.sql);
604
609
  if (step.transactional && !statementOutsideTransaction(step.sql)) {
605
- batch.push(step.sql);
610
+ batch.push(sql);
606
611
  } else {
607
612
  await flush();
608
- await connection.execute(step.sql);
613
+ await connection.execute(sql);
609
614
  }
610
615
  if (created !== undefined && created === migrationRole) {
611
616
  await flush();