okmodel 0.3.0 → 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 +182 -22
- package/dist/contracts/catalog/document.d.ts +6 -2
- 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/index.js +3 -3
- package/dist/dialects/pg/role/sql.d.ts +11 -4
- package/dist/dialects/pg/table.d.ts +17 -1
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +3 -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 +2 -2
- package/dist/runtime/pg/pg.js +2 -2
- package/dist/runtime/pg/pglite.js +2 -2
- package/dist/runtime/pg/postgresjs.js +2 -2
- package/dist/runtime/traits/index.js +5 -4
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
- package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
- package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
- package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
- package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
- package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
- package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
- package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
- package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
- package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
- package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
- package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
- package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
- package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
- package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
- package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
- package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
- package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
- package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
- package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
- package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
- package/dist/tooling/cli.js +46 -17
- package/dist/tooling/migrate/apply.d.ts +59 -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 -2
- package/dist/tooling/migrate/config.d.ts +22 -0
- package/dist/tooling/migrate/doctor.d.ts +3 -0
- package/dist/tooling/migrate/drift.d.ts +18 -2
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/extensions.d.ts +2 -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 +1 -1
- package/dist/tooling/migrate/plan.d.ts +64 -9
- package/dist/tooling/migrate/project.d.ts +42 -7
- 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/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 +6 -2
- package/dist/shared/dbjej1tw.js +0 -456
- package/dist/shared/g4f5kcx9.js +0 -37
- package/dist/shared/qbxzvwwc.js +0 -88
- package/dist/shared/zwmqxb0g.js +0 -79
- package/dist/shared/{xva8v7qk.js → awrsqrn7.js} +3 -3
- package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
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
|
-
Version 0.
|
|
5
|
+
Version 0.4.0. Apache-2.0.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -54,7 +54,14 @@ Version 0.3.0. Apache-2.0.
|
|
|
54
54
|
- [Views](#views)
|
|
55
55
|
- [Roles and grants](#roles-and-grants)
|
|
56
56
|
- [okm ext and okm doctor](#okm-ext-and-okm-doctor)
|
|
57
|
+
- [Linter](#linter)
|
|
58
|
+
- [Reference data](#reference-data)
|
|
59
|
+
- [Backfill](#backfill)
|
|
60
|
+
- [Testing](#testing)
|
|
61
|
+
- [Startup](#startup)
|
|
62
|
+
- [Protected targets](#protected-targets)
|
|
57
63
|
- [Commands](#commands)
|
|
64
|
+
- [Check a history in CI](#check-a-history-in-ci)
|
|
58
65
|
- [Roadmap](#roadmap)
|
|
59
66
|
- [Size](#size)
|
|
60
67
|
- [Docs](#docs)
|
|
@@ -143,13 +150,15 @@ A protected target refuses push. Production settings are in the [production chec
|
|
|
143
150
|
|
|
144
151
|
### Reviewed migrations
|
|
145
152
|
|
|
146
|
-
Generate writes a SQL file and `.okm
|
|
153
|
+
Generate writes a SQL file and `.okm`. On an empty database, apply installs that schema and any reference rows. A database that already has history runs the new file, so you can read it first.
|
|
147
154
|
|
|
148
155
|
```sh
|
|
149
156
|
bunx okm generate init
|
|
150
157
|
bunx okm migrate apply
|
|
151
158
|
```
|
|
152
159
|
|
|
160
|
+
Each step is `expand`, `contract`, or unclassified SQL. Expand is additive. Contract removes or tightens something. On an existing table the planner writes the safe form: a concurrent index, a check or foreign key as `NOT VALID` then `VALIDATE`, and `SET NOT NULL` through a validated check. A backfill step updates rows in batches and resumes after a failure. See [Backfill](#backfill).
|
|
161
|
+
|
|
153
162
|
### One client
|
|
154
163
|
|
|
155
164
|
Import `db` from this file wherever the server handles a request. The process keeps one client.
|
|
@@ -880,25 +889,173 @@ okm doctor
|
|
|
880
889
|
okm doctor OKM1811
|
|
881
890
|
```
|
|
882
891
|
|
|
892
|
+
### Linter
|
|
893
|
+
|
|
894
|
+
A migration plan is checked before it runs. Dropping a table, renaming a column, or adding a unique constraint on a table that already exists is an error. On an existing table the planner writes the safe form: a concurrent index, a check or foreign key as `NOT VALID` then `VALIDATE`, and `SET NOT NULL` through a validated check. The same statements written by hand, without that form, are errors. A type change that rewrites the table stays a warning. `okm generate` prints the finding and still writes the file. `okm migrate plan` and `okm check` exit non-zero on an error. `okm migrate apply` refuses with OKM1510 before any statement. The linter reads the plan and the catalogs. It does not connect. See [the linter](https://github.com/omqkhafi/okmodel/blob/main/docs/linter.md).
|
|
895
|
+
|
|
896
|
+
```text
|
|
897
|
+
error OKM1511 step 1: drops a table -- fix: Stop reading the table in an expand migration, then drop it in a later contract. Or allow OKM1511 with a reason.
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
```sql
|
|
901
|
+
-- okm-allow OKM1511: the table is empty and nothing reads it
|
|
902
|
+
drop table "public"."notes";
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Reference data
|
|
906
|
+
|
|
907
|
+
Rows the application needs are declared on the table. Apply on an empty database inserts a missing key. It does not update or delete. The rows are not part of the catalog hash, so a new key does not need a DDL migration.
|
|
908
|
+
|
|
909
|
+
`reference.ts`:
|
|
910
|
+
|
|
911
|
+
```ts
|
|
912
|
+
import { schema, table, t } from "okmodel/pg";
|
|
913
|
+
|
|
914
|
+
const roles = table(
|
|
915
|
+
"roles",
|
|
916
|
+
{ code: t.text().primaryKey(), label: t.text() },
|
|
917
|
+
{
|
|
918
|
+
reference: {
|
|
919
|
+
key: "code",
|
|
920
|
+
rows: [{ code: "admin", label: "Admin" }],
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
export default schema({ tables: [roles] });
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### Backfill
|
|
929
|
+
|
|
930
|
+
A backfill is one idempotent `UPDATE` in the migration file. `$1` is the exclusive lower bound and `$2` is the inclusive upper bound. Null opens that side. Apply commits one batch at a time and resumes from `okm_backfill`. There is no `okm backfill` command. A protected target refuses the step unless that invocation passes `--allow-protected`.
|
|
931
|
+
|
|
932
|
+
```sql
|
|
933
|
+
-- name: fill
|
|
934
|
+
-- class: expand
|
|
935
|
+
|
|
936
|
+
-- class: expand
|
|
937
|
+
-- action: backfill
|
|
938
|
+
-- lock: ROW EXCLUSIVE
|
|
939
|
+
-- transactional: false
|
|
940
|
+
-- backfill table="public"."notes" key="id" batch=10
|
|
941
|
+
update "public"."notes" set "title" = 'noted' where "title" = 'hello' and ("id" > $1 or $1 is null) and ("id" <= $2 or $2 is null);
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
### Testing
|
|
945
|
+
|
|
946
|
+
`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`.
|
|
947
|
+
|
|
948
|
+
`factory.ts`:
|
|
949
|
+
|
|
950
|
+
```ts
|
|
951
|
+
import { schema, table, t } from "okmodel/pg";
|
|
952
|
+
import { open } from "okmodel/pg/pglite";
|
|
953
|
+
import { testing } from "okmodel/testing";
|
|
954
|
+
|
|
955
|
+
const notes = table("notes", { id: t.identity(), title: t.text() });
|
|
956
|
+
const app = schema({ tables: [notes] });
|
|
957
|
+
const harness = await testing(app, { driver: open() });
|
|
958
|
+
const factories = harness.factories({
|
|
959
|
+
notes: (x) => ({ title: x.words(2) }),
|
|
960
|
+
});
|
|
961
|
+
await harness.expectQueries(1, () => factories.notes.create());
|
|
962
|
+
const rows = await harness.db.notes.find({ limit: 5 });
|
|
963
|
+
if (rows.length !== 1) throw new Error("expected one note");
|
|
964
|
+
await harness.close();
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
`seed.ts`:
|
|
968
|
+
|
|
969
|
+
```ts
|
|
970
|
+
export default async function seed(t: {
|
|
971
|
+
factories(definitions: {
|
|
972
|
+
authors(x: { words(count: number): string }): { name: string };
|
|
973
|
+
notes(x: { words(count: number): string; ref(table: string): unknown }): {
|
|
974
|
+
title: string;
|
|
975
|
+
authorId: unknown;
|
|
976
|
+
};
|
|
977
|
+
}): {
|
|
978
|
+
notes: { create(): Promise<unknown> };
|
|
979
|
+
};
|
|
980
|
+
}): Promise<void> {
|
|
981
|
+
const factories = t.factories({
|
|
982
|
+
authors: (x) => ({ name: x.words(2) }),
|
|
983
|
+
notes: (x) => ({ title: x.words(2), authorId: x.ref("authors") }),
|
|
984
|
+
});
|
|
985
|
+
await factories.notes.create();
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
```sh
|
|
990
|
+
bunx okm seed seed.ts
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
### Startup
|
|
994
|
+
|
|
995
|
+
`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)`.
|
|
996
|
+
|
|
997
|
+
### Protected targets
|
|
998
|
+
|
|
999
|
+
`protected: true` is a flag on the target, not on the name and not on `NODE_ENV`. Plan, status, check, ext check, and drift stay allowed. Expand and reference rows stay allowed. An empty protected database can be provisioned. Contract, unclassified SQL, push, backfill, and seed are OKM1850 unless that invocation passes `--allow-protected`. `okm migrate check` writes a scratch schema, so it stays refused, and the flag does not apply. Point it at a throwaway Postgres. Protection does not affect `connect()`.
|
|
1000
|
+
|
|
883
1001
|
## Commands
|
|
884
1002
|
|
|
885
|
-
| Command | What it does
|
|
886
|
-
| ------------------------- |
|
|
887
|
-
| `okm build` | Validates the schema and writes `.okm
|
|
888
|
-
| `okm check` |
|
|
889
|
-
| `okm generate [name]` | Writes a SQL migration and `.okm/`.
|
|
890
|
-
| `okm dev` |
|
|
891
|
-
| `okm push` | Applies the schema directly.
|
|
892
|
-
| `okm migrate plan <name>` | Prints
|
|
893
|
-
| `okm migrate apply` |
|
|
894
|
-
| `okm migrate
|
|
895
|
-
| `okm
|
|
896
|
-
| `okm
|
|
897
|
-
| `okm
|
|
898
|
-
| `okm
|
|
1003
|
+
| Command | What it does |
|
|
1004
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1005
|
+
| `okm build` | Validates the schema and writes `.okm/`. Does not connect. |
|
|
1006
|
+
| `okm check` | Lints the schema and, after `okm_meta` exists, compares the database. Drift is OKM1520. Allowed on a protected target. |
|
|
1007
|
+
| `okm generate [name]` | Writes a SQL migration and `.okm/`. Prints lint findings and still writes the file. Offline. |
|
|
1008
|
+
| `okm dev` | Opens a PGlite database in `.okm/dev-db`, or a target named `dev`. It does not apply migrations. |
|
|
1009
|
+
| `okm push` | Applies the schema directly. OKM1850 on a protected target unless `--allow-protected`. |
|
|
1010
|
+
| `okm migrate plan <name>` | Prints each step's class and lock, plus a row estimate when the target answers. Read-only on a protected target. An error finding exits non-zero. |
|
|
1011
|
+
| `okm migrate apply` | Installs the head snapshot on an empty database, or runs pending files. Expand is allowed on a protected target. Contract, unclassified SQL, and backfill need `--allow-protected`. |
|
|
1012
|
+
| `okm migrate check` | Replays the history in a scratch schema and checks the previous catalog, the head, and the linter. Prints `ok N migrations`. Refused on a protected target, and `--allow-protected` does not apply. |
|
|
1013
|
+
| `okm migrate status` | Prints version, catalog hash, and state. Read-only on a protected target. An unfinished backfill is listed under the table. |
|
|
1014
|
+
| `okm seed <file>` | Runs the file's default export against the migrated target. OKM1850 on a protected target unless `--allow-protected`. |
|
|
1015
|
+
| `okm ext list` | Prints extensions the server can install. Read-only on a protected target. |
|
|
1016
|
+
| `okm ext check` | Compares installed extensions with the schema. OKM1811 if one is missing, OKM1812 if the pin is not met. Read-only on a protected target. `okm ext test` and `okm ext scaffold` are not in this version. |
|
|
1017
|
+
| `okm doctor [code]` | Lists triggers, or explains one error code. A role check is read-only on a protected target. |
|
|
1018
|
+
| `okm --version` | Prints the package version. |
|
|
899
1019
|
|
|
900
1020
|
`okmodel` and `okm` are the same command.
|
|
901
1021
|
|
|
1022
|
+
## Check a history in CI
|
|
1023
|
+
|
|
1024
|
+
`okm migrate check` is the command for a pipeline. It applies every migration into a scratch schema, plans each result back to that file's catalog, checks the previous catalog, requires the last catalog to match the schema, and lints the history. Success prints `ok N migrations`. A failure exits non-zero.
|
|
1025
|
+
|
|
1026
|
+
Point it at a throwaway Postgres. A protected target is refused, and `--allow-protected` does not apply. `defineConfig({ lintFrom: "<migration id>" })` skips lint for files before that id, which is how a project adopts the linter without failing on old migrations. Apply still lints pending migrations only.
|
|
1027
|
+
|
|
1028
|
+
The previous-catalog check is schema-level. It does not prove application behaviour. Tenant targets are not checked.
|
|
1029
|
+
|
|
1030
|
+
```yaml
|
|
1031
|
+
name: migrate
|
|
1032
|
+
on: [push, pull_request]
|
|
1033
|
+
jobs:
|
|
1034
|
+
check:
|
|
1035
|
+
runs-on: ubuntu-latest
|
|
1036
|
+
services:
|
|
1037
|
+
postgres:
|
|
1038
|
+
image: postgres:17
|
|
1039
|
+
env:
|
|
1040
|
+
POSTGRES_PASSWORD: postgres
|
|
1041
|
+
ports:
|
|
1042
|
+
- 5432:5432
|
|
1043
|
+
options: >-
|
|
1044
|
+
--health-cmd "pg_isready -U postgres"
|
|
1045
|
+
--health-interval 10s
|
|
1046
|
+
--health-timeout 5s
|
|
1047
|
+
--health-retries 5
|
|
1048
|
+
steps:
|
|
1049
|
+
- uses: actions/checkout@v5
|
|
1050
|
+
- uses: oven-sh/setup-bun@v2
|
|
1051
|
+
- run: bun install --frozen-lockfile
|
|
1052
|
+
- run: bunx okm migrate check
|
|
1053
|
+
env:
|
|
1054
|
+
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
The config reads `DATABASE_URL`, and that target is not protected.
|
|
1058
|
+
|
|
902
1059
|
## Roadmap
|
|
903
1060
|
|
|
904
1061
|
Status is on the [board](https://github.com/users/omqkhafi/projects/1). Each release is a milestone.
|
|
@@ -906,7 +1063,7 @@ Status is on the [board](https://github.com/users/omqkhafi/projects/1). Each rel
|
|
|
906
1063
|
- [x] [0.1](https://github.com/omqkhafi/okmodel/milestone/1) — Schema, queries, and migrations on PostgreSQL, with postgres.js and PGlite.
|
|
907
1064
|
- [x] [0.2](https://github.com/omqkhafi/okmodel/milestone/2) — Hidden and sensitive fields, validation, traits, tenancy, archive and restore, richer relations, presets, transactions, and operators for JSON, arrays, ranges, and search.
|
|
908
1065
|
- [x] [0.3](https://github.com/omqkhafi/okmodel/milestone/3) — Extensions, domains, functions, triggers, views, roles, and grants.
|
|
909
|
-
- [
|
|
1066
|
+
- [x] [0.4](https://github.com/omqkhafi/okmodel/milestone/4) — Safer migration plans, backfill, drift checks, provisioning, reference data, and a testing package.
|
|
910
1067
|
- [ ] [0.5](https://github.com/omqkhafi/okmodel/milestone/5) — A primary with replicas, read routing, and a reference app.
|
|
911
1068
|
|
|
912
1069
|
`okmodel/internal` has no stability promise. Names on that subpath can change or disappear in any release.
|
|
@@ -919,11 +1076,11 @@ Measured on this release.
|
|
|
919
1076
|
|
|
920
1077
|
| | Minified | Gzip | Cold import |
|
|
921
1078
|
| -------------------------------------------- | -------: | -----: | ----------: |
|
|
922
|
-
| Runtime entry | 5,288 | 2,026 |
|
|
923
|
-
| App startup (10 tables, one find) | 89,
|
|
924
|
-
| App startup, every 0.2 feature in use (full) | 119,
|
|
1079
|
+
| Runtime entry | 5,288 | 2,026 | 2.563 ms |
|
|
1080
|
+
| App startup (10 tables, one find) | 89,461 | 29,720 | 13.379 ms |
|
|
1081
|
+
| App startup, every 0.2 feature in use (full) | 119,936 | 39,121 | 13.745 ms |
|
|
925
1082
|
|
|
926
|
-
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,475 minified and 9,
|
|
1083
|
+
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,475 minified and 9,401 gzip bytes above the plain one. The runtime entry and the plain app are gated. The full app is printed, not gated.
|
|
927
1084
|
|
|
928
1085
|
The rest of the measurements are in [size](https://github.com/omqkhafi/okmodel/blob/main/docs/size.md).
|
|
929
1086
|
|
|
@@ -931,6 +1088,9 @@ The rest of the measurements are in [size](https://github.com/omqkhafi/okmodel/b
|
|
|
931
1088
|
|
|
932
1089
|
- [Quickstart](https://github.com/omqkhafi/okmodel/blob/main/docs/quickstart.md)
|
|
933
1090
|
- [Production checklist](https://github.com/omqkhafi/okmodel/blob/main/docs/production.md)
|
|
1091
|
+
- [Linter](https://github.com/omqkhafi/okmodel/blob/main/docs/linter.md)
|
|
1092
|
+
- [Backfill](https://github.com/omqkhafi/okmodel/blob/main/docs/backfill.md)
|
|
1093
|
+
- [Provisioning](https://github.com/omqkhafi/okmodel/blob/main/docs/provisioning.md)
|
|
1094
|
+
- [Testing](https://github.com/omqkhafi/okmodel/blob/main/docs/testing.md)
|
|
934
1095
|
- [Known limits](https://github.com/omqkhafi/okmodel/blob/main/docs/known-limits.md)
|
|
935
1096
|
- [Changelog](https://github.com/omqkhafi/okmodel/blob/main/changelog.md)
|
|
936
|
-
- [Design spec](https://github.com/omqkhafi/okmodel/blob/main/docs/okmodel-api-design.md)
|
|
@@ -35,8 +35,12 @@ export declare function renameColumn(source: Catalog, change: {
|
|
|
35
35
|
/**
|
|
36
36
|
* Renames a table without renaming its constraints or indexes.
|
|
37
37
|
*
|
|
38
|
-
* Column, index, and
|
|
39
|
-
* that
|
|
38
|
+
* Column, index, constraint, and trigger parents move to the new name, and
|
|
39
|
+
* edges that pointed at those objects move with them. Expression text that
|
|
40
|
+
* names the table is rewritten. Constraint and index names stay. A grant on
|
|
41
|
+
* the table, or on an identity sequence named `{table}_{column}_seq`, follows
|
|
42
|
+
* the name the schema will use. The planner emits the `RENAME` for a default
|
|
43
|
+
* name; a custom name is left as it is.
|
|
40
44
|
*
|
|
41
45
|
* @param source - Catalog document
|
|
42
46
|
* @param change - Namespace, current table name, and the new name
|
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
many2,
|
|
21
21
|
manyThrough2,
|
|
22
22
|
schema2
|
|
23
|
-
} from "../../shared/
|
|
23
|
+
} from "../../shared/3p4b1fg0.js";
|
|
24
24
|
import {
|
|
25
25
|
domainType
|
|
26
|
-
} from "../../shared/
|
|
26
|
+
} from "../../shared/g7ez901n.js";
|
|
27
27
|
import {
|
|
28
28
|
id2,
|
|
29
29
|
uuid2
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
table2,
|
|
33
33
|
index2,
|
|
34
34
|
sql2
|
|
35
|
-
} from "../../shared/
|
|
35
|
+
} from "../../shared/jsd07prz.js";
|
|
36
36
|
import {
|
|
37
37
|
text2,
|
|
38
38
|
varchar2,
|
|
@@ -8,11 +8,16 @@ import type { CatalogObject } from "../../../contracts/catalog/types.js";
|
|
|
8
8
|
/** Statements the planner inserts around the rest of the catalog. */
|
|
9
9
|
export type PrivilegeSql = {
|
|
10
10
|
/** Revokes, run while the objects still exist. */
|
|
11
|
-
readonly revoke: readonly
|
|
11
|
+
readonly revoke: readonly PrivilegeStatement[];
|
|
12
12
|
/** Role creates, role alters, and default privileges. Before new tables. */
|
|
13
|
-
readonly prepare: readonly
|
|
13
|
+
readonly prepare: readonly PrivilegeStatement[];
|
|
14
14
|
/** Grants on objects that exist, or that this plan creates first. */
|
|
15
|
-
readonly grant: readonly
|
|
15
|
+
readonly grant: readonly PrivilegeStatement[];
|
|
16
|
+
};
|
|
17
|
+
/** One privilege statement and the planner kind it is. */
|
|
18
|
+
export type PrivilegeStatement = {
|
|
19
|
+
readonly sql: string;
|
|
20
|
+
readonly kind: "revoke" | "revoke-default" | "create-role" | "alter-role" | "grant-default" | "grant";
|
|
16
21
|
};
|
|
17
22
|
/**
|
|
18
23
|
* Diffs role, grant, and default-privilege objects into SQL.
|
|
@@ -25,9 +30,11 @@ export type PrivilegeSql = {
|
|
|
25
30
|
* @param before - Previous catalog objects
|
|
26
31
|
* @param after - Next catalog objects
|
|
27
32
|
* @param schema - Concrete schema name
|
|
33
|
+
* @param recreated - Identity keys this plan drops and creates. Postgres drops
|
|
34
|
+
* grants with the object, so those grants are issued again after it exists
|
|
28
35
|
* @returns Statements in three groups
|
|
29
36
|
*/
|
|
30
|
-
export declare function privilegeSql(before: readonly CatalogObject[], after: readonly CatalogObject[], schema: string): PrivilegeSql;
|
|
37
|
+
export declare function privilegeSql(before: readonly CatalogObject[], after: readonly CatalogObject[], schema: string, recreated?: ReadonlySet<string>): PrivilegeSql;
|
|
31
38
|
/**
|
|
32
39
|
* Role name in `create role`, when the statement is one.
|
|
33
40
|
*
|
|
@@ -112,7 +112,13 @@ export type TableOptions<TColumns> = {
|
|
|
112
112
|
*/
|
|
113
113
|
readonly presets?: PresetMap<RowFrom<TColumns>>;
|
|
114
114
|
readonly policies?: unknown;
|
|
115
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Rows the application requires to exist.
|
|
117
|
+
*
|
|
118
|
+
* Apply inserts a row when its key is missing. It does not update or delete.
|
|
119
|
+
* The rows stay on the schema. They are not a diff in the catalog.
|
|
120
|
+
*/
|
|
121
|
+
readonly reference?: ReferenceOption;
|
|
116
122
|
/** `one` and `many`. Other relation kinds stay reserved. */
|
|
117
123
|
readonly relations?: unknown;
|
|
118
124
|
};
|
|
@@ -207,6 +213,16 @@ export type InsertFrom<TColumns> = {
|
|
|
207
213
|
export type UpdateFrom<TColumns, TKey extends string = never> = {
|
|
208
214
|
readonly [K in keyof TColumns as K extends TKey ? never : ColumnUpdateOf<TColumns[K]> extends never ? never : K]: ColumnUpdateOf<TColumns[K]>;
|
|
209
215
|
};
|
|
216
|
+
/**
|
|
217
|
+
* Rows declared with the table option `reference`.
|
|
218
|
+
*
|
|
219
|
+
* `key` is one column name or several, in constraint order. `rows` are the
|
|
220
|
+
* values apply inserts when that key is absent.
|
|
221
|
+
*/
|
|
222
|
+
export type ReferenceOption = {
|
|
223
|
+
readonly key: string | readonly string[];
|
|
224
|
+
readonly rows: readonly Readonly<Record<string, string | number | boolean | null>>[];
|
|
225
|
+
};
|
|
210
226
|
/**
|
|
211
227
|
* Declares a table.
|
|
212
228
|
*
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Verifies declared views on a scratch schema.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* replaces the author query and `pg_depend`
|
|
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).
|
|
6
7
|
* The scratch schema is dropped before this returns. Plans never use it.
|
|
7
8
|
*/
|
|
8
9
|
import type { Catalog } from "../../../contracts/catalog/types.js";
|
|
@@ -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,7 +3,7 @@ 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,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
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,7 +3,7 @@ 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,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
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,7 +3,7 @@ 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,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
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,7 +3,7 @@ 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,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
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,3 +1,4 @@
|
|
|
1
|
+
import"../../shared/8jb4ymh2.js";
|
|
1
2
|
import {
|
|
2
3
|
checkPresetNames,
|
|
3
4
|
duplicatePreset
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
} from "../../shared/n0dgs0pb.js";
|
|
8
9
|
import {
|
|
9
10
|
quoteIdent
|
|
10
|
-
} from "../../shared/
|
|
11
|
+
} from "../../shared/41kv2xdm.js";
|
|
11
12
|
import {
|
|
12
13
|
functionObject,
|
|
13
14
|
triggerObject
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
} from "../../shared/dyj86cmj.js";
|
|
21
22
|
import {
|
|
22
23
|
snakeCase
|
|
23
|
-
} from "../../shared/
|
|
24
|
+
} from "../../shared/jsd07prz.js";
|
|
24
25
|
import {
|
|
25
26
|
ColumnBuilder2,
|
|
26
27
|
retarget
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
queryHandle,
|
|
38
39
|
attachHttp,
|
|
39
40
|
settleCall
|
|
40
|
-
} from "../../shared/
|
|
41
|
+
} from "../../shared/kkzwp3d2.js";
|
|
41
42
|
import {
|
|
42
43
|
installArchiveInspect
|
|
43
44
|
} from "../../shared/xrfnq5k8.js";
|
|
@@ -458,7 +459,7 @@ function refuse(host, table) {
|
|
|
458
459
|
}
|
|
459
460
|
var archiveMod;
|
|
460
461
|
function loadArchive() {
|
|
461
|
-
archiveMod ??= import("../../shared/
|
|
462
|
+
archiveMod ??= import("../../shared/radd3z4g.js");
|
|
462
463
|
return archiveMod;
|
|
463
464
|
}
|
|
464
465
|
async function settle(host, table, mods) {
|