okmodel 0.5.0 → 0.6.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 +36 -18
- package/dist/adapters/pg/no-inflight-timeout.d.ts +17 -0
- package/dist/adapters/pg/postgres-extra.d.ts +22 -0
- package/dist/contracts/catalog/document.d.ts +19 -5
- package/dist/contracts/catalog/object.d.ts +2 -1
- package/dist/contracts/catalog/policy.d.ts +48 -0
- package/dist/contracts/catalog/types.d.ts +25 -1
- package/dist/contracts/catalog/view.d.ts +2 -0
- package/dist/contracts/internal.js +1 -1
- package/dist/contracts/location.d.ts +10 -0
- package/dist/dialects/pg/column.d.ts +13 -2
- package/dist/dialects/pg/ddl.d.ts +37 -1
- package/dist/dialects/pg/emit.d.ts +19 -1
- package/dist/dialects/pg/errors.d.ts +32 -1
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/filter-parse.d.ts +10 -3
- package/dist/dialects/pg/fn/index.js +10 -2
- package/dist/dialects/pg/index.d.ts +2 -1
- package/dist/dialects/pg/index.js +29 -17
- package/dist/dialects/pg/introspect.d.ts +7 -0
- package/dist/dialects/pg/model.d.ts +7 -2
- package/dist/dialects/pg/ops/or.d.ts +1 -1
- package/dist/dialects/pg/owner.d.ts +42 -0
- package/dist/dialects/pg/path-endpoint.d.ts +20 -0
- package/dist/dialects/pg/role/check.d.ts +3 -0
- package/dist/dialects/pg/role/index.d.ts +71 -6
- package/dist/dialects/pg/role/sql.d.ts +11 -0
- package/dist/dialects/pg/schema.d.ts +2 -0
- package/dist/dialects/pg/table.d.ts +25 -3
- package/dist/dialects/pg/temporal.d.ts +5 -76
- package/dist/dialects/pg/tenancy.d.ts +45 -3
- package/dist/dialects/pg/view/client.d.ts +19 -1
- package/dist/dialects/pg/view/index.d.ts +4 -0
- package/dist/dialects/pg/view/index.js +21 -178
- package/dist/dialects/pg/view/refresh.d.ts +24 -0
- package/dist/runtime/batch.d.ts +1 -1
- package/dist/runtime/client.d.ts +6 -1
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/drift.d.ts +9 -0
- package/dist/runtime/inflight.d.ts +14 -0
- package/dist/runtime/pg/bun.js +6 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.js +6 -5
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +22 -1
- package/dist/runtime/predicate.d.ts +19 -0
- package/dist/runtime/safety/index.js +1 -1
- package/dist/runtime/tenancy/composite.d.ts +26 -0
- package/dist/runtime/tenancy/context.d.ts +16 -0
- package/dist/runtime/tenancy/index.d.ts +9 -5
- package/dist/runtime/tenancy/index.js +1508 -37
- package/dist/runtime/tenancy/path.d.ts +87 -0
- package/dist/runtime/tenancy/rls.d.ts +67 -0
- package/dist/runtime/tenancy/via.d.ts +22 -0
- package/dist/runtime/traits/index.js +17 -11
- package/dist/runtime/types.d.ts +13 -1
- package/dist/runtime/validate/index.js +4 -4
- package/dist/runtime/write.d.ts +1 -1
- package/dist/shared/09p049xa.js +702 -0
- package/dist/shared/{ykv7wh5b.js → 1f0xa3cb.js} +5 -5
- package/dist/shared/1nw1f3vs.js +23 -0
- package/dist/shared/{6p5k20sm.js → 1z04fa85.js} +21 -11
- package/dist/shared/{p7pfx624.js → 2cg3wnxc.js} +314 -65
- package/dist/shared/{dyj86cmj.js → 2jd1sn0e.js} +2 -2
- package/dist/shared/{qgvt8nx1.js → 2z7wv5tj.js} +21 -5
- package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
- package/dist/shared/{sf8m6tfy.js → 32cnm7kn.js} +8 -3
- package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
- package/dist/shared/{jdgqqx31.js → 3fqwmr09.js} +38 -14
- package/dist/shared/{922qbgnw.js → 3qnhc7w5.js} +16 -9
- package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
- package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
- package/dist/shared/{k7cc9nzk.js → 5d8t84hg.js} +15 -3
- package/dist/shared/{fa9vt245.js → 5j36z8dn.js} +17 -6
- package/dist/shared/{1g0drakv.js → 5yv4k6a6.js} +1 -1
- package/dist/shared/8dqhth8c.js +12 -0
- package/dist/shared/8etn7d8w.js +62 -0
- package/dist/shared/{g516zdsb.js → 8x72s86k.js} +476 -30
- package/dist/shared/{nkc9sfqb.js → 929h19xp.js} +145 -7
- package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
- package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
- package/dist/shared/{b066g80h.js → ag4c7516.js} +2 -2
- package/dist/shared/b3awxn7h.js +45 -0
- package/dist/shared/b4v57v3x.js +9 -0
- package/dist/shared/bdk4sty6.js +58 -0
- package/dist/shared/{s6gqfn5j.js → c58secgj.js} +125 -8
- package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
- package/dist/shared/d5azex6s.js +6 -0
- package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
- package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
- package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
- package/dist/shared/{wxn4b7pp.js → f0f1n281.js} +59 -12
- package/dist/shared/{gdh9mrtp.js → g90m9b0g.js} +3 -2
- package/dist/shared/{tbszx8mz.js → gwn6v667.js} +2 -2
- package/dist/shared/{c8aghh05.js → h15jx9bc.js} +8 -2
- package/dist/shared/{3p4b1fg0.js → hazqqecq.js} +17 -93
- package/dist/shared/{4ef7mmqf.js → j3rkk835.js} +34 -11
- package/dist/shared/{bt9sfq0z.js → j60w0dvr.js} +56 -21
- package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
- package/dist/shared/k68eedmy.js +85 -0
- package/dist/shared/{szm0cksa.js → knmes2kh.js} +34 -6
- package/dist/shared/{ckjbyrt9.js → mb2ycs1p.js} +7 -9
- package/dist/shared/{tq9afp8d.js → n076ydsc.js} +1 -1
- package/dist/shared/{xhh1srw3.js → nx9hh281.js} +50 -32
- package/dist/shared/{kdkxh3kb.js → p248q79y.js} +58 -59
- package/dist/shared/{990a291j.js → p6bjzq9q.js} +14 -9
- package/dist/shared/{h4nqvjkp.js → pjr6xrm5.js} +2 -2
- package/dist/shared/pz6rj8ay.js +212 -0
- package/dist/shared/{5vdm4sgp.js → q3j1tq7a.js} +36 -23
- package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
- package/dist/shared/{dxjp14rk.js → r11n8grd.js} +2 -2
- package/dist/shared/r1fdd0f6.js +19 -0
- package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
- package/dist/shared/rhs3xe4k.js +7 -0
- package/dist/shared/{jsd07prz.js → rpycarcr.js} +6 -6
- package/dist/shared/{xsq7v6wp.js → sbdme3vy.js} +14 -2
- package/dist/shared/{xdpj5kkm.js → t3g0k88x.js} +775 -264
- package/dist/shared/{ayz64vzj.js → ttmg09vd.js} +708 -19
- package/dist/shared/{r6pj4fq2.js → venqrjna.js} +8 -4
- package/dist/shared/{fancm5pt.js → vjpnzwjz.js} +138 -15
- package/dist/shared/{9tgmr1gh.js → vmfkhszw.js} +44 -9
- package/dist/shared/{wqem4jed.js → wkme6tep.js} +11 -9
- package/dist/shared/{qy9rdq0g.js → wt5rzv1h.js} +15 -11
- package/dist/shared/wxr8cwns.js +6 -0
- package/dist/shared/{bzsjtd7c.js → wynzeggs.js} +19 -14
- package/dist/shared/x3jc53kg.js +220 -0
- package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
- package/dist/shared/{fyqkk1m0.js → xq0mvhts.js} +10 -9
- package/dist/shared/xqve9598.js +53 -0
- package/dist/shared/{47xzb9fc.js → y2t3jktd.js} +2 -2
- package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
- package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
- package/dist/shared/{h8ne16fq.js → ys0xzyaj.js} +1 -1
- package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
- package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
- package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
- package/dist/shared/{fb00y8fp.js → zm0mafk4.js} +12 -6
- package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
- package/dist/tooling/cli.js +97 -24
- package/dist/tooling/migrate/apply.d.ts +14 -0
- package/dist/tooling/migrate/check.d.ts +3 -4
- package/dist/tooling/migrate/classify.d.ts +4 -0
- package/dist/tooling/migrate/commands.d.ts +1 -2
- package/dist/tooling/migrate/config.d.ts +3 -2
- package/dist/tooling/migrate/drift.d.ts +1 -1
- package/dist/tooling/migrate/estimate.d.ts +14 -1
- package/dist/tooling/migrate/files.d.ts +11 -1
- package/dist/tooling/migrate/index.js +17 -16
- package/dist/tooling/migrate/lint.d.ts +4 -4
- package/dist/tooling/migrate/lost.d.ts +28 -0
- package/dist/tooling/migrate/paths.d.ts +14 -0
- package/dist/tooling/migrate/plan.d.ts +10 -3
- package/dist/tooling/migrate/policy.d.ts +11 -0
- package/dist/tooling/migrate/runner.d.ts +25 -0
- package/dist/tooling/migrate/sandbox.d.ts +57 -0
- package/dist/tooling/migrate/sql-lex.d.ts +39 -0
- package/dist/tooling/migrate/text.d.ts +42 -0
- package/dist/tooling/node-version.d.ts +21 -0
- package/dist/tooling/testing/factories.d.ts +10 -0
- package/dist/tooling/testing/facts.d.ts +33 -5
- package/dist/tooling/testing/index.d.ts +1 -1
- package/dist/tooling/testing/index.js +3 -3
- package/dist/tooling/testing/isolation.d.ts +15 -8
- package/package.json +10 -6
- package/dist/shared/0v1g424p.js +0 -26
- package/dist/shared/r73pt594.js +0 -426
- package/dist/shared/rasvvzgz.js +0 -21
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
okmodel is a catalog-first TypeScript ORM, PostgreSQL first. Other SQL databases are the direction. CI runs PostgreSQL 15 to 18. The schema is the single source. Migrations and queries come from it.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Apache-2.0. The published version is [okmodel on npm](https://www.npmjs.com/package/okmodel).
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -80,9 +80,11 @@ bun add okmodel @electric-sql/pglite
|
|
|
80
80
|
|
|
81
81
|
Bun.sql needs no extra package: `import { connect } from "okmodel/pg/bun"`. okmodel has no runtime dependencies. Each installed driver is an optional peer.
|
|
82
82
|
|
|
83
|
+
The `okm` bin's shebang is node, so `bunx okm` runs it with Node even inside a Bun project. Run `bun --bun okm` when the command must use the Bun runtime (Bun.sql runs only there). A `.ts` config file under plain Node needs Node 22.18 or newer for type stripping.
|
|
84
|
+
|
|
83
85
|
## Quickstart
|
|
84
86
|
|
|
85
|
-
This builds two tables, applies them, and runs one script. The script writes an author and a note, then reads the note with its author. `DATABASE_URL` is a direct Postgres URL, not a pooler.
|
|
87
|
+
This builds two tables, applies them, and runs one script. The script writes an author and a note, then reads the note with its author. `DATABASE_URL` is a direct Postgres URL, not a pooler. Port `6432` and port `6543` count as a pooler. Apply, seed, and check also refuse when the backend pid changes between two statements (OKM1854), unless `--allow-pooler` is set.
|
|
86
88
|
|
|
87
89
|
### Configure
|
|
88
90
|
|
|
@@ -343,12 +345,12 @@ if (row?.title !== "hello" || row.author?.name !== "Ada") {
|
|
|
343
345
|
|
|
344
346
|
### One
|
|
345
347
|
|
|
346
|
-
`one` returns the
|
|
348
|
+
`one` returns the note for that id, or null when none matches. More than one match throws `not_unique`.
|
|
347
349
|
|
|
348
350
|
```ts
|
|
349
|
-
// One row. More than one match throws.
|
|
351
|
+
// One row, or null. More than one match throws.
|
|
350
352
|
const single = await db.notes.one({ where: { id: note.id } });
|
|
351
|
-
if (single.title !== "hello") throw new Error("one did not return the note");
|
|
353
|
+
if (single === null || single.title !== "hello") throw new Error("one did not return the note");
|
|
352
354
|
```
|
|
353
355
|
|
|
354
356
|
### Count
|
|
@@ -373,7 +375,7 @@ if (!(await db.notes.exists({ where: { id: note.id } }))) {
|
|
|
373
375
|
|
|
374
376
|
### Update
|
|
375
377
|
|
|
376
|
-
`update` changes the columns in `set` on the rows that match `where`. A
|
|
378
|
+
`update` changes the columns in `set` on the rows that match `where`. A write with no effective predicate, including undefined values, is OKM1102. An empty `or()` branch is OKM1121. `.all(reason)` is the form that means every row.
|
|
377
379
|
|
|
378
380
|
```ts
|
|
379
381
|
// where picks the row. set names the new title.
|
|
@@ -382,7 +384,7 @@ await db.notes.update({ where: { id: note.id }, set: { title: "next" } });
|
|
|
382
384
|
|
|
383
385
|
### Delete
|
|
384
386
|
|
|
385
|
-
`delete` removes the rows that match `where`. A
|
|
387
|
+
`delete` removes the rows that match `where`. A write with no effective predicate, including undefined values, is OKM1102. An empty `or()` branch is OKM1121. `.all(reason)` is the form that means every row.
|
|
386
388
|
|
|
387
389
|
```ts
|
|
388
390
|
// where picks the row. Without it the call is refused.
|
|
@@ -396,6 +398,8 @@ Inserting Ada again fails because the name is unique. `safe` returns that `OkmEr
|
|
|
396
398
|
```ts
|
|
397
399
|
import { OkmError, safe } from "okmodel";
|
|
398
400
|
|
|
401
|
+
import { db } from "./db.ts";
|
|
402
|
+
|
|
399
403
|
// safe returns the unique error instead of throwing.
|
|
400
404
|
const duplicate = await safe(db.authors.insert({ name: "Ada" }));
|
|
401
405
|
if (duplicate.ok || !(duplicate.error instanceof OkmError) || duplicate.error.kind !== "unique") {
|
|
@@ -429,7 +433,7 @@ export const app = schema({ tables: [tasks] });
|
|
|
429
433
|
|
|
430
434
|
### Hidden
|
|
431
435
|
|
|
432
|
-
A password hash should not come back on an ordinary read. `.hidden()` leaves the column out of `find` and out of `include
|
|
436
|
+
A password hash should not come back on an ordinary read, and a caller should not be able to filter on it. `.hidden()` leaves the column out of `find` and out of `include`, and `where` and `orderBy` refuse it (OKM1120). Name it in `select` when that call needs it. `hidden({ filterable: true })` allows `where` and `orderBy` and still leaves it out of the default row. `filters()` still refuses a hidden column.
|
|
433
437
|
|
|
434
438
|
`hidden.ts`:
|
|
435
439
|
|
|
@@ -709,7 +713,7 @@ export async function hide(url: string, id: string): Promise<void> {
|
|
|
709
713
|
|
|
710
714
|
### Tenancy
|
|
711
715
|
|
|
712
|
-
Every note belongs to one tenant. Column tenancy adds `tenantId` and puts that tenant on every read and write. `db.for({ tenantId }).notes.find({ limit: 20 })` reads one tenant. `global("shared")` marks a table every tenant can read, such as a list of countries.
|
|
716
|
+
Every note belongs to one tenant. Column tenancy adds `tenantId` and puts that tenant on every read and write. `db.for({ tenantId }).notes.find({ limit: 20 })` reads one tenant. Composite tenancy passes every key, `compositeTenancy({ key: ["organizationId", "workspaceId"], type: "uuid" })` and `db.for({ organizationId, workspaceId })`. A table without its own key uses `tenancy: via("project.organization")`, up to three relations, and reads and writes follow that path. `global("shared")` marks a table every tenant can read, such as a list of countries. `rlsTenancy({ key: "tenantId", type: "uuid" })` keeps that predicate and adds row-level security policies. A role that owns the tables, a superuser, or `BYPASSRLS` is refused on the first scoped call. See [known limits](https://github.com/omqkhafi/okmodel/blob/main/docs/known-limits.md).
|
|
713
717
|
|
|
714
718
|
`tenancy.ts`:
|
|
715
719
|
|
|
@@ -828,7 +832,7 @@ export const app = schema({
|
|
|
828
832
|
|
|
829
833
|
### Views
|
|
830
834
|
|
|
831
|
-
A view is a saved query. You give it the SQL and the columns that query returns. `db.views.active.find({ limit: 20 })` reads `active`. That handle has no insert or update. There is no query-builder form yet. `okm check` reprints the query on the connected server before it compares, so a difference that is only how that server prints the query is not drift. A materialized view that refreshes while people are reading it needs a unique index, or the declaration is OKM1822. See [known limits](https://github.com/omqkhafi/okmodel/blob/main/docs/known-limits.md).
|
|
835
|
+
A view is a saved query. You give it the SQL and the columns that query returns. `db.views.active.find({ limit: 20 })` reads `active`. That handle has no insert or update. View handles are camelCased: a view named `active_projects` reads as `db.views.activeProjects`. There is no query-builder form yet. `okm check` reprints the query on the connected server before it compares, so a difference that is only how that server prints the query is not drift. A materialized view refreshes with `REFRESH MATERIALIZED VIEW`: the plan populates it with a plain refresh first (Postgres rejects `CONCURRENTLY` on an empty view), then refreshes concurrently when the declaration sets `refresh: "concurrently"`. A materialized view that refreshes while people are reading it needs a unique index, or the declaration is OKM1822. See [known limits](https://github.com/omqkhafi/okmodel/blob/main/docs/known-limits.md).
|
|
832
836
|
|
|
833
837
|
`views.ts`:
|
|
834
838
|
|
|
@@ -862,7 +866,13 @@ export const app = schema({
|
|
|
862
866
|
|
|
863
867
|
### Roles and grants
|
|
864
868
|
|
|
865
|
-
The migration connects as one role and the app connects as another. `roles` names both. The app role receives select, insert, update, and delete on tables and views, select on materialized views, execute on functions, and usage plus select on sequences. Apply runs as the migration role, and switches to it with `SET ROLE` when the connection is someone else.
|
|
869
|
+
The migration connects as one role and the app connects as another. `roles` names both. The app role receives select, insert, update, and delete on tables and views, select on materialized views, execute on functions, and usage plus select on sequences. Apply also grants that role select on `okm_meta`, and nothing else there, so `connect()` can read the stored hash. Apply runs as the migration role, and switches to it with `SET ROLE` when the connection is someone else. A role you already created is external, unless you list it in `managed`.
|
|
870
|
+
|
|
871
|
+
`roles.reporting` is a third role that can only select. It gets select on tables, views, and materialized views, usage on schemas, and the same select on tables created later. It does not get insert, update, delete, or execute. A hidden column is left out unless `roles.columns` names it. Under `rlsTenancy()` this role is not `BYPASSRLS` and it is not tenant-safe against its own SQL: it sees no tenant rows until the session sets `app.tenant`, and it can set `app.unscoped` itself and then read every tenant. `unscoped()` is the application client's read escape and is not given to this role.
|
|
872
|
+
|
|
873
|
+
`roles.columns` grants select or update on named columns: `{ role, on, select, update }`. `on` is the table or view. A listed column grant on a view replaces select on the whole view for the reporting role.
|
|
874
|
+
|
|
875
|
+
`withOwner(table(...), "ignored")` keeps a table out of diffs, plans, and `okm check`. `"external"` is known and never created, altered, or dropped. A managed table that becomes either stays in the database. Drop it while it is still managed if it should go away. See [known limits](https://github.com/omqkhafi/okmodel/blob/main/docs/known-limits.md).
|
|
866
876
|
|
|
867
877
|
`roles.ts`:
|
|
868
878
|
|
|
@@ -876,7 +886,13 @@ export default defineConfig({
|
|
|
876
886
|
schema: "./schema.ts",
|
|
877
887
|
database: url,
|
|
878
888
|
// Apply uses the migration role. The app uses the app role.
|
|
879
|
-
roles: {
|
|
889
|
+
roles: {
|
|
890
|
+
migration: "okm_migrate",
|
|
891
|
+
app: "okm_app",
|
|
892
|
+
reporting: "okm_report",
|
|
893
|
+
managed: [{ name: "okm_migrate" }, { name: "okm_report", login: true }],
|
|
894
|
+
columns: [{ role: "okm_report", on: "tasks", select: ["title"] }],
|
|
895
|
+
},
|
|
880
896
|
});
|
|
881
897
|
```
|
|
882
898
|
|
|
@@ -945,7 +961,7 @@ update "public"."notes" set "title" = 'noted' where "title" = 'hello' and ("id"
|
|
|
945
961
|
|
|
946
962
|
### Testing
|
|
947
963
|
|
|
948
|
-
`okmodel/testing` opens a real pool. `factories` insert rows, `expectQueries` counts statements, and `isolation()` checks that tenant A cannot see tenant B. `okm seed <file>` calls the file's default export with that harness. The target must already be migrated. A protected target is refused unless `--allow-protected`.
|
|
964
|
+
`okmodel/testing` opens a real pool. `factories` insert rows, `expectQueries` counts statements, and `isolation()` checks that tenant A cannot see the row it inserted for tenant B, including through a tenant view or materialized view. Each call uses a new pair of tenant ids, deletes what it inserted, and names a factory that is missing. A global table or view is skipped and listed. `okm seed <file>` calls the file's default export with that harness. The target must already be migrated. A protected target is refused unless `--allow-protected`.
|
|
949
965
|
|
|
950
966
|
`factory.ts`:
|
|
951
967
|
|
|
@@ -1071,7 +1087,7 @@ The worked example is a project tracker: workspaces as tenants, archive, a funct
|
|
|
1071
1087
|
|
|
1072
1088
|
### Startup
|
|
1073
1089
|
|
|
1074
|
-
`connect()` compares the app with `okm_history`. A database that is ahead by expand migrations still opens. Ahead by a contract migration, or behind the app, is OKM1520 and names the migration. `okm migrate status` prints the same states, plus `failed at step N (resume with okm migrate apply)
|
|
1090
|
+
`connect()` compares the app with `okm_history`. A database that is ahead by expand migrations still opens. Ahead by a contract migration, or behind the app, is OKM1520 and names the migration. `okm migrate status` prints the same states, plus `failed at step N (resume with okm migrate apply)`, where N is the stored step index. Apply's own `failed at step N` counts from 1, the same number the linter prints.
|
|
1075
1091
|
|
|
1076
1092
|
### Protected targets
|
|
1077
1093
|
|
|
@@ -1144,6 +1160,8 @@ Status is on the [board](https://github.com/users/omqkhafi/projects/1). Each rel
|
|
|
1144
1160
|
- [x] [0.3](https://github.com/omqkhafi/okmodel/milestone/3) — Extensions, domains, functions, triggers, views, roles, and grants.
|
|
1145
1161
|
- [x] [0.4](https://github.com/omqkhafi/okmodel/milestone/4) — Safer migration plans, backfill, drift checks, provisioning, reference data, and a testing package.
|
|
1146
1162
|
- [x] [0.5](https://github.com/omqkhafi/okmodel/milestone/5) — A primary with replicas, read routing, and a reference app.
|
|
1163
|
+
- [x] [0.5.1](https://github.com/omqkhafi/okmodel/milestone/7) — QA fixes for 0.5.0.
|
|
1164
|
+
- [x] [0.6](https://github.com/omqkhafi/okmodel/milestone/8) — Composite, path, and row-level tenancy, column grants, a reporting role, and SQL constraint names.
|
|
1147
1165
|
|
|
1148
1166
|
`okmodel/internal` has no stability promise. Names on that subpath can change or disappear in any release.
|
|
1149
1167
|
|
|
@@ -1155,11 +1173,11 @@ Measured on this release.
|
|
|
1155
1173
|
|
|
1156
1174
|
| | Minified | Gzip | Cold import |
|
|
1157
1175
|
| -------------------------------------------- | -------: | -----: | ----------: |
|
|
1158
|
-
| Runtime entry | 5,288 | 2,026 |
|
|
1159
|
-
| App startup (10 tables, one find) |
|
|
1160
|
-
| App startup, every 0.2 feature in use (full) |
|
|
1176
|
+
| Runtime entry | 5,288 | 2,026 | 1.790 ms |
|
|
1177
|
+
| App startup (10 tables, one find) | 90,762 | 30,139 | 10.490 ms |
|
|
1178
|
+
| App startup, every 0.2 feature in use (full) | 121,506 | 39,741 | 12.455 ms |
|
|
1161
1179
|
|
|
1162
|
-
The full app has column tenancy, `archivable()`, `timestamps()`, validation rules, `one`, `many` and `manyThrough` relations, presets, and calls `include`, `page`, `aggregate`, `tx` and `batch`. A feature costs bytes only in an app that uses it: the full app is 30,
|
|
1180
|
+
The full app has column tenancy, `archivable()`, `timestamps()`, validation rules, `one`, `many` and `manyThrough` relations, presets, and calls `include`, `page`, `aggregate`, `tx` and `batch`. A feature costs bytes only in an app that uses it: the full app is 30,744 minified and 9,602 gzip bytes above the plain one. The runtime entry and the plain app are gated. The full app is printed, not gated. The plain app's cold import on this sample is under the 15 ms local reference. That figure is printed and is not a gate.
|
|
1163
1181
|
|
|
1164
1182
|
The rest of the measurements are in [size](https://github.com/omqkhafi/okmodel/blob/main/docs/size.md).
|
|
1165
1183
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refusal for a driver that cannot cancel an in-flight statement (D224).
|
|
3
|
+
*
|
|
4
|
+
* Bun.sql and PGlite throw OKM1120 for a positive `timeout`. The message
|
|
5
|
+
* stays `timeout` because the PGlite connect graph has two gzip bytes left.
|
|
6
|
+
* Kind is `invalid`, which separates it from kind `timeout`. `timeout: 0`
|
|
7
|
+
* still fails immediately as kind `timeout`. `signal` is unchanged.
|
|
8
|
+
* postgres.js and node-postgres do not import this.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Throws OKM1120 when `timeout` is a positive number.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Per-call options. Absent, and a non-positive timeout, pass through
|
|
14
|
+
*/
|
|
15
|
+
export declare function noTimeout(options: {
|
|
16
|
+
readonly timeout?: number;
|
|
17
|
+
} | undefined): void;
|
|
@@ -42,6 +42,28 @@ export declare function describe(sql: Sql, text: string, params?: readonly WireV
|
|
|
42
42
|
* @returns Chunks of wire rows
|
|
43
43
|
*/
|
|
44
44
|
export declare function stream(sql: Sql, text: string, params?: readonly WireValue[]): AsyncIterable<readonly (readonly WireValue[])[]>;
|
|
45
|
+
type Lost = {
|
|
46
|
+
on: boolean;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Rejects later writes once postgres.js reports the reserved socket closed.
|
|
50
|
+
*
|
|
51
|
+
* The first statement installs `onclose`. The checkout guard then refuses
|
|
52
|
+
* the next statement, savepoint, or COMMIT before `socket.write`.
|
|
53
|
+
*
|
|
54
|
+
* @param sql - Reserved connection
|
|
55
|
+
* @param lost - Set when that socket closes
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Sets `lost` when the reserved socket closes.
|
|
59
|
+
*
|
|
60
|
+
* The first statement installs `onclose`. Later statements see the flag
|
|
61
|
+
* before they write.
|
|
62
|
+
*
|
|
63
|
+
* @param sql - Reserved connection
|
|
64
|
+
* @param lost - Set when that socket closes
|
|
65
|
+
*/
|
|
66
|
+
export declare function watchReserved(sql: Sql, lost: Lost): void;
|
|
45
67
|
/** Refcount a checkout or a listener borrows from the pool. */
|
|
46
68
|
type Gate = {
|
|
47
69
|
enter(): void;
|
|
@@ -67,11 +67,13 @@ export declare function catalogHash(source: Catalog): string;
|
|
|
67
67
|
*
|
|
68
68
|
* Roles, grants, and default privileges come from the migration config, not
|
|
69
69
|
* the schema, so they are left out. A `schema()` catalog never has them, so
|
|
70
|
-
* `connect()` hashes it unchanged. `okm check` still verifies them.
|
|
71
|
-
*
|
|
70
|
+
* `connect()` hashes it unchanged. `okm check` still verifies them. An
|
|
71
|
+
* `external` or `ignored` owner is planning state. The startup copy writes
|
|
72
|
+
* it back to `managed`, which is what `schema()` stored. A catalog with
|
|
73
|
+
* neither privileges nor those owners is returned as is.
|
|
72
74
|
*
|
|
73
75
|
* @param source - Catalog document
|
|
74
|
-
* @returns The catalog
|
|
76
|
+
* @returns The catalog `connect()` hashes
|
|
75
77
|
*/
|
|
76
78
|
export declare function startupCatalog(source: Catalog): Catalog;
|
|
77
79
|
/**
|
|
@@ -89,11 +91,23 @@ export declare function startupHash(source: Catalog): string;
|
|
|
89
91
|
* stored hash and the version, then trusts the objects (D133). `okm check`
|
|
90
92
|
* and dev keep {@link parseCatalog}.
|
|
91
93
|
*
|
|
92
|
-
* @param text -
|
|
93
|
-
* @param hash - SHA-256 of
|
|
94
|
+
* @param text - Catalog JSON. A BOM is stripped and `\r` is removed before the hash check
|
|
95
|
+
* @param hash - SHA-256 of that normalized text, stored beside the artifact
|
|
94
96
|
* @returns The catalog document
|
|
95
97
|
*/
|
|
96
98
|
export declare function loadTrustedCatalog(text: string, hash: string): Catalog;
|
|
99
|
+
/**
|
|
100
|
+
* Catalog file bytes the hash covers.
|
|
101
|
+
*
|
|
102
|
+
* A leading UTF-8 BOM is dropped. Carriage returns are removed, so a CRLF
|
|
103
|
+
* checkout hashes like the LF file the writer stored. An LF file is unchanged.
|
|
104
|
+
* UTF-16 is OKM1027.
|
|
105
|
+
*
|
|
106
|
+
* @param text - Raw catalog JSON or hash file
|
|
107
|
+
* @param label - Name used when the text is UTF-16
|
|
108
|
+
* @returns Text to hash and parse
|
|
109
|
+
*/
|
|
110
|
+
export declare function catalogFileText(text: string, label?: string): string;
|
|
97
111
|
/**
|
|
98
112
|
* Reads canonical catalog JSON.
|
|
99
113
|
*
|
|
@@ -106,7 +106,8 @@ export declare function index(input: IndexInput): IndexObject;
|
|
|
106
106
|
* Builds a constraint object.
|
|
107
107
|
*
|
|
108
108
|
* Primary key names are `{parent}_pkey` and do not include column names.
|
|
109
|
-
*
|
|
109
|
+
* Unique and foreign-key names use every SQL column. Checks use `nameKey`,
|
|
110
|
+
* which stays fixed when a field is renamed. `nameKey` is always stored.
|
|
110
111
|
*
|
|
111
112
|
* @param input - Parent, kind, columns, and the stable name key
|
|
112
113
|
* @returns A constraint envelope
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row-level security policy catalog objects.
|
|
3
|
+
*
|
|
4
|
+
* The factory stores the record. It does not plan SQL. Postgres DDL stays in
|
|
5
|
+
* the dialect. A schema that never imports `rlsTenancy` does not load this.
|
|
6
|
+
*/
|
|
7
|
+
import type { ObjectIdentity, ObjectRef, Owner, PolicyCommand, PolicyObject, Provenance } from "./types.js";
|
|
8
|
+
/** Input for {@link policyObject}. */
|
|
9
|
+
export type PolicyInput = {
|
|
10
|
+
readonly parent: ObjectRef;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly command: PolicyCommand;
|
|
13
|
+
readonly expression: string;
|
|
14
|
+
/** True when the table is `FORCE ROW LEVEL SECURITY`. */
|
|
15
|
+
readonly force: boolean;
|
|
16
|
+
readonly owner?: Owner;
|
|
17
|
+
readonly provenance: Provenance;
|
|
18
|
+
/** Extra edges. The parent table is always included. */
|
|
19
|
+
readonly dependencies?: readonly ObjectIdentity[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Predicate stored for `{table}_tenant`.
|
|
23
|
+
*
|
|
24
|
+
* The text is what `pg_get_expr` prints, so a replayed catalog matches.
|
|
25
|
+
* An unset `app.tenant` becomes null and the comparison matches no row.
|
|
26
|
+
*
|
|
27
|
+
* @param column - SQL name of the tenant column
|
|
28
|
+
* @param dataType - Postgres type of that column
|
|
29
|
+
* @returns The `USING` and `WITH CHECK` expression
|
|
30
|
+
*/
|
|
31
|
+
export declare function tenantPolicyExpression(column: string, dataType: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Predicate stored for `{table}_unscoped_select`.
|
|
34
|
+
*
|
|
35
|
+
* `SELECT` only. Writes stay on the tenant policy.
|
|
36
|
+
*
|
|
37
|
+
* @returns The `USING` expression
|
|
38
|
+
*/
|
|
39
|
+
export declare function unscopedPolicyExpression(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Builds a policy record.
|
|
42
|
+
*
|
|
43
|
+
* Identity is `(parent, name)`. A name past the dialect limit is fitted.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Parent table, command, and expression
|
|
46
|
+
* @returns The catalog object
|
|
47
|
+
*/
|
|
48
|
+
export declare function policyObject(input: PolicyInput): PolicyObject;
|
|
@@ -393,6 +393,13 @@ export type ViewColumn = {
|
|
|
393
393
|
export type ViewDefinition = {
|
|
394
394
|
readonly columns: readonly ViewColumn[];
|
|
395
395
|
readonly query: string;
|
|
396
|
+
/**
|
|
397
|
+
* `WITH (security_invoker = true)`.
|
|
398
|
+
*
|
|
399
|
+
* Set only for a view over tenant tables under `rlsTenancy`. Omitted
|
|
400
|
+
* otherwise, so a view under another strategy keeps the same catalog bytes.
|
|
401
|
+
*/
|
|
402
|
+
readonly securityInvoker?: true;
|
|
396
403
|
};
|
|
397
404
|
/**
|
|
398
405
|
* Materialized view definition.
|
|
@@ -440,10 +447,27 @@ export type RoleObject = CatalogEnvelope<"role", RoleIdentity, RoleDefinition>;
|
|
|
440
447
|
export type GrantObject = CatalogEnvelope<"grant", GrantIdentity, GrantDefinition>;
|
|
441
448
|
/** A default privilege. Identity is `(forRole, namespace, objectKind, grantee, privilege)`. */
|
|
442
449
|
export type DefaultPrivilegeObject = CatalogEnvelope<"defaultPrivilege", DefaultPrivilegeIdentity, DefaultPrivilegeDefinition>;
|
|
450
|
+
/** Which statements a policy covers. `all` is every command. `select` is reads. */
|
|
451
|
+
export type PolicyCommand = "all" | "select";
|
|
452
|
+
/**
|
|
453
|
+
* One row-level security policy.
|
|
454
|
+
*
|
|
455
|
+
* `expression` is the `USING` text, and the `WITH CHECK` text when `command`
|
|
456
|
+
* is `all`. `force` is true when the table is `FORCE ROW LEVEL SECURITY`.
|
|
457
|
+
*/
|
|
458
|
+
export type PolicyDefinition = {
|
|
459
|
+
readonly command: PolicyCommand;
|
|
460
|
+
readonly expression: string;
|
|
461
|
+
readonly force: boolean;
|
|
462
|
+
};
|
|
463
|
+
/** A policy. Identity is `(parent, name)`. */
|
|
464
|
+
export type PolicyObject = CatalogEnvelope<"policy", AnchoredIdentity & {
|
|
465
|
+
readonly kind: "policy";
|
|
466
|
+
}, PolicyDefinition>;
|
|
443
467
|
/**
|
|
444
468
|
* One built catalog object.
|
|
445
469
|
*/
|
|
446
|
-
export type CatalogObject = TableObject | ColumnObject | IndexObject | ConstraintObject | SequenceObject | TypeObject | ExtensionObject | FunctionObject | TriggerObject | ViewObject | MaterializedViewObject | RoleObject | GrantObject | DefaultPrivilegeObject;
|
|
470
|
+
export type CatalogObject = TableObject | ColumnObject | IndexObject | ConstraintObject | SequenceObject | TypeObject | ExtensionObject | FunctionObject | TriggerObject | ViewObject | MaterializedViewObject | RoleObject | GrantObject | DefaultPrivilegeObject | PolicyObject;
|
|
447
471
|
/** Format version stored in the serialized catalog. */
|
|
448
472
|
export declare const CATALOG_VERSION = 1;
|
|
449
473
|
/** A catalog document. Object order is identity-key order. */
|
|
@@ -11,6 +11,8 @@ export type ViewInput = {
|
|
|
11
11
|
readonly name: string;
|
|
12
12
|
readonly columns: readonly ViewColumn[];
|
|
13
13
|
readonly query: string;
|
|
14
|
+
/** `WITH (security_invoker = true)`. Omitted from the record when unset. */
|
|
15
|
+
readonly securityInvoker?: true;
|
|
14
16
|
readonly owner?: Owner;
|
|
15
17
|
readonly provenance: Provenance;
|
|
16
18
|
readonly dependencies?: readonly ObjectIdentity[];
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
* @returns `file.ts:line`, or `undefined` when the runtime has no stack
|
|
15
15
|
*/
|
|
16
16
|
export declare function callerLocation(skip: number): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* File and line from stack lines, skipping `location.ts` frames.
|
|
19
|
+
*
|
|
20
|
+
* A frame may use `/` or `\`.
|
|
21
|
+
*
|
|
22
|
+
* @param lines - `Error.stack` split on `\n`
|
|
23
|
+
* @param skip - Callers to pass before the frame that is recorded
|
|
24
|
+
* @returns `file.ts:line`, or `undefined` when no frame remains
|
|
25
|
+
*/
|
|
26
|
+
export declare function locationFromLines(lines: readonly string[], skip: number): string | undefined;
|
|
17
27
|
/**
|
|
18
28
|
* Appends a source location to an error message.
|
|
19
29
|
*
|
|
@@ -207,6 +207,12 @@ export type ColumnState<TValue> = {
|
|
|
207
207
|
} | undefined;
|
|
208
208
|
readonly guarded: boolean;
|
|
209
209
|
readonly hidden: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Hidden, and allowed in `where` and `orderBy`.
|
|
212
|
+
*
|
|
213
|
+
* Absent otherwise. Not part of the catalog or its hash.
|
|
214
|
+
*/
|
|
215
|
+
readonly filterable?: true;
|
|
210
216
|
/** Redacted in logs, errors, and `inspect()`. Not part of the catalog hash. */
|
|
211
217
|
readonly sensitive: boolean;
|
|
212
218
|
readonly omitWrite: boolean;
|
|
@@ -383,11 +389,16 @@ export declare class ColumnBuilder<TValue, TFlags extends ColumnFlags> {
|
|
|
383
389
|
/**
|
|
384
390
|
* Excludes the column from the default row.
|
|
385
391
|
*
|
|
386
|
-
* A named `select` still returns it. Includes never do.
|
|
392
|
+
* A named `select` still returns it. Includes never do. `where` and
|
|
393
|
+
* `orderBy` refuse it (OKM1120) unless `filterable` is set. `filters()`
|
|
394
|
+
* still refuses it.
|
|
387
395
|
*
|
|
396
|
+
* @param options - `filterable` allows the column in `where` and `orderBy`
|
|
388
397
|
* @returns The same column, hidden
|
|
389
398
|
*/
|
|
390
|
-
hidden(
|
|
399
|
+
hidden(options?: {
|
|
400
|
+
readonly filterable?: boolean;
|
|
401
|
+
}): ColumnBuilder<TValue, FlagTrue<TFlags, "hidden">>;
|
|
391
402
|
/**
|
|
392
403
|
* Redacts the value in logs, errors, and `inspect()`.
|
|
393
404
|
*
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Planning and scratch comparison both render through here, so a statement
|
|
5
5
|
* has one spelling.
|
|
6
6
|
*/
|
|
7
|
-
import type { Catalog, CatalogObject, ColumnObject, FunctionObject, MaterializedViewObject, SequenceObject, TableObject, ViewObject } from "../../contracts/catalog/types.js";
|
|
7
|
+
import type { Catalog, CatalogObject, ColumnObject, FunctionObject, MaterializedViewObject, PolicyObject, SequenceObject, TableObject, ViewObject } from "../../contracts/catalog/types.js";
|
|
8
8
|
/**
|
|
9
9
|
* Quotes one identifier.
|
|
10
10
|
*
|
|
@@ -23,6 +23,18 @@ export declare function quoteIdent(name: string): string;
|
|
|
23
23
|
* @returns SQL statements
|
|
24
24
|
*/
|
|
25
25
|
export declare function renderCatalog(source: Catalog, schema: string): readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Pulls a unique constraint in front of a foreign key that references it.
|
|
28
|
+
*
|
|
29
|
+
* The primary key is created with the table. A foreign key aimed at another
|
|
30
|
+
* unique has to wait for that unique. Create order ties those two by name, so
|
|
31
|
+
* the foreign key can otherwise come first. The planner uses this for creates
|
|
32
|
+
* and for the list it reverses when it drops.
|
|
33
|
+
*
|
|
34
|
+
* @param objects - Create order
|
|
35
|
+
* @returns The same objects, with those uniques moved forward
|
|
36
|
+
*/
|
|
37
|
+
export declare function uniqueBeforeForeignKey(objects: readonly CatalogObject[]): readonly CatalogObject[];
|
|
26
38
|
/**
|
|
27
39
|
* Reports whether an index belongs to a view or a materialized view.
|
|
28
40
|
*
|
|
@@ -74,6 +86,30 @@ export declare function functionSql(object: FunctionObject, schema: string, repl
|
|
|
74
86
|
* @returns The statement
|
|
75
87
|
*/
|
|
76
88
|
export declare function viewSql(object: ViewObject, schema: string, replace: boolean): string;
|
|
89
|
+
/** One policy statement and the planner kind that classifies it. */
|
|
90
|
+
export type PolicyPlanStep = {
|
|
91
|
+
readonly sql: string;
|
|
92
|
+
readonly kind: "enable-rls" | "create-policy" | "drop-policy" | "disable-rls";
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* `ENABLE`, `FORCE`, and `CREATE POLICY` for one policy.
|
|
96
|
+
*
|
|
97
|
+
* The `all` command policy also enables and forces row-level security.
|
|
98
|
+
* The `select` policy is the unscoped read. Names are the stored identity.
|
|
99
|
+
*
|
|
100
|
+
* @param object - Policy to create
|
|
101
|
+
* @param schema - Concrete schema name
|
|
102
|
+
* @returns Statements in apply order
|
|
103
|
+
*/
|
|
104
|
+
export declare function policyCreatePlan(object: PolicyObject, schema: string): readonly PolicyPlanStep[];
|
|
105
|
+
/**
|
|
106
|
+
* `DROP POLICY`, and for the `all` command policy `NO FORCE` and `DISABLE`.
|
|
107
|
+
*
|
|
108
|
+
* @param object - Policy to drop
|
|
109
|
+
* @param schema - Concrete schema name
|
|
110
|
+
* @returns Statements in apply order
|
|
111
|
+
*/
|
|
112
|
+
export declare function policyDropPlan(object: PolicyObject, schema: string): readonly PolicyPlanStep[];
|
|
77
113
|
/**
|
|
78
114
|
* `CREATE MATERIALIZED VIEW ... WITH NO DATA`.
|
|
79
115
|
*
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type BuiltSchema } from "./schema.js";
|
|
8
8
|
import { type AnyTable } from "./table.js";
|
|
9
|
+
/**
|
|
10
|
+
* Type arguments read from `t.json<T>()` and `t.jsonb<T>()` in schema source.
|
|
11
|
+
*
|
|
12
|
+
* The key is `table.field`. QA-L3 kept the wrapper for a bare `t.jsonb()`,
|
|
13
|
+
* which has no argument and stays `jsonb<unknown>`. The documented call
|
|
14
|
+
* `t.jsonb<{ published?: boolean }>()` is this map.
|
|
15
|
+
*/
|
|
16
|
+
export type JsonColumnTypes = ReadonlyMap<string, string>;
|
|
9
17
|
/**
|
|
10
18
|
* Emits row, insert, and update interfaces for every table.
|
|
11
19
|
*
|
|
@@ -13,6 +21,16 @@ import { type AnyTable } from "./table.js";
|
|
|
13
21
|
* and `TasksUpdate`.
|
|
14
22
|
*
|
|
15
23
|
* @param source - Schema from {@link schema}
|
|
24
|
+
* @param jsonTypes - Type arguments from the schema source. Omitted, json and jsonb stay `unknown`
|
|
16
25
|
* @returns Declaration text
|
|
17
26
|
*/
|
|
18
|
-
export declare function emitRowTypes(source: BuiltSchema<readonly AnyTable[]
|
|
27
|
+
export declare function emitRowTypes(source: BuiltSchema<readonly AnyTable[]>, jsonTypes?: JsonColumnTypes): string;
|
|
28
|
+
/**
|
|
29
|
+
* Reads `t.json<T>()` and `t.jsonb<T>()` type arguments from schema source.
|
|
30
|
+
*
|
|
31
|
+
* A bare `t.jsonb()` has no argument. The key is the table name and the field.
|
|
32
|
+
*
|
|
33
|
+
* @param source - Schema source, comments removed by the caller or still present
|
|
34
|
+
* @returns `table.field` to the type argument text
|
|
35
|
+
*/
|
|
36
|
+
export declare function jsonColumnTypes(source: string): JsonColumnTypes;
|
|
@@ -2,9 +2,30 @@
|
|
|
2
2
|
* Maps a Postgres {@link DriverError} to an {@link OkmError} (spec §4.2, §14).
|
|
3
3
|
*
|
|
4
4
|
* Adapters normalise the driver into `DriverError`. They do not construct
|
|
5
|
-
* `OkmError`.
|
|
5
|
+
* `OkmError`. `connect()` passes its schema. A reason then comes from the
|
|
6
|
+
* error's columns matched to a catalog object. With no catalog, the
|
|
7
|
+
* constraint name is parsed.
|
|
6
8
|
*/
|
|
9
|
+
import type { Catalog } from "../../contracts/catalog/types.js";
|
|
7
10
|
import { OkmError, type ErrorStatuses } from "../../contracts/error.js";
|
|
11
|
+
/**
|
|
12
|
+
* Catalog and model used to name a unique, foreign key, or check.
|
|
13
|
+
*
|
|
14
|
+
* `connect()` passes the schema it already holds. The reference is enough:
|
|
15
|
+
* matching stays in this module.
|
|
16
|
+
*/
|
|
17
|
+
export type MapPostgresErrorSchema = {
|
|
18
|
+
readonly catalog: Catalog;
|
|
19
|
+
readonly model: Readonly<Record<string, {
|
|
20
|
+
readonly sql: string;
|
|
21
|
+
readonly columns: readonly SqlField[];
|
|
22
|
+
}>>;
|
|
23
|
+
};
|
|
24
|
+
/** One column the reason mapper can turn back into a TypeScript field. */
|
|
25
|
+
type SqlField = {
|
|
26
|
+
readonly field: string;
|
|
27
|
+
readonly sql: string;
|
|
28
|
+
};
|
|
8
29
|
/** Options for {@link mapPostgresError}. */
|
|
9
30
|
export type MapPostgresErrorOptions = {
|
|
10
31
|
/**
|
|
@@ -15,6 +36,15 @@ export type MapPostgresErrorOptions = {
|
|
|
15
36
|
readonly includeValues?: boolean;
|
|
16
37
|
/** Category overrides from `connect({ errors })`. */
|
|
17
38
|
readonly http?: ErrorStatuses;
|
|
39
|
+
/**
|
|
40
|
+
* Schema whose catalog still has the stored name key.
|
|
41
|
+
*
|
|
42
|
+
* Columns in the error select the object. The stored name key is the
|
|
43
|
+
* reason. A composite key that is only the SQL column list becomes the
|
|
44
|
+
* TypeScript fields. With no columns, the stored name key is the reason.
|
|
45
|
+
* An index keeps its full name.
|
|
46
|
+
*/
|
|
47
|
+
readonly schema?: MapPostgresErrorSchema;
|
|
18
48
|
};
|
|
19
49
|
/**
|
|
20
50
|
* Maps a thrown driver error into an {@link OkmError}.
|
|
@@ -28,3 +58,4 @@ export type MapPostgresErrorOptions = {
|
|
|
28
58
|
* @returns A categorised error
|
|
29
59
|
*/
|
|
30
60
|
export declare function mapPostgresError(error: unknown, options?: MapPostgresErrorOptions): OkmError;
|
|
61
|
+
export {};
|
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Loaded the first time `parse()` is called. `filters()` validates the spec
|
|
5
5
|
* before this module exists.
|
|
6
|
+
*
|
|
7
|
+
* A request key is equality when the value is a scalar and `eq` is allowed.
|
|
8
|
+
* An object value names operators `parse` applies. `sort` is a field name
|
|
9
|
+
* from the sort list. A relation key becomes `has` over the allowlisted fields.
|
|
10
|
+
* A column key wins when the same name is in `allow` and `relations`.
|
|
6
11
|
*/
|
|
7
12
|
import type { ParsedFilters } from "./table.js";
|
|
8
13
|
/**
|
|
9
14
|
* Reads one request object.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
16
|
+
* Lookups use own properties, so `constructor`, `__proto__`, `toString`, and
|
|
17
|
+
* `hasOwnProperty` are ordinary keys. A key that is not in `allow` is OKM1123.
|
|
18
|
+
* `sort` is the exception.
|
|
13
19
|
*
|
|
14
20
|
* @param allow - Fields and the operators each one accepts
|
|
15
21
|
* @param sort - Fields a caller may sort by
|
|
16
22
|
* @param query - One request object
|
|
23
|
+
* @param relations - Relation name to fields, when `filters` passed any
|
|
17
24
|
* @returns Filters a caller can spread into `find`
|
|
18
25
|
*/
|
|
19
|
-
export declare function parseQuery(allow: Readonly<Record<string, readonly string[]>>, sort: readonly string[], query: unknown): ParsedFilters;
|
|
26
|
+
export declare function parseQuery(allow: Readonly<Record<string, readonly string[]>>, sort: readonly string[], query: unknown, relations?: Readonly<Record<string, readonly string[]>>): ParsedFilters;
|