okmodel 0.5.0 → 0.5.1
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 +9 -6
- package/dist/dialects/pg/filter-parse.d.ts +8 -2
- package/dist/dialects/pg/fn/index.js +8 -0
- package/dist/dialects/pg/index.js +8 -2
- package/dist/dialects/pg/ops/or.d.ts +1 -1
- package/dist/dialects/pg/temporal.d.ts +5 -76
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/runtime/client.d.ts +6 -1
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/pg/bun.js +5 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.js +5 -5
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +13 -0
- package/dist/runtime/tenancy/index.js +1 -1
- package/dist/runtime/traits/index.js +4 -4
- package/dist/runtime/validate/index.js +3 -3
- package/dist/shared/{ckjbyrt9.js → 0t22wwh1.js} +5 -9
- package/dist/shared/{jdgqqx31.js → 2pqx26h8.js} +23 -8
- package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
- package/dist/shared/{9tgmr1gh.js → 32zqc1dw.js} +18 -7
- package/dist/shared/{47xzb9fc.js → 47x8x9ze.js} +1 -1
- package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
- package/dist/shared/{s6gqfn5j.js → 51mvgj8w.js} +27 -3
- package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
- package/dist/shared/{3p4b1fg0.js → 5enne4ma.js} +1 -1
- package/dist/shared/{bzsjtd7c.js → 6mnp99hx.js} +7 -6
- package/dist/shared/{ykv7wh5b.js → 6qd27mx0.js} +2 -2
- package/dist/shared/{h4nqvjkp.js → 6rk2gen5.js} +2 -2
- package/dist/shared/{r73pt594.js → 7zwwyh0v.js} +32 -10
- package/dist/shared/{fa9vt245.js → 87jvvhnt.js} +2 -2
- package/dist/shared/{c8aghh05.js → 8rsy3hxz.js} +7 -1
- package/dist/shared/{6p5k20sm.js → 96qcj21c.js} +3 -3
- package/dist/shared/{xsq7v6wp.js → aj2qxyhj.js} +2 -2
- package/dist/shared/{5vdm4sgp.js → caw8b9d8.js} +4 -4
- package/dist/shared/{xhh1srw3.js → cctw17hj.js} +23 -8
- package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
- package/dist/shared/{tq9afp8d.js → cr74e96e.js} +1 -1
- package/dist/shared/{b066g80h.js → efmy2gcg.js} +2 -2
- package/dist/shared/{gdh9mrtp.js → f5cw5q5r.js} +2 -2
- package/dist/shared/{ayz64vzj.js → ga3sfy93.js} +357 -3
- package/dist/shared/{qgvt8nx1.js → gbtcc2d7.js} +44 -3
- package/dist/shared/{kdkxh3kb.js → gde73es6.js} +5 -7
- package/dist/shared/{bt9sfq0z.js → hf22e0wt.js} +51 -17
- package/dist/shared/{990a291j.js → janx09m5.js} +6 -5
- package/dist/shared/{4ef7mmqf.js → jwtxdj06.js} +5 -5
- package/dist/shared/{sf8m6tfy.js → k8v5v5wj.js} +3 -3
- package/dist/shared/{r6pj4fq2.js → kfjp0gck.js} +2 -2
- package/dist/shared/{wqem4jed.js → krxx77g0.js} +7 -7
- package/dist/shared/{h8ne16fq.js → nkaxvt9q.js} +1 -1
- package/dist/shared/{jsd07prz.js → ntneex2a.js} +1 -1
- package/dist/shared/{nkc9sfqb.js → nxdp2ze3.js} +40 -4
- package/dist/shared/{tbszx8mz.js → pf0d8f77.js} +1 -1
- package/dist/shared/{qy9rdq0g.js → ps7a6h14.js} +6 -6
- package/dist/shared/{rasvvzgz.js → q58y17hy.js} +5 -5
- package/dist/shared/{p7pfx624.js → qm7cdtvr.js} +22 -9
- package/dist/shared/{xdpj5kkm.js → qv2ydck9.js} +149 -209
- package/dist/shared/{k7cc9nzk.js → r2sp8hrc.js} +6 -3
- package/dist/shared/{fb00y8fp.js → r8x5qhd7.js} +5 -5
- package/dist/shared/{fyqkk1m0.js → sdc7r2ww.js} +6 -5
- package/dist/shared/{wxn4b7pp.js → sycnx8gt.js} +42 -5
- package/dist/shared/{dxjp14rk.js → tm5ab8we.js} +2 -2
- package/dist/shared/{g516zdsb.js → v36c5g6p.js} +78 -19
- package/dist/shared/wq9n6pwr.js +139 -0
- package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
- package/dist/shared/{922qbgnw.js → zqrnhwsq.js} +3 -3
- package/dist/tooling/cli.js +68 -21
- package/dist/tooling/migrate/commands.d.ts +1 -2
- package/dist/tooling/migrate/drift.d.ts +1 -1
- package/dist/tooling/migrate/estimate.d.ts +14 -1
- package/dist/tooling/migrate/index.js +8 -7
- package/dist/tooling/migrate/lint.d.ts +3 -3
- package/dist/tooling/migrate/plan.d.ts +7 -3
- package/dist/tooling/migrate/policy.d.ts +11 -0
- package/dist/tooling/migrate/sql-lex.d.ts +22 -0
- package/dist/tooling/testing/factories.d.ts +10 -0
- package/dist/tooling/testing/index.js +1 -1
- package/dist/tooling/testing/isolation.d.ts +4 -3
- package/package.json +6 -5
- package/dist/shared/0v1g424p.js +0 -26
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
introspectSchema
|
|
7
7
|
} from "./fancm5pt.js";
|
|
8
8
|
import {
|
|
9
|
-
planMigration
|
|
10
|
-
|
|
9
|
+
planMigration,
|
|
10
|
+
assertDirectConnection,
|
|
11
|
+
assertStableBackend
|
|
12
|
+
} from "./ga3sfy93.js";
|
|
11
13
|
import {
|
|
12
14
|
quoteIdent,
|
|
13
15
|
createObjectSql
|
|
@@ -49,8 +51,11 @@ async function sealViews(runner, source) {
|
|
|
49
51
|
return source;
|
|
50
52
|
await dropScratch(runner);
|
|
51
53
|
await runner.query(`create schema ${quoteIdent(SCRATCH)}`);
|
|
54
|
+
const previous = await runner.query(`select current_setting('search_path') as path`);
|
|
55
|
+
const saved = cellText(previous[0]?.path);
|
|
56
|
+
const path = await scratchSearchPath(runner, source);
|
|
52
57
|
try {
|
|
53
|
-
await runner.query(`
|
|
58
|
+
await runner.query(`select set_config('search_path', $1, false)`, [path]);
|
|
54
59
|
await createObjects(runner, source, "type");
|
|
55
60
|
for (const object of source.objects) {
|
|
56
61
|
if (object.kind !== "table")
|
|
@@ -99,10 +104,37 @@ async function sealViews(runner, source) {
|
|
|
99
104
|
});
|
|
100
105
|
return catalog(next);
|
|
101
106
|
} finally {
|
|
102
|
-
await runner.query(
|
|
107
|
+
await runner.query(`select set_config('search_path', $1, false)`, [saved]).catch(() => {
|
|
108
|
+
return;
|
|
109
|
+
});
|
|
103
110
|
await dropScratch(runner);
|
|
104
111
|
}
|
|
105
112
|
}
|
|
113
|
+
async function scratchSearchPath(runner, source) {
|
|
114
|
+
const names = new Set;
|
|
115
|
+
for (const object of source.objects) {
|
|
116
|
+
if (object.kind !== "column")
|
|
117
|
+
continue;
|
|
118
|
+
const bare = object.definition.dataType.split(".").pop()?.replace(/\(.*$/, "").replace(/\[\]$/, "").trim().toLowerCase();
|
|
119
|
+
if (bare !== undefined && bare.length > 0)
|
|
120
|
+
names.add(bare);
|
|
121
|
+
}
|
|
122
|
+
const extra = [];
|
|
123
|
+
if (names.size > 0) {
|
|
124
|
+
const rows = await runner.query(`select distinct n.nspname as schema
|
|
125
|
+
from pg_type t
|
|
126
|
+
join pg_namespace n on n.oid = t.typnamespace
|
|
127
|
+
where t.typname = any(string_to_array($1, E'\\x1f'))
|
|
128
|
+
and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')`, [[...names].join("\x1F")]);
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
const schema = cellText(row.schema);
|
|
131
|
+
if (schema.length > 0 && schema !== "public" && schema !== SCRATCH)
|
|
132
|
+
extra.push(schema);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
extra.sort();
|
|
136
|
+
return [SCRATCH, ...extra, "public"].map((name) => quoteIdent(name)).join(", ");
|
|
137
|
+
}
|
|
106
138
|
async function createObjects(runner, source, kind) {
|
|
107
139
|
for (const object of source.objects) {
|
|
108
140
|
if (object.kind !== kind)
|
|
@@ -193,7 +225,8 @@ async function dropScratch(runner) {
|
|
|
193
225
|
}
|
|
194
226
|
|
|
195
227
|
// src/tooling/migrate/drift.ts
|
|
196
|
-
async function assertAuthorDrift2(url, author, roles) {
|
|
228
|
+
async function assertAuthorDrift2(url, author, roles, allowPooler = false) {
|
|
229
|
+
assertDirectConnection(url, allowPooler);
|
|
197
230
|
const sql = postgres(url, {
|
|
198
231
|
max: 1,
|
|
199
232
|
connect_timeout: 5,
|
|
@@ -201,6 +234,10 @@ async function assertAuthorDrift2(url, author, roles) {
|
|
|
201
234
|
onnotice: () => {}
|
|
202
235
|
});
|
|
203
236
|
try {
|
|
237
|
+
await assertStableBackend(async () => {
|
|
238
|
+
const rows = await sql`select pg_backend_pid()::text as pid`;
|
|
239
|
+
return rows[0]?.pid ?? "";
|
|
240
|
+
}, allowPooler);
|
|
204
241
|
const present = await sql`
|
|
205
242
|
select to_regclass('public.okm_meta')::text as reg
|
|
206
243
|
`;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./8xsbagad.js";
|
|
4
4
|
import {
|
|
5
5
|
createClient
|
|
6
|
-
} from "./
|
|
6
|
+
} from "./hf22e0wt.js";
|
|
7
7
|
import {
|
|
8
8
|
OkmError2
|
|
9
9
|
} from "./52szzakm.js";
|
|
@@ -463,6 +463,9 @@ function fieldOf(facts, sql) {
|
|
|
463
463
|
function cacheKey(table, tenant) {
|
|
464
464
|
return `${tenant ?? "*"}:${table}`;
|
|
465
465
|
}
|
|
466
|
+
function forgetCached(host, table, tenant) {
|
|
467
|
+
host.cache.delete(cacheKey(table, tenant));
|
|
468
|
+
}
|
|
466
469
|
function refOf(value) {
|
|
467
470
|
if (typeof value !== "object" || value === null || !(REF in value))
|
|
468
471
|
return;
|
|
@@ -502,14 +505,14 @@ function isRow(value) {
|
|
|
502
505
|
}
|
|
503
506
|
|
|
504
507
|
// src/tooling/testing/isolation.ts
|
|
505
|
-
var TENANT_A = "00000000-0000-4000-8000-00000000000a";
|
|
506
|
-
var TENANT_B = "00000000-0000-4000-8000-00000000000b";
|
|
507
508
|
async function isolation(host) {
|
|
508
509
|
const saved = host.cache;
|
|
509
510
|
host.cache = new Map;
|
|
510
511
|
try {
|
|
511
512
|
const checked = [];
|
|
512
513
|
const skipped = [];
|
|
514
|
+
const tenantA = crypto.randomUUID();
|
|
515
|
+
const tenantB = crypto.randomUUID();
|
|
513
516
|
for (const facts of host.facts.values()) {
|
|
514
517
|
if (facts.globalReason !== undefined) {
|
|
515
518
|
skipped.push({ table: facts.name, reason: facts.globalReason });
|
|
@@ -517,7 +520,7 @@ async function isolation(host) {
|
|
|
517
520
|
}
|
|
518
521
|
if (!facts.tenant)
|
|
519
522
|
continue;
|
|
520
|
-
await checkTable(host, facts);
|
|
523
|
+
await checkTable(host, facts, tenantA, tenantB);
|
|
521
524
|
checked.push(facts.name);
|
|
522
525
|
}
|
|
523
526
|
return { checked, skipped };
|
|
@@ -525,25 +528,68 @@ async function isolation(host) {
|
|
|
525
528
|
host.cache = saved;
|
|
526
529
|
}
|
|
527
530
|
}
|
|
528
|
-
async function checkTable(host, facts) {
|
|
531
|
+
async function checkTable(host, facts, tenantA, tenantB) {
|
|
529
532
|
const key = host.schema.tenancy?.key;
|
|
530
533
|
if (key === undefined)
|
|
531
534
|
return;
|
|
532
|
-
|
|
535
|
+
if (host.definitions.size > 0 && !host.definitions.has(facts.name)) {
|
|
536
|
+
throw new OkmError2("invalid", `Factory ${facts.name} is missing.`, {
|
|
537
|
+
fix: { summary: `Add ${facts.name} to factories().` }
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
const row = await insertRow(host, facts.name, { [key]: tenantB }, undefined, tenantB);
|
|
533
541
|
const where = primaryWhere(facts, row, key);
|
|
534
|
-
const client = openTenant(host,
|
|
542
|
+
const client = openTenant(host, tenantA);
|
|
535
543
|
const handle = tableHandle2(client, facts.name);
|
|
536
|
-
|
|
537
|
-
|
|
544
|
+
try {
|
|
545
|
+
await readTenant(facts, handle, where);
|
|
546
|
+
const set = updateSet(host, facts, row);
|
|
547
|
+
if (set !== undefined) {
|
|
548
|
+
const updated = asCount(await handle.update({ where, set }));
|
|
549
|
+
if (updated > 0)
|
|
550
|
+
leak(facts.name, "update", { count: updated });
|
|
551
|
+
if (typeof client.batch === "function") {
|
|
552
|
+
const batched = await client.batch([handle.update({ where, set })]);
|
|
553
|
+
if (Array.isArray(batched) && batched.some((item) => asCount(item) > 0)) {
|
|
554
|
+
leak(facts.name, "batch", batched);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const removed = asCount(await handle.delete({ where }));
|
|
559
|
+
if (removed > 0)
|
|
560
|
+
leak(facts.name, "delete", { count: removed });
|
|
561
|
+
} finally {
|
|
562
|
+
const owner = tableHandle2(openTenant(host, tenantB), facts.name);
|
|
563
|
+
await owner.delete({ where }).catch(() => {
|
|
564
|
+
return;
|
|
565
|
+
});
|
|
566
|
+
forgetCached(host, facts.name, tenantB);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function readTenant(facts, handle, where) {
|
|
570
|
+
const query = handle.find({ where, limit: 1 });
|
|
571
|
+
const rows = asRows(await query);
|
|
538
572
|
if (rows.length > 0)
|
|
539
573
|
leak(facts.name, "find", rows[0]);
|
|
574
|
+
const streamed = handle.find({ where, limit: 1 });
|
|
575
|
+
if (typeof streamed.stream === "function") {
|
|
576
|
+
try {
|
|
577
|
+
for await (const item of streamed.stream()) {
|
|
578
|
+
if (isRow2(item))
|
|
579
|
+
leak(facts.name, "stream", item);
|
|
580
|
+
}
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (!(error instanceof OkmError2) || error.code !== "OKM1111")
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
540
586
|
const one = await handle.one({ where });
|
|
541
587
|
if (isRow2(one))
|
|
542
588
|
leak(facts.name, "one", one);
|
|
543
|
-
const counted = asCount(await handle.count());
|
|
589
|
+
const counted = asCount(await handle.count({ where }));
|
|
544
590
|
if (counted > 0)
|
|
545
591
|
leak(facts.name, "count", { count: counted });
|
|
546
|
-
const exists = await handle.exists();
|
|
592
|
+
const exists = await handle.exists({ where });
|
|
547
593
|
if (exists === true)
|
|
548
594
|
leak(facts.name, "exists", { exists: true });
|
|
549
595
|
const relation = facts.relations[0];
|
|
@@ -552,15 +598,28 @@ async function checkTable(host, facts) {
|
|
|
552
598
|
if (isRow2(included))
|
|
553
599
|
leak(facts.name, "include", included);
|
|
554
600
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
601
|
+
if (typeof handle.aggregate === "function") {
|
|
602
|
+
const aggregated = await handle.aggregate({ where, count: true });
|
|
603
|
+
if (Array.isArray(aggregated)) {
|
|
604
|
+
for (const item of aggregated) {
|
|
605
|
+
if (isRow2(item) && asCount(item) > 0)
|
|
606
|
+
leak(facts.name, "aggregate", item);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (typeof handle.page === "function") {
|
|
611
|
+
const orderField = Object.keys(where)[0];
|
|
612
|
+
if (orderField !== undefined) {
|
|
613
|
+
const paged = await handle.page({
|
|
614
|
+
where,
|
|
615
|
+
orderBy: { [orderField]: "asc" },
|
|
616
|
+
limit: 5
|
|
617
|
+
});
|
|
618
|
+
if (isRow2(paged) && Array.isArray(paged.items) && paged.items.length > 0) {
|
|
619
|
+
leak(facts.name, "page", paged.items[0]);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
560
622
|
}
|
|
561
|
-
const deleted = asCount(await handle.delete({ where }));
|
|
562
|
-
if (deleted > 0)
|
|
563
|
-
leak(facts.name, "delete", { count: deleted });
|
|
564
623
|
}
|
|
565
624
|
function includeOf(relation) {
|
|
566
625
|
if (relation.kind === "many")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tag
|
|
3
|
+
} from "./cn0n68ft.js";
|
|
4
|
+
import {
|
|
5
|
+
OkmError2
|
|
6
|
+
} from "./52szzakm.js";
|
|
7
|
+
|
|
8
|
+
// src/dialects/pg/filter-parse.ts
|
|
9
|
+
var PARSED_OPS = ",eq,in,inList,notIn,lt,lte,gt,gte,between,startsWith,endsWith,contains,like,ilike,";
|
|
10
|
+
var PARSED_FIX = "Use eq, in, notIn, lt, lte, gt, gte, between, startsWith, endsWith, contains, like, or ilike.";
|
|
11
|
+
function parseQuery(allow, sort, query) {
|
|
12
|
+
if (typeof query !== "object" || query === null || Array.isArray(query)) {
|
|
13
|
+
throw new OkmError2("OKM1121", "filters parse expects an object of fields.");
|
|
14
|
+
}
|
|
15
|
+
for (const field of Object.keys(allow)) {
|
|
16
|
+
for (const op of allow[field] ?? []) {
|
|
17
|
+
if (PARSED_OPS.includes(`,${op},`))
|
|
18
|
+
continue;
|
|
19
|
+
throw new OkmError2("OKM1120", `filters() cannot apply ${op} on ${field}.`, {
|
|
20
|
+
fix: { summary: PARSED_FIX }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const whereParts = [];
|
|
25
|
+
let orderBy;
|
|
26
|
+
for (const key of Object.keys(query)) {
|
|
27
|
+
const value = query[key];
|
|
28
|
+
if (key === "sort" && typeof value === "string" && sort.includes(value)) {
|
|
29
|
+
orderBy = { [value]: "asc" };
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!Object.hasOwn(allow, key)) {
|
|
33
|
+
throw new OkmError2("OKM1123", `Field ${key} is not in the allow list.`, {
|
|
34
|
+
fix: { summary: "Remove the field, or add it to filters({ allow })." }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const ops = allow[key] ?? [];
|
|
38
|
+
whereParts.push(...clauses(key, ops, value));
|
|
39
|
+
}
|
|
40
|
+
const where = combine(whereParts);
|
|
41
|
+
return orderBy === undefined ? { where } : { where, orderBy };
|
|
42
|
+
}
|
|
43
|
+
function clauses(field, ops, value) {
|
|
44
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
if (!ops.includes("eq")) {
|
|
46
|
+
throw new OkmError2("OKM1120", `filters() cannot apply eq on ${field}.`, {
|
|
47
|
+
fix: { summary: "Add eq to that field, or send an object of allowed operators." }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return [{ [field]: value }];
|
|
51
|
+
}
|
|
52
|
+
const applied = [];
|
|
53
|
+
for (const op of Object.keys(value)) {
|
|
54
|
+
if (!Object.hasOwn(value, op))
|
|
55
|
+
continue;
|
|
56
|
+
if (!ops.includes(op) && !(op === "inList" && ops.includes("in"))) {
|
|
57
|
+
throw new OkmError2("OKM1120", `filters() cannot apply ${op} on ${field}.`, {
|
|
58
|
+
fix: { summary: "Send an operator that field's allow list names." }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
applied.push(one(field, op, value[op]));
|
|
62
|
+
}
|
|
63
|
+
if (applied.length === 0) {
|
|
64
|
+
throw new OkmError2("OKM1120", `filters() cannot apply a value on ${field}.`, {
|
|
65
|
+
fix: { summary: "Send eq as a scalar, or an object of allowed operators." }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return applied;
|
|
69
|
+
}
|
|
70
|
+
function one(field, op, value) {
|
|
71
|
+
switch (op) {
|
|
72
|
+
case "eq":
|
|
73
|
+
return {
|
|
74
|
+
[field]: value !== null && typeof value === "object" ? tag("eq", value) : value
|
|
75
|
+
};
|
|
76
|
+
case "in":
|
|
77
|
+
case "inList":
|
|
78
|
+
return { [field]: tag("inList", list(value, op)) };
|
|
79
|
+
case "notIn":
|
|
80
|
+
return { [field]: tag("notIn", list(value, op)) };
|
|
81
|
+
case "lt":
|
|
82
|
+
return { [field]: tag("lt", value) };
|
|
83
|
+
case "lte":
|
|
84
|
+
return { [field]: tag("lte", value) };
|
|
85
|
+
case "gt":
|
|
86
|
+
return { [field]: tag("gt", value) };
|
|
87
|
+
case "gte":
|
|
88
|
+
return { [field]: tag("gte", value) };
|
|
89
|
+
case "between": {
|
|
90
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
91
|
+
throw new OkmError2("OKM1120", `between on ${field} needs two bounds.`, {
|
|
92
|
+
fix: { summary: "Send between as [from, to]." }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return { [field]: tag("between", [value[0], value[1]]) };
|
|
96
|
+
}
|
|
97
|
+
case "startsWith":
|
|
98
|
+
return { [field]: tag("startsWith", text(value, op)) };
|
|
99
|
+
case "endsWith":
|
|
100
|
+
return { [field]: tag("endsWith", text(value, op)) };
|
|
101
|
+
case "contains":
|
|
102
|
+
return { [field]: tag("contains", value) };
|
|
103
|
+
case "like":
|
|
104
|
+
return { [field]: tag("like", text(value, op)) };
|
|
105
|
+
case "ilike":
|
|
106
|
+
return { [field]: tag("ilike", text(value, op)) };
|
|
107
|
+
default:
|
|
108
|
+
throw new OkmError2("OKM1120", `filters() cannot apply ${op} on ${field}.`, {
|
|
109
|
+
fix: { summary: "Use an operator parse applies." }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function combine(parts) {
|
|
114
|
+
const first = parts[0];
|
|
115
|
+
if (first === undefined)
|
|
116
|
+
return {};
|
|
117
|
+
if (parts.length === 1)
|
|
118
|
+
return first;
|
|
119
|
+
return tag("and", parts);
|
|
120
|
+
}
|
|
121
|
+
function list(value, op) {
|
|
122
|
+
if (!Array.isArray(value)) {
|
|
123
|
+
throw new OkmError2("OKM1120", `${op} needs an array.`, {
|
|
124
|
+
fix: { summary: "Send that operator an array of values." }
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function text(value, op) {
|
|
130
|
+
if (typeof value !== "string") {
|
|
131
|
+
throw new OkmError2("OKM1120", `${op} needs a string.`, {
|
|
132
|
+
fix: { summary: "Send that operator a string." }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
parseQuery
|
|
139
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withHttp
|
|
3
|
-
} from "./
|
|
3
|
+
} from "./hf22e0wt.js";
|
|
4
4
|
import {
|
|
5
5
|
decodeResult
|
|
6
|
-
} from "./
|
|
6
|
+
} from "./gbtcc2d7.js";
|
|
7
7
|
import {
|
|
8
8
|
OkmError2
|
|
9
9
|
} from "./52szzakm.js";
|
|
@@ -17,7 +17,7 @@ async function* streamRows(session, call, prepare, log) {
|
|
|
17
17
|
})));
|
|
18
18
|
}
|
|
19
19
|
const { plan, params } = await prepare();
|
|
20
|
-
const decode = plan.outputs.includes.length === 0 ? decodeResult : (await import("./
|
|
20
|
+
const decode = plan.outputs.includes.length === 0 ? decodeResult : (await import("./nkaxvt9q.js")).decodeIncluded;
|
|
21
21
|
for await (const chunk of session.pool.stream(plan.text, params)) {
|
|
22
22
|
const decoded = decode(plan, chunk, call.table);
|
|
23
23
|
if (!Array.isArray(decoded))
|
package/dist/tooling/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import"../shared/8bwbwy8h.js";
|
|
2
2
|
import"../shared/n442h54c.js";
|
|
3
|
-
import"../shared/
|
|
3
|
+
import"../shared/krxx77g0.js";
|
|
4
4
|
import {
|
|
5
5
|
formatFindings,
|
|
6
6
|
hasError,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
generateProject,
|
|
10
10
|
planProject,
|
|
11
11
|
checkProject
|
|
12
|
-
} from "../shared/
|
|
13
|
-
import"../shared/
|
|
12
|
+
} from "../shared/qv2ydck9.js";
|
|
13
|
+
import"../shared/ga3sfy93.js";
|
|
14
14
|
import"../shared/8xsbagad.js";
|
|
15
15
|
import {
|
|
16
16
|
OkmError2
|
|
@@ -22,14 +22,28 @@ import { dirname, join } from "node:path";
|
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
|
|
24
24
|
// src/tooling/migrate/commands.ts
|
|
25
|
-
function formatFailure(error) {
|
|
26
|
-
|
|
27
|
-
${
|
|
25
|
+
function formatFailure(error, verbose = false) {
|
|
26
|
+
const mapped = OkmError2.from(error);
|
|
27
|
+
let text = `error ${mapped.code}: ${mapped.message}
|
|
28
|
+
${mapped.fix.summary}
|
|
28
29
|
`;
|
|
30
|
+
if (verbose && error instanceof Error && error.stack !== undefined) {
|
|
31
|
+
text += `${error.stack}
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
return text;
|
|
29
35
|
}
|
|
30
36
|
async function run(argv, io) {
|
|
31
37
|
const cwd = io?.cwd ?? process.cwd();
|
|
32
38
|
const stdout = io?.stdout ?? ((text) => process.stdout.write(text));
|
|
39
|
+
if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
|
|
40
|
+
stdout(commandsTable());
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
44
|
+
stdout(commandHelp(argv.filter((arg) => arg !== "--help" && arg !== "-h")));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
33
47
|
const [command, ...rest] = argv;
|
|
34
48
|
if (command === "build") {
|
|
35
49
|
const directory = await buildProject(cwd);
|
|
@@ -57,12 +71,12 @@ async function run(argv, io) {
|
|
|
57
71
|
return;
|
|
58
72
|
}
|
|
59
73
|
if (command === "dev") {
|
|
60
|
-
const { devProject } = await import("../shared/
|
|
74
|
+
const { devProject } = await import("../shared/sdc7r2ww.js");
|
|
61
75
|
stdout(await devProject(cwd));
|
|
62
76
|
return;
|
|
63
77
|
}
|
|
64
78
|
if (command === "push") {
|
|
65
|
-
const { pushProject } = await import("../shared/
|
|
79
|
+
const { pushProject } = await import("../shared/q58y17hy.js");
|
|
66
80
|
stdout(await pushProject(cwd, invoke(splitFlags(rest))));
|
|
67
81
|
return;
|
|
68
82
|
}
|
|
@@ -81,17 +95,17 @@ async function run(argv, io) {
|
|
|
81
95
|
return;
|
|
82
96
|
}
|
|
83
97
|
if (sub === "apply") {
|
|
84
|
-
const { applyProject } = await import("../shared/
|
|
98
|
+
const { applyProject } = await import("../shared/q58y17hy.js");
|
|
85
99
|
stdout(await applyProject(cwd, invoke(parsed), stdout));
|
|
86
100
|
return;
|
|
87
101
|
}
|
|
88
102
|
if (sub === "status") {
|
|
89
|
-
const { statusProject } = await import("../shared/
|
|
103
|
+
const { statusProject } = await import("../shared/6mnp99hx.js");
|
|
90
104
|
stdout(await statusProject(cwd, invoke(parsed)));
|
|
91
105
|
return;
|
|
92
106
|
}
|
|
93
107
|
if (sub === "check") {
|
|
94
|
-
const { checkMigrations } = await import("../shared/
|
|
108
|
+
const { checkMigrations } = await import("../shared/7zwwyh0v.js");
|
|
95
109
|
const provision = parsed.flags.includes("--provision") || rest.slice(1).includes("--provision");
|
|
96
110
|
stdout(await checkMigrations(cwd, invoke(parsed), { provision }));
|
|
97
111
|
return;
|
|
@@ -104,13 +118,13 @@ async function run(argv, io) {
|
|
|
104
118
|
fix: { summary: "Pass okm seed <file>." }
|
|
105
119
|
});
|
|
106
120
|
}
|
|
107
|
-
const { seedProject } = await import("../shared/
|
|
121
|
+
const { seedProject } = await import("../shared/2pqx26h8.js");
|
|
108
122
|
stdout(await seedProject(cwd, parsed.name, invoke(parsed)));
|
|
109
123
|
return;
|
|
110
124
|
}
|
|
111
125
|
if (command === "doctor") {
|
|
112
126
|
const parsed = splitFlags(rest);
|
|
113
|
-
const { doctorProject } = await import("../shared/
|
|
127
|
+
const { doctorProject } = await import("../shared/ps7a6h14.js");
|
|
114
128
|
stdout(await doctorProject(cwd, parsed.name, invoke(parsed)));
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
@@ -122,7 +136,7 @@ async function run(argv, io) {
|
|
|
122
136
|
});
|
|
123
137
|
}
|
|
124
138
|
if (sub === "list" || sub === "check") {
|
|
125
|
-
const { extProject } = await import("../shared/
|
|
139
|
+
const { extProject } = await import("../shared/janx09m5.js");
|
|
126
140
|
stdout(await extProject(cwd, sub, invoke(splitFlags(rest.slice(1)))));
|
|
127
141
|
return;
|
|
128
142
|
}
|
|
@@ -210,6 +224,42 @@ function splitFlags(args) {
|
|
|
210
224
|
statementTimeoutMs
|
|
211
225
|
};
|
|
212
226
|
}
|
|
227
|
+
var COMMANDS = [
|
|
228
|
+
["build", "Write the schema build."],
|
|
229
|
+
["check", "Lint the schema and compare it with one target."],
|
|
230
|
+
["dev", "Watch the schema and reload."],
|
|
231
|
+
["doctor [code]", "Print a target report, or one error code."],
|
|
232
|
+
["ext list", "List extensions the server can install."],
|
|
233
|
+
["ext check", "Compare declared extensions with the server."],
|
|
234
|
+
["generate [name]", "Write the next migration."],
|
|
235
|
+
["migrate plan <name>", "Print the plan and its lock estimates."],
|
|
236
|
+
["migrate apply", "Apply pending migrations."],
|
|
237
|
+
["migrate status", "Print whether the target is current."],
|
|
238
|
+
["migrate check", "Replay migration history in a scratch schema."],
|
|
239
|
+
["push", "Apply the schema diff to one target."],
|
|
240
|
+
["seed <file>", "Run a seed file on one target."]
|
|
241
|
+
];
|
|
242
|
+
function commandsTable() {
|
|
243
|
+
const lines = ["okm <command>", ""];
|
|
244
|
+
for (const [name, summary] of COMMANDS)
|
|
245
|
+
lines.push(` ${name.padEnd(24)} ${summary}`);
|
|
246
|
+
lines.push("", "okm <command> --help prints that command.", "");
|
|
247
|
+
return `${lines.join(`
|
|
248
|
+
`)}
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
function commandHelp(argv) {
|
|
252
|
+
const key = argv.join(" ");
|
|
253
|
+
const found = COMMANDS.find(([name]) => name === key || name.startsWith(`${key} `));
|
|
254
|
+
if (found === undefined) {
|
|
255
|
+
return `Unknown command ${key}.
|
|
256
|
+
|
|
257
|
+
${commandsTable()}`;
|
|
258
|
+
}
|
|
259
|
+
return `${found[0]}
|
|
260
|
+
${found[1]}
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
213
263
|
function numberFlag(flag, value) {
|
|
214
264
|
const parsed = value === undefined ? Number.NaN : Number(value);
|
|
215
265
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -226,16 +276,13 @@ var packageJsonPath = join(here, "..", "..", "package.json");
|
|
|
226
276
|
if (process.argv.includes("--version")) {
|
|
227
277
|
process.stdout.write(`${readPackageVersion(packageJsonPath)}
|
|
228
278
|
`);
|
|
229
|
-
} else
|
|
279
|
+
} else {
|
|
230
280
|
try {
|
|
231
281
|
await run(process.argv.slice(2));
|
|
232
282
|
} catch (error) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
} else {
|
|
237
|
-
throw error;
|
|
238
|
-
}
|
|
283
|
+
const verbose = process.argv.includes("--verbose") || process.env.OKM_DEBUG === "1";
|
|
284
|
+
process.stderr.write(formatFailure(error, verbose));
|
|
285
|
+
process.exitCode = 1;
|
|
239
286
|
}
|
|
240
287
|
}
|
|
241
288
|
function readPackageVersion(path) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* The bin wrapper imports {@link run}. Importing this module does not parse
|
|
5
5
|
* `process.argv`.
|
|
6
6
|
*/
|
|
7
|
-
import { OkmError } from "../../contracts/error.js";
|
|
8
7
|
/** Where a command reads the project and writes its text. */
|
|
9
8
|
export type CommandIo = {
|
|
10
9
|
readonly cwd?: string;
|
|
@@ -18,7 +17,7 @@ export type CommandIo = {
|
|
|
18
17
|
* @param error - Failure from a command
|
|
19
18
|
* @returns The stderr text, including the trailing newline
|
|
20
19
|
*/
|
|
21
|
-
export declare function formatFailure(error:
|
|
20
|
+
export declare function formatFailure(error: unknown, verbose?: boolean): string;
|
|
22
21
|
/**
|
|
23
22
|
* Runs one `okm` invocation.
|
|
24
23
|
*
|
|
@@ -18,7 +18,7 @@ import type { RolesInput } from "../../dialects/pg/role/index.js";
|
|
|
18
18
|
* @param author - Schema catalog, including roles when the config names them
|
|
19
19
|
* @param roles - `defineConfig({ roles })`, when set
|
|
20
20
|
*/
|
|
21
|
-
export declare function assertAuthorDrift(url: string, author: Catalog, roles?: RolesInput): Promise<void>;
|
|
21
|
+
export declare function assertAuthorDrift(url: string, author: Catalog, roles?: RolesInput, allowPooler?: boolean): Promise<void>;
|
|
22
22
|
/**
|
|
23
23
|
* Turns a postgres.js handle into the query shape introspection uses.
|
|
24
24
|
*
|
|
@@ -40,7 +40,20 @@ export declare function readRowEstimates(url: string, schema: string, tables: re
|
|
|
40
40
|
* @param estimates - `reltuples` for tables that exist on the target
|
|
41
41
|
* @returns The text after `-- lock: `
|
|
42
42
|
*/
|
|
43
|
-
export declare function annotateLock(step: PlanStep, estimates: ReadonlyMap<string, RowEstimate>): string;
|
|
43
|
+
export declare function annotateLock(step: PlanStep, estimates: ReadonlyMap<string, RowEstimate>, aliases?: ReadonlyMap<string, string>): string;
|
|
44
|
+
/**
|
|
45
|
+
* Maps a rename destination to the table that still has `reltuples`.
|
|
46
|
+
*
|
|
47
|
+
* The rename statement names the old table. Later steps name the new one,
|
|
48
|
+
* which is not in `pg_class` yet.
|
|
49
|
+
*
|
|
50
|
+
* @param steps - Plan steps
|
|
51
|
+
* @returns New name to old name
|
|
52
|
+
*/
|
|
53
|
+
export declare function renameAliases(steps: readonly {
|
|
54
|
+
readonly kind?: string;
|
|
55
|
+
readonly sql: string;
|
|
56
|
+
}[]): ReadonlyMap<string, string>;
|
|
44
57
|
/**
|
|
45
58
|
* Compact phrase for one estimate.
|
|
46
59
|
*
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import"../../shared/
|
|
1
|
+
import"../../shared/krxx77g0.js";
|
|
2
2
|
import"../../shared/8bwbwy8h.js";
|
|
3
3
|
import"../../shared/n442h54c.js";
|
|
4
4
|
import {
|
|
5
5
|
applyTarget2,
|
|
6
6
|
backfillTiming2
|
|
7
|
-
} from "../../shared/
|
|
7
|
+
} from "../../shared/cctw17hj.js";
|
|
8
8
|
import {
|
|
9
9
|
loadMigrations,
|
|
10
10
|
loadHeadSnapshot,
|
|
11
11
|
readReference
|
|
12
|
-
} from "../../shared/
|
|
12
|
+
} from "../../shared/gde73es6.js";
|
|
13
13
|
import"../../shared/szm0cksa.js";
|
|
14
14
|
import"../../shared/fancm5pt.js";
|
|
15
15
|
import {
|
|
16
|
-
assertTargetPolicy,
|
|
17
|
-
selectTarget,
|
|
18
16
|
loadBuiltSchema,
|
|
19
17
|
loadConfig
|
|
20
|
-
} from "../../shared/
|
|
21
|
-
import
|
|
18
|
+
} from "../../shared/qv2ydck9.js";
|
|
19
|
+
import {
|
|
20
|
+
assertTargetPolicy,
|
|
21
|
+
selectTarget
|
|
22
|
+
} from "../../shared/ga3sfy93.js";
|
|
22
23
|
import"../../shared/8xsbagad.js";
|
|
23
24
|
import {
|
|
24
25
|
OkmError2
|