okmodel 0.8.0 → 0.9.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 (202) hide show
  1. package/README.md +14 -0
  2. package/dist/dialects/pg/ext/btree-gin.d.ts +26 -0
  3. package/dist/dialects/pg/ext/btree-gin.js +20 -0
  4. package/dist/dialects/pg/ext/btree-gist.d.ts +26 -0
  5. package/dist/dialects/pg/ext/btree-gist.js +20 -0
  6. package/dist/dialects/pg/ext/call.d.ts +53 -0
  7. package/dist/dialects/pg/ext/citext.js +1 -1
  8. package/dist/dialects/pg/ext/feature.d.ts +26 -0
  9. package/dist/dialects/pg/ext/fuzzystrmatch.d.ts +34 -0
  10. package/dist/dialects/pg/ext/fuzzystrmatch.js +33 -0
  11. package/dist/dialects/pg/ext/hstore.d.ts +41 -0
  12. package/dist/dialects/pg/ext/hstore.js +29 -0
  13. package/dist/dialects/pg/ext/index.d.ts +56 -0
  14. package/dist/dialects/pg/ext/index.js +1 -1
  15. package/dist/dialects/pg/ext/ltree.d.ts +41 -0
  16. package/dist/dialects/pg/ext/ltree.js +29 -0
  17. package/dist/dialects/pg/ext/pg-trgm.js +4 -13
  18. package/dist/dialects/pg/ext/pgcrypto.d.ts +33 -0
  19. package/dist/dialects/pg/ext/pgcrypto.js +33 -0
  20. package/dist/dialects/pg/ext/unaccent.d.ts +26 -0
  21. package/dist/dialects/pg/ext/unaccent.js +21 -0
  22. package/dist/dialects/pg/ext/using.d.ts +23 -0
  23. package/dist/dialects/pg/ext/versions.d.ts +36 -0
  24. package/dist/dialects/pg/external.d.ts +19 -0
  25. package/dist/dialects/pg/fn/index.js +4 -4
  26. package/dist/dialects/pg/index.d.ts +1 -0
  27. package/dist/dialects/pg/index.js +16 -11
  28. package/dist/dialects/pg/view/index.js +5 -5
  29. package/dist/dialects/pg/view/scratch.d.ts +4 -1
  30. package/dist/runtime/artifact-file.d.ts +22 -0
  31. package/dist/runtime/client.d.ts +10 -0
  32. package/dist/runtime/drift-detail.d.ts +2 -1
  33. package/dist/runtime/drift.d.ts +3 -10
  34. package/dist/runtime/pg/bun.js +4 -4
  35. package/dist/runtime/pg/neon.js +4 -4
  36. package/dist/runtime/pg/pg.js +4 -4
  37. package/dist/runtime/pg/pglite.js +4 -4
  38. package/dist/runtime/pg/postgresjs.js +4 -4
  39. package/dist/runtime/server-check.d.ts +36 -6
  40. package/dist/runtime/traits/index.js +5 -5
  41. package/dist/runtime/validate/index.js +1 -1
  42. package/dist/shared/{4m6hskg8.js → 0j6qe91y.js} +4 -4
  43. package/dist/shared/{14x60fbf.js → 1mxm04fy.js} +5 -5
  44. package/dist/shared/{b6d6p0zc.js → 2szgv6yj.js} +5 -5
  45. package/dist/shared/{f4jy1j7x.js → 2zdxkt7b.js} +909 -100
  46. package/dist/shared/{mnvjvqyp.js → 3456242y.js} +17 -17
  47. package/dist/shared/3t6kw83m.js +1 -1
  48. package/dist/shared/{6m78c8ss.js → 5kyb3tqx.js} +1 -1
  49. package/dist/shared/61bmrmhr.js +35 -0
  50. package/dist/shared/{zbqbmmrp.js → 62b603gr.js} +8 -8
  51. package/dist/shared/{jgqj11w4.js → 6f0z3hzh.js} +4 -0
  52. package/dist/shared/{dpc2r7rb.js → 6nae0spm.js} +150 -89
  53. package/dist/shared/{0wrea3jq.js → 71n0jmhc.js} +1 -1
  54. package/dist/shared/7ba5h6pb.js +17 -0
  55. package/dist/shared/{k86n8vq3.js → 8jesqsrk.js} +1 -1
  56. package/dist/shared/{qd9hhd8f.js → 8n688yjg.js} +1 -1
  57. package/dist/shared/{zgex00fd.js → 8qzkd9f3.js} +5 -5
  58. package/dist/shared/{mkrn359n.js → 90sg0w5a.js} +159 -31
  59. package/dist/shared/{7ghwamrf.js → 99qp9vtg.js} +1 -1
  60. package/dist/shared/{bxas03mq.js → 9dtgn1z2.js} +1 -1
  61. package/dist/shared/{p5nh3r8q.js → aksb89mn.js} +2 -2
  62. package/dist/shared/{wcrheftc.js → bphrgwz9.js} +2 -2
  63. package/dist/shared/{wz2a78mp.js → c3mvnq7a.js} +1 -1
  64. package/dist/shared/{pan0370z.js → ccsmhv9r.js} +24 -6
  65. package/dist/shared/{n0jmpgj1.js → cexb7z6b.js} +1 -1
  66. package/dist/shared/{wvdjznah.js → cf8zdz5p.js} +5 -5
  67. package/dist/shared/{3449rcae.js → ckz05z15.js} +4 -4
  68. package/dist/shared/{35q0n9tz.js → dd5pmnky.js} +7 -7
  69. package/dist/shared/{e8zyaqvj.js → ds229x4r.js} +1 -1
  70. package/dist/{tooling/shared/yw30vnq2.js → shared/e275q7sd.js} +1 -1
  71. package/dist/shared/{mfedzr74.js → ebhz8vh6.js} +4 -4
  72. package/dist/shared/{z581d7ny.js → fdsz3w80.js} +2 -2
  73. package/dist/shared/{sjk86yrx.js → fk9vef30.js} +84 -73
  74. package/dist/shared/{needfe47.js → g89fmz5s.js} +1 -1
  75. package/dist/shared/{m4xff2a6.js → ghqzdp94.js} +2 -2
  76. package/dist/shared/h9811jjm.js +17 -0
  77. package/dist/shared/{kj9d8maq.js → jpybhf98.js} +1 -1
  78. package/dist/shared/kzgrnckd.js +29 -0
  79. package/dist/shared/{agsfb86s.js → mazpbcdj.js} +4 -4
  80. package/dist/shared/{195pzzzz.js → mgaq2sd3.js} +10 -7
  81. package/dist/shared/{5mtf6cj3.js → mvmm3xpb.js} +5 -2
  82. package/dist/shared/{p3xbdcvt.js → myk8fswx.js} +55 -6
  83. package/dist/shared/{njga14v4.js → n306gqkd.js} +8 -8
  84. package/dist/shared/n5whk3mt.js +665 -0
  85. package/dist/shared/{bah9nbg3.js → npdftanb.js} +12 -20
  86. package/dist/shared/{16kvjebh.js → pg4ezgyf.js} +7 -26
  87. package/dist/{tooling/shared/08kemxjd.js → shared/pgsak1yw.js} +1 -1
  88. package/dist/shared/{jp36k7dp.js → qvwwp1mp.js} +61 -40
  89. package/dist/shared/rbxt795q.js +1766 -0
  90. package/dist/shared/{g5yc06ka.js → s7he5e5d.js} +1 -1
  91. package/dist/shared/{hfqmpasr.js → ss2kv2rs.js} +5 -5
  92. package/dist/shared/stfa9xcw.js +450 -0
  93. package/dist/{tooling/shared/rs1jb051.js → shared/tr64w5p0.js} +1 -1
  94. package/dist/shared/{jebgrkeq.js → tw1nvvfj.js} +2 -2
  95. package/dist/shared/{v1wad97q.js → wvtjg5m3.js} +1 -1
  96. package/dist/shared/xqacq37x.js +61 -0
  97. package/dist/shared/{n05z9nwz.js → xtsydxjg.js} +1 -1
  98. package/dist/shared/{jx2m62cs.js → xv8qj97g.js} +5 -5
  99. package/dist/shared/{63f4ksbz.js → xwhdcrm4.js} +1 -1
  100. package/dist/shared/{hm5f8jby.js → yvabyg9k.js} +6 -6
  101. package/dist/shared/yybhre6n.js +136 -0
  102. package/dist/shared/zdqf8k5d.js +8 -0
  103. package/dist/tooling/cli.js +94 -22
  104. package/dist/tooling/driver-run.js +15 -332
  105. package/dist/tooling/github/comment.d.ts +50 -0
  106. package/dist/tooling/github/post-comment.d.ts +17 -0
  107. package/dist/tooling/migrate/commands.d.ts +2 -1
  108. package/dist/tooling/migrate/config.d.ts +13 -0
  109. package/dist/tooling/migrate/export.d.ts +165 -0
  110. package/dist/tooling/migrate/ext-surface.d.ts +80 -0
  111. package/dist/tooling/migrate/ext.d.ts +31 -4
  112. package/dist/tooling/migrate/import-drizzle.d.ts +40 -0
  113. package/dist/tooling/migrate/index.d.ts +3 -0
  114. package/dist/tooling/migrate/index.js +55 -10
  115. package/dist/tooling/migrate/lint-rules.d.ts +18 -1
  116. package/dist/tooling/migrate/lint.d.ts +14 -1
  117. package/dist/tooling/migrate/place.d.ts +18 -0
  118. package/dist/tooling/migrate/plan-json.d.ts +65 -0
  119. package/dist/tooling/migrate/project.d.ts +10 -1
  120. package/dist/tooling/migrate/pull-emit.d.ts +98 -0
  121. package/dist/tooling/migrate/pull-routines.d.ts +126 -0
  122. package/dist/tooling/migrate/pull-verify.d.ts +26 -0
  123. package/dist/tooling/migrate/pull.d.ts +62 -0
  124. package/dist/tooling/migrate/reprint.d.ts +32 -0
  125. package/dist/tooling/migrate/sandbox.d.ts +8 -2
  126. package/dist/tooling/migrate/schema-api.d.ts +43 -0
  127. package/dist/tooling/migrate/sql-lex.d.ts +18 -0
  128. package/dist/tooling/shared/{3m1hkg7z.js → 04svnxxj.js} +4 -4
  129. package/dist/tooling/shared/{ppnnnvj8.js → 0j0jtse1.js} +1 -1
  130. package/dist/tooling/shared/{z8d7ptht.js → 0r05zhyq.js} +5 -5
  131. package/dist/tooling/shared/{a6pp9d12.js → 1800eymw.js} +3 -3
  132. package/dist/tooling/shared/{cmptmb4f.js → 35zemrmf.js} +1 -1
  133. package/dist/tooling/shared/{w7qe8sj1.js → 3rmaz3pq.js} +1 -1
  134. package/dist/{shared/q0cqt2sp.js → tooling/shared/3yn79q5m.js} +1 -1
  135. package/dist/tooling/shared/{2revefqt.js → 57btdkgm.js} +2 -2
  136. package/dist/tooling/shared/{ma2340rx.js → 5bmksndh.js} +2 -2
  137. package/dist/tooling/shared/{9xzm7hjg.js → 5p8km5jb.js} +3 -3
  138. package/dist/tooling/shared/{xhrnjch9.js → 5x3pf400.js} +3 -3
  139. package/dist/tooling/shared/{wwnekq5n.js → 5zrzffnk.js} +1 -1
  140. package/dist/tooling/shared/{p7f08ktn.js → 62fxpc1p.js} +7 -7
  141. package/dist/tooling/shared/{7ae7rjmn.js → 68tf18v4.js} +2 -2
  142. package/dist/tooling/shared/{rf83gr2d.js → 6vk28rjp.js} +7 -3
  143. package/dist/tooling/shared/76pv6qff.js +9 -0
  144. package/dist/tooling/shared/{bz2dqhab.js → 7aews1zg.js} +3 -3
  145. package/dist/tooling/shared/{9kazs5p6.js → 7b3xp9gc.js} +3 -3
  146. package/dist/tooling/shared/{4d2w25vw.js → 7pxncnzs.js} +2 -2
  147. package/dist/tooling/shared/{zvnhh1n0.js → 829fjxyw.js} +2 -2
  148. package/dist/tooling/shared/{dzx85q70.js → 8m5znpdt.js} +1 -1
  149. package/dist/tooling/shared/{g9ad4n80.js → 8yr7ges1.js} +7 -7
  150. package/dist/tooling/shared/{tn8gay14.js → 9qr9rkbh.js} +6 -6
  151. package/dist/tooling/shared/{8sqrsw3s.js → 9sfbtwsk.js} +3 -3
  152. package/dist/{shared/bnby0esn.js → tooling/shared/9wsnsvbf.js} +1 -1
  153. package/dist/tooling/shared/{aftb2mwr.js → a2xe04bm.js} +1 -1
  154. package/dist/tooling/shared/{nf7jmhfa.js → a4pzghha.js} +2 -2
  155. package/dist/tooling/shared/{y5656f0e.js → am04v8na.js} +5 -5
  156. package/dist/tooling/shared/{417zdsk7.js → asgr07mp.js} +5 -5
  157. package/dist/tooling/shared/{pqy21k24.js → c0xevn1h.js} +4 -4
  158. package/dist/tooling/shared/dhatassp.js +134 -0
  159. package/dist/tooling/shared/{spk1jg3r.js → ezqn88nj.js} +8 -8
  160. package/dist/tooling/shared/{6nyfyw70.js → f77nnfqy.js} +2 -2
  161. package/dist/tooling/shared/{wt7j7nf6.js → fh07z1dt.js} +1 -1
  162. package/dist/tooling/shared/{3rrw5w8f.js → gqwevek4.js} +1 -1
  163. package/dist/tooling/shared/{f9mphxej.js → hfq3n3bd.js} +346 -19
  164. package/dist/tooling/shared/{vs0t6qt8.js → j664qjrt.js} +2 -2
  165. package/dist/tooling/shared/{733a1tq3.js → jpgmjsx3.js} +3 -3
  166. package/dist/tooling/shared/{mtrqn2hn.js → jr2nmr81.js} +22 -30
  167. package/dist/{shared/mtw6jz76.js → tooling/shared/jsjjz875.js} +1 -1
  168. package/dist/tooling/shared/{2wtvc7rw.js → kbqnrs1x.js} +2 -2
  169. package/dist/tooling/shared/{gfvtj6d0.js → kz715295.js} +7 -7
  170. package/dist/tooling/shared/{a7q7t4n3.js → m3xt8b1h.js} +2 -2
  171. package/dist/tooling/shared/{dw08f3m8.js → m4hhb8aa.js} +2 -2
  172. package/dist/tooling/shared/{y031jct9.js → mw5npye0.js} +2 -2
  173. package/dist/tooling/shared/{vraj6d74.js → nxq95eb6.js} +3 -3
  174. package/dist/tooling/shared/{fb9k2qwd.js → qartgazh.js} +11 -30
  175. package/dist/tooling/shared/{xjs6zqtc.js → rcywj2w8.js} +9 -9
  176. package/dist/tooling/shared/{dy672fz6.js → s6qha57m.js} +2 -2
  177. package/dist/tooling/shared/{vx86qjv8.js → shjs0379.js} +3 -3
  178. package/dist/tooling/shared/{345tbc2k.js → sjwkt1pg.js} +1 -1
  179. package/dist/tooling/shared/{rq5kk8j6.js → stebqyyd.js} +3 -3
  180. package/dist/tooling/shared/{yk9mp7vb.js → t4xveemq.js} +4 -4
  181. package/dist/tooling/shared/{djydjpqt.js → t6zjeake.js} +5 -5
  182. package/dist/tooling/shared/{zhwnfyb2.js → tcwpjvxe.js} +1 -1
  183. package/dist/tooling/shared/{72fyg4zv.js → tpssp7ac.js} +1 -1
  184. package/dist/tooling/shared/{jne7b93p.js → v6j1ep6w.js} +3 -3
  185. package/dist/tooling/shared/{ec71zqgt.js → vwr2y833.js} +2 -2
  186. package/dist/tooling/shared/{j9t5s12a.js → w17sj05d.js} +2 -2
  187. package/dist/tooling/shared/{3tq8pnb8.js → w5d89rb4.js} +1 -1
  188. package/dist/tooling/shared/{smn6kz1p.js → wbwq1jsa.js} +2 -2
  189. package/dist/tooling/shared/{pwsytxm5.js → wezxf3h8.js} +1 -1
  190. package/dist/tooling/shared/{5bvw2nev.js → yb4kd19s.js} +1 -1
  191. package/dist/tooling/shared/{gmcf9cxf.js → ybdew6j7.js} +2 -2
  192. package/dist/tooling/shared/{ep8zh3bk.js → yfedr1zb.js} +1 -1
  193. package/dist/tooling/shared/{ddjtfgnx.js → yhp829bx.js} +3 -3
  194. package/dist/tooling/shared/{yfr8hzhm.js → ytk1tjs0.js} +1 -1
  195. package/dist/tooling/shared/{03k6res6.js → zs9aymcp.js} +1 -1
  196. package/dist/tooling/shared/zx3v9phv.js +8 -0
  197. package/dist/tooling/testing/index.js +2 -2
  198. package/package.json +38 -9
  199. package/dist/shared/fjpnhqan.js +0 -71
  200. package/dist/shared/n41w4fbz.js +0 -113
  201. package/dist/tooling/shared/pw8fgahx.js +0 -69
  202. /package/dist/shared/{ae4j5f42.js → mfvx6kjx.js} +0 -0
@@ -1,7 +1,3 @@
1
- import {
2
- VALIDATION_NOT_IMPORTED2,
3
- VALIDATION_NOT_IMPORTED_FIX2
4
- } from "./n442h54c.js";
5
1
  import {
6
2
  roleObject,
7
3
  grantObject,
@@ -9,12 +5,20 @@ import {
9
5
  serializeCatalog2,
10
6
  catalogHash2,
11
7
  startupCatalog2,
8
+ startupHash2,
12
9
  catalogFileText2,
13
10
  parseCatalog2
14
11
  } from "./12z7aznj.js";
12
+ import {
13
+ quoteIdent2
14
+ } from "./fk9vef30.js";
15
+ import {
16
+ VALIDATION_NOT_IMPORTED2,
17
+ VALIDATION_NOT_IMPORTED_FIX2
18
+ } from "./n442h54c.js";
15
19
  import {
16
20
  open2
17
- } from "./35q0n9tz.js";
21
+ } from "./dd5pmnky.js";
18
22
  import {
19
23
  sqlStatements,
20
24
  transactionControl,
@@ -35,7 +39,7 @@ import {
35
39
  assertTargetAlias,
36
40
  listTargets,
37
41
  selectTarget
38
- } from "./dpc2r7rb.js";
42
+ } from "./6nae0spm.js";
39
43
  import {
40
44
  contributedFields
41
45
  } from "./n0dgs0pb.js";
@@ -78,6 +82,374 @@ function projectPath(cwd, ...parts) {
78
82
  return resolve(cwd, ...parts);
79
83
  }
80
84
 
85
+ // src/tooling/migrate/export.ts
86
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
87
+ import { isAbsolute, join } from "node:path";
88
+ var EXPORT_FORMAT = 1;
89
+ var LOGICAL_FORMAT = 1;
90
+ function snapshotFromCatalog(source, namespace, database) {
91
+ return {
92
+ format: EXPORT_FORMAT,
93
+ logical: { namespace, name: namespace, format: LOGICAL_FORMAT },
94
+ catalogHash: startupHash2(source),
95
+ ...database !== undefined ? { database } : {},
96
+ tables: exportTables(source)
97
+ };
98
+ }
99
+ function serializeSnapshot(snapshot) {
100
+ return `${JSON.stringify(snapshot, null, 2)}
101
+ `;
102
+ }
103
+ function parseSnapshot(path, text) {
104
+ let parsed;
105
+ try {
106
+ parsed = JSON.parse(text);
107
+ } catch {
108
+ throw snapshotError(path, "stale", `Catalog snapshot ${path} is not JSON.`);
109
+ }
110
+ if (!isSnapshot(parsed)) {
111
+ throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the file format is not ${String(EXPORT_FORMAT)}.`);
112
+ }
113
+ return parsed;
114
+ }
115
+ function databaseName(url) {
116
+ try {
117
+ const name = decodeURIComponent(new URL(url).pathname.replace(/^\//, ""));
118
+ return name.length > 0 ? name : undefined;
119
+ } catch {
120
+ return;
121
+ }
122
+ }
123
+ function catalogNamespace(config) {
124
+ return config.namespace ?? "public";
125
+ }
126
+ function loadSnapshots(cwd, config) {
127
+ const paths = config.external ?? [];
128
+ return paths.map((path) => {
129
+ const file = resolveSnapshot(cwd, path);
130
+ if (!existsSync(file)) {
131
+ throw snapshotError(path, "missing", `Catalog snapshot ${path} is missing.`);
132
+ }
133
+ return { path, snapshot: parseSnapshot(path, readFileSync(file, "utf8")) };
134
+ });
135
+ }
136
+ function externalNamespaceMap(snapshots) {
137
+ const names = new Map;
138
+ for (const loaded of snapshots) {
139
+ for (const table of loaded.snapshot.tables)
140
+ names.set(table.name, table.namespace);
141
+ }
142
+ return names;
143
+ }
144
+ function couplingFindings(source) {
145
+ const findings = [];
146
+ for (const object of source.objects) {
147
+ if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
148
+ continue;
149
+ const references = object.definition.references;
150
+ if (references === undefined)
151
+ continue;
152
+ const from = object.identity.parent.namespace;
153
+ const to = references.parent.namespace;
154
+ if (from.form !== "static" || to.form !== "static" || from.name === to.name)
155
+ continue;
156
+ findings.push({
157
+ severity: "warning",
158
+ code: "coupling",
159
+ place: `${from.name}.${object.identity.parent.name}.${object.definition.columns.join(",")}`,
160
+ reason: `references ${to.name}.${references.parent.name}.${references.columns.join(",")}`,
161
+ fix: "Cross-catalog foreign keys are allowed inside one database."
162
+ });
163
+ }
164
+ return findings;
165
+ }
166
+ function assertSnapshots(cwd, config, source) {
167
+ if ((config.external ?? []).length === 0)
168
+ return;
169
+ const snapshots = loadSnapshots(cwd, config);
170
+ for (const table of externalTables(source)) {
171
+ const found = findTable(snapshots, table.namespace, table.name);
172
+ if (found === undefined) {
173
+ throw snapshotError(table.name, "missing", `External table ${table.namespace}.${table.name} is missing from the catalog snapshots.`);
174
+ }
175
+ const differ = columnDifference(table.columns, found.table.columns);
176
+ if (differ !== undefined) {
177
+ throw snapshotError(found.path, "stale", `Catalog snapshot ${found.path} is stale: ${table.namespace}.${table.name}.${differ} does not match the export.`);
178
+ }
179
+ }
180
+ }
181
+ async function assertLiveExternal(url, source, snapshots) {
182
+ if (snapshots.length === 0 && externalTables(source).length === 0)
183
+ return;
184
+ const postgres = (await import("postgres")).default;
185
+ const sql = postgres(url, { max: 1, connect_timeout: 5, idle_timeout: 1, onnotice: () => {} });
186
+ try {
187
+ const current = await sql`select current_database() as name`;
188
+ const database = current[0]?.name;
189
+ if (database !== undefined)
190
+ assertSameDatabase(source, snapshots, database);
191
+ for (const table of externalTables(source)) {
192
+ const found = findTable(snapshots, table.namespace, table.name);
193
+ if (found === undefined)
194
+ continue;
195
+ await assertLiveTable(sql, found.path, found.snapshot, table);
196
+ }
197
+ } finally {
198
+ await sql.end({ timeout: 5 });
199
+ }
200
+ }
201
+ function renderExport(cwd, config, source, args) {
202
+ const flags = exportFlags(args);
203
+ const database = exportDatabase(config);
204
+ const snapshot = snapshotFromCatalog(source, catalogNamespace(config), database);
205
+ const text = serializeSnapshot(snapshot);
206
+ if (flags.out !== undefined) {
207
+ const path = isAbsolute(flags.out) ? flags.out : join(cwd, flags.out);
208
+ writeFileSync(path, text);
209
+ }
210
+ if (flags.json || flags.out === undefined)
211
+ return text;
212
+ return `${flags.out}
213
+ `;
214
+ }
215
+ function exportTables(source) {
216
+ const tables = [];
217
+ for (const object of source.objects) {
218
+ if (object.kind !== "table" || object.owner === "external" || object.owner === "ignored") {
219
+ continue;
220
+ }
221
+ if (object.identity.namespace.form !== "static")
222
+ continue;
223
+ const columns = [];
224
+ for (const column of source.objects) {
225
+ if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
226
+ continue;
227
+ columns.push({
228
+ name: column.identity.name,
229
+ type: column.definition.dataType,
230
+ nullable: column.definition.nullable
231
+ });
232
+ }
233
+ tables.push({
234
+ namespace: object.identity.namespace.name,
235
+ name: object.identity.name,
236
+ columns
237
+ });
238
+ }
239
+ return tables;
240
+ }
241
+ function isColumnOf(object, namespace, table) {
242
+ return object.kind === "column" && object.identity.parent.name === table && object.identity.parent.namespace.form === "static" && object.identity.parent.namespace.name === namespace;
243
+ }
244
+ function externalTables(source) {
245
+ const tables = [];
246
+ for (const object of source.objects) {
247
+ if (object.kind !== "table" || object.owner !== "external")
248
+ continue;
249
+ if (object.identity.namespace.form !== "static")
250
+ continue;
251
+ const columns = [];
252
+ for (const column of source.objects) {
253
+ if (!isColumnOf(column, object.identity.namespace.name, object.identity.name))
254
+ continue;
255
+ columns.push({
256
+ name: column.identity.name,
257
+ type: column.definition.dataType,
258
+ nullable: column.definition.nullable
259
+ });
260
+ }
261
+ tables.push({ namespace: object.identity.namespace.name, name: object.identity.name, columns });
262
+ }
263
+ return tables;
264
+ }
265
+ function findTable(snapshots, namespace, name) {
266
+ for (const loaded of snapshots) {
267
+ for (const table of loaded.snapshot.tables) {
268
+ if (table.name === name && (table.namespace === namespace || namespace === "public")) {
269
+ return { path: loaded.path, snapshot: loaded.snapshot, table };
270
+ }
271
+ }
272
+ }
273
+ for (const loaded of snapshots) {
274
+ for (const table of loaded.snapshot.tables) {
275
+ if (table.name === name)
276
+ return { path: loaded.path, snapshot: loaded.snapshot, table };
277
+ }
278
+ }
279
+ return;
280
+ }
281
+ function columnDifference(left, right) {
282
+ const byName = new Map(right.map((column) => [column.name, column]));
283
+ for (const column of left) {
284
+ const other = byName.get(column.name);
285
+ if (other === undefined)
286
+ return column.name;
287
+ if (other.type !== column.type || other.nullable !== column.nullable)
288
+ return column.name;
289
+ }
290
+ for (const column of right) {
291
+ if (!left.some((item) => item.name === column.name))
292
+ return column.name;
293
+ }
294
+ return;
295
+ }
296
+ function assertSameDatabase(source, snapshots, database) {
297
+ if (!hasCrossCatalogForeignKey(source))
298
+ return;
299
+ for (const loaded of snapshots) {
300
+ const name = loaded.snapshot.database;
301
+ if (name !== undefined && name !== database) {
302
+ throw new OkmError2("OKM1842", `A foreign key references ${loaded.snapshot.logical.namespace} in database ${name}. This database is ${database}.`, {
303
+ kind: "invalid",
304
+ fieldReason: "database",
305
+ fix: {
306
+ summary: "Keep the foreign key inside one database. Cross-catalog keys are allowed only there."
307
+ }
308
+ });
309
+ }
310
+ }
311
+ }
312
+ function hasCrossCatalogForeignKey(source) {
313
+ for (const object of source.objects) {
314
+ if (object.kind !== "constraint" || object.definition.constraintKind !== "foreignKey")
315
+ continue;
316
+ const references = object.definition.references;
317
+ if (references === undefined)
318
+ continue;
319
+ const from = object.identity.parent.namespace;
320
+ const to = references.parent.namespace;
321
+ if (from.form === "static" && to.form === "static" && from.name !== to.name)
322
+ return true;
323
+ }
324
+ return false;
325
+ }
326
+ async function assertLiveTable(sql, path, snapshot, table) {
327
+ const hash = await liveHash(sql, table.namespace);
328
+ if (hash !== undefined && hash !== snapshot.catalogHash) {
329
+ throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: the catalog hash changed.`);
330
+ }
331
+ const live = await liveColumns(sql, table.namespace, table.name);
332
+ if (live === undefined) {
333
+ throw snapshotError(path, "missing", `External table ${table.namespace}.${table.name} is missing on the database.`);
334
+ }
335
+ const differ = columnDifference(snapshotTableColumns(snapshot, table), live);
336
+ if (differ !== undefined) {
337
+ throw snapshotError(path, "stale", `Catalog snapshot ${path} is stale: ${table.namespace}.${table.name}.${differ} is missing on the database.`);
338
+ }
339
+ }
340
+ function snapshotTableColumns(snapshot, table) {
341
+ for (const item of snapshot.tables) {
342
+ if (item.name === table.name)
343
+ return item.columns;
344
+ }
345
+ return table.columns;
346
+ }
347
+ async function liveHash(sql, namespace) {
348
+ const reg = `${quoteIdent2(namespace)}.${quoteIdent2("okm_meta")}`;
349
+ const present = await sql.unsafe(`select to_regclass('${reg}')::text as reg`);
350
+ if (present[0]?.reg == null)
351
+ return;
352
+ const rows = await sql.unsafe(`select catalog_hash from ${reg} where id = 'head'`);
353
+ return rows[0]?.catalog_hash;
354
+ }
355
+ async function liveColumns(sql, namespace, table) {
356
+ const rows = await sql`select a.attname as name, format_type(a.atttypid, a.atttypmod) as type, a.attnotnull as not_null
357
+ from pg_attribute a
358
+ join pg_class c on c.oid = a.attrelid
359
+ join pg_namespace n on n.oid = c.relnamespace
360
+ where n.nspname = ${namespace} and c.relname = ${table}
361
+ and a.attnum > 0 and not a.attisdropped
362
+ order by a.attnum`;
363
+ if (rows.length === 0) {
364
+ const exists = await sql`
365
+ select to_regclass(${`${quoteIdent2(namespace)}.${quoteIdent2(table)}`})::text as reg
366
+ `;
367
+ if (exists[0]?.reg == null)
368
+ return;
369
+ }
370
+ return rows.map((row) => ({ name: row.name, type: row.type, nullable: !row.not_null }));
371
+ }
372
+ function resolveSnapshot(cwd, path) {
373
+ return isAbsolute(path) ? path : projectPath(cwd, path);
374
+ }
375
+ function exportDatabase(config) {
376
+ const target = config.database;
377
+ if (typeof target === "string")
378
+ return databaseName(target);
379
+ if (target?.url !== undefined)
380
+ return databaseName(target.url);
381
+ return;
382
+ }
383
+ function exportFlags(args) {
384
+ let json = false;
385
+ let out;
386
+ for (let index = 0;index < args.length; index += 1) {
387
+ const arg = args[index] ?? "";
388
+ if (arg === "--json") {
389
+ json = true;
390
+ continue;
391
+ }
392
+ if (arg === "--out") {
393
+ const value = args[index + 1];
394
+ if (value === undefined || value.startsWith("--")) {
395
+ throw new OkmError2("invalid", "--out needs a file.", {
396
+ fix: { summary: "Pass --out <file>." }
397
+ });
398
+ }
399
+ out = value;
400
+ index += 1;
401
+ continue;
402
+ }
403
+ if (arg.startsWith("--out=")) {
404
+ out = arg.slice("--out=".length);
405
+ continue;
406
+ }
407
+ throw new OkmError2("invalid", `Unknown flag ${arg}.`, {
408
+ fix: { summary: "okm catalog export accepts --json and --out <file>." }
409
+ });
410
+ }
411
+ return out === undefined ? { json } : { json, out };
412
+ }
413
+ function snapshotError(path, reason, message) {
414
+ return new OkmError2("OKM1841", message, {
415
+ kind: reason === "missing" ? "not_found" : "schema_drift",
416
+ fieldReason: reason,
417
+ columns: [path],
418
+ fix: {
419
+ summary: reason === "missing" ? "Point external at the file okm catalog export wrote, and apply the owning catalog." : "Run okm catalog export in the owning catalog and update the dependent."
420
+ }
421
+ });
422
+ }
423
+ function isSnapshot(value) {
424
+ if (!isRecord(value) || value.format !== EXPORT_FORMAT)
425
+ return false;
426
+ if (typeof value.catalogHash !== "string")
427
+ return false;
428
+ if (!isLogical(value.logical))
429
+ return false;
430
+ if (value.database !== undefined && typeof value.database !== "string")
431
+ return false;
432
+ if (!Array.isArray(value.tables))
433
+ return false;
434
+ return value.tables.every(isTable);
435
+ }
436
+ function isLogical(value) {
437
+ if (!isRecord(value))
438
+ return false;
439
+ return typeof value.namespace === "string" && typeof value.name === "string" && value.format === LOGICAL_FORMAT;
440
+ }
441
+ function isTable(value) {
442
+ if (!isRecord(value) || typeof value.namespace !== "string" || typeof value.name !== "string") {
443
+ return false;
444
+ }
445
+ if (!Array.isArray(value.columns))
446
+ return false;
447
+ return value.columns.every((column) => isRecord(column) && typeof column.name === "string" && typeof column.type === "string" && typeof column.nullable === "boolean");
448
+ }
449
+ function isRecord(value) {
450
+ return typeof value === "object" && value !== null;
451
+ }
452
+
81
453
  // src/tooling/errors/registry.ts
82
454
  var ERROR_DOCS = [
83
455
  {
@@ -455,7 +827,7 @@ var ERROR_DOCS = [
455
827
  {
456
828
  code: "OKM1538",
457
829
  title: "Type rewrite",
458
- summary: "A statement changes a column type in a way that rewrites the table.",
830
+ summary: "A statement changes a column type in a way that rewrites the table. Binary-coercible changes, varchar widening, varchar to text, and a numeric precision increase at the same scale do not.",
459
831
  fix: "There is no safe form for a rewriting type change in this version. A column swap is expand and contract work for a later step. OKM1524 already requires a reason for the type change."
460
832
  },
461
833
  {
@@ -536,6 +908,12 @@ var ERROR_DOCS = [
536
908
  summary: "A step contains COMMIT, ROLLBACK, or other transaction control, including inside a DO body. A function or procedure body is not this.",
537
909
  fix: "Remove the transaction control. okm migrate check replays one transaction and rolls it back. Apply accepts the step only with -- okm-allow OKM1551: reason."
538
910
  },
911
+ {
912
+ code: "OKM1552",
913
+ title: "Reprinted declaration",
914
+ summary: "A domain check or a LANGUAGE sql function body is stored differently from the text in the migration catalog.",
915
+ fix: "Write the check or the body the way Postgres prints it, or allow OKM1552 with a reason. The warning does not fail apply. A domain whose check differs is still OKM1547."
916
+ },
539
917
  {
540
918
  code: "OKM1601",
541
919
  title: "Stale typed SQL",
@@ -751,6 +1129,18 @@ var ERROR_DOCS = [
751
1129
  title: "Pooler session",
752
1130
  summary: "Two statements on the same connection saw different backend pids. A transaction-mode pooler is in the way.",
753
1131
  fix: "Use a direct connection, or pass --allow-pooler."
1132
+ },
1133
+ {
1134
+ code: "OKM1855",
1135
+ title: "Pull would overwrite",
1136
+ summary: "okm pull or okm import drizzle found a file it would replace. It writes nothing.",
1137
+ fix: "Pass --out <directory> to write somewhere else, or move the file."
1138
+ },
1139
+ {
1140
+ code: "OKM1856",
1141
+ title: "Drizzle is not installed",
1142
+ summary: "okm import drizzle could not resolve drizzle-orm from the project.",
1143
+ fix: "Install drizzle-orm in the project that owns the schema."
754
1144
  }
755
1145
  ];
756
1146
  var byCode;
@@ -767,25 +1157,25 @@ function indexDocs() {
767
1157
  }
768
1158
 
769
1159
  // src/tooling/migrate/files.ts
770
- import { existsSync, readdirSync, readFileSync } from "node:fs";
771
- import { join } from "node:path";
1160
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
1161
+ import { join as join2 } from "node:path";
772
1162
  function loadMigrations(directory) {
773
- if (!existsSync(directory))
1163
+ if (!existsSync2(directory))
774
1164
  return [];
775
1165
  const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
776
1166
  const migrations = [];
777
1167
  for (const file of names) {
778
1168
  const id = file.slice(0, -".sql".length);
779
- const catalogPath = join(directory, `${id}.catalog.json`);
780
- if (!existsSync(catalogPath)) {
1169
+ const catalogPath = join2(directory, `${id}.catalog.json`);
1170
+ if (!existsSync2(catalogPath)) {
781
1171
  throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
782
1172
  fix: {
783
1173
  summary: "Generate the migration again so the SQL and the catalog are written together."
784
1174
  }
785
1175
  });
786
1176
  }
787
- const plan = readMigrationPlan(join(directory, file));
788
- const stored = parseCatalog2(readFileSync(catalogPath, "utf8"));
1177
+ const plan = readMigrationPlan(join2(directory, file));
1178
+ const stored = parseCatalog2(readFileSync2(catalogPath, "utf8"));
789
1179
  const startup = startupCatalog2(stored);
790
1180
  const hash = catalogHash2(startup);
791
1181
  migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
@@ -793,7 +1183,7 @@ function loadMigrations(directory) {
793
1183
  return migrations;
794
1184
  }
795
1185
  function readMigrationPlan(path) {
796
- const text = prepareMigrationText(readFileSync(path, "utf8"), path);
1186
+ const text = prepareMigrationText(readFileSync2(path, "utf8"), path);
797
1187
  const plan = parsePlan(text);
798
1188
  assertMigrationSteps(text, plan.steps.length, path);
799
1189
  return plan;
@@ -806,13 +1196,13 @@ function loadHeadSnapshot(directory) {
806
1196
  return {
807
1197
  id: last.id,
808
1198
  catalogHash: last.catalogHash,
809
- catalog: parseCatalog2(readFileSync(join(directory, `${last.id}.catalog.json`), "utf8"))
1199
+ catalog: parseCatalog2(readFileSync2(join2(directory, `${last.id}.catalog.json`), "utf8"))
810
1200
  };
811
1201
  }
812
1202
 
813
1203
  // src/tooling/migrate/lint.ts
814
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
815
- import { join as join2 } from "node:path";
1204
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
1205
+ import { join as join3 } from "node:path";
816
1206
 
817
1207
  // src/tooling/migrate/lint-rules.ts
818
1208
  var INTEGER_WIDTH = {
@@ -899,7 +1289,7 @@ function rule(code, category, severity, doc, check) {
899
1289
  function hit(matched, reason) {
900
1290
  return matched ? [reason] : [];
901
1291
  }
902
- function normalized(sql) {
1292
+ function normalizedSql(sql) {
903
1293
  const collapsed = sql.trim().replace(/\s+/g, " ");
904
1294
  let out = "";
905
1295
  let quoted = false;
@@ -914,7 +1304,7 @@ function normalized(sql) {
914
1304
  return out;
915
1305
  }
916
1306
  function weakensRls(input) {
917
- const text = normalized(input.step.sql);
1307
+ const text = normalizedSql(input.step.sql);
918
1308
  if (/\bdisable row level security\b/.test(text))
919
1309
  return ["disables row level security"];
920
1310
  if (/\bno force row level security\b/.test(text))
@@ -1031,7 +1421,7 @@ function parenthesized(text, open) {
1031
1421
  }
1032
1422
  function policyExpression(input, table, name) {
1033
1423
  const expression = expressionOf(input.after, table, name) ?? expressionOf(input.before, table, name);
1034
- return expression === undefined ? undefined : normalized(expression);
1424
+ return expression === undefined ? undefined : normalizedSql(expression);
1035
1425
  }
1036
1426
  function expressionOf(source, table, name) {
1037
1427
  for (const object of source.objects) {
@@ -1054,7 +1444,7 @@ function hasPolicy(source, table) {
1054
1444
  return source.objects.some((object) => object.kind === "policy" && object.identity.parent.name === table);
1055
1445
  }
1056
1446
  function dataStatement(step) {
1057
- const text = normalized(step.sql);
1447
+ const text = normalizedSql(step.sql);
1058
1448
  if (/^(?:do|call)\b/.test(text))
1059
1449
  return ["the body is not analysed"];
1060
1450
  if (step.backfill !== undefined)
@@ -1068,16 +1458,16 @@ function dataStatement(step) {
1068
1458
  return [];
1069
1459
  }
1070
1460
  function matches(step, kind, pattern) {
1071
- return pattern.test(normalized(step.sql)) || step.kind === kind;
1461
+ return pattern.test(normalizedSql(step.sql)) || step.kind === kind;
1072
1462
  }
1073
1463
  function renamesTable(step) {
1074
- const text = normalized(step.sql);
1464
+ const text = normalizedSql(step.sql);
1075
1465
  if (/^alter table\s+\S+\s+rename to\b/.test(text))
1076
1466
  return true;
1077
1467
  return step.kind === "rename-table" && !/^alter table\s+\S+\s+rename (column|constraint)\b/.test(text);
1078
1468
  }
1079
1469
  function droppedType(input) {
1080
- const text = normalized(input.step.sql);
1470
+ const text = normalizedSql(input.step.sql);
1081
1471
  const dropping = /^drop type\b/.test(text);
1082
1472
  if (!dropping && input.step.kind !== "drop-enum" && input.step.kind !== "drop-domain") {
1083
1473
  return;
@@ -1093,7 +1483,7 @@ function droppedType(input) {
1093
1483
  return "enum";
1094
1484
  }
1095
1485
  function requiredColumn(input) {
1096
- const text = normalized(input.step.sql);
1486
+ const text = normalizedSql(input.step.sql);
1097
1487
  const adding = /^alter table\s+\S+\s+add column\b/.test(text) || input.step.kind === "add-column-required";
1098
1488
  if (!adding)
1099
1489
  return false;
@@ -1109,7 +1499,7 @@ function requiredColumn(input) {
1109
1499
  function uniqueConstraint(input) {
1110
1500
  if (!tableExists(input))
1111
1501
  return false;
1112
- const text = normalized(input.step.sql);
1502
+ const text = normalizedSql(input.step.sql);
1113
1503
  if (/\busing index\b/.test(text))
1114
1504
  return false;
1115
1505
  if (/^alter table\s+\S+\s+add constraint\b/.test(text) && /\b(unique|primary key)\b/.test(text)) {
@@ -1120,16 +1510,16 @@ function uniqueConstraint(input) {
1120
1510
  const name = createdIndexName(text);
1121
1511
  if (name === undefined)
1122
1512
  return false;
1123
- return input.later.some((step) => usesIndex(normalized(step.sql), name));
1513
+ return input.later.some((step) => usesIndex(normalizedSql(step.sql), name));
1124
1514
  }
1125
1515
  function uniqueIndex(input) {
1126
- const text = normalized(input.step.sql);
1516
+ const text = normalizedSql(input.step.sql);
1127
1517
  if (!/^create unique index\b/.test(text))
1128
1518
  return false;
1129
1519
  if (!tableExists(input))
1130
1520
  return false;
1131
1521
  const name = createdIndexName(text);
1132
- if (name !== undefined && input.later.some((step) => usesIndex(normalized(step.sql), name))) {
1522
+ if (name !== undefined && input.later.some((step) => usesIndex(normalizedSql(step.sql), name))) {
1133
1523
  return false;
1134
1524
  }
1135
1525
  return true;
@@ -1142,7 +1532,7 @@ function usesIndex(text, name) {
1142
1532
  return text.includes(`using index "${name}"`) || text.includes(`using index ${name}`);
1143
1533
  }
1144
1534
  function unsafeSetNotNull(input) {
1145
- const text = normalized(input.step.sql);
1535
+ const text = normalizedSql(input.step.sql);
1146
1536
  const match = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+set not null\b/.exec(text);
1147
1537
  if (match === null && input.step.kind !== "set-not-null")
1148
1538
  return false;
@@ -1158,21 +1548,21 @@ function unsafeSetNotNull(input) {
1158
1548
  function validatedNotNullCheck(earlier, table, column) {
1159
1549
  const needle = `check (${column} is not null) not valid`;
1160
1550
  for (const step of earlier) {
1161
- const text = normalized(step.sql);
1551
+ const text = normalizedSql(step.sql);
1162
1552
  if (!text.startsWith(`alter table ${table} `) || !text.includes(needle))
1163
1553
  continue;
1164
1554
  const added = /^alter table\s+\S+\s+add constraint\s+(\S+)\s+check\b/.exec(text);
1165
1555
  const name = added?.[1];
1166
1556
  if (name === undefined)
1167
1557
  continue;
1168
- const validated = earlier.some((item) => normalized(item.sql).startsWith(`alter table ${table} validate constraint ${name}`));
1558
+ const validated = earlier.some((item) => normalizedSql(item.sql).startsWith(`alter table ${table} validate constraint ${name}`));
1169
1559
  if (validated)
1170
1560
  return true;
1171
1561
  }
1172
1562
  return false;
1173
1563
  }
1174
1564
  function blockingIndex(input) {
1175
- const text = normalized(input.step.sql);
1565
+ const text = normalizedSql(input.step.sql);
1176
1566
  if (!/^create(?: unique)? index\b/.test(text) && input.step.kind !== "create-index")
1177
1567
  return false;
1178
1568
  if (/\bconcurrently\b/.test(text))
@@ -1180,10 +1570,10 @@ function blockingIndex(input) {
1180
1570
  return tableExists(input);
1181
1571
  }
1182
1572
  function checkWithoutNotValid(input) {
1183
- return addedConstraint(input, "check") && !/\bnot valid\b/.test(normalized(input.step.sql));
1573
+ return addedConstraint(input, "check") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
1184
1574
  }
1185
1575
  function foreignKeyWithoutNotValid(input) {
1186
- return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(normalized(input.step.sql));
1576
+ return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(normalizedSql(input.step.sql));
1187
1577
  }
1188
1578
  function validatingCheck(input) {
1189
1579
  if (widenValidate(input))
@@ -1193,7 +1583,7 @@ function validatingCheck(input) {
1193
1583
  return checkWithoutNotValid(input);
1194
1584
  }
1195
1585
  function widenValidate(input) {
1196
- const text = normalized(input.step.sql);
1586
+ const text = normalizedSql(input.step.sql);
1197
1587
  if (!/^alter table\s+\S+\s+validate constraint\b/.test(text))
1198
1588
  return false;
1199
1589
  if (input.step.kind === "widen-check")
@@ -1204,7 +1594,7 @@ function widenValidate(input) {
1204
1594
  return input.earlier.some((step) => {
1205
1595
  if (step.kind !== "widen-check")
1206
1596
  return false;
1207
- const prior = normalized(step.sql);
1597
+ const prior = normalizedSql(step.sql);
1208
1598
  return prior.includes(`add constraint ${name} `);
1209
1599
  });
1210
1600
  }
@@ -1216,13 +1606,13 @@ function validatingForeignKey(input) {
1216
1606
  function addedConstraint(input, kind) {
1217
1607
  if (!tableExists(input))
1218
1608
  return false;
1219
- const text = normalized(input.step.sql);
1609
+ const text = normalizedSql(input.step.sql);
1220
1610
  if (!/^alter table\s+\S+\s+add constraint\b/.test(text))
1221
1611
  return false;
1222
1612
  return new RegExp(`\\b${kind}\\b`).test(text);
1223
1613
  }
1224
1614
  function validates(input, kind) {
1225
- const text = normalized(input.step.sql);
1615
+ const text = normalizedSql(input.step.sql);
1226
1616
  const table = /^alter table\s+\S+\s+validate constraint\s+(\S+)/.exec(text);
1227
1617
  if (table !== null) {
1228
1618
  const token = table[1];
@@ -1242,7 +1632,7 @@ function validates(input, kind) {
1242
1632
  }
1243
1633
  var TYPE_CHANGE = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+(?:set data type|type)\b/;
1244
1634
  function typeChange(input, mode) {
1245
- const text = normalized(input.step.sql);
1635
+ const text = normalizedSql(input.step.sql);
1246
1636
  const changing = TYPE_CHANGE.test(text) || input.step.kind === "set-column-type";
1247
1637
  if (!changing)
1248
1638
  return [];
@@ -1258,18 +1648,19 @@ function typeChange(input, mode) {
1258
1648
  const shrink = lengthShrink(before, after);
1259
1649
  if (mode === "shrink")
1260
1650
  return shrink ? ["shortens a character length"] : [];
1261
- if (shrink)
1262
- return [];
1651
+ if (shrink) {
1652
+ return mode === "rewrite" && tableRewrites(before, after, input) ? ["rewrites the table"] : [];
1653
+ }
1263
1654
  if (mode === "narrow")
1264
1655
  return narrows(before, after) ? ["narrows a type"] : [];
1265
- if (lengthGrow(before, after))
1656
+ if (lengthGrow(before, after) && mode !== "rewrite")
1266
1657
  return [];
1267
1658
  if (mode === "incompatible")
1268
1659
  return ["changes a column type"];
1269
- return rewrites(before, after) ? ["rewrites the table"] : [];
1660
+ return tableRewrites(before, after, input) ? ["rewrites the table"] : [];
1270
1661
  }
1271
1662
  function columnTypes(input) {
1272
- const text = normalized(input.step.sql);
1663
+ const text = normalizedSql(input.step.sql);
1273
1664
  const match = TYPE_CHANGE.exec(text);
1274
1665
  const table = lastName(match?.[1]);
1275
1666
  const column = lastName(match?.[2]);
@@ -1294,13 +1685,115 @@ function lengthGrow(before, after) {
1294
1685
  const right = charLength(after);
1295
1686
  return left !== undefined && right !== undefined && left.family === right.family && right.length > left.length;
1296
1687
  }
1297
- function rewrites(before, after) {
1298
- if (lengthShrink(before, after) || lengthGrow(before, after))
1688
+ function tableRewrites(before, after, input) {
1689
+ if (usingRewrites(input))
1690
+ return true;
1691
+ if (varyingRelaxation(before, after))
1692
+ return false;
1693
+ if (isVarying(before) && after === "text")
1299
1694
  return false;
1300
- if (isCharacter(before) && after === "text")
1695
+ if (before === "text" && unconstrainedVarying(after))
1696
+ return false;
1697
+ if ((before === "text" || isVarying(before)) && unconstrainedCharacter(after))
1698
+ return false;
1699
+ if (numericRelaxation(before, after))
1700
+ return false;
1701
+ if (binaryPair(before, after))
1702
+ return false;
1703
+ if (domainSkipsRewrite(before, after, input))
1301
1704
  return false;
1302
1705
  return true;
1303
1706
  }
1707
+ function statementMayReprint(sql) {
1708
+ const text = normalizedSql(sql);
1709
+ if (/^(?:create|alter) domain\b/.test(text))
1710
+ return true;
1711
+ return /^create(?: or replace)? function\b/.test(text) && /\blanguage sql\b/.test(text);
1712
+ }
1713
+ function usingRewrites(input) {
1714
+ const text = normalizedSql(input.step.sql);
1715
+ const at = text.search(/\busing\b/);
1716
+ if (at < 0)
1717
+ return false;
1718
+ const column = lastName(TYPE_CHANGE.exec(text)?.[2]);
1719
+ if (column === undefined)
1720
+ return true;
1721
+ const expression = text.slice(at + "using".length).trim().replace(/;$/, "");
1722
+ return !plainCast(expression, column);
1723
+ }
1724
+ function plainCast(expression, column) {
1725
+ const bare = column.toLowerCase();
1726
+ const quoted = `"${column}"`;
1727
+ return expression.startsWith(`${bare}::`) || expression.startsWith(`${quoted}::`) || expression.startsWith(`cast(${bare} as `) || expression.startsWith(`cast(${quoted} as `);
1728
+ }
1729
+ function isVarying(type) {
1730
+ return type === "character varying" || type === "varchar" || type.startsWith("character varying(") || type.startsWith("varchar(");
1731
+ }
1732
+ function unconstrainedVarying(type) {
1733
+ return type === "character varying" || type === "varchar";
1734
+ }
1735
+ function unconstrainedCharacter(type) {
1736
+ return type === "bpchar" || type === "character";
1737
+ }
1738
+ function varyingRelaxation(before, after) {
1739
+ const left = charLength(before);
1740
+ if (left?.family === "varying" && unconstrainedVarying(after))
1741
+ return true;
1742
+ const right = charLength(after);
1743
+ return left?.family === "varying" && right?.family === "varying" && right.length > left.length;
1744
+ }
1745
+ function numericRelaxation(before, after) {
1746
+ const left = numericBounds(before);
1747
+ const right = numericBounds(after);
1748
+ if (left === undefined || right === undefined)
1749
+ return false;
1750
+ if (right.precision === undefined)
1751
+ return left.precision !== undefined;
1752
+ if (left.precision === undefined || left.scale === undefined || right.scale === undefined) {
1753
+ return false;
1754
+ }
1755
+ return right.scale === left.scale && right.precision > left.precision;
1756
+ }
1757
+ function numericBounds(type) {
1758
+ const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
1759
+ if (match === null)
1760
+ return;
1761
+ if (match[1] === undefined)
1762
+ return { precision: undefined, scale: undefined };
1763
+ return {
1764
+ precision: Number(match[1]),
1765
+ scale: match[2] === undefined ? 0 : Number(match[2])
1766
+ };
1767
+ }
1768
+ function binaryPair(before, after) {
1769
+ if (before === "integer" && after === "oid")
1770
+ return true;
1771
+ if (before === "oid" && after === "integer")
1772
+ return true;
1773
+ if (before === "cidr" && after === "inet")
1774
+ return true;
1775
+ return before === "xml" && after === "text";
1776
+ }
1777
+ function domainSkipsRewrite(before, after, input) {
1778
+ const next = domainNamed(input, after);
1779
+ if (next !== undefined && next.check.trim().length === 0 && canonical(next.base) === before) {
1780
+ return true;
1781
+ }
1782
+ const previous = domainNamed(input, before);
1783
+ return previous !== undefined && canonical(previous.base) === after;
1784
+ }
1785
+ function domainNamed(input, name) {
1786
+ for (const source of [input.after, input.before]) {
1787
+ for (const object of source.objects) {
1788
+ if (object.kind !== "type" || object.identity.name !== name)
1789
+ continue;
1790
+ if (!isDomain(object.definition))
1791
+ continue;
1792
+ return object.definition;
1793
+ }
1794
+ }
1795
+ return;
1796
+ }
1304
1797
  function narrows(before, after) {
1305
1798
  const leftInt = INTEGER_WIDTH[before];
1306
1799
  const rightInt = INTEGER_WIDTH[after];
@@ -1325,9 +1818,6 @@ function charLength(type) {
1325
1818
  return { family: "fixed", length: Number(fixed[1]) };
1326
1819
  return;
1327
1820
  }
1328
- function isCharacter(type) {
1329
- return type === "text" || charLength(type) !== undefined;
1330
- }
1331
1821
  function numericSpec(type) {
1332
1822
  const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
1333
1823
  if (match === null)
@@ -1344,7 +1834,7 @@ function tableExists(input) {
1344
1834
  return name !== undefined && input.existingTables.has(name);
1345
1835
  }
1346
1836
  function statementTable(step) {
1347
- const text = normalized(step.sql);
1837
+ const text = normalizedSql(step.sql);
1348
1838
  const altered = /^(?:alter|drop) table\s+(\S+)/.exec(text);
1349
1839
  if (altered?.[1] !== undefined)
1350
1840
  return lastName(altered[1]);
@@ -1380,6 +1870,134 @@ function isSerial(column) {
1380
1870
  return /nextval\s*\(/i.test(column.definition.defaultExpression ?? "");
1381
1871
  }
1382
1872
 
1873
+ // src/tooling/migrate/reprint.ts
1874
+ var REPRINT_CODE = "OKM1552";
1875
+ function reprintHits(declared, stored, steps) {
1876
+ const hits = [];
1877
+ const silenced = new Set;
1878
+ for (const difference of differences(declared, stored)) {
1879
+ const index = stepIndex(steps, difference.kind, difference.name);
1880
+ if (index === undefined)
1881
+ continue;
1882
+ const step = steps[index];
1883
+ const allow = step?.allows?.find((item) => item.code === REPRINT_CODE);
1884
+ if (allow !== undefined && allow.reason.trim().length > 0) {
1885
+ silenced.add(index);
1886
+ continue;
1887
+ }
1888
+ hits.push({
1889
+ severity: "warning",
1890
+ code: REPRINT_CODE,
1891
+ place: `step ${String(index + 1)}`,
1892
+ reason: difference.reason
1893
+ });
1894
+ }
1895
+ steps.forEach((step, index) => {
1896
+ for (const allow of step.allows ?? []) {
1897
+ if (allow.code !== REPRINT_CODE)
1898
+ continue;
1899
+ const place = `step ${String(index + 1)}`;
1900
+ if (allow.reason.trim().length === 0) {
1901
+ hits.push({
1902
+ severity: "error",
1903
+ code: "OKM1510",
1904
+ place,
1905
+ reason: "override needs a reason"
1906
+ });
1907
+ continue;
1908
+ }
1909
+ if (silenced.has(index))
1910
+ continue;
1911
+ if (hits.some((item) => item.place === place && item.code === REPRINT_CODE))
1912
+ continue;
1913
+ if (!statementMayReprint(step.sql))
1914
+ continue;
1915
+ hits.push({
1916
+ severity: "error",
1917
+ code: "OKM1510",
1918
+ place,
1919
+ reason: `override ${REPRINT_CODE} does not match this statement`
1920
+ });
1921
+ }
1922
+ });
1923
+ return hits;
1924
+ }
1925
+ function differences(declared, stored) {
1926
+ const found = [];
1927
+ const storedDomains = domainChecks(stored);
1928
+ for (const [name, check] of domainChecks(declared)) {
1929
+ const live = storedDomains.get(name);
1930
+ if (live === undefined || live === check)
1931
+ continue;
1932
+ found.push({
1933
+ kind: "domain",
1934
+ name,
1935
+ reason: `domain ${name} check reprints differently`
1936
+ });
1937
+ }
1938
+ const storedFunctions = sqlBodies(stored);
1939
+ for (const [key, body] of sqlBodies(declared)) {
1940
+ const live = storedFunctions.get(key);
1941
+ if (live === undefined || live.body === body.body)
1942
+ continue;
1943
+ found.push({
1944
+ kind: "function",
1945
+ name: body.name,
1946
+ reason: `function ${body.name} body reprints differently`
1947
+ });
1948
+ }
1949
+ return found;
1950
+ }
1951
+ function domainChecks(source) {
1952
+ const checks = new Map;
1953
+ for (const object of source.objects) {
1954
+ if (object.kind !== "type" || !isDomain(object.definition))
1955
+ continue;
1956
+ checks.set(object.identity.name, object.definition.check.trim());
1957
+ }
1958
+ return checks;
1959
+ }
1960
+ function sqlBodies(source) {
1961
+ const bodies = new Map;
1962
+ for (const object of source.objects) {
1963
+ if (object.kind !== "function" || object.definition.language !== "sql")
1964
+ continue;
1965
+ bodies.set(functionKey(object), {
1966
+ name: object.identity.name,
1967
+ body: object.definition.body.trim()
1968
+ });
1969
+ }
1970
+ return bodies;
1971
+ }
1972
+ function functionKey(object) {
1973
+ const args = object.identity.argTypes.map((typeName) => canonicalTypeName(typeName)).join(",");
1974
+ return `${object.identity.name}(${args})`;
1975
+ }
1976
+ function stepIndex(steps, kind, name) {
1977
+ let found;
1978
+ steps.forEach((step, index) => {
1979
+ if (mentions(normalizedSql(step.sql), kind, name))
1980
+ found = index;
1981
+ });
1982
+ return found;
1983
+ }
1984
+ function mentions(text, kind, name) {
1985
+ if (kind === "domain" && !/^(?:create|alter) domain\b/.test(text))
1986
+ return false;
1987
+ if (kind === "function") {
1988
+ if (!/^create(?: or replace)? function\b/.test(text) || !/\blanguage sql\b/.test(text)) {
1989
+ return false;
1990
+ }
1991
+ }
1992
+ const quoted = `"${name}"`;
1993
+ if (text.includes(quoted))
1994
+ return true;
1995
+ return new RegExp(`\\b${escapeRegExp(name.toLowerCase())}\\b`).test(text);
1996
+ }
1997
+ function escapeRegExp(value) {
1998
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1999
+ }
2000
+
1383
2001
  // src/tooling/migrate/lint.ts
1384
2002
  function lintPlan(plan, before, after) {
1385
2003
  const existingTables = tableNames(before);
@@ -1389,6 +2007,9 @@ function lintPlan(plan, before, after) {
1389
2007
  });
1390
2008
  return findings;
1391
2009
  }
2010
+ function lintReprints(declared, stored, steps) {
2011
+ return reprintHits(declared, stored, steps).map((hit) => finding(hit.severity, hit.code, hit.place, hit.reason));
2012
+ }
1392
2013
  function lintCatalog(source) {
1393
2014
  const findings = [];
1394
2015
  for (const object of source.objects) {
@@ -1404,15 +2025,15 @@ function lintCatalog(source) {
1404
2025
  return findings;
1405
2026
  }
1406
2027
  function lintMigrationDirectory(directory, pending) {
1407
- if (!existsSync2(directory))
2028
+ if (!existsSync3(directory))
1408
2029
  return [];
1409
2030
  const names = readdirSync2(directory).filter((file) => file.endsWith(".sql")).sort();
1410
2031
  let before = catalog([]);
1411
2032
  const findings = [];
1412
2033
  for (const file of names) {
1413
2034
  const id = file.slice(0, -".sql".length);
1414
- const plan = readMigrationPlan(join2(directory, file));
1415
- const after = parseCatalog2(readFileSync2(join2(directory, `${id}.catalog.json`), "utf8"));
2035
+ const plan = readMigrationPlan(join3(directory, file));
2036
+ const after = parseCatalog2(readFileSync3(join3(directory, `${id}.catalog.json`), "utf8"));
1416
2037
  if (pending === undefined || pending.has(id)) {
1417
2038
  for (const item of lintPlan(plan, before, after)) {
1418
2039
  findings.push({ ...item, place: `${id} ${item.place}` });
@@ -1462,6 +2083,8 @@ function lintStep(step, number, before, after, existingTables, earlier, later) {
1462
2083
  triggered.add(hit.code);
1463
2084
  }
1464
2085
  }
2086
+ if (statementMayReprint(statement.sql))
2087
+ triggered.add(REPRINT_CODE);
1465
2088
  const place = multi ? `step ${String(number)} statement ${String(index + 1)}` : `step ${String(number)}`;
1466
2089
  perStatement.push({ place, hits });
1467
2090
  });
@@ -1531,8 +2154,16 @@ function columnPlace(column) {
1531
2154
  }
1532
2155
 
1533
2156
  // src/tooling/migrate/project.ts
1534
- import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync, writeFileSync } from "node:fs";
1535
- import { basename, dirname, join as join3 } from "node:path";
2157
+ import {
2158
+ existsSync as existsSync4,
2159
+ mkdirSync,
2160
+ readdirSync as readdirSync3,
2161
+ readFileSync as readFileSync4,
2162
+ statSync,
2163
+ unlinkSync,
2164
+ writeFileSync as writeFileSync2
2165
+ } from "node:fs";
2166
+ import { basename, dirname, join as join4 } from "node:path";
1536
2167
  import { pathToFileURL } from "node:url";
1537
2168
 
1538
2169
  // src/dialects/pg/declarations.ts
@@ -2143,6 +2774,131 @@ function columnNames(target, built, table) {
2143
2774
  return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
2144
2775
  }
2145
2776
 
2777
+ // src/tooling/migrate/place.ts
2778
+ function placeCatalog(source, namespace, externalNamespaces) {
2779
+ if (namespace === "public" && externalNamespaces.size === 0)
2780
+ return source;
2781
+ const homes = tableHomes(source, namespace, externalNamespaces);
2782
+ let changed = false;
2783
+ const objects = source.objects.map((object) => {
2784
+ const next = relocateObject(object, homes, namespace);
2785
+ if (next !== object)
2786
+ changed = true;
2787
+ return next;
2788
+ });
2789
+ if (!changed)
2790
+ return source;
2791
+ return catalog(objects);
2792
+ }
2793
+ function tableHomes(source, namespace, externalNamespaces) {
2794
+ const homes = new Map;
2795
+ for (const object of source.objects) {
2796
+ if (object.kind !== "table" && object.kind !== "view" && object.kind !== "materializedView") {
2797
+ continue;
2798
+ }
2799
+ const home = object.owner === "external" ? externalNamespaces.get(object.identity.name) ?? namespace : namespace;
2800
+ homes.set(object.identity.name, home);
2801
+ }
2802
+ return homes;
2803
+ }
2804
+ function relocateObject(object, homes, namespace) {
2805
+ const dependencies = relocateEdges(object.dependencies, homes, namespace);
2806
+ if (object.kind === "extension") {
2807
+ if (object.definition.schema === "public" && namespace !== "public") {
2808
+ return { ...object, definition: { ...object.definition, schema: namespace }, dependencies };
2809
+ }
2810
+ return dependencies === object.dependencies ? object : { ...object, dependencies };
2811
+ }
2812
+ if (object.kind === "role") {
2813
+ return dependencies === object.dependencies ? object : { ...object, dependencies };
2814
+ }
2815
+ const identity = relocateIdentity(object.identity, homes, namespace);
2816
+ if (object.kind === "constraint") {
2817
+ const references = relocateReferences(object, homes);
2818
+ if (identity === object.identity && references === object.definition.references && dependencies === object.dependencies) {
2819
+ return object;
2820
+ }
2821
+ if (references === undefined || references === object.definition.references) {
2822
+ return { ...object, identity, dependencies };
2823
+ }
2824
+ return {
2825
+ ...object,
2826
+ identity,
2827
+ definition: { ...object.definition, references },
2828
+ dependencies
2829
+ };
2830
+ }
2831
+ if (identity === object.identity && dependencies === object.dependencies)
2832
+ return object;
2833
+ return { ...object, identity, dependencies };
2834
+ }
2835
+ function relocateReferences(object, homes) {
2836
+ const references = object.definition.references;
2837
+ if (references === undefined)
2838
+ return;
2839
+ const home = homes.get(references.parent.name);
2840
+ if (home === undefined)
2841
+ return references;
2842
+ const parent = moveRef(references.parent, home);
2843
+ if (parent === references.parent)
2844
+ return references;
2845
+ return { ...references, parent };
2846
+ }
2847
+ function relocateEdges(edges, homes, namespace) {
2848
+ let changed = false;
2849
+ const next = edges.map((edge) => {
2850
+ const target = relocateIdentity(edge.target, homes, namespace);
2851
+ if (target === edge.target)
2852
+ return edge;
2853
+ changed = true;
2854
+ return { target };
2855
+ });
2856
+ return changed ? next : edges;
2857
+ }
2858
+ function relocateIdentity(identity, homes, namespace) {
2859
+ if (identity.kind === "role" || identity.kind === "extension")
2860
+ return identity;
2861
+ if (identity.kind === "column" || identity.kind === "index" || identity.kind === "constraint" || identity.kind === "trigger" || identity.kind === "policy") {
2862
+ const home = homes.get(identity.parent.name) ?? namespace;
2863
+ const parent = moveRef(identity.parent, home);
2864
+ if (parent === identity.parent)
2865
+ return identity;
2866
+ return { ...identity, parent };
2867
+ }
2868
+ if (identity.kind === "grant") {
2869
+ const home = homes.get(identity.object.name) ?? namespace;
2870
+ const moved = moveNamespace(identity.object.namespace, home);
2871
+ if (moved === identity.object.namespace)
2872
+ return identity;
2873
+ return { ...identity, object: { ...identity.object, namespace: moved } };
2874
+ }
2875
+ if (identity.kind === "defaultPrivilege") {
2876
+ const moved = moveNamespace(identity.namespace, namespace);
2877
+ if (moved === identity.namespace)
2878
+ return identity;
2879
+ return { ...identity, namespace: moved };
2880
+ }
2881
+ if (identity.kind === "table" || identity.kind === "view" || identity.kind === "materializedView" || identity.kind === "sequence" || identity.kind === "type" || identity.kind === "function") {
2882
+ const home = homes.get(identity.name) ?? namespace;
2883
+ const moved = moveNamespace(identity.namespace, home);
2884
+ if (moved === identity.namespace)
2885
+ return identity;
2886
+ return { ...identity, namespace: moved };
2887
+ }
2888
+ return identity;
2889
+ }
2890
+ function moveRef(ref, namespace) {
2891
+ const moved = moveNamespace(ref.namespace, namespace);
2892
+ if (moved === ref.namespace)
2893
+ return ref;
2894
+ return { ...ref, namespace: moved };
2895
+ }
2896
+ function moveNamespace(namespace, name) {
2897
+ if (namespace.form !== "static" || namespace.name === name)
2898
+ return namespace;
2899
+ return staticNamespace2(name);
2900
+ }
2901
+
2146
2902
  // src/tooling/migrate/estimate.ts
2147
2903
  var LARGE_TABLE_ROWS = 1e6;
2148
2904
  var ESTIMATE_SQL = `
@@ -2336,11 +3092,35 @@ function trimZero(text) {
2336
3092
  return text.endsWith(".0") ? text.slice(0, -2) : text;
2337
3093
  }
2338
3094
 
3095
+ // src/tooling/migrate/plan-json.ts
3096
+ function planDocument(plan, findings, lockText) {
3097
+ return {
3098
+ name: plan.name,
3099
+ class: plan.class,
3100
+ steps: plan.steps.map((step) => ({
3101
+ class: step.class,
3102
+ lock: lockText === undefined ? step.lock : lockText(step),
3103
+ sql: step.sql
3104
+ })),
3105
+ findings: findings.map((finding) => ({
3106
+ severity: finding.severity,
3107
+ code: finding.code,
3108
+ place: finding.place,
3109
+ reason: finding.reason,
3110
+ fix: finding.fix
3111
+ }))
3112
+ };
3113
+ }
3114
+ function formatPlanJson(plan, findings, lockText) {
3115
+ return `${JSON.stringify(planDocument(plan, findings, lockText))}
3116
+ `;
3117
+ }
3118
+
2339
3119
  // src/tooling/migrate/project.ts
2340
3120
  async function buildProject(cwd) {
2341
3121
  const config = await loadConfig(cwd);
2342
3122
  const built = await loadSchema(cwd, config.schema);
2343
- return writeArtifact(cwd, config, withRoles(built, config));
3123
+ return writeArtifact(cwd, config, withRoles(built, config, cwd));
2344
3124
  }
2345
3125
  async function generateProject(cwd, name, flags) {
2346
3126
  const prepared = await prepare(cwd, name, flags);
@@ -2352,17 +3132,20 @@ async function generateProject(cwd, name, flags) {
2352
3132
  const directory = projectPath(cwd, prepared.config.migrations ?? "migrations");
2353
3133
  mkdirSync(directory, { recursive: true });
2354
3134
  const base = `${nextNumber(directory)}_${safeName(name)}`;
2355
- const sqlPath = join3(directory, `${base}.sql`);
2356
- writeFileSync(sqlPath, formatPlan(prepared.plan));
2357
- writeFileSync(join3(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
3135
+ const sqlPath = join4(directory, `${base}.sql`);
3136
+ writeFileSync2(sqlPath, formatPlan(prepared.plan));
3137
+ writeFileSync2(join4(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
2358
3138
  return { path: sqlPath, findings: lintPrepared(prepared) };
2359
3139
  }
2360
3140
  async function planProject(cwd, name, flags, invoke) {
2361
3141
  const prepared = await prepare(cwd, name, flags);
3142
+ const findings = lintPrepared(prepared);
3143
+ const lockText = await planLockText(prepared.config, prepared.plan, invoke);
2362
3144
  return {
2363
3145
  plan: prepared.plan,
2364
- findings: lintPrepared(prepared),
2365
- text: await displayPlan(prepared.config, prepared.plan, invoke)
3146
+ findings,
3147
+ text: formatPlan(prepared.plan, lockText),
3148
+ json: formatPlanJson(prepared.plan, findings, lockText)
2366
3149
  };
2367
3150
  }
2368
3151
  async function checkProject(cwd, flags) {
@@ -2378,8 +3161,8 @@ async function checkProject(cwd, flags) {
2378
3161
  assertTargetAlias(targets);
2379
3162
  if (opened.config.tables !== undefined) {
2380
3163
  const directory = projectPath(cwd, opened.config.tables);
2381
- const files = existsSync3(directory) ? readdirSync3(directory) : [];
2382
- const source = readFileSync3(projectPath(cwd, opened.config.schema), "utf8");
3164
+ const files = existsSync4(directory) ? readdirSync3(directory) : [];
3165
+ const source = readFileSync4(projectPath(cwd, opened.config.schema), "utf8");
2383
3166
  const missing = unlistedTableFiles(source, files);
2384
3167
  if (missing.length > 0) {
2385
3168
  throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
@@ -2387,7 +3170,8 @@ async function checkProject(cwd, flags) {
2387
3170
  });
2388
3171
  }
2389
3172
  }
2390
- const findings = lintOpened(opened);
3173
+ assertSnapshots(cwd, opened.config, opened.built.catalog);
3174
+ const findings = [...lintOpened(opened), ...couplingFindings(opened.built.catalog)];
2391
3175
  if (hasError(findings))
2392
3176
  throw lintRefusal(findings);
2393
3177
  const named = flags?.target;
@@ -2398,8 +3182,9 @@ async function checkProject(cwd, flags) {
2398
3182
  const target = selectTarget(opened.config, named);
2399
3183
  assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
2400
3184
  assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
2401
- const { assertAuthorDrift } = await import("./3449rcae.js");
3185
+ const { assertAuthorDrift } = await import("./ckz05z15.js");
2402
3186
  const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
3187
+ await assertLiveExternal(target.url, opened.built.catalog, loadSnapshots(cwd, opened.config));
2403
3188
  await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
2404
3189
  return findings;
2405
3190
  }
@@ -2429,6 +3214,7 @@ async function prepare(cwd, name, flags) {
2429
3214
  renames: opened.renames,
2430
3215
  replacements: flags.map((flag) => parseReplace(flag)),
2431
3216
  name,
3217
+ ...placedSchema(opened.config),
2432
3218
  ...batchSizeField(opened.config.backfill?.batchSize)
2433
3219
  });
2434
3220
  return {
@@ -2442,26 +3228,25 @@ async function prepare(cwd, name, flags) {
2442
3228
  function lintPrepared(prepared) {
2443
3229
  return lintPlan(prepared.plan, prepared.before, prepared.after);
2444
3230
  }
2445
- async function displayPlan(config, plan, invoke) {
2446
- const offline = formatPlan(plan);
3231
+ async function planLockText(config, plan, invoke) {
2447
3232
  const targets = listTargets(config);
2448
3233
  if (targets.length === 0)
2449
- return offline;
3234
+ return;
2450
3235
  assertTargetAlias(targets);
2451
3236
  const named = invoke?.target;
2452
3237
  if (named === undefined && targets.length !== 1)
2453
- return offline;
3238
+ return;
2454
3239
  const target = selectTarget(config, named);
2455
3240
  assertTargetPolicy(target, "plan", invoke?.allowProtected ?? false);
2456
3241
  let estimates;
2457
3242
  try {
2458
3243
  const names = plan.steps.flatMap((step) => step.tables ?? []);
2459
- estimates = await readRowEstimates(target.url, "public", names);
3244
+ estimates = await readRowEstimates(target.url, catalogNamespace(config), names);
2460
3245
  } catch {
2461
- return offline;
3246
+ return;
2462
3247
  }
2463
3248
  const aliases = renameAliases(plan.steps);
2464
- return formatPlan(plan, (step) => annotateLock(step, estimates, aliases));
3249
+ return (step) => annotateLock(step, estimates, aliases);
2465
3250
  }
2466
3251
  function lintOpened(opened) {
2467
3252
  const plan = planMigration({
@@ -2469,6 +3254,7 @@ function lintOpened(opened) {
2469
3254
  after: opened.built.catalog,
2470
3255
  renames: opened.renames,
2471
3256
  name: "check",
3257
+ ...placedSchema(opened.config),
2472
3258
  ...batchSizeField(opened.config.backfill?.batchSize)
2473
3259
  });
2474
3260
  return [
@@ -2478,7 +3264,7 @@ function lintOpened(opened) {
2478
3264
  }
2479
3265
  async function openProject(cwd) {
2480
3266
  const config = await loadConfig(cwd);
2481
- const built = withRoles(await loadSchema(cwd, config.schema), config);
3267
+ const built = await loadBuiltSchema(cwd, config);
2482
3268
  const declarations = schemaDeclarations(built);
2483
3269
  const previous = readPrevious(projectPath(cwd, config.migrations ?? "migrations"));
2484
3270
  const stale = staleRenames(previous?.catalog, declarations.renames);
@@ -2494,15 +3280,22 @@ async function openProject(cwd) {
2494
3280
  renames: declarations.renames
2495
3281
  };
2496
3282
  }
2497
- function withRoles(built, config) {
3283
+ function withRoles(built, config, cwd) {
2498
3284
  const marks = ownershipMarks(built);
2499
3285
  const stamped = marks.size === 0 ? built.catalog : stampOwnership(built.catalog, marks);
3286
+ const placed = placeCatalog(stamped, catalogNamespace(config), externalNamespaceMap(loadSnapshots(cwd, config)));
2500
3287
  const roles = config.roles;
2501
- const catalog = roles === undefined ? stamped : attachRoles(stamped, roles, exposureFor(built, roles));
3288
+ const catalog = roles === undefined ? placed : attachRoles(placed, roles, exposureFor(built, roles));
2502
3289
  if (catalog === built.catalog)
2503
3290
  return built;
2504
3291
  return { ...built, catalog };
2505
3292
  }
3293
+ function placedSchema(config) {
3294
+ const namespace = catalogNamespace(config);
3295
+ if (namespace === "public")
3296
+ return;
3297
+ return { schema: namespace };
3298
+ }
2506
3299
  function ownershipMarks(built) {
2507
3300
  const marks = new Map;
2508
3301
  const models = built.model;
@@ -2539,9 +3332,9 @@ var GIT_ATTRIBUTES = [
2539
3332
  `);
2540
3333
  function writeGitAttributes(cwd) {
2541
3334
  const path = projectPath(cwd, ".gitattributes");
2542
- if (existsSync3(path))
3335
+ if (existsSync4(path))
2543
3336
  return;
2544
- writeFileSync(path, GIT_ATTRIBUTES);
3337
+ writeFileSync2(path, GIT_ATTRIBUTES);
2545
3338
  }
2546
3339
  function typesText(cwd, spec, built) {
2547
3340
  return `${emitRowTypes(built, jsonColumnTypes(readSchemaSource(cwd, spec)))}${referenceAugmentation(built)}`;
@@ -2550,7 +3343,7 @@ function readSchemaSource(cwd, spec) {
2550
3343
  const parts = [];
2551
3344
  const seen = new Set;
2552
3345
  const visit = (file) => {
2553
- if (seen.has(file) || !existsSync3(file))
3346
+ if (seen.has(file) || !existsSync4(file))
2554
3347
  return;
2555
3348
  let stat;
2556
3349
  try {
@@ -2561,16 +3354,16 @@ function readSchemaSource(cwd, spec) {
2561
3354
  if (!stat.isFile())
2562
3355
  return;
2563
3356
  seen.add(file);
2564
- const text = readFileSync3(file, "utf8");
3357
+ const text = readFileSync4(file, "utf8");
2565
3358
  parts.push(text);
2566
3359
  for (const match of text.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
2567
3360
  const rel = match[1];
2568
3361
  if (rel === undefined)
2569
3362
  continue;
2570
- const base = join3(dirname(file), rel);
2571
- const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join3(base, "index.ts")];
3363
+ const base = join4(dirname(file), rel);
3364
+ const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join4(base, "index.ts")];
2572
3365
  for (const candidate of candidates) {
2573
- if (existsSync3(candidate)) {
3366
+ if (existsSync4(candidate)) {
2574
3367
  visit(candidate);
2575
3368
  break;
2576
3369
  }
@@ -2586,19 +3379,35 @@ function writeArtifact(cwd, config, built) {
2586
3379
  mkdirSync(directory, { recursive: true });
2587
3380
  const startup = startupCatalog2(built.catalog);
2588
3381
  const text = catalogFileText2(serializeCatalog2(startup));
2589
- writeFileSync(join3(directory, "catalog.json"), text);
2590
- writeFileSync(join3(directory, "catalog.hash"), `${sha2562(text)}
3382
+ writeFileSync2(join4(directory, "catalog.json"), text);
3383
+ writeFileSync2(join4(directory, "catalog.hash"), `${sha2562(text)}
2591
3384
  `);
2592
- writeFileSync(join3(directory, "types.d.ts"), typesText(cwd, config.schema, built));
3385
+ writeFileSync2(join4(directory, "types.d.ts"), typesText(cwd, config.schema, built));
3386
+ writeNamespace(directory, catalogNamespace(config));
2593
3387
  return directory;
2594
3388
  }
3389
+ function writeNamespace(directory, namespace) {
3390
+ const path = join4(directory, "namespace");
3391
+ if (namespace === "public") {
3392
+ if (existsSync4(path))
3393
+ unlinkSync(path);
3394
+ return;
3395
+ }
3396
+ writeFileSync2(path, `${namespace}
3397
+ `);
3398
+ }
2595
3399
  async function loadBuiltSchema(cwd, config) {
2596
- return withRoles(await loadSchema(cwd, config.schema), config);
3400
+ return withRoles(await loadSchema(cwd, config.schema), config, cwd);
3401
+ }
3402
+ async function exportProject(cwd, args) {
3403
+ const config = await loadConfig(cwd);
3404
+ const built = await loadBuiltSchema(cwd, config);
3405
+ return renderExport(cwd, config, built.catalog, args);
2597
3406
  }
2598
3407
  async function loadConfig(cwd) {
2599
3408
  const path = projectPath(cwd, "okmodel.config.ts");
2600
3409
  const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2601
- const record = isRecord(imported) ? imported : {};
3410
+ const record = isRecord2(imported) ? imported : {};
2602
3411
  const config = record.default ?? record.config;
2603
3412
  if (!isConfig(config)) {
2604
3413
  throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
@@ -2608,7 +3417,7 @@ async function loadConfig(cwd) {
2608
3417
  async function loadSchema(cwd, spec) {
2609
3418
  const path = projectPath(cwd, spec);
2610
3419
  const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2611
- const record = isRecord(imported) ? imported : {};
3420
+ const record = isRecord2(imported) ? imported : {};
2612
3421
  for (const value of Object.values(record)) {
2613
3422
  if (!isBuilt(value))
2614
3423
  continue;
@@ -2618,13 +3427,13 @@ async function loadSchema(cwd, spec) {
2618
3427
  throw new OkmError2("invalid", `${spec} must export a schema().`);
2619
3428
  }
2620
3429
  function readPrevious(directory) {
2621
- if (!existsSync3(directory))
3430
+ if (!existsSync4(directory))
2622
3431
  return;
2623
3432
  const catalogs = readdirSync3(directory).filter((file) => file.endsWith(".catalog.json")).sort();
2624
3433
  const latest = catalogs.at(-1);
2625
3434
  if (latest === undefined)
2626
3435
  return;
2627
- const text = readFileSync3(join3(directory, latest), "utf8");
3436
+ const text = readFileSync4(join4(directory, latest), "utf8");
2628
3437
  return { catalog: parseCatalog2(text) };
2629
3438
  }
2630
3439
  function referenceAugmentation(source) {
@@ -2649,7 +3458,7 @@ function referenceAugmentation(source) {
2649
3458
  }
2650
3459
  function nextNumber(directory) {
2651
3460
  let max = 0;
2652
- if (existsSync3(directory)) {
3461
+ if (existsSync4(directory)) {
2653
3462
  for (const file of readdirSync3(directory)) {
2654
3463
  const match = /^(\d+)_/.exec(file);
2655
3464
  const value = match?.[1];
@@ -2666,14 +3475,14 @@ function safeName(name) {
2666
3475
  return name;
2667
3476
  }
2668
3477
  function isBuilt(value) {
2669
- if (!isRecord(value) || !Array.isArray(value.tables))
3478
+ if (!isRecord2(value) || !Array.isArray(value.tables))
2670
3479
  return false;
2671
3480
  return "catalog" in value;
2672
3481
  }
2673
3482
  function isConfig(value) {
2674
- return isRecord(value) && typeof value.schema === "string";
3483
+ return isRecord2(value) && typeof value.schema === "string";
2675
3484
  }
2676
- function isRecord(value) {
3485
+ function isRecord2(value) {
2677
3486
  return typeof value === "object" && value !== null;
2678
3487
  }
2679
3488
  var SKIP_SCAN = new Set(["node_modules", "dist", "coverage"]);
@@ -2698,7 +3507,7 @@ function directoryImportsValidate(dir) {
2698
3507
  for (const entry of entries) {
2699
3508
  if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
2700
3509
  continue;
2701
- const path = join3(dir, entry.name);
3510
+ const path = join4(dir, entry.name);
2702
3511
  if (entry.isDirectory()) {
2703
3512
  if (directoryImportsValidate(path))
2704
3513
  return true;
@@ -2706,7 +3515,7 @@ function directoryImportsValidate(dir) {
2706
3515
  }
2707
3516
  if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
2708
3517
  continue;
2709
- if (runtimeImportsValidate(readFileSync3(path, "utf8")))
3518
+ if (runtimeImportsValidate(readFileSync4(path, "utf8")))
2710
3519
  return true;
2711
3520
  }
2712
3521
  return false;
@@ -2728,4 +3537,4 @@ function runtimeImportsValidate(source) {
2728
3537
  return false;
2729
3538
  }
2730
3539
 
2731
- export { projectPath, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
3540
+ export { projectPath, LOGICAL_FORMAT, catalogNamespace, loadSnapshots, assertSnapshots, assertLiveExternal, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintReprints, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, exportProject, loadConfig, loadSchema };