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.
- package/README.md +882 -48
- package/dist/contracts/catalog/document.d.ts +6 -2
- package/dist/contracts/catalog/enum.d.ts +31 -4
- package/dist/contracts/catalog/extension.d.ts +61 -0
- package/dist/contracts/catalog/privilege.d.ts +64 -0
- package/dist/contracts/catalog/routine.d.ts +56 -0
- package/dist/contracts/catalog/types.d.ts +164 -10
- package/dist/contracts/catalog/view.d.ts +73 -0
- package/dist/dialects/pg/column.d.ts +3 -1
- package/dist/dialects/pg/ddl.d.ts +65 -1
- package/dist/dialects/pg/enum.d.ts +10 -8
- package/dist/dialects/pg/ext/citext.d.ts +14 -0
- package/dist/dialects/pg/ext/citext.js +11 -0
- package/dist/dialects/pg/ext/index.d.ts +52 -0
- package/dist/dialects/pg/ext/index.js +6 -0
- package/dist/dialects/pg/ext/pg-trgm.d.ts +53 -0
- package/dist/dialects/pg/ext/pg-trgm.js +42 -0
- package/dist/dialects/pg/fn/index.d.ts +107 -0
- package/dist/dialects/pg/fn/index.js +175 -0
- package/dist/dialects/pg/fn/touch.d.ts +14 -0
- package/dist/dialects/pg/index.js +104 -19
- package/dist/dialects/pg/introspect.d.ts +10 -1
- package/dist/dialects/pg/operators.d.ts +8 -1
- package/dist/dialects/pg/role/check.d.ts +45 -0
- package/dist/dialects/pg/role/index.d.ts +50 -0
- package/dist/dialects/pg/role/sql.d.ts +51 -0
- package/dist/dialects/pg/table.d.ts +24 -1
- package/dist/dialects/pg/tenancy.d.ts +9 -0
- package/dist/dialects/pg/view/client.d.ts +30 -0
- package/dist/dialects/pg/view/depend.d.ts +35 -0
- package/dist/dialects/pg/view/index.d.ts +114 -0
- package/dist/dialects/pg/view/index.js +289 -0
- package/dist/dialects/pg/view/scratch.d.ts +22 -0
- package/dist/dialects/pg/where.d.ts +2 -2
- package/dist/runtime/catalog-cache.d.ts +17 -0
- package/dist/runtime/compat-words.d.ts +23 -0
- package/dist/runtime/drift-detail.d.ts +20 -4
- package/dist/runtime/drift.d.ts +4 -2
- package/dist/runtime/pg/bun.js +3 -3
- package/dist/runtime/pg/pg.js +3 -3
- package/dist/runtime/pg/pglite.js +3 -3
- package/dist/runtime/pg/postgresjs.js +3 -3
- package/dist/runtime/tenancy/index.js +6 -3
- package/dist/runtime/traits/index.d.ts +7 -2
- package/dist/runtime/traits/index.js +151 -13
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +4 -4
- package/dist/shared/{xkz8g6by.js → 1g0drakv.js} +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{7mht49yd.js → 3p4b1fg0.js} +57 -18
- package/dist/shared/41kv2xdm.js +318 -0
- package/dist/shared/{97g2jnrw.js → 4ef7mmqf.js} +3 -3
- package/dist/shared/{sacqha1y.js → 4m9fn52z.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{1dp5knt7.js → 5vdm4sgp.js} +2 -2
- package/dist/shared/{a1gdp7j9.js → 6693j9rs.js} +1 -1
- package/dist/shared/{38crxbgn.js → 6p5k20sm.js} +3 -3
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/84ptn35q.js +71 -0
- package/dist/shared/{d2whjz4k.js → 8jb4ymh2.js} +581 -28
- package/dist/shared/8ktwd3rh.js +97 -0
- package/dist/shared/94ez8kzh.js +19 -0
- package/dist/shared/{3a5ne750.js → 9m80mkac.js} +2 -2
- package/dist/shared/9x25fxhc.js +150 -0
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{4jzzghr6.js → awrsqrn7.js} +6 -6
- package/dist/shared/{4b9da9d7.js → axhk06q1.js} +19 -1
- package/dist/shared/{d04j53v8.js → c5g81ngb.js} +82 -14
- package/dist/shared/{n47rd821.js → c8aghh05.js} +1 -1
- package/dist/shared/{dd4f8mnx.js → c9z5g0fm.js} +4 -2
- package/dist/shared/{ps43134h.js → dyj86cmj.js} +4 -4
- package/dist/shared/{0qkxbm1f.js → fa9vt245.js} +1 -1
- package/dist/shared/{qa5dsc7z.js → fb00y8fp.js} +3 -3
- package/dist/shared/{qxa0yb9j.js → g7ez901n.js} +30 -1
- package/dist/shared/{py3nh7jy.js → gdh9mrtp.js} +2 -2
- package/dist/shared/{yw5c610b.js → gjthds9j.js} +2 -2
- package/dist/shared/hcmdtwgh.js +102 -0
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{v18jva5b.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{x9v1qgeb.js → jsd07prz.js} +3 -3
- package/dist/shared/{g9f0zqce.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{mxmcdsh5.js → kkzwp3d2.js} +9 -9
- package/dist/shared/{g1b5zzj9.js → n0dgs0pb.js} +1 -1
- package/dist/shared/n5y9zyj1.js +2431 -0
- package/dist/shared/{zt8y0wzb.js → n7mcc1gk.js} +1 -1
- package/dist/shared/{ge03bfca.js → nxb42q7h.js} +2 -2
- package/dist/shared/q6zsyh31.js +2192 -0
- package/dist/shared/{sfh4pk75.js → q795afwa.js} +26 -21
- package/dist/shared/{10tjenmd.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{ytg3xf9p.js → sf8m6tfy.js} +3 -3
- package/dist/shared/{zd93g3rq.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{xatfg0gn.js → tmbdywx0.js} +4 -19
- package/dist/shared/tny2t3sm.js +100 -0
- package/dist/shared/{e7a268fy.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{ecdzej21.js → wqem4jed.js} +7 -7
- package/dist/shared/xghkt7a9.js +1144 -0
- package/dist/shared/y005b761.js +112 -0
- package/dist/shared/ycatkxmq.js +84 -0
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{eaxwrgby.js → yjw02enp.js} +6 -3
- package/dist/shared/{mrq77sgw.js → zg24x0hm.js} +1 -1
- package/dist/shared/{cstgkzdx.js → zvbjc949.js} +1 -1
- package/dist/tooling/cli.js +66 -13
- package/dist/tooling/migrate/apply.d.ts +75 -5
- package/dist/tooling/migrate/backfill.d.ts +136 -0
- package/dist/tooling/migrate/check.d.ts +65 -0
- package/dist/tooling/migrate/classify.d.ts +108 -0
- package/dist/tooling/migrate/commands.d.ts +2 -1
- package/dist/tooling/migrate/config.d.ts +30 -0
- package/dist/tooling/migrate/doctor.d.ts +28 -0
- package/dist/tooling/migrate/drift.d.ts +35 -0
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/ext.d.ts +17 -0
- package/dist/tooling/migrate/extensions.d.ts +53 -0
- package/dist/tooling/migrate/files.d.ts +12 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +67 -1
- package/dist/tooling/migrate/lint-rules.d.ts +51 -0
- package/dist/tooling/migrate/lint.d.ts +71 -0
- package/dist/tooling/migrate/managed.d.ts +17 -0
- package/dist/tooling/migrate/plan.d.ts +78 -10
- package/dist/tooling/migrate/project.d.ts +64 -5
- package/dist/tooling/migrate/provision.d.ts +18 -0
- package/dist/tooling/migrate/reference.d.ts +40 -0
- package/dist/tooling/migrate/runner.d.ts +108 -0
- package/dist/tooling/migrate/safe.d.ts +77 -0
- package/dist/tooling/migrate/seed.d.ts +20 -0
- package/dist/tooling/migrate/snapshot.d.ts +78 -0
- package/dist/tooling/migrate/status.d.ts +25 -6
- package/dist/tooling/migrate/type-name.d.ts +19 -0
- package/dist/tooling/testing/factories.d.ts +117 -0
- package/dist/tooling/testing/facts.d.ts +72 -0
- package/dist/tooling/testing/index.d.ts +89 -0
- package/dist/tooling/testing/index.js +8 -0
- package/dist/tooling/testing/isolation.d.ts +30 -0
- package/dist/tooling/testing/random.d.ts +40 -0
- package/dist/tooling/testing/record.d.ts +38 -0
- package/package.json +26 -2
- package/dist/shared/5z4q2zda.js +0 -2073
- package/dist/shared/eqmtn1bj.js +0 -399
- package/dist/shared/rtnaqre0.js +0 -37
- package/dist/shared/zwmqxb0g.js +0 -79
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /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[]):
|
|
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
|
*
|
package/dist/runtime/drift.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup catalog check.
|
|
3
3
|
*
|
|
4
|
-
* The fast path compares two hashes. History
|
|
5
|
-
*
|
|
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";
|
package/dist/runtime/pg/bun.js
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
} from "../../shared/kyxb62mc.js";
|
|
4
4
|
import {
|
|
5
5
|
open5
|
|
6
|
-
} from "../../shared/
|
|
6
|
+
} from "../../shared/sf8m6tfy.js";
|
|
7
7
|
import {
|
|
8
8
|
BUNSQL_CAPABILITIES,
|
|
9
9
|
readCapability2,
|
|
10
10
|
hasCapability2
|
|
11
|
-
} from "../../shared/
|
|
11
|
+
} from "../../shared/rrstp3kb.js";
|
|
12
12
|
import {
|
|
13
13
|
createClient
|
|
14
|
-
} from "../../shared/
|
|
14
|
+
} from "../../shared/kkzwp3d2.js";
|
|
15
15
|
|
|
16
16
|
// src/runtime/pg/bun.ts
|
|
17
17
|
function connect(target, options) {
|
package/dist/runtime/pg/pg.js
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
} from "../../shared/kyxb62mc.js";
|
|
4
4
|
import {
|
|
5
5
|
open4
|
|
6
|
-
} from "../../shared/
|
|
6
|
+
} from "../../shared/6p5k20sm.js";
|
|
7
7
|
import {
|
|
8
8
|
NODE_POSTGRES_CAPABILITIES,
|
|
9
9
|
readCapability2,
|
|
10
10
|
hasCapability2
|
|
11
|
-
} from "../../shared/
|
|
11
|
+
} from "../../shared/rrstp3kb.js";
|
|
12
12
|
import {
|
|
13
13
|
createClient
|
|
14
|
-
} from "../../shared/
|
|
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/
|
|
6
|
+
} from "../../shared/fa9vt245.js";
|
|
7
7
|
import {
|
|
8
8
|
PGLITE_CAPABILITIES,
|
|
9
9
|
readCapability2,
|
|
10
10
|
hasCapability2
|
|
11
|
-
} from "../../shared/
|
|
11
|
+
} from "../../shared/rrstp3kb.js";
|
|
12
12
|
import {
|
|
13
13
|
createClient
|
|
14
|
-
} from "../../shared/
|
|
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/
|
|
6
|
+
} from "../../shared/wqem4jed.js";
|
|
7
7
|
import {
|
|
8
8
|
POSTGRESJS_CAPABILITIES,
|
|
9
9
|
readCapability2,
|
|
10
10
|
hasCapability2
|
|
11
|
-
} from "../../shared/
|
|
11
|
+
} from "../../shared/rrstp3kb.js";
|
|
12
12
|
import {
|
|
13
13
|
createClient
|
|
14
|
-
} from "../../shared/
|
|
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/
|
|
3
|
+
} from "../../shared/dyj86cmj.js";
|
|
4
4
|
import {
|
|
5
5
|
ColumnBuilder2,
|
|
6
6
|
retarget
|
|
7
|
-
} from "../../shared/
|
|
7
|
+
} from "../../shared/c9z5g0fm.js";
|
|
8
8
|
import {
|
|
9
9
|
definition,
|
|
10
10
|
unavailable
|
|
11
|
-
} from "../../shared/
|
|
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"`
|
|
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" }`
|
|
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
|
/**
|