@prisma/orm-sqlite 8.0.0-rc.9-dev.8 → 8.0.0-rc.9-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ Shared concepts (result consumption, script teardown, cross-target pitfalls, cap
9
9
  **Mongo** (`mongo<Contract>(...)` from `@internal/mongo/runtime`):
10
10
 
11
11
  - **`db.orm.<root>`** — ORM, lowercased plural contract root (`db.orm.users`, `db.orm.posts`). Same fluent chaining; `.where({ field: value })` object equality is the idiomatic filter form.
12
- - **`db.query`** — typed aggregation-pipeline builder. Start with `db.query.from('<root>')`, chain `.match(...)` / `.project(...)` / `.group(...)` / `.lookup(...)`, terminal with `.build()`. Execute via `(await db.runtime()).execute(plan)`.
12
+ - **`db.query`** — typed aggregation-pipeline builder. Start with `db.query.from('<root>')`, chain `.match(...)` / `.project(...)` / `.group(...)` / `.lookup(...)`, terminal with `.build()`. Run via `(await db.runtime()).query(plan)` for anything that returns documents; `execute(plan)` is only for a write you want an affected count from, and it throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED` on a find or aggregate.
13
13
 
14
14
  Reach for the ORM first; drop to `db.query` when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app.
15
15
 
@@ -49,7 +49,7 @@ const recent = await db.orm.posts
49
49
 
50
50
  **`.where(...)`** accepts a plain object whose keys are model field names and values are compared with equality (codec-aware — `ObjectId` fields accept string ids from the contract). Chain multiple `.where({ ... })` calls to AND-compose filters.
51
51
 
52
- For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@internal/mongo-query-ast/execution` (a façade-completeness gap; see *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices.
52
+ For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@prisma/orm-mongo/query-ast/execution` (a façade-completeness gap; see *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices.
53
53
 
54
54
  **Polymorphic roots.** When the contract declares variants on a model, narrow before querying:
55
55
 
@@ -123,7 +123,7 @@ await db.orm.users.where({ email: 'alice@example.com' }).upsert({
123
123
  The Mongo ORM does not expose `.aggregate(...)` / `.groupBy(...)`. Express aggregations through **`db.query`** — the pipeline builder — with `.group(...)` and accumulator helpers:
124
124
 
125
125
  ```typescript
126
- import { acc } from '@internal/mongo-query-builder';
126
+ import { acc } from '@prisma/orm-mongo/query-builder';
127
127
 
128
128
  const runtime = await db.runtime();
129
129
  const plan = db.query
@@ -137,18 +137,18 @@ const plan = db.query
137
137
  .sort({ postCount: -1 })
138
138
  .build();
139
139
 
140
- const byKind = await runtime.execute(plan);
140
+ const byKind = await runtime.query(plan);
141
141
  ```
142
142
 
143
- Import `acc` and expression helpers (`fn`) from `@internal/mongo-query-builder` when building computed pipeline stages.
143
+ Import `acc` and expression helpers (`fn`) from `@prisma/orm-mongo/query-builder` when building computed pipeline stages (as `examples/mongo-demo/src/server.ts` does).
144
144
 
145
145
  ## Workflow — Query builder (`db.query`)
146
146
 
147
- The concept: `db.query.from('<root>')` starts a typed aggregation-pipeline chain. Terminal methods produce a `MongoQueryPlan`; execute it through the runtime:
147
+ The concept: `db.query.from('<root>')` starts a typed aggregation-pipeline chain. Terminal methods produce a `MongoQueryPlan`; run it through the runtime with `query(plan)` (an `AsyncIterableResult` of documents — `await` it for an array):
148
148
 
149
149
  ```typescript
150
150
  // src/queries/analytics.ts
151
- import { acc, fn } from '@internal/mongo-query-builder';
151
+ import { acc, fn } from '@prisma/orm-mongo/query-builder';
152
152
  import { db } from '../prisma/db';
153
153
 
154
154
  const runtime = await db.runtime();
@@ -161,7 +161,7 @@ const plan = db.query
161
161
  .limit(10)
162
162
  .project('title', 'authorId', 'createdAt')
163
163
  .build();
164
- const recent = await runtime.execute(plan);
164
+ const recent = await runtime.query(plan);
165
165
 
166
166
  // Cross-collection join ($lookup).
167
167
  const withAuthor = db.query
@@ -175,26 +175,26 @@ const withAuthor = db.query
175
175
  .as('author'),
176
176
  )
177
177
  .build();
178
- const rows = await runtime.execute(withAuthor);
178
+ const rows = await runtime.query(withAuthor);
179
179
  ```
180
180
 
181
181
  **Filters — `.match(...)`.** Callback form: `.match((f) => f.status.eq('active'))`. Filters AND-compose across chained `.match(...)` calls. Field accessors support property access (`f.email`), callable dot paths (`f('address.city').eq('NYC')`), and `f.rawPath('path')` for migration/backfill paths outside the current contract.
182
182
 
183
- **Write terminals on the builder.** After `.from('users')` or `.from('users').match(...)`, use insert/update/delete terminals:
183
+ **Write terminals on the builder.** After `.from('users')` or `.from('users').match(...)`, use insert/update/delete terminals. Write plans run through `query(plan)` too (one result row carrying the driver's response — the inserted ids, or the document `findOneAndUpdate` returns); reach for `execute(plan)` only on an `update*` / `delete*` plan when all you want is the affected count (any other command kind throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED`):
184
184
 
185
185
  ```typescript
186
- await runtime.execute(
186
+ const inserted = await runtime.query(
187
187
  db.query.from('users').insertOne({ name: 'Alice', email: 'a@e.com', bio: null }),
188
188
  );
189
189
 
190
- await runtime.execute(
190
+ const { affectedRows } = await runtime.execute(
191
191
  db.query
192
192
  .from('users')
193
193
  .match((f) => f.name.eq('Alice'))
194
194
  .updateMany((f) => [f.bio.set('filled')]),
195
195
  );
196
196
 
197
- await runtime.execute(
197
+ const [updated] = await runtime.query(
198
198
  db.query
199
199
  .from('users')
200
200
  .match((f) => f.email.eq('a@e.com'))
@@ -204,7 +204,7 @@ await runtime.execute(
204
204
 
205
205
  Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.pull`, …). Pipeline-style updates use `f.stage.set(...)` inside an aggregation chain, then `.updateMany()` with no callback.
206
206
 
207
- **Plans vs ORM.** The ORM's `.create` / `.update` / `.all` issue queries directly. Don't pass ORM collections to `runtime.execute` — that entry point is for `db.query` plans (and migration/runtime internals).
207
+ **Plans vs ORM.** The ORM's `.create` / `.update` / `.all` issue queries directly. Don't pass ORM collections to `runtime.query` / `runtime.execute` — those entry points are for `db.query` plans (and migration/runtime internals).
208
208
 
209
209
  ## Common Pitfalls (Mongo)
210
210
 
@@ -215,7 +215,7 @@ Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.p
215
215
  5. **Expecting Postgres-style lambda `.where((u) => u.email.eq(...))` on ORM.** Prefer object equality `.where({ email: '...' })`; richer operators need `MongoFilterExpr` helpers (façade gap today).
216
216
  6. **Expecting `db.transaction(...)`.** The Mongo façade does not expose it today. Multi-document atomicity requires MongoDB transactions on a replica set via the driver — not yet wrapped in the Prisma 8 façade. Route to *What Prisma 8 doesn't do yet* / `references/feedback.md` if the user needs this.
217
217
  7. **Trying to use `db.sql`.** There is no `db.sql` on Mongo.
218
- 8. **Trying to `db.execute(plan)` directly.** Execute query-builder plans via `(await db.runtime()).execute(plan)`.
218
+ 8. **Trying to `db.execute(plan)` directly, or reading documents with `execute`.** Run query-builder plans via `(await db.runtime()).query(plan)`. `execute(plan)` resolves statistics only and throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED` for a find or aggregate.
219
219
  9. **Expecting ORM `.aggregate(...)` / `.groupBy(...)`.** Use `db.query.from(...).group(...).build()` instead.
220
220
 
221
221
  ## Reference Files
@@ -230,7 +230,7 @@ Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.p
230
230
  - [ ] Used lowercased plural ORM roots (`db.orm.users`, not `db.orm.User`).
231
231
  - [ ] Chose the right lane (ORM by default; `db.query` for shapes the ORM doesn't express).
232
232
  - [ ] Used `.where({ ... }).first()` for single-row reads — not `.all()`.
233
- - [ ] Executed query-builder plans via `(await db.runtime()).execute(plan)`.
233
+ - [ ] Ran query-builder plans via `(await db.runtime()).query(plan)`; used `execute(plan)` only for an affected count on a write.
234
234
  - [ ] For aggregations, used `db.query.from(...).group(...)` rather than a non-existent ORM `.aggregate(...)`.
235
235
  - [ ] Did NOT confabulate `db.transaction`, `db.sql`, or ORM `.aggregate(...)` — routed to *What Prisma 8 doesn't do yet* / `references/feedback.md` instead.
236
236
  - [ ] Did NOT use the lower-level builder for something the ORM cleanly expresses.
@@ -8,8 +8,8 @@ Shared concepts (result consumption, script teardown, cross-target pitfalls, cap
8
8
 
9
9
  **Postgres** (`postgres<Contract>(...)` from `@internal/postgres/runtime`):
10
10
 
11
- - **`db.orm.<Model>`** — ORM, PascalCase model name (`db.orm.User`). Fluent `.where(...).select(...).orderBy(...).all()`, fully typed against `Contract`. Default lane for CRUD with relations.
12
- - **`db.sql.<table>`** — SQL builder, lowercase storage name (`db.sql.user`). Produces a *plan* executed via `db.runtime().execute(plan)`. Use when the ORM is too high-level — explicit `JOIN`, computed projections, set operations, window functions.
11
+ - **`db.orm.<ns>.<Model>`** — ORM, PascalCase model name (`db.orm.public.User`). Fluent `.where(...).select(...).orderBy(...).all()`, fully typed against `Contract`. Default lane for CRUD with relations.
12
+ - **`db.sql.<ns>.<table>`** — SQL builder, lowercase storage name (`db.sql.public.user`). Produces a *plan*. A plan that returns rows (`select`, or a write with `.returning(...)`) runs through `db.runtime().query(plan)`; a write with no `RETURNING` runs through `db.runtime().execute(plan)`, which resolves `{ affectedRows }`. Use when the ORM is too high-level — explicit `JOIN`, computed projections, set operations, window functions.
13
13
 
14
14
  Reach for the ORM first; drop to `db.sql` when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app.
15
15
 
@@ -17,33 +17,33 @@ Reach for the ORM first; drop to `db.sql` when the ORM can't express the shape.
17
17
 
18
18
  | Need | Choose | Why |
19
19
  | --- | --- | --- |
20
- | Standard CRUD with relations | **ORM (`db.orm.<Model>`)** | Highest ergonomics; fully typed; model-shaped. |
20
+ | Standard CRUD with relations | **ORM (`db.orm.<ns>.<Model>`)** | Highest ergonomics; fully typed; model-shaped. |
21
21
  | Eager-load related records | **ORM `.include(...)`** | Composes with `.where` / `.select` / `.orderBy` / `.limit` per branch. |
22
22
  | Aggregate (count, sum, avg) | **ORM `.aggregate(...)`** | Typed result; works with grouping (`.groupBy(...).aggregate(...)`). |
23
- | `INSERT ... RETURNING` / `UPDATE ... RETURNING` typed result | **ORM mutations** (returns updated rows) or **`db.sql.<t>.insert(...).returning(...)`** | ORM returns inserted/updated rows; SQL builder exposes `.returning(...)` explicitly. |
24
- | Computed projection (e.g. `ST_DistanceSphere(location, point) AS meters`) alongside model fields | **SQL builder (`db.sql.<t>`)** | The ORM projects model fields; arbitrary expression projection is the SQL builder's seam. |
23
+ | `INSERT ... RETURNING` / `UPDATE ... RETURNING` typed result | **ORM mutations** (returns updated rows) or **`db.sql.<ns>.<t>.insert(...).returning(...)`** | ORM returns inserted/updated rows; SQL builder exposes `.returning(...)` explicitly. |
24
+ | Computed projection (e.g. `ST_DistanceSphere(location, point) AS meters`) alongside model fields | **SQL builder (`db.sql.<ns>.<t>`)** | The ORM projects model fields; arbitrary expression projection is the SQL builder's seam. |
25
25
  | Complex `JOIN`, set operation, window function | **SQL builder** | The ORM doesn't express arbitrary joins. |
26
- | Postgres-specific feature (`LATERAL`, `FILTER`, custom aggregates) | **SQL builder**, falling back to extension operators when the extension provides them | DSL first; extensions can contribute operators (`postgis`, `pgvector`, `cipherstash`). |
26
+ | Postgres-specific feature (`LATERAL`, `FILTER`, custom aggregates) | **SQL builder**, falling back to extension operators when the extension provides them | DSL first; extensions can contribute operators (`postgis`, `pgvector`). |
27
27
 
28
28
  ## Workflow — ORM reads
29
29
 
30
- The concept: `db.orm.<Model>` returns a *collection* you compose method-by-method. Each call returns a new collection (immutable chaining); the terminal verb (`.all()` / `.first()` / `.count()` / `.aggregate(...)`) issues the query. Predicates are lambdas over a field proxy: `u.field.<op>(value)`.
30
+ The concept: `db.orm.<ns>.<Model>` returns a *collection* you compose method-by-method. Each call returns a new collection (immutable chaining); the terminal verb (`.all()` / `.first()` / `.aggregate(...)`) issues the query. There is no `.count()` terminal on the collection — `count()` is an `include` reducer and an `aggregate(...)` operation (both below). Predicates are lambdas over a field proxy: `u.field.<op>(value)`.
31
31
 
32
32
  ```typescript
33
33
  // src/queries/users.ts — one directory deep under src/, so the import is '../prisma/db'
34
34
  import { db } from '../prisma/db';
35
35
 
36
36
  // Find one record by primary key shorthand.
37
- const user = await db.orm.User.first({ id: userId });
37
+ const user = await db.orm.public.User.first({ id: userId });
38
38
  // Returns the full row or `null`.
39
39
 
40
40
  // Find one matching a predicate.
41
- const alice = await db.orm.User
41
+ const alice = await db.orm.public.User
42
42
  .where((u) => u.email.eq('alice@example.com'))
43
43
  .first();
44
44
 
45
45
  // Find many with projection, sort, and limit.
46
- const recentUsers = await db.orm.User
46
+ const recentUsers = await db.orm.public.User
47
47
  .select('id', 'email', 'createdAt')
48
48
  .orderBy((u) => u.createdAt.desc())
49
49
  .limit(10)
@@ -54,38 +54,38 @@ const recentUsers = await db.orm.User
54
54
 
55
55
  ```typescript
56
56
  // Lambda form — full expression power.
57
- db.orm.User.where((u) => u.email.eq('alice@example.com'));
57
+ db.orm.public.User.where((u) => u.email.eq('alice@example.com'));
58
58
 
59
59
  // Shorthand object form — equality on the named fields.
60
- db.orm.User.where({ kind: 'admin' });
60
+ db.orm.public.User.where({ kind: 'admin' });
61
61
  ```
62
62
 
63
- Operators on the field proxy include `.eq`, `.neq`, `.lt`, `.lte`, `.gt`, `.gte`, `.like`, `.ilike`, `.in([...])`, `.isNull()`, `.isNotNull()`. Extensions add target-specific operators on extension-typed columns (`pgvector`'s `.cosineDistance(...)`, `postgis`'s `.within(...)` / `.intersectsBbox(...)` / `.distanceSphere(...)`, `cipherstash`'s `.cipherstashEq(...)` / `.cipherstashGt(...)` / …).
63
+ Operators on the field proxy include `.eq`, `.neq`, `.lt`, `.lte`, `.gt`, `.gte`, `.like`, `.ilike`, `.in([...])`, `.isNull()`, `.isNotNull()`. Extensions add target-specific operators on extension-typed columns (`pgvector`'s `.cosineDistance(...)`, `postgis`'s `.within(...)` / `.intersectsBbox(...)` / `.distanceSphere(...)`).
64
64
 
65
65
  **There is no `.between(a, b)` operator.** Express ranges either as two chained `.where(...)` clauses (the idiomatic form — clauses AND-compose) or with the `and(...)` combinator inside one clause:
66
66
 
67
67
  ```typescript
68
68
  // Chained .where() — each clause AND-composes with the previous one.
69
- await db.orm.Sale
69
+ await db.orm.public.Sale
70
70
  .where((s) => s.day.gte(start))
71
71
  .where((s) => s.day.lte(end))
72
72
  .all();
73
73
 
74
74
  // Equivalent with an explicit `and(...)` inside one clause.
75
- import { and } from '@internal/sql-orm-client'; // façade re-export pending — see *What PN doesn't do yet* in queries.md
76
- await db.orm.Sale
75
+ import { and } from '@prisma/orm-postgres/orm-client';
76
+ await db.orm.public.Sale
77
77
  .where((s) => and(s.day.gte(start), s.day.lte(end)))
78
78
  .all();
79
79
  ```
80
80
 
81
81
  The two forms emit the same SQL. Pick chained `.where()` when each clause adds a separate condition that reads as its own thought; pick `and(...)` when one logical predicate happens to have two parts and you want the visual grouping. Don't reach for a `between` helper — there isn't one.
82
82
 
83
- **Combinators** (`and`, `or`, `not`) compose predicates, and **relation predicates** (`.some(...)`, `.none(...)`, `.every(...)`) recurse into a relation. These currently come from the internal `@internal/sql-orm-client` package — see *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md):
83
+ **Combinators** (`and`, `or`, `not`) compose predicates, and **relation predicates** (`.some(...)`, `.none(...)`, `.every(...)`) recurse into a relation. The combinators are exported from the façade's `orm-client` subpath:
84
84
 
85
85
  ```typescript
86
- import { and, or, not } from '@internal/sql-orm-client';
86
+ import { and, or, not } from '@prisma/orm-postgres/orm-client';
87
87
 
88
- await db.orm.User
88
+ await db.orm.public.User
89
89
  .where((u) =>
90
90
  and(
91
91
  or(u.kind.eq('admin'), u.email.ilike('%@example.com')),
@@ -98,7 +98,7 @@ await db.orm.User
98
98
  **Sorting and pagination.** `.orderBy(...)` accepts a single lambda or an array of lambdas (each calling `.asc()` / `.desc()` on a field). `.limit(n)` limits; `.offset(n)` offsets.
99
99
 
100
100
  ```typescript
101
- await db.orm.Post
101
+ await db.orm.public.Post
102
102
  .where((p) => p.authorId.eq(userId))
103
103
  .orderBy([(p) => p.createdAt.desc(), (p) => p.id.desc()])
104
104
  .limit(20)
@@ -108,13 +108,13 @@ await db.orm.Post
108
108
  **Cursor pagination.** Call `.cursor({ field: lastValue })` after `.orderBy(...)` to resume from a known position. The cursor requires a prior `orderBy` — the type system enforces this. Direction (forward or backward) follows the sort: ascending order means "greater than the cursor value", descending means "less than".
109
109
 
110
110
  ```typescript
111
- const page1 = await db.orm.Post
111
+ const page1 = await db.orm.public.Post
112
112
  .orderBy((p) => p.createdAt.desc())
113
113
  .limit(20)
114
114
  .all();
115
115
 
116
116
  const last = page1[page1.length - 1]!;
117
- const page2 = await db.orm.Post
117
+ const page2 = await db.orm.public.Post
118
118
  .orderBy((p) => p.createdAt.desc())
119
119
  .cursor({ createdAt: last.createdAt })
120
120
  .limit(20)
@@ -130,7 +130,7 @@ Cursor keys must match fields in the active `orderBy`. For a composite `orderBy`
130
130
  The concept: `.include('<relation>', (branch) => branch.<chain>)` adds a relation branch to the parent query. The branch is its own collection — compose `.where` / `.select` / `.orderBy` / `.limit` on it just like the parent.
131
131
 
132
132
  ```typescript
133
- await db.orm.User
133
+ await db.orm.public.User
134
134
  .select('id', 'email')
135
135
  .include('posts', (post) =>
136
136
  post
@@ -146,25 +146,25 @@ await db.orm.User
146
146
  **Reduce a to-many relation to a scalar.** A refinement callback may return a *reducer* — `count()`, `sum(field)`, `avg(field)`, `min(field)`, `max(field)`, plus the lossless `countBigInt()`, `sumBigInt(field)`, and `avgDecimal(field)` — instead of a collection. The parent's relation field then carries that one value rather than an array. Reducers exist only inside an `include(...)` callback; calling one elsewhere throws.
147
147
 
148
148
  ```typescript
149
- await db.orm.User.include('posts', (posts) => posts.count()).all();
149
+ await db.orm.public.User.include('posts', (posts) => posts.count()).all();
150
150
  // → Array<{ ...user, posts: number }> — a parent with no posts reads 0
151
151
 
152
- await db.orm.User.include('posts', (posts) => posts.sum('views')).all();
152
+ await db.orm.public.User.include('posts', (posts) => posts.sum('views')).all();
153
153
  // → Array<{ ...user, posts: number | null }>
154
154
 
155
- await db.orm.User.include('posts', (posts) => posts.avg('views')).all();
155
+ await db.orm.public.User.include('posts', (posts) => posts.avg('views')).all();
156
156
  // → Array<{ ...user, posts: number | null }>
157
157
 
158
- await db.orm.User.include('posts', (posts) => posts.min('views')).all();
159
- await db.orm.User.include('posts', (posts) => posts.max('views')).all();
158
+ await db.orm.public.User.include('posts', (posts) => posts.min('views')).all();
159
+ await db.orm.public.User.include('posts', (posts) => posts.max('views')).all();
160
160
  // → Array<{ ...user, posts: number | null }>
161
161
 
162
162
  // The lossless form, for a total that may outgrow a JS number:
163
- await db.orm.User.include('posts', (posts) => posts.sumBigInt('views')).all();
163
+ await db.orm.public.User.include('posts', (posts) => posts.sumBigInt('views')).all();
164
164
  // → Array<{ ...user, posts: bigint | null }>
165
165
 
166
166
  // Several sub-views of one relation at once:
167
- await db.orm.User.include('posts', (posts) =>
167
+ await db.orm.public.User.include('posts', (posts) =>
168
168
  posts.combine({ recent: posts.limit(3), total: posts.count() }),
169
169
  ).all();
170
170
  // → Array<{ ...user, posts: { recent: Post[]; total: number } }>
@@ -178,27 +178,27 @@ Nested `1:N → 1:N` includes (e.g. `User → posts → comments`) require the c
178
178
 
179
179
  ```typescript
180
180
  // Create — returns the inserted row.
181
- const user = await db.orm.User.create({ id, email, displayName, kind, createdAt });
181
+ const user = await db.orm.public.User.create({ id, email, displayName, kind, createdAt });
182
182
 
183
183
  // Create with selected return — narrows the return shape.
184
- const summary = await db.orm.User
184
+ const summary = await db.orm.public.User
185
185
  .select('id', 'email', 'kind')
186
186
  .create({ id, email, displayName, kind, createdAt });
187
187
 
188
188
  // Update by predicate.
189
- await db.orm.User.where({ id }).update({ email: newEmail });
189
+ await db.orm.public.User.where({ id }).update({ email: newEmail });
190
190
 
191
191
  // Update with selected return.
192
- await db.orm.User
192
+ await db.orm.public.User
193
193
  .where({ id })
194
194
  .select('id', 'email', 'kind')
195
195
  .update({ email: newEmail });
196
196
 
197
197
  // Delete by predicate.
198
- await db.orm.User.where({ id }).delete();
198
+ await db.orm.public.User.where({ id }).delete();
199
199
 
200
200
  // Upsert — typed by the create branch's shape.
201
- await db.orm.User
201
+ await db.orm.public.User
202
202
  .select('id', 'email', 'kind', 'createdAt')
203
203
  .upsert({
204
204
  create: { id, email, displayName, kind, createdAt: new Date() },
@@ -211,18 +211,18 @@ The ORM returns inserted / updated rows by default. The `.returning(...)` select
211
211
  ## Workflow — Aggregates
212
212
 
213
213
  ```typescript
214
- const totals = await db.orm.User.aggregate((aggregate) => ({
214
+ const totals = await db.orm.public.User.aggregate((aggregate) => ({
215
215
  totalUsers: aggregate.count(),
216
216
  }));
217
217
 
218
- const adminTotals = await db.orm.User
218
+ const adminTotals = await db.orm.public.User
219
219
  .where({ kind: 'admin' })
220
220
  .aggregate((aggregate) => ({
221
221
  adminUsers: aggregate.count(),
222
222
  }));
223
223
 
224
224
  // Group-by + aggregate.
225
- const byKind = await db.orm.User
225
+ const byKind = await db.orm.public.User
226
226
  .groupBy('kind')
227
227
  .having((having) => having.count().gte(minUsers))
228
228
  .aggregate((aggregate) => ({
@@ -261,7 +261,7 @@ const byKind = await db.orm.User
261
261
  Nullability isn't a typing bug — it's faithful to what the database returns. Coalesce client-side when you want zero-fill:
262
262
 
263
263
  ```typescript
264
- const revenue = await db.orm.Sale
264
+ const revenue = await db.orm.public.Sale
265
265
  .where((s) => s.day.gte(start))
266
266
  .aggregate((a) => ({ total: a.sum('amount') }));
267
267
  // revenue.total: number | null
@@ -271,22 +271,22 @@ const safe = revenue.total ?? 0; // ← apply at the consumption site, not in
271
271
 
272
272
  If `?? 0` is showing up on every aggregate, that's a signal you're calling `sum` (or peers) over potentially-empty filters — which is exactly when SQL returns NULL. The pattern is correct; the typing is honest.
273
273
 
274
- ## Workflow — SQL builder (`db.sql.<table>`)
274
+ ## Workflow — SQL builder (`db.sql.<ns>.<table>`)
275
275
 
276
- The concept: `db.sql.<table>` is a table-shaped builder that produces a *plan*. The plan is a serialisable description of the query (AST + parameters); you execute it through the runtime with `db.runtime().execute(plan)`. The builder gives you the lanes the ORM doesn't express — explicit `JOIN`, arbitrary expression projection, target-specific operations through extension helpers — without dropping to raw SQL.
276
+ The concept: `db.sql.<ns>.<table>` is a table-shaped builder that produces a *plan*. The plan is a serialisable description of the query (AST + parameters); you run it through the runtime. Pick the runtime method by the result the plan declares: `db.runtime().query(plan)` for rows (it returns the same `AsyncIterableResult` as `.all()`, so `await` it for an array), `db.runtime().execute(plan)` for a write with no `RETURNING` (it resolves `{ affectedRows }` and returns no rows). The builder gives you the lanes the ORM doesn't express — explicit `JOIN`, arbitrary expression projection, target-specific operations through extension helpers — without dropping to raw SQL.
277
277
 
278
278
  ```typescript
279
279
  // src/queries/posts.ts — adjust the relative import to match file depth.
280
280
  import { db } from '../prisma/db';
281
281
 
282
282
  // Select with predicate and limit.
283
- const plan = db.sql.post
283
+ const plan = db.sql.public.post
284
284
  .select('id', 'title', 'userId', 'createdAt')
285
285
  .where((f, fns) => fns.eq(f.userId, userId))
286
286
  .limit(limit)
287
287
  .build();
288
288
 
289
- const rows = await db.runtime().execute(plan);
289
+ const rows = await db.runtime().query(plan);
290
290
  ```
291
291
 
292
292
  The `.where(...)` callback receives `(fields, fns)` — `fields` is the field proxy (column references), `fns` is the operator namespace (`fns.eq`, `fns.ne`, `fns.gt`, …). Extensions inject extension-shaped helpers into the same `fns` namespace (`fns.distanceSphere`, `fns.cosineDistance`, etc.).
@@ -294,27 +294,27 @@ The `.where(...)` callback receives `(fields, fns)` — `fields` is the field pr
294
294
  ### `INSERT` / `UPDATE` / `DELETE` with `RETURNING`
295
295
 
296
296
  ```typescript
297
- // Insert and return selected columns.
298
- const plan = db.sql.user
299
- .insert({ email })
297
+ // Insert and return selected columns. `insert()` takes an array of rows.
298
+ const plan = db.sql.public.user
299
+ .insert([{ email }])
300
300
  .returning('id', 'email')
301
301
  .build();
302
- const [row] = await db.runtime().execute(plan);
302
+ const [row] = await db.runtime().query(plan);
303
303
 
304
304
  // Update with predicate and returning.
305
- const updatePlan = db.sql.user
305
+ const updatePlan = db.sql.public.user
306
306
  .update({ email: newEmail })
307
307
  .where((f, fns) => fns.eq(f.id, userId))
308
308
  .returning('id', 'email')
309
309
  .build();
310
- const rows = await db.runtime().execute(updatePlan);
310
+ const rows = await db.runtime().query(updatePlan);
311
311
 
312
- // Delete with predicate.
313
- const deletePlan = db.sql.user
312
+ // Delete with predicate, no RETURNING — `execute` resolves the affected count.
313
+ const deletePlan = db.sql.public.user
314
314
  .delete()
315
315
  .where((f, fns) => fns.eq(f.id, userId))
316
316
  .build();
317
- await db.runtime().execute(deletePlan);
317
+ const { affectedRows } = await db.runtime().execute(deletePlan);
318
318
  ```
319
319
 
320
320
  `.returning(...)` requires the target adapter to advertise the `returning` capability. The Postgres adapter advertises it by default.
@@ -323,36 +323,36 @@ await db.runtime().execute(deletePlan);
323
323
 
324
324
  ```typescript
325
325
  // Project a computed expression alongside model fields.
326
- const plan = db.sql.cafe
326
+ const plan = db.sql.public.cafe
327
327
  .select('id', 'name')
328
328
  .select('meters', (f, fns) => fns.distanceSphere(f.location, point))
329
329
  .orderBy((f, fns) => fns.distanceSphere(f.location, point), { direction: 'asc' })
330
330
  .orderBy((f) => f.id, { direction: 'asc' })
331
331
  .limit(limit)
332
332
  .build();
333
- const rows = await db.runtime().execute(plan);
333
+ const rows = await db.runtime().query(plan);
334
334
 
335
335
  // Self-join with an alias.
336
- db.sql.post
337
- .innerJoin(db.sql.post.as('p2'), (f, fns) => fns.ne(f.p1.userId, f.p2.userId))
336
+ db.sql.public.post
337
+ .innerJoin(db.sql.public.post.as('p2'), (f, fns) => fns.ne(f.p1.userId, f.p2.userId))
338
338
  // ...
339
339
  .build();
340
340
  ```
341
341
 
342
342
  ## Workflow — Transactions
343
343
 
344
- The concept: `db.transaction(fn)` opens a transaction and passes a `tx` context to the callback. `tx.orm` and `tx.sql` mirror `db.orm` / `db.sql` but ride the same transaction; `tx.execute(plan)` executes a SQL-builder plan within it. The transaction commits on the callback's successful return and rolls back on any thrown error.
344
+ The concept: `db.transaction(fn)` opens a transaction and passes a `tx` context to the callback. `tx.orm` and `tx.sql` mirror `db.orm` / `db.sql` but ride the same transaction; `tx.query(plan)` / `tx.execute(plan)` run a SQL-builder plan within it (rows vs affected count, as on the runtime). The transaction commits on the callback's successful return and rolls back on any thrown error.
345
345
 
346
346
  ```typescript
347
347
  await db.transaction(async (tx) => {
348
- const user = await tx.orm.User.create({ id, email });
349
- await tx.orm.Post.create({ userId: user.id, title: 'hello' });
348
+ const user = await tx.orm.public.User.create({ id, email });
349
+ await tx.orm.public.Post.create({ userId: user.id, title: 'hello' });
350
350
 
351
- // SQL-builder plan inside the transaction.
352
- const plan = tx.sql.post.update({ status: 'archived' })
351
+ // SQL-builder plan inside the transaction — no RETURNING, so `execute`.
352
+ const plan = tx.sql.public.post.update({ status: 'archived' })
353
353
  .where((f, fns) => fns.lt(f.createdAt, cutoff))
354
354
  .build();
355
- await tx.execute(plan);
355
+ const { affectedRows } = await tx.execute(plan);
356
356
 
357
357
  // If anything throws, all three operations roll back.
358
358
  });
@@ -362,20 +362,20 @@ The callback's return value passes through `db.transaction(...)`. Capture insert
362
362
 
363
363
  ## Namespace-aware accessors
364
364
 
365
- When the contract declares multiple namespaces, both `db.sql` and `db.orm` expose a namespace coordinate alongside the flat bare-name surface:
365
+ On Postgres both `db.sql` and `db.orm` are keyed by storage namespace (the Postgres schema) — always, not only when a contract declares more than one. A model outside any `namespace { }` block is in `public`:
366
366
 
367
367
  ```typescript
368
368
  // db.sql.<namespace>.<table>
369
- const plan = db.sql.public.users.select('id', 'email').build();
369
+ const plan = db.sql.public.user.select('id', 'email').build();
370
370
  const authPlan = db.sql.auth.users.select('id', 'token').build();
371
- await db.runtime().execute(plan);
371
+ const rows = await db.runtime().query(plan);
372
372
 
373
373
  // db.orm.<namespace>.<Model>
374
374
  const user = await db.orm.public.User.create({ id: 1, email: 'a@x.io' });
375
375
  const authUser = await db.orm.auth.User.create({ id: 2, token: 'tok' });
376
376
  ```
377
377
 
378
- The flat `db.sql.users` / `db.orm.User` form still works when bare names are unique across all namespaces. When the same bare name appears in more than one namespace, use the coordinate form — both the type system and the runtime require it to resolve to the right table.
378
+ There is no flat `db.sql.user` / `db.orm.User` on the Postgres façade (`Db` is "one facet per storage namespace, and nothing else"); reaching for one is a type error. The flat spelling belongs to SQLite, whose façade exposes its single unbound namespace directly — see `references/queries.md` § *Namespace-aware accessors*.
379
379
 
380
380
  Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the same `.include()` syntax; the ORM resolves the correct schema-qualified join automatically.
381
381
 
@@ -385,13 +385,13 @@ Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the sam
385
385
  2. **Using `.all()` when you wanted one row.** `.all()` issues no implicit limit. Use `.first()` or `.first({ pk })`.
386
386
  3. **Coalescing `count()` with `?? 0` "just in case".** `count()` is `number`, not `number | null` — SQL answers an empty set with `0`. The `?? 0` belongs on `sum` / `avg` / `min` / `max`, and its zero should match the aggregate's own type (`0` for an integer sum, `0n` for `sumBigInt`, `'0'` where the result is a decimal string).
387
387
  4. **Reaching for `.between(a, b)` on a field proxy.** It doesn't exist. Either chain `.where((m) => m.field.gte(a)).where((m) => m.field.lte(b))` or use `and(m.field.gte(a), m.field.lte(b))` inside one `.where()` clause.
388
- 5. **Importing `and` / `or` / `not` from a Postgres façade subpath.** The combinators currently live in `@internal/sql-orm-client` — an internal package. See *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md).
389
- 6. **Trying to `db.sql.from(tables.user)`.** That surface does not exist. The builder is table-shaped: `db.sql.<tableName>.select(...)`. There is no `db.schema.tables` either.
390
- 7. **Trying to `db.execute(plan)` directly.** Plans execute through the runtime: `db.runtime().execute(plan)`. Inside a transaction, use `tx.execute(plan)`.
391
- 8. **Setting `capabilities: { lateral: true }` in `prisma.config.ts`.** `defineConfig` does not take `capabilities`. Capabilities are declared by the active adapter and become part of the emitted contract; the Postgres adapter advertises `lateral`, `jsonAgg`, and `returning` out of the box. Enable extension capabilities through `extensions: [...]` in the config (see `references/contract.md`).
392
- 9. **Confabulating a TypedSQL or `.stream()` surface.** Neither exists today. Raw SQL does: the client's raw lane, ``db.raw.sql`…` ``. See *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md) for all three.
393
- 10. **Mixing the ORM mutation return with `runtime.execute(plan)`.** ORM terminals issue the query themselves and return rows. `runtime.execute` is for SQL-builder plans.
394
- 11. **Ordering grouped rows by an aggregate metric.** The grouped collection supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but it cannot order by an aggregate alias such as `SUM(amount)`. Sorting the materialized aggregate result in JS is fine at small cardinalities; for large grouped result sets, drop to `db.sql.<table>`.
388
+ 5. **Importing `and` / `or` / `not` from an internal package.** They are exported from `@prisma/orm-postgres/orm-client`; do not reach into `@internal/sql-orm-client`.
389
+ 6. **Trying to `db.sql.from(tables.user)`.** That surface does not exist. The builder is table-shaped: `db.sql.<ns>.<tableName>.select(...)`. There is no `db.schema.tables` either.
390
+ 7. **Trying to `db.execute(plan)` directly, or reading rows with `execute`.** Plans run through the runtime: `db.runtime().query(plan)` for rows, `db.runtime().execute(plan)` for a non-returning write (`{ affectedRows }`). Inside a transaction, `tx.query(plan)` / `tx.execute(plan)`. `execute` never yields rows — a `select` or `.returning(...)` plan passed to it gives you statistics, not data.
391
+ 8. **Setting `capabilities: { lateral: true }` in `prisma.config.ts`.** The ORM config (`ormConfig({...})`) does not take `capabilities`. Capabilities are declared by the active adapter and become part of the emitted contract; the Postgres adapter advertises `lateral`, `jsonAgg`, and `returning` out of the box. Enable extension capabilities through `extensions: [...]` in the config (see `references/contract.md`).
392
+ 9. **Confabulating a TypedSQL or `.stream()` surface.** Neither exists. Raw SQL does: the client's raw lane, ``db.raw.sql`…` ``. Reusable statements do: `db.prepare(...)` (see *Prepared statements* in [`queries.md`](./queries.md)). Streaming: `for await` over a read terminal or `runtime.query(plan)` — with the caveats in *Streaming* in [`queries.md`](./queries.md).
393
+ 10. **Mixing the ORM mutation return with `runtime.query(plan)` / `runtime.execute(plan)`.** ORM terminals issue the query themselves and return rows. The runtime methods are for SQL-builder plans.
394
+ 11. **Ordering grouped rows by an aggregate metric.** The grouped collection supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but it cannot order by an aggregate alias such as `SUM(amount)`. Sorting the materialized aggregate result in JS is fine at small cardinalities; for large grouped result sets, drop to `db.sql.<ns>.<table>`.
395
395
 
396
396
  ## Reference Files
397
397
 
@@ -408,8 +408,8 @@ Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the sam
408
408
  - [ ] Compared and serialised aggregate *results* as what they are — a `bigint` from a suffixed variant needs `0n` literals and `String(value)` rather than bare `JSON.stringify` — leaving the ORM's `having(...)` operands as numbers, and matching each SQL-builder comparison literal to the aggregate's own result codec (`fns.gt(fns.count(), 1)`).
409
409
  - [ ] Expressed ranges as chained `.where(...)` clauses or a single `and(...)` clause — did NOT reach for a non-existent `.between(...)` operator.
410
410
  - [ ] For cursor pagination, used `.orderBy(...).cursor({ field: lastValue }).limit(n).all()` — did NOT hand-write a `.where(p => p.field.lt(cursor))` workaround when the `.cursor()` API serves the same purpose.
411
- - [ ] For ORM combinators, imported `and` / `or` / `not` from the (currently internal) `@internal/sql-orm-client` and noted the façade gap to the user.
412
- - [ ] Executed SQL-builder plans via `db.runtime().execute(plan)` (or `tx.execute(plan)` inside a transaction).
411
+ - [ ] For ORM combinators, imported `and` / `or` / `not` from `@prisma/orm-postgres/orm-client`.
412
+ - [ ] Ran SQL-builder plans via `db.runtime().query(plan)` when they return rows and `db.runtime().execute(plan)` only for non-returning writes (`tx.query` / `tx.execute` inside a transaction). Passed `insert()` an array of rows.
413
413
  - [ ] Wrapped multi-statement work in `db.transaction(async (tx) => { ... })` where atomicity matters.
414
- - [ ] For top-N grouped aggregates at meaningful scale, dropped to `db.sql.<table>` rather than JS-side sort + slice over `groupBy(...).aggregate(...)`.
415
- - [ ] Did NOT confabulate TypedSQL, `.stream()`, `db.batch`, `.between(...)`, a `capabilities` field on `defineConfig`, or a `db.sql.from(tables.user)` API — routed to *What Prisma 8 doesn't do yet* / `references/feedback.md` instead. Raw SQL is spelled `db.raw.sql`, not `db.sql.raw`.
414
+ - [ ] For top-N grouped aggregates at meaningful scale, dropped to `db.sql.<ns>.<table>` rather than JS-side sort + slice over `groupBy(...).aggregate(...)`.
415
+ - [ ] Did NOT confabulate TypedSQL, `.stream()`, `db.batch`, `.between(...)`, a collection-level `.count()`, a `capabilities` field on the config, or a `db.sql.from(tables.user)` API — routed to *What Prisma 8 doesn't do yet* / `references/feedback.md` instead. Raw SQL is spelled `db.raw.sql`, not `db.sql.raw`.