From bf18897d15fa49303d79448081b2fa18010d7544 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 21:25:28 +0200 Subject: [PATCH 01/60] docs(drive): re-plan umbrella as sequential IR-canonicalization-first stack S1 closed but shipped storage under a `namespaces` wrapper and never wired the domain plane, so the emitted IR diverges from ADR 221. Re-plan the umbrella's remaining work from scratch as one sequential thread (S2 IR-canonicalization -> S3 Postgres public-by-default -> S4 runtime qualification -> S5 explicit-namespace DSL, deferrable) rather than two parallel projects: the only real concurrency (GAP 2) is not worth the cross-project coordination, and GAP 1 must precede S4 regardless. Signed-off-by: Will Madden --- .../target-extensible-ir-namespaces/plan.md | 158 ++++++++++------- .../target-extensible-ir-namespaces/spec.md | 163 +++++++++--------- 2 files changed, 177 insertions(+), 144 deletions(-) diff --git a/projects/target-extensible-ir-namespaces/plan.md b/projects/target-extensible-ir-namespaces/plan.md index 71ead9145a..e4b464057c 100644 --- a/projects/target-extensible-ir-namespaces/plan.md +++ b/projects/target-extensible-ir-namespaces/plan.md @@ -2,11 +2,17 @@ **Spec:** [`projects/target-extensible-ir-namespaces/spec.md`](./spec.md) **Linear Project:** [Target-Extensible IR + Namespaces](https://linear.app/prisma-company/project/target-extensible-ir-namespaces-fd69eff8aec6) -**Purpose** _(from spec)_: Make first-class namespaces and target-extensible IR usable for the downstream Supabase integration. The IR gains a pack-contributed entity-kind mechanism (proven by Postgres enum migrating off the framework-shared `types` slot); runtime SQL and the DSL/ORM surfaces qualify identifiers and dispatch through default-namespace fallback so existing single-namespace consumers experience zero breakage; the explicit namespace-aware surface (`db.sql.auth.user`) lands later as purely additive work. +**Purpose** _(from spec)_: Make first-class namespaces and target-extensible IR usable for the downstream Supabase integration. The contract IR reaches its canonical two-plane shape; runtime SQL and the DSL/ORM surfaces qualify identifiers and dispatch through a default-namespace fallback so existing single-namespace consumers experience zero breakage; the explicit namespace-aware surface (`db.sql.auth.user`) lands later as purely additive work. ## At a glance -Three units, single stack thread: **S1 (sub-project) → S2 (slice) → S3 (slice)**. S1 + S2 are the must-ship core; S3 is additive and can land independently. No parallelisation at the umbrella level — each unit gates the next on substrate, not just on convention. The umbrella plan delegates execution detail to S1 (now closed; durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)) and to S2/S3 slice specs which will be authored via `drive-specify-slice` at pickup time. +Single sequential stack. S1 closed and proved the IR substrate (durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)), but it shipped the storage plane under a `namespaces` wrapper and never wired the `domain` plane — so the emitted IR doesn't yet match ADR 221's prose. The remaining work is one re-planned-from-scratch thread: + +```text +S2 — IR canonicalization (GAP 1 → GAP 2) → S3 — Postgres public-by-default → S4 — runtime qualification → S5 — explicit-namespace DSL (deferrable) +``` + +We do **not** split this into parallel projects: the only real concurrency is GAP 2 vs the rest, and it isn't enough independent work to justify a second project, worktree, Linear project, and the cross-project merge-order bookkeeping. GAP 1 and S4 touch the same identifier-emission paths, so the IR-shape fix must land first regardless. One worktree + branch per slice; new slice tickets created at pickup, not all upfront. ## Composition @@ -14,118 +20,139 @@ Three units, single stack thread: **S1 (sub-project) → S2 (slice) → S3 (slic #### S1 — contract IR planes + pack-contributed entity-kind mechanism + Postgres enum migration -**Unit type:** Sub-project (multi-slice; closed — durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)). +**Unit type:** Sub-project (multi-slice; **closed** — durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)). **Purpose.** Restructure the contract IR around two planes (`domain`, `storage`) with uniform `...` indexing and canonical entity coordinates. Add the pack-contributed entity-kind descriptor mechanism. Migrate Postgres enum off the framework-shared `types` slot as the substrate's load-bearing exemplar. -**Scope.** ~50 source files across framework / SQL family / Postgres target / emitter + ~10 on-disk contracts. Delivered across five merged slices; durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md). +**Outcome.** Delivered across five merged slices. Proved the two-plane model and the pack-contributed entity-kind mechanism (Postgres enum). **What it did not finish:** the emitted storage plane still carries a `storage.namespaces.` wrapper, and the `domain` plane was never wired (models/valueObjects/types remain flat at the contract root). S2 closes that gap. -**Linear:** [TML-2584](https://linear.app/prisma-company/issue/TML-2584). Six internal-slice tickets need creating before slice pickup (folded into the Linear audit pass). +**Linear:** [TML-2584](https://linear.app/prisma-company/issue/TML-2584) (Done). -**Depends on.** PR #534 (TML-2520) merged. Met at commit `66da80f96`. +**Priority.** Closed. -**Validation gate.** Each S1 slice has its own validation gate (see S1's plan). S1 closes when all six slices land and S1's PDoD1-PDoD11 are met. +#### S2 — IR canonicalization to the ADR-221 shape -**Priority.** Must-ship core (closed). +**Unit type:** Sub-project (two slices — GAP 1 then GAP 2). -#### S2 — runtime SQL qualification + default-namespace DSL/ORM fallback +**Purpose.** Bring the emitted IR to the canonical shape ADR 221 describes. This is S1's unfinished FR2, pulled forward as its own focused work. -**Unit type:** Slice (single PR). +- **GAP 1 — drop the `storage.namespaces` wrapper.** Storage is indexed directly as `storage...` (no `namespaces` segment). Touches the framework `Storage` IR type + `elementCoordinates` walk, SQL-family `SqlStorage`, serializer/deserializer hydration + validators, canonicalization + emitter, and every on-disk `contract.json` / `contract.d.ts` (storageHash churn). **Picked up now.** Linear [TML-2747](https://linear.app/prisma-company/issue/TML-2747). +- **GAP 2 — wire the `domain` plane.** Flat `contract.models` / `contract.valueObjects` / `contract.types` move under `contract.domain..{models, valueObjects, types}`. Pure IR-shape correctness — does **not** affect runtime SQL qualification. Likely ~2 dispatches (type + emitter, then consumer migration). Linear ticket created at pickup. -**Purpose.** Close the runtime loop on PR #534's namespace IR: runtime SQL emits namespace-qualified identifiers; DSL/ORM reads through per-family default-namespace fallback (`'public'` for Postgres; `'__unbound__'` for Mongo/SQLite) so legacy query code keeps working unchanged. The fallback is the load-bearing backward-compatibility mechanism. +**Depends on.** S1 (closed). -**Scope.** ~10-12 files: family façade hardcoded `defaultNamespace` constant; DSL `Db` type-resolution path; ORM accessor type-resolution path; runtime SQL identifier-qualification at the relational-core / runtime layers. Slice spec authored via `drive-specify-slice` at pickup time; lands at `projects/target-extensible-ir-namespaces/slices/runtime-qualification/spec.md`. +**Validation gate (per slice PR).** -**Linear:** [TML-2605](https://linear.app/prisma-company/issue/TML-2605). +- `pnpm typecheck` · `pnpm test:packages` · `pnpm test:integration` · `pnpm test:e2e` · `pnpm lint:deps` +- `pnpm fixtures:check` clean after regeneration. +- **Project-specific check:** the emitted IR matches ADR 221's described shape for the touched plane (no `storage.namespaces` wrapper after GAP 1; `domain.` populated after GAP 2). Grep gate over the removed wrapper path. + +**Priority.** Must-ship core (critical path: GAP 1 precedes S4). -**Depends on.** S1 (consumes the two-plane IR shape + entity coordinate; the qualification path needs `Storage.elementCoordinates()` to walk). +#### S3 — Postgres public-by-default at the PSL interpreter + +**Unit type:** Slice (single PR). + +**Purpose.** Make the Postgres PSL interpreter interpret models that declare no explicit namespace as belonging to the `public` namespace. `__unbound__` becomes an explicit PSL opt-in rather than the implicit default. This removes the hardcoded `"public".`-prefixing logic from runtime/render and makes `public` a real, first-class namespace in the contract — the prerequisite for S4 emitting `"public"."user"` honestly rather than by string interpolation. + +**Scope.** PSL interpreter default-namespace policy; deletion of the hardcoded `public`-prefix logic; regenerate all Postgres contract artifacts (demo, examples, fixtures) so un-namespaced models carry the `public` namespace. Existing integration test asserting the *absence* of a `"public".` prefix for unbound contracts is re-expressed for the new default. + +**Depends on.** S2 (IR shape correct before regenerating artifacts against it). **Validation gate.** -- `pnpm typecheck` -- `pnpm test:packages` -- `pnpm test:integration` (loads the cross-namespace FK integration test plus a new regression test demonstrating default-namespace fallback works unchanged) -- `pnpm test:e2e` -- `pnpm lint:deps` -- **Project-specific check:** the demo app's `examples/prisma-next-demo/src/queries/*.ts` files compile and run unchanged after S2 lands. (Surfaces FR5 satisfaction.) +- `pnpm typecheck` · `pnpm test:packages` · `pnpm test:integration` · `pnpm test:e2e` · `pnpm lint:deps` +- `pnpm fixtures:check` clean after regeneration. +- **Project-specific check:** an un-namespaced Postgres model emits under the `public` namespace; opting a model into `__unbound__` in PSL is possible and round-trips. + +**Linear:** ticket created at pickup. **Priority.** Must-ship core. -#### S3 — explicit namespace-aware DSL/ORM surface +#### S4 — runtime SQL qualification + default-namespace DSL/ORM fallback -**Unit type:** Slice (single PR). +**Unit type:** Slice (single PR; may split into ~2 dispatches). -**Purpose.** Add `db.sql..` and `db..` for explicit multi-namespace navigation. Purely additive on S2's default-namespace fallback — non-default-namespace consumers opt in to the explicit surface; default-namespace consumers see no change. +**Purpose.** Runtime SQL emits namespace-qualified identifiers (Postgres `"public"."user"`; SQLite unqualified `"user"`; Mongo collection in the right namespace). DSL/ORM keeps its flat-by-name surface (`db.sql.
`, `db.`), with every lookup resolving through a per-family default namespace (`'public'` for Postgres now that S3 makes it real; `'__unbound__'` for Mongo/SQLite) so legacy query code keeps working unchanged. The fallback is the load-bearing backward-compatibility mechanism. -**Scope.** ~8-10 files: DSL accessor type construction (the `Db` type machinery walks `contract..` to produce per-namespace accessors); ORM accessor type construction; runtime resolution from the explicit surface to the same identifier-qualification path S2 established. Slice spec authored via `drive-specify-slice` at pickup time; lands at `projects/target-extensible-ir-namespaces/slices/explicit-namespace-dsl/spec.md`. +**Scope.** Family façade default-namespace constant; DSL `Db` type-resolution path; ORM accessor type-resolution path; runtime SQL identifier-qualification at the relational-core / runtime layers (AST enrichment with `namespaceId` and/or renderer-side `(tableName, alias) → namespace` lookup). -**Linear:** [TML-2550](https://linear.app/prisma-company/issue/TML-2550). +**Linear:** [TML-2605](https://linear.app/prisma-company/issue/TML-2605). -**Depends on.** S2 (the explicit surface is additive; it reuses S2's qualification path under the hood). +**Depends on.** S2 GAP 1 (walks `storage.` directly) **and** S3 (the `public` namespace must exist for the qualified identifier to be honest). **Validation gate.** -- `pnpm typecheck` -- `pnpm test:packages` -- `pnpm test:integration` (includes a multi-namespace integration test demonstrating `db.sql.auth.user` works alongside `db.sql.user` for the default namespace) -- `pnpm test:e2e` -- `pnpm lint:deps` -- **Project-specific check:** demo app migrates one query from `db.sql.user` to `db.sql.public.user` (explicit-namespace form) and both forms compile + run. +- `pnpm typecheck` · `pnpm test:packages` · `pnpm test:integration` · `pnpm test:e2e` · `pnpm lint:deps` +- `pnpm test:integration` loads the cross-namespace FK integration test plus a new regression test demonstrating default-namespace fallback works unchanged. +- **Project-specific check:** the demo app's `examples/prisma-next-demo/src/queries/*.ts` files compile and run unchanged; emitted SQL contains `"public"."user"`. -**Priority.** Additive (can land independently). +**Priority.** Must-ship core. -### Parallel groups +#### S5 — explicit namespace-aware DSL/ORM surface -None. Single stack thread S1 → S2 → S3. +**Unit type:** Slice (single PR). **Deferrable.** -S2 cannot start until S1 ships the two-plane IR shape (S2's qualification path needs to walk `contract.storage..` generically). S3 cannot start until S2 ships the qualification path (S3 builds the explicit-namespace surface on top of S2's resolution mechanism). +**Purpose.** Add `db.sql..
` and `db..` for explicit multi-namespace navigation. Purely additive on S4's default-namespace fallback — default-namespace consumers see no change. + +**Scope.** DSL accessor type construction (walks `contract..` for per-namespace accessors); ORM accessor type construction; runtime resolution from the explicit surface to the same identifier-qualification path S4 established. + +**Linear:** [TML-2550](https://linear.app/prisma-company/issue/TML-2550). -Within S1, S1's own plan defines parallelisation opportunities (Slices 4 + 5 are parallelisable per its plan). Those are S1-internal sequencing decisions and don't surface at the umbrella level. +**Depends on.** S4. + +**Priority.** Additive — only needed once a real multi-namespace consumer wants explicit navigation. Can land independently or be deferred to a sibling initiative with the deferral recorded in `deferred.md`. + +### Parallel groups + +None. Single sequential thread. GAP 2 is the only piece that is technically independent of the runtime work, but it stays in-line (right after GAP 1) rather than running as a parallel project — the coordination overhead exceeds the gain. ## Dependencies (external) -- [x] **PR #534 (TML-2520) merged.** Met at commit `66da80f96`. Required base for S1. -- [x] **Linear audit completed** 2026-05-20. Triage outcomes: - - **Mainline (refreshed descriptions):** TML-2584 (S1), TML-2605 (S2), TML-2550 (S3) - - **S1 internal slice tickets created:** TML-2622 (S1.A), TML-2623 (S1.B), TML-2624 (S1.C), TML-2625 (S1.D) — all `relatedTo` TML-2584 - - **Subsumed by S1 (auto-close on slice PR merge):** TML-2579, TML-2580, TML-2582, TML-2545, TML-2563, TML-2586 - - **Out-of-umbrella (stay in Linear project for discoverability):** TML-2537 (TML-2537 PSL substrate), TML-2541, TML-2542, TML-2543, TML-2540, TML-2513 - - **No action needed:** TML-2583 (orthogonal; spec § non-goals already covers); previously-resolved TML-2459/2520/2521/2575/2576/2577/2578/2581 -- [ ] **Supabase initiative awareness.** The downstream Supabase integration consumes this umbrella's substrate. Coordinated at initiative-level — not a blocker for this umbrella, but the Supabase initiative's planning needs to know what S1 ships and when. +- [x] **PR #534 (TML-2520) merged.** Required base for S1; S1 closed on top of it. +- [x] **S1 closed.** ADR 221 captures the durable decisions; the IR substrate (two-plane model, entity coordinate, pack-contributed entity kinds) exists. +- [ ] **Supabase initiative awareness.** The downstream Supabase integration consumes this substrate. Coordinated at initiative-level — not a blocker, but the Supabase initiative's planning needs to know what S2–S4 ship and when. ## Project-DoD coverage map | Project-DoD | Delivered by | |---|---| -| **PDoD1.** All units delivered or deferred | S1 + S2 + S3 (additive; or deferred to a sibling initiative) | -| **PDoD2.** Multi-namespace contract authorable + emittable + queryable end-to-end | S1 (authoring + emission) + S2 (queryable) | -| **PDoD3.** Pack-contributed entity-kind substrate exercised end-to-end (Postgres enum migration) | S1 (inherits its own PDoD3) | -| **PDoD4.** Runtime SQL identifier-qualification | S2 | -| **PDoD5.** Existing consumers on default-namespace contracts experience zero query-API breakage | S2 (default-namespace fallback) + verified across S1+S2 landings | -| **PDoD6.** Long-lived ADRs migrated to `docs/architecture docs/adrs/` | Close-out task; lifts ADRs from S1 + any S2/S3 ADRs | -| **PDoD7.** Linear Project marked Completed | Close-out task (auto via PR-merge integration) | -| **PDoD8.** Rolled-up project folders archived; umbrella folder deleted | Close-out task | -| **PDoD9.** Repo-wide references stripped | Close-out task | +| **PDoD1.** All units delivered or deferred | S2 + S3 + S4 (must-ship); S5 (additive; deliver or defer with record) | +| **PDoD2.** Emitted contract IR matches ADR 221's canonical shape (`storage.` with no wrapper; `domain.` wired) | S2 (GAP 1 + GAP 2) | +| **PDoD3.** Un-namespaced Postgres models default to the `public` namespace; `__unbound__` is an explicit PSL opt-in; hardcoded `public`-prefix logic deleted | S3 | +| **PDoD4.** Runtime SQL identifier-qualification (demo emits `"public"."user"`) | S3 (makes `public` real) + S4 (renders it) | +| **PDoD5.** Existing consumers on default-namespace contracts experience zero query-API breakage | S3 (public default keeps un-namespaced authoring working) + S4 (default-namespace fallback) | +| **PDoD6.** Multi-namespace contract authorable + emittable + queryable end-to-end | S2 (shape) + S4 (queryable) | +| **PDoD7.** Pack-contributed entity-kind substrate exercised end-to-end (Postgres enum) | S1 (closed) | +| **PDoD8.** Long-lived ADRs migrated to `docs/architecture docs/adrs/` | Close-out (S1's ADR 221 already migrated; any S2–S4 ADRs) | +| **PDoD9.** Linear Project marked Completed | Close-out (auto via PR-merge integration) | +| **PDoD10.** Rolled-up project folders archived; umbrella folder deleted; repo-wide references stripped | Close-out | ## Risks + open questions -1. **S1 internal slice falsifications cascade to umbrella.** *(Resolved — S1 closed; durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md).)* S1's assumptions held without forcing an umbrella-level re-sequencing of S2 / S3. -2. **A3 default-namespace fallback sufficiency.** If consumers start writing multi-namespace contracts before S3 lands, the flat DSL surface becomes inadequate and S3 rises in priority. Mitigation: document the namespace-aware surface as a planned addition so consumers know explicit navigation is coming. -3. **Linear audit surfaces unexpected scope.** The Linear tickets in the project may include work that wasn't accounted for in this umbrella's three-unit composition. If audit surfaces real-scope work the umbrella missed, the plan re-opens. +1. **Fixture churn cost (S2 + S3).** Both GAP 1 and S3 regenerate every on-disk contract. The fixture-regen path has been a repeated time-sink. Mitigation: lean on `pnpm fixtures:emit` / `pnpm fixtures:check`; the implementer brief calls out regeneration explicitly as part of "done." +2. **GAP 1 ↔ S4 merge order.** Both touch the storage-walk / identifier-emission paths. Mitigation handled by sequencing — GAP 1 lands first, S4 rebases on it. +3. **GAP 2 value vs cost.** GAP 2 (domain plane) is pure shape-correctness; it doesn't unblock the Supabase runtime story. It stays in must-ship to make the IR honestly match ADR 221, but if schedule pressure returns it is the first candidate to defer (records to `deferred.md`). +4. **S3 default-namespace policy blast radius.** Flipping the PSL default from `__unbound__` to `public` for Postgres regenerates every Postgres contract and re-expresses an existing integration test. Risk that downstream consumers pinned to the old shape break; this umbrella owns regenerating the in-repo artifacts, downstream consumers get upgrade instructions if their *source* shape changes. ## Sequencing visualisation ```text -PR #534 ✓ (merged) +S1 — contract-ir-planes (sub-project, 5 merged slices) ✓ CLOSED (ADR 221) + │ + ▼ +S2 — IR canonicalization + ├─ GAP 1: drop storage.namespaces wrapper → in progress (TML-2747) + └─ GAP 2: wire domain plane │ ▼ -S1 — contract-ir-planes (sub-project, 5 merged slices) ✓ CLOSED +S3 — Postgres public-by-default at PSL │ ▼ -S2 — runtime-qualification (slice) → next +S4 — runtime SQL qualification + default-namespace fallback (TML-2605) │ ▼ -S3 — explicit-namespace-dsl (slice, additive) +S5 — explicit-namespace DSL (additive / deferrable) (TML-2550) │ ▼ Downstream: Supabase integration consumes this substrate @@ -135,13 +162,10 @@ Downstream: Supabase integration consumes this substrate - [ ] Verify all PDoDs in [`projects/target-extensible-ir-namespaces/spec.md`](./spec.md) - [ ] Mandatory final retro complete; output landed in canonical / project-context / ADR -- [ ] Migrate long-lived ADRs into `docs/architecture docs/adrs/`: - - S1's `0001-contract-planes.md` - - Any S2/S3 ADRs produced during execution -- [ ] Archive / delete rolled-up project folders with long-lived contents migrated to `docs/`: +- [ ] Migrate any long-lived ADRs produced by S2–S4 into `docs/architecture docs/adrs/` (S1's ADR 221 already migrated) +- [ ] Archive / delete rolled-up predecessor project folders with long-lived contents migrated to `docs/`: - `projects/target-extensible-ir/` (substrate predecessor) - - `projects/namespace-exemplar/` (TML-2520 / PR #534 — predecessor) - - S1 sub-project (closed; folder removed at its own close-out — durable decisions migrated to [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)) + - `projects/namespace-exemplar/` (TML-2520 / PR #534 predecessor) - [ ] Strip repo-wide references to `projects/target-extensible-ir-namespaces/**` and all rolled-up sibling folders (replace with canonical `docs/` links or remove) - [ ] Delete `projects/target-extensible-ir-namespaces/` - [ ] Linear Project "Target-Extensible IR + Namespaces" marked Completed (auto via PR-merge integration when the close-out PR lands) diff --git a/projects/target-extensible-ir-namespaces/spec.md b/projects/target-extensible-ir-namespaces/spec.md index 7f0ea75521..f5be5983a0 100644 --- a/projects/target-extensible-ir-namespaces/spec.md +++ b/projects/target-extensible-ir-namespaces/spec.md @@ -1,148 +1,157 @@ # Summary -Deliver target-extensible IR + first-class namespaces — the substrate the downstream Supabase integration depends on. Three units of work: one sub-project (contract IR reshape proven by Postgres enum migration), one slice (runtime SQL qualification + default-namespace DSL/ORM fallback), and one additive slice (explicit namespace-aware DSL/ORM surface). +Deliver target-extensible IR + first-class namespaces — the substrate the downstream Supabase integration depends on. S1 (closed) proved the two-plane IR model and the pack-contributed entity-kind mechanism, but shipped storage under a `namespaces` wrapper and never wired the `domain` plane. The remaining work, re-planned from scratch as a single sequential stack: canonicalize the IR to ADR 221's shape, make Postgres `public`-by-default at the PSL interpreter, qualify identifiers at runtime with a default-namespace fallback, then (additive, deferrable) add the explicit namespace-aware DSL/ORM surface. # Purpose -Make first-class namespaces and target-extensible IR usable for the downstream Supabase integration. The IR gains a pack-contributed entity-kind mechanism (proven by Postgres enum migrating off the framework-shared `types` slot); runtime SQL and the DSL/ORM qualify identifiers through a default-namespace fallback so existing single-namespace consumers experience zero query-API breakage; the explicit namespace-aware surface (`db.sql.auth.user`) lands later as purely additive work. +Make first-class namespaces and target-extensible IR usable for the downstream Supabase integration. The contract IR reaches its canonical two-plane shape (`contract.{domain, storage}...`); un-namespaced Postgres models default to the `public` namespace; runtime SQL and the DSL/ORM qualify identifiers through a default-namespace fallback so existing single-namespace consumers experience zero query-API breakage; the explicit namespace-aware surface (`db.sql.auth.user`) lands later as purely additive work. # At a glance -This is the umbrella for the work that started as PR #534 (TML-2520 namespace exemplar, merged) and continues with two structural follow-ons and one additive ergonomic follow-on. The composition: +This is the umbrella for the work that started as PR #534 (TML-2520 namespace exemplar, merged), continued through the S1 sub-project (contract-ir-planes, closed), and now completes the namespace story. The re-planned composition: ```text -┌──────────────────────────────────────────────────────────────────┐ -│ PR #534 — namespace exemplar + cross-namespace FKs ✓ MERGED │ -│ (storage IR is per-namespace; cross-namespace FKs ship) │ -└──────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────┐ +│ S1 — contract-ir-planes (sub-project, TML-2584) CLOSED │ +│ Two-plane IR model + pack-contributed entity-kind mechanism + │ +│ Postgres enum exemplar. 5 merged slices. ADR 221. │ +│ Did NOT finish: storage still under a `namespaces` wrapper; │ +│ `domain` plane never wired. │ +└────────────────────────────────────────────────────────────────────┘ │ ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ S1 — contract-ir-planes (sub-project, TML-2584) CLOSED │ -│ Contract IR two-plane reshape + pack-contributed entity-kind │ -│ mechanism + Postgres enum migration as exemplar │ -│ 5 merged slices │ -└─────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────┐ +│ S2 — IR canonicalization (sub-project) → ACTIVE │ +│ GAP 1: drop the storage.namespaces wrapper (storage.) │ +│ GAP 2: wire the domain plane (contract.domain..{...}) │ +└────────────────────────────────────────────────────────────────────┘ │ ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ S2 — runtime-qualification (slice, TML-2605) → NEXT │ -│ Runtime SQL emits namespace-qualified identifiers; DSL/ORM │ -│ reads through per-family default-namespace fallback so legacy │ -│ query code keeps working unchanged │ -│ ~1 PR │ -└─────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────┐ +│ S3 — Postgres public-by-default at the PSL interpreter (slice) │ +│ Un-namespaced models → `public` namespace; `__unbound__` becomes │ +│ an explicit PSL opt-in; hardcoded `public`-prefix logic deleted. │ +│ Regenerate Postgres contract artifacts. │ +└────────────────────────────────────────────────────────────────────┘ │ ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ S3 — explicit-namespace-dsl (slice, TML-2550) additive │ -│ Explicit namespace-aware DSL/ORM surface (db.sql.auth.user, │ -│ db.auth.User). Purely additive on S2's default-namespace │ -│ fallback — non-default-namespace consumers opt in │ -│ ~1 PR │ -└─────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────┐ +│ S4 — runtime qualification + default-ns fallback (slice, TML-2605) │ +│ Runtime SQL emits `"public"."user"`; DSL/ORM resolves through a │ +│ per-family default namespace so legacy query code is untouched. │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────────────────┐ +│ S5 — explicit-namespace DSL/ORM (slice, TML-2550) additive │ +│ db.sql.auth.user, db.auth.User. Purely additive on S4. Deferrable.│ +└────────────────────────────────────────────────────────────────────┘ │ ▼ Downstream: Supabase integration initiative consumes this substrate. Out of this umbrella's scope. ``` -Existing consumers on a single-default-namespace contract write zero query-code changes across S1+S2. Multi-namespace contracts that want explicit per-namespace navigation opt in to S3 when it lands. +Existing consumers on a single-default-namespace contract write zero query-code changes across the must-ship stack (S2–S4). Multi-namespace contracts that want explicit per-namespace navigation opt in to S5 when it lands. # Scope ## In scope -- **S1 — contract IR two-plane reshape + pack-contributed entity-kind mechanism + Postgres enum migration as exemplar.** Sub-project (closed; delivered across five merged slices). Durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md). Tracking ticket [TML-2584](https://linear.app/prisma-company/issue/TML-2584). -- **S2 — runtime SQL qualification + default-namespace DSL/ORM fallback.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate (Postgres: `"public"."user"`; SQLite: unqualified `"user"`; Mongo: collection key in correct namespace). DSL/ORM continues to expose flat-by-name surface (`db.sql.
`, `db.`); every lookup resolves through a per-family default namespace (`'public'` for Postgres; `'__unbound__'` for Mongo/SQLite) hardcoded in the family façade function. Single PR. Tracking ticket [TML-2605](https://linear.app/prisma-company/issue/TML-2605). -- **S3 — explicit namespace-aware DSL/ORM surface.** Adds `db.sql..
` and `db..` for explicit multi-namespace navigation. Purely additive on S2 — default-namespace lookups keep working unchanged. Single PR. Tracking ticket [TML-2550](https://linear.app/prisma-company/issue/TML-2550). +- **S2 — IR canonicalization to the ADR-221 shape.** Sub-project, two slices. **GAP 1** ([TML-2747](https://linear.app/prisma-company/issue/TML-2747)): drop the `storage.namespaces.` wrapper so storage is indexed `storage...`. **GAP 2**: wire the `domain` plane so `models` / `valueObjects` / `types` move under `contract.domain..{...}`. Closes S1's unfinished FR2. +- **S3 — Postgres public-by-default at the PSL interpreter.** Un-namespaced Postgres models interpret as the `public` namespace; `__unbound__` becomes an explicit PSL opt-in. Delete the hardcoded `"public".`-prefixing logic. Regenerate Postgres contract artifacts (demo, examples, fixtures). Ticket created at pickup. +- **S4 — runtime SQL qualification + default-namespace DSL/ORM fallback.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate (Postgres: `"public"."user"`; SQLite: unqualified `"user"`; Mongo: collection key in correct namespace). DSL/ORM keeps its flat-by-name surface (`db.sql.
`, `db.`); every lookup resolves through a per-family default namespace (`'public'` for Postgres; `'__unbound__'` for Mongo/SQLite) hardcoded in the family façade function. Single PR. Tracking ticket [TML-2605](https://linear.app/prisma-company/issue/TML-2605). +- **S5 — explicit namespace-aware DSL/ORM surface.** Adds `db.sql..
` and `db..` for explicit multi-namespace navigation. Purely additive on S4 — default-namespace lookups keep working unchanged. Single PR, **deferrable**. Tracking ticket [TML-2550](https://linear.app/prisma-company/issue/TML-2550). ## Non-goals - **First-class Postgres enum user affordances.** Typed `Role.member` references, `db.enums.X` runtime surface, codec value-narrowing, `@default(EnumName.value)` PSL lowering. Owned by the separate `postgres-enum-finishing` project; consumes this umbrella's substrate. -- **Pack-contributed PSL block grammar** (`policy {…}`, `role {…}`, etc.). Owned by [TML-2537](https://linear.app/prisma-company/issue/TML-2537) (target-contributed top-level PSL blocks). Independent project. The Postgres pack reuses the framework's existing `enum {…}` block syntax for its enum migration in S1 — pack-contributed grammar is a separate substrate. +- **Pack-contributed PSL block grammar** (`policy {…}`, `role {…}`, etc.). Owned by [TML-2537](https://linear.app/prisma-company/issue/TML-2537) (target-contributed top-level PSL blocks). Independent project. - **PostgresRLS** (policies, roles, grants, row-level security DDL). Independent project; depends on TML-2537 substrate. The Supabase initiative's marquee feature, but not this umbrella's deliverable. - **The `@prisma-next/supabase` extension pack.** Lives in the downstream Supabase integration initiative; consumes this umbrella's substrate. - **Auth roles, JWT, RBAC modelling.** Downstream Supabase initiative. - **Mongo enum support.** Mongo lacks the native type; emulating via application-side validation is out of scope here. -- **SQLite native enums** via CHECK-constraint emulation. Future axis; if added, follows the same pack-contributed entity-kind pattern S1 establishes. -- **Cross-target portability of Postgres-only features.** Postgres-pack-contributed entity kinds are Postgres-only by design; cross-target portability is not a goal. +- **SQLite native enums** via CHECK-constraint emulation. Future axis; if added, follows the same pack-contributed entity-kind pattern S1 established. +- **Cross-target portability of Postgres-only features.** Postgres-pack-contributed entity kinds are Postgres-only by design. +- **The S1-deferred structural follow-ups** ([TML-2743](https://linear.app/prisma-company/issue/TML-2743) findSqlTable, [TML-2744](https://linear.app/prisma-company/issue/TML-2744) stripNamespaceKinds, [TML-2745](https://linear.app/prisma-company/issue/TML-2745) query-builder UnboundTables). Tracked independently; not gating this umbrella. - **`projects/` folder cleanup** of older rolled-up project folders (`target-extensible-ir/`, `namespace-exemplar/`). Cleanup happens at this umbrella's close-out per drive's `projects/` transient-folder discipline. # Approach -Three units of work, sequenced as a stack: +The remaining work is one sequential stack — re-planned from scratch after S1 closed, because what S1 actually shipped diverged from ADR 221's prose. + +**S2 makes the emitted IR honestly match ADR 221.** S1 proved the two-plane model in the type system but shipped storage under a `namespaces` wrapper (`contract.storage.namespaces..tables`) and left the `domain` plane unwired (models/valueObjects/types flat at the contract root). GAP 1 drops the wrapper; GAP 2 wires the domain plane. GAP 1 is on the critical path with S4 (both touch the storage-walk / identifier-emission paths), so it leads. GAP 2 is pure shape-correctness — it doesn't unblock runtime — but it lands in-line so the IR matches the ADR before feature work resumes. -**S1 establishes the IR-shape substrate** that every other unit depends on. The two-plane reshape (`contract.{domain, storage}...`) plus the pack-contributed entity-kind mechanism are what makes the rest of the umbrella ship. Postgres enum migrating off the framework-shared `types` slot is the proof the mechanism works — without an exemplar, the descriptor surface ships untested. S1's durable decisions live in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md); it was delivered across five merged slices. +**S3 makes `public` a real namespace.** Today every single-namespace Postgres contract uses the `__unbound__` sentinel, and runtime fakes qualification by string-prefixing `"public".`. S3 flips the PSL interpreter so un-namespaced models default to the `public` namespace, makes `__unbound__` an explicit opt-in, and deletes the hardcoded prefix logic. This is the prerequisite for S4 to emit `"public"."user"` honestly (from a namespace that exists in the contract) rather than by interpolation. It regenerates the in-repo Postgres contract artifacts to carry the `public` namespace. -**S2 makes the namespace IR actually useful for runtime queries.** PR #534 shipped namespace-aware storage IR but the runtime kept emitting unqualified identifiers, and the DSL/ORM kept reading from a flat-by-name surface. S2 closes the loop on both: runtime SQL qualifies identifiers (no more `SELECT * FROM user` when the table is `auth.user`); DSL/ORM reads through a per-family default-namespace fallback (`db.sql.user` resolves to `'public'.user` for Postgres, `'__unbound__'.user` for Mongo/SQLite). The fallback is the **load-bearing backward-compatibility mechanism**: existing single-default-namespace consumers experience no query-API breakage from this umbrella's work. +**S4 makes the namespace IR useful for runtime queries.** Runtime SQL qualifies identifiers (no more `SELECT * FROM user` when the table lives in `auth`); DSL/ORM reads through a per-family default-namespace fallback (`db.sql.user` resolves to `public.user` for Postgres now that S3 makes `public` real, `__unbound__.user` for Mongo/SQLite). The fallback is the **load-bearing backward-compatibility mechanism**: existing single-default-namespace consumers experience no query-API breakage. -**S3 adds the explicit namespace-aware DSL/ORM surface.** `db.sql.auth.user`, `db.auth.User`. Purely additive on S2 — default-namespace lookups keep working — so it can land independently without breaking anyone. Multi-namespace consumers opt in to explicit navigation when they need it. +**S5 adds the explicit namespace-aware surface.** `db.sql.auth.user`, `db.auth.User`. Purely additive on S4 — default-namespace lookups keep working — so it can land independently or defer. Multi-namespace consumers opt in when they need explicit navigation. The composition's two load-bearing properties: -1. **The shipping scope (S1 + S2) introduces zero user-facing query-API breakage.** Existing consumers on default-namespace contracts write the same query code they wrote before this umbrella. -2. **S1's substrate makes future pack-contributed entity kinds cheap.** RLS policies, roles, sequences, materialised views — every Postgres-only feature future work needs to ship is one descriptor registration through `AuthoringContributions.entityTypes` plus a slot key. The Supabase integration's downstream work pays only the per-feature cost, not the per-feature substrate cost. +1. **The must-ship scope (S2 + S3 + S4) introduces zero user-facing query-API breakage.** Existing consumers on default-namespace contracts write the same query code they wrote before this umbrella; their regenerated contracts change shape, but their query code does not. +2. **S1's substrate makes future pack-contributed entity kinds cheap.** RLS policies, roles, sequences, materialised views — every Postgres-only feature future work needs to ship is one descriptor registration plus a slot key. The Supabase integration pays only the per-feature cost, not the per-feature substrate cost. # Project Definition of Done -- [ ] **PDoD1.** All three units (S1, S2, S3) delivered, or the additive unit (S3) explicitly deferred to a sibling initiative with the deferral recorded in `projects/target-extensible-ir-namespaces/deferred.md`. -- [ ] **PDoD2.** A multi-namespace Postgres contract is authorable (PSL + TS DSL), emittable, queryable end-to-end. Verified by an integration test that exercises a two-namespace contract through PSL authoring → contract emission → runtime DSL query → SQL execution against PGlite. (Lives in the S1 plan as one of its acceptance checks, plus a smoke test in S2.) -- [ ] **PDoD3.** The pack-contributed entity-kind substrate is exercised end-to-end. Postgres enum migrates from the framework-shared `storage..types` slot to a Postgres-pack-contributed `storage..postgresEnums` slot. No hardcoded `'postgres-enum'` paths or codec-hook hacks remain in `packages/1-framework/**` or `packages/2-sql/9-family/**` (audit gate: grep returns hits only in Postgres-target / Postgres-adapter packages + test fixtures). Inherits from S1's PDoD3. -- [ ] **PDoD4.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate. The demo's emitted SQL contains `"public"."user"` (Postgres) / `"user"` (SQLite no-op) / `auth.users` (Mongo collection key) consistently. Inherits from S2's acceptance. -- [ ] **PDoD5.** Existing consumers on single-default-namespace contracts experience zero query-API breakage caused by this umbrella's work. Verified by: (a) demo apps' query code under `examples/prisma-next-demo/src/queries/` compiles and runs unchanged across S1+S2 landings, and (b) a regression test demonstrates `db.sql.user` resolves to the default namespace's `user` table with no explicit namespace argument. -- [ ] **PDoD6.** Long-lived ADRs migrated to `docs/architecture docs/adrs/`: - - ADR 0001 (contract IR planes + entity-coordinate + pack-contributed entity-kind mechanism) from S1 - - Any ADR S2 produces about the default-namespace family-façade convention (if the discussion during execution surfaces enough design to warrant one) - - Any ADR S3 produces about the namespace-aware DSL/ORM surface shape (if warranted) -- [ ] **PDoD7.** Linear Project "Target-Extensible IR + Namespaces" marked Completed (auto via GitHub PR-merge integration when the close-out PR lands referencing the appropriate Linear identifiers). -- [ ] **PDoD8.** Rolled-up project folders (`projects/target-extensible-ir/`, `projects/namespace-exemplar/`) archived or deleted with their long-lived contents migrated to `docs/` per drive's `projects/` transient-folder discipline. The umbrella's own `projects/target-extensible-ir-namespaces/` folder also deleted at close-out. -- [ ] **PDoD9.** Repo-wide references to `projects/target-extensible-ir-namespaces/**` and rolled-up sibling folders removed / replaced with `docs/` links. +- [ ] **PDoD1.** Must-ship units (S2, S3, S4) delivered. The additive unit (S5) delivered, or explicitly deferred to a sibling initiative with the deferral recorded in `projects/target-extensible-ir-namespaces/deferred.md`. +- [ ] **PDoD2.** The emitted contract IR matches ADR 221's canonical shape: storage is `contract.storage..` with no `namespaces` wrapper segment, and the domain plane is wired as `contract.domain..{models, valueObjects, types}`. Verified by `pnpm fixtures:check` against regenerated artifacts plus a grep gate confirming no `storage.namespaces` wrapper path remains. Delivered by S2. +- [ ] **PDoD3.** Un-namespaced Postgres models default to the `public` namespace at the PSL interpreter; opting a model into `__unbound__` is an explicit PSL affordance; the hardcoded `"public".`-prefixing logic is deleted. Verified by an authoring round-trip test and a grep gate over the removed prefix logic. Delivered by S3. +- [ ] **PDoD4.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate. The demo's emitted SQL contains `"public"."user"` (Postgres) / `"user"` (SQLite no-op) / `auth.users` (Mongo collection key) consistently. Delivered by S3 (makes `public` real) + S4 (renders it). +- [ ] **PDoD5.** Existing consumers on single-default-namespace contracts experience zero query-API breakage caused by this umbrella's work. Verified by: (a) demo apps' query code under `examples/prisma-next-demo/src/queries/` compiles and runs unchanged across S3+S4 landings, and (b) a regression test demonstrates `db.sql.user` resolves to the default namespace's `user` table with no explicit namespace argument. +- [ ] **PDoD6.** A multi-namespace Postgres contract is authorable (PSL + TS DSL), emittable, and queryable end-to-end. Verified by an integration test exercising a two-namespace contract through PSL authoring → emission → runtime DSL query → SQL execution against PGlite. Delivered by S2 (shape) + S4 (queryable). +- [ ] **PDoD7.** The pack-contributed entity-kind substrate is exercised end-to-end (Postgres enum off the framework-shared slot). Inherited from S1 (closed). +- [ ] **PDoD8.** Long-lived ADRs migrated to `docs/architecture docs/adrs/`: S1's ADR 221 (already migrated); any ADR S2–S4 produce about the canonical-shape migration, the Postgres default-namespace policy, or the default-namespace family-façade convention (if execution surfaces enough design to warrant one). +- [ ] **PDoD9.** Linear Project "Target-Extensible IR + Namespaces" marked Completed (auto via GitHub PR-merge integration when the close-out PR lands). +- [ ] **PDoD10.** Rolled-up predecessor project folders (`projects/target-extensible-ir/`, `projects/namespace-exemplar/`) archived or deleted with long-lived contents migrated to `docs/`; the umbrella's own `projects/target-extensible-ir-namespaces/` folder deleted at close-out; repo-wide references stripped / replaced with `docs/` links. # Functional Requirements -- **FR1.** Pack-contributed entity-kind mechanism exists at the framework level. Target packs register new entity kinds (storage-slot key, IR-class factory, serializer hydration, validator schema) through `AuthoringContributions.entityTypes`. Delivered by S1. -- **FR2.** Contract IR follows the canonical shape `contract.{domain, storage}...` everywhere. Cross-references use object pairs. Entity coordinate `(namespaceId, entityKind, entityName)` is the canonical addressing primitive. Delivered by S1. -- **FR3.** Postgres enum migrates to a Postgres-pack-contributed entity slot; framework-shared `storage..types` slot deleted as a load-bearing surface. Delivered by S1 (acts as the substrate's exemplar). -- **FR4.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate. Delivered by S2. -- **FR5.** DSL/ORM exposes the existing flat-by-name surface (`db.sql.
`, `db.`), with every lookup resolving through a per-family default namespace (`'public'` for Postgres; `'__unbound__'` for Mongo/SQLite) hardcoded in the family façade function. Delivered by S2. -- **FR6.** Explicit namespace-aware DSL/ORM surface (`db.sql..
`, `db..`) ships as purely additive on FR5. Delivered by S3. +- **FR1.** Pack-contributed entity-kind mechanism exists at the framework level. Delivered by S1 (closed). +- **FR2.** Contract IR follows the canonical shape `contract.{domain, storage}...` everywhere, with no `namespaces` wrapper segment in the storage plane and the domain plane wired. Entity coordinate `(namespaceId, entityKind, entityName)` is the canonical addressing primitive. Substrate delivered by S1; canonical shape completed by **S2**. +- **FR3.** Postgres enum migrates to a Postgres-pack-contributed entity slot; framework-shared `types` slot deleted as a load-bearing surface. Delivered by S1 (closed). +- **FR4.** The Postgres PSL interpreter interprets models with no explicit namespace as belonging to the `public` namespace; `__unbound__` is an explicit PSL opt-in. Hardcoded `public`-prefixing logic deleted. Delivered by **S3**. +- **FR5.** Runtime SQL emission qualifies every identifier by its namespace's family-specific DDL coordinate. Delivered by **S4**. +- **FR6.** DSL/ORM exposes the existing flat-by-name surface (`db.sql.
`, `db.`), with every lookup resolving through a per-family default namespace hardcoded in the family façade function. Delivered by **S4**. +- **FR7.** Explicit namespace-aware DSL/ORM surface (`db.sql..
`, `db..`) ships as purely additive on FR6. Delivered by **S5** (deferrable). # Non-Functional Requirements - **NFR1.** No regression in `pnpm test:packages` or `pnpm test:integration` runtime across the umbrella's lifetime. -- **NFR2.** Generated `contract.d.ts` file sizes do not 2x compared to pre-S1 baseline. The new nested shape adds depth but should not balloon the type bytes. -- **NFR3.** S2's runtime qualification adds at most one identifier-lookup hop per query — no quadratic re-traversal of the storage IR per query. -- **NFR4.** S3's explicit-namespace surface compiles in TypeScript without ballooning the inferred `Db` type beyond what S2 already produces. Verified by a tsc trace if the bound is breached. +- **NFR2.** Generated `contract.d.ts` file sizes do not 2x compared to the pre-S2 baseline. The canonical nested shape changes depth but should not balloon the type bytes. +- **NFR3.** S4's runtime qualification adds at most one identifier-lookup hop per query — no quadratic re-traversal of the storage IR per query. +- **NFR4.** S5's explicit-namespace surface compiles in TypeScript without ballooning the inferred `Db` type beyond what S4 already produces. Verified by a tsc trace if the bound is breached. # Constraints + Assumptions -- **A1.** PR #534 (TML-2520) is merged into `main`. Met as of `66da80f96`. -- **A2.** The Supabase integration consumes this substrate. Specifically, the Supabase pack will register pack-contributed entity kinds for `policy` (RLS) and `role` through the S1-delivered descriptor mechanism. Verified by Supabase initiative's own project planning, not this umbrella. -- **A3.** Default-namespace fallback (S2) is sufficient for consumers on default-namespace-only contracts. Falsified if consumers start writing multi-namespace contracts before S3 lands — would force a deprecation conversation about the flat DSL surface. Mitigation: document the namespace-aware surface as a planned addition. -- **A4.** S1's slice plan holds without re-shaping. *(Resolved — S1 closed; durable decisions in [ADR 221](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md).)* S1's assumptions held without forcing an umbrella-level re-sequencing. +- **A1.** S1 is closed; ADR 221 captures the durable decisions; the two-plane IR substrate, entity coordinate, and pack-contributed entity-kind mechanism exist. +- **A2.** The Supabase integration consumes this substrate. The Supabase pack will register pack-contributed entity kinds for `policy` (RLS) and `role` through the S1-delivered descriptor mechanism. Verified by the Supabase initiative's own planning, not this umbrella. +- **A3.** Flipping the Postgres PSL default from `__unbound__` to `public` (S3) requires regenerating the in-repo Postgres contract artifacts. This umbrella owns that regeneration; downstream consumers whose *source* shape changes get upgrade instructions. +- **A4.** Default-namespace fallback (S4) is sufficient for consumers on default-namespace-only contracts. Falsified if consumers start writing multi-namespace contracts before S5 lands — would force a deprecation conversation about the flat DSL surface. Mitigation: document the namespace-aware surface as a planned addition. +- **A5.** GAP 2 (domain plane) is pure shape-correctness and does not unblock the runtime story. It stays in must-ship to honor ADR 221, but is the first deferral candidate under schedule pressure. # Open Questions -These are residual umbrella-level questions; sub-project / slice specs own their own open questions. +These are residual umbrella-level questions; slice specs own their own. -1. **Should the umbrella's close-out absorb the legacy-project-folders cleanup**, or is that a separate housekeeping pass? Working position: **absorb at close-out** — `projects/` is transient per drive discipline, and the umbrella's close-out is the natural moment to archive `projects/target-extensible-ir/`, `projects/namespace-exemplar/`, and similar rolled-up artifacts. Alternative: file a separate cleanup project. Cost-benefit favours absorption. -2. **Does S2 produce a long-lived ADR**, or is the default-namespace family-façade convention narrow enough to live only in S2's slice spec? Working position: **S2 produces an ADR if execution surfaces enough design content to warrant one** — likely yes, because "family façade hardcodes its own default namespace" is a load-bearing convention future families will need to know. -3. **Does S3 produce a long-lived ADR** about the namespace-aware DSL/ORM surface shape? Working position: **likely yes** — the explicit-namespace surface design is what every future authoring affordance will follow. -4. **Linear ticket creation for the umbrella's S1 slices.** S1's plan names 6 internal slices that need Linear tickets. Working position: **create all six during the Linear audit pass that runs alongside this umbrella's plan drafting** so the audit pass resolves duplicate/stale tickets in the same touch. +1. **Does S3 warrant a long-lived ADR** about the Postgres default-namespace policy (`public`-by-default, `__unbound__` opt-in)? Working position: **likely yes** — it's a load-bearing authoring convention that changes the meaning of every existing Postgres schema. +2. **Does S4 produce a long-lived ADR** about the default-namespace family-façade convention? Working position: **likely yes** — "family façade hardcodes its own default namespace" is a load-bearing convention future families will need to know. +3. **Does S5 produce a long-lived ADR** about the namespace-aware DSL/ORM surface shape? Working position: **likely yes** — the explicit-namespace surface design is what every future authoring affordance will follow. # References - **Linear Project:** [Target-Extensible IR + Namespaces](https://linear.app/prisma-company/project/target-extensible-ir-namespaces-fd69eff8aec6) - **Tracking tickets:** - - [TML-2520](https://linear.app/prisma-company/issue/TML-2520) — namespace exemplar; PR #534 merged at commit `66da80f96` (predecessor, not in umbrella scope) - - [TML-2584](https://linear.app/prisma-company/issue/TML-2584) — contract IR planes (S1) - - [TML-2605](https://linear.app/prisma-company/issue/TML-2605) — runtime SQL qualification (S2) - - [TML-2550](https://linear.app/prisma-company/issue/TML-2550) — explicit namespace-aware DSL surface (S3) + - [TML-2520](https://linear.app/prisma-company/issue/TML-2520) — namespace exemplar; PR #534 merged (predecessor, not in umbrella scope) + - [TML-2584](https://linear.app/prisma-company/issue/TML-2584) — contract IR planes (S1, Done) + - [TML-2747](https://linear.app/prisma-company/issue/TML-2747) — IR canonicalization GAP 1 (S2 first slice) + - [TML-2605](https://linear.app/prisma-company/issue/TML-2605) — runtime SQL qualification (S4) + - [TML-2550](https://linear.app/prisma-company/issue/TML-2550) — explicit namespace-aware DSL surface (S5) - [TML-2537](https://linear.app/prisma-company/issue/TML-2537) — target-contributed PSL blocks (separate project; out of umbrella) - **S1 (closed) durable decisions:** - - [ADR 221 — Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md) (S1's spec/plan were transient and removed at S1's close-out) -- **Downstream consumer (out of scope):** `projects/supabase-integration/` — the initiative this umbrella's substrate enables. + - [ADR 221 — Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds](../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md) +- **Downstream consumer (out of scope):** the Supabase integration initiative this umbrella's substrate enables. - **Reference docs:** - [Architecture Overview](../../docs/Architecture%20Overview.md) - - [ADR 004 — Storage Hash vs Profile Hash](../../docs/architecture%20docs/adrs/ADR%20004%20-%20Storage%20Hash%20vs%20Profile%20Hash.md) — the prior art S1's two-plane reshape generalises + - [ADR 004 — Storage Hash vs Profile Hash](../../docs/architecture%20docs/adrs/ADR%20004%20-%20Storage%20Hash%20vs%20Profile%20Hash.md) — prior art the two-plane reshape generalises From 9068522397fe9a14b6cd2cf7ea68a30091f1fed4 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 21:25:29 +0200 Subject: [PATCH 02/60] =?UTF-8?q?docs(drive):=20spec=20GAP=201=20slice=20?= =?UTF-8?q?=E2=80=94=20drop=20the=20storage.namespaces=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intent-driven slice spec for TML-2747: bring the storage plane to ADR 221's canonical `storage.` shape (no `namespaces` segment; `storageHash` a reserved sibling key). File discovery left to the implementer. Signed-off-by: Will Madden --- .../storage-namespaces-wrapper-drop/spec.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/spec.md diff --git a/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/spec.md b/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/spec.md new file mode 100644 index 0000000000..811b3bb810 --- /dev/null +++ b/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/spec.md @@ -0,0 +1,99 @@ +# Slice: storage-namespaces-wrapper-drop + +_Parent project `projects/target-extensible-ir-namespaces/` (S2, GAP 1). Outcome: the emitted storage plane matches ADR 221's canonical shape — `storage.` with no `namespaces` wrapper segment — closing the first half of S1's unfinished FR2._ + +## At a glance + +Today the emitted contract puts every namespace under a literal `storage.namespaces.` wrapper. ADR 221's canonical shape has no such segment: namespace IDs are keys directly under `storage`, alongside the reserved `storageHash`. This slice drops the wrapper everywhere — IR types, serializer/deserializer, validators, canonicalization, emitter — and regenerates every on-disk contract. It unblocks honest runtime qualification (S4) and makes the IR match the ADR the project already shipped. + +## Chosen design + +ADR 221 § grounding example is the authority. The storage plane's canonical shape: + +**Before (shipped by S1):** + +```jsonc +"storage": { + "namespaces": { + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { "bug": { /* columns … */ } } + } + } +} +``` + +**After (ADR 221 canonical):** + +```jsonc +"storage": { + "storageHash": "…", + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { "bug": { /* columns … */ } } + } +} +``` + +The decided shape, stated precisely: + +- The literal word `"namespaces"` never appears as a storage-plane IR segment (ADR 221 commitment 2). Namespace IDs are the direct keys under `storage`. +- `storageHash` is a **reserved sibling key** under `storage`, not a namespace. Any walk over `storage` that enumerates namespaces must treat `storageHash` as reserved and skip it. +- `elementCoordinates` (the canonical free-function walk) stops descending through a `namespaces` segment and instead iterates the namespace-ID keys directly under the storage object. +- This is the JSON shape **and** the in-memory shape (per the json-canonical-class-in-memory pattern). The in-memory storage representation must expose `storageHash` as a typed member while presenting namespace IDs as the entity-bearing keys; how that's typed (reserved-key handling on the family storage classes) is the implementer's call — see Open Questions. + +This is a wrapper-removal refactor, not a semantic change: the same namespaces, entity kinds, and entities are present before and after; only their path shortens by one segment. Because `storageHash` is content-addressed over the canonical storage object, the hash of every contract changes — so the slice is **not** done until all on-disk artifacts are regenerated and `fixtures:check` is clean. + +## Coherence rationale + +One PR. The wrapper appears in one logical place (the storage shape) but is read by many consumers — the framework storage type + `elementCoordinates` walk, the SQL/Mongo family serializer/deserializer + validators, canonicalization, the emitter, and every on-disk `contract.json` / `contract.d.ts`. Splitting the type change from the consumer migration or the fixture regen would leave the IR in a half-migrated, unhashable state that can't pass `fixtures:check` — there's no coherent intermediate PR. One reviewer holds "the wrapper is gone, end to end, and hashes are regenerated" in one sitting. + +## Scope + +**In:** + +- Framework storage IR shape + the `elementCoordinates` walk (drop the `namespaces` segment; skip the reserved `storageHash` key). +- SQL-family (`SqlStorage`) and Mongo-family storage shapes — wrapper removed. +- Serializer / deserializer hydration and the namespace-walk validators. +- Canonicalization + the contract emitter (`contract.json` + `contract.d.ts`). +- Regeneration of all on-disk artifacts: example apps, package test fixtures, extension contract spaces, and any migration metadata pinned to a `storageHash`. + +**Out:** + +- The `domain` plane (flat `contract.models` / `valueObjects` / `types` → `contract.domain..{...}`) — that's GAP 2, the next slice. +- Postgres `public`-by-default at the PSL interpreter — S3. +- Runtime SQL qualification / DSL-ORM fallback — S4. +- The S1-deferred structural follow-ups (TML-2743 `findSqlTable`, TML-2744 `stripNamespaceKinds`, TML-2745 query-builder `UnboundTables`). Touch them only if the wrapper drop mechanically requires it; otherwise leave them. + +## Contract-impact + +Touches the contract surface directly. The `storage` plane shape changes for every target family (postgres / sqlite / mongo). No entity kinds are added or removed; the wrapper segment is removed. Downstream: `storageHash` (and therefore any `profileHash` derived over it) changes for every contract — every committed `contract.json`, `contract.d.ts`, and `storageHash`-pinned migration ref regenerates. Downstream consumers that walk the IR shape programmatically (not through DSL handles) see the shorter path; if any consumer's **source** shape changes, record upgrade instructions (see `record-upgrade-instructions`). + +## Adapter-impact + +All SQL targets (postgres, sqlite) and mongo are affected via their family serializers/validators. No target gains or loses capability; each family's storage shape loses the `namespaces` wrapper symmetrically. + +## Pre-investigated edge cases + +| Edge case | Disposition | Notes | +|---|---|---| +| `storageHash` enumerated as if it were a namespace | Handle | After the wrapper drops, `storageHash` is a sibling of the namespace-ID keys under `storage`. Every walk that enumerates namespaces (`elementCoordinates`, validators, serializer) must treat `storageHash` as a reserved key and skip it. | +| Fixture/hash regen doesn't cascade to extension migration pins | Handle | `pnpm fixtures:emit` regenerates `contract.{json,d.ts}` but is known not to chain the per-extension migration regen loop (TML-2698). Extension `migrations/refs/head.json` + per-migration `to`/`from` hash pins may need separate regeneration; `fixtures:check` surfaces the drift. | + +## Slice-specific done conditions + +- [ ] A grep gate confirms no `storage.namespaces` wrapper path remains — neither in emitted artifacts (`**/contract.json`, `**/contract.d.ts`) nor in source that reads/writes the storage shape. +- [ ] All on-disk contract + migration artifacts regenerated and committed; `pnpm fixtures:check` clean. + +## Open Questions + +1. **How is the reserved-key storage map typed in-memory?** Mixing a typed `storageHash: string` with namespace-ID keys (each a `Namespace`) under one object is the reserved-key-in-a-map tension the `namespaces` wrapper originally sidestepped. Working position: keep `storageHash` a typed member on the family storage class and present namespaces through the entity-coordinate walk / non-enumerable-vs-enumerable property discipline already used for `kind`; the serializer writes namespace IDs as direct keys and `storageHash` as a reserved sibling. The implementer picks the cleanest typing that avoids `any` / wide casts. **Refusal trigger:** if flattening the in-memory type forces a structural rewrite beyond this slice (e.g. a query-builder type rewrite à la TML-2745), stop and report — that's a re-decomposition, not part of GAP 1. + +## References + +- Parent project: [`projects/target-extensible-ir-namespaces/spec.md`](../../spec.md) +- Linear issue: [TML-2747](https://linear.app/prisma-company/issue/TML-2747) +- [ADR 221 — Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds](../../../../docs/architecture%20docs/adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md) (commitment 2: `"namespaces"` never appears as an IR segment) +- [TML-2698](https://linear.app/prisma-company/issue/TML-2698) — `fixtures:emit` doesn't chain the per-extension migration regen loop (known fixture-regen footgun) From 148e6c853e811e6be3a7cbf657423fb68155086b Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 21:32:25 +0200 Subject: [PATCH 03/60] docs(drive): plan GAP 1 slice into two dispatches D1 surgical substrate change (wrapper drop + in-memory typing decision); D2 mechanical artifact regeneration + full gate sweep. Kept apart so design judgment and mechanical fan-out do not share a dispatch. Signed-off-by: Will Madden --- .../storage-namespaces-wrapper-drop/plan.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/plan.md diff --git a/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/plan.md b/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/plan.md new file mode 100644 index 0000000000..73f055ffc0 --- /dev/null +++ b/projects/target-extensible-ir-namespaces/slices/storage-namespaces-wrapper-drop/plan.md @@ -0,0 +1,17 @@ +## Dispatch plan + +_Slice: `storage-namespaces-wrapper-drop` (S2 GAP 1, TML-2747). Two dispatches: a surgical substrate change carrying the one design decision, then a mechanical artifact regeneration. Kept apart deliberately — bundling design judgment with mechanical fan-out is a recognised mis-sizing._ + +### Dispatch 1: Drop the wrapper across the IR substrate + +- **Outcome:** The storage plane is emitted in ADR 221's canonical shape — namespace IDs are direct keys under `storage`, `storageHash` is a reserved sibling key, and the literal `namespaces` segment is gone from the IR types, serializer/deserializer, namespace-walk validators, canonicalization, the emitter, and the `elementCoordinates` walk. The in-memory reserved-key typing decision (Open Question 1) is settled and applied without `any`/wide casts. Source typechecks; a focused emit/serialize test confirms a contract round-trips through the wrapper-less shape; the changed packages' own unit tests pass. +- **Builds on:** The slice's chosen design (ADR 221 § grounding example). +- **Hands to:** A wrapper-less IR end to end in source — `pnpm typecheck` green, changed-package unit tests green, the emitter producing `storage.`. **Committed on-disk fixtures are now stale by design** (their `storageHash` changed); the fixture-diff suites (`fixtures:check`, integration/e2e that load committed contracts) are expected red until Dispatch 2. The reserved-key typing approach is documented in the PR for the reviewer. +- **Focus:** The shape change only. Do **not** touch the `domain` plane, the Postgres default-namespace policy, or runtime qualification. Honour the spec's refusal trigger: if flattening the in-memory type forces a structural rewrite beyond this slice (e.g. a query-builder type rewrite), stop and report rather than expanding scope. Run only the gates relevant to the changed packages at this stage — leave the full-repo fixture/integration sweep to Dispatch 2. + +### Dispatch 2: Regenerate all on-disk artifacts and turn every gate green + +- **Outcome:** Every committed `contract.json` / `contract.d.ts` (examples, package fixtures, extension contract spaces) and every `storageHash`-pinned migration ref (`migrations/refs/head.json` + per-migration `to`/`from`) is regenerated to the wrapper-less shape. `pnpm fixtures:check` is clean; `pnpm typecheck`, `pnpm test:packages`, `pnpm test:integration`, `pnpm test:e2e`, and `pnpm lint:deps` are green. The slice-DoD grep gate returns zero `storage.namespaces` wrapper paths in both artifacts and source. +- **Builds on:** Dispatch 1's wrapper-less emitter + settled typing. +- **Hands to:** Slice-DoD met — PR ready for review. +- **Focus:** Mechanical regeneration + fallout repair. Prefer `pnpm fixtures:emit` / `pnpm fixtures:check`; the per-extension migration regen is known not to chain automatically (TML-2698), so regenerate extension migration metadata explicitly where `fixtures:check` surfaces drift. Any non-mechanical fallout (a test asserting the old shape that needs its *intent* re-expressed, not just its bytes) is in scope only insofar as it's the wrapper drop; a deeper behavioural surprise is a refusal-and-report. From 15671acb0a458250cf86dbda81420ceecd2d70a8 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 21:59:31 +0200 Subject: [PATCH 04/60] refactor(framework): drop storage.namespaces wrapper in framework IR substrate Namespace ids become direct keys under `storage` alongside the reserved `storageHash`/`types` keys (ADR 221 canonical shape). Adds `storage-plane-keys` with the reserved-key vocabulary plus `storageNamespaceEntries`/`storageNamespaceValues`/`getStorageNamespace` walk helpers, and reworks `elementCoordinates` to enumerate namespace-id keys directly while skipping reserved keys. `StorageBase` loses its `namespaces` member; `Storage` keeps only the typed `storageHash`. Updates the foundation testing factories and the framework unit tests to the flat shape. Signed-off-by: Will Madden --- .../contract/src/testing-factories.ts | 15 ++-- .../0-foundation/contract/src/types.ts | 10 +-- .../canonicalization-storage-sort.test.ts | 58 ++++++-------- .../contract/test/canonicalization.test.ts | 51 +++++------- .../contract/test/contract-factories.test.ts | 24 +++--- .../contract/test/contract-types.test.ts | 8 +- .../0-foundation/utils/src/assertions.ts | 3 +- .../framework-components/src/exports/ir.ts | 9 +++ .../src/ir/storage-plane-keys.ts | 80 +++++++++++++++++++ .../framework-components/src/ir/storage.ts | 55 ++++++------- .../test/element-coordinates.test.ts | 43 ++++++---- 11 files changed, 216 insertions(+), 140 deletions(-) create mode 100644 packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts diff --git a/packages/1-framework/0-foundation/contract/src/testing-factories.ts b/packages/1-framework/0-foundation/contract/src/testing-factories.ts index 0c710ce1cc..b64d2c395b 100644 --- a/packages/1-framework/0-foundation/contract/src/testing-factories.ts +++ b/packages/1-framework/0-foundation/contract/src/testing-factories.ts @@ -33,16 +33,14 @@ type ContractOverrides< const DUMMY_HASH = coreHash('sha256:test'); -const DEFAULT_FRAMEWORK_STORAGE = { namespaces: {} } as const; +const DEFAULT_FRAMEWORK_STORAGE = {} as const; const UNBOUND_NAMESPACE_ID = '__unbound__' as const; const DEFAULT_SQL_STORAGE = { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: {}, }, } as const; @@ -99,11 +97,10 @@ export function createContract< } type SqlStorageLike = StorageBase & { - readonly namespaces: Readonly< + readonly types?: Record; +} & Readonly< Record> }> >; - readonly types?: Record; -}; type SqlModelLike = ContractModel; diff --git a/packages/1-framework/0-foundation/contract/src/types.ts b/packages/1-framework/0-foundation/contract/src/types.ts index f16eb4d93c..bc6f14a23e 100644 --- a/packages/1-framework/0-foundation/contract/src/types.ts +++ b/packages/1-framework/0-foundation/contract/src/types.ts @@ -67,15 +67,15 @@ export interface StorageNamespace { /** * Base type for family-specific storage blocks. * Family storage types (SqlStorage, MongoStorage, etc.) extend this to carry the - * storage hash alongside family-specific data (tables, collections, etc.). + * storage hash alongside namespace entries keyed directly by namespace id + * (ADR 221 — no `namespaces` wrapper segment). * - * The `namespaces` map is carried by every hydrated storage block. Serialized - * envelope shape is target-owned; this types the in-memory contract after - * `deserializeContract`. + * Namespace entries are dynamic keys on the storage object; use + * `storageNamespaceEntries` from `@prisma-next/framework-components/ir` + * to walk them without treating `storageHash` / `types` as namespaces. */ export interface StorageBase { readonly storageHash: StorageHashBase; - readonly namespaces: Readonly>; } export interface FieldType { diff --git a/packages/1-framework/0-foundation/contract/test/canonicalization-storage-sort.test.ts b/packages/1-framework/0-foundation/contract/test/canonicalization-storage-sort.test.ts index 66e3563bfb..33304bf7f9 100644 --- a/packages/1-framework/0-foundation/contract/test/canonicalization-storage-sort.test.ts +++ b/packages/1-framework/0-foundation/contract/test/canonicalization-storage-sort.test.ts @@ -6,7 +6,7 @@ import { } from '../src/canonicalization-storage-sort'; const sqlSortTargets = [ - { path: ['namespaces', '*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, + { path: ['*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, ] as const satisfies readonly NamedArraySortTarget[]; describe('createStorageSort', () => { @@ -14,22 +14,20 @@ describe('createStorageSort', () => { it('sorts indexes and uniques by name within each table', () => { const storage = { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - users: { - columns: {}, - indexes: [{ name: 'idx_z' }, { name: 'idx_a' }], - uniques: [{ name: 'uq_z' }, { name: 'uq_a' }], - }, + __unbound__: { + id: '__unbound__', + tables: { + users: { + columns: {}, + indexes: [{ name: 'idx_z' }, { name: 'idx_a' }], + uniques: [{ name: 'uq_z' }, { name: 'uq_a' }], }, }, }, }; const sorted = sortStorage(storage) as typeof storage; - const table = sorted.namespaces.__unbound__.tables.users; + const table = sorted.__unbound__.tables.users; expect(table.indexes.map((i) => i.name)).toEqual(['idx_a', 'idx_z']); expect(table.uniques.map((u) => u.name)).toEqual(['uq_a', 'uq_z']); }); @@ -41,11 +39,9 @@ describe('createStorageSort', () => { it('passes through namespaces without tables', () => { const storage = { - namespaces: { - __unbound__: { - id: '__unbound__', - collections: { posts: { columns: {} } }, - }, + __unbound__: { + id: '__unbound__', + collections: { posts: { columns: {} } }, }, }; expect(sortStorage(storage)).toEqual(storage); @@ -53,35 +49,31 @@ describe('createStorageSort', () => { it('passes non-object namespace and table entries through unchanged', () => { const storage = { - namespaces: { - broken: null, - __unbound__: { - id: '__unbound__', - tables: { bad: null }, - }, + broken: null, + __unbound__: { + id: '__unbound__', + tables: { bad: null }, }, }; const sorted = sortStorage(storage) as typeof storage; - expect(sorted.namespaces.broken).toBeNull(); - expect(sorted.namespaces.__unbound__.tables.bad).toBeNull(); + expect(sorted.broken).toBeNull(); + expect(sorted.__unbound__.tables.bad).toBeNull(); }); it('sorts entries without name using empty-string fallback', () => { const storage = { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - users: { - columns: {}, - indexes: [{ columns: ['b'] }, { name: 'idx_a', columns: ['a'] }], - }, + __unbound__: { + id: '__unbound__', + tables: { + users: { + columns: {}, + indexes: [{ columns: ['b'] }, { name: 'idx_a', columns: ['a'] }], }, }, }, }; const sorted = sortStorage(storage) as typeof storage; - const indexes = sorted.namespaces.__unbound__.tables.users.indexes; + const indexes = sorted.__unbound__.tables.users.indexes; expect(indexes[0]).toEqual({ columns: ['b'] }); expect(indexes[1]).toEqual({ name: 'idx_a', columns: ['a'] }); }); diff --git a/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts b/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts index 38c3171587..e8cf4484f3 100644 --- a/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts +++ b/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts @@ -38,15 +38,15 @@ function canonicalizeContract( } const sqlPreserveEmptyPatterns = [ - ['storage', 'namespaces', '*', 'tables'], - ['storage', 'namespaces', '*', 'tables', '*'], - ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], - ['storage', 'namespaces', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], + ['storage', '*', 'tables'], + ['storage', '*', 'tables', '*'], + ['storage', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], ['storage', 'types', '*', 'typeParams'], ] as const satisfies readonly PathPattern[]; const sqlSortTargets = [ - { path: ['namespaces', '*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, + { path: ['*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, ] as const satisfies readonly NamedArraySortTarget[]; const sqlPreserveEmpty = createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); @@ -58,7 +58,7 @@ function minimal(overrides?: Record): Contract { target: 'postgres', roots: {}, models: {}, - storage: { storageHash: coreHash('sha256:stub'), namespaces: {} }, + storage: { storageHash: coreHash('sha256:stub') }, extensionPacks: {}, capabilities: {}, meta: {}, @@ -72,9 +72,7 @@ const UNBOUND = '__unbound__'; function unboundStorage(tables: Record): Record { return { storageHash: 'sha256:stub', - namespaces: { - [UNBOUND]: { id: UNBOUND, tables }, - }, + [UNBOUND]: { id: UNBOUND, tables }, }; } @@ -87,7 +85,7 @@ function drill(obj: Record, ...keys: string[]): Record): Record { - return drill(result, 'storage', 'namespaces', UNBOUND, 'tables'); + return drill(result, 'storage', UNBOUND, 'tables'); } describe('canonicalizeContractToObject', () => { @@ -131,7 +129,7 @@ describe('canonicalizeContractToObject', () => { it('includes storageHash when provided inside storage', () => { const result = canonicalizeContractToObject( - minimal({ storage: { storageHash: 'sha256:abc', namespaces: {} } }), + minimal({ storage: { storageHash: 'sha256:abc' } }), ); expect(drill(result, 'storage')['storageHash']).toBe('sha256:abc'); }); @@ -142,9 +140,7 @@ describe('canonicalizeContractToObject', () => { }); it('keeps storageHash inside storage', () => { - const result = canonicalizeContractToObject( - minimal({ storage: { storageHash: 'sha256:s', namespaces: {} } }), - ); + const result = canonicalizeContractToObject(minimal({ storage: { storageHash: 'sha256:s' } })); expect(result).not.toHaveProperty('storageHash'); expect(drill(result, 'storage')['storageHash']).toBe('sha256:s'); }); @@ -263,13 +259,13 @@ describe('default omission', () => { expect(result['meta']).toEqual({}); }); - it('strips empty storage.namespaces[X].tables without a shouldPreserveEmpty hook', () => { + it('strips empty storage namespace tables without a shouldPreserveEmpty hook', () => { const result = canonicalizeContractToObject(minimal({ storage: unboundStorage({}) })); - const ns = drill(result, 'storage', 'namespaces', UNBOUND) as Record; + const ns = drill(result, 'storage', UNBOUND) as Record; expect(ns).not.toHaveProperty('tables'); }); - it('preserves empty storage.namespaces[].tables when shouldPreserveEmpty hook returns true', () => { + it('preserves empty storage namespace tables when shouldPreserveEmpty hook returns true', () => { const result = canonicalizeContractToObject(minimal({ storage: unboundStorage({}) }), { shouldPreserveEmpty: sqlPreserveEmpty, }); @@ -473,7 +469,7 @@ describe('index and unique sorting', () => { it('handles storage without namespaces (no-op)', () => { const result = canonicalizeContractToObject( - minimal({ storage: { storageHash: 'sha256:stub', namespaces: {} } }), + minimal({ storage: { storageHash: 'sha256:stub' } }), ); expect(result['storage']).toBeDefined(); }); @@ -551,14 +547,12 @@ describe('index and unique sorting', () => { minimal({ storage: { storageHash: 'sha256:stub', - namespaces: { - broken: null as unknown as Record, - }, + broken: null as unknown as Record, }, }), ); const storage = drill(result, 'storage'); - expect((storage['namespaces'] as Record)['broken']).toBeNull(); + expect(storage['broken']).toBeNull(); }); it('passes namespaces without a tables map through unchanged (e.g. Mongo)', () => { @@ -566,16 +560,14 @@ describe('index and unique sorting', () => { minimal({ storage: { storageHash: 'sha256:stub', - namespaces: { - [UNBOUND]: { - id: UNBOUND, - collections: { posts: { columns: {} } }, - }, + [UNBOUND]: { + id: UNBOUND, + collections: { posts: { columns: {} } }, }, }, }), ); - const ns = drill(result, 'storage', 'namespaces', UNBOUND); + const ns = drill(result, 'storage', UNBOUND); expect(ns).not.toHaveProperty('tables'); expect(ns['collections']).toEqual({ posts: {} }); }); @@ -599,7 +591,7 @@ describe('canonicalizeContract', () => { it('produces identical output as JSON.stringify of canonicalizeContractToObject', () => { const input = minimal({ - storage: { storageHash: 'sha256:test', namespaces: {} }, + storage: { storageHash: 'sha256:test' }, profileHash: 'sha256:profile', }); const objResult = canonicalizeContractToObject(input); @@ -688,7 +680,6 @@ describe('typeParams preservation', () => { minimal({ storage: { storageHash: 'sha256:stub', - namespaces: {}, types: { MyType: { typeParams: {} } }, }, }), diff --git a/packages/1-framework/0-foundation/contract/test/contract-factories.test.ts b/packages/1-framework/0-foundation/contract/test/contract-factories.test.ts index 80fe5d96aa..3edccc0a7f 100644 --- a/packages/1-framework/0-foundation/contract/test/contract-factories.test.ts +++ b/packages/1-framework/0-foundation/contract/test/contract-factories.test.ts @@ -48,12 +48,10 @@ describe('createContract', () => { }); it('computes different storageHash for different storage', () => { - const c1 = createContract({ storage: { namespaces: {} } }); + const c1 = createContract({ storage: {} }); const c2 = createSqlContract({ storage: { - namespaces: { - public: { id: 'public', tables: { user: { columns: {} } } }, - }, + public: { id: 'public', tables: { user: { columns: {} } } }, }, }); expect(c1.storage.storageHash).not.toBe(c2.storage.storageHash); @@ -70,21 +68,21 @@ describe('createSqlContract', () => { it('includes storage with tables', () => { const contract = createSqlContract({ storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - }, + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, }, }, }, }, }, }); - const unbound = contract.storage.namespaces['__unbound__']; + const unbound = (contract.storage as Record }>)[ + '__unbound__' + ]; expect(unbound).toBeDefined(); const tables = unbound!.tables as Record; expect(tables).toHaveProperty('user'); diff --git a/packages/1-framework/0-foundation/contract/test/contract-types.test.ts b/packages/1-framework/0-foundation/contract/test/contract-types.test.ts index cad25abc7b..37f5f5d0d0 100644 --- a/packages/1-framework/0-foundation/contract/test/contract-types.test.ts +++ b/packages/1-framework/0-foundation/contract/test/contract-types.test.ts @@ -28,7 +28,7 @@ describe('unified contract types', () => { describe('StorageBase', () => { it('carries branded storageHash', () => { const hash = 'sha256:abc123' as StorageHashBase<'sha256:abc123'>; - const storage = { storageHash: hash, namespaces: {} }; + const storage = { storageHash: hash }; expect(storage.storageHash).toBe('sha256:abc123'); }); }); @@ -48,7 +48,7 @@ describe('unified contract types', () => { storage: {}, }, }, - storage: { storageHash: hash, namespaces: {} }, + storage: { storageHash: hash }, capabilities: {}, extensionPacks: {}, meta: {}, @@ -67,7 +67,7 @@ describe('unified contract types', () => { targetFamily: 'sql', roots: {}, models: {}, - storage: { storageHash: hash, namespaces: {} }, + storage: { storageHash: hash }, capabilities: {}, extensionPacks: {}, meta: {}, @@ -107,7 +107,7 @@ describe('unified contract types', () => { storage: {}, }, }, - storage: { storageHash: hash, namespaces: {} }, + storage: { storageHash: hash }, capabilities: {}, extensionPacks: {}, meta: {}, diff --git a/packages/1-framework/0-foundation/utils/src/assertions.ts b/packages/1-framework/0-foundation/utils/src/assertions.ts index fe7749c927..310195398a 100644 --- a/packages/1-framework/0-foundation/utils/src/assertions.ts +++ b/packages/1-framework/0-foundation/utils/src/assertions.ts @@ -6,7 +6,8 @@ * * @example * ```typescript - * const table = storage.namespaces[namespaceId].tables[tableName]; + * const ns = getStorageNamespace(storage as Record, namespaceId); + * const table = ns?.tables[tableName]; * assertDefined(table, `Table "${tableName}" not found`); * // table is now narrowed to non-nullable * ``` diff --git a/packages/1-framework/1-core/framework-components/src/exports/ir.ts b/packages/1-framework/1-core/framework-components/src/exports/ir.ts index a4e2c8308a..0289b5e54f 100644 --- a/packages/1-framework/1-core/framework-components/src/exports/ir.ts +++ b/packages/1-framework/1-core/framework-components/src/exports/ir.ts @@ -4,4 +4,13 @@ export type { Namespace } from '../ir/namespace'; export { NamespaceBase, UNBOUND_NAMESPACE_ID } from '../ir/namespace'; export type { EntityCoordinate, Storage } from '../ir/storage'; export { elementCoordinates } from '../ir/storage'; +export type { FlatStorageInput, StoragePlaneReservedKey } from '../ir/storage-plane-keys'; +export { + flatStorageInput, + getStorageNamespace, + isStoragePlaneReservedKey, + STORAGE_PLANE_RESERVED_KEYS, + storageNamespaceEntries, + storageNamespaceValues, +} from '../ir/storage-plane-keys'; export type { StorageType } from '../ir/storage-type'; diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts new file mode 100644 index 0000000000..ccb3623dbd --- /dev/null +++ b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts @@ -0,0 +1,80 @@ +import type { StorageHashBase } from '@prisma-next/contract/types'; +import { blindCast } from '@prisma-next/utils/casts'; +import type { Namespace } from './namespace'; + +/** + * Own-enumerable keys under a storage plane object that are not namespace + * entries. Walkers (`elementCoordinates`, validators, serializers) skip these. + */ +export const STORAGE_PLANE_RESERVED_KEYS = ['storageHash', 'types'] as const; + +export type StoragePlaneReservedKey = (typeof STORAGE_PLANE_RESERVED_KEYS)[number]; + +export function isStoragePlaneReservedKey(key: string): key is StoragePlaneReservedKey { + return (STORAGE_PLANE_RESERVED_KEYS as readonly string[]).includes(key); +} + +function isNamespaceEntry(value: unknown): value is Namespace { + return ( + typeof value === 'object' && + value !== null && + 'id' in value && + typeof (value as { id: unknown }).id === 'string' + ); +} + +/** + * Enumerate namespace-id → namespace pairs on a storage-shaped value. + * Skips {@link STORAGE_PLANE_RESERVED_KEYS} and non-namespace entries. + */ +export function* storageNamespaceEntries( + storage: Record, +): Generator { + for (const [key, value] of Object.entries(storage)) { + if (isStoragePlaneReservedKey(key)) continue; + if (isNamespaceEntry(value)) { + yield [key, value]; + } + } +} + +export function storageNamespaceValues(storage: Record): Namespace[] { + return [...storageNamespaceEntries(storage)].map(([, ns]) => ns); +} + +export function getStorageNamespace( + storage: Record, + namespaceId: string, +): Namespace | undefined { + if (isStoragePlaneReservedKey(namespaceId)) { + return undefined; + } + const value = storage[namespaceId]; + return isNamespaceEntry(value) ? value : undefined; +} + +export type FlatStorageInput = { + readonly storageHash: StorageHashBase; + readonly types?: Readonly>; +} & Readonly>; + +/** + * Spread a namespace map into flat storage input (namespace ids as direct keys). + */ +export function flatStorageInput(params: { + readonly storageHash: StorageHashBase; + readonly types?: Readonly>; + readonly namespaces: Readonly>; +}): FlatStorageInput { + const result = blindCast< + FlatStorageInput, + 'namespace map spread merges dynamic keys onto storageHash base' + >({ + storageHash: params.storageHash, + ...params.namespaces, + }); + if (params.types !== undefined) { + Object.assign(result, { types: params.types }); + } + return result; +} diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage.ts b/packages/1-framework/1-core/framework-components/src/ir/storage.ts index 33e25b854f..5094e14d4a 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/storage.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/storage.ts @@ -1,6 +1,6 @@ -import type { StorageBase } from '@prisma-next/contract/types'; +import type { StorageHashBase } from '@prisma-next/contract/types'; import type { IRNode } from './ir-node'; -import type { Namespace } from './namespace'; +import { isStoragePlaneReservedKey, storageNamespaceEntries } from './storage-plane-keys'; /** * Canonical address for a named entity in Contract IR / Schema IR. @@ -32,17 +32,17 @@ export interface EntityCoordinate { * `plane: 'storage'` (the parameter type binds the plane). * * Iterates each namespace's own-enumerable properties structurally. - * Skips `id` (known scalar); `kind` is non-enumerable on namespace + * Skips reserved storage-plane keys (`storageHash`, `types`) and the + * namespace scalar `id`. `kind` is non-enumerable on namespace * concretions and does not appear in `Object.entries`. For every other * property whose value is a non-null object, yields one coordinate per * entry key in that map. No family-specific slot vocabulary is required. */ -export function* elementCoordinates( - storage: Pick, -): Generator { - for (const [namespaceId, ns] of Object.entries(storage.namespaces)) { +export function* elementCoordinates(storage: Record): Generator { + for (const [namespaceId, ns] of storageNamespaceEntries(storage)) { for (const [entityKind, slot] of Object.entries(ns)) { if (entityKind === 'id') continue; + if (isStoragePlaneReservedKey(entityKind)) continue; if (slot === null || typeof slot !== 'object') continue; for (const entityName of Object.keys(slot)) { yield { plane: 'storage', namespaceId, entityKind, entityName }; @@ -52,18 +52,17 @@ export function* elementCoordinates( } /** - * Framework-level promise that every Contract IR / Schema IR carries a - * collection of namespaces keyed by namespace id. Family storage - * concretions (`SqlStorage`, `MongoStorage`) refine the shape with - * family-specific fields (tables, collections, enums, …); target - * concretions add target fields where the family vocabulary doesn't + * Framework-level promise that every Contract IR / Schema IR storage plane + * carries a content hash and one or more namespace entries keyed directly + * by namespace id (ADR 221 — no `namespaces` wrapper segment). Family + * storage concretions (`SqlStorage`, `MongoStorage`) refine namespace + * entries with family-specific fields (tables, collections, enums, …); + * target concretions add target fields where the family vocabulary doesn't * reach. * - * Keeping `namespaces` at the framework layer enforces that every storage - * object — across any target — is namespace-scoped. The framework can - * therefore walk the namespace map without knowing the family alphabet, and - * the `(namespace.id, name)` keying that the verifier and planner depend on - * is honest at every layer. + * `storageHash` is a typed own property; namespace ids are own-enumerable + * keys on the same object. Reserved-key skipping for walks lives in + * {@link storageNamespaceEntries} / {@link isStoragePlaneReservedKey}. * * Extends `IRNode` so the framework's IR-walking surfaces (verifiers, * serializers) can dispatch on `Storage`-typed slots through the same @@ -72,20 +71,14 @@ export function* elementCoordinates( * the interface promotion makes the typing honest. * * **Persisted envelope shape is target-owned, not framework-promised.** - * Whether the `namespaces` map appears in the on-disk JSON envelope is - * a per-target decision made by `ContractSerializer.serializeContract`. - * Some targets emit a JSON-clean namespace shape that round-trips - * through `JSON.stringify` cleanly (SQL today via the family-layer - * identity serializer); others ship runtime-only fields on their - * namespace concretions and override `serializeContract` to strip - * them (Mongo). Future open (F16): extend the per-target - * `ContractSerializer` integration-test surface with an explicit - * envelope-shape assertion for each target, so the strip-vs-pass-through - * choice is locked at test time rather than implied by the override - * presence/absence. Earned by PR2's per-target namespace lift, when - * `PostgresSchema` / `SqliteUnboundDatabase` start carrying - * target-specific fields. + * Whether runtime-only fields on namespace concretions are stripped at + * serialize time is a per-target decision made by + * `ContractSerializer.serializeContract`. Some targets emit a JSON-clean + * namespace shape that round-trips through `JSON.stringify` cleanly (SQL + * today via the family-layer identity serializer); others ship + * runtime-only fields on their namespace concretions and override + * `serializeContract` to strip them (Mongo). */ export interface Storage extends IRNode { - readonly namespaces: Readonly>; + readonly storageHash: StorageHashBase; } diff --git a/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts b/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts index 1a41632a8c..c5a7f90694 100644 --- a/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts +++ b/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts @@ -17,20 +17,19 @@ function assertStoragePlaneCoordinates(coordinates: EntityCoordinate[]): void { describe('elementCoordinates', () => { it('walks a synthetic Storage literal structurally', () => { const storage = { - namespaces: { - alpha: { - id: 'alpha', - kind: 'test-namespace', - widgets: { a: {}, b: {} }, - gadgets: { x: {} }, - skippedNull: null, - skippedScalar: 'ignored', - }, - beta: { - id: 'beta', - kind: 'test-namespace', - tables: { users: {}, posts: {}, comments: {} }, - }, + storageHash: 'sha256:test', + alpha: { + id: 'alpha', + kind: 'test-namespace', + widgets: { a: {}, b: {} }, + gadgets: { x: {} }, + skippedNull: null, + skippedScalar: 'ignored', + }, + beta: { + id: 'beta', + kind: 'test-namespace', + tables: { users: {}, posts: {}, comments: {} }, }, }; @@ -52,4 +51,20 @@ describe('elementCoordinates', () => { expect(coordinates.some((c) => c.entityKind === 'skippedNull')).toBe(false); expect(coordinates.some((c) => c.entityKind === 'skippedScalar')).toBe(false); }); + + it('skips storageHash and types reserved keys', () => { + const storage = { + storageHash: 'sha256:test', + types: { foo: { kind: 'codec-instance' } }, + __unbound__: { + id: '__unbound__', + tables: { users: {} }, + }, + }; + + const coordinates = [...elementCoordinates(storage)]; + expect(coordinates).toEqual([ + { plane: 'storage', namespaceId: '__unbound__', entityKind: 'tables', entityName: 'users' }, + ]); + }); }); From 11407814620883acb052f37ec90d163a8cfd717b Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:00:01 +0200 Subject: [PATCH 05/60] refactor(sql-contract): key namespaces directly under storage SqlStorage sets namespace ids as own-enumerable keys via `Object.defineProperty`, keeping `storageHash`/`types` as reserved siblings; `buildSqlStorageInput` spreads a namespace map into the flat input. Validators and canonicalization hooks walk the flat shape via the framework `storageNamespaceEntries`/`getStorageNamespace` helpers, and the arktype storage schema validates every non-reserved key as a namespace entry. Signed-off-by: Will Madden --- .../contract/src/canonicalization-hooks.ts | 10 +- .../1-core/contract/src/exports/types.ts | 3 + .../contract/src/index-type-validation.ts | 8 +- .../1-core/contract/src/ir/sql-storage.ts | 48 ++++++-- packages/2-sql/1-core/contract/src/types.ts | 3 + .../2-sql/1-core/contract/src/validators.ts | 104 ++++++++++-------- .../contract/test/element-coordinates.test.ts | 16 +-- 7 files changed, 125 insertions(+), 67 deletions(-) diff --git a/packages/2-sql/1-core/contract/src/canonicalization-hooks.ts b/packages/2-sql/1-core/contract/src/canonicalization-hooks.ts index 794373d9a5..415771d048 100644 --- a/packages/2-sql/1-core/contract/src/canonicalization-hooks.ts +++ b/packages/2-sql/1-core/contract/src/canonicalization-hooks.ts @@ -7,15 +7,15 @@ import { } from '@prisma-next/contract/hashing-utils'; const preserveEmptyPatterns = [ - ['storage', 'namespaces', '*', 'tables'], - ['storage', 'namespaces', '*', 'tables', '*'], - ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], - ['storage', 'namespaces', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], + ['storage', '*', 'tables'], + ['storage', '*', 'tables', '*'], + ['storage', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], ['storage', 'types', '*', 'typeParams'], ] as const satisfies readonly PathPattern[]; const sortTargets = [ - { path: ['namespaces', '*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, + { path: ['*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, ] as const satisfies readonly NamedArraySortTarget[]; const shouldPreserveEmpty: PreserveEmptyPredicate = diff --git a/packages/2-sql/1-core/contract/src/exports/types.ts b/packages/2-sql/1-core/contract/src/exports/types.ts index d3ed6cab48..6cd7af5dd3 100644 --- a/packages/2-sql/1-core/contract/src/exports/types.ts +++ b/packages/2-sql/1-core/contract/src/exports/types.ts @@ -23,9 +23,11 @@ export type { ResolveCodecTypes, SqlModelFieldStorage, SqlModelStorage, + SqlNamespace, SqlNamespaceTablesInput, SqlQueryOperationTypes, SqlStorageInput, + SqlStorageNamespacesInput, SqlStorageTypeEntry, StorageColumnInput, StorageTableInput, @@ -39,6 +41,7 @@ export { applyFkDefaults, buildSqlNamespace, buildSqlNamespaceMap, + buildSqlStorageInput, CODEC_INSTANCE_KIND, DEFAULT_FK_CONSTRAINT, DEFAULT_FK_INDEX, diff --git a/packages/2-sql/1-core/contract/src/index-type-validation.ts b/packages/2-sql/1-core/contract/src/index-type-validation.ts index b5266cf9ef..d6e26e8ee1 100644 --- a/packages/2-sql/1-core/contract/src/index-type-validation.ts +++ b/packages/2-sql/1-core/contract/src/index-type-validation.ts @@ -1,15 +1,19 @@ import { ContractValidationError } from '@prisma-next/contract/contract-validation-error'; import type { Contract } from '@prisma-next/contract/types'; +import { storageNamespaceEntries } from '@prisma-next/framework-components/ir'; import { type } from 'arktype'; import type { IndexTypeRegistry } from './index-types'; +import type { SqlNamespace } from './ir/sql-storage'; import type { SqlStorage, StorageTable } from './types'; export function validateIndexTypes( contract: Contract, indexTypeRegistry: IndexTypeRegistry, ): void { - for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) { - for (const [tableName, rawTable] of Object.entries(ns.tables)) { + for (const [namespaceId, ns] of [ + ...storageNamespaceEntries(contract.storage as unknown as Record), + ]) { + for (const [tableName, rawTable] of Object.entries((ns as SqlNamespace).tables)) { const table = rawTable as StorageTable; for (const index of table.indexes) { if (index.type === undefined && index.options !== undefined) { diff --git a/packages/2-sql/1-core/contract/src/ir/sql-storage.ts b/packages/2-sql/1-core/contract/src/ir/sql-storage.ts index a8793d427d..9348cd6a36 100644 --- a/packages/2-sql/1-core/contract/src/ir/sql-storage.ts +++ b/packages/2-sql/1-core/contract/src/ir/sql-storage.ts @@ -1,5 +1,11 @@ import type { StorageHashBase } from '@prisma-next/contract/types'; -import { freezeNode, type Namespace, type Storage } from '@prisma-next/framework-components/ir'; +import { + flatStorageInput, + freezeNode, + isStoragePlaneReservedKey, + type Namespace, + type Storage, +} from '@prisma-next/framework-components/ir'; import { isPostgresEnumStorageEntry, type PostgresEnumStorageEntry, @@ -16,7 +22,7 @@ import { * Polymorphic value type for document-scoped `SqlStorage.types` entries * (codec aliases / parameterised native type registrations). Postgres * native enum registrations live under - * `storage.namespaces[namespaceId].enum` instead. + * `storage..enum` instead. */ export type SqlStorageTypeEntry = | StorageTypeInstance @@ -29,12 +35,29 @@ export interface SqlNamespaceTablesInput { readonly enum?: Record; } -export interface SqlStorageInput { +export type SqlStorageInput = { + readonly storageHash: StorageHashBase; + readonly types?: Record; +} & Readonly> & { + readonly __unbound__: SqlNamespace; + }; + +export type SqlStorageNamespacesInput = { readonly storageHash: StorageHashBase; readonly types?: Record; readonly namespaces: Readonly> & { readonly __unbound__: SqlNamespace; }; +}; + +export function buildSqlStorageInput( + input: SqlStorageNamespacesInput, +): SqlStorageInput { + return flatStorageInput({ + storageHash: input.storageHash, + ...(input.types !== undefined ? { types: input.types } : {}), + namespaces: input.namespaces, + }) as SqlStorageInput; } /** @@ -46,8 +69,9 @@ export interface SqlStorageInput { * target-specific concretion (target-specific derived fields, * target-specific storage extensions). * - * Honours the framework `Storage` interface: every SQL IR carries a - * `namespaces` map keyed by namespace id. Callers must supply fully + * Honours the framework `Storage` interface: namespace ids are direct + * keys on the storage object alongside the reserved `storageHash` (and + * optional document-scoped `types`). Callers must supply fully * constructed `Namespace` instances — construction discipline lives * in the authoring builders and deserializer hydration paths. * @@ -75,15 +99,12 @@ export type SqlNamespace = Namespace & { export class SqlStorage extends SqlNode implements Storage { readonly storageHash: StorageHashBase; - readonly namespaces: Readonly> & { - readonly __unbound__: SqlNamespace; - }; + declare readonly __unbound__: SqlNamespace; declare readonly types?: Readonly>; constructor(input: SqlStorageInput) { super(); this.storageHash = input.storageHash; - this.namespaces = Object.freeze(input.namespaces); if (input.types !== undefined) { this.types = Object.freeze( Object.fromEntries( @@ -91,6 +112,15 @@ export class SqlStorage extends SqlNode implement ), ); } + for (const [key, value] of Object.entries(input)) { + if (isStoragePlaneReservedKey(key)) continue; + Object.defineProperty(this, key, { + value: Object.freeze(value), + writable: false, + enumerable: true, + configurable: false, + }); + } freezeNode(this); } } diff --git a/packages/2-sql/1-core/contract/src/types.ts b/packages/2-sql/1-core/contract/src/types.ts index ed0ca1d233..1f19354128 100644 --- a/packages/2-sql/1-core/contract/src/types.ts +++ b/packages/2-sql/1-core/contract/src/types.ts @@ -20,9 +20,12 @@ export { PrimaryKey, type PrimaryKeyInput } from './ir/primary-key'; export { Index, type IndexInput } from './ir/sql-index'; export { SqlNode } from './ir/sql-node'; export { + buildSqlStorageInput, + type SqlNamespace, type SqlNamespaceTablesInput, SqlStorage, type SqlStorageInput, + type SqlStorageNamespacesInput, type SqlStorageTypeEntry, } from './ir/sql-storage'; export { SqlUnboundNamespace } from './ir/sql-unbound-namespace'; diff --git a/packages/2-sql/1-core/contract/src/validators.ts b/packages/2-sql/1-core/contract/src/validators.ts index 94f845d618..cba1033917 100644 --- a/packages/2-sql/1-core/contract/src/validators.ts +++ b/packages/2-sql/1-core/contract/src/validators.ts @@ -6,18 +6,26 @@ import { CrossReferenceSchema, } from '@prisma-next/contract/types'; import { validateContractDomain } from '@prisma-next/contract/validate-domain'; -import { type Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + type Namespace, + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { blindCast } from '@prisma-next/utils/casts'; import { ifDefined } from '@prisma-next/utils/defined'; import { type Type, type } from 'arktype'; import { buildSqlNamespaceMap } from './ir/build-sql-namespace'; +import type { SqlNamespace } from './ir/sql-storage'; import { SqlUnboundNamespace } from './ir/sql-unbound-namespace'; import { + buildSqlStorageInput, type ForeignKeyInput, type ForeignKeyReferenceInput, type PrimaryKeyInput, type ReferentialAction, type SqlModelStorage, + type SqlNamespaceTablesInput, SqlStorage, type SqlStorageInput, type StorageTable, @@ -107,7 +115,7 @@ const StorageTypeInstanceSchema = type }); /** - * Postgres native enum entry under `storage.namespaces[namespaceId].enum[name]`. + * Postgres native enum entry under `getStorageNamespace(storage as Record, namespaceId).enum[name]`. * Document-scoped `storage.types` carries codec aliases only * (`DocumentScopedStorageTypeSchema`). */ @@ -233,7 +241,7 @@ function namespaceSlotEntrySchema( } /** - * Builds the per-namespace entry schema for `storage.namespaces[id]`. + * Builds the per-namespace entry schema for `storage.`. * Pack-contributed `validatorSchema` fragments — keyed by the * descriptor's `discriminator` — validate each entry by matching the * entry's `kind` field on the `'enum?'` slot. @@ -266,41 +274,41 @@ export function createSqlStorageSchema( '+': 'reject', storageHash: 'string', 'types?': type({ '[string]': DocumentScopedStorageTypeSchema }), - // `__unbound__` is NOT required here: cross-namespace contracts can - // declare only named namespaces (see cross-namespace FK fixtures). The - // `__unbound__` brand on `SqlStorageInput['namespaces']` is kept sound at - // construction time by injecting the unbound singleton when absent - // (see `validateStorage` / `hydrateSqlStorage`), not by structural require. - 'namespaces?': type({ '[string]': namespaceEntry }), + }).narrow((storage, ctx) => { + if (typeof storage !== 'object' || storage === null) { + return ctx.mustBe('an object'); + } + for (const [key, value] of Object.entries(storage)) { + if (key === 'storageHash' || key === 'types') continue; + const parsed = namespaceEntry(value); + if (parsed instanceof type.errors) { + return ctx.reject({ expected: `storage.${key}: ${parsed.summary}` }); + } + } + return true; }) as Type; } const StorageSchema = createSqlStorageSchema(); -// SQL-specific namespace walk shape (`tables` is the SQL family's idiom — -// the framework `Namespace` interface no longer carries it). The wider -// `object` table value keeps this helper structurally compatible with -// `SqlNamespace` (whose tables narrow to `StorageTable`) and the JSON -// envelope variants that lose class identity. -type NamespacedStorageWalk = { - readonly namespaces: Readonly< - Record> }> - >; -}; - -function eachStorageTable(storage: NamespacedStorageWalk) { - return Object.entries(storage.namespaces).flatMap(([namespaceId, ns]) => - Object.entries(ns.tables ?? {}).map(([tableName, table]) => ({ - namespaceId, - tableName, - table, - })), +function eachStorageTable(storage: unknown) { + return [...storageNamespaceEntries(storage as Record)].flatMap( + ([namespaceId, ns]) => + Object.entries( + (ns as Namespace & { readonly tables?: Readonly> }).tables ?? {}, + ).map(([tableName, table]) => ({ + namespaceId, + tableName, + table, + })), ); } -function findStorageTableByTableName(storage: NamespacedStorageWalk, tableName: string): unknown { - for (const ns of Object.values(storage.namespaces)) { - const t = ns.tables?.[tableName]; +function findStorageTableByTableName(storage: unknown, tableName: string): unknown { + for (const [, ns] of storageNamespaceEntries(storage as Record)) { + const t = (ns as Namespace & { readonly tables?: Readonly> }).tables?.[ + tableName + ]; if (t !== undefined) { return t; } @@ -452,21 +460,26 @@ export function validateStorage(value: unknown): SqlStorage { // can't express (see NOTE above), so bridge the validated shape to the // input type. Construction below re-materialises nested IR fields. const validated = blindCast< - SqlStorageInput & { readonly namespaces?: SqlStorageInput['namespaces'] }, + SqlStorageInput & Record, 'arktype validated the JSON envelope but its output type is unknown (ColumnDefault carries runtime-only bigint|Date); bridge to the input shape' >(result); - const namespaces = buildSqlNamespaceMap(validated.namespaces ?? {}); - // The `__unbound__` brand is made true at construction: if the validated - // namespaces omit the late-bound slot (e.g. a contract declaring only named - // namespaces), inject the family unbound singleton rather than asserting a - // shape that isn't there. Reconstructing the literal lets the branded - // `SqlStorageInput['namespaces']` hold with no cast. + const namespaceEntries = Object.fromEntries( + Object.entries(validated).filter(([key]) => key !== 'storageHash' && key !== 'types'), + ); + const namespaces = buildSqlNamespaceMap( + blindCast< + Readonly>, + 'flat storage keys validated as namespace entries by createSqlStorageSchema' + >(namespaceEntries), + ); const unbound = namespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance; - return new SqlStorage({ - storageHash: validated.storageHash, - ...ifDefined('types', validated.types), - namespaces: { ...namespaces, [UNBOUND_NAMESPACE_ID]: unbound }, - }); + return new SqlStorage( + buildSqlStorageInput({ + storageHash: validated.storageHash, + ...ifDefined('types', validated.types), + namespaces: { ...namespaces, [UNBOUND_NAMESPACE_ID]: unbound }, + }), + ); } export function validateModel(value: unknown): unknown { @@ -794,8 +807,11 @@ export function validateSqlStorageConsistency(contract: Contract): v } } - const targetNamespace = contract.storage.namespaces[fk.target.namespaceId]; - const referencedRaw = targetNamespace?.tables?.[fk.target.tableName]; + const targetNamespace = getStorageNamespace( + contract.storage as unknown as Record, + fk.target.namespaceId, + ) as SqlNamespace | undefined; + const referencedRaw = targetNamespace?.tables[fk.target.tableName]; if (referencedRaw === undefined) { throw new ContractValidationError( `Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`, diff --git a/packages/2-sql/1-core/contract/test/element-coordinates.test.ts b/packages/2-sql/1-core/contract/test/element-coordinates.test.ts index 3dc1ee569d..7eb3ac9252 100644 --- a/packages/2-sql/1-core/contract/test/element-coordinates.test.ts +++ b/packages/2-sql/1-core/contract/test/element-coordinates.test.ts @@ -2,7 +2,7 @@ import { coreHash } from '@prisma-next/contract/types'; import { elementCoordinates } from '@prisma-next/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { buildSqlNamespace } from '../src/ir/build-sql-namespace'; -import { SqlStorage } from '../src/ir/sql-storage'; +import { buildSqlStorageInput, SqlStorage } from '../src/ir/sql-storage'; const emptyTableInput = { columns: {}, @@ -13,12 +13,14 @@ const emptyTableInput = { describe('elementCoordinates with SqlStorage', () => { it('walks SQL namespace tables slot', () => { - const storage = new SqlStorage({ - storageHash: coreHash('sha256:element-coordinates-sql'), - namespaces: { - app: buildSqlNamespace({ id: 'app', tables: { users: emptyTableInput } }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:element-coordinates-sql'), + namespaces: { + app: buildSqlNamespace({ id: 'app', tables: { users: emptyTableInput } }), + }, + }), + ); const coordinates = [...elementCoordinates(storage)]; expect(coordinates).toContainEqual({ From fe65418fb715551485bf53b977ee21332589d73c Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:00:10 +0200 Subject: [PATCH 06/60] refactor(sql-authoring,sql-emitter): emit flat storage namespace keys The SQL emitter generates `storage.` namespace entries as direct siblings of `storageHash` (no `namespaces` wrapper) and walks the flat shape for table/enum resolution. Authoring builders and their unit tests move to the flat shape. Signed-off-by: Will Madden --- .../test/interpreter.namespaces.test.ts | 15 +++- .../contract-psl/test/interpreter.test.ts | 41 ++++++++-- .../test/interpreter.types.test.ts | 2 +- .../test/psl-ts-namespace-parity.test.ts | 55 ++++++++++--- .../contract-psl/test/ts-psl-parity.test.ts | 17 +++- .../contract-psl/test/unbound-tables.ts | 21 ++--- .../contract-ts/src/contract-definition.ts | 2 +- ...ntract-builder.contract-definition.test.ts | 8 +- .../test/contract-builder.namespaces.test.ts | 53 ++++++++++--- ...ntract-builder.per-model-namespace.test.ts | 15 ++-- .../contract-ts/test/unbound-tables.ts | 5 +- packages/2-sql/3-tooling/emitter/src/index.ts | 78 ++++++++++++------- .../test/emitter-hook.structure.test.ts | 7 +- 13 files changed, 231 insertions(+), 88 deletions(-) diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts index 3764ab84f5..15bfe579cc 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts @@ -1,5 +1,6 @@ +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; -import type { ForeignKey, SqlStorage } from '@prisma-next/sql-contract/types'; +import type { ForeignKey, SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { interpretPslDocumentToSqlContract } from '../src/interpreter'; import { @@ -41,7 +42,11 @@ namespace auth { if (!result.ok) return; const storage = result.value.storage as SqlStorage; - const postTable = storage.namespaces['public']?.tables['post']; + const postTable = ( + getStorageNamespace(storage as unknown as Record, 'public') as + | SqlNamespace + | undefined + )?.tables['post']; expect(postTable).toBeDefined(); const fks: readonly ForeignKey[] = postTable?.foreignKeys ?? []; @@ -77,7 +82,11 @@ namespace auth { if (!result.ok) return; const storage = result.value.storage as SqlStorage; - const postTable = storage.namespaces['public']?.tables['post']; + const postTable = ( + getStorageNamespace(storage as unknown as Record, 'public') as + | SqlNamespace + | undefined + )?.tables['post']; const fks: readonly ForeignKey[] = postTable?.foreignKeys ?? []; expect(fks.length).toBe(1); expect(fks[0]).toMatchObject({ diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts index d56429170e..c80476854f 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts @@ -1,7 +1,8 @@ import { crossRef } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; import { defineIndexTypes } from '@prisma-next/sql-contract/index-types'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { type } from 'arktype'; import { describe, expect, it } from 'vitest'; import { @@ -809,7 +810,14 @@ model OrderItem { expect(result.ok).toBe(true); if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); - expect(storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables['doc']).toMatchObject({ + expect( + ( + getStorageNamespace( + storage as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as SqlNamespace | undefined + )?.tables['doc'], + ).toMatchObject({ indexes: [{ columns: ['body'] }], }); }); @@ -877,7 +885,11 @@ model OrderItem { expect(result.ok).toBe(true); if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); - const user = storage.namespaces['auth']?.tables['user']; + const user = ( + getStorageNamespace(storage as unknown as Record, 'auth') as + | SqlNamespace + | undefined + )?.tables['user']; expect(user).toBeDefined(); expect(unboundTables(storage)['user']).toBeUndefined(); const json = JSON.parse(JSON.stringify(user)) as Record; @@ -909,7 +921,12 @@ namespace tenant_a { expect(result.ok).toBe(true); if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); - const enums = storage.namespaces['tenant_a']?.enum ?? {}; + const enums = + ( + getStorageNamespace(storage as unknown as Record, 'tenant_a') as + | SqlNamespace + | undefined + )?.enum ?? {}; expect(enums).toHaveProperty('Status'); expect(enums).toHaveProperty('Tier'); }); @@ -942,8 +959,20 @@ namespace logs { if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); expect(unboundTables(storage)['post']).toBeDefined(); - expect(storage.namespaces['auth']?.tables['user']).toBeDefined(); - expect(storage.namespaces['logs']?.tables['auditLog']).toBeDefined(); + expect( + ( + getStorageNamespace(storage as unknown as Record, 'auth') as + | SqlNamespace + | undefined + )?.tables['user'], + ).toBeDefined(); + expect( + ( + getStorageNamespace(storage as unknown as Record, 'logs') as + | SqlNamespace + | undefined + )?.tables['auditLog'], + ).toBeDefined(); expect(unboundTables(storage)['user']).toBeUndefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts index 25c40ef0f5..13fbee542a 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts @@ -343,7 +343,7 @@ model Account { expect(storageNs['auth']).toBeUndefined(); }); - it('lowers a namespace-scoped enum into storage.namespaces[nsId].enum', () => { + it('lowers a namespace-scoped enum into getStorageNamespace(storage as unknown as Record, nsId).enum', () => { const document = parsePslDocument({ schema: `namespace auth { enum user_type { diff --git a/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts b/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts index d8ab23ef21..2adb513903 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts @@ -1,5 +1,6 @@ +import { getStorageNamespace, storageNamespaceEntries } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; -import type { ForeignKey, SqlStorage } from '@prisma-next/sql-contract/types'; +import type { ForeignKey, SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import { defineContract, field, model, rel } from '@prisma-next/sql-contract-ts/contract-builder'; import { describe, expect, it } from 'vitest'; import { interpretPslDocumentToSqlContract } from '../src/interpreter'; @@ -78,24 +79,58 @@ namespace public { const tsStorage = tsContract.storage as unknown as SqlStorage; // Same namespace keys - expect(Object.keys(pslStorage.namespaces).sort()).toEqual( - Object.keys(tsStorage.namespaces).sort(), + expect( + [...storageNamespaceEntries(pslStorage as unknown as Record)] + .map(([id]) => id) + .sort(), + ).toEqual( + [...storageNamespaceEntries(tsStorage as unknown as Record)] + .map(([id]) => id) + .sort(), ); // Same per-namespace table keys - for (const nsId of Object.keys(pslStorage.namespaces)) { - const pslTables = pslStorage.namespaces[nsId]?.tables ?? {}; - const tsTables = tsStorage.namespaces[nsId]?.tables ?? {}; + for (const nsId of [ + ...storageNamespaceEntries(pslStorage as unknown as Record), + ].map(([id]) => id)) { + const pslTables = + ( + getStorageNamespace(pslStorage as unknown as Record, nsId) as + | SqlNamespace + | undefined + )?.tables ?? {}; + const tsTables = + ( + getStorageNamespace(tsStorage as unknown as Record, nsId) as + | SqlNamespace + | undefined + )?.tables ?? {}; expect(Object.keys(pslTables).sort()).toEqual(Object.keys(tsTables).sort()); } // Same per-table column shapes - const pslAuthUser = pslStorage.namespaces['auth']?.tables['user']; - const tsAuthUser = tsStorage.namespaces['auth']?.tables['user']; + const pslAuthUser = ( + getStorageNamespace(pslStorage as unknown as Record, 'auth') as + | SqlNamespace + | undefined + )?.tables['user']; + const tsAuthUser = ( + getStorageNamespace(tsStorage as unknown as Record, 'auth') as + | SqlNamespace + | undefined + )?.tables['user']; expect(pslAuthUser?.columns).toEqual(tsAuthUser?.columns); - const pslPublicPost = pslStorage.namespaces['public']?.tables['post']; - const tsPublicPost = tsStorage.namespaces['public']?.tables['post']; + const pslPublicPost = ( + getStorageNamespace(pslStorage as unknown as Record, 'public') as + | SqlNamespace + | undefined + )?.tables['post']; + const tsPublicPost = ( + getStorageNamespace(tsStorage as unknown as Record, 'public') as + | SqlNamespace + | undefined + )?.tables['post']; expect(pslPublicPost?.columns).toEqual(tsPublicPost?.columns); // Same FK source/target diff --git a/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts b/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts index e027d8b551..d45cd2eb0c 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts @@ -5,8 +5,9 @@ import type { FamilyPackRef, TargetPackRef, } from '@prisma-next/framework-components/components'; +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; -import type { ForeignKey, SqlStorage } from '@prisma-next/sql-contract/types'; +import type { ForeignKey, SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import { defineContract, field, model, rel } from '@prisma-next/sql-contract-ts/contract-builder'; import { countSemanticLines } from '@prisma-next/test-utils/semantic-lines'; import { describe, expect, it } from 'vitest'; @@ -536,9 +537,19 @@ model Post { const pslStorage = pslContract.value.storage as unknown as SqlStorage; const tsStorage = tsContract.storage as unknown as SqlStorage; const pslFks: readonly ForeignKey[] = - pslStorage.namespaces['__unbound__']?.tables['post']?.foreignKeys ?? []; + ( + getStorageNamespace( + pslStorage as unknown as unknown as Record, + '__unbound__', + ) as SqlNamespace | undefined + )?.tables['post']?.foreignKeys ?? []; const tsFks: readonly ForeignKey[] = - tsStorage.namespaces['__unbound__']?.tables['post']?.foreignKeys ?? []; + ( + getStorageNamespace( + tsStorage as unknown as unknown as Record, + '__unbound__', + ) as SqlNamespace | undefined + )?.tables['post']?.foreignKeys ?? []; expect(tsFks.length).toBe(1); expect(pslFks.length).toBe(1); diff --git a/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts b/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts index 3a360a3d18..03cebbc8a8 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts @@ -1,16 +1,17 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; -type StorageLike = { - readonly namespaces: Readonly< - Record> }> - >; -}; +type StorageLike = Readonly< + Record> }> +>; export function unboundTables( storage: StorageLike | SqlStorage, ): Readonly> { - return (storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}) as Readonly< - Record - >; + return (( + getStorageNamespace( + storage as unknown as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as SqlNamespace | undefined + )?.tables ?? {}) as Readonly>; } diff --git a/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts b/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts index 899eeb92d2..03bc75c7c3 100644 --- a/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts +++ b/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts @@ -124,7 +124,7 @@ export interface ContractDefinition { /** * Enum types declared inside a named `namespace { enum … }` block, * keyed first by namespace id then by type name. These are routed to - * `storage.namespaces[nsId].enum` rather than the implicit fallback + * `getStorageNamespace(storage as Record, nsId).enum` rather than the implicit fallback * namespace used for top-level `storageTypes` enums. */ readonly namespaceTypes?: Readonly< diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts index 0769abf936..29db807c0f 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts @@ -361,7 +361,7 @@ describe('shared contract definition lowering', () => { ); }); - it('routes namespaceTypes enums to storage.namespaces[nsId].enum', () => { + it('routes namespaceTypes enums to getStorageNamespace(storage as Record, nsId).enum', () => { const contract = buildSqlContractFromDefinition({ target: postgresTargetPack, namespaceTypes: { @@ -396,9 +396,11 @@ describe('shared contract definition lowering', () => { const nsStorage = contract.storage as unknown as { namespaces: Record }>; }; - expect(nsStorage.namespaces['auth']?.enum).toMatchObject({ + expect(getStorageNamespace(nsStorage as Record, 'auth')?.enum).toMatchObject({ user_type: { kind: 'postgres-enum', values: ['admin', 'user'] }, }); - expect(nsStorage.namespaces['public']?.enum).toBeUndefined(); + expect( + getStorageNamespace(nsStorage as Record, 'public')?.enum, + ).toBeUndefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts index 159b09c35c..da08935a0d 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts @@ -37,8 +37,13 @@ describe('SqlStorage.namespaces population', () => { target: postgresTargetPack, models: [minimalModelArgs], }); - expect(Object.keys(contract.storage.namespaces)).toEqual([UNBOUND_NAMESPACE_ID]); - const slot = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!; + expect( + [...storageNamespaceEntries(contract.storage as Record)].map(([id]) => id), + ).toEqual([UNBOUND_NAMESPACE_ID]); + const slot = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )!; expect(slot).not.toBe(SqlUnboundNamespace.instance); expect(slot.id).toBe(UNBOUND_NAMESPACE_ID); expect(slot.tables['app_user']).toBeDefined(); @@ -50,11 +55,22 @@ describe('SqlStorage.namespaces population', () => { namespaces: ['public', 'auth'], models: [minimalModelArgs], }); - const namespaceIds = Object.keys(contract.storage.namespaces).sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] + .map(([id]) => id) + .sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth', 'public']); - expect(Object.keys(contract.storage.namespaces['public']!.tables)).toHaveLength(0); - expect(Object.keys(contract.storage.namespaces['auth']!.tables)).toHaveLength(0); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables['app_user']).toBeDefined(); + expect( + Object.keys( + getStorageNamespace(contract.storage as Record, 'public')!.tables, + ), + ).toHaveLength(0); + expect( + Object.keys(getStorageNamespace(contract.storage as Record, 'auth')!.tables), + ).toHaveLength(0); + expect( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! + .tables['app_user'], + ).toBeDefined(); }); it('places tables in the namespace referenced by the model coordinate', () => { @@ -65,10 +81,17 @@ describe('SqlStorage.namespaces population', () => { { ...minimalModelArgs, modelName: 'Post', tableName: 'blog_post' }, ], }); - const namespaceIds = Object.keys(contract.storage.namespaces).sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] + .map(([id]) => id) + .sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth']); - expect(contract.storage.namespaces['auth']!.tables['app_user']).toBeDefined(); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables['blog_post']).toBeDefined(); + expect( + getStorageNamespace(contract.storage as Record, 'auth')!.tables['app_user'], + ).toBeDefined(); + expect( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! + .tables['blog_post'], + ).toBeDefined(); }); it('keeps the unbound singleton only when the unbound coordinate has no tables and no namespace types', () => { @@ -76,7 +99,9 @@ describe('SqlStorage.namespaces population', () => { target: postgresTargetPack, models: [], }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]).toBe(SqlUnboundNamespace.instance); + expect( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID), + ).toBe(SqlUnboundNamespace.instance); }); it('accepts declared namespaces without a createNamespace factory', () => { @@ -95,8 +120,12 @@ describe('SqlStorage.namespaces population', () => { namespaces: ['auth'], models: [{ ...minimalModelArgs, namespaceId: 'auth' }], }); - const namespaceIds = Object.keys(contract.storage.namespaces).sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] + .map(([id]) => id) + .sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth']); - expect(contract.storage.namespaces['auth']!.tables['app_user']).toBeDefined(); + expect( + getStorageNamespace(contract.storage as Record, 'auth')!.tables['app_user'], + ).toBeDefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts index c449e296f8..f11484d541 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts @@ -83,8 +83,12 @@ describe('per-model `namespace` field (TS builder)', () => { // `keyof` as `never` (preventing `Db` from collapsing to a string // index signature). The runtime value is correct; cast to verify it. expect( - (contract.storage.namespaces as Record }>)['auth'] - ?.tables['User'], + ( + getStorageNamespace(contract.storage as Record, 'auth')?.tables as Record< + string, + unknown + > + )['User'], ).toBeDefined(); }); @@ -100,9 +104,10 @@ describe('per-model `namespace` field (TS builder)', () => { }); expect( - (contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables as Record)[ - 'User' - ], + ( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) + ?.tables as Record + )['User'], ).toBeDefined(); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts b/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts index 3a360a3d18..52f0e7e019 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts @@ -10,7 +10,6 @@ type StorageLike = { export function unboundTables( storage: StorageLike | SqlStorage, ): Readonly> { - return (storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}) as Readonly< - Record - >; + return (getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables ?? + {}) as Readonly>; } diff --git a/packages/2-sql/3-tooling/emitter/src/index.ts b/packages/2-sql/3-tooling/emitter/src/index.ts index bd9cce8c0d..434677aed8 100644 --- a/packages/2-sql/3-tooling/emitter/src/index.ts +++ b/packages/2-sql/3-tooling/emitter/src/index.ts @@ -8,11 +8,18 @@ import type { GenerateContractTypesOptions, ValidationContext, } from '@prisma-next/framework-components/emission'; -import { type Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + type Namespace, + storageNamespaceEntries, + storageNamespaceValues, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { isPostgresEnumStorageEntry, type PostgresEnumStorageEntry, type SqlModelStorage, + type SqlNamespace, type SqlStorage, type StorageTable, type StorageTypeInstance, @@ -44,8 +51,10 @@ function findSqlTable( storage: SqlStorage, tableName: string, ): { readonly table: StorageTable; readonly namespaceId: string } | undefined { - for (const [namespaceId, ns] of Object.entries(storage.namespaces)) { - const table = ns.tables[tableName] as StorageTable | undefined; + for (const [namespaceId, ns] of storageNamespaceEntries( + storage as unknown as Record, + )) { + const table = (ns as SqlNamespace).tables[tableName] as StorageTable | undefined; if (table !== undefined) { return { table, namespaceId }; } @@ -55,8 +64,10 @@ function findSqlTable( function assertUniqueSqlTableNames(storage: SqlStorage): void { const seen = new Map(); - for (const [namespaceId, ns] of Object.entries(storage.namespaces)) { - for (const tableName of Object.keys(ns.tables)) { + for (const [namespaceId, ns] of storageNamespaceEntries( + storage as unknown as Record, + )) { + for (const tableName of Object.keys((ns as SqlNamespace).tables)) { const existing = seen.get(tableName); if (existing !== undefined && existing !== namespaceId) { throw new Error( @@ -73,14 +84,14 @@ export const sqlEmission = { validateTypes(contract: Contract, _ctx: ValidationContext): void { const storage = contract.storage as unknown as SqlStorage | undefined; - if (!storage?.namespaces) { + if (!storage?.storageHash) { return; } const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/; - for (const ns of Object.values(storage.namespaces)) { - for (const [tableName, tableUnknown] of Object.entries(ns.tables)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const [tableName, tableUnknown] of Object.entries((ns as SqlNamespace).tables)) { const table = tableUnknown as StorageTable; for (const [colName, colUnknown] of Object.entries(table.columns)) { const col = colUnknown as { codecId?: string }; @@ -106,16 +117,16 @@ export const sqlEmission = { } const storage = contract.storage as unknown as SqlStorage | undefined; - if (!storage?.namespaces) { - throw new Error('SQL contract must have storage.namespaces'); + if (!storage?.storageHash) { + throw new Error('SQL contract must have storage with storageHash'); } assertUniqueSqlTableNames(storage); const models = contract.models as Record>; const tableNames = new Set(); - for (const ns of Object.values(storage.namespaces)) { - for (const t of Object.keys(ns.tables)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const t of Object.keys((ns as SqlNamespace).tables)) { tableNames.add(t); } } @@ -159,8 +170,8 @@ export const sqlEmission = { } } - for (const ns of Object.values(storage.namespaces)) { - for (const [tableName, tableUnknown] of Object.entries(ns.tables)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const [tableName, tableUnknown] of Object.entries((ns as SqlNamespace).tables)) { const table = tableUnknown as StorageTable; const columnNames = new Set(Object.keys(table.columns)); @@ -219,9 +230,13 @@ export const sqlEmission = { } } - const referencedTable = storage.namespaces[fk.target.namespaceId]?.tables[ - fk.target.tableName - ] as StorageTable | undefined; + const referencedNs = getStorageNamespace( + storage as unknown as Record, + fk.target.namespaceId, + ) as SqlNamespace | undefined; + const referencedTable = referencedNs?.tables[fk.target.tableName] as + | StorageTable + | undefined; if (!referencedTable) { throw new Error( `Table "${tableName}" foreignKey references non-existent table "${fk.target.tableName}" in namespace "${fk.target.namespaceId}"`, @@ -249,13 +264,13 @@ export const sqlEmission = { generateStorageType(contract: Contract, storageHashTypeName: string): string { const storage = contract.storage as unknown as SqlStorage; - const namespacesType = generateStorageNamespacesType(storage.namespaces); + const namespaceTypeEntries = generateFlatStorageNamespaceTypeEntries(storage); const domainTypes = contract.domain?.[UNBOUND_NAMESPACE_ID]?.['types'] as | Readonly> | undefined; const docTypes = generateDocumentScopedStorageTypesType(domainTypes ?? storage.types); const typesClause = docTypes === undefined ? '' : `; readonly types: ${docTypes}`; - return `{ readonly namespaces: ${namespacesType}${typesClause}; readonly storageHash: ${storageHashTypeName} }`; + return `{ readonly storageHash: ${storageHashTypeName}${namespaceTypeEntries}${typesClause} }`; }, generateModelStorageType(_modelName: string, model: ContractModel): string { @@ -298,7 +313,10 @@ export const sqlEmission = { if (!column) return undefined; if (column.typeRef) { - const ns = storage.namespaces[located.namespaceId]; + const ns = getStorageNamespace( + storage as unknown as Record, + located.namespaceId, + ) as SqlNamespace | undefined; const nsEnums = ns !== undefined && 'enum' in ns ? ( @@ -362,7 +380,7 @@ export const sqlEmission = { return [ `export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`, '', - "export type Namespaces = Contract['storage']['namespaces'];", + "export type Namespaces = Omit;", "export type Models = Contract['models'];", ].join('\n'); }, @@ -377,7 +395,7 @@ function generateDocumentScopedStorageTypesType(types: SqlStorage['types']): str for (const [typeName, typeInstance] of Object.entries(types)) { if (isPostgresEnumStorageEntry(typeInstance)) { throw new Error( - `Document-scoped storage.types entry "${typeName}" is a postgres-enum; enums belong under storage.namespaces[namespaceId].enum`, + `Document-scoped storage.types entry "${typeName}" is a postgres-enum; enums belong under storage..enum`, ); } const codecInstanceShape = typeInstance as Partial; @@ -544,22 +562,26 @@ function generateTablesMapType(tables: Readonly>): return `{ ${tableEntries.join('; ')} }`; } -function generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): string { - const entries = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b)); +function generateFlatStorageNamespaceTypeEntries(storage: SqlStorage): string { + const entries = [...storageNamespaceEntries(storage as unknown as Record)].sort( + ([a], [b]) => a.localeCompare(b), + ); if (entries.length === 0) { - return 'Record'; + return ''; } const parts: string[] = []; for (const [name, ns] of entries) { const kindSuffix = `; ${namespaceSerializedKind(ns)}`; - const tablesType = generateTablesMapType(ns.tables as Readonly>); + const tablesType = generateTablesMapType( + (ns as SqlNamespace).tables as Readonly>, + ); const isPg = isPostgresSchemaNamespace(ns); const enumClause = isPg - ? `; readonly enum: ${generatePostgresNamespaceTypesType(ns.enum)}` + ? `; readonly enum: ${generatePostgresNamespaceTypesType((ns as SqlNamespace & { enum: Readonly> }).enum)}` : ''; parts.push( `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}${kindSuffix}; readonly tables: ${tablesType}${enumClause} }`, ); } - return `{ ${parts.join('; ')} }`; + return `; ${parts.join('; ')}`; } diff --git a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts index b4c1ff0761..b906c7aef6 100644 --- a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -63,7 +63,8 @@ function installNamespacedTableDeletionRace(ir: Contract, tableName: string): vo }, }); - delete originalStorage.namespaces[UNBOUND_NAMESPACE_ID].tables[tableName]; + delete getStorageNamespace(originalStorage as Record, UNBOUND_NAMESPACE_ID) + .tables[tableName]; tableDeleted = true; (ir as { storage: unknown }).storage = proxiedStorage; } @@ -178,7 +179,7 @@ describe('sql-target-family-hook', () => { expect(() => { sqlEmission.validateStructure(ir); - }).toThrow('SQL contract must have storage.namespaces'); + }).toThrow('SQL contract must have storage namespace entries'); }); it('validates structure with missing storage.tables', () => { @@ -188,7 +189,7 @@ describe('sql-target-family-hook', () => { expect(() => { sqlEmission.validateStructure(ir); - }).toThrow('SQL contract must have storage.namespaces'); + }).toThrow('SQL contract must have storage namespace entries'); }); it('validates structure with model missing storage.table', () => { From 862d710b34096893aca76e3c82e2762c35c6e752 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:01:13 +0200 Subject: [PATCH 07/60] refactor(sql-query-builder,relational-core): read flat storage namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Query-builder selection and relational-core codec lookup walk namespace entries directly under `storage` rather than through `storage.namespaces`. Storage-path update only — no change to the per-namespace type structure. Signed-off-by: Will Madden --- .../4-lanes/query-builder/src/selection.ts | 2 +- .../src/codec-ref-for-column.ts | 8 ++++--- .../test/codec-descriptor-registry.test.ts | 23 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/2-sql/4-lanes/query-builder/src/selection.ts b/packages/2-sql/4-lanes/query-builder/src/selection.ts index 161db55b9b..8e2c7458a1 100644 --- a/packages/2-sql/4-lanes/query-builder/src/selection.ts +++ b/packages/2-sql/4-lanes/query-builder/src/selection.ts @@ -8,7 +8,7 @@ import type { DrainOuterGeneric } from './type-atoms'; * the unbound namespace is the slot the target resolves at connection time. */ export type UnboundTables> = - TContract['storage']['namespaces']['__unbound__']['tables']; + TContract['storage']['__unbound__']['tables']; /** * A utility type to extract the output type of a referenced column from a contract. diff --git a/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts b/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts index 638070b5c4..88587071b8 100644 --- a/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts +++ b/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts @@ -1,8 +1,10 @@ import type { JsonValue } from '@prisma-next/contract/types'; import type { CodecRef } from '@prisma-next/framework-components/codec'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import { isPostgresEnumStorageEntry, isStorageTypeInstance, + type SqlNamespace, type SqlStorage, type StorageTable, } from '@prisma-next/sql-contract/types'; @@ -24,8 +26,8 @@ export function codecRefForStorageColumn( columnName: string, ): CodecRef | undefined { let tableDef: StorageTable | undefined; - for (const ns of Object.values(storage.namespaces)) { - const candidate = ns.tables[tableName] as StorageTable | undefined; + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + const candidate = (ns as SqlNamespace).tables[tableName] as StorageTable | undefined; if (candidate !== undefined) { tableDef = candidate; break; @@ -37,7 +39,7 @@ export function codecRefForStorageColumn( if (columnDef.typeRef !== undefined) { let instance: unknown = storage.types?.[columnDef.typeRef]; if (!instance) { - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { const nsEntry = nsEnums[columnDef.typeRef]; diff --git a/packages/2-sql/4-lanes/relational-core/test/codec-descriptor-registry.test.ts b/packages/2-sql/4-lanes/relational-core/test/codec-descriptor-registry.test.ts index be7b44599f..c3da5adf40 100644 --- a/packages/2-sql/4-lanes/relational-core/test/codec-descriptor-registry.test.ts +++ b/packages/2-sql/4-lanes/relational-core/test/codec-descriptor-registry.test.ts @@ -2,6 +2,7 @@ import type { CodecDescriptor } from '@prisma-next/framework-components/codec'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type SqlStorageTypeEntry, type StorageTableInput, @@ -75,16 +76,18 @@ describe('buildCodecDescriptorRegistry — codecRefForColumn', () => { tables: Record; types?: Record; }): SqlStorage { - return new SqlStorage({ - storageHash: 'sha256:test' as SqlStorage['storageHash'], - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: parts.tables, - }), - }, - ...ifDefined('types', parts.types), - }); + return new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as SqlStorage['storageHash'], + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: parts.tables, + }), + }, + ...ifDefined('types', parts.types), + }), + ); } const descriptors = [stub('pg/vector@1', ['vector']), stub('pg/text@1', ['text'])]; From 467392c012493594d8f1f1013c67f0a73f4b76f1 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:04:17 +0200 Subject: [PATCH 08/60] refactor(sql-builder): root Db/TableProxyContract types at flat storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sql-builder runtime and `Db`/`UnboundTables`/`TableProxyContract` types index namespace entries directly under `storage` instead of `storage.namespaces`. Storage-path update only — the `TableNamesAcrossNamespaces`/`TableInAnyNamespace` indexing keeps the same per-namespace structure, just rooted at the flat storage object. The deferred `UnboundTables` rewrite (TML-2745) is NOT pulled in. Signed-off-by: Will Madden --- .../4-lanes/sql-builder/src/runtime/sql.ts | 7 +++++- .../2-sql/4-lanes/sql-builder/src/types/db.ts | 23 ++++++++++--------- .../test/runtime/field-proxy.test.ts | 10 ++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts index c0c00e88cb..e68cefb76b 100644 --- a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts +++ b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts @@ -1,4 +1,9 @@ import type { Contract } from '@prisma-next/contract/types'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression'; import type { ExecutionContext } from '@prisma-next/sql-relational-core/query-lane-context'; @@ -17,7 +22,7 @@ export interface SqlOptions & TableProxyContract> // at the DSL call site; landing the namespace-aware DSL is tracked // separately. function findTableAcrossNamespaces(storage: SqlStorage, name: string): StorageTable | undefined { - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues(storage as Record)) { const tables = (ns as { tables?: Readonly> }).tables ?? {}; if (Object.hasOwn(tables, name)) { return tables[name]; diff --git a/packages/2-sql/4-lanes/sql-builder/src/types/db.ts b/packages/2-sql/4-lanes/sql-builder/src/types/db.ts index b4192f0ebb..b2f492c556 100644 --- a/packages/2-sql/4-lanes/sql-builder/src/types/db.ts +++ b/packages/2-sql/4-lanes/sql-builder/src/types/db.ts @@ -3,18 +3,20 @@ import type { TableProxy } from './table-proxy'; export type CapabilitiesBase = Record>; +type StorageNamespaceEntry = { readonly tables: Readonly> }; + +type StorageNamespaceKey = Exclude; + // The sql-builder DSL is flat by table name across every declared // namespace. Two namespaces declaring tables with the same name produce // a union at the DSL surface (which collapses to a type error at the // first call site); landing the namespace-aware DSL surface (db..
) // is tracked separately. Within scope here: the DSL accepts the -// namespaced storage shape directly and walks every namespace. +// flat storage shape directly and walks every namespace. export type TableProxyContract = { readonly storage: { - readonly namespaces: Readonly< - Record> }> - >; - }; + readonly storageHash: string; + } & Readonly>; readonly capabilities: CapabilitiesBase; }; @@ -25,15 +27,14 @@ export type UnboundTables = { }; type TableNamesAcrossNamespaces = { - [NSId in keyof C['storage']['namespaces']]: keyof C['storage']['namespaces'][NSId]['tables'] & - string; -}[keyof C['storage']['namespaces']]; + [NSId in StorageNamespaceKey]: keyof C['storage'][NSId]['tables'] & string; +}[StorageNamespaceKey]; type TableInAnyNamespace = { - [NSId in keyof C['storage']['namespaces']]: Name extends keyof C['storage']['namespaces'][NSId]['tables'] - ? C['storage']['namespaces'][NSId]['tables'][Name] + [NSId in StorageNamespaceKey]: Name extends keyof C['storage'][NSId]['tables'] + ? C['storage'][NSId]['tables'][Name] : never; -}[keyof C['storage']['namespaces']]; +}[StorageNamespaceKey]; export type Db = { [Name in TableNamesAcrossNamespaces]: TableProxy; diff --git a/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts b/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts index ee053b4337..33e6a0eaf3 100644 --- a/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts +++ b/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts @@ -75,12 +75,10 @@ describe('createFieldProxy', () => { }; const storage = new SqlStorage({ storageHash: coreHash('sha256:h'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { Post: table }, - }), - }, + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { Post: table }, + }), types: { Embedding1536: { kind: 'codec-instance', From 00f22683e0deed8cdc272464fbac88fffc9240d9 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:04:27 +0200 Subject: [PATCH 09/60] refactor(sql-runtime): read flat storage namespaces in sql-context The SQL runtime context and codec validation walk namespace entries directly under `storage` via the framework helpers instead of `storage.namespaces`. Unit-test fixtures updated to the flat shape. Signed-off-by: Will Madden --- .../2-sql/5-runtime/src/codecs/validation.ts | 18 +- packages/2-sql/5-runtime/src/sql-context.ts | 12 +- .../5-runtime/test/codec-integrity.test.ts | 102 +++---- .../5-runtime/test/context.types.test-d.ts | 30 +- .../test/contract-codec-registry.test.ts | 16 +- .../5-runtime/test/intercept-decoding.test.ts | 16 +- .../test/marker-verification.test.ts | 16 +- .../test/marker-vs-intercept-ordering.test.ts | 16 +- .../test/mutation-default-generators.test.ts | 34 +-- .../test/parameterized-types.test.ts | 39 +-- .../test/runtime-ctx-passthrough.test.ts | 16 +- .../5-runtime/test/scope-plumbing.test.ts | 16 +- .../test/sql-context.codec-context.test.ts | 69 +++-- .../2-sql/5-runtime/test/sql-context.test.ts | 261 ++++++++++-------- .../5-runtime/test/sql-family-adapter.test.ts | 16 +- .../5-runtime/test/sql-runtime-abort.test.ts | 16 +- .../2-sql/5-runtime/test/sql-runtime.test.ts | 16 +- packages/2-sql/5-runtime/test/utils.ts | 21 +- 18 files changed, 408 insertions(+), 322 deletions(-) diff --git a/packages/2-sql/5-runtime/src/codecs/validation.ts b/packages/2-sql/5-runtime/src/codecs/validation.ts index 36045049a9..403d6b1550 100644 --- a/packages/2-sql/5-runtime/src/codecs/validation.ts +++ b/packages/2-sql/5-runtime/src/codecs/validation.ts @@ -1,21 +1,21 @@ import type { Contract } from '@prisma-next/contract/types'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import { runtimeError } from '@prisma-next/framework-components/runtime'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import type { CodecDescriptorRegistry } from '@prisma-next/sql-relational-core/query-lane-context'; // Framework `Namespace.tables` is widened to `Record` so // emitted `contract.d.ts` table literals (which omit the optional `kind` // discriminator) satisfy it structurally. At runtime, every `SqlStorage` // namespace holds `StorageTable` instances — the constructor enforces it. -// Narrowing per-iteration via a single-step cast (not `as unknown as`) -// keeps Pattern C walks readable without weakening the substrate type. -type SqlNamespaceTables = Readonly>; export function extractCodecIds(contract: Contract): Set { const codecIds = new Set(); - for (const ns of Object.values(contract.storage.namespaces)) { - for (const table of Object.values(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues( + contract.storage as unknown as Record, + ) as SqlNamespace[]) { + for (const table of Object.values(ns.tables)) { for (const column of Object.values(table.columns)) { const codecId = column.codecId; codecIds.add(codecId); @@ -29,8 +29,10 @@ export function extractCodecIds(contract: Contract): Set { function extractCodecIdsFromColumns(contract: Contract): Map { const codecIds = new Map(); - for (const ns of Object.values(contract.storage.namespaces)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues( + contract.storage as unknown as Record, + ) as SqlNamespace[]) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const [columnName, column] of Object.entries(table.columns)) { const codecId = column.codecId; const key = `${tableName}.${columnName}`; diff --git a/packages/2-sql/5-runtime/src/sql-context.ts b/packages/2-sql/5-runtime/src/sql-context.ts index 7629b9fe3c..23a0eaefe2 100644 --- a/packages/2-sql/5-runtime/src/sql-context.ts +++ b/packages/2-sql/5-runtime/src/sql-context.ts @@ -25,7 +25,7 @@ import { type RuntimeTargetDescriptor, type RuntimeTargetInstance, } from '@prisma-next/framework-components/execution'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { storageNamespaceValues, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { runtimeError } from '@prisma-next/framework-components/runtime'; import { canonicalizeJson } from '@prisma-next/framework-components/utils'; import { @@ -351,7 +351,7 @@ function collectTypeRefSites( storage: SqlStorage, ): Map> { const sites = new Map>(); - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (typeof column.typeRef !== 'string') continue; @@ -411,7 +411,7 @@ function validateColumnTypeParams( storage: SqlStorage, codecDescriptors: Map, ): void { - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (column.typeParams) { @@ -440,7 +440,7 @@ function assertColumnCodecIntegrity( storage: SqlStorage, codecDescriptors: CodecDescriptorRegistry, ): void { - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { for (const columnName of Object.keys(table.columns)) { const ref = codecDescriptors.codecRefForColumn(tableName, columnName); @@ -555,7 +555,7 @@ function buildContractCodecRegistry( } } - for (const ns of Object.values(contract.storage.namespaces)) { + for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (column.typeRef !== undefined) continue; @@ -587,7 +587,7 @@ function buildContractCodecRegistry( }; }); - for (const ns of Object.values(contract.storage.namespaces)) { + for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { for (const columnName of Object.keys(table.columns)) { const ref = codecDescriptors.codecRefForColumn(tableName, columnName); diff --git a/packages/2-sql/5-runtime/test/codec-integrity.test.ts b/packages/2-sql/5-runtime/test/codec-integrity.test.ts index 8d2dcaa53d..682e99c11e 100644 --- a/packages/2-sql/5-runtime/test/codec-integrity.test.ts +++ b/packages/2-sql/5-runtime/test/codec-integrity.test.ts @@ -116,31 +116,33 @@ describe('createExecutionContext — column codec integrity', () => { readonly typeParams?: Record; readonly typeRef?: string; }): Contract { - const storage: SqlStorage = new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Doc: { - columns: { - field: { - nativeType: column.nativeType, - codecId: column.codecId, - nullable: false, - ...(column.typeParams ? { typeParams: column.typeParams } : {}), - ...(column.typeRef ? { typeRef: column.typeRef } : {}), + const storage: SqlStorage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Doc: { + columns: { + field: { + nativeType: column.nativeType, + codecId: column.codecId, + nullable: false, + ...(column.typeParams ? { typeParams: column.typeParams } : {}), + ...(column.typeRef ? { typeRef: column.typeRef } : {}), + }, }, + primaryKey: { columns: ['field'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['field'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }); + }), + }, + }), + ); return { targetFamily: 'sql', target: 'postgres', @@ -266,38 +268,40 @@ describe('createExecutionContext — column codec integrity', () => { }); it('accepts a typeRef column whose typed instance carries typeParams', () => { - const storage: SqlStorage = new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Doc: { - columns: { - embedding: { - nativeType: 'vector', - codecId: 'pgvector/vector@1', - nullable: false, - typeRef: 'V1536', + const storage: SqlStorage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Doc: { + columns: { + embedding: { + nativeType: 'vector', + codecId: 'pgvector/vector@1', + nullable: false, + typeRef: 'V1536', + }, }, + primaryKey: { columns: ['embedding'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['embedding'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, + }), + }, + types: { + V1536: { + kind: 'codec-instance', + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: { length: 1536 }, }, - }), - }, - types: { - V1536: { - kind: 'codec-instance', - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: { length: 1536 }, }, - }, - }); + }), + ); const contract: Contract = { targetFamily: 'sql', target: 'postgres', diff --git a/packages/2-sql/5-runtime/test/context.types.test-d.ts b/packages/2-sql/5-runtime/test/context.types.test-d.ts index c72265f670..b478f3172c 100644 --- a/packages/2-sql/5-runtime/test/context.types.test-d.ts +++ b/packages/2-sql/5-runtime/test/context.types.test-d.ts @@ -7,24 +7,22 @@ import type { ExecutionContext, TypeHelperRegistry } from '../src/sql-context'; type TestContract = Contract< { readonly storageHash: StorageHashBase; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly document: { - readonly columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - nullable: false; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly document: { + readonly columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + nullable: false; }; - readonly primaryKey: { readonly columns: readonly ['id'] }; - readonly uniques: readonly []; - readonly indexes: readonly []; - readonly foreignKeys: readonly []; }; + readonly primaryKey: { readonly columns: readonly ['id'] }; + readonly uniques: readonly []; + readonly indexes: readonly []; + readonly foreignKeys: readonly []; }; }; }; diff --git a/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts b/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts index af89e67341..ca83a512d2 100644 --- a/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts +++ b/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts @@ -136,13 +136,15 @@ function createTestContract( profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ id: '__unbound__', tables: tableEntries }), - }, - ...ifDefined('types', types), - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ id: '__unbound__', tables: tableEntries }), + }, + ...ifDefined('types', types), + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/intercept-decoding.test.ts b/packages/2-sql/5-runtime/test/intercept-decoding.test.ts index 358f25b7f5..7f873e6054 100644 --- a/packages/2-sql/5-runtime/test/intercept-decoding.test.ts +++ b/packages/2-sql/5-runtime/test/intercept-decoding.test.ts @@ -6,7 +6,11 @@ import { type RuntimeDriverInstance, type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlDriver, SqlExecuteRequest } from '@prisma-next/sql-relational-core/ast'; import { ColumnRef, @@ -39,10 +43,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/marker-verification.test.ts b/packages/2-sql/5-runtime/test/marker-verification.test.ts index 14098ea73a..83773e33f2 100644 --- a/packages/2-sql/5-runtime/test/marker-verification.test.ts +++ b/packages/2-sql/5-runtime/test/marker-verification.test.ts @@ -7,7 +7,11 @@ import { type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; import type { RuntimeLog } from '@prisma-next/framework-components/runtime'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, MarkerReadResult, @@ -44,10 +48,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test-profile'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/marker-vs-intercept-ordering.test.ts b/packages/2-sql/5-runtime/test/marker-vs-intercept-ordering.test.ts index 32338d0dd6..f824491a73 100644 --- a/packages/2-sql/5-runtime/test/marker-vs-intercept-ordering.test.ts +++ b/packages/2-sql/5-runtime/test/marker-vs-intercept-ordering.test.ts @@ -7,7 +7,11 @@ import { type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; import type { RuntimeLog } from '@prisma-next/framework-components/runtime'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlDriver, SqlExecuteRequest } from '@prisma-next/sql-relational-core/ast'; import { SelectAst, TableSource } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan'; @@ -38,10 +42,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts b/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts index a870de2e64..b13c661e11 100644 --- a/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts +++ b/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts @@ -18,24 +18,26 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/parameterized-types.test.ts b/packages/2-sql/5-runtime/test/parameterized-types.test.ts index 579c1e05f4..9fd9012384 100644 --- a/packages/2-sql/5-runtime/test/parameterized-types.test.ts +++ b/packages/2-sql/5-runtime/test/parameterized-types.test.ts @@ -6,6 +6,7 @@ import type { } from '@prisma-next/framework-components/codec'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type SqlStorageTypeEntry, } from '@prisma-next/sql-contract/types'; @@ -56,26 +57,28 @@ function createParamTypesTestContract( profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - test: { - columns: options?.tableColumns ?? { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + test: { + columns: options?.tableColumns ?? { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - ...ifDefined('types', options?.types), - }), + }), + }, + ...ifDefined('types', options?.types), + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/runtime-ctx-passthrough.test.ts b/packages/2-sql/5-runtime/test/runtime-ctx-passthrough.test.ts index 4039e510f5..cb58918ec9 100644 --- a/packages/2-sql/5-runtime/test/runtime-ctx-passthrough.test.ts +++ b/packages/2-sql/5-runtime/test/runtime-ctx-passthrough.test.ts @@ -6,7 +6,11 @@ import { type RuntimeDriverInstance, type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlDriver, SqlExecuteRequest } from '@prisma-next/sql-relational-core/ast'; import { SelectAst, TableSource } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan'; @@ -32,10 +36,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/scope-plumbing.test.ts b/packages/2-sql/5-runtime/test/scope-plumbing.test.ts index 95790002a5..5b83698653 100644 --- a/packages/2-sql/5-runtime/test/scope-plumbing.test.ts +++ b/packages/2-sql/5-runtime/test/scope-plumbing.test.ts @@ -6,7 +6,11 @@ import { type RuntimeDriverInstance, type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SelectAst, @@ -45,10 +49,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts b/packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts index 6a160b1026..665c59a92a 100644 --- a/packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts +++ b/packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts @@ -2,7 +2,12 @@ import type { Contract } from '@prisma-next/contract/types'; import { coreHash, profileHash } from '@prisma-next/contract/types'; import type { CodecDescriptor } from '@prisma-next/framework-components/codec'; import { voidParamsSchema } from '@prisma-next/framework-components/codec'; -import { buildSqlNamespace, SqlStorage, type StorageTable } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, + type StorageTable, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlCodecInstanceContext } from '@prisma-next/sql-relational-core/ast'; import { ifDefined } from '@prisma-next/utils/defined'; import { describe, expect, it } from 'vitest'; @@ -81,10 +86,12 @@ describe('buildContractCodecRegistry — per-column codec instance context', () profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, @@ -188,26 +195,28 @@ describe('buildContractCodecRegistry — forCodecRef content-keyed cache', () => }; } - const storage = new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, - ...ifDefined( - 'types', - types - ? Object.fromEntries( - Object.entries(types).map(([name, params]) => [ - name, - { - kind: 'codec-instance' as const, - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: params as Record, - }, - ]), - ) - : undefined, - ), - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, + ...ifDefined( + 'types', + types + ? Object.fromEntries( + Object.entries(types).map(([name, params]) => [ + name, + { + kind: 'codec-instance' as const, + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: params as Record, + }, + ]), + ) + : undefined, + ), + }), + ); return { targetFamily: 'sql', @@ -435,10 +444,12 @@ describe('buildContractCodecRegistry — forColumn delegates to forCodecRef', () profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: buildSqlNamespace({ id: '__unbound__', tables }) }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/sql-context.test.ts b/packages/2-sql/5-runtime/test/sql-context.test.ts index c262cfd2cf..88448b94a7 100644 --- a/packages/2-sql/5-runtime/test/sql-context.test.ts +++ b/packages/2-sql/5-runtime/test/sql-context.test.ts @@ -3,6 +3,7 @@ import { mergeCapabilityMatrices } from '@prisma-next/framework-components/compo import type { RuntimeDriverDescriptor } from '@prisma-next/framework-components/execution'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, SqlUnboundNamespace, } from '@prisma-next/sql-contract/types'; @@ -31,10 +32,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, @@ -302,24 +305,26 @@ describe('contract/stack validation errors', () => { it('throws RUNTIME.MISSING_MUTATION_DEFAULT_GENERATOR when contract references a generator the stack does not provide', () => { const contractWithUnknownGenerator: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -350,25 +355,27 @@ describe('contract/stack validation errors', () => { it('lists all missing mutation default generator ids in a single error', () => { const contractWithMissingGenerators: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - slug: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + slug: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -401,24 +408,26 @@ describe('contract/stack validation errors', () => { it('passes when all referenced mutation default generator ids are registered', () => { const contractWithRegisteredGenerator: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -441,26 +450,28 @@ describe('contract/stack validation errors', () => { describe('applyMutationDefaults', () => { const contractWithDefaults: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - slug: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + slug: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -558,25 +569,27 @@ describe('applyMutationDefaults', () => { const contractWithCounter: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - touchedAt: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + touchedAt: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -653,26 +666,28 @@ describe('applyMutationDefaults', () => { const contractWithCorrelationId: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - event: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - causation: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - correlation: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + event: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + causation: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + correlation: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { @@ -740,24 +755,26 @@ describe('applyMutationDefaults', () => { const contractWithCounter: Contract = { ...testContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - __unbound__: buildSqlNamespace({ - id: '__unbound__', - tables: { - user: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + __unbound__: buildSqlNamespace({ + id: '__unbound__', + tables: { + user: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }, + uniques: [], + indexes: [], + foreignKeys: [], }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), execution: { executionHash: executionHash('sha256:test'), mutations: { diff --git a/packages/2-sql/5-runtime/test/sql-family-adapter.test.ts b/packages/2-sql/5-runtime/test/sql-family-adapter.test.ts index 09006d0f9a..f6d14758b7 100644 --- a/packages/2-sql/5-runtime/test/sql-family-adapter.test.ts +++ b/packages/2-sql/5-runtime/test/sql-family-adapter.test.ts @@ -1,6 +1,10 @@ import type { Contract } from '@prisma-next/contract/types'; import { coreHash, profileHash } from '@prisma-next/contract/types'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { AdapterProfile } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan'; import { describe, expect, it } from 'vitest'; @@ -14,10 +18,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test-hash'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test-hash'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test-hash'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts b/packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts index dbc7293a2d..8ac088f96f 100644 --- a/packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts +++ b/packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts @@ -6,7 +6,11 @@ import { type RuntimeDriverInstance, type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlCodecCallContext, @@ -37,10 +41,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/sql-runtime.test.ts b/packages/2-sql/5-runtime/test/sql-runtime.test.ts index 633c75a89c..29f432c04c 100644 --- a/packages/2-sql/5-runtime/test/sql-runtime.test.ts +++ b/packages/2-sql/5-runtime/test/sql-runtime.test.ts @@ -6,7 +6,11 @@ import { type RuntimeDriverInstance, type RuntimeExtensionInstance, } from '@prisma-next/framework-components/execution'; -import { SqlStorage, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + SqlUnboundNamespace, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlDriver, SqlExecuteRequest } from '@prisma-next/sql-relational-core/ast'; import { BinaryExpr, @@ -40,10 +44,12 @@ const testContract: Contract = { profileHash: profileHash('sha256:test'), models: {}, roots: {}, - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { __unbound__: SqlUnboundNamespace.instance }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-sql/5-runtime/test/utils.ts b/packages/2-sql/5-runtime/test/utils.ts index f7b261d311..e129f8357a 100644 --- a/packages/2-sql/5-runtime/test/utils.ts +++ b/packages/2-sql/5-runtime/test/utils.ts @@ -17,8 +17,10 @@ import { builtinGeneratorIds } from '@prisma-next/ids'; import { generateId } from '@prisma-next/ids/runtime'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type SqlStorageInput, + type SqlStorageNamespacesInput, SqlUnboundNamespace, type StorageTableInput, } from '@prisma-next/sql-contract/types'; @@ -409,7 +411,7 @@ export function createTestContract( contract: Partial, 'profileHash' | 'storage'>> & { storageHash?: string; profileHash?: string; - storage?: Partial>; + storage?: Partial>; }, ): Contract { const { execution, ...rest } = contract; @@ -418,16 +420,13 @@ export function createTestContract( return { target: rest['target'] ?? 'postgres', targetFamily: rest['targetFamily'] ?? 'sql', - storage: rest['storage'] - ? new SqlStorage({ - ...rest['storage'], - storageHash: storageHashValue, - namespaces: rest['storage'].namespaces ?? { __unbound__: SqlUnboundNamespace.instance }, - }) - : new SqlStorage({ - storageHash: storageHashValue, - namespaces: { __unbound__: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: storageHashValue, + namespaces: rest['storage']?.namespaces ?? { __unbound__: SqlUnboundNamespace.instance }, + ...(rest['storage']?.types !== undefined ? { types: rest['storage'].types } : {}), + }), + ), models: rest['models'] ?? {}, roots: rest['roots'] ?? {}, capabilities: rest['capabilities'] ?? {}, From 66d3117f710433eebfef78ad5df38f951fcabdd2 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:04:35 +0200 Subject: [PATCH 10/60] refactor(sql-family): hydrate and verify flat storage namespaces The SQL family contract-serializer hydration rebuilds `SqlStorage` from flat namespace keys (via `buildSqlStorageInput`), and the schema-verify, migration field-event planner, and contract-to-schema-IR walks iterate namespace entries directly under `storage`. Family unit tests and helpers updated to the flat shape. Signed-off-by: Will Madden --- .../9-family/src/core/control-instance.ts | 15 +- .../core/ir/sql-contract-serializer-base.ts | 41 +- .../core/migrations/contract-to-schema-ir.ts | 33 +- .../core/migrations/field-event-planner.ts | 21 +- .../core/schema-verify/verify-sql-schema.ts | 51 +- .../test/contract-to-schema-ir.test.ts | 1071 +++++++++-------- ...stance.descriptor-self-consistency.test.ts | 20 +- .../test/cross-reference-roundtrip.test.ts | 32 +- .../9-family/test/field-event-planner.test.ts | 15 +- .../9-family/test/schema-verify.helpers.ts | 17 +- 10 files changed, 710 insertions(+), 606 deletions(-) diff --git a/packages/2-sql/9-family/src/core/control-instance.ts b/packages/2-sql/9-family/src/core/control-instance.ts index fd490b928b..0ef66a2d83 100644 --- a/packages/2-sql/9-family/src/core/control-instance.ts +++ b/packages/2-sql/9-family/src/core/control-instance.ts @@ -25,6 +25,10 @@ import { VERIFY_CODE_TARGET_MISMATCH, } from '@prisma-next/framework-components/control'; import type { TypesImportSpec } from '@prisma-next/framework-components/emission'; +import { + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; @@ -63,14 +67,11 @@ function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] { 'storage' in contract && typeof contract.storage === 'object' && contract.storage !== null && - 'namespaces' in contract.storage && - typeof contract.storage.namespaces === 'object' && - contract.storage.namespaces !== null + storageNamespaceValues(contract.storage as unknown as Record).length > 0 ) { - const namespaces = contract.storage.namespaces as Record< - string, - { readonly tables?: Readonly> } - >; + const namespaces = Object.fromEntries( + storageNamespaceEntries(contract.storage as unknown as Record), + ) as Record> }>; for (const ns of Object.values(namespaces)) { const tbls = ns.tables; if (typeof tbls !== 'object' || tbls === null) continue; diff --git a/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts b/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts index 389274d615..e251de031f 100644 --- a/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts +++ b/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts @@ -4,14 +4,16 @@ import type { ContractSerializer } from '@prisma-next/framework-components/contr import { type Namespace, NamespaceBase, + storageNamespaceEntries, UNBOUND_NAMESPACE_ID, } from '@prisma-next/framework-components/ir'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; import { buildSqlNamespace, + buildSqlStorageInput, type SqlNamespaceTablesInput, SqlStorage, - type SqlStorageInput, + type SqlStorageNamespacesInput, type SqlStorageTypeEntry, SqlUnboundNamespace, StorageTable, @@ -115,33 +117,24 @@ export abstract class SqlContractSerializerBase), + ); + const hydratedNamespaces = this.hydrateSqlNamespaceMap(rawNamespaceEntries); const unbound = hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance; return { ...validated, - storage: new SqlStorage({ - storageHash: validated.storage.storageHash, - ...ifDefined('types', hydratedTypes), - // `__unbound__` is guaranteed present above; the residual narrowing is - // the family invariant that hydrated SQL namespaces are `SqlNamespace` - // instances (target/family classes, not the wider framework `Namespace`). - namespaces: blindCast< - SqlStorageInput['namespaces'], - 'hydrated SQL namespaces are SqlNamespace instances; __unbound__ guaranteed present (injected above)' - >({ ...hydratedNamespaces, [UNBOUND_NAMESPACE_ID]: unbound }), - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: validated.storage.storageHash, + ...ifDefined('types', hydratedTypes), + namespaces: blindCast< + SqlStorageNamespacesInput['namespaces'], + 'hydrated SQL namespaces are SqlNamespace instances; __unbound__ guaranteed present (injected above)' + >({ ...hydratedNamespaces, [UNBOUND_NAMESPACE_ID]: unbound }), + }), + ), }; } diff --git a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts index 7365f29189..8e020bb7c0 100644 --- a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts +++ b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts @@ -1,12 +1,18 @@ import type { ColumnDefault, Contract } from '@prisma-next/contract/types'; import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { type ForeignKey, type Index, isPostgresEnumStorageEntry, isStorageTypeInstance, type PostgresEnumStorageEntry, + type SqlNamespace, type SqlStorage, type StorageColumn, StorageTable, @@ -244,12 +250,19 @@ export function detectDestructiveChanges( const conflicts: MigrationPlannerConflict[] = []; const namespaceIds = [ - ...new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)]), + ...new Set([ + ...[...storageNamespaceEntries(from as unknown as Record)].map(([id]) => id), + ...[...storageNamespaceEntries(to as unknown as Record)].map(([id]) => id), + ]), ].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const fromNs = from.namespaces[namespaceId]; - const toNs = to.namespaces[namespaceId]; + const fromNs = getStorageNamespace(from as unknown as Record, namespaceId) as + | SqlNamespace + | undefined; + const toNs = getStorageNamespace(to as unknown as Record, namespaceId) as + | SqlNamespace + | undefined; const fromTables = fromNs?.tables; if (!fromTables) continue; @@ -326,7 +339,9 @@ export function contractToSchemaIR( const allTypes: Record = { ...((storage.types ?? {}) as ResolvedStorageTypes), }; - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues( + storage as unknown as Record, + ) as SqlNamespace[]) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -336,7 +351,9 @@ export function contractToSchemaIR( } const storageTypes = allTypes as ResolvedStorageTypes; const tables: Record = {}; - for (const ns of Object.values(storage.namespaces)) { + for (const ns of storageNamespaceValues( + storage as unknown as Record, + ) as SqlNamespace[]) { for (const [tableName, tableDefRaw] of Object.entries(ns.tables)) { if (!(tableDefRaw instanceof StorageTable)) { throw new Error( @@ -414,7 +431,9 @@ function deriveAnnotations( // Namespace-scoped enums: schema-qualified compound key matching the target's // `readExistingEnumValues` read side, so two namespaces sharing an enum name // (or native type) resolve to distinct live-database types. - for (const [namespaceId, ns] of Object.entries(storage.namespaces)) { + for (const [namespaceId, ns] of [ + ...storageNamespaceEntries(storage as unknown as Record), + ]) { const nsEnums = (ns as { enum?: Record }).enum; if (!nsEnums) continue; for (const entry of Object.values(nsEnums)) { diff --git a/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts b/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts index b2c0e690be..a3e2514750 100644 --- a/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts +++ b/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts @@ -24,7 +24,13 @@ import type { Contract } from '@prisma-next/contract/types'; import type { OpFactoryCall } from '@prisma-next/framework-components/control'; -import { type SqlStorage, type StorageColumn, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, storageNamespaceEntries } from '@prisma-next/framework-components/ir'; +import { + type SqlNamespace, + type SqlStorage, + type StorageColumn, + StorageTable, +} from '@prisma-next/sql-contract/types'; import type { CodecControlHooks, FieldEvent, FieldEventContext } from './types'; export interface PlanFieldEventOperationsOptions { @@ -69,14 +75,19 @@ export function planFieldEventOperations( const dropped: FieldEntry[] = []; const altered: FieldEntry[] = []; + const priorStorage = priorContract?.storage as unknown as Record | undefined; + const newStorage = newContract.storage as unknown as Record; + const namespaceIds = unionSorted( - priorContract ? Object.keys(priorContract.storage.namespaces) : [], - Object.keys(newContract.storage.namespaces), + priorStorage ? [...storageNamespaceEntries(priorStorage)].map(([id]) => id) : [], + [...storageNamespaceEntries(newStorage)].map(([id]) => id), ); for (const namespaceId of namespaceIds) { - const priorNs = priorContract?.storage.namespaces[namespaceId]; - const newNs = newContract.storage.namespaces[namespaceId]; + const priorNs = getStorageNamespace(priorStorage ?? {}, namespaceId) as + | SqlNamespace + | undefined; + const newNs = getStorageNamespace(newStorage, namespaceId) as SqlNamespace | undefined; const priorTables = priorNs?.tables; const newTables = newNs?.tables; diff --git a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts index f7b0b99839..41ec504559 100644 --- a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts +++ b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts @@ -14,11 +14,17 @@ import type { SchemaVerificationNode, VerifyDatabaseSchemaResult, } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { isPostgresEnumStorageEntry, isStorageTypeInstance, type PostgresEnumStorageEntry, + type SqlNamespace, type SqlStorage, type StorageColumn, StorageTable, @@ -139,7 +145,9 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase PostgresEnumStorageEntry | StorageTypeInstance >), }; - for (const ns of Object.values(contract.storage.namespaces)) { + for (const ns of storageNamespaceValues( + contract.storage as unknown as Record, + ) as SqlNamespace[]) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -221,8 +229,12 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase } // Namespace-scoped enums, verified per `(namespaceId, typeName)`. - for (const nsId of Object.keys(contract.storage.namespaces)) { - const ns = contract.storage.namespaces[nsId]; + for (const nsId of [ + ...storageNamespaceEntries(contract.storage as unknown as Record), + ].map(([id]) => id)) { + const ns = getStorageNamespace(contract.storage as unknown as Record, nsId) as + | SqlNamespace + | undefined; if (!ns) continue; const nsEnums = ns.enum; if (!nsEnums) continue; @@ -230,7 +242,7 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase if (!isPostgresEnumStorageEntry(entry)) continue; pushTypeNode( typeName, - `storage.namespaces.${nsId}.enum.${typeName}`, + `storage.${nsId}.enum.${typeName}`, verifyEnumType({ typeName, typeInstance: entry, @@ -400,22 +412,27 @@ function verifySchemaTables(options: { const issues: SchemaIssue[] = []; const rootChildren: SchemaVerificationNode[] = []; const schemaTables = schema.tables; - const namespaceIds = Object.keys(contract.storage.namespaces).sort((a, b) => - a < b ? -1 : a > b ? 1 : 0, - ); + const namespaceIds = [ + ...storageNamespaceEntries(contract.storage as unknown as Record), + ] + .map(([id]) => id) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const ns = contract.storage.namespaces[namespaceId]; + const ns = getStorageNamespace( + contract.storage as unknown as Record, + namespaceId, + ) as SqlNamespace | undefined; if (!ns) continue; for (const [tableName, contractTableRaw] of Object.entries(ns.tables)) { if (!(contractTableRaw instanceof StorageTable)) { throw new Error( - `verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.tables.${tableName}`, + `verifySqlSchema: expected StorageTable at storage.${namespaceId}.tables.${tableName}`, ); } const contractTable = contractTableRaw; const schemaTable = schemaTables[tableName]; - const tablePath = `storage.namespaces.${namespaceId}.tables.${tableName}`; + const tablePath = `storage.${namespaceId}.tables.${tableName}`; if (!schemaTable) { issues.push({ @@ -459,7 +476,13 @@ function verifySchemaTables(options: { if (strict) { for (const tableName of Object.keys(schemaTables)) { const claimed = namespaceIds.some( - (namespaceId) => contract.storage.namespaces[namespaceId]?.tables[tableName] !== undefined, + (namespaceId) => + ( + getStorageNamespace( + contract.storage as unknown as Record, + namespaceId, + ) as SqlNamespace | undefined + )?.tables[tableName] !== undefined, ); if (!claimed) { // `namespaceId` is intentionally absent: an extra table exists in the @@ -475,7 +498,7 @@ function verifySchemaTables(options: { status: 'fail', kind: 'table', name: `table ${tableName}`, - contractPath: `storage.namespaces.*.tables.${tableName}`, + contractPath: `storage.*.tables.${tableName}`, code: 'extra_table', message: `Extra table "${tableName}" found`, expected: undefined, @@ -1183,7 +1206,7 @@ function resolveContractColumnTypeMetadata( return { codecId: referencedType.codecId, nativeType: referencedType.nativeType, - typeParams: { values: referencedType.values } as Record, + typeParams: { values: referencedType.values } as unknown as Record, }; } if (isStorageTypeInstance(referencedType)) { diff --git a/packages/2-sql/9-family/test/contract-to-schema-ir.test.ts b/packages/2-sql/9-family/test/contract-to-schema-ir.test.ts index efc284ab8e..86200dba33 100644 --- a/packages/2-sql/9-family/test/contract-to-schema-ir.test.ts +++ b/packages/2-sql/9-family/test/contract-to-schema-ir.test.ts @@ -3,6 +3,7 @@ import { asNamespaceId, profileHash } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -64,12 +65,14 @@ function unboundStorage( storageHash: StorageHashBase, tables: Record, ): SqlStorage { - return new SqlStorage({ - storageHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }); + return new SqlStorage( + buildSqlStorageInput({ + storageHash, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ); } function contractToSchemaIR( @@ -89,23 +92,25 @@ describe('contractToSchemaIR', () => { }); it('converts a single table with columns', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - User: table({ - columns: { - id: col({ nativeType: 'text' }), - email: col({ nativeType: 'text', nullable: false }), - name: col({ nativeType: 'text', nullable: true }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + User: table({ + columns: { + id: col({ nativeType: 'text' }), + email: col({ nativeType: 'text', nullable: false }), + name: col({ nativeType: 'text', nullable: true }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); @@ -119,38 +124,40 @@ describe('contractToSchemaIR', () => { }); it('drops codecId, typeParams, and typeRef from columns', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - a: col({ - nativeType: 'vector', - codecId: 'pgvector/vector@1', - typeParams: { dimensions: 1536 }, - }), - b: col({ - nativeType: 'vector', - codecId: 'pgvector/vector@1', - typeRef: 'MyVector', - }), - }, - }), + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + a: col({ + nativeType: 'vector', + codecId: 'pgvector/vector@1', + typeParams: { dimensions: 1536 }, + }), + b: col({ + nativeType: 'vector', + codecId: 'pgvector/vector@1', + typeRef: 'MyVector', + }), + }, + }), + }, + }), + }, + types: { + MyVector: { + kind: 'codec-instance', + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: { dimensions: 1536 }, }, - }), - }, - types: { - MyVector: { - kind: 'codec-instance', - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: { dimensions: 1536 }, }, - }, - }); + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); const columnA = result.tables['T']!.columns['a']!; @@ -167,26 +174,28 @@ describe('contractToSchemaIR', () => { }); it('expands parameterized native types when expandNativeType is provided', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - id: col({ - nativeType: 'character', - codecId: 'sql/char@1', - typeParams: { length: 36 }, - }), - name: col({ nativeType: 'text', codecId: 'pg/text@1' }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + id: col({ + nativeType: 'character', + codecId: 'sql/char@1', + typeParams: { length: 36 }, + }), + name: col({ nativeType: 'text', codecId: 'pg/text@1' }), + }, + }), + }, + }), + }, + }), + ); const expand = (input: { nativeType: string; @@ -216,34 +225,36 @@ describe('contractToSchemaIR', () => { // producing a spurious `type_mismatch` (and a spurious // `alterColumnType` op) when planning from one revision of the // contract to itself. - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Post: table({ - columns: { - embedding: col({ - nativeType: 'vector', - codecId: 'pg/vector@1', - nullable: true, - typeRef: 'Embedding1536', - }), - }, - }), + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Post: table({ + columns: { + embedding: col({ + nativeType: 'vector', + codecId: 'pg/vector@1', + nullable: true, + typeRef: 'Embedding1536', + }), + }, + }), + }, + }), + }, + types: { + Embedding1536: { + kind: 'codec-instance', + codecId: 'pg/vector@1', + nativeType: 'vector', + typeParams: { length: 1536 }, }, - }), - }, - types: { - Embedding1536: { - kind: 'codec-instance', - codecId: 'pg/vector@1', - nativeType: 'vector', - typeParams: { length: 1536 }, }, - }, - }); + }), + ); const expand = (input: { nativeType: string; @@ -265,142 +276,154 @@ describe('contractToSchemaIR', () => { }); it('uses base nativeType when no expandNativeType is provided', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - id: col({ - nativeType: 'character', - codecId: 'sql/char@1', - typeParams: { length: 36 }, - }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + id: col({ + nativeType: 'character', + codecId: 'sql/char@1', + typeParams: { length: 36 }, + }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['id']!.nativeType).toBe('character'); }); it('converts literal column defaults', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - status: col({ - nativeType: 'text', - default: { kind: 'literal', value: 'active' }, - }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + status: col({ + nativeType: 'text', + default: { kind: 'literal', value: 'active' }, + }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['status']!.default).toBe("'active'"); }); it('escapes single quotes in string literal defaults', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - author: col({ - nativeType: 'text', - default: { kind: 'literal', value: "O'Reilly" }, - }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + author: col({ + nativeType: 'text', + default: { kind: 'literal', value: "O'Reilly" }, + }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['author']!.default).toBe("'O''Reilly'"); }); it('escapes repeated single quotes in string literal defaults', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - textValue: col({ - nativeType: 'text', - default: { kind: 'literal', value: "a'b''c" }, - }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + textValue: col({ + nativeType: 'text', + default: { kind: 'literal', value: "a'b''c" }, + }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['textValue']!.default).toBe("'a''b''''c'"); }); it('converts function column defaults', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - createdAt: col({ - nativeType: 'timestamptz', - default: { kind: 'function', expression: 'now()' }, - }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + createdAt: col({ + nativeType: 'timestamptz', + default: { kind: 'function', expression: 'now()' }, + }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['createdAt']!.default).toBe('now()'); }); it('omits default field when column has no default', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - name: col({ nativeType: 'text' }), - }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + name: col({ nativeType: 'text' }), + }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['name']!.default).toBeUndefined(); @@ -408,66 +431,72 @@ describe('contractToSchemaIR', () => { }); it('converts primary key', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - id: col({ nativeType: 'text' }), - }, - primaryKey: { columns: ['id'], name: 'T_pkey' }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + id: col({ nativeType: 'text' }), + }, + primaryKey: { columns: ['id'], name: 'T_pkey' }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.primaryKey).toEqual({ columns: ['id'], name: 'T_pkey' }); }); it('converts unique constraints', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - email: col({ nativeType: 'text' }), - }, - uniques: [{ columns: ['email'], name: 'T_email_key' }], - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + email: col({ nativeType: 'text' }), + }, + uniques: [{ columns: ['email'], name: 'T_email_key' }], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.uniques).toEqual([{ columns: ['email'], name: 'T_email_key' }]); }); it('converts indexes with unique: false', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - email: col({ nativeType: 'text' }), - }, - indexes: [{ columns: ['email'], name: 'T_email_idx' }], - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + email: col({ nativeType: 'text' }), + }, + indexes: [{ columns: ['email'], name: 'T_email_idx' }], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.indexes).toEqual([ @@ -476,40 +505,42 @@ describe('contractToSchemaIR', () => { }); it('converts foreign keys (reshapes references)', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Post: table({ - columns: { - authorId: col({ nativeType: 'text' }), - }, - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['authorId'], - }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], - }, - name: 'Post_authorId_fkey', - onDelete: 'cascade', - onUpdate: 'restrict', - constraint: true, - index: true, + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Post: table({ + columns: { + authorId: col({ nativeType: 'text' }), }, - ], - }), - }, - }), - }, - }); + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['authorId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + name: 'Post_authorId_fkey', + onDelete: 'cascade', + onUpdate: 'restrict', + constraint: true, + index: true, + }, + ], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['Post']!.foreignKeys).toEqual([ @@ -526,22 +557,24 @@ describe('contractToSchemaIR', () => { }); it('converts multiple tables', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - User: table({ - columns: { id: col({ nativeType: 'text' }) }, - }), - Post: table({ - columns: { id: col({ nativeType: 'text' }) }, - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + User: table({ + columns: { id: col({ nativeType: 'text' }) }, + }), + Post: table({ + columns: { id: col({ nativeType: 'text' }) }, + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(Object.keys(result.tables)).toEqual(expect.arrayContaining(['User', 'Post'])); @@ -549,29 +582,31 @@ describe('contractToSchemaIR', () => { }); it('propagates storage types into annotations', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - embedding: col({ nativeType: 'vector', typeRef: 'Embedding' }), - }, - }), + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + embedding: col({ nativeType: 'vector', typeRef: 'Embedding' }), + }, + }), + }, + }), + }, + types: { + Embedding: { + kind: 'codec-instance', + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: { dimensions: 1536 }, }, - }), - }, - types: { - Embedding: { - kind: 'codec-instance', - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: { dimensions: 1536 }, }, - }, - }); + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.columns['embedding']!.nativeType).toBe('vector'); @@ -587,29 +622,31 @@ describe('contractToSchemaIR', () => { }); it('writes storage type annotations using the configured namespace', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - embedding: col({ nativeType: 'vector', typeRef: 'Embedding' }), - }, - }), + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + embedding: col({ nativeType: 'vector', typeRef: 'Embedding' }), + }, + }), + }, + }), + }, + types: { + Embedding: { + kind: 'codec-instance', + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: { dimensions: 1536 }, }, - }), - }, - types: { - Embedding: { - kind: 'codec-instance', - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: { dimensions: 1536 }, }, - }, - }); + }), + ); const result = contractToSchemaIRImpl(wrap(storage), { annotationNamespace: 'custom', @@ -627,58 +664,62 @@ describe('contractToSchemaIR', () => { }); it('handles unique constraints without names', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ - columns: { - a: col({ nativeType: 'text' }), - b: col({ nativeType: 'text' }), - }, - uniques: [{ columns: ['a', 'b'] }], - }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ + columns: { + a: col({ nativeType: 'text' }), + b: col({ nativeType: 'text' }), + }, + uniques: [{ columns: ['a', 'b'] }], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['T']!.uniques[0]).toEqual({ columns: ['a', 'b'] }); }); it('handles foreign keys without names', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Post: table({ - columns: { authorId: col({ nativeType: 'text' }) }, - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['authorId'], + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Post: table({ + columns: { authorId: col({ nativeType: 'text' }) }, + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['authorId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + constraint: true, + index: true, }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], - }, - constraint: true, - index: true, - }, - ], - }), - }, - }), - }, - }); + ], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage), { renderDefault: testRenderer }); expect(result.tables['Post']!.foreignKeys[0]).toEqual({ @@ -690,133 +731,139 @@ describe('contractToSchemaIR', () => { }); it('does not synthesize FK backing index when FK columns match primary key columns', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - User: table({ - columns: { id: col({ nativeType: 'text' }) }, - primaryKey: { columns: ['id'] }, - }), - Post: table({ - columns: { userId: col({ nativeType: 'text' }) }, - primaryKey: { columns: ['userId'] }, - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['userId'], - }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + User: table({ + columns: { id: col({ nativeType: 'text' }) }, + primaryKey: { columns: ['id'] }, + }), + Post: table({ + columns: { userId: col({ nativeType: 'text' }) }, + primaryKey: { columns: ['userId'] }, + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['userId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + constraint: true, + index: true, }, - constraint: true, - index: true, - }, - ], - }), - }, - }), - }, - }); + ], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage)); expect(result.tables['Post']!.indexes).toEqual([]); }); it('does not synthesize FK backing index when FK columns match unique columns', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - User: table({ - columns: { id: col({ nativeType: 'text' }) }, - primaryKey: { columns: ['id'] }, - }), - Post: table({ - columns: { userId: col({ nativeType: 'text' }) }, - uniques: [{ columns: ['userId'] }], - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['userId'], - }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + User: table({ + columns: { id: col({ nativeType: 'text' }) }, + primaryKey: { columns: ['id'] }, + }), + Post: table({ + columns: { userId: col({ nativeType: 'text' }) }, + uniques: [{ columns: ['userId'] }], + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['userId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + constraint: true, + index: true, }, - constraint: true, - index: true, - }, - ], - }), - }, - }), - }, - }); + ], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage)); expect(result.tables['Post']!.indexes).toEqual([]); }); it('deduplicates synthesized FK backing indexes for repeated FK column sets', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - User: table({ - columns: { id: col({ nativeType: 'text' }) }, - primaryKey: { columns: ['id'] }, - }), - Post: table({ - columns: { userId: col({ nativeType: 'text' }) }, - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['userId'], - }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + User: table({ + columns: { id: col({ nativeType: 'text' }) }, + primaryKey: { columns: ['id'] }, + }), + Post: table({ + columns: { userId: col({ nativeType: 'text' }) }, + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['userId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + constraint: true, + index: true, }, - constraint: true, - index: true, - }, - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'Post', - columns: ['userId'], + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'Post', + columns: ['userId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'User', + columns: ['id'], + }, + constraint: true, + index: true, }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'User', - columns: ['id'], - }, - constraint: true, - index: true, - }, - ], - }), - }, - }), - }, - }); + ], + }), + }, + }), + }, + }), + ); const result = contractToSchemaIR(wrap(storage)); expect(result.tables['Post']!.indexes).toEqual([ @@ -834,17 +881,19 @@ describe('detectDestructiveChanges', () => { }); it('returns empty when no removals', () => { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - T: table({ columns: { a: col({ nativeType: 'text' }) } }), - }, - }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + T: table({ columns: { a: col({ nativeType: 'text' }) } }), + }, + }), + }, + }), + ); expect(detectDestructiveChanges(storage, storage)).toEqual([]); }); diff --git a/packages/2-sql/9-family/test/control-instance.descriptor-self-consistency.test.ts b/packages/2-sql/9-family/test/control-instance.descriptor-self-consistency.test.ts index 96d94e11d2..874d1b1845 100644 --- a/packages/2-sql/9-family/test/control-instance.descriptor-self-consistency.test.ts +++ b/packages/2-sql/9-family/test/control-instance.descriptor-self-consistency.test.ts @@ -9,7 +9,11 @@ import { createControlStack } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { MigrationToolsError } from '@prisma-next/migration-tools/errors'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { createSqlFamilyInstance } from '../src/core/control-instance'; import type { SqlControlExtensionDescriptor } from '../src/core/migrations/types'; @@ -30,9 +34,7 @@ const fixtureTables = { }; const fixtureHashBody = { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: fixtureTables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: fixtureTables }, }; const fixtureStorageBody = { @@ -61,10 +63,12 @@ function buildContract(): Contract { extensionPacks: {}, meta: {}, profileHash: profileHash('fixture-profile-v1'), - storage: new SqlStorage({ - ...fixtureStorageBody, - storageHash: coreHash(FIXTURE_HEAD_HASH), - }), + storage: new SqlStorage( + buildSqlStorageInput({ + ...fixtureStorageBody, + storageHash: coreHash(FIXTURE_HEAD_HASH), + }), + ), }; } diff --git a/packages/2-sql/9-family/test/cross-reference-roundtrip.test.ts b/packages/2-sql/9-family/test/cross-reference-roundtrip.test.ts index e9c1f629de..2f64476f6c 100644 --- a/packages/2-sql/9-family/test/cross-reference-roundtrip.test.ts +++ b/packages/2-sql/9-family/test/cross-reference-roundtrip.test.ts @@ -36,24 +36,22 @@ describe('cross-reference shape round-trip', () => { }, }, storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - kind: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], - }, - post: { - columns: {}, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + kind: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: {}, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/2-sql/9-family/test/field-event-planner.test.ts b/packages/2-sql/9-family/test/field-event-planner.test.ts index a7b2c436b1..3aa93d4800 100644 --- a/packages/2-sql/9-family/test/field-event-planner.test.ts +++ b/packages/2-sql/9-family/test/field-event-planner.test.ts @@ -4,6 +4,7 @@ import type { OpFactoryCall } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -36,12 +37,14 @@ function table(columns: Record): StorageTable { } function contract(tables: Record): Contract { - const storage = new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ); return { target: 'postgres', targetFamily: 'sql', diff --git a/packages/2-sql/9-family/test/schema-verify.helpers.ts b/packages/2-sql/9-family/test/schema-verify.helpers.ts index 82c039ce8a..0cad5be5b8 100644 --- a/packages/2-sql/9-family/test/schema-verify.helpers.ts +++ b/packages/2-sql/9-family/test/schema-verify.helpers.ts @@ -14,6 +14,7 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { applyFkDefaults, buildSqlNamespace, + buildSqlStorageInput, type ReferentialAction, SqlStorage, StorageTable, @@ -45,13 +46,15 @@ export function createTestContract( targetFamily: 'sql', roots: {}, profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - ...ifDefined('types', storageTypes), - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + ...ifDefined('types', storageTypes), + }), + ), models: {}, capabilities: {}, meta: {}, From c02bdc86080cf9b10e40218102374006cb29538a Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:04:44 +0200 Subject: [PATCH 11/60] refactor(mongo-contract): key namespaces directly under storage MongoStorage sets namespace ids as own-enumerable keys via `Object.defineProperty` with `storageHash` reserved; `buildMongoStorageInput` spreads a namespace map into the flat input. The contract schema validates every non-reserved storage key as a namespace envelope, and validate-storage / canonicalization hooks walk the flat shape. Foundation tests and the hand-authored orm-contract fixture updated to the flat shape. Signed-off-by: Will Madden --- .../src/canonicalization-hooks.ts | 4 +- .../mongo-contract/src/contract-schema.ts | 15 ++- .../mongo-contract/src/contract-types.ts | 18 ++-- .../mongo-contract/src/exports/index.ts | 3 +- .../mongo-contract/src/ir/mongo-collection.ts | 2 +- .../mongo-contract/src/ir/mongo-storage.ts | 33 +++++-- .../mongo-contract/src/validate-storage.ts | 6 +- .../test/contract-types.test-d.ts | 24 ++--- .../test/element-coordinates.test.ts | 18 ++-- .../test/fixtures/orm-contract.d.ts | 2 +- .../test/fixtures/orm-contract.json | 13 ++- .../mongo-contract/test/mongo-storage.test.ts | 97 +++++++++++-------- .../test/validate-storage.test.ts | 22 +++-- 13 files changed, 154 insertions(+), 103 deletions(-) diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/canonicalization-hooks.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/canonicalization-hooks.ts index e7879652c4..d07a715514 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/canonicalization-hooks.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/canonicalization-hooks.ts @@ -5,8 +5,8 @@ import { } from '@prisma-next/contract/hashing-utils'; const preserveEmptyPatterns = [ - ['storage', 'namespaces', '*', 'collections'], - ['storage', 'namespaces', '*', 'collections', '*'], + ['storage', '*', 'collections'], + ['storage', '*', 'collections', '*'], ] as const satisfies readonly PathPattern[]; const shouldPreserveEmpty: PreserveEmptyPredicate = diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts index 64d06e5f3a..807960f7a4 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts @@ -392,8 +392,19 @@ export function createMongoContractSchema( 'domain?': 'unknown', storage: type({ '+': 'reject', - namespaces: type({ '[string]': namespaceEnvelope }), - 'storageHash?': 'string', + storageHash: 'string', + }).narrow((storage, ctx) => { + if (typeof storage !== 'object' || storage === null) { + return ctx.mustBe('an object'); + } + for (const [key, value] of Object.entries(storage)) { + if (key === 'storageHash') continue; + const parsed = namespaceEnvelope(value); + if (parsed instanceof type.errors) { + return ctx.reject({ expected: `storage.${key}: ${parsed.summary}` }); + } + } + return true; }), models: type({ '[string]': ModelDefinitionSchema }), 'valueObjects?': type({ diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts index c7b26492f1..3f44b92f35 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts @@ -4,6 +4,7 @@ import type { ContractModel, ContractValueObject, StorageBase, + StorageHashBase, } from '@prisma-next/contract/types'; import type { Namespace } from '@prisma-next/framework-components/ir'; import type { MongoCollection } from './ir/mongo-collection'; @@ -53,17 +54,16 @@ export type MongoModelDefinition = ContractModel; * runtime in-memory representation is the concrete {@link MongoStorage} * class from `./ir/mongo-storage`; this type is the structural superset * used as the generic-parameter constraint so consumers can name - * `MongoContract<...>` over either the raw JSON envelope (no - * `namespaces` field) or a fully-constructed class instance (with - * `namespaces`). The class structurally satisfies this shape. + * `MongoContract<...>` over either the raw JSON envelope or a fully- + * constructed class instance. Namespace ids are direct keys on the + * storage object (ADR 221 — no `namespaces` wrapper). */ +export type MongoNamespaceShape = Namespace & { + readonly collections: Readonly>; +}; + export type MongoStorageShape = StorageBase & { - readonly namespaces: Record< - string, - Namespace & { - readonly collections: Readonly>; - } - >; + readonly [namespaceId: string]: MongoNamespaceShape | StorageHashBase; }; export type MongoContract< diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts index 59f8b7f4e5..bc923c41e9 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts @@ -21,6 +21,7 @@ export type { MongoJsonValue, MongoModelDefinition, MongoModelStorage, + MongoNamespaceShape, MongoStorageShape, MongoTypeMaps, MongoTypeMapsPhantomKey, @@ -59,7 +60,7 @@ export { MongoIndexOptionDefaults } from '../ir/mongo-index-option-defaults'; export type { MongoIndexOptionsInput } from '../ir/mongo-index-options'; export { MongoIndexOptions } from '../ir/mongo-index-options'; export type { MongoStorageInput } from '../ir/mongo-storage'; -export { MongoStorage } from '../ir/mongo-storage'; +export { buildMongoStorageInput, MongoStorage } from '../ir/mongo-storage'; export type { MongoTimeSeriesCollectionOptionsInput, MongoTimeSeriesGranularity, diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts index d27e0315b1..ffcb9bdab8 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts @@ -9,7 +9,7 @@ import { MongoValidator, type MongoValidatorInput } from './mongo-validator'; /** * Hydration / construction input shape for {@link MongoCollection}. * Mirrors the on-disk storage JSON envelope exactly (the value held at - * `contract.storage.namespaces[].collections[]`) so the family-base + * `getStorageNamespace(contract.storage as Record, ).collections[]`) so the family-base * serializer's hydration walker can hand an arktype-validated literal * straight to `new`. Nested IR-class fields may be supplied as either * plain data literals (typical for JSON-derived input) or diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts index c5d67deeee..02e57a9d40 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts @@ -1,7 +1,9 @@ import type { StorageHashBase } from '@prisma-next/contract/types'; import { + flatStorageInput, freezeNode, IRNodeBase, + isStoragePlaneReservedKey, type Namespace, type Storage, } from '@prisma-next/framework-components/ir'; @@ -15,22 +17,33 @@ export interface MongoNamespaceCollectionsInput { // Mongo concretions always store `MongoCollection` instances in // `collections` (Mongo idiom — distinct from the SQL family's `tables`). // Narrowing the namespace map here lets target/family-level consumers -// iterate `namespaces[*].collections[*]` and recover the concrete -// collection type without the framework's wider `Namespace` tripping -// them up. +// iterate namespace collections and recover the concrete collection type +// without the framework's wider `Namespace` tripping them up. export type MongoNamespace = Namespace & { readonly collections: Readonly>; }; -export interface MongoStorageInput { +export type MongoStorageInput = { + readonly storageHash: StorageHashBase; +} & Readonly>; + +export type MongoStorageNamespacesInput = { readonly storageHash: StorageHashBase; readonly namespaces: Readonly>; +}; + +export function buildMongoStorageInput( + input: MongoStorageNamespacesInput, +): MongoStorageInput { + return flatStorageInput({ + storageHash: input.storageHash, + namespaces: input.namespaces, + }) as MongoStorageInput; } export class MongoStorage extends IRNodeBase implements Storage { declare readonly kind: 'mongo-storage'; readonly storageHash: StorageHashBase; - readonly namespaces: Readonly>; constructor(input: MongoStorageInput) { super(); @@ -41,7 +54,15 @@ export class MongoStorage extends IRNodeBase impl configurable: true, }); this.storageHash = input.storageHash; - this.namespaces = Object.freeze(input.namespaces); + for (const [key, value] of Object.entries(input)) { + if (isStoragePlaneReservedKey(key)) continue; + Object.defineProperty(this, key, { + value: Object.freeze(value), + writable: false, + enumerable: true, + configurable: false, + }); + } freezeNode(this); } } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts index 14f55638d8..fe3bced2b7 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts @@ -1,4 +1,6 @@ +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import type { MongoContract } from './contract-types'; +import type { MongoNamespace } from './ir/mongo-storage'; function formatCrossRef(crossRef: { readonly namespace: string; readonly model: string }): string { return `${crossRef.namespace}.${crossRef.model}`; @@ -8,8 +10,8 @@ function storageDeclaresCollection( storage: MongoContract['storage'], collectionName: string, ): boolean { - for (const ns of Object.values(storage.namespaces)) { - if (Object.hasOwn(ns.collections, collectionName)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + if (Object.hasOwn((ns as MongoNamespace).collections, collectionName)) { return true; } } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts index f5994c7def..bd79674a81 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts @@ -80,12 +80,10 @@ type ContractWithVO = MongoContractWithTypeMaps< }; }; readonly storage: { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly collections: { readonly users: MongoCollection }; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly collections: { readonly users: MongoCollection }; }; readonly storageHash: StorageHashBase<'sha256:test-storage'>; }; @@ -143,10 +141,9 @@ test('ExtractMongoFieldOutputTypes extracts fieldOutputTypes from contract', () readonly models: Record; readonly valueObjects: Record; readonly storage: { - readonly namespaces: Record< - string, - { readonly id: string; readonly collections: Record } - >; + readonly [namespaceId: string]: + | { readonly id: string; readonly collections: Record } + | StorageHashBase<'sha256:s'>; readonly storageHash: StorageHashBase<'sha256:s'>; }; }, @@ -179,10 +176,9 @@ test('ExtractMongoFieldInputTypes extracts fieldInputTypes from contract', () => readonly models: Record; readonly valueObjects: Record; readonly storage: { - readonly namespaces: Record< - string, - { readonly id: string; readonly collections: Record } - >; + readonly [namespaceId: string]: + | { readonly id: string; readonly collections: Record } + | StorageHashBase<'sha256:s'>; readonly storageHash: StorageHashBase<'sha256:s'>; }; }, diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/element-coordinates.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/element-coordinates.test.ts index f713a4a2c6..e9f68a090a 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/element-coordinates.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/element-coordinates.test.ts @@ -2,18 +2,20 @@ import { coreHash } from '@prisma-next/contract/types'; import { elementCoordinates } from '@prisma-next/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { buildMongoNamespace } from '../src/ir/build-mongo-namespace'; -import { MongoStorage } from '../src/ir/mongo-storage'; +import { buildMongoStorageInput, MongoStorage } from '../src/ir/mongo-storage'; describe('elementCoordinates with MongoStorage', () => { it('walks Mongo namespace collections slot', () => { - const storage = new MongoStorage({ - storageHash: coreHash('sha256:element-coordinates-mongo'), - namespaces: { - app: buildMongoNamespace({ id: 'app', collections: { posts: {} } }), - }, - }); + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: coreHash('sha256:element-coordinates-mongo'), + namespaces: { + app: buildMongoNamespace({ id: 'app', collections: { posts: {} } }), + }, + }), + ); - const coordinates = [...elementCoordinates(storage)]; + const coordinates = [...elementCoordinates(storage as unknown as Record)]; expect(coordinates).toContainEqual({ plane: 'storage', namespaceId: 'app', diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts index ba8de5a74a..8461aec733 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts @@ -32,7 +32,7 @@ type ContractBase = { readonly targetFamily: 'mongo'; readonly roots: { readonly tasks: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' }; readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' } }; readonly models: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly zip: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } } }; readonly relations: Record; readonly storage: Record; readonly owner: 'User' }; readonly Bug: { readonly fields: { readonly severity: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } } }; readonly relations: Record; readonly storage: { readonly collection: 'tasks' }; readonly base: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' } }; readonly Comment: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' } }; readonly text: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly createdAt: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' } } }; readonly relations: Record; readonly storage: Record; readonly owner: 'Task' }; readonly Feature: { readonly fields: { readonly priority: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly targetRelease: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } } }; readonly relations: Record; readonly storage: { readonly collection: 'tasks' }; readonly base: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' } }; readonly Task: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly type: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly assigneeId: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' } } }; readonly relations: { readonly assignee: { readonly to: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; readonly cardinality: 'N:1'; readonly on: { readonly localFields: readonly ['assigneeId']; readonly targetFields: readonly ['_id'] } }; readonly comments: { readonly to: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Comment' }; readonly cardinality: '1:N' } }; readonly storage: { readonly collection: 'tasks'; readonly relations: { readonly comments: { readonly field: 'comments' } } }; readonly discriminator: { readonly field: 'type' }; readonly variants: { readonly Bug: { readonly value: 'bug' }; readonly Feature: { readonly value: 'feature' } } }; readonly User: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly email: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' } }; readonly loginCount: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' } }; readonly tags: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; readonly many: true }; readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: 'valueObject'; readonly name: 'HomeAddress' } } }; readonly relations: { readonly addresses: { readonly to: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Address' }; readonly cardinality: '1:N' }; readonly tasks: { readonly to: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' }; readonly cardinality: '1:N'; readonly on: { readonly localFields: readonly ['_id']; readonly targetFields: readonly ['assigneeId'] } } }; readonly storage: { readonly collection: 'users'; readonly relations: { readonly addresses: { readonly field: 'addresses' } } } } }; - readonly storage: { readonly namespaces: { readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; readonly collections: { readonly tasks: MongoCollection; readonly users: MongoCollection } } }; readonly storageHash: StorageHash }; + readonly storage: { readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; readonly collections: { readonly tasks: MongoCollection; readonly users: MongoCollection } } }; readonly capabilities: { }; readonly extensionPacks: { }; readonly meta: { }; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json index 2d36ce1984..6470ca146c 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json @@ -12,13 +12,12 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "collections": { - "tasks": {}, - "users": {} - } + "storageHash": "fixture-hash", + "__unbound__": { + "id": "__unbound__", + "collections": { + "tasks": {}, + "users": {} } } }, diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts index 99d7734dd6..90bb554fb7 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts @@ -1,6 +1,7 @@ import { coreHash } from '@prisma-next/contract/types'; import { freezeNode, + getStorageNamespace, NamespaceBase, UNBOUND_NAMESPACE_ID, } from '@prisma-next/framework-components/ir'; @@ -8,7 +9,8 @@ import { describe, expect, it } from 'vitest'; import { buildMongoNamespace } from '../src/ir/build-mongo-namespace'; import { MongoCollection } from '../src/ir/mongo-collection'; import { MongoIndex } from '../src/ir/mongo-index'; -import { MongoStorage } from '../src/ir/mongo-storage'; +import type { MongoNamespace } from '../src/ir/mongo-storage'; +import { buildMongoStorageInput, MongoStorage } from '../src/ir/mongo-storage'; import { MongoUnboundNamespace } from '../src/ir/mongo-unbound-namespace'; const hash = coreHash('h_0'); @@ -28,58 +30,73 @@ class TestNamespace extends NamespaceBase { describe('MongoStorage', () => { const defaultNamespace = new TestNamespace('default'); - it('exposes storageHash and namespaces as enumerable fields', () => { - const storage = new MongoStorage({ - storageHash: hash, - namespaces: { default: defaultNamespace }, - }); - expect(Object.keys(storage)).toEqual(expect.arrayContaining(['storageHash', 'namespaces'])); + it('exposes storageHash and namespace ids as enumerable fields', () => { + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: hash, + namespaces: { default: defaultNamespace }, + }), + ); + expect(Object.keys(storage)).toEqual(expect.arrayContaining(['storageHash', 'default'])); }); it('accepts built namespace instances with collections', () => { - const storage = new MongoStorage({ - storageHash: hash, - namespaces: { - default: buildMongoNamespace({ - id: 'default', - collections: { - events: new MongoCollection({ - indexes: [new MongoIndex({ keys: [{ field: 'ts', direction: 1 }] })], - }), - }, - }), - }, - }); - expect(storage.namespaces['default']!.collections['events']).toBeInstanceOf(MongoCollection); + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: hash, + namespaces: { + default: buildMongoNamespace({ + id: 'default', + collections: { + events: new MongoCollection({ + indexes: [new MongoIndex({ keys: [{ field: 'ts', direction: 1 }] })], + }), + }, + }), + }, + }), + ); + expect( + (getStorageNamespace( + storage as unknown as Record, + 'default', + ) as MongoNamespace)!.collections['events'], + ).toBeInstanceOf(MongoCollection); }); it('preserves namespace instances passed in (target supplies)', () => { const auth = new TestNamespace('auth'); - const namespaces = { default: defaultNamespace, auth }; - const storage = new MongoStorage({ - storageHash: hash, - namespaces, - }); - expect(storage.namespaces['default']).toBe(defaultNamespace); - expect(storage.namespaces['auth']).toBe(auth); + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: hash, + namespaces: { default: defaultNamespace, auth }, + }), + ); + expect(getStorageNamespace(storage as unknown as Record, 'default')).toBe( + defaultNamespace, + ); + expect(getStorageNamespace(storage as unknown as Record, 'auth')).toBe(auth); }); it('is frozen after construction', () => { - const storage = new MongoStorage({ - storageHash: hash, - namespaces: { default: defaultNamespace }, - }); + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: hash, + namespaces: { default: defaultNamespace }, + }), + ); expect(Object.isFrozen(storage)).toBe(true); }); it('constructs from the unbound namespace singleton alone', () => { - // `namespaces` is a required field on `MongoStorageInput`, so the - // empty/omitted case is a type error rather than a runtime throw — - // this exercises the happy path of an unbound-only storage. - const storage = new MongoStorage({ - storageHash: hash, - namespaces: { [UNBOUND_NAMESPACE_ID]: MongoUnboundNamespace.instance }, - }); - expect(storage.namespaces[UNBOUND_NAMESPACE_ID]).toBe(MongoUnboundNamespace.instance); + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash: hash, + namespaces: { [UNBOUND_NAMESPACE_ID]: MongoUnboundNamespace.instance }, + }), + ); + expect( + getStorageNamespace(storage as unknown as Record, UNBOUND_NAMESPACE_ID), + ).toBe(MongoUnboundNamespace.instance); }); }); diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-storage.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-storage.test.ts index ce45d118ec..25a25500df 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-storage.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-storage.test.ts @@ -9,7 +9,7 @@ function crossRef(model: string, namespace = 'default') { import type { MongoContract } from '../src/contract-types'; import { buildMongoNamespace } from '../src/ir/build-mongo-namespace'; import { MongoCollection } from '../src/ir/mongo-collection'; -import { MongoStorage } from '../src/ir/mongo-storage'; +import { buildMongoStorageInput, MongoStorage } from '../src/ir/mongo-storage'; import { validateMongoStorage } from '../src/validate-storage'; const DUMMY_HASH = coreHash('sha256:test'); @@ -17,15 +17,17 @@ const DUMMY_HASH = coreHash('sha256:test'); function storageWithItemsCollections( collections: Record, ): MongoContract['storage'] { - return new MongoStorage({ - storageHash: DUMMY_HASH, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ - id: UNBOUND_NAMESPACE_ID, - collections, - }), - }, - }); + return new MongoStorage( + buildMongoStorageInput({ + storageHash: DUMMY_HASH, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ + id: UNBOUND_NAMESPACE_ID, + collections, + }), + }, + }), + ) as unknown as MongoContract['storage']; } function makeMinimalContract(overrides: Partial = {}): MongoContract { From ae626057d36367799c95757281861bd8c80c1aba Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:04:53 +0200 Subject: [PATCH 12/60] refactor(mongo-family): emit and hydrate flat storage namespaces The Mongo authoring interpreters/builders, emitter (flat `storage.` namespace type generation), family contract-serializer hydration, schema verifier, schema-diff, and contract-to-schema walks read namespace entries directly under `storage`. Authoring + family unit tests updated to the flat shape. Signed-off-by: Will Madden --- .../contract-psl/src/interpreter.ts | 21 ++-- .../test/interpreter.polymorphism.test.ts | 13 ++- .../contract-psl/test/interpreter.test.ts | 98 ++++++++++--------- .../contract-ts/src/contract-builder.ts | 21 ++-- .../test/contract-builder.dsl.test.ts | 48 +++++++-- .../contract-builder.polymorphism.test.ts | 43 +++++--- .../3-tooling/emitter/src/index.ts | 57 +++++++---- .../test/emitter-hook.structure.test.ts | 11 ++- .../orm/test/value-object-inputs.test-d.ts | 34 +++---- .../9-family/src/core/contract-to-schema.ts | 10 +- .../src/core/control-target-descriptor.ts | 3 +- .../core/ir/mongo-contract-serializer-base.ts | 52 +++++----- .../src/core/ir/mongo-schema-verifier-base.ts | 11 ++- .../9-family/src/core/schema-diff.ts | 26 ++--- 14 files changed, 272 insertions(+), 176 deletions(-) diff --git a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts index 0e58af47b6..2bf73b93d7 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts @@ -16,6 +16,7 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { applyPolymorphicScopeToMongoIndex, buildMongoNamespace, + buildMongoStorageInput, MongoCollection, MongoIndex, type MongoIndexKeyDirection, @@ -1142,15 +1143,17 @@ export function interpretPslDocumentToMongoContract( storage: storageWithoutHash, ...mongoContractCanonicalizationHooks, }); - const storage = new MongoStorage({ - storageHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ - id: UNBOUND_NAMESPACE_ID, - collections: collectionsAsClasses, - }), - }, - }) as Contract['storage']; + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ + id: UNBOUND_NAMESPACE_ID, + collections: collectionsAsClasses, + }), + }, + }), + ) as Contract['storage']; const capabilities: Record> = {}; return ok({ diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts index 9fbf7a57b0..538f653636 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts @@ -1,6 +1,7 @@ import { crossRef } from '@prisma-next/contract/types'; import type { CodecLookup } from '@prisma-next/framework-components/codec'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { MongoNamespaceShape } from '@prisma-next/mongo-contract'; import { parsePslDocument } from '@prisma-next/psl-parser'; import { describe, expect, it } from 'vitest'; import { interpretPslDocumentToMongoContract } from '../src/interpreter'; @@ -41,10 +42,12 @@ const mongoCodecLookup: CodecLookup = { }; function mongoCollectionsOf(ir: { readonly storage: unknown }): Record { - const storage = ir.storage as { - namespaces: Record }>; - }; - return storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections; + return ( + getStorageNamespace( + ir.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as Record; } function interpret(schema: string) { diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts index 16c9da7dbe..62037bfdd6 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts @@ -5,10 +5,12 @@ import type { } from '@prisma-next/contract/types'; import { crossRef } from '@prisma-next/contract/types'; import type { CodecLookup } from '@prisma-next/framework-components/codec'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildMongoNamespace, + buildMongoStorageInput, MongoCollection, + type MongoNamespaceShape, MongoStorage, MongoValidator, } from '@prisma-next/mongo-contract'; @@ -57,10 +59,12 @@ const mongoCodecLookup: CodecLookup = { function mongoCollectionsFromIr(ir: { readonly storage: unknown; }): Record> { - const storage = ir.storage as { - namespaces: Record> }>; - }; - return storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections; + return ( + getStorageNamespace( + ir.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record>; } interface MongoModel { @@ -809,51 +813,53 @@ describe('interpretPslDocumentToMongoContract', () => { storage: { collection: 'posts' }, }, }, - storage: new MongoStorage({ - storageHash: expect.stringMatching( - /^sha256:/, - ) as unknown as StorageHashBase<`sha256:${string}`>, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ - id: UNBOUND_NAMESPACE_ID, - collections: { - users: new MongoCollection({ - validator: new MongoValidator({ - jsonSchema: { - bsonType: 'object', - required: ['_id', 'email', 'name'], - properties: { - _id: { bsonType: 'objectId' }, - name: { bsonType: 'string' }, - email: { bsonType: 'string' }, - bio: { bsonType: ['null', 'string'] }, + storage: new MongoStorage( + buildMongoStorageInput({ + storageHash: expect.stringMatching( + /^sha256:/, + ) as unknown as StorageHashBase<`sha256:${string}`>, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ + id: UNBOUND_NAMESPACE_ID, + collections: { + users: new MongoCollection({ + validator: new MongoValidator({ + jsonSchema: { + bsonType: 'object', + required: ['_id', 'email', 'name'], + properties: { + _id: { bsonType: 'objectId' }, + name: { bsonType: 'string' }, + email: { bsonType: 'string' }, + bio: { bsonType: ['null', 'string'] }, + }, }, - }, - validationLevel: 'strict', - validationAction: 'error', + validationLevel: 'strict', + validationAction: 'error', + }), }), - }), - posts: new MongoCollection({ - validator: new MongoValidator({ - jsonSchema: { - bsonType: 'object', - required: ['_id', 'authorId', 'content', 'createdAt', 'title'], - properties: { - _id: { bsonType: 'objectId' }, - title: { bsonType: 'string' }, - content: { bsonType: 'string' }, - authorId: { bsonType: 'objectId' }, - createdAt: { bsonType: 'date' }, + posts: new MongoCollection({ + validator: new MongoValidator({ + jsonSchema: { + bsonType: 'object', + required: ['_id', 'authorId', 'content', 'createdAt', 'title'], + properties: { + _id: { bsonType: 'objectId' }, + title: { bsonType: 'string' }, + content: { bsonType: 'string' }, + authorId: { bsonType: 'objectId' }, + createdAt: { bsonType: 'date' }, + }, }, - }, - validationLevel: 'strict', - validationAction: 'error', + validationLevel: 'strict', + validationAction: 'error', + }), }), - }), - }, - }), - }, - }), + }, + }), + }, + }), + ), extensionPacks: {}, capabilities: {}, meta: {}, diff --git a/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts b/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts index b628833696..10057bf90c 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts @@ -31,6 +31,7 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { applyPolymorphicScopeToMongoIndex, buildMongoNamespace, + buildMongoStorageInput, MongoCollection, type MongoCollectionInput, MongoCollectionOptions, @@ -1537,15 +1538,17 @@ function buildContractFromDefinition< ...mongoContractCanonicalizationHooks, }); - const storage = new MongoStorage({ - storageHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ - id: UNBOUND_NAMESPACE_ID, - collections, - }), - }, - }) as unknown as MongoStorageShape; + const storage = new MongoStorage( + buildMongoStorageInput({ + storageHash, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildMongoNamespace({ + id: UNBOUND_NAMESPACE_ID, + collections, + }), + }, + }), + ) as unknown as MongoStorageShape; const builtContract = { target: definition.target.targetId, diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts index bb609664dc..57848e0300 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts @@ -1,6 +1,7 @@ import { crossRef } from '@prisma-next/contract/types'; import type { FamilyPackRef, TargetPackRef } from '@prisma-next/framework-components/components'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { MongoNamespaceShape } from '@prisma-next/mongo-contract'; import { describe, expect, it } from 'vitest'; import { defineContract, field, index, model, rel, valueObject } from '../src/contract-builder'; @@ -58,7 +59,14 @@ describe('mongo contract builder', () => { users: crossRef('User'), posts: crossRef('Post'), }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections).toEqual({ + expect( + ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections, + ).toEqual({ users: { kind: 'mongo-collection' }, posts: { kind: 'mongo-collection' }, }); @@ -142,7 +150,14 @@ describe('mongo contract builder', () => { expect(contract.roots).toEqual({ tasks: crossRef('Task'), }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections).toEqual({ + expect( + ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections, + ).toEqual({ tasks: { kind: 'mongo-collection' }, }); expect(contract.valueObjects).toEqual({ @@ -189,7 +204,14 @@ describe('mongo contract builder', () => { models: { User }, }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections).toEqual({ + expect( + ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections, + ).toEqual({ users: { kind: 'mongo-collection', indexes: [ @@ -258,7 +280,14 @@ describe('mongo contract builder', () => { models: { TaskBase, TaskDerived }, }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections).toEqual({ + expect( + ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections, + ).toEqual({ tasks: { kind: 'mongo-collection', indexes: [ @@ -395,7 +424,14 @@ describe('mongo contract builder', () => { models: { User }, }); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.collections).toEqual({ + expect( + ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections, + ).toEqual({ users: { kind: 'mongo-collection', options: { diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts index 2ac7089b1a..8d3ebc5a17 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts @@ -1,5 +1,6 @@ import type { FamilyPackRef, TargetPackRef } from '@prisma-next/framework-components/components'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { MongoNamespaceShape } from '@prisma-next/mongo-contract'; import { describe, expect, it } from 'vitest'; import { defineContract, field, index, model } from '../src/contract-builder'; @@ -61,8 +62,12 @@ describe('mongo contract builder — polymorphic index scoping', () => { models: { Task, Bug, Feature }, }); - const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]! - .collections as unknown as Record< + const collections = ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record< string, { indexes?: Array<{ @@ -125,8 +130,12 @@ describe('mongo contract builder — polymorphic index scoping', () => { models: { taskModel: Task, bugModel: Bug, featureModel: Feature }, }); - const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]! - .collections as unknown as Record< + const collections = ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record< string, { indexes?: Array<{ @@ -183,8 +192,12 @@ describe('mongo contract builder — polymorphic index scoping', () => { models: { Task, Bug, Feature }, }); - const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]! - .collections as unknown as Record< + const collections = ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record< string, { indexes?: Array<{ @@ -232,8 +245,12 @@ describe('mongo contract builder — polymorphic index scoping', () => { models: { Task, Bug }, }); - const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]! - .collections as unknown as Record< + const collections = ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record< string, { indexes?: Array<{ @@ -275,8 +292,12 @@ describe('mongo contract builder — polymorphic index scoping', () => { models: { Task, Bug }, }); - const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]! - .collections as unknown as Record< + const collections = ( + getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections as unknown as Record< string, { indexes?: Array<{ diff --git a/packages/2-mongo-family/3-tooling/emitter/src/index.ts b/packages/2-mongo-family/3-tooling/emitter/src/index.ts index 87026a1b07..6b7b284c07 100644 --- a/packages/2-mongo-family/3-tooling/emitter/src/index.ts +++ b/packages/2-mongo-family/3-tooling/emitter/src/index.ts @@ -1,8 +1,17 @@ import type { Contract, ContractModel } from '@prisma-next/contract/types'; import { serializeObjectKey, serializeValue } from '@prisma-next/emitter/domain-type-generation'; import type { ValidationContext } from '@prisma-next/framework-components/emission'; -import type { Namespace } from '@prisma-next/framework-components/ir'; -import type { MongoCollection, MongoStorage } from '@prisma-next/mongo-contract'; +import { + getStorageNamespace, + type Namespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; +import type { + MongoCollection, + MongoNamespaceShape, + MongoStorage, +} from '@prisma-next/mongo-contract'; const MONGO_NAMESPACE_KIND_FALLBACK = 'mongo-namespace' as const; @@ -16,8 +25,10 @@ function mongoNamespaceSerializedKind(ns: Namespace): string { function assertUniqueMongoCollectionNames(storage: MongoStorage): void { const seen = new Map(); - for (const [namespaceId, ns] of Object.entries(storage.namespaces)) { - for (const coll of Object.keys(ns.collections)) { + for (const [namespaceId, ns] of [ + ...storageNamespaceEntries(storage as unknown as Record), + ]) { + for (const coll of Object.keys((ns as MongoNamespaceShape).collections)) { const existing = seen.get(coll); if (existing !== undefined && existing !== namespaceId) { throw new Error( @@ -54,21 +65,23 @@ function generateMongoNamespaceCollectionsType( return `{ ${entries.join('; ')} }`; } -function generateMongoNamespacesType(namespaces: MongoStorage['namespaces']): string { - const sorted = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b)); +function generateMongoFlatStorageType(storage: MongoStorage): string { + const sorted = [...storageNamespaceEntries(storage as unknown as Record)].sort( + ([a], [b]) => a.localeCompare(b), + ); if (sorted.length === 0) { - return 'Record'; + return ''; } const parts: string[] = []; for (const [name, ns] of sorted) { const collectionsType = generateMongoNamespaceCollectionsType( - ns.collections as Readonly>, + (ns as MongoNamespaceShape).collections as Readonly>, ); parts.push( `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}; ${mongoNamespaceSerializedKind(ns)}; readonly collections: ${collectionsType} }`, ); } - return `{ ${parts.join('; ')} }`; + return `; ${parts.join('; ')}`; } export const mongoEmission = { @@ -119,15 +132,21 @@ export const mongoEmission = { } const storage = contract.storage as MongoStorage | undefined; - if (!storage?.namespaces || typeof storage.namespaces !== 'object') { - throw new Error('Mongo contract must have storage.namespaces'); + if (!storage) { + throw new Error('Mongo contract must have storage'); + } + if ( + storageNamespaceValues(storage as unknown as Record).length === 0 && + !getStorageNamespace(storage as unknown as Record, '__unbound__') + ) { + throw new Error('Mongo contract must have at least one storage namespace entry'); } assertUniqueMongoCollectionNames(storage); const collectionNames = new Set(); - for (const ns of Object.values(storage.namespaces)) { - for (const c of Object.keys(ns.collections)) { + for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const c of Object.keys((ns as MongoNamespaceShape).collections)) { collectionNames.add(c); } } @@ -167,7 +186,7 @@ export const mongoEmission = { } else if (collection) { if (!collectionNames.has(collection)) { throw new Error( - `Model "${modelName}" references collection "${collection}" which is not in storage.namespaces[..].collections`, + `Model "${modelName}" references collection "${collection}" which is not in getStorageNamespace(storage as unknown as Record, ..).collections`, ); } } @@ -188,7 +207,9 @@ export const mongoEmission = { } } - const storageRelations = model.storage['relations'] as Record | undefined; + const storageRelations = model.storage['relations'] as unknown as + | Record + | undefined; if (storageRelations) { for (const relName of Object.keys(storageRelations)) { if (!model.relations[relName]) { @@ -200,7 +221,7 @@ export const mongoEmission = { } for (const [relName, rel] of Object.entries(model.relations)) { - const relObj = rel as Record; + const relObj = rel as unknown as Record; const targetRef = relObj['to'] as { readonly model?: string } | undefined; const targetModelName = targetRef?.model; if (targetModelName) { @@ -217,8 +238,8 @@ export const mongoEmission = { generateStorageType(contract: Contract, storageHashTypeName: string): string { const storage = contract.storage as MongoStorage; - const namespacesType = generateMongoNamespacesType(storage.namespaces); - return `{ readonly namespaces: ${namespacesType}; readonly storageHash: ${storageHashTypeName} }`; + const flatType = generateMongoFlatStorageType(storage); + return `{ readonly storageHash: ${storageHashTypeName}${flatType} }`; }, generateModelStorageType(_modelName: string, model: ContractModel): string { diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts index 63199c2259..908eb45773 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -1,5 +1,10 @@ import type { Contract } from '@prisma-next/contract/types'; import { crossRef } from '@prisma-next/contract/types'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { mongoEmission } from '../src/index'; import { @@ -36,7 +41,9 @@ describe('mongoEmission.validateStructure', () => { ...createMongoContract(), storage: { storageHash: 'sha256:test' }, } as Contract; - expect(() => mongoEmission.validateStructure(contract)).toThrow('must have storage.namespaces'); + expect(() => mongoEmission.validateStructure(contract)).toThrow( + 'must have storage namespace entries', + ); }); it('throws when model references non-existent collection', () => { @@ -53,7 +60,7 @@ describe('mongoEmission.validateStructure', () => { storage: namespacedMongoStorageFromCollections({}), }); expect(() => mongoEmission.validateStructure(contract)).toThrow( - 'references collection "users" which is not in storage.namespaces[..].collections', + 'references collection "users" which is not in getStorageNamespace(storage as Record, ..).collections', ); }); diff --git a/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts b/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts index d2b5f93607..e1be96f4d0 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts @@ -72,12 +72,10 @@ type VOContract = MongoContractWithTypeMaps< }; readonly storage: { readonly storageHash: StorageHashBase<'sha256:test-storage'>; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly collections: { readonly users: MongoCollection }; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly collections: { readonly users: MongoCollection }; }; }; }, @@ -211,12 +209,10 @@ type VOContractWithFieldTypes = MongoContractWithTypeMaps< }; readonly storage: { readonly storageHash: StorageHashBase<'sha256:test-storage'>; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly collections: { readonly users: MongoCollection }; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly collections: { readonly users: MongoCollection }; }; }; }, @@ -388,14 +384,12 @@ type ExtContract = MongoContractWithTypeMaps< }; readonly storage: { readonly storageHash: StorageHashBase<'sha256:ext-storage'>; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly collections: { - readonly tasks: MongoCollection; - readonly users: MongoCollection; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly collections: { + readonly tasks: MongoCollection; + readonly users: MongoCollection; }; }; }; diff --git a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts index 3167cdbd90..ea77330622 100644 --- a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts +++ b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts @@ -1,8 +1,10 @@ +import type { Contract } from '@prisma-next/contract/types'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import type { MongoCollection, MongoCollectionOptions, - MongoContract, MongoIndex, + MongoNamespaceShape, MongoValidator, } from '@prisma-next/mongo-contract'; import { @@ -71,14 +73,14 @@ function convertCollection(name: string, def: MongoCollection): MongoSchemaColle }); } -export function contractToMongoSchemaIR(contract: MongoContract | null): MongoSchemaIR { +export function contractToMongoSchemaIR(contract: Contract | null): MongoSchemaIR { if (!contract) { return new MongoSchemaIR([]); } const collections: MongoSchemaCollection[] = []; - for (const ns of Object.values(contract.storage.namespaces)) { - for (const [name, def] of Object.entries(ns.collections)) { + for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { + for (const [name, def] of Object.entries((ns as MongoNamespaceShape).collections)) { collections.push(convertCollection(name, def)); } } diff --git a/packages/2-mongo-family/9-family/src/core/control-target-descriptor.ts b/packages/2-mongo-family/9-family/src/core/control-target-descriptor.ts index 0be996643a..795a7eb810 100644 --- a/packages/2-mongo-family/9-family/src/core/control-target-descriptor.ts +++ b/packages/2-mongo-family/9-family/src/core/control-target-descriptor.ts @@ -1,3 +1,4 @@ +import type { Contract } from '@prisma-next/contract/types'; import type { ContractSerializer, MigratableTargetDescriptor, @@ -19,7 +20,7 @@ import type { MongoControlFamilyInstance } from './control-instance'; * The descriptor itself is the aggregator; no extra `Target` interface is introduced. */ -export interface MongoControlTargetDescriptor +export interface MongoControlTargetDescriptor extends MigratableTargetDescriptor<'mongo', 'mongo', MongoControlFamilyInstance> { readonly contractSerializer: ContractSerializer; readonly schemaVerifier: SchemaVerifier; diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts index 3933d67696..4cf0b88a5c 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts @@ -1,5 +1,6 @@ import { validateContractDomain } from '@prisma-next/contract/validate-domain'; import type { ContractSerializer } from '@prisma-next/framework-components/control'; +import { storageNamespaceEntries } from '@prisma-next/framework-components/ir'; import { createMongoContractSchema, MongoCollection, @@ -77,7 +78,7 @@ export abstract class MongoContractSerializerBase * default. * * The returned `MongoContract` carries class instances under - * `storage.namespaces[namespaceId].collections[collectionName]` (each value is a + * `getStorageNamespace(storage as Record, namespaceId).collections[collectionName]` (each value is a * `MongoCollection`, with nested `MongoIndex` / `MongoValidator` / * `MongoCollectionOptions` constructed by the `MongoCollection` constructor). * The rest of the contract envelope (models, valueObjects, capabilities, …) @@ -99,7 +100,7 @@ export abstract class MongoContractSerializerBase // hand-authored `MongoContract` generic surface. The schema and // the type are kept in lockstep by the round-trip fixtures under // `test/validate.test.ts`. The hydration walk below additionally - // re-shapes `storage.namespaces.*.collections` from plain data into IR-class + // re-shapes `storage.*.collections` from plain data into IR-class // instances, so the `MongoContract` returned here carries class identity // under those collections maps (and transitively under `indexes` / `validator` // / `options`). @@ -115,37 +116,34 @@ export abstract class MongoContractSerializerBase /** * Walk a structurally-validated Mongo contract and convert each - * `storage.namespaces[nsId].collections[collectionName]` entry from plain + * `getStorageNamespace(storage as Record, nsId).collections[collectionName]` entry from plain * data into `MongoCollection` IR-class instances. Idempotent: already-class * instances pass through unchanged. */ protected hydrateMongoContract(contract: MongoContract): MongoContract { - const rawNamespaces = contract.storage.namespaces; - const hydratedNamespaces = Object.fromEntries( - Object.entries(rawNamespaces).map(([nsId, nsEnvelope]) => { - const rawCollections = nsEnvelope.collections ?? {}; - const hydratedCollections = Object.fromEntries( - Object.entries(rawCollections).map(([name, raw]) => [ - name, - raw instanceof MongoCollection ? raw : new MongoCollection(raw as MongoCollectionInput), - ]), - ); - return [ - nsId, - { - ...nsEnvelope, - id: nsEnvelope.id, - collections: hydratedCollections, - }, - ]; - }), - ); + const hydratedStorage: Record = { + storageHash: contract.storage.storageHash, + }; + for (const [nsId, nsEnvelope] of storageNamespaceEntries( + contract.storage as Record, + )) { + const rawCollections = + (nsEnvelope as { collections?: Record }).collections ?? {}; + const hydratedCollections = Object.fromEntries( + Object.entries(rawCollections).map(([name, raw]) => [ + name, + raw instanceof MongoCollection ? raw : new MongoCollection(raw as MongoCollectionInput), + ]), + ); + hydratedStorage[nsId] = { + ...nsEnvelope, + id: nsEnvelope.id, + collections: hydratedCollections, + }; + } return { ...contract, - storage: { - ...contract.storage, - namespaces: hydratedNamespaces, - }, + storage: hydratedStorage as MongoContract['storage'], }; } diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts index cf3aca9cef..eefc159f21 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts @@ -5,12 +5,13 @@ import type { SchemaVerifyResult, } from '@prisma-next/framework-components/control'; import type { Namespace } from '@prisma-next/framework-components/ir'; +import { storageNamespaceEntries } from '@prisma-next/framework-components/ir'; import type { MongoStorage } from '@prisma-next/mongo-contract'; /** * Mongo family `SchemaVerifier` abstract base. Commits the Mongo family * to namespace-keyed verification: the family-shared walk iterates - * `storage.namespaces` in sorted order and dispatches per-namespace + * storage namespace entries in sorted order and dispatches per-namespace * through the protected `verifyNamespace` hook, then aggregates * target-extension issues from `verifyTargetExtensions`. * @@ -44,10 +45,10 @@ export abstract class MongoSchemaVerifierBase< options: SchemaVerifyOptions, ): readonly SchemaIssue[] { const issues: SchemaIssue[] = []; - const { namespaces } = options.contract.storage; - const namespaceIds = Object.keys(namespaces).sort(); - for (const namespaceId of namespaceIds) { - const namespace = namespaces[namespaceId]; + const namespaceEntries = [ + ...storageNamespaceEntries(options.contract.storage as unknown as Record), + ].sort(([a], [b]) => a.localeCompare(b)); + for (const [namespaceId, namespace] of namespaceEntries) { if (!namespace) continue; issues.push( ...this.verifyNamespace({ diff --git a/packages/2-mongo-family/9-family/src/core/schema-diff.ts b/packages/2-mongo-family/9-family/src/core/schema-diff.ts index 2fa1215206..fdcd2249c7 100644 --- a/packages/2-mongo-family/9-family/src/core/schema-diff.ts +++ b/packages/2-mongo-family/9-family/src/core/schema-diff.ts @@ -41,7 +41,7 @@ export function diffMongoSchemas( status: 'fail', kind: 'collection', name, - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${name}`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${name}`, code: 'MISSING_COLLECTION', message: `Collection "${name}" is missing`, expected: name, @@ -63,7 +63,7 @@ export function diffMongoSchemas( status, kind: 'collection', name, - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${name}`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${name}`, code: 'EXTRA_COLLECTION', message: `Extra collection "${name}" found`, expected: null, @@ -101,7 +101,7 @@ export function diffMongoSchemas( status: worstStatus, kind: 'collection', name, - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${name}`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${name}`, code: worstStatus === 'pass' ? 'MATCH' : 'DRIFT', message: worstStatus === 'pass' ? `Collection "${name}" matches` : `Collection "${name}" has drift`, @@ -171,7 +171,7 @@ function diffIndexes( status: 'pass', kind: 'index', name: formatIndexName(idx), - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, code: 'MATCH', message: `Index ${formatIndexName(idx)} matches`, expected: key, @@ -189,7 +189,7 @@ function diffIndexes( status: 'fail', kind: 'index', name: formatIndexName(idx), - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, code: 'MISSING_INDEX', message: `Index ${formatIndexName(idx)} missing`, expected: key, @@ -212,7 +212,7 @@ function diffIndexes( status, kind: 'index', name: formatIndexName(idx), - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`, code: 'EXTRA_INDEX', message: `Extra index ${formatIndexName(idx)}`, expected: null, @@ -245,7 +245,7 @@ function diffValidator( status: 'fail', kind: 'validator', name: 'validator', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, code: 'MISSING_VALIDATOR', message: 'Validator missing', expected: canonicalize(expected.validator.jsonSchema), @@ -267,7 +267,7 @@ function diffValidator( status, kind: 'validator', name: 'validator', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, code: 'EXTRA_VALIDATOR', message: 'Extra validator found', expected: null, @@ -299,7 +299,7 @@ function diffValidator( status: 'fail', kind: 'validator', name: 'validator', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, code: 'VALIDATOR_MISMATCH', message: 'Validator mismatch', expected: { @@ -322,7 +322,7 @@ function diffValidator( status: 'pass', kind: 'validator', name: 'validator', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`, code: 'MATCH', message: 'Validator matches', expected: expectedSchema, @@ -354,7 +354,7 @@ function diffOptions( status, kind: 'options', name: 'options', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, code: 'EXTRA_OPTIONS', message: 'Extra collection options found', expected: null, @@ -370,7 +370,7 @@ function diffOptions( status: 'pass', kind: 'options', name: 'options', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, code: 'MATCH', message: 'Collection options match', expected: canonicalize(expected.options), @@ -392,7 +392,7 @@ function diffOptions( status: 'fail', kind: 'options', name: 'options', - contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, + contractPath: `storage.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`, code: 'OPTIONS_MISMATCH', message: 'Collection options mismatch', expected: expected.options, From 2299520d366de4b51bb5a4928eef9d4fae2de0a2 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:05:01 +0200 Subject: [PATCH 13/60] refactor(migration): walk flat storage namespaces in aggregate tooling The migration aggregate verifier, project-schema-to-space, and integrity check read namespace entries directly under `storage` via the framework helpers instead of `storage.namespaces`. Aggregate tests updated to the flat shape. Signed-off-by: Will Madden --- .../src/aggregate/check-integrity.ts | 4 ++- .../src/aggregate/project-schema-to-space.ts | 4 ++- .../migration/src/aggregate/verifier.ts | 4 ++- .../aggregate/loader.catastrophic-io.test.ts | 2 +- .../migration/test/aggregate/loader.test.ts | 2 +- .../aggregate/project-schema-to-space.test.ts | 27 +++++++++---------- .../test/aggregate/strategies/synth.test.ts | 4 +-- .../migration/test/aggregate/verifier.test.ts | 4 +-- ...assert-descriptor-self-consistency.test.ts | 18 +++++-------- .../test/deletable-node-modules.test.ts | 16 +++++------ 10 files changed, 38 insertions(+), 47 deletions(-) diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts b/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts index e105fc78e9..ceeb6cb937 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts @@ -216,7 +216,9 @@ function contractViolations(input: IntegrityComputationInput): readonly Integrit }); } - for (const { entityName: elementName } of elementCoordinates(contract.storage)) { + for (const { entityName: elementName } of elementCoordinates( + contract.storage as unknown as Record, + )) { const claimers = elementClaimedBy.get(elementName); if (claimers) claimers.push(member.spaceId); else elementClaimedBy.set(elementName, [member.spaceId]); diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts b/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts index aac3e0615e..314b7fb2fe 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts @@ -97,7 +97,9 @@ function collectOwnedNames( const owned = new Set(); for (const other of otherMembers) { if (other.spaceId === member.spaceId) continue; - for (const { entityName } of elementCoordinates(other.contract().storage)) { + for (const { entityName } of elementCoordinates( + other.contract().storage as unknown as Record, + )) { owned.add(entityName); } } diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts b/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts index 15a6cb0cd2..4b70301d99 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts @@ -214,7 +214,9 @@ function detectOrphanElements( const claimedTables = new Set(); for (const member of members) { const contract = member.contract(); - for (const { entityName } of elementCoordinates(contract.storage)) { + for (const { entityName } of elementCoordinates( + contract.storage as unknown as Record, + )) { claimedTables.add(entityName); } } diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/loader.catastrophic-io.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/loader.catastrophic-io.test.ts index 92f67c2df0..fab30f04cd 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/loader.catastrophic-io.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/loader.catastrophic-io.test.ts @@ -20,7 +20,7 @@ vi.mock('../../src/read-contract-space-head-ref', async (importOriginal) => { const APP_CONTRACT = createSqlContract({ target: 'postgres', storage: { - namespaces: { [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: { user: {} } } }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: { user: {} } }, }, }); diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/loader.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/loader.test.ts index 3707ec7702..5c196ba27d 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/loader.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/loader.test.ts @@ -19,7 +19,7 @@ function sqlContractWithTables(args: { target?: string; tables: readonly string[ return createSqlContract({ target: args.target ?? 'postgres', storage: { - namespaces: { [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables } }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }, }); } diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts index f8fdc44cc6..17bee012f4 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts @@ -6,12 +6,13 @@ import { projectSchemaToSpace } from '../../src/aggregate/project-schema-to-spac import type { ContractSpaceMember } from '../../src/aggregate/types'; import { makeContractSpaceMember } from '../fixtures'; -type MongoStorageLike = StorageBase & { - readonly namespaces: Record< - string, - { readonly id: string; readonly kind: string; readonly collections: Record } +type MongoStorageLike = StorageBase & + Readonly< + Record< + string, + { readonly id: string; readonly kind: string; readonly collections: Record } + > >; -}; /** * Unit tests for the duck-typed schema projector used by the aggregate @@ -30,7 +31,7 @@ type MongoStorageLike = StorageBase & { describe('projectSchemaToSpace', () => { /** * Build a synthetic member with only the fields `projectSchemaToSpace` - * inspects (`spaceId`, `contract.storage.namespaces[…].tables`). The rest is filled + * inspects (`spaceId`, `getStorageNamespace(contract.storage as Record, …).tables`). The rest is filled * with empty / sentinel values to satisfy the type without committing * to a particular family. */ @@ -39,9 +40,7 @@ describe('projectSchemaToSpace', () => { spaceId, contract: createSqlContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }, }), }); @@ -62,12 +61,10 @@ describe('projectSchemaToSpace', () => { target: 'mongo', targetFamily: 'mongo', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-namespace', - collections, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-namespace', + collections, }, }, }), diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/strategies/synth.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/strategies/synth.test.ts index 012cbb0925..c8ca5db02b 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/strategies/synth.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/strategies/synth.test.ts @@ -27,9 +27,7 @@ function makeMember(spaceId: string, tables: Record): ContractS contract: createSqlContract({ target: 'postgres', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }, }), }); diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/verifier.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/verifier.test.ts index f26583cea6..f7292769e8 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/verifier.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/verifier.test.ts @@ -22,9 +22,7 @@ function makeMember(args: { const contract = createSqlContract({ target: 'postgres', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }, }); return makeContractSpaceMember({ diff --git a/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts b/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts index 76e980bfea..40b7062611 100644 --- a/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts +++ b/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts @@ -146,11 +146,9 @@ describe('assertDescriptorSelfConsistency', () => { // recomputing against on-disk JSON that *does* carry `kind` must // still match the authoring-time hash. const namespacedBody = { - namespaces: { - public: { - id: 'public', - tables: STORAGE_BODY.tables, - }, + public: { + id: 'public', + tables: STORAGE_BODY.tables, }, }; const namespacedHash = computeStorageHash({ @@ -160,12 +158,10 @@ describe('assertDescriptorSelfConsistency', () => { ...SQL_HOOKS, }); const onDiskStorage = { - namespaces: { - public: { - id: 'public', - kind: 'postgres-schema', - tables: STORAGE_BODY.tables, - }, + public: { + id: 'public', + kind: 'postgres-schema', + tables: STORAGE_BODY.tables, }, storageHash: namespacedHash, }; diff --git a/packages/1-framework/3-tooling/migration/test/deletable-node-modules.test.ts b/packages/1-framework/3-tooling/migration/test/deletable-node-modules.test.ts index 7d6c99d92a..c0c5ed60ca 100644 --- a/packages/1-framework/3-tooling/migration/test/deletable-node-modules.test.ts +++ b/packages/1-framework/3-tooling/migration/test/deletable-node-modules.test.ts @@ -220,11 +220,9 @@ describe('aggregate pipeline (loader → planner → verifier) against deleted n const spaceContract = createSqlContract({ target: 'postgres', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { test_box: { columns: { x: {}, y: {} } } }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { test_box: { columns: { x: {}, y: {} } } }, }, }, }); @@ -254,11 +252,9 @@ describe('aggregate pipeline (loader → planner → verifier) against deleted n const appContract = createSqlContract({ target: 'postgres', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { user: { columns: { id: {} } } }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { user: { columns: { id: {} } } }, }, }, }); From 8b9e6a33f4c09bee03443bdf336a106f606b97ec Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:05:15 +0200 Subject: [PATCH 14/60] refactor(targets): walk flat storage namespaces in postgres/sqlite Postgres contract serializer, schema, migration planners (enum-planning, issue-planner, planner-strategies, verify-postgres-namespaces), the postgres SQL renderer, and the sqlite planner read namespace entries directly under `storage` via the framework helpers. Target serializer and snapshot tests updated to the flat shape. Signed-off-by: Will Madden --- .../src/core/migrations/enum-planning.ts | 2 +- .../src/core/migrations/issue-planner.ts | 7 +++++- .../src/core/migrations/planner-strategies.ts | 25 +++++++++++-------- .../migrations/verify-postgres-namespaces.ts | 6 +++-- .../src/core/postgres-contract-serializer.ts | 2 +- .../postgres/src/core/postgres-schema.ts | 2 +- .../test/postgres-contract-serializer.test.ts | 20 ++++++++++----- .../test/snapshot-read-shapes.test.ts | 8 +++++- .../src/core/migrations/planner-strategies.ts | 4 ++- .../test/sqlite-contract-serializer.test.ts | 22 ++++++++++------ .../postgres/src/core/sql-renderer.ts | 7 +++++- 11 files changed, 72 insertions(+), 33 deletions(-) diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts index 53700feaed..f338459e98 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts @@ -115,7 +115,7 @@ export function resolveDdlSchemaForNamespaceStorage( if (namespaceId === UNBOUND_NAMESPACE_ID) { return (schemaIr ? readPostgresSchemaIrAnnotations(schemaIr).schema : undefined) ?? 'public'; } - const namespace = storage.namespaces[namespaceId]; + const namespace = getStorageNamespace(storage as Record, namespaceId); if (namespace && isPostgresSchema(namespace)) { return namespace.ddlSchemaName(storage); } diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts index 3e3dcce4e4..a876f2b91c 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts @@ -18,6 +18,11 @@ import type { import { arraysEqual } from '@prisma-next/family-sql/schema-verify'; import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, SqlStorage, @@ -72,7 +77,7 @@ function locateNamespaceTypeInStorage( namespaceId: string, typeName: string, ): unknown { - const ns = storage.namespaces[namespaceId]; + const ns = getStorageNamespace(storage as Record, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return undefined; return (ns.enum as Record)[typeName]; } diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts index 51587d3732..a0f33ff650 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts @@ -93,7 +93,9 @@ export function tableAt( ): StorageTable | undefined { // Namespace.tables is typed as Record at the interface level; // SQL family namespaces always hold StorageTable instances. - return storage.namespaces[namespaceId]?.tables[tableName] as StorageTable | undefined; + return getStorageNamespace(storage as Record, namespaceId)?.tables[tableName] as + | StorageTable + | undefined; } /** @@ -120,7 +122,10 @@ export function resolveNamespaceIdForIssue(issue: { readonly namespaceId?: strin * unchanged so downstream `qualifyTableName` resolves polymorphically. */ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: string): string { - const namespace = ctx.toContract.storage.namespaces[namespaceId]; + const namespace = ctx.getStorageNamespace( + toContract.storage as Record, + namespaceId, + ); if (isPostgresSchema(namespace)) { return namespace.ddlSchemaName(ctx.toContract.storage); } @@ -133,7 +138,7 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: const DEFAULT_ENUM_NAMESPACE_ID = 'public'; function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean { - const ns = storage.namespaces[namespaceId]; + const ns = getStorageNamespace(storage as Record, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return false; return (ns.enum as Record)[typeName] !== undefined; } @@ -156,9 +161,9 @@ function resolveColumnEnumNamespace( typeName: string, ): string | undefined { if (namespaceHasEnum(storage, columnNamespaceId, typeName)) return columnNamespaceId; - const owners = Object.keys(storage.namespaces).filter((nsId) => - namespaceHasEnum(storage, nsId, typeName), - ); + const owners = [...storageNamespaceEntries(storage as Record)] + .map(([id]) => id) + .filter((nsId) => namespaceHasEnum(storage, nsId, typeName)); if (owners.length === 1) return owners[0]; if (owners.includes(DEFAULT_ENUM_NAMESPACE_ID)) return DEFAULT_ENUM_NAMESPACE_ID; return owners[0]; @@ -166,7 +171,7 @@ function resolveColumnEnumNamespace( /** * Finds a type entry by explicit namespace coordinate. Namespace types (e.g. - * Postgres enums) live under `storage.namespaces[nsId].enum`. Returns the + * Postgres enums) live under `getStorageNamespace(storage as Record, nsId).enum`. Returns the * entry from the named namespace only — never scans other namespaces, so two * namespaces that hold an enum with the same name resolve independently. */ @@ -175,7 +180,7 @@ function locateNamespaceType( namespaceId: string, typeName: string, ): PostgresEnumStorageEntry | undefined { - const ns = storage.namespaces[namespaceId]; + const ns = getStorageNamespace(storage as Record, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return undefined; return (ns.enum as Record)[typeName]; } @@ -435,7 +440,7 @@ function enumRebuildCallRecipe( // namespace correctly binds to a `public`-namespace enum, while two // same-named enums in distinct namespaces keep their columns disjoint. const columnRefs: { namespaceId: string; table: string; column: string }[] = []; - for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) { + for (const [nsId, ns] of Object.entries(ctx.toContract.storage as Record)) { for (const [tableName, tableNode] of Object.entries(ns.tables)) { const table = tableNode as StorageTable; for (const [columnName, column] of Object.entries(table.columns)) { @@ -638,7 +643,7 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) = */ function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap { const result = new Map(); - for (const [nsId, ns] of Object.entries(storage.namespaces)) { + for (const [nsId, ns] of [...storageNamespaceEntries(storage as Record)]) { if (!('enum' in ns) || ns.enum == null) continue; const nsEnums = ns.enum as Record; for (const [name, instance] of Object.entries(nsEnums).sort(([a], [b]) => a.localeCompare(b))) { diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts index e23b542b0a..5185f6f7b8 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts @@ -15,7 +15,7 @@ import { isPostgresSchema } from '../postgres-schema'; * itself. */ function resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string { - const namespace = storage.namespaces[namespaceId]; + const namespace = getStorageNamespace(storage as Record, namespaceId); if (isPostgresSchema(namespace)) { return namespace.ddlSchemaName(storage); } @@ -71,7 +71,9 @@ export function verifyPostgresNamespacePresence(input: { const { contract, schema } = input; const existing = new Set(existingSchemasFromSchema(schema)); const issues: SchemaIssue[] = []; - const namespaceIds = Object.keys(contract.storage.namespaces).sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] + .map(([id]) => id) + .sort(); for (const namespaceId of namespaceIds) { if (namespaceId === UNBOUND_NAMESPACE_ID) continue; const ddlName = resolveDdlSchemaName(contract.storage, namespaceId); diff --git a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts index c56e08fee7..248a2da9a1 100644 --- a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts +++ b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts @@ -113,7 +113,7 @@ export class PostgresContractSerializer extends SqlContractSerializerBase): JsonObject { const { storage, ...rest } = contract; const namespacesJson: Record = {}; - for (const [nsId, ns] of Object.entries(storage.namespaces)) { + for (const [nsId, ns] of [...storageNamespaceEntries(storage as Record)]) { if (isPostgresSchema(ns)) { namespacesJson[nsId] = this.serializePostgresNamespace(ns, ns.id === UNBOUND_NAMESPACE_ID); } else { diff --git a/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts b/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts index 9482d1cca5..ca3cfa8794 100644 --- a/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts +++ b/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts @@ -173,7 +173,7 @@ export class PostgresUnboundSchema extends PostgresSchema { * (e.g. emit a conflict instead of silently picking a schema). */ override ddlSchemaName(storage: SqlStorage): string { - if (storage.namespaces['public'] !== undefined) { + if (getStorageNamespace(storage as Record, 'public') !== undefined) { return 'public'; } return UNBOUND_NAMESPACE_ID; diff --git a/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts b/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts index fc3e9367f6..a7e4cd1351 100644 --- a/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts +++ b/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts @@ -77,7 +77,10 @@ describe('PostgresContractSerializer', () => { const serializer = new PostgresContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); expect(contract.targetFamily).toBe('sql'); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables).toEqual({}); + expect( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! + .tables, + ).toEqual({}); }); it('hydrates JSON storage into the SQL Contract IR class hierarchy', () => { @@ -85,7 +88,10 @@ describe('PostgresContractSerializer', () => { const contract = serializer.deserializeContract(makeContractWithTablesJson()); expect(contract.storage).toBeInstanceOf(SqlStorage); - const tables = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables; + const tables = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )!.tables; const userTable = tables['user'] as StorageTable | undefined; expect(userTable).toBeInstanceOf(StorageTable); expect(userTable?.columns['id']).toBeInstanceOf(StorageColumn); @@ -124,11 +130,13 @@ describe('PostgresContractSerializer', () => { }, }); expect(reparsed.storage).not.toHaveProperty('kind'); - expect(reparsed.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.user).not.toHaveProperty( - 'kind', - ); expect( - reparsed.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.user.columns.id, + getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables + .user, + ).not.toHaveProperty('kind'); + expect( + getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables + .user.columns.id, ).not.toHaveProperty('kind'); }); diff --git a/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts b/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts index 49f42a56dd..086baf9dd4 100644 --- a/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts +++ b/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts @@ -1,5 +1,10 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import { SqlStorage } from '@prisma-next/sql-contract/types'; import { join, relative, resolve } from 'pathe'; import { describe, expect, it } from 'vitest'; @@ -88,7 +93,8 @@ describe('snapshot-read shape fixtures — per-kind round-trip (TML-2536)', () = const raw = JSON.parse(readFileSync(join(FIXTURES_DIR, 'postgres-enum.json'), 'utf-8')); const contract = serializer.deserializeContract(raw); expect(contract.storage).toBeInstanceOf(SqlStorage); - const entry = contract.storage.namespaces['public']?.enum?.['user_role']; + const entry = getStorageNamespace(contract.storage as Record, 'public') + ?.enum?.['user_role']; expect(entry).toBeInstanceOf(PostgresEnumType); expect(entry).toMatchObject({ kind: 'postgres-enum', diff --git a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts index 7770bdaff6..4c454549d5 100644 --- a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts +++ b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts @@ -61,7 +61,9 @@ export function tableAt( namespaceId: string, tableName: string, ): StorageTable | undefined { - return storage.namespaces[namespaceId]?.tables[tableName] as StorageTable | undefined; + return getStorageNamespace(storage as Record, namespaceId)?.tables[tableName] as + | StorageTable + | undefined; } /** diff --git a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts index 48e489b7b6..23544eb401 100644 --- a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts +++ b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts @@ -45,7 +45,10 @@ describe('SqliteContractSerializer', () => { const serializer = new SqliteContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); expect(contract.targetFamily).toBe('sql'); - expect(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables).toEqual({}); + expect( + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! + .tables, + ).toEqual({}); }); it('hydrates JSON storage into the SQL Contract IR class hierarchy', () => { @@ -53,9 +56,10 @@ describe('SqliteContractSerializer', () => { const contract = serializer.deserializeContract(makeContractWithTablesJson()); expect(contract.storage).toBeInstanceOf(SqlStorage); - const userTable = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables['user'] as - | StorageTable - | undefined; + const userTable = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )!.tables['user'] as StorageTable | undefined; expect(userTable).toBeInstanceOf(StorageTable); expect(userTable?.columns['id']).toBeInstanceOf(StorageColumn); }); @@ -72,11 +76,13 @@ describe('SqliteContractSerializer', () => { const json = serializer.serializeContract(contract); const reparsed = JSON.parse(JSON.stringify(json)); expect(reparsed.storage).not.toHaveProperty('kind'); - expect(reparsed.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.user).not.toHaveProperty( - 'kind', - ); expect( - reparsed.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.user.columns.id, + getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables + .user, + ).not.toHaveProperty('kind'); + expect( + getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables + .user.columns.id, ).not.toHaveProperty('kind'); }); }); diff --git a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts index 199fa5f2eb..8ddfd6fe1b 100644 --- a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts +++ b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts @@ -1,4 +1,9 @@ import type { CodecLookup } from '@prisma-next/framework-components/codec'; +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import { runtimeError } from '@prisma-next/framework-components/runtime'; import { type AggregateExpr, @@ -679,7 +684,7 @@ function getInsertColumnOrder( } let table: { columns: Record } | undefined; - for (const ns of Object.values(contract.storage.namespaces)) { + for (const ns of storageNamespaceValues(contract.storage as Record)) { // Namespace.tables is Record at the interface level; // SQL family namespaces hold StorageTable instances which have .columns. const found = ns.tables[tableName] as { columns: Record } | undefined; From 0771568bb70f3635eb49e3241b29a3ce91fa5430 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:05:25 +0200 Subject: [PATCH 15/60] refactor(mongo-target): hydrate flat storage namespaces The Mongo target contract serializer and contract construction wrap the flat namespace shape (namespace ids keyed directly under `storage`), and target serializer/schema-verifier tests follow. Signed-off-by: Will Madden --- .../core/mongo-target-contract-serializer.ts | 71 +++++++---- .../src/core/mongo-target-contract.ts | 16 +-- .../mongo-target-contract-serializer.test.ts | 120 ++++++++++-------- .../test/mongo-target-schema-verifier.test.ts | 33 ++--- 4 files changed, 137 insertions(+), 103 deletions(-) diff --git a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts index 285bb83ca1..faed13b20b 100644 --- a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts +++ b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts @@ -1,6 +1,14 @@ import { MongoContractSerializerBase } from '@prisma-next/family-mongo/ir'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { type MongoContract, MongoStorage } from '@prisma-next/mongo-contract'; +import { + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; +import { + buildMongoStorageInput, + type MongoContract, + type MongoNamespaceShape, + MongoStorage, +} from '@prisma-next/mongo-contract'; import type { JsonObject } from '@prisma-next/utils/json'; import type { MongoTargetContract } from './mongo-target-contract'; import { MongoTargetDatabase, MongoTargetUnboundDatabase } from './mongo-target-database'; @@ -9,48 +17,55 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase { - const collections = nsData.collections; - const collectionCount = Object.keys(collections).length; - if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0) { - return [nsId, MongoTargetUnboundDatabase.instance]; - } - return [ - nsId, - new MongoTargetDatabase({ - id: nsData.id, - collections, - }), - ]; + [...storageNamespaceEntries(storage as unknown as Record)].map( + ([nsId, nsData]) => { + const ns = nsData as MongoNamespaceShape; + const collections = ns.collections; + const collectionCount = Object.keys(collections).length; + if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0) { + return [nsId, MongoTargetUnboundDatabase.instance]; + } + return [ + nsId, + new MongoTargetDatabase({ + id: ns.id, + collections, + }), + ]; + }, + ), + ); + const targetStorage = new MongoStorage( + buildMongoStorageInput({ + storageHash: storage.storageHash, + namespaces, }), ); - const targetStorage = new MongoStorage({ - storageHash: storage.storageHash, - namespaces, - }); return { ...rest, storage: targetStorage }; } override serializeContract(contract: MongoTargetContract): JsonObject { const { storage, ...rest } = contract; - const namespacesJson: Record = {}; - for (const [nsId, ns] of Object.entries(storage.namespaces)) { + const storageOut: Record = { + storageHash: String(storage.storageHash), + }; + for (const [nsId, ns] of [ + ...storageNamespaceEntries(storage as unknown as Record), + ]) { + const mongoNs = ns as MongoNamespaceShape; const collectionsOut: Record = {}; - for (const [collName, coll] of Object.entries(ns.collections)) { + for (const [collName, coll] of Object.entries(mongoNs.collections)) { collectionsOut[collName] = JSON.parse(JSON.stringify(coll)) as JsonObject; } - namespacesJson[nsId] = { - id: ns.id, + storageOut[nsId] = { + id: mongoNs.id, kind: 'mongo-database', collections: collectionsOut, }; } return { ...rest, - storage: { - storageHash: String(storage.storageHash), - namespaces: namespacesJson, - }, + storage: storageOut, // `rest` carries Contract fields typed against framework interfaces // (e.g. `ContractExecutionSection`) that TypeScript can't structurally // prove are JSON-compatible without a per-field re-validation pass. diff --git a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract.ts b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract.ts index 9180b02993..fd732b8148 100644 --- a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract.ts +++ b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract.ts @@ -1,15 +1,13 @@ -import type { MongoContract, MongoStorage } from '@prisma-next/mongo-contract'; +import type { Contract } from '@prisma-next/contract/types'; +import type { MongoStorage } from '@prisma-next/mongo-contract'; /** * Mongo target contract envelope: the result of * `descriptor.contractSerializer.deserializeContract(json)`. * - * Structurally `MongoContract` with the storage envelope promoted to - * the family-layer `MongoStorage` class instance — the class carries - * `namespaces` and gives the rest of the framework a stable surface to - * reach for. The leaf collection / index shapes inside - * `storage.collections` are family-layer `MongoCollection` instances. + * Structurally a {@link Contract} with the storage envelope promoted to + * the family-layer {@link MongoStorage} class instance — namespace keys live + * alongside `storageHash` on that class. The leaf collection / index shapes + * inside each namespace's `collections` are family-layer `MongoCollection` instances. */ -export type MongoTargetContract = Omit & { - readonly storage: MongoStorage; -}; +export type MongoTargetContract = Contract; diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts index a48f96e166..738c01d85e 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts @@ -1,9 +1,10 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { MongoCollationOptions, MongoCollection, MongoCollectionOptions, MongoIndex, + type MongoNamespaceShape, MongoStorage, MongoValidator, } from '@prisma-next/mongo-contract'; @@ -19,12 +20,10 @@ function makeSingletonUnboundContractJson() { roots: {}, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-database', - collections: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-database', + collections: {}, }, }, models: {}, @@ -39,13 +38,11 @@ function makeValidContractJson() { roots: { items: { model: 'Item', namespace: UNBOUND_NAMESPACE_ID } }, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-database', - collections: { - items: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-database', + collections: { + items: {}, }, }, }, @@ -71,14 +68,21 @@ describe('MongoTargetContractSerializer', () => { const serializer = new MongoTargetContractSerializer(); const contract = serializer.deserializeContract(makeSingletonUnboundContractJson()); - expect(contract.storage.namespaces['__unbound__']).toBe(MongoTargetUnboundDatabase.instance); + expect( + getStorageNamespace(contract.storage as unknown as Record, '__unbound__'), + ).toBe(MongoTargetUnboundDatabase.instance); }); it('hydrates collections into MongoCollection IR-class instances', () => { const serializer = new MongoTargetContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); - const items = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.collections['items']; + const items = ( + getStorageNamespace( + contract.storage as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape | undefined + )?.collections['items']; expect(items).toBeInstanceOf(MongoCollection); expect(items?.kind).toBe('mongo-collection'); }); @@ -89,19 +93,19 @@ describe('MongoTargetContractSerializer', () => { expect(() => serializer.deserializeContract(bad)).toThrow(); }); - it('serializeContract emits canonical nested namespaces on disk', () => { + it('serializeContract emits flat namespace keys on disk', () => { const serializer = new MongoTargetContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); const json = serializer.serializeContract(contract) as { storage: Record; }; - expect(json.storage).toHaveProperty('namespaces'); + expect(json.storage).toHaveProperty(UNBOUND_NAMESPACE_ID); + expect(json.storage).not.toHaveProperty('namespaces'); expect(json.storage).not.toHaveProperty('collections'); - const namespaces = json.storage['namespaces'] as Record< - string, - { collections: Record } - >; - expect(namespaces[UNBOUND_NAMESPACE_ID]?.collections['items']).toMatchObject({ + const namespace = json.storage[UNBOUND_NAMESPACE_ID] as { + collections: Record; + }; + expect(namespace.collections['items']).toMatchObject({ kind: 'mongo-collection', }); }); @@ -115,28 +119,26 @@ describe('MongoTargetContractSerializer', () => { roots: { items: { model: 'Item', namespace: UNBOUND_NAMESPACE_ID } }, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-database', - collections: { - items: { - indexes: [ - { - keys: [{ field: 'email', direction: 1 as const }], - unique: true, - collation: { locale: 'en', strength: 2 }, - }, - ], - validator: { - jsonSchema: { type: 'object' }, - validationLevel: 'strict' as const, - validationAction: 'error' as const, - }, - options: { + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-database', + collections: { + items: { + indexes: [ + { + keys: [{ field: 'email', direction: 1 as const }], + unique: true, collation: { locale: 'en', strength: 2 }, - changeStreamPreAndPostImages: { enabled: true }, }, + ], + validator: { + jsonSchema: { type: 'object' }, + validationLevel: 'strict' as const, + validationAction: 'error' as const, + }, + options: { + collation: { locale: 'en', strength: 2 }, + changeStreamPreAndPostImages: { enabled: true }, }, }, }, @@ -157,7 +159,12 @@ describe('MongoTargetContractSerializer', () => { const serializer = new MongoTargetContractSerializer(); const contract = serializer.deserializeContract(makeFullyPopulatedJson()); - const items = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.collections['items']; + const items = ( + getStorageNamespace( + contract.storage as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape | undefined + )?.collections['items']; expect(items).toBeInstanceOf(MongoCollection); expect(items?.indexes?.[0]).toBeInstanceOf(MongoIndex); expect(items?.validator).toBeInstanceOf(MongoValidator); @@ -172,16 +179,27 @@ describe('MongoTargetContractSerializer', () => { const out = serializer.serializeContract(contract); const reparsed = JSON.parse(JSON.stringify(out)); - const items = reparsed.storage.namespaces[UNBOUND_NAMESPACE_ID].collections.items; - expect(items.kind).toBe('mongo-collection'); - expect(items.indexes[0].kind).toBe('mongo-index'); - expect(items.validator.kind).toBe('mongo-validator'); - expect(items.options.kind).toBe('mongo-collection-options'); - expect(items.options.collation.kind).toBe('mongo-collation-options'); + const items = ( + getStorageNamespace( + reparsed.storage as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape + ).collections['items']; + expect(items).toBeDefined(); + expect(items!.kind).toBe('mongo-collection'); + expect(items!.indexes![0]!.kind).toBe('mongo-index'); + expect(items!.validator!.kind).toBe('mongo-validator'); + expect(items!.options!.kind).toBe('mongo-collection-options'); + expect(items!.options!.collation!.kind).toBe('mongo-collation-options'); const roundtripped = serializer.deserializeContract(reparsed); expect( - roundtripped.storage.namespaces[UNBOUND_NAMESPACE_ID]?.collections['items'], + ( + getStorageNamespace( + roundtripped.storage as unknown as Record, + UNBOUND_NAMESPACE_ID, + ) as MongoNamespaceShape | undefined + )?.collections['items'], ).toBeInstanceOf(MongoCollection); }); }); diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts index cb70236576..7b65ba3ed8 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts @@ -1,4 +1,7 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { MongoSchemaIR } from '@prisma-next/mongo-schema-ir'; import { describe, expect, it } from 'vitest'; import { MongoTargetContractSerializer } from '../src/core/mongo-target-contract-serializer'; @@ -12,13 +15,11 @@ function deserializedContract() { roots: { items: { namespace: UNBOUND_NAMESPACE_ID, model: 'Item' } }, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-database', - collections: { - items: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-database', + collections: { + items: {}, }, }, }, @@ -42,12 +43,10 @@ describe('MongoTargetSchemaVerifier', () => { roots: {}, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-database', - collections: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-database', + collections: {}, }, }, models: {}, @@ -75,7 +74,11 @@ describe('MongoTargetSchemaVerifier', () => { it('uses the family-shared scaffolding: walks each namespace and aggregates issues', () => { const verifier = new MongoTargetSchemaVerifier(); const contract = deserializedContract(); - expect(Object.keys(contract.storage.namespaces)).toEqual(['__unbound__']); + expect( + [...storageNamespaceEntries(contract.storage as unknown as Record)].map( + ([id]) => id, + ), + ).toEqual(['__unbound__']); const result = verifier.verifySchema({ contract, schema: new MongoSchemaIR([]) }); expect(result).toBeDefined(); From be04a48766a04eef7f0b8db334dd64bcd78594d2 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:05:27 +0200 Subject: [PATCH 16/60] refactor(extensions): read flat storage namespaces The sql-orm-client model accessor, collection contract, where-binding, and query-plan helpers resolve tables through namespace entries directly under `storage` via the framework helpers. Extension PSL-interpretation and descriptor tests updated to the flat shape. Signed-off-by: Will Madden --- .../cipherstash/test/descriptor.test.ts | 9 +++++++- .../test/psl-interpretation-numeric.test.ts | 3 ++- .../psl-interpretation-other-types.test.ts | 3 ++- .../test/psl-interpretation.test.ts | 3 ++- .../pgvector/test/descriptor.test.ts | 2 +- .../psl-namespace-qualifier-routing.test.ts | 18 ++++++++++----- .../sql-orm-client/src/collection-contract.ts | 5 ++--- .../sql-orm-client/src/model-accessor.ts | 7 +++--- .../sql-orm-client/src/query-plan-meta.ts | 7 +++--- .../src/query-plan-mutations.ts | 5 ++--- .../sql-orm-client/src/where-binding.ts | 5 ++--- .../sql-orm-client/test/helpers.ts | 22 ++++++++++++++----- .../test/mutation-executor.test.ts | 22 +++++++++---------- .../sql-orm-client/test/unbound-tables.ts | 5 ++--- 14 files changed, 72 insertions(+), 44 deletions(-) diff --git a/packages/3-extensions/cipherstash/test/descriptor.test.ts b/packages/3-extensions/cipherstash/test/descriptor.test.ts index b1656f3230..950531d63f 100644 --- a/packages/3-extensions/cipherstash/test/descriptor.test.ts +++ b/packages/3-extensions/cipherstash/test/descriptor.test.ts @@ -22,6 +22,11 @@ * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md */ +import { + getStorageNamespace, + storageNamespaceEntries, + storageNamespaceValues, +} from '@prisma-next/framework-components/ir'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; import { describe, expect, it } from 'vitest'; @@ -47,7 +52,9 @@ describe('cipherstash extension descriptor (contract-space package layout)', () it('exposes a contractSpace declaring the eql_v2_configuration table', () => { const space = cipherstashExtensionDescriptor.contractSpace; expect(space).toBeDefined(); - const unboundTables = space!.contractJson.storage.namespaces['__unbound__']?.tables ?? {}; + const unboundTables = + space!.getStorageNamespace(contractJson.storage as Record, '__unbound__') + ?.tables ?? {}; expect(Object.keys(unboundTables)).toEqual([EQL_V2_CONFIGURATION_TABLE]); }); diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts index 1cbdf7082a..400336783e 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts @@ -55,7 +55,8 @@ type StorageView = { readonly types?: Record>; }; const asStorage = (storage: unknown): StorageView => storage as StorageView; -const unboundTables = (s: StorageView) => s.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}; +const unboundTables = (s: StorageView) => + getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDouble constructor', () => { it('lowers full args to a column with cipherstash/double@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts index 24e64a95a0..5abb8b9e5b 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts @@ -56,7 +56,8 @@ type StorageView = { readonly types?: Record>; }; const asStorage = (storage: unknown): StorageView => storage as StorageView; -const unboundTables = (s: StorageView) => s.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}; +const unboundTables = (s: StorageView) => + getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDate constructor', () => { it('lowers full args to a column with cipherstash/date@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts index c1f85ecaed..e26cb8862b 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts @@ -72,7 +72,8 @@ type StorageView = { readonly types?: Record>; }; const asStorage = (storage: unknown): StorageView => storage as StorageView; -const unboundTables = (s: StorageView) => s.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}; +const unboundTables = (s: StorageView) => + getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedString constructor', () => { it('lowers full args to a column with codecId, nativeType, typeParams', () => { diff --git a/packages/3-extensions/pgvector/test/descriptor.test.ts b/packages/3-extensions/pgvector/test/descriptor.test.ts index 011db15197..286d3454b2 100644 --- a/packages/3-extensions/pgvector/test/descriptor.test.ts +++ b/packages/3-extensions/pgvector/test/descriptor.test.ts @@ -48,7 +48,7 @@ describe('pgvector extension descriptor (contract-space package layout)', () => it('exposes a contractSpace declaring the vector parameterised native type', () => { const space = pgvectorExtensionDescriptor.contractSpace; expect(space).toBeDefined(); - const namespaces = space!.contractJson.storage.namespaces as Record< + const namespaces = space!.contractJson.storage as Record as Record< string, { readonly tables?: Record } >; diff --git a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts index 61b37b5063..d1e3f99247 100644 --- a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts +++ b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts @@ -65,11 +65,15 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect(storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables['tenant']).toBeDefined(); + expect( + getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables[ + 'tenant' + ], + ).toBeDefined(); // The storage map carries the Postgres target concretion (not the // SQL family placeholder) at the unbound slot. - const namespace = storage.namespaces[UNBOUND_NAMESPACE_ID]; + const namespace = getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID); expect(namespace).toBeInstanceOf(PostgresUnboundSchema); // The qualifier elides — DDL emission against this namespace @@ -103,9 +107,11 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect(storage.namespaces['auth']?.tables['user']).toBeDefined(); + expect( + getStorageNamespace(storage as Record, 'auth')?.tables['user'], + ).toBeDefined(); - const namespace = storage.namespaces['auth']; + const namespace = getStorageNamespace(storage as Record, 'auth'); expect(namespace).toBeInstanceOf(PostgresSchema); expect(namespace).not.toBeInstanceOf(PostgresUnboundSchema); if (!(namespace instanceof PostgresSchema)) { @@ -137,6 +143,8 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a // Top-level declarations lower to the unbound namespace — the // planner falls back to its `ctx.schemaName` (today `"public"`) // for DDL qualification. - expect(storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables['post']).toBeDefined(); + expect( + getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables['post'], + ).toBeDefined(); }); }); diff --git a/packages/3-extensions/sql-orm-client/src/collection-contract.ts b/packages/3-extensions/sql-orm-client/src/collection-contract.ts index 8676a6ce86..cdf427e37b 100644 --- a/packages/3-extensions/sql-orm-client/src/collection-contract.ts +++ b/packages/3-extensions/sql-orm-client/src/collection-contract.ts @@ -31,9 +31,8 @@ export interface PolymorphismInfo { } function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables[tableName] as - | StorageTable - | undefined; + return getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) + ?.tables[tableName] as StorageTable | undefined; } function modelsOf(contract: Contract): ModelsMap { diff --git a/packages/3-extensions/sql-orm-client/src/model-accessor.ts b/packages/3-extensions/sql-orm-client/src/model-accessor.ts index 6521216e71..177ee2ee4a 100644 --- a/packages/3-extensions/sql-orm-client/src/model-accessor.ts +++ b/packages/3-extensions/sql-orm-client/src/model-accessor.ts @@ -117,9 +117,10 @@ function resolveColumn( tableName: string, columnName: string, ): { readonly codecId: string; readonly nullable: boolean } | undefined { - const table = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables[tableName] as - | StorageTable - | undefined; + const table = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )?.tables[tableName] as StorageTable | undefined; const column = table?.columns?.[columnName]; if (!column) return undefined; return { codecId: column.codecId, nullable: column.nullable }; diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts index f7ea59a5a7..bfc93e74a8 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts @@ -15,9 +15,10 @@ export function deriveParamsFromAst(ast: AnyQueryAst): { } export function resolveTableColumns(contract: Contract, tableName: string): string[] { - const table = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables[tableName] as - | StorageTable - | undefined; + const table = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )?.tables[tableName] as StorageTable | undefined; if (!table) { throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`); } diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts index 345345d62d..b5cc2248c6 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts @@ -20,9 +20,8 @@ import { buildOrmQueryPlan, deriveParamsFromAst, resolveTableColumns } from './q import { combineWhereExprs } from './where-utils'; function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables[tableName] as - | StorageTable - | undefined; + return getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) + ?.tables[tableName] as StorageTable | undefined; } function buildReturningColumns( diff --git a/packages/3-extensions/sql-orm-client/src/where-binding.ts b/packages/3-extensions/sql-orm-client/src/where-binding.ts index e1d8b540ef..91af988cf3 100644 --- a/packages/3-extensions/sql-orm-client/src/where-binding.ts +++ b/packages/3-extensions/sql-orm-client/src/where-binding.ts @@ -131,9 +131,8 @@ function createParamRef( ): ParamRef { if ( !( - contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables[columnRef.table] as - | StorageTable - | undefined + getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) + ?.tables[columnRef.table] as StorageTable | undefined )?.columns[columnRef.column] ) { throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`); diff --git a/packages/3-extensions/sql-orm-client/test/helpers.ts b/packages/3-extensions/sql-orm-client/test/helpers.ts index 90cd481240..846a9a9294 100644 --- a/packages/3-extensions/sql-orm-client/test/helpers.ts +++ b/packages/3-extensions/sql-orm-client/test/helpers.ts @@ -154,7 +154,7 @@ export function buildMixedPolyContract(): TestContract { base: 'Task', }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.tasks = { + getStorageNamespace(raw.storage as Record, UNBOUND_NAMESPACE_ID).tables.tasks = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, @@ -167,7 +167,10 @@ export function buildMixedPolyContract(): TestContract { foreignKeys: [], }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.features = { + getStorageNamespace( + raw.storage as Record, + UNBOUND_NAMESPACE_ID, + ).tables.features = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, priority: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, @@ -215,17 +218,26 @@ export function buildStiPolyContract(): TestContract { base: 'User', }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.kind = { + getStorageNamespace( + raw.storage as Record, + UNBOUND_NAMESPACE_ID, + ).tables.users.columns.kind = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.role = { + getStorageNamespace( + raw.storage as Record, + UNBOUND_NAMESPACE_ID, + ).tables.users.columns.role = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.plan = { + getStorageNamespace( + raw.storage as Record, + UNBOUND_NAMESPACE_ID, + ).tables.users.columns.plan = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, diff --git a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts index c35b397d8d..fb76f88510 100644 --- a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts +++ b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts @@ -144,21 +144,21 @@ describe('mutation-executor', () => { it('buildPrimaryKeyFilterFromRow() resolves custom primary key columns', () => { const contract = getTestContract(); - const unboundNs = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!; + const unboundNs = getStorageNamespace( + contract.storage as Record, + UNBOUND_NAMESPACE_ID, + )!; const withCustomPk = { ...contract, storage: { ...contract.storage, - namespaces: { - ...contract.storage.namespaces, - [UNBOUND_NAMESPACE_ID]: { - ...unboundNs, - tables: { - ...unboundNs.tables, - users: { - ...unboundNs.tables.users, - primaryKey: { columns: ['pk_id'] }, - }, + [UNBOUND_NAMESPACE_ID]: { + ...unboundNs, + tables: { + ...unboundNs.tables, + users: { + ...unboundNs.tables.users, + primaryKey: { columns: ['pk_id'] }, }, }, }, diff --git a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts index 3a360a3d18..52f0e7e019 100644 --- a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts +++ b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts @@ -10,7 +10,6 @@ type StorageLike = { export function unboundTables( storage: StorageLike | SqlStorage, ): Readonly> { - return (storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}) as Readonly< - Record - >; + return (getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables ?? + {}) as Readonly>; } From 4c619897693e0aa045ad29ee825799ebf53f9c6b Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:18:37 +0200 Subject: [PATCH 17/60] refactor(framework,sql): make flat-storage walk helpers generic `storageNamespaceEntries`/`storageNamespaceValues`/`getStorageNamespace` take `object` and are generic over the namespace concretion (callers select `SqlNamespace` etc.), so class instances and plain records flow in without per-call-site casts. Drops the now-redundant `as Record` arg casts across the SQL substrate, routes build-contract through `buildSqlStorageInput`, and fixes the typecheck fallout in SQL authoring/runtime/family and their tests. Signed-off-by: Will Madden --- .../0-foundation/utils/src/assertions.ts | 2 +- .../src/ir/storage-plane-keys.ts | 39 +++++++++---- .../aggregate/project-schema-to-space.test.ts | 2 +- .../contract/src/index-type-validation.ts | 4 +- .../2-sql/1-core/contract/src/validators.ts | 32 +++++------ .../test/interpreter.namespaces.test.ts | 16 ++---- .../contract-psl/test/interpreter.test.ts | 35 +++--------- .../test/interpreter.types.test.ts | 2 +- .../test/psl-ts-namespace-parity.test.ts | 55 +++++-------------- .../contract-psl/test/ts-psl-parity.test.ts | 16 ++---- .../contract-psl/test/unbound-tables.ts | 18 ++---- .../contract-ts/src/build-contract.ts | 18 ++++-- .../contract-ts/src/contract-definition.ts | 2 +- ...ntract-builder.contract-definition.test.ts | 14 ++--- .../test/contract-builder.namespaces.test.ts | 53 ++++++++---------- ...ntract-builder.per-model-namespace.test.ts | 11 ++-- .../contract-ts/test/unbound-tables.ts | 16 ++---- packages/2-sql/3-tooling/emitter/src/index.ts | 30 ++++------ .../test/emitter-hook.structure.test.ts | 3 +- .../src/codec-ref-for-column.ts | 4 +- .../4-lanes/sql-builder/src/runtime/sql.ts | 2 +- .../2-sql/5-runtime/src/codecs/validation.ts | 10 +--- packages/2-sql/5-runtime/src/sql-context.ts | 30 ++++------ .../5-runtime/test/codec-integrity.test.ts | 6 +- .../test/contract-codec-registry.test.ts | 6 +- .../test/mutation-default-generators.test.ts | 6 +- packages/2-sql/5-runtime/test/utils.ts | 1 - .../9-family/src/core/control-instance.ts | 9 +-- .../core/ir/sql-contract-serializer-base.ts | 4 +- .../core/migrations/contract-to-schema-ir.ts | 24 +++----- .../core/schema-verify/verify-sql-schema.ts | 30 +++------- 31 files changed, 201 insertions(+), 299 deletions(-) diff --git a/packages/1-framework/0-foundation/utils/src/assertions.ts b/packages/1-framework/0-foundation/utils/src/assertions.ts index 310195398a..417ca4533c 100644 --- a/packages/1-framework/0-foundation/utils/src/assertions.ts +++ b/packages/1-framework/0-foundation/utils/src/assertions.ts @@ -6,7 +6,7 @@ * * @example * ```typescript - * const ns = getStorageNamespace(storage as Record, namespaceId); + * const ns = getStorageNamespace(storage, namespaceId); * const table = ns?.tables[tableName]; * assertDefined(table, `Table "${tableName}" not found`); * // table is now narrowed to non-nullable diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts index ccb3623dbd..9bebaf50f0 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts @@ -26,31 +26,48 @@ function isNamespaceEntry(value: unknown): value is Namespace { /** * Enumerate namespace-id → namespace pairs on a storage-shaped value. * Skips {@link STORAGE_PLANE_RESERVED_KEYS} and non-namespace entries. + * + * Accepts `object` so both the family storage class instances + * (`SqlStorage` / `MongoStorage`, whose namespace ids are own-enumerable + * keys) and plain validated JSON records flow in without a cast. */ -export function* storageNamespaceEntries( - storage: Record, -): Generator { +export function* storageNamespaceEntries( + storage: object, +): Generator { for (const [key, value] of Object.entries(storage)) { if (isStoragePlaneReservedKey(key)) continue; if (isNamespaceEntry(value)) { - yield [key, value]; + yield [ + key, + blindCast( + value, + ), + ]; } } } -export function storageNamespaceValues(storage: Record): Namespace[] { - return [...storageNamespaceEntries(storage)].map(([, ns]) => ns); +export function storageNamespaceValues(storage: object): T[] { + return [...storageNamespaceEntries(storage)].map(([, ns]) => ns); } -export function getStorageNamespace( - storage: Record, +/** + * Look up one namespace entry by id, skipping reserved keys. The type + * parameter lets callers select the family namespace concretion + * (`SqlNamespace` / `MongoNamespace`) they know the storage carries; + * it defaults to the framework `Namespace`. + */ +export function getStorageNamespace( + storage: object, namespaceId: string, -): Namespace | undefined { +): T | undefined { if (isStoragePlaneReservedKey(namespaceId)) { return undefined; } - const value = storage[namespaceId]; - return isNamespaceEntry(value) ? value : undefined; + const value = (storage as Record)[namespaceId]; + return isNamespaceEntry(value) + ? blindCast(value) + : undefined; } export type FlatStorageInput = { diff --git a/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts b/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts index 17bee012f4..2dcd9f2eb0 100644 --- a/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts +++ b/packages/1-framework/3-tooling/migration/test/aggregate/project-schema-to-space.test.ts @@ -31,7 +31,7 @@ type MongoStorageLike = StorageBase & describe('projectSchemaToSpace', () => { /** * Build a synthetic member with only the fields `projectSchemaToSpace` - * inspects (`spaceId`, `getStorageNamespace(contract.storage as Record, …).tables`). The rest is filled + * inspects (`spaceId`, `getStorageNamespace(contract.storage, …).tables`). The rest is filled * with empty / sentinel values to satisfy the type without committing * to a particular family. */ diff --git a/packages/2-sql/1-core/contract/src/index-type-validation.ts b/packages/2-sql/1-core/contract/src/index-type-validation.ts index d6e26e8ee1..b2e08b0892 100644 --- a/packages/2-sql/1-core/contract/src/index-type-validation.ts +++ b/packages/2-sql/1-core/contract/src/index-type-validation.ts @@ -10,9 +10,7 @@ export function validateIndexTypes( contract: Contract, indexTypeRegistry: IndexTypeRegistry, ): void { - for (const [namespaceId, ns] of [ - ...storageNamespaceEntries(contract.storage as unknown as Record), - ]) { + for (const [namespaceId, ns] of [...storageNamespaceEntries(contract.storage)]) { for (const [tableName, rawTable] of Object.entries((ns as SqlNamespace).tables)) { const table = rawTable as StorageTable; for (const index of table.indexes) { diff --git a/packages/2-sql/1-core/contract/src/validators.ts b/packages/2-sql/1-core/contract/src/validators.ts index cba1033917..2f2bcaa480 100644 --- a/packages/2-sql/1-core/contract/src/validators.ts +++ b/packages/2-sql/1-core/contract/src/validators.ts @@ -115,7 +115,7 @@ const StorageTypeInstanceSchema = type }); /** - * Postgres native enum entry under `getStorageNamespace(storage as Record, namespaceId).enum[name]`. + * Postgres native enum entry under `getStorageNamespace(storage, namespaceId).enum[name]`. * Document-scoped `storage.types` carries codec aliases only * (`DocumentScopedStorageTypeSchema`). */ @@ -291,21 +291,20 @@ export function createSqlStorageSchema( const StorageSchema = createSqlStorageSchema(); -function eachStorageTable(storage: unknown) { - return [...storageNamespaceEntries(storage as Record)].flatMap( - ([namespaceId, ns]) => - Object.entries( - (ns as Namespace & { readonly tables?: Readonly> }).tables ?? {}, - ).map(([tableName, table]) => ({ - namespaceId, - tableName, - table, - })), +function eachStorageTable(storage: object) { + return [...storageNamespaceEntries(storage)].flatMap(([namespaceId, ns]) => + Object.entries( + (ns as Namespace & { readonly tables?: Readonly> }).tables ?? {}, + ).map(([tableName, table]) => ({ + namespaceId, + tableName, + table, + })), ); } -function findStorageTableByTableName(storage: unknown, tableName: string): unknown { - for (const [, ns] of storageNamespaceEntries(storage as Record)) { +function findStorageTableByTableName(storage: object, tableName: string): unknown { + for (const [, ns] of storageNamespaceEntries(storage)) { const t = (ns as Namespace & { readonly tables?: Readonly> }).tables?.[ tableName ]; @@ -807,10 +806,9 @@ export function validateSqlStorageConsistency(contract: Contract): v } } - const targetNamespace = getStorageNamespace( - contract.storage as unknown as Record, - fk.target.namespaceId, - ) as SqlNamespace | undefined; + const targetNamespace = getStorageNamespace(contract.storage, fk.target.namespaceId) as + | SqlNamespace + | undefined; const referencedRaw = targetNamespace?.tables[fk.target.tableName]; if (referencedRaw === undefined) { throw new ContractValidationError( diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts index 15bfe579cc..ce40a0b220 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.ts @@ -42,11 +42,9 @@ namespace auth { if (!result.ok) return; const storage = result.value.storage as SqlStorage; - const postTable = ( - getStorageNamespace(storage as unknown as Record, 'public') as - | SqlNamespace - | undefined - )?.tables['post']; + const postTable = (getStorageNamespace(storage, 'public') as SqlNamespace | undefined)?.tables[ + 'post' + ]; expect(postTable).toBeDefined(); const fks: readonly ForeignKey[] = postTable?.foreignKeys ?? []; @@ -82,11 +80,9 @@ namespace auth { if (!result.ok) return; const storage = result.value.storage as SqlStorage; - const postTable = ( - getStorageNamespace(storage as unknown as Record, 'public') as - | SqlNamespace - | undefined - )?.tables['post']; + const postTable = (getStorageNamespace(storage, 'public') as SqlNamespace | undefined)?.tables[ + 'post' + ]; const fks: readonly ForeignKey[] = postTable?.foreignKeys ?? []; expect(fks.length).toBe(1); expect(fks[0]).toMatchObject({ diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts index c80476854f..e1da5a0b6e 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts @@ -811,12 +811,9 @@ model OrderItem { if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); expect( - ( - getStorageNamespace( - storage as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as SqlNamespace | undefined - )?.tables['doc'], + (getStorageNamespace(storage, UNBOUND_NAMESPACE_ID) as SqlNamespace | undefined)?.tables[ + 'doc' + ], ).toMatchObject({ indexes: [{ columns: ['body'] }], }); @@ -885,11 +882,9 @@ model OrderItem { expect(result.ok).toBe(true); if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); - const user = ( - getStorageNamespace(storage as unknown as Record, 'auth') as - | SqlNamespace - | undefined - )?.tables['user']; + const user = (getStorageNamespace(storage, 'auth') as SqlNamespace | undefined)?.tables[ + 'user' + ]; expect(user).toBeDefined(); expect(unboundTables(storage)['user']).toBeUndefined(); const json = JSON.parse(JSON.stringify(user)) as Record; @@ -922,11 +917,7 @@ namespace tenant_a { if (!result.ok) return; const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); const enums = - ( - getStorageNamespace(storage as unknown as Record, 'tenant_a') as - | SqlNamespace - | undefined - )?.enum ?? {}; + (getStorageNamespace(storage, 'tenant_a') as SqlNamespace | undefined)?.enum ?? {}; expect(enums).toHaveProperty('Status'); expect(enums).toHaveProperty('Tier'); }); @@ -960,18 +951,10 @@ namespace logs { const storage = sqlStorageFromSuccessfulSqlInterpretation(result.value); expect(unboundTables(storage)['post']).toBeDefined(); expect( - ( - getStorageNamespace(storage as unknown as Record, 'auth') as - | SqlNamespace - | undefined - )?.tables['user'], + (getStorageNamespace(storage, 'auth') as SqlNamespace | undefined)?.tables['user'], ).toBeDefined(); expect( - ( - getStorageNamespace(storage as unknown as Record, 'logs') as - | SqlNamespace - | undefined - )?.tables['auditLog'], + (getStorageNamespace(storage, 'logs') as SqlNamespace | undefined)?.tables['auditLog'], ).toBeDefined(); expect(unboundTables(storage)['user']).toBeUndefined(); }); diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts index 13fbee542a..45767abc08 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts @@ -343,7 +343,7 @@ model Account { expect(storageNs['auth']).toBeUndefined(); }); - it('lowers a namespace-scoped enum into getStorageNamespace(storage as unknown as Record, nsId).enum', () => { + it('lowers a namespace-scoped enum into getStorageNamespace(storage, nsId).enum', () => { const document = parsePslDocument({ schema: `namespace auth { enum user_type { diff --git a/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts b/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts index 2adb513903..ccd56fb209 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.ts @@ -79,58 +79,31 @@ namespace public { const tsStorage = tsContract.storage as unknown as SqlStorage; // Same namespace keys - expect( - [...storageNamespaceEntries(pslStorage as unknown as Record)] - .map(([id]) => id) - .sort(), - ).toEqual( - [...storageNamespaceEntries(tsStorage as unknown as Record)] - .map(([id]) => id) - .sort(), + expect([...storageNamespaceEntries(pslStorage)].map(([id]) => id).sort()).toEqual( + [...storageNamespaceEntries(tsStorage)].map(([id]) => id).sort(), ); // Same per-namespace table keys - for (const nsId of [ - ...storageNamespaceEntries(pslStorage as unknown as Record), - ].map(([id]) => id)) { + for (const nsId of [...storageNamespaceEntries(pslStorage)].map(([id]) => id)) { const pslTables = - ( - getStorageNamespace(pslStorage as unknown as Record, nsId) as - | SqlNamespace - | undefined - )?.tables ?? {}; + (getStorageNamespace(pslStorage, nsId) as SqlNamespace | undefined)?.tables ?? {}; const tsTables = - ( - getStorageNamespace(tsStorage as unknown as Record, nsId) as - | SqlNamespace - | undefined - )?.tables ?? {}; + (getStorageNamespace(tsStorage, nsId) as SqlNamespace | undefined)?.tables ?? {}; expect(Object.keys(pslTables).sort()).toEqual(Object.keys(tsTables).sort()); } // Same per-table column shapes - const pslAuthUser = ( - getStorageNamespace(pslStorage as unknown as Record, 'auth') as - | SqlNamespace - | undefined - )?.tables['user']; - const tsAuthUser = ( - getStorageNamespace(tsStorage as unknown as Record, 'auth') as - | SqlNamespace - | undefined - )?.tables['user']; + const pslAuthUser = (getStorageNamespace(pslStorage, 'auth') as SqlNamespace | undefined) + ?.tables['user']; + const tsAuthUser = (getStorageNamespace(tsStorage, 'auth') as SqlNamespace | undefined)?.tables[ + 'user' + ]; expect(pslAuthUser?.columns).toEqual(tsAuthUser?.columns); - const pslPublicPost = ( - getStorageNamespace(pslStorage as unknown as Record, 'public') as - | SqlNamespace - | undefined - )?.tables['post']; - const tsPublicPost = ( - getStorageNamespace(tsStorage as unknown as Record, 'public') as - | SqlNamespace - | undefined - )?.tables['post']; + const pslPublicPost = (getStorageNamespace(pslStorage, 'public') as SqlNamespace | undefined) + ?.tables['post']; + const tsPublicPost = (getStorageNamespace(tsStorage, 'public') as SqlNamespace | undefined) + ?.tables['post']; expect(pslPublicPost?.columns).toEqual(tsPublicPost?.columns); // Same FK source/target diff --git a/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts b/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts index d45cd2eb0c..d207fb1fb1 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.ts @@ -537,19 +537,11 @@ model Post { const pslStorage = pslContract.value.storage as unknown as SqlStorage; const tsStorage = tsContract.storage as unknown as SqlStorage; const pslFks: readonly ForeignKey[] = - ( - getStorageNamespace( - pslStorage as unknown as unknown as Record, - '__unbound__', - ) as SqlNamespace | undefined - )?.tables['post']?.foreignKeys ?? []; + getStorageNamespace(pslStorage, '__unbound__')?.tables['post']?.foreignKeys ?? + []; const tsFks: readonly ForeignKey[] = - ( - getStorageNamespace( - tsStorage as unknown as unknown as Record, - '__unbound__', - ) as SqlNamespace | undefined - )?.tables['post']?.foreignKeys ?? []; + getStorageNamespace(tsStorage, '__unbound__')?.tables['post']?.foreignKeys ?? + []; expect(tsFks.length).toBe(1); expect(pslFks.length).toBe(1); diff --git a/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts b/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts index 03cebbc8a8..fc9c8041e4 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/unbound-tables.ts @@ -1,17 +1,7 @@ import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, StorageTable } from '@prisma-next/sql-contract/types'; -type StorageLike = Readonly< - Record> }> ->; - -export function unboundTables( - storage: StorageLike | SqlStorage, -): Readonly> { - return (( - getStorageNamespace( - storage as unknown as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as SqlNamespace | undefined - )?.tables ?? {}) as Readonly>; +export function unboundTables(storage: object): Readonly> { + return (getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? + {}) as Readonly>; } diff --git a/packages/2-sql/2-authoring/contract-ts/src/build-contract.ts b/packages/2-sql/2-authoring/contract-ts/src/build-contract.ts index e0742d4445..a592d09a4b 100644 --- a/packages/2-sql/2-authoring/contract-ts/src/build-contract.ts +++ b/packages/2-sql/2-authoring/contract-ts/src/build-contract.ts @@ -32,11 +32,12 @@ import { import { applyFkDefaults, buildSqlNamespace, + buildSqlStorageInput, isPostgresEnumStorageEntry, type PostgresEnumStorageEntry, type SqlNamespaceTablesInput, SqlStorage, - type SqlStorageInput, + type SqlStorageNamespacesInput, type StorageColumn, StorageTable, type StorageTableInput, @@ -548,7 +549,7 @@ export function buildSqlContractFromDefinition( } const { createNamespace } = definition; const namespaces = blindCast< - SqlStorageInput['namespaces'], + SqlStorageNamespacesInput['namespaces'], 'contract authoring always materialises the __unbound__ namespace coordinate' >( Object.fromEntries( @@ -567,9 +568,10 @@ export function buildSqlContractFromDefinition( Object.keys(documentTypes).length > 0 ? { types: documentTypes } : undefined; const domain = domainUnboundTypes !== undefined ? { [UNBOUND_NAMESPACE_ID]: domainUnboundTypes } : undefined; + const hasDocumentTypes = Object.keys(documentTypes).length > 0; const storageWithoutHash = { - ...(Object.keys(documentTypes).length > 0 ? { types: documentTypes } : {}), - namespaces, + ...(hasDocumentTypes ? { types: documentTypes } : {}), + ...namespaces, }; const storageHash: StorageHashBase = definition.storageHash ? coreHash(definition.storageHash) @@ -579,7 +581,13 @@ export function buildSqlContractFromDefinition( storage: storageWithoutHash as Record, ...sqlContractCanonicalizationHooks, }); - const storage = new SqlStorage({ ...storageWithoutHash, storageHash }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash, + ...(hasDocumentTypes ? { types: documentTypes } : {}), + namespaces, + }), + ); const executionSection = executionDefaults.length > 0 diff --git a/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts b/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts index 03bc75c7c3..d42813957c 100644 --- a/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts +++ b/packages/2-sql/2-authoring/contract-ts/src/contract-definition.ts @@ -124,7 +124,7 @@ export interface ContractDefinition { /** * Enum types declared inside a named `namespace { enum … }` block, * keyed first by namespace id then by type name. These are routed to - * `getStorageNamespace(storage as Record, nsId).enum` rather than the implicit fallback + * `getStorageNamespace(storage, nsId).enum` rather than the implicit fallback * namespace used for top-level `storageTypes` enums. */ readonly namespaceTypes?: Readonly< diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts index 29db807c0f..1c79b8fa23 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.ts @@ -1,6 +1,7 @@ import type { CodecLookup } from '@prisma-next/framework-components/codec'; import type { TargetPackRef } from '@prisma-next/framework-components/components'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { buildSqlContractFromDefinition } from '../src/contract-builder'; import { crossRef, documentScopedTypes } from './cross-ref-helpers'; @@ -361,7 +362,7 @@ describe('shared contract definition lowering', () => { ); }); - it('routes namespaceTypes enums to getStorageNamespace(storage as Record, nsId).enum', () => { + it('routes namespaceTypes enums to getStorageNamespace(storage, nsId).enum', () => { const contract = buildSqlContractFromDefinition({ target: postgresTargetPack, namespaceTypes: { @@ -393,14 +394,9 @@ describe('shared contract definition lowering', () => { ], }); - const nsStorage = contract.storage as unknown as { - namespaces: Record }>; - }; - expect(getStorageNamespace(nsStorage as Record, 'auth')?.enum).toMatchObject({ + expect(getStorageNamespace(contract.storage, 'auth')?.enum).toMatchObject({ user_type: { kind: 'postgres-enum', values: ['admin', 'user'] }, }); - expect( - getStorageNamespace(nsStorage as Record, 'public')?.enum, - ).toBeUndefined(); + expect(getStorageNamespace(contract.storage, 'public')?.enum).toBeUndefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts index da08935a0d..a825493f3a 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.namespaces.test.ts @@ -1,6 +1,10 @@ import type { TargetPackRef } from '@prisma-next/framework-components/components'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; +import { + getStorageNamespace, + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; +import { type SqlNamespace, SqlUnboundNamespace } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { buildSqlContractFromDefinition } from '../src/contract-builder'; @@ -37,13 +41,10 @@ describe('SqlStorage.namespaces population', () => { target: postgresTargetPack, models: [minimalModelArgs], }); - expect( - [...storageNamespaceEntries(contract.storage as Record)].map(([id]) => id), - ).toEqual([UNBOUND_NAMESPACE_ID]); - const slot = getStorageNamespace( - contract.storage as Record, + expect([...storageNamespaceEntries(contract.storage)].map(([id]) => id)).toEqual([ UNBOUND_NAMESPACE_ID, - )!; + ]); + const slot = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!; expect(slot).not.toBe(SqlUnboundNamespace.instance); expect(slot.id).toBe(UNBOUND_NAMESPACE_ID); expect(slot.tables['app_user']).toBeDefined(); @@ -55,21 +56,16 @@ describe('SqlStorage.namespaces population', () => { namespaces: ['public', 'auth'], models: [minimalModelArgs], }); - const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] - .map(([id]) => id) - .sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage)].map(([id]) => id).sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth', 'public']); expect( - Object.keys( - getStorageNamespace(contract.storage as Record, 'public')!.tables, - ), + Object.keys(getStorageNamespace(contract.storage, 'public')!.tables), ).toHaveLength(0); expect( - Object.keys(getStorageNamespace(contract.storage as Record, 'auth')!.tables), + Object.keys(getStorageNamespace(contract.storage, 'auth')!.tables), ).toHaveLength(0); expect( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! - .tables['app_user'], + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables['app_user'], ).toBeDefined(); }); @@ -81,16 +77,15 @@ describe('SqlStorage.namespaces population', () => { { ...minimalModelArgs, modelName: 'Post', tableName: 'blog_post' }, ], }); - const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] - .map(([id]) => id) - .sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage)].map(([id]) => id).sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth']); expect( - getStorageNamespace(contract.storage as Record, 'auth')!.tables['app_user'], + getStorageNamespace(contract.storage, 'auth')!.tables['app_user'], ).toBeDefined(); expect( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! - .tables['blog_post'], + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables[ + 'blog_post' + ], ).toBeDefined(); }); @@ -99,9 +94,9 @@ describe('SqlStorage.namespaces population', () => { target: postgresTargetPack, models: [], }); - expect( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID), - ).toBe(SqlUnboundNamespace.instance); + expect(getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)).toBe( + SqlUnboundNamespace.instance, + ); }); it('accepts declared namespaces without a createNamespace factory', () => { @@ -120,12 +115,10 @@ describe('SqlStorage.namespaces population', () => { namespaces: ['auth'], models: [{ ...minimalModelArgs, namespaceId: 'auth' }], }); - const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] - .map(([id]) => id) - .sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage)].map(([id]) => id).sort(); expect(namespaceIds).toEqual(['__unbound__', 'auth']); expect( - getStorageNamespace(contract.storage as Record, 'auth')!.tables['app_user'], + getStorageNamespace(contract.storage, 'auth')!.tables['app_user'], ).toBeDefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts index f11484d541..30eb26b4ca 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract-builder.per-model-namespace.test.ts @@ -1,12 +1,13 @@ import type { FamilyPackRef, TargetPackRef } from '@prisma-next/framework-components/components'; import { freezeNode, + getStorageNamespace, type IRNode, type Namespace, NamespaceBase, UNBOUND_NAMESPACE_ID, } from '@prisma-next/framework-components/ir'; -import type { SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { defineContract, field, model } from '../src/contract-builder'; import { columnDescriptor } from './helpers/column-descriptor'; @@ -84,7 +85,7 @@ describe('per-model `namespace` field (TS builder)', () => { // index signature). The runtime value is correct; cast to verify it. expect( ( - getStorageNamespace(contract.storage as Record, 'auth')?.tables as Record< + getStorageNamespace(contract.storage, 'auth')?.tables as Record< string, unknown > @@ -105,8 +106,10 @@ describe('per-model `namespace` field (TS builder)', () => { expect( ( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) - ?.tables as Record + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables as Record< + string, + unknown + > )['User'], ).toBeDefined(); }); diff --git a/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts b/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts index 52f0e7e019..fc9c8041e4 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/unbound-tables.ts @@ -1,15 +1,7 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, StorageTable } from '@prisma-next/sql-contract/types'; -type StorageLike = { - readonly namespaces: Readonly< - Record> }> - >; -}; - -export function unboundTables( - storage: StorageLike | SqlStorage, -): Readonly> { - return (getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables ?? +export function unboundTables(storage: object): Readonly> { + return (getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? {}) as Readonly>; } diff --git a/packages/2-sql/3-tooling/emitter/src/index.ts b/packages/2-sql/3-tooling/emitter/src/index.ts index 434677aed8..bad6dd5c5b 100644 --- a/packages/2-sql/3-tooling/emitter/src/index.ts +++ b/packages/2-sql/3-tooling/emitter/src/index.ts @@ -51,9 +51,7 @@ function findSqlTable( storage: SqlStorage, tableName: string, ): { readonly table: StorageTable; readonly namespaceId: string } | undefined { - for (const [namespaceId, ns] of storageNamespaceEntries( - storage as unknown as Record, - )) { + for (const [namespaceId, ns] of storageNamespaceEntries(storage)) { const table = (ns as SqlNamespace).tables[tableName] as StorageTable | undefined; if (table !== undefined) { return { table, namespaceId }; @@ -64,9 +62,7 @@ function findSqlTable( function assertUniqueSqlTableNames(storage: SqlStorage): void { const seen = new Map(); - for (const [namespaceId, ns] of storageNamespaceEntries( - storage as unknown as Record, - )) { + for (const [namespaceId, ns] of storageNamespaceEntries(storage)) { for (const tableName of Object.keys((ns as SqlNamespace).tables)) { const existing = seen.get(tableName); if (existing !== undefined && existing !== namespaceId) { @@ -90,7 +86,7 @@ export const sqlEmission = { const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/; - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { for (const [tableName, tableUnknown] of Object.entries((ns as SqlNamespace).tables)) { const table = tableUnknown as StorageTable; for (const [colName, colUnknown] of Object.entries(table.columns)) { @@ -125,7 +121,7 @@ export const sqlEmission = { const models = contract.models as Record>; const tableNames = new Set(); - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { for (const t of Object.keys((ns as SqlNamespace).tables)) { tableNames.add(t); } @@ -170,7 +166,7 @@ export const sqlEmission = { } } - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { for (const [tableName, tableUnknown] of Object.entries((ns as SqlNamespace).tables)) { const table = tableUnknown as StorageTable; const columnNames = new Set(Object.keys(table.columns)); @@ -230,10 +226,9 @@ export const sqlEmission = { } } - const referencedNs = getStorageNamespace( - storage as unknown as Record, - fk.target.namespaceId, - ) as SqlNamespace | undefined; + const referencedNs = getStorageNamespace(storage, fk.target.namespaceId) as + | SqlNamespace + | undefined; const referencedTable = referencedNs?.tables[fk.target.tableName] as | StorageTable | undefined; @@ -313,10 +308,7 @@ export const sqlEmission = { if (!column) return undefined; if (column.typeRef) { - const ns = getStorageNamespace( - storage as unknown as Record, - located.namespaceId, - ) as SqlNamespace | undefined; + const ns = getStorageNamespace(storage, located.namespaceId) as SqlNamespace | undefined; const nsEnums = ns !== undefined && 'enum' in ns ? ( @@ -563,9 +555,7 @@ function generateTablesMapType(tables: Readonly>): } function generateFlatStorageNamespaceTypeEntries(storage: SqlStorage): string { - const entries = [...storageNamespaceEntries(storage as unknown as Record)].sort( - ([a], [b]) => a.localeCompare(b), - ); + const entries = [...storageNamespaceEntries(storage)].sort(([a], [b]) => a.localeCompare(b)); if (entries.length === 0) { return ''; } diff --git a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts index b906c7aef6..51d80ad220 100644 --- a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -63,8 +63,7 @@ function installNamespacedTableDeletionRace(ir: Contract, tableName: string): vo }, }); - delete getStorageNamespace(originalStorage as Record, UNBOUND_NAMESPACE_ID) - .tables[tableName]; + delete getStorageNamespace(originalStorage, UNBOUND_NAMESPACE_ID).tables[tableName]; tableDeleted = true; (ir as { storage: unknown }).storage = proxiedStorage; } diff --git a/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts b/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts index 88587071b8..3ce3e52813 100644 --- a/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts +++ b/packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts @@ -26,7 +26,7 @@ export function codecRefForStorageColumn( columnName: string, ): CodecRef | undefined { let tableDef: StorageTable | undefined; - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { const candidate = (ns as SqlNamespace).tables[tableName] as StorageTable | undefined; if (candidate !== undefined) { tableDef = candidate; @@ -39,7 +39,7 @@ export function codecRefForStorageColumn( if (columnDef.typeRef !== undefined) { let instance: unknown = storage.types?.[columnDef.typeRef]; if (!instance) { - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { const nsEntry = nsEnums[columnDef.typeRef]; diff --git a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts index e68cefb76b..0e43478cbe 100644 --- a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts +++ b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts @@ -22,7 +22,7 @@ export interface SqlOptions & TableProxyContract> // at the DSL call site; landing the namespace-aware DSL is tracked // separately. function findTableAcrossNamespaces(storage: SqlStorage, name: string): StorageTable | undefined { - for (const ns of storageNamespaceValues(storage as Record)) { + for (const ns of storageNamespaceValues(storage)) { const tables = (ns as { tables?: Readonly> }).tables ?? {}; if (Object.hasOwn(tables, name)) { return tables[name]; diff --git a/packages/2-sql/5-runtime/src/codecs/validation.ts b/packages/2-sql/5-runtime/src/codecs/validation.ts index 403d6b1550..541c5da752 100644 --- a/packages/2-sql/5-runtime/src/codecs/validation.ts +++ b/packages/2-sql/5-runtime/src/codecs/validation.ts @@ -1,7 +1,7 @@ import type { Contract } from '@prisma-next/contract/types'; import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import { runtimeError } from '@prisma-next/framework-components/runtime'; -import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import type { CodecDescriptorRegistry } from '@prisma-next/sql-relational-core/query-lane-context'; // Framework `Namespace.tables` is widened to `Record` so @@ -12,9 +12,7 @@ import type { CodecDescriptorRegistry } from '@prisma-next/sql-relational-core/q export function extractCodecIds(contract: Contract): Set { const codecIds = new Set(); - for (const ns of storageNamespaceValues( - contract.storage as unknown as Record, - ) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(contract.storage)) { for (const table of Object.values(ns.tables)) { for (const column of Object.values(table.columns)) { const codecId = column.codecId; @@ -29,9 +27,7 @@ export function extractCodecIds(contract: Contract): Set { function extractCodecIdsFromColumns(contract: Contract): Map { const codecIds = new Map(); - for (const ns of storageNamespaceValues( - contract.storage as unknown as Record, - ) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(contract.storage)) { for (const [tableName, table] of Object.entries(ns.tables)) { for (const [columnName, column] of Object.entries(table.columns)) { const codecId = column.codecId; diff --git a/packages/2-sql/5-runtime/src/sql-context.ts b/packages/2-sql/5-runtime/src/sql-context.ts index 23a0eaefe2..0c4d756b7c 100644 --- a/packages/2-sql/5-runtime/src/sql-context.ts +++ b/packages/2-sql/5-runtime/src/sql-context.ts @@ -30,20 +30,12 @@ import { runtimeError } from '@prisma-next/framework-components/runtime'; import { canonicalizeJson } from '@prisma-next/framework-components/utils'; import { isPostgresEnumStorageEntry, + type SqlNamespace, type SqlStorage, - type StorageTable, type StorageTypeInstance, } from '@prisma-next/sql-contract/types'; import { blindCast } from '@prisma-next/utils/casts'; -// Framework `Namespace.tables` is widened to `Record` so -// emitted `contract.d.ts` table literals satisfy it structurally. At -// runtime, every `SqlStorage` namespace holds `StorageTable` instances — -// the constructor enforces it. Narrowing per-iteration with a single-step -// cast (not `as unknown as`) keeps Pattern C walks readable without -// weakening the substrate type. -type SqlNamespaceTables = Readonly>; - function documentScopedCodecTypes( contract: Contract, ): Record | undefined { @@ -351,8 +343,8 @@ function collectTypeRefSites( storage: SqlStorage, ): Map> { const sites = new Map>(); - for (const ns of storageNamespaceValues(storage as unknown as Record)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues(storage)) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (typeof column.typeRef !== 'string') continue; const list = sites.get(column.typeRef); @@ -411,8 +403,8 @@ function validateColumnTypeParams( storage: SqlStorage, codecDescriptors: Map, ): void { - for (const ns of storageNamespaceValues(storage as unknown as Record)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues(storage)) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (column.typeParams) { const descriptor = codecDescriptors.get(column.codecId); @@ -440,8 +432,8 @@ function assertColumnCodecIntegrity( storage: SqlStorage, codecDescriptors: CodecDescriptorRegistry, ): void { - for (const ns of storageNamespaceValues(storage as unknown as Record)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues(storage)) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const columnName of Object.keys(table.columns)) { const ref = codecDescriptors.codecRefForColumn(tableName, columnName); if (!ref) continue; @@ -555,8 +547,8 @@ function buildContractCodecRegistry( } } - for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues(contract.storage)) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const [columnName, column] of Object.entries(table.columns)) { if (column.typeRef !== undefined) continue; const ref = codecDescriptors.codecRefForColumn(tableName, columnName); @@ -587,8 +579,8 @@ function buildContractCodecRegistry( }; }); - for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { - for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) { + for (const ns of storageNamespaceValues(contract.storage)) { + for (const [tableName, table] of Object.entries(ns.tables)) { for (const columnName of Object.keys(table.columns)) { const ref = codecDescriptors.codecRefForColumn(tableName, columnName); if (!ref) continue; diff --git a/packages/2-sql/5-runtime/test/codec-integrity.test.ts b/packages/2-sql/5-runtime/test/codec-integrity.test.ts index 682e99c11e..37e54f1048 100644 --- a/packages/2-sql/5-runtime/test/codec-integrity.test.ts +++ b/packages/2-sql/5-runtime/test/codec-integrity.test.ts @@ -3,7 +3,11 @@ import { coreHash, profileHash } from '@prisma-next/contract/types'; import type { CodecDescriptor } from '@prisma-next/framework-components/codec'; import { voidParamsSchema } from '@prisma-next/framework-components/codec'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { Codec, SqlCodecInstanceContext } from '@prisma-next/sql-relational-core/ast'; import { describe, expect, it } from 'vitest'; import type { SqlRuntimeExtensionDescriptor } from '../src/sql-context'; diff --git a/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts b/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts index ca83a512d2..e3bccbcaad 100644 --- a/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts +++ b/packages/2-sql/5-runtime/test/contract-codec-registry.test.ts @@ -5,7 +5,11 @@ import type { CodecInstanceContext, } from '@prisma-next/framework-components/codec'; import { voidParamsSchema } from '@prisma-next/framework-components/codec'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { Codec } from '@prisma-next/sql-relational-core/ast'; import { ifDefined } from '@prisma-next/utils/defined'; import { describe, expect, it } from 'vitest'; diff --git a/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts b/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts index b13c661e11..66927f8a33 100644 --- a/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts +++ b/packages/2-sql/5-runtime/test/mutation-default-generators.test.ts @@ -1,5 +1,9 @@ import { type Contract, coreHash, executionHash, profileHash } from '@prisma-next/contract/types'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { createExecutionContext, diff --git a/packages/2-sql/5-runtime/test/utils.ts b/packages/2-sql/5-runtime/test/utils.ts index e129f8357a..5708c187a2 100644 --- a/packages/2-sql/5-runtime/test/utils.ts +++ b/packages/2-sql/5-runtime/test/utils.ts @@ -19,7 +19,6 @@ import { buildSqlNamespace, buildSqlStorageInput, SqlStorage, - type SqlStorageInput, type SqlStorageNamespacesInput, SqlUnboundNamespace, type StorageTableInput, diff --git a/packages/2-sql/9-family/src/core/control-instance.ts b/packages/2-sql/9-family/src/core/control-instance.ts index 0ef66a2d83..86eae1345f 100644 --- a/packages/2-sql/9-family/src/core/control-instance.ts +++ b/packages/2-sql/9-family/src/core/control-instance.ts @@ -67,11 +67,12 @@ function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] { 'storage' in contract && typeof contract.storage === 'object' && contract.storage !== null && - storageNamespaceValues(contract.storage as unknown as Record).length > 0 + storageNamespaceValues(contract.storage).length > 0 ) { - const namespaces = Object.fromEntries( - storageNamespaceEntries(contract.storage as unknown as Record), - ) as Record> }>; + const namespaces = Object.fromEntries(storageNamespaceEntries(contract.storage)) as Record< + string, + { readonly tables?: Readonly> } + >; for (const ns of Object.values(namespaces)) { const tbls = ns.tables; if (typeof tbls !== 'object' || tbls === null) continue; diff --git a/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts b/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts index e251de031f..7fecf7cfab 100644 --- a/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts +++ b/packages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.ts @@ -117,9 +117,7 @@ export abstract class SqlContractSerializerBase), - ); + const rawNamespaceEntries = Object.fromEntries(storageNamespaceEntries(validated.storage)); const hydratedNamespaces = this.hydrateSqlNamespaceMap(rawNamespaceEntries); const unbound = hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance; diff --git a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts index 8e020bb7c0..e4aac2281a 100644 --- a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts +++ b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts @@ -251,18 +251,14 @@ export function detectDestructiveChanges( const namespaceIds = [ ...new Set([ - ...[...storageNamespaceEntries(from as unknown as Record)].map(([id]) => id), - ...[...storageNamespaceEntries(to as unknown as Record)].map(([id]) => id), + ...[...storageNamespaceEntries(from)].map(([id]) => id), + ...[...storageNamespaceEntries(to)].map(([id]) => id), ]), ].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const fromNs = getStorageNamespace(from as unknown as Record, namespaceId) as - | SqlNamespace - | undefined; - const toNs = getStorageNamespace(to as unknown as Record, namespaceId) as - | SqlNamespace - | undefined; + const fromNs = getStorageNamespace(from, namespaceId) as SqlNamespace | undefined; + const toNs = getStorageNamespace(to, namespaceId) as SqlNamespace | undefined; const fromTables = fromNs?.tables; if (!fromTables) continue; @@ -339,9 +335,7 @@ export function contractToSchemaIR( const allTypes: Record = { ...((storage.types ?? {}) as ResolvedStorageTypes), }; - for (const ns of storageNamespaceValues( - storage as unknown as Record, - ) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(storage) as SqlNamespace[]) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -351,9 +345,7 @@ export function contractToSchemaIR( } const storageTypes = allTypes as ResolvedStorageTypes; const tables: Record = {}; - for (const ns of storageNamespaceValues( - storage as unknown as Record, - ) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(storage) as SqlNamespace[]) { for (const [tableName, tableDefRaw] of Object.entries(ns.tables)) { if (!(tableDefRaw instanceof StorageTable)) { throw new Error( @@ -431,9 +423,7 @@ function deriveAnnotations( // Namespace-scoped enums: schema-qualified compound key matching the target's // `readExistingEnumValues` read side, so two namespaces sharing an enum name // (or native type) resolve to distinct live-database types. - for (const [namespaceId, ns] of [ - ...storageNamespaceEntries(storage as unknown as Record), - ]) { + for (const [namespaceId, ns] of [...storageNamespaceEntries(storage)]) { const nsEnums = (ns as { enum?: Record }).enum; if (!nsEnums) continue; for (const entry of Object.values(nsEnums)) { diff --git a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts index 41ec504559..88f849c7d2 100644 --- a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts +++ b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts @@ -145,9 +145,7 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase PostgresEnumStorageEntry | StorageTypeInstance >), }; - for (const ns of storageNamespaceValues( - contract.storage as unknown as Record, - ) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(contract.storage) as SqlNamespace[]) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -229,12 +227,8 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase } // Namespace-scoped enums, verified per `(namespaceId, typeName)`. - for (const nsId of [ - ...storageNamespaceEntries(contract.storage as unknown as Record), - ].map(([id]) => id)) { - const ns = getStorageNamespace(contract.storage as unknown as Record, nsId) as - | SqlNamespace - | undefined; + for (const nsId of [...storageNamespaceEntries(contract.storage)].map(([id]) => id)) { + const ns = getStorageNamespace(contract.storage, nsId) as SqlNamespace | undefined; if (!ns) continue; const nsEnums = ns.enum; if (!nsEnums) continue; @@ -412,17 +406,12 @@ function verifySchemaTables(options: { const issues: SchemaIssue[] = []; const rootChildren: SchemaVerificationNode[] = []; const schemaTables = schema.tables; - const namespaceIds = [ - ...storageNamespaceEntries(contract.storage as unknown as Record), - ] + const namespaceIds = [...storageNamespaceEntries(contract.storage)] .map(([id]) => id) .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const ns = getStorageNamespace( - contract.storage as unknown as Record, - namespaceId, - ) as SqlNamespace | undefined; + const ns = getStorageNamespace(contract.storage, namespaceId) as SqlNamespace | undefined; if (!ns) continue; for (const [tableName, contractTableRaw] of Object.entries(ns.tables)) { if (!(contractTableRaw instanceof StorageTable)) { @@ -477,12 +466,9 @@ function verifySchemaTables(options: { for (const tableName of Object.keys(schemaTables)) { const claimed = namespaceIds.some( (namespaceId) => - ( - getStorageNamespace( - contract.storage as unknown as Record, - namespaceId, - ) as SqlNamespace | undefined - )?.tables[tableName] !== undefined, + (getStorageNamespace(contract.storage, namespaceId) as SqlNamespace | undefined)?.tables[ + tableName + ] !== undefined, ); if (!claimed) { // `namespaceId` is intentionally absent: an extra table exists in the From 980fdc9515063ad7c56f898ae1f9b65fd76f032e Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:18:46 +0200 Subject: [PATCH 18/60] refactor(mongo,targets,extensions): drop redundant flat-storage casts Removes the now-unnecessary `as Record` arg casts at the generic walk-helper call sites across the Mongo family/target, the SQL extensions, sqlite, and the postgres adapter, and fixes the related typecheck fallout. Signed-off-by: Will Madden --- .../mongo-contract/src/ir/mongo-collection.ts | 2 +- .../mongo-contract/src/validate-storage.ts | 2 +- .../mongo-contract/test/mongo-storage.test.ts | 15 ++----- .../test/interpreter.polymorphism.test.ts | 8 +--- .../contract-psl/test/interpreter.test.ts | 8 +--- .../test/contract-builder.dsl.test.ts | 40 +++++-------------- .../contract-builder.polymorphism.test.ts | 25 +++--------- .../3-tooling/emitter/src/index.ts | 16 +++----- .../test/emitter-hook.structure.test.ts | 2 +- .../9-family/src/core/contract-to-schema.ts | 2 +- .../core/ir/mongo-contract-serializer-base.ts | 8 ++-- .../src/core/ir/mongo-schema-verifier-base.ts | 6 +-- .../cipherstash/test/descriptor.test.ts | 3 +- .../test/psl-interpretation-numeric.test.ts | 2 +- .../psl-interpretation-other-types.test.ts | 2 +- .../test/psl-interpretation.test.ts | 2 +- .../psl-namespace-qualifier-routing.test.ts | 18 +++------ .../sql-orm-client/src/collection-contract.ts | 5 ++- .../sql-orm-client/src/model-accessor.ts | 7 ++-- .../sql-orm-client/src/query-plan-meta.ts | 7 ++-- .../src/query-plan-mutations.ts | 5 ++- .../sql-orm-client/src/where-binding.ts | 5 ++- .../sql-orm-client/test/helpers.ts | 22 +++------- .../test/mutation-executor.test.ts | 5 +-- .../sql-orm-client/test/unbound-tables.ts | 5 ++- .../core/mongo-target-contract-serializer.ts | 36 ++++++++--------- .../mongo-target-contract-serializer.test.ts | 30 +++++--------- .../test/mongo-target-schema-verifier.test.ts | 8 ++-- .../src/core/migrations/planner-strategies.ts | 4 +- .../test/sqlite-contract-serializer.test.ts | 18 +++------ .../postgres/src/core/sql-renderer.ts | 2 +- 31 files changed, 110 insertions(+), 210 deletions(-) diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts index ffcb9bdab8..a0abae58b7 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-collection.ts @@ -9,7 +9,7 @@ import { MongoValidator, type MongoValidatorInput } from './mongo-validator'; /** * Hydration / construction input shape for {@link MongoCollection}. * Mirrors the on-disk storage JSON envelope exactly (the value held at - * `getStorageNamespace(contract.storage as Record, ).collections[]`) so the family-base + * `getStorageNamespace(contract.storage, ).collections[]`) so the family-base * serializer's hydration walker can hand an arktype-validated literal * straight to `new`. Nested IR-class fields may be supplied as either * plain data literals (typical for JSON-derived input) or diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts index fe3bced2b7..5dd8a3c9d1 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts @@ -10,7 +10,7 @@ function storageDeclaresCollection( storage: MongoContract['storage'], collectionName: string, ): boolean { - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { if (Object.hasOwn((ns as MongoNamespace).collections, collectionName)) { return true; } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts index 90bb554fb7..bd7681c42d 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.ts @@ -57,10 +57,7 @@ describe('MongoStorage', () => { }), ); expect( - (getStorageNamespace( - storage as unknown as Record, - 'default', - ) as MongoNamespace)!.collections['events'], + (getStorageNamespace(storage, 'default') as MongoNamespace)!.collections['events'], ).toBeInstanceOf(MongoCollection); }); @@ -72,10 +69,8 @@ describe('MongoStorage', () => { namespaces: { default: defaultNamespace, auth }, }), ); - expect(getStorageNamespace(storage as unknown as Record, 'default')).toBe( - defaultNamespace, - ); - expect(getStorageNamespace(storage as unknown as Record, 'auth')).toBe(auth); + expect(getStorageNamespace(storage, 'default')).toBe(defaultNamespace); + expect(getStorageNamespace(storage, 'auth')).toBe(auth); }); it('is frozen after construction', () => { @@ -95,8 +90,6 @@ describe('MongoStorage', () => { namespaces: { [UNBOUND_NAMESPACE_ID]: MongoUnboundNamespace.instance }, }), ); - expect( - getStorageNamespace(storage as unknown as Record, UNBOUND_NAMESPACE_ID), - ).toBe(MongoUnboundNamespace.instance); + expect(getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)).toBe(MongoUnboundNamespace.instance); }); }); diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts index 538f653636..31a1ca791f 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts @@ -42,12 +42,8 @@ const mongoCodecLookup: CodecLookup = { }; function mongoCollectionsOf(ir: { readonly storage: unknown }): Record { - return ( - getStorageNamespace( - ir.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections as Record; + return (getStorageNamespace(ir.storage as object, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections as Record; } function interpret(schema: string) { diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts index 62037bfdd6..5394b78c1f 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts @@ -59,12 +59,8 @@ const mongoCodecLookup: CodecLookup = { function mongoCollectionsFromIr(ir: { readonly storage: unknown; }): Record> { - return ( - getStorageNamespace( - ir.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections as unknown as Record>; + return (getStorageNamespace(ir.storage as object, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections as unknown as Record>; } interface MongoModel { diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts index 57848e0300..b3f92f1b6f 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts @@ -60,12 +60,8 @@ describe('mongo contract builder', () => { posts: crossRef('Post'), }); expect( - ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections, + (getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections, ).toEqual({ users: { kind: 'mongo-collection' }, posts: { kind: 'mongo-collection' }, @@ -151,12 +147,8 @@ describe('mongo contract builder', () => { tasks: crossRef('Task'), }); expect( - ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections, + (getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections, ).toEqual({ tasks: { kind: 'mongo-collection' }, }); @@ -205,12 +197,8 @@ describe('mongo contract builder', () => { }); expect( - ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections, + (getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections, ).toEqual({ users: { kind: 'mongo-collection', @@ -281,12 +269,8 @@ describe('mongo contract builder', () => { }); expect( - ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections, + (getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections, ).toEqual({ tasks: { kind: 'mongo-collection', @@ -425,12 +409,8 @@ describe('mongo contract builder', () => { }); expect( - ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape - ).collections, + (getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape) + .collections, ).toEqual({ users: { kind: 'mongo-collection', diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts index 8d3ebc5a17..310c4f18ad 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.polymorphism.test.ts @@ -63,10 +63,7 @@ describe('mongo contract builder — polymorphic index scoping', () => { }); const collections = ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections as unknown as Record< string, { @@ -131,10 +128,7 @@ describe('mongo contract builder — polymorphic index scoping', () => { }); const collections = ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections as unknown as Record< string, { @@ -193,10 +187,7 @@ describe('mongo contract builder — polymorphic index scoping', () => { }); const collections = ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections as unknown as Record< string, { @@ -246,10 +237,7 @@ describe('mongo contract builder — polymorphic index scoping', () => { }); const collections = ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections as unknown as Record< string, { @@ -293,10 +281,7 @@ describe('mongo contract builder — polymorphic index scoping', () => { }); const collections = ( - getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections as unknown as Record< string, { diff --git a/packages/2-mongo-family/3-tooling/emitter/src/index.ts b/packages/2-mongo-family/3-tooling/emitter/src/index.ts index 6b7b284c07..071fe32add 100644 --- a/packages/2-mongo-family/3-tooling/emitter/src/index.ts +++ b/packages/2-mongo-family/3-tooling/emitter/src/index.ts @@ -25,9 +25,7 @@ function mongoNamespaceSerializedKind(ns: Namespace): string { function assertUniqueMongoCollectionNames(storage: MongoStorage): void { const seen = new Map(); - for (const [namespaceId, ns] of [ - ...storageNamespaceEntries(storage as unknown as Record), - ]) { + for (const [namespaceId, ns] of [...storageNamespaceEntries(storage)]) { for (const coll of Object.keys((ns as MongoNamespaceShape).collections)) { const existing = seen.get(coll); if (existing !== undefined && existing !== namespaceId) { @@ -66,9 +64,7 @@ function generateMongoNamespaceCollectionsType( } function generateMongoFlatStorageType(storage: MongoStorage): string { - const sorted = [...storageNamespaceEntries(storage as unknown as Record)].sort( - ([a], [b]) => a.localeCompare(b), - ); + const sorted = [...storageNamespaceEntries(storage)].sort(([a], [b]) => a.localeCompare(b)); if (sorted.length === 0) { return ''; } @@ -136,8 +132,8 @@ export const mongoEmission = { throw new Error('Mongo contract must have storage'); } if ( - storageNamespaceValues(storage as unknown as Record).length === 0 && - !getStorageNamespace(storage as unknown as Record, '__unbound__') + storageNamespaceValues(storage).length === 0 && + !getStorageNamespace(storage, '__unbound__') ) { throw new Error('Mongo contract must have at least one storage namespace entry'); } @@ -145,7 +141,7 @@ export const mongoEmission = { assertUniqueMongoCollectionNames(storage); const collectionNames = new Set(); - for (const ns of storageNamespaceValues(storage as unknown as Record)) { + for (const ns of storageNamespaceValues(storage)) { for (const c of Object.keys((ns as MongoNamespaceShape).collections)) { collectionNames.add(c); } @@ -186,7 +182,7 @@ export const mongoEmission = { } else if (collection) { if (!collectionNames.has(collection)) { throw new Error( - `Model "${modelName}" references collection "${collection}" which is not in getStorageNamespace(storage as unknown as Record, ..).collections`, + `Model "${modelName}" references collection "${collection}" which is not in getStorageNamespace(storage, ..).collections`, ); } } diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts index 908eb45773..1384a61de6 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -60,7 +60,7 @@ describe('mongoEmission.validateStructure', () => { storage: namespacedMongoStorageFromCollections({}), }); expect(() => mongoEmission.validateStructure(contract)).toThrow( - 'references collection "users" which is not in getStorageNamespace(storage as Record, ..).collections', + 'references collection "users" which is not in getStorageNamespace(storage, ..).collections', ); }); diff --git a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts index ea77330622..2b407f0ad3 100644 --- a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts +++ b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts @@ -79,7 +79,7 @@ export function contractToMongoSchemaIR(contract: Contract | null): MongoSchemaI } const collections: MongoSchemaCollection[] = []; - for (const ns of storageNamespaceValues(contract.storage as unknown as Record)) { + for (const ns of storageNamespaceValues(contract.storage)) { for (const [name, def] of Object.entries((ns as MongoNamespaceShape).collections)) { collections.push(convertCollection(name, def)); } diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts index 4cf0b88a5c..a451e851f4 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts @@ -78,7 +78,7 @@ export abstract class MongoContractSerializerBase * default. * * The returned `MongoContract` carries class instances under - * `getStorageNamespace(storage as Record, namespaceId).collections[collectionName]` (each value is a + * `getStorageNamespace(storage, namespaceId).collections[collectionName]` (each value is a * `MongoCollection`, with nested `MongoIndex` / `MongoValidator` / * `MongoCollectionOptions` constructed by the `MongoCollection` constructor). * The rest of the contract envelope (models, valueObjects, capabilities, …) @@ -116,7 +116,7 @@ export abstract class MongoContractSerializerBase /** * Walk a structurally-validated Mongo contract and convert each - * `getStorageNamespace(storage as Record, nsId).collections[collectionName]` entry from plain + * `getStorageNamespace(storage, nsId).collections[collectionName]` entry from plain * data into `MongoCollection` IR-class instances. Idempotent: already-class * instances pass through unchanged. */ @@ -124,9 +124,7 @@ export abstract class MongoContractSerializerBase const hydratedStorage: Record = { storageHash: contract.storage.storageHash, }; - for (const [nsId, nsEnvelope] of storageNamespaceEntries( - contract.storage as Record, - )) { + for (const [nsId, nsEnvelope] of storageNamespaceEntries(contract.storage)) { const rawCollections = (nsEnvelope as { collections?: Record }).collections ?? {}; const hydratedCollections = Object.fromEntries( diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts index eefc159f21..d82ec887de 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-schema-verifier-base.ts @@ -45,9 +45,9 @@ export abstract class MongoSchemaVerifierBase< options: SchemaVerifyOptions, ): readonly SchemaIssue[] { const issues: SchemaIssue[] = []; - const namespaceEntries = [ - ...storageNamespaceEntries(options.contract.storage as unknown as Record), - ].sort(([a], [b]) => a.localeCompare(b)); + const namespaceEntries = [...storageNamespaceEntries(options.contract.storage)].sort( + ([a], [b]) => a.localeCompare(b), + ); for (const [namespaceId, namespace] of namespaceEntries) { if (!namespace) continue; issues.push( diff --git a/packages/3-extensions/cipherstash/test/descriptor.test.ts b/packages/3-extensions/cipherstash/test/descriptor.test.ts index 950531d63f..3df86defe2 100644 --- a/packages/3-extensions/cipherstash/test/descriptor.test.ts +++ b/packages/3-extensions/cipherstash/test/descriptor.test.ts @@ -53,8 +53,7 @@ describe('cipherstash extension descriptor (contract-space package layout)', () const space = cipherstashExtensionDescriptor.contractSpace; expect(space).toBeDefined(); const unboundTables = - space!.getStorageNamespace(contractJson.storage as Record, '__unbound__') - ?.tables ?? {}; + space!.getStorageNamespace(contractJson.storage, '__unbound__')?.tables ?? {}; expect(Object.keys(unboundTables)).toEqual([EQL_V2_CONFIGURATION_TABLE]); }); diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts index 400336783e..d85d0e4c42 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts @@ -56,7 +56,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDouble constructor', () => { it('lowers full args to a column with cipherstash/double@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts index 5abb8b9e5b..68ba7be935 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts @@ -57,7 +57,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDate constructor', () => { it('lowers full args to a column with cipherstash/date@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts index e26cb8862b..44441270d9 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts @@ -73,7 +73,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s as Record, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedString constructor', () => { it('lowers full args to a column with codecId, nativeType, typeParams', () => { diff --git a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts index d1e3f99247..30664581f8 100644 --- a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts +++ b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts @@ -65,15 +65,11 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect( - getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables[ - 'tenant' - ], - ).toBeDefined(); + expect(getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['tenant']).toBeDefined(); // The storage map carries the Postgres target concretion (not the // SQL family placeholder) at the unbound slot. - const namespace = getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID); + const namespace = getStorageNamespace(storage, UNBOUND_NAMESPACE_ID); expect(namespace).toBeInstanceOf(PostgresUnboundSchema); // The qualifier elides — DDL emission against this namespace @@ -107,11 +103,9 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect( - getStorageNamespace(storage as Record, 'auth')?.tables['user'], - ).toBeDefined(); + expect(getStorageNamespace(storage, 'auth')?.tables['user']).toBeDefined(); - const namespace = getStorageNamespace(storage as Record, 'auth'); + const namespace = getStorageNamespace(storage, 'auth'); expect(namespace).toBeInstanceOf(PostgresSchema); expect(namespace).not.toBeInstanceOf(PostgresUnboundSchema); if (!(namespace instanceof PostgresSchema)) { @@ -143,8 +137,6 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a // Top-level declarations lower to the unbound namespace — the // planner falls back to its `ctx.schemaName` (today `"public"`) // for DDL qualification. - expect( - getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables['post'], - ).toBeDefined(); + expect(getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['post']).toBeDefined(); }); }); diff --git a/packages/3-extensions/sql-orm-client/src/collection-contract.ts b/packages/3-extensions/sql-orm-client/src/collection-contract.ts index cdf427e37b..1e0f5c3c6a 100644 --- a/packages/3-extensions/sql-orm-client/src/collection-contract.ts +++ b/packages/3-extensions/sql-orm-client/src/collection-contract.ts @@ -31,8 +31,9 @@ export interface PolymorphismInfo { } function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) - ?.tables[tableName] as StorageTable | undefined; + return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as + | StorageTable + | undefined; } function modelsOf(contract: Contract): ModelsMap { diff --git a/packages/3-extensions/sql-orm-client/src/model-accessor.ts b/packages/3-extensions/sql-orm-client/src/model-accessor.ts index 177ee2ee4a..eeafeb53d6 100644 --- a/packages/3-extensions/sql-orm-client/src/model-accessor.ts +++ b/packages/3-extensions/sql-orm-client/src/model-accessor.ts @@ -117,10 +117,9 @@ function resolveColumn( tableName: string, columnName: string, ): { readonly codecId: string; readonly nullable: boolean } | undefined { - const table = getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - )?.tables[tableName] as StorageTable | undefined; + const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as + | StorageTable + | undefined; const column = table?.columns?.[columnName]; if (!column) return undefined; return { codecId: column.codecId, nullable: column.nullable }; diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts index bfc93e74a8..f534e8008a 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts @@ -15,10 +15,9 @@ export function deriveParamsFromAst(ast: AnyQueryAst): { } export function resolveTableColumns(contract: Contract, tableName: string): string[] { - const table = getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - )?.tables[tableName] as StorageTable | undefined; + const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as + | StorageTable + | undefined; if (!table) { throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`); } diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts index b5cc2248c6..c74316b4c1 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts @@ -20,8 +20,9 @@ import { buildOrmQueryPlan, deriveParamsFromAst, resolveTableColumns } from './q import { combineWhereExprs } from './where-utils'; function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) - ?.tables[tableName] as StorageTable | undefined; + return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as + | StorageTable + | undefined; } function buildReturningColumns( diff --git a/packages/3-extensions/sql-orm-client/src/where-binding.ts b/packages/3-extensions/sql-orm-client/src/where-binding.ts index 91af988cf3..3d1df735f9 100644 --- a/packages/3-extensions/sql-orm-client/src/where-binding.ts +++ b/packages/3-extensions/sql-orm-client/src/where-binding.ts @@ -131,8 +131,9 @@ function createParamRef( ): ParamRef { if ( !( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID) - ?.tables[columnRef.table] as StorageTable | undefined + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[columnRef.table] as + | StorageTable + | undefined )?.columns[columnRef.column] ) { throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`); diff --git a/packages/3-extensions/sql-orm-client/test/helpers.ts b/packages/3-extensions/sql-orm-client/test/helpers.ts index 846a9a9294..31769d95ff 100644 --- a/packages/3-extensions/sql-orm-client/test/helpers.ts +++ b/packages/3-extensions/sql-orm-client/test/helpers.ts @@ -154,7 +154,7 @@ export function buildMixedPolyContract(): TestContract { base: 'Task', }; - getStorageNamespace(raw.storage as Record, UNBOUND_NAMESPACE_ID).tables.tasks = { + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.tasks = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, @@ -167,10 +167,7 @@ export function buildMixedPolyContract(): TestContract { foreignKeys: [], }; - getStorageNamespace( - raw.storage as Record, - UNBOUND_NAMESPACE_ID, - ).tables.features = { + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.features = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, priority: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, @@ -218,26 +215,17 @@ export function buildStiPolyContract(): TestContract { base: 'User', }; - getStorageNamespace( - raw.storage as Record, - UNBOUND_NAMESPACE_ID, - ).tables.users.columns.kind = { + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.kind = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, }; - getStorageNamespace( - raw.storage as Record, - UNBOUND_NAMESPACE_ID, - ).tables.users.columns.role = { + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.role = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, }; - getStorageNamespace( - raw.storage as Record, - UNBOUND_NAMESPACE_ID, - ).tables.users.columns.plan = { + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.plan = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, diff --git a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts index fb76f88510..8b2d058071 100644 --- a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts +++ b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts @@ -144,10 +144,7 @@ describe('mutation-executor', () => { it('buildPrimaryKeyFilterFromRow() resolves custom primary key columns', () => { const contract = getTestContract(); - const unboundNs = getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - )!; + const unboundNs = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!; const withCustomPk = { ...contract, storage: { diff --git a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts index 52f0e7e019..69cd883e4d 100644 --- a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts +++ b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts @@ -10,6 +10,7 @@ type StorageLike = { export function unboundTables( storage: StorageLike | SqlStorage, ): Readonly> { - return (getStorageNamespace(storage as Record, UNBOUND_NAMESPACE_ID)?.tables ?? - {}) as Readonly>; + return (getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? {}) as Readonly< + Record + >; } diff --git a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts index faed13b20b..cb9fb574ef 100644 --- a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts +++ b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts @@ -17,23 +17,21 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase)].map( - ([nsId, nsData]) => { - const ns = nsData as MongoNamespaceShape; - const collections = ns.collections; - const collectionCount = Object.keys(collections).length; - if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0) { - return [nsId, MongoTargetUnboundDatabase.instance]; - } - return [ - nsId, - new MongoTargetDatabase({ - id: ns.id, - collections, - }), - ]; - }, - ), + [...storageNamespaceEntries(storage)].map(([nsId, nsData]) => { + const ns = nsData as MongoNamespaceShape; + const collections = ns.collections; + const collectionCount = Object.keys(collections).length; + if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0) { + return [nsId, MongoTargetUnboundDatabase.instance]; + } + return [ + nsId, + new MongoTargetDatabase({ + id: ns.id, + collections, + }), + ]; + }), ); const targetStorage = new MongoStorage( buildMongoStorageInput({ @@ -49,9 +47,7 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase = { storageHash: String(storage.storageHash), }; - for (const [nsId, ns] of [ - ...storageNamespaceEntries(storage as unknown as Record), - ]) { + for (const [nsId, ns] of [...storageNamespaceEntries(storage)]) { const mongoNs = ns as MongoNamespaceShape; const collectionsOut: Record = {}; for (const [collName, coll] of Object.entries(mongoNs.collections)) { diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts index 738c01d85e..cb6c44f9e4 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-target-contract-serializer.test.ts @@ -68,9 +68,9 @@ describe('MongoTargetContractSerializer', () => { const serializer = new MongoTargetContractSerializer(); const contract = serializer.deserializeContract(makeSingletonUnboundContractJson()); - expect( - getStorageNamespace(contract.storage as unknown as Record, '__unbound__'), - ).toBe(MongoTargetUnboundDatabase.instance); + expect(getStorageNamespace(contract.storage, '__unbound__')).toBe( + MongoTargetUnboundDatabase.instance, + ); }); it('hydrates collections into MongoCollection IR-class instances', () => { @@ -78,10 +78,7 @@ describe('MongoTargetContractSerializer', () => { const contract = serializer.deserializeContract(makeValidContractJson()); const items = ( - getStorageNamespace( - contract.storage as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape | undefined + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape | undefined )?.collections['items']; expect(items).toBeInstanceOf(MongoCollection); expect(items?.kind).toBe('mongo-collection'); @@ -160,10 +157,9 @@ describe('MongoTargetContractSerializer', () => { const contract = serializer.deserializeContract(makeFullyPopulatedJson()); const items = ( - getStorageNamespace( - contract.storage as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape | undefined + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID) as + | MongoNamespaceShape + | undefined )?.collections['items']; expect(items).toBeInstanceOf(MongoCollection); expect(items?.indexes?.[0]).toBeInstanceOf(MongoIndex); @@ -180,10 +176,7 @@ describe('MongoTargetContractSerializer', () => { const reparsed = JSON.parse(JSON.stringify(out)); const items = ( - getStorageNamespace( - reparsed.storage as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape + getStorageNamespace(reparsed.storage, UNBOUND_NAMESPACE_ID) as MongoNamespaceShape ).collections['items']; expect(items).toBeDefined(); expect(items!.kind).toBe('mongo-collection'); @@ -195,10 +188,9 @@ describe('MongoTargetContractSerializer', () => { const roundtripped = serializer.deserializeContract(reparsed); expect( ( - getStorageNamespace( - roundtripped.storage as unknown as Record, - UNBOUND_NAMESPACE_ID, - ) as MongoNamespaceShape | undefined + getStorageNamespace(roundtripped.storage, UNBOUND_NAMESPACE_ID) as + | MongoNamespaceShape + | undefined )?.collections['items'], ).toBeInstanceOf(MongoCollection); }); diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts index 7b65ba3ed8..fdad8a3450 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-target-schema-verifier.test.ts @@ -74,11 +74,9 @@ describe('MongoTargetSchemaVerifier', () => { it('uses the family-shared scaffolding: walks each namespace and aggregates issues', () => { const verifier = new MongoTargetSchemaVerifier(); const contract = deserializedContract(); - expect( - [...storageNamespaceEntries(contract.storage as unknown as Record)].map( - ([id]) => id, - ), - ).toEqual(['__unbound__']); + expect([...storageNamespaceEntries(contract.storage)].map(([id]) => id)).toEqual([ + '__unbound__', + ]); const result = verifier.verifySchema({ contract, schema: new MongoSchemaIR([]) }); expect(result).toBeDefined(); diff --git a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts index 4c454549d5..7b8319907f 100644 --- a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts +++ b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts @@ -61,9 +61,7 @@ export function tableAt( namespaceId: string, tableName: string, ): StorageTable | undefined { - return getStorageNamespace(storage as Record, namespaceId)?.tables[tableName] as - | StorageTable - | undefined; + return getStorageNamespace(storage, namespaceId)?.tables[tableName] as StorageTable | undefined; } /** diff --git a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts index 23544eb401..9ddb4cd557 100644 --- a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts +++ b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts @@ -45,10 +45,7 @@ describe('SqliteContractSerializer', () => { const serializer = new SqliteContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); expect(contract.targetFamily).toBe('sql'); - expect( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! - .tables, - ).toEqual({}); + expect(getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables).toEqual({}); }); it('hydrates JSON storage into the SQL Contract IR class hierarchy', () => { @@ -56,10 +53,9 @@ describe('SqliteContractSerializer', () => { const contract = serializer.deserializeContract(makeContractWithTablesJson()); expect(contract.storage).toBeInstanceOf(SqlStorage); - const userTable = getStorageNamespace( - contract.storage as Record, - UNBOUND_NAMESPACE_ID, - )!.tables['user'] as StorageTable | undefined; + const userTable = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables['user'] as + | StorageTable + | undefined; expect(userTable).toBeInstanceOf(StorageTable); expect(userTable?.columns['id']).toBeInstanceOf(StorageColumn); }); @@ -77,12 +73,10 @@ describe('SqliteContractSerializer', () => { const reparsed = JSON.parse(JSON.stringify(json)); expect(reparsed.storage).not.toHaveProperty('kind'); expect( - getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables - .user, + getStorageNamespace(reparsed.storage, UNBOUND_NAMESPACE_ID).tables.user, ).not.toHaveProperty('kind'); expect( - getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables - .user.columns.id, + getStorageNamespace(reparsed.storage, UNBOUND_NAMESPACE_ID).tables.user.columns.id, ).not.toHaveProperty('kind'); }); }); diff --git a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts index 8ddfd6fe1b..c33ac53cbe 100644 --- a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts +++ b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts @@ -684,7 +684,7 @@ function getInsertColumnOrder( } let table: { columns: Record } | undefined; - for (const ns of storageNamespaceValues(contract.storage as Record)) { + for (const ns of storageNamespaceValues(contract.storage)) { // Namespace.tables is Record at the interface level; // SQL family namespaces hold StorageTable instances which have .columns. const found = ns.tables[tableName] as { columns: Record } | undefined; From b695845166da836536e2db54bba621769965154b Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:28:22 +0200 Subject: [PATCH 19/60] fix(target-postgres): complete flat-storage migration and widen elementCoordinates Finishes the postgres target wrapper drop the mechanical pass left incomplete: imports the generic walk helpers in the migration planners, schema, and serializer; fixes a botched `ctx.toContract.storage` lookup; routes test fixtures through `buildSqlStorageInput`; and selects `SqlNamespace` at `.tables`/`.enum` access sites. Also widens `elementCoordinates` to accept `object` so class instances pass without a cast. Signed-off-by: Will Madden --- .../framework-components/src/ir/storage.ts | 2 +- .../src/core/migrations/enum-planning.ts | 4 +- .../src/core/migrations/issue-planner.ts | 8 +- .../src/core/migrations/planner-strategies.ts | 27 ++--- .../migrations/verify-postgres-namespaces.ts | 12 +- .../src/core/postgres-contract-serializer.ts | 4 +- .../postgres/src/core/postgres-schema.ts | 3 +- .../postgres/test/element-coordinates.test.ts | 18 +-- .../test/migrations/enum-collision.test.ts | 70 ++++++------ .../test/migrations/issue-planner.test.ts | 43 +++++--- .../verify-postgres-namespaces.test.ts | 18 ++- .../test/postgres-contract-serializer.test.ts | 104 +++++++++--------- .../postgres/test/postgres-schema.test.ts | 36 +++--- .../test/snapshot-read-shapes.test.ts | 13 +-- 14 files changed, 189 insertions(+), 173 deletions(-) diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage.ts b/packages/1-framework/1-core/framework-components/src/ir/storage.ts index 5094e14d4a..02f0da847e 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/storage.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/storage.ts @@ -38,7 +38,7 @@ export interface EntityCoordinate { * property whose value is a non-null object, yields one coordinate per * entry key in that map. No family-specific slot vocabulary is required. */ -export function* elementCoordinates(storage: Record): Generator { +export function* elementCoordinates(storage: object): Generator { for (const [namespaceId, ns] of storageNamespaceEntries(storage)) { for (const [entityKind, slot] of Object.entries(ns)) { if (entityKind === 'id') continue; diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts index f338459e98..c7e04ef18f 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts @@ -5,7 +5,7 @@ */ import { arraysEqual } from '@prisma-next/family-sql/schema-verify'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, SqlStorage } from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { PG_ENUM_CODEC_ID } from '../codec-ids'; @@ -115,7 +115,7 @@ export function resolveDdlSchemaForNamespaceStorage( if (namespaceId === UNBOUND_NAMESPACE_ID) { return (schemaIr ? readPostgresSchemaIrAnnotations(schemaIr).schema : undefined) ?? 'public'; } - const namespace = getStorageNamespace(storage as Record, namespaceId); + const namespace = getStorageNamespace(storage, namespaceId); if (namespace && isPostgresSchema(namespace)) { return namespace.ddlSchemaName(storage); } diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts index a876f2b91c..684627e611 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/issue-planner.ts @@ -18,11 +18,7 @@ import type { import { arraysEqual } from '@prisma-next/family-sql/schema-verify'; import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, SqlStorage, @@ -77,7 +73,7 @@ function locateNamespaceTypeInStorage( namespaceId: string, typeName: string, ): unknown { - const ns = getStorageNamespace(storage as Record, namespaceId); + const ns = getStorageNamespace(storage, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return undefined; return (ns.enum as Record)[typeName]; } diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts index a0f33ff650..5ad795c3ba 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/planner-strategies.ts @@ -26,10 +26,15 @@ import type { } from '@prisma-next/family-sql/control'; import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import { isPostgresEnumStorageEntry, type PostgresEnumStorageEntry, + type SqlNamespace, type SqlStorage, type StorageTable, type StorageTypeInstance, @@ -91,9 +96,8 @@ export function tableAt( namespaceId: string, tableName: string, ): StorageTable | undefined { - // Namespace.tables is typed as Record at the interface level; // SQL family namespaces always hold StorageTable instances. - return getStorageNamespace(storage as Record, namespaceId)?.tables[tableName] as + return getStorageNamespace(storage, namespaceId)?.tables[tableName] as | StorageTable | undefined; } @@ -122,10 +126,7 @@ export function resolveNamespaceIdForIssue(issue: { readonly namespaceId?: strin * unchanged so downstream `qualifyTableName` resolves polymorphically. */ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: string): string { - const namespace = ctx.getStorageNamespace( - toContract.storage as Record, - namespaceId, - ); + const namespace = getStorageNamespace(ctx.toContract.storage, namespaceId); if (isPostgresSchema(namespace)) { return namespace.ddlSchemaName(ctx.toContract.storage); } @@ -138,7 +139,7 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: const DEFAULT_ENUM_NAMESPACE_ID = 'public'; function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean { - const ns = getStorageNamespace(storage as Record, namespaceId); + const ns = getStorageNamespace(storage, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return false; return (ns.enum as Record)[typeName] !== undefined; } @@ -161,7 +162,7 @@ function resolveColumnEnumNamespace( typeName: string, ): string | undefined { if (namespaceHasEnum(storage, columnNamespaceId, typeName)) return columnNamespaceId; - const owners = [...storageNamespaceEntries(storage as Record)] + const owners = [...storageNamespaceEntries(storage)] .map(([id]) => id) .filter((nsId) => namespaceHasEnum(storage, nsId, typeName)); if (owners.length === 1) return owners[0]; @@ -171,7 +172,7 @@ function resolveColumnEnumNamespace( /** * Finds a type entry by explicit namespace coordinate. Namespace types (e.g. - * Postgres enums) live under `getStorageNamespace(storage as Record, nsId).enum`. Returns the + * Postgres enums) live under `getStorageNamespace(storage, nsId).enum`. Returns the * entry from the named namespace only — never scans other namespaces, so two * namespaces that hold an enum with the same name resolve independently. */ @@ -180,7 +181,7 @@ function locateNamespaceType( namespaceId: string, typeName: string, ): PostgresEnumStorageEntry | undefined { - const ns = getStorageNamespace(storage as Record, namespaceId); + const ns = getStorageNamespace(storage, namespaceId); if (!ns || !('enum' in ns) || ns.enum == null) return undefined; return (ns.enum as Record)[typeName]; } @@ -440,7 +441,7 @@ function enumRebuildCallRecipe( // namespace correctly binds to a `public`-namespace enum, while two // same-named enums in distinct namespaces keep their columns disjoint. const columnRefs: { namespaceId: string; table: string; column: string }[] = []; - for (const [nsId, ns] of Object.entries(ctx.toContract.storage as Record)) { + for (const [nsId, ns] of storageNamespaceEntries(ctx.toContract.storage)) { for (const [tableName, tableNode] of Object.entries(ns.tables)) { const table = tableNode as StorageTable; for (const [columnName, column] of Object.entries(table.columns)) { @@ -643,7 +644,7 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) = */ function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap { const result = new Map(); - for (const [nsId, ns] of [...storageNamespaceEntries(storage as Record)]) { + for (const [nsId, ns] of [...storageNamespaceEntries(storage)]) { if (!('enum' in ns) || ns.enum == null) continue; const nsEnums = ns.enum as Record; for (const [name, instance] of Object.entries(nsEnums).sort(([a], [b]) => a.localeCompare(b))) { diff --git a/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts b/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts index 5185f6f7b8..76256068a5 100644 --- a/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts +++ b/packages/3-targets/3-targets/postgres/src/core/migrations/verify-postgres-namespaces.ts @@ -1,6 +1,10 @@ import type { Contract } from '@prisma-next/contract/types'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + getStorageNamespace, + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import type { SqlStorage } from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { isPostgresSchema } from '../postgres-schema'; @@ -15,7 +19,7 @@ import { isPostgresSchema } from '../postgres-schema'; * itself. */ function resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string { - const namespace = getStorageNamespace(storage as Record, namespaceId); + const namespace = getStorageNamespace(storage, namespaceId); if (isPostgresSchema(namespace)) { return namespace.ddlSchemaName(storage); } @@ -71,9 +75,7 @@ export function verifyPostgresNamespacePresence(input: { const { contract, schema } = input; const existing = new Set(existingSchemasFromSchema(schema)); const issues: SchemaIssue[] = []; - const namespaceIds = [...storageNamespaceEntries(contract.storage as Record)] - .map(([id]) => id) - .sort(); + const namespaceIds = [...storageNamespaceEntries(contract.storage)].map(([id]) => id).sort(); for (const namespaceId of namespaceIds) { if (namespaceId === UNBOUND_NAMESPACE_ID) continue; const ddlName = resolveDdlSchemaName(contract.storage, namespaceId); diff --git a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts index 248a2da9a1..55d784b839 100644 --- a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts +++ b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts @@ -12,9 +12,11 @@ import { import { type Namespace, NamespaceBase, + storageNamespaceEntries, UNBOUND_NAMESPACE_ID, } from '@prisma-next/framework-components/ir'; import type { + SqlNamespace, SqlNamespaceTablesInput, SqlStorage, SqlStorageTypeEntry, @@ -113,7 +115,7 @@ export class PostgresContractSerializer extends SqlContractSerializerBase): JsonObject { const { storage, ...rest } = contract; const namespacesJson: Record = {}; - for (const [nsId, ns] of [...storageNamespaceEntries(storage as Record)]) { + for (const [nsId, ns] of [...storageNamespaceEntries(storage)]) { if (isPostgresSchema(ns)) { namespacesJson[nsId] = this.serializePostgresNamespace(ns, ns.id === UNBOUND_NAMESPACE_ID); } else { diff --git a/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts b/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts index ca3cfa8794..3c5ba77186 100644 --- a/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts +++ b/packages/3-targets/3-targets/postgres/src/core/postgres-schema.ts @@ -1,5 +1,6 @@ import { freezeNode, + getStorageNamespace, NamespaceBase, UNBOUND_NAMESPACE_ID, } from '@prisma-next/framework-components/ir'; @@ -173,7 +174,7 @@ export class PostgresUnboundSchema extends PostgresSchema { * (e.g. emit a conflict instead of silently picking a schema). */ override ddlSchemaName(storage: SqlStorage): string { - if (getStorageNamespace(storage as Record, 'public') !== undefined) { + if (getStorageNamespace(storage, 'public') !== undefined) { return 'public'; } return UNBOUND_NAMESPACE_ID; diff --git a/packages/3-targets/3-targets/postgres/test/element-coordinates.test.ts b/packages/3-targets/3-targets/postgres/test/element-coordinates.test.ts index 408856b424..be40406600 100644 --- a/packages/3-targets/3-targets/postgres/test/element-coordinates.test.ts +++ b/packages/3-targets/3-targets/postgres/test/element-coordinates.test.ts @@ -1,6 +1,6 @@ import { coreHash } from '@prisma-next/contract/types'; import { elementCoordinates, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { SqlStorage } from '@prisma-next/sql-contract/types'; +import { buildSqlStorageInput, SqlStorage } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { PostgresSchema, PostgresUnboundSchema } from '../src/core/postgres-schema'; @@ -19,13 +19,15 @@ describe('elementCoordinates with PostgresSchema', () => { }); expect(schema.kind).toBe('schema'); - const storage = new SqlStorage({ - storageHash: coreHash('sha256:element-coordinates-test'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, - public: schema, - }, - }); + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:element-coordinates-test'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, + public: schema, + }, + }), + ); const coordinates = [...elementCoordinates(storage)]; expect(coordinates).toContainEqual({ diff --git a/packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts b/packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts index 10ca569131..3a864b75f9 100644 --- a/packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts +++ b/packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts @@ -4,7 +4,7 @@ import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, StorageTableInput } from '@prisma-next/sql-contract/types'; -import { SqlStorage } from '@prisma-next/sql-contract/types'; +import { buildSqlStorageInput, SqlStorage } from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { PG_ENUM_CODEC_ID } from '@prisma-next/target-postgres/codec-ids'; import { describe, expect, it } from 'vitest'; @@ -46,22 +46,24 @@ function makeCollisionContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:enum-collision'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:enum-collision-contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, - audit: new PostgresSchema({ - id: 'audit', - tables: overrides.audit?.tables ?? {}, - ...(auditEnum !== undefined ? { enum: { Status: auditEnum } } : {}), - }), - public: new PostgresSchema({ - id: 'public', - tables: overrides.public?.tables ?? {}, - ...(publicEnum !== undefined ? { enum: { Status: publicEnum } } : {}), - }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:enum-collision-contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, + audit: new PostgresSchema({ + id: 'audit', + tables: overrides.audit?.tables ?? {}, + ...(auditEnum !== undefined ? { enum: { Status: auditEnum } } : {}), + }), + public: new PostgresSchema({ + id: 'public', + tables: overrides.public?.tables ?? {}, + ...(publicEnum !== undefined ? { enum: { Status: publicEnum } } : {}), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -298,22 +300,24 @@ describe('enum namespace collision planning', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:public-enum-unbound-col'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:public-enum-unbound-col'), - namespaces: { - public: new PostgresSchema({ - id: 'public', - tables: {}, - enum: { - status: new PostgresEnumType({ name: 'status', values: ['active', 'archived'] }), - }, - }), - [UNBOUND_NAMESPACE_ID]: new PostgresUnboundSchema({ - id: UNBOUND_NAMESPACE_ID, - tables: { user: userTable }, - }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:public-enum-unbound-col'), + namespaces: { + public: new PostgresSchema({ + id: 'public', + tables: {}, + enum: { + status: new PostgresEnumType({ name: 'status', values: ['active', 'archived'] }), + }, + }), + [UNBOUND_NAMESPACE_ID]: new PostgresUnboundSchema({ + id: UNBOUND_NAMESPACE_ID, + tables: { user: userTable }, + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/3-targets/postgres/test/migrations/issue-planner.test.ts b/packages/3-targets/3-targets/postgres/test/migrations/issue-planner.test.ts index ba59d7850b..fec71e6b97 100644 --- a/packages/3-targets/3-targets/postgres/test/migrations/issue-planner.test.ts +++ b/packages/3-targets/3-targets/postgres/test/migrations/issue-planner.test.ts @@ -2,9 +2,10 @@ import { type Contract, coreHash, profileHash } from '@prisma-next/contract/type import type { SchemaIssue } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { + buildSqlStorageInput, type PostgresEnumStorageEntry, SqlStorage, - type SqlStorageInput, + type SqlStorageNamespacesInput, type StorageTableInput, } from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; @@ -35,10 +36,12 @@ function makeContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -781,7 +784,7 @@ describe('planIssues', () => { function makeNamespacedContract( namespaces: Record }>, ): Contract { - const nsMap: SqlStorageInput['namespaces'] = { + const nsMap: SqlStorageNamespacesInput['namespaces'] = { [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, ...Object.fromEntries( Object.entries(namespaces).map(([id, ns]) => [ @@ -794,10 +797,12 @@ describe('planIssues', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: nsMap, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: nsMap, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -896,14 +901,16 @@ describe('planIssues', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:multi-namespace-contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, - tenant_a: new PostgresSchema({ id: 'tenant_a', tables: { users: userTable } }), - tenant_b: new PostgresSchema({ id: 'tenant_b', tables: { users: userTable } }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:multi-namespace-contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, + tenant_a: new PostgresSchema({ id: 'tenant_a', tables: { users: userTable } }), + tenant_b: new PostgresSchema({ id: 'tenant_b', tables: { users: userTable } }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/3-targets/postgres/test/migrations/verify-postgres-namespaces.test.ts b/packages/3-targets/3-targets/postgres/test/migrations/verify-postgres-namespaces.test.ts index c72587f9e8..66da847fac 100644 --- a/packages/3-targets/3-targets/postgres/test/migrations/verify-postgres-namespaces.test.ts +++ b/packages/3-targets/3-targets/postgres/test/migrations/verify-postgres-namespaces.test.ts @@ -1,6 +1,10 @@ import { type Contract, coreHash, profileHash } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { SqlStorage, type SqlStorageInput } from '@prisma-next/sql-contract/types'; +import { + buildSqlStorageInput, + SqlStorage, + type SqlStorageNamespacesInput, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { describe, expect, it } from 'vitest'; import { verifyPostgresNamespacePresence } from '../../src/core/migrations/verify-postgres-namespaces'; @@ -14,7 +18,7 @@ function makeContract( options.useUnbound || !namespaceIds.includes(UNBOUND_NAMESPACE_ID) ? PostgresUnboundSchema.instance : new PostgresSchema({ id: UNBOUND_NAMESPACE_ID, tables: {} }); - const namespaces: SqlStorageInput['namespaces'] = { + const namespaces: SqlStorageNamespacesInput['namespaces'] = { [UNBOUND_NAMESPACE_ID]: unboundEntry, ...Object.fromEntries( namespaceIds @@ -26,10 +30,12 @@ function makeContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts b/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts index a7e4cd1351..ff3cd650e8 100644 --- a/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts +++ b/packages/3-targets/3-targets/postgres/test/postgres-contract-serializer.test.ts @@ -4,10 +4,11 @@ import { SqlContractSerializerBase, type SqlEntityHydrationFactory, } from '@prisma-next/family-sql/ir'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { ForeignKey, PrimaryKey, + type SqlNamespace, SqlStorage, StorageColumn, StorageTable, @@ -25,41 +26,39 @@ function makeValidContractJson() { function makeContractWithTablesJson() { return createSqlContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, }, - post: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - userId: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [ - { - source: { - namespaceId: UNBOUND_NAMESPACE_ID, - tableName: 'post', - columns: ['userId'], - }, - target: { namespaceId: UNBOUND_NAMESPACE_ID, tableName: 'user', columns: ['id'] }, - constraint: true, - index: true, - }, - ], + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + userId: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [ + { + source: { + namespaceId: UNBOUND_NAMESPACE_ID, + tableName: 'post', + columns: ['userId'], + }, + target: { namespaceId: UNBOUND_NAMESPACE_ID, tableName: 'user', columns: ['id'] }, + constraint: true, + index: true, + }, + ], }, }, }, @@ -78,8 +77,7 @@ describe('PostgresContractSerializer', () => { const contract = serializer.deserializeContract(makeValidContractJson()); expect(contract.targetFamily).toBe('sql'); expect( - getStorageNamespace(contract.storage as Record, UNBOUND_NAMESPACE_ID)! - .tables, + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables, ).toEqual({}); }); @@ -88,8 +86,8 @@ describe('PostgresContractSerializer', () => { const contract = serializer.deserializeContract(makeContractWithTablesJson()); expect(contract.storage).toBeInstanceOf(SqlStorage); - const tables = getStorageNamespace( - contract.storage as Record, + const tables = getStorageNamespace( + contract.storage, UNBOUND_NAMESPACE_ID, )!.tables; const userTable = tables['user'] as StorageTable | undefined; @@ -115,29 +113,25 @@ describe('PostgresContractSerializer', () => { expect(reparsed).toMatchObject({ targetFamily: 'sql', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'postgres-unbound-schema', - tables: { - user: { - columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false } }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'postgres-unbound-schema', + tables: { + user: { + columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false } }, }, - enum: {}, }, + enum: {}, }, }, }); expect(reparsed.storage).not.toHaveProperty('kind'); - expect( - getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables - .user, - ).not.toHaveProperty('kind'); - expect( - getStorageNamespace(reparsed.storage as Record, UNBOUND_NAMESPACE_ID).tables - .user.columns.id, - ).not.toHaveProperty('kind'); + const reparsedUnbound = getStorageNamespace( + reparsed.storage, + UNBOUND_NAMESPACE_ID, + )!; + expect(reparsedUnbound.tables['user']).not.toHaveProperty('kind'); + expect(reparsedUnbound.tables['user']!.columns['id']).not.toHaveProperty('kind'); }); it('hydrates storage.types entries via the family registry dispatch path', () => { diff --git a/packages/3-targets/3-targets/postgres/test/postgres-schema.test.ts b/packages/3-targets/3-targets/postgres/test/postgres-schema.test.ts index 58b14b68b6..fc75c9e544 100644 --- a/packages/3-targets/3-targets/postgres/test/postgres-schema.test.ts +++ b/packages/3-targets/3-targets/postgres/test/postgres-schema.test.ts @@ -1,6 +1,6 @@ import { coreHash } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { buildSqlStorageInput, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { PostgresEnumType } from '../src/core/postgres-enum-type'; import { @@ -77,21 +77,25 @@ describe('PostgresUnboundSchema', () => { }); describe('ddlSchemaName', () => { - const storageWithPublic = new SqlStorage({ - storageHash: coreHash('sha256:test-with-public'), - namespaces: { - public: new PostgresSchema({ id: 'public' }), - [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, - }, - }); - - const storageWithoutPublic = new SqlStorage({ - storageHash: coreHash('sha256:test-without-public'), - namespaces: { - auth: new PostgresSchema({ id: 'auth' }), - [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, - }, - }); + const storageWithPublic = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test-with-public'), + namespaces: { + public: new PostgresSchema({ id: 'public' }), + [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, + }, + }), + ); + + const storageWithoutPublic = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test-without-public'), + namespaces: { + auth: new PostgresSchema({ id: 'auth' }), + [UNBOUND_NAMESPACE_ID]: PostgresUnboundSchema.instance, + }, + }), + ); it('returns its own id for a named public schema', () => { const schema = new PostgresSchema({ id: 'public' }); diff --git a/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts b/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts index 086baf9dd4..442746d885 100644 --- a/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts +++ b/packages/3-targets/3-targets/postgres/test/snapshot-read-shapes.test.ts @@ -1,11 +1,7 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; -import { SqlStorage } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; +import { type SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import { join, relative, resolve } from 'pathe'; import { describe, expect, it } from 'vitest'; import { PostgresContractSerializer } from '../src/core/postgres-contract-serializer'; @@ -93,8 +89,9 @@ describe('snapshot-read shape fixtures — per-kind round-trip (TML-2536)', () = const raw = JSON.parse(readFileSync(join(FIXTURES_DIR, 'postgres-enum.json'), 'utf-8')); const contract = serializer.deserializeContract(raw); expect(contract.storage).toBeInstanceOf(SqlStorage); - const entry = getStorageNamespace(contract.storage as Record, 'public') - ?.enum?.['user_role']; + const entry = getStorageNamespace(contract.storage, 'public')?.enum?.[ + 'user_role' + ]; expect(entry).toBeInstanceOf(PostgresEnumType); expect(entry).toMatchObject({ kind: 'postgres-enum', From 61763ff025b39b92c66b52eb632c254189603844 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:31:31 +0200 Subject: [PATCH 20/60] fix(target-sqlite): complete flat-storage migration in planner and tests Imports the generic walk helper in the sqlite migration planner and routes the planner/serializer test fixtures through `buildSqlStorageInput` with `SqlNamespace`-typed lookups, finishing the wrapper drop the mechanical pass left incomplete for this target. Signed-off-by: Will Madden --- .../src/core/migrations/planner-strategies.ts | 7 ++- .../test/migrations/issue-planner.test.ts | 11 ++-- .../migrations/nullability-backfill.test.ts | 15 +++--- .../migrations/planner-strategies.test.ts | 11 ++-- .../planner.authoring-surface.test.ts | 49 +++++++++-------- .../test/sqlite-contract-serializer.test.ts | 54 ++++++++++--------- 6 files changed, 84 insertions(+), 63 deletions(-) diff --git a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts index 7b8319907f..38d92dccc0 100644 --- a/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts +++ b/packages/3-targets/3-targets/sqlite/src/core/migrations/planner-strategies.ts @@ -21,9 +21,10 @@ import type { } from '@prisma-next/family-sql/control'; import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components'; import type { SchemaIssue } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, + SqlNamespace, SqlStorage, StorageTable, StorageTypeInstance, @@ -61,7 +62,9 @@ export function tableAt( namespaceId: string, tableName: string, ): StorageTable | undefined { - return getStorageNamespace(storage, namespaceId)?.tables[tableName] as StorageTable | undefined; + return getStorageNamespace(storage, namespaceId)?.tables[tableName] as + | StorageTable + | undefined; } /** diff --git a/packages/3-targets/3-targets/sqlite/test/migrations/issue-planner.test.ts b/packages/3-targets/3-targets/sqlite/test/migrations/issue-planner.test.ts index 23320c6de6..262789dbe7 100644 --- a/packages/3-targets/3-targets/sqlite/test/migrations/issue-planner.test.ts +++ b/packages/3-targets/3-targets/sqlite/test/migrations/issue-planner.test.ts @@ -3,6 +3,7 @@ import type { SchemaIssue } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageTableInput, } from '@prisma-next/sql-contract/types'; @@ -21,10 +22,12 @@ function makeContract( target: 'sqlite', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/3-targets/sqlite/test/migrations/nullability-backfill.test.ts b/packages/3-targets/3-targets/sqlite/test/migrations/nullability-backfill.test.ts index cae72e5bb3..8e419228cb 100644 --- a/packages/3-targets/3-targets/sqlite/test/migrations/nullability-backfill.test.ts +++ b/packages/3-targets/3-targets/sqlite/test/migrations/nullability-backfill.test.ts @@ -3,6 +3,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -35,12 +36,14 @@ function makeContract(tables: Record): Contract { target: 'sqlite', targetFamily: 'sql', profileHash: profileHash('sha256:profile'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:to'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, - email: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:to'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, + email: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -46,10 +49,12 @@ function createContract(): Contract { function fromContractWithHash(hash: string): Contract { return { ...createContract(), - storage: new SqlStorage({ - storageHash: coreHash(hash), - namespaces: { [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(hash), + namespaces: { [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }, + }), + ), }; } diff --git a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts index 9ddb4cd557..b4971a547e 100644 --- a/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts +++ b/packages/3-targets/3-targets/sqlite/test/sqlite-contract-serializer.test.ts @@ -1,7 +1,12 @@ import { createSqlContract } from '@prisma-next/contract/testing'; import { SqlContractSerializerBase } from '@prisma-next/family-sql/ir'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { SqlStorage, StorageColumn, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + type SqlNamespace, + SqlStorage, + StorageColumn, + StorageTable, +} from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import sqliteControlTargetDescriptor from '../src/core/control-target'; import { SqliteContractSerializer } from '../src/core/sqlite-contract-serializer'; @@ -14,20 +19,18 @@ function makeContractWithTablesJson() { return createSqlContract({ target: 'sqlite', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'INTEGER', codecId: 'sqlite/integer@1', nullable: false }, - email: { nativeType: 'TEXT', codecId: 'sqlite/text@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'INTEGER', codecId: 'sqlite/integer@1', nullable: false }, + email: { nativeType: 'TEXT', codecId: 'sqlite/text@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, @@ -45,7 +48,9 @@ describe('SqliteContractSerializer', () => { const serializer = new SqliteContractSerializer(); const contract = serializer.deserializeContract(makeValidContractJson()); expect(contract.targetFamily).toBe('sql'); - expect(getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables).toEqual({}); + expect( + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables, + ).toEqual({}); }); it('hydrates JSON storage into the SQL Contract IR class hierarchy', () => { @@ -53,9 +58,8 @@ describe('SqliteContractSerializer', () => { const contract = serializer.deserializeContract(makeContractWithTablesJson()); expect(contract.storage).toBeInstanceOf(SqlStorage); - const userTable = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!.tables['user'] as - | StorageTable - | undefined; + const userTable = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)! + .tables['user'] as StorageTable | undefined; expect(userTable).toBeInstanceOf(StorageTable); expect(userTable?.columns['id']).toBeInstanceOf(StorageColumn); }); @@ -72,12 +76,12 @@ describe('SqliteContractSerializer', () => { const json = serializer.serializeContract(contract); const reparsed = JSON.parse(JSON.stringify(json)); expect(reparsed.storage).not.toHaveProperty('kind'); - expect( - getStorageNamespace(reparsed.storage, UNBOUND_NAMESPACE_ID).tables.user, - ).not.toHaveProperty('kind'); - expect( - getStorageNamespace(reparsed.storage, UNBOUND_NAMESPACE_ID).tables.user.columns.id, - ).not.toHaveProperty('kind'); + const reparsedUnbound = getStorageNamespace( + reparsed.storage, + UNBOUND_NAMESPACE_ID, + )!; + expect(reparsedUnbound.tables['user']).not.toHaveProperty('kind'); + expect(reparsedUnbound.tables['user']!.columns['id']).not.toHaveProperty('kind'); }); }); From 697fd57803e010d7eee786aeff308e1d830ffab3 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:34:42 +0200 Subject: [PATCH 21/60] fix(adapter-postgres): complete flat-storage migration in renderer and tests Trims the unused walk-helper imports in the SQL renderer, selects `SqlNamespace` at the `.tables` lookup, and routes every migration test fixture through `buildSqlStorageInput`, finishing the wrapper drop for the postgres adapter. Signed-off-by: Will Madden --- .../postgres/src/core/sql-renderer.ts | 11 +- .../cross-namespace-fk.integration.test.ts | 98 ++++++------ .../db-init-update.cli.integration.test.ts | 52 ++++--- .../migrations/fixtures/runner-fixtures.ts | 44 +++--- .../planner.authoring-surface.test.ts | 34 +++-- .../planner.codec-field-event.test.ts | 15 +- .../test/migrations/planner.fk-config.test.ts | 94 ++++++------ ...planner.reconciliation.integration.test.ts | 141 ++++++++++-------- .../migrations/planner.reconciliation.test.ts | 20 ++- .../planner.referential-actions.test.ts | 55 +++---- .../planner.semantic-satisfaction.test.ts | 11 +- .../planner.storage-types.integration.test.ts | 63 ++++---- .../runner.multi-space.integration.test.ts | 20 ++- ...nner.unbound-namespace.integration.test.ts | 44 +++--- ...ma-verify.after-runner.integration.test.ts | 124 ++++++++------- .../6-adapters/postgres/test/test-utils.ts | 11 +- 16 files changed, 462 insertions(+), 375 deletions(-) diff --git a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts index c33ac53cbe..d8fd7265e5 100644 --- a/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts +++ b/packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts @@ -1,10 +1,7 @@ import type { CodecLookup } from '@prisma-next/framework-components/codec'; -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import { runtimeError } from '@prisma-next/framework-components/runtime'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { type AggregateExpr, type AnyExpression, @@ -684,9 +681,7 @@ function getInsertColumnOrder( } let table: { columns: Record } | undefined; - for (const ns of storageNamespaceValues(contract.storage)) { - // Namespace.tables is Record at the interface level; - // SQL family namespaces hold StorageTable instances which have .columns. + for (const ns of storageNamespaceValues(contract.storage)) { const found = ns.tables[tableName] as { columns: Record } | undefined; if (found !== undefined) { table = found; diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/cross-namespace-fk.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/cross-namespace-fk.integration.test.ts index b54f293aaf..93e524dbf6 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/cross-namespace-fk.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/cross-namespace-fk.integration.test.ts @@ -2,7 +2,11 @@ import { asNamespaceId, type Contract, coreHash, profileHash } from '@prisma-nex import { INIT_ADDITIVE_POLICY } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { createDriver, @@ -27,55 +31,57 @@ function buildCrossNamespaceFkContract(): Contract { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:cross-ns-fk'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:cross-ns-fk'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - post: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - author_id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'post', - columns: ['author_id'], - }, - target: { - namespaceId: asNamespaceId('auth'), - tableName: 'user', - columns: ['id'], - }, - constraint: true, - index: false, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:cross-ns-fk'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + post: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + author_id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, }, - ], + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'post', + columns: ['author_id'], + }, + target: { + namespaceId: asNamespaceId('auth'), + tableName: 'user', + columns: ['id'], + }, + constraint: true, + index: false, + }, + ], + }, }, - }, - }), - auth: buildSqlNamespace({ - id: 'auth', - tables: { - user: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }), + auth: buildSqlNamespace({ + id: 'auth', + tables: { + user: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/db-init-update.cli.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/db-init-update.cli.integration.test.ts index b0f94615a1..3a43d5aa5f 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/db-init-update.cli.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/db-init-update.cli.integration.test.ts @@ -9,7 +9,11 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { computeMigrationHash } from '@prisma-next/migration-tools/hash'; import { materialiseMigrationPackage } from '@prisma-next/migration-tools/io'; import { emitContractSpaceArtefacts } from '@prisma-next/migration-tools/spaces'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { contract as appContract, @@ -48,30 +52,32 @@ function buildExtensionContract(version: 1 | 2): Contract { target: 'postgres', targetFamily: 'sql', profileHash: profileHash(`sha256:pg-ext-test-v${version}`), - storage: new SqlStorage({ - storageHash: coreHash(`sha256:pg-ext-contract-v${version}`), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - _ext_helper: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - ...(version === 2 - ? { - note: { nativeType: 'text', codecId: 'pg/text@1', nullable: true }, - } - : {}), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(`sha256:pg-ext-contract-v${version}`), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + _ext_helper: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + ...(version === 2 + ? { + note: { nativeType: 'text', codecId: 'pg/text@1', nullable: true }, + } + : {}), + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts b/packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts index 78980b10f2..1314f130ef 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts @@ -6,7 +6,11 @@ import sqlFamilyDescriptor, { } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import postgresTargetDescriptor from '@prisma-next/target-postgres/control'; import type { PostgresPlanTargetDetails } from '@prisma-next/target-postgres/planner-target-details'; @@ -18,26 +22,28 @@ export const contract: Contract = { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [{ columns: ['email'] }], + indexes: [{ columns: ['email'] }], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [{ columns: ['email'] }], - indexes: [{ columns: ['email'] }], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.authoring-surface.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.authoring-surface.test.ts index 4fcc40a476..b231ef860c 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.authoring-surface.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.authoring-surface.test.ts @@ -10,7 +10,11 @@ import { type MigrationPlannerSuccessResult, } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import postgresTargetDescriptor, { postgresRenderDefault, } from '@prisma-next/target-postgres/control'; @@ -29,12 +33,14 @@ function createEmptyContract(): Contract { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -63,12 +69,14 @@ describe('PostgresMigrationPlanner authoring surface', () => { const fromContract: Contract = { ...createEmptyContract(), - storage: new SqlStorage({ - storageHash: coreHash('sha256:from'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:from'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), + }, + }), + ), }; const result = planner.plan({ contract, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.codec-field-event.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.codec-field-event.test.ts index 5ca447edcb..2831d88355 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.codec-field-event.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.codec-field-event.test.ts @@ -6,6 +6,7 @@ import { APP_SPACE_ID, type OpFactoryCall } from '@prisma-next/framework-compone import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -33,12 +34,14 @@ function contract(tables: Record, hash = 'sha256:c'): Cont target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash(hash), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(hash), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ), models: {}, roots: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.fk-config.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.fk-config.test.ts index 58aabe17ec..b417409df4 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.fk-config.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.fk-config.test.ts @@ -2,7 +2,11 @@ import { asNamespaceId, type Contract, coreHash, profileHash } from '@prisma-nex import { INIT_ADDITIVE_POLICY } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { createPostgresMigrationPlanner } from '@prisma-next/target-postgres/planner'; import { describe, expect, it } from 'vitest'; @@ -16,52 +20,54 @@ function createFkTestContract(fkConfig: { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], - }, - post: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - userId: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: (fkConfig.includeUserIndex ?? true) ? [{ columns: ['userId'] }] : [], - foreignKeys: [ - { - source: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'post', - columns: ['userId'], - }, - target: { - namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), - tableName: 'user', - columns: ['id'], - }, - constraint: fkConfig.constraint, - index: fkConfig.index, + post: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + userId: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, }, - ], + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: (fkConfig.includeUserIndex ?? true) ? [{ columns: ['userId'] }] : [], + foreignKeys: [ + { + source: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'post', + columns: ['userId'], + }, + target: { + namespaceId: asNamespaceId(UNBOUND_NAMESPACE_ID), + tableName: 'user', + columns: ['id'], + }, + constraint: fkConfig.constraint, + index: fkConfig.index, + }, + ], + }, }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.integration.test.ts index 96284fd8b4..bfeeac880c 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.integration.test.ts @@ -5,7 +5,12 @@ import { type MigrationOperationPolicy, } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage, type StorageTable } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, + type StorageTable, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { PostgresEnumType } from '@prisma-next/target-postgres/types'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; @@ -34,12 +39,14 @@ function makeContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash(`sha256:reconciliation-integ-${hashSuffix}`), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(`sha256:reconciliation-integ-${hashSuffix}`), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -992,38 +999,40 @@ describe.sequential('PostgresMigrationPlanner - reconciliation integration', () target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:reconciliation-integ-text-to-enum-updated'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - item: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - status: { - nativeType: 'status_type', - codecId: 'pg/enum@1', - nullable: false, - typeRef: 'status_type', + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:reconciliation-integ-text-to-enum-updated'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + item: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + status: { + nativeType: 'status_type', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'status_type', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - enum: { - status_type: new PostgresEnumType({ - name: 'status_type', - nativeType: 'status_type', - values: ['active', 'inactive'], - }), - }, - }), - }, - }), + enum: { + status_type: new PostgresEnumType({ + name: 'status_type', + nativeType: 'status_type', + values: ['active', 'inactive'], + }), + }, + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -1268,38 +1277,40 @@ describe.sequential('PostgresMigrationPlanner - reconciliation integration', () target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:reconciliation-integ-text-to-mixed-enum-updated'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - item: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - status: { - nativeType: 'StatusType', - codecId: 'pg/enum@1', - nullable: false, - typeRef: 'StatusType', + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:reconciliation-integ-text-to-mixed-enum-updated'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + item: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + status: { + nativeType: 'StatusType', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'StatusType', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - enum: { - StatusType: new PostgresEnumType({ - name: 'StatusType', - nativeType: 'StatusType', - values: ['active', 'inactive'], - }), - }, - }), - }, - }), + enum: { + StatusType: new PostgresEnumType({ + name: 'StatusType', + nativeType: 'StatusType', + values: ['active', 'inactive'], + }), + }, + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.test.ts index ff00aae139..8283307c47 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.reconciliation.test.ts @@ -4,7 +4,11 @@ import { type MigrationOperationPolicy, } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import { createPostgresMigrationPlanner } from '@prisma-next/target-postgres/planner'; import { describe, expect, it } from 'vitest'; @@ -179,12 +183,14 @@ function createContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:reconciliation-contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:reconciliation-contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.referential-actions.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.referential-actions.test.ts index 49ddad8fa5..a690b882f1 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.referential-actions.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.referential-actions.test.ts @@ -4,6 +4,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, type ForeignKey, type ReferentialAction, SqlStorage, @@ -37,35 +38,37 @@ function createRefActionContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], - }, - post: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - userId: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + post: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + userId: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [fk], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [fk], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.semantic-satisfaction.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.semantic-satisfaction.test.ts index 271ce4efac..cd91aea68f 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.semantic-satisfaction.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.semantic-satisfaction.test.ts @@ -12,6 +12,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageTableInput, } from '@prisma-next/sql-contract/types'; @@ -219,10 +220,12 @@ function createTestContract(tables: Record = {}): Con target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/planner.storage-types.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/planner.storage-types.integration.test.ts index 9d00da8260..fb35c45adf 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/planner.storage-types.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/planner.storage-types.integration.test.ts @@ -2,7 +2,11 @@ import { type Contract, coreHash, profileHash } from '@prisma-next/contract/type import { INIT_ADDITIVE_POLICY } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { PostgresEnumType } from '@prisma-next/target-postgres/types'; import { expectNarrowedType } from '@prisma-next/test-utils/typed-expectations'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; @@ -23,33 +27,40 @@ const contractWithEnum: Contract = { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - role: { nativeType: 'role', codecId: 'pg/enum@1', nullable: false, typeRef: 'Role' }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + role: { + nativeType: 'role', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'Role', + }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - enum: { - Role: new PostgresEnumType({ - name: 'Role', - nativeType: 'role', - values: ['USER', 'ADMIN'], - }), - }, - }), - }, - }), + enum: { + Role: new PostgresEnumType({ + name: 'Role', + nativeType: 'role', + values: ['USER', 'ADMIN'], + }), + }, + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/runner.multi-space.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/runner.multi-space.integration.test.ts index 123e095099..8699178718 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/runner.multi-space.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/runner.multi-space.integration.test.ts @@ -1,7 +1,11 @@ import { type Contract, coreHash, profileHash } from '@prisma-next/contract/types'; import { INIT_ADDITIVE_POLICY } from '@prisma-next/family-sql/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { PostgresPlanTargetDetails } from '@prisma-next/target-postgres/planner-target-details'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { @@ -21,12 +25,14 @@ const extensionContract: Contract = { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:ext-test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:ext-contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:ext-contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/runner.unbound-namespace.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/runner.unbound-namespace.integration.test.ts index 532403c1e3..3ab3cd4b00 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/runner.unbound-namespace.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/runner.unbound-namespace.integration.test.ts @@ -5,7 +5,11 @@ import { } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { createDriver, @@ -42,26 +46,28 @@ function buildUnboundContract(): Contract { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:unbound-multi-tenant'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:unbound-multi-tenant'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - tenant: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - label: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:unbound-multi-tenant'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + tenant: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + label: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/migrations/schema-verify.after-runner.integration.test.ts b/packages/3-targets/6-adapters/postgres/test/migrations/schema-verify.after-runner.integration.test.ts index 7f818c75c4..a76b911668 100644 --- a/packages/3-targets/6-adapters/postgres/test/migrations/schema-verify.after-runner.integration.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/migrations/schema-verify.after-runner.integration.test.ts @@ -2,7 +2,11 @@ import { type Contract, coreHash, profileHash } from '@prisma-next/contract/type import { INIT_ADDITIVE_POLICY } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { PostgresEnumType } from '@prisma-next/target-postgres/types'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { @@ -119,37 +123,39 @@ describe.sequential('Schema verification after runner - integration', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract-with-defaults'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { - nativeType: 'int4', - codecId: 'pg/int4@1', - nullable: false, - default: { kind: 'function', expression: 'autoincrement()' }, - }, - createdAt: { - nativeType: 'timestamptz', - codecId: 'pg/timestamptz@1', - nullable: false, - default: { kind: 'function', expression: 'now()' }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract-with-defaults'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { + nativeType: 'int4', + codecId: 'pg/int4@1', + nullable: false, + default: { kind: 'function', expression: 'autoincrement()' }, + }, + createdAt: { + nativeType: 'timestamptz', + codecId: 'pg/timestamptz@1', + nullable: false, + default: { kind: 'function', expression: 'now()' }, + }, + email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, }, - email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -183,38 +189,40 @@ describe.sequential('Schema verification after runner - integration', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract-enum-mixed-case'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - Organization: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - billingState: { - nativeType: 'BillingState', - codecId: 'pg/enum@1', - nullable: false, - typeRef: 'BillingState', + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract-enum-mixed-case'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + Organization: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + billingState: { + nativeType: 'BillingState', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'BillingState', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - enum: { - BillingState: new PostgresEnumType({ - name: 'BillingState', - nativeType: 'BillingState', - values: ['ok', 'atRisk', 'blocked'], - }), - }, - }), - }, - }), + enum: { + BillingState: new PostgresEnumType({ + name: 'BillingState', + nativeType: 'BillingState', + values: ['ok', 'atRisk', 'blocked'], + }), + }, + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/postgres/test/test-utils.ts b/packages/3-targets/6-adapters/postgres/test/test-utils.ts index b834c2404c..7fc8dc7299 100644 --- a/packages/3-targets/6-adapters/postgres/test/test-utils.ts +++ b/packages/3-targets/6-adapters/postgres/test/test-utils.ts @@ -10,6 +10,7 @@ import type { Contract } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageTableInput, type StorageTypeInstance, @@ -24,10 +25,12 @@ export function createTestContract( tables: overrides.tables ?? {}, }); return createContract({ - storage: new SqlStorage({ - storageHash: (overrides.storageHash ?? 'sha256:test') as never, - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: (overrides.storageHash ?? 'sha256:test') as never, + namespaces: { [UNBOUND_NAMESPACE_ID]: unboundNs }, + }), + ), }); } From 8cd119f8df2d1b7a2454125743ecc638198ab420 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:38:39 +0200 Subject: [PATCH 22/60] fix(adapter-sqlite): complete flat-storage migration in tests Routes every sqlite migration test fixture through `buildSqlStorageInput` (namespace ids keyed directly under `storage`), finishing the wrapper drop for the sqlite adapter that the mechanical pass left incomplete. Signed-off-by: Will Madden --- .../migrations/db-init-update.cli.test.ts | 96 ++++++++++--------- .../migrations/fixtures/runner-fixtures.ts | 44 +++++---- .../planner-introspection.integration.test.ts | 15 +-- .../planner.codec-field-event.test.ts | 15 +-- .../sqlite/test/migrations/planner.test.ts | 15 +-- .../migrations/runner.multi-space.test.ts | 20 ++-- 6 files changed, 117 insertions(+), 88 deletions(-) diff --git a/packages/3-targets/6-adapters/sqlite/test/migrations/db-init-update.cli.test.ts b/packages/3-targets/6-adapters/sqlite/test/migrations/db-init-update.cli.test.ts index 13fe611f88..16e4edb145 100644 --- a/packages/3-targets/6-adapters/sqlite/test/migrations/db-init-update.cli.test.ts +++ b/packages/3-targets/6-adapters/sqlite/test/migrations/db-init-update.cli.test.ts @@ -15,7 +15,11 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { computeMigrationHash } from '@prisma-next/migration-tools/hash'; import { materialiseMigrationPackage } from '@prisma-next/migration-tools/io'; import { emitContractSpaceArtefacts } from '@prisma-next/migration-tools/spaces'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { timeouts } from '@prisma-next/test-utils'; import { join } from 'pathe'; import { afterEach, describe, expect, it } from 'vitest'; @@ -57,30 +61,32 @@ function buildExtensionContract(version: 1 | 2): Contract { target: 'sqlite', targetFamily: 'sql', profileHash: profileHash(`sha256:ext-test-v${version}`), - storage: new SqlStorage({ - storageHash: coreHash(`sha256:ext-contract-v${version}`), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - _ext_helper: { - columns: { - id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, - ...(version === 2 - ? { - note: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: true }, - } - : {}), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(`sha256:ext-contract-v${version}`), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + _ext_helper: { + columns: { + id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, + ...(version === 2 + ? { + note: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: true }, + } + : {}), + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -443,30 +449,32 @@ describe('db init / db update aggregate pipeline (CLI) - sqlite', { const hookedAppContract: Contract = { ...appContract, - storage: new SqlStorage({ - storageHash: coreHash('sha256:app-with-hooked-email'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, - email: { - nativeType: 'text', - codecId: HOOKED_CODEC, - nullable: false, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:app-with-hooked-email'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, + email: { + nativeType: 'text', + codecId: HOOKED_CODEC, + nullable: false, + }, }, + primaryKey: { columns: ['id'] }, + uniques: [{ columns: ['email'] }], + indexes: [{ columns: ['email'] }], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [{ columns: ['email'] }], - indexes: [{ columns: ['email'] }], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), profileHash: profileHash('sha256:app-with-hooked-email'), }; diff --git a/packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts b/packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts index 9f0f13d9cb..f483061e35 100644 --- a/packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts +++ b/packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts @@ -10,7 +10,11 @@ import sqlFamilyDescriptor, { } from '@prisma-next/family-sql/control'; import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; import sqliteTargetDescriptor from '@prisma-next/target-sqlite/control'; import type { SqlitePlanTargetDetails } from '@prisma-next/target-sqlite/planner-target-details'; @@ -21,26 +25,28 @@ export const contract: Contract = { target: 'sqlite', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, - email: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: false }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'integer', codecId: 'sqlite/integer@1', nullable: false }, + email: { nativeType: 'text', codecId: 'sqlite/text@1', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [{ columns: ['email'] }], + indexes: [{ columns: ['email'] }], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [{ columns: ['email'] }], - indexes: [{ columns: ['email'] }], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/sqlite/test/migrations/planner-introspection.integration.test.ts b/packages/3-targets/6-adapters/sqlite/test/migrations/planner-introspection.integration.test.ts index ddc883d03b..fa99346123 100644 --- a/packages/3-targets/6-adapters/sqlite/test/migrations/planner-introspection.integration.test.ts +++ b/packages/3-targets/6-adapters/sqlite/test/migrations/planner-introspection.integration.test.ts @@ -13,6 +13,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -63,12 +64,14 @@ function makeContract(tables: Record): Contract, hash = 'sha256:c'): Cont target: 'sqlite', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash(hash), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(hash), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ), models: {}, roots: {}, capabilities: {}, diff --git a/packages/3-targets/6-adapters/sqlite/test/migrations/planner.test.ts b/packages/3-targets/6-adapters/sqlite/test/migrations/planner.test.ts index e2fc1b5119..51b4a21eeb 100644 --- a/packages/3-targets/6-adapters/sqlite/test/migrations/planner.test.ts +++ b/packages/3-targets/6-adapters/sqlite/test/migrations/planner.test.ts @@ -3,6 +3,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, type StorageColumn, type StorageTable, @@ -34,12 +35,14 @@ function makeContract(tables: Record): Contract = { target: 'sqlite', targetFamily: 'sql', profileHash: profileHash('sha256:ext-test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:ext-contract'), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:ext-contract'), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables: {} }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, From 2445ea0997f77674376f382063a3a1dbeda001e0 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:49:43 +0200 Subject: [PATCH 23/60] fix(sql-builder): derive flat-storage table bound via constrained infer The flat `TableProxyContract` constraint only requires the reserved `storageHash` key (intersecting `Record` would force `storageHash` itself to be a namespace, which the reserved-key storage shape cannot satisfy and which broke `Db>` in the adapter runtimes). The table-name walk re-establishes the `StorageTable` upper bound the prior `namespaces`-keyed constraint gave via `infer Tables extends Record`, and excludes the reserved sibling keys. Drops the now-unused walk-helper imports in the runtime and routes the field-proxy fixture through `buildSqlStorageInput`. This is the reserved-key typing decision, not the deferred multi-namespace DSL surface (TML-2745). Signed-off-by: Will Madden --- .../4-lanes/sql-builder/src/runtime/sql.ts | 6 +-- .../2-sql/4-lanes/sql-builder/src/types/db.ts | 36 ++++++++++++++--- .../test/runtime/field-proxy.test.ts | 39 ++++++++++++------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts index 0e43478cbe..bbfdcfd42b 100644 --- a/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts +++ b/packages/2-sql/4-lanes/sql-builder/src/runtime/sql.ts @@ -1,9 +1,5 @@ import type { Contract } from '@prisma-next/contract/types'; -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression'; import type { ExecutionContext } from '@prisma-next/sql-relational-core/query-lane-context'; diff --git a/packages/2-sql/4-lanes/sql-builder/src/types/db.ts b/packages/2-sql/4-lanes/sql-builder/src/types/db.ts index b2f492c556..e86be92e2d 100644 --- a/packages/2-sql/4-lanes/sql-builder/src/types/db.ts +++ b/packages/2-sql/4-lanes/sql-builder/src/types/db.ts @@ -3,8 +3,9 @@ import type { TableProxy } from './table-proxy'; export type CapabilitiesBase = Record>; -type StorageNamespaceEntry = { readonly tables: Readonly> }; - +// Reserved sibling keys under the flat `storage` plane (ADR 221): they sit +// alongside the namespace-id keys but are not namespaces, so the +// table-name walk below excludes them. type StorageNamespaceKey = Exclude; // The sql-builder DSL is flat by table name across every declared @@ -13,10 +14,17 @@ type StorageNamespaceKey = Exclude; // first call site); landing the namespace-aware DSL surface (db..
) // is tracked separately. Within scope here: the DSL accepts the // flat storage shape directly and walks every namespace. +// +// The constraint only requires the reserved `storageHash` key to be +// present; namespace-id keys are read structurally per concrete contract +// (the `SqlStorage` class type exposes them, as does the emitted +// `contract.d.ts`). Intersecting a `Record` here +// would force `storageHash` itself to be a namespace entry, which the +// reserved-key storage shape cannot satisfy. export type TableProxyContract = { readonly storage: { readonly storageHash: string; - } & Readonly>; + }; readonly capabilities: CapabilitiesBase; }; @@ -26,13 +34,29 @@ export type UnboundTables = { readonly [Name in TableNamesAcrossNamespaces]: TableInAnyNamespace; }; +// Each non-reserved key holds a namespace entry whose `tables` map we walk. +// `.tables` is pulled out with a constrained `infer` (rather than indexed +// directly) for two reasons: the reserved `storageHash` sibling — which the +// constraint permits but which is not a namespace — never has `['tables']` +// indexed into it, and `infer Tables extends Record` +// re-establishes the `StorageTable` upper bound that the prior +// `namespaces`-keyed constraint supplied, so downstream `Scope`/`ScopeTable` +// derivations still see a `StorageTable`. type TableNamesAcrossNamespaces = { - [NSId in StorageNamespaceKey]: keyof C['storage'][NSId]['tables'] & string; + [NSId in StorageNamespaceKey]: C['storage'][NSId] extends { + readonly tables: infer Tables extends Record; + } + ? keyof Tables & string + : never; }[StorageNamespaceKey]; type TableInAnyNamespace = { - [NSId in StorageNamespaceKey]: Name extends keyof C['storage'][NSId]['tables'] - ? C['storage'][NSId]['tables'][Name] + [NSId in StorageNamespaceKey]: C['storage'][NSId] extends { + readonly tables: infer Tables extends Record; + } + ? Name extends keyof Tables + ? Tables[Name] + : never : never; }[StorageNamespaceKey]; diff --git a/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts b/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts index 33e6a0eaf3..bb58ae7aaa 100644 --- a/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts +++ b/packages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.ts @@ -1,6 +1,11 @@ import { coreHash } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage, type StorageTable } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, + type StorageTable, +} from '@prisma-next/sql-contract/types'; import { ColumnRef, IdentifierRef } from '@prisma-next/sql-relational-core/ast'; import { describe, expect, it } from 'vitest'; import { tableToScope } from '../../src/runtime/builder-base'; @@ -73,21 +78,25 @@ describe('createFieldProxy', () => { indexes: [], foreignKeys: [], }; - const storage = new SqlStorage({ - storageHash: coreHash('sha256:h'), - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { Post: table }, - }), - types: { - Embedding1536: { - kind: 'codec-instance', - codecId: 'pgvector/vector@1', - nativeType: 'vector', - typeParams: { length: 1536 }, + const storage = new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:h'), + types: { + Embedding1536: { + kind: 'codec-instance', + codecId: 'pgvector/vector@1', + nativeType: 'vector', + typeParams: { length: 1536 }, + }, }, - }, - }); + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { Post: table }, + }), + }, + }), + ); const scope = tableToScope('post_alias', table, { storage, tableName: 'Post' }); expect(scope.namespaces['post_alias']?.['embedding']?.codec).toEqual({ codecId: 'pgvector/vector@1', From 751b46d153c4217c2cec36eb6501ac7591354ea3 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:56:58 +0200 Subject: [PATCH 24/60] fix(sql-orm-client): complete flat-storage migration in source and tests Imports the generic walk helper across the model accessor, collection contract, where-binding, and query-plan helpers; selects `SqlNamespace` at `.tables` lookups; flattens the `NamespaceTableDef` source type to walk namespace-id keys directly (excluding the reserved `storageHash`/`types` siblings); and gives the test helper a mutable namespace view for in-place fixture assembly. Signed-off-by: Will Madden --- .../sql-orm-client/src/collection-contract.ts | 10 +++---- .../sql-orm-client/src/model-accessor.ts | 10 +++---- .../sql-orm-client/src/query-plan-meta.ts | 10 +++---- .../src/query-plan-mutations.ts | 10 +++---- .../3-extensions/sql-orm-client/src/types.ts | 10 +++++-- .../sql-orm-client/src/where-binding.ts | 10 +++---- .../sql-orm-client/test/helpers.ts | 27 ++++++++++++++----- .../test/mutation-executor.test.ts | 2 +- .../sql-orm-client/test/unbound-tables.ts | 2 +- 9 files changed, 56 insertions(+), 35 deletions(-) diff --git a/packages/3-extensions/sql-orm-client/src/collection-contract.ts b/packages/3-extensions/sql-orm-client/src/collection-contract.ts index 1e0f5c3c6a..7d65c6c6da 100644 --- a/packages/3-extensions/sql-orm-client/src/collection-contract.ts +++ b/packages/3-extensions/sql-orm-client/src/collection-contract.ts @@ -1,6 +1,6 @@ import type { Contract, ContractFieldType, CrossReference } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import type { RelationCardinalityTag } from './types'; type ModelStorageFields = Record; @@ -31,9 +31,9 @@ export interface PolymorphismInfo { } function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as - | StorageTable - | undefined; + return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[ + tableName + ] as StorageTable | undefined; } function modelsOf(contract: Contract): ModelsMap { diff --git a/packages/3-extensions/sql-orm-client/src/model-accessor.ts b/packages/3-extensions/sql-orm-client/src/model-accessor.ts index eeafeb53d6..9efcd43790 100644 --- a/packages/3-extensions/sql-orm-client/src/model-accessor.ts +++ b/packages/3-extensions/sql-orm-client/src/model-accessor.ts @@ -1,6 +1,6 @@ import type { Contract } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import type { SqlOperationEntry } from '@prisma-next/sql-operations'; import { AndExpr, @@ -117,9 +117,9 @@ function resolveColumn( tableName: string, columnName: string, ): { readonly codecId: string; readonly nullable: boolean } | undefined { - const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as - | StorageTable - | undefined; + const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[ + tableName + ] as StorageTable | undefined; const column = table?.columns?.[columnName]; if (!column) return undefined; return { codecId: column.codecId, nullable: column.nullable }; diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts index f534e8008a..e4936daa74 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-meta.ts @@ -1,7 +1,7 @@ import type { Contract, PlanMeta } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import type { AnnotationValue, OperationKind } from '@prisma-next/framework-components/runtime'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import { type AnyQueryAst, collectOrderedParamRefs } from '@prisma-next/sql-relational-core/ast'; import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan'; import { ifDefined } from '@prisma-next/utils/defined'; @@ -15,9 +15,9 @@ export function deriveParamsFromAst(ast: AnyQueryAst): { } export function resolveTableColumns(contract: Contract, tableName: string): string[] { - const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as - | StorageTable - | undefined; + const table = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[ + tableName + ] as StorageTable | undefined; if (!table) { throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`); } diff --git a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts index c74316b4c1..f5704f7b84 100644 --- a/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts +++ b/packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts @@ -1,6 +1,6 @@ import type { Contract } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import { type AnyExpression, ColumnRef, @@ -20,9 +20,9 @@ import { buildOrmQueryPlan, deriveParamsFromAst, resolveTableColumns } from './q import { combineWhereExprs } from './where-utils'; function unboundTable(contract: Contract, tableName: string): StorageTable | undefined { - return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[tableName] as - | StorageTable - | undefined; + return getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[ + tableName + ] as StorageTable | undefined; } function buildReturningColumns( diff --git a/packages/3-extensions/sql-orm-client/src/types.ts b/packages/3-extensions/sql-orm-client/src/types.ts index bbe9d2c887..4ac2c0fe3b 100644 --- a/packages/3-extensions/sql-orm-client/src/types.ts +++ b/packages/3-extensions/sql-orm-client/src/types.ts @@ -577,15 +577,21 @@ type FieldColumnName< : FieldName) & string; +// Namespace ids are direct keys under the flat `storage` plane (ADR 221); +// `storageHash` / `types` are reserved sibling keys, not namespaces, so the +// walk excludes them before pulling each namespace's `tables` map. type NamespaceTableDef, TableName extends string> = { - [K in keyof TContract['storage']['namespaces']]: TContract['storage']['namespaces'][K] extends { + [K in Exclude< + keyof TContract['storage'], + 'storageHash' | 'types' + >]: TContract['storage'][K] extends { readonly tables: infer Tables; } ? TableName extends keyof Tables ? Tables[TableName] : never : never; -}[keyof TContract['storage']['namespaces']]; +}[Exclude]; type ResolvedStorageColumn< TContract extends Contract, diff --git a/packages/3-extensions/sql-orm-client/src/where-binding.ts b/packages/3-extensions/sql-orm-client/src/where-binding.ts index 3d1df735f9..52da54b0b0 100644 --- a/packages/3-extensions/sql-orm-client/src/where-binding.ts +++ b/packages/3-extensions/sql-orm-client/src/where-binding.ts @@ -1,6 +1,6 @@ import type { Contract } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; import { AndExpr, type AnyExpression, @@ -131,9 +131,9 @@ function createParamRef( ): ParamRef { if ( !( - getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[columnRef.table] as - | StorageTable - | undefined + getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)?.tables[ + columnRef.table + ] as StorageTable | undefined )?.columns[columnRef.column] ) { throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`); diff --git a/packages/3-extensions/sql-orm-client/test/helpers.ts b/packages/3-extensions/sql-orm-client/test/helpers.ts index 31769d95ff..3c79332544 100644 --- a/packages/3-extensions/sql-orm-client/test/helpers.ts +++ b/packages/3-extensions/sql-orm-client/test/helpers.ts @@ -4,7 +4,16 @@ import type { CodecDescriptor, CodecInstanceContext, } from '@prisma-next/framework-components/codec'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; + +// Mutable namespace view for assembling raw test contracts in place. The +// runtime fixtures key namespaces directly under `storage` (ADR 221 flat +// shape); this loose shape lets the helpers populate `tables` without the +// readonly SQL IR types fighting the in-place mutation. +type MutableNamespace = { + tables: Record; [key: string]: unknown }>; +}; + import { AsyncIterableResult } from '@prisma-next/framework-components/runtime'; import type { Codec, SelectAst } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan'; @@ -154,7 +163,7 @@ export function buildMixedPolyContract(): TestContract { base: 'Task', }; - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.tasks = { + (getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace).tables.tasks = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, @@ -167,7 +176,7 @@ export function buildMixedPolyContract(): TestContract { foreignKeys: [], }; - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.features = { + (getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace).tables.features = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, priority: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, @@ -215,17 +224,23 @@ export function buildStiPolyContract(): TestContract { base: 'User', }; - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.kind = { + ( + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace + ).tables.users.columns.kind = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, }; - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.role = { + ( + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace + ).tables.users.columns.role = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, }; - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID).tables.users.columns.plan = { + ( + getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace + ).tables.users.columns.plan = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, diff --git a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts index 8b2d058071..dc6cf62d81 100644 --- a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts +++ b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts @@ -1,4 +1,4 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { type AnyExpression, BinaryExpr, diff --git a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts index 69cd883e4d..86ad9364ad 100644 --- a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts +++ b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts @@ -1,4 +1,4 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; type StorageLike = { From 58a225253e5731214dd7b14a5e1d0b099000a795 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 22:57:07 +0200 Subject: [PATCH 25/60] fix(extensions): complete flat-storage migration in cipherstash/pgvector/postgres tests Adds the generic walk-helper imports, selects `SqlNamespace` at `.tables`/`.enum` lookups, repairs a mechanically-mangled `getStorageNamespace` call in the cipherstash descriptor test, and routes the cipherstash codec fixture through `buildSqlStorageInput`. Signed-off-by: Will Madden --- .../test/cipherstash-codec.test.ts | 21 ++++++++++++------- .../cipherstash/test/descriptor.test.ts | 9 +++----- .../test/psl-interpretation-numeric.test.ts | 5 +++-- .../psl-interpretation-other-types.test.ts | 5 +++-- .../test/psl-interpretation.test.ts | 5 +++-- .../pgvector/test/descriptor.test.ts | 12 +++++------ .../psl-namespace-qualifier-routing.test.ts | 14 ++++++++----- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/packages/3-extensions/cipherstash/test/cipherstash-codec.test.ts b/packages/3-extensions/cipherstash/test/cipherstash-codec.test.ts index 3ea2b9b75a..1c55d446c3 100644 --- a/packages/3-extensions/cipherstash/test/cipherstash-codec.test.ts +++ b/packages/3-extensions/cipherstash/test/cipherstash-codec.test.ts @@ -27,7 +27,12 @@ import { } from '@prisma-next/family-sql/control'; import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import { buildSqlNamespace, SqlStorage, type StorageTable } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, + type StorageTable, +} from '@prisma-next/sql-contract/types'; import { ifDefined } from '@prisma-next/utils/defined'; import { describe, expect, it } from 'vitest'; import cipherstashExtensionDescriptor from '../src/exports/control'; @@ -99,12 +104,14 @@ describe('planFieldEventOperations driving the cipherstash hook', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: 'sha256:test' as StorageHashBase, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: 'sha256:test' as StorageHashBase, + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), + }, + }), + ), models: {}, roots: {}, capabilities: {}, diff --git a/packages/3-extensions/cipherstash/test/descriptor.test.ts b/packages/3-extensions/cipherstash/test/descriptor.test.ts index 3df86defe2..b8e2e72f71 100644 --- a/packages/3-extensions/cipherstash/test/descriptor.test.ts +++ b/packages/3-extensions/cipherstash/test/descriptor.test.ts @@ -22,13 +22,10 @@ * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md */ -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import cipherstashExtensionDescriptor from '../src/exports/control'; import { @@ -53,7 +50,7 @@ describe('cipherstash extension descriptor (contract-space package layout)', () const space = cipherstashExtensionDescriptor.contractSpace; expect(space).toBeDefined(); const unboundTables = - space!.getStorageNamespace(contractJson.storage, '__unbound__')?.tables ?? {}; + getStorageNamespace(space!.contractJson.storage, '__unbound__')?.tables ?? {}; expect(Object.keys(unboundTables)).toEqual([EQL_V2_CONFIGURATION_TABLE]); }); diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts index d85d0e4c42..a12c04997c 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-numeric.test.ts @@ -11,8 +11,9 @@ * byte-for-byte (PSL/TS parity). */ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { interpretPslDocumentToSqlContract } from '@prisma-next/sql-contract-psl'; import { describe, expect, it } from 'vitest'; import cipherstashControl from '../src/exports/control'; @@ -56,7 +57,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDouble constructor', () => { it('lowers full args to a column with cipherstash/double@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts index 68ba7be935..d3757c4c82 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation-other-types.test.ts @@ -12,8 +12,9 @@ * `true` in every case. */ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { interpretPslDocumentToSqlContract } from '@prisma-next/sql-contract-psl'; import { describe, expect, it } from 'vitest'; import cipherstashControl from '../src/exports/control'; @@ -57,7 +58,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedDate constructor', () => { it('lowers full args to a column with cipherstash/date@1 codec, eql_v2_encrypted nativeType', () => { diff --git a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts index 44441270d9..03a77f3760 100644 --- a/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts +++ b/packages/3-extensions/cipherstash/test/psl-interpretation.test.ts @@ -28,8 +28,9 @@ * (`EncryptedDate`, `EncryptedBoolean`, `EncryptedJson`) */ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { interpretPslDocumentToSqlContract } from '@prisma-next/sql-contract-psl'; import { describe, expect, it } from 'vitest'; import cipherstashControl from '../src/exports/control'; @@ -73,7 +74,7 @@ type StorageView = { }; const asStorage = (storage: unknown): StorageView => storage as StorageView; const unboundTables = (s: StorageView) => - getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; + getStorageNamespace(s, UNBOUND_NAMESPACE_ID)?.tables ?? {}; describe('PSL interpretation: cipherstash.EncryptedString constructor', () => { it('lowers full args to a column with codecId, nativeType, typeParams', () => { diff --git a/packages/3-extensions/pgvector/test/descriptor.test.ts b/packages/3-extensions/pgvector/test/descriptor.test.ts index 286d3454b2..01ebc805bc 100644 --- a/packages/3-extensions/pgvector/test/descriptor.test.ts +++ b/packages/3-extensions/pgvector/test/descriptor.test.ts @@ -22,9 +22,10 @@ * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md */ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { VECTOR_CODEC_ID } from '../src/core/constants'; import { @@ -48,11 +49,10 @@ describe('pgvector extension descriptor (contract-space package layout)', () => it('exposes a contractSpace declaring the vector parameterised native type', () => { const space = pgvectorExtensionDescriptor.contractSpace; expect(space).toBeDefined(); - const namespaces = space!.contractJson.storage as Record as Record< - string, - { readonly tables?: Record } - >; - expect(Object.keys(namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {})).toEqual([]); + const unboundTables = + getStorageNamespace(space!.contractJson.storage, UNBOUND_NAMESPACE_ID) + ?.tables ?? {}; + expect(Object.keys(unboundTables)).toEqual([]); expect(space!.contractJson.storage.types).toBeDefined(); expect(space!.contractJson.storage.types?.[PGVECTOR_NATIVE_TYPE]).toMatchObject({ codecId: VECTOR_CODEC_ID, diff --git a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts index 30664581f8..9a3b75e270 100644 --- a/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts +++ b/packages/3-extensions/postgres/test/psl-namespace-qualifier-routing.test.ts @@ -1,7 +1,7 @@ import type { TargetPackRef } from '@prisma-next/framework-components/components'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { parsePslDocument } from '@prisma-next/psl-parser'; -import type { SqlStorage } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import { interpretPslDocumentToSqlContract } from '@prisma-next/sql-contract-psl'; import { PostgresSchema, @@ -65,7 +65,9 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect(getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['tenant']).toBeDefined(); + expect( + getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['tenant'], + ).toBeDefined(); // The storage map carries the Postgres target concretion (not the // SQL family placeholder) at the unbound slot. @@ -103,7 +105,7 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a return; } const storage = result.value.storage as SqlStorage; - expect(getStorageNamespace(storage, 'auth')?.tables['user']).toBeDefined(); + expect(getStorageNamespace(storage, 'auth')?.tables['user']).toBeDefined(); const namespace = getStorageNamespace(storage, 'auth'); expect(namespace).toBeInstanceOf(PostgresSchema); @@ -137,6 +139,8 @@ describe('PSL → SqlStorage.namespaces qualifier routing (FR15 slice 3 + FR16a // Top-level declarations lower to the unbound namespace — the // planner falls back to its `ctx.schemaName` (today `"public"`) // for DDL qualification. - expect(getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['post']).toBeDefined(); + expect( + getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables['post'], + ).toBeDefined(); }); }); From ca5a3b6cb08c5bce8511cb837f5eaa355ee12d81 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:02:14 +0200 Subject: [PATCH 26/60] fix(pgvector,cli): flatten remaining storage test fixtures Routes the pgvector planner test helpers and storage-types fixtures through `buildSqlStorageInput` with the namespaces-keyed input type, and drops the stale `namespaces: {}` wrapper from the cli contract-enrichment test IR. pgvector typechecks fully green. Signed-off-by: Will Madden --- .../control-api/contract-enrichment.test.ts | 2 +- .../test/migrations/planner.behavior.test.ts | 21 +- .../planner.contract-to-schema-ir.test.ts | 80 +++--- .../migrations/planner.storage-types.test.ts | 265 +++++++++--------- 4 files changed, 198 insertions(+), 170 deletions(-) diff --git a/packages/1-framework/3-tooling/cli/test/control-api/contract-enrichment.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/contract-enrichment.test.ts index b75e267c53..ca7b0381fe 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/contract-enrichment.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/contract-enrichment.test.ts @@ -10,7 +10,7 @@ function makeIR(overrides?: Partial): Contract { target: 'postgres', roots: {}, models: {}, - storage: { storageHash: coreHash('sha256:test'), namespaces: {} }, + storage: { storageHash: coreHash('sha256:test') }, extensionPacks: {}, capabilities: {}, profileHash: profileHash('sha256:test'), diff --git a/packages/3-extensions/pgvector/test/migrations/planner.behavior.test.ts b/packages/3-extensions/pgvector/test/migrations/planner.behavior.test.ts index c6a455d20f..09c114ee2f 100644 --- a/packages/3-extensions/pgvector/test/migrations/planner.behavior.test.ts +++ b/packages/3-extensions/pgvector/test/migrations/planner.behavior.test.ts @@ -12,8 +12,10 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, buildSqlNamespaceMap, + buildSqlStorageInput, SqlStorage, type SqlStorageInput, + type SqlStorageNamespacesInput, type StorageTable, } from '@prisma-next/sql-contract/types'; import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types'; @@ -533,7 +535,10 @@ describe('buildBuiltinIdentityValue (built-in fallback)', () => { function createTestContract( overrides?: Partial, 'storage'>> & { - storage?: Partial>; + storage?: { + readonly namespaces?: SqlStorageNamespacesInput['namespaces']; + readonly types?: SqlStorageInput['types']; + }; }, ): Contract { const storageHashValue = coreHash('sha256:contract'); @@ -589,16 +594,18 @@ function createTestContract( tables: defaultTables, }), } - ) as SqlStorageInput['namespaces']; + ) as SqlStorageNamespacesInput['namespaces']; return { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - ...(storageInput.types !== undefined ? { types: storageInput.types } : {}), - namespaces, - storageHash: storageHashValue, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: storageHashValue, + ...(storageInput.types !== undefined ? { types: storageInput.types } : {}), + namespaces, + }), + ), roots: {}, models: {}, capabilities: {}, diff --git a/packages/3-extensions/pgvector/test/migrations/planner.contract-to-schema-ir.test.ts b/packages/3-extensions/pgvector/test/migrations/planner.contract-to-schema-ir.test.ts index ff551c03ad..48d6df86d3 100644 --- a/packages/3-extensions/pgvector/test/migrations/planner.contract-to-schema-ir.test.ts +++ b/packages/3-extensions/pgvector/test/migrations/planner.contract-to-schema-ir.test.ts @@ -15,8 +15,9 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, - type SqlStorageInput, + type SqlStorageNamespacesInput, type StorageColumn, type StorageTable, } from '@prisma-next/sql-contract/types'; @@ -33,7 +34,7 @@ const expandParameterizedNativeType: NativeTypeExpander = (input) => { return hooks?.expandNativeType?.(input) ?? input.nativeType; }; -function ns(tables: Record): Pick { +function ns(tables: Record): Pick { return { namespaces: { [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ id: UNBOUND_NAMESPACE_ID, tables }), @@ -61,7 +62,7 @@ function table( } function createTestContract( - storage: Omit | SqlStorage, + storage: Omit | SqlStorage, overrides?: Partial>, ): Contract { const storageHashValue = coreHash('sha256:test'); @@ -72,7 +73,7 @@ function createTestContract( storage: storage instanceof SqlStorage ? storage - : new SqlStorage({ ...storage, storageHash: storageHashValue }), + : new SqlStorage(buildSqlStorageInput({ storageHash: storageHashValue, ...storage })), roots: {}, models: {}, capabilities: {}, @@ -90,8 +91,8 @@ function contractToSchemaIR( } function planFromStorages( - from: Omit | null, - to: Omit, + from: Omit | null, + to: Omit, ): SqlPlannerResult { const toContract = createTestContract(to); const fromSchemaIR = contractToSchemaIR(from ? createTestContract(from) : null, { @@ -111,7 +112,7 @@ function planFromStorages( describe('contractToSchemaIR → planner round-trip', () => { it('produces no ops when contract and schemaIR represent the same state', () => { - const storage: SqlStorageInput = { + const storage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: { @@ -151,7 +152,7 @@ describe('contractToSchemaIR → planner round-trip', () => { }); it('detects additive changes from empty state', () => { - const storage: SqlStorageInput = { + const storage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: { @@ -192,7 +193,7 @@ describe('contractToSchemaIR → planner round-trip', () => { }); it('detects incremental table addition', () => { - const fromStorage: SqlStorageInput = { + const fromStorage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: { @@ -207,7 +208,7 @@ describe('contractToSchemaIR → planner round-trip', () => { }), }; - const toStorage: SqlStorageInput = { + const toStorage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: { @@ -260,7 +261,7 @@ describe('contractToSchemaIR → planner round-trip', () => { }); it('handles default values in round-trip', () => { - const storage: SqlStorageInput = { + const storage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ item: { @@ -312,7 +313,7 @@ describe('contractToSchemaIR → planner round-trip', () => { describe('planner — additive scenarios', () => { it('detects added column on existing table', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -325,7 +326,7 @@ describe('planner — additive scenarios', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -350,7 +351,7 @@ describe('planner — additive scenarios', () => { }); it('detects added table', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -362,7 +363,7 @@ describe('planner — additive scenarios', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -391,7 +392,7 @@ describe('planner — additive scenarios', () => { }); it('detects multiple changes at once (table + unique + index)', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -403,7 +404,7 @@ describe('planner — additive scenarios', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -438,7 +439,7 @@ describe('planner — additive scenarios', () => { }); it('returns no ops when storages are identical', () => { - const storage: SqlStorageInput = { + const storage: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -462,7 +463,7 @@ describe('planner — additive scenarios', () => { describe('detectDestructiveChanges', () => { it('rejects column removal with conflict', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -476,7 +477,7 @@ describe('detectDestructiveChanges', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -489,7 +490,10 @@ describe('detectDestructiveChanges', () => { }), }; - const conflicts = detectDestructiveChanges(new SqlStorage(from), new SqlStorage(to)); + const conflicts = detectDestructiveChanges( + new SqlStorage(buildSqlStorageInput(from)), + new SqlStorage(buildSqlStorageInput(to)), + ); expect(conflicts).toHaveLength(1); expect(conflicts[0]!.kind).toBe('columnRemoved'); @@ -497,7 +501,7 @@ describe('detectDestructiveChanges', () => { }); it('rejects table removal with conflict', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -511,7 +515,7 @@ describe('detectDestructiveChanges', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -521,7 +525,10 @@ describe('detectDestructiveChanges', () => { }), }; - const conflicts = detectDestructiveChanges(new SqlStorage(from), new SqlStorage(to)); + const conflicts = detectDestructiveChanges( + new SqlStorage(buildSqlStorageInput(from)), + new SqlStorage(buildSqlStorageInput(to)), + ); expect(conflicts).toHaveLength(1); expect(conflicts[0]!.kind).toBe('tableRemoved'); @@ -529,7 +536,7 @@ describe('detectDestructiveChanges', () => { }); it('rejects multiple destructive changes with all conflicts', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -546,7 +553,7 @@ describe('detectDestructiveChanges', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -556,7 +563,10 @@ describe('detectDestructiveChanges', () => { }), }; - const conflicts = detectDestructiveChanges(new SqlStorage(from), new SqlStorage(to)); + const conflicts = detectDestructiveChanges( + new SqlStorage(buildSqlStorageInput(from)), + new SqlStorage(buildSqlStorageInput(to)), + ); expect(conflicts).toHaveLength(2); const kinds = conflicts.map((c) => c.kind); @@ -567,7 +577,7 @@ describe('detectDestructiveChanges', () => { describe('planner — type and nullability change behavior', () => { it('rejects type change (text → int4) as non-additive conflict', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -580,7 +590,7 @@ describe('planner — type and nullability change behavior', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -606,7 +616,7 @@ describe('planner — type and nullability change behavior', () => { }); it('rejects nullability tightening (nullable → non-nullable) as non-additive conflict', () => { - const from: SqlStorageInput = { + const from: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -619,7 +629,7 @@ describe('planner — type and nullability change behavior', () => { }), }; - const to: SqlStorageInput = { + const to: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns({ user: table({ @@ -773,7 +783,7 @@ const DEMO_BASE_TABLES = { }), }; -const DEMO_BASE_STORAGE: SqlStorageInput = { +const DEMO_BASE_STORAGE: SqlStorageNamespacesInput = { storageHash: coreHash('sha256:test'), ...ns(DEMO_BASE_TABLES), types: { @@ -787,7 +797,7 @@ const DEMO_BASE_STORAGE: SqlStorageInput = { }; function createDemoContract( - storage: Omit, + storage: Omit, overrides?: Partial>, ): Contract { const storageHashValue = coreHash('sha256:demo'); @@ -795,7 +805,7 @@ function createDemoContract( target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ ...storage, storageHash: storageHashValue }), + storage: new SqlStorage(buildSqlStorageInput({ storageHash: storageHashValue, ...storage })), roots: {}, models: {}, capabilities: {}, @@ -809,7 +819,7 @@ describe('incremental migration with full contract surface (enums, FKs)', () => const frameworkComponents = [createAdapterHooksComponent(), pgvectorDescriptor]; it('only emits ops for the actual change when adding a column to an existing table', () => { - const toStorage: Omit = { + const toStorage: Omit = { ...DEMO_BASE_STORAGE, ...ns({ ...DEMO_BASE_TABLES, diff --git a/packages/3-extensions/pgvector/test/migrations/planner.storage-types.test.ts b/packages/3-extensions/pgvector/test/migrations/planner.storage-types.test.ts index 33984c4c28..cc42891104 100644 --- a/packages/3-extensions/pgvector/test/migrations/planner.storage-types.test.ts +++ b/packages/3-extensions/pgvector/test/migrations/planner.storage-types.test.ts @@ -6,6 +6,7 @@ import { APP_SPACE_ID } from '@prisma-next/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { buildSqlNamespace, + buildSqlStorageInput, SqlStorage, SqlUnboundNamespace, } from '@prisma-next/sql-contract/types'; @@ -59,39 +60,41 @@ describe('PostgresMigrationPlanner - storage types', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - types: { - Role: { - kind: 'codec-instance', - codecId: 'pg/enum@1', - nativeType: 'role', - typeParams: { values: ['USER'] }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + types: { + Role: { + kind: 'codec-instance', + codecId: 'pg/enum@1', + nativeType: 'role', + typeParams: { values: ['USER'] }, + }, }, - }, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - role: { - nativeType: 'role', - codecId: 'pg/enum@1', - nullable: false, - typeRef: 'Role', + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + role: { + nativeType: 'role', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'Role', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -151,18 +154,20 @@ describe('PostgresMigrationPlanner - storage types', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - types: { - Role: { - kind: 'codec-instance', - codecId: 'pg/enum@1', - nativeType: 'role', - typeParams: { values: ['USER'] }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + types: { + Role: { + kind: 'codec-instance', + codecId: 'pg/enum@1', + nativeType: 'role', + typeParams: { values: ['USER'] }, + }, }, - }, - namespaces: { [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }, - }), + namespaces: { [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -230,39 +235,41 @@ describe('PostgresMigrationPlanner - storage types', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - types: { - UserKind: { - kind: 'codec-instance', - codecId: 'pg/enum@1', - nativeType: 'UserKind', - typeParams: { values: ['ADMIN', 'USER'] }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + types: { + UserKind: { + kind: 'codec-instance', + codecId: 'pg/enum@1', + nativeType: 'UserKind', + typeParams: { values: ['ADMIN', 'USER'] }, + }, }, - }, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - kind: { - nativeType: 'UserKind', - codecId: 'pg/enum@1', - nullable: false, - typeRef: 'UserKind', + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, + kind: { + nativeType: 'UserKind', + codecId: 'pg/enum@1', + nullable: false, + typeRef: 'UserKind', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -297,39 +304,41 @@ describe('PostgresMigrationPlanner - storage types', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - types: { - Embedding1536: { - kind: 'codec-instance', - codecId: 'pg/vector@1', - nativeType: 'vector', - typeParams: { length: 1536 }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + types: { + Embedding1536: { + kind: 'codec-instance', + codecId: 'pg/vector@1', + nativeType: 'vector', + typeParams: { length: 1536 }, + }, }, - }, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - document: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - embedding: { - nativeType: 'vector', - codecId: 'pg/vector@1', - nullable: false, - typeRef: 'Embedding1536', + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + document: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + embedding: { + nativeType: 'vector', + codecId: 'pg/vector@1', + nullable: false, + typeRef: 'Embedding1536', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, @@ -362,39 +371,41 @@ describe('PostgresMigrationPlanner - storage types', () => { target: 'postgres', targetFamily: 'sql', profileHash: profileHash('sha256:test'), - storage: new SqlStorage({ - storageHash: coreHash('sha256:test'), - types: { - Embedding1536: { - kind: 'codec-instance', - codecId: 'pg/vector@1', - nativeType: 'vector', - typeParams: { length: 1536 }, + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash('sha256:test'), + types: { + Embedding1536: { + kind: 'codec-instance', + codecId: 'pg/vector@1', + nativeType: 'vector', + typeParams: { length: 1536 }, + }, }, - }, - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ - id: UNBOUND_NAMESPACE_ID, - tables: { - document: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - embedding: { - nativeType: 'vector', - codecId: 'pg/vector@1', - nullable: false, - typeRef: 'Embedding1536', + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace({ + id: UNBOUND_NAMESPACE_ID, + tables: { + document: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + embedding: { + nativeType: 'vector', + codecId: 'pg/vector@1', + nullable: false, + typeRef: 'Embedding1536', + }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - }, - }), - }, - }), + }), + }, + }), + ), roots: {}, models: {}, capabilities: {}, From efedf49a2e8a893db2717cf93eafd3573d553ad3 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:03:37 +0200 Subject: [PATCH 27/60] fix(cli): flatten storage fixture in per-member-verifier test Drops the stale `namespaces` wrapper from the db-verify test contract so namespace ids key directly under `storage`. CLI typechecks fully green. Signed-off-by: Will Madden --- .../cli/test/control-api/db-verify.per-member-verifier.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/1-framework/3-tooling/cli/test/control-api/db-verify.per-member-verifier.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/db-verify.per-member-verifier.test.ts index 35132d32ab..e5addba917 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/db-verify.per-member-verifier.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/db-verify.per-member-verifier.test.ts @@ -14,7 +14,7 @@ describe('createPerMemberVerifier', () => { const contract = createSqlContract({ target: 'postgres', storage: { - namespaces: { [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: { user: {} } } }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: { user: {} } }, }, }); const member = createContractSpaceMember({ From 5f3918508bdac7479f6335a491db22572bb9911b Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:06:57 +0200 Subject: [PATCH 28/60] fix(sql-contract,mongo-contract): allow flat namespace-id keys in storage schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The storage arktype schema used `+: reject`, which rejected the namespace-id keys that now sit directly under `storage` (ADR 221 flat shape) before the narrow could validate them — so genuine flat contracts failed structural validation with "storage. must be removed". Switches the base to `+: ignore` and lets the narrow validate every non-reserved key as a namespace entry. Flattens the validators test fixtures to the wrapper-less shape. Signed-off-by: Will Madden --- .../mongo-contract/src/contract-schema.ts | 2 +- .../2-sql/1-core/contract/src/validators.ts | 7 +- .../1-core/contract/test/validators.test.ts | 320 +++++++++--------- 3 files changed, 158 insertions(+), 171 deletions(-) diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts index 807960f7a4..b934eea545 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts @@ -391,7 +391,7 @@ export function createMongoContractSchema( '_generated?': 'Record', 'domain?': 'unknown', storage: type({ - '+': 'reject', + '+': 'ignore', storageHash: 'string', }).narrow((storage, ctx) => { if (typeof storage !== 'object' || storage === null) { diff --git a/packages/2-sql/1-core/contract/src/validators.ts b/packages/2-sql/1-core/contract/src/validators.ts index 2f2bcaa480..3aa8cc6b5c 100644 --- a/packages/2-sql/1-core/contract/src/validators.ts +++ b/packages/2-sql/1-core/contract/src/validators.ts @@ -270,8 +270,13 @@ export function createSqlStorageSchema( fragments?: ReadonlyMap>, ): Type { const namespaceEntry = createNamespaceEntrySchema(fragments); + // Namespace ids are dynamic keys directly under `storage` (ADR 221 flat + // shape), so the base object must allow undeclared keys (`'+': 'ignore'`); + // the narrow then validates every non-reserved key as a namespace entry. + // A `'+': 'reject'` base would reject the namespace ids themselves before + // the narrow runs. return type({ - '+': 'reject', + '+': 'ignore', storageHash: 'string', 'types?': type({ '[string]': DocumentScopedStorageTypeSchema }), }).narrow((storage, ctx) => { diff --git a/packages/2-sql/1-core/contract/test/validators.test.ts b/packages/2-sql/1-core/contract/test/validators.test.ts index c5fdcb23dd..dbf0650bf5 100644 --- a/packages/2-sql/1-core/contract/test/validators.test.ts +++ b/packages/2-sql/1-core/contract/test/validators.test.ts @@ -13,9 +13,7 @@ import { function unboundTables(tables: Record) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }; } @@ -35,11 +33,9 @@ describe('SQL contract validators', () => { it('throws on invalid storage structure', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: 'not-an-object', - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: 'not-an-object', }, } as unknown; expect(() => validateStorage(invalid)).toThrow(); @@ -48,13 +44,11 @@ describe('SQL contract validators', () => { it('throws on invalid table structure', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: 'not-an-object', - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: 'not-an-object', }, }, }, @@ -65,14 +59,12 @@ describe('SQL contract validators', () => { it('throws on invalid nativeType', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 123, codecId: 'pg/int4@1', nullable: false }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 123, codecId: 'pg/int4@1', nullable: false }, }, }, }, @@ -84,14 +76,12 @@ describe('SQL contract validators', () => { it('throws on invalid nullable type', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: 'yes' }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: 'yes' }, }, }, }, @@ -103,24 +93,22 @@ describe('SQL contract validators', () => { it('throws when column declares both typeParams and typeRef', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - embedding: { - nativeType: 'vector', - codecId: 'pg/vector@1', - nullable: false, - typeParams: { dimensions: 1536 }, - typeRef: 'vector_1536', - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + embedding: { + nativeType: 'vector', + codecId: 'pg/vector@1', + nullable: false, + typeParams: { dimensions: 1536 }, + typeRef: 'vector_1536', }, - uniques: [], - indexes: [], - foreignKeys: [], }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, @@ -503,35 +491,33 @@ describe('SQL contract validators', () => { it('throws on invalid referential action string', () => { const invalid = { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - post: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - userId: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [ - { - source: { - namespaceId: UNBOUND_NAMESPACE_ID, - tableName: 'post', - columns: ['userId'], - }, - target: { - namespaceId: UNBOUND_NAMESPACE_ID, - tableName: 'user', - columns: ['id'], - }, - onDelete: 'invalidAction', - constraint: true, - index: true, - }, - ], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + post: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + userId: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [ + { + source: { + namespaceId: UNBOUND_NAMESPACE_ID, + tableName: 'post', + columns: ['userId'], + }, + target: { + namespaceId: UNBOUND_NAMESPACE_ID, + tableName: 'user', + columns: ['id'], + }, + onDelete: 'invalidAction', + constraint: true, + index: true, + }, + ], }, }, }, @@ -594,58 +580,56 @@ describe('SQL contract validators', () => { const rawContract = createContract({ storage: { storageHash: 'sha256:cross-ns', - namespaces: { - auth: { - id: 'auth', - tables: { - users: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + auth: { + id: 'auth', + tables: { + users: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, - analytics: { - id: 'analytics', - tables: { - users: { - columns: { - user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - }, - primaryKey: { columns: ['user_uuid'] }, - uniques: [], - indexes: [], - foreignKeys: [], + }, + analytics: { + id: 'analytics', + tables: { + users: { + columns: { + user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, }, - events: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [ - { - source: { - namespaceId: 'analytics', - tableName: 'events', - columns: ['user_uuid'], - }, - target: { - namespaceId: 'analytics', - tableName: 'users', - columns: ['user_uuid'], - }, - constraint: true, - index: true, - }, - ], + primaryKey: { columns: ['user_uuid'] }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + events: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [ + { + source: { + namespaceId: 'analytics', + tableName: 'events', + columns: ['user_uuid'], + }, + target: { + namespaceId: 'analytics', + tableName: 'users', + columns: ['user_uuid'], + }, + constraint: true, + index: true, + }, + ], }, }, }, @@ -663,58 +647,56 @@ describe('SQL contract validators', () => { const rawContract = createContract({ storage: { storageHash: 'sha256:cross-ns-mismatch', - namespaces: { - auth: { - id: 'auth', - tables: { - users: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + auth: { + id: 'auth', + tables: { + users: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, - analytics: { - id: 'analytics', - tables: { - users: { - columns: { - user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - }, - primaryKey: { columns: ['user_uuid'] }, - uniques: [], - indexes: [], - foreignKeys: [], + }, + analytics: { + id: 'analytics', + tables: { + users: { + columns: { + user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, }, - events: { - columns: { - id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, - user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [ - { - source: { - namespaceId: 'analytics', - tableName: 'events', - columns: ['user_uuid'], - }, - target: { - namespaceId: 'auth', - tableName: 'users', - columns: ['user_uuid'], - }, - constraint: true, - index: true, - }, - ], + primaryKey: { columns: ['user_uuid'] }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + events: { + columns: { + id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, + user_uuid: { nativeType: 'uuid', codecId: 'pg/uuid@1', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [ + { + source: { + namespaceId: 'analytics', + tableName: 'events', + columns: ['user_uuid'], + }, + target: { + namespaceId: 'auth', + tableName: 'users', + columns: ['user_uuid'], + }, + constraint: true, + index: true, + }, + ], }, }, }, From fbaf8c6caa2d0aed2053a7c10740fd685e73b6d2 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:12:09 +0200 Subject: [PATCH 29/60] fix(mongo-family): flatten storage in family schema-verify and serializer tests Unwraps the `namespaces` wrapper from the mongo family test fixtures and routes the fake-storage builder through `buildMongoStorageInput`, and restores the optional `storageHash` on the mongo storage schema. Signed-off-by: Will Madden --- .../mongo-contract/src/contract-schema.ts | 2 +- .../9-family/test/contract-to-schema.test.ts | 8 +++----- ...instance.descriptor-self-consistency.test.ts | 12 +++++------- .../test/mongo-contract-serializer-base.test.ts | 4 +--- .../test/mongo-schema-verifier-base.test.ts | 17 +++++++++++------ .../9-family/test/schema-verify.test.ts | 12 ++++-------- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts index b934eea545..1337f0c31e 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts @@ -392,7 +392,7 @@ export function createMongoContractSchema( 'domain?': 'unknown', storage: type({ '+': 'ignore', - storageHash: 'string', + 'storageHash?': 'string', }).narrow((storage, ctx) => { if (typeof storage !== 'object' || storage === null) { return ctx.mustBe('an object'); diff --git a/packages/2-mongo-family/9-family/test/contract-to-schema.test.ts b/packages/2-mongo-family/9-family/test/contract-to-schema.test.ts index 1b6273052c..381582770e 100644 --- a/packages/2-mongo-family/9-family/test/contract-to-schema.test.ts +++ b/packages/2-mongo-family/9-family/test/contract-to-schema.test.ts @@ -38,11 +38,9 @@ function makeContract(collections: Record): MongoCo models: {}, storage: { storageHash: 'sha256:test-storage', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: builtCollections, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: builtCollections, }, }, } as unknown as MongoContract; diff --git a/packages/2-mongo-family/9-family/test/control-instance.descriptor-self-consistency.test.ts b/packages/2-mongo-family/9-family/test/control-instance.descriptor-self-consistency.test.ts index 084a55385c..5a4f2b9dd5 100644 --- a/packages/2-mongo-family/9-family/test/control-instance.descriptor-self-consistency.test.ts +++ b/packages/2-mongo-family/9-family/test/control-instance.descriptor-self-consistency.test.ts @@ -16,13 +16,11 @@ const TARGET = 'mongo' as const; const TARGET_FAMILY = 'mongo' as const; const fixtureStorageBody = { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: { - fixture_box: { - indexes: [{ keys: [{ field: 'email', direction: 1 as const }], unique: true }], - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: { + fixture_box: { + indexes: [{ keys: [{ field: 'email', direction: 1 as const }], unique: true }], }, }, }, diff --git a/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts b/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts index 69a286d36d..478bc56cb9 100644 --- a/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts +++ b/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts @@ -10,9 +10,7 @@ function makeValidContractJson() { targetFamily: 'mongo', roots: { items: crossRef('Item') }, storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections: { items: {} } }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections: { items: {} } }, }, models: { Item: { diff --git a/packages/2-mongo-family/9-family/test/mongo-schema-verifier-base.test.ts b/packages/2-mongo-family/9-family/test/mongo-schema-verifier-base.test.ts index 7e506cfdc3..fb10f2cb17 100644 --- a/packages/2-mongo-family/9-family/test/mongo-schema-verifier-base.test.ts +++ b/packages/2-mongo-family/9-family/test/mongo-schema-verifier-base.test.ts @@ -1,7 +1,11 @@ import { coreHash } from '@prisma-next/contract/types'; import type { SchemaIssue, SchemaVerifyOptions } from '@prisma-next/framework-components/control'; import type { Namespace } from '@prisma-next/framework-components/ir'; -import { MongoStorage } from '@prisma-next/mongo-contract'; +import { + buildMongoStorageInput, + type MongoNamespaceShape, + MongoStorage, +} from '@prisma-next/mongo-contract'; import { describe, expect, it } from 'vitest'; import { MongoSchemaVerifierBase } from '../src/core/ir/mongo-schema-verifier-base'; @@ -11,11 +15,12 @@ class FakeNamespace implements Namespace { } function makeFakeStorage(namespaces: Readonly>): MongoStorage { - return new MongoStorage({ - storageHash: coreHash('fake-hash'), - collections: {}, - namespaces, - }); + return new MongoStorage( + buildMongoStorageInput({ + storageHash: coreHash('fake-hash'), + namespaces: namespaces as Readonly>, + }), + ); } interface FakeContract { diff --git a/packages/2-mongo-family/9-family/test/schema-verify.test.ts b/packages/2-mongo-family/9-family/test/schema-verify.test.ts index d2c4291ee1..2d4f000834 100644 --- a/packages/2-mongo-family/9-family/test/schema-verify.test.ts +++ b/packages/2-mongo-family/9-family/test/schema-verify.test.ts @@ -46,11 +46,9 @@ function buildContract( models: {}, storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: builtCollections, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: builtCollections, }, }, capabilities: {}, @@ -1360,9 +1358,7 @@ describe('verifyMongoSchema', () => { return { storage: { storageHash: 'sha256:authoring-test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections: {} }, }, } as unknown as MongoContract; } From ce11ed3ffb199aa0fadf2ae3d17d2c650fe0cb74 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:13:59 +0200 Subject: [PATCH 30/60] fix(emitter): flatten storage in canonicalization test fixtures and path patterns Drops the `namespaces` wrapper from the test fixtures, helpers, sort/ preserve-empty path patterns, and the structural assertion so they match the flat ADR 221 storage shape. Signed-off-by: Will Madden --- .../emitter/test/canonicalization.test.ts | 58 +++++++------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts b/packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts index 60d95bdfda..f347de93f3 100644 --- a/packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts @@ -16,15 +16,12 @@ import { createTestContract } from './utils'; function unboundNamespaceTables(tables: Record) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }; } function tablesFromCanonicalStorage(storage: Record): Record { - const namespaces = storage['namespaces'] as Record; - const unbound = namespaces[UNBOUND_NAMESPACE_ID] as Record; + const unbound = storage[UNBOUND_NAMESPACE_ID] as Record; return unbound['tables'] as Record; } @@ -36,15 +33,15 @@ const canonicalizeContract = ( ): string => canonicalizeContractRaw(c, { serializeContract: identitySerialize, ...opts }); const sqlPreserveEmptyPatterns = [ - ['storage', 'namespaces', '*', 'tables'], - ['storage', 'namespaces', '*', 'tables', '*'], - ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], - ['storage', 'namespaces', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], + ['storage', '*', 'tables'], + ['storage', '*', 'tables', '*'], + ['storage', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], ['storage', 'types', '*', 'typeParams'], ] as const satisfies readonly PathPattern[]; const sqlSortTargets = [ - { path: ['namespaces', '*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, + { path: ['*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, ] as const satisfies readonly NamedArraySortTarget[]; const sqlPreserveEmpty = createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); @@ -169,7 +166,7 @@ describe('canonicalization', () => { expect(parsed).toMatchObject({ models: expect.anything(), storage: { - namespaces: expect.anything(), + storageHash: expect.anything(), }, }); // Required top-level fields (capabilities, extensionPacks, meta) are preserved even when empty. @@ -184,17 +181,14 @@ describe('canonicalization', () => { it('preserves an empty per-namespace tables slot when SQL shouldPreserveEmpty hook is provided', () => { const ir = createTestContract({ storage: { - namespaces: { - public: { id: 'public', tables: {} }, - }, + public: { id: 'public', tables: {} }, }, }); const result = canonicalizeContract(ir, { shouldPreserveEmpty: sqlPreserveEmpty }); const parsed = JSON.parse(result) as Record; const storage = parsed['storage'] as Record; - const namespaces = storage['namespaces'] as Record; - const publicNs = namespaces['public'] as Record; + const publicNs = storage['public'] as Record; expect(publicNs).toMatchObject({ id: 'public', tables: {} }); }); @@ -336,11 +330,9 @@ describe('canonicalization', () => { it('preserves empty namespace table entries when SQL shouldPreserveEmpty hook is provided', () => { const ir = createTestContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { users: {}, posts: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { users: {}, posts: {} }, }, }, }); @@ -355,11 +347,9 @@ describe('canonicalization', () => { it('sorts table names lexicographically within a namespace', () => { const ir = createTestContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { zebras: {}, apples: {}, mangoes: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { zebras: {}, apples: {}, mangoes: {} }, }, }, }); @@ -373,21 +363,17 @@ describe('canonicalization', () => { it('produces different hashes when namespace tables differ', () => { const ir1 = createTestContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { users: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { users: {} }, }, }, }); const ir2 = createTestContract({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { users: {}, posts: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { users: {}, posts: {} }, }, }, }); From 9604c361dd1b4105c437444d5d07e7e542edd448 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:15:46 +0200 Subject: [PATCH 31/60] fix(target-postgres): emit flat namespace keys from serializeContract The Postgres serializer wrapped the serialized namespaces under a `namespaces` key; spread them directly under `storage` alongside the reserved `storageHash`/`types` so the on-the-wire shape matches ADR 221. Signed-off-by: Will Madden --- .../postgres/src/core/postgres-contract-serializer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts index 55d784b839..5e548ba95e 100644 --- a/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts +++ b/packages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.ts @@ -146,9 +146,11 @@ export class PostgresContractSerializer extends SqlContractSerializerBase = { storageHash: String(storage.storageHash), - namespaces: namespacesJson, + ...namespacesJson, }; if (storage.types !== undefined) { const typesOut: Record = {}; From f61e8b67943d21c6845974eaf26de20ddabd20ea Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:46:16 +0200 Subject: [PATCH 32/60] refactor: replace bare storage casts with walk helpers / justified blindCast Cleans up the bare `as` casts the flat-storage migration introduced in production source. Most are eliminated by using the generic walk helpers (`storageNamespaceValues` / `getStorageNamespace` / etc.) and the family namespace concretion types, so the namespace family type is recovered without a cast. The few that remain are converted to `blindCast`: the storage-plane key lookup (reads a dynamic namespace-id key off the structurally-opaque `object` parameter), the `buildSqlStorageInput`/`buildMongoStorageInput` flatteners (a spread cannot reconstruct the `__unbound__` brand / omit the shared `types?` slot), the Mongo hydration reassembly, and the SQL emission hooks (which run only for SQL-family contracts but see the family-agnostic `StorageBase`). lint:casts is green (delta negative). Signed-off-by: Will Madden --- .../src/ir/storage-plane-keys.ts | 18 ++++++++---- .../src/aggregate/check-integrity.ts | 4 +-- .../src/aggregate/project-schema-to-space.ts | 4 +-- .../migration/src/aggregate/verifier.ts | 4 +-- .../mongo-contract/src/ir/mongo-storage.ts | 17 ++++++++--- .../mongo-contract/src/validate-storage.ts | 4 +-- .../contract-psl/src/interpreter.ts | 2 +- .../3-tooling/emitter/src/index.ts | 23 ++++++++------- .../9-family/src/core/contract-to-schema.ts | 4 +-- .../core/ir/mongo-contract-serializer-base.ts | 29 +++++++++++++++---- .../1-core/contract/src/ir/sql-storage.ts | 20 +++++++++---- packages/2-sql/3-tooling/emitter/src/index.ts | 23 +++++++++++---- .../9-family/src/core/control-instance.ts | 8 ++--- .../core/migrations/contract-to-schema-ir.ts | 8 ++--- .../core/migrations/field-event-planner.ts | 10 +++---- .../core/schema-verify/verify-sql-schema.ts | 13 ++++----- .../core/mongo-target-contract-serializer.ts | 10 +++---- 17 files changed, 123 insertions(+), 78 deletions(-) diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts index 9bebaf50f0..e6d302e3c2 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/storage-plane-keys.ts @@ -10,16 +10,15 @@ export const STORAGE_PLANE_RESERVED_KEYS = ['storageHash', 'types'] as const; export type StoragePlaneReservedKey = (typeof STORAGE_PLANE_RESERVED_KEYS)[number]; +const RESERVED_KEY_SET: ReadonlySet = new Set(STORAGE_PLANE_RESERVED_KEYS); + export function isStoragePlaneReservedKey(key: string): key is StoragePlaneReservedKey { - return (STORAGE_PLANE_RESERVED_KEYS as readonly string[]).includes(key); + return RESERVED_KEY_SET.has(key); } function isNamespaceEntry(value: unknown): value is Namespace { return ( - typeof value === 'object' && - value !== null && - 'id' in value && - typeof (value as { id: unknown }).id === 'string' + typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'string' ); } @@ -64,7 +63,14 @@ export function getStorageNamespace( if (isStoragePlaneReservedKey(namespaceId)) { return undefined; } - const value = (storage as Record)[namespaceId]; + // The public parameter is `object` so both family storage class instances + // and plain validated records flow in without a cast; reading a dynamic + // namespace-id key requires asserting the string index-signature shape that + // `object` deliberately omits. + const value = blindCast< + Record, + 'storage walk reads a dynamic namespace-id key off the structurally-opaque `object` parameter' + >(storage)[namespaceId]; return isNamespaceEntry(value) ? blindCast(value) : undefined; diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts b/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts index ceeb6cb937..e105fc78e9 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/check-integrity.ts @@ -216,9 +216,7 @@ function contractViolations(input: IntegrityComputationInput): readonly Integrit }); } - for (const { entityName: elementName } of elementCoordinates( - contract.storage as unknown as Record, - )) { + for (const { entityName: elementName } of elementCoordinates(contract.storage)) { const claimers = elementClaimedBy.get(elementName); if (claimers) claimers.push(member.spaceId); else elementClaimedBy.set(elementName, [member.spaceId]); diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts b/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts index 314b7fb2fe..aac3e0615e 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/project-schema-to-space.ts @@ -97,9 +97,7 @@ function collectOwnedNames( const owned = new Set(); for (const other of otherMembers) { if (other.spaceId === member.spaceId) continue; - for (const { entityName } of elementCoordinates( - other.contract().storage as unknown as Record, - )) { + for (const { entityName } of elementCoordinates(other.contract().storage)) { owned.add(entityName); } } diff --git a/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts b/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts index 4b70301d99..15a6cb0cd2 100644 --- a/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts +++ b/packages/1-framework/3-tooling/migration/src/aggregate/verifier.ts @@ -214,9 +214,7 @@ function detectOrphanElements( const claimedTables = new Set(); for (const member of members) { const contract = member.contract(); - for (const { entityName } of elementCoordinates( - contract.storage as unknown as Record, - )) { + for (const { entityName } of elementCoordinates(contract.storage)) { claimedTables.add(entityName); } } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts index 02e57a9d40..5e3feb4c5a 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts @@ -7,6 +7,7 @@ import { type Namespace, type Storage, } from '@prisma-next/framework-components/ir'; +import { blindCast } from '@prisma-next/utils/casts'; import type { MongoCollection, MongoCollectionInput } from './mongo-collection'; export interface MongoNamespaceCollectionsInput { @@ -35,10 +36,18 @@ export type MongoStorageNamespacesInput = { export function buildMongoStorageInput( input: MongoStorageNamespacesInput, ): MongoStorageInput { - return flatStorageInput({ - storageHash: input.storageHash, - namespaces: input.namespaces, - }) as MongoStorageInput; + // `flatStorageInput` carries a shared optional `types?` slot that + // `MongoStorageInput` omits, so the spread result is not nominally the + // Mongo input type even though it is structurally the flat Mongo storage. + return blindCast< + MongoStorageInput, + 'flatStorageInput exposes an optional types slot MongoStorageInput omits; the spread is otherwise the flat Mongo storage input' + >( + flatStorageInput({ + storageHash: input.storageHash, + namespaces: input.namespaces, + }), + ); } export class MongoStorage extends IRNodeBase implements Storage { diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts index 5dd8a3c9d1..189e9d58f2 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts @@ -10,8 +10,8 @@ function storageDeclaresCollection( storage: MongoContract['storage'], collectionName: string, ): boolean { - for (const ns of storageNamespaceValues(storage)) { - if (Object.hasOwn((ns as MongoNamespace).collections, collectionName)) { + for (const ns of storageNamespaceValues(storage)) { + if (Object.hasOwn(ns.collections, collectionName)) { return true; } } diff --git a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts index 2bf73b93d7..18ee766933 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts @@ -1153,7 +1153,7 @@ export function interpretPslDocumentToMongoContract( }), }, }), - ) as Contract['storage']; + ); const capabilities: Record> = {}; return ok({ diff --git a/packages/2-mongo-family/3-tooling/emitter/src/index.ts b/packages/2-mongo-family/3-tooling/emitter/src/index.ts index 071fe32add..550caf22c5 100644 --- a/packages/2-mongo-family/3-tooling/emitter/src/index.ts +++ b/packages/2-mongo-family/3-tooling/emitter/src/index.ts @@ -16,7 +16,10 @@ import type { const MONGO_NAMESPACE_KIND_FALLBACK = 'mongo-namespace' as const; function mongoNamespaceSerializedKind(ns: Namespace): string { - const kind = (ns as { kind?: unknown }).kind; + // `kind` is typed `string` on `Namespace`, but plain-literal namespaces + // built through the DSL omit it at runtime; widen to `unknown` (no cast) + // and fall back when it is absent. + const kind: unknown = ns.kind; if (typeof kind === 'string') { return `readonly kind: ${serializeValue(kind)}`; } @@ -25,8 +28,8 @@ function mongoNamespaceSerializedKind(ns: Namespace): string { function assertUniqueMongoCollectionNames(storage: MongoStorage): void { const seen = new Map(); - for (const [namespaceId, ns] of [...storageNamespaceEntries(storage)]) { - for (const coll of Object.keys((ns as MongoNamespaceShape).collections)) { + for (const [namespaceId, ns] of [...storageNamespaceEntries(storage)]) { + for (const coll of Object.keys(ns.collections)) { const existing = seen.get(coll); if (existing !== undefined && existing !== namespaceId) { throw new Error( @@ -64,15 +67,15 @@ function generateMongoNamespaceCollectionsType( } function generateMongoFlatStorageType(storage: MongoStorage): string { - const sorted = [...storageNamespaceEntries(storage)].sort(([a], [b]) => a.localeCompare(b)); + const sorted = [...storageNamespaceEntries(storage)].sort(([a], [b]) => + a.localeCompare(b), + ); if (sorted.length === 0) { return ''; } const parts: string[] = []; for (const [name, ns] of sorted) { - const collectionsType = generateMongoNamespaceCollectionsType( - (ns as MongoNamespaceShape).collections as Readonly>, - ); + const collectionsType = generateMongoNamespaceCollectionsType(ns.collections); parts.push( `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}; ${mongoNamespaceSerializedKind(ns)}; readonly collections: ${collectionsType} }`, ); @@ -132,7 +135,7 @@ export const mongoEmission = { throw new Error('Mongo contract must have storage'); } if ( - storageNamespaceValues(storage).length === 0 && + storageNamespaceValues(storage).length === 0 && !getStorageNamespace(storage, '__unbound__') ) { throw new Error('Mongo contract must have at least one storage namespace entry'); @@ -141,8 +144,8 @@ export const mongoEmission = { assertUniqueMongoCollectionNames(storage); const collectionNames = new Set(); - for (const ns of storageNamespaceValues(storage)) { - for (const c of Object.keys((ns as MongoNamespaceShape).collections)) { + for (const ns of storageNamespaceValues(storage)) { + for (const c of Object.keys(ns.collections)) { collectionNames.add(c); } } diff --git a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts index 2b407f0ad3..76d1ed2dc1 100644 --- a/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts +++ b/packages/2-mongo-family/9-family/src/core/contract-to-schema.ts @@ -79,8 +79,8 @@ export function contractToMongoSchemaIR(contract: Contract | null): MongoSchemaI } const collections: MongoSchemaCollection[] = []; - for (const ns of storageNamespaceValues(contract.storage)) { - for (const [name, def] of Object.entries((ns as MongoNamespaceShape).collections)) { + for (const ns of storageNamespaceValues(contract.storage)) { + for (const [name, def] of Object.entries(ns.collections)) { collections.push(convertCollection(name, def)); } } diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts index a451e851f4..f07ffe3651 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts @@ -7,9 +7,11 @@ import { type MongoCollectionInput, type MongoContract, MongoContractSchema, + type MongoNamespaceShape, validateMongoStorage, } from '@prisma-next/mongo-contract'; import { mongoContractCanonicalizationHooks } from '@prisma-next/mongo-contract/canonicalization-hooks'; +import { blindCast } from '@prisma-next/utils/casts'; import type { JsonObject } from '@prisma-next/utils/json'; import { type as arktypeType, type Type } from 'arktype'; @@ -124,13 +126,24 @@ export abstract class MongoContractSerializerBase const hydratedStorage: Record = { storageHash: contract.storage.storageHash, }; - for (const [nsId, nsEnvelope] of storageNamespaceEntries(contract.storage)) { - const rawCollections = - (nsEnvelope as { collections?: Record }).collections ?? {}; + for (const [nsId, nsEnvelope] of storageNamespaceEntries( + contract.storage, + )) { + const rawCollections = nsEnvelope.collections ?? {}; const hydratedCollections = Object.fromEntries( Object.entries(rawCollections).map(([name, raw]) => [ name, - raw instanceof MongoCollection ? raw : new MongoCollection(raw as MongoCollectionInput), + // Structurally-validated (not yet hydrated) entries are typed as + // `MongoCollection` but carry plain input data until this + // constructor runs; the input shape is the constructor's contract. + raw instanceof MongoCollection + ? raw + : new MongoCollection( + blindCast< + MongoCollectionInput, + 'validated-but-unhydrated collection entry is plain input data, not yet a MongoCollection instance' + >(raw), + ), ]), ); hydratedStorage[nsId] = { @@ -139,9 +152,15 @@ export abstract class MongoContractSerializerBase collections: hydratedCollections, }; } + // `hydratedStorage` is assembled as a flat record (namespace ids keyed + // directly under storage); the namespaced `MongoContract['storage']` + // type cannot be reconstructed from the dynamic-key assembly. return { ...contract, - storage: hydratedStorage as MongoContract['storage'], + storage: blindCast< + MongoContract['storage'], + 'flat namespace-keyed storage reassembled from dynamic keys; the namespaced storage type is not derivable from the record build' + >(hydratedStorage), }; } diff --git a/packages/2-sql/1-core/contract/src/ir/sql-storage.ts b/packages/2-sql/1-core/contract/src/ir/sql-storage.ts index 9348cd6a36..2003e94bf2 100644 --- a/packages/2-sql/1-core/contract/src/ir/sql-storage.ts +++ b/packages/2-sql/1-core/contract/src/ir/sql-storage.ts @@ -6,6 +6,7 @@ import { type Namespace, type Storage, } from '@prisma-next/framework-components/ir'; +import { blindCast } from '@prisma-next/utils/casts'; import { isPostgresEnumStorageEntry, type PostgresEnumStorageEntry, @@ -53,11 +54,20 @@ export type SqlStorageNamespacesInput = { export function buildSqlStorageInput( input: SqlStorageNamespacesInput, ): SqlStorageInput { - return flatStorageInput({ - storageHash: input.storageHash, - ...(input.types !== undefined ? { types: input.types } : {}), - namespaces: input.namespaces, - }) as SqlStorageInput; + // `flatStorageInput` widens the namespace spread to + // `Record`; TS cannot re-derive the + // `__unbound__`-branded `SqlStorageInput` from a spread, but the + // `SqlStorageNamespacesInput` parameter already guarantees that brand. + return blindCast< + SqlStorageInput, + 'flat spread cannot reconstruct the __unbound__-branded SqlStorageInput; the namespaces-keyed input guarantees the brand' + >( + flatStorageInput({ + storageHash: input.storageHash, + ...(input.types !== undefined ? { types: input.types } : {}), + namespaces: input.namespaces, + }), + ); } /** diff --git a/packages/2-sql/3-tooling/emitter/src/index.ts b/packages/2-sql/3-tooling/emitter/src/index.ts index bad6dd5c5b..2a2fd4433a 100644 --- a/packages/2-sql/3-tooling/emitter/src/index.ts +++ b/packages/2-sql/3-tooling/emitter/src/index.ts @@ -24,6 +24,7 @@ import { type StorageTable, type StorageTypeInstance, } from '@prisma-next/sql-contract/types'; +import { blindCast } from '@prisma-next/utils/casts'; function serializeTypeParamsLiteral(params: Record | undefined): string { if (!params || Object.keys(params).length === 0) { @@ -79,7 +80,10 @@ export const sqlEmission = { id: 'sql', validateTypes(contract: Contract, _ctx: ValidationContext): void { - const storage = contract.storage as unknown as SqlStorage | undefined; + const storage = blindCast< + SqlStorage | undefined, + 'SQL emission hook runs only for SQL-family contracts; the framework Contract surface types storage as the family-agnostic StorageBase' + >(contract.storage); if (!storage?.storageHash) { return; } @@ -112,7 +116,10 @@ export const sqlEmission = { throw new Error(`Expected targetFamily "sql", got "${contract.targetFamily}"`); } - const storage = contract.storage as unknown as SqlStorage | undefined; + const storage = blindCast< + SqlStorage | undefined, + 'SQL emission hook runs only for SQL-family contracts; the framework Contract surface types storage as the family-agnostic StorageBase' + >(contract.storage); if (!storage?.storageHash) { throw new Error('SQL contract must have storage with storageHash'); } @@ -258,7 +265,10 @@ export const sqlEmission = { }, generateStorageType(contract: Contract, storageHashTypeName: string): string { - const storage = contract.storage as unknown as SqlStorage; + const storage = blindCast< + SqlStorage, + 'SQL emission hook runs only for SQL-family contracts; the framework Contract surface types storage as the family-agnostic StorageBase' + >(contract.storage); const namespaceTypeEntries = generateFlatStorageNamespaceTypeEntries(storage); const domainTypes = contract.domain?.[UNBOUND_NAMESPACE_ID]?.['types'] as | Readonly> @@ -297,7 +307,10 @@ export const sqlEmission = { const storageField = sqlModel.storage?.fields?.[fieldName]; if (!storageField) return undefined; - const storage = contract.storage as unknown as SqlStorage | undefined; + const storage = blindCast< + SqlStorage | undefined, + 'SQL emission hook runs only for SQL-family contracts; the framework Contract surface types storage as the family-agnostic StorageBase' + >(contract.storage); if (!storage) return undefined; const tableName = sqlModel.storage.table; @@ -308,7 +321,7 @@ export const sqlEmission = { if (!column) return undefined; if (column.typeRef) { - const ns = getStorageNamespace(storage, located.namespaceId) as SqlNamespace | undefined; + const ns = getStorageNamespace(storage, located.namespaceId); const nsEnums = ns !== undefined && 'enum' in ns ? ( diff --git a/packages/2-sql/9-family/src/core/control-instance.ts b/packages/2-sql/9-family/src/core/control-instance.ts index 86eae1345f..18a1f7937a 100644 --- a/packages/2-sql/9-family/src/core/control-instance.ts +++ b/packages/2-sql/9-family/src/core/control-instance.ts @@ -32,7 +32,7 @@ import { import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; -import type { SqlStorage } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; import type { AnyQueryAst, LoweredStatement, @@ -69,11 +69,7 @@ function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] { contract.storage !== null && storageNamespaceValues(contract.storage).length > 0 ) { - const namespaces = Object.fromEntries(storageNamespaceEntries(contract.storage)) as Record< - string, - { readonly tables?: Readonly> } - >; - for (const ns of Object.values(namespaces)) { + for (const ns of storageNamespaceValues(contract.storage)) { const tbls = ns.tables; if (typeof tbls !== 'object' || tbls === null) continue; for (const table of Object.values(tbls)) { diff --git a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts index e4aac2281a..b20f986b82 100644 --- a/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts +++ b/packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts @@ -257,8 +257,8 @@ export function detectDestructiveChanges( ].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const fromNs = getStorageNamespace(from, namespaceId) as SqlNamespace | undefined; - const toNs = getStorageNamespace(to, namespaceId) as SqlNamespace | undefined; + const fromNs = getStorageNamespace(from, namespaceId); + const toNs = getStorageNamespace(to, namespaceId); const fromTables = fromNs?.tables; if (!fromTables) continue; @@ -335,7 +335,7 @@ export function contractToSchemaIR( const allTypes: Record = { ...((storage.types ?? {}) as ResolvedStorageTypes), }; - for (const ns of storageNamespaceValues(storage) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(storage)) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -345,7 +345,7 @@ export function contractToSchemaIR( } const storageTypes = allTypes as ResolvedStorageTypes; const tables: Record = {}; - for (const ns of storageNamespaceValues(storage) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(storage)) { for (const [tableName, tableDefRaw] of Object.entries(ns.tables)) { if (!(tableDefRaw instanceof StorageTable)) { throw new Error( diff --git a/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts b/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts index a3e2514750..25e7b5d483 100644 --- a/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts +++ b/packages/2-sql/9-family/src/core/migrations/field-event-planner.ts @@ -75,8 +75,8 @@ export function planFieldEventOperations( const dropped: FieldEntry[] = []; const altered: FieldEntry[] = []; - const priorStorage = priorContract?.storage as unknown as Record | undefined; - const newStorage = newContract.storage as unknown as Record; + const priorStorage = priorContract?.storage; + const newStorage = newContract.storage; const namespaceIds = unionSorted( priorStorage ? [...storageNamespaceEntries(priorStorage)].map(([id]) => id) : [], @@ -84,10 +84,8 @@ export function planFieldEventOperations( ); for (const namespaceId of namespaceIds) { - const priorNs = getStorageNamespace(priorStorage ?? {}, namespaceId) as - | SqlNamespace - | undefined; - const newNs = getStorageNamespace(newStorage, namespaceId) as SqlNamespace | undefined; + const priorNs = getStorageNamespace(priorStorage ?? {}, namespaceId); + const newNs = getStorageNamespace(newStorage, namespaceId); const priorTables = priorNs?.tables; const newTables = newNs?.tables; diff --git a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts index 88f849c7d2..23f062a716 100644 --- a/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts +++ b/packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts @@ -145,7 +145,7 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase PostgresEnumStorageEntry | StorageTypeInstance >), }; - for (const ns of storageNamespaceValues(contract.storage) as SqlNamespace[]) { + for (const ns of storageNamespaceValues(contract.storage)) { const nsEnums = (ns as { enum?: Record }).enum; if (nsEnums) { for (const [k, v] of Object.entries(nsEnums)) { @@ -228,7 +228,7 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase // Namespace-scoped enums, verified per `(namespaceId, typeName)`. for (const nsId of [...storageNamespaceEntries(contract.storage)].map(([id]) => id)) { - const ns = getStorageNamespace(contract.storage, nsId) as SqlNamespace | undefined; + const ns = getStorageNamespace(contract.storage, nsId); if (!ns) continue; const nsEnums = ns.enum; if (!nsEnums) continue; @@ -411,7 +411,7 @@ function verifySchemaTables(options: { .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); for (const namespaceId of namespaceIds) { - const ns = getStorageNamespace(contract.storage, namespaceId) as SqlNamespace | undefined; + const ns = getStorageNamespace(contract.storage, namespaceId); if (!ns) continue; for (const [tableName, contractTableRaw] of Object.entries(ns.tables)) { if (!(contractTableRaw instanceof StorageTable)) { @@ -466,9 +466,8 @@ function verifySchemaTables(options: { for (const tableName of Object.keys(schemaTables)) { const claimed = namespaceIds.some( (namespaceId) => - (getStorageNamespace(contract.storage, namespaceId) as SqlNamespace | undefined)?.tables[ - tableName - ] !== undefined, + getStorageNamespace(contract.storage, namespaceId)?.tables[tableName] !== + undefined, ); if (!claimed) { // `namespaceId` is intentionally absent: an extra table exists in the @@ -1192,7 +1191,7 @@ function resolveContractColumnTypeMetadata( return { codecId: referencedType.codecId, nativeType: referencedType.nativeType, - typeParams: { values: referencedType.values } as unknown as Record, + typeParams: { values: referencedType.values }, }; } if (isStorageTypeInstance(referencedType)) { diff --git a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts index cb9fb574ef..75a91279a7 100644 --- a/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts +++ b/packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts @@ -17,8 +17,7 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase { - const ns = nsData as MongoNamespaceShape; + [...storageNamespaceEntries(storage)].map(([nsId, ns]) => { const collections = ns.collections; const collectionCount = Object.keys(collections).length; if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0) { @@ -47,14 +46,13 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase = { storageHash: String(storage.storageHash), }; - for (const [nsId, ns] of [...storageNamespaceEntries(storage)]) { - const mongoNs = ns as MongoNamespaceShape; + for (const [nsId, ns] of [...storageNamespaceEntries(storage)]) { const collectionsOut: Record = {}; - for (const [collName, coll] of Object.entries(mongoNs.collections)) { + for (const [collName, coll] of Object.entries(ns.collections)) { collectionsOut[collName] = JSON.parse(JSON.stringify(coll)) as JsonObject; } storageOut[nsId] = { - id: mongoNs.id, + id: ns.id, kind: 'mongo-database', collections: collectionsOut, }; From 6a8430d28fb846cbd020e268fac101c9202e854f Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:56:57 +0200 Subject: [PATCH 33/60] fix(migration-tools): strip namespace kind on flat storage keys D1 dropped the storage.namespaces wrapper but left assertDescriptorSelfConsistency still walking the old nested shape, blocking extension descriptor checks during emit. Signed-off-by: Will Madden --- .../src/assert-descriptor-self-consistency.ts | 21 +++++++++++-------- ...assert-descriptor-self-consistency.test.ts | 6 +++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/1-framework/3-tooling/migration/src/assert-descriptor-self-consistency.ts b/packages/1-framework/3-tooling/migration/src/assert-descriptor-self-consistency.ts index 2ec2929d17..30efbf84cd 100644 --- a/packages/1-framework/3-tooling/migration/src/assert-descriptor-self-consistency.ts +++ b/packages/1-framework/3-tooling/migration/src/assert-descriptor-self-consistency.ts @@ -1,21 +1,24 @@ import type { PreserveEmptyPredicate, StorageSort } from '@prisma-next/contract/hashing'; import { computeStorageHash } from '@prisma-next/contract/hashing'; +import { isStoragePlaneReservedKey } from '@prisma-next/framework-components/ir'; import { ifDefined } from '@prisma-next/utils/defined'; import { errorDescriptorHeadHashMismatch } from './errors'; function stripNamespaceKinds(storage: Record): Record { - const namespaces = storage['namespaces'] as Record> | undefined; - if (!namespaces) return storage; - const stripped: Record> = {}; - for (const [nsId, ns] of Object.entries(namespaces)) { - if (ns && typeof ns === 'object') { - const { kind: _kind, ...rest } = ns as Record; - stripped[nsId] = rest as Record; + const stripped: Record = {}; + for (const [key, value] of Object.entries(storage)) { + if (isStoragePlaneReservedKey(key)) { + stripped[key] = value; + continue; + } + if (value && typeof value === 'object' && 'id' in value) { + const { kind: _kind, ...rest } = value as Record; + stripped[key] = rest; } else { - stripped[nsId] = ns; + stripped[key] = value; } } - return { ...storage, namespaces: stripped }; + return stripped; } /** diff --git a/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts b/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts index 40b7062611..8c18f95adb 100644 --- a/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts +++ b/packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts @@ -25,9 +25,9 @@ const TARGET = 'postgres'; const FAMILY = 'sql'; const sqlPreserveEmptyPatterns = [ - ['storage', 'namespaces', '*', 'tables'], - ['storage', 'namespaces', '*', 'tables', '*'], - ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', '*', 'tables'], + ['storage', '*', 'tables', '*'], + ['storage', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], ] as const satisfies readonly PathPattern[]; const sqlPreserveEmpty = createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); From 390cf8bef86c3d0094b9ccde338ed0d9f7ace447 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:57:00 +0200 Subject: [PATCH 34/60] chore: regen extension contract spaces and migration pins Re-emits contract.json/d.ts to the flat storage shape and repins head.json, migration.json, end-contract.*, and migration.ts to hashes for the wrapper-less storage plane. Signed-off-by: Will Madden --- .../end-contract.d.ts | 101 ++++++++++-------- .../end-contract.json | 62 ++++++----- .../migration.json | 4 +- .../migration.ts | 2 +- .../cipherstash/migrations/refs/head.json | 2 +- .../cipherstash/src/contract.d.ts | 56 +++++----- .../cipherstash/src/contract.json | 60 +++++------ .../end-contract.d.ts | 96 +++++++++++++++++ .../end-contract.json | 16 +-- .../migration.json | 4 +- .../migration.ts | 2 +- .../paradedb/migrations/refs/head.json | 2 +- .../3-extensions/paradedb/src/contract.d.ts | 16 ++- .../3-extensions/paradedb/src/contract.json | 12 +-- .../end-contract.d.ts | 61 ++++++----- .../end-contract.json | 28 +++-- .../migration.json | 4 +- .../migration.ts | 2 +- .../pgvector/migrations/refs/head.json | 2 +- .../3-extensions/pgvector/src/contract.d.ts | 16 ++- .../3-extensions/pgvector/src/contract.json | 12 +-- .../end-contract.d.ts | 61 ++++++----- .../end-contract.json | 28 +++-- .../migration.json | 4 +- .../migration.ts | 2 +- .../postgis/migrations/refs/head.json | 2 +- .../3-extensions/postgis/src/contract.d.ts | 16 ++- .../3-extensions/postgis/src/contract.json | 12 +-- 28 files changed, 407 insertions(+), 278 deletions(-) create mode 100644 packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.d.ts diff --git a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.d.ts b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.d.ts index 851a6f7ce1..880ae78a07 100644 --- a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.d.ts +++ b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -22,12 +24,13 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + NamespaceId, ProfileHashBase, StorageHashBase, } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec'>; + StorageHashBase<'sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -60,9 +63,10 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; @@ -92,42 +96,48 @@ type ContractBase = ContractType< }; }; }; - }; - readonly storageHash: StorageHash; - }, - { - readonly EqlV2Configuration: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly state: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly data: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/jsonb@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'eql_v2_configuration'; + }, + { + readonly EqlV2Configuration: { readonly fields: { - readonly id: { readonly column: 'id' }; - readonly state: { readonly column: 'state' }; - readonly data: { readonly column: 'data' }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly state: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly data: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/jsonb@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'eql_v2_configuration'; + readonly fields: { + readonly id: { readonly column: 'id' }; + readonly state: { readonly column: 'state' }; + readonly data: { readonly column: 'data' }; + }; }; }; - }; - } + } + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; - readonly roots: { readonly eql_v2_configuration: 'EqlV2Configuration' }; + readonly roots: { + readonly eql_v2_configuration: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'EqlV2Configuration'; + }; + }; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -137,6 +147,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -148,5 +159,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.json b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.json index 5842fde584..16ee5fc4dd 100644 --- a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.json +++ b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/end-contract.json @@ -4,7 +4,10 @@ "target": "postgres", "profileHash": "sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e", "roots": { - "eql_v2_configuration": "EqlV2Configuration" + "eql_v2_configuration": { + "model": "EqlV2Configuration", + "namespace": "__unbound__" + } }, "models": { "EqlV2Configuration": { @@ -49,42 +52,42 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "eql_v2_configuration": { - "columns": { - "data": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": false - }, - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "state": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "eql_v2_configuration": { + "columns": { + "data": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "state": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec" + "storageHash": "sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2" }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -94,6 +97,7 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, diff --git a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.json b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.json index 0aec8a1c20..281374c563 100644 --- a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.json +++ b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec", + "to": "sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2", "providedInvariants": ["cipherstash:install-eql-bundle-v1"], "createdAt": "2026-05-09T03:42:56.902Z", - "migrationHash": "sha256:51256125a87dd40756c9f97eee181c4ea33d308fefe252f4d9860fa95033c1b7" + "migrationHash": "sha256:35fd41c85929b78fde05cdfee0f1a52b3355d4cf6a22ea477201ac7b546ef0cf" } diff --git a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.ts b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.ts index ebbeb10f55..eeaf11a665 100755 --- a/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.ts +++ b/packages/3-extensions/cipherstash/migrations/20260601T0000_install_eql_bundle/migration.ts @@ -31,7 +31,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec', + to: 'sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2', }; } diff --git a/packages/3-extensions/cipherstash/migrations/refs/head.json b/packages/3-extensions/cipherstash/migrations/refs/head.json index 0faf88aa6d..ba47e637e5 100644 --- a/packages/3-extensions/cipherstash/migrations/refs/head.json +++ b/packages/3-extensions/cipherstash/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec", + "hash": "sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2", "invariants": ["cipherstash:install-eql-bundle-v1"] } diff --git a/packages/3-extensions/cipherstash/src/contract.d.ts b/packages/3-extensions/cipherstash/src/contract.d.ts index 8ebaa1f070..880ae78a07 100644 --- a/packages/3-extensions/cipherstash/src/contract.d.ts +++ b/packages/3-extensions/cipherstash/src/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec'>; + StorageHashBase<'sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -66,38 +66,36 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly eql_v2_configuration: { - columns: { - readonly id: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly state: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly data: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly eql_v2_configuration: { + columns: { + readonly id: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly state: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly data: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly EqlV2Configuration: { @@ -161,5 +159,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/cipherstash/src/contract.json b/packages/3-extensions/cipherstash/src/contract.json index 23ead99146..d45c0a47ec 100644 --- a/packages/3-extensions/cipherstash/src/contract.json +++ b/packages/3-extensions/cipherstash/src/contract.json @@ -52,42 +52,40 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "eql_v2_configuration": { - "columns": { - "data": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": false - }, - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "state": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "eql_v2_configuration": { + "columns": { + "data": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "state": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:5fde95fc595b8f1a040f5e6dd986cd913d07c018319d6fa18fdbeb6d507489ec" + "storageHash": "sha256:1d80ee12f4dcc582393f331c89a7a691866d5071348009e7caff9c6c5b1880f2" }, "capabilities": { "postgres": { diff --git a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.d.ts b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.d.ts new file mode 100644 index 0000000000..9022188151 --- /dev/null +++ b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.d.ts @@ -0,0 +1,96 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma-next contract emit'. +// To regenerate, run: prisma-next contract emit +import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; + +import type { + ContractWithTypeMaps, + TypeMaps as TypeMapsType, +} from '@prisma-next/sql-contract/types'; +import type { + Contract as ContractType, + ExecutionHashBase, + NamespaceId, + ProfileHashBase, + StorageHashBase, +} from '@prisma-next/contract/types'; + +export type StorageHash = + StorageHashBase<'sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; + +export type CodecTypes = PgTypes; +export type LaneCodecTypes = CodecTypes; +export type QueryOperationTypes = PgAdapterQueryOps; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? CodecTypes[CodecId]['output'] + : _Encoded; + +export type FieldOutputTypes = Record; +export type FieldInputTypes = Record; +export type TypeMaps = TypeMapsType< + CodecTypes, + QueryOperationTypes, + FieldOutputTypes, + FieldInputTypes +>; + +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: {}; + }; + }, + Record + >, + 'roots' +> & { + readonly target: 'postgres'; + readonly targetFamily: 'sql'; + readonly roots: Record; + readonly capabilities: { + readonly postgres: { + readonly distinctOn: true; + readonly jsonAgg: true; + readonly lateral: true; + readonly limit: true; + readonly orderBy: true; + readonly returning: true; + }; + readonly sql: { + readonly defaultInInsert: true; + readonly enums: true; + readonly lateral: true; + readonly returning: true; + }; + }; + readonly extensionPacks: {}; + readonly meta: {}; + + readonly profileHash: ProfileHash; +}; + +export type Contract = ContractWithTypeMaps; + +export type Namespaces = Omit; +export type Models = Contract['models']; diff --git a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.json b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.json index 114248ab53..96ceb89dc1 100644 --- a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.json +++ b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/end-contract.json @@ -6,17 +6,16 @@ "roots": {}, "models": {}, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716" + "storageHash": "sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919" }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -26,13 +25,14 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, "extensionPacks": {}, "meta": {}, "_generated": { - "warning": "\u26a0\ufe0f GENERATED FILE - DO NOT EDIT", + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", "message": "This file is automatically generated by \"prisma-next contract emit\".", "regenerate": "To regenerate, run: prisma-next contract emit" } diff --git a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.json b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.json index b73a6d054a..f72d519c44 100644 --- a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.json +++ b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716", + "to": "sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919", "providedInvariants": ["paradedb:install-pg-search-v1"], "createdAt": "2026-06-01T00:00:00.000Z", - "migrationHash": "sha256:b1b0a426b92236d755c27a1e961b05382f11438155ae95838fc4b08eb98942af" + "migrationHash": "sha256:0d0b8a8612f7ea869964488e04cb78869edc7a3855d5fff8738b4aa47d94921c" } diff --git a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.ts b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.ts index 255712aed2..f582faf451 100644 --- a/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.ts +++ b/packages/3-extensions/paradedb/migrations/20260601T0000_install_pg_search_extension/migration.ts @@ -29,7 +29,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716', + to: 'sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919', }; } diff --git a/packages/3-extensions/paradedb/migrations/refs/head.json b/packages/3-extensions/paradedb/migrations/refs/head.json index 40a3ca99f0..460bc124fd 100644 --- a/packages/3-extensions/paradedb/migrations/refs/head.json +++ b/packages/3-extensions/paradedb/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716", + "hash": "sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919", "invariants": ["paradedb:install-pg-search-v1"] } diff --git a/packages/3-extensions/paradedb/src/contract.d.ts b/packages/3-extensions/paradedb/src/contract.d.ts index 1060e2da9e..9022188151 100644 --- a/packages/3-extensions/paradedb/src/contract.d.ts +++ b/packages/3-extensions/paradedb/src/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716'>; + StorageHashBase<'sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -54,14 +54,12 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: {}; - }; - }; readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: {}; + }; }, Record >, @@ -94,5 +92,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/paradedb/src/contract.json b/packages/3-extensions/paradedb/src/contract.json index 7b5b1b5ffb..69e9f9243c 100644 --- a/packages/3-extensions/paradedb/src/contract.json +++ b/packages/3-extensions/paradedb/src/contract.json @@ -6,14 +6,12 @@ "roots": {}, "models": {}, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716" + "storageHash": "sha256:3cecabb7df81134678f27699ad531351e74237d7ab902fab5adf27f5a115b919" }, "capabilities": { "postgres": { diff --git a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.d.ts b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.d.ts index 020086ddf3..8a6b090aba 100644 --- a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.d.ts +++ b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -28,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716'>; + StorageHashBase<'sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -49,32 +51,34 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; readonly tables: {}; }; - }; - readonly types: { - readonly vector: { - readonly kind: 'codec-instance'; - readonly codecId: 'pg/vector@1'; - readonly nativeType: 'vector'; - readonly typeParams: Record; + readonly types: { + readonly vector: { + readonly kind: 'codec-instance'; + readonly codecId: 'pg/vector@1'; + readonly nativeType: 'vector'; + readonly typeParams: Record; + }; }; - }; - readonly storageHash: StorageHash; - }, - Record + }, + Record + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; readonly roots: Record; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -84,6 +88,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -95,5 +100,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.json b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.json index f15e414085..835780b859 100644 --- a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.json +++ b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/end-contract.json @@ -5,15 +5,25 @@ "profileHash": "sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e", "roots": {}, "models": {}, - "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} + "domain": { + "__unbound__": { + "types": { + "vector": { + "codecId": "pg/vector@1", + "kind": "codec-instance", + "nativeType": "vector", + "typeParams": {} + } } + } + }, + "storage": { + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:06733db8971afcf2fb373dcd1278e801e9e1e083d6f07d07bc7479e143c938e3", + "storageHash": "sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2", "types": { "vector": { "codecId": "pg/vector@1", @@ -25,6 +35,7 @@ }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -34,13 +45,14 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, "extensionPacks": {}, "meta": {}, "_generated": { - "warning": "\u26a0\ufe0f GENERATED FILE - DO NOT EDIT", + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", "message": "This file is automatically generated by \"prisma-next contract emit\".", "regenerate": "To regenerate, run: prisma-next contract emit" } diff --git a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.json b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.json index 82857246e8..4df731e26a 100644 --- a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.json +++ b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:06733db8971afcf2fb373dcd1278e801e9e1e083d6f07d07bc7479e143c938e3", + "to": "sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2", "providedInvariants": ["pgvector:install-vector-v1"], "createdAt": "2026-06-01T00:00:00.000Z", - "migrationHash": "sha256:d7357d41724e5a75f74909bf558d6d36f317d01a8f1a61e5a92f80d4ca07718b" + "migrationHash": "sha256:8e13a18abc8faa67d001f3de938066b0c819bd1d2ffceb558bcece82d337f509" } diff --git a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.ts b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.ts index ff7a8ed85a..4ed31e8464 100644 --- a/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.ts +++ b/packages/3-extensions/pgvector/migrations/20260601T0000_install_vector_extension/migration.ts @@ -31,7 +31,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716', + to: 'sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2', }; } diff --git a/packages/3-extensions/pgvector/migrations/refs/head.json b/packages/3-extensions/pgvector/migrations/refs/head.json index 8448284168..72fd4f0fa1 100644 --- a/packages/3-extensions/pgvector/migrations/refs/head.json +++ b/packages/3-extensions/pgvector/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:06733db8971afcf2fb373dcd1278e801e9e1e083d6f07d07bc7479e143c938e3", + "hash": "sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2", "invariants": ["pgvector:install-vector-v1"] } diff --git a/packages/3-extensions/pgvector/src/contract.d.ts b/packages/3-extensions/pgvector/src/contract.d.ts index 161af94792..8a6b090aba 100644 --- a/packages/3-extensions/pgvector/src/contract.d.ts +++ b/packages/3-extensions/pgvector/src/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:06733db8971afcf2fb373dcd1278e801e9e1e083d6f07d07bc7479e143c938e3'>; + StorageHashBase<'sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -54,12 +54,11 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: {}; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: {}; }; readonly types: { readonly vector: { @@ -69,7 +68,6 @@ type ContractBase = Omit< readonly typeParams: Record; }; }; - readonly storageHash: StorageHash; }, Record >, @@ -102,5 +100,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/pgvector/src/contract.json b/packages/3-extensions/pgvector/src/contract.json index 7fd0309f22..7f7e86d88e 100644 --- a/packages/3-extensions/pgvector/src/contract.json +++ b/packages/3-extensions/pgvector/src/contract.json @@ -18,14 +18,12 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:06733db8971afcf2fb373dcd1278e801e9e1e083d6f07d07bc7479e143c938e3", + "storageHash": "sha256:115db76021324eb08a3febda023639a9825fc2ccdad424a43bd40e90fb0c72a2", "types": { "vector": { "codecId": "pg/vector@1", diff --git a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.d.ts b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.d.ts index 3446bdad4c..9c8fc7d3f6 100644 --- a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.d.ts +++ b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -28,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716'>; + StorageHashBase<'sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -49,32 +51,34 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; readonly tables: {}; }; - }; - readonly types: { - readonly geometry: { - readonly kind: 'codec-instance'; - readonly codecId: 'pg/geometry@1'; - readonly nativeType: 'geometry'; - readonly typeParams: Record; + readonly types: { + readonly geometry: { + readonly kind: 'codec-instance'; + readonly codecId: 'pg/geometry@1'; + readonly nativeType: 'geometry'; + readonly typeParams: Record; + }; }; - }; - readonly storageHash: StorageHash; - }, - Record + }, + Record + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; readonly roots: Record; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -84,6 +88,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -95,5 +100,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.json b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.json index 54c86aca6a..91cdc46ec8 100644 --- a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.json +++ b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/end-contract.json @@ -5,15 +5,25 @@ "profileHash": "sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e", "roots": {}, "models": {}, - "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} + "domain": { + "__unbound__": { + "types": { + "geometry": { + "codecId": "pg/geometry@1", + "kind": "codec-instance", + "nativeType": "geometry", + "typeParams": {} + } } + } + }, + "storage": { + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:9f708ee91f4d72f868ef5e93ce6dd13ccf48bab53c8015241a6e808c6f853584", + "storageHash": "sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd", "types": { "geometry": { "codecId": "pg/geometry@1", @@ -25,6 +35,7 @@ }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -34,13 +45,14 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, "extensionPacks": {}, "meta": {}, "_generated": { - "warning": "\u26a0\ufe0f GENERATED FILE - DO NOT EDIT", + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", "message": "This file is automatically generated by \"prisma-next contract emit\".", "regenerate": "To regenerate, run: prisma-next contract emit" } diff --git a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.json b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.json index 7a01998af3..0b0f3405db 100644 --- a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.json +++ b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:9f708ee91f4d72f868ef5e93ce6dd13ccf48bab53c8015241a6e808c6f853584", + "to": "sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd", "providedInvariants": ["postgis:install-postgis-v1"], "createdAt": "2026-06-01T00:00:00.000Z", - "migrationHash": "sha256:aedc7025e377b8359829f552af79274ebbfe69d0ed73db9ad9489491d57885ae" + "migrationHash": "sha256:559866a0f50a9aad9fa8ca542a31194d558a9f2978eec1e934703d76d7608326" } diff --git a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.ts b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.ts index ccc2b2ad1e..c94115df41 100644 --- a/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.ts +++ b/packages/3-extensions/postgis/migrations/20260601T0000_install_postgis_extension/migration.ts @@ -29,7 +29,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:59877923532fc526703f572f1d54bdd6050502ef30ede3333ee1393ea93b9716', + to: 'sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd', }; } diff --git a/packages/3-extensions/postgis/migrations/refs/head.json b/packages/3-extensions/postgis/migrations/refs/head.json index 4981930c9f..45ef0ad05d 100644 --- a/packages/3-extensions/postgis/migrations/refs/head.json +++ b/packages/3-extensions/postgis/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:9f708ee91f4d72f868ef5e93ce6dd13ccf48bab53c8015241a6e808c6f853584", + "hash": "sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd", "invariants": ["postgis:install-postgis-v1"] } diff --git a/packages/3-extensions/postgis/src/contract.d.ts b/packages/3-extensions/postgis/src/contract.d.ts index 01f57fa979..9c8fc7d3f6 100644 --- a/packages/3-extensions/postgis/src/contract.d.ts +++ b/packages/3-extensions/postgis/src/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:9f708ee91f4d72f868ef5e93ce6dd13ccf48bab53c8015241a6e808c6f853584'>; + StorageHashBase<'sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -54,12 +54,11 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: {}; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: {}; }; readonly types: { readonly geometry: { @@ -69,7 +68,6 @@ type ContractBase = Omit< readonly typeParams: Record; }; }; - readonly storageHash: StorageHash; }, Record >, @@ -102,5 +100,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/postgis/src/contract.json b/packages/3-extensions/postgis/src/contract.json index 1b8a28c650..0bc7edfa45 100644 --- a/packages/3-extensions/postgis/src/contract.json +++ b/packages/3-extensions/postgis/src/contract.json @@ -18,14 +18,12 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {} - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {} }, - "storageHash": "sha256:9f708ee91f4d72f868ef5e93ce6dd13ccf48bab53c8015241a6e808c6f853584", + "storageHash": "sha256:08ab8402844e68e603fb59cd255e16ad63157287308fac8f828db9faff5573cd", "types": { "geometry": { "codecId": "pg/geometry@1", From 792eb0a45c180b0b32b492ebf04d1c63f47624d3 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:57:03 +0200 Subject: [PATCH 35/60] chore: regen example and app contracts to flat storage shape Signed-off-by: Will Madden --- .../src/prisma/contract.d.ts | 184 ++- .../src/prisma/contract.json | 202 ++- .../src/mongo/generated/contract.d.ts | 12 +- .../src/mongo/generated/contract.json | 18 +- .../src/postgres/generated/contract.d.ts | 38 +- .../src/postgres/generated/contract.json | 48 +- .../src/prisma/contract.d.ts | 114 +- .../src/prisma/contract.json | 144 +- .../mongo-blog-leaderboard/src/contract.d.ts | 170 ++- .../mongo-blog-leaderboard/src/contract.json | 316 +++-- examples/mongo-demo/src/contract.d.ts | 166 ++- examples/mongo-demo/src/contract.json | 306 ++-- .../paradedb-demo/src/prisma/contract.d.ts | 80 +- .../paradedb-demo/src/prisma/contract.json | 98 +- .../src/prisma/contract.d.ts | 362 +++-- .../src/prisma/contract.json | 444 +++--- .../src/prisma/contract.d.ts | 156 +- .../src/prisma/contract.json | 192 ++- .../prisma-next-demo/src/prisma/contract.d.ts | 374 +++-- .../prisma-next-demo/src/prisma/contract.json | 462 +++--- .../src/prisma/contract.d.ts | 152 +- .../src/prisma/contract.json | 176 ++- .../src/prisma/contract.d.ts | 142 +- .../src/prisma/contract.json | 176 ++- examples/retail-store/src/contract.d.ts | 736 +++++----- examples/retail-store/src/contract.json | 1250 ++++++++--------- 26 files changed, 3232 insertions(+), 3286 deletions(-) diff --git a/apps/telemetry-backend/src/prisma/contract.d.ts b/apps/telemetry-backend/src/prisma/contract.d.ts index dfc0f9d134..6c9c1568a3 100644 --- a/apps/telemetry-backend/src/prisma/contract.d.ts +++ b/apps/telemetry-backend/src/prisma/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:41700ef5fda97339b39ea345a56aae72a1ff4be11ddc3ffcab7130bfc71c109d'>; + StorageHashBase<'sha256:4f585a6d5ec8b2a0b4026bad9292393ac5298aee4e6f7dccfdda4a1e13279c84'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -90,103 +90,101 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly telemetry_event: { - columns: { - readonly id: { - readonly nativeType: 'int8'; - readonly codecId: 'pg/int8@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly ingestedAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly installationId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly version: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly command: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly flags: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; - }; - readonly runtimeName: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly runtimeVersion: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly os: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly arch: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly packageManager: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly databaseTarget: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly tsVersion: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly agent: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly extensions: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly telemetry_event: { + columns: { + readonly id: { + readonly nativeType: 'int8'; + readonly codecId: 'pg/int8@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [{ readonly columns: readonly ['ingestedAt'] }]; - foreignKeys: readonly []; + readonly ingestedAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly installationId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly version: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly command: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly flags: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; + }; + readonly runtimeName: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly runtimeVersion: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly os: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly arch: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly packageManager: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly databaseTarget: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly tsVersion: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly agent: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly extensions: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; + }; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [{ readonly columns: readonly ['ingestedAt'] }]; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly TelemetryEvent: { @@ -312,5 +310,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/apps/telemetry-backend/src/prisma/contract.json b/apps/telemetry-backend/src/prisma/contract.json index 09c8ad240f..ebd46d4698 100644 --- a/apps/telemetry-backend/src/prisma/contract.json +++ b/apps/telemetry-backend/src/prisma/contract.json @@ -174,116 +174,114 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "telemetry_event": { - "columns": { - "agent": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "arch": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "command": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "databaseTarget": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "extensions": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": false - }, - "flags": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": false - }, - "id": { - "codecId": "pg/int8@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int8", - "nullable": false - }, - "ingestedAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "installationId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "os": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "packageManager": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "runtimeName": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "runtimeVersion": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "telemetry_event": { + "columns": { + "agent": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + }, + "arch": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "command": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "databaseTarget": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + }, + "extensions": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": false + }, + "flags": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": false + }, + "id": { + "codecId": "pg/int8@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "tsVersion": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true + "nativeType": "int8", + "nullable": false + }, + "ingestedAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "version": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "timestamptz", + "nullable": false + }, + "installationId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "os": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "packageManager": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true }, - "foreignKeys": [], - "indexes": [ - { - "columns": [ - "ingestedAt" - ] - } - ], - "primaryKey": { + "runtimeName": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "runtimeVersion": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "tsVersion": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + }, + "version": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [ + { "columns": [ - "id" + "ingestedAt" ] - }, - "uniques": [] - } + } + ], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:41700ef5fda97339b39ea345a56aae72a1ff4be11ddc3ffcab7130bfc71c109d" + "storageHash": "sha256:4f585a6d5ec8b2a0b4026bad9292393ac5298aee4e6f7dccfdda4a1e13279c84" }, "capabilities": { "postgres": { diff --git a/examples/bundle-size/src/mongo/generated/contract.d.ts b/examples/bundle-size/src/mongo/generated/contract.d.ts index 97496ac3e0..f668dcc9af 100644 --- a/examples/bundle-size/src/mongo/generated/contract.d.ts +++ b/examples/bundle-size/src/mongo/generated/contract.d.ts @@ -35,14 +35,12 @@ export type TypeMaps = MongoTypeMaps; + StorageHashBase<'sha256:28583a90f7a7ff4a76a53eead4a5e4e84bd089f1c1adb08dc5cbb56e56970aef'>; export type ExecutionHash = ExecutionHashBase<'sha256:63d6acb216ecdc40b22f2e9d59e179ccec2d77cdc9ac3d6711a363046d02660e'>; export type ProfileHash = @@ -55,29 +55,27 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly Note: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly Note: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Note: { @@ -141,5 +139,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/bundle-size/src/postgres/generated/contract.json b/examples/bundle-size/src/postgres/generated/contract.json index 50a998284d..f2693bd98e 100644 --- a/examples/bundle-size/src/postgres/generated/contract.json +++ b/examples/bundle-size/src/postgres/generated/contract.json @@ -35,35 +35,33 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "Note": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "Note": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:4c8df727b4bc8b1c76ea8ecf452785f537e8e80fa6d66e271262e81bd4bd232c" + "storageHash": "sha256:28583a90f7a7ff4a76a53eead4a5e4e84bd089f1c1adb08dc5cbb56e56970aef" }, "execution": { "executionHash": "sha256:63d6acb216ecdc40b22f2e9d59e179ccec2d77cdc9ac3d6711a363046d02660e", diff --git a/examples/cipherstash-integration/src/prisma/contract.d.ts b/examples/cipherstash-integration/src/prisma/contract.d.ts index f70c1d3c35..62c0ac671a 100644 --- a/examples/cipherstash-integration/src/prisma/contract.d.ts +++ b/examples/cipherstash-integration/src/prisma/contract.d.ts @@ -40,7 +40,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:904fa8a2fad61c89714ac8144d9244f8f0dd9827d45555f2f09d571d46f9d22d'>; + StorageHashBase<'sha256:4687e5a836c1c9a4a3d0d889de2fe1abc4f7068432ada9f3fa15d34c5b872ed4'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -85,68 +85,66 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/string@1'; - readonly nullable: false; - readonly typeParams: { - readonly equality: true; - readonly freeTextSearch: true; - readonly orderAndRange: true; - }; - }; - readonly salary: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/double@1'; - readonly nullable: false; - readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; - }; - readonly accountid: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/bigint@1'; - readonly nullable: false; - readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; - }; - readonly birthday: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/date@1'; - readonly nullable: false; - readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; - }; - readonly emailverified: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/boolean@1'; - readonly nullable: false; - readonly typeParams: { readonly equality: true }; - }; - readonly preferences: { - readonly nativeType: 'eql_v2_encrypted'; - readonly codecId: 'cipherstash/json@1'; - readonly nullable: false; - readonly typeParams: { readonly searchableJson: true }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/string@1'; + readonly nullable: false; + readonly typeParams: { + readonly equality: true; + readonly freeTextSearch: true; + readonly orderAndRange: true; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly salary: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/double@1'; + readonly nullable: false; + readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; + }; + readonly accountid: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/bigint@1'; + readonly nullable: false; + readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; + }; + readonly birthday: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/date@1'; + readonly nullable: false; + readonly typeParams: { readonly equality: true; readonly orderAndRange: true }; + }; + readonly emailverified: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/boolean@1'; + readonly nullable: false; + readonly typeParams: { readonly equality: true }; + }; + readonly preferences: { + readonly nativeType: 'eql_v2_encrypted'; + readonly codecId: 'cipherstash/json@1'; + readonly nullable: false; + readonly typeParams: { readonly searchableJson: true }; + }; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly User: { @@ -499,5 +497,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/cipherstash-integration/src/prisma/contract.json b/examples/cipherstash-integration/src/prisma/contract.json index a16f0049a0..91cbca3aa5 100644 --- a/examples/cipherstash-integration/src/prisma/contract.json +++ b/examples/cipherstash-integration/src/prisma/contract.json @@ -115,85 +115,83 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "users": { - "columns": { - "accountid": { - "codecId": "cipherstash/bigint@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } - }, - "birthday": { - "codecId": "cipherstash/date@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } - }, - "email": { - "codecId": "cipherstash/string@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "freeTextSearch": true, - "orderAndRange": true - } - }, - "emailverified": { - "codecId": "cipherstash/boolean@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true - } - }, - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "preferences": { - "codecId": "cipherstash/json@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "searchableJson": true - } - }, - "salary": { - "codecId": "cipherstash/double@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "users": { + "columns": { + "accountid": { + "codecId": "cipherstash/bigint@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "birthday": { + "codecId": "cipherstash/date@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true + } }, - "uniques": [] - } + "email": { + "codecId": "cipherstash/string@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "freeTextSearch": true, + "orderAndRange": true + } + }, + "emailverified": { + "codecId": "cipherstash/boolean@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true + } + }, + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "preferences": { + "codecId": "cipherstash/json@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "searchableJson": true + } + }, + "salary": { + "codecId": "cipherstash/double@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true + } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:904fa8a2fad61c89714ac8144d9244f8f0dd9827d45555f2f09d571d46f9d22d" + "storageHash": "sha256:4687e5a836c1c9a4a3d0d889de2fe1abc4f7068432ada9f3fa15d34c5b872ed4" }, "capabilities": { "postgres": { diff --git a/examples/mongo-blog-leaderboard/src/contract.d.ts b/examples/mongo-blog-leaderboard/src/contract.d.ts index d284584dd7..fb4cfd8d20 100644 --- a/examples/mongo-blog-leaderboard/src/contract.d.ts +++ b/examples/mongo-blog-leaderboard/src/contract.d.ts @@ -85,102 +85,100 @@ export type TypeMaps = MongoTypeMaps; + StorageHashBase<'sha256:7a0dcb146f55a9e986e84aa8aa3557ea8a4cb0211e818789487c808cd90fd358'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -70,50 +70,48 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly item: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly description: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly category: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly rating: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly item: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly description: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly category: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly rating: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly columns: readonly ['id', 'description', 'category', 'rating']; - readonly name: 'item_bm25_idx'; - readonly type: 'bm25'; - readonly options: { readonly key_field: 'id' }; - }, - ]; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly columns: readonly ['id', 'description', 'category', 'rating']; + readonly name: 'item_bm25_idx'; + readonly type: 'bm25'; + readonly options: { readonly key_field: 'id' }; + }, + ]; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Item: { @@ -198,5 +196,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/paradedb-demo/src/prisma/contract.json b/examples/paradedb-demo/src/prisma/contract.json index 83991cf080..4c2b718c4f 100644 --- a/examples/paradedb-demo/src/prisma/contract.json +++ b/examples/paradedb-demo/src/prisma/contract.json @@ -62,61 +62,59 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "item": { - "columns": { - "category": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "description": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "rating": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "item": { + "columns": { + "category": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [ - { - "columns": [ - "id", - "description", - "category", - "rating" - ], - "name": "item_bm25_idx", - "options": { - "key_field": "id" - }, - "type": "bm25" - } - ], - "primaryKey": { - "columns": [ - "id" - ] + "description": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "rating": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [ + { + "columns": [ + "id", + "description", + "category", + "rating" + ], + "name": "item_bm25_idx", + "options": { + "key_field": "id" + }, + "type": "bm25" + } + ], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:3e50138f416ab435ac8933c9d2ddcb725f486464d13054a8100f17f06d97f970" + "storageHash": "sha256:7a0dcb146f55a9e986e84aa8aa3557ea8a4cb0211e818789487c808cd90fd358" }, "capabilities": { "postgres": { diff --git a/examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts b/examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts index 2b1e82d9df..aa09d8948b 100644 --- a/examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts +++ b/examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:17abc48ded558b285a28eb130b6ac0f66d0d2a11ac02389fbbef48731b82960a'>; + StorageHashBase<'sha256:f109722aa5127cc3d6332578eba041e6d6003d572389ae2e9536ad6b4bfa352b'>; export type ExecutionHash = ExecutionHashBase<'sha256:516d134296237bb5f427dfe28f42f79077d0b72cbcae281fdd1ba3c974b9568e'>; export type ProfileHash = @@ -130,204 +130,202 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly bug: { - columns: { - readonly severity: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly stepsToRepro: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly bug: { + columns: { + readonly severity: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly stepsToRepro: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly feature: { - columns: { - readonly priority: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly targetRelease: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly feature: { + columns: { + readonly priority: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly targetRelease: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly post: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly post: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly task: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; - readonly description: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly status: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; - }; - }; - readonly type: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly task: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly description: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly status: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; + readonly type: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; }; - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly displayName: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly kind: { - readonly nativeType: 'user_type'; - readonly codecId: 'pg/enum@1'; - readonly nullable: false; - readonly typeRef: 'user_type'; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['userId']; }; - readonly address: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly displayName: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly kind: { + readonly nativeType: 'user_type'; + readonly codecId: 'pg/enum@1'; + readonly nullable: false; + readonly typeRef: 'user_type'; + }; + readonly address: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; - readonly public: { - readonly id: 'public'; - readonly kind: 'sql-namespace'; - readonly tables: {}; - }; }; - readonly storageHash: StorageHash; + readonly public: { + readonly id: 'public'; + readonly kind: 'sql-namespace'; + readonly tables: {}; + }; }, { readonly Bug: { @@ -626,5 +624,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/prisma-next-cloudflare-worker/src/prisma/contract.json b/examples/prisma-next-cloudflare-worker/src/prisma/contract.json index 6bb1ceb98d..701bd32c29 100644 --- a/examples/prisma-next-cloudflare-worker/src/prisma/contract.json +++ b/examples/prisma-next-cloudflare-worker/src/prisma/contract.json @@ -405,251 +405,249 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "bug": { - "columns": { - "severity": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "stepsToRepro": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "bug": { + "columns": { + "severity": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "uniques": [] + "stepsToRepro": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + } }, - "feature": { - "columns": { - "priority": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "targetRelease": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - } + "foreignKeys": [], + "indexes": [], + "uniques": [] + }, + "feature": { + "columns": { + "priority": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "uniques": [] + "targetRelease": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + } }, - "post": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "foreignKeys": [], + "indexes": [], + "uniques": [] + }, + "post": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "userId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "task": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "description": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "post" }, - "status": { - "codecId": "pg/text@1", - "default": { - "kind": "literal", - "value": "open" - }, - "nativeType": "text", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "type": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "userId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "task": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false + }, + "description": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "task" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] - }, - "user": { - "columns": { - "address": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true - }, - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "displayName": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "status": { + "codecId": "pg/text@1", + "default": { + "kind": "literal", + "value": "open" }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "nativeType": "text", + "nullable": false + }, + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "type": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "task" }, - "kind": { - "codecId": "pg/enum@1", - "nativeType": "user_type", - "nullable": false, - "typeRef": "user_type" + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "user": { + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false }, - "uniques": [] - } - } - }, - "public": { - "enum": { - "user_type": { - "codecId": "pg/enum@1", - "kind": "postgres-enum", - "name": "user_type", - "nativeType": "user_type", - "values": [ - "admin", - "user" + "displayName": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } + }, + "kind": { + "codecId": "pg/enum@1", + "nativeType": "user_type", + "nullable": false, + "typeRef": "user_type" + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] - } - }, - "id": "public", - "kind": "postgres-schema", - "tables": {} + }, + "uniques": [] + } } }, - "storageHash": "sha256:17abc48ded558b285a28eb130b6ac0f66d0d2a11ac02389fbbef48731b82960a" + "public": { + "enum": { + "user_type": { + "codecId": "pg/enum@1", + "kind": "postgres-enum", + "name": "user_type", + "nativeType": "user_type", + "values": [ + "admin", + "user" + ] + } + }, + "id": "public", + "kind": "postgres-schema", + "tables": {} + }, + "storageHash": "sha256:f109722aa5127cc3d6332578eba041e6d6003d572389ae2e9536ad6b4bfa352b" }, "execution": { "executionHash": "sha256:516d134296237bb5f427dfe28f42f79077d0b72cbcae281fdd1ba3c974b9568e", diff --git a/examples/prisma-next-demo-sqlite/src/prisma/contract.d.ts b/examples/prisma-next-demo-sqlite/src/prisma/contract.d.ts index 70dc977677..331ae7584b 100644 --- a/examples/prisma-next-demo-sqlite/src/prisma/contract.d.ts +++ b/examples/prisma-next-demo-sqlite/src/prisma/contract.d.ts @@ -16,7 +16,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:8ea47fc08b79e387a9136d5a4ac708723e4941a7b3cffe71d57ceca09229a486'>; + StorageHashBase<'sha256:6226d6da02629ab654132cbd0203889b99cf0754bb74e4d6a1354d51269d27e0'>; export type ExecutionHash = ExecutionHashBase<'sha256:0903547be862dca3fa2dbc62a85cd52e9ca595f00cf43b6b26a3da3d4b9740ae'>; export type ProfileHash = @@ -67,92 +67,90 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly post: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly createdAt: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/datetime@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly post: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly createdAt: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/datetime@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly name: 'post_userId_fkey'; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly displayName: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/datetime@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; + readonly name: 'post_userId_fkey'; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly displayName: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/datetime@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Post: { @@ -295,5 +293,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/prisma-next-demo-sqlite/src/prisma/contract.json b/examples/prisma-next-demo-sqlite/src/prisma/contract.json index eb6716c898..12867caac1 100644 --- a/examples/prisma-next-demo-sqlite/src/prisma/contract.json +++ b/examples/prisma-next-demo-sqlite/src/prisma/contract.json @@ -157,115 +157,113 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "post": { - "columns": { - "createdAt": { - "codecId": "sqlite/datetime@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "post": { + "columns": { + "createdAt": { + "codecId": "sqlite/datetime@1", + "default": { + "expression": "now()", + "kind": "function" }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "userId": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - } + "nativeType": "text", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "name": "post_userId_fkey", - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] + "title": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } + } }, - "user": { - "columns": { - "createdAt": { - "codecId": "sqlite/datetime@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "text", - "nullable": false - }, - "displayName": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "email": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false + "foreignKeys": [ + { + "constraint": true, + "index": true, + "name": "post_userId_fkey", + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "post" }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "user": { + "columns": { + "createdAt": { + "codecId": "sqlite/datetime@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "displayName": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "email": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:8ea47fc08b79e387a9136d5a4ac708723e4941a7b3cffe71d57ceca09229a486" + "storageHash": "sha256:6226d6da02629ab654132cbd0203889b99cf0754bb74e4d6a1354d51269d27e0" }, "execution": { "executionHash": "sha256:0903547be862dca3fa2dbc62a85cd52e9ca595f00cf43b6b26a3da3d4b9740ae", diff --git a/examples/prisma-next-demo/src/prisma/contract.d.ts b/examples/prisma-next-demo/src/prisma/contract.d.ts index 6a18c32137..6fc82fcbb2 100644 --- a/examples/prisma-next-demo/src/prisma/contract.d.ts +++ b/examples/prisma-next-demo/src/prisma/contract.d.ts @@ -35,7 +35,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:e75bed0cca186c56a9b3b0e93e70c90e3fec8900f565c602c229e44979e4d83d'>; + StorageHashBase<'sha256:51ccfc517b4587059223adee0925ec430e469b7f9296e125b428be6e04cbefa6'>; export type ExecutionHash = ExecutionHashBase<'sha256:516d134296237bb5f427dfe28f42f79077d0b72cbcae281fdd1ba3c974b9568e'>; export type ProfileHash = @@ -138,208 +138,207 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly bug: { - columns: { - readonly severity: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly stepsToRepro: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly bug: { + columns: { + readonly severity: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly stepsToRepro: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly feature: { - columns: { - readonly priority: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly targetRelease: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly feature: { + columns: { + readonly priority: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly targetRelease: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly post: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeRef: 'Embedding1536'; - }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly post: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeRef: 'Embedding1536'; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly task: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly description: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly status: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; - }; - }; - readonly type: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; }; - readonly userId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly task: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly description: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + }; + readonly status: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; + readonly type: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; }; - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly displayName: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly kind: { - readonly nativeType: 'user_type'; - readonly codecId: 'pg/enum@1'; - readonly nullable: false; - readonly typeRef: 'user_type'; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['userId']; }; - readonly address: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly displayName: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly kind: { + readonly nativeType: 'user_type'; + readonly codecId: 'pg/enum@1'; + readonly nullable: false; + readonly typeRef: 'user_type'; + }; + readonly address: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; - readonly public: { - readonly id: 'public'; - readonly kind: 'sql-namespace'; - readonly tables: {}; - }; + }; + readonly public: { + readonly id: 'public'; + readonly kind: 'sql-namespace'; + readonly tables: {}; }; readonly types: { readonly Embedding1536: { @@ -349,7 +348,6 @@ type ContractBase = Omit< readonly typeParams: { readonly length: 1536 }; }; }; - readonly storageHash: StorageHash; }, { readonly Bug: { @@ -701,5 +699,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/prisma-next-demo/src/prisma/contract.json b/examples/prisma-next-demo/src/prisma/contract.json index 2a1b51d954..e21861340a 100644 --- a/examples/prisma-next-demo/src/prisma/contract.json +++ b/examples/prisma-next-demo/src/prisma/contract.json @@ -429,257 +429,255 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "bug": { - "columns": { - "severity": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "stepsToRepro": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "bug": { + "columns": { + "severity": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "uniques": [] - }, - "feature": { - "columns": { - "priority": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "targetRelease": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - } + "stepsToRepro": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "uniques": [] + }, + "feature": { + "columns": { + "priority": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "uniques": [] - }, - "post": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeRef": "Embedding1536" - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "targetRelease": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "uniques": [] + }, + "post": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "userId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeRef": "Embedding1536" + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] - }, - "task": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "description": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "status": { - "codecId": "pg/text@1", - "default": { - "kind": "literal", - "value": "open" - }, - "nativeType": "text", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "type": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "post" }, - "userId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "task": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "task" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "description": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] - }, - "user": { - "columns": { - "address": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true - }, - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "displayName": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "status": { + "codecId": "pg/text@1", + "default": { + "kind": "literal", + "value": "open" }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "nativeType": "text", + "nullable": false + }, + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "type": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "task" }, - "kind": { - "codecId": "pg/enum@1", - "nativeType": "user_type", - "nullable": false, - "typeRef": "user_type" + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "user": { + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false }, - "uniques": [] - } - } - }, - "public": { - "enum": { - "user_type": { - "codecId": "pg/enum@1", - "kind": "postgres-enum", - "name": "user_type", - "nativeType": "user_type", - "values": [ - "admin", - "user" + "displayName": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } + }, + "kind": { + "codecId": "pg/enum@1", + "nativeType": "user_type", + "nullable": false, + "typeRef": "user_type" + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] - } - }, - "id": "public", - "kind": "postgres-schema", - "tables": {} + }, + "uniques": [] + } } }, - "storageHash": "sha256:e75bed0cca186c56a9b3b0e93e70c90e3fec8900f565c602c229e44979e4d83d", + "public": { + "enum": { + "user_type": { + "codecId": "pg/enum@1", + "kind": "postgres-enum", + "name": "user_type", + "nativeType": "user_type", + "values": [ + "admin", + "user" + ] + } + }, + "id": "public", + "kind": "postgres-schema", + "tables": {} + }, + "storageHash": "sha256:51ccfc517b4587059223adee0925ec430e469b7f9296e125b428be6e04cbefa6", "types": { "Embedding1536": { "codecId": "pg/vector@1", diff --git a/examples/prisma-next-postgis-demo/src/prisma/contract.d.ts b/examples/prisma-next-postgis-demo/src/prisma/contract.d.ts index 5be45532c2..ec520c2f95 100644 --- a/examples/prisma-next-postgis-demo/src/prisma/contract.d.ts +++ b/examples/prisma-next-postgis-demo/src/prisma/contract.d.ts @@ -35,7 +35,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:05692bcae4c7e37855a66f6bbd809676cfa57fc42fa985e465846615cc3c715f'>; + StorageHashBase<'sha256:3af5dc8fea082e50a05522704dab8e2910621a41f485b0e72964d81678ce8b79'>; export type ExecutionHash = ExecutionHashBase<'sha256:815d2a9f2f35488d3bb0aeba687849e2475b621c331c1d27ad79e37b6e56182f'>; export type ProfileHash = @@ -93,86 +93,85 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly cafe: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly location: { - readonly nativeType: 'geometry'; - readonly codecId: 'pg/geometry@1'; - readonly nullable: false; - readonly typeRef: 'WgsGeometry'; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly cafe: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly location: { + readonly nativeType: 'geometry'; + readonly codecId: 'pg/geometry@1'; + readonly nullable: false; + readonly typeRef: 'WgsGeometry'; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly neighborhood: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly boundary: { - readonly nativeType: 'geometry'; - readonly codecId: 'pg/geometry@1'; - readonly nullable: false; - readonly typeRef: 'WgsGeometry'; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly neighborhood: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly boundary: { + readonly nativeType: 'geometry'; + readonly codecId: 'pg/geometry@1'; + readonly nullable: false; + readonly typeRef: 'WgsGeometry'; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly route: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly path: { - readonly nativeType: 'geometry'; - readonly codecId: 'pg/geometry@1'; - readonly nullable: false; - readonly typeRef: 'WgsGeometry'; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly route: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly path: { + readonly nativeType: 'geometry'; + readonly codecId: 'pg/geometry@1'; + readonly nullable: false; + readonly typeRef: 'WgsGeometry'; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; @@ -184,7 +183,6 @@ type ContractBase = Omit< readonly typeParams: { readonly srid: 4326 }; }; }; - readonly storageHash: StorageHash; }, { readonly Cafe: { @@ -372,5 +370,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/prisma-next-postgis-demo/src/prisma/contract.json b/examples/prisma-next-postgis-demo/src/prisma/contract.json index 4c5b7706bd..ee714db1ec 100644 --- a/examples/prisma-next-postgis-demo/src/prisma/contract.json +++ b/examples/prisma-next-postgis-demo/src/prisma/contract.json @@ -163,108 +163,106 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "cafe": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "location": { - "codecId": "pg/geometry@1", - "nativeType": "geometry", - "nullable": false, - "typeRef": "WgsGeometry" - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "cafe": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "location": { + "codecId": "pg/geometry@1", + "nativeType": "geometry", + "nullable": false, + "typeRef": "WgsGeometry" }, - "uniques": [] + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "neighborhood": { - "columns": { - "boundary": { - "codecId": "pg/geometry@1", - "nativeType": "geometry", - "nullable": false, - "typeRef": "WgsGeometry" - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "neighborhood": { + "columns": { + "boundary": { + "codecId": "pg/geometry@1", + "nativeType": "geometry", + "nullable": false, + "typeRef": "WgsGeometry" }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } }, - "uniques": [] + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "route": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "path": { - "codecId": "pg/geometry@1", - "nativeType": "geometry", - "nullable": false, - "typeRef": "WgsGeometry" + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "route": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "path": { + "codecId": "pg/geometry@1", + "nativeType": "geometry", + "nullable": false, + "typeRef": "WgsGeometry" + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:05692bcae4c7e37855a66f6bbd809676cfa57fc42fa985e465846615cc3c715f", + "storageHash": "sha256:3af5dc8fea082e50a05522704dab8e2910621a41f485b0e72964d81678ce8b79", "types": { "WgsGeometry": { "codecId": "pg/geometry@1", diff --git a/examples/react-router-demo/src/prisma/contract.d.ts b/examples/react-router-demo/src/prisma/contract.d.ts index e7bb6e3cda..47f24845dd 100644 --- a/examples/react-router-demo/src/prisma/contract.d.ts +++ b/examples/react-router-demo/src/prisma/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:5d2b2c2468240c79ee5f380951267aa777888966aeba3bb19d573825189e7816'>; + StorageHashBase<'sha256:c9ceab1ec344e64f4a3799b97e7d458c360b422993dc71895758726ebdac9e0a'>; export type ExecutionHash = ExecutionHashBase<'sha256:8c5eef43d2153fd832b8288ed2d8ffc9f5afb62908f8b4b7e6a4b7018444c41f'>; export type ProfileHash = @@ -79,85 +79,83 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly post: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly post: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly userId: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Post: { @@ -296,5 +294,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/react-router-demo/src/prisma/contract.json b/examples/react-router-demo/src/prisma/contract.json index 5ee83f6119..4ff408cbc7 100644 --- a/examples/react-router-demo/src/prisma/contract.json +++ b/examples/react-router-demo/src/prisma/contract.json @@ -144,107 +144,105 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "post": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "post": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "userId": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "userId" - ], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "user" - } + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "user": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "userId" + ], + "namespaceId": "__unbound__", + "tableName": "post" }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "user" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "user": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" + }, + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 + } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:5d2b2c2468240c79ee5f380951267aa777888966aeba3bb19d573825189e7816" + "storageHash": "sha256:c9ceab1ec344e64f4a3799b97e7d458c360b422993dc71895758726ebdac9e0a" }, "execution": { "executionHash": "sha256:8c5eef43d2153fd832b8288ed2d8ffc9f5afb62908f8b4b7e6a4b7018444c41f", diff --git a/examples/retail-store/src/contract.d.ts b/examples/retail-store/src/contract.d.ts index a3eb81b473..d21e36eab9 100644 --- a/examples/retail-store/src/contract.d.ts +++ b/examples/retail-store/src/contract.d.ts @@ -243,421 +243,417 @@ export type TypeMaps = MongoTypeMaps Date: Sat, 30 May 2026 23:57:20 +0200 Subject: [PATCH 36/60] chore: regen sql-builder and sql-orm-client package fixtures Signed-off-by: Will Madden --- .../test/fixtures/generated/contract.d.ts | 248 +++++---- .../test/fixtures/generated/contract.json | 282 +++++----- .../test/fixtures/generated/contract.d.ts | 408 +++++++------- .../test/fixtures/generated/contract.json | 506 +++++++++--------- 4 files changed, 718 insertions(+), 726 deletions(-) diff --git a/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts b/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts index 0cacf19081..3f162477c5 100644 --- a/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts +++ b/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts @@ -35,7 +35,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:704c054ab903f2505136a2b7731c33b1c82394c8d2a5a74f8152d6a14fbd9138'>; + StorageHashBase<'sha256:bc8892c2546fef8a5c65dce066d76e4c04d6bbcd60cd8a28597f775c6f9d6d26'>; export type ExecutionHash = ExecutionHashBase<'sha256:4d09909b2e09a240919c201ce4a5e63c3a2ec70515932e145dccca82936d8be5'>; export type ProfileHash = @@ -114,142 +114,140 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly articles: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly articles: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly comments: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly body: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly post_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly comments: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly body: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly post_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly posts: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly views: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 3 }; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly posts: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly views: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 3 }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly profiles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly bio: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly profiles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly bio: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly invited_by_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: true; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly invited_by_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Article: { @@ -568,5 +566,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json b/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json index 9bfbd5382f..f856ea447f 100644 --- a/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json +++ b/packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json @@ -346,166 +346,164 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "articles": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "articles": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "comments": { - "columns": { - "body": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "post_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "comments": { + "columns": { + "body": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "post_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "posts": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeParams": { - "length": 3 - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "views": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "posts": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeParams": { + "length": 3 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "views": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "profiles": { - "columns": { - "bio": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "profiles": { + "columns": { + "bio": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "users": { - "columns": { - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "invited_by_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": true - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "users": { + "columns": { + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - } + "invited_by_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": true + }, + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:704c054ab903f2505136a2b7731c33b1c82394c8d2a5a74f8152d6a14fbd9138" + "storageHash": "sha256:bc8892c2546fef8a5c65dce066d76e4c04d6bbcd60cd8a28597f775c6f9d6d26" }, "execution": { "executionHash": "sha256:4d09909b2e09a240919c201ce4a5e63c3a2ec70515932e145dccca82936d8be5", diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts index 2f048e2c2b..dcd2661fbe 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts @@ -39,7 +39,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:09cdc4f47bebe6d16423f62b2be3025be4bdd8936fe2f7f529d176a9524a6cdf'>; + StorageHashBase<'sha256:e1b41da5eceab4b14db8a439e047010df31a09d926fe87ed1b5be20fa1850da6'>; export type ExecutionHash = ExecutionHashBase<'sha256:e216decd356eea44980cf151c6044d85fb936e1fad093fbfb93ca34b96cf5847'>; export type ProfileHash = @@ -139,230 +139,228 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly articles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly reviewer_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly articles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly reviewer_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly comments: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly body: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly post_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly comments: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly body: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly post_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'comments'; - readonly columns: readonly ['post_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'posts'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly posts: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'comments'; + readonly columns: readonly ['post_id']; }; - readonly views: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 3 }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'posts'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly posts: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly views: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 3 }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'posts'; - readonly columns: readonly ['user_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly profiles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'posts'; + readonly columns: readonly ['user_id']; }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly bio: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly profiles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly bio: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['user_id'] }]; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'profiles'; - readonly columns: readonly ['user_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly tags: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['user_id'] }]; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'profiles'; + readonly columns: readonly ['user_id']; }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly tags: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['name'] }]; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly invited_by_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: true; - }; - readonly address: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['name'] }]; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly invited_by_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: true; + }; + readonly address: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['email'] }]; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['invited_by_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['email'] }]; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['invited_by_id']; + }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; + }; + readonly constraint: true; + readonly index: true; + }, + ]; }; }; }; - readonly storageHash: StorageHash; }, { readonly Article: { @@ -753,5 +751,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json index d9193bfaa9..38ae78fcad 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json +++ b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json @@ -459,292 +459,290 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "articles": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "reviewer_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "articles": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "reviewer_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - }, - "comments": { - "columns": { - "body": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "post_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "comments": { + "columns": { + "body": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "post_id" - ], - "namespaceId": "__unbound__", - "tableName": "comments" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "posts" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - }, - "posts": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeParams": { - "length": 3 - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "post_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "post_id" + ], + "namespaceId": "__unbound__", + "tableName": "comments" }, - "views": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "posts" } - }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "user_id" - ], - "namespaceId": "__unbound__", - "tableName": "posts" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "posts": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeParams": { + "length": 3 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] - }, - "profiles": { - "columns": { - "bio": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "user_id" - ], - "namespaceId": "__unbound__", - "tableName": "profiles" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "views": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { "columns": [ "user_id" - ] + ], + "namespaceId": "__unbound__", + "tableName": "posts" + }, + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] }, - "tags": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "uniques": [] + }, + "profiles": { + "columns": { + "bio": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "user_id" + ], + "namespaceId": "__unbound__", + "tableName": "profiles" + }, + "target": { "columns": [ - "name" - ] + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] }, - "users": { - "columns": { - "address": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "invited_by_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": true - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "uniques": [ + { + "columns": [ + "user_id" + ] + } + ] + }, + "tags": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "invited_by_id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } - } - ], - "indexes": [], - "primaryKey": { + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [ + { "columns": [ - "id" + "name" ] + } + ] + }, + "users": { + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "invited_by_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": true + }, + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "invited_by_id" + ], + "namespaceId": "__unbound__", + "tableName": "users" + }, + "target": { "columns": [ - "email" - ] + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] - } + }, + "uniques": [ + { + "columns": [ + "email" + ] + } + ] } } }, - "storageHash": "sha256:09cdc4f47bebe6d16423f62b2be3025be4bdd8936fe2f7f529d176a9524a6cdf" + "storageHash": "sha256:e1b41da5eceab4b14db8a439e047010df31a09d926fe87ed1b5be20fa1850da6" }, "execution": { "executionHash": "sha256:e216decd356eea44980cf151c6044d85fb936e1fad093fbfb93ca34b96cf5847", From 2ecb2176c4aadaad19998df4b0dd34a969be4e6d Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:57:20 +0200 Subject: [PATCH 37/60] chore: regen integration and e2e contract fixtures Updates main integration fixture, mongo generated copy, authoring parity expected JSON, side-by-side fixtures, and flat-storage strip helper in side-by-side-contracts.test.ts. Signed-off-by: Will Madden --- .../test/fixtures/generated/contract.d.ts | 660 ++++++++------- .../test/fixtures/generated/contract.json | 792 +++++++++--------- .../sqlite/fixtures/generated/contract.d.ts | 312 ++++--- .../sqlite/fixtures/generated/contract.json | 354 ++++---- .../expected.contract.json | 224 +++-- .../expected.contract.json | 52 +- .../expected.contract.json | 50 +- .../expected.contract.json | 52 +- .../expected.contract.json | 54 +- .../expected.contract.json | 52 +- .../expected.contract.json | 54 +- .../core-surface/expected.contract.json | 248 +++--- .../default-cuid-2/expected.contract.json | 42 +- .../expected.contract.json | 46 +- .../default-nanoid-16/expected.contract.json | 42 +- .../default-nanoid/expected.contract.json | 42 +- .../expected.contract.json | 38 +- .../default-ulid/expected.contract.json | 42 +- .../default-uuid-v4/expected.contract.json | 42 +- .../default-uuid-v7/expected.contract.json | 42 +- .../map-attributes/expected.contract.json | 122 ++- .../expected.contract.json | 56 +- .../expected.contract.json | 106 ++- .../authoring/side-by-side-contracts.test.ts | 51 +- .../side-by-side/mongo/contract.json | 134 ++- .../side-by-side/postgres/contract.json | 176 ++-- test/integration/test/fixtures/contract.d.ts | 56 +- test/integration/test/fixtures/contract.json | 60 +- .../mongo/fixtures/generated/contract.d.ts | 44 +- .../mongo/fixtures/generated/contract.json | 60 +- 30 files changed, 2023 insertions(+), 2082 deletions(-) diff --git a/test/e2e/framework/test/fixtures/generated/contract.d.ts b/test/e2e/framework/test/fixtures/generated/contract.d.ts index 5dff80b3e6..83ddef4c88 100644 --- a/test/e2e/framework/test/fixtures/generated/contract.d.ts +++ b/test/e2e/framework/test/fixtures/generated/contract.d.ts @@ -36,7 +36,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:c01040e095a1fe5dd776c2d5afe4a7767c506e044e96ecd46bfa369a75a13733'>; + StorageHashBase<'sha256:da755f593677f7710efeaf6a073bc0d8d580bb191c379a09c61772b1ce7269b6'>; export type ExecutionHash = ExecutionHashBase<'sha256:adc296c2bde14cd4e6a8a85ba202108dc7a320b5870a14d7dd8e2d2e2f5a7f27'>; export type ProfileHash = @@ -176,368 +176,366 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly comment: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly postId: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly content: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly created_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly update_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: true; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly comment: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly postId: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly content: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly created_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly update_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: true; + }; }; - readonly embedding: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 1536 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly embedding: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; - readonly profile: { - readonly nativeType: 'jsonb'; - readonly codecId: 'arktype/json@1'; - readonly nullable: false; - readonly typeParams: { - readonly expression: '{ age: number, name: string }'; - readonly jsonIr: { - readonly required: readonly [ - { readonly key: 'age'; readonly value: 'number' }, - { readonly key: 'name'; readonly value: 'string' }, - ]; - readonly domain: 'object'; - }; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 1536 }; + }; + readonly profile: { + readonly nativeType: 'jsonb'; + readonly codecId: 'arktype/json@1'; + readonly nullable: false; + readonly typeParams: { + readonly expression: '{ age: number, name: string }'; + readonly jsonIr: { + readonly required: readonly [ + { readonly key: 'age'; readonly value: 'number' }, + { readonly key: 'name'; readonly value: 'string' }, + ]; + readonly domain: 'object'; }; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly event: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly scheduled_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue< - 'pg/timestamptz@1', - '2024-01-15T10:30:00.000Z' - >; - }; - }; - readonly created_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly event: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly scheduled_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue< + 'pg/timestamptz@1', + '2024-01-15T10:30:00.000Z' + >; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly created_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; }; - readonly literal_defaults: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly literal_defaults: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; - readonly label: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'draft'>; - }; + }; + readonly label: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/text@1', 'draft'>; }; - readonly score: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/int4@1', 0>; - }; + }; + readonly score: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/int4@1', 0>; }; - readonly rating: { - readonly nativeType: 'float8'; - readonly codecId: 'pg/float8@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/float8@1', 3.14>; - }; + }; + readonly rating: { + readonly nativeType: 'float8'; + readonly codecId: 'pg/float8@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/float8@1', 3.14>; }; - readonly active: { - readonly nativeType: 'bool'; - readonly codecId: 'pg/bool@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/bool@1', true>; - }; + }; + readonly active: { + readonly nativeType: 'bool'; + readonly codecId: 'pg/bool@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/bool@1', true>; }; - readonly big_count: { - readonly nativeType: 'int8'; - readonly codecId: 'pg/int8@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/int8@1', 9007199254740991>; - }; + }; + readonly big_count: { + readonly nativeType: 'int8'; + readonly codecId: 'pg/int8@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/int8@1', 9007199254740991>; }; - readonly metadata: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/jsonb@1', { readonly key: 'default' }>; - }; + }; + readonly metadata: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/jsonb@1', { readonly key: 'default' }>; }; - readonly tags: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/jsonb@1', readonly ['alpha', 'beta']>; - }; + }; + readonly tags: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/jsonb@1', readonly ['alpha', 'beta']>; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly param_types: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly name: { - readonly nativeType: 'character varying'; - readonly codecId: 'sql/varchar@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 255 }; - }; - readonly code: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 16 }; - }; - readonly price: { - readonly nativeType: 'numeric'; - readonly codecId: 'pg/numeric@1'; - readonly nullable: true; - readonly typeParams: { readonly precision: 10; readonly scale: 2 }; - }; - readonly flags: { - readonly nativeType: 'bit'; - readonly codecId: 'pg/bit@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 8 }; - }; - readonly bits: { - readonly nativeType: 'bit varying'; - readonly codecId: 'pg/varbit@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 12 }; - }; - readonly created_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: true; - readonly typeParams: { readonly precision: 3 }; - }; - readonly starts_at: { - readonly nativeType: 'time'; - readonly codecId: 'pg/time@1'; - readonly nullable: true; - readonly typeParams: { readonly precision: 2 }; - }; - readonly starts_at_tz: { - readonly nativeType: 'timetz'; - readonly codecId: 'pg/timetz@1'; - readonly nullable: true; - readonly typeParams: { readonly precision: 2 }; - }; - readonly duration: { - readonly nativeType: 'interval'; - readonly codecId: 'pg/interval@1'; - readonly nullable: true; - readonly typeParams: { readonly precision: 6 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly param_types: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly name: { + readonly nativeType: 'character varying'; + readonly codecId: 'sql/varchar@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 255 }; + }; + readonly code: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 16 }; + }; + readonly price: { + readonly nativeType: 'numeric'; + readonly codecId: 'pg/numeric@1'; + readonly nullable: true; + readonly typeParams: { readonly precision: 10; readonly scale: 2 }; + }; + readonly flags: { + readonly nativeType: 'bit'; + readonly codecId: 'pg/bit@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 8 }; + }; + readonly bits: { + readonly nativeType: 'bit varying'; + readonly codecId: 'pg/varbit@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 12 }; + }; + readonly created_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: true; + readonly typeParams: { readonly precision: 3 }; + }; + readonly starts_at: { + readonly nativeType: 'time'; + readonly codecId: 'pg/time@1'; + readonly nullable: true; + readonly typeParams: { readonly precision: 2 }; + }; + readonly starts_at_tz: { + readonly nativeType: 'timetz'; + readonly codecId: 'pg/timetz@1'; + readonly nullable: true; + readonly typeParams: { readonly precision: 2 }; + }; + readonly duration: { + readonly nativeType: 'interval'; + readonly codecId: 'pg/interval@1'; + readonly nullable: true; + readonly typeParams: { readonly precision: 6 }; + }; }; - readonly post: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly userId: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly created_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly update_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: true; - }; - readonly published: { - readonly nativeType: 'bool'; - readonly codecId: 'pg/bool@1'; - readonly nullable: false; - }; - readonly meta: { - readonly nativeType: 'json'; - readonly codecId: 'pg/json@1'; - readonly nullable: true; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly post: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly userId: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly created_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly update_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: true; + }; + readonly published: { + readonly nativeType: 'bool'; + readonly codecId: 'pg/bool@1'; + readonly nullable: false; + }; + readonly meta: { + readonly nativeType: 'json'; + readonly codecId: 'pg/json@1'; + readonly nullable: true; + }; }; - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly email: { - readonly nativeType: 'character varying'; - readonly codecId: 'sql/varchar@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 255 }; - }; - readonly created_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly update_at: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: true; - }; - readonly profile: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [ - { readonly columns: readonly ['email']; readonly name: 'user_email_key' }, - ]; - indexes: readonly []; - foreignKeys: readonly []; + readonly email: { + readonly nativeType: 'character varying'; + readonly codecId: 'sql/varchar@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 255 }; + }; + readonly created_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + }; + readonly update_at: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: true; + }; + readonly profile: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + }; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [ + { readonly columns: readonly ['email']; readonly name: 'user_email_key' }, + ]; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Comment: { @@ -1063,5 +1061,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/test/e2e/framework/test/fixtures/generated/contract.json b/test/e2e/framework/test/fixtures/generated/contract.json index 840b0d2a0c..5dc3eabafb 100644 --- a/test/e2e/framework/test/fixtures/generated/contract.json +++ b/test/e2e/framework/test/fixtures/generated/contract.json @@ -642,446 +642,444 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "comment": { - "columns": { - "content": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "created_at": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "comment": { + "columns": { + "content": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "created_at": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "postId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "nativeType": "timestamptz", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "update_at": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": true - } + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "postId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "update_at": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": true + } }, - "embedding": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": false, - "typeParams": { - "length": 1536 - } - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "profile": { - "codecId": "arktype/json@1", - "nativeType": "jsonb", - "nullable": false, - "typeParams": { - "expression": "{ age: number, name: string }", - "jsonIr": { - "domain": "object", - "required": [ - { - "key": "age", - "value": "number" - }, - { - "key": "name", - "value": "string" - } - ] - } - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "embedding": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": false, + "typeParams": { + "length": 1536 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] - }, - "event": { - "columns": { - "created_at": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 + "nativeType": "int4", + "nullable": false + }, + "profile": { + "codecId": "arktype/json@1", + "nativeType": "jsonb", + "nullable": false, + "typeParams": { + "expression": "{ age: number, name: string }", + "jsonIr": { + "domain": "object", + "required": [ + { + "key": "age", + "value": "number" + }, + { + "key": "name", + "value": "string" + } + ] } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "scheduled_at": { - "codecId": "pg/timestamptz@1", - "default": { - "kind": "literal", - "value": "2024-01-15T10:30:00.000Z" - }, - "nativeType": "timestamptz", - "nullable": false } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] + } }, - "literal_defaults": { - "columns": { - "active": { - "codecId": "pg/bool@1", - "default": { - "kind": "literal", - "value": true - }, - "nativeType": "bool", - "nullable": false - }, - "big_count": { - "codecId": "pg/int8@1", - "default": { - "kind": "literal", - "value": 9007199254740991 - }, - "nativeType": "int8", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "label": { - "codecId": "pg/text@1", - "default": { - "kind": "literal", - "value": "draft" - }, - "nativeType": "text", - "nullable": false - }, - "metadata": { - "codecId": "pg/jsonb@1", - "default": { - "kind": "literal", - "value": { - "key": "default" - } - }, - "nativeType": "jsonb", - "nullable": false - }, - "rating": { - "codecId": "pg/float8@1", - "default": { - "kind": "literal", - "value": 3.14 - }, - "nativeType": "float8", - "nullable": false - }, - "score": { - "codecId": "pg/int4@1", - "default": { - "kind": "literal", - "value": 0 - }, - "nativeType": "int4", - "nullable": false + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "event": { + "columns": { + "created_at": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "tags": { - "codecId": "pg/jsonb@1", - "default": { - "kind": "literal", - "value": [ - "alpha", - "beta" - ] - }, - "nativeType": "jsonb", - "nullable": false + "nativeType": "timestamptz", + "nullable": false + }, + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] + "scheduled_at": { + "codecId": "pg/timestamptz@1", + "default": { + "kind": "literal", + "value": "2024-01-15T10:30:00.000Z" + }, + "nativeType": "timestamptz", + "nullable": false + } }, - "param_types": { - "columns": { - "bits": { - "codecId": "pg/varbit@1", - "nativeType": "bit varying", - "nullable": true, - "typeParams": { - "length": 12 - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "literal_defaults": { + "columns": { + "active": { + "codecId": "pg/bool@1", + "default": { + "kind": "literal", + "value": true }, - "code": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": true, - "typeParams": { - "length": 16 - } + "nativeType": "bool", + "nullable": false + }, + "big_count": { + "codecId": "pg/int8@1", + "default": { + "kind": "literal", + "value": 9007199254740991 }, - "created_at": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": true, - "typeParams": { - "precision": 3 - } + "nativeType": "int8", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "duration": { - "codecId": "pg/interval@1", - "nativeType": "interval", - "nullable": true, - "typeParams": { - "precision": 6 - } + "nativeType": "int4", + "nullable": false + }, + "label": { + "codecId": "pg/text@1", + "default": { + "kind": "literal", + "value": "draft" }, - "flags": { - "codecId": "pg/bit@1", - "nativeType": "bit", - "nullable": true, - "typeParams": { - "length": 8 + "nativeType": "text", + "nullable": false + }, + "metadata": { + "codecId": "pg/jsonb@1", + "default": { + "kind": "literal", + "value": { + "key": "default" } }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "name": { - "codecId": "sql/varchar@1", - "nativeType": "character varying", - "nullable": true, - "typeParams": { - "length": 255 - } + "nativeType": "jsonb", + "nullable": false + }, + "rating": { + "codecId": "pg/float8@1", + "default": { + "kind": "literal", + "value": 3.14 }, - "price": { - "codecId": "pg/numeric@1", - "nativeType": "numeric", - "nullable": true, - "typeParams": { - "precision": 10, - "scale": 2 - } + "nativeType": "float8", + "nullable": false + }, + "score": { + "codecId": "pg/int4@1", + "default": { + "kind": "literal", + "value": 0 }, - "starts_at": { - "codecId": "pg/time@1", - "nativeType": "time", - "nullable": true, - "typeParams": { - "precision": 2 - } + "nativeType": "int4", + "nullable": false + }, + "tags": { + "codecId": "pg/jsonb@1", + "default": { + "kind": "literal", + "value": [ + "alpha", + "beta" + ] }, - "starts_at_tz": { - "codecId": "pg/timetz@1", - "nativeType": "timetz", - "nullable": true, - "typeParams": { - "precision": 2 - } + "nativeType": "jsonb", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "param_types": { + "columns": { + "bits": { + "codecId": "pg/varbit@1", + "nativeType": "bit varying", + "nullable": true, + "typeParams": { + "length": 12 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "code": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": true, + "typeParams": { + "length": 16 + } }, - "uniques": [] - }, - "post": { - "columns": { - "created_at": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "meta": { - "codecId": "pg/json@1", - "nativeType": "json", - "nullable": true - }, - "published": { - "codecId": "pg/bool@1", - "nativeType": "bool", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "update_at": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": true + "created_at": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": true, + "typeParams": { + "precision": 3 + } + }, + "duration": { + "codecId": "pg/interval@1", + "nativeType": "interval", + "nullable": true, + "typeParams": { + "precision": 6 + } + }, + "flags": { + "codecId": "pg/bit@1", + "nativeType": "bit", + "nullable": true, + "typeParams": { + "length": 8 + } + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "userId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "nativeType": "int4", + "nullable": false + }, + "name": { + "codecId": "sql/varchar@1", + "nativeType": "character varying", + "nullable": true, + "typeParams": { + "length": 255 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "price": { + "codecId": "pg/numeric@1", + "nativeType": "numeric", + "nullable": true, + "typeParams": { + "precision": 10, + "scale": 2 + } + }, + "starts_at": { + "codecId": "pg/time@1", + "nativeType": "time", + "nullable": true, + "typeParams": { + "precision": 2 + } }, - "uniques": [] + "starts_at_tz": { + "codecId": "pg/timetz@1", + "nativeType": "timetz", + "nullable": true, + "typeParams": { + "precision": 2 + } + } }, - "user": { - "columns": { - "created_at": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "email": { - "codecId": "sql/varchar@1", - "nativeType": "character varying", - "nullable": false, - "typeParams": { - "length": 255 - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "post": { + "columns": { + "created_at": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false + "nativeType": "timestamptz", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "profile": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true + "nativeType": "int4", + "nullable": false + }, + "meta": { + "codecId": "pg/json@1", + "nativeType": "json", + "nullable": true + }, + "published": { + "codecId": "pg/bool@1", + "nativeType": "bool", + "nullable": false + }, + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "update_at": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": true + }, + "userId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "user": { + "columns": { + "created_at": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "update_at": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": true + "nativeType": "timestamptz", + "nullable": false + }, + "email": { + "codecId": "sql/varchar@1", + "nativeType": "character varying", + "nullable": false, + "typeParams": { + "length": 255 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" + }, + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { - "columns": [ - "email" - ], - "name": "user_email_key" - } + "profile": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true + }, + "update_at": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] - } + }, + "uniques": [ + { + "columns": [ + "email" + ], + "name": "user_email_key" + } + ] } } }, - "storageHash": "sha256:c01040e095a1fe5dd776c2d5afe4a7767c506e044e96ecd46bfa369a75a13733" + "storageHash": "sha256:da755f593677f7710efeaf6a073bc0d8d580bb191c379a09c61772b1ce7269b6" }, "execution": { "executionHash": "sha256:adc296c2bde14cd4e6a8a85ba202108dc7a320b5870a14d7dd8e2d2e2f5a7f27", diff --git a/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts b/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts index a2ca3c8e42..a3a284fb9d 100644 --- a/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts +++ b/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts @@ -16,7 +16,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:f52ad65b0b6148af276fd084c349f8f21a4a4da2ba8644dfac21b53dd47d9791'>; + StorageHashBase<'sha256:c9b8112d2bc5ad1845c89c5e6c1e6d24e9970755d5e274cef0d11b0e9e36447e'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:c336398c1b6d00d93aa155a4f9072e69f55d1897ba5b9bbfd3d7d32f6b270bf4'>; @@ -110,177 +110,175 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly comments: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly body: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly post_id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly comments: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly body: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly post_id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly items: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly label: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'sqlite/text@1', 'unnamed'>; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly items: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly label: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'sqlite/text@1', 'unnamed'>; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly posts: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly views: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly posts: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly views: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly profiles: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly bio: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly profiles: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly bio: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly typed_rows: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly active: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly created_at: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/datetime@1'; - readonly nullable: false; - }; - readonly metadata: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/json@1'; - readonly nullable: true; - }; - readonly label: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly typed_rows: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly active: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly created_at: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/datetime@1'; + readonly nullable: false; + }; + readonly metadata: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/json@1'; + readonly nullable: true; + }; + readonly label: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'sqlite/text@1'; - readonly nullable: false; - }; - readonly invited_by_id: { - readonly nativeType: 'integer'; - readonly codecId: 'sqlite/integer@1'; - readonly nullable: true; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'sqlite/text@1'; + readonly nullable: false; + }; + readonly invited_by_id: { + readonly nativeType: 'integer'; + readonly codecId: 'sqlite/integer@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Comment: { @@ -541,5 +539,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/test/e2e/framework/test/sqlite/fixtures/generated/contract.json b/test/e2e/framework/test/sqlite/fixtures/generated/contract.json index d6201bab00..4f686ca8b5 100644 --- a/test/e2e/framework/test/sqlite/fixtures/generated/contract.json +++ b/test/e2e/framework/test/sqlite/fixtures/generated/contract.json @@ -374,200 +374,198 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "comments": { - "columns": { - "body": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "post_id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "comments": { + "columns": { + "body": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "uniques": [] - }, - "items": { - "columns": { - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "label": { - "codecId": "sqlite/text@1", - "default": { - "kind": "literal", - "value": "unnamed" - }, - "nativeType": "text", - "nullable": false + "post_id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "items": { + "columns": { + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false + }, + "label": { + "codecId": "sqlite/text@1", + "default": { + "kind": "literal", + "value": "unnamed" }, - "name": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "name": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "posts": { + "columns": { + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "uniques": [] - }, - "posts": { - "columns": { - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "title": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "user_id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "views": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - } + "title": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "user_id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "uniques": [] - }, - "profiles": { - "columns": { - "bio": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "user_id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - } + "views": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "profiles": { + "columns": { + "bio": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "uniques": [] - }, - "typed_rows": { - "columns": { - "active": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "created_at": { - "codecId": "sqlite/datetime@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "label": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "metadata": { - "codecId": "sqlite/json@1", - "nativeType": "text", - "nullable": true - } + "user_id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "typed_rows": { + "columns": { + "active": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "created_at": { + "codecId": "sqlite/datetime@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - }, - "users": { - "columns": { - "email": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": false - }, - "invited_by_id": { - "codecId": "sqlite/integer@1", - "nativeType": "integer", - "nullable": true - }, - "name": { - "codecId": "sqlite/text@1", - "nativeType": "text", - "nullable": false - } + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "label": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "metadata": { + "codecId": "sqlite/json@1", + "nativeType": "text", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "users": { + "columns": { + "email": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": false + }, + "invited_by_id": { + "codecId": "sqlite/integer@1", + "nativeType": "integer", + "nullable": true + }, + "name": { + "codecId": "sqlite/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:f52ad65b0b6148af276fd084c349f8f21a4a4da2ba8644dfac21b53dd47d9791" + "storageHash": "sha256:c9b8112d2bc5ad1845c89c5e6c1e6d24e9970755d5e274cef0d11b0e9e36447e" }, "capabilities": { "sql": { diff --git a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json index 1f28919c49..ae51c461cb 100644 --- a/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json +++ b/test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json @@ -182,132 +182,130 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "post": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "rating": { - "codecId": "pg/float8@1", - "nativeType": "float8", - "nullable": true - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "post": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "userId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "onDelete": "cascade", - "onUpdate": "cascade", - "source": { - "columns": ["userId"], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": ["id"], - "namespaceId": "__unbound__", - "tableName": "user" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "rating": { + "codecId": "pg/float8@1", + "nativeType": "float8", + "nullable": true + }, + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] + "userId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "user": { - "columns": { - "age": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeRef": "Embedding" + "foreignKeys": [ + { + "constraint": true, + "index": true, + "onDelete": "cascade", + "onUpdate": "cascade", + "source": { + "columns": ["userId"], + "namespaceId": "__unbound__", + "tableName": "post" }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false + "target": { + "columns": ["id"], + "namespaceId": "__unbound__", + "tableName": "user" + } + } + ], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] + }, + "user": { + "columns": { + "age": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "isActive": { - "codecId": "pg/bool@1", - "default": { - "kind": "literal", - "value": true - }, - "nativeType": "bool", - "nullable": false + "nativeType": "timestamptz", + "nullable": false + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeRef": "Embedding" + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "profile": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true + "nativeType": "int4", + "nullable": false + }, + "isActive": { + "codecId": "pg/bool@1", + "default": { + "kind": "literal", + "value": true }, - "score": { - "codecId": "pg/float8@1", - "nativeType": "float8", - "nullable": true - } + "nativeType": "bool", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "profile": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true }, - "uniques": [ - { - "columns": ["email"] - } - ] - } + "score": { + "codecId": "pg/float8@1", + "nativeType": "float8", + "nullable": true + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [ + { + "columns": ["email"] + } + ] } } }, - "storageHash": "sha256:8fa256466ba485fa9d52edc98e76680a9afd7042e65db1fcbb35b8518cf9fd28", + "storageHash": "sha256:759ded773797f219d64ee004c5bf28c0719f6d0414d5dbfec201e02189632bfa", "types": { "Embedding": { "codecId": "pg/vector@1", diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-bigint/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-bigint/expected.contract.json index 3a82f9cea3..17a556fdcd 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-bigint/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-bigint/expected.contract.json @@ -46,38 +46,36 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "accountId": { - "codecId": "cipherstash/bigint@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "accountId": { + "codecId": "cipherstash/bigint@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:73209f1b4c69e0b60c2a2764d14577580e306833a75d8799f1293337efebdbfd" + "storageHash": "sha256:5d9b62063802f52eb5caead1d1c9895573704d07f74b09974cf2e89b567ada0c" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-boolean/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-boolean/expected.contract.json index 45f95e992c..5e7274393b 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-boolean/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-boolean/expected.contract.json @@ -45,37 +45,35 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "emailVerified": { - "codecId": "cipherstash/boolean@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "emailVerified": { + "codecId": "cipherstash/boolean@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:bb3360cc55ef940de7380342ae8d961c8b577ebb128cfbb105e783ff5a4b63ff" + "storageHash": "sha256:3926c4fe50922d16928c80929b25d6d3e9e4e085d2f0b5dcd5e3e2b50d49ceea" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-date/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-date/expected.contract.json index feb9430607..38d49e4ec3 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-date/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-date/expected.contract.json @@ -46,38 +46,36 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "birthday": { - "codecId": "cipherstash/date@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "birthday": { + "codecId": "cipherstash/date@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:f0f3476b3b6543b8fb0e98817a05e901c18357e3f574e94a96a2563279215cc8" + "storageHash": "sha256:b761630456bf634631cbe66b1a23a2b2dd086f46edac6a83f58cdeed524318f9" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-double/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-double/expected.contract.json index 56ffab53cc..65ec3f7282 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-double/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-double/expected.contract.json @@ -46,38 +46,36 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "salary": { - "codecId": "cipherstash/double@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "orderAndRange": true - } - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "salary": { + "codecId": "cipherstash/double@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "orderAndRange": true + } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:cb02d8912705f7d58c2764b1eab07574d5da2a33987fd18070fcc9c0c6ad4694" + "storageHash": "sha256:2737062537ae456c9cd227e37aef1d247fdec529223530bc3031f750293f3026" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-json/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-json/expected.contract.json index 85e125a993..a3968f18d1 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-json/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-json/expected.contract.json @@ -45,37 +45,35 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "preferences": { - "codecId": "cipherstash/json@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "searchableJson": true - } - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "preferences": { + "codecId": "cipherstash/json@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "searchableJson": true + } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:05fc3fac8877097cf5c790ae9980c3bb0e88238a9257db31f43ad38f6a46191f" + "storageHash": "sha256:967389112ae67e5e688d2c51c7eed42046068428979e18a169d4de06b4d29db7" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/cipherstash-encrypted-string/expected.contract.json b/test/integration/test/authoring/parity/cipherstash-encrypted-string/expected.contract.json index da92af9bef..4e25964e4a 100644 --- a/test/integration/test/authoring/parity/cipherstash-encrypted-string/expected.contract.json +++ b/test/integration/test/authoring/parity/cipherstash-encrypted-string/expected.contract.json @@ -47,39 +47,37 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "email": { - "codecId": "cipherstash/string@1", - "nativeType": "eql_v2_encrypted", - "nullable": false, - "typeParams": { - "equality": true, - "freeTextSearch": true, - "orderAndRange": true - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "email": { + "codecId": "cipherstash/string@1", + "nativeType": "eql_v2_encrypted", + "nullable": false, + "typeParams": { + "equality": true, + "freeTextSearch": true, + "orderAndRange": true } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:66d27072311c1ff92b0d031502fbcdab7064b2a35b62037163ec00fe62d25161" + "storageHash": "sha256:2ac33870ee3d9eb0bfd710fb1a98a64751e0e3e09ed99e37b1b427f426e8ab53" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/core-surface/expected.contract.json b/test/integration/test/authoring/parity/core-surface/expected.contract.json index 3a6ca84fa8..16d3e93521 100644 --- a/test/integration/test/authoring/parity/core-surface/expected.contract.json +++ b/test/integration/test/authoring/parity/core-surface/expected.contract.json @@ -160,144 +160,142 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "post": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "rating": { - "codecId": "pg/float8@1", - "nativeType": "float8", - "nullable": true - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "tables": { + "post": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "userId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "onDelete": "cascade", - "onUpdate": "cascade", - "source": { - "columns": ["userId"], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": ["id"], - "namespaceId": "__unbound__", - "tableName": "user" - } - } - ], - "indexes": [ - { - "columns": ["userId"] - } - ], - "primaryKey": { - "columns": ["id"] + "rating": { + "codecId": "pg/float8@1", + "nativeType": "float8", + "nullable": true }, - "uniques": [ - { - "columns": ["title", "userId"] - } - ] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "userId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "user": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "default": { - "expression": "now()", - "kind": "function" - }, - "nativeType": "timestamptz", - "nullable": false - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false, - "typeRef": "Email" + "foreignKeys": [ + { + "constraint": true, + "index": true, + "onDelete": "cascade", + "onUpdate": "cascade", + "source": { + "columns": ["userId"], + "namespaceId": "__unbound__", + "tableName": "post" }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false + "target": { + "columns": ["id"], + "namespaceId": "__unbound__", + "tableName": "user" + } + } + ], + "indexes": [ + { + "columns": ["userId"] + } + ], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [ + { + "columns": ["title", "userId"] + } + ] + }, + "user": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "default": { + "expression": "now()", + "kind": "function" }, - "isActive": { - "codecId": "pg/bool@1", - "default": { - "kind": "literal", - "value": true - }, - "nativeType": "bool", - "nullable": false + "nativeType": "timestamptz", + "nullable": false + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false, + "typeRef": "Email" + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "profile": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true + "nativeType": "int4", + "nullable": false + }, + "isActive": { + "codecId": "pg/bool@1", + "default": { + "kind": "literal", + "value": true }, - "role": { - "codecId": "pg/enum@1", - "nativeType": "Role", - "nullable": false, - "typeRef": "Role" - } + "nativeType": "bool", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "profile": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true }, - "uniques": [ - { - "columns": ["email"] - } - ] - } + "role": { + "codecId": "pg/enum@1", + "nativeType": "Role", + "nullable": false, + "typeRef": "Role" + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [ + { + "columns": ["email"] + } + ] } - }, - "public": { - "enum": { - "Role": { - "codecId": "pg/enum@1", - "kind": "postgres-enum", - "name": "Role", - "nativeType": "Role", - "values": ["USER", "ADMIN"] - } - }, - "id": "public", - "tables": {} } }, - "storageHash": "sha256:a0eae5e868776b85d3a394fe5d916c1518660c9e7cf7ab5f3ece00a05b27190d", + "public": { + "enum": { + "Role": { + "codecId": "pg/enum@1", + "kind": "postgres-enum", + "name": "Role", + "nativeType": "Role", + "values": ["USER", "ADMIN"] + } + }, + "id": "public", + "tables": {} + }, + "storageHash": "sha256:7a748bcab9c02019cfb07d521f1657038e499eeea544bc664dc50d0058af2f39", "types": { "Email": { "codecId": "pg/text@1", diff --git a/test/integration/test/authoring/parity/default-cuid-2/expected.contract.json b/test/integration/test/authoring/parity/default-cuid-2/expected.contract.json index 078c2a4251..7dff1a527a 100644 --- a/test/integration/test/authoring/parity/default-cuid-2/expected.contract.json +++ b/test/integration/test/authoring/parity/default-cuid-2/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 24 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 24 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:42dd30e9b8ca92b6dcc0088e6bfb919040dfa0f78b5f3d6b9df3d656b3f4de0f" + "storageHash": "sha256:b3bb2ff9828a05682f9e93e88fd90976ebeb6f33f162b79747dfe5510584cd4a" }, "execution": { "executionHash": "sha256:e5986419d0cb43d7ff5fcd302ff1eec9953be526fe5b7cb7d19d57e463d77be4", diff --git a/test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json b/test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json index 272867aab1..42cfc6ea66 100644 --- a/test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json +++ b/test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json @@ -32,33 +32,31 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "pg/text@1", - "default": { - "expression": "gen_random_uuid()", - "kind": "function" - }, - "nativeType": "text", - "nullable": false - } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "pg/text@1", + "default": { + "expression": "gen_random_uuid()", + "kind": "function" + }, + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:f83367183867bad124b5fc91b8913d1a1e36bccedfba80a471df5751201d63cc" + "storageHash": "sha256:fc56bbb2475e94d9c0ee07db843d5d5dbdd1ac39e61641bd621e8788b38d1ff2" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json b/test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json index 35a2eca9ca..28c94fde50 100644 --- a/test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json +++ b/test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 16 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 16 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:01d9c763a7df982f92a7e21f79cef04342f01359b9ac3e01b41910948b50e4d9" + "storageHash": "sha256:e535808942cb20f6c9031b6dde085445e15b52f1629f748ab853fcb31af70d31" }, "execution": { "executionHash": "sha256:5a3a18f251eb8c82d18de2823d2e08108d644bafc9b7d3a878548bd129b385ad", diff --git a/test/integration/test/authoring/parity/default-nanoid/expected.contract.json b/test/integration/test/authoring/parity/default-nanoid/expected.contract.json index 0e9a1b49e3..4e0b910df8 100644 --- a/test/integration/test/authoring/parity/default-nanoid/expected.contract.json +++ b/test/integration/test/authoring/parity/default-nanoid/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 21 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 21 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:0b6029e25e87d38d823db2b2161dc081432de8c47a9c98782a38a0e36a10369f" + "storageHash": "sha256:ab3f89f3d270217d631f562a8ece251b55c48267eabcbb8c097e830c76d62c59" }, "execution": { "executionHash": "sha256:e7f58f864ad93d52e679f7f632be1f191f41d557fe41b4178c44a7f951f44f1f", diff --git a/test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json b/test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json index 82a1d96029..8b6b289f7f 100644 --- a/test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json +++ b/test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json @@ -32,29 +32,27 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:6764054ee730ad25f73bf02601c20f277c697af7d97fcd328ed820413b8d18a0" + "storageHash": "sha256:02e6cb58f66d21d15708b18cd1314ec95e3b91a9caa0321b480f89866a28b9bd" }, "execution": { "executionHash": "sha256:91b2baa0b7cc5262f4706a2deb109734404c5edc7873bb666525f077278adce1", diff --git a/test/integration/test/authoring/parity/default-ulid/expected.contract.json b/test/integration/test/authoring/parity/default-ulid/expected.contract.json index f97c6a25fc..d319e7df2b 100644 --- a/test/integration/test/authoring/parity/default-ulid/expected.contract.json +++ b/test/integration/test/authoring/parity/default-ulid/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 26 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 26 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:be0b1990ccec59fa87be83e70cf53ac356ba3fe419bd1efe35b11b3023a983c6" + "storageHash": "sha256:02412bc24f241fbda50ba1b400d53d1a483ac80b51ed02b32f7d3e9c9bb7a6a3" }, "execution": { "executionHash": "sha256:99b3473eb434bbb594a648b84bd1a4a14f479baa0ddb961a69a7c32788f5d781", diff --git a/test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json b/test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json index 4dc083a994..a6646423bf 100644 --- a/test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json +++ b/test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:bff81c374d48799d019e3c2df41af83fc62a216f65b60d6faebdf74144d9f2fd" + "storageHash": "sha256:db1ec0ede6b4d9962a41d451940de7e0609995bdc5efc3480f2df8905c770b62" }, "execution": { "executionHash": "sha256:bbf9323d237939899fe2c607723bfb7ac8500a46c81a4e122dc1d6f8c05b699f", diff --git a/test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json b/test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json index abdaa97342..6b4bd191cf 100644 --- a/test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json +++ b/test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json @@ -35,32 +35,30 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "user": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "user": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:bff81c374d48799d019e3c2df41af83fc62a216f65b60d6faebdf74144d9f2fd" + "storageHash": "sha256:db1ec0ede6b4d9962a41d451940de7e0609995bdc5efc3480f2df8905c770b62" }, "execution": { "executionHash": "sha256:e0c5cd734cd6bc575e53aaaf0e795c063436683c2116da9836828f25b4704e37", diff --git a/test/integration/test/authoring/parity/map-attributes/expected.contract.json b/test/integration/test/authoring/parity/map-attributes/expected.contract.json index 15ba577f33..0b2296bc98 100644 --- a/test/integration/test/authoring/parity/map-attributes/expected.contract.json +++ b/test/integration/test/authoring/parity/map-attributes/expected.contract.json @@ -78,75 +78,73 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "org_team": { - "columns": { - "team_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["team_id"] + "__unbound__": { + "id": "__unbound__", + "tables": { + "org_team": { + "columns": { + "team_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["team_id"] + }, + "uniques": [] + }, + "team_member": { + "columns": { + "member_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "team_ref": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "team_member": { - "columns": { - "member_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "foreignKeys": [ + { + "constraint": true, + "index": true, + "name": "team_member_team_ref_fkey", + "onDelete": "cascade", + "onUpdate": "cascade", + "source": { + "columns": ["team_ref"], + "namespaceId": "__unbound__", + "tableName": "team_member" }, - "team_ref": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } - }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "name": "team_member_team_ref_fkey", - "onDelete": "cascade", - "onUpdate": "cascade", - "source": { - "columns": ["team_ref"], - "namespaceId": "__unbound__", - "tableName": "team_member" - }, - "target": { - "columns": ["team_id"], - "namespaceId": "__unbound__", - "tableName": "org_team" - } - } - ], - "indexes": [ - { - "columns": ["team_ref"] - } - ], - "primaryKey": { - "columns": ["member_id"] - }, - "uniques": [ - { - "columns": ["team_ref", "member_id"] + "target": { + "columns": ["team_id"], + "namespaceId": "__unbound__", + "tableName": "org_team" } - ] - } + } + ], + "indexes": [ + { + "columns": ["team_ref"] + } + ], + "primaryKey": { + "columns": ["member_id"] + }, + "uniques": [ + { + "columns": ["team_ref", "member_id"] + } + ] } } }, - "storageHash": "sha256:9732291f4b31310d455b987a854d21cb96db158ec1d29d3aa5ea09f5ae6c64a0" + "storageHash": "sha256:e79f296fc99f96b3336aa68e23abae9083bc2c3dcf7d8f890f04bd0631b7fc75" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json b/test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json index 0c65d7923b..1d9c3838e6 100644 --- a/test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json +++ b/test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json @@ -56,39 +56,37 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "document": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": false, - "typeRef": "Embedding1536" - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "__unbound__": { + "id": "__unbound__", + "tables": { + "document": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": false, + "typeRef": "Embedding1536" }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" + }, + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:923650d8962586ae327c2224a8d4a589abf94ce9465182179bf7939228ef8cef", + "storageHash": "sha256:a51b8bbde9f188ed3546fab64ac73745174afb6ea5788f3c79dc5ca85f2c68a9", "types": { "Embedding1536": { "codecId": "pg/vector@1", diff --git a/test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json b/test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json index 06a686b933..265afe83fe 100644 --- a/test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json +++ b/test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json @@ -90,66 +90,64 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "post": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "userId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } - }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "onDelete": "cascade", - "onUpdate": "cascade", - "source": { - "columns": ["userId"], - "namespaceId": "__unbound__", - "tableName": "post" - }, - "target": { - "columns": ["id"], - "namespaceId": "__unbound__", - "tableName": "user" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": ["id"] + "__unbound__": { + "id": "__unbound__", + "tables": { + "post": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "userId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "user": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "foreignKeys": [ + { + "constraint": true, + "index": true, + "onDelete": "cascade", + "onUpdate": "cascade", + "source": { + "columns": ["userId"], + "namespaceId": "__unbound__", + "tableName": "post" + }, + "target": { + "columns": ["id"], + "namespaceId": "__unbound__", + "tableName": "user" } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": ["id"] - }, - "uniques": [] - } + } + ], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] + }, + "user": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": ["id"] + }, + "uniques": [] } } }, - "storageHash": "sha256:f9bb3161722aabaf15e816b7cdb650ec156424879c60d3ad4eea3a70abb14cc9" + "storageHash": "sha256:c6f1bfd8193c64ba1688e258689db360369fd87172e41973a298953fc1266c18" }, "capabilities": { "postgres": { diff --git a/test/integration/test/authoring/side-by-side-contracts.test.ts b/test/integration/test/authoring/side-by-side-contracts.test.ts index 8a197de3f0..3df0052572 100644 --- a/test/integration/test/authoring/side-by-side-contracts.test.ts +++ b/test/integration/test/authoring/side-by-side-contracts.test.ts @@ -11,6 +11,7 @@ import { MongoContractSerializer } from '@prisma-next/family-mongo/ir'; import sql from '@prisma-next/family-sql/control'; import { SqlContractSerializer } from '@prisma-next/family-sql/ir'; import { createControlStack } from '@prisma-next/framework-components/control'; +import { isStoragePlaneReservedKey } from '@prisma-next/framework-components/ir'; import type { MongoContract } from '@prisma-next/mongo-contract'; import { mongoContractCanonicalizationHooks } from '@prisma-next/mongo-contract/canonicalization-hooks'; import { mongoContract } from '@prisma-next/mongo-contract-psl/provider'; @@ -72,6 +73,28 @@ interface LoadedFixture { readonly tsContract: Contract; } +const stripMongoValidatorFieldsFromStorage = ( + storage: Record, +): Record => { + const { storageHash: _sh, ...restStorage } = storage; + const stripped: Record = {}; + for (const [key, value] of Object.entries(restStorage)) { + if (isStoragePlaneReservedKey(key)) { + stripped[key] = value; + continue; + } + const ns = value as Record>; + const collections = ns['collections'] as Record>; + const strippedCollections: Record = {}; + for (const [name, coll] of Object.entries(collections)) { + const { validator: _, ...rest } = coll; + strippedCollections[name] = rest; + } + stripped[key] = { ...ns, collections: strippedCollections }; + } + return stripped; +}; + const fixtureNames = ['postgres', 'mongo'] as const satisfies readonly FixtureName[]; const fixtureCases: readonly FixtureCase[] = fixtureNames.map((name): FixtureCase => { @@ -254,19 +277,7 @@ describe('side-by-side contract examples', () => { const stripValidatorFields = (contract: typeof normalizedTs) => { const storage = contract.storage as unknown as Record; - const namespaces = storage['namespaces'] as Record>; - const strippedNamespaces: Record = {}; - for (const [nsId, ns] of Object.entries(namespaces)) { - const collections = ns['collections'] as Record>; - const strippedCollections: Record = {}; - for (const [name, coll] of Object.entries(collections)) { - const { validator: _, ...rest } = coll; - strippedCollections[name] = rest; - } - strippedNamespaces[nsId] = { ...ns, collections: strippedCollections }; - } - const { storageHash: _sh, ...restStorage } = storage; - return { ...contract, storage: { ...restStorage, namespaces: strippedNamespaces } }; + return { ...contract, storage: stripMongoValidatorFieldsFromStorage(storage) }; }; expect(stripValidatorFields(normalizedTs)).toEqual(stripValidatorFields(normalizedPsl)); @@ -284,19 +295,7 @@ describe('side-by-side contract examples', () => { const stripForComparison = (json: string) => { const parsed = JSON.parse(json) as Record; const storage = parsed['storage'] as Record; - const namespaces = storage['namespaces'] as Record>; - const strippedNamespaces: Record = {}; - for (const [nsId, ns] of Object.entries(namespaces)) { - const collections = ns['collections'] as Record>; - const strippedCollections: Record = {}; - for (const [name, coll] of Object.entries(collections)) { - const { validator: _, ...rest } = coll; - strippedCollections[name] = rest; - } - strippedNamespaces[nsId] = { ...ns, collections: strippedCollections }; - } - const { storageHash: _sh, ...restStorage } = storage; - return { ...parsed, storage: { ...restStorage, namespaces: strippedNamespaces } }; + return { ...parsed, storage: stripMongoValidatorFieldsFromStorage(storage) }; }; expect(stripForComparison(emittedTs.contractJson)).toEqual( stripForComparison(emittedPsl.contractJson), diff --git a/test/integration/test/authoring/side-by-side/mongo/contract.json b/test/integration/test/authoring/side-by-side/mongo/contract.json index 1b5a6fe5f7..8c65c4d624 100644 --- a/test/integration/test/authoring/side-by-side/mongo/contract.json +++ b/test/integration/test/authoring/side-by-side/mongo/contract.json @@ -120,79 +120,77 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "collections": { - "posts": { - "kind": "mongo-collection", - "validator": { - "jsonSchema": { - "bsonType": "object", - "properties": { - "_id": { - "bsonType": "objectId" - }, - "authorId": { - "bsonType": "objectId" - }, - "publishedAt": { - "bsonType": [ - "null", - "date" - ] - }, - "title": { - "bsonType": "string" - } + "__unbound__": { + "collections": { + "posts": { + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" }, - "required": [ - "_id", - "authorId", - "title" - ] - }, - "kind": "mongo-validator", - "validationAction": "error", - "validationLevel": "strict" - } - }, - "users": { - "kind": "mongo-collection", - "validator": { - "jsonSchema": { - "bsonType": "object", - "properties": { - "_id": { - "bsonType": "objectId" - }, - "bio": { - "bsonType": [ - "null", - "string" - ] - }, - "email": { - "bsonType": "string" - }, - "name": { - "bsonType": "string" - } + "authorId": { + "bsonType": "objectId" }, - "required": [ - "_id", - "email", - "name" - ] + "publishedAt": { + "bsonType": [ + "null", + "date" + ] + }, + "title": { + "bsonType": "string" + } }, - "kind": "mongo-validator", - "validationAction": "error", - "validationLevel": "strict" - } + "required": [ + "_id", + "authorId", + "title" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" } }, - "id": "__unbound__", - "kind": "mongo-database" - } + "users": { + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "bio": { + "bsonType": [ + "null", + "string" + ] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + } + }, + "required": [ + "_id", + "email", + "name" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" + } + } + }, + "id": "__unbound__", + "kind": "mongo-database" }, "storageHash": "sha256:c01f9b67df3a1057686184f42e85b2d62e239a28c19458ce6e8d4bb404cfbf9c" }, diff --git a/test/integration/test/authoring/side-by-side/postgres/contract.json b/test/integration/test/authoring/side-by-side/postgres/contract.json index 9ad92e498f..7151d8b32d 100644 --- a/test/integration/test/authoring/side-by-side/postgres/contract.json +++ b/test/integration/test/authoring/side-by-side/postgres/contract.json @@ -148,106 +148,104 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "posts": { - "columns": { - "authorId": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "publishedAt": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": true + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "posts": { + "columns": { + "authorId": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "authorId" - ], - "namespaceId": "__unbound__", - "tableName": "posts" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "publishedAt": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": true }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "users": { - "columns": { - "bio": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": true - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "authorId" + ], + "namespaceId": "__unbound__", + "tableName": "posts" }, - "id": { - "codecId": "pg/int4@1", - "default": { - "expression": "autoincrement()", - "kind": "function" - }, - "nativeType": "int4", - "nullable": false - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "users": { + "columns": { + "bio": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": true }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "default": { + "expression": "autoincrement()", + "kind": "function" + }, + "nativeType": "int4", + "nullable": false + }, + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:0dd598e2a231ab7b6d81a6d82aed7e54e2894e3f4036809a80e71191086834fa" + "storageHash": "sha256:f0261d3913b7d0e81b38faf45e17b036387630abaeb49e831397e983d6340a20" }, "capabilities": { "postgres": { diff --git a/test/integration/test/fixtures/contract.d.ts b/test/integration/test/fixtures/contract.d.ts index 8cbfd94b33..c113aab559 100644 --- a/test/integration/test/fixtures/contract.d.ts +++ b/test/integration/test/fixtures/contract.d.ts @@ -30,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:dce59e55ea27ee077fccb16ab776cc36817f1d60352a358267cbbbdf876dc9b1'>; + StorageHashBase<'sha256:b41a153d3d076ecfdf4e4e232153af17a773fc3fd418f2c7f5b5228f54e29bf6'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -66,38 +66,36 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly user: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly nativeType: 'timestamptz'; - readonly codecId: 'pg/timestamptz@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly user: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly createdAt: { + readonly nativeType: 'timestamptz'; + readonly codecId: 'pg/timestamptz@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly User: { @@ -158,5 +156,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/test/integration/test/fixtures/contract.json b/test/integration/test/fixtures/contract.json index c414422e3b..baefa796ea 100644 --- a/test/integration/test/fixtures/contract.json +++ b/test/integration/test/fixtures/contract.json @@ -52,42 +52,40 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "user": { - "columns": { - "createdAt": { - "codecId": "pg/timestamptz@1", - "nativeType": "timestamptz", - "nullable": false - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "user": { + "columns": { + "createdAt": { + "codecId": "pg/timestamptz@1", + "nativeType": "timestamptz", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:dce59e55ea27ee077fccb16ab776cc36817f1d60352a358267cbbbdf876dc9b1" + "storageHash": "sha256:b41a153d3d076ecfdf4e4e232153af17a773fc3fd418f2c7f5b5228f54e29bf6" }, "capabilities": { "postgres": { diff --git a/test/integration/test/mongo/fixtures/generated/contract.d.ts b/test/integration/test/mongo/fixtures/generated/contract.d.ts index 637e64ab04..7e218f382f 100644 --- a/test/integration/test/mongo/fixtures/generated/contract.d.ts +++ b/test/integration/test/mongo/fixtures/generated/contract.d.ts @@ -85,34 +85,32 @@ export type TypeMaps = MongoTypeMaps Date: Sat, 30 May 2026 23:57:21 +0200 Subject: [PATCH 38/60] chore: flatten target-postgres snapshot-read-shapes fixtures Signed-off-by: Will Madden --- .../snapshot-read-shapes/codec-instance.json | 34 +++++++++--------- .../snapshot-read-shapes/postgres-enum.json | 36 +++++++++---------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/codec-instance.json b/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/codec-instance.json index 9a8f7c8590..9e75289be5 100644 --- a/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/codec-instance.json +++ b/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/codec-instance.json @@ -7,24 +7,22 @@ "models": {}, "storage": { "storageHash": "sha256:fixture-codec-instance", - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "tables": { - "embeddings": { - "columns": { - "vec": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": false, - "typeRef": "Embedding1536" - } - }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { "columns": ["vec"] }, - "uniques": [] - } + "__unbound__": { + "id": "__unbound__", + "tables": { + "embeddings": { + "columns": { + "vec": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": false, + "typeRef": "Embedding1536" + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { "columns": ["vec"] }, + "uniques": [] } } }, diff --git a/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/postgres-enum.json b/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/postgres-enum.json index 449dfef466..514bbae115 100644 --- a/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/postgres-enum.json +++ b/packages/3-targets/3-targets/postgres/test/fixtures/snapshot-read-shapes/postgres-enum.json @@ -9,25 +9,23 @@ "meta": {}, "storage": { "storageHash": "sha256:fixture-postgres-enum", - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": {}, - "enum": {} - }, - "public": { - "id": "public", - "kind": "postgres-schema", - "tables": {}, - "enum": { - "user_role": { - "kind": "postgres-enum", - "name": "user_role", - "nativeType": "user_role", - "values": ["admin", "user"], - "codecId": "pg/enum@1" - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": {}, + "enum": {} + }, + "public": { + "id": "public", + "kind": "postgres-schema", + "tables": {}, + "enum": { + "user_role": { + "kind": "postgres-enum", + "name": "user_role", + "nativeType": "user_role", + "values": ["admin", "user"], + "codecId": "pg/enum@1" } } } From 7ce76f9de9fa02ab021785378b75e51fcaa379ae Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:57:46 +0200 Subject: [PATCH 39/60] chore: regen integration sql-builder and sql-orm-client fixtures Signed-off-by: Will Madden --- .../fixtures/generated/contract.d.ts | 248 +++++---- .../fixtures/generated/contract.json | 282 +++++----- .../fixtures/generated/contract.d.ts | 408 +++++++------- .../fixtures/generated/contract.json | 506 +++++++++--------- 4 files changed, 718 insertions(+), 726 deletions(-) diff --git a/test/integration/test/sql-builder/fixtures/generated/contract.d.ts b/test/integration/test/sql-builder/fixtures/generated/contract.d.ts index 0cacf19081..3f162477c5 100644 --- a/test/integration/test/sql-builder/fixtures/generated/contract.d.ts +++ b/test/integration/test/sql-builder/fixtures/generated/contract.d.ts @@ -35,7 +35,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:704c054ab903f2505136a2b7731c33b1c82394c8d2a5a74f8152d6a14fbd9138'>; + StorageHashBase<'sha256:bc8892c2546fef8a5c65dce066d76e4c04d6bbcd60cd8a28597f775c6f9d6d26'>; export type ExecutionHash = ExecutionHashBase<'sha256:4d09909b2e09a240919c201ce4a5e63c3a2ec70515932e145dccca82936d8be5'>; export type ProfileHash = @@ -114,142 +114,140 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly articles: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly articles: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly comments: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly body: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly post_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly comments: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly body: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly post_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly posts: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly views: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 3 }; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly posts: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly views: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 3 }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly profiles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly bio: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly profiles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly bio: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly invited_by_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: true; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly invited_by_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; }; }; - readonly storageHash: StorageHash; }, { readonly Article: { @@ -568,5 +566,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/test/integration/test/sql-builder/fixtures/generated/contract.json b/test/integration/test/sql-builder/fixtures/generated/contract.json index 9bfbd5382f..f856ea447f 100644 --- a/test/integration/test/sql-builder/fixtures/generated/contract.json +++ b/test/integration/test/sql-builder/fixtures/generated/contract.json @@ -346,166 +346,164 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "articles": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "articles": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } }, - "comments": { - "columns": { - "body": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "post_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "comments": { + "columns": { + "body": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "post_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "posts": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeParams": { - "length": 3 - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "views": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "posts": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeParams": { + "length": 3 } }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + }, + "views": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "profiles": { - "columns": { - "bio": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "profiles": { + "columns": { + "bio": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } }, - "users": { - "columns": { - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "invited_by_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": true - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "users": { + "columns": { + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - } + "invited_by_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": true + }, + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:704c054ab903f2505136a2b7731c33b1c82394c8d2a5a74f8152d6a14fbd9138" + "storageHash": "sha256:bc8892c2546fef8a5c65dce066d76e4c04d6bbcd60cd8a28597f775c6f9d6d26" }, "execution": { "executionHash": "sha256:4d09909b2e09a240919c201ce4a5e63c3a2ec70515932e145dccca82936d8be5", diff --git a/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts index 8cdef43298..f6d967a086 100644 --- a/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts @@ -35,7 +35,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:09cdc4f47bebe6d16423f62b2be3025be4bdd8936fe2f7f529d176a9524a6cdf'>; + StorageHashBase<'sha256:e1b41da5eceab4b14db8a439e047010df31a09d926fe87ed1b5be20fa1850da6'>; export type ExecutionHash = ExecutionHashBase<'sha256:e216decd356eea44980cf151c6044d85fb936e1fad093fbfb93ca34b96cf5847'>; export type ProfileHash = @@ -135,230 +135,228 @@ export type TypeMaps = TypeMapsType< type ContractBase = Omit< ContractType< { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly articles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly reviewer_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + readonly storageHash: StorageHash; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly articles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly reviewer_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly comments: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly body: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly post_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly comments: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly body: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly post_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'comments'; - readonly columns: readonly ['post_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'posts'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly posts: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly title: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'comments'; + readonly columns: readonly ['post_id']; }; - readonly views: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly embedding: { - readonly nativeType: 'vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 3 }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'posts'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly posts: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly title: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly views: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly embedding: { + readonly nativeType: 'vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 3 }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'posts'; - readonly columns: readonly ['user_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly profiles: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'posts'; + readonly columns: readonly ['user_id']; }; - readonly user_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly bio: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly profiles: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly user_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly bio: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['user_id'] }]; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'profiles'; - readonly columns: readonly ['user_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; - readonly tags: { - columns: { - readonly id: { - readonly nativeType: 'character'; - readonly codecId: 'sql/char@1'; - readonly nullable: false; - readonly typeParams: { readonly length: 36 }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['user_id'] }]; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'profiles'; + readonly columns: readonly ['user_id']; }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; }; + readonly constraint: true; + readonly index: true; + }, + ]; + }; + readonly tags: { + columns: { + readonly id: { + readonly nativeType: 'character'; + readonly codecId: 'sql/char@1'; + readonly nullable: false; + readonly typeParams: { readonly length: 36 }; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['name'] }]; - indexes: readonly []; - foreignKeys: readonly []; }; - readonly users: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly invited_by_id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: true; - }; - readonly address: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; - }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['name'] }]; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly users: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly invited_by_id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: true; + }; + readonly address: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['email'] }]; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['invited_by_id']; - }; - readonly target: { - readonly namespaceId: '__unbound__' & NamespaceId; - readonly tableName: 'users'; - readonly columns: readonly ['id']; - }; - readonly constraint: true; - readonly index: true; - }, - ]; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['email'] }]; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['invited_by_id']; + }; + readonly target: { + readonly namespaceId: '__unbound__' & NamespaceId; + readonly tableName: 'users'; + readonly columns: readonly ['id']; + }; + readonly constraint: true; + readonly index: true; + }, + ]; }; }; }; - readonly storageHash: StorageHash; }, { readonly Article: { @@ -749,5 +747,5 @@ type ContractBase = Omit< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/test/integration/test/sql-orm-client/fixtures/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/generated/contract.json index d9193bfaa9..38ae78fcad 100644 --- a/test/integration/test/sql-orm-client/fixtures/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/generated/contract.json @@ -459,292 +459,290 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "articles": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "reviewer_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "articles": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "reviewer_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - }, - "comments": { - "columns": { - "body": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "post_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "comments": { + "columns": { + "body": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "post_id" - ], - "namespaceId": "__unbound__", - "tableName": "comments" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "posts" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [] - }, - "posts": { - "columns": { - "embedding": { - "codecId": "pg/vector@1", - "nativeType": "vector", - "nullable": true, - "typeParams": { - "length": 3 - } - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "title": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "post_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "post_id" + ], + "namespaceId": "__unbound__", + "tableName": "comments" }, - "views": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "posts" } - }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "user_id" - ], - "namespaceId": "__unbound__", - "tableName": "posts" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + }, + "posts": { + "columns": { + "embedding": { + "codecId": "pg/vector@1", + "nativeType": "vector", + "nullable": true, + "typeParams": { + "length": 3 } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] }, - "uniques": [] - }, - "profiles": { - "columns": { - "bio": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "user_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - } + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "user_id" - ], - "namespaceId": "__unbound__", - "tableName": "profiles" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } - } - ], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "title": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "views": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { "columns": [ "user_id" - ] + ], + "namespaceId": "__unbound__", + "tableName": "posts" + }, + "target": { + "columns": [ + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] }, - "tags": { - "columns": { - "id": { - "codecId": "sql/char@1", - "nativeType": "character", - "nullable": false, - "typeParams": { - "length": 36 - } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "uniques": [] + }, + "profiles": { + "columns": { + "bio": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "user_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "user_id" + ], + "namespaceId": "__unbound__", + "tableName": "profiles" + }, + "target": { "columns": [ - "name" - ] + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] }, - "users": { - "columns": { - "address": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true - }, - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false - }, - "invited_by_id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": true - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false + "uniques": [ + { + "columns": [ + "user_id" + ] + } + ] + }, + "tags": { + "columns": { + "id": { + "codecId": "sql/char@1", + "nativeType": "character", + "nullable": false, + "typeParams": { + "length": 36 } }, - "foreignKeys": [ - { - "constraint": true, - "index": true, - "source": { - "columns": [ - "invited_by_id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - }, - "target": { - "columns": [ - "id" - ], - "namespaceId": "__unbound__", - "tableName": "users" - } - } - ], - "indexes": [], - "primaryKey": { + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [ + { "columns": [ - "id" + "name" ] + } + ] + }, + "users": { + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true + }, + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + }, + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false }, - "uniques": [ - { + "invited_by_id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": true + }, + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [ + { + "constraint": true, + "index": true, + "source": { + "columns": [ + "invited_by_id" + ], + "namespaceId": "__unbound__", + "tableName": "users" + }, + "target": { "columns": [ - "email" - ] + "id" + ], + "namespaceId": "__unbound__", + "tableName": "users" } + } + ], + "indexes": [], + "primaryKey": { + "columns": [ + "id" ] - } + }, + "uniques": [ + { + "columns": [ + "email" + ] + } + ] } } }, - "storageHash": "sha256:09cdc4f47bebe6d16423f62b2be3025be4bdd8936fe2f7f529d176a9524a6cdf" + "storageHash": "sha256:e1b41da5eceab4b14db8a439e047010df31a09d926fe87ed1b5be20fa1850da6" }, "execution": { "executionHash": "sha256:e216decd356eea44980cf151c6044d85fb936e1fad093fbfb93ca34b96cf5847", From 887140cb8219d75d21c644cfa91a9f748cf75b04 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sat, 30 May 2026 23:59:29 +0200 Subject: [PATCH 40/60] fix(family-sql): drop unused storageNamespaceEntries import Signed-off-by: Will Madden --- packages/2-sql/9-family/src/core/control-instance.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/2-sql/9-family/src/core/control-instance.ts b/packages/2-sql/9-family/src/core/control-instance.ts index 18a1f7937a..77c89c0461 100644 --- a/packages/2-sql/9-family/src/core/control-instance.ts +++ b/packages/2-sql/9-family/src/core/control-instance.ts @@ -25,10 +25,7 @@ import { VERIFY_CODE_TARGET_MISMATCH, } from '@prisma-next/framework-components/control'; import type { TypesImportSpec } from '@prisma-next/framework-components/emission'; -import { - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast'; import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; From 4c2c839854df47388480a696774406781278fae8 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 00:18:04 +0200 Subject: [PATCH 41/60] refactor(sql-orm-client): migrate test helpers and type fixtures to flat storage Signed-off-by: Will Madden --- .../test/create-input.test-d.ts | 58 +++++++------ .../test/generated-contract-types.test-d.ts | 82 +++++++++---------- .../sql-orm-client/test/helpers.ts | 25 +++--- .../test/mutation-executor.test.ts | 5 +- .../test/query-plan-select.test.ts | 7 +- .../sql-orm-client/test/unbound-tables.ts | 16 +--- 6 files changed, 88 insertions(+), 105 deletions(-) diff --git a/packages/3-extensions/sql-orm-client/test/create-input.test-d.ts b/packages/3-extensions/sql-orm-client/test/create-input.test-d.ts index 891a1d84f1..382911b00e 100644 --- a/packages/3-extensions/sql-orm-client/test/create-input.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/create-input.test-d.ts @@ -4,40 +4,38 @@ import type { CreateInput } from '../src/types'; type CreateInputContract = Contract< { storageHash: StorageHashBase; - namespaces: { - __unbound__: { - id: '__unbound__'; - kind: 'sql-namespace'; - tables: { - user: { - columns: { - id: { - nativeType: 'int4'; - codecId: 'pg/int4@1'; - nullable: false; - default: { - kind: 'function'; - expression: "nextval('user_id_seq'::regclass)"; - }; + __unbound__: { + id: '__unbound__'; + kind: 'sql-namespace'; + tables: { + user: { + columns: { + id: { + nativeType: 'int4'; + codecId: 'pg/int4@1'; + nullable: false; + default: { + kind: 'function'; + expression: "nextval('user_id_seq'::regclass)"; }; - email: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: true }; - slug: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - created_at: { - nativeType: 'timestamptz'; - codecId: 'pg/text@1'; - nullable: false; - default: { - kind: 'function'; - expression: 'now()'; - }; + }; + email: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: true }; + slug: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + created_at: { + nativeType: 'timestamptz'; + codecId: 'pg/text@1'; + nullable: false; + default: { + kind: 'function'; + expression: 'now()'; }; }; - primaryKey: { columns: ['id'] }; - uniques: []; - indexes: []; - foreignKeys: []; }; + primaryKey: { columns: ['id'] }; + uniques: []; + indexes: []; + foreignKeys: []; }; }; }; diff --git a/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts b/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts index 87f39d83cc..9059c6c9d9 100644 --- a/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts @@ -44,35 +44,33 @@ type GeneratedLikeTypeMaps = TypeMaps< type GeneratedLikeContractBase = Contract< { storageHash: StorageHashBase; - namespaces: { - __unbound__: { - id: '__unbound__'; - kind: 'sql-namespace'; - tables: { - user: { - columns: { - id: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - email: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - active: { nativeType: 'bool'; codecId: 'pg/bool@1'; nullable: false }; - metadata: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: false }; - }; - primaryKey: { columns: ['id'] }; - uniques: []; - indexes: []; - foreignKeys: []; + __unbound__: { + id: '__unbound__'; + kind: 'sql-namespace'; + tables: { + user: { + columns: { + id: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + email: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + active: { nativeType: 'bool'; codecId: 'pg/bool@1'; nullable: false }; + metadata: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: false }; }; - post: { - columns: { - id: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - userId: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - title: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - }; - primaryKey: { columns: ['id'] }; - uniques: []; - indexes: []; - foreignKeys: []; + primaryKey: { columns: ['id'] }; + uniques: []; + indexes: []; + foreignKeys: []; + }; + post: { + columns: { + id: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + userId: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + title: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; }; + primaryKey: { columns: ['id'] }; + uniques: []; + indexes: []; + foreignKeys: []; }; }; }; @@ -399,23 +397,21 @@ type VOTypeMaps = TypeMaps, VOFieldOutputTyp type VOContractBase = Contract< { storageHash: StorageHashBase; - namespaces: { - __unbound__: { - id: '__unbound__'; - kind: 'sql-namespace'; - tables: { - users: { - columns: { - id: { nativeType: 'int4'; codecId: 'pg/int4@1'; nullable: false }; - name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; - home_address: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: true }; - work_address: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: false }; - }; - primaryKey: { columns: ['id'] }; - uniques: []; - indexes: []; - foreignKeys: []; + __unbound__: { + id: '__unbound__'; + kind: 'sql-namespace'; + tables: { + users: { + columns: { + id: { nativeType: 'int4'; codecId: 'pg/int4@1'; nullable: false }; + name: { nativeType: 'text'; codecId: 'pg/text@1'; nullable: false }; + home_address: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: true }; + work_address: { nativeType: 'jsonb'; codecId: 'pg/jsonb@1'; nullable: false }; }; + primaryKey: { columns: ['id'] }; + uniques: []; + indexes: []; + foreignKeys: []; }; }; }; diff --git a/packages/3-extensions/sql-orm-client/test/helpers.ts b/packages/3-extensions/sql-orm-client/test/helpers.ts index 3c79332544..d3db7cd0c8 100644 --- a/packages/3-extensions/sql-orm-client/test/helpers.ts +++ b/packages/3-extensions/sql-orm-client/test/helpers.ts @@ -5,6 +5,7 @@ import type { CodecInstanceContext, } from '@prisma-next/framework-components/codec'; import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; // Mutable namespace view for assembling raw test contracts in place. The // runtime fixtures key namespaces directly under `storage` (ADR 221 flat @@ -14,6 +15,13 @@ type MutableNamespace = { tables: Record; [key: string]: unknown }>; }; +function mutableUnbound(storage: object): MutableNamespace { + return getStorageNamespace( + storage, + UNBOUND_NAMESPACE_ID, + ) as unknown as MutableNamespace; +} + import { AsyncIterableResult } from '@prisma-next/framework-components/runtime'; import type { Codec, SelectAst } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan'; @@ -163,7 +171,7 @@ export function buildMixedPolyContract(): TestContract { base: 'Task', }; - (getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace).tables.tasks = { + mutableUnbound(raw.storage).tables['tasks'] = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, @@ -176,7 +184,7 @@ export function buildMixedPolyContract(): TestContract { foreignKeys: [], }; - (getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace).tables.features = { + mutableUnbound(raw.storage).tables['features'] = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, priority: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, @@ -224,23 +232,18 @@ export function buildStiPolyContract(): TestContract { base: 'User', }; - ( - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace - ).tables.users.columns.kind = { + const usersColumns = mutableUnbound(raw.storage).tables['users']!.columns; + usersColumns['kind'] = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, }; - ( - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace - ).tables.users.columns.role = { + usersColumns['role'] = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, }; - ( - getStorageNamespace(raw.storage, UNBOUND_NAMESPACE_ID) as MutableNamespace - ).tables.users.columns.plan = { + usersColumns['plan'] = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, diff --git a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts index dc6cf62d81..f8fe4d726b 100644 --- a/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts +++ b/packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts @@ -1,4 +1,5 @@ import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { type AnyExpression, BinaryExpr, @@ -144,7 +145,7 @@ describe('mutation-executor', () => { it('buildPrimaryKeyFilterFromRow() resolves custom primary key columns', () => { const contract = getTestContract(); - const unboundNs = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!; + const unboundNs = getStorageNamespace(contract.storage, UNBOUND_NAMESPACE_ID)!; const withCustomPk = { ...contract, storage: { @@ -154,7 +155,7 @@ describe('mutation-executor', () => { tables: { ...unboundNs.tables, users: { - ...unboundNs.tables.users, + ...unboundNs.tables['users'], primaryKey: { columns: ['pk_id'] }, }, }, diff --git a/packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts b/packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts index 9d50eb911e..312ed0aa90 100644 --- a/packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts +++ b/packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts @@ -1079,12 +1079,7 @@ describe('compileSelectWithIncludeStrategy', () => { describe('compileSelect MTI JOINs', () => { type AnyContract = { - storage: { - namespaces: Record< - string, - { tables?: Record }> } - >; - }; + storage: object; }; function codecRefForColumn( contract: AnyContract, diff --git a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts index 86ad9364ad..96a9bb0077 100644 --- a/packages/3-extensions/sql-orm-client/test/unbound-tables.ts +++ b/packages/3-extensions/sql-orm-client/test/unbound-tables.ts @@ -1,16 +1,6 @@ import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import type { SqlNamespace, StorageTable } from '@prisma-next/sql-contract/types'; -type StorageLike = { - readonly namespaces: Readonly< - Record> }> - >; -}; - -export function unboundTables( - storage: StorageLike | SqlStorage, -): Readonly> { - return (getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? {}) as Readonly< - Record - >; +export function unboundTables(storage: object): Readonly> { + return getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? {}; } From 4301a5d62ca0dfc20da64e47903cf55ae3ce886a Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 00:36:17 +0200 Subject: [PATCH 42/60] fix(contract-ts): flatten BuiltStorage output type to ADR 221 shape D1 flattened the IR SqlStorage type and the runtime emit but left the contract-builder output type BuiltStorage nesting namespace ids under a `namespaces` wrapper, so every defineContract result was unassignable to the flat Contract. Hoist namespace ids (and the named- namespace mapped type) to direct keys under storage, mirroring the flat IR shape. Type-only; no runtime behaviour change. Signed-off-by: Will Madden --- .../contract-ts/src/contract-types.ts | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/2-sql/2-authoring/contract-ts/src/contract-types.ts b/packages/2-sql/2-authoring/contract-ts/src/contract-types.ts index bb55aba318..d871a7b898 100644 --- a/packages/2-sql/2-authoring/contract-ts/src/contract-types.ts +++ b/packages/2-sql/2-authoring/contract-ts/src/contract-types.ts @@ -578,32 +578,30 @@ type BuiltDomain = }; }; +// SQL contracts always carry a literal `__unbound__` namespace whose tables +// slot is narrowed to the actual built table shape so downstream DSL +// surfaces (TableProxyContract, Ref, SelectBuilder) keep literal-keyed +// access without an optional-narrowing dance. Namespace ids are direct keys +// under `storage` (ADR 221 canonical flat shape), alongside the reserved +// `storageHash` (and optional document-scoped `types`). The shapes are +// described inline (rather than intersecting with a `Record` index signature) so `keyof tables` does not collapse to +// `string`. Each value still satisfies the framework `Namespace` interface. type BuiltStorage = { readonly storageHash: StorageHashBase; readonly types?: BuiltDocumentScopedTypes; - // SQL contracts always carry a literal `__unbound__` namespace whose tables - // slot is narrowed to the actual built table shape so downstream DSL - // surfaces (TableProxyContract, Ref, SelectBuilder) keep literal-keyed - // access without an optional-narrowing dance. The shape is described - // inline (rather than intersecting with `SqlStorage['namespaces']`) so - // its `Readonly>` index signature doesn't - // collapse `keyof tables` to `string`. The literal object is still - // structurally assignable to `SqlStorage['namespaces']` because every - // value satisfies the framework `Namespace` interface. - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: string; - readonly tables: BuiltStorageTables; - readonly enum?: Readonly>; - }; - } & { - readonly [Ns in Exclude, '__unbound__'>]: { - readonly id: Ns; - readonly kind: string; - readonly tables: Record; - readonly enum?: Readonly>; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: string; + readonly tables: BuiltStorageTables; + readonly enum?: Readonly>; + }; +} & { + readonly [Ns in Exclude, '__unbound__'>]: { + readonly id: Ns; + readonly kind: string; + readonly tables: Record; + readonly enum?: Readonly>; }; }; From 505276010e8e1c6e948ec09465a26168597e712c Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 00:36:26 +0200 Subject: [PATCH 43/60] refactor(integration-tests): migrate storage reads and inline fixtures to flat shape Signed-off-by: Will Madden --- .../test/cli.emit-cli-process.e2e.test.ts | 54 +++++---- .../test/cli.mongo-db-sign.e2e.test.ts | 20 ++-- .../test/cli.mongo-db-verify.e2e.test.ts | 20 ++-- .../contract-space-fixture-mongo/contract.ts | 48 ++++---- .../descriptor.test.ts | 15 ++- .../test/contract-space-fixture/contract.ts | 42 +++---- .../contract-space-fixture/descriptor.test.ts | 8 +- ...tter.adapter-query-operation-types.test.ts | 2 +- ...gvector-scenario-a.e2e.integration.test.ts | 26 ++--- .../test/mongo/aggregate-e2e.test.ts | 28 +++-- .../mongo/codec-rehydration-guardrail.test.ts | 56 +++++----- .../test/mongo/db-verify-sign.test.ts | 20 ++-- .../test/mongo/migration-e2e.test.ts | 32 +++--- .../mongo/migration-m2-vocabulary.test.ts | 10 +- .../mongo/migration-psl-authoring.test.ts | 9 +- .../test/mongo/multi-space-runner.test.ts | 104 +++++++++--------- test/integration/test/mongo/orm.test.ts | 2 +- .../test/sql-orm-client/unbound-tables.ts | 18 +-- .../test/sql-orm-client/upsert.test.ts | 2 +- 19 files changed, 245 insertions(+), 271 deletions(-) diff --git a/test/integration/test/cli.emit-cli-process.e2e.test.ts b/test/integration/test/cli.emit-cli-process.e2e.test.ts index 7162dfb518..f056c3c512 100644 --- a/test/integration/test/cli.emit-cli-process.e2e.test.ts +++ b/test/integration/test/cli.emit-cli-process.e2e.test.ts @@ -20,29 +20,27 @@ const fixtureSubdir = 'emit-command'; type EmittedContract = Contract< { readonly storageHash: StorageHashBase; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly user: { - readonly columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - }; - readonly email: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly user: { + readonly columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + }; + readonly email: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; }; - readonly primaryKey: { readonly columns: readonly ['id'] }; - readonly uniques: readonly []; - readonly indexes: readonly []; - readonly foreignKeys: readonly []; }; + readonly primaryKey: { readonly columns: readonly ['id'] }; + readonly uniques: readonly []; + readonly indexes: readonly []; + readonly foreignKeys: readonly []; }; }; }; @@ -129,11 +127,9 @@ describe('contract emit command (CLI process e2e)', () => { targetFamily: 'sql', target: 'postgres', storage: { - namespaces: { - __unbound__: { - tables: { - user: expect.anything(), - }, + __unbound__: { + tables: { + user: expect.anything(), }, }, }, @@ -189,11 +185,11 @@ describe('contract emit command (CLI process e2e)', () => { expect(validatedContract.targetFamily).toBe(originalContract.targetFamily); expect(validatedContract.target).toBe(originalContract.target); - const tables = (validatedContract.storage as SqlStorage).namespaces.__unbound__?.tables as + const tables = (validatedContract.storage as SqlStorage).__unbound__?.tables as | Record | undefined; - const originalTables = (originalContract.storage as SqlStorage | undefined)?.namespaces - .__unbound__?.tables as Record | undefined; + const originalTables = (originalContract.storage as SqlStorage | undefined)?.__unbound__ + ?.tables as Record | undefined; const userTable = tables?.['user'] as Record | undefined; const originalUserTable = originalTables?.['user'] as Record | undefined; if (userTable && originalUserTable) { diff --git a/test/integration/test/cli.mongo-db-sign.e2e.test.ts b/test/integration/test/cli.mongo-db-sign.e2e.test.ts index 69f2ce8d07..5f600b96c6 100644 --- a/test/integration/test/cli.mongo-db-sign.e2e.test.ts +++ b/test/integration/test/cli.mongo-db-sign.e2e.test.ts @@ -32,17 +32,15 @@ const testContract: MongoContract = { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: new MongoCollection({ - indexes: [ - new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), - ], - }), - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: new MongoCollection({ + indexes: [ + new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), + ], + }), }, }, storageHash: coreHash('sha256:mongo-sign-test'), diff --git a/test/integration/test/cli.mongo-db-verify.e2e.test.ts b/test/integration/test/cli.mongo-db-verify.e2e.test.ts index 93997208c2..f7e1daeed9 100644 --- a/test/integration/test/cli.mongo-db-verify.e2e.test.ts +++ b/test/integration/test/cli.mongo-db-verify.e2e.test.ts @@ -32,17 +32,15 @@ const testContract: MongoContract = { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: new MongoCollection({ - indexes: [ - new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), - ], - }), - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: new MongoCollection({ + indexes: [ + new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), + ], + }), }, }, storageHash: coreHash('sha256:mongo-verify-test'), diff --git a/test/integration/test/contract-space-fixture-mongo/contract.ts b/test/integration/test/contract-space-fixture-mongo/contract.ts index bc6ece7095..5f2e3c0351 100644 --- a/test/integration/test/contract-space-fixture-mongo/contract.ts +++ b/test/integration/test/contract-space-fixture-mongo/contract.ts @@ -8,33 +8,31 @@ const TARGET = 'mongo' as const; const TARGET_FAMILY = 'mongo' as const; const storageBody = { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - [MONGO_TEST_COLLECTION]: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'tenantId', direction: 1 as const }], - unique: true, - }, - ], - validator: { - kind: 'mongo-validator' as const, - jsonSchema: { - bsonType: 'object', - required: ['tenantId', 'event'], - properties: { - tenantId: { bsonType: 'string' }, - event: { bsonType: 'string' }, - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + [MONGO_TEST_COLLECTION]: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'tenantId', direction: 1 as const }], + unique: true, + }, + ], + validator: { + kind: 'mongo-validator' as const, + jsonSchema: { + bsonType: 'object', + required: ['tenantId', 'event'], + properties: { + tenantId: { bsonType: 'string' }, + event: { bsonType: 'string' }, }, - validationLevel: 'strict' as const, - validationAction: 'error' as const, }, + validationLevel: 'strict' as const, + validationAction: 'error' as const, }, }, }, diff --git a/test/integration/test/contract-space-fixture-mongo/descriptor.test.ts b/test/integration/test/contract-space-fixture-mongo/descriptor.test.ts index 7abd19fa3b..640395afef 100644 --- a/test/integration/test/contract-space-fixture-mongo/descriptor.test.ts +++ b/test/integration/test/contract-space-fixture-mongo/descriptor.test.ts @@ -1,4 +1,6 @@ import type { MigrationPlanOperation } from '@prisma-next/framework-components/control'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { MongoNamespaceShape } from '@prisma-next/mongo-contract'; import { describe, expect, it } from 'vitest'; import { MONGO_TEST_BASELINE_INVARIANT_ID, @@ -22,15 +24,18 @@ describe('test-mongo-contract-space fixture descriptor', () => { it('exposes a contractSpace whose contract declares the test_audit_event collection', () => { const space = mongoTestContractSpaceExtensionDescriptor.contractSpace; expect(space).toBeDefined(); - const ns = space!.contractJson.storage.namespaces['__unbound__']; + const ns = getStorageNamespace( + space!.contractJson.storage, + UNBOUND_NAMESPACE_ID, + ); expect(Object.keys(ns!.collections)).toEqual([MONGO_TEST_COLLECTION]); }); it('declares one unique index and one strict validator on the test collection', () => { - const ns = - mongoTestContractSpaceExtensionDescriptor.contractSpace!.contractJson.storage.namespaces[ - '__unbound__' - ]; + const ns = getStorageNamespace( + mongoTestContractSpaceExtensionDescriptor.contractSpace!.contractJson.storage, + UNBOUND_NAMESPACE_ID, + ); const collection = ns!.collections[MONGO_TEST_COLLECTION]; expect(collection).toBeDefined(); expect(collection!.indexes).toHaveLength(1); diff --git a/test/integration/test/contract-space-fixture/contract.ts b/test/integration/test/contract-space-fixture/contract.ts index 09c66bfe86..188ec1032b 100644 --- a/test/integration/test/contract-space-fixture/contract.ts +++ b/test/integration/test/contract-space-fixture/contract.ts @@ -2,26 +2,28 @@ import { computeStorageHash } from '@prisma-next/contract/hashing'; import { type Contract, coreHash, profileHash } from '@prisma-next/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks'; -import { buildSqlNamespace, SqlStorage } from '@prisma-next/sql-contract/types'; +import { + buildSqlNamespace, + buildSqlStorageInput, + SqlStorage, +} from '@prisma-next/sql-contract/types'; import { TEST_BOX_TABLE } from './constants'; const TARGET = 'postgres' as const; const TARGET_FAMILY = 'sql' as const; const storageBody = { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - [TEST_BOX_TABLE]: { - columns: { - x: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - y: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + [TEST_BOX_TABLE]: { + columns: { + x: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + y: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, @@ -57,10 +59,12 @@ export const testContractSpaceContract: Contract = { extensionPacks: {}, meta: {}, profileHash: profileHash('synthetic-test-contract-space-profile-v1'), - storage: new SqlStorage({ - storageHash: coreHash(TEST_HEAD_HASH), - namespaces: { - [UNBOUND_NAMESPACE_ID]: buildSqlNamespace(storageBody.namespaces[UNBOUND_NAMESPACE_ID]), - }, - }), + storage: new SqlStorage( + buildSqlStorageInput({ + storageHash: coreHash(TEST_HEAD_HASH), + namespaces: { + [UNBOUND_NAMESPACE_ID]: buildSqlNamespace(storageBody[UNBOUND_NAMESPACE_ID]), + }, + }), + ), }; diff --git a/test/integration/test/contract-space-fixture/descriptor.test.ts b/test/integration/test/contract-space-fixture/descriptor.test.ts index 50ffe95ffc..b73e519b83 100644 --- a/test/integration/test/contract-space-fixture/descriptor.test.ts +++ b/test/integration/test/contract-space-fixture/descriptor.test.ts @@ -1,5 +1,6 @@ import type { MigrationPlanOperation } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { describe, expect, it } from 'vitest'; import { TEST_BASELINE_INVARIANT_ID, @@ -24,7 +25,10 @@ describe('test-contract-space fixture descriptor', () => { const space = testContractSpaceExtensionDescriptor.contractSpace; expect(space).toBeDefined(); expect( - Object.keys(space!.contractJson.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables), + Object.keys( + getStorageNamespace(space!.contractJson.storage, UNBOUND_NAMESPACE_ID)! + .tables, + ), ).toEqual([TEST_BOX_TABLE]); }); diff --git a/test/integration/test/emitter.adapter-query-operation-types.test.ts b/test/integration/test/emitter.adapter-query-operation-types.test.ts index a407a8ed7a..d13861f34d 100644 --- a/test/integration/test/emitter.adapter-query-operation-types.test.ts +++ b/test/integration/test/emitter.adapter-query-operation-types.test.ts @@ -16,7 +16,7 @@ describe('emitter + postgres adapter descriptor', () => { models: {}, storage: { storageHash: 'storage:sha256:test' as never, - namespaces: { [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }, + [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance, }, capabilities: {}, extensionPacks: {}, diff --git a/test/integration/test/extension-pgvector-scenario-a.e2e.integration.test.ts b/test/integration/test/extension-pgvector-scenario-a.e2e.integration.test.ts index 832d2af4b3..3a4d1dda2a 100644 --- a/test/integration/test/extension-pgvector-scenario-a.e2e.integration.test.ts +++ b/test/integration/test/extension-pgvector-scenario-a.e2e.integration.test.ts @@ -143,21 +143,19 @@ function buildAppContractPojo(opts: { readonly withLength: boolean }): Contract< profileHash: APP_PROFILE_HASH, storage: { storageHash: APP_CONTRACT_HASH, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'sql-namespace', - tables: { - [APP_TABLE]: { - columns: { - id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - [APP_FIELD]: embeddingColumn, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'sql-namespace', + tables: { + [APP_TABLE]: { + columns: { + id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + [APP_FIELD]: embeddingColumn, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/test/integration/test/mongo/aggregate-e2e.test.ts b/test/integration/test/mongo/aggregate-e2e.test.ts index 4cbc07600d..7981b52e19 100644 --- a/test/integration/test/mongo/aggregate-e2e.test.ts +++ b/test/integration/test/mongo/aggregate-e2e.test.ts @@ -79,21 +79,19 @@ function buildAppContract(): MongoContract { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'email', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'email', direction: 1 as const }], + unique: true, + }, + ], }, }, }, diff --git a/test/integration/test/mongo/codec-rehydration-guardrail.test.ts b/test/integration/test/mongo/codec-rehydration-guardrail.test.ts index 2ae16d86a3..f80def9ea6 100644 --- a/test/integration/test/mongo/codec-rehydration-guardrail.test.ts +++ b/test/integration/test/mongo/codec-rehydration-guardrail.test.ts @@ -72,21 +72,19 @@ function appContract(): MongoContract { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'email', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'email', direction: 1 as const }], + unique: true, + }, + ], }, }, }, @@ -106,21 +104,19 @@ function extContract(): MongoContract { roots: {}, models: {}, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - cipherstash_state: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'tenantId', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + cipherstash_state: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'tenantId', direction: 1 as const }], + unique: true, + }, + ], }, }, }, diff --git a/test/integration/test/mongo/db-verify-sign.test.ts b/test/integration/test/mongo/db-verify-sign.test.ts index f99cc2af87..184e41c6a4 100644 --- a/test/integration/test/mongo/db-verify-sign.test.ts +++ b/test/integration/test/mongo/db-verify-sign.test.ts @@ -30,17 +30,15 @@ const baseContract: MongoContract = { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: new MongoCollection({ - indexes: [ - new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), - ], - }), - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: new MongoCollection({ + indexes: [ + new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), + ], + }), }, }, storageHash: coreHash('sha256:verify-test'), diff --git a/test/integration/test/mongo/migration-e2e.test.ts b/test/integration/test/mongo/migration-e2e.test.ts index 5ef3344206..210fb4a591 100644 --- a/test/integration/test/mongo/migration-e2e.test.ts +++ b/test/integration/test/mongo/migration-e2e.test.ts @@ -35,13 +35,11 @@ const emptyContract: MongoContract = { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: new MongoCollection(), - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: new MongoCollection(), }, }, storageHash: coreHash('sha256:empty-contract'), @@ -67,17 +65,15 @@ const indexedContract: MongoContract = { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: new MongoCollection({ - indexes: [ - new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), - ], - }), - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: new MongoCollection({ + indexes: [ + new MongoIndex({ keys: [{ field: 'email', direction: 1 as const }], unique: true }), + ], + }), }, }, storageHash: coreHash('sha256:indexed-contract'), diff --git a/test/integration/test/mongo/migration-m2-vocabulary.test.ts b/test/integration/test/mongo/migration-m2-vocabulary.test.ts index 0f940e2bfc..45018ecd07 100644 --- a/test/integration/test/mongo/migration-m2-vocabulary.test.ts +++ b/test/integration/test/mongo/migration-m2-vocabulary.test.ts @@ -61,12 +61,10 @@ function makeContract( ]), ), storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: normalized, - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: normalized, }, storageHash: coreHash(`sha256:${hashSeed}`), }, diff --git a/test/integration/test/mongo/migration-psl-authoring.test.ts b/test/integration/test/mongo/migration-psl-authoring.test.ts index dc866ab428..a0cfcb0c48 100644 --- a/test/integration/test/mongo/migration-psl-authoring.test.ts +++ b/test/integration/test/mongo/migration-psl-authoring.test.ts @@ -7,8 +7,8 @@ import { createMongoFamilyInstance, } from '@prisma-next/family-mongo/control'; import type { CodecLookup } from '@prisma-next/framework-components/codec'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { MongoContract } from '@prisma-next/mongo-contract'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { MongoContract, MongoNamespaceShape } from '@prisma-next/mongo-contract'; import { interpretPslDocumentToMongoContract } from '@prisma-next/mongo-contract-psl'; import type { MongoMigrationPlanOperation } from '@prisma-next/mongo-query-ast/control'; import { parsePslDocument } from '@prisma-next/psl-parser'; @@ -241,7 +241,10 @@ describe('PSL authoring → migration E2E', { timeout: timeouts.spinUpMongoMemor } `); - const postColl = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.collections['post']; + const postColl = getStorageNamespace( + contract.storage, + UNBOUND_NAMESPACE_ID, + )?.collections['post']; expect(postColl?.indexes).toBeDefined(); expect(postColl?.validator).toBeDefined(); diff --git a/test/integration/test/mongo/multi-space-runner.test.ts b/test/integration/test/mongo/multi-space-runner.test.ts index 4d5f8a6c39..f7a27b772f 100644 --- a/test/integration/test/mongo/multi-space-runner.test.ts +++ b/test/integration/test/mongo/multi-space-runner.test.ts @@ -70,31 +70,29 @@ function buildAppContract(): MongoContract { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'email', direction: 1 as const }], - unique: true, - }, - ], - }, - posts: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'slug', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'email', direction: 1 as const }], + unique: true, + }, + ], + }, + posts: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'slug', direction: 1 as const }], + unique: true, + }, + ], }, }, }, @@ -131,21 +129,19 @@ function buildAppContractMissingPosts(): MongoContract { }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'email', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'email', direction: 1 as const }], + unique: true, + }, + ], }, }, }, @@ -165,21 +161,19 @@ function buildExtContract(): MongoContract { roots: {}, models: {}, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - cipherstash_state: { - kind: 'mongo-collection' as const, - indexes: [ - { - kind: 'mongo-index' as const, - keys: [{ field: 'tenantId', direction: 1 as const }], - unique: true, - }, - ], - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + cipherstash_state: { + kind: 'mongo-collection' as const, + indexes: [ + { + kind: 'mongo-index' as const, + keys: [{ field: 'tenantId', direction: 1 as const }], + unique: true, + }, + ], }, }, }, diff --git a/test/integration/test/mongo/orm.test.ts b/test/integration/test/mongo/orm.test.ts index 70da83f410..612eb9c302 100644 --- a/test/integration/test/mongo/orm.test.ts +++ b/test/integration/test/mongo/orm.test.ts @@ -11,7 +11,7 @@ const contract = new MongoContractSerializer().deserializeContract(ormContractJs describeWithMongoDB('mongoOrm integration', (ctx) => { it('loads generated collection indexes and options', () => { - expect(contract.storage.namespaces.__unbound__.collections.users).toEqual({ + expect(contract.storage.__unbound__.collections.users).toEqual({ kind: 'mongo-collection', indexes: [{ kind: 'mongo-index', keys: [{ field: 'email', direction: 1 }], unique: true }], options: { diff --git a/test/integration/test/sql-orm-client/unbound-tables.ts b/test/integration/test/sql-orm-client/unbound-tables.ts index 3a360a3d18..96a9bb0077 100644 --- a/test/integration/test/sql-orm-client/unbound-tables.ts +++ b/test/integration/test/sql-orm-client/unbound-tables.ts @@ -1,16 +1,6 @@ -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; -import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, StorageTable } from '@prisma-next/sql-contract/types'; -type StorageLike = { - readonly namespaces: Readonly< - Record> }> - >; -}; - -export function unboundTables( - storage: StorageLike | SqlStorage, -): Readonly> { - return (storage.namespaces[UNBOUND_NAMESPACE_ID]?.tables ?? {}) as Readonly< - Record - >; +export function unboundTables(storage: object): Readonly> { + return getStorageNamespace(storage, UNBOUND_NAMESPACE_ID)?.tables ?? {}; } diff --git a/test/integration/test/sql-orm-client/upsert.test.ts b/test/integration/test/sql-orm-client/upsert.test.ts index 8ce9c00dad..370f04b913 100644 --- a/test/integration/test/sql-orm-client/upsert.test.ts +++ b/test/integration/test/sql-orm-client/upsert.test.ts @@ -101,7 +101,7 @@ describe('integration/upsert', () => { await withCollectionRuntime(async (runtime) => { const contract = withReturningCapability(getTestContract()); delete ( - contract.storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables.users as { + contract.storage[UNBOUND_NAMESPACE_ID].tables.users as { primaryKey?: unknown; } ).primaryKey; From 94ffbe0da4623b60251a7a0b15e6fd1c6e7d67f5 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 00:47:03 +0200 Subject: [PATCH 44/60] refactor(integration-tests): migrate remaining storage reads + regen value-objects fixtures to flat shape Signed-off-by: Will Madden --- .../authoring/paradedb-bm25-narrowing.test.ts | 2 +- test/integration/test/cli.db-sign.e2e.test.ts | 4 +- .../integration/test/contract-builder.test.ts | 47 +++++------- .../test/contract-builder.types.test-d.ts | 28 +++---- .../integration/test/contract-imports.test.ts | 8 +- .../test/dsl-type-inference.test-d.ts | 18 ++--- ...ma-verify.dependencies.integration.test.ts | 25 +++---- .../test/family.schema-verify.types.test.ts | 25 +++---- .../test/sql-orm-client/helpers.ts | 10 +-- .../fixtures/generated/mongo-contract.d.ts | 12 ++- .../fixtures/generated/mongo-contract.json | 14 ++-- .../fixtures/generated/sql-contract.d.ts | 70 +++++++++--------- .../fixtures/generated/sql-contract.json | 74 +++++++++---------- .../value-objects.integration.test.ts | 12 ++- .../test/vite-plugin.hmr.e2e.test.ts | 8 +- 15 files changed, 160 insertions(+), 197 deletions(-) diff --git a/test/integration/test/authoring/paradedb-bm25-narrowing.test.ts b/test/integration/test/authoring/paradedb-bm25-narrowing.test.ts index 4a2323a124..b505e4b69c 100644 --- a/test/integration/test/authoring/paradedb-bm25-narrowing.test.ts +++ b/test/integration/test/authoring/paradedb-bm25-narrowing.test.ts @@ -38,7 +38,7 @@ describe('paradedb bm25 narrowing in TS authoring DSL', () => { }, ); - const indexes = contract.storage.namespaces.__unbound__.tables.doc.indexes; + const indexes = contract.storage.__unbound__.tables.doc.indexes; expect(indexes).toHaveLength(1); expect(indexes[0]).toMatchObject({ columns: ['body'], diff --git a/test/integration/test/cli.db-sign.e2e.test.ts b/test/integration/test/cli.db-sign.e2e.test.ts index da4653e917..e7ba48881f 100644 --- a/test/integration/test/cli.db-sign.e2e.test.ts +++ b/test/integration/test/cli.db-sign.e2e.test.ts @@ -351,7 +351,7 @@ withTempDir(({ createTempDir }) => { const contractPath = resolve(testSetup.testDir, 'src/prisma/contract.json'); const { readFile, writeFile } = await import('node:fs/promises'); const contractJson = JSON.parse(await readFile(contractPath, 'utf-8')); - contractJson.storage.namespaces.__unbound__.tables.user.columns.email = { + contractJson.storage.__unbound__.tables.user.columns.email = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, @@ -395,7 +395,7 @@ withTempDir(({ createTempDir }) => { const contractPath = resolve(testSetup.testDir, 'src/prisma/contract.json'); const { readFile, writeFile } = await import('node:fs/promises'); const contractJson = JSON.parse(await readFile(contractPath, 'utf-8')); - contractJson.storage.namespaces.__unbound__.tables.user.columns.email = { + contractJson.storage.__unbound__.tables.user.columns.email = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, diff --git a/test/integration/test/contract-builder.test.ts b/test/integration/test/contract-builder.test.ts index 98f0ac3698..12ee95ec64 100644 --- a/test/integration/test/contract-builder.test.ts +++ b/test/integration/test/contract-builder.test.ts @@ -50,21 +50,19 @@ describe('builder integration', () => { }), }), }); - const userTable = contract.storage.namespaces.__unbound__.tables.user; + const userTable = contract.storage.__unbound__.tables.user; expect(userTable).toBeDefined(); expect(userTable?.columns).toMatchObject({ id: expect.anything(), email: expect.anything(), createdAt: expect.anything(), }); - expectTypeOf< - keyof typeof contract.storage.namespaces.__unbound__.tables - >().toEqualTypeOf<'user'>(); + expectTypeOf().toEqualTypeOf<'user'>(); type ContractCodecTypes = ExtractCodecTypes; type IntCodecOutput = ContractCodecTypes['pg/int4@1']['output']; expectTypeOf().toEqualTypeOf(); type ColumnMeta = - (typeof contract)['storage']['namespaces']['__unbound__']['tables']['user']['columns']['id']; + (typeof contract)['storage']['__unbound__']['tables']['user']['columns']['id']; expectTypeOf().toExtend(); expectTypeOf().toEqualTypeOf(); @@ -86,10 +84,10 @@ describe('builder integration', () => { expectTypeOf(contract.targetFamily).toEqualTypeOf<'sql'>(); // Verify table name is literal 'user', not string - expectTypeOf(contract.storage.namespaces.__unbound__.tables).toHaveProperty('user'); + expectTypeOf(contract.storage.__unbound__.tables).toHaveProperty('user'); // Verify column names are literal types - const userTableType = contract.storage.namespaces.__unbound__.tables.user; + const userTableType = contract.storage.__unbound__.tables.user; expectTypeOf(userTableType.columns).toHaveProperty('id'); expectTypeOf(userTableType.columns).toHaveProperty('email'); expectTypeOf(userTableType.columns).toHaveProperty('createdAt'); @@ -139,7 +137,7 @@ describe('builder integration', () => { }); expect(contract.target).toBe('postgres'); - expect(contract.storage.namespaces.__unbound__.tables.user).toBeDefined(); + expect(contract.storage.__unbound__.tables.user).toBeDefined(); }); it('contract works with sql() function', () => { @@ -246,16 +244,15 @@ describe('builder integration', () => { // Runtime checks expect(builderContract.target).toBe(fixtureContract.target); expect(builderContract.targetFamily).toBe(fixtureContract.targetFamily); - expect(builderContract.storage.namespaces.__unbound__.tables.user.columns).toMatchObject({ + expect(builderContract.storage.__unbound__.tables.user.columns).toMatchObject({ id: { - codecId: fixtureContract.storage.namespaces.__unbound__.tables.user.columns.id.codecId, + codecId: fixtureContract.storage.__unbound__.tables.user.columns.id.codecId, }, email: { - codecId: fixtureContract.storage.namespaces.__unbound__.tables.user.columns.email.codecId, + codecId: fixtureContract.storage.__unbound__.tables.user.columns.email.codecId, }, createdAt: { - codecId: - fixtureContract.storage.namespaces.__unbound__.tables.user.columns.createdAt.codecId, + codecId: fixtureContract.storage.__unbound__.tables.user.columns.createdAt.codecId, }, }); type ModelShape = { @@ -274,14 +271,10 @@ describe('builder integration', () => { expectTypeOf(builderContract.targetFamily).toEqualTypeOf<'sql'>(); // Verify table and column types match - expectTypeOf(builderContract.storage.namespaces.__unbound__.tables).toHaveProperty('user'); - expectTypeOf(builderContract.storage.namespaces.__unbound__.tables.user.columns).toHaveProperty( - 'id', - ); - expectTypeOf(builderContract.storage.namespaces.__unbound__.tables.user.columns).toHaveProperty( - 'email', - ); - expectTypeOf(builderContract.storage.namespaces.__unbound__.tables.user.columns).toHaveProperty( + expectTypeOf(builderContract.storage.__unbound__.tables).toHaveProperty('user'); + expectTypeOf(builderContract.storage.__unbound__.tables.user.columns).toHaveProperty('id'); + expectTypeOf(builderContract.storage.__unbound__.tables.user.columns).toHaveProperty('email'); + expectTypeOf(builderContract.storage.__unbound__.tables.user.columns).toHaveProperty( 'createdAt', ); @@ -308,14 +301,10 @@ describe('builder integration', () => { }); // Type checks - verify codecId is a string (TypeScript may widen literal types) - expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.id.codecId, - ).toExtend(); - expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.email.codecId, - ).toExtend(); + expectTypeOf(contract.storage.__unbound__.tables.user.columns.id.codecId).toExtend(); + expectTypeOf(contract.storage.__unbound__.tables.user.columns.email.codecId).toExtend(); // Runtime check that they match expected values - expect(contract.storage.namespaces.__unbound__.tables.user.columns).toMatchObject({ + expect(contract.storage.__unbound__.tables.user.columns).toMatchObject({ id: { codecId: 'pg/int4@1' }, email: { codecId: 'pg/text@1' }, }); @@ -336,7 +325,7 @@ describe('builder integration', () => { }, }); // Contract builds successfully - invalid codecId will cause errors at runtime - expect(contract.storage.namespaces.__unbound__.tables.user.columns.id.codecId).toBe('invalid'); + expect(contract.storage.__unbound__.tables.user.columns.id.codecId).toBe('invalid'); }); describe('relation builder', () => { diff --git a/test/integration/test/contract-builder.types.test-d.ts b/test/integration/test/contract-builder.types.test-d.ts index ae3d36fa13..2a7d9f5671 100644 --- a/test/integration/test/contract-builder.types.test-d.ts +++ b/test/integration/test/contract-builder.types.test-d.ts @@ -49,11 +49,9 @@ test('builder contract types match fixture contract types', () => { ) as Contract; type BuilderUserTable = NonNullable< - (typeof _validatedBuilderContract.storage.namespaces.__unbound__.tables)['user'] - >; - type FixtureUserTable = NonNullable< - (typeof _fixtureContract.storage.namespaces.__unbound__.tables)['user'] + (typeof _validatedBuilderContract.storage.__unbound__.tables)['user'] >; + type FixtureUserTable = NonNullable<(typeof _fixtureContract.storage.__unbound__.tables)['user']>; expectTypeOf().toHaveProperty('columns'); expectTypeOf().toHaveProperty('columns'); @@ -143,12 +141,10 @@ test('refined object contract preserves downstream model token inference', () => const validated = new SqlContractSerializer().deserializeContract(contract) as typeof contract; type RefinedUserColumns = NonNullable< - NonNullable<(typeof validated.storage.namespaces.__unbound__.tables)['user']>['columns'] + NonNullable<(typeof validated.storage.__unbound__.tables)['user']>['columns'] >; - expectTypeOf().toExtend< - Record - >(); + expectTypeOf().toExtend>(); expectTypeOf().toExtend>(); expectTypeOf(validated.models.User.storage.table).toExtend(); expectTypeOf< @@ -230,31 +226,31 @@ test('integrated callback authoring exposes composition-shaped type helpers', () expectTypeOf().toEqualTypeOf<'pg/vector@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.id.codecId, + contract.storage.__unbound__.tables.user.columns.id.codecId, ).toEqualTypeOf<'pg/int4@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.email.codecId, + contract.storage.__unbound__.tables.user.columns.email.codecId, ).toEqualTypeOf<'pg/text@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.age.codecId, + contract.storage.__unbound__.tables.user.columns.age.codecId, ).toEqualTypeOf<'pg/int4@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.isActive.codecId, + contract.storage.__unbound__.tables.user.columns.isActive.codecId, ).toEqualTypeOf<'pg/bool@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.score.codecId, + contract.storage.__unbound__.tables.user.columns.score.codecId, ).toEqualTypeOf<'pg/float8@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.profile.codecId, + contract.storage.__unbound__.tables.user.columns.profile.codecId, ).toEqualTypeOf<'pg/jsonb@1'>(); expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.createdAt.codecId, + contract.storage.__unbound__.tables.user.columns.createdAt.codecId, ).toEqualTypeOf<'pg/timestamptz@1'>(); // `role.typeRef` and `embedding.typeRef` capture is gated on the // descriptor-level generic forwarding noted above; the contract // still carries the correct typeRef strings at runtime. expectTypeOf( - contract.storage.namespaces.__unbound__.tables.user.columns.embedding.typeRef, + contract.storage.__unbound__.tables.user.columns.embedding.typeRef, ).toEqualTypeOf<'Embedding'>(); }); diff --git a/test/integration/test/contract-imports.test.ts b/test/integration/test/contract-imports.test.ts index 555cc884b8..315ea335c3 100644 --- a/test/integration/test/contract-imports.test.ts +++ b/test/integration/test/contract-imports.test.ts @@ -179,9 +179,7 @@ import type { SqlStorage } from '@prisma-next/sql-contract/types'; // biome-ignore lint/suspicious/noExplicitAny: test code with type assertions const _contract: Contract = {} as any; const _storage: Contract['storage'] = _contract.storage; -const _namespaces: Contract['storage']['namespaces'] = _storage.namespaces; -const _tables: Contract['storage']['namespaces']['__unbound__']['tables'] = - _namespaces['__unbound__'].tables; +const _tables: Contract['storage']['__unbound__']['tables'] = _storage['__unbound__'].tables; // Verify we can access CodecTypes const _codecTypes: CodecTypes = {} as any; @@ -190,7 +188,7 @@ const _codecTypes: CodecTypes = {} as any; const _sqlStorage: SqlStorage = _contract.storage; // Verify the contract type is correctly structured -type UserTable = Contract['storage']['namespaces']['__unbound__']['tables']['user']; +type UserTable = Contract['storage']['__unbound__']['tables']['user']; type UserColumns = UserTable['columns']; type UserIdColumn = UserColumns['id']; `; @@ -318,7 +316,7 @@ import contractJson from './contract.json' with { type: 'json' }; const contract = new SqlContractSerializer().deserializeContract(contractJson) as Contract; // Verify we can access all exported types -const _namespaces: Namespaces = contract.storage.namespaces; +const _namespaces: Namespaces = contract.storage; const _tables = _namespaces['__unbound__'].tables; const _models: Models = contract.models; diff --git a/test/integration/test/dsl-type-inference.test-d.ts b/test/integration/test/dsl-type-inference.test-d.ts index 925cc581c7..b443fb99e0 100644 --- a/test/integration/test/dsl-type-inference.test-d.ts +++ b/test/integration/test/dsl-type-inference.test-d.ts @@ -49,13 +49,12 @@ const singleModelContract = defineContract({ test('table name literals survive in storage.tables (single model)', () => { expectTypeOf< - keyof typeof singleModelContract.storage.namespaces.__unbound__.tables + keyof typeof singleModelContract.storage.__unbound__.tables >().toEqualTypeOf<'user'>(); }); test('column name literals survive in storage.tables[name].columns', () => { - type UserColumns = - (typeof singleModelContract.storage.namespaces.__unbound__.tables)['user']['columns']; + type UserColumns = (typeof singleModelContract.storage.__unbound__.tables)['user']['columns']; expectTypeOf().toEqualTypeOf<'id' | 'email'>(); }); @@ -73,9 +72,7 @@ test('deserializeContract preserves table name literals', () => { const validated = new SqlContractSerializer().deserializeContract( singleModelContract, ) as typeof singleModelContract; - expectTypeOf< - keyof typeof validated.storage.namespaces.__unbound__.tables - >().toEqualTypeOf<'user'>(); + expectTypeOf().toEqualTypeOf<'user'>(); }); test('deserializeContract preserves model name literals', () => { @@ -120,9 +117,9 @@ const multiModelContract = defineContract({ }); test('multi-model contract preserves table name literals', () => { - expectTypeOf< - keyof typeof multiModelContract.storage.namespaces.__unbound__.tables - >().toEqualTypeOf<'user' | 'post'>(); + expectTypeOf().toEqualTypeOf< + 'user' | 'post' + >(); }); test('multi-model contract preserves model name literals', () => { @@ -130,8 +127,7 @@ test('multi-model contract preserves model name literals', () => { }); test('multi-model contract preserves column literals per table', () => { - type PostColumns = - (typeof multiModelContract.storage.namespaces.__unbound__.tables)['post']['columns']; + type PostColumns = (typeof multiModelContract.storage.__unbound__.tables)['post']['columns']; expectTypeOf().toEqualTypeOf<'id' | 'userId' | 'title'>(); }); diff --git a/test/integration/test/family.schema-verify.dependencies.integration.test.ts b/test/integration/test/family.schema-verify.dependencies.integration.test.ts index 1d0e18cd4b..6803bf8cc4 100644 --- a/test/integration/test/family.schema-verify.dependencies.integration.test.ts +++ b/test/integration/test/family.schema-verify.dependencies.integration.test.ts @@ -323,20 +323,17 @@ describe('family instance schemaVerify', () => { ...contract, storage: { ...contract.storage, - namespaces: { - ...contract.storage.namespaces, - __unbound__: { - ...contract.storage.namespaces.__unbound__, - tables: { - ...contract.storage.namespaces.__unbound__.tables, - user: { - ...contract.storage.namespaces.__unbound__.tables.user, - columns: { - ...contract.storage.namespaces.__unbound__.tables.user.columns, - email: { - ...contract.storage.namespaces.__unbound__.tables.user.columns.email, - codecId: 'pg/unknown-type@1' as const, - }, + __unbound__: { + ...contract.storage.__unbound__, + tables: { + ...contract.storage.__unbound__.tables, + user: { + ...contract.storage.__unbound__.tables.user, + columns: { + ...contract.storage.__unbound__.tables.user.columns, + email: { + ...contract.storage.__unbound__.tables.user.columns.email, + codecId: 'pg/unknown-type@1' as const, }, }, }, diff --git a/test/integration/test/family.schema-verify.types.test.ts b/test/integration/test/family.schema-verify.types.test.ts index 6199e72261..f5d7239af2 100644 --- a/test/integration/test/family.schema-verify.types.test.ts +++ b/test/integration/test/family.schema-verify.types.test.ts @@ -213,20 +213,17 @@ describe('family instance schemaVerify - types', () => { ...contract, storage: { ...contract.storage, - namespaces: { - ...contract.storage.namespaces, - __unbound__: { - ...contract.storage.namespaces.__unbound__, - tables: { - ...contract.storage.namespaces.__unbound__.tables, - user: { - ...contract.storage.namespaces.__unbound__.tables.user, - columns: { - ...contract.storage.namespaces.__unbound__.tables.user.columns, - email: { - ...contract.storage.namespaces.__unbound__.tables.user.columns.email, - codecId: 'pg/unknown-type@1' as const, - }, + __unbound__: { + ...contract.storage.__unbound__, + tables: { + ...contract.storage.__unbound__.tables, + user: { + ...contract.storage.__unbound__.tables.user, + columns: { + ...contract.storage.__unbound__.tables.user.columns, + email: { + ...contract.storage.__unbound__.tables.user.columns.email, + codecId: 'pg/unknown-type@1' as const, }, }, }, diff --git a/test/integration/test/sql-orm-client/helpers.ts b/test/integration/test/sql-orm-client/helpers.ts index f8fda45d4a..8851eaa06e 100644 --- a/test/integration/test/sql-orm-client/helpers.ts +++ b/test/integration/test/sql-orm-client/helpers.ts @@ -105,7 +105,7 @@ export function buildMixedPolyContract(): TestContract { base: 'Task', }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.tasks = { + raw.storage[UNBOUND_NAMESPACE_ID].tables.tasks = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, title: { nativeType: 'text', codecId: 'pg/text@1', nullable: false }, @@ -118,7 +118,7 @@ export function buildMixedPolyContract(): TestContract { foreignKeys: [], }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.features = { + raw.storage[UNBOUND_NAMESPACE_ID].tables.features = { columns: { id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, priority: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false }, @@ -166,17 +166,17 @@ export function buildStiPolyContract(): TestContract { base: 'User', }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.kind = { + raw.storage[UNBOUND_NAMESPACE_ID].tables.users.columns.kind = { codecId: 'pg/text@1', nativeType: 'text', nullable: false, }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.role = { + raw.storage[UNBOUND_NAMESPACE_ID].tables.users.columns.role = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, }; - raw.storage.namespaces[UNBOUND_NAMESPACE_ID].tables.users.columns.plan = { + raw.storage[UNBOUND_NAMESPACE_ID].tables.users.columns.plan = { codecId: 'pg/text@1', nativeType: 'text', nullable: true, diff --git a/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts b/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts index aac1053128..e714a554d0 100644 --- a/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts +++ b/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts @@ -68,13 +68,11 @@ type ContractBase = { }; }; readonly storage: { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly collections: { - readonly shops: { readonly kind: 'mongo-collection' }; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly collections: { + readonly shops: { readonly kind: 'mongo-collection' }; }; }; readonly storageHash: StorageHash; diff --git a/test/integration/test/value-objects/fixtures/generated/mongo-contract.json b/test/integration/test/value-objects/fixtures/generated/mongo-contract.json index 6a970d66b3..563b5b02eb 100644 --- a/test/integration/test/value-objects/fixtures/generated/mongo-contract.json +++ b/test/integration/test/value-objects/fixtures/generated/mongo-contract.json @@ -75,14 +75,12 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "mongo-namespace", - "collections": { - "shops": { - "kind": "mongo-collection" - } + "__unbound__": { + "id": "__unbound__", + "kind": "mongo-namespace", + "collections": { + "shops": { + "kind": "mongo-collection" } } }, diff --git a/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts b/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts index ce2a51b87b..9dc5fe2774 100644 --- a/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts +++ b/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts @@ -35,46 +35,44 @@ export type TypeMaps = TypeMapsType; - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'sql-namespace'; - readonly tables: { - readonly shop: { - columns: { - readonly id: { - readonly nativeType: 'int4'; - readonly codecId: 'pg/int4@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'function'; - readonly expression: 'autoincrement()'; - }; - }; - readonly name: { - readonly nativeType: 'text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly location: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: false; - }; - readonly notes: { - readonly nativeType: 'jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'sql-namespace'; + readonly tables: { + readonly shop: { + columns: { + readonly id: { + readonly nativeType: 'int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'function'; + readonly expression: 'autoincrement()'; }; }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; + readonly name: { + readonly nativeType: 'text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + }; + readonly location: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: false; + }; + readonly notes: { + readonly nativeType: 'jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + }; }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; }; - readonly types: Record; }; + readonly types: Record; }; readonly storageHash: StorageHash; }, @@ -148,5 +146,5 @@ type ContractBase = ContractShape< export type Contract = ContractWithTypeMaps; -export type Tables = Contract['storage']['namespaces']['__unbound__']['tables']; +export type Tables = Contract['storage']['__unbound__']['tables']; export type Models = Contract['models']; diff --git a/test/integration/test/value-objects/fixtures/generated/sql-contract.json b/test/integration/test/value-objects/fixtures/generated/sql-contract.json index 52f0ab45cf..b81da1d63b 100644 --- a/test/integration/test/value-objects/fixtures/generated/sql-contract.json +++ b/test/integration/test/value-objects/fixtures/generated/sql-contract.json @@ -90,47 +90,45 @@ }, "storage": { "storageHash": "sha256:vo-sql-test-storage-hash", - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "shop": { - "columns": { - "id": { - "codecId": "pg/int4@1", - "nativeType": "int4", - "nullable": false, - "default": { - "kind": "function", - "expression": "autoincrement()" - } - }, - "name": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "location": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": false - }, - "notes": { - "codecId": "pg/jsonb@1", - "nativeType": "jsonb", - "nullable": true + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "shop": { + "columns": { + "id": { + "codecId": "pg/int4@1", + "nativeType": "int4", + "nullable": false, + "default": { + "kind": "function", + "expression": "autoincrement()" } }, - "primaryKey": { - "columns": [ - "id" - ] + "name": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [], - "indexes": [], - "foreignKeys": [] - } + "location": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": false + }, + "notes": { + "codecId": "pg/jsonb@1", + "nativeType": "jsonb", + "nullable": true + } + }, + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [], + "indexes": [], + "foreignKeys": [] } } } diff --git a/test/integration/test/value-objects/value-objects.integration.test.ts b/test/integration/test/value-objects/value-objects.integration.test.ts index cbb7712da6..2e0baf524f 100644 --- a/test/integration/test/value-objects/value-objects.integration.test.ts +++ b/test/integration/test/value-objects/value-objects.integration.test.ts @@ -193,13 +193,11 @@ describe('value objects: end-to-end SQL pipeline', () => { expect(homeAddressField.type).toEqual({ kind: 'valueObject', name: 'Address' }); expect(homeAddressField.nullable).toBe(false); - const storage = contract.storage as unknown as { - namespaces: Record< - string, - { tables: Record }> } - >; - }; - const userTable = storage.namespaces[UNBOUND_NAMESPACE_ID]!.tables['user']; + const storage = contract.storage as unknown as Record< + string, + { tables: Record }> } + >; + const userTable = storage[UNBOUND_NAMESPACE_ID]!.tables['user']; expect(userTable).toBeDefined(); expect(userTable!.columns['homeAddress']).toBeDefined(); expect(userTable!.columns['homeAddress']!.nativeType).toBe('jsonb'); diff --git a/test/integration/test/vite-plugin.hmr.e2e.test.ts b/test/integration/test/vite-plugin.hmr.e2e.test.ts index ef79d40c48..00d5497a61 100644 --- a/test/integration/test/vite-plugin.hmr.e2e.test.ts +++ b/test/integration/test/vite-plugin.hmr.e2e.test.ts @@ -1,5 +1,7 @@ import { copyFileSync, existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; +import { getStorageNamespace } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { timeouts } from '@prisma-next/test-utils'; import * as vite7 from 'vite7'; import * as vite8 from 'vite8'; @@ -16,10 +18,8 @@ const pslFixtureSubdir = 'vite-plugin-psl'; const UNBOUND_NAMESPACE = '__unbound__'; -function unboundUserColumns(storage: { - namespaces: Record } } }>; -}) { - return storage.namespaces[UNBOUND_NAMESPACE]!.tables.user.columns; +function unboundUserColumns(storage: object) { + return getStorageNamespace(storage, UNBOUND_NAMESPACE)!.tables['user']!.columns; } type ViteModuleNodeLike = object; From d59feebe45b0406459e246120a6029f03ce92422 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 00:50:55 +0200 Subject: [PATCH 45/60] refactor(examples): migrate demo and retail-store storage reads to flat shape Signed-off-by: Will Madden --- .../prisma-next-demo/src/app/ContractView.tsx | 5 +++-- .../test/contract-authoring.test.ts | 2 +- .../test/demo-dx.integration.test.ts | 6 +++-- .../test/demo-dx.types.test.ts | 4 ++-- .../retail-store/test/migration-chain.test.ts | 22 +++++++++---------- examples/retail-store/test/migration.test.ts | 2 +- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/examples/prisma-next-demo/src/app/ContractView.tsx b/examples/prisma-next-demo/src/app/ContractView.tsx index dd7ab52d46..ab72e398ed 100644 --- a/examples/prisma-next-demo/src/app/ContractView.tsx +++ b/examples/prisma-next-demo/src/app/ContractView.tsx @@ -1,4 +1,5 @@ -import type { StorageTable } from '@prisma-next/sql-contract/types'; +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace, StorageTable } from '@prisma-next/sql-contract/types'; import type { Contract } from '../prisma/contract.d'; type ContractModel = Contract['models'][keyof Contract['models']]; @@ -139,7 +140,7 @@ export function ContractView({ contract }: { contract: Contract }) {
- {Object.values(contract.storage.namespaces).flatMap((ns) => + {storageNamespaceValues(contract.storage).flatMap((ns) => Object.entries(ns.tables as Record).map(([tableName, table]) => ( )), diff --git a/examples/prisma-next-demo/test/contract-authoring.test.ts b/examples/prisma-next-demo/test/contract-authoring.test.ts index c890eee1bd..dd2e6b85bb 100644 --- a/examples/prisma-next-demo/test/contract-authoring.test.ts +++ b/examples/prisma-next-demo/test/contract-authoring.test.ts @@ -3,7 +3,7 @@ import { contract } from '../prisma/contract'; describe('demo TS contract authoring', () => { it('keeps Post.userId storage aligned with User.id', () => { - const tables = contract.storage.namespaces.__unbound__.tables; + const tables = contract.storage.__unbound__.tables; const userIdColumn = tables.post.columns.userId; const userIdTargetColumn = tables.user.columns.id; diff --git a/examples/prisma-next-demo/test/demo-dx.integration.test.ts b/examples/prisma-next-demo/test/demo-dx.integration.test.ts index 14d1130ce9..e279525380 100644 --- a/examples/prisma-next-demo/test/demo-dx.integration.test.ts +++ b/examples/prisma-next-demo/test/demo-dx.integration.test.ts @@ -7,6 +7,8 @@ * Spec: agent-os/specs/2026-02-15-runtime-dx-ir-shaped-contract-mappings-on-executioncontext/spec.md */ +import { storageNamespaceValues } from '@prisma-next/framework-components/ir'; +import type { SqlNamespace } from '@prisma-next/sql-contract/types'; import { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime'; import { describe, expect, it } from 'vitest'; import type { Contract } from '../src/prisma/contract.d'; @@ -22,7 +24,7 @@ describe('demo contract visualization DX', () => { expect(contract.models).toBeDefined(); expect(typeof contract.models).toBe('object'); expect(contract.storage).toBeDefined(); - expect(contract.storage.namespaces).toBeDefined(); + expect(contract.storage.__unbound__).toBeDefined(); expect(contract.capabilities).toBeDefined(); expect(typeof contract.capabilities).toBe('object'); expect(contract.extensionPacks).toBeDefined(); @@ -61,7 +63,7 @@ describe('demo contract visualization DX', () => { expect(typeof m['relations']).toBe('object'); } - for (const [, ns] of Object.entries(contract.storage.namespaces)) { + for (const ns of storageNamespaceValues(contract.storage)) { for (const [, table] of Object.entries(ns.tables)) { expect(table.columns).toBeDefined(); } diff --git a/examples/prisma-next-demo/test/demo-dx.types.test.ts b/examples/prisma-next-demo/test/demo-dx.types.test.ts index 60d0e2020f..963b72065d 100644 --- a/examples/prisma-next-demo/test/demo-dx.types.test.ts +++ b/examples/prisma-next-demo/test/demo-dx.types.test.ts @@ -23,8 +23,8 @@ test('SPI deserializeContract output is assignable to visualization shape', () = expectTypeOf(contract.models).toHaveProperty('User'); expectTypeOf(contract.models).toHaveProperty('Post'); - expectTypeOf(contract.storage.namespaces['__unbound__'].tables).toHaveProperty('user'); - expectTypeOf(contract.storage.namespaces['__unbound__'].tables).toHaveProperty('post'); + expectTypeOf(contract.storage['__unbound__'].tables).toHaveProperty('user'); + expectTypeOf(contract.storage['__unbound__'].tables).toHaveProperty('post'); expectTypeOf(contract.models.User.storage.fields).toHaveProperty('email'); expectTypeOf(contract.models.Post.storage.fields).toHaveProperty('userId'); }); diff --git a/examples/retail-store/test/migration-chain.test.ts b/examples/retail-store/test/migration-chain.test.ts index cf634a624f..acae440604 100644 --- a/examples/retail-store/test/migration-chain.test.ts +++ b/examples/retail-store/test/migration-chain.test.ts @@ -33,28 +33,28 @@ type FlatMongoStorage = { function namespacedMongoContract(contract: MongoContract): MongoContract { const storage = contract.storage; - if ('namespaces' in storage && storage.namespaces != null) { + if (UNBOUND_NAMESPACE_ID in storage) { return contract; } if (!('collections' in storage)) { return contract; } - const { collections, storageHash, ...rest } = storage as FlatMongoStorage; - // Test-only rewrap of a legacy on-disk end-contract.json whose storageHash - // is a plain string. MongoContract's storage carries a branded StorageHash; - // the brand is purely a type-level marker and the runtime payload is + const { collections, storageHash, ...rest } = storage as unknown as FlatMongoStorage; + // Test-only rewrap of a legacy on-disk end-contract.json whose top-level + // `collections` predate the per-namespace storage plane and whose + // storageHash is a plain string. MongoContract's storage carries a branded + // StorageHash and keys namespaces directly under `storage` (ADR 221 flat + // shape); the brand is purely a type-level marker and the runtime payload is // identical, so a structural rewrap is safe here. return { ...contract, storage: { ...rest, storageHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-namespace' as const, - collections, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-namespace' as const, + collections, }, }, } as unknown as MongoContract; diff --git a/examples/retail-store/test/migration.test.ts b/examples/retail-store/test/migration.test.ts index fbd8bbcc15..b6133a3ca8 100644 --- a/examples/retail-store/test/migration.test.ts +++ b/examples/retail-store/test/migration.test.ts @@ -27,7 +27,7 @@ describe('migration', { timeout: timeouts.spinUpMongoMemoryServer }, () => { it('contract contains expected index definitions', () => { const contract = new MongoContractSerializer().deserializeContract(contractJson) as Contract; - const collections = contract.storage.namespaces['__unbound__'].collections; + const collections = contract.storage['__unbound__'].collections; expect(collections.products.indexes).toMatchObject([ { From 279ab72c25581f655ed092a0608d44bd90fbba59 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 01:00:07 +0200 Subject: [PATCH 46/60] refactor(target-mongo): flatten storage in planner/runner test fixtures Signed-off-by: Will Madden --- .../1-mongo-target/test/mongo-planner.test.ts | 10 ++++------ .../test/mongo-runner-integration.test.ts | 14 +++++--------- .../mongo-runner.schema-verify.integration.test.ts | 10 ++++------ ...ongo-runner.validator-widen.integration.test.ts | 10 ++++------ 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-planner.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-planner.test.ts index 5cb532e8a5..6556ce3908 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-planner.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-planner.test.ts @@ -65,12 +65,10 @@ function makeContract( models: {}, storage: { storageHash, - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: builtCollections, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: builtCollections, }, }, } as unknown as MongoContract; diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts index 1535bca947..a30e7d3e37 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts @@ -77,12 +77,10 @@ function makeContract( return { storage: { storageHash, - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: storageCollections, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: storageCollections, }, }, } as unknown as MongoContract; @@ -96,9 +94,7 @@ function bareContract(storageHash: string): MongoContract { return { storage: { storageHash, - namespaces: { - __unbound__: { id: '__unbound__', kind: 'mongo-namespace', collections: {} }, - }, + __unbound__: { id: '__unbound__', kind: 'mongo-namespace', collections: {} }, }, } as unknown as MongoContract; } diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.schema-verify.integration.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.schema-verify.integration.test.ts index 181486624f..445007f26f 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.schema-verify.integration.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.schema-verify.integration.test.ts @@ -70,12 +70,10 @@ function makeContract( return { storage: { storageHash, - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: storageCollections, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: storageCollections, }, }, } as unknown as MongoContract; diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.validator-widen.integration.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.validator-widen.integration.test.ts index 671ab6d3b5..ec211128bb 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.validator-widen.integration.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.validator-widen.integration.test.ts @@ -73,12 +73,10 @@ function makeContractWithValidator( models: {}, storage: { storageHash, - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: { [collectionName]: collection }, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: { [collectionName]: collection }, }, }, } as unknown as MongoContract; From bd33c590f303d827441bde76410744e959f7f1c9 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 01:02:05 +0200 Subject: [PATCH 47/60] refactor(mongo-family): flatten storage in shared test fixtures Signed-off-by: Will Madden --- .../emitter/test/fixtures/blog-contract.ts | 12 +++++------- .../test/fixtures/create-mongo-contract.ts | 4 +--- .../query-builder/test/fixtures/test-contract.ts | 16 +++++++--------- .../test/fixtures/decode-fixture-contract.ts | 14 ++++++-------- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/blog-contract.ts b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/blog-contract.ts index 373811efa8..2138d49a41 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/blog-contract.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/blog-contract.ts @@ -62,13 +62,11 @@ export const blogContract: Contract = { }, storage: { storageHash: 'sha256:test', - namespaces: { - __unbound__: { - id: '__unbound__', - collections: { - users: {}, - posts: {}, - }, + __unbound__: { + id: '__unbound__', + collections: { + users: {}, + posts: {}, }, }, }, diff --git a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts index 4fe29af375..981ac5d260 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts @@ -7,9 +7,7 @@ export function namespacedMongoStorageFromCollections( ) { return { storageHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, collections }, } as Contract['storage']; } diff --git a/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts b/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts index 1c481fc589..f1e4ed4f3c 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts @@ -227,15 +227,13 @@ export const testContractJson = { }, storage: { storageHash: 'test-hash', - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: { - orders: { kind: 'mongo-collection' }, - users: { kind: 'mongo-collection' }, - customers: { kind: 'mongo-collection' }, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: { + orders: { kind: 'mongo-collection' }, + users: { kind: 'mongo-collection' }, + customers: { kind: 'mongo-collection' }, }, }, }, diff --git a/packages/2-mongo-family/7-runtime/test/fixtures/decode-fixture-contract.ts b/packages/2-mongo-family/7-runtime/test/fixtures/decode-fixture-contract.ts index e0384f1253..9bc8e856ff 100644 --- a/packages/2-mongo-family/7-runtime/test/fixtures/decode-fixture-contract.ts +++ b/packages/2-mongo-family/7-runtime/test/fixtures/decode-fixture-contract.ts @@ -99,14 +99,12 @@ export const decodeFixtureContractJson = { targetFamily: 'mongo' as const, roots: { users: crossRef('User'), posts: crossRef('Post') }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__' as const, - kind: 'mongo-namespace' as const, - collections: { - users: { kind: 'mongo-collection' as const }, - posts: { kind: 'mongo-collection' as const }, - }, + __unbound__: { + id: '__unbound__' as const, + kind: 'mongo-namespace' as const, + collections: { + users: { kind: 'mongo-collection' as const }, + posts: { kind: 'mongo-collection' as const }, }, }, storageHash: 'decode-integration-test', From d11c39248fb35b7fe19903ade0eb739566d954d9 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 07:52:39 +0200 Subject: [PATCH 48/60] fix(tests): repair botched codemod edits in flat-storage type literals Signed-off-by: Will Madden --- .../contract-space-seed-phase.mongo.test.ts | 40 +++++++++---------- .../contract-ts/test/fixtures/contract.d.ts | 2 +- .../test/fixtures/contract.d.ts | 2 +- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/1-framework/3-tooling/cli/test/utils/contract-space-seed-phase.mongo.test.ts b/packages/1-framework/3-tooling/cli/test/utils/contract-space-seed-phase.mongo.test.ts index 962c84e77b..159f970b16 100644 --- a/packages/1-framework/3-tooling/cli/test/utils/contract-space-seed-phase.mongo.test.ts +++ b/packages/1-framework/3-tooling/cli/test/utils/contract-space-seed-phase.mongo.test.ts @@ -45,20 +45,18 @@ interface MongoShapedExtensionContract { readonly roots: Record; readonly models: Record; readonly storage: { - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly tables: Record< - string, - { - readonly indexes: ReadonlyArray<{ - readonly keys: ReadonlyArray<{ readonly field: string; readonly direction: 1 | -1 }>; - readonly unique?: boolean; - }>; - } - >; - }; + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly tables: Record< + string, + { + readonly indexes: ReadonlyArray<{ + readonly keys: ReadonlyArray<{ readonly field: string; readonly direction: 1 | -1 }>; + readonly unique?: boolean; + }>; + } + >; }; readonly storageHash: string; }; @@ -75,14 +73,12 @@ function buildMongoExtensionContract(): MongoShapedExtensionContract { roots: {}, models: {}, storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - tables: { - cipherstash_state: { - indexes: [{ keys: [{ field: 'tenantId', direction: 1 }], unique: true }], - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + tables: { + cipherstash_state: { + indexes: [{ keys: [{ field: 'tenantId', direction: 1 }], unique: true }], }, }, }, diff --git a/packages/2-sql/2-authoring/contract-ts/test/fixtures/contract.d.ts b/packages/2-sql/2-authoring/contract-ts/test/fixtures/contract.d.ts index 115c0976aa..4af9a0fb38 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/fixtures/contract.d.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/fixtures/contract.d.ts @@ -28,7 +28,7 @@ export type Contract = ContractWithTypeMaps; }; }; - readonly namespaces: { readonly __unbound__: { readonly id: '__unbound__' } }; + readonly __unbound__: { readonly id: '__unbound__' }; }, { readonly User: { diff --git a/packages/2-sql/4-lanes/relational-core/test/fixtures/contract.d.ts b/packages/2-sql/4-lanes/relational-core/test/fixtures/contract.d.ts index eb6936383f..2310460d84 100644 --- a/packages/2-sql/4-lanes/relational-core/test/fixtures/contract.d.ts +++ b/packages/2-sql/4-lanes/relational-core/test/fixtures/contract.d.ts @@ -42,7 +42,7 @@ export type Contract = ContractWithTypeMaps Date: Sun, 31 May 2026 07:52:50 +0200 Subject: [PATCH 49/60] chore(examples): regen multi-extension-monorepo contract spaces to flat storage Regenerate src contracts (audit, feature-flags, app) and per-space migration refs (ops.json, migration.json, head.json) to the flat storage shape via the documented emit + tsx incantation. The migration end-contract bookends are not auto-regenerated (TML-2698) and remain on the prior shape; they are explicitly excluded from the snapshot-read-shapes scan. Signed-off-by: Will Madden --- .../app/src/contract.d.ts | 83 +++++++++-------- .../app/src/contract.json | 54 +++++------ .../migration.json | 4 +- .../migration.ts | 2 +- .../packages/audit/migrations/refs/head.json | 2 +- .../packages/audit/src/contract.d.ts | 93 ++++++++++--------- .../packages/audit/src/contract.json | 62 ++++++------- .../migration.json | 4 +- .../migration.ts | 2 +- .../feature-flags/migrations/refs/head.json | 2 +- .../packages/feature-flags/src/contract.d.ts | 83 +++++++++-------- .../packages/feature-flags/src/contract.json | 54 +++++------ 12 files changed, 230 insertions(+), 215 deletions(-) diff --git a/examples/multi-extension-monorepo/app/src/contract.d.ts b/examples/multi-extension-monorepo/app/src/contract.d.ts index 785bde3020..f5497e2666 100644 --- a/examples/multi-extension-monorepo/app/src/contract.d.ts +++ b/examples/multi-extension-monorepo/app/src/contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -28,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:9d23dfc6ca8bec5ff07d61b3060a8201b2bf52000edc3cfc2d12782ce54783f8'>; + StorageHashBase<'sha256:42271e808e524488b52c09e3472dc15a192464827ab2ddaeca3cc30314b0e24e'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -59,9 +61,10 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; @@ -86,31 +89,31 @@ type ContractBase = ContractType< }; }; }; - }; - readonly storageHash: StorageHash; - }, - { - readonly User: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'app_user'; + }, + { + readonly User: { readonly fields: { - readonly id: { readonly column: 'id' }; - readonly email: { readonly column: 'email' }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'app_user'; + readonly fields: { + readonly id: { readonly column: 'id' }; + readonly email: { readonly column: 'email' }; + }; }; }; - }; - } + } + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; @@ -119,6 +122,7 @@ type ContractBase = ContractType< }; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -128,6 +132,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -154,5 +159,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/multi-extension-monorepo/app/src/contract.json b/examples/multi-extension-monorepo/app/src/contract.json index 946d4db2dd..2091d773e3 100644 --- a/examples/multi-extension-monorepo/app/src/contract.json +++ b/examples/multi-extension-monorepo/app/src/contract.json @@ -42,40 +42,39 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "app_user": { - "columns": { - "email": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "app_user": { + "columns": { + "email": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] - }, - "uniques": [] - } + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:9d23dfc6ca8bec5ff07d61b3060a8201b2bf52000edc3cfc2d12782ce54783f8" + "storageHash": "sha256:42271e808e524488b52c09e3472dc15a192464827ab2ddaeca3cc30314b0e24e" }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -85,6 +84,7 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, diff --git a/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.json b/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.json index 290f3fadcf..e9d4b5fd22 100644 --- a/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.json +++ b/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:8443ccb2248a9ec206413fc3555bbe615d78bbabebc71aa8bdab00c025f78fbb", + "to": "sha256:401aa8b872d0c71aef9bb232a0107b13fc01120b5ce7680808d4d1e4216d2546", "providedInvariants": ["audit:create-audit_event-v1"], "createdAt": "2026-05-09T04:24:04.431Z", - "migrationHash": "sha256:c007b0b3831969c79838a217516a49cffbaec549638b033c96fd34f82bb32aa4" + "migrationHash": "sha256:1a1ae7635ab641cd01983e0ed82403d86fa48f11e36613df19585765e1f623ce" } diff --git a/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.ts b/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.ts index 41c873c442..bd74776513 100755 --- a/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.ts +++ b/examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.ts @@ -21,7 +21,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:8443ccb2248a9ec206413fc3555bbe615d78bbabebc71aa8bdab00c025f78fbb', + to: 'sha256:401aa8b872d0c71aef9bb232a0107b13fc01120b5ce7680808d4d1e4216d2546', }; } diff --git a/examples/multi-extension-monorepo/packages/audit/migrations/refs/head.json b/examples/multi-extension-monorepo/packages/audit/migrations/refs/head.json index f6238812af..cd9e094a7c 100644 --- a/examples/multi-extension-monorepo/packages/audit/migrations/refs/head.json +++ b/examples/multi-extension-monorepo/packages/audit/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:8443ccb2248a9ec206413fc3555bbe615d78bbabebc71aa8bdab00c025f78fbb", + "hash": "sha256:401aa8b872d0c71aef9bb232a0107b13fc01120b5ce7680808d4d1e4216d2546", "invariants": ["audit:create-audit_event-v1"] } diff --git a/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts b/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts index df0bbc2c9e..6840ec73f5 100644 --- a/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -28,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:8443ccb2248a9ec206413fc3555bbe615d78bbabebc71aa8bdab00c025f78fbb'>; + StorageHashBase<'sha256:401aa8b872d0c71aef9bb232a0107b13fc01120b5ce7680808d4d1e4216d2546'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -61,9 +63,10 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; @@ -93,36 +96,36 @@ type ContractBase = ContractType< }; }; }; - }; - readonly storageHash: StorageHash; - }, - { - readonly AuditEvent: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly actor: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly action: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'audit_event'; + }, + { + readonly AuditEvent: { readonly fields: { - readonly id: { readonly column: 'id' }; - readonly actor: { readonly column: 'actor' }; - readonly action: { readonly column: 'action' }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly actor: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly action: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'audit_event'; + readonly fields: { + readonly id: { readonly column: 'id' }; + readonly actor: { readonly column: 'actor' }; + readonly action: { readonly column: 'action' }; + }; }; }; - }; - } + } + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; @@ -134,6 +137,7 @@ type ContractBase = ContractType< }; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -143,6 +147,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -154,5 +159,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/multi-extension-monorepo/packages/audit/src/contract.json b/examples/multi-extension-monorepo/packages/audit/src/contract.json index 078d2f31dd..005db3d779 100644 --- a/examples/multi-extension-monorepo/packages/audit/src/contract.json +++ b/examples/multi-extension-monorepo/packages/audit/src/contract.json @@ -52,45 +52,44 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "audit_event": { - "columns": { - "action": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "actor": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - }, - "id": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "audit_event": { + "columns": { + "action": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "id" - ] + "actor": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false }, - "uniques": [] - } + "id": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:8443ccb2248a9ec206413fc3555bbe615d78bbabebc71aa8bdab00c025f78fbb" + "storageHash": "sha256:401aa8b872d0c71aef9bb232a0107b13fc01120b5ce7680808d4d1e4216d2546" }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -100,6 +99,7 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, diff --git a/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.json b/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.json index c6ba82e1b8..a333080918 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.json +++ b/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "sha256:1a11ef0c4ef2dc0d6ae0663e65bf61113f980fee7f55e3dd828f65fe439a88cb", + "to": "sha256:870ac003337d47265961f3a18e8001be890d2d86d2fe094576eeea4cde1f3a9d", "providedInvariants": ["feature-flags:create-feature_flag-v1"], "createdAt": "2026-05-09T04:27:33.360Z", - "migrationHash": "sha256:59796a538c6acea2e89459b8a0ab4a2717bf87597e7506bc5faebdd2a36467b9" + "migrationHash": "sha256:39056be21ed7f6cf9be3f0702a72b292804a7be9c924a6f74b810f3bcf6cc7e4" } diff --git a/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.ts b/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.ts index 225d97f46c..240f37ec39 100755 --- a/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.ts +++ b/examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.ts @@ -19,7 +19,7 @@ export default class M extends Migration { override describe() { return { from: null, - to: 'sha256:1a11ef0c4ef2dc0d6ae0663e65bf61113f980fee7f55e3dd828f65fe439a88cb', + to: 'sha256:870ac003337d47265961f3a18e8001be890d2d86d2fe094576eeea4cde1f3a9d', }; } diff --git a/examples/multi-extension-monorepo/packages/feature-flags/migrations/refs/head.json b/examples/multi-extension-monorepo/packages/feature-flags/migrations/refs/head.json index 5957af9c35..559eb5b730 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/migrations/refs/head.json +++ b/examples/multi-extension-monorepo/packages/feature-flags/migrations/refs/head.json @@ -1,4 +1,4 @@ { - "hash": "sha256:1a11ef0c4ef2dc0d6ae0663e65bf61113f980fee7f55e3dd828f65fe439a88cb", + "hash": "sha256:870ac003337d47265961f3a18e8001be890d2d86d2fe094576eeea4cde1f3a9d", "invariants": ["feature-flags:create-feature_flag-v1"] } diff --git a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts index e072cf78e6..09c23cc696 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts @@ -1,19 +1,21 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma-next contract emit'. // To regenerate, run: prisma-next contract emit -import type { CodecTypes as PgTypes } from '@prisma-next/target-postgres/codec-types'; -import type { JsonValue } from '@prisma-next/target-postgres/codec-types'; -import type { Char } from '@prisma-next/target-postgres/codec-types'; -import type { Varchar } from '@prisma-next/target-postgres/codec-types'; -import type { Numeric } from '@prisma-next/target-postgres/codec-types'; -import type { Bit } from '@prisma-next/target-postgres/codec-types'; -import type { VarBit } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamp } from '@prisma-next/target-postgres/codec-types'; -import type { Timestamptz } from '@prisma-next/target-postgres/codec-types'; -import type { Time } from '@prisma-next/target-postgres/codec-types'; -import type { Timetz } from '@prisma-next/target-postgres/codec-types'; -import type { Interval } from '@prisma-next/target-postgres/codec-types'; import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types'; +import type { + Bit, + Char, + CodecTypes as PgTypes, + Interval, + JsonValue, + Numeric, + Time, + Timestamp, + Timestamptz, + Timetz, + VarBit, + Varchar, +} from '@prisma-next/target-postgres/codec-types'; import type { ContractWithTypeMaps, @@ -28,7 +30,7 @@ import type { } from '@prisma-next/contract/types'; export type StorageHash = - StorageHashBase<'sha256:1a11ef0c4ef2dc0d6ae0663e65bf61113f980fee7f55e3dd828f65fe439a88cb'>; + StorageHashBase<'sha256:870ac003337d47265961f3a18e8001be890d2d86d2fe094576eeea4cde1f3a9d'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'sha256:1a8dbe044289f30a1de958fe800cc5a8378b285d2e126a8c44b58864bac2c18e'>; @@ -59,9 +61,10 @@ export type TypeMaps = TypeMapsType< FieldInputTypes >; -type ContractBase = ContractType< - { - readonly namespaces: { +type ContractBase = Omit< + ContractType< + { + readonly storageHash: StorageHash; readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'sql-namespace'; @@ -86,31 +89,31 @@ type ContractBase = ContractType< }; }; }; - }; - readonly storageHash: StorageHash; - }, - { - readonly FeatureFlag: { - readonly fields: { - readonly key: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly enabled: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/bool@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'feature_flag'; + }, + { + readonly FeatureFlag: { readonly fields: { - readonly key: { readonly column: 'key' }; - readonly enabled: { readonly column: 'enabled' }; + readonly key: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly enabled: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/bool@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'feature_flag'; + readonly fields: { + readonly key: { readonly column: 'key' }; + readonly enabled: { readonly column: 'enabled' }; + }; }; }; - }; - } + } + >, + 'roots' > & { readonly target: 'postgres'; readonly targetFamily: 'sql'; @@ -122,6 +125,7 @@ type ContractBase = ContractType< }; readonly capabilities: { readonly postgres: { + readonly distinctOn: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -131,6 +135,7 @@ type ContractBase = ContractType< readonly sql: { readonly defaultInInsert: true; readonly enums: true; + readonly lateral: true; readonly returning: true; }; }; @@ -142,5 +147,5 @@ type ContractBase = ContractType< export type Contract = ContractWithTypeMaps; -export type Namespaces = Contract['storage']['namespaces']; +export type Namespaces = Omit; export type Models = Contract['models']; diff --git a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.json b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.json index ed0015290c..b3a1d95f6b 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.json +++ b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.json @@ -42,40 +42,39 @@ } }, "storage": { - "namespaces": { - "__unbound__": { - "id": "__unbound__", - "kind": "postgres-unbound-schema", - "tables": { - "feature_flag": { - "columns": { - "enabled": { - "codecId": "pg/bool@1", - "nativeType": "bool", - "nullable": false - }, - "key": { - "codecId": "pg/text@1", - "nativeType": "text", - "nullable": false - } + "__unbound__": { + "id": "__unbound__", + "kind": "postgres-unbound-schema", + "tables": { + "feature_flag": { + "columns": { + "enabled": { + "codecId": "pg/bool@1", + "nativeType": "bool", + "nullable": false }, - "foreignKeys": [], - "indexes": [], - "primaryKey": { - "columns": [ - "key" - ] - }, - "uniques": [] - } + "key": { + "codecId": "pg/text@1", + "nativeType": "text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "key" + ] + }, + "uniques": [] } } }, - "storageHash": "sha256:1a11ef0c4ef2dc0d6ae0663e65bf61113f980fee7f55e3dd828f65fe439a88cb" + "storageHash": "sha256:870ac003337d47265961f3a18e8001be890d2d86d2fe094576eeea4cde1f3a9d" }, "capabilities": { "postgres": { + "distinctOn": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -85,6 +84,7 @@ "sql": { "defaultInInsert": true, "enums": true, + "lateral": true, "returning": true } }, From 2d37740d6ad8cd67180c86674fb79ed05dbeba61 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 07:59:23 +0200 Subject: [PATCH 50/60] fix(sql-emitter-tests): migrate structure/types fixtures and assertions to flat storage Signed-off-by: Will Madden --- .../emitter-hook.parameterized-types.test.ts | 4 +- .../test/emitter-hook.structure.test.ts | 52 +++---------------- .../emitter/test/sql-storage-fixture.ts | 10 ++-- 3 files changed, 12 insertions(+), 54 deletions(-) diff --git a/packages/2-sql/3-tooling/emitter/test/emitter-hook.parameterized-types.test.ts b/packages/2-sql/3-tooling/emitter/test/emitter-hook.parameterized-types.test.ts index 48846b2fe2..08b5e3ce5d 100644 --- a/packages/2-sql/3-tooling/emitter/test/emitter-hook.parameterized-types.test.ts +++ b/packages/2-sql/3-tooling/emitter/test/emitter-hook.parameterized-types.test.ts @@ -85,7 +85,7 @@ describe('sql-target-family-hook parameterized type emission', () => { const types = generateContractDts(ir, sqlEmission, [], [], testHashes); - expect(types).toContain('readonly namespaces:'); + expect(types).toContain('readonly __unbound__:'); expect(types).not.toContain('readonly Vector1536'); }); @@ -108,7 +108,7 @@ describe('sql-target-family-hook parameterized type emission', () => { const types = generateContractDts(ir, sqlEmission, [], [], testHashes); - expect(types).toContain('readonly namespaces:'); + expect(types).toContain('readonly __unbound__:'); expect(types).not.toContain('readonly Vector1536'); }); diff --git a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts index 51d80ad220..0b92842eb4 100644 --- a/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-sql/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -1,5 +1,5 @@ import type { Contract } from '@prisma-next/contract/types'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { getStorageNamespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { sqlEmission } from '../src/index'; import { normalizeRootSqlStorage } from './sql-storage-fixture'; @@ -22,50 +22,10 @@ function createContract(overrides: Partial): Contract { } function installNamespacedTableDeletionRace(ir: Contract, tableName: string): void { - const originalStorage = ir.storage as { - namespaces: Record }>; + const namespace = getStorageNamespace(ir.storage, UNBOUND_NAMESPACE_ID) as { + tables: Record; }; - let tableDeleted = false; - const proxiedStorage = new Proxy(originalStorage, { - get(target, prop, receiver) { - if (prop === 'namespaces') { - return new Proxy(target.namespaces, { - get(nsTarget, nsKey) { - if (nsKey !== UNBOUND_NAMESPACE_ID) { - return Reflect.get(nsTarget, nsKey, nsTarget); - } - const inner = Reflect.get(nsTarget, nsKey) as { tables: Record }; - return new Proxy(inner, { - get(innerTarget, innerProp) { - if (innerProp !== 'tables') { - return Reflect.get(innerTarget, innerProp, innerTarget); - } - return new Proxy(innerTarget.tables, { - get(tableTarget, tableProp) { - if (tableProp === tableName && tableDeleted) { - return undefined; - } - return Reflect.get(tableTarget, tableProp, tableTarget); - }, - has(tableTarget, tableProp) { - return Reflect.has(tableTarget, tableProp); - }, - ownKeys(tableTarget) { - return Reflect.ownKeys(tableTarget); - }, - }); - }, - }); - }, - }); - } - return Reflect.get(target, prop, receiver); - }, - }); - - delete getStorageNamespace(originalStorage, UNBOUND_NAMESPACE_ID).tables[tableName]; - tableDeleted = true; - (ir as { storage: unknown }).storage = proxiedStorage; + delete namespace.tables[tableName]; } describe('sql-target-family-hook', () => { @@ -178,7 +138,7 @@ describe('sql-target-family-hook', () => { expect(() => { sqlEmission.validateStructure(ir); - }).toThrow('SQL contract must have storage namespace entries'); + }).toThrow('SQL contract must have storage with storageHash'); }); it('validates structure with missing storage.tables', () => { @@ -188,7 +148,7 @@ describe('sql-target-family-hook', () => { expect(() => { sqlEmission.validateStructure(ir); - }).toThrow('SQL contract must have storage namespace entries'); + }).toThrow('SQL contract must have storage with storageHash'); }); it('validates structure with model missing storage.table', () => { diff --git a/packages/2-sql/3-tooling/emitter/test/sql-storage-fixture.ts b/packages/2-sql/3-tooling/emitter/test/sql-storage-fixture.ts index 4d711546f0..f5dd07b9e1 100644 --- a/packages/2-sql/3-tooling/emitter/test/sql-storage-fixture.ts +++ b/packages/2-sql/3-tooling/emitter/test/sql-storage-fixture.ts @@ -4,11 +4,11 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; export function namespacedSqlStorage(parts: { readonly tables: Record; readonly types?: Record; + readonly storageHash?: string; }) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: parts.tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: parts.tables }, + storageHash: parts.storageHash ?? 'sha256:test', ...(parts.types !== undefined ? { types: parts.types } : {}), }; } @@ -20,13 +20,11 @@ export function normalizeRootSqlStorage( return storage; } const s = storage as Record; - if ('namespaces' in s) { - return storage; - } if ('tables' in s) { return namespacedSqlStorage({ tables: s.tables as Record, ...(s.types !== undefined ? { types: s.types as Record } : {}), + ...(typeof s.storageHash === 'string' ? { storageHash: s.storageHash } : {}), }) as Contract['storage']; } return storage; From 8e01bb4307e3c74d25fc4dd7063a2bf59a94de4f Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:00:42 +0200 Subject: [PATCH 51/60] fix(mongo-emitter-tests): migrate storage type assertions to flat shape Signed-off-by: Will Madden --- .../3-tooling/emitter/test/emitter-hook.e2e.test.ts | 2 +- .../3-tooling/emitter/test/emitter-hook.generation.test.ts | 2 +- .../3-tooling/emitter/test/emitter-hook.structure.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.e2e.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.e2e.test.ts index c20cb03b6b..7f89980c64 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.e2e.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.e2e.test.ts @@ -74,7 +74,7 @@ describe('Mongo emitter hook end-to-end (blog fixture)', () => { it('generates storage section with namespaces', () => { const types = generateContractDts(blogContract, mongoEmission, [], testHashes); - expect(types).toContain('readonly namespaces:'); + expect(types).toContain('readonly __unbound__:'); expect(types).toContain('readonly collections:'); expect(types).toContain('readonly users: MongoCollection'); expect(types).toContain('readonly posts: MongoCollection'); diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts index 13c5435140..6c4a2f90ef 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts @@ -325,7 +325,7 @@ describe('mongoEmission.generateContractTypes', () => { storage: namespacedMongoStorageFromCollections({ users: {}, posts: {} }), }); const types = generateContractDts(contract, mongoEmission, [], testHashes); - expect(types).toContain('readonly namespaces:'); + expect(types).toContain('readonly __unbound__:'); expect(types).toContain('readonly collections:'); expect(types).toContain('readonly users: MongoCollection'); expect(types).toContain('readonly posts: MongoCollection'); diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts index 1384a61de6..e2e0d1e257 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -42,7 +42,7 @@ describe('mongoEmission.validateStructure', () => { storage: { storageHash: 'sha256:test' }, } as Contract; expect(() => mongoEmission.validateStructure(contract)).toThrow( - 'must have storage namespace entries', + 'must have at least one storage namespace entry', ); }); From 061b5377630aa71bb53063b56c6a8ababe6e3311 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:02:40 +0200 Subject: [PATCH 52/60] fix(framework-emitter-tests): migrate storage read helpers and hash path predicates to flat shape Signed-off-by: Will Madden --- .../contract/test/hashing.test.ts | 46 ++++++++----------- .../emitter/test/emitter.roundtrip.test.ts | 7 +-- .../3-tooling/emitter/test/emitter.test.ts | 12 ++--- .../3-tooling/emitter/test/hashing.test.ts | 6 +-- 4 files changed, 25 insertions(+), 46 deletions(-) diff --git a/packages/1-framework/0-foundation/contract/test/hashing.test.ts b/packages/1-framework/0-foundation/contract/test/hashing.test.ts index b11107c5c2..a78cec88fb 100644 --- a/packages/1-framework/0-foundation/contract/test/hashing.test.ts +++ b/packages/1-framework/0-foundation/contract/test/hashing.test.ts @@ -6,10 +6,8 @@ import { computeExecutionHash, computeProfileHash, computeStorageHash } from '.. const sqlPreserveEmpty: PreserveEmptyPredicate = (path) => { const len = path.length; if (len < 2 || path[0] !== 'storage') return false; - if (path[1] === 'namespaces') { - if (len === 4 && path[3] === 'tables') return true; - if (len === 5 && path[3] === 'tables') return true; - } + if (len === 3 && path[2] === 'tables') return true; + if (len === 4 && path[2] === 'tables') return true; return false; }; @@ -18,9 +16,7 @@ const sqlSortStorage: StorageSort = (storage) => storage; const SQL_HOOKS = { shouldPreserveEmpty: sqlPreserveEmpty, sortStorage: sqlSortStorage }; const emptyNamespacedStorage = () => ({ - namespaces: { - __unbound__: { id: '__unbound__' as const, tables: {} }, - }, + __unbound__: { id: '__unbound__' as const, tables: {} }, }); describe('computeStorageHash', () => { @@ -50,13 +46,11 @@ describe('computeStorageHash', () => { const hash2 = computeStorageHash({ ...base, storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false } }, - }, + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false } }, }, }, }, @@ -88,13 +82,11 @@ describe('computeStorageHash', () => { const hash1 = computeStorageHash({ ...base, storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - a: { columns: { x: { codecId: 'pg/text@1', nativeType: 'text' } } }, - b: { columns: { y: { codecId: 'pg/text@1', nativeType: 'text' } } }, - }, + __unbound__: { + id: '__unbound__', + tables: { + a: { columns: { x: { codecId: 'pg/text@1', nativeType: 'text' } } }, + b: { columns: { y: { codecId: 'pg/text@1', nativeType: 'text' } } }, }, }, }, @@ -103,13 +95,11 @@ describe('computeStorageHash', () => { const hash2 = computeStorageHash({ ...base, storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - b: { columns: { y: { codecId: 'pg/text@1', nativeType: 'text' } } }, - a: { columns: { x: { codecId: 'pg/text@1', nativeType: 'text' } } }, - }, + __unbound__: { + id: '__unbound__', + tables: { + b: { columns: { y: { codecId: 'pg/text@1', nativeType: 'text' } } }, + a: { columns: { x: { codecId: 'pg/text@1', nativeType: 'text' } } }, }, }, }, diff --git a/packages/1-framework/3-tooling/emitter/test/emitter.roundtrip.test.ts b/packages/1-framework/3-tooling/emitter/test/emitter.roundtrip.test.ts index 9428cbc93d..33a435d7fe 100644 --- a/packages/1-framework/3-tooling/emitter/test/emitter.roundtrip.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/emitter.roundtrip.test.ts @@ -11,15 +11,12 @@ const mockSqlHook = createMockSpi(); function unboundNamespaceTables(tables: Record) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }; } function tablesFromCanonicalStorage(storage: Record): Record { - const namespaces = storage['namespaces'] as Record; - const unbound = namespaces[UNBOUND_NAMESPACE_ID] as Record; + const unbound = storage[UNBOUND_NAMESPACE_ID] as Record; return unbound['tables'] as Record; } diff --git a/packages/1-framework/3-tooling/emitter/test/emitter.test.ts b/packages/1-framework/3-tooling/emitter/test/emitter.test.ts index 7b1eeb3957..164cb7e217 100644 --- a/packages/1-framework/3-tooling/emitter/test/emitter.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/emitter.test.ts @@ -10,16 +10,12 @@ import { createTestContract, emit } from './utils'; const mockSqlHook = createMockSpi(); const emptySqlStorage = { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables: {} }, }; function unboundNamespaceTables(tables: Record) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }; } @@ -108,9 +104,7 @@ describe('emitter', () => { const contractJson = JSON.parse(result.contractJson) as Record; const storage = contractJson['storage'] as Record; - const namespaces = storage['namespaces'] as Record; - expect(namespaces).toBeDefined(); - const unbound = namespaces[UNBOUND_NAMESPACE_ID] as Record; + const unbound = storage[UNBOUND_NAMESPACE_ID] as Record; expect(unbound).toBeDefined(); const tables = unbound['tables'] as Record; expect(tables).toBeDefined(); diff --git a/packages/1-framework/3-tooling/emitter/test/hashing.test.ts b/packages/1-framework/3-tooling/emitter/test/hashing.test.ts index 0ec8fe8279..a1c125cb2a 100644 --- a/packages/1-framework/3-tooling/emitter/test/hashing.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/hashing.test.ts @@ -3,15 +3,13 @@ import { computeProfileHash, computeStorageHash } from '@prisma-next/contract/ha import { describe, expect, it } from 'vitest'; const emptyNamespacedStorage = () => ({ - namespaces: { - __unbound__: { id: '__unbound__' as const, tables: {} }, - }, + __unbound__: { id: '__unbound__' as const, tables: {} }, }); const sqlPreserveEmpty: PreserveEmptyPredicate = (path) => { const len = path.length; if (len < 2 || path[0] !== 'storage') return false; - if (path[1] === 'namespaces' && len === 4 && path[3] === 'tables') return true; + if (len === 3 && path[2] === 'tables') return true; return false; }; From 231601a17926830c7d59254236e2a771cf1718fe Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:20:37 +0200 Subject: [PATCH 53/60] fix(adapter-postgres): read flat storage namespace ids in introspection extractContractNamespaceIds still reached through the dropped storage.namespaces wrapper, so flat-storage contracts yielded zero declared namespaces and introspect fell back to the single public schema. Cross-namespace contracts then verified as missing every non-public table. Read namespace ids via storageNamespaceEntries so the multi-namespace introspection walk covers every declared schema. Signed-off-by: Will Madden --- .../postgres/src/core/control-adapter.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts b/packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts index 43e2f30629..3cd1aae5db 100644 --- a/packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts +++ b/packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts @@ -7,7 +7,10 @@ import { APP_SPACE_ID, type ControlDriverInstance, } from '@prisma-next/framework-components/control'; -import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; +import { + storageNamespaceEntries, + UNBOUND_NAMESPACE_ID, +} from '@prisma-next/framework-components/ir'; import type { PostgresEnumStorageEntry, SqlStorage } from '@prisma-next/sql-contract/types'; import type { AnyQueryAst, @@ -806,17 +809,17 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> { /** * Extracts the namespace coordinate ids declared on a contract's storage, - * or returns an empty array when no contract (or no storage / namespaces) - * is present. Used by `PostgresControlAdapter.introspect` to decide - * between the multi-namespace walk and the single-schema fallback. + * or returns an empty array when no contract (or no storage) is present. + * Reads ADR 221 flat storage (namespace ids are direct keys alongside the + * reserved `storageHash` / `types` siblings). Used by + * `PostgresControlAdapter.introspect` to decide between the multi-namespace + * walk and the single-schema fallback. */ function extractContractNamespaceIds(contract: unknown): readonly string[] { if (contract === null || typeof contract !== 'object') return []; const storage = (contract as { storage?: unknown }).storage; if (storage === null || typeof storage !== 'object') return []; - const namespaces = (storage as { namespaces?: unknown }).namespaces; - if (namespaces === null || typeof namespaces !== 'object') return []; - return Object.keys(namespaces as Record); + return [...storageNamespaceEntries(storage)].map(([id]) => id); } function normalizeFormattedType(formattedType: string, dataType: string, udtName: string): string { From 7b9725f847b5d088fcefa3e3f49a32dfbdbe758f Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:20:46 +0200 Subject: [PATCH 54/60] refactor(cli-tests): migrate storage reads and fixtures to flat shape Signed-off-by: Will Madden --- .../cli/test/commands/cross-consumer-integrity.test.ts | 6 +++--- .../cli/test/commands/migrate-to-contract.test.ts | 2 +- .../cli/test/commands/migration-invariants.test.ts | 4 ++-- .../cli/test/commands/migration-plan-command.test.ts | 2 +- .../3-tooling/cli/test/commands/migration-tamper.test.ts | 6 +++--- .../cli/test/control-api/apply-aggregate.test.ts | 2 +- .../3-tooling/cli/test/control-api/client.test.ts | 6 +++--- .../3-tooling/cli/test/control-api/db-init.test.ts | 2 +- .../3-tooling/cli/test/control-api/db-update.test.ts | 2 +- .../3-tooling/cli/test/control-api/progress.test.ts | 6 +++--- .../1-framework/3-tooling/cli/test/sql-storage-fixture.ts | 8 +++----- .../utils/contract-space-aggregate-loader.ac15.test.ts | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/1-framework/3-tooling/cli/test/commands/cross-consumer-integrity.test.ts b/packages/1-framework/3-tooling/cli/test/commands/cross-consumer-integrity.test.ts index 5396bb036b..bc1fc1e513 100644 --- a/packages/1-framework/3-tooling/cli/test/commands/cross-consumer-integrity.test.ts +++ b/packages/1-framework/3-tooling/cli/test/commands/cross-consumer-integrity.test.ts @@ -159,7 +159,7 @@ async function writeContract(cwd: string, storageHash: string): Promise { await writeFile( join(contractDir, 'contract.json'), JSON.stringify({ - storage: { storageHash, namespaces: {} }, + storage: { storageHash }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, @@ -190,7 +190,7 @@ async function writeCleanOrphanSpace(cwd: string, spaceId: string, hash: string) await writeFile( join(spaceDir, 'contract.json'), JSON.stringify({ - storage: { storageHash: hash, namespaces: {} }, + storage: { storageHash: hash }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, @@ -307,7 +307,7 @@ async function setupExtSpaceCorruptionFixture(): Promise<{ cwd: string; extId: s await writeFile( join(extDir, 'contract.json'), JSON.stringify({ - storage: { storageHash: HASH_C, namespaces: {} }, + storage: { storageHash: HASH_C }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, diff --git a/packages/1-framework/3-tooling/cli/test/commands/migrate-to-contract.test.ts b/packages/1-framework/3-tooling/cli/test/commands/migrate-to-contract.test.ts index 93bcda5d7a..54cc9b0175 100644 --- a/packages/1-framework/3-tooling/cli/test/commands/migrate-to-contract.test.ts +++ b/packages/1-framework/3-tooling/cli/test/commands/migrate-to-contract.test.ts @@ -50,7 +50,7 @@ const OPS: readonly MigrationPlanOperation[] = [ function contractEnvelope(storageHash: string): Record { return { - storage: { storageHash, namespaces: {} }, + storage: { storageHash }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, diff --git a/packages/1-framework/3-tooling/cli/test/commands/migration-invariants.test.ts b/packages/1-framework/3-tooling/cli/test/commands/migration-invariants.test.ts index cf4bc6c915..7bc0366b2c 100644 --- a/packages/1-framework/3-tooling/cli/test/commands/migration-invariants.test.ts +++ b/packages/1-framework/3-tooling/cli/test/commands/migration-invariants.test.ts @@ -144,7 +144,7 @@ async function setupDivergentFixture(): Promise { await writeFile( join(contractDir, 'contract.json'), JSON.stringify({ - storage: { storageHash: TO_HASH, namespaces: {} }, + storage: { storageHash: TO_HASH }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, @@ -439,7 +439,7 @@ describe('migration tamper detection (tolerant model, per-command class)', () => await writeFile( join(orphanDir, 'contract.json'), JSON.stringify({ - storage: { storageHash: ORPHAN_HASH, namespaces: {} }, + storage: { storageHash: ORPHAN_HASH }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, @@ -451,7 +451,7 @@ describe('migration tamper detection (tolerant model, per-command class)', () => await writeFile( join(contractDir, 'contract.json'), JSON.stringify({ - storage: { storageHash: TO_HASH, namespaces: {} }, + storage: { storageHash: TO_HASH }, schemaVersion: SCHEMA_VERSION, target: TARGET, targetFamily: TARGET_FAMILY, diff --git a/packages/1-framework/3-tooling/cli/test/control-api/apply-aggregate.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/apply-aggregate.test.ts index 6f26b09d15..e5c0683edb 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/apply-aggregate.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/apply-aggregate.test.ts @@ -23,7 +23,7 @@ const APP_HASH = `sha256:${'a'.repeat(64)}`; function makeAppMember(): ContractSpaceMember { const contract = { - storage: { storageHash: APP_HASH, tables: {}, namespaces: {} }, + storage: { storageHash: APP_HASH, tables: {} }, } as unknown as ReturnType; return { spaceId: 'app', diff --git a/packages/1-framework/3-tooling/cli/test/control-api/client.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/client.test.ts index 47d0c21046..41b6da2a63 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/client.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/client.test.ts @@ -761,7 +761,7 @@ describe('ControlClient progress emission', () => { const result = await client.dbUpdate({ contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, }, mode: 'apply', connection: 'postgres://test', @@ -823,7 +823,7 @@ describe('ControlClient progress emission', () => { const result = await client.dbUpdate({ contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, }, mode: 'plan', connection: 'postgres://test', @@ -849,7 +849,7 @@ describe('ControlClient progress emission', () => { const result = await client.dbUpdate({ contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, }, mode: 'plan', connection: 'postgres://test', diff --git a/packages/1-framework/3-tooling/cli/test/control-api/db-init.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/db-init.test.ts index 2683892971..6223634e6d 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/db-init.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/db-init.test.ts @@ -27,7 +27,7 @@ function createMockFamilyInstance() { const dummyContract = { schemaVersion: '1', target: 'postgres', - storage: { storageHash: 'sha256:dummy', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:dummy', tables: {} }, } as unknown as Contract; describe('executeDbInit', () => { diff --git a/packages/1-framework/3-tooling/cli/test/control-api/db-update.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/db-update.test.ts index b4e5610649..a4f16ca5a8 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/db-update.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/db-update.test.ts @@ -111,7 +111,7 @@ function createMockMigrations(overrides?: { const dummyContract = { schemaVersion: '1', target: 'postgres', - storage: { storageHash: 'sha256:dummy', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:dummy', tables: {} }, } as unknown as Contract; describe('executeDbUpdate', () => { diff --git a/packages/1-framework/3-tooling/cli/test/control-api/progress.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/progress.test.ts index 8d827cb56c..fe7cdd3d47 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/progress.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/progress.test.ts @@ -59,7 +59,7 @@ describe('executeDbInit progress emission', () => { familyInstance: mockFamilyInstance, contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, } as unknown as Contract, mode: 'plan', migrations: mockMigrations, @@ -144,7 +144,7 @@ describe('executeDbInit progress emission', () => { familyInstance: mockFamilyInstance, contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, } as unknown as Contract, mode: 'apply', migrations: mockMigrations, @@ -211,7 +211,7 @@ describe('executeDbInit progress emission', () => { familyInstance: mockFamilyInstance, contract: { target: 'postgres', - storage: { storageHash: 'sha256:fixture', tables: {}, namespaces: {} }, + storage: { storageHash: 'sha256:fixture', tables: {} }, } as unknown as Contract, mode: 'plan', migrations: mockMigrations, diff --git a/packages/1-framework/3-tooling/cli/test/sql-storage-fixture.ts b/packages/1-framework/3-tooling/cli/test/sql-storage-fixture.ts index 4dc9428143..e3df1b341a 100644 --- a/packages/1-framework/3-tooling/cli/test/sql-storage-fixture.ts +++ b/packages/1-framework/3-tooling/cli/test/sql-storage-fixture.ts @@ -2,11 +2,9 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir'; export function sqlTestStorageWithTables(tables: Record) { return { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables, }, }; } diff --git a/packages/1-framework/3-tooling/cli/test/utils/contract-space-aggregate-loader.ac15.test.ts b/packages/1-framework/3-tooling/cli/test/utils/contract-space-aggregate-loader.ac15.test.ts index dc0badb6f8..d9f58bcc06 100644 --- a/packages/1-framework/3-tooling/cli/test/utils/contract-space-aggregate-loader.ac15.test.ts +++ b/packages/1-framework/3-tooling/cli/test/utils/contract-space-aggregate-loader.ac15.test.ts @@ -25,7 +25,7 @@ import { buildContractSpaceAggregate } from '../../src/utils/contract-space-aggr const STUB_HASH = 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; const STUB_APP_CONTRACT = { - storage: { storageHash: STUB_HASH, namespaces: {} }, + storage: { storageHash: STUB_HASH }, target: 'postgres', } as unknown as Contract; From bb1fcaaca4215bf8ae068e3349c814931d746f80 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:20:54 +0200 Subject: [PATCH 55/60] refactor(contract-tests): migrate sql/mongo contract+psl test reads to flat storage Re-express the contract-ts edge case for the flat shape: storage with only storageHash now deserializes to an empty unbound namespace and is accepted rather than rejected. Repair the dropped trailing comma the codemod left in sql-storage fixtures and switch interpreter type assertions to flat namespace-key access. Signed-off-by: Will Madden --- .../contract-psl/test/interpreter.test.ts | 28 +- .../1-core/contract/test/sql-storage.test.ts | 12 +- .../test/interpreter.defaults.test.ts | 116 ++++----- .../test/interpreter.extensions.test.ts | 28 +- .../contract-psl/test/interpreter.test.ts | 204 +++++++-------- .../test/interpreter.types.test.ts | 240 +++++++++--------- .../test/interpreter.value-objects.test.ts | 54 ++-- .../contract-psl/test/provider.test.ts | 34 +-- .../test/contract.edge-cases.test.ts | 6 +- .../test/contract.integration.test.ts | 4 +- .../contract-ts/test/contract.logic.test.ts | 11 +- .../test/storage-with-namespaced-tables.ts | 8 +- 12 files changed, 337 insertions(+), 408 deletions(-) diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts index 5394b78c1f..289c8bbc27 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts @@ -231,11 +231,9 @@ describe('interpretPslDocumentToMongoContract', () => { storage: { collection: 'userProfile' }, }); expect(ir.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: { userProfile: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: { userProfile: {} }, }, }); }); @@ -252,11 +250,9 @@ describe('interpretPslDocumentToMongoContract', () => { storage: { collection: 'users' }, }); expect(ir.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: { users: {} }, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: { users: {} }, }, }); }); @@ -595,13 +591,11 @@ describe('interpretPslDocumentToMongoContract', () => { `); expect(ir.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - collections: { - user: {}, - post: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + collections: { + user: {}, + post: {}, }, }, }); diff --git a/packages/2-sql/1-core/contract/test/sql-storage.test.ts b/packages/2-sql/1-core/contract/test/sql-storage.test.ts index da57b9ecbf..3c63094aff 100644 --- a/packages/2-sql/1-core/contract/test/sql-storage.test.ts +++ b/packages/2-sql/1-core/contract/test/sql-storage.test.ts @@ -31,7 +31,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { it('accepts a tagged codec-instance entry unchanged', () => { const storage = new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { Score: { kind: 'codec-instance', @@ -53,7 +53,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { const { toStorageTypeInstance } = await import('../src/ir/storage-type-instance'); const storage = new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { Score: toStorageTypeInstance({ codecId: 'pg/int4@1', @@ -75,7 +75,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { () => new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { Embedding1536: untagged }, }), ).toThrow(/Embedding1536/); @@ -91,7 +91,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { () => new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { Score: untagged }, }), ).toThrow(/missing.*kind/i); @@ -106,7 +106,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { () => new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { Mystery: unknownKind }, }), ).toThrow(/Mystery.*mystery-kind/); @@ -124,7 +124,7 @@ describe('SqlStorage — polymorphic storage.types normalisation', () => { () => new SqlStorage({ storageHash: 'sha256:abc', - namespaces: { [UNBOUND_NAMESPACE_ID]: unboundWithUsers }, + [UNBOUND_NAMESPACE_ID]: unboundWithUsers, types: { user_type: rawPostgresEnum }, }), ).toThrow(/postgres-enum/); diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.defaults.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.defaults.test.ts index 4ef837e379..6f4f66d10e 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.defaults.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.defaults.test.ts @@ -80,32 +80,30 @@ describe('interpretPslDocumentToSqlContract default lowering', () => { }, }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - defaults: { - columns: { - idNanoidDefault: { - codecId: 'sql/char@1', - nativeType: 'character', - typeParams: { length: 21 }, - }, - idNanoidSized: { - codecId: 'sql/char@1', - nativeType: 'character', - typeParams: { length: 16 }, - }, - dbExpr: { - default: { - kind: 'function', - expression: 'gen_random_uuid()', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + defaults: { + columns: { + idNanoidDefault: { + codecId: 'sql/char@1', + nativeType: 'character', + typeParams: { length: 21 }, + }, + idNanoidSized: { + codecId: 'sql/char@1', + nativeType: 'character', + typeParams: { length: 16 }, + }, + dbExpr: { + default: { + kind: 'function', + expression: 'gen_random_uuid()', }, - createdAt: { - default: { - kind: 'function', - expression: 'now()', - }, + }, + createdAt: { + default: { + kind: 'function', + expression: 'now()', }, }, }, @@ -210,22 +208,20 @@ describe('interpretPslDocumentToSqlContract default lowering', () => { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - defaults: { - columns: { - touchedAt: { - default: { - kind: 'function', - expression: 'clock_timestamp()', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + defaults: { + columns: { + touchedAt: { + default: { + kind: 'function', + expression: 'clock_timestamp()', }, - payload: { - default: { - kind: 'function', - expression: "'{}'::jsonb", - }, + }, + payload: { + default: { + kind: 'function', + expression: "'{}'::jsonb", }, }, }, @@ -455,19 +451,17 @@ describe('interpretPslDocumentToSqlContract default lowering', () => { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - synthetic: { - columns: { - example: { - codecId: 'pg/text@1', - nativeType: 'text', - nullable: false, - default: { - kind: 'function', - expression: "'synthetic-default'", - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + synthetic: { + columns: { + example: { + codecId: 'pg/text@1', + nativeType: 'text', + nullable: false, + default: { + kind: 'function', + expression: "'synthetic-default'", }, }, }, @@ -523,15 +517,13 @@ describe('interpretPslDocumentToSqlContract default lowering', () => { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - synthetic: { - columns: { - example: { - codecId: 'pg/text@1', - nativeType: 'text', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + synthetic: { + columns: { + example: { + codecId: 'pg/text@1', + nativeType: 'text', }, }, }, diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.extensions.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.extensions.test.ts index dafd4daa44..1710cacd79 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.extensions.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.extensions.test.ts @@ -359,21 +359,19 @@ model Document { if (!result.ok) return; expect(documentScopedTypes(result.value) ?? {}).toEqual({}); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - document: { - columns: { - shortName: { - codecId: 'custom/varchar@1', - nativeType: 'character varying', - nullable: false, - }, - embedding: { - codecId: 'custom/vector@1', - nativeType: 'vector', - nullable: true, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + document: { + columns: { + shortName: { + codecId: 'custom/varchar@1', + nativeType: 'character varying', + nullable: false, + }, + embedding: { + codecId: 'custom/vector@1', + nativeType: 'vector', + nullable: true, }, }, }, diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts index e1da5a0b6e..dc99a4687b 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.test.ts @@ -62,15 +62,13 @@ describe('interpretPslDocumentToSqlContract', () => { expect(result.ok).toBe(true); if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - email: { - codecId: 'custom/text@1', - nativeType: 'custom_text', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + email: { + codecId: 'custom/text@1', + nativeType: 'custom_text', }, }, }, @@ -165,15 +163,13 @@ describe('interpretPslDocumentToSqlContract', () => { expect(result.ok).toBe(true); if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - slug: { - codecId: 'pg/text@1', - nativeType: 'text', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + slug: { + codecId: 'pg/text@1', + nativeType: 'text', }, }, }, @@ -243,16 +239,14 @@ model Comment { expect(result.value.target).toBe('postgres'); expect(result.value.roots).toEqual({ user: crossRef('User') }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4' }, - email: { codecId: 'pg/text@1', nativeType: 'text' }, - }, - primaryKey: { columns: ['id'] }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4' }, + email: { codecId: 'pg/text@1', nativeType: 'text' }, }, + primaryKey: { columns: ['id'] }, }, }, }, @@ -289,16 +283,14 @@ model Comment { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - idlessThing: { - columns: { - email: { codecId: 'pg/text@1', nativeType: 'text' }, - token: { codecId: 'pg/text@1', nativeType: 'text' }, - }, - uniques: [{ columns: ['email'] }], + [UNBOUND_NAMESPACE_ID]: { + tables: { + idlessThing: { + columns: { + email: { codecId: 'pg/text@1', nativeType: 'text' }, + token: { codecId: 'pg/text@1', nativeType: 'text' }, }, + uniques: [{ columns: ['email'] }], }, }, }, @@ -341,12 +333,10 @@ model Comment { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - compositeThing: { - primaryKey: { columns: ['email', 'token'] }, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + compositeThing: { + primaryKey: { columns: ['email', 'token'] }, }, }, }, @@ -375,14 +365,12 @@ model Comment { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - composite_thing: { - primaryKey: { - columns: ['email_address', 'api_token'], - name: 'composite_thing_pkey', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + composite_thing: { + primaryKey: { + columns: ['email_address', 'api_token'], + name: 'composite_thing_pkey', }, }, }, @@ -479,38 +467,36 @@ model Member { team_member: crossRef('Member'), }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - org_team: { - columns: { - team_id: { codecId: 'pg/int4@1', nativeType: 'int4' }, - }, - primaryKey: { columns: ['team_id'] }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + org_team: { + columns: { + team_id: { codecId: 'pg/int4@1', nativeType: 'int4' }, }, - team_member: { - columns: { - member_id: { codecId: 'pg/int4@1', nativeType: 'int4' }, - team_ref: { codecId: 'pg/int4@1', nativeType: 'int4' }, - }, - primaryKey: { columns: ['member_id'] }, - indexes: [{ columns: ['team_ref'] }], - uniques: [{ columns: ['team_ref', 'member_id'] }], - foreignKeys: [ - { - source: { - namespaceId: UNBOUND_NAMESPACE_ID, - tableName: 'team_member', - columns: ['team_ref'], - }, - target: { - namespaceId: UNBOUND_NAMESPACE_ID, - tableName: 'org_team', - columns: ['team_id'], - }, - }, - ], + primaryKey: { columns: ['team_id'] }, + }, + team_member: { + columns: { + member_id: { codecId: 'pg/int4@1', nativeType: 'int4' }, + team_ref: { codecId: 'pg/int4@1', nativeType: 'int4' }, }, + primaryKey: { columns: ['member_id'] }, + indexes: [{ columns: ['team_ref'] }], + uniques: [{ columns: ['team_ref', 'member_id'] }], + foreignKeys: [ + { + source: { + namespaceId: UNBOUND_NAMESPACE_ID, + tableName: 'team_member', + columns: ['team_ref'], + }, + target: { + namespaceId: UNBOUND_NAMESPACE_ID, + tableName: 'org_team', + columns: ['team_id'], + }, + }, + ], }, }, }, @@ -585,14 +571,12 @@ model OrderItem { expect(result.ok).toBe(true); if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - order_item: { - primaryKey: { - columns: ['order_id', 'product_id'], - name: 'order_item_pkey', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + order_item: { + primaryKey: { + columns: ['order_id', 'product_id'], + name: 'order_item_pkey', }, }, }, @@ -623,12 +607,10 @@ model OrderItem { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - membership: { - primaryKey: { columns: ['org_id', 'user_id'], name: 'membership_pkey' }, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + membership: { + primaryKey: { columns: ['org_id', 'user_id'], name: 'membership_pkey' }, }, }, }, @@ -657,19 +639,17 @@ model OrderItem { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - doc: { - indexes: [ - { - columns: ['body'], - name: 'doc_body_bm25_idx', - type: 'bm25', - options: { key_field: 'id' }, - }, - ], - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + doc: { + indexes: [ + { + columns: ['body'], + name: 'doc_body_bm25_idx', + type: 'bm25', + options: { key_field: 'id' }, + }, + ], }, }, }, @@ -695,12 +675,10 @@ model OrderItem { expect(result.ok).toBe(true); if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - doc: { - indexes: [{ type: 'bm25', options: { key_field: 'id', language: 'en' } }], - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + doc: { + indexes: [{ type: 'bm25', options: { key_field: 'id', language: 'en' } }], }, }, }, diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts index 45767abc08..f41abf5117 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.types.test.ts @@ -76,51 +76,49 @@ model Event { }, }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - event: { - columns: { - id: { codecId: 'pg/text@1', nativeType: 'uuid', nullable: false, typeRef: 'Id' }, - slug: { - codecId: 'sql/varchar@1', - nativeType: 'character varying', - nullable: false, - typeRef: 'Slug', - }, - rating: { - codecId: 'pg/int2@1', - nativeType: 'int2', - nullable: false, - typeRef: 'Rating', - }, - happenedAt: { - codecId: 'pg/time@1', - nativeType: 'time', - nullable: false, - typeRef: 'HappenedAt', - }, - publishDay: { - codecId: 'pg/timestamptz@1', - nativeType: 'date', - nullable: false, - typeRef: 'PublishDay', - }, - payload: { - codecId: 'pg/json@1', - nativeType: 'json', - nullable: false, - typeRef: 'Payload', - }, - amount: { - codecId: 'pg/numeric@1', - nativeType: 'numeric', - nullable: false, - typeRef: 'Amount', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + event: { + columns: { + id: { codecId: 'pg/text@1', nativeType: 'uuid', nullable: false, typeRef: 'Id' }, + slug: { + codecId: 'sql/varchar@1', + nativeType: 'character varying', + nullable: false, + typeRef: 'Slug', + }, + rating: { + codecId: 'pg/int2@1', + nativeType: 'int2', + nullable: false, + typeRef: 'Rating', + }, + happenedAt: { + codecId: 'pg/time@1', + nativeType: 'time', + nullable: false, + typeRef: 'HappenedAt', + }, + publishDay: { + codecId: 'pg/timestamptz@1', + nativeType: 'date', + nullable: false, + typeRef: 'PublishDay', + }, + payload: { + codecId: 'pg/json@1', + nativeType: 'json', + nullable: false, + typeRef: 'Payload', + }, + amount: { + codecId: 'pg/numeric@1', + nativeType: 'numeric', + nullable: false, + typeRef: 'Amount', }, - primaryKey: { columns: ['id'] }, }, + primaryKey: { columns: ['id'] }, }, }, }, @@ -159,40 +157,38 @@ model User { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - public: { - enum: { - UserRole: { - kind: 'postgres-enum', - name: 'UserRole', - nativeType: 'user_role', - values: ['USER', 'ADMIN'], - }, - Role: { - kind: 'postgres-enum', - name: 'Role', - nativeType: 'Role', - values: ['OWNER'], - }, + public: { + enum: { + UserRole: { + kind: 'postgres-enum', + name: 'UserRole', + nativeType: 'user_role', + values: ['USER', 'ADMIN'], + }, + Role: { + kind: 'postgres-enum', + name: 'Role', + nativeType: 'Role', + values: ['OWNER'], }, }, - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - role: { - codecId: 'test/enum@1', - nativeType: 'user_role', - nullable: false, - typeRef: 'UserRole', - }, - legacyRole: { - codecId: 'test/enum@1', - nativeType: 'Role', - nullable: false, - typeRef: 'Role', - }, + }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + role: { + codecId: 'test/enum@1', + nativeType: 'user_role', + nullable: false, + typeRef: 'UserRole', + }, + legacyRole: { + codecId: 'test/enum@1', + nativeType: 'Role', + nullable: false, + typeRef: 'Role', }, }, }, @@ -259,42 +255,40 @@ model Event { }, }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - event: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - code: { - codecId: 'sql/char@1', - nativeType: 'character', - nullable: false, - typeRef: 'Code', - }, - score: { - codecId: 'pg/float4@1', - nativeType: 'float4', - nullable: false, - typeRef: 'Score', - }, - createdAt: { - codecId: 'pg/timestamp@1', - nativeType: 'timestamp', - nullable: false, - typeRef: 'CreatedAt', - }, - publishedAt: { - codecId: 'pg/timestamptz@1', - nativeType: 'timestamptz', - nullable: false, - typeRef: 'PublishedAt', - }, - reminderAt: { - codecId: 'pg/timetz@1', - nativeType: 'timetz', - nullable: false, - typeRef: 'ReminderAt', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + event: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + code: { + codecId: 'sql/char@1', + nativeType: 'character', + nullable: false, + typeRef: 'Code', + }, + score: { + codecId: 'pg/float4@1', + nativeType: 'float4', + nullable: false, + typeRef: 'Score', + }, + createdAt: { + codecId: 'pg/timestamp@1', + nativeType: 'timestamp', + nullable: false, + typeRef: 'CreatedAt', + }, + publishedAt: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + nullable: false, + typeRef: 'PublishedAt', + }, + reminderAt: { + codecId: 'pg/timetz@1', + nativeType: 'timetz', + nullable: false, + typeRef: 'ReminderAt', }, }, }, @@ -329,17 +323,13 @@ model Account { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - public: { - enum: { - UserRole: { kind: 'postgres-enum', values: ['ADMIN', 'USER'] }, - }, + public: { + enum: { + UserRole: { kind: 'postgres-enum', values: ['ADMIN', 'USER'] }, }, }, }); - const storageNs = ( - result.value.storage as unknown as { namespaces: Record } - ).namespaces; + const storageNs = result.value.storage as unknown as Record; expect(storageNs['auth']).toBeUndefined(); }); @@ -370,17 +360,13 @@ model Account { if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - auth: { - enum: { - user_type: { kind: 'postgres-enum', values: ['admin', 'user'] }, - }, + auth: { + enum: { + user_type: { kind: 'postgres-enum', values: ['admin', 'user'] }, }, }, }); - const storageNs2 = ( - result.value.storage as unknown as { namespaces: Record } - ).namespaces; + const storageNs2 = result.value.storage as unknown as Record; expect(storageNs2['public']?.enum).toBeUndefined(); }); }); diff --git a/packages/2-sql/2-authoring/contract-psl/test/interpreter.value-objects.test.ts b/packages/2-sql/2-authoring/contract-psl/test/interpreter.value-objects.test.ts index 648ad43736..aa8ea8103f 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/interpreter.value-objects.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/interpreter.value-objects.test.ts @@ -122,16 +122,14 @@ model User { }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - homeAddress: { - nativeType: 'jsonb', - codecId: 'pg/jsonb@1', - nullable: true, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + homeAddress: { + nativeType: 'jsonb', + codecId: 'pg/jsonb@1', + nullable: true, }, }, }, @@ -170,16 +168,14 @@ model User { }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - tags: { - nativeType: 'jsonb', - codecId: 'pg/jsonb@1', - nullable: false, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + tags: { + nativeType: 'jsonb', + codecId: 'pg/jsonb@1', + nullable: false, }, }, }, @@ -223,16 +219,14 @@ model User { }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - addresses: { - nativeType: 'jsonb', - codecId: 'pg/jsonb@1', - nullable: false, - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + addresses: { + nativeType: 'jsonb', + codecId: 'pg/jsonb@1', + nullable: false, }, }, }, diff --git a/packages/2-sql/2-authoring/contract-psl/test/provider.test.ts b/packages/2-sql/2-authoring/contract-psl/test/provider.test.ts index 536a8fae79..6dfbfb5d7f 100644 --- a/packages/2-sql/2-authoring/contract-psl/test/provider.test.ts +++ b/packages/2-sql/2-authoring/contract-psl/test/provider.test.ts @@ -86,11 +86,9 @@ describe('prismaContract provider helper', () => { targetFamily: 'sql', target: 'postgres', storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: expect.any(Object), - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: expect.any(Object), }, }, }, @@ -123,11 +121,9 @@ describe('prismaContract provider helper', () => { expect(result.value).toMatchObject({ storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: expect.any(Object), - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: expect.any(Object), }, }, }, @@ -449,16 +445,14 @@ model Document { }, }); expect(result.value.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - tables: { - user: { - columns: { - dbExpr: { - default: { - kind: 'function', - expression: 'gen_random_uuid()', - }, + [UNBOUND_NAMESPACE_ID]: { + tables: { + user: { + columns: { + dbExpr: { + default: { + kind: 'function', + expression: 'gen_random_uuid()', }, }, }, diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract.edge-cases.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract.edge-cases.test.ts index 4f8a9a7e59..373a41fd2b 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract.edge-cases.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract.edge-cases.test.ts @@ -26,7 +26,7 @@ describe('SqlContractSerializer edge cases', () => { expect(() => validateSqlContractFully>(contractInput)).toThrow(); }); - it('handles storage without tables property', () => { + it('accepts storage with only storageHash (defaults to an empty unbound namespace)', () => { const contractInput = { schemaVersion: '1', target: 'postgres', @@ -38,9 +38,9 @@ describe('SqlContractSerializer edge cases', () => { roots: {}, models: {}, storage: { storageHash: 'sha256:test' }, - // biome-ignore lint/suspicious/noExplicitAny: testing invalid input + // biome-ignore lint/suspicious/noExplicitAny: testing minimal valid input } as any; - expect(() => validateSqlContractFully>(contractInput)).toThrow(); + expect(() => validateSqlContractFully>(contractInput)).not.toThrow(); }); it('rejects models with null relations', () => { diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract.integration.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract.integration.test.ts index c41fbfff29..d396e20362 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract.integration.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract.integration.test.ts @@ -8,9 +8,7 @@ import { unboundTables } from './unbound-tables'; function sqlStorageFixture(tables: Record) { return { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, - }, + [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, tables }, }; } diff --git a/packages/2-sql/2-authoring/contract-ts/test/contract.logic.test.ts b/packages/2-sql/2-authoring/contract-ts/test/contract.logic.test.ts index 782cf19a6d..5e2cdcb946 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/contract.logic.test.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/contract.logic.test.ts @@ -9,19 +9,16 @@ import { unboundTables } from './unbound-tables'; function sqlStorageFixture(tables: Record) { return { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables, }, }; } function contractTablesRecord(contract: Record): Record { const storage = contract['storage'] as Record; - const namespaces = storage['namespaces'] as Record; - const slot = namespaces[UNBOUND_NAMESPACE_ID] as Record; + const slot = storage[UNBOUND_NAMESPACE_ID] as Record; return slot['tables'] as Record; } diff --git a/packages/2-sql/2-authoring/contract-ts/test/storage-with-namespaced-tables.ts b/packages/2-sql/2-authoring/contract-ts/test/storage-with-namespaced-tables.ts index 98984ed8c5..59c5e4f3cd 100644 --- a/packages/2-sql/2-authoring/contract-ts/test/storage-with-namespaced-tables.ts +++ b/packages/2-sql/2-authoring/contract-ts/test/storage-with-namespaced-tables.ts @@ -11,11 +11,9 @@ export function storageWithNamespacedTables(storage: LegacyStorage): Record Date: Sun, 31 May 2026 08:20:59 +0200 Subject: [PATCH 56/60] refactor(extension-tests): migrate cipherstash/pgvector/postgres/sql-orm-client storage reads to flat shape Signed-off-by: Will Madden --- .../cipherstash/test/helpers.test.ts | 80 ++++++++--------- .../test/operator-lowering.helpers.ts | 88 +++++++++---------- .../test/control-adapter-lower-parity.test.ts | 22 +++-- .../pgvector/test/operation-lowering.test.ts | 26 +++--- .../pgvector/test/rich-adapter.test.ts | 50 +++++------ .../test/sql-renderer.cast-policy.test.ts | 22 +++-- .../postgres/test/raw-sql-composition.test.ts | 42 +++++---- .../test/collection-contract.test.ts | 14 ++- 8 files changed, 164 insertions(+), 180 deletions(-) diff --git a/packages/3-extensions/cipherstash/test/helpers.test.ts b/packages/3-extensions/cipherstash/test/helpers.test.ts index 142d579386..04f489e7a6 100644 --- a/packages/3-extensions/cipherstash/test/helpers.test.ts +++ b/packages/3-extensions/cipherstash/test/helpers.test.ts @@ -78,49 +78,47 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:cipherstash-helpers-test-storage', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - [TABLE]: { - columns: { - id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - email: { - codecId: CIPHERSTASH_STRING_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - score: { - codecId: CIPHERSTASH_DOUBLE_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - amount: { - codecId: CIPHERSTASH_BIGINT_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - birthday: { - codecId: CIPHERSTASH_DATE_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - enabled: { - codecId: CIPHERSTASH_BOOLEAN_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - payload: { - codecId: CIPHERSTASH_JSON_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - plain: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + [TABLE]: { + columns: { + id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + email: { + codecId: CIPHERSTASH_STRING_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, }, - uniques: [], - indexes: [], - foreignKeys: [], + score: { + codecId: CIPHERSTASH_DOUBLE_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + amount: { + codecId: CIPHERSTASH_BIGINT_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + birthday: { + codecId: CIPHERSTASH_DATE_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + enabled: { + codecId: CIPHERSTASH_BOOLEAN_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + payload: { + codecId: CIPHERSTASH_JSON_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + plain: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/cipherstash/test/operator-lowering.helpers.ts b/packages/3-extensions/cipherstash/test/operator-lowering.helpers.ts index fcb4f065be..9198f15778 100644 --- a/packages/3-extensions/cipherstash/test/operator-lowering.helpers.ts +++ b/packages/3-extensions/cipherstash/test/operator-lowering.helpers.ts @@ -77,53 +77,51 @@ export const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:cipherstash-operator-lowering-test-storage', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - [TABLE]: { - columns: { - id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - [COLUMN]: { - codecId: CIPHERSTASH_STRING_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - // Per-codec columns so the trait-dispatched operators - // can be exercised against each column type (the - // postgres renderer reads `nativeType` from the codec - // descriptor at lower time; the column is what gives - // the renderer the codec id to look up). - score: { - codecId: CIPHERSTASH_DOUBLE_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - amount: { - codecId: CIPHERSTASH_BIGINT_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - birthday: { - codecId: CIPHERSTASH_DATE_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - enabled: { - codecId: CIPHERSTASH_BOOLEAN_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, - payload: { - codecId: CIPHERSTASH_JSON_CODEC_ID, - nativeType: EQL_V2_ENCRYPTED_TYPE, - nullable: true, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + [TABLE]: { + columns: { + id: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + [COLUMN]: { + codecId: CIPHERSTASH_STRING_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + // Per-codec columns so the trait-dispatched operators + // can be exercised against each column type (the + // postgres renderer reads `nativeType` from the codec + // descriptor at lower time; the column is what gives + // the renderer the codec id to look up). + score: { + codecId: CIPHERSTASH_DOUBLE_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + amount: { + codecId: CIPHERSTASH_BIGINT_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + birthday: { + codecId: CIPHERSTASH_DATE_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + enabled: { + codecId: CIPHERSTASH_BOOLEAN_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, + }, + payload: { + codecId: CIPHERSTASH_JSON_CODEC_ID, + nativeType: EQL_V2_ENCRYPTED_TYPE, + nullable: true, }, - uniques: [], - indexes: [], - foreignKeys: [], }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/pgvector/test/control-adapter-lower-parity.test.ts b/packages/3-extensions/pgvector/test/control-adapter-lower-parity.test.ts index cc5afec49b..605b86067f 100644 --- a/packages/3-extensions/pgvector/test/control-adapter-lower-parity.test.ts +++ b/packages/3-extensions/pgvector/test/control-adapter-lower-parity.test.ts @@ -28,19 +28,17 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/pgvector/test/operation-lowering.test.ts b/packages/3-extensions/pgvector/test/operation-lowering.test.ts index 7b966cba9e..0ebc2befa0 100644 --- a/packages/3-extensions/pgvector/test/operation-lowering.test.ts +++ b/packages/3-extensions/pgvector/test/operation-lowering.test.ts @@ -26,21 +26,19 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'test-hash', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, - otherVector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, + otherVector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/pgvector/test/rich-adapter.test.ts b/packages/3-extensions/pgvector/test/rich-adapter.test.ts index ad8d748073..8868ffcb59 100644 --- a/packages/3-extensions/pgvector/test/rich-adapter.test.ts +++ b/packages/3-extensions/pgvector/test/rich-adapter.test.ts @@ -37,35 +37,33 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - createdAt: { - codecId: 'pg/timestamptz@1', - nativeType: 'timestamptz', - nullable: false, - }, - vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + createdAt: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + nullable: false, }, - uniques: [], - indexes: [], - foreignKeys: [], + vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, }, - post: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - user_id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + user_id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/pgvector/test/sql-renderer.cast-policy.test.ts b/packages/3-extensions/pgvector/test/sql-renderer.cast-policy.test.ts index e17caa1d32..49da58842d 100644 --- a/packages/3-extensions/pgvector/test/sql-renderer.cast-policy.test.ts +++ b/packages/3-extensions/pgvector/test/sql-renderer.cast-policy.test.ts @@ -29,19 +29,17 @@ describe('pgvector cast policy', () => { meta: {}, storage: { storageHash: 'sha256:vector-cast-policy', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - vec: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + vec: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/postgres/test/raw-sql-composition.test.ts b/packages/3-extensions/postgres/test/raw-sql-composition.test.ts index 2742af180c..c846ed590b 100644 --- a/packages/3-extensions/postgres/test/raw-sql-composition.test.ts +++ b/packages/3-extensions/postgres/test/raw-sql-composition.test.ts @@ -24,30 +24,28 @@ function makeStubContext(): ExecutionContext> { target: 'postgres', storage: { storageHash: 'sha256:raw-sql-composition-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - users: { - columns: { - id: { codecId: 'pg/int4@1', nullable: false }, - name: { codecId: 'pg/text@1', nullable: false }, - score: { codecId: 'pg/int4@1', nullable: true }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + users: { + columns: { + id: { codecId: 'pg/int4@1', nullable: false }, + name: { codecId: 'pg/text@1', nullable: false }, + score: { codecId: 'pg/int4@1', nullable: true }, }, - events: { - columns: { - id: { codecId: 'pg/int4@1', nullable: false }, - createdAt: { codecId: 'pg/timestamptz@1', nullable: false }, - score: { codecId: 'pg/int4@1', nullable: true }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + events: { + columns: { + id: { codecId: 'pg/int4@1', nullable: false }, + createdAt: { codecId: 'pg/timestamptz@1', nullable: false }, + score: { codecId: 'pg/int4@1', nullable: true }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-extensions/sql-orm-client/test/collection-contract.test.ts b/packages/3-extensions/sql-orm-client/test/collection-contract.test.ts index 70215f5b3c..fb0a5bef7d 100644 --- a/packages/3-extensions/sql-orm-client/test/collection-contract.test.ts +++ b/packages/3-extensions/sql-orm-client/test/collection-contract.test.ts @@ -245,14 +245,12 @@ describe('collection-contract capability detection', () => { }) => ({ storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - t: { - primaryKey: table.primaryKey, - uniques: table.uniques ?? [], - }, + __unbound__: { + id: '__unbound__', + tables: { + t: { + primaryKey: table.primaryKey, + uniques: table.uniques ?? [], }, }, }, From abc27cc4a7c12cb575427e5dd1adc8be908f5180 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:21:05 +0200 Subject: [PATCH 57/60] refactor(target-tests): migrate mongo-target and postgres/sqlite adapter test reads to flat storage Signed-off-by: Will Madden --- .../1-mongo-target/test/mongo-runner.test.ts | 4 +- .../1-mongo-target/test/render-ops.test.ts | 10 ++-- .../postgres/test/adapter-joins.test.ts | 56 +++++++++---------- .../6-adapters/postgres/test/adapter.test.ts | 54 +++++++++--------- .../test/control-adapter-lower-parity.test.ts | 26 ++++----- .../postgres/test/raw-expr-lowering.test.ts | 22 ++++---- .../test/sql-renderer.cast-policy.test.ts | 30 +++++----- .../6-adapters/sqlite/test/adapter.test.ts | 42 +++++++------- .../sqlite/test/raw-expr-lowering.test.ts | 22 ++++---- 9 files changed, 124 insertions(+), 142 deletions(-) diff --git a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts index 922bb94f05..36707f29ef 100644 --- a/packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts @@ -234,9 +234,7 @@ function makeContract(profileHash: string): MongoContract { profileHash, storage: { storageHash: 'sha256:dest', - namespaces: { - __unbound__: { id: '__unbound__', kind: 'mongo-namespace', collections: {} }, - }, + __unbound__: { id: '__unbound__', kind: 'mongo-namespace', collections: {} }, }, } as unknown as MongoContract; } diff --git a/packages/3-mongo-target/1-mongo-target/test/render-ops.test.ts b/packages/3-mongo-target/1-mongo-target/test/render-ops.test.ts index 8ab2c7c20a..895c58c471 100644 --- a/packages/3-mongo-target/1-mongo-target/test/render-ops.test.ts +++ b/packages/3-mongo-target/1-mongo-target/test/render-ops.test.ts @@ -63,12 +63,10 @@ function makeContract(collections: Record): MongoCo models: {}, storage: { storageHash: 'sha256:test-storage', - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: builtCollections, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: builtCollections, }, }, } as unknown as MongoContract; diff --git a/packages/3-targets/6-adapters/postgres/test/adapter-joins.test.ts b/packages/3-targets/6-adapters/postgres/test/adapter-joins.test.ts index 7de3bd5075..ad930046de 100644 --- a/packages/3-targets/6-adapters/postgres/test/adapter-joins.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/adapter-joins.test.ts @@ -24,38 +24,36 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, - post: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, - comment: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - postId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + comment: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + postId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/postgres/test/adapter.test.ts b/packages/3-targets/6-adapters/postgres/test/adapter.test.ts index 924db1685f..5677733fbf 100644 --- a/packages/3-targets/6-adapters/postgres/test/adapter.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/adapter.test.ts @@ -42,37 +42,35 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - createdAt: { - codecId: 'pg/timestamptz@1', - nativeType: 'timestamptz', - nullable: false, - }, - profile: { codecId: 'pg/jsonb@1', nativeType: 'jsonb', nullable: true }, - metadata: { codecId: 'pg/json@1', nativeType: 'json', nullable: true }, - vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + createdAt: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + nullable: false, }, - uniques: [], - indexes: [], - foreignKeys: [], + profile: { codecId: 'pg/jsonb@1', nativeType: 'jsonb', nullable: true }, + metadata: { codecId: 'pg/json@1', nativeType: 'json', nullable: true }, + vector: { codecId: 'pg/vector@1', nativeType: 'vector', nullable: false }, }, - post: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts b/packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts index 85c8ac6abb..6a5a09198c 100644 --- a/packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts @@ -31,21 +31,19 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - profile: { codecId: 'pg/jsonb@1', nativeType: 'jsonb', nullable: true }, - settings: { codecId: 'pg/json@1', nativeType: 'json', nullable: true }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + profile: { codecId: 'pg/jsonb@1', nativeType: 'jsonb', nullable: true }, + settings: { codecId: 'pg/json@1', nativeType: 'json', nullable: true }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/postgres/test/raw-expr-lowering.test.ts b/packages/3-targets/6-adapters/postgres/test/raw-expr-lowering.test.ts index 45924649d0..638f74fbea 100644 --- a/packages/3-targets/6-adapters/postgres/test/raw-expr-lowering.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/raw-expr-lowering.test.ts @@ -41,19 +41,17 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:raw-expr-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts b/packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts index e2bcf486d4..1caeee1691 100644 --- a/packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts +++ b/packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts @@ -54,23 +54,21 @@ const baseContract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:cast-policy', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - tag: { codecId: 'app/test-foo@1', nativeType: 'foo', nullable: false }, - score: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - note: { codecId: 'pg/enum@1', nativeType: 'tag', nullable: false }, - geo: { codecId: 'app/geography@1', nativeType: 'geography', nullable: false }, - profile: { codecId: 'arktype/json@1', nativeType: 'jsonb', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + tag: { codecId: 'app/test-foo@1', nativeType: 'foo', nullable: false }, + score: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + note: { codecId: 'pg/enum@1', nativeType: 'tag', nullable: false }, + geo: { codecId: 'app/geography@1', nativeType: 'geography', nullable: false }, + profile: { codecId: 'arktype/json@1', nativeType: 'jsonb', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/sqlite/test/adapter.test.ts b/packages/3-targets/6-adapters/sqlite/test/adapter.test.ts index baae1d4634..2d107ddb05 100644 --- a/packages/3-targets/6-adapters/sqlite/test/adapter.test.ts +++ b/packages/3-targets/6-adapters/sqlite/test/adapter.test.ts @@ -42,30 +42,28 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:test-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, - email: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, - metadata: { codecId: 'sqlite/json@1', nativeType: 'text', nullable: true }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, + email: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, + metadata: { codecId: 'sqlite/json@1', nativeType: 'text', nullable: true }, }, - post: { - columns: { - id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, - userId: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, - title: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, + userId: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, + title: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/packages/3-targets/6-adapters/sqlite/test/raw-expr-lowering.test.ts b/packages/3-targets/6-adapters/sqlite/test/raw-expr-lowering.test.ts index be0a1f1b37..7b71d4c605 100644 --- a/packages/3-targets/6-adapters/sqlite/test/raw-expr-lowering.test.ts +++ b/packages/3-targets/6-adapters/sqlite/test/raw-expr-lowering.test.ts @@ -22,19 +22,17 @@ const contract = new SqlContractSerializer().deserializeContract({ meta: {}, storage: { storageHash: 'sha256:raw-expr-core', - namespaces: { - __unbound__: { - id: '__unbound__', - tables: { - user: { - columns: { - id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, - email: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, - }, - uniques: [], - indexes: [], - foreignKeys: [], + __unbound__: { + id: '__unbound__', + tables: { + user: { + columns: { + id: { codecId: 'sqlite/integer@1', nativeType: 'integer', nullable: false }, + email: { codecId: 'sqlite/text@1', nativeType: 'text', nullable: false }, }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, From 734a7b8365ec14918f4b641ee009b915b0c14cb4 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 08:32:33 +0200 Subject: [PATCH 58/60] refactor(integration-tests): drop storage.namespaces wrapper from literals and assertions The flat-storage codemod had not reached test/integration, so these contract literals and toMatchObject assertions still wrapped namespace entries under storage.namespaces. Promote namespace ids to direct storage keys (ADR 221 flat shape). Signed-off-by: Will Madden --- .../authoring/mongo-pack-composition.test.ts | 10 +-- ...psl-index-type-options.integration.test.ts | 24 +++--- .../test/cli.db-verify.e2e.test.ts | 30 ++++--- .../test/cli.emit-command.additional.test.ts | 14 ++-- test/integration/test/cli.emit.test.ts | 8 +- .../integration/test/contract-builder.test.ts | 8 +- .../integration/test/contract-imports.test.ts | 76 ++++++++--------- .../test/mongo-runtime/query-builder.test.ts | 14 ++-- .../mongo/migration-authoring-e2e.test.ts | 32 +++---- .../test/vite-plugin.hmr.e2e.test.ts | 84 ++++++++----------- 10 files changed, 131 insertions(+), 169 deletions(-) diff --git a/test/integration/test/authoring/mongo-pack-composition.test.ts b/test/integration/test/authoring/mongo-pack-composition.test.ts index b19365e1e8..32eb85f120 100644 --- a/test/integration/test/authoring/mongo-pack-composition.test.ts +++ b/test/integration/test/authoring/mongo-pack-composition.test.ts @@ -22,12 +22,10 @@ describe('Mongo pack composition', () => { users: { namespace: '__unbound__', model: 'User' }, }, storage: { - namespaces: { - __unbound__: { - id: '__unbound__', - collections: { - users: {}, - }, + __unbound__: { + id: '__unbound__', + collections: { + users: {}, }, }, }, diff --git a/test/integration/test/authoring/psl-index-type-options.integration.test.ts b/test/integration/test/authoring/psl-index-type-options.integration.test.ts index 9cd4ac15dc..1e6ca1ae81 100644 --- a/test/integration/test/authoring/psl-index-type-options.integration.test.ts +++ b/test/integration/test/authoring/psl-index-type-options.integration.test.ts @@ -32,19 +32,17 @@ describe('PSL @@index type and options — integration with real paradedb pack', expect(result.ok).toBe(true); if (!result.ok) return; expect(result.value.storage).toMatchObject({ - namespaces: { - __unbound__: { - tables: { - doc: { - indexes: [ - { - columns: ['body'], - name: 'doc_body_bm25_idx', - type: 'bm25', - options: { key_field: 'id' }, - }, - ], - }, + __unbound__: { + tables: { + doc: { + indexes: [ + { + columns: ['body'], + name: 'doc_body_bm25_idx', + type: 'bm25', + options: { key_field: 'id' }, + }, + ], }, }, }, diff --git a/test/integration/test/cli.db-verify.e2e.test.ts b/test/integration/test/cli.db-verify.e2e.test.ts index 78cd20f103..aacf153af6 100644 --- a/test/integration/test/cli.db-verify.e2e.test.ts +++ b/test/integration/test/cli.db-verify.e2e.test.ts @@ -43,22 +43,20 @@ function createTestContract( targetFamily: 'sql', storage: { storageHash: 'sha256:test', - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: Object.fromEntries( - Object.entries(tables).map(([name, { columns, uniques = [] }]) => [ - name, - { - columns, - primaryKey: { columns: ['id'] }, - uniques, - indexes: [], - foreignKeys: [], - }, - ]), - ), - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: Object.fromEntries( + Object.entries(tables).map(([name, { columns, uniques = [] }]) => [ + name, + { + columns, + primaryKey: { columns: ['id'] }, + uniques, + indexes: [], + foreignKeys: [], + }, + ]), + ), }, }, roots: {}, diff --git a/test/integration/test/cli.emit-command.additional.test.ts b/test/integration/test/cli.emit-command.additional.test.ts index 0a95a270c8..57e096dc6c 100644 --- a/test/integration/test/cli.emit-command.additional.test.ts +++ b/test/integration/test/cli.emit-command.additional.test.ts @@ -367,14 +367,12 @@ model Post { targetFamily: 'mongo', target: 'mongo', storage: { - namespaces: { - __unbound__: { - collections: { - users: { - indexes: [{ keys: [{ field: 'email', direction: 1 }], unique: true }], - options: { - collation: { locale: 'en', strength: 2 }, - }, + __unbound__: { + collections: { + users: { + indexes: [{ keys: [{ field: 'email', direction: 1 }], unique: true }], + options: { + collation: { locale: 'en', strength: 2 }, }, }, }, diff --git a/test/integration/test/cli.emit.test.ts b/test/integration/test/cli.emit.test.ts index 76436bdbfe..5cafce9969 100644 --- a/test/integration/test/cli.emit.test.ts +++ b/test/integration/test/cli.emit.test.ts @@ -69,11 +69,9 @@ describe('emit command functionality', () => { targetFamily: 'sql', target: 'postgres', storage: { - namespaces: { - __unbound__: { - tables: { - user: expect.anything(), - }, + __unbound__: { + tables: { + user: expect.anything(), }, }, }, diff --git a/test/integration/test/contract-builder.test.ts b/test/integration/test/contract-builder.test.ts index 12ee95ec64..63ea2a22cb 100644 --- a/test/integration/test/contract-builder.test.ts +++ b/test/integration/test/contract-builder.test.ts @@ -41,11 +41,9 @@ describe('builder integration', () => { targetFamily: 'sql', storage: expect.objectContaining({ storageHash: 'sha256:test-core', - namespaces: expect.objectContaining({ - __unbound__: expect.objectContaining({ - tables: expect.objectContaining({ - user: expect.anything(), - }), + __unbound__: expect.objectContaining({ + tables: expect.objectContaining({ + user: expect.anything(), }), }), }), diff --git a/test/integration/test/contract-imports.test.ts b/test/integration/test/contract-imports.test.ts index 315ea335c3..ad276bcd95 100644 --- a/test/integration/test/contract-imports.test.ts +++ b/test/integration/test/contract-imports.test.ts @@ -111,36 +111,34 @@ describe('contract.d.ts imports resolution', () => { }, }, storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - createdAt: { - codecId: 'pg/timestamptz@1', - nativeType: 'timestamptz', - nullable: false, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + createdAt: { + codecId: 'pg/timestamptz@1', + nativeType: 'timestamptz', + nullable: false, }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, - post: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + post: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, + userId: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, @@ -265,20 +263,18 @@ type UserIdColumn = UserColumns['id']; }, }, storage: { - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, - email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, - }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + tables: { + user: { + columns: { + id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false }, + email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false }, }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], }, }, }, diff --git a/test/integration/test/mongo-runtime/query-builder.test.ts b/test/integration/test/mongo-runtime/query-builder.test.ts index 813465caab..e553c18f39 100644 --- a/test/integration/test/mongo-runtime/query-builder.test.ts +++ b/test/integration/test/mongo-runtime/query-builder.test.ts @@ -80,14 +80,12 @@ const contractJson = { }, storage: { storageHash: 'test-hash', - namespaces: { - __unbound__: { - id: '__unbound__', - kind: 'mongo-namespace', - collections: { - orders: { kind: 'mongo-collection' }, - users: { kind: 'mongo-collection' }, - }, + __unbound__: { + id: '__unbound__', + kind: 'mongo-namespace', + collections: { + orders: { kind: 'mongo-collection' }, + users: { kind: 'mongo-collection' }, }, }, }, diff --git a/test/integration/test/mongo/migration-authoring-e2e.test.ts b/test/integration/test/mongo/migration-authoring-e2e.test.ts index 38bb9df9a9..eb3a3d708e 100644 --- a/test/integration/test/mongo/migration-authoring-e2e.test.ts +++ b/test/integration/test/mongo/migration-authoring-e2e.test.ts @@ -89,17 +89,15 @@ describe('Migration authoring round-trip (factory → serialize → deserialize // Synthetic-contract opt-out (paired with `strictVerification: false`): // these tests exercise the runner against hand-rolled migration ops, // not a real authored contract. Supply the minimum well-formed shape - // `contractToMongoSchemaIR` reads (`storage.namespaces`) so the + // `contractToMongoSchemaIR` reads (flat `storage.`) so the // verifier degrades to an empty-expected diff rather than crashing. destinationContract: { storage: { storageHash: destinationHash, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-namespace' as const, - collections: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-namespace' as const, + collections: {}, }, }, } as unknown as MongoContract, @@ -350,12 +348,10 @@ describe('Migration authoring round-trip (factory → serialize → deserialize destinationContract: { storage: { storageHash: destinationHashV2, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-namespace' as const, - collections: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-namespace' as const, + collections: {}, }, }, } as unknown as MongoContract, @@ -401,12 +397,10 @@ describe('Migration authoring round-trip (factory → serialize → deserialize destinationContract: { storage: { storageHash: destinationHashV3, - namespaces: { - [UNBOUND_NAMESPACE_ID]: { - id: UNBOUND_NAMESPACE_ID, - kind: 'mongo-namespace' as const, - collections: {}, - }, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'mongo-namespace' as const, + collections: {}, }, }, } as unknown as MongoContract, diff --git a/test/integration/test/vite-plugin.hmr.e2e.test.ts b/test/integration/test/vite-plugin.hmr.e2e.test.ts index 00d5497a61..1bb42ad224 100644 --- a/test/integration/test/vite-plugin.hmr.e2e.test.ts +++ b/test/integration/test/vite-plugin.hmr.e2e.test.ts @@ -174,13 +174,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(initialContract.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - email: expect.anything(), - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + email: expect.anything(), }, }, }, @@ -209,13 +207,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(updatedContract.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - name: { nullable: true }, - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + name: { nullable: true }, }, }, }, @@ -259,13 +255,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(initialContract.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - email: expect.anything(), - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + email: expect.anything(), }, }, }, @@ -285,13 +279,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(updatedContract.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - name: { nullable: true }, - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + name: { nullable: true }, }, }, }, @@ -355,13 +347,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(contractAfterConfigChange.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - name: { nullable: true }, - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + name: { nullable: true }, }, }, }, @@ -384,13 +374,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(contractAfterAltEdit.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - nickname: { nullable: true }, - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + nickname: { nullable: true }, }, }, }, @@ -485,13 +473,11 @@ function runVitePluginHmrSuite(viteVersionLabel: string, createViteServer: Creat await readJsonFileWhenReady(contractJsonPath, timeouts.typeScriptCompilation), ); expect(recoveredContract.storage).toMatchObject({ - namespaces: { - [UNBOUND_NAMESPACE]: { - tables: { - user: { - columns: { - name: { nullable: true }, - }, + [UNBOUND_NAMESPACE]: { + tables: { + user: { + columns: { + name: { nullable: true }, }, }, }, From 6ad2f0851da61b93c776be9e72cfa423961b2bbf Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 09:13:05 +0200 Subject: [PATCH 59/60] fix(mongo-emitter): drop unused storage-walk imports in emitter-hook test The flat-storage migration left getStorageNamespace / storageNamespaceEntries / storageNamespaceValues imported but unused (the only textual hit was inside an error-message string), tripping biome noUnusedImports under --error-on-warnings in CI. Signed-off-by: Will Madden --- .../3-tooling/emitter/test/emitter-hook.structure.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts index e2e0d1e257..e7f7836037 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.structure.test.ts @@ -1,10 +1,5 @@ import type { Contract } from '@prisma-next/contract/types'; import { crossRef } from '@prisma-next/contract/types'; -import { - getStorageNamespace, - storageNamespaceEntries, - storageNamespaceValues, -} from '@prisma-next/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { mongoEmission } from '../src/index'; import { From 037436ea0bd51aca39dfc224e1224bcfea4710d9 Mon Sep 17 00:00:00 2001 From: Will Madden Date: Sun, 31 May 2026 09:23:47 +0200 Subject: [PATCH 60/60] fix(mongo-contract): flatten storage paths in canonicalization-hooks test The preserve-empty predicate now matches the flat storage..collections path (no namespaces wrapper); the test still passed the old wrapped path, so the empty-collections-slot case asserted false where the flat hook returns true. Signed-off-by: Will Madden --- .../mongo-contract/test/canonicalization-hooks.test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/canonicalization-hooks.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/canonicalization-hooks.test.ts index 60b7f5d949..88f993eff6 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/canonicalization-hooks.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/canonicalization-hooks.test.ts @@ -8,7 +8,6 @@ describe('mongoContractCanonicalizationHooks.shouldPreserveEmpty', () => { expect( shouldPreserveEmpty([ 'storage', - 'namespaces', 'app', 'collections', 'users', @@ -23,7 +22,6 @@ describe('mongoContractCanonicalizationHooks.shouldPreserveEmpty', () => { expect( shouldPreserveEmpty([ 'storage', - 'namespaces', 'app', 'collections', 'users', @@ -40,7 +38,6 @@ describe('mongoContractCanonicalizationHooks.shouldPreserveEmpty', () => { expect( shouldPreserveEmpty([ 'storage', - 'namespaces', 'app', 'collections', 'events', @@ -54,10 +51,10 @@ describe('mongoContractCanonicalizationHooks.shouldPreserveEmpty', () => { }); it('preserves the empty collections slot', () => { - expect(shouldPreserveEmpty(['storage', 'namespaces', 'app', 'collections'])).toBe(true); + expect(shouldPreserveEmpty(['storage', 'app', 'collections'])).toBe(true); }); it('does not preserve unrelated empty defaults', () => { - expect(shouldPreserveEmpty(['storage', 'namespaces', 'app', 'somethingElse'])).toBe(false); + expect(shouldPreserveEmpty(['storage', 'app', 'somethingElse'])).toBe(false); }); });