okmodel 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +263 -23
  2. package/dist/contracts/catalog/document.d.ts +26 -2
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/ext/citext.js +1 -1
  5. package/dist/dialects/pg/ext/index.js +1 -1
  6. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  7. package/dist/dialects/pg/index.js +3 -3
  8. package/dist/dialects/pg/introspect.d.ts +6 -0
  9. package/dist/dialects/pg/role/sql.d.ts +21 -4
  10. package/dist/dialects/pg/schema.d.ts +15 -6
  11. package/dist/dialects/pg/table.d.ts +17 -1
  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 -1
  15. package/dist/runtime/catalog-cache.d.ts +17 -0
  16. package/dist/runtime/compat-words.d.ts +23 -0
  17. package/dist/runtime/drift-detail.d.ts +20 -4
  18. package/dist/runtime/drift.d.ts +4 -2
  19. package/dist/runtime/pg/bun.d.ts +12 -7
  20. package/dist/runtime/pg/bun.js +5 -2
  21. package/dist/runtime/pg/pg.d.ts +12 -6
  22. package/dist/runtime/pg/pg.js +5 -2
  23. package/dist/runtime/pg/pglite.d.ts +11 -3
  24. package/dist/runtime/pg/pglite.js +5 -2
  25. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  26. package/dist/runtime/pg/postgresjs.js +5 -2
  27. package/dist/runtime/plan.d.ts +3 -1
  28. package/dist/runtime/tenancy/index.js +1 -1
  29. package/dist/runtime/topology.d.ts +191 -0
  30. package/dist/runtime/traits/index.js +7 -6
  31. package/dist/runtime/types.d.ts +59 -2
  32. package/dist/runtime/validate/index.js +2 -2
  33. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  34. package/dist/shared/{ty3v3hfk.js → 47xzb9fc.js} +2 -2
  35. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  36. package/dist/shared/{qybh48nd.js → 5fqx9yh4.js} +6 -2
  37. package/dist/shared/5mtf6cj3.js +94 -0
  38. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  39. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  40. package/dist/shared/{t616ynfn.js → 8bwbwy8h.js} +71 -13
  41. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  42. package/dist/shared/{wekze34d.js → 8xsbagad.js} +74 -2
  43. package/dist/shared/{bryqk9k9.js → 922qbgnw.js} +3 -3
  44. package/dist/shared/{meck53jv.js → 990a291j.js} +6 -5
  45. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  46. package/dist/shared/9yn6a2vd.js +11 -0
  47. package/dist/shared/{2ssqrv66.js → ayz64vzj.js} +1037 -181
  48. package/dist/shared/{k34e0xf2.js → bt9sfq0z.js} +16 -14
  49. package/dist/shared/{scwxrpx2.js → bzsjtd7c.js} +84 -15
  50. package/dist/shared/{m9tv7xn7.js → ckjbyrt9.js} +4 -4
  51. package/dist/shared/{ydaz1sv3.js → dxjp14rk.js} +10 -2
  52. package/dist/shared/{49ypnfwt.js → fancm5pt.js} +24 -229
  53. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  54. package/dist/shared/{1tfdfvhh.js → fyqkk1m0.js} +6 -4
  55. package/dist/shared/g516zdsb.js +905 -0
  56. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  57. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  58. package/dist/shared/{p24x2334.js → h4nqvjkp.js} +5 -3
  59. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  60. package/dist/shared/htnaj77v.js +17 -0
  61. package/dist/shared/jdgqqx31.js +72 -0
  62. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  63. package/dist/shared/{m78s7898.js → k7cc9nzk.js} +24 -7
  64. package/dist/shared/kdkxh3kb.js +758 -0
  65. package/dist/shared/nkc9sfqb.js +1161 -0
  66. package/dist/shared/{xva8v7qk.js → p7pfx624.js} +5 -5
  67. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  68. package/dist/shared/{ne02ng5c.js → qy9rdq0g.js} +9 -5
  69. package/dist/shared/{3ekj9x92.js → r6pj4fq2.js} +5 -3
  70. package/dist/shared/r73pt594.js +426 -0
  71. package/dist/shared/rasvvzgz.js +21 -0
  72. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  73. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  74. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  75. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  76. package/dist/shared/wxn4b7pp.js +252 -0
  77. package/dist/shared/xdpj5kkm.js +2203 -0
  78. package/dist/shared/xhh1srw3.js +589 -0
  79. package/dist/shared/{mt5wswjz.js → xsq7v6wp.js} +2 -2
  80. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  81. package/dist/shared/ykv7wh5b.js +14 -0
  82. package/dist/shared/{fptpmvxp.js → z2rnngj4.js} +26 -21
  83. package/dist/tooling/cli.js +46 -17
  84. package/dist/tooling/migrate/apply.d.ts +61 -5
  85. package/dist/tooling/migrate/backfill.d.ts +136 -0
  86. package/dist/tooling/migrate/check.d.ts +65 -0
  87. package/dist/tooling/migrate/classify.d.ts +108 -0
  88. package/dist/tooling/migrate/commands.d.ts +2 -2
  89. package/dist/tooling/migrate/config.d.ts +22 -0
  90. package/dist/tooling/migrate/doctor.d.ts +3 -0
  91. package/dist/tooling/migrate/drift.d.ts +18 -2
  92. package/dist/tooling/migrate/estimate.d.ts +50 -0
  93. package/dist/tooling/migrate/extensions.d.ts +2 -0
  94. package/dist/tooling/migrate/files.d.ts +19 -0
  95. package/dist/tooling/migrate/index.d.ts +3 -2
  96. package/dist/tooling/migrate/index.js +69 -1
  97. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  98. package/dist/tooling/migrate/lint.d.ts +71 -0
  99. package/dist/tooling/migrate/managed.d.ts +1 -1
  100. package/dist/tooling/migrate/plan.d.ts +64 -9
  101. package/dist/tooling/migrate/project.d.ts +42 -7
  102. package/dist/tooling/migrate/provision.d.ts +18 -0
  103. package/dist/tooling/migrate/reference.d.ts +40 -0
  104. package/dist/tooling/migrate/runner.d.ts +108 -0
  105. package/dist/tooling/migrate/safe.d.ts +77 -0
  106. package/dist/tooling/migrate/seed.d.ts +20 -0
  107. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  108. package/dist/tooling/migrate/status.d.ts +25 -6
  109. package/dist/tooling/testing/factories.d.ts +117 -0
  110. package/dist/tooling/testing/facts.d.ts +75 -0
  111. package/dist/tooling/testing/index.d.ts +89 -0
  112. package/dist/tooling/testing/index.js +8 -0
  113. package/dist/tooling/testing/isolation.d.ts +30 -0
  114. package/dist/tooling/testing/random.d.ts +40 -0
  115. package/dist/tooling/testing/record.d.ts +38 -0
  116. package/package.json +6 -2
  117. package/dist/shared/5nmdenb6.js +0 -1229
  118. package/dist/shared/dbjej1tw.js +0 -456
  119. package/dist/shared/g4f5kcx9.js +0 -37
  120. package/dist/shared/qbxzvwwc.js +0 -88
  121. package/dist/shared/zwmqxb0g.js +0 -79
  122. /package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  123. /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
@@ -0,0 +1,2203 @@
1
+ import {
2
+ VALIDATION_NOT_IMPORTED2,
3
+ VALIDATION_NOT_IMPORTED_FIX2
4
+ } from "./n442h54c.js";
5
+ import {
6
+ roleObject,
7
+ grantObject,
8
+ defaultPrivilegeObject,
9
+ serializeCatalog2,
10
+ catalogHash2,
11
+ startupCatalog2,
12
+ parseCatalog2
13
+ } from "./8bwbwy8h.js";
14
+ import {
15
+ open2
16
+ } from "./wqem4jed.js";
17
+ import {
18
+ canonicalTypeName,
19
+ batchSizeField,
20
+ aboutBatchCount,
21
+ parseReplace,
22
+ planMigration,
23
+ formatPlan,
24
+ parsePlan,
25
+ staleRenames
26
+ } from "./ayz64vzj.js";
27
+ import {
28
+ contributedFields
29
+ } from "./n0dgs0pb.js";
30
+ import {
31
+ assertValidation,
32
+ writeWouldValidate
33
+ } from "./9m80mkac.js";
34
+ import {
35
+ catalog,
36
+ isDomain
37
+ } from "./g7ez901n.js";
38
+ import {
39
+ snakeCase,
40
+ emittedTypeName
41
+ } from "./jsd07prz.js";
42
+ import {
43
+ ColumnBuilder2
44
+ } from "./c9z5g0fm.js";
45
+ import {
46
+ staticNamespace2
47
+ } from "./t4tbsy9h.js";
48
+ import {
49
+ OkmError2
50
+ } from "./52szzakm.js";
51
+
52
+ // src/tooling/errors/registry.ts
53
+ var ERROR_DOCS = [
54
+ {
55
+ code: "OKM1012",
56
+ title: "Trait field conflict",
57
+ summary: "A trait added a field that the table already declares.",
58
+ fix: "Rename the table field or drop the trait that introduces it. The error names both."
59
+ },
60
+ {
61
+ code: "OKM1020",
62
+ title: "Unknown table",
63
+ summary: "A table, dependency, or catalog object is missing or does not match its identity.",
64
+ fix: "Use a name that is in the schema. The error lists the accepted names and a nearest-name hint when one is close."
65
+ },
66
+ {
67
+ code: "OKM1021",
68
+ title: "Foreign key target",
69
+ summary: "A foreign key is missing its target table or column, or the target is ambiguous.",
70
+ fix: "Name the referenced table and columns. The error lists the columns that exist on the target."
71
+ },
72
+ {
73
+ code: "OKM1022",
74
+ title: "Foreign key type",
75
+ summary: "A foreign key column and the column it references have different types.",
76
+ fix: "Give both columns the same type. The error names the type that is accepted."
77
+ },
78
+ {
79
+ code: "OKM1023",
80
+ title: "Duplicate name",
81
+ summary: "Two catalog objects share an identity, or two tables emit the same type name.",
82
+ fix: "Rename one of them. Table names and emitted type names must be unique in the schema."
83
+ },
84
+ {
85
+ code: "OKM1024",
86
+ title: "Table file not in schema",
87
+ summary: "A table file is not passed to schema().",
88
+ fix: "Add the file's table to the schema() call that okm check reads."
89
+ },
90
+ {
91
+ code: "OKM1025",
92
+ title: "Multiple Register",
93
+ summary: "More than one Register augmentation is in the project.",
94
+ fix: 'Keep a single declare module "okmodel" that sets schema. Pass an explicit schema type everywhere else.'
95
+ },
96
+ {
97
+ code: "OKM1026",
98
+ title: "Dependency cycle",
99
+ summary: "The catalog dependency graph contains a cycle, so objects cannot be ordered.",
100
+ fix: "Break the cycle. The error is raised while ordering the catalog."
101
+ },
102
+ {
103
+ code: "OKM1027",
104
+ title: "Unreadable catalog",
105
+ summary: "The catalog document is malformed or uses a version this build cannot read.",
106
+ fix: "Regenerate the catalog with this version of okmodel. Do not hand-edit the document."
107
+ },
108
+ {
109
+ code: "OKM1030",
110
+ title: "Validation in two places",
111
+ summary: "A field is validated both on the column and somewhere else.",
112
+ fix: "Keep one validation for the field. The error names the field."
113
+ },
114
+ {
115
+ code: "OKM1040",
116
+ title: "Preset name collision",
117
+ summary: "A preset uses the name of a client method or a reserved name, or a table and its traits (or two traits) define the same preset.",
118
+ fix: "Rename the preset. The error names both sources when two define it. okm upgrade renames a preset when a later release claims its name."
119
+ },
120
+ {
121
+ code: "OKM1051",
122
+ title: "Archive foreign key",
123
+ summary: "A foreign key into a table-strategy archivable table uses CASCADE, SET NULL, or SET DEFAULT and is not listed in cascade.",
124
+ fix: "List the child table, the foreign key, and its action in cascade, or use RESTRICT or NO ACTION."
125
+ },
126
+ {
127
+ code: "OKM1052",
128
+ title: "Not archivable",
129
+ summary: "archive or restore was called on a table that is not archivable.",
130
+ fix: "Call it only on an archivable table. The methods are absent from the types of other tables."
131
+ },
132
+ {
133
+ code: "OKM1060",
134
+ title: "Invalid column",
135
+ summary: "A column definition has a bad length, precision, scale, array rank, interval qualifier, or an empty or repeated picklist or enum.",
136
+ fix: "Use a value the error lists as accepted."
137
+ },
138
+ {
139
+ code: "OKM1061",
140
+ title: "Reserved option",
141
+ summary: "The option exists in the types but this version does not implement it.",
142
+ fix: "Remove the option until the prompt or milestone named in the error adds it."
143
+ },
144
+ {
145
+ code: "OKM1101",
146
+ title: "Find without limit",
147
+ summary: "find was called without a limit.",
148
+ fix: "Pass limit, or call .all(reason) when the full set is intentional."
149
+ },
150
+ {
151
+ code: "OKM1102",
152
+ title: "Unfiltered write",
153
+ summary: "update or delete would run with an empty where.",
154
+ fix: "Pass a filter. undefined in a write filter does not mean every row."
155
+ },
156
+ {
157
+ code: "OKM1104",
158
+ title: "Upsert target",
159
+ summary: "onConflict names columns that are not a unique constraint.",
160
+ fix: "Set on to the columns of a unique constraint or the primary key."
161
+ },
162
+ {
163
+ code: "OKM1105",
164
+ title: "Include without limit",
165
+ summary: "A to-many include has no limit.",
166
+ fix: "Pass limit on the include, or call .all(reason)."
167
+ },
168
+ {
169
+ code: "OKM1110",
170
+ title: "Engine version",
171
+ summary: "The feature needs a newer engine than schema({ requires }) allows.",
172
+ fix: "Raise requires, or stop using the feature. The error names both versions."
173
+ },
174
+ {
175
+ code: "OKM1111",
176
+ title: "Missing capability",
177
+ summary: "The call needs a driver capability this driver does not have.",
178
+ fix: "Use a driver that declares the capability, or drop the call. The error names the flag."
179
+ },
180
+ {
181
+ code: "OKM1120",
182
+ title: "Unknown field",
183
+ summary: "A field name in where, select, orderBy, or include is not on the table.",
184
+ fix: "Use a field from the catalog. The error includes a nearest-name hint when one is close."
185
+ },
186
+ {
187
+ code: "OKM1121",
188
+ title: "Object as value",
189
+ summary: "A plain object was passed where a value is required. JSON cannot create an operator.",
190
+ fix: "Wrap the value with eq. eq is the equality form for json and jsonb."
191
+ },
192
+ {
193
+ code: "OKM1122",
194
+ title: "Bad identifier",
195
+ summary: "An identifier is empty, too long, contains NUL or another control character, or is an unquoted reserved word.",
196
+ fix: "Use a shorter name without control characters. Quote is not accepted here. The error names the rule that failed."
197
+ },
198
+ {
199
+ code: "OKM1123",
200
+ title: "Hidden field allowlist",
201
+ summary: "A hidden field was added to a filter or sort allowlist.",
202
+ fix: "Remove the hidden field from the allowlist. Hidden fields stay out of caller filters and sorts."
203
+ },
204
+ {
205
+ code: "OKM1124",
206
+ title: "Operator mismatch",
207
+ summary: "An operator does not apply to the column type.",
208
+ fix: "Use an operator the error lists for that column type. Text search needs a tsvector column."
209
+ },
210
+ {
211
+ code: "OKM1130",
212
+ title: "Cursor order",
213
+ summary: "A cursor was reused with a different orderBy than the one that created it, or is not a cursor page() returned.",
214
+ fix: "Request the next page with the same orderBy and the exact next value the previous page returned. The cursor encodes that order."
215
+ },
216
+ {
217
+ code: "OKM1190",
218
+ title: "Safety violation",
219
+ summary: "A query composition broke a core invariant. violations lists every broken rule in stable order.",
220
+ fix: "Change the contribution named on the first violation, or pass an explicit escape hatch (unscoped, all, trusted, allow) and record the reason."
221
+ },
222
+ {
223
+ code: "OKM1191",
224
+ title: "No snapshot plan",
225
+ summary: "A multi-statement read was requested inside READ COMMITTED and no single-statement plan exists.",
226
+ fix: "Run the read in REPEATABLE READ or SERIALIZABLE, or shape it so it is one statement."
227
+ },
228
+ {
229
+ code: "OKM1200",
230
+ title: "Validation failed",
231
+ summary: "A write or a standalone check failed a validation rule. issues lists each path and message key.",
232
+ fix: "Fix the fields named in issues. Each issue message is a key, not display text."
233
+ },
234
+ {
235
+ code: "OKM1201",
236
+ title: "Validation not imported",
237
+ summary: VALIDATION_NOT_IMPORTED2,
238
+ fix: VALIDATION_NOT_IMPORTED_FIX2
239
+ },
240
+ {
241
+ code: "OKM1210",
242
+ title: "Codec rejected a value",
243
+ summary: "A codec refused a value: not numeric text, not a valid temporal value, or the wrong shape.",
244
+ fix: "Pass a value the error lists as accepted. A missing Temporal global tells you to assign one to globalThis.Temporal."
245
+ },
246
+ {
247
+ code: "OKM1401",
248
+ title: "Outcome unknown",
249
+ summary: "A commit or batch was sent and its result never arrived, so the write may or may not have happened.",
250
+ fix: "Check whether the write landed, using an idempotency key, before retrying. This error is never retried automatically."
251
+ },
252
+ {
253
+ code: "OKM1510",
254
+ title: "Unsafe migration",
255
+ summary: "A migration has an unresolved lint error, or an override with an empty reason or a code the statement did not trigger.",
256
+ fix: "Put `-- okm-allow OKM15xx: reason` on the line above the statement. The code must be one that statement triggered, and the reason must not be empty."
257
+ },
258
+ {
259
+ code: "OKM1511",
260
+ title: "Drop table",
261
+ summary: "A statement drops a table. The rows and anything that reads them are gone.",
262
+ fix: "Stop reading the table in an expand migration, then drop it in a later contract. Or allow OKM1511 with a reason."
263
+ },
264
+ {
265
+ code: "OKM1512",
266
+ title: "Drop column",
267
+ summary: "A statement drops a column. Stored values and the code that reads them are gone.",
268
+ fix: "Stop reading the column in an expand migration, then drop it in a later contract. Or allow OKM1512 with a reason."
269
+ },
270
+ {
271
+ code: "OKM1513",
272
+ title: "Drop enum",
273
+ summary: "A statement drops an enum that columns or code may still depend on.",
274
+ fix: "Move every column off the enum first. Or allow OKM1513 with a reason."
275
+ },
276
+ {
277
+ code: "OKM1514",
278
+ title: "Drop domain",
279
+ summary: "A statement drops a domain that columns or code may still depend on.",
280
+ fix: "Move every column off the domain first. Or allow OKM1514 with a reason."
281
+ },
282
+ {
283
+ code: "OKM1515",
284
+ title: "Drop function",
285
+ summary: "A statement drops a function that queries or triggers may still call.",
286
+ fix: "Remove the callers in an expand migration, then drop the function. Or allow OKM1515 with a reason."
287
+ },
288
+ {
289
+ code: "OKM1516",
290
+ title: "Drop view",
291
+ summary: "A statement drops a view that queries may still read.",
292
+ fix: "Stop reading the view in an expand migration, then drop it. Or allow OKM1516 with a reason."
293
+ },
294
+ {
295
+ code: "OKM1517",
296
+ title: "Drop extension",
297
+ summary: "A statement drops an extension. A dependent still in the catalog is refused before this finding.",
298
+ fix: "Confirm nothing outside the catalog uses the extension. Or allow OKM1517 with a reason."
299
+ },
300
+ {
301
+ code: "OKM1518",
302
+ title: "Drop materialized view",
303
+ summary: "A statement drops a materialized view and the rows it stores.",
304
+ fix: "Stop reading the view in an expand migration, then drop it. Or allow OKM1518 with a reason."
305
+ },
306
+ {
307
+ code: "OKM1519",
308
+ title: "Rename column",
309
+ summary: "A statement renames a column. Existing queries still use the old name.",
310
+ fix: "Add the new column, backfill, and switch readers before dropping the old name. Or allow OKM1519 with a reason."
311
+ },
312
+ {
313
+ code: "OKM1520",
314
+ title: "Schema drift",
315
+ summary: "The database has drifted past what an expand migration can repair.",
316
+ fix: "Inspect the diff and repair the database, or generate a migration that matches the drift."
317
+ },
318
+ {
319
+ code: "OKM1521",
320
+ title: "Snapshot mismatch",
321
+ summary: "Provisioning from the snapshot does not match the replayed migration history.",
322
+ fix: "Regenerate the snapshot from the history. okm migrate check --provision is the command that reports this."
323
+ },
324
+ {
325
+ code: "OKM1522",
326
+ title: "Apply lock held",
327
+ summary: "Another apply already holds the target's migration lock.",
328
+ fix: "Wait for that apply to finish. Do not start a second apply against the same target."
329
+ },
330
+ {
331
+ code: "OKM1523",
332
+ title: "Rename table",
333
+ summary: "A statement renames a table. Existing queries still use the old name.",
334
+ fix: "Create the new table, backfill, and switch readers before dropping the old name. Or allow OKM1523 with a reason."
335
+ },
336
+ {
337
+ code: "OKM1524",
338
+ title: "Column type change",
339
+ summary: "A statement changes a column type. Existing readers and writers may not accept the new type.",
340
+ fix: "Add a new column, backfill, and switch readers before dropping the old one. Or allow OKM1524 with a reason."
341
+ },
342
+ {
343
+ code: "OKM1525",
344
+ title: "Required column",
345
+ summary: "A statement adds a NOT NULL column with no default to a table that already has rows.",
346
+ fix: "Add the column nullable, backfill, then set it NOT NULL. Or allow OKM1525 with a reason."
347
+ },
348
+ {
349
+ code: "OKM1526",
350
+ title: "Drop default",
351
+ summary: "A statement removes a column default. Writers that omitted the column will fail.",
352
+ fix: "Update writers to send the column, then drop the default. Or allow OKM1526 with a reason."
353
+ },
354
+ {
355
+ code: "OKM1527",
356
+ title: "Shrink length",
357
+ summary: "A statement shortens a character length. Values longer than the new limit no longer fit.",
358
+ fix: "Shorten the stored values first, or keep the old length. Or allow OKM1527 with a reason."
359
+ },
360
+ {
361
+ code: "OKM1528",
362
+ title: "Unique constraint",
363
+ summary: "A statement adds a unique or primary-key constraint on a table that already has rows.",
364
+ fix: "Deduplicate the rows first. Or allow OKM1528 with a reason."
365
+ },
366
+ {
367
+ code: "OKM1529",
368
+ title: "Unique index",
369
+ summary: "A statement creates a unique index on a table that already has rows.",
370
+ fix: "Deduplicate the rows first. Or allow OKM1529 with a reason."
371
+ },
372
+ {
373
+ code: "OKM1530",
374
+ title: "Ambiguous rename",
375
+ summary: "A rename could match more than one object.",
376
+ fix: "Declare the rename explicitly so the plan has one pairing."
377
+ },
378
+ {
379
+ code: "OKM1531",
380
+ title: "Validating check",
381
+ summary: "A statement adds or validates a check against rows already stored.",
382
+ fix: "Add the check NOT VALID, repair the rows, then validate it. Or allow OKM1531 with a reason."
383
+ },
384
+ {
385
+ code: "OKM1532",
386
+ title: "Validating foreign key",
387
+ summary: "A statement adds or validates a foreign key against rows already stored.",
388
+ fix: "Add the foreign key NOT VALID, repair the rows, then validate it. Or allow OKM1532 with a reason."
389
+ },
390
+ {
391
+ code: "OKM1533",
392
+ title: "Narrowing type",
393
+ summary: "A statement narrows a numeric or integer type. Values outside the new range no longer fit.",
394
+ fix: "Rewrite the values into the new range first. Or allow OKM1533 with a reason."
395
+ },
396
+ {
397
+ code: "OKM1534",
398
+ title: "Index locks",
399
+ summary: "A statement creates an index on an existing table without CONCURRENTLY.",
400
+ fix: "Create the index with CREATE INDEX CONCURRENTLY, outside a transaction. The planner does this on an existing table."
401
+ },
402
+ {
403
+ code: "OKM1535",
404
+ title: "Check without NOT VALID",
405
+ summary: "A statement adds a check on an existing table without NOT VALID.",
406
+ fix: "Add the check NOT VALID, then validate it in its own step. The planner does this on an existing table."
407
+ },
408
+ {
409
+ code: "OKM1536",
410
+ title: "Foreign key without NOT VALID",
411
+ summary: "A statement adds a foreign key on an existing table without NOT VALID.",
412
+ fix: "Add the foreign key NOT VALID, then validate it in its own step. The planner does this on an existing table."
413
+ },
414
+ {
415
+ code: "OKM1537",
416
+ title: "Set not null",
417
+ summary: "A statement sets NOT NULL on a column that already exists.",
418
+ fix: "Add CHECK (column IS NOT NULL) NOT VALID, validate it, then SET NOT NULL and drop the check. The planner does this on an existing column."
419
+ },
420
+ {
421
+ code: "OKM1538",
422
+ title: "Type rewrite",
423
+ summary: "A statement changes a column type in a way that rewrites the table.",
424
+ 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."
425
+ },
426
+ {
427
+ code: "OKM1539",
428
+ title: "Timestamp without time zone",
429
+ summary: "A column is timestamp without time zone. The instant depends on the session time zone.",
430
+ fix: "Use timestamptz."
431
+ },
432
+ {
433
+ code: "OKM1540",
434
+ title: "varchar length",
435
+ summary: "A column is varchar(n). text stores the same values without a length limit in the type.",
436
+ fix: "Use text, and check the length in the application when one is required."
437
+ },
438
+ {
439
+ code: "OKM1541",
440
+ title: "Picklist removal",
441
+ summary: "A picklist or enum value was removed and the replacement was not given.",
442
+ fix: "Pass --replace <table>.<column>.<old>=<new>, or =null on a nullable column. The replacement must be in the new list."
443
+ },
444
+ {
445
+ code: "OKM1542",
446
+ title: "Data statement",
447
+ summary: "A migration contains a data statement outside backfill().",
448
+ fix: "Move the statement into backfill(). Data changes are batched and resumable there."
449
+ },
450
+ {
451
+ code: "OKM1543",
452
+ title: "serial column",
453
+ summary: "A column uses serial or a nextval default. Identity is the form this repo generates.",
454
+ fix: "Use an identity column generated always."
455
+ },
456
+ {
457
+ code: "OKM1544",
458
+ title: "json column",
459
+ summary: "A column is json. jsonb is available and stores the same documents in binary.",
460
+ fix: "Use jsonb."
461
+ },
462
+ {
463
+ code: "OKM1545",
464
+ title: "Identity not always",
465
+ summary: "An identity column is generated by default. The repo default is generated always.",
466
+ fix: "Declare the identity as generated always."
467
+ },
468
+ {
469
+ code: "OKM1546",
470
+ title: "Backfill key",
471
+ summary: "A backfill step targets a table that has no primary key.",
472
+ fix: "Add a primary key. A backfill walks that key and does not scan the table."
473
+ },
474
+ {
475
+ code: "OKM1547",
476
+ title: "History differs",
477
+ summary: "Replaying a migration does not match its stored catalog, or the file was generated from a different parent than the previous migration.",
478
+ fix: "Regenerate the migration from the previous catalog so the SQL and the stored catalog describe the same schema."
479
+ },
480
+ {
481
+ code: "OKM1548",
482
+ title: "Previous catalog",
483
+ summary: "An expand migration drops or changes a table, column, constraint, or type the previous catalog relies on.",
484
+ fix: "Keep every table, column, constraint, and type the previous catalog relies on, or classify the migration as contract."
485
+ },
486
+ {
487
+ code: "OKM1549",
488
+ title: "Stale head",
489
+ summary: "The last migration's catalog does not match the schema.",
490
+ fix: "Run okm generate."
491
+ },
492
+ {
493
+ code: "OKM1601",
494
+ title: "Stale typed SQL",
495
+ summary: "A typed SQL signature no longer matches the database.",
496
+ fix: "Regenerate the typed SQL and commit the new signature."
497
+ },
498
+ {
499
+ code: "OKM1701",
500
+ title: "Tenant context missing",
501
+ summary: "A tenant table was used on a client that has no tenant context.",
502
+ fix: "Open the client with the tenant context before reading or writing that table."
503
+ },
504
+ {
505
+ code: "OKM1702",
506
+ title: "Unverifiable tenant SQL",
507
+ summary: "Raw SQL touches a tenant table and the tenant predicate cannot be proved.",
508
+ fix: "Use the query builder, or mark the fragment trusted with a reason if it is reviewed."
509
+ },
510
+ {
511
+ code: "OKM1704",
512
+ title: "Tenant key change",
513
+ summary: "An update tried to change the tenant key.",
514
+ fix: "Remove the tenant key from the update. Moving a row between tenants is not an update."
515
+ },
516
+ {
517
+ code: "OKM1705",
518
+ title: "Global references tenant",
519
+ summary: "A global table references a tenant table.",
520
+ fix: "Drop the reference, or make the referencing table a tenant table."
521
+ },
522
+ {
523
+ code: "OKM1706",
524
+ title: "Tenant index",
525
+ summary: "An index on a tenant table does not lead with the tenant key.",
526
+ fix: "Put the tenant key first in the index columns."
527
+ },
528
+ {
529
+ code: "OKM1707",
530
+ title: "RLS role",
531
+ summary: "connect() was given a role that owns the tables or is a superuser, under the rls strategy.",
532
+ fix: "Connect as a role that is neither the owner nor a superuser."
533
+ },
534
+ {
535
+ code: "OKM1801",
536
+ title: "Dialect mismatch",
537
+ summary: "The schema dialect and the driver dialect are not the same.",
538
+ fix: "Open the schema with the driver package that matches it."
539
+ },
540
+ {
541
+ code: "OKM1802",
542
+ title: "Server version",
543
+ summary: "The connected server does not satisfy schema({ requires }).",
544
+ fix: "Upgrade the server, or lower requires to a version the server meets."
545
+ },
546
+ {
547
+ code: "OKM1803",
548
+ title: "Postgres floor",
549
+ summary: "The connected server is older than PostgreSQL 15, and the schema does not set requires to that older major.",
550
+ fix: "Upgrade the server to PostgreSQL 15 or newer, or set schema({ requires }) to the older major on purpose."
551
+ },
552
+ {
553
+ code: "OKM1810",
554
+ title: "Extension not declared",
555
+ summary: "An extension builder was used but the extension is not declared on the schema.",
556
+ fix: "Declare the extension next to the schema that uses it."
557
+ },
558
+ {
559
+ code: "OKM1811",
560
+ title: "Extension unavailable",
561
+ summary: "A declared extension is not installed on the server.",
562
+ fix: "Install the extension, or remove the declaration. okm doctor reports this against the connected server."
563
+ },
564
+ {
565
+ code: "OKM1812",
566
+ title: "Feature version",
567
+ summary: "A feature needs a newer Postgres or extension version than the one declared or connected.",
568
+ fix: 'Raise the declared version, or avoid the feature. uuidv7() below 18 can use t.id({ default: "uuidv4" }), defaults.id set to "uuidv4", or a client generator. Core features are never gated on an extension.'
569
+ },
570
+ {
571
+ code: "OKM1813",
572
+ title: "Extension defined twice",
573
+ summary: "The same extension definition was registered twice.",
574
+ fix: "Keep one definition. A second copy fails at build."
575
+ },
576
+ {
577
+ code: "OKM1814",
578
+ title: "Extension change refused",
579
+ summary: "The plan would downgrade an extension, move a non-relocatable one, or drop one that still has dependents.",
580
+ fix: "Drop or move the dependents first. Postgres cannot lower an extension version."
581
+ },
582
+ {
583
+ code: "OKM1820",
584
+ title: "View tenancy",
585
+ summary: "A view over tenant tables does not expose the tenant key and is not marked global.",
586
+ fix: "Expose the tenant key so the predicate can be applied, or declare global(reason)."
587
+ },
588
+ {
589
+ code: "OKM1821",
590
+ title: "Replace needs recreate",
591
+ summary: "An incompatible replace would have to recreate dependents. CASCADE is never used.",
592
+ fix: "Review the dependents the plan lists and recreate them explicitly."
593
+ },
594
+ {
595
+ code: "OKM1822",
596
+ title: "Concurrent refresh",
597
+ summary: "REFRESH CONCURRENTLY was declared without a unique index on the materialized view.",
598
+ fix: "Add a unique index, or refresh without CONCURRENTLY."
599
+ },
600
+ {
601
+ code: "OKM1823",
602
+ title: "Security definer path",
603
+ summary: "A SECURITY DEFINER function has no search_path.",
604
+ fix: "Set search_path on the function to the schemas it is allowed to see."
605
+ },
606
+ {
607
+ code: "OKM1824",
608
+ title: "Function dependencies",
609
+ summary: "A plpgsql function has no dependsOn. Postgres does not record dependencies inside the body.",
610
+ fix: "List dependsOn. LANGUAGE sql with BEGIN ATOMIC is inferred and does not need the list."
611
+ },
612
+ {
613
+ code: "OKM1825",
614
+ title: "Missing privilege",
615
+ summary: "The application role lacks a privilege on a managed object.",
616
+ fix: "Grant the privilege, or change the role. okm doctor reports the object and the privilege."
617
+ },
618
+ {
619
+ code: "OKM1830",
620
+ title: "Lock outside transaction",
621
+ summary: "find was called with a row lock outside tx().",
622
+ fix: "Take the lock inside tx(). Advisory locks are transaction-scoped too."
623
+ },
624
+ {
625
+ code: "OKM1840",
626
+ title: "Replica not allowed",
627
+ summary: "A replica route was used on an operation that requires the primary, or inside tx().",
628
+ fix: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
629
+ },
630
+ {
631
+ code: "OKM1841",
632
+ title: "External object differs",
633
+ summary: "An external object does not match the owner's exported catalog.",
634
+ fix: "Update the dependency's export, or stop treating the object as external."
635
+ },
636
+ {
637
+ code: "OKM1842",
638
+ title: "Cross-database foreign key",
639
+ summary: "A foreign key points at a table in another database.",
640
+ fix: "Keep the foreign key inside one database. Cross-catalog keys are allowed only there."
641
+ },
642
+ {
643
+ code: "OKM1843",
644
+ title: "No eligible replica",
645
+ summary: "A replica was required and none is eligible: unhealthy, behind the session, or none configured.",
646
+ fix: "Configure a replica or drop the route. This call does not read the primary."
647
+ },
648
+ {
649
+ code: "OKM1844",
650
+ title: "Fallback refused",
651
+ summary: "An automatic read found no eligible replica and fallback is error.",
652
+ fix: "Restore a replica, or set fallback to primary if the primary should absorb the read."
653
+ },
654
+ {
655
+ code: "OKM1845",
656
+ title: "Target unresolved",
657
+ summary: "The target could not be resolved. The tenant is unknown or the resolver failed.",
658
+ fix: "Check the tenant id and the resolver. Connection details are not guessed."
659
+ },
660
+ {
661
+ code: "OKM1846",
662
+ title: "Acquire timeout",
663
+ summary: "The pool did not hand out a connection within timeouts.acquire.",
664
+ fix: "Raise timeouts.acquire, or stop holding connections. The wait does not spill into another pool."
665
+ },
666
+ {
667
+ code: "OKM1850",
668
+ title: "Protected target",
669
+ summary: "The operation is blocked on a protected target and --allow-protected was not passed.",
670
+ fix: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
671
+ },
672
+ {
673
+ code: "OKM1851",
674
+ title: "Target not empty",
675
+ summary: "Provisioning was asked to fill a target that already has objects.",
676
+ fix: "Provision an empty schema or an empty database. A non-empty target is refused."
677
+ },
678
+ {
679
+ code: "OKM1852",
680
+ title: "Protection mismatch",
681
+ summary: "Targets that resolve to the same database disagree about protection, or tenants of one schema-per-tenant database do.",
682
+ fix: "Give those targets the same protection flag. okm check and okm doctor both report this."
683
+ },
684
+ {
685
+ code: "OKM1853",
686
+ title: "Target not named",
687
+ summary: "Several targets are configured and the command was not given --target.",
688
+ fix: "Pass --target <name>. The command does not guess."
689
+ }
690
+ ];
691
+ var byCode;
692
+ function errorDoc(code) {
693
+ const index = byCode ?? indexDocs();
694
+ return index.get(code);
695
+ }
696
+ function indexDocs() {
697
+ const index = new Map;
698
+ for (const doc of ERROR_DOCS)
699
+ index.set(doc.code, doc);
700
+ byCode = index;
701
+ return index;
702
+ }
703
+
704
+ // src/tooling/migrate/lint.ts
705
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
706
+ import { join } from "node:path";
707
+
708
+ // src/tooling/migrate/lint-rules.ts
709
+ var INTEGER_WIDTH = {
710
+ smallint: 2,
711
+ integer: 4,
712
+ bigint: 8
713
+ };
714
+ var FLOAT_WIDTH = {
715
+ real: 4,
716
+ "double precision": 8
717
+ };
718
+ var STEP_RULES = [
719
+ rule("OKM1511", "destructive", "error", "Dropping a table destroys its rows.", (input) => hit(matches(input.step, "drop-table", /^drop table\b/), "drops a table")),
720
+ rule("OKM1512", "destructive", "error", "Dropping a column destroys its values.", (input) => hit(matches(input.step, "drop-column", /^alter table\s+\S+\s+drop column\b/), "drops a column")),
721
+ rule("OKM1513", "destructive", "error", "Dropping an enum can break columns that use it.", (input) => hit(droppedType(input) === "enum", "drops an enum")),
722
+ rule("OKM1514", "destructive", "error", "Dropping a domain can break columns that use it.", (input) => hit(droppedType(input) === "domain", "drops a domain")),
723
+ rule("OKM1515", "destructive", "error", "Dropping a function can break its callers.", (input) => hit(matches(input.step, "drop-function", /^drop function\b/), "drops a function")),
724
+ rule("OKM1516", "destructive", "error", "Dropping a view can break queries that read it.", (input) => hit(matches(input.step, "drop-view", /^drop view\b/), "drops a view")),
725
+ rule("OKM1517", "destructive", "error", "Dropping an extension removes it even when the catalog has no dependent.", (input) => hit(matches(input.step, "drop-extension", /^drop extension\b/), "drops an extension")),
726
+ rule("OKM1518", "destructive", "error", "Dropping a materialized view destroys the rows it stores.", (input) => hit(matches(input.step, "drop-matview", /^drop materialized view\b/), "drops a materialized view")),
727
+ rule("OKM1519", "backward-incompatible", "error", "Renaming a column breaks queries that still use the old name.", (input) => hit(matches(input.step, "rename-column", /^alter table\s+\S+\s+rename column\b/), "renames a column")),
728
+ rule("OKM1523", "backward-incompatible", "error", "Renaming a table breaks queries that still use the old name.", (input) => hit(renamesTable(input.step), "renames a table")),
729
+ rule("OKM1524", "backward-incompatible", "error", "Changing a column type can break readers and writers.", (input) => typeChange(input, "incompatible")),
730
+ rule("OKM1525", "backward-incompatible", "error", "Adding NOT NULL without a default fails when the table already has rows.", (input) => hit(requiredColumn(input), "adds NOT NULL without a default")),
731
+ rule("OKM1526", "backward-incompatible", "error", "Removing a default breaks writers that omit the column.", (input) => hit(matches(input.step, "drop-default", /^alter table\s+\S+\s+alter column\s+\S+\s+drop default\b/), "removes a default")),
732
+ rule("OKM1527", "backward-incompatible", "error", "Shortening a character length rejects values that used to fit.", (input) => typeChange(input, "shrink")),
733
+ rule("OKM1528", "data-dependent", "error", "A new unique or primary key on an existing table fails when rows collide.", (input) => hit(uniqueConstraint(input), "builds a unique or primary key")),
734
+ rule("OKM1529", "data-dependent", "error", "A new unique index on an existing table fails when rows collide.", (input) => hit(uniqueIndex(input), "creates a unique index")),
735
+ rule("OKM1531", "data-dependent", "error", "A check that validates existing rows fails when a row does not pass.", (input) => hit(validatingCheck(input), "validates a check")),
736
+ rule("OKM1532", "data-dependent", "error", "A foreign key that validates existing rows fails when a row has no target.", (input) => hit(validatingForeignKey(input), "validates a foreign key")),
737
+ rule("OKM1533", "data-dependent", "error", "Narrowing an integer, float, or numeric type rejects values outside the new range.", (input) => typeChange(input, "narrow")),
738
+ rule("OKM1534", "locking", "error", "Creating an index on an existing table without CONCURRENTLY locks writes.", (input) => hit(blockingIndex(input), "creates an index without CONCURRENTLY")),
739
+ rule("OKM1535", "locking", "error", "Adding a check without NOT VALID locks the table while it scans.", (input) => hit(checkWithoutNotValid(input), "adds a check without NOT VALID")),
740
+ rule("OKM1536", "locking", "error", "Adding a foreign key without NOT VALID locks the table while it scans.", (input) => hit(foreignKeyWithoutNotValid(input), "adds a foreign key without NOT VALID")),
741
+ rule("OKM1537", "locking", "error", "SET NOT NULL locks the table while it scans for nulls.", (input) => hit(unsafeSetNotNull(input), "sets NOT NULL")),
742
+ rule("OKM1538", "locking", "warning", "A type change that rewrites the table locks it for the rewrite.", (input) => typeChange(input, "rewrite")),
743
+ rule("OKM1542", "data-dependent", "error", "A data statement outside backfill() is not a migration step.", (input) => hit(dataStatement(input.step), "changes rows outside backfill()"))
744
+ ];
745
+ var COLUMN_RULES = [
746
+ {
747
+ code: "OKM1539",
748
+ category: "type-preference",
749
+ severity: "warning",
750
+ doc: "timestamp without time zone depends on the session time zone; use timestamptz.",
751
+ check: (column) => /^timestamp(?:\(\d+\))? without time zone$/.test(canonical(column.definition.dataType)) ? "is timestamp without time zone" : undefined
752
+ },
753
+ {
754
+ code: "OKM1540",
755
+ category: "type-preference",
756
+ severity: "warning",
757
+ doc: "varchar(n) can be text; a length check belongs in the application.",
758
+ check: (column) => /^character varying\(\d+\)$/.test(canonical(column.definition.dataType)) ? "is varchar(n)" : undefined
759
+ },
760
+ {
761
+ code: "OKM1543",
762
+ category: "type-preference",
763
+ severity: "warning",
764
+ doc: "serial and nextval defaults should be identity generated always.",
765
+ check: (column) => isSerial(column) ? "uses serial or nextval" : undefined
766
+ },
767
+ {
768
+ code: "OKM1544",
769
+ category: "type-preference",
770
+ severity: "warning",
771
+ doc: "json should be jsonb, which is available on every supported Postgres.",
772
+ check: (column) => canonical(column.definition.dataType) === "json" ? "is json" : undefined
773
+ },
774
+ {
775
+ code: "OKM1545",
776
+ category: "type-preference",
777
+ severity: "warning",
778
+ doc: "An identity that is not generated always differs from the repo default.",
779
+ check: (column) => column.definition.identity?.always === false ? "is generated by default" : undefined
780
+ }
781
+ ];
782
+ function rule(code, category, severity, doc, check) {
783
+ return { code, category, severity, doc, check };
784
+ }
785
+ function hit(matched, reason) {
786
+ return matched ? [reason] : [];
787
+ }
788
+ function normalized(sql) {
789
+ const collapsed = sql.trim().replace(/\s+/g, " ");
790
+ let out = "";
791
+ let quoted = false;
792
+ for (const char of collapsed) {
793
+ if (char === '"') {
794
+ quoted = !quoted;
795
+ out += char;
796
+ continue;
797
+ }
798
+ out += quoted ? char : char.toLowerCase();
799
+ }
800
+ return out;
801
+ }
802
+ function dataStatement(step) {
803
+ if (step.backfill !== undefined)
804
+ return false;
805
+ const text = normalized(step.sql);
806
+ if (/^(?:insert|update|delete|merge|truncate)\b/.test(text))
807
+ return true;
808
+ return /^with\b/.test(text) && /\b(?:insert|update|delete|merge)\b/.test(text);
809
+ }
810
+ function matches(step, kind, pattern) {
811
+ return pattern.test(normalized(step.sql)) || step.kind === kind;
812
+ }
813
+ function renamesTable(step) {
814
+ const text = normalized(step.sql);
815
+ if (/^alter table\s+\S+\s+rename to\b/.test(text))
816
+ return true;
817
+ return step.kind === "rename-table" && !/^alter table\s+\S+\s+rename (column|constraint)\b/.test(text);
818
+ }
819
+ function droppedType(input) {
820
+ const text = normalized(input.step.sql);
821
+ const dropping = /^drop type\b/.test(text);
822
+ if (!dropping && input.step.kind !== "drop-enum" && input.step.kind !== "drop-domain") {
823
+ return;
824
+ }
825
+ if (input.step.kind === "drop-domain")
826
+ return "domain";
827
+ if (input.step.kind === "drop-enum")
828
+ return "enum";
829
+ const name = lastName(/^drop type\s+(\S+)/.exec(text)?.[1]);
830
+ const found = input.before.objects.find((object) => object.kind === "type" && object.identity.name === name);
831
+ if (found !== undefined && isDomain(found.definition))
832
+ return "domain";
833
+ return "enum";
834
+ }
835
+ function requiredColumn(input) {
836
+ const text = normalized(input.step.sql);
837
+ const adding = /^alter table\s+\S+\s+add column\b/.test(text) || input.step.kind === "add-column-required";
838
+ if (!adding)
839
+ return false;
840
+ if (input.step.kind === "add-column")
841
+ return false;
842
+ if (!tableExists(input))
843
+ return false;
844
+ if (input.step.kind === "add-column-required")
845
+ return true;
846
+ const body = text.replace(/^alter table\s+\S+\s+add column\s+/, "");
847
+ return /\bnot null\b/.test(body) && !/\bdefault\b/.test(body) && !/\bgenerated\b/.test(body);
848
+ }
849
+ function uniqueConstraint(input) {
850
+ if (!tableExists(input))
851
+ return false;
852
+ const text = normalized(input.step.sql);
853
+ if (/\busing index\b/.test(text))
854
+ return false;
855
+ if (/^alter table\s+\S+\s+add constraint\b/.test(text) && /\b(unique|primary key)\b/.test(text)) {
856
+ return true;
857
+ }
858
+ if (!/^create unique index\b/.test(text))
859
+ return false;
860
+ const name = createdIndexName(text);
861
+ if (name === undefined)
862
+ return false;
863
+ return input.later.some((step) => usesIndex(normalized(step.sql), name));
864
+ }
865
+ function uniqueIndex(input) {
866
+ const text = normalized(input.step.sql);
867
+ if (!/^create unique index\b/.test(text))
868
+ return false;
869
+ if (!tableExists(input))
870
+ return false;
871
+ const name = createdIndexName(text);
872
+ if (name !== undefined && input.later.some((step) => usesIndex(normalized(step.sql), name))) {
873
+ return false;
874
+ }
875
+ return true;
876
+ }
877
+ function createdIndexName(text) {
878
+ const match = /^create unique index (?:concurrently )?(?:"([^"]+)"|([a-z_][\w$]*))/.exec(text);
879
+ return match?.[1] ?? match?.[2];
880
+ }
881
+ function usesIndex(text, name) {
882
+ return text.includes(`using index "${name}"`) || text.includes(`using index ${name}`);
883
+ }
884
+ function unsafeSetNotNull(input) {
885
+ const text = normalized(input.step.sql);
886
+ const match = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+set not null\b/.exec(text);
887
+ if (match === null && input.step.kind !== "set-not-null")
888
+ return false;
889
+ if (!tableExists(input))
890
+ return false;
891
+ const table = match?.[1];
892
+ const column = match?.[2];
893
+ if (table !== undefined && column !== undefined && validatedNotNullCheck(input.earlier, table, column)) {
894
+ return false;
895
+ }
896
+ return true;
897
+ }
898
+ function validatedNotNullCheck(earlier, table, column) {
899
+ const needle = `check (${column} is not null) not valid`;
900
+ for (const step of earlier) {
901
+ const text = normalized(step.sql);
902
+ if (!text.startsWith(`alter table ${table} `) || !text.includes(needle))
903
+ continue;
904
+ const added = /^alter table\s+\S+\s+add constraint\s+(\S+)\s+check\b/.exec(text);
905
+ const name = added?.[1];
906
+ if (name === undefined)
907
+ continue;
908
+ const validated = earlier.some((item) => normalized(item.sql).startsWith(`alter table ${table} validate constraint ${name}`));
909
+ if (validated)
910
+ return true;
911
+ }
912
+ return false;
913
+ }
914
+ function blockingIndex(input) {
915
+ const text = normalized(input.step.sql);
916
+ if (!/^create(?: unique)? index\b/.test(text) && input.step.kind !== "create-index")
917
+ return false;
918
+ if (/\bconcurrently\b/.test(text))
919
+ return false;
920
+ return tableExists(input);
921
+ }
922
+ function checkWithoutNotValid(input) {
923
+ return addedConstraint(input, "check") && !/\bnot valid\b/.test(normalized(input.step.sql));
924
+ }
925
+ function foreignKeyWithoutNotValid(input) {
926
+ return addedConstraint(input, "foreign key") && !/\bnot valid\b/.test(normalized(input.step.sql));
927
+ }
928
+ function validatingCheck(input) {
929
+ if (widenValidate(input))
930
+ return false;
931
+ if (validates(input, "check"))
932
+ return true;
933
+ return checkWithoutNotValid(input);
934
+ }
935
+ function widenValidate(input) {
936
+ const text = normalized(input.step.sql);
937
+ if (!/^alter table\s+\S+\s+validate constraint\b/.test(text))
938
+ return false;
939
+ if (input.step.kind === "widen-check")
940
+ return true;
941
+ const name = /^alter table\s+\S+\s+validate constraint\s+(\S+)/.exec(text)?.[1];
942
+ if (name === undefined)
943
+ return false;
944
+ return input.earlier.some((step) => {
945
+ if (step.kind !== "widen-check")
946
+ return false;
947
+ const prior = normalized(step.sql);
948
+ return prior.includes(`add constraint ${name} `);
949
+ });
950
+ }
951
+ function validatingForeignKey(input) {
952
+ if (validates(input, "foreignKey"))
953
+ return true;
954
+ return foreignKeyWithoutNotValid(input);
955
+ }
956
+ function addedConstraint(input, kind) {
957
+ if (!tableExists(input))
958
+ return false;
959
+ const text = normalized(input.step.sql);
960
+ if (!/^alter table\s+\S+\s+add constraint\b/.test(text))
961
+ return false;
962
+ return new RegExp(`\\b${kind}\\b`).test(text);
963
+ }
964
+ function validates(input, kind) {
965
+ const text = normalized(input.step.sql);
966
+ const table = /^alter table\s+\S+\s+validate constraint\s+(\S+)/.exec(text);
967
+ if (table !== null) {
968
+ const token = table[1];
969
+ if (token === undefined)
970
+ return kind === "check";
971
+ const name = lastName(token);
972
+ if (name === undefined)
973
+ return kind === "check";
974
+ const constraint = findConstraint(input.after, name);
975
+ if (constraint === undefined)
976
+ return kind === "check";
977
+ return constraint.definition.constraintKind === kind;
978
+ }
979
+ if (kind === "check" && /^alter domain\s+\S+\s+validate constraint\b/.test(text))
980
+ return true;
981
+ return false;
982
+ }
983
+ function typeChange(input, mode) {
984
+ const text = normalized(input.step.sql);
985
+ const changing = /^alter table\s+\S+\s+alter column\s+\S+\s+set data type\b/.test(text) || input.step.kind === "set-column-type";
986
+ if (!changing)
987
+ return [];
988
+ const compared = columnTypes(input);
989
+ if (compared === undefined) {
990
+ if (mode === "incompatible" || mode === "rewrite")
991
+ return ["changes a column type"];
992
+ return [];
993
+ }
994
+ const { before, after } = compared;
995
+ if (before === after)
996
+ return [];
997
+ const shrink = lengthShrink(before, after);
998
+ if (mode === "shrink")
999
+ return shrink ? ["shortens a character length"] : [];
1000
+ if (shrink)
1001
+ return [];
1002
+ if (mode === "narrow")
1003
+ return narrows(before, after) ? ["narrows a type"] : [];
1004
+ if (lengthGrow(before, after))
1005
+ return [];
1006
+ if (mode === "incompatible")
1007
+ return ["changes a column type"];
1008
+ return rewrites(before, after) ? ["rewrites the table"] : [];
1009
+ }
1010
+ function columnTypes(input) {
1011
+ const text = normalized(input.step.sql);
1012
+ const match = /^alter table\s+(\S+)\s+alter column\s+(\S+)\s+set data type\b/.exec(text);
1013
+ const table = lastName(match?.[1]);
1014
+ const column = lastName(match?.[2]);
1015
+ if (table === undefined || column === undefined)
1016
+ return;
1017
+ const previous = findColumn(input.before, table, column);
1018
+ const next = findColumn(input.after, table, column);
1019
+ if (previous === undefined || next === undefined)
1020
+ return;
1021
+ return {
1022
+ before: canonical(previous.definition.dataType),
1023
+ after: canonical(next.definition.dataType)
1024
+ };
1025
+ }
1026
+ function lengthShrink(before, after) {
1027
+ const left = charLength(before);
1028
+ const right = charLength(after);
1029
+ return left !== undefined && right !== undefined && left.family === right.family && right.length < left.length;
1030
+ }
1031
+ function lengthGrow(before, after) {
1032
+ const left = charLength(before);
1033
+ const right = charLength(after);
1034
+ return left !== undefined && right !== undefined && left.family === right.family && right.length > left.length;
1035
+ }
1036
+ function rewrites(before, after) {
1037
+ if (lengthShrink(before, after) || lengthGrow(before, after))
1038
+ return false;
1039
+ if (isCharacter(before) && after === "text")
1040
+ return false;
1041
+ return true;
1042
+ }
1043
+ function narrows(before, after) {
1044
+ const leftInt = INTEGER_WIDTH[before];
1045
+ const rightInt = INTEGER_WIDTH[after];
1046
+ if (leftInt !== undefined && rightInt !== undefined)
1047
+ return rightInt < leftInt;
1048
+ const leftFloat = FLOAT_WIDTH[before];
1049
+ const rightFloat = FLOAT_WIDTH[after];
1050
+ if (leftFloat !== undefined && rightFloat !== undefined)
1051
+ return rightFloat < leftFloat;
1052
+ const left = numericSpec(before);
1053
+ const right = numericSpec(after);
1054
+ if (left === undefined || right === undefined)
1055
+ return false;
1056
+ return right.precision < left.precision || right.scale < left.scale;
1057
+ }
1058
+ function charLength(type) {
1059
+ const varying = /^character varying\((\d+)\)$/.exec(type);
1060
+ if (varying?.[1] !== undefined)
1061
+ return { family: "varying", length: Number(varying[1]) };
1062
+ const fixed = /^character\((\d+)\)$/.exec(type);
1063
+ if (fixed?.[1] !== undefined)
1064
+ return { family: "fixed", length: Number(fixed[1]) };
1065
+ return;
1066
+ }
1067
+ function isCharacter(type) {
1068
+ return type === "text" || charLength(type) !== undefined;
1069
+ }
1070
+ function numericSpec(type) {
1071
+ const match = /^numeric(?:\((\d+)(?:,(\d+))?\))?$/.exec(type);
1072
+ if (match === null)
1073
+ return;
1074
+ if (match[1] === undefined)
1075
+ return { precision: 1000, scale: 0 };
1076
+ return {
1077
+ precision: Number(match[1]),
1078
+ scale: match[2] === undefined ? 0 : Number(match[2])
1079
+ };
1080
+ }
1081
+ function tableExists(input) {
1082
+ const name = statementTable(input.step);
1083
+ return name !== undefined && input.existingTables.has(name);
1084
+ }
1085
+ function statementTable(step) {
1086
+ const text = normalized(step.sql);
1087
+ const altered = /^(?:alter|drop) table\s+(\S+)/.exec(text);
1088
+ if (altered?.[1] !== undefined)
1089
+ return lastName(altered[1]);
1090
+ const index = /^create(?: unique)? index(?: concurrently)?\s+\S+\s+on\s+(\S+)/.exec(text);
1091
+ return lastName(index?.[1]);
1092
+ }
1093
+ function lastName(token) {
1094
+ if (token === undefined)
1095
+ return;
1096
+ const parts = [...token.matchAll(/"(?:[^"]|"")*"|[A-Za-z_][\w$]*/g)];
1097
+ const last = parts.at(-1)?.[0];
1098
+ if (last === undefined)
1099
+ return;
1100
+ if (last.startsWith('"'))
1101
+ return last.slice(1, -1).replaceAll('""', '"');
1102
+ return last;
1103
+ }
1104
+ function findColumn(source, table, name) {
1105
+ return source.objects.find((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
1106
+ }
1107
+ function findConstraint(source, name) {
1108
+ return source.objects.find((object) => object.kind === "constraint" && object.identity.name === name);
1109
+ }
1110
+ function canonical(typeName) {
1111
+ return canonicalTypeName(typeName);
1112
+ }
1113
+ function isSerial(column) {
1114
+ const typeName = column.definition.dataType.toLowerCase();
1115
+ if (typeName === "serial" || typeName === "smallserial" || typeName === "bigserial")
1116
+ return true;
1117
+ if (column.definition.identity !== undefined)
1118
+ return false;
1119
+ return /nextval\s*\(/i.test(column.definition.defaultExpression ?? "");
1120
+ }
1121
+
1122
+ // src/tooling/migrate/lint.ts
1123
+ function lintPlan(plan, before, after) {
1124
+ const existingTables = tableNames(before);
1125
+ const findings = [];
1126
+ plan.steps.forEach((step, index) => {
1127
+ findings.push(...lintStep(step, index + 1, before, after, existingTables, plan.steps.slice(0, index), plan.steps.slice(index + 1)));
1128
+ });
1129
+ return findings;
1130
+ }
1131
+ function lintCatalog(source) {
1132
+ const findings = [];
1133
+ for (const object of source.objects) {
1134
+ if (object.kind !== "column")
1135
+ continue;
1136
+ for (const rule of COLUMN_RULES) {
1137
+ const reason = rule.check(object);
1138
+ if (reason === undefined)
1139
+ continue;
1140
+ findings.push(finding(rule.severity, rule.code, columnPlace(object), reason));
1141
+ }
1142
+ }
1143
+ return findings;
1144
+ }
1145
+ function lintMigrationDirectory(directory, pending) {
1146
+ if (!existsSync(directory))
1147
+ return [];
1148
+ const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
1149
+ let before = catalog([]);
1150
+ const findings = [];
1151
+ for (const file of names) {
1152
+ const id = file.slice(0, -".sql".length);
1153
+ const plan = parsePlan(readFileSync(join(directory, file), "utf8"));
1154
+ const after = parseCatalog2(readFileSync(join(directory, `${id}.catalog.json`), "utf8"));
1155
+ if (pending === undefined || pending.has(id)) {
1156
+ for (const item of lintPlan(plan, before, after)) {
1157
+ findings.push({ ...item, place: `${id} ${item.place}` });
1158
+ }
1159
+ }
1160
+ before = after;
1161
+ }
1162
+ return findings;
1163
+ }
1164
+ function formatFindings(findings) {
1165
+ if (findings.length === 0)
1166
+ return "";
1167
+ return `${findings.map(formatFinding).join(`
1168
+ `)}
1169
+ `;
1170
+ }
1171
+ function hasError(findings) {
1172
+ return findings.some((item) => item.severity === "error");
1173
+ }
1174
+ function lintRefusal(findings) {
1175
+ const doc = errorDoc("OKM1510");
1176
+ return new OkmError2("OKM1510", formatFindings(findings).trim(), {
1177
+ fix: { summary: doc?.fix ?? "Allow the finding with a reason, or change the statement." }
1178
+ });
1179
+ }
1180
+ function lintStep(step, number, before, after, existingTables, earlier, later) {
1181
+ const hits = [];
1182
+ for (const rule of STEP_RULES) {
1183
+ for (const reason of rule.check({ step, before, after, existingTables, earlier, later })) {
1184
+ hits.push({ code: rule.code, severity: rule.severity, reason });
1185
+ }
1186
+ }
1187
+ const triggered = new Set(hits.map((item) => item.code));
1188
+ const silenced = new Set;
1189
+ const findings = [];
1190
+ const place = `step ${String(number)}`;
1191
+ for (const allow of step.allows ?? []) {
1192
+ if (allow.reason.trim().length === 0 || allow.code.length === 0) {
1193
+ findings.push(finding("error", "OKM1510", place, "override needs a reason"));
1194
+ continue;
1195
+ }
1196
+ if (!triggered.has(allow.code)) {
1197
+ findings.push(finding("error", "OKM1510", place, `override ${allow.code} does not match this statement`));
1198
+ continue;
1199
+ }
1200
+ silenced.add(allow.code);
1201
+ }
1202
+ for (const hit of hits) {
1203
+ if (silenced.has(hit.code))
1204
+ continue;
1205
+ findings.push(finding(hit.severity, hit.code, place, hit.reason));
1206
+ }
1207
+ return findings;
1208
+ }
1209
+ function finding(severity, code, place, reason) {
1210
+ const doc = errorDoc(code);
1211
+ if (doc === undefined)
1212
+ throw new Error(`${code} missing from the error registry.`);
1213
+ return { severity, code, place, reason, fix: doc.fix };
1214
+ }
1215
+ function formatFinding(item) {
1216
+ return `${item.severity} ${item.code} ${item.place}: ${item.reason} -- fix: ${item.fix}`;
1217
+ }
1218
+ function tableNames(source) {
1219
+ const names = new Set;
1220
+ for (const object of source.objects) {
1221
+ if (object.kind === "table")
1222
+ names.add(object.identity.name);
1223
+ }
1224
+ return names;
1225
+ }
1226
+ function columnPlace(column) {
1227
+ return `${column.identity.parent.name}.${column.identity.name}`;
1228
+ }
1229
+
1230
+ // src/tooling/migrate/policy.ts
1231
+ var READ = new Set([
1232
+ "plan",
1233
+ "status",
1234
+ "check",
1235
+ "drift",
1236
+ "verify",
1237
+ "pull",
1238
+ "catalog-export",
1239
+ "inspect"
1240
+ ]);
1241
+ var ALLOWED = new Set(["expand", "reference", "provision"]);
1242
+ var FORWARD_ONLY = new Set(["drop", "rollback"]);
1243
+ function assertTargetPolicy(target, operation, allowProtected = false) {
1244
+ if (FORWARD_ONLY.has(operation)) {
1245
+ throw new OkmError2("invalid", "There is no down migration.", {
1246
+ fix: { summary: "Recovery is a new forward migration." }
1247
+ });
1248
+ }
1249
+ if (!target.protected || allowProtected)
1250
+ return;
1251
+ if (READ.has(operation) || ALLOWED.has(operation))
1252
+ return;
1253
+ throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
1254
+ kind: "forbidden",
1255
+ fix: {
1256
+ summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
1257
+ }
1258
+ });
1259
+ }
1260
+ function assertTargetAlias(targets) {
1261
+ const groups = new Map;
1262
+ for (const target of targets) {
1263
+ const key = `${target.host}
1264
+ ${target.port}
1265
+ ${target.database}`;
1266
+ const group = groups.get(key);
1267
+ if (group === undefined)
1268
+ groups.set(key, [target]);
1269
+ else
1270
+ group.push(target);
1271
+ }
1272
+ for (const group of groups.values()) {
1273
+ const protectedTarget = group.find((target) => target.protected);
1274
+ const open = group.find((target) => !target.protected);
1275
+ if (protectedTarget !== undefined && open !== undefined) {
1276
+ throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
1277
+ fix: {
1278
+ summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
1279
+ }
1280
+ });
1281
+ }
1282
+ }
1283
+ }
1284
+ function assertDirectConnection(url, allowPooler) {
1285
+ if (allowPooler)
1286
+ return;
1287
+ const endpoint = endpointOf(url);
1288
+ const parsed = new URL(url);
1289
+ const host = endpoint.host;
1290
+ if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || parsed.searchParams.get("pgbouncer") === "true") {
1291
+ throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
1292
+ }
1293
+ }
1294
+ function listTargets(config) {
1295
+ if (config.database !== undefined && config.targets !== undefined) {
1296
+ throw new OkmError2("invalid", "Set database or targets, not both.", {
1297
+ fix: { summary: "database is the one target named default. Use targets for more than one." }
1298
+ });
1299
+ }
1300
+ const entries = [];
1301
+ if (config.database !== undefined)
1302
+ entries.push(["default", config.database]);
1303
+ if (config.targets !== undefined) {
1304
+ for (const name of Object.keys(config.targets).sort()) {
1305
+ const input = config.targets[name];
1306
+ if (input !== undefined)
1307
+ entries.push([name, input]);
1308
+ }
1309
+ }
1310
+ return entries.map(([name, input]) => resolveTarget(name, input));
1311
+ }
1312
+ function selectTarget(config, name) {
1313
+ const targets = listTargets(config);
1314
+ if (targets.length === 0) {
1315
+ throw new OkmError2("OKM1845", "No target is configured.", {
1316
+ fix: { summary: "Set database or targets in okmodel.config.ts." }
1317
+ });
1318
+ }
1319
+ if (name === undefined) {
1320
+ if (targets.length === 1) {
1321
+ const only = targets[0];
1322
+ if (only !== undefined)
1323
+ return only;
1324
+ }
1325
+ throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
1326
+ fix: { summary: "Pass --target <name>. The command does not guess." }
1327
+ });
1328
+ }
1329
+ const found = targets.find((target) => target.name === name);
1330
+ if (found === undefined) {
1331
+ throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
1332
+ fix: { summary: "Pass a name from targets, or database for the target named default." }
1333
+ });
1334
+ }
1335
+ return found;
1336
+ }
1337
+ function resolveTarget(name, input) {
1338
+ if (typeof input !== "string" && isReplicaTarget(input)) {
1339
+ throw new OkmError2("OKM1845", `Target ${name} is a replica. Migrations run on the primary only.`, {
1340
+ fix: {
1341
+ summary: "Set database or targets to the primary URL. A replica is not a migration target."
1342
+ }
1343
+ });
1344
+ }
1345
+ const url = typeof input === "string" ? input : input.url;
1346
+ const protectedTarget = typeof input === "string" ? false : input.protected === true;
1347
+ if (url === undefined || url.length === 0) {
1348
+ throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
1349
+ fix: { summary: "Set the target url. A target is not inferred from the environment." }
1350
+ });
1351
+ }
1352
+ const endpoint = endpointOf(url);
1353
+ return {
1354
+ name,
1355
+ url,
1356
+ protected: protectedTarget,
1357
+ host: endpoint.host,
1358
+ port: endpoint.port,
1359
+ database: endpoint.database
1360
+ };
1361
+ }
1362
+ function isReplicaTarget(input) {
1363
+ return "primary" in input || "replicas" in input || "weight" in input || "pool" in input;
1364
+ }
1365
+ function endpointOf(url) {
1366
+ let parsed;
1367
+ try {
1368
+ parsed = new URL(url);
1369
+ } catch {
1370
+ throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
1371
+ fix: { summary: "Pass a postgres:// URL for the target." }
1372
+ });
1373
+ }
1374
+ const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
1375
+ return {
1376
+ host: parsed.hostname.toLowerCase(),
1377
+ port: parsed.port.length > 0 ? parsed.port : "5432",
1378
+ database
1379
+ };
1380
+ }
1381
+
1382
+ // src/tooling/migrate/project.ts
1383
+ import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
1384
+ import { basename, join as join2 } from "node:path";
1385
+ import { pathToFileURL } from "node:url";
1386
+
1387
+ // src/dialects/pg/declarations.ts
1388
+ function schemaDeclarations(source) {
1389
+ const renames = [];
1390
+ for (const item of source.tables) {
1391
+ const tableName = sqlTableName(item, source.casing);
1392
+ const renamed = tableRenamedFrom(item);
1393
+ if (renamed !== undefined) {
1394
+ renames.push({ kind: "table", from: renamed, to: tableName });
1395
+ }
1396
+ for (const [field, value] of Object.entries(item.columns)) {
1397
+ if (!(value instanceof ColumnBuilder2))
1398
+ continue;
1399
+ const state = value.state;
1400
+ const columnName = state.sqlName ?? sqlIdentifier(field, source.casing);
1401
+ if (state.renamedFrom !== undefined) {
1402
+ renames.push({
1403
+ kind: "column",
1404
+ table: tableName,
1405
+ from: state.renamedFrom,
1406
+ to: columnName
1407
+ });
1408
+ }
1409
+ }
1410
+ }
1411
+ return { renames };
1412
+ }
1413
+ function sqlTableName(item, casing) {
1414
+ const options = item.options;
1415
+ return options?.sqlName ?? sqlIdentifier(item.name, casing);
1416
+ }
1417
+ function tableRenamedFrom(item) {
1418
+ const options = item.options;
1419
+ return options?.renamedFrom;
1420
+ }
1421
+ function sqlIdentifier(name, casing) {
1422
+ return casing === "snake" ? snakeCase(name) : name;
1423
+ }
1424
+
1425
+ // src/dialects/pg/emit.ts
1426
+ function emitRowTypes(source) {
1427
+ const lines = [
1428
+ "/**",
1429
+ " * Row types emitted from the schema.",
1430
+ ` * Mode: ${source.types}.`,
1431
+ " */",
1432
+ ""
1433
+ ];
1434
+ const rows = [];
1435
+ const inserts = [];
1436
+ const updates = [];
1437
+ for (const item of source.tables) {
1438
+ const name = emittedTypeName(item.name);
1439
+ const key = propertyName(item.name);
1440
+ rows.push(` readonly ${key}: ${name};`);
1441
+ inserts.push(` readonly ${key}: ${name}Insert;`);
1442
+ updates.push(` readonly ${key}: ${name}Update;`);
1443
+ lines.push(`export interface ${name} {`);
1444
+ lines.push(fields(item, "row", source));
1445
+ lines.push("}", "");
1446
+ lines.push(`export interface ${name}Insert {`);
1447
+ lines.push(fields(item, "insert", source));
1448
+ lines.push("}", "");
1449
+ lines.push(`export interface ${name}Update {`);
1450
+ lines.push(fields(item, "update", source));
1451
+ lines.push("}", "");
1452
+ }
1453
+ lines.push("export interface Rows {");
1454
+ lines.push(rows.join(`
1455
+ `));
1456
+ lines.push("}", "");
1457
+ lines.push("export interface Inserts {");
1458
+ lines.push(inserts.join(`
1459
+ `));
1460
+ lines.push("}", "");
1461
+ lines.push("export interface Updates {");
1462
+ lines.push(updates.join(`
1463
+ `));
1464
+ lines.push("}");
1465
+ return `${lines.join(`
1466
+ `)}
1467
+ `;
1468
+ }
1469
+ function propertyName(name) {
1470
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
1471
+ }
1472
+ function fields(item, kind, source) {
1473
+ const printed = [];
1474
+ const options = item.options;
1475
+ const entries = Object.entries(item.columns);
1476
+ for (const pair of contributedFields(source.traits, options)) {
1477
+ if (!Object.hasOwn(item.columns, pair[0]))
1478
+ entries.push([pair[0], pair[1]]);
1479
+ }
1480
+ if (source.tenancy !== undefined && options?.tenancy?.kind !== "global" && !Object.hasOwn(item.columns, source.tenancy.key)) {
1481
+ entries.push([source.tenancy.key, source.tenancy.column()]);
1482
+ }
1483
+ for (const [field, builder] of entries) {
1484
+ if (!(builder instanceof ColumnBuilder2)) {
1485
+ continue;
1486
+ }
1487
+ const state = builder.state;
1488
+ if (kind === "row") {
1489
+ if (!state.hidden) {
1490
+ printed.push(` readonly ${field}: ${valueType(state, "required")};`);
1491
+ }
1492
+ continue;
1493
+ }
1494
+ const written = kind === "update" ? updateMode(state) : insertMode(state);
1495
+ if (written === "omit") {
1496
+ continue;
1497
+ }
1498
+ const mode = kind === "update" ? "optional" : written;
1499
+ printed.push(` readonly ${field}: ${valueType(state, mode)};`);
1500
+ }
1501
+ return printed.join(`
1502
+ `);
1503
+ }
1504
+ function insertMode(state) {
1505
+ if (state.omitWrite || state.generated !== undefined || state.guarded) {
1506
+ return "omit";
1507
+ }
1508
+ if (state.hasDefault || state.nullable) {
1509
+ return "optional";
1510
+ }
1511
+ return "required";
1512
+ }
1513
+ function updateMode(state) {
1514
+ if (state.omitUpdate || insertMode(state) === "omit")
1515
+ return "omit";
1516
+ return "optional";
1517
+ }
1518
+ function valueType(state, mode) {
1519
+ let label = scalarLabel(state);
1520
+ if (state.dims > 0) {
1521
+ for (let rank = 0;rank < state.dims; rank += 1) {
1522
+ label = `readonly (${label})[]`;
1523
+ }
1524
+ }
1525
+ if (state.nullable) {
1526
+ label += " | null";
1527
+ }
1528
+ if (mode === "optional") {
1529
+ label += " | undefined";
1530
+ }
1531
+ return label;
1532
+ }
1533
+ function scalarLabel(state) {
1534
+ if (state.picklist !== undefined) {
1535
+ return state.picklist.values.map((value) => JSON.stringify(value)).join(" | ");
1536
+ }
1537
+ if (state.typeLabel !== undefined) {
1538
+ return state.typeLabel;
1539
+ }
1540
+ const base = baseName(state.baseType);
1541
+ switch (base) {
1542
+ case "smallint":
1543
+ case "integer":
1544
+ case "real":
1545
+ case "double precision":
1546
+ return "number";
1547
+ case "bigint":
1548
+ case "numeric":
1549
+ case "text":
1550
+ case "varchar":
1551
+ case "char":
1552
+ case "citext":
1553
+ case "uuid":
1554
+ case "inet":
1555
+ case "cidr":
1556
+ case "macaddr":
1557
+ case "macaddr8":
1558
+ case "tsvector":
1559
+ case "ltree":
1560
+ return "string";
1561
+ case "boolean":
1562
+ return "boolean";
1563
+ case "bytea":
1564
+ return "Uint8Array";
1565
+ case "json":
1566
+ case "jsonb":
1567
+ return "unknown";
1568
+ case "timestamptz":
1569
+ return "Temporal.Instant";
1570
+ case "timestamp":
1571
+ return "Temporal.PlainDateTime";
1572
+ case "date":
1573
+ return "Temporal.PlainDate";
1574
+ case "time":
1575
+ return "Temporal.PlainTime";
1576
+ case "timetz":
1577
+ return "{ readonly time: Temporal.PlainTime; readonly offset: string }";
1578
+ case "interval":
1579
+ return "Temporal.Duration";
1580
+ case "tstzrange":
1581
+ return rangeLabel("Temporal.Instant");
1582
+ case "daterange":
1583
+ return rangeLabel("Temporal.PlainDate");
1584
+ case "int4range":
1585
+ return rangeLabel("number");
1586
+ case "int8range":
1587
+ case "numrange":
1588
+ return rangeLabel("string");
1589
+ case "point":
1590
+ return "{ readonly x: number; readonly y: number }";
1591
+ case "line":
1592
+ return "{ readonly a: number; readonly b: number; readonly c: number }";
1593
+ default:
1594
+ return "unknown";
1595
+ }
1596
+ }
1597
+ function rangeLabel(bound) {
1598
+ return `{ readonly empty: true } | { readonly empty: false; readonly lower: ${bound} | null; readonly upper: ${bound} | null; readonly lowerInclusive: boolean; readonly upperInclusive: boolean }`;
1599
+ }
1600
+ function baseName(dataType) {
1601
+ if (dataType.startsWith("double precision")) {
1602
+ return "double precision";
1603
+ }
1604
+ const space = dataType.indexOf(" ");
1605
+ const head = space === -1 ? dataType : dataType.slice(0, space);
1606
+ const paren = head.indexOf("(");
1607
+ return paren === -1 ? head : head.slice(0, paren);
1608
+ }
1609
+
1610
+ // src/dialects/pg/role/index.ts
1611
+ var PROVENANCE = { origin: "file", name: "roles" };
1612
+ var RELATION = ["SELECT", "INSERT", "UPDATE", "DELETE"];
1613
+ var READ2 = ["SELECT"];
1614
+ var ROUTINE = ["EXECUTE"];
1615
+ var SEQUENCE = ["USAGE", "SELECT"];
1616
+ function attachRoles(source, input) {
1617
+ return catalog([...source.objects, ...contributeRoles(input, source.objects)]);
1618
+ }
1619
+ function contributeRoles(input, built) {
1620
+ const migration = requireName(input.migration, "migration");
1621
+ const app = requireName(input.app, "app");
1622
+ const managed = new Map;
1623
+ for (const role of input.managed ?? []) {
1624
+ const name = requireName(role.name, "managed role");
1625
+ managed.set(name, role);
1626
+ }
1627
+ const objects = [];
1628
+ const names = new Set([migration, app, ...managed.keys()]);
1629
+ for (const name of [...names].sort()) {
1630
+ const declaration = managed.get(name);
1631
+ objects.push(roleObject({
1632
+ name,
1633
+ owner: declaration === undefined ? "external" : "managed",
1634
+ provenance: PROVENANCE,
1635
+ ...declaration === undefined ? { login: false, inherit: true } : {
1636
+ ...declaration.login !== undefined ? { login: declaration.login } : {},
1637
+ ...declaration.inherit !== undefined ? { inherit: declaration.inherit } : {}
1638
+ }
1639
+ }));
1640
+ }
1641
+ const roleIdentity = (name) => ({ kind: "role", name });
1642
+ const namespaces = new Set;
1643
+ for (const object of built) {
1644
+ if (object.owner === "ignored")
1645
+ continue;
1646
+ const ref = grantRef(object);
1647
+ if (ref === undefined)
1648
+ continue;
1649
+ if (ref.namespace.form === "static")
1650
+ namespaces.add(ref.namespace.name);
1651
+ const privileges = privilegesFor(ref.kind);
1652
+ for (const privilege of privileges) {
1653
+ objects.push(grantObject({
1654
+ role: app,
1655
+ object: ref,
1656
+ privilege,
1657
+ provenance: PROVENANCE,
1658
+ dependencies: [roleIdentity(app), objectIdentity(object)]
1659
+ }));
1660
+ }
1661
+ }
1662
+ const schemas = namespaces.size === 0 ? ["public"] : [...namespaces].sort();
1663
+ for (const name of schemas) {
1664
+ const namespace = staticNamespace2(name);
1665
+ for (const [objectKind, privileges] of [
1666
+ ["table", RELATION],
1667
+ ["function", ROUTINE],
1668
+ ["sequence", SEQUENCE]
1669
+ ]) {
1670
+ for (const privilege of privileges) {
1671
+ objects.push(defaultPrivilegeObject({
1672
+ forRole: migration,
1673
+ namespace,
1674
+ objectKind,
1675
+ grantee: app,
1676
+ privilege,
1677
+ provenance: PROVENANCE,
1678
+ dependencies: [roleIdentity(migration), roleIdentity(app)]
1679
+ }));
1680
+ }
1681
+ }
1682
+ }
1683
+ return objects;
1684
+ }
1685
+ function requireName(name, role) {
1686
+ if (typeof name !== "string" || name.length === 0) {
1687
+ throw new OkmError2("invalid", `roles.${role} needs a name.`, {
1688
+ fix: { summary: "Set roles.migration and roles.app to role names." }
1689
+ });
1690
+ }
1691
+ return name;
1692
+ }
1693
+ function grantRef(object) {
1694
+ if (object.kind === "table" || object.kind === "view" || object.kind === "materializedView") {
1695
+ return { kind: object.kind, namespace: object.identity.namespace, name: object.identity.name };
1696
+ }
1697
+ if (object.kind === "sequence") {
1698
+ return { kind: "sequence", namespace: object.identity.namespace, name: object.identity.name };
1699
+ }
1700
+ if (object.kind === "function") {
1701
+ return {
1702
+ kind: "function",
1703
+ namespace: object.identity.namespace,
1704
+ name: `${object.identity.name}(${object.identity.argTypes.join(",")})`
1705
+ };
1706
+ }
1707
+ return;
1708
+ }
1709
+ function privilegesFor(kind) {
1710
+ if (kind === "function")
1711
+ return ROUTINE;
1712
+ if (kind === "sequence")
1713
+ return SEQUENCE;
1714
+ if (kind === "materializedView")
1715
+ return READ2;
1716
+ if (kind === "namespace")
1717
+ return [];
1718
+ return RELATION;
1719
+ }
1720
+ function objectIdentity(object) {
1721
+ return object.identity;
1722
+ }
1723
+
1724
+ // src/tooling/migrate/estimate.ts
1725
+ var LARGE_TABLE_ROWS = 1e6;
1726
+ var ESTIMATE_SQL = `
1727
+ select c.relname, c.reltuples::float8::text
1728
+ from pg_class c
1729
+ join pg_namespace n on n.oid = c.relnamespace
1730
+ where n.nspname = $1
1731
+ and c.relname = any(string_to_array($2, E'\\x1f'))
1732
+ and c.relkind in ('r', 'p', 'f', 'm')
1733
+ `;
1734
+ async function readRowEstimates(url, schema, tables) {
1735
+ const pool = open2({ url, max: 1 });
1736
+ try {
1737
+ const connection = await pool.reserve?.();
1738
+ if (connection === undefined) {
1739
+ throw new Error("The target connection cannot start a transaction.");
1740
+ }
1741
+ let began = false;
1742
+ try {
1743
+ await connection.execute("begin read only");
1744
+ began = true;
1745
+ await connection.execute("set local statement_timeout = '5s'");
1746
+ const map = new Map;
1747
+ const unique = [...new Set(tables)];
1748
+ if (unique.length > 0) {
1749
+ const result = await connection.execute(ESTIMATE_SQL, [schema, unique.join("\x1F")]);
1750
+ for (const row of result.rows) {
1751
+ const name = row[0];
1752
+ if (name === null || name === undefined)
1753
+ continue;
1754
+ map.set(name, classify(row[1] ?? null));
1755
+ }
1756
+ }
1757
+ await connection.execute("commit");
1758
+ began = false;
1759
+ return map;
1760
+ } catch (error) {
1761
+ if (began)
1762
+ await connection.execute("rollback").catch(() => {
1763
+ return;
1764
+ });
1765
+ throw error;
1766
+ } finally {
1767
+ await connection.release();
1768
+ }
1769
+ } finally {
1770
+ await pool.close();
1771
+ }
1772
+ }
1773
+ function annotateLock(step, estimates) {
1774
+ const pieces = lockPieces(step);
1775
+ const text = withBatches(step, pieces.map((piece) => {
1776
+ if (piece.table === undefined)
1777
+ return piece.mode;
1778
+ return `${piece.mode} on ${piece.table}, ${estimatePhrase(estimates.get(piece.table))}`;
1779
+ }).join("; "), estimates);
1780
+ if (step.safeRewrite === true)
1781
+ return `${text}; safe rewrite applied`;
1782
+ if (exclusiveOverLarge(pieces, estimates)) {
1783
+ return `${text}; note: more than ${String(LARGE_TABLE_ROWS)} estimated rows`;
1784
+ }
1785
+ return text;
1786
+ }
1787
+ function aboutRows(count) {
1788
+ const rounded = Math.max(0, Math.round(count));
1789
+ if (rounded < 1000)
1790
+ return `about ${String(rounded)} rows`;
1791
+ const units = [
1792
+ [1e9, "B"],
1793
+ [1e6, "M"],
1794
+ [1000, "K"]
1795
+ ];
1796
+ for (const [unit, suffix] of units) {
1797
+ if (rounded < unit)
1798
+ continue;
1799
+ const scaled = rounded / unit;
1800
+ const digits = scaled >= 10 || Number.isInteger(scaled) ? 0 : 1;
1801
+ const text = trimZero(scaled.toFixed(digits));
1802
+ return `about ${text}${suffix} rows`;
1803
+ }
1804
+ return `about ${String(rounded)} rows`;
1805
+ }
1806
+ function withBatches(step, text, estimates) {
1807
+ const batch = step.backfill?.batch;
1808
+ if (batch === undefined)
1809
+ return text;
1810
+ const name = step.tables?.[0];
1811
+ const estimate = name === undefined ? undefined : estimates.get(name);
1812
+ if (estimate === undefined || estimate.kind !== "rows")
1813
+ return text;
1814
+ return `${text}, about ${String(aboutBatchCount(estimate.reltuples, batch))} batches`;
1815
+ }
1816
+ function classify(raw) {
1817
+ if (raw === null)
1818
+ return { kind: "unknown" };
1819
+ const value = Number(raw);
1820
+ if (!Number.isFinite(value) || value < 0)
1821
+ return { kind: "unknown" };
1822
+ return { kind: "rows", reltuples: value };
1823
+ }
1824
+ function estimatePhrase(estimate) {
1825
+ if (estimate === undefined)
1826
+ return "new table";
1827
+ if (estimate.kind === "unknown")
1828
+ return "rows unknown (table not analyzed)";
1829
+ return aboutRows(estimate.reltuples);
1830
+ }
1831
+ function lockPieces(step) {
1832
+ const segments = step.lock.split("; ").map((part) => part.trim()).filter((part) => part.length > 0);
1833
+ const named = new Map;
1834
+ for (const segment of segments) {
1835
+ const table = lockTable(segment);
1836
+ if (table !== undefined)
1837
+ named.set(table, stripLockTable(segment));
1838
+ }
1839
+ const primary = segments[0] ?? step.lock;
1840
+ const primaryTable = lockTable(primary);
1841
+ const primaryMode = primaryTable === undefined ? primary : stripLockTable(primary);
1842
+ const pieces = [];
1843
+ const seen = new Set;
1844
+ if (primaryTable === undefined) {
1845
+ const lead = (step.tables ?? []).filter((name) => !named.has(name));
1846
+ if (lead.length === 0 && named.size === 0)
1847
+ pieces.push({ mode: step.lock, table: undefined });
1848
+ for (const table of lead) {
1849
+ seen.add(table);
1850
+ pieces.push({ mode: primaryMode, table });
1851
+ }
1852
+ }
1853
+ for (const segment of segments) {
1854
+ const table = lockTable(segment);
1855
+ if (table === undefined) {
1856
+ if (primaryTable !== undefined || pieces.length === 0) {
1857
+ pieces.push({ mode: segment, table: undefined });
1858
+ }
1859
+ continue;
1860
+ }
1861
+ if (seen.has(table))
1862
+ continue;
1863
+ seen.add(table);
1864
+ pieces.push({ mode: stripLockTable(segment), table });
1865
+ }
1866
+ for (const table of step.tables ?? []) {
1867
+ if (seen.has(table))
1868
+ continue;
1869
+ pieces.push({ mode: primaryMode, table });
1870
+ }
1871
+ return pieces;
1872
+ }
1873
+ function exclusiveOverLarge(pieces, estimates) {
1874
+ for (const piece of pieces) {
1875
+ if (piece.mode !== "ACCESS EXCLUSIVE" || piece.table === undefined)
1876
+ continue;
1877
+ const estimate = estimates.get(piece.table);
1878
+ if (estimate === undefined || estimate.kind !== "rows")
1879
+ continue;
1880
+ if (estimate.reltuples > LARGE_TABLE_ROWS)
1881
+ return true;
1882
+ }
1883
+ return false;
1884
+ }
1885
+ function lockTable(segment) {
1886
+ const match = /\bon\s+"((?:[^"]|"")*)"\."((?:[^"]|"")*)"/.exec(segment);
1887
+ const name = match?.[2];
1888
+ if (name === undefined)
1889
+ return;
1890
+ return name.replaceAll('""', '"');
1891
+ }
1892
+ function stripLockTable(segment) {
1893
+ return segment.replace(/\s+on\s+"((?:[^"]|"")*)"\."((?:[^"]|"")*)"/, "").trim();
1894
+ }
1895
+ function trimZero(text) {
1896
+ return text.endsWith(".0") ? text.slice(0, -2) : text;
1897
+ }
1898
+
1899
+ // src/tooling/migrate/project.ts
1900
+ async function buildProject(cwd) {
1901
+ const config = await loadConfig(cwd);
1902
+ const built = await loadSchema(cwd, config.schema);
1903
+ return writeArtifact(cwd, config, withRoles(built, config));
1904
+ }
1905
+ async function generateProject(cwd, name, flags) {
1906
+ const prepared = await prepare(cwd, name, flags);
1907
+ writeArtifact(cwd, prepared.config, prepared.built);
1908
+ if (prepared.plan.steps.length === 0)
1909
+ return;
1910
+ const directory = join2(cwd, prepared.config.migrations ?? "migrations");
1911
+ mkdirSync(directory, { recursive: true });
1912
+ const base = `${nextNumber(directory)}_${safeName(name)}`;
1913
+ const sqlPath = join2(directory, `${base}.sql`);
1914
+ writeFileSync(sqlPath, formatPlan(prepared.plan));
1915
+ writeFileSync(join2(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
1916
+ return { path: sqlPath, findings: lintPrepared(prepared) };
1917
+ }
1918
+ async function planProject(cwd, name, flags, invoke) {
1919
+ const prepared = await prepare(cwd, name, flags);
1920
+ return {
1921
+ plan: prepared.plan,
1922
+ findings: lintPrepared(prepared),
1923
+ text: await displayPlan(prepared.config, prepared.plan, invoke)
1924
+ };
1925
+ }
1926
+ async function checkProject(cwd, flags) {
1927
+ const opened = await openProject(cwd);
1928
+ assertValidation(opened.built);
1929
+ if (schemaRequestsValidation(opened.built) && !projectImportsValidate(cwd)) {
1930
+ const doc = errorDoc("OKM1201");
1931
+ if (doc === undefined)
1932
+ throw new Error("OKM1201 missing from the error registry.");
1933
+ throw new OkmError2(doc.code, doc.summary, { fix: { summary: doc.fix } });
1934
+ }
1935
+ const targets = listTargets(opened.config);
1936
+ assertTargetAlias(targets);
1937
+ if (opened.config.tables !== undefined) {
1938
+ const directory = join2(cwd, opened.config.tables);
1939
+ const files = existsSync2(directory) ? readdirSync2(directory) : [];
1940
+ const source = readFileSync2(join2(cwd, opened.config.schema), "utf8");
1941
+ const missing = unlistedTableFiles(source, files);
1942
+ if (missing.length > 0) {
1943
+ throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
1944
+ fix: { summary: "Add the file's table to the schema() call that okm check reads." }
1945
+ });
1946
+ }
1947
+ }
1948
+ const findings = lintOpened(opened);
1949
+ if (hasError(findings))
1950
+ throw lintRefusal(findings);
1951
+ const named = flags?.target;
1952
+ if (targets.length === 0)
1953
+ return findings;
1954
+ if (targets.length > 1 && named === undefined)
1955
+ return findings;
1956
+ const target = selectTarget(opened.config, named);
1957
+ assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
1958
+ assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
1959
+ const { assertAuthorDrift } = await import("./ykv7wh5b.js");
1960
+ await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles);
1961
+ return findings;
1962
+ }
1963
+ function unlistedTableFiles(schemaSource, fileNames) {
1964
+ const missing = [];
1965
+ for (const file of fileNames) {
1966
+ if (!file.endsWith(".ts"))
1967
+ continue;
1968
+ const stem = basename(file, ".ts");
1969
+ if (stem.length === 0 || stem.startsWith("."))
1970
+ continue;
1971
+ if (!schemaSource.includes(stem))
1972
+ missing.push(file);
1973
+ }
1974
+ return missing;
1975
+ }
1976
+ async function projectHead(cwd) {
1977
+ const opened = await openProject(cwd);
1978
+ return { previous: opened.previous, catalog: opened.built.catalog, renames: opened.renames };
1979
+ }
1980
+ async function prepare(cwd, name, flags) {
1981
+ const opened = await openProject(cwd);
1982
+ const plan = planMigration({
1983
+ before: opened.previous,
1984
+ after: opened.built.catalog,
1985
+ renames: opened.renames,
1986
+ replacements: flags.map((flag) => parseReplace(flag)),
1987
+ name,
1988
+ ...batchSizeField(opened.config.backfill?.batchSize)
1989
+ });
1990
+ return {
1991
+ config: opened.config,
1992
+ built: opened.built,
1993
+ plan,
1994
+ before: opened.previous,
1995
+ after: opened.built.catalog
1996
+ };
1997
+ }
1998
+ function lintPrepared(prepared) {
1999
+ return lintPlan(prepared.plan, prepared.before, prepared.after);
2000
+ }
2001
+ async function displayPlan(config, plan, invoke) {
2002
+ const offline = formatPlan(plan);
2003
+ const targets = listTargets(config);
2004
+ if (targets.length === 0)
2005
+ return offline;
2006
+ assertTargetAlias(targets);
2007
+ const named = invoke?.target;
2008
+ if (named === undefined && targets.length !== 1)
2009
+ return offline;
2010
+ const target = selectTarget(config, named);
2011
+ assertTargetPolicy(target, "plan", invoke?.allowProtected ?? false);
2012
+ let estimates;
2013
+ try {
2014
+ const names = plan.steps.flatMap((step) => step.tables ?? []);
2015
+ estimates = await readRowEstimates(target.url, "public", names);
2016
+ } catch {
2017
+ return offline;
2018
+ }
2019
+ return formatPlan(plan, (step) => annotateLock(step, estimates));
2020
+ }
2021
+ function lintOpened(opened) {
2022
+ const plan = planMigration({
2023
+ before: opened.previous,
2024
+ after: opened.built.catalog,
2025
+ renames: opened.renames,
2026
+ name: "check",
2027
+ ...batchSizeField(opened.config.backfill?.batchSize)
2028
+ });
2029
+ return [
2030
+ ...lintPlan(plan, opened.previous, opened.built.catalog),
2031
+ ...lintCatalog(opened.built.catalog)
2032
+ ];
2033
+ }
2034
+ async function openProject(cwd) {
2035
+ const config = await loadConfig(cwd);
2036
+ const built = withRoles(await loadSchema(cwd, config.schema), config);
2037
+ const declarations = schemaDeclarations(built);
2038
+ const previous = readPrevious(join2(cwd, config.migrations ?? "migrations"));
2039
+ const stale = staleRenames(previous?.catalog, declarations.renames);
2040
+ if (stale.length > 0) {
2041
+ throw new OkmError2("OKM1020", stale.join(" "), {
2042
+ fix: { summary: "Remove renamedFrom once the migration that used it has been applied." }
2043
+ });
2044
+ }
2045
+ return {
2046
+ config,
2047
+ built,
2048
+ previous: previous?.catalog ?? catalog([]),
2049
+ renames: declarations.renames
2050
+ };
2051
+ }
2052
+ function withRoles(built, config) {
2053
+ if (config.roles === undefined)
2054
+ return built;
2055
+ return { ...built, catalog: attachRoles(built.catalog, config.roles) };
2056
+ }
2057
+ function writeArtifact(cwd, config, built) {
2058
+ const directory = join2(cwd, config.out ?? ".okm");
2059
+ mkdirSync(directory, { recursive: true });
2060
+ const startup = startupCatalog2(built.catalog);
2061
+ writeFileSync(join2(directory, "catalog.json"), serializeCatalog2(startup));
2062
+ writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(startup)}
2063
+ `);
2064
+ writeFileSync(join2(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
2065
+ return directory;
2066
+ }
2067
+ async function loadBuiltSchema(cwd, config) {
2068
+ return withRoles(await loadSchema(cwd, config.schema), config);
2069
+ }
2070
+ async function loadConfig(cwd) {
2071
+ const path = join2(cwd, "okmodel.config.ts");
2072
+ const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2073
+ const record = isRecord(imported) ? imported : {};
2074
+ const config = record.default ?? record.config;
2075
+ if (!isConfig(config)) {
2076
+ throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
2077
+ }
2078
+ return config;
2079
+ }
2080
+ async function loadSchema(cwd, spec) {
2081
+ const path = join2(cwd, spec);
2082
+ const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2083
+ const record = isRecord(imported) ? imported : {};
2084
+ for (const value of Object.values(record)) {
2085
+ if (!isBuilt(value))
2086
+ continue;
2087
+ value.catalog;
2088
+ return value;
2089
+ }
2090
+ throw new OkmError2("invalid", `${spec} must export a schema().`);
2091
+ }
2092
+ function readPrevious(directory) {
2093
+ if (!existsSync2(directory))
2094
+ return;
2095
+ const catalogs = readdirSync2(directory).filter((file) => file.endsWith(".catalog.json")).sort();
2096
+ const latest = catalogs.at(-1);
2097
+ if (latest === undefined)
2098
+ return;
2099
+ const text = readFileSync2(join2(directory, latest), "utf8");
2100
+ return { catalog: parseCatalog2(text) };
2101
+ }
2102
+ function referenceAugmentation(source) {
2103
+ const names = source.tables.map((item) => JSON.stringify(item.name));
2104
+ const union = names.length === 0 ? "never" : names.join(" | ");
2105
+ return [
2106
+ 'import type { ColumnFlags, ReferenceOptions } from "okmodel/pg";',
2107
+ "",
2108
+ 'declare module "okmodel/pg" {',
2109
+ " interface ColumnBuilder<TValue, TFlags extends ColumnFlags> {",
2110
+ " references(",
2111
+ ` table: ${union},`,
2112
+ " options?: ReferenceOptions,",
2113
+ " ): ColumnBuilder<TValue, TFlags>;",
2114
+ " }",
2115
+ "}",
2116
+ "",
2117
+ `export type TableName = ${union};`,
2118
+ ""
2119
+ ].join(`
2120
+ `);
2121
+ }
2122
+ function nextNumber(directory) {
2123
+ let max = 0;
2124
+ if (existsSync2(directory)) {
2125
+ for (const file of readdirSync2(directory)) {
2126
+ const match = /^(\d+)_/.exec(file);
2127
+ const value = match?.[1];
2128
+ if (value !== undefined)
2129
+ max = Math.max(max, Number(value));
2130
+ }
2131
+ }
2132
+ return String(max + 1).padStart(4, "0");
2133
+ }
2134
+ function safeName(name) {
2135
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) {
2136
+ throw new OkmError2("invalid", `Migration name ${name} must be letters, digits, _ or -.`);
2137
+ }
2138
+ return name;
2139
+ }
2140
+ function isBuilt(value) {
2141
+ if (!isRecord(value) || !Array.isArray(value.tables))
2142
+ return false;
2143
+ return "catalog" in value;
2144
+ }
2145
+ function isConfig(value) {
2146
+ return isRecord(value) && typeof value.schema === "string";
2147
+ }
2148
+ function isRecord(value) {
2149
+ return typeof value === "object" && value !== null;
2150
+ }
2151
+ var SKIP_SCAN = new Set(["node_modules", "dist", "coverage"]);
2152
+ function schemaRequestsValidation(built) {
2153
+ const flag = built.validation;
2154
+ for (const item of built.tables) {
2155
+ if (writeWouldValidate(flag, item, {}))
2156
+ return true;
2157
+ }
2158
+ return false;
2159
+ }
2160
+ function projectImportsValidate(cwd) {
2161
+ return directoryImportsValidate(cwd);
2162
+ }
2163
+ function directoryImportsValidate(dir) {
2164
+ let entries;
2165
+ try {
2166
+ entries = readdirSync2(dir, { withFileTypes: true });
2167
+ } catch {
2168
+ return false;
2169
+ }
2170
+ for (const entry of entries) {
2171
+ if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
2172
+ continue;
2173
+ const path = join2(dir, entry.name);
2174
+ if (entry.isDirectory()) {
2175
+ if (directoryImportsValidate(path))
2176
+ return true;
2177
+ continue;
2178
+ }
2179
+ if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
2180
+ continue;
2181
+ if (runtimeImportsValidate(readFileSync2(path, "utf8")))
2182
+ return true;
2183
+ }
2184
+ return false;
2185
+ }
2186
+ function runtimeImportsValidate(source) {
2187
+ const text = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
2188
+ const pattern = /["']okmodel\/validate(?:\.js)?["']/g;
2189
+ for (const match of text.matchAll(pattern)) {
2190
+ const index = match.index ?? 0;
2191
+ const lead = text.slice(Math.max(0, index - 240), index);
2192
+ const start = Math.max(lead.lastIndexOf(`
2193
+ `), lead.lastIndexOf(";"));
2194
+ const head = lead.slice(start + 1);
2195
+ if (/\bimport\s+type\b/.test(head) || /\bexport\s+type\b/.test(head))
2196
+ continue;
2197
+ if (/\bimport\b/.test(head) || /\bexport\b/.test(head))
2198
+ return true;
2199
+ }
2200
+ return false;
2201
+ }
2202
+
2203
+ export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal, assertTargetPolicy, assertDirectConnection, listTargets, selectTarget, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };