okmodel 0.2.1 → 0.4.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 (148) hide show
  1. package/README.md +882 -48
  2. package/dist/contracts/catalog/document.d.ts +6 -2
  3. package/dist/contracts/catalog/enum.d.ts +31 -4
  4. package/dist/contracts/catalog/extension.d.ts +61 -0
  5. package/dist/contracts/catalog/privilege.d.ts +64 -0
  6. package/dist/contracts/catalog/routine.d.ts +56 -0
  7. package/dist/contracts/catalog/types.d.ts +164 -10
  8. package/dist/contracts/catalog/view.d.ts +73 -0
  9. package/dist/dialects/pg/column.d.ts +3 -1
  10. package/dist/dialects/pg/ddl.d.ts +65 -1
  11. package/dist/dialects/pg/enum.d.ts +10 -8
  12. package/dist/dialects/pg/ext/citext.d.ts +14 -0
  13. package/dist/dialects/pg/ext/citext.js +11 -0
  14. package/dist/dialects/pg/ext/index.d.ts +52 -0
  15. package/dist/dialects/pg/ext/index.js +6 -0
  16. package/dist/dialects/pg/ext/pg-trgm.d.ts +53 -0
  17. package/dist/dialects/pg/ext/pg-trgm.js +42 -0
  18. package/dist/dialects/pg/fn/index.d.ts +107 -0
  19. package/dist/dialects/pg/fn/index.js +175 -0
  20. package/dist/dialects/pg/fn/touch.d.ts +14 -0
  21. package/dist/dialects/pg/index.js +104 -19
  22. package/dist/dialects/pg/introspect.d.ts +10 -1
  23. package/dist/dialects/pg/operators.d.ts +8 -1
  24. package/dist/dialects/pg/role/check.d.ts +45 -0
  25. package/dist/dialects/pg/role/index.d.ts +50 -0
  26. package/dist/dialects/pg/role/sql.d.ts +51 -0
  27. package/dist/dialects/pg/table.d.ts +24 -1
  28. package/dist/dialects/pg/tenancy.d.ts +9 -0
  29. package/dist/dialects/pg/view/client.d.ts +30 -0
  30. package/dist/dialects/pg/view/depend.d.ts +35 -0
  31. package/dist/dialects/pg/view/index.d.ts +114 -0
  32. package/dist/dialects/pg/view/index.js +289 -0
  33. package/dist/dialects/pg/view/scratch.d.ts +22 -0
  34. package/dist/dialects/pg/where.d.ts +2 -2
  35. package/dist/runtime/catalog-cache.d.ts +17 -0
  36. package/dist/runtime/compat-words.d.ts +23 -0
  37. package/dist/runtime/drift-detail.d.ts +20 -4
  38. package/dist/runtime/drift.d.ts +4 -2
  39. package/dist/runtime/pg/bun.js +3 -3
  40. package/dist/runtime/pg/pg.js +3 -3
  41. package/dist/runtime/pg/pglite.js +3 -3
  42. package/dist/runtime/pg/postgresjs.js +3 -3
  43. package/dist/runtime/tenancy/index.js +6 -3
  44. package/dist/runtime/traits/index.d.ts +7 -2
  45. package/dist/runtime/traits/index.js +151 -13
  46. package/dist/runtime/types.d.ts +2 -0
  47. package/dist/runtime/validate/index.js +4 -4
  48. package/dist/shared/{xkz8g6by.js → 1g0drakv.js} +2 -2
  49. package/dist/shared/2321avmn.js +418 -0
  50. package/dist/shared/{7mht49yd.js → 3p4b1fg0.js} +57 -18
  51. package/dist/shared/41kv2xdm.js +318 -0
  52. package/dist/shared/{97g2jnrw.js → 4ef7mmqf.js} +3 -3
  53. package/dist/shared/{sacqha1y.js → 4m9fn52z.js} +2 -2
  54. package/dist/shared/5mtf6cj3.js +94 -0
  55. package/dist/shared/{1dp5knt7.js → 5vdm4sgp.js} +2 -2
  56. package/dist/shared/{a1gdp7j9.js → 6693j9rs.js} +1 -1
  57. package/dist/shared/{38crxbgn.js → 6p5k20sm.js} +3 -3
  58. package/dist/shared/7nk68fj8.js +751 -0
  59. package/dist/shared/84ptn35q.js +71 -0
  60. package/dist/shared/{d2whjz4k.js → 8jb4ymh2.js} +581 -28
  61. package/dist/shared/8ktwd3rh.js +97 -0
  62. package/dist/shared/94ez8kzh.js +19 -0
  63. package/dist/shared/{3a5ne750.js → 9m80mkac.js} +2 -2
  64. package/dist/shared/9x25fxhc.js +150 -0
  65. package/dist/shared/9yn6a2vd.js +11 -0
  66. package/dist/shared/ady1ms74.js +72 -0
  67. package/dist/shared/{4jzzghr6.js → awrsqrn7.js} +6 -6
  68. package/dist/shared/{4b9da9d7.js → axhk06q1.js} +19 -1
  69. package/dist/shared/{d04j53v8.js → c5g81ngb.js} +82 -14
  70. package/dist/shared/{n47rd821.js → c8aghh05.js} +1 -1
  71. package/dist/shared/{dd4f8mnx.js → c9z5g0fm.js} +4 -2
  72. package/dist/shared/{ps43134h.js → dyj86cmj.js} +4 -4
  73. package/dist/shared/{0qkxbm1f.js → fa9vt245.js} +1 -1
  74. package/dist/shared/{qa5dsc7z.js → fb00y8fp.js} +3 -3
  75. package/dist/shared/{qxa0yb9j.js → g7ez901n.js} +30 -1
  76. package/dist/shared/{py3nh7jy.js → gdh9mrtp.js} +2 -2
  77. package/dist/shared/{yw5c610b.js → gjthds9j.js} +2 -2
  78. package/dist/shared/hcmdtwgh.js +102 -0
  79. package/dist/shared/hqjsxnz7.js +13 -0
  80. package/dist/shared/htnaj77v.js +17 -0
  81. package/dist/shared/{v18jva5b.js → jgxzjzcz.js} +1 -1
  82. package/dist/shared/{x9v1qgeb.js → jsd07prz.js} +3 -3
  83. package/dist/shared/{g9f0zqce.js → kafaa5zv.js} +1 -1
  84. package/dist/shared/kj680nxv.js +19 -0
  85. package/dist/shared/{mxmcdsh5.js → kkzwp3d2.js} +9 -9
  86. package/dist/shared/{g1b5zzj9.js → n0dgs0pb.js} +1 -1
  87. package/dist/shared/n5y9zyj1.js +2431 -0
  88. package/dist/shared/{zt8y0wzb.js → n7mcc1gk.js} +1 -1
  89. package/dist/shared/{ge03bfca.js → nxb42q7h.js} +2 -2
  90. package/dist/shared/q6zsyh31.js +2192 -0
  91. package/dist/shared/{sfh4pk75.js → q795afwa.js} +26 -21
  92. package/dist/shared/{10tjenmd.js → qn0srwwq.js} +1 -1
  93. package/dist/shared/s4mb6a4b.js +545 -0
  94. package/dist/shared/{ytg3xf9p.js → sf8m6tfy.js} +3 -3
  95. package/dist/shared/{zd93g3rq.js → sr5ms7b3.js} +1 -1
  96. package/dist/shared/{xatfg0gn.js → tmbdywx0.js} +4 -19
  97. package/dist/shared/tny2t3sm.js +100 -0
  98. package/dist/shared/{e7a268fy.js → v7tpnhnz.js} +1 -1
  99. package/dist/shared/{ecdzej21.js → wqem4jed.js} +7 -7
  100. package/dist/shared/xghkt7a9.js +1144 -0
  101. package/dist/shared/y005b761.js +112 -0
  102. package/dist/shared/ycatkxmq.js +84 -0
  103. package/dist/shared/ygem90rn.js +900 -0
  104. package/dist/shared/{eaxwrgby.js → yjw02enp.js} +6 -3
  105. package/dist/shared/{mrq77sgw.js → zg24x0hm.js} +1 -1
  106. package/dist/shared/{cstgkzdx.js → zvbjc949.js} +1 -1
  107. package/dist/tooling/cli.js +66 -13
  108. package/dist/tooling/migrate/apply.d.ts +75 -5
  109. package/dist/tooling/migrate/backfill.d.ts +136 -0
  110. package/dist/tooling/migrate/check.d.ts +65 -0
  111. package/dist/tooling/migrate/classify.d.ts +108 -0
  112. package/dist/tooling/migrate/commands.d.ts +2 -1
  113. package/dist/tooling/migrate/config.d.ts +30 -0
  114. package/dist/tooling/migrate/doctor.d.ts +28 -0
  115. package/dist/tooling/migrate/drift.d.ts +35 -0
  116. package/dist/tooling/migrate/estimate.d.ts +50 -0
  117. package/dist/tooling/migrate/ext.d.ts +17 -0
  118. package/dist/tooling/migrate/extensions.d.ts +53 -0
  119. package/dist/tooling/migrate/files.d.ts +12 -0
  120. package/dist/tooling/migrate/index.d.ts +3 -2
  121. package/dist/tooling/migrate/index.js +67 -1
  122. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  123. package/dist/tooling/migrate/lint.d.ts +71 -0
  124. package/dist/tooling/migrate/managed.d.ts +17 -0
  125. package/dist/tooling/migrate/plan.d.ts +78 -10
  126. package/dist/tooling/migrate/project.d.ts +64 -5
  127. package/dist/tooling/migrate/provision.d.ts +18 -0
  128. package/dist/tooling/migrate/reference.d.ts +40 -0
  129. package/dist/tooling/migrate/runner.d.ts +108 -0
  130. package/dist/tooling/migrate/safe.d.ts +77 -0
  131. package/dist/tooling/migrate/seed.d.ts +20 -0
  132. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  133. package/dist/tooling/migrate/status.d.ts +25 -6
  134. package/dist/tooling/migrate/type-name.d.ts +19 -0
  135. package/dist/tooling/testing/factories.d.ts +117 -0
  136. package/dist/tooling/testing/facts.d.ts +72 -0
  137. package/dist/tooling/testing/index.d.ts +89 -0
  138. package/dist/tooling/testing/index.js +8 -0
  139. package/dist/tooling/testing/isolation.d.ts +30 -0
  140. package/dist/tooling/testing/random.d.ts +40 -0
  141. package/dist/tooling/testing/record.d.ts +38 -0
  142. package/package.json +26 -2
  143. package/dist/shared/5z4q2zda.js +0 -2073
  144. package/dist/shared/eqmtn1bj.js +0 -399
  145. package/dist/shared/rtnaqre0.js +0 -37
  146. package/dist/shared/zwmqxb0g.js +0 -79
  147. package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
  148. /package/dist/shared/{9k6vetvf.js → rrstp3kb.js} +0 -0
@@ -0,0 +1,289 @@
1
+ import {
2
+ viewObject,
3
+ materializedViewObject,
4
+ materializedViewIndex
5
+ } from "../../../shared/y005b761.js";
6
+ import {
7
+ encodeUuid,
8
+ decodeUuid
9
+ } from "../../../shared/dyj86cmj.js";
10
+ import {
11
+ snakeCase
12
+ } from "../../../shared/jsd07prz.js";
13
+ import {
14
+ encodeText,
15
+ decodeText
16
+ } from "../../../shared/4m9fn52z.js";
17
+ import {
18
+ staticNamespace2
19
+ } from "../../../shared/t4tbsy9h.js";
20
+ import {
21
+ OkmError2
22
+ } from "../../../shared/52szzakm.js";
23
+
24
+ // src/dialects/pg/view/client.ts
25
+ function viewClient(name, columns, query, tenancy) {
26
+ const field = camelCase(name);
27
+ const marked = readGlobal(name, tenancy);
28
+ return {
29
+ field,
30
+ install(model, scope, tables, casing, scoped) {
31
+ if (Object.hasOwn(model, field)) {
32
+ throw new OkmError2("OKM1023", `View ${name} uses the client name ${field}.`, {
33
+ fix: { summary: "Give the view a name that no table uses." }
34
+ });
35
+ }
36
+ const exposes = exposesKey(columns, scope);
37
+ if (scope !== undefined && readsTenant(query, tables, casing, scoped)) {
38
+ if (!exposes && marked === undefined) {
39
+ throw new OkmError2("OKM1820", `View ${name} reads a tenant table and does not expose ${scope.key}.`, {
40
+ fix: {
41
+ summary: "Expose the tenant key so the predicate can be applied, or declare global(reason)."
42
+ }
43
+ });
44
+ }
45
+ if (exposes) {
46
+ scope.scopeView(field);
47
+ scoped.add(name);
48
+ }
49
+ }
50
+ model[field] = {
51
+ name: field,
52
+ sql: name,
53
+ primary: [],
54
+ uniques: [],
55
+ columns: columnModels(columns, scope),
56
+ relations: []
57
+ };
58
+ },
59
+ hook(target, ctx) {
60
+ publish(field, target, ctx);
61
+ }
62
+ };
63
+ }
64
+ function publish(field, target, ctx) {
65
+ if (ctx.tables === undefined || ctx.table !== undefined)
66
+ return;
67
+ const handle = ctx.tables[field];
68
+ if (typeof handle !== "object" || handle === null)
69
+ return;
70
+ const source = handle;
71
+ const views = target.views;
72
+ const map = typeof views === "object" && views !== null ? views : {};
73
+ map[field] = {
74
+ find: source.find,
75
+ one: source.one,
76
+ count: source.count,
77
+ exists: source.exists
78
+ };
79
+ target.views = map;
80
+ delete ctx.tables[field];
81
+ delete target[field];
82
+ }
83
+ function columnModels(columns, tenancy) {
84
+ const seen = new Set;
85
+ const models = [];
86
+ for (const column of columns) {
87
+ const field = fieldName(column.name, tenancy);
88
+ if (seen.has(field)) {
89
+ throw new OkmError2("OKM1020", `View column ${column.name} repeats ${field}.`, {
90
+ fix: { summary: "Declare each output column once." }
91
+ });
92
+ }
93
+ seen.add(field);
94
+ const uuid = column.type.toLowerCase() === "uuid";
95
+ const key = tenancy !== undefined && field === tenancy.key;
96
+ models.push({
97
+ field,
98
+ sql: column.name,
99
+ dataType: column.type,
100
+ encode: uuid ? (value) => encodeUuid(textValue(value)) : (value) => encodeText(textValue(value)),
101
+ decode: uuid ? decodeUuid : decodeText,
102
+ hidden: false,
103
+ sensitive: false,
104
+ guarded: key,
105
+ writable: false,
106
+ guardUpdate: false
107
+ });
108
+ }
109
+ return models;
110
+ }
111
+ function exposesKey(columns, tenancy) {
112
+ if (tenancy === undefined)
113
+ return false;
114
+ return columns.some((column) => fieldName(column.name, tenancy) === tenancy.key);
115
+ }
116
+ function readsTenant(query, tables, casing, scoped) {
117
+ for (const name of scoped) {
118
+ if (mentions(query, name))
119
+ return true;
120
+ }
121
+ for (const table of tables) {
122
+ const mark = table.options?.tenancy;
123
+ if (isGlobalMark(mark))
124
+ continue;
125
+ const sql = casing === "snake" ? snakeCase(table.name) : table.name;
126
+ if (mentions(query, sql) || mentions(query, table.name))
127
+ return true;
128
+ }
129
+ return false;
130
+ }
131
+ function readGlobal(name, tenancy) {
132
+ if (tenancy === undefined)
133
+ return;
134
+ if (!isGlobalMark(tenancy)) {
135
+ throw new OkmError2("OKM1020", `View ${name} tenancy must be global("reason").`, {
136
+ fix: { summary: 'Pass global("reason") from okmodel/tenancy, or omit tenancy.' }
137
+ });
138
+ }
139
+ return tenancy.reason;
140
+ }
141
+ function isGlobalMark(value) {
142
+ if (typeof value !== "object" || value === null)
143
+ return false;
144
+ const record = value;
145
+ return record.kind === "global" && typeof record.reason === "string" && record.reason.trim().length > 0;
146
+ }
147
+ function fieldName(column, tenancy) {
148
+ const field = camelCase(column);
149
+ if (tenancy !== undefined && (column === tenancy.key || field === tenancy.key))
150
+ return tenancy.key;
151
+ return field;
152
+ }
153
+ function camelCase(name) {
154
+ let out = "";
155
+ let upper = false;
156
+ for (const char of name) {
157
+ if (char === "_") {
158
+ upper = out.length > 0;
159
+ continue;
160
+ }
161
+ out += upper ? char.toUpperCase() : char;
162
+ upper = false;
163
+ }
164
+ return out;
165
+ }
166
+ function mentions(query, name) {
167
+ let pattern = '(^|[^A-Za-z0-9_])"?';
168
+ for (const char of name) {
169
+ if ("\\^$[]().*+?|{}".includes(char))
170
+ pattern += "\\";
171
+ pattern += char;
172
+ }
173
+ pattern += '"?(?![A-Za-z0-9_])';
174
+ return new RegExp(pattern).test(query);
175
+ }
176
+ function textValue(value) {
177
+ if (typeof value !== "string") {
178
+ throw new OkmError2("OKM1121", "A view column value must be text.", {
179
+ fix: { summary: "Pass a string." }
180
+ });
181
+ }
182
+ return value;
183
+ }
184
+
185
+ // src/dialects/pg/view/index.ts
186
+ function view(name, options) {
187
+ const schema = options.schema ?? "public";
188
+ const columns = options.columns.map((column) => ({ name: column.name, dataType: column.type }));
189
+ const query = bodyText(options.query);
190
+ const bound = viewClient(name, options.columns, query, options.tenancy);
191
+ return {
192
+ name,
193
+ schema,
194
+ install: bound.install,
195
+ hook: bound.hook,
196
+ contribute(peers, built) {
197
+ assertPeers(peers);
198
+ assertNoTable(built, name);
199
+ const provenance = { origin: "file", name };
200
+ return [
201
+ viewObject({
202
+ namespace: staticNamespace2(schema),
203
+ name,
204
+ columns,
205
+ query,
206
+ provenance
207
+ })
208
+ ];
209
+ }
210
+ };
211
+ }
212
+ function materializedView(name, options) {
213
+ const schema = options.schema ?? "public";
214
+ const columns = options.columns.map((column) => ({ name: column.name, dataType: column.type }));
215
+ const query = bodyText(options.query);
216
+ const bound = viewClient(name, options.columns, query, options.tenancy);
217
+ const indexes = options.indexes ?? [];
218
+ if (options.refresh === "concurrently" && !indexes.some((index) => index.unique === true)) {
219
+ throw new OkmError2("OKM1822", `Materialized view ${name} refreshes concurrently and has no unique index.`, { fix: { summary: "Add a unique index, or refresh without CONCURRENTLY." } });
220
+ }
221
+ const columnNames = new Set(columns.map((column) => column.name));
222
+ for (const index of indexes) {
223
+ for (const column of index.columns) {
224
+ if (!columnNames.has(column)) {
225
+ throw new OkmError2("OKM1020", `Index on ${name} names column ${column}, which the materialized view does not have.`, { fix: { summary: "Name a column of the materialized view." } });
226
+ }
227
+ }
228
+ }
229
+ return {
230
+ name,
231
+ schema,
232
+ install: bound.install,
233
+ hook: bound.hook,
234
+ contribute(peers, built) {
235
+ assertPeers(peers);
236
+ assertNoTable(built, name);
237
+ const namespace = staticNamespace2(schema);
238
+ const provenance = { origin: "file", name };
239
+ const parent = { namespace, name };
240
+ return [
241
+ materializedViewObject({
242
+ namespace,
243
+ name,
244
+ columns,
245
+ query,
246
+ provenance,
247
+ ...options.refresh === "concurrently" ? { refresh: "concurrently" } : {}
248
+ }),
249
+ ...indexes.map((index) => materializedViewIndex({
250
+ parent,
251
+ ...index.name !== undefined ? { name: index.name } : {},
252
+ columns: index.columns,
253
+ ...index.unique === true ? { unique: true } : {},
254
+ provenance
255
+ }))
256
+ ];
257
+ }
258
+ };
259
+ }
260
+ function bodyText(body) {
261
+ return typeof body === "string" ? body : body.text;
262
+ }
263
+ function assertPeers(peers) {
264
+ if (!Array.isArray(peers)) {
265
+ throw new OkmError2("OKM1020", "schema({ views }) must be a list.", {
266
+ fix: { summary: "Pass an array to schema({ views })." }
267
+ });
268
+ }
269
+ }
270
+ function assertNoTable(built, name) {
271
+ if (!Array.isArray(built))
272
+ return;
273
+ for (const object of built) {
274
+ if (!isRecord(object) || object.kind !== "table" || !isRecord(object.identity))
275
+ continue;
276
+ if (object.identity.name === name) {
277
+ throw new OkmError2("OKM1023", `View ${name} uses the name of a table.`, {
278
+ fix: { summary: "Give the view a name that no table uses." }
279
+ });
280
+ }
281
+ }
282
+ }
283
+ function isRecord(value) {
284
+ return typeof value === "object" && value !== null;
285
+ }
286
+ export {
287
+ materializedView,
288
+ view
289
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Verifies declared views on a scratch schema.
3
+ *
4
+ * Domain and enum types from the catalog are created there, then the tables,
5
+ * then each view. `pg_get_viewdef` replaces the author query and `pg_depend`
6
+ * supplies column edges (D117).
7
+ * The scratch schema is dropped before this returns. Plans never use it.
8
+ */
9
+ import type { Catalog } from "../../../contracts/catalog/types.js";
10
+ import type { CatalogQuery } from "../introspect.js";
11
+ /**
12
+ * Reads dependencies and reprints for every view in `source`.
13
+ *
14
+ * The returned catalog matches what introspection prints for those views
15
+ * when the same query is applied. A declared column list that disagrees
16
+ * with the database is OKM1020.
17
+ *
18
+ * @param runner - Database connection. Statements share its session
19
+ * @param source - Catalog whose views are still author SQL
20
+ * @returns A catalog whose views carry the reprint and `pg_depend` edges
21
+ */
22
+ export declare function sealViews(runner: CatalogQuery, source: Catalog): Promise<Catalog>;
@@ -5,11 +5,11 @@
5
5
  * `where` without importing the client types. `runtime/types.ts` re-exports
6
6
  * the two names callers use.
7
7
  */
8
- import type { Between, Compare, Containment, Eq, HasAnyKey, HasKey, InList, Matches, Not, NotIn, Overlaps, Path, Pattern, RawPattern } from "./operators.js";
8
+ import type { Between, Compare, Containment, Eq, HasAnyKey, HasKey, InList, Matches, Trigram, Not, NotIn, Overlaps, Path, Pattern, RawPattern } from "./operators.js";
9
9
  import type { Range } from "./range.js";
10
10
  export type IsObject<V> = V extends object ? V extends readonly unknown[] ? false : null extends V ? false : true : false;
11
11
  /** Text patterns, including substring `contains`. `matches` is for tsvector, which is also `string`. */
12
- type TextOps = Pattern<"startsWith"> | Pattern<"contains"> | Pattern<"endsWith"> | RawPattern<"like"> | RawPattern<"ilike"> | Matches;
12
+ type TextOps = Pattern<"startsWith"> | Pattern<"contains"> | Pattern<"endsWith"> | RawPattern<"like"> | RawPattern<"ilike"> | Matches | Trigram<"similar"> | Trigram<"wordSimilar">;
13
13
  /** Array, jsonb, or range containment, plus overlap where that operator fits. */
14
14
  type StructuredOps<V> = Containment<"contains" | "containedBy", V> | Overlaps<V>;
15
15
  /** A JSON fragment. Containment matches part of a document, not the whole column type. */
@@ -0,0 +1,17 @@
1
+ /**
2
+ * One in-flight catalog load per key.
3
+ *
4
+ * Concurrent `connect()` calls in one process share the parse. A failure is
5
+ * dropped so the next call can try again. Success stays for the process.
6
+ */
7
+ /**
8
+ * Runs `load` once for `key`.
9
+ *
10
+ * The map is filled before `load` starts, so a second call on the same tick
11
+ * waits on the first promise instead of parsing again.
12
+ *
13
+ * @param key - Directory and hash, or the artifact hash
14
+ * @param load - Reads and trusts the catalog
15
+ * @returns The shared load
16
+ */
17
+ export declare function loadOnce(key: string, load: () => Promise<void>): Promise<void>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Words shared by `connect()` and `okm migrate status` (D197).
3
+ *
4
+ * The startup check and the status command name the same situations.
5
+ * A failed step adds the resume hint status prints.
6
+ */
7
+ /** The database and the app record the same catalog. */
8
+ export declare const CURRENT = "current";
9
+ /** Pending migrations are expand only. */
10
+ export declare const BEHIND_EXPAND = "behind by expand";
11
+ /** A pending migration is not expand. */
12
+ export declare const BEHIND_CONTRACT = "behind by contract";
13
+ /** The database has expand migrations the app does not. */
14
+ export declare const AHEAD_EXPAND = "ahead by expand";
15
+ /** The database has a contract migration the app does not. */
16
+ export declare const AHEAD_CONTRACT = "ahead by contract";
17
+ /**
18
+ * Status text for a migration that stopped mid-way.
19
+ *
20
+ * @param step - Zero-based step that has no `okm_history` row
21
+ * @returns The state, including the resume hint
22
+ */
23
+ export declare function failedAt(step: number): string;
@@ -2,25 +2,41 @@
2
2
  * Compatibility check used only when the catalog hashes differ.
3
3
  *
4
4
  * Ahead by expand is compatible. Ahead by a contract step, or behind, fails
5
- * closed (OKM1520).
5
+ * closed (OKM1520). The class is the value stored in `okm_history`.
6
6
  */
7
7
  import type { DriverPool, ExecuteOptions } from "../contracts/driver.js";
8
8
  /** One applied migration, in history order. */
9
9
  export type DriftMigration = {
10
10
  readonly id: string;
11
11
  readonly catalogHash: string;
12
- /** True when every step is expand. */
12
+ /** True when every stored step class is `expand`. */
13
13
  readonly expand: boolean;
14
14
  };
15
+ /** How far the database and the app have diverged. */
16
+ export type DriftVerdict = {
17
+ readonly state: "ok";
18
+ } | {
19
+ readonly state: "ahead by contract";
20
+ readonly migrationId: string;
21
+ } | {
22
+ readonly state: "behind by expand";
23
+ readonly migrationId: string;
24
+ } | {
25
+ readonly state: "behind by contract";
26
+ readonly migrationId: string;
27
+ } | {
28
+ readonly state: "behind";
29
+ readonly migrationId: string;
30
+ };
15
31
  /**
16
- * Classifies a hash mismatch.
32
+ * Classifies a hash mismatch from stored history classes.
17
33
  *
18
34
  * @param codeHash - Hash of the code's catalog
19
35
  * @param recordedHash - Hash stored in `okm_meta`
20
36
  * @param migrations - Applied migrations, oldest first
21
37
  * @returns `ok` when the database is ahead by expand only
22
38
  */
23
- export declare function driftVerdict(codeHash: string, recordedHash: string, migrations: readonly DriftMigration[]): "ok" | "behind" | "contract";
39
+ export declare function driftVerdict(codeHash: string, recordedHash: string, migrations: readonly DriftMigration[]): DriftVerdict;
24
40
  /**
25
41
  * Reads history and throws OKM1520 when the database is not expand-ahead.
26
42
  *
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Startup catalog check.
3
3
  *
4
- * The fast path compares two hashes. History, catalog JSON, and
5
- * {@link loadTrustedCatalog} load only when those hashes differ.
4
+ * The fast path compares two hashes and returns. History and
5
+ * `.okm/catalog.json` load only when those hashes differ. A hash read from
6
+ * `catalog.hash` requires that JSON file; a missing file or a hash that does
7
+ * not match is OKM1027. Concurrent loads share one parse.
6
8
  */
7
9
  import type { DriverPool, ExecuteOptions } from "../contracts/driver.js";
8
10
  import type { QuerySchema } from "../dialects/pg/model.js";
@@ -3,15 +3,15 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open5
6
- } from "../../shared/ytg3xf9p.js";
6
+ } from "../../shared/sf8m6tfy.js";
7
7
  import {
8
8
  BUNSQL_CAPABILITIES,
9
9
  readCapability2,
10
10
  hasCapability2
11
- } from "../../shared/9k6vetvf.js";
11
+ } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/mxmcdsh5.js";
14
+ } from "../../shared/kkzwp3d2.js";
15
15
 
16
16
  // src/runtime/pg/bun.ts
17
17
  function connect(target, options) {
@@ -3,15 +3,15 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open4
6
- } from "../../shared/38crxbgn.js";
6
+ } from "../../shared/6p5k20sm.js";
7
7
  import {
8
8
  NODE_POSTGRES_CAPABILITIES,
9
9
  readCapability2,
10
10
  hasCapability2
11
- } from "../../shared/9k6vetvf.js";
11
+ } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/mxmcdsh5.js";
14
+ } from "../../shared/kkzwp3d2.js";
15
15
 
16
16
  // src/runtime/pg/pg.ts
17
17
  function connect(target, options) {
@@ -3,15 +3,15 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open3
6
- } from "../../shared/0qkxbm1f.js";
6
+ } from "../../shared/fa9vt245.js";
7
7
  import {
8
8
  PGLITE_CAPABILITIES,
9
9
  readCapability2,
10
10
  hasCapability2
11
- } from "../../shared/9k6vetvf.js";
11
+ } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/mxmcdsh5.js";
14
+ } from "../../shared/kkzwp3d2.js";
15
15
 
16
16
  // src/runtime/pg/pglite.ts
17
17
  async function connect(target, options) {
@@ -3,15 +3,15 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open2
6
- } from "../../shared/ecdzej21.js";
6
+ } from "../../shared/wqem4jed.js";
7
7
  import {
8
8
  POSTGRESJS_CAPABILITIES,
9
9
  readCapability2,
10
10
  hasCapability2
11
- } from "../../shared/9k6vetvf.js";
11
+ } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/mxmcdsh5.js";
14
+ } from "../../shared/kkzwp3d2.js";
15
15
 
16
16
  // src/runtime/pg/postgresjs.ts
17
17
  function connect(target, options) {
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  uuid2
3
- } from "../../shared/ps43134h.js";
3
+ } from "../../shared/dyj86cmj.js";
4
4
  import {
5
5
  ColumnBuilder2,
6
6
  retarget
7
- } from "../../shared/dd4f8mnx.js";
7
+ } from "../../shared/c9z5g0fm.js";
8
8
  import {
9
9
  definition,
10
10
  unavailable
11
- } from "../../shared/xatfg0gn.js";
11
+ } from "../../shared/tmbdywx0.js";
12
12
  import {
13
13
  quote
14
14
  } from "../../shared/xrfnq5k8.js";
@@ -76,6 +76,9 @@ function columnTenancy(input) {
76
76
  rules(table, source, scope) {
77
77
  return tenancyRules(table, source, scope, tenants, globals, exemptions);
78
78
  },
79
+ scopeView(name) {
80
+ tenants.add(name);
81
+ },
79
82
  hook(target, ctx) {
80
83
  if (ctx.tables === undefined || ctx.names === undefined || ctx.open === undefined || ctx.scoped === undefined) {
81
84
  return;
@@ -51,13 +51,14 @@ export declare function trait<const TFields extends Readonly<Record<string, obje
51
51
  readonly touch?: readonly (keyof TFields & string)[];
52
52
  readonly sealed?: readonly (keyof TFields & string)[];
53
53
  apply(model: TraitModel, ctx: TraitContext): void;
54
+ contribute(peers: unknown, built: unknown): readonly unknown[];
54
55
  } & ([keyof TPresets] extends [never] ? unknown : {
55
56
  readonly presets: TPresets;
56
57
  });
57
58
  /**
58
59
  * Options for {@link timestamps}.
59
60
  *
60
- * `enforce: "trigger"` is the database trigger. It arrives in 0.3.
61
+ * `enforce: "trigger"` adds a before-update trigger and its function.
61
62
  */
62
63
  export type TimestampsOptions = {
63
64
  readonly enforce?: "trigger";
@@ -68,8 +69,10 @@ export type TimestampsOptions = {
68
69
  * Both are `timestamptz not null default now()`, and input cannot set either
69
70
  * of them. Insert leaves them to the default, so one statement stamps both
70
71
  * with the same time. Update sets `updatedAt` to `now()` and leaves `createdAt`.
72
+ * `{ enforce: "trigger" }` also writes `updatedAt` in the database, including
73
+ * for writers that bypass the client.
71
74
  *
72
- * @param options - Pass `{ enforce: "trigger" }` only when the trigger ships
75
+ * @param options - Pass `{ enforce: "trigger" }` for the database trigger
73
76
  * @returns The timestamps trait
74
77
  */
75
78
  export declare function timestamps(options?: TimestampsOptions): {
@@ -81,6 +84,7 @@ export declare function timestamps(options?: TimestampsOptions): {
81
84
  readonly touch?: readonly ("createdAt" | "updatedAt")[];
82
85
  readonly sealed?: readonly ("createdAt" | "updatedAt")[];
83
86
  apply(model: TraitModel, ctx: TraitContext): void;
87
+ contribute(peers: unknown, built: unknown): readonly unknown[];
84
88
  };
85
89
  /**
86
90
  * Options for {@link archivable}.
@@ -111,6 +115,7 @@ export declare function archivable(options?: ArchivableOptions): {
111
115
  };
112
116
  touch?: readonly ("archiveId" | "archivedAt")[];
113
117
  sealed?: readonly ("archiveId" | "archivedAt")[];
118
+ contribute(peers: unknown, built: unknown): readonly unknown[];
114
119
  "~archive": true;
115
120
  apply(model: TraitModel, ctx: TraitContext): void;
116
121
  /**