okmodel 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +263 -23
  2. package/dist/contracts/catalog/document.d.ts +26 -2
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/ext/citext.js +1 -1
  5. package/dist/dialects/pg/ext/index.js +1 -1
  6. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  7. package/dist/dialects/pg/index.js +3 -3
  8. package/dist/dialects/pg/introspect.d.ts +6 -0
  9. package/dist/dialects/pg/role/sql.d.ts +21 -4
  10. package/dist/dialects/pg/schema.d.ts +15 -6
  11. package/dist/dialects/pg/table.d.ts +17 -1
  12. package/dist/dialects/pg/view/index.d.ts +20 -6
  13. package/dist/dialects/pg/view/index.js +1 -1
  14. package/dist/dialects/pg/view/scratch.d.ts +2 -1
  15. package/dist/runtime/catalog-cache.d.ts +17 -0
  16. package/dist/runtime/compat-words.d.ts +23 -0
  17. package/dist/runtime/drift-detail.d.ts +20 -4
  18. package/dist/runtime/drift.d.ts +4 -2
  19. package/dist/runtime/pg/bun.d.ts +12 -7
  20. package/dist/runtime/pg/bun.js +5 -2
  21. package/dist/runtime/pg/pg.d.ts +12 -6
  22. package/dist/runtime/pg/pg.js +5 -2
  23. package/dist/runtime/pg/pglite.d.ts +11 -3
  24. package/dist/runtime/pg/pglite.js +5 -2
  25. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  26. package/dist/runtime/pg/postgresjs.js +5 -2
  27. package/dist/runtime/plan.d.ts +3 -1
  28. package/dist/runtime/tenancy/index.js +1 -1
  29. package/dist/runtime/topology.d.ts +191 -0
  30. package/dist/runtime/traits/index.js +7 -6
  31. package/dist/runtime/types.d.ts +59 -2
  32. package/dist/runtime/validate/index.js +2 -2
  33. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  34. package/dist/shared/{ty3v3hfk.js → 47xzb9fc.js} +2 -2
  35. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  36. package/dist/shared/{qybh48nd.js → 5fqx9yh4.js} +6 -2
  37. package/dist/shared/5mtf6cj3.js +94 -0
  38. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  39. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  40. package/dist/shared/{t616ynfn.js → 8bwbwy8h.js} +71 -13
  41. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  42. package/dist/shared/{wekze34d.js → 8xsbagad.js} +74 -2
  43. package/dist/shared/{bryqk9k9.js → 922qbgnw.js} +3 -3
  44. package/dist/shared/{meck53jv.js → 990a291j.js} +6 -5
  45. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  46. package/dist/shared/9yn6a2vd.js +11 -0
  47. package/dist/shared/{2ssqrv66.js → ayz64vzj.js} +1037 -181
  48. package/dist/shared/{k34e0xf2.js → bt9sfq0z.js} +16 -14
  49. package/dist/shared/{scwxrpx2.js → bzsjtd7c.js} +84 -15
  50. package/dist/shared/{m9tv7xn7.js → ckjbyrt9.js} +4 -4
  51. package/dist/shared/{ydaz1sv3.js → dxjp14rk.js} +10 -2
  52. package/dist/shared/{49ypnfwt.js → fancm5pt.js} +24 -229
  53. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  54. package/dist/shared/{1tfdfvhh.js → fyqkk1m0.js} +6 -4
  55. package/dist/shared/g516zdsb.js +905 -0
  56. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  57. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  58. package/dist/shared/{p24x2334.js → h4nqvjkp.js} +5 -3
  59. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  60. package/dist/shared/htnaj77v.js +17 -0
  61. package/dist/shared/jdgqqx31.js +72 -0
  62. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  63. package/dist/shared/{m78s7898.js → k7cc9nzk.js} +24 -7
  64. package/dist/shared/kdkxh3kb.js +758 -0
  65. package/dist/shared/nkc9sfqb.js +1161 -0
  66. package/dist/shared/{xva8v7qk.js → p7pfx624.js} +5 -5
  67. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  68. package/dist/shared/{ne02ng5c.js → qy9rdq0g.js} +9 -5
  69. package/dist/shared/{3ekj9x92.js → r6pj4fq2.js} +5 -3
  70. package/dist/shared/r73pt594.js +426 -0
  71. package/dist/shared/rasvvzgz.js +21 -0
  72. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  73. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  74. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  75. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  76. package/dist/shared/wxn4b7pp.js +252 -0
  77. package/dist/shared/xdpj5kkm.js +2203 -0
  78. package/dist/shared/xhh1srw3.js +589 -0
  79. package/dist/shared/{mt5wswjz.js → xsq7v6wp.js} +2 -2
  80. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  81. package/dist/shared/ykv7wh5b.js +14 -0
  82. package/dist/shared/{fptpmvxp.js → z2rnngj4.js} +26 -21
  83. package/dist/tooling/cli.js +46 -17
  84. package/dist/tooling/migrate/apply.d.ts +61 -5
  85. package/dist/tooling/migrate/backfill.d.ts +136 -0
  86. package/dist/tooling/migrate/check.d.ts +65 -0
  87. package/dist/tooling/migrate/classify.d.ts +108 -0
  88. package/dist/tooling/migrate/commands.d.ts +2 -2
  89. package/dist/tooling/migrate/config.d.ts +22 -0
  90. package/dist/tooling/migrate/doctor.d.ts +3 -0
  91. package/dist/tooling/migrate/drift.d.ts +18 -2
  92. package/dist/tooling/migrate/estimate.d.ts +50 -0
  93. package/dist/tooling/migrate/extensions.d.ts +2 -0
  94. package/dist/tooling/migrate/files.d.ts +19 -0
  95. package/dist/tooling/migrate/index.d.ts +3 -2
  96. package/dist/tooling/migrate/index.js +69 -1
  97. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  98. package/dist/tooling/migrate/lint.d.ts +71 -0
  99. package/dist/tooling/migrate/managed.d.ts +1 -1
  100. package/dist/tooling/migrate/plan.d.ts +64 -9
  101. package/dist/tooling/migrate/project.d.ts +42 -7
  102. package/dist/tooling/migrate/provision.d.ts +18 -0
  103. package/dist/tooling/migrate/reference.d.ts +40 -0
  104. package/dist/tooling/migrate/runner.d.ts +108 -0
  105. package/dist/tooling/migrate/safe.d.ts +77 -0
  106. package/dist/tooling/migrate/seed.d.ts +20 -0
  107. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  108. package/dist/tooling/migrate/status.d.ts +25 -6
  109. package/dist/tooling/testing/factories.d.ts +117 -0
  110. package/dist/tooling/testing/facts.d.ts +75 -0
  111. package/dist/tooling/testing/index.d.ts +89 -0
  112. package/dist/tooling/testing/index.js +8 -0
  113. package/dist/tooling/testing/isolation.d.ts +30 -0
  114. package/dist/tooling/testing/random.d.ts +40 -0
  115. package/dist/tooling/testing/record.d.ts +38 -0
  116. package/package.json +6 -2
  117. package/dist/shared/5nmdenb6.js +0 -1229
  118. package/dist/shared/dbjej1tw.js +0 -456
  119. package/dist/shared/g4f5kcx9.js +0 -37
  120. package/dist/shared/qbxzvwwc.js +0 -88
  121. package/dist/shared/zwmqxb0g.js +0 -79
  122. /package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  123. /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
@@ -0,0 +1,252 @@
1
+ import {
2
+ cellText,
3
+ VIEW_DEPENDENCIES,
4
+ viewDependencyEdges,
5
+ PUBLIC,
6
+ introspectSchema
7
+ } from "./fancm5pt.js";
8
+ import {
9
+ planMigration
10
+ } from "./ayz64vzj.js";
11
+ import {
12
+ quoteIdent,
13
+ createObjectSql
14
+ } from "./8xsbagad.js";
15
+ import {
16
+ normaliseViewQuery,
17
+ viewObject,
18
+ materializedViewObject
19
+ } from "./y005b761.js";
20
+ import {
21
+ catalog
22
+ } from "./g7ez901n.js";
23
+ import {
24
+ OkmError2
25
+ } from "./52szzakm.js";
26
+
27
+ // src/tooling/migrate/drift.ts
28
+ import postgres from "postgres";
29
+
30
+ // src/dialects/pg/view/scratch.ts
31
+ var SCRATCH = "okm_view_seal";
32
+ var VIEW_SHAPE = `
33
+ select c.relname as name, c.relkind as kind, pg_get_viewdef(c.oid, true) as query,
34
+ (
35
+ select coalesce(string_agg(
36
+ a.attname || '|' || format_type(a.atttypid, a.atttypmod),
37
+ E'\\n' order by a.attnum
38
+ ), '')
39
+ from pg_attribute a
40
+ where a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
41
+ ) as columns
42
+ from pg_class c
43
+ join pg_namespace n on n.oid = c.relnamespace
44
+ where n.nspname = $1 and c.relkind in ('v', 'm')
45
+ `;
46
+ async function sealViews(runner, source) {
47
+ const views = source.objects.filter((object) => object.kind === "view" || object.kind === "materializedView");
48
+ if (views.length === 0)
49
+ return source;
50
+ await dropScratch(runner);
51
+ await runner.query(`create schema ${quoteIdent(SCRATCH)}`);
52
+ try {
53
+ await runner.query(`set search_path to ${quoteIdent(SCRATCH)}`);
54
+ await createObjects(runner, source, "type");
55
+ for (const object of source.objects) {
56
+ if (object.kind !== "table")
57
+ continue;
58
+ await runner.query(stubTable(object, source));
59
+ }
60
+ await createObjects(runner, source, "function");
61
+ await createViews(runner, views);
62
+ const shapes = await runner.query(VIEW_SHAPE, [SCRATCH]);
63
+ const deps = await runner.query(VIEW_DEPENDENCIES, [SCRATCH]);
64
+ const edges = viewDependencyEdges(deps, PUBLIC, source.objects);
65
+ const byName = new Map(shapes.map((row) => [cellText(row.name), row]));
66
+ const next = source.objects.map((object) => {
67
+ if (object.kind !== "view" && object.kind !== "materializedView")
68
+ return object;
69
+ const row = byName.get(object.identity.name);
70
+ if (row === undefined) {
71
+ throw new OkmError2("OKM1020", `View ${object.identity.name} was not created in the scratch schema.`, {
72
+ fix: { summary: "The query has to run against the tables in this schema." }
73
+ });
74
+ }
75
+ const columns = readColumns(object, cellText(row.columns));
76
+ const query = normaliseViewQuery(cellText(row.query));
77
+ const dependencies = edges.get(object.identity.name) ?? [];
78
+ if (object.kind === "view") {
79
+ return viewObject({
80
+ namespace: object.identity.namespace,
81
+ name: object.identity.name,
82
+ columns,
83
+ query,
84
+ owner: object.owner,
85
+ provenance: object.provenance,
86
+ dependencies
87
+ });
88
+ }
89
+ return materializedViewObject({
90
+ namespace: object.identity.namespace,
91
+ name: object.identity.name,
92
+ columns,
93
+ query,
94
+ owner: object.owner,
95
+ provenance: object.provenance,
96
+ dependencies,
97
+ ...object.definition.refresh !== undefined ? { refresh: object.definition.refresh } : {}
98
+ });
99
+ });
100
+ return catalog(next);
101
+ } finally {
102
+ await runner.query("set search_path to public");
103
+ await dropScratch(runner);
104
+ }
105
+ }
106
+ async function createObjects(runner, source, kind) {
107
+ for (const object of source.objects) {
108
+ if (object.kind !== kind)
109
+ continue;
110
+ const statement = createObjectSql(object, SCRATCH);
111
+ if (statement !== undefined)
112
+ await runner.query(statement);
113
+ }
114
+ }
115
+ async function createViews(runner, views) {
116
+ const pending = [...views];
117
+ let last = "";
118
+ for (let pass = 0;pass < views.length && pending.length > 0; pass += 1) {
119
+ const failed = [];
120
+ let created = 0;
121
+ for (const object of pending) {
122
+ const sql = object.kind === "materializedView" ? `create materialized view ${quoteIdent(object.identity.name)} as ${object.definition.query} with no data` : `create view ${quoteIdent(object.identity.name)} as ${object.definition.query}`;
123
+ try {
124
+ await runner.query(sql);
125
+ created += 1;
126
+ } catch (error) {
127
+ last = error instanceof Error ? error.message : String(error);
128
+ failed.push(object);
129
+ }
130
+ }
131
+ if (created === 0) {
132
+ throw new OkmError2("OKM1020", `View ${failed[0]?.identity.name ?? ""} could not be created. ${last}`, {
133
+ fix: { summary: "The query has to run against the tables in this schema." }
134
+ });
135
+ }
136
+ pending.length = 0;
137
+ pending.push(...failed);
138
+ }
139
+ }
140
+ function stubTable(object, source) {
141
+ const columns = source.objects.filter((item) => item.kind === "column" && item.identity.parent.name === object.identity.name);
142
+ const lines = columns.map((column) => `${quoteIdent(column.identity.name)} ${column.definition.dataType}`);
143
+ return `create table ${quoteIdent(object.identity.name)} (${lines.join(", ")})`;
144
+ }
145
+ function readColumns(object, printed) {
146
+ const live = printed.length === 0 ? [] : printed.split(`
147
+ `);
148
+ const declared = object.definition.columns;
149
+ if (live.length !== declared.length) {
150
+ throw mismatch(object.identity.name, declared.map((column) => column.name).join(", "), printed);
151
+ }
152
+ return declared.map((column, index) => {
153
+ const line = live[index] ?? "";
154
+ const splitAt = line.indexOf("|");
155
+ const name = splitAt < 0 ? line : line.slice(0, splitAt);
156
+ const type = splitAt < 0 ? "" : line.slice(splitAt + 1);
157
+ if (name !== column.name || type !== column.dataType) {
158
+ throw mismatch(object.identity.name, `${column.name}:${column.dataType}`, live[index] ?? "");
159
+ }
160
+ return column;
161
+ });
162
+ }
163
+ function mismatch(view, declared, live) {
164
+ return new OkmError2("OKM1020", `View ${view} declares ${declared} and the database has ${live}.`, { fix: { summary: "Declare the columns the query returns, in that order, with those types." } });
165
+ }
166
+ async function dropScratch(runner) {
167
+ const present = await runner.query(`select 1 as ok from pg_namespace where nspname = $1`, [
168
+ SCRATCH
169
+ ]);
170
+ if (present.length === 0)
171
+ return;
172
+ const rows = await runner.query(`
173
+ select c.relname as name, c.relkind as kind
174
+ from pg_class c
175
+ join pg_namespace n on n.oid = c.relnamespace
176
+ where n.nspname = $1 and c.relkind in ('v', 'm', 'r')
177
+ `, [SCRATCH]);
178
+ const views = rows.filter((row) => cellText(row.kind) === "v" || cellText(row.kind) === "m");
179
+ for (let pass = 0;pass < Math.max(views.length, 1); pass += 1) {
180
+ for (const row of views) {
181
+ const kind = cellText(row.kind) === "m" ? "materialized view" : "view";
182
+ try {
183
+ await runner.query(`drop ${kind} if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
184
+ } catch {}
185
+ }
186
+ }
187
+ for (const row of rows) {
188
+ if (cellText(row.kind) !== "r")
189
+ continue;
190
+ await runner.query(`drop table if exists ${quoteIdent(SCRATCH)}.${quoteIdent(cellText(row.name))}`);
191
+ }
192
+ await runner.query(`drop schema if exists ${quoteIdent(SCRATCH)} cascade`);
193
+ }
194
+
195
+ // src/tooling/migrate/drift.ts
196
+ async function assertAuthorDrift2(url, author, roles) {
197
+ const sql = postgres(url, {
198
+ max: 1,
199
+ connect_timeout: 5,
200
+ idle_timeout: 1,
201
+ onnotice: () => {}
202
+ });
203
+ try {
204
+ const present = await sql`
205
+ select to_regclass('public.okm_meta')::text as reg
206
+ `;
207
+ if (present[0]?.reg == null)
208
+ return;
209
+ const runner = catalogQuery2(sql);
210
+ const authorOnServer = await sealViews(runner, author);
211
+ const live = await introspectSchema(runner, "public", "public", managedRoleOptions2(roles));
212
+ const plan = planMigration({ before: live, after: authorOnServer, name: "check" });
213
+ if (plan.steps.length === 0)
214
+ return;
215
+ throw new OkmError2("OKM1520", `The database differs from the schema.
216
+ ${plan.steps.map((step) => step.sql).join(`
217
+ `)}`, {
218
+ fix: {
219
+ summary: "Change the schema or the database so okm check reports no difference."
220
+ }
221
+ });
222
+ } finally {
223
+ await sql.end({ timeout: 5 });
224
+ }
225
+ }
226
+ function catalogQuery2(sql) {
227
+ return {
228
+ async query(text, params) {
229
+ const rows = await sql.unsafe(text, params === undefined ? undefined : [...params]);
230
+ return rows.map((row) => {
231
+ const copy = {};
232
+ for (const [key, value] of Object.entries(row))
233
+ copy[key] = value;
234
+ return copy;
235
+ });
236
+ }
237
+ };
238
+ }
239
+ function managedRoleOptions2(roles) {
240
+ const names = managedRoleNames(roles);
241
+ return names === undefined ? undefined : { managedRoles: names };
242
+ }
243
+ function managedRoleNames(roles) {
244
+ if (roles === undefined)
245
+ return;
246
+ const names = new Set([roles.migration, roles.app]);
247
+ for (const role of roles.managed ?? [])
248
+ names.add(role.name);
249
+ return [...names];
250
+ }
251
+
252
+ export { sealViews, assertAuthorDrift2, catalogQuery2, managedRoleOptions2 };