okmodel 0.0.0 → 0.1.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 (147) hide show
  1. package/README.md +44 -1
  2. package/dist/adapters/capabilities.d.ts +62 -0
  3. package/dist/adapters/error.d.ts +91 -0
  4. package/dist/adapters/failure.d.ts +39 -0
  5. package/dist/adapters/pg/batch.d.ts +36 -0
  6. package/dist/adapters/pg/call.d.ts +59 -0
  7. package/dist/adapters/pg/pglite.d.ts +21 -0
  8. package/dist/adapters/pg/postgres-extra.d.ts +43 -0
  9. package/dist/adapters/pg/postgresjs.d.ts +95 -0
  10. package/dist/adapters/pg/result.d.ts +30 -0
  11. package/dist/contracts/catalog/build.d.ts +18 -0
  12. package/dist/contracts/catalog/document.d.ts +79 -0
  13. package/dist/contracts/catalog/enum.d.ts +35 -0
  14. package/dist/contracts/catalog/identifier.d.ts +71 -0
  15. package/dist/contracts/catalog/identity.d.ts +98 -0
  16. package/dist/contracts/catalog/index.d.ts +11 -0
  17. package/dist/contracts/catalog/json.d.ts +17 -0
  18. package/dist/contracts/catalog/object.d.ts +142 -0
  19. package/dist/contracts/catalog/order.d.ts +43 -0
  20. package/dist/contracts/catalog/rewrite.d.ts +18 -0
  21. package/dist/contracts/catalog/types.d.ts +292 -0
  22. package/dist/contracts/catalog/words.d.ts +13 -0
  23. package/dist/contracts/connection.d.ts +33 -0
  24. package/dist/contracts/driver.d.ts +163 -0
  25. package/dist/contracts/error.d.ts +298 -0
  26. package/dist/contracts/index.d.ts +11 -0
  27. package/dist/contracts/index.js +16 -0
  28. package/dist/contracts/internal.d.ts +15 -0
  29. package/dist/contracts/internal.js +68 -0
  30. package/dist/contracts/nearest.d.ts +15 -0
  31. package/dist/contracts/rows.d.ts +84 -0
  32. package/dist/contracts/sha256.d.ts +14 -0
  33. package/dist/contracts/utf8.d.ts +33 -0
  34. package/dist/dialects/pg/array-literal.d.ts +25 -0
  35. package/dist/dialects/pg/bool.d.ts +46 -0
  36. package/dist/dialects/pg/column.d.ts +386 -0
  37. package/dist/dialects/pg/compile.d.ts +81 -0
  38. package/dist/dialects/pg/custom.d.ts +21 -0
  39. package/dist/dialects/pg/ddl.d.ts +70 -0
  40. package/dist/dialects/pg/decimal.d.ts +62 -0
  41. package/dist/dialects/pg/declarations.d.ts +34 -0
  42. package/dist/dialects/pg/emit.d.ts +18 -0
  43. package/dist/dialects/pg/enum.d.ts +30 -0
  44. package/dist/dialects/pg/errors.d.ts +30 -0
  45. package/dist/dialects/pg/finite.d.ts +15 -0
  46. package/dist/dialects/pg/geometry.d.ts +55 -0
  47. package/dist/dialects/pg/index.d.ts +45 -0
  48. package/dist/dialects/pg/index.js +1821 -0
  49. package/dist/dialects/pg/integer.d.ts +96 -0
  50. package/dist/dialects/pg/introspect.d.ts +43 -0
  51. package/dist/dialects/pg/json.d.ts +43 -0
  52. package/dist/dialects/pg/keys.d.ts +32 -0
  53. package/dist/dialects/pg/misuse.d.ts +31 -0
  54. package/dist/dialects/pg/model.d.ts +71 -0
  55. package/dist/dialects/pg/namespace.d.ts +63 -0
  56. package/dist/dialects/pg/network.d.ts +28 -0
  57. package/dist/dialects/pg/operators.d.ts +71 -0
  58. package/dist/dialects/pg/ops/between.d.ts +10 -0
  59. package/dist/dialects/pg/ops/contains.d.ts +8 -0
  60. package/dist/dialects/pg/ops/endsWith.d.ts +8 -0
  61. package/dist/dialects/pg/ops/eq.d.ts +9 -0
  62. package/dist/dialects/pg/ops/every.d.ts +9 -0
  63. package/dist/dialects/pg/ops/gt.d.ts +9 -0
  64. package/dist/dialects/pg/ops/gte.d.ts +9 -0
  65. package/dist/dialects/pg/ops/has.d.ts +9 -0
  66. package/dist/dialects/pg/ops/ilike.d.ts +8 -0
  67. package/dist/dialects/pg/ops/inList.d.ts +9 -0
  68. package/dist/dialects/pg/ops/inc.d.ts +13 -0
  69. package/dist/dialects/pg/ops/like.d.ts +8 -0
  70. package/dist/dialects/pg/ops/lt.d.ts +9 -0
  71. package/dist/dialects/pg/ops/lte.d.ts +9 -0
  72. package/dist/dialects/pg/ops/none.d.ts +9 -0
  73. package/dist/dialects/pg/ops/not.d.ts +9 -0
  74. package/dist/dialects/pg/ops/notIn.d.ts +9 -0
  75. package/dist/dialects/pg/ops/or.d.ts +9 -0
  76. package/dist/dialects/pg/ops/startsWith.d.ts +8 -0
  77. package/dist/dialects/pg/quote.d.ts +24 -0
  78. package/dist/dialects/pg/range.d.ts +50 -0
  79. package/dist/dialects/pg/relations.d.ts +45 -0
  80. package/dist/dialects/pg/schema.d.ts +77 -0
  81. package/dist/dialects/pg/search.d.ts +23 -0
  82. package/dist/dialects/pg/table.d.ts +201 -0
  83. package/dist/dialects/pg/temporal.d.ts +211 -0
  84. package/dist/dialects/pg/text.d.ts +44 -0
  85. package/dist/dialects/pg/time.d.ts +46 -0
  86. package/dist/okm.js +2 -0
  87. package/dist/okmodel.js +2 -0
  88. package/dist/runtime/client.d.ts +47 -0
  89. package/dist/runtime/conflict.d.ts +38 -0
  90. package/dist/runtime/drift-detail.d.ts +32 -0
  91. package/dist/runtime/drift.d.ts +29 -0
  92. package/dist/runtime/include.d.ts +15 -0
  93. package/dist/runtime/pg/pglite.d.ts +25 -0
  94. package/dist/runtime/pg/pglite.js +48 -0
  95. package/dist/runtime/pg/postgresjs.d.ts +29 -0
  96. package/dist/runtime/pg/postgresjs.js +62 -0
  97. package/dist/runtime/plan.d.ts +167 -0
  98. package/dist/runtime/tx.d.ts +29 -0
  99. package/dist/runtime/types.d.ts +369 -0
  100. package/dist/runtime/write.d.ts +48 -0
  101. package/dist/shared/4j72449d.js +37 -0
  102. package/dist/shared/79ggp945.js +148 -0
  103. package/dist/shared/7ck0dtry.js +259 -0
  104. package/dist/shared/8b04d28c.js +14 -0
  105. package/dist/shared/ax16zpvm.js +171 -0
  106. package/dist/shared/ckb1p0a4.js +566 -0
  107. package/dist/shared/cn0n68ft.js +16 -0
  108. package/dist/shared/dynq16vb.js +81 -0
  109. package/dist/shared/f5thc1s0.js +1050 -0
  110. package/dist/shared/fgqgsb5z.js +281 -0
  111. package/dist/shared/g2v6gnc8.js +347 -0
  112. package/dist/shared/g5fhqmwe.js +188 -0
  113. package/dist/shared/gk7cs7sp.js +566 -0
  114. package/dist/shared/hd293j9g.js +98 -0
  115. package/dist/shared/j5cn2963.js +701 -0
  116. package/dist/shared/kynx7vbm.js +1490 -0
  117. package/dist/shared/mkjmqjv4.js +456 -0
  118. package/dist/shared/mtkwv8wx.js +18 -0
  119. package/dist/shared/ngk8gj61.js +40 -0
  120. package/dist/shared/p0cbr3g2.js +79 -0
  121. package/dist/shared/px90pf9p.js +396 -0
  122. package/dist/shared/qk3hxfja.js +83 -0
  123. package/dist/shared/tb4ze1tr.js +119 -0
  124. package/dist/shared/vkm3q4x1.js +30 -0
  125. package/dist/shared/w8samvk1.js +18 -0
  126. package/dist/shared/xy79j5hq.js +177 -0
  127. package/dist/shared/y9m15dfv.js +888 -0
  128. package/dist/shared/ycwz42az.js +412 -0
  129. package/dist/shared/zhc5vmgf.js +257 -0
  130. package/dist/tooling/cli.d.ts +1 -0
  131. package/dist/tooling/cli.js +193 -0
  132. package/dist/tooling/errors/registry.d.ts +33 -0
  133. package/dist/tooling/migrate/apply.d.ts +86 -0
  134. package/dist/tooling/migrate/commands.d.ts +21 -0
  135. package/dist/tooling/migrate/config.d.ts +48 -0
  136. package/dist/tooling/migrate/dev.d.ts +16 -0
  137. package/dist/tooling/migrate/equal.d.ts +15 -0
  138. package/dist/tooling/migrate/files.d.ts +19 -0
  139. package/dist/tooling/migrate/index.d.ts +7 -0
  140. package/dist/tooling/migrate/index.js +7 -0
  141. package/dist/tooling/migrate/plan.d.ts +76 -0
  142. package/dist/tooling/migrate/policy.d.ts +78 -0
  143. package/dist/tooling/migrate/project.d.ts +69 -0
  144. package/dist/tooling/migrate/status.d.ts +74 -0
  145. package/dist/tooling/migrate/values.d.ts +29 -0
  146. package/package.json +94 -7
  147. package/dist/index.js +0 -0
@@ -0,0 +1,1490 @@
1
+ import {
2
+ creationOrder2,
3
+ renameColumn2,
4
+ renameTable2,
5
+ serializeCatalog2,
6
+ catalogHash2,
7
+ parseCatalog2
8
+ } from "./y9m15dfv.js";
9
+ import {
10
+ quoteLiteral,
11
+ ColumnBuilder2,
12
+ snakeCase,
13
+ emittedTypeName
14
+ } from "./gk7cs7sp.js";
15
+ import {
16
+ catalog,
17
+ sameEnumLabels
18
+ } from "./g5fhqmwe.js";
19
+ import {
20
+ staticNamespace2,
21
+ identityKey2,
22
+ compareText
23
+ } from "./j5cn2963.js";
24
+ import {
25
+ OkmError2
26
+ } from "./ycwz42az.js";
27
+
28
+ // src/dialects/pg/ddl.ts
29
+ function quoteIdent(name) {
30
+ return `"${name.replaceAll('"', '""')}"`;
31
+ }
32
+ function dropObjectSql(object, schema) {
33
+ if (object.owner === "ignored")
34
+ return;
35
+ switch (object.kind) {
36
+ case "table":
37
+ return `drop table ${qualify(schema, object.identity.name)}`;
38
+ case "column":
39
+ return `alter table ${qualify(schema, object.identity.parent.name)} drop column ${quoteIdent(object.identity.name)}`;
40
+ case "index":
41
+ return `drop index ${qualify(schema, object.identity.name)}`;
42
+ case "constraint":
43
+ return `alter table ${qualify(schema, object.identity.parent.name)} drop constraint ${quoteIdent(object.identity.name)}`;
44
+ case "sequence":
45
+ return `drop sequence ${qualify(schema, object.identity.name)}`;
46
+ case "type":
47
+ return `drop type ${qualify(schema, object.identity.name)}`;
48
+ default:
49
+ return;
50
+ }
51
+ }
52
+ function createObjectSql(object, schema) {
53
+ switch (object.kind) {
54
+ case "column":
55
+ return `alter table ${qualify(schema, object.identity.parent.name)} add column ${columnSql(object)}`;
56
+ case "index":
57
+ return createIndexSql(object, schema);
58
+ case "constraint":
59
+ return `alter table ${qualify(schema, object.identity.parent.name)} add constraint ${quoteIdent(object.identity.name)} ${constraintBody(object, schema)}`;
60
+ case "sequence":
61
+ return createSequenceSql(object, schema);
62
+ case "type":
63
+ return createEnumSql(object, schema);
64
+ case "table":
65
+ return;
66
+ default:
67
+ return;
68
+ }
69
+ }
70
+ function alterColumnSql(before, after, schema) {
71
+ const statements = [];
72
+ const table = qualify(schema, after.identity.parent.name);
73
+ const name = quoteIdent(after.identity.name);
74
+ if (before.definition.dataType !== after.definition.dataType) {
75
+ statements.push(`alter table ${table} alter column ${name} set data type ${after.definition.dataType} using ${name}::${after.definition.dataType}`);
76
+ }
77
+ if (before.definition.nullable !== after.definition.nullable) {
78
+ statements.push(`alter table ${table} alter column ${name} ${after.definition.nullable ? "drop not null" : "set not null"}`);
79
+ }
80
+ if (before.definition.defaultExpression !== after.definition.defaultExpression) {
81
+ statements.push(after.definition.defaultExpression === undefined ? `alter table ${table} alter column ${name} drop default` : `alter table ${table} alter column ${name} set default ${after.definition.defaultExpression}`);
82
+ }
83
+ return statements;
84
+ }
85
+ function identitySequence(sequence, source) {
86
+ const key = identityKey2(sequence.identity);
87
+ return source.objects.some((object) => {
88
+ if (object.kind !== "column" || object.definition.identity === undefined)
89
+ return false;
90
+ return object.dependencies.some((edge) => identityKey2(edge.target) === key);
91
+ });
92
+ }
93
+ function createTableSql(tableObject, source, schema) {
94
+ const parent = tableObject.identity.name;
95
+ const columns = source.objects.filter((object) => object.kind === "column" && object.identity.parent.name === parent);
96
+ const primary = source.objects.find((object) => object.kind === "constraint" && object.definition.constraintKind === "primaryKey" && object.identity.parent.name === parent);
97
+ const lines = columns.map((column) => columnSql(column));
98
+ if (primary !== undefined) {
99
+ const cols = primary.definition.columns.map((name) => quoteIdent(name)).join(", ");
100
+ lines.push(`primary key (${cols})`);
101
+ }
102
+ const partition = tableObject.definition.partition;
103
+ const tail = partition === undefined ? "" : ` partition by ${partition.method} (${partition.columns.map((name) => quoteIdent(name)).join(", ")})`;
104
+ return `create table ${qualify(schema, parent)} (
105
+ ${lines.join(`,
106
+ `)}
107
+ )${tail}`;
108
+ }
109
+ function createEnumSql(object, schema) {
110
+ const labels = object.definition.labels.map((label) => quoteLiteral(label)).join(", ");
111
+ return `create type ${qualify(schema, object.identity.name)} as enum (${labels})`;
112
+ }
113
+ function columnSql(column) {
114
+ const definition = column.definition;
115
+ let sql = `${quoteIdent(column.identity.name)} ${definition.dataType}`;
116
+ if (definition.identity !== undefined) {
117
+ sql += definition.identity.always ? " generated always as identity" : " generated by default as identity";
118
+ } else if (definition.generated !== undefined) {
119
+ sql += ` generated always as (${definition.generated.expression}) stored`;
120
+ }
121
+ if (!definition.nullable)
122
+ sql += " not null";
123
+ if (definition.defaultExpression !== undefined)
124
+ sql += ` default ${definition.defaultExpression}`;
125
+ return sql;
126
+ }
127
+ function createIndexSql(object, schema) {
128
+ const unique = object.definition.unique ? "unique " : "";
129
+ const target = object.definition.expression !== undefined ? `(${object.definition.expression})` : object.definition.columns.map((name) => quoteIdent(name)).join(", ");
130
+ const where = object.definition.predicate === undefined ? "" : ` where ${object.definition.predicate}`;
131
+ return `create ${unique}index ${quoteIdent(object.identity.name)} on ${qualify(schema, object.identity.parent.name)} (${target})${where}`;
132
+ }
133
+ function createSequenceSql(object, schema) {
134
+ const definition = object.definition;
135
+ const cycle = definition.cycle ? " cycle" : "";
136
+ return `create sequence ${qualify(schema, object.identity.name)} as ${definition.dataType} start ${definition.start} increment ${definition.increment}${cycle}`;
137
+ }
138
+ function constraintBody(object, schema) {
139
+ const definition = object.definition;
140
+ const columns = definition.columns.map((name) => quoteIdent(name)).join(", ");
141
+ switch (definition.constraintKind) {
142
+ case "primaryKey":
143
+ return `primary key (${columns})`;
144
+ case "unique": {
145
+ const nulls = definition.nullsNotDistinct ? " nulls not distinct" : "";
146
+ return `unique${nulls} (${columns})${deferClause(definition)}`;
147
+ }
148
+ case "check":
149
+ return `check (${definition.expression ?? "true"})${deferClause(definition)}`;
150
+ case "foreignKey": {
151
+ const references = definition.references;
152
+ const target = references === undefined ? "" : ` references ${qualify(schema, references.parent.name)} (${references.columns.map((name) => quoteIdent(name)).join(", ")})`;
153
+ const onDelete = references?.onDelete === undefined ? "" : ` on delete ${references.onDelete}`;
154
+ const onUpdate = references?.onUpdate === undefined ? "" : ` on update ${references.onUpdate}`;
155
+ return `foreign key (${columns})${target}${onDelete}${onUpdate}${deferClause(definition)}`;
156
+ }
157
+ default:
158
+ return definition.constraintKind;
159
+ }
160
+ }
161
+ function deferClause(definition) {
162
+ if (!definition.deferrable)
163
+ return "";
164
+ return ` deferrable initially ${definition.initially}`;
165
+ }
166
+ function qualify(schema, name) {
167
+ return `${quoteIdent(schema)}.${quoteIdent(name)}`;
168
+ }
169
+
170
+ // src/tooling/migrate/values.ts
171
+ function parseReplace(flag) {
172
+ const eq = flag.indexOf("=");
173
+ if (eq <= 0) {
174
+ throw new OkmError2("OKM1541", `--replace ${flag} must be table.column.old=new.`, {
175
+ fix: {
176
+ summary: "Pass --replace <table>.<column>.<old>=<new>, or =null on a nullable column."
177
+ }
178
+ });
179
+ }
180
+ const left = flag.slice(0, eq);
181
+ const raw = flag.slice(eq + 1);
182
+ const first = left.indexOf(".");
183
+ const second = first < 0 ? -1 : left.indexOf(".", first + 1);
184
+ if (first <= 0 || second <= first + 1 || second === left.length - 1) {
185
+ throw new OkmError2("OKM1541", `--replace ${flag} must be table.column.old=new.`, {
186
+ fix: {
187
+ summary: "Pass --replace <table>.<column>.<old>=<new>, or =null on a nullable column."
188
+ }
189
+ });
190
+ }
191
+ const table = left.slice(0, first);
192
+ const column = left.slice(first + 1, second);
193
+ const from = left.slice(second + 1);
194
+ return { table, column, from, to: raw === "null" ? null : raw };
195
+ }
196
+ function assertNoChains(replacements) {
197
+ const removed = new Set(replacements.map((item) => pair(item.table, item.column, item.from)));
198
+ for (const item of replacements) {
199
+ if (item.to !== null && removed.has(pair(item.table, item.column, item.to))) {
200
+ const flag = `--replace ${item.table}.${item.column}.${item.from}=${item.to}`;
201
+ throw new OkmError2("OKM1541", `${flag} chains through another removed value.`, {
202
+ fix: { summary: "The replacement must be in the new list. No chains." }
203
+ });
204
+ }
205
+ }
206
+ }
207
+ function pair(table, column, value) {
208
+ return `${table}\x00${column}\x00${value}`;
209
+ }
210
+
211
+ // src/tooling/migrate/plan.ts
212
+ var ACCESS = "ACCESS EXCLUSIVE";
213
+ var SHARE = "SHARE";
214
+ var SHARE_UPDATE = "SHARE UPDATE EXCLUSIVE";
215
+ var ROW = "ROW EXCLUSIVE";
216
+ function planMigration(request) {
217
+ const schema = request.schema ?? "public";
218
+ const renames = request.renames ?? [];
219
+ const replacements = request.replacements ?? [];
220
+ assertNoChains(replacements);
221
+ const renamed = applyRenames(request.before, renames);
222
+ const beforeList = index(renamed);
223
+ const afterList = index(request.after);
224
+ const beforeBy = new Map(beforeList.map((item) => [item.key, item.object]));
225
+ const afterBy = new Map(afterList.map((item) => [item.key, item.object]));
226
+ assertUnambiguous(beforeBy, afterBy);
227
+ const dropKeys = new Set;
228
+ const createKeys = new Set;
229
+ const alters = [];
230
+ for (const item of beforeList) {
231
+ if (!afterBy.has(item.key))
232
+ dropKeys.add(item.key);
233
+ }
234
+ for (const item of afterList) {
235
+ if (!beforeBy.has(item.key))
236
+ createKeys.add(item.key);
237
+ }
238
+ for (const item of afterList) {
239
+ const previous = beforeBy.get(item.key);
240
+ if (previous === undefined || sameDefinition(previous, item.object))
241
+ continue;
242
+ if (previous.kind === "type" && item.object.kind === "type")
243
+ continue;
244
+ if (previous.kind === "column" && item.object.kind === "column" && columnAlterable(previous, item.object)) {
245
+ alters.push({ before: previous, after: item.object });
246
+ continue;
247
+ }
248
+ dropKeys.add(item.key);
249
+ createKeys.add(item.key);
250
+ }
251
+ const keptNames = pairSameShape(beforeBy, afterBy, dropKeys, createKeys);
252
+ const picklists = picklistChanges(beforeBy, afterBy, request.after);
253
+ const enums = enumEdits(beforeBy, afterBy, schema);
254
+ requireReplacements(picklists, enums, replacements);
255
+ recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists);
256
+ const steps = [];
257
+ steps.push(...renameSteps(request.before, renames, schema));
258
+ steps.push(...keptNames.map((item) => renameShapeStep(item, schema)));
259
+ steps.push(...enumAddSteps(enums));
260
+ steps.push(...expandBackfills(picklists, enums, replacements, schema));
261
+ const droppedTables = new Set([...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
262
+ const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
263
+ const typeDrops = [];
264
+ for (const object of [...creationOrder2(renamed)].reverse()) {
265
+ const key = identityKey2(object.identity);
266
+ if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
267
+ continue;
268
+ if (object.kind === "type") {
269
+ typeDrops.push(object);
270
+ continue;
271
+ }
272
+ const sql = dropObjectSql(object, schema);
273
+ if (sql === undefined)
274
+ continue;
275
+ steps.push(step(sql, "contract", "ddl", ACCESS));
276
+ }
277
+ for (const object of typeDrops) {
278
+ const sql = dropObjectSql(object, schema);
279
+ if (sql === undefined)
280
+ continue;
281
+ steps.push(step(sql, "contract", "ddl", ACCESS));
282
+ }
283
+ for (const change of alters) {
284
+ for (const sql of alterColumnSql(change.before, change.after, schema)) {
285
+ steps.push(step(sql, alterClass(sql), "ddl", ACCESS));
286
+ }
287
+ }
288
+ const createdTables = new Set([...createKeys].map((key) => afterBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
289
+ const emitted = new Set;
290
+ for (const object of creationOrder2(request.after)) {
291
+ if (object.kind !== "type")
292
+ continue;
293
+ const key = identityKey2(object.identity);
294
+ if (!createKeys.has(key) || emitted.has(key))
295
+ continue;
296
+ emitted.add(key);
297
+ const sql = createObjectSql(object, schema);
298
+ if (sql === undefined)
299
+ continue;
300
+ steps.push(step(sql, "expand", "ddl", ACCESS));
301
+ }
302
+ for (const object of creationOrder2(request.after)) {
303
+ if (object.kind === "type")
304
+ continue;
305
+ const key = identityKey2(object.identity);
306
+ if (deferred.has(key))
307
+ continue;
308
+ const parent = anchoredParent(object);
309
+ const tableIsNew = parent !== undefined && createdTables.has(parent);
310
+ if (tableIsNew && folded(object, request.after))
311
+ continue;
312
+ const wanted = createKeys.has(key) || tableIsNew && !folded(object, request.after);
313
+ if (!wanted || emitted.has(key))
314
+ continue;
315
+ emitted.add(key);
316
+ if (object.kind === "table") {
317
+ steps.push(step(createTableSql(object, request.after, schema), "expand", "ddl", ACCESS));
318
+ continue;
319
+ }
320
+ const sql = createObjectSql(object, schema);
321
+ if (sql === undefined)
322
+ continue;
323
+ steps.push(step(sql, "expand", "ddl", object.kind === "index" ? SHARE : ACCESS));
324
+ }
325
+ steps.push(...contractSwaps(picklists, enums, replacements, schema));
326
+ return {
327
+ name: request.name ?? "migration",
328
+ class: overall(steps),
329
+ steps
330
+ };
331
+ }
332
+ function formatPlan(plan) {
333
+ const lines = [`-- class: ${plan.class}`, `-- name: ${plan.name}`, ""];
334
+ if (plan.steps.length === 0) {
335
+ lines.push("-- no steps");
336
+ lines.push("");
337
+ return lines.join(`
338
+ `);
339
+ }
340
+ for (const item of plan.steps) {
341
+ lines.push(`-- class: ${item.class}`);
342
+ lines.push(`-- action: ${item.action}`);
343
+ lines.push(`-- lock: ${item.lock}`);
344
+ if (!item.transactional)
345
+ lines.push("-- transactional: false");
346
+ lines.push(`${item.sql};`);
347
+ lines.push("");
348
+ }
349
+ return lines.join(`
350
+ `);
351
+ }
352
+ function parsePlan(text) {
353
+ const lines = text.split(`
354
+ `);
355
+ let name = "migration";
356
+ let planClass = "expand";
357
+ let index = 0;
358
+ while (index < lines.length && lines[index] !== "") {
359
+ const line = lines[index] ?? "";
360
+ if (line.startsWith("-- name: "))
361
+ name = line.slice("-- name: ".length);
362
+ if (line.startsWith("-- class: "))
363
+ planClass = readClass(line.slice("-- class: ".length));
364
+ index += 1;
365
+ }
366
+ const steps = [];
367
+ while (index < lines.length) {
368
+ if ((lines[index] ?? "").trim() === "") {
369
+ index += 1;
370
+ continue;
371
+ }
372
+ if (lines[index] === "-- no steps")
373
+ break;
374
+ let stepClass = "expand";
375
+ let action = "ddl";
376
+ let lock = "";
377
+ let transactional = true;
378
+ const sql = [];
379
+ while (index < lines.length && (lines[index] ?? "") !== "") {
380
+ const line = lines[index] ?? "";
381
+ index += 1;
382
+ if (line.startsWith("-- class: "))
383
+ stepClass = readClass(line.slice("-- class: ".length));
384
+ else if (line.startsWith("-- action: "))
385
+ action = line.endsWith("backfill") ? "backfill" : "ddl";
386
+ else if (line.startsWith("-- lock: "))
387
+ lock = line.slice("-- lock: ".length);
388
+ else if (line === "-- transactional: false")
389
+ transactional = false;
390
+ else if (!line.startsWith("--"))
391
+ sql.push(line);
392
+ }
393
+ const statement = sql.join(`
394
+ `).replace(/;\s*$/, "");
395
+ if (statement.length > 0) {
396
+ steps.push({ sql: statement, class: stepClass, action, lock, transactional });
397
+ }
398
+ }
399
+ return { name, class: steps.length === 0 ? planClass : overall(steps), steps };
400
+ }
401
+ function readClass(value) {
402
+ if (value === "contract" || value === "unclassified")
403
+ return value;
404
+ return "expand";
405
+ }
406
+ function staleRenames(previous, renames) {
407
+ if (previous === undefined)
408
+ return [];
409
+ const messages = [];
410
+ for (const rename of renames) {
411
+ if (rename.kind === "table") {
412
+ if (!hasTable(previous, rename.from)) {
413
+ messages.push(`Table renamedFrom ${rename.from} is not in the previous snapshot.`);
414
+ }
415
+ continue;
416
+ }
417
+ const tableRename = renames.find((item) => item.kind === "table" && item.to === rename.table);
418
+ const previousTable = tableRename?.kind === "table" ? tableRename.from : rename.table;
419
+ if (!hasColumn(previous, previousTable, rename.from)) {
420
+ messages.push(`Column ${rename.table}.${rename.from} renamedFrom is not in the previous snapshot.`);
421
+ }
422
+ }
423
+ return messages;
424
+ }
425
+ function applyRenames(source, renames) {
426
+ let current = source;
427
+ const namespace = staticNamespace2("public");
428
+ for (const rename of renames) {
429
+ if (rename.kind !== "table")
430
+ continue;
431
+ if (!hasTable(current, rename.from))
432
+ continue;
433
+ current = renameTable2(current, { namespace, from: rename.from, to: rename.to });
434
+ }
435
+ for (const rename of renames) {
436
+ if (rename.kind !== "column")
437
+ continue;
438
+ const parent = { namespace, name: rename.table };
439
+ if (!hasColumn(current, rename.table, rename.from))
440
+ continue;
441
+ current = renameColumn2(current, { parent, from: rename.from, to: rename.to });
442
+ }
443
+ return current;
444
+ }
445
+ function renameSteps(source, renames, schema) {
446
+ const steps = [];
447
+ const tables = new Map;
448
+ for (const rename of renames) {
449
+ if (rename.kind !== "table" || !hasTable(source, rename.from))
450
+ continue;
451
+ tables.set(rename.to, rename.from);
452
+ steps.push(step(`alter table ${qualify2(schema, rename.from)} rename to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
453
+ }
454
+ for (const rename of renames) {
455
+ if (rename.kind !== "column")
456
+ continue;
457
+ const previousTable = tables.get(rename.table) ?? rename.table;
458
+ if (!hasColumn(source, previousTable, rename.from))
459
+ continue;
460
+ steps.push(step(`alter table ${qualify2(schema, rename.table)} rename column ${quoteIdent(rename.from)} to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
461
+ }
462
+ return steps;
463
+ }
464
+ function pairSameShape(beforeBy, afterBy, dropKeys, createKeys) {
465
+ const dropped = [...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object !== undefined);
466
+ const created = [...createKeys].map((key) => afterBy.get(key)).filter((object) => object !== undefined);
467
+ const pairs = [];
468
+ const used = new Set;
469
+ for (const left of dropped) {
470
+ if (left.kind !== "index" && left.kind !== "constraint")
471
+ continue;
472
+ const leftKey = identityKey2(left.identity);
473
+ const hits = created.filter((right) => {
474
+ const key = identityKey2(right.identity);
475
+ return key !== leftKey && !used.has(key) && sameShape(left, right);
476
+ });
477
+ const right = hits.length === 1 ? hits[0] : undefined;
478
+ if (right === undefined)
479
+ continue;
480
+ const rivals = dropped.filter((item) => sameShape(item, right));
481
+ if (rivals.length !== 1)
482
+ continue;
483
+ const rightKey = identityKey2(right.identity);
484
+ used.add(rightKey);
485
+ dropKeys.delete(identityKey2(left.identity));
486
+ createKeys.delete(rightKey);
487
+ if (left.identity.name !== right.identity.name)
488
+ pairs.push({ from: left, to: right });
489
+ }
490
+ return pairs;
491
+ }
492
+ function sameShape(left, right) {
493
+ if (left.kind !== right.kind)
494
+ return false;
495
+ if (left.kind !== "index" && left.kind !== "constraint" || right.kind !== "index" && right.kind !== "constraint") {
496
+ return false;
497
+ }
498
+ if (left.identity.parent.name !== right.identity.parent.name)
499
+ return false;
500
+ return stable(withoutNameKey(left.definition)) === stable(withoutNameKey(right.definition));
501
+ }
502
+ function withoutNameKey(value) {
503
+ const copy = { ...value };
504
+ delete copy.nameKey;
505
+ return copy;
506
+ }
507
+ function renameShapeStep(pair, schema) {
508
+ if (pair.from.kind === "index" && pair.to.kind === "index") {
509
+ return step(`alter index ${qualify2(schema, pair.from.identity.name)} rename to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
510
+ }
511
+ if (pair.from.kind !== "constraint" || pair.to.kind !== "constraint") {
512
+ return step("select 1", "unclassified", "ddl", ACCESS);
513
+ }
514
+ return step(`alter table ${qualify2(schema, pair.from.identity.parent.name)} rename constraint ${quoteIdent(pair.from.identity.name)} to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
515
+ }
516
+ function assertUnambiguous(beforeBy, afterBy) {
517
+ const dropped = new Map;
518
+ const added = new Map;
519
+ for (const [key, object] of beforeBy) {
520
+ if (afterBy.has(key))
521
+ continue;
522
+ const group = groupOf(object);
523
+ if (group === undefined)
524
+ continue;
525
+ const list = dropped.get(group) ?? [];
526
+ list.push(object.identity.name);
527
+ dropped.set(group, list);
528
+ }
529
+ for (const [key, object] of afterBy) {
530
+ if (beforeBy.has(key))
531
+ continue;
532
+ const group = groupOf(object);
533
+ if (group === undefined)
534
+ continue;
535
+ const list = added.get(group) ?? [];
536
+ list.push(object.identity.name);
537
+ added.set(group, list);
538
+ }
539
+ for (const [group, names] of added) {
540
+ const gone = dropped.get(group);
541
+ if (gone === undefined || gone.length === 0 || names.length === 0)
542
+ continue;
543
+ const from = gone[0] ?? "";
544
+ const to = names[0] ?? "";
545
+ const line = group.startsWith("table:") ? `Add renamedFrom: "${from}" on ${to}.` : `Add .renamedFrom("${from}") on ${to}.`;
546
+ throw new OkmError2("OKM1530", `Drop ${from} and add ${to} could be a rename. ${line}`, {
547
+ fix: { summary: line }
548
+ });
549
+ }
550
+ }
551
+ function groupOf(object) {
552
+ if (object.kind === "table")
553
+ return "table:public";
554
+ if (object.kind === "column")
555
+ return `column:${object.identity.parent.name}`;
556
+ return;
557
+ }
558
+ function recreateDependents(beforeList, afterList, alters, dropKeys, createKeys, picklists) {
559
+ const changed = new Set(alters.filter((item) => item.before.definition.dataType !== item.after.definition.dataType).map((item) => identityKey2(item.after.identity)));
560
+ if (changed.size === 0)
561
+ return;
562
+ const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
563
+ const afterBy = new Map(afterList.map((item) => [item.key, item.object]));
564
+ for (const item of beforeList) {
565
+ const key = identityKey2(item.object.identity);
566
+ if (deferred.has(key) || !afterBy.has(key))
567
+ continue;
568
+ if (!dependsOnChanged(item.object, changed))
569
+ continue;
570
+ dropKeys.add(key);
571
+ createKeys.add(key);
572
+ }
573
+ }
574
+ function dependsOnChanged(object, columns) {
575
+ if (object.kind !== "index" && object.kind !== "constraint")
576
+ return false;
577
+ return object.dependencies.some((edge) => columns.has(identityKey2(edge.target)));
578
+ }
579
+ function enumAddSteps(enums) {
580
+ const steps = [];
581
+ for (const change of enums) {
582
+ if (change.kind !== "add")
583
+ continue;
584
+ for (const sql of change.statements) {
585
+ steps.push(step(sql, "expand", "ddl", ACCESS, false));
586
+ }
587
+ }
588
+ return steps;
589
+ }
590
+ function expandBackfills(picklists, enums, replacements, schema) {
591
+ return dataSteps(picklists, enums, replacements, schema, "expand");
592
+ }
593
+ function contractSwaps(picklists, enums, replacements, schema) {
594
+ const steps = [];
595
+ steps.push(...dataSteps(picklists, enums, replacements, schema, "contract"));
596
+ for (const change of picklists) {
597
+ const table = qualify2(schema, change.after.identity.parent.name);
598
+ const name = quoteIdent(change.after.identity.name);
599
+ steps.push(step(`alter table ${table} drop constraint ${name}`, change.removed.length > 0 ? "contract" : "expand", "ddl", ACCESS));
600
+ const expression = change.after.definition.expression ?? "true";
601
+ steps.push(step(`alter table ${table} add constraint ${name} check (${expression}) not valid`, change.removed.length > 0 ? "contract" : "expand", "ddl", ACCESS));
602
+ steps.push(step(`alter table ${table} validate constraint ${name}`, change.removed.length > 0 ? "contract" : "expand", "ddl", SHARE_UPDATE));
603
+ }
604
+ for (const change of enums) {
605
+ if (change.kind !== "replace")
606
+ continue;
607
+ const type = qualify2(schema, change.name);
608
+ const old = quoteIdent(`${change.name}_old`);
609
+ const labels = change.labels.map((label) => sqlString(label)).join(", ");
610
+ steps.push(step(`alter type ${type} rename to ${old}`, "contract", "ddl", ACCESS));
611
+ steps.push(step(`create type ${type} as enum (${labels})`, "contract", "ddl", ACCESS));
612
+ for (const column of change.columns) {
613
+ const table = qualify2(schema, column.table);
614
+ const name = quoteIdent(column.column);
615
+ steps.push(step(`alter table ${table} alter column ${name} type ${type} using ${name}::text::${type}`, "contract", "ddl", ACCESS));
616
+ }
617
+ steps.push(step(`drop type ${qualify2(schema, `${change.name}_old`)}`, "contract", "ddl", ACCESS));
618
+ }
619
+ return steps;
620
+ }
621
+ function dataSteps(picklists, enums, replacements, schema, phase) {
622
+ const steps = [];
623
+ for (const change of picklists) {
624
+ if (change.removed.length === 0)
625
+ continue;
626
+ const table = change.after.identity.parent.name;
627
+ for (const value of change.removed) {
628
+ const replacement = findReplacement(replacements, table, change.column, value);
629
+ if (replacement === undefined)
630
+ continue;
631
+ steps.push(step(updateSql(schema, table, change.column, value, replacement.to), phase, "backfill", ROW));
632
+ }
633
+ }
634
+ for (const change of enums) {
635
+ if (change.kind !== "replace" || change.removed.length === 0)
636
+ continue;
637
+ for (const column of change.columns) {
638
+ if (!column.existed)
639
+ continue;
640
+ for (const value of change.removed) {
641
+ const replacement = findReplacement(replacements, column.table, column.column, value);
642
+ if (replacement === undefined)
643
+ continue;
644
+ steps.push(step(updateSql(schema, column.table, column.column, value, replacement.to, true), phase, "backfill", ROW));
645
+ }
646
+ }
647
+ }
648
+ return steps;
649
+ }
650
+ function requireReplacements(picklists, enums, replacements) {
651
+ const missing = [];
652
+ for (const change of picklists) {
653
+ for (const value of change.removed) {
654
+ const table = change.after.identity.parent.name;
655
+ const found = findReplacement(replacements, table, change.column, value);
656
+ if (found === undefined) {
657
+ missing.push(`--replace ${table}.${change.column}.${value}=<new>`);
658
+ continue;
659
+ }
660
+ assertReplacement(found, change.after.definition.expression, change.nullable, parseValues(change.after.definition.expression));
661
+ }
662
+ }
663
+ for (const change of enums) {
664
+ if (change.kind !== "replace")
665
+ continue;
666
+ for (const column of change.columns) {
667
+ if (!column.existed)
668
+ continue;
669
+ for (const value of change.removed) {
670
+ const found = findReplacement(replacements, column.table, column.column, value);
671
+ if (found === undefined) {
672
+ missing.push(`--replace ${column.table}.${column.column}.${value}=<new>`);
673
+ continue;
674
+ }
675
+ assertReplacement(found, undefined, column.nullable, change.labels);
676
+ }
677
+ }
678
+ }
679
+ if (missing.length > 0) {
680
+ throw new OkmError2("OKM1541", `Removed values need a replacement: ${missing.join(", ")}.`, {
681
+ fix: { summary: missing.join(" ") }
682
+ });
683
+ }
684
+ }
685
+ function assertReplacement(replacement, expression, nullable, allowed) {
686
+ if (replacement.to === null) {
687
+ if (!nullable) {
688
+ throw new OkmError2("OKM1541", `--replace ${replacement.table}.${replacement.column}.${replacement.from}=null needs a nullable column.`, { fix: { summary: "Pass a value that is in the new list, or make the column nullable." } });
689
+ }
690
+ return;
691
+ }
692
+ const list = allowed ?? (expression === undefined ? [] : parseValues(expression) ?? []);
693
+ if (!list.includes(replacement.to)) {
694
+ const flag = `--replace ${replacement.table}.${replacement.column}.${replacement.from}=${replacement.to}`;
695
+ throw new OkmError2("OKM1541", `${flag} is not in the new list.`, {
696
+ fix: { summary: "The replacement must be in the new list. No chains." }
697
+ });
698
+ }
699
+ }
700
+ function parseValues(expression) {
701
+ if (expression === undefined)
702
+ return;
703
+ return parseInList(expression)?.values;
704
+ }
705
+ function findReplacement(replacements, table, column, from) {
706
+ return replacements.find((item) => item.table === table && item.column === column && item.from === from);
707
+ }
708
+ function picklistChanges(beforeBy, afterBy, after) {
709
+ const changes = [];
710
+ for (const [key, object] of beforeBy) {
711
+ if (object.kind !== "constraint" || object.definition.constraintKind !== "check")
712
+ continue;
713
+ const next = afterBy.get(key);
714
+ if (next === undefined || next.kind !== "constraint")
715
+ continue;
716
+ const left = parseInList(object.definition.expression ?? "");
717
+ const right = parseInList(next.definition.expression ?? "");
718
+ if (left === undefined || right === undefined || left.column !== right.column)
719
+ continue;
720
+ const removed = left.values.filter((value) => !right.values.includes(value));
721
+ const added = right.values.filter((value) => !left.values.includes(value));
722
+ if (removed.length === 0 && added.length === 0)
723
+ continue;
724
+ changes.push({
725
+ before: object,
726
+ after: next,
727
+ column: right.column,
728
+ removed,
729
+ added,
730
+ nullable: columnNullable(after, next.identity.parent.name, right.column)
731
+ });
732
+ }
733
+ return changes;
734
+ }
735
+ function enumEdits(beforeBy, afterBy, schema) {
736
+ const edits = [];
737
+ for (const [key, next] of afterBy) {
738
+ if (next.kind !== "type")
739
+ continue;
740
+ const previous = beforeBy.get(key);
741
+ if (previous === undefined || previous.kind !== "type")
742
+ continue;
743
+ if (sameEnumLabels(previous.definition.labels, next.definition.labels))
744
+ continue;
745
+ const removed = previous.definition.labels.filter((label) => !next.definition.labels.includes(label));
746
+ const columns = enumColumns(previous.identity.name, beforeBy, afterBy);
747
+ if (removed.length === 0 && labelSubsequence(previous.definition.labels, next.definition.labels)) {
748
+ edits.push({
749
+ kind: "add",
750
+ name: next.identity.name,
751
+ statements: addValueStatements(schema, next.identity.name, previous.definition.labels, next.definition.labels)
752
+ });
753
+ continue;
754
+ }
755
+ edits.push({
756
+ kind: "replace",
757
+ name: next.identity.name,
758
+ labels: next.definition.labels,
759
+ removed,
760
+ columns
761
+ });
762
+ }
763
+ return edits;
764
+ }
765
+ function enumColumns(typeName, beforeBy, afterBy) {
766
+ const columns = [];
767
+ for (const object of afterBy.values()) {
768
+ if (object.kind !== "column")
769
+ continue;
770
+ if (!usesType(object, typeName))
771
+ continue;
772
+ columns.push({
773
+ table: object.identity.parent.name,
774
+ column: object.identity.name,
775
+ nullable: object.definition.nullable,
776
+ existed: beforeBy.has(identityKey2(object.identity))
777
+ });
778
+ }
779
+ columns.sort((left, right) => {
780
+ const table = compareText(left.table, right.table);
781
+ return table === 0 ? compareText(left.column, right.column) : table;
782
+ });
783
+ return columns;
784
+ }
785
+ function usesType(object, typeName) {
786
+ return object.dependencies.some((edge) => edge.target.kind === "type" && edge.target.name === typeName);
787
+ }
788
+ function labelSubsequence(before, after) {
789
+ let index = 0;
790
+ for (const label of after) {
791
+ if (before[index] === label)
792
+ index += 1;
793
+ }
794
+ return index === before.length;
795
+ }
796
+ function addValueStatements(schema, name, before, after) {
797
+ const present = new Set(before);
798
+ const statements = [];
799
+ const type = qualify2(schema, name);
800
+ for (let index = 0;index < after.length; index += 1) {
801
+ const label = after[index];
802
+ if (label === undefined || present.has(label))
803
+ continue;
804
+ let beforeNeighbor;
805
+ for (let cursor = index + 1;cursor < after.length; cursor += 1) {
806
+ const next = after[cursor];
807
+ if (next !== undefined && present.has(next)) {
808
+ beforeNeighbor = next;
809
+ break;
810
+ }
811
+ }
812
+ let sql = `alter type ${type} add value ${sqlString(label)}`;
813
+ if (beforeNeighbor !== undefined) {
814
+ sql += ` before ${sqlString(beforeNeighbor)}`;
815
+ } else {
816
+ let afterNeighbor;
817
+ for (let cursor = index - 1;cursor >= 0; cursor -= 1) {
818
+ const previous = after[cursor];
819
+ if (previous !== undefined && present.has(previous)) {
820
+ afterNeighbor = previous;
821
+ break;
822
+ }
823
+ }
824
+ if (afterNeighbor !== undefined)
825
+ sql += ` after ${sqlString(afterNeighbor)}`;
826
+ }
827
+ statements.push(sql);
828
+ present.add(label);
829
+ }
830
+ return statements;
831
+ }
832
+ function parseInList(expression) {
833
+ const match = /^\(\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.*)\)\s*\)$/i.exec(expression.trim());
834
+ const column = match?.[1];
835
+ const body = match?.[2];
836
+ if (column === undefined || body === undefined)
837
+ return;
838
+ const values = [];
839
+ let index = 0;
840
+ while (index < body.length) {
841
+ const char = body[index] ?? "";
842
+ if (char === " " || char === ",") {
843
+ index += 1;
844
+ continue;
845
+ }
846
+ if (char !== "'")
847
+ return;
848
+ let end = index + 1;
849
+ let value = "";
850
+ while (end < body.length) {
851
+ const next = body[end] ?? "";
852
+ if (next === "'" && body[end + 1] === "'") {
853
+ value += "'";
854
+ end += 2;
855
+ continue;
856
+ }
857
+ if (next === "'") {
858
+ end += 1;
859
+ break;
860
+ }
861
+ value += next;
862
+ end += 1;
863
+ }
864
+ values.push(value);
865
+ index = end;
866
+ }
867
+ return { column, values };
868
+ }
869
+ function columnNullable(source, table, column) {
870
+ for (const object of source.objects) {
871
+ if (object.kind === "column" && object.identity.parent.name === table && object.identity.name === column) {
872
+ return object.definition.nullable;
873
+ }
874
+ }
875
+ return false;
876
+ }
877
+ function columnAlterable(before, after) {
878
+ return stable(before.definition.identity) === stable(after.definition.identity) && stable(before.definition.generated) === stable(after.definition.generated);
879
+ }
880
+ function sameDefinition(left, right) {
881
+ return stable(left.definition) === stable(right.definition);
882
+ }
883
+ function covered(object, tables) {
884
+ if (object.kind === "column" || object.kind === "index" || object.kind === "constraint") {
885
+ return tables.has(object.identity.parent.name);
886
+ }
887
+ if (object.kind === "sequence") {
888
+ return object.dependencies.some((edge) => edge.target.kind === "table" && tables.has(edge.target.name));
889
+ }
890
+ return false;
891
+ }
892
+ function folded(object, source) {
893
+ if (object.kind === "column")
894
+ return true;
895
+ if (object.kind === "constraint" && object.definition.constraintKind === "primaryKey")
896
+ return true;
897
+ if (object.kind === "sequence" && identitySequence(object, source))
898
+ return true;
899
+ return false;
900
+ }
901
+ function anchoredParent(object) {
902
+ if (object.kind === "column" || object.kind === "index" || object.kind === "constraint") {
903
+ return object.identity.parent.name;
904
+ }
905
+ return;
906
+ }
907
+ function alterClass(sql) {
908
+ if (sql.includes("set data type") || sql.includes("set not null"))
909
+ return "contract";
910
+ return "expand";
911
+ }
912
+ function overall(steps) {
913
+ if (steps.some((item) => item.class === "contract"))
914
+ return "contract";
915
+ if (steps.some((item) => item.class === "unclassified"))
916
+ return "unclassified";
917
+ return "expand";
918
+ }
919
+ function updateSql(schema, table, column, from, to, cast = false) {
920
+ const value = to === null ? "null" : sqlString(to);
921
+ const compare = cast ? `${quoteIdent(column)}::text` : quoteIdent(column);
922
+ return `update ${qualify2(schema, table)} set ${quoteIdent(column)} = ${value} where ${compare} = ${sqlString(from)}`;
923
+ }
924
+ function hasTable(source, name) {
925
+ return source.objects.some((object) => object.kind === "table" && object.identity.name === name);
926
+ }
927
+ function hasColumn(source, table, name) {
928
+ return source.objects.some((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
929
+ }
930
+ function index(source) {
931
+ return source.objects.map((object) => ({ key: identityKey2(object.identity), object }));
932
+ }
933
+ function step(sql, classification, action, lock, transactional = true) {
934
+ return { sql, class: classification, action, lock, transactional };
935
+ }
936
+ function qualify2(schema, name) {
937
+ return `${quoteIdent(schema)}.${quoteIdent(name)}`;
938
+ }
939
+ function sqlString(value) {
940
+ return `'${value.replaceAll("'", "''")}'`;
941
+ }
942
+ function stable(value) {
943
+ return JSON.stringify(order(value));
944
+ }
945
+ function order(value) {
946
+ if (Array.isArray(value))
947
+ return value.map((item) => order(item));
948
+ if (value !== null && typeof value === "object") {
949
+ const record = value;
950
+ const next = {};
951
+ for (const key of Object.keys(record).sort())
952
+ next[key] = order(record[key]);
953
+ return next;
954
+ }
955
+ return value;
956
+ }
957
+
958
+ // src/tooling/migrate/policy.ts
959
+ var READ = new Set([
960
+ "plan",
961
+ "status",
962
+ "check",
963
+ "drift",
964
+ "verify",
965
+ "pull",
966
+ "catalog-export",
967
+ "inspect"
968
+ ]);
969
+ var ALLOWED = new Set(["expand", "reference", "provision"]);
970
+ var FORWARD_ONLY = new Set(["drop", "rollback"]);
971
+ function assertTargetPolicy(target, operation, allowProtected = false) {
972
+ if (FORWARD_ONLY.has(operation)) {
973
+ throw new OkmError2("invalid", "There is no down migration.", {
974
+ fix: { summary: "Recovery is a new forward migration." }
975
+ });
976
+ }
977
+ if (!target.protected || allowProtected)
978
+ return;
979
+ if (READ.has(operation) || ALLOWED.has(operation))
980
+ return;
981
+ throw new OkmError2("OKM1850", `${operation} is blocked on protected target ${target.name}.`, {
982
+ kind: "forbidden",
983
+ fix: {
984
+ summary: "Run a read-only command or an expand migration, or pass --allow-protected for this invocation."
985
+ }
986
+ });
987
+ }
988
+ function assertTargetAlias(targets) {
989
+ const groups = new Map;
990
+ for (const target of targets) {
991
+ const key = `${target.host}
992
+ ${target.port}
993
+ ${target.database}`;
994
+ const group = groups.get(key);
995
+ if (group === undefined)
996
+ groups.set(key, [target]);
997
+ else
998
+ group.push(target);
999
+ }
1000
+ for (const group of groups.values()) {
1001
+ const protectedTarget = group.find((target) => target.protected);
1002
+ const open = group.find((target) => !target.protected);
1003
+ if (protectedTarget !== undefined && open !== undefined) {
1004
+ throw new OkmError2("OKM1852", `${open.name} and ${protectedTarget.name} resolve to the same database and disagree about protection.`, {
1005
+ fix: {
1006
+ summary: "Give those targets the same protection flag. okm check and okm doctor both report this."
1007
+ }
1008
+ });
1009
+ }
1010
+ }
1011
+ }
1012
+ function assertDirectConnection(url, allowPooler) {
1013
+ if (allowPooler)
1014
+ return;
1015
+ const endpoint = endpointOf(url);
1016
+ const parsed = new URL(url);
1017
+ const host = endpoint.host;
1018
+ if (host.includes("pooler") || host.includes("pgbouncer") || host.includes("-pooler") || endpoint.port === "6543" || parsed.searchParams.get("pgbouncer") === "true") {
1019
+ throw new OkmError2("invalid", `Refusing pooler host ${host}. Apply needs a direct connection.`, { fix: { summary: "Use a direct connection, or pass --allow-pooler." } });
1020
+ }
1021
+ }
1022
+ function listTargets(config) {
1023
+ if (config.database !== undefined && config.targets !== undefined) {
1024
+ throw new OkmError2("invalid", "Set database or targets, not both.", {
1025
+ fix: { summary: "database is the one target named default. Use targets for more than one." }
1026
+ });
1027
+ }
1028
+ const entries = [];
1029
+ if (config.database !== undefined)
1030
+ entries.push(["default", config.database]);
1031
+ if (config.targets !== undefined) {
1032
+ for (const name of Object.keys(config.targets).sort()) {
1033
+ const input = config.targets[name];
1034
+ if (input !== undefined)
1035
+ entries.push([name, input]);
1036
+ }
1037
+ }
1038
+ return entries.map(([name, input]) => resolveTarget(name, input));
1039
+ }
1040
+ function selectTarget(config, name) {
1041
+ const targets = listTargets(config);
1042
+ if (targets.length === 0) {
1043
+ throw new OkmError2("OKM1845", "No target is configured.", {
1044
+ fix: { summary: "Set database or targets in okmodel.config.ts." }
1045
+ });
1046
+ }
1047
+ if (name === undefined) {
1048
+ if (targets.length === 1) {
1049
+ const only = targets[0];
1050
+ if (only !== undefined)
1051
+ return only;
1052
+ }
1053
+ throw new OkmError2("OKM1853", "Several targets are configured and --target was not passed.", {
1054
+ fix: { summary: "Pass --target <name>. The command does not guess." }
1055
+ });
1056
+ }
1057
+ const found = targets.find((target) => target.name === name);
1058
+ if (found === undefined) {
1059
+ throw new OkmError2("OKM1845", `Target ${name} is not configured.`, {
1060
+ fix: { summary: "Pass a name from targets, or database for the target named default." }
1061
+ });
1062
+ }
1063
+ return found;
1064
+ }
1065
+ function resolveTarget(name, input) {
1066
+ const url = typeof input === "string" ? input : input.url;
1067
+ const protectedTarget = typeof input === "string" ? false : input.protected === true;
1068
+ if (url === undefined || url.length === 0) {
1069
+ throw new OkmError2("OKM1845", `Target ${name} has no url.`, {
1070
+ fix: { summary: "Set the target url. A target is not inferred from the environment." }
1071
+ });
1072
+ }
1073
+ const endpoint = endpointOf(url);
1074
+ return {
1075
+ name,
1076
+ url,
1077
+ protected: protectedTarget,
1078
+ host: endpoint.host,
1079
+ port: endpoint.port,
1080
+ database: endpoint.database
1081
+ };
1082
+ }
1083
+ function endpointOf(url) {
1084
+ let parsed;
1085
+ try {
1086
+ parsed = new URL(url);
1087
+ } catch {
1088
+ throw new OkmError2("OKM1845", "The target URL is not a Postgres URL.", {
1089
+ fix: { summary: "Pass a postgres:// URL for the target." }
1090
+ });
1091
+ }
1092
+ const database = decodeURIComponent(parsed.pathname.replace(/^\//, ""));
1093
+ return {
1094
+ host: parsed.hostname.toLowerCase(),
1095
+ port: parsed.port.length > 0 ? parsed.port : "5432",
1096
+ database
1097
+ };
1098
+ }
1099
+
1100
+ // src/tooling/migrate/project.ts
1101
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
1102
+ import { basename, join } from "node:path";
1103
+ import { pathToFileURL } from "node:url";
1104
+
1105
+ // src/dialects/pg/declarations.ts
1106
+ function schemaDeclarations(source) {
1107
+ const renames = [];
1108
+ for (const item of source.tables) {
1109
+ const tableName = sqlTableName(item, source.casing);
1110
+ const renamed = tableRenamedFrom(item);
1111
+ if (renamed !== undefined) {
1112
+ renames.push({ kind: "table", from: renamed, to: tableName });
1113
+ }
1114
+ for (const [field, value] of Object.entries(item.columns)) {
1115
+ if (!(value instanceof ColumnBuilder2))
1116
+ continue;
1117
+ const state = value.state;
1118
+ const columnName = state.sqlName ?? sqlIdentifier(field, source.casing);
1119
+ if (state.renamedFrom !== undefined) {
1120
+ renames.push({
1121
+ kind: "column",
1122
+ table: tableName,
1123
+ from: state.renamedFrom,
1124
+ to: columnName
1125
+ });
1126
+ }
1127
+ }
1128
+ }
1129
+ return { renames };
1130
+ }
1131
+ function sqlTableName(item, casing) {
1132
+ const options = item.options;
1133
+ return options?.sqlName ?? sqlIdentifier(item.name, casing);
1134
+ }
1135
+ function tableRenamedFrom(item) {
1136
+ const options = item.options;
1137
+ return options?.renamedFrom;
1138
+ }
1139
+ function sqlIdentifier(name, casing) {
1140
+ return casing === "snake" ? snakeCase(name) : name;
1141
+ }
1142
+
1143
+ // src/dialects/pg/emit.ts
1144
+ function emitRowTypes(source) {
1145
+ const lines = [
1146
+ "/**",
1147
+ " * Row types emitted from the schema.",
1148
+ ` * Mode: ${source.types}.`,
1149
+ " */",
1150
+ ""
1151
+ ];
1152
+ const rows = [];
1153
+ const inserts = [];
1154
+ const updates = [];
1155
+ for (const item of source.tables) {
1156
+ const name = emittedTypeName(item.name);
1157
+ const key = propertyName(item.name);
1158
+ rows.push(` readonly ${key}: ${name};`);
1159
+ inserts.push(` readonly ${key}: ${name}Insert;`);
1160
+ updates.push(` readonly ${key}: ${name}Update;`);
1161
+ lines.push(`export interface ${name} {`);
1162
+ lines.push(fields(item, "row"));
1163
+ lines.push("}", "");
1164
+ lines.push(`export interface ${name}Insert {`);
1165
+ lines.push(fields(item, "insert"));
1166
+ lines.push("}", "");
1167
+ lines.push(`export interface ${name}Update {`);
1168
+ lines.push(fields(item, "update"));
1169
+ lines.push("}", "");
1170
+ }
1171
+ lines.push("export interface Rows {");
1172
+ lines.push(rows.join(`
1173
+ `));
1174
+ lines.push("}", "");
1175
+ lines.push("export interface Inserts {");
1176
+ lines.push(inserts.join(`
1177
+ `));
1178
+ lines.push("}", "");
1179
+ lines.push("export interface Updates {");
1180
+ lines.push(updates.join(`
1181
+ `));
1182
+ lines.push("}");
1183
+ return `${lines.join(`
1184
+ `)}
1185
+ `;
1186
+ }
1187
+ function propertyName(name) {
1188
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
1189
+ }
1190
+ function fields(item, kind) {
1191
+ const printed = [];
1192
+ for (const [field, builder] of Object.entries(item.columns)) {
1193
+ if (!(builder instanceof ColumnBuilder2)) {
1194
+ continue;
1195
+ }
1196
+ const state = builder.state;
1197
+ const written = insertMode(state);
1198
+ if (kind === "row") {
1199
+ if (!state.hidden) {
1200
+ printed.push(` readonly ${field}: ${valueType(state, "required")};`);
1201
+ }
1202
+ continue;
1203
+ }
1204
+ if (written === "omit") {
1205
+ continue;
1206
+ }
1207
+ const mode = kind === "update" ? "optional" : written;
1208
+ printed.push(` readonly ${field}: ${valueType(state, mode)};`);
1209
+ }
1210
+ return printed.join(`
1211
+ `);
1212
+ }
1213
+ function insertMode(state) {
1214
+ if (state.omitWrite || state.generated !== undefined || state.guarded) {
1215
+ return "omit";
1216
+ }
1217
+ if (state.hasDefault || state.nullable) {
1218
+ return "optional";
1219
+ }
1220
+ return "required";
1221
+ }
1222
+ function valueType(state, mode) {
1223
+ let label = scalarLabel(state);
1224
+ if (state.dims > 0) {
1225
+ for (let rank = 0;rank < state.dims; rank += 1) {
1226
+ label = `readonly (${label})[]`;
1227
+ }
1228
+ }
1229
+ if (state.nullable) {
1230
+ label += " | null";
1231
+ }
1232
+ if (mode === "optional") {
1233
+ label += " | undefined";
1234
+ }
1235
+ return label;
1236
+ }
1237
+ function scalarLabel(state) {
1238
+ if (state.picklist !== undefined) {
1239
+ return state.picklist.values.map((value) => JSON.stringify(value)).join(" | ");
1240
+ }
1241
+ if (state.typeLabel !== undefined) {
1242
+ return state.typeLabel;
1243
+ }
1244
+ const base = baseName(state.baseType);
1245
+ switch (base) {
1246
+ case "smallint":
1247
+ case "integer":
1248
+ case "real":
1249
+ case "double precision":
1250
+ return "number";
1251
+ case "bigint":
1252
+ case "numeric":
1253
+ case "text":
1254
+ case "varchar":
1255
+ case "char":
1256
+ case "citext":
1257
+ case "uuid":
1258
+ case "inet":
1259
+ case "cidr":
1260
+ case "macaddr":
1261
+ case "macaddr8":
1262
+ case "tsvector":
1263
+ case "ltree":
1264
+ return "string";
1265
+ case "boolean":
1266
+ return "boolean";
1267
+ case "bytea":
1268
+ return "Uint8Array";
1269
+ case "json":
1270
+ case "jsonb":
1271
+ return "unknown";
1272
+ case "timestamptz":
1273
+ return "Temporal.Instant";
1274
+ case "timestamp":
1275
+ return "Temporal.PlainDateTime";
1276
+ case "date":
1277
+ return "Temporal.PlainDate";
1278
+ case "time":
1279
+ return "Temporal.PlainTime";
1280
+ case "timetz":
1281
+ return "{ readonly time: Temporal.PlainTime; readonly offset: string }";
1282
+ case "interval":
1283
+ return "Temporal.Duration";
1284
+ case "tstzrange":
1285
+ return rangeLabel("Temporal.Instant");
1286
+ case "daterange":
1287
+ return rangeLabel("Temporal.PlainDate");
1288
+ case "int4range":
1289
+ return rangeLabel("number");
1290
+ case "int8range":
1291
+ case "numrange":
1292
+ return rangeLabel("string");
1293
+ case "point":
1294
+ return "{ readonly x: number; readonly y: number }";
1295
+ case "line":
1296
+ return "{ readonly a: number; readonly b: number; readonly c: number }";
1297
+ default:
1298
+ return "unknown";
1299
+ }
1300
+ }
1301
+ function rangeLabel(bound) {
1302
+ return `{ readonly empty: true } | { readonly empty: false; readonly lower: ${bound} | null; readonly upper: ${bound} | null; readonly lowerInclusive: boolean; readonly upperInclusive: boolean }`;
1303
+ }
1304
+ function baseName(dataType) {
1305
+ if (dataType.startsWith("double precision")) {
1306
+ return "double precision";
1307
+ }
1308
+ const space = dataType.indexOf(" ");
1309
+ const head = space === -1 ? dataType : dataType.slice(0, space);
1310
+ const paren = head.indexOf("(");
1311
+ return paren === -1 ? head : head.slice(0, paren);
1312
+ }
1313
+
1314
+ // src/tooling/migrate/project.ts
1315
+ async function buildProject(cwd) {
1316
+ const config = await loadConfig(cwd);
1317
+ const built = await loadSchema(cwd, config.schema);
1318
+ return writeArtifact(cwd, config, built);
1319
+ }
1320
+ async function generateProject(cwd, name, flags) {
1321
+ const prepared = await prepare(cwd, name, flags);
1322
+ writeArtifact(cwd, prepared.config, prepared.built);
1323
+ if (prepared.plan.steps.length === 0)
1324
+ return;
1325
+ const directory = join(cwd, prepared.config.migrations ?? "migrations");
1326
+ mkdirSync(directory, { recursive: true });
1327
+ const base = `${nextNumber(directory)}_${safeName(name)}`;
1328
+ const sqlPath = join(directory, `${base}.sql`);
1329
+ writeFileSync(sqlPath, formatPlan(prepared.plan));
1330
+ writeFileSync(join(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
1331
+ return sqlPath;
1332
+ }
1333
+ async function planProject(cwd, name, flags) {
1334
+ const prepared = await prepare(cwd, name, flags);
1335
+ return prepared.plan;
1336
+ }
1337
+ async function checkProject(cwd) {
1338
+ const opened = await openProject(cwd);
1339
+ assertTargetAlias(listTargets(opened.config));
1340
+ if (opened.config.tables !== undefined) {
1341
+ const directory = join(cwd, opened.config.tables);
1342
+ const files = existsSync(directory) ? readdirSync(directory) : [];
1343
+ const source = readFileSync(join(cwd, opened.config.schema), "utf8");
1344
+ const missing = unlistedTableFiles(source, files);
1345
+ if (missing.length > 0) {
1346
+ throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
1347
+ fix: { summary: "Add the file's table to the schema() call that okm check reads." }
1348
+ });
1349
+ }
1350
+ }
1351
+ }
1352
+ function unlistedTableFiles(schemaSource, fileNames) {
1353
+ const missing = [];
1354
+ for (const file of fileNames) {
1355
+ if (!file.endsWith(".ts"))
1356
+ continue;
1357
+ const stem = basename(file, ".ts");
1358
+ if (stem.length === 0 || stem.startsWith("."))
1359
+ continue;
1360
+ if (!schemaSource.includes(stem))
1361
+ missing.push(file);
1362
+ }
1363
+ return missing;
1364
+ }
1365
+ async function projectHead(cwd) {
1366
+ const opened = await openProject(cwd);
1367
+ return { previous: opened.previous, catalog: opened.built.catalog, renames: opened.renames };
1368
+ }
1369
+ async function prepare(cwd, name, flags) {
1370
+ const opened = await openProject(cwd);
1371
+ const plan = planMigration({
1372
+ before: opened.previous,
1373
+ after: opened.built.catalog,
1374
+ renames: opened.renames,
1375
+ replacements: flags.map((flag) => parseReplace(flag)),
1376
+ name
1377
+ });
1378
+ return { config: opened.config, built: opened.built, plan };
1379
+ }
1380
+ async function openProject(cwd) {
1381
+ const config = await loadConfig(cwd);
1382
+ const built = await loadSchema(cwd, config.schema);
1383
+ const declarations = schemaDeclarations(built);
1384
+ const previous = readPrevious(join(cwd, config.migrations ?? "migrations"));
1385
+ const stale = staleRenames(previous?.catalog, declarations.renames);
1386
+ if (stale.length > 0) {
1387
+ throw new OkmError2("OKM1020", stale.join(" "), {
1388
+ fix: { summary: "Remove renamedFrom once the migration that used it has been applied." }
1389
+ });
1390
+ }
1391
+ return {
1392
+ config,
1393
+ built,
1394
+ previous: previous?.catalog ?? catalog([]),
1395
+ renames: declarations.renames
1396
+ };
1397
+ }
1398
+ function writeArtifact(cwd, config, built) {
1399
+ const directory = join(cwd, config.out ?? ".okm");
1400
+ mkdirSync(directory, { recursive: true });
1401
+ const text = serializeCatalog2(built.catalog);
1402
+ writeFileSync(join(directory, "catalog.json"), text);
1403
+ writeFileSync(join(directory, "catalog.hash"), `${catalogHash2(built.catalog)}
1404
+ `);
1405
+ writeFileSync(join(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
1406
+ return directory;
1407
+ }
1408
+ async function loadConfig(cwd) {
1409
+ const path = join(cwd, "okmodel.config.ts");
1410
+ const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
1411
+ const record = isRecord(imported) ? imported : {};
1412
+ const config = record.default ?? record.config;
1413
+ if (!isConfig(config)) {
1414
+ throw new OkmError2("invalid", "okmodel.config.ts must default-export defineConfig(...).");
1415
+ }
1416
+ return config;
1417
+ }
1418
+ async function loadSchema(cwd, spec) {
1419
+ const path = join(cwd, spec);
1420
+ const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
1421
+ const record = isRecord(imported) ? imported : {};
1422
+ for (const value of Object.values(record)) {
1423
+ if (!isBuilt(value))
1424
+ continue;
1425
+ value.catalog;
1426
+ return value;
1427
+ }
1428
+ throw new OkmError2("invalid", `${spec} must export a schema().`);
1429
+ }
1430
+ function readPrevious(directory) {
1431
+ if (!existsSync(directory))
1432
+ return;
1433
+ const catalogs = readdirSync(directory).filter((file) => file.endsWith(".catalog.json")).sort();
1434
+ const latest = catalogs.at(-1);
1435
+ if (latest === undefined)
1436
+ return;
1437
+ const text = readFileSync(join(directory, latest), "utf8");
1438
+ return { catalog: parseCatalog2(text) };
1439
+ }
1440
+ function referenceAugmentation(source) {
1441
+ const names = source.tables.map((item) => JSON.stringify(item.name));
1442
+ const union = names.length === 0 ? "never" : names.join(" | ");
1443
+ return [
1444
+ 'import type { ColumnFlags, ReferenceOptions } from "okmodel/pg";',
1445
+ "",
1446
+ 'declare module "okmodel/pg" {',
1447
+ " interface ColumnBuilder<TValue, TFlags extends ColumnFlags> {",
1448
+ " references(",
1449
+ ` table: ${union},`,
1450
+ " options?: ReferenceOptions,",
1451
+ " ): ColumnBuilder<TValue, TFlags>;",
1452
+ " }",
1453
+ "}",
1454
+ "",
1455
+ `export type TableName = ${union};`,
1456
+ ""
1457
+ ].join(`
1458
+ `);
1459
+ }
1460
+ function nextNumber(directory) {
1461
+ let max = 0;
1462
+ if (existsSync(directory)) {
1463
+ for (const file of readdirSync(directory)) {
1464
+ const match = /^(\d+)_/.exec(file);
1465
+ const value = match?.[1];
1466
+ if (value !== undefined)
1467
+ max = Math.max(max, Number(value));
1468
+ }
1469
+ }
1470
+ return String(max + 1).padStart(4, "0");
1471
+ }
1472
+ function safeName(name) {
1473
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) {
1474
+ throw new OkmError2("invalid", `Migration name ${name} must be letters, digits, _ or -.`);
1475
+ }
1476
+ return name;
1477
+ }
1478
+ function isBuilt(value) {
1479
+ if (!isRecord(value) || !Array.isArray(value.tables))
1480
+ return false;
1481
+ return "catalog" in value;
1482
+ }
1483
+ function isConfig(value) {
1484
+ return isRecord(value) && typeof value.schema === "string";
1485
+ }
1486
+ function isRecord(value) {
1487
+ return typeof value === "object" && value !== null;
1488
+ }
1489
+
1490
+ export { quoteIdent, planMigration, formatPlan, parsePlan, assertTargetPolicy, assertDirectConnection, listTargets, selectTarget, buildProject, generateProject, planProject, checkProject, projectHead, loadConfig };