@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.
- package/package.json +8 -8
- package/skills/prisma-8/SKILL.md +16 -13
- package/skills/prisma-8/references/build.md +1 -1
- package/skills/prisma-8/references/contract.md +60 -31
- package/skills/prisma-8/references/debug.md +44 -41
- package/skills/prisma-8/references/feedback.md +8 -8
- package/skills/prisma-8/references/migration-model.md +2 -2
- package/skills/prisma-8/references/migration-review.md +28 -15
- package/skills/prisma-8/references/migrations.md +86 -70
- package/skills/prisma-8/references/queries-mongo.md +16 -16
- package/skills/prisma-8/references/queries-postgres.md +78 -78
- package/skills/prisma-8/references/queries.md +54 -28
- package/skills/prisma-8/references/quickstart.md +34 -43
- package/skills/prisma-8/references/runtime.md +76 -54
- package/skills/prisma-8/references/supabase.md +15 -28
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.9-to-8.0.0-rc.10/instructions.md +42 -1
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.9-to-8.0.0-rc.10/instructions.md +29 -1
|
@@ -28,22 +28,22 @@ Prisma 8 ships **two query lanes per target** on the same `db` value from `src/p
|
|
|
28
28
|
|
|
29
29
|
| Runtime import in `db.ts` | Load |
|
|
30
30
|
| --- | --- |
|
|
31
|
-
| `@internal/postgres/runtime` | [`queries-postgres.md`](./queries-postgres.md) — `db.orm.<Model>` + `db.sql.<table>` |
|
|
31
|
+
| `@internal/postgres/runtime` | [`queries-postgres.md`](./queries-postgres.md) — `db.orm.<ns>.<Model>` + `db.sql.<ns>.<table>` |
|
|
32
32
|
| `@internal/mongo/runtime` | [`queries-mongo.md`](./queries-mongo.md) — `db.orm.<root>` + `db.query.from(...)` |
|
|
33
|
-
| `@internal/extension-supabase/runtime` | [`queries-postgres.md`](./queries-postgres.md) — a Supabase `RoleBoundDb` is a Postgres surface (`db.orm.<Model>` + `db.sql.<table>`); bind a role first via `references/supabase.md` |
|
|
33
|
+
| `@internal/extension-supabase/runtime` | [`queries-postgres.md`](./queries-postgres.md) — a Supabase `RoleBoundDb` is a Postgres surface (`db.orm.<ns>.<Model>` + `db.sql.<ns>.<table>`); bind a role first via `references/supabase.md` |
|
|
34
34
|
|
|
35
35
|
Both targets share the contract and connection on one `db` value. Reach for the ORM first; drop to the lower-level lane when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app.
|
|
36
36
|
|
|
37
|
-
**Do not mix target examples.** Postgres uses PascalCase model roots (`db.orm.User`) and `db.sql.user`; Mongo uses lowercased plural roots (`db.orm.users`) and `db.query.from('users')`. There is no `db.sql` on Mongo and no `db.query` SQL-builder equivalent on Postgres.
|
|
37
|
+
**Do not mix target examples.** Postgres uses PascalCase model roots (`db.orm.public.User`) and `db.sql.public.user`; Mongo uses lowercased plural roots (`db.orm.users`) and `db.query.from('users')`. There is no `db.sql` on Mongo and no `db.query` SQL-builder equivalent on Postgres.
|
|
38
38
|
|
|
39
39
|
## Namespace-aware accessors
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
On Postgres, models and tables are **always** addressed by namespace coordinate — the storage namespace is the Postgres schema, and a model declared outside any `namespace { }` block lands in `public`:
|
|
42
42
|
|
|
43
43
|
- **ORM**: `db.orm.<namespace>.<Model>` — e.g. `db.orm.public.User`, `db.orm.auth.User`
|
|
44
|
-
- **SQL builder**: `db.sql.<namespace>.<table>` — e.g. `db.sql.public.
|
|
44
|
+
- **SQL builder**: `db.sql.<namespace>.<table>` — e.g. `db.sql.public.user`, `db.sql.auth.users`
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
There is no flat `db.orm.User` / `db.sql.user` on the Postgres façade: `db.sql` is one facet per storage namespace and nothing else, and `db.orm` is keyed the same way (`examples/prisma-8-demo` uses `db.orm.public.User` and `db.sql.public.user` throughout). SQLite has no schemas, so its façade exposes the single unbound namespace directly — `db.orm.User` and `db.sql.user` are the SQLite spellings (`examples/prisma-8-demo-sqlite`). Mongo is keyed by collection storage name (`db.orm.users`).
|
|
47
47
|
|
|
48
48
|
See [`queries-postgres.md` § Namespace-aware accessors](./queries-postgres.md#namespace-aware-accessors) for a worked example.
|
|
49
49
|
|
|
@@ -52,7 +52,7 @@ See [`queries-postgres.md` § Namespace-aware accessors](./queries-postgres.md#n
|
|
|
52
52
|
Critical to get right early — on **both Postgres and Mongo**, `.all()` returns an **`AsyncIterableResult<Row>`**, which is *both* a `PromiseLike<Row[]>` and an `AsyncIterable<Row>`. That means three consumption forms all work, and the canonical one is the shortest:
|
|
53
53
|
|
|
54
54
|
```typescript
|
|
55
|
-
const users = await db.orm.User.select('id', 'email').all();
|
|
55
|
+
const users = await db.orm.public.User.select('id', 'email').all();
|
|
56
56
|
// ^? Row[] ← the Thenable resolves to a real array. This is the default idiom.
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -67,12 +67,11 @@ You do **not** need a `collect()` / `toArray()` helper — `await` is enough. In
|
|
|
67
67
|
// `Promise.race` combinators all accept the thenable directly — those are
|
|
68
68
|
// NOT reasons to call `.toArray()`. Whenever you are just going to await it
|
|
69
69
|
// here, use `await ...all()` and skip `.toArray()`.
|
|
70
|
-
const rows: Promise<User[]> = db.orm.User.select('id', 'email').all().toArray();
|
|
70
|
+
const rows: Promise<User[]> = db.orm.public.User.select('id', 'email').all().toArray();
|
|
71
71
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
for await (const user of db.orm.User.select('id', 'email').all()) {
|
|
72
|
+
// Iterate — decode and handle rows one at a time. Whether the raw rows are
|
|
73
|
+
// also fetched incrementally depends on the façade; see *Streaming* below.
|
|
74
|
+
for await (const user of db.orm.public.User.select('id', 'email').all()) {
|
|
76
75
|
process(user);
|
|
77
76
|
}
|
|
78
77
|
```
|
|
@@ -80,9 +79,9 @@ for await (const user of db.orm.User.select('id', 'email').all()) {
|
|
|
80
79
|
Two single-row shortcuts also exist on the result, in addition to the collection-level `.first()` (which issues `LIMIT 1` on Postgres):
|
|
81
80
|
|
|
82
81
|
```typescript
|
|
83
|
-
const user = await db.orm.User.where({ id }).all().first();
|
|
82
|
+
const user = await db.orm.public.User.where({ id }).all().first();
|
|
84
83
|
// ^? Row | null ← buffers, returns the first row or null. Issues no LIMIT.
|
|
85
|
-
const required = await db.orm.User.where({ id }).all().firstOrThrow();
|
|
84
|
+
const required = await db.orm.public.User.where({ id }).all().firstOrThrow();
|
|
86
85
|
// ^? Row ← buffers; throws `RUNTIME.NO_ROWS` if empty.
|
|
87
86
|
```
|
|
88
87
|
|
|
@@ -92,12 +91,12 @@ For genuine single-row reads, prefer the *collection*-level `.first()` (which ad
|
|
|
92
91
|
|
|
93
92
|
```typescript
|
|
94
93
|
// Bad — second await throws RUNTIME.ITERATOR_CONSUMED.
|
|
95
|
-
const result = db.orm.User.select('id', 'email').all();
|
|
94
|
+
const result = db.orm.public.User.select('id', 'email').all();
|
|
96
95
|
const a = await result;
|
|
97
96
|
const b = await result;
|
|
98
97
|
|
|
99
98
|
// Good — buffer once, reuse the array.
|
|
100
|
-
const users = await db.orm.User.select('id', 'email').all();
|
|
99
|
+
const users = await db.orm.public.User.select('id', 'email').all();
|
|
101
100
|
const a = users;
|
|
102
101
|
const b = users;
|
|
103
102
|
```
|
|
@@ -114,7 +113,7 @@ import { db } from '../prisma/db';
|
|
|
114
113
|
|
|
115
114
|
// Postgres — PascalCase model root from contract
|
|
116
115
|
for (const u of users) {
|
|
117
|
-
await db.orm.User.create(u);
|
|
116
|
+
await db.orm.public.User.create(u);
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
// Mongo — lowercased plural root from contract (e.g. users, not User)
|
|
@@ -126,11 +125,40 @@ console.log('Seeded.');
|
|
|
126
125
|
await db.close();
|
|
127
126
|
```
|
|
128
127
|
|
|
128
|
+
## Streaming
|
|
129
|
+
|
|
130
|
+
Every read terminal (`.all()`, and `runtime.query(plan)` for a SQL-builder plan) returns an `AsyncIterableResult`, so `for await` is always available. What it buys you depends on the façade:
|
|
131
|
+
|
|
132
|
+
- **Long-lived `postgres()` façade** (the usual `db.ts`): the driver runs with cursors disabled. The full result set is fetched from the server before the first row is yielded; only *decoding* happens per row. `for await` therefore does not bound the memory held by the raw result. For very large sets, paginate (`.limit()` / `.offset()`, or `.orderBy(...).cursor(...)`) instead.
|
|
133
|
+
- **Serverless façade** (`@prisma/orm-postgres/serverless`, one `connect()` per invocation): the driver reads through a server-side cursor in batches of 100 rows by default (`cursor: { batchSize }` on the façade options), so `for await` really does stream.
|
|
134
|
+
|
|
135
|
+
There is no `.stream()` method on either façade.
|
|
136
|
+
|
|
137
|
+
## Prepared statements (Postgres, SQLite)
|
|
138
|
+
|
|
139
|
+
`db.prepare(declaration, (sql, params) => plan)` builds a statement once and binds it per call. The declaration names each parameter's codec (`{ email: 'pg/text@1' }`); the callback receives the façade's `sql` builder plus typed `params` and returns a plan. A row-returning plan gives a `PreparedStatement` you run with `ps.query(runtime, params)`; a plan whose result is an affected count gives a `PreparedExecution` you run with `ps.execute(runtime, params)`. Declaring a parameter the plan never references throws `RUNTIME.PREPARE_UNUSED_PARAM`. `runtime.prepare(declaration, (params) => plan)` is the same thing on a `Runtime`.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// examples/prisma-8-demo/src/queries/get-user-by-email-prepared.ts
|
|
143
|
+
const ps = await db.prepare({ email: 'pg/text@1' }, (sql, params) =>
|
|
144
|
+
sql.public.user
|
|
145
|
+
.select('id', 'email', 'displayName', 'createdAt', 'kind')
|
|
146
|
+
.where((f, fns) => fns.eq(f.email, params.email))
|
|
147
|
+
.limit(1)
|
|
148
|
+
.build(),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const runtime = db.runtime();
|
|
152
|
+
for (const email of emails) {
|
|
153
|
+
const rows = await ps.query(runtime, { email });
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
129
157
|
## Naming model and result types
|
|
130
158
|
|
|
131
159
|
The model is the whole row plus its relations, and each related model carries its own relations in turn, so no query returns a value of the model type. A query returns the fields it fetched. The default fetch returns `Scalars<Model>`, the model without relations: `db.orm.public.User.first()` returns `Scalars<Model> | null`, and `db.orm.public.User.all()` returns `Scalars<Model>[]` (or its async iterable). Four types cover every case, and none needs a client in scope:
|
|
132
160
|
|
|
133
|
-
- `Models.<ns>_<Model>` (from `contract.d.ts`) — every scalar field and every relation. On SQLite, which has no schemas, the name is bare: `Models.User` and `typeof models.User`. On Postgres the schema is part of the name: `Models.public_User`, or `typeof models.public.User` by dotted access
|
|
161
|
+
- `Models.<ns>_<Model>` (from `contract.d.ts`) — every scalar field and every relation. On SQLite, which has no schemas, the name is bare: `Models.User` and `typeof models.User`. On Postgres the schema is part of the name: `Models.public_User`, or `typeof models.public.User` by dotted access — a model declared outside any `namespace { }` block is in `public`, so that is also its name. Mongo names its models the same way. A polymorphic base also emits one member per variant and an `Any<Base>` union (`Models.public_AnyTask`).
|
|
134
162
|
- `Scalars<M>` — the model without relations; what a default fetch returns. Distributes over unions, so `Scalars<Models.public_AnyTask>` is the union of variant rows.
|
|
135
163
|
- `Shape<M, Spec>` — a data structure derived from the model, for declaring an endpoint's response type once and having the compiler check the body at the `return`. At every level of `Spec`: `'+'` is a union of scalar and relation names to keep (a relation named there comes with all of its scalars and none of its relations; the scalars are narrowed only when `'+'` names a scalar, so `'+': 'posts'` alone is every scalar plus posts); `'-'` is a union of scalar names to drop; `'+'` naming a scalar beside `'-'` is a compile error, while `{ '-': 'passwordHash'; '+': 'posts' }` is every scalar but the hash plus posts; any other key is a relation whose value is a nested spec that narrows the related model. Relations are absent unless asked for; `X[]`, `X | null`, or `X` comes from the model. Wrong names, a relation in `'-'`, a non-object relation value, and a relation both in `'+'` and as a key are compile errors. No `where`/`orderBy`/`limit`; compose extras with TypeScript (`Shape<M> & { postCount: number }`).
|
|
136
164
|
- `ResultType<typeof query>` — the row of any ORM collection value (plain, `.include()`, `.select()`, `.variant()`), and of SQL lane plans. Bind the query to a name first; `typeof` needs a value.
|
|
@@ -179,18 +207,16 @@ Target-specific pitfalls live in the per-target guides.
|
|
|
179
207
|
|
|
180
208
|
## What Prisma 8 doesn't do yet
|
|
181
209
|
|
|
182
|
-
- **
|
|
183
|
-
- **
|
|
184
|
-
- **`and` / `or` / `not` combinators in the postgres façade.** The combinators currently import from `@internal/sql-orm-client` (an internal package). Workaround today: import them from `@internal/sql-orm-client` directly, the way the example apps do. If you want them on `@internal/postgres/runtime`, file a feature request via `references/feedback.md`.
|
|
185
|
-
- **Ordering grouped aggregates by an aggregate alias (Postgres).** `db.orm.<Model>.groupBy(...)` supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but the grouped collection cannot order by an aggregate alias such as `SUM(amount)`. A "top-N groups by SUM" query therefore falls back to JS-side sort + slice over the full grouped result, which is fine at small cardinalities and bad at scale. Workarounds: (a) drop to `db.sql.<table>` and write the `GROUP BY` + `ORDER BY` + `LIMIT` against the aggregated table directly; (b) live with the JS-side sort/slice if the grouped cardinality is bounded. File a feature request via `references/feedback.md` if this is hitting you in production.
|
|
210
|
+
- **Many-to-many relations work through the junction.** `.include('tags', (tag) => tag.select(...))` traverses an N:M relation's `through` table, and nested `create` / `connect` / `disconnect` on an N:M relation write the junction rows for you (`examples/prisma-8-demo/src/orm-client/get-post-tags.ts`, `create-post-with-tags.ts`). The one refusal: a junction with required payload columns the relation API cannot populate throws `ORM.RELATION_MUTATION_UNSUPPORTED` — write that junction directly or use the SQL builder.
|
|
211
|
+
- **Ordering grouped aggregates by an aggregate alias (Postgres).** `db.orm.<ns>.<Model>.groupBy(...)` supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but the grouped collection cannot order by an aggregate alias such as `SUM(amount)`. A "top-N groups by SUM" query therefore falls back to JS-side sort + slice over the full grouped result, which is fine at small cardinalities and bad at scale. Workarounds: (a) drop to `db.sql.<ns>.<table>` and write the `GROUP BY` + `ORDER BY` + `LIMIT` against the aggregated table directly; (b) live with the JS-side sort/slice if the grouped cardinality is bounded. File a feature request via `references/feedback.md` if this is hitting you in production.
|
|
186
212
|
- **A raw-SQL lane.** This one exists. Write whole-query raw SQL through the client's raw lane: ``db.raw.sql`SELECT ...`.returnsRow({ ... }).build()`` for rows, or `.affectedCount()` for a mutation's row count. Each declared column names the codec that decodes it, so the row stays typed. For an expression fragment inside a builder query, use `fns.raw` in a `.select(...)` callback instead.
|
|
187
|
-
- **TypedSQL (`.sql` files compiled into typed callables).** Not implemented.
|
|
213
|
+
- **TypedSQL (`.sql` files compiled into typed callables).** Not implemented. For a repeated query, use `db.prepare(...)` (see *Prepared statements* above) or a function that returns the built plan and `db.runtime().query(plan)` at the call site. If you want a `.sql`-file compile path, file a feature request via `references/feedback.md`.
|
|
188
214
|
- **`EXPLAIN` / query-plan inspection.** Prisma 8 does not expose an `.explain()` method. Workaround: connect a `pg.Pool` you control via the runtime's `pg:` binding (see `references/runtime.md`) and issue `EXPLAIN ANALYZE` through it. If you want a first-class plan-inspection surface, file a feature request via `references/feedback.md`.
|
|
189
|
-
- **
|
|
215
|
+
- **Cursor-backed streaming on the long-lived façade.** `for await` works everywhere, but on `postgres()` the raw result is fetched in full before iteration (see *Streaming* above); only the serverless façade reads through a cursor. Paginate for very large sets on the long-lived façade. If you want cursor streaming there, file a feature request via `references/feedback.md`.
|
|
190
216
|
- **Multi-statement batching (Prisma-7-style `db.$transaction([call1, call2])`).** Prisma 8 runs each call sequentially. Workaround: wrap atomically-related work in `db.transaction(async (tx) => { ... })` on Postgres. If you want batch-as-array semantics, file a feature request via `references/feedback.md`.
|
|
191
217
|
- **Mongo façade transactions.** `@internal/mongo/runtime` does not expose `db.transaction(...)`. Multi-document atomicity is not yet wrapped in the Prisma 8 Mongo façade. Workaround: use the MongoDB driver's session API directly if you control the client binding (`mongoClient:` option). File a feature request via `references/feedback.md` if you need a first-class façade surface.
|
|
192
|
-
- **Mongo ORM aggregates.** No `.aggregate(...)` / `.groupBy(...)` on `db.orm.<root>`. Workaround: express aggregations through `db.query.from(...).group(...).build()` and `runtime.
|
|
193
|
-
- **Mongo filter helpers on the façade.** Rich filters (`.in`, ranges, boolean composition) currently import from `@
|
|
218
|
+
- **Mongo ORM aggregates.** No `.aggregate(...)` / `.groupBy(...)` on `db.orm.<root>`. Workaround: express aggregations through `db.query.from(...).group(...).build()` and `runtime.query(plan)`.
|
|
219
|
+
- **Mongo filter helpers on the façade.** Rich filters (`.in`, ranges, boolean composition) currently import from `@prisma/orm-mongo/query-ast/execution` (`MongoFieldFilter`, etc.) — not re-exported on `@internal/mongo/runtime`. Workaround: use object equality `.where({ field: value })` where possible; import from the internal package only when necessary. Tracked alongside façade-completeness gaps in Linear `TML-2526`.
|
|
194
220
|
- **Automatic N+1 detection.** Prisma 8 does not warn when an `.include(...)` is missing. Workaround: be deliberate about `.include(...)` in code review; the `lints` middleware (see `references/runtime.md`) catches the more common authoring slips (missing `WHERE` on a `DELETE` / `UPDATE`, missing `LIMIT` on a `SELECT`).
|
|
195
221
|
|
|
196
222
|
## Reference Files
|
|
@@ -203,9 +229,9 @@ This skill is split for selective loading. Target-specific reference paths live
|
|
|
203
229
|
## Checklist
|
|
204
230
|
|
|
205
231
|
- [ ] Confirmed the active target from `db.ts` and loaded the matching guide ([`queries-postgres.md`](./queries-postgres.md) or [`queries-mongo.md`](./queries-mongo.md)).
|
|
206
|
-
- [ ]
|
|
232
|
+
- [ ] On Postgres, used `db.orm.<ns>.<Model>` / `db.sql.<ns>.<table>` coordinates (usually `public`) — not a flat `db.orm.User`, which exists only on SQLite.
|
|
207
233
|
- [ ] Chose the right lane (ORM by default; lower-level builder for shapes the ORM doesn't express).
|
|
208
234
|
- [ ] Used `.first()` / `.first({ pk })` (Postgres) or `.where({ ... }).first()` (Mongo) for single-row reads — not `.all()`.
|
|
209
|
-
- [ ] Consumed `.all()` with plain `await` (not a `collect()` / `toArray()` helper). Used `for await` only when
|
|
235
|
+
- [ ] Consumed `.all()` with plain `await` (not a `collect()` / `toArray()` helper). Used `for await` only when per-row handling is actually wanted — and did not promise it bounds memory on the long-lived façade — and never iterated the same result twice.
|
|
210
236
|
- [ ] Did NOT use `db.sql` on a Mongo project or `db.query` where the Postgres SQL builder is meant.
|
|
211
237
|
- [ ] Completed the target-specific checklist in the loaded guide.
|
|
@@ -34,7 +34,7 @@ This skill does **not** cover migrating from another ORM (Drizzle, Prisma 6/7, S
|
|
|
34
34
|
- **Authoring mode**: how you write the contract. `psl` (Prisma Schema Language, default) or `typescript` (programmatic builder, optionally paired with the Vite plugin for auto-emit during `vite dev` — see `references/build.md`).
|
|
35
35
|
- **Façade packages.** The scaffold installs exactly one façade per target — `@internal/postgres` (or `@internal/mongo`). User code imports from façade subpaths (`@internal/postgres/config`, `@internal/postgres/runtime`, `@internal/postgres/contract-builder`). The façade bakes in the family / target / adapter / driver wiring; never reach past it. See `references/contract.md` for the full list.
|
|
36
36
|
- **`db.ts`**: the runtime entry point. Lives next to the contract source at `src/prisma/db.ts`. Imports the contract artefacts and exports a `db` value the rest of the app uses.
|
|
37
|
-
- **Marker**: a `
|
|
37
|
+
- **Marker**: a row in the `prisma_contract.marker` table (Postgres) or a document in the `_prisma_migrations` collection (Mongo) that records the contract hash per contract space. Lets PN detect drift between contract and live DB. Created by `db init` (greenfield / first-touch orientation) or `db sign` (brownfield).
|
|
38
38
|
|
|
39
39
|
### Canonical on-disk layout
|
|
40
40
|
|
|
@@ -70,7 +70,7 @@ Three things to internalise:
|
|
|
70
70
|
|
|
71
71
|
**Contributors building extension packages or aggregate-root monorepo packages use a different layout** — `src/contract.{prisma,ts}` (no `prisma/` subdir) + `migrations/<timestamp>_<slug>/` (no `app/` segment). That distinction is intentional; see `references/contract.md` for which path applies to you.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
`prisma orm init` scaffolds this layout by default: the contract at `src/prisma/contract.prisma` (or `.ts`) and `db.ts` beside it. A project scaffolded by an older init may have a top-level `prisma/` directory instead — read the `contract` path in `prisma.config.ts` rather than assuming either.
|
|
74
74
|
|
|
75
75
|
## Your first arc — connect, write, read
|
|
76
76
|
|
|
@@ -85,18 +85,20 @@ import { db } from './prisma/db';
|
|
|
85
85
|
|
|
86
86
|
// Write a row against the starter model. Adapt the field names to whatever
|
|
87
87
|
// model your contract source actually declares — read it first.
|
|
88
|
-
await db.orm.User.create({ email: 'alice@example.com' });
|
|
88
|
+
await db.orm.public.User.create({ email: 'alice@example.com' });
|
|
89
89
|
|
|
90
90
|
// Read it back.
|
|
91
|
-
const users = await db.orm.User.select('id', 'email').all();
|
|
91
|
+
const users = await db.orm.public.User.select('id', 'email').all();
|
|
92
92
|
console.log(users);
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
If that prints `[{ id: 1, email: 'alice@example.com' }]`, the project is wired end-to-end and the user has crossed from *"I have a project"* to *"I'm building."*
|
|
96
96
|
|
|
97
|
-
`db.orm.<Model>` is the default ORM lane — model-shaped, fully typed against the contract, lazily connects to the database on first use (it picks up `DATABASE_URL` from `.env` via the runtime's `dotenv/config`-loaded environment). The deeper `references/queries.md` reference covers the rest of the supported surface (filters, joins, transactions, the SQL builder) when the user is ready — and names the gaps (
|
|
97
|
+
`db.orm.<ns>.<Model>` is the default ORM lane — model-shaped, fully typed against the contract, lazily connects to the database on first use (it picks up `DATABASE_URL` from `.env` via the runtime's `dotenv/config`-loaded environment). The deeper `references/queries.md` reference covers the rest of the supported surface (filters, joins, transactions, the SQL builder, raw SQL via `db.raw.sql`, prepared statements) when the user is ready — and names the gaps (TypedSQL is not available).
|
|
98
98
|
|
|
99
|
-
> **
|
|
99
|
+
> **SQLite target:** `prisma orm init` scaffolds only `postgres` and `mongodb`; a SQLite project is wired by hand with the `@internal/sqlite` façade (`references/runtime.md` § *Switch between Postgres, SQLite, and Mongo*; `examples/prisma-8-demo-sqlite`). SQLite has no schemas, so that façade exposes the unbound namespace directly — write `db.orm.User` rather than `db.orm.public.User`.
|
|
100
|
+
>
|
|
101
|
+
> **Mongo target:** the snippet above is SQL-target shape. On `@internal/mongo`, `db.orm` is keyed by the collection's storage name (`@@map(...)`, or the lowercased model name if no `@@map`), so the same arc reads `await db.orm.users.create(...)` / `await db.orm.users.select('id', 'email').all()` — not `db.orm.public.User`. Full rule and rewrite recipe in `references/queries.md` § *MongoDB ORM addressing*.
|
|
100
102
|
|
|
101
103
|
**Prerequisites for the arc to work.** All three paths leave these in place by the time you reach the arc:
|
|
102
104
|
|
|
@@ -127,10 +129,10 @@ The first **arc** — once oriented — is **connect → write → read**. Not e
|
|
|
127
129
|
Before saying anything specific to the user, read:
|
|
128
130
|
|
|
129
131
|
- `prisma.config.ts` at the repo root — what target (`postgres` / `mongodb`) is wired, what `contract:` path it declares, what extensions are installed.
|
|
130
|
-
- The contract source the config declares (canonically `src/prisma/contract.prisma` or `src/prisma/contract.ts`; a project
|
|
132
|
+
- The contract source the config declares (canonically `src/prisma/contract.prisma` or `src/prisma/contract.ts`; a project scaffolded by an older init may have it at `prisma/contract.{prisma,ts}` instead — check the `contract` field of the config) — what starter models, if any, exist.
|
|
131
133
|
- `src/prisma/db.ts` (next to the contract) — the runtime entry point.
|
|
132
134
|
- `.env` / `.env.example` — is `DATABASE_URL` set, or only the example?
|
|
133
|
-
- Optionally `pnpm prisma
|
|
135
|
+
- Optionally `pnpm prisma db verify` — does the live DB match the contract? (Exit `4` with findings means drift or no marker; `2` means it could not run.)
|
|
134
136
|
|
|
135
137
|
Then **say the contract path back to the user, with its role attached**. Something like: *"Your contract is at `src/prisma/contract.prisma`, and it currently declares a `User` model. The contract describes your app — every query type, migration, and runtime type the framework gives you flows from this file. Let's get your app connected to a database next."* The exact wording is up to the agent; what matters is that the user leaves the first response knowing *where the contract is* and *that it is the source of truth*.
|
|
136
138
|
|
|
@@ -139,9 +141,9 @@ Then **say the contract path back to the user, with its role attached**. Somethi
|
|
|
139
141
|
The motivation is *"so your app can actually run against your database"*, not *"so the prerequisite checklist passes"*. The mechanics depend on what's already in place from Step 1:
|
|
140
142
|
|
|
141
143
|
- **Everything already wired.** Go straight to writing and reading a row (see *Your first arc — connect, write, read* above). Adapt the snippet to whatever model the contract declares.
|
|
142
|
-
- **`DATABASE_URL` not set.** Have the user set it in `.env` (not in `prisma.config.ts` — see Pitfall 5). Then `pnpm prisma
|
|
143
|
-
- **Database is connectable but not yet aware of the contract** (marker row missing; `db verify` reports drift). Run `pnpm prisma
|
|
144
|
-
- **Contract is empty** (bootstrap left the source blank). Add **one** model with **two** fields (e.g. `User { id, email }`), `pnpm prisma
|
|
144
|
+
- **`DATABASE_URL` not set.** Have the user set it in `.env` (not in `prisma.config.ts` — see Pitfall 5). Then `pnpm prisma db init` to apply the current contract to that database and write the marker row. Now the app can connect.
|
|
145
|
+
- **Database is connectable but not yet aware of the contract** (marker row missing; `db verify` reports drift). Run `pnpm prisma db init`. (`db update` is the alternative for quick dev cycles — it's looser, doesn't write a migration history, and is what users reach for when they want to iterate on the schema fast. Mention it if the user asks how to make schema changes flow to the DB; don't pre-explain it.)
|
|
146
|
+
- **Contract is empty** (bootstrap left the source blank). Add **one** model with **two** fields (e.g. `User { id, email }`), `pnpm prisma contract emit`, then `pnpm prisma db init`. Minimal — get the round-trip working, *then* extend.
|
|
145
147
|
|
|
146
148
|
The user encounters `db init` (and optionally `db update`, `contract emit`) here because they're the commands their current move *requires*. They learn what those commands are by using them.
|
|
147
149
|
|
|
@@ -180,46 +182,36 @@ pnpm dlx prisma@latest orm init # interactive
|
|
|
180
182
|
pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl
|
|
181
183
|
```
|
|
182
184
|
|
|
183
|
-
> **Telemetry is opt-out.** The CLI collects anonymous usage data by default. Every command — including `init` — prints a one-time notice to **stderr** on first use, then sends; there is no interactive consent prompt. Opt out anytime by running `prisma telemetry disable`, or with `DO_NOT_TRACK=1` or `
|
|
185
|
+
> **Telemetry is opt-out.** The CLI collects anonymous usage data by default. Every command — including `init` — prints a one-time notice to **stderr** on first use, then sends; there is no interactive consent prompt. Opt out anytime by running `prisma telemetry disable`, or with `DO_NOT_TRACK=1` or `PRISMA_DISABLE_TELEMETRY=1`. The command stores `"enableTelemetry": false` in your user config for you (the CLI's per-user config dir, **not** `prisma.config.ts`). Run `prisma telemetry status` to see what's currently in effect. This is relevant for agent-driven runs — the CLI records that an agent invoked it. What's collected, the per-user config path, and how to fully reset are documented in `docs/Telemetry.md`.
|
|
184
186
|
|
|
185
187
|
The flags `init` accepts (run `prisma orm init --help` for the source of truth):
|
|
186
188
|
|
|
187
189
|
- `--target <db>` — `postgres` or `mongodb`.
|
|
188
190
|
- `--authoring <style>` — `psl` or `typescript`.
|
|
189
|
-
- `--schema-path <path>` —
|
|
190
|
-
- `--confirm <directory name>` — grant the reinit consent non-interactively. Re-running init in a scaffolded directory asks you to type the directory name back before it overwrites; non-interactive runs pass the name with this flag instead.
|
|
191
|
+
- `--schema-path <path>` — where to write the starter schema. Defaults to `src/prisma/contract.prisma` (or `src/prisma/contract.ts` with `--authoring typescript`), the canonical layout above. The extension must agree with `--authoring` (`CLI.INIT_AUTHORING_SCHEMA_PATH_MISMATCH` otherwise).
|
|
192
|
+
- `--confirm <directory name>` — grant the reinit consent non-interactively. Re-running init in a scaffolded directory asks you to type the directory name back before it overwrites; non-interactive runs pass the name with this flag instead (`--yes` does not grant it).
|
|
191
193
|
- `--write-env` — also write `.env` (default writes only `.env.example`; `.env` stays under your control).
|
|
192
194
|
- `--probe-db` — connect to `DATABASE_URL` once and check the server version against the target's minimum.
|
|
193
|
-
- `--strict-probe` — fail init if the probe fails (
|
|
195
|
+
- `--strict-probe` — fail init if the probe fails (errors without `--probe-db`).
|
|
194
196
|
- `--skip-install` — skip dependency install + initial contract emit.
|
|
195
|
-
- `--
|
|
197
|
+
- `--keep-previous-facade` — when re-running init to switch targets, keep the previous target package in `package.json`.
|
|
198
|
+
|
|
199
|
+
`init` does not install agent skills and has no `--skip-skills` flag: the `prisma-8` skill ships inside the `@prisma/orm-*` package the project installs, and the family-level `prisma init` / `prisma skills sync` commands copy it into the agent harness directories.
|
|
196
200
|
|
|
197
201
|
`init` writes (when it runs cleanly):
|
|
198
202
|
|
|
199
|
-
- `prisma.config.ts` at the project root.
|
|
200
|
-
- The contract source at `--schema-path`
|
|
203
|
+
- `prisma.config.ts` at the project root (envelope form — see `references/contract.md`).
|
|
204
|
+
- The contract source at `--schema-path` (`src/prisma/contract.prisma` by default).
|
|
201
205
|
- `db.ts` in the same directory as the contract source.
|
|
202
|
-
- `prisma-
|
|
206
|
+
- `prisma-8.md` — a human quick-reference.
|
|
203
207
|
- `.env.example` (and `.env` if `--write-env`).
|
|
204
|
-
- Updates `package.json` (deps +
|
|
205
|
-
- Installs deps and runs `prisma
|
|
206
|
-
- Registers Prisma 8 skills with the local agent runtime.
|
|
207
|
-
|
|
208
|
-
**If you took `init`'s default and ended up with a top-level `prisma/` directory** (TML-2532), the cleanup is one move + one config edit:
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
mkdir -p src && mv prisma src/prisma
|
|
212
|
-
# Then update prisma.config.ts so `contract` reads
|
|
213
|
-
# 'src/prisma/contract.prisma' (or .ts) instead of 'prisma/contract.prisma'.
|
|
214
|
-
pnpm prisma-cli contract emit # re-emits contract.json + contract.d.ts under src/prisma/
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
Do this before running `db init` — once the marker row is written, restructuring is harder.
|
|
208
|
+
- Updates `package.json` (deps + a `contract:emit` script) and `tsconfig.json` (required compiler options).
|
|
209
|
+
- Installs deps and runs `prisma contract emit` once. If the install or the emit fails, the scaffold is still on disk and init exits `4` (`CLI.INIT_INSTALL_FAILED`) or `5` (`CLI.INIT_EMIT_FAILED`) with the step to re-run.
|
|
218
210
|
|
|
219
211
|
After init succeeds, the path converges on *Your first arc — connect, write, read* above. `init` has already seeded a starter contract with `User` and `Post` models (with a relation between them) and run `contract emit` once; the only remaining prerequisites are setting `DATABASE_URL` and initialising the database. Two commands:
|
|
220
212
|
|
|
221
213
|
1. Set `DATABASE_URL` in `.env` (copy from `.env.example`).
|
|
222
|
-
2. Initialise the database: `pnpm prisma
|
|
214
|
+
2. Initialise the database: `pnpm prisma db init`. Creates tables, indexes, constraints, and writes the marker row — using the starter contract `init` generated.
|
|
223
215
|
|
|
224
216
|
Then run the snippet from *Your first arc* above against the `User` model. When the user is ready to extend the contract — add more models, change fields, add relations — chain to `references/contract.md`. For more queries, chain to `references/queries.md`.
|
|
225
217
|
|
|
@@ -232,15 +224,14 @@ The concept: against an existing database with no PN contract, `contract infer`
|
|
|
232
224
|
```bash
|
|
233
225
|
mkdir my-app && cd my-app
|
|
234
226
|
pnpm init
|
|
235
|
-
pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl
|
|
236
|
-
|
|
237
|
-
# scaffold lands; you'll overwrite the starter schema below
|
|
227
|
+
pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl
|
|
228
|
+
# scaffold lands at src/prisma/; you'll overwrite the starter schema below
|
|
238
229
|
```
|
|
239
230
|
|
|
240
231
|
Then, with `DATABASE_URL` set in `.env`:
|
|
241
232
|
|
|
242
233
|
```bash
|
|
243
|
-
pnpm prisma
|
|
234
|
+
pnpm prisma contract infer --db "$DATABASE_URL" --output src/prisma/contract.prisma
|
|
244
235
|
```
|
|
245
236
|
|
|
246
237
|
(Note: the flag is `--output`, not `--out`. Run `prisma contract infer --help` for the full surface.)
|
|
@@ -255,9 +246,9 @@ The agent should pause here and read the inferred PSL. Symptoms a re-author pass
|
|
|
255
246
|
Then re-emit and sign:
|
|
256
247
|
|
|
257
248
|
```bash
|
|
258
|
-
pnpm prisma
|
|
259
|
-
pnpm prisma
|
|
260
|
-
pnpm prisma
|
|
249
|
+
pnpm prisma contract emit
|
|
250
|
+
pnpm prisma db sign
|
|
251
|
+
pnpm prisma db verify # exit 0 immediately after a pull; exit 4 with findings if the DB drifts later
|
|
261
252
|
```
|
|
262
253
|
|
|
263
254
|
Then run the snippet from *Your first arc — connect, write, read* above, using one of your existing tables in place of the starter model. The arc is the same; only the path that got you there differs.
|
|
@@ -292,7 +283,7 @@ Switch authoring later by re-running `prisma orm init` in the same directory. Th
|
|
|
292
283
|
2. **`init` doesn't connect to your database.** It only scaffolds files and installs dependencies (and runs the initial `contract emit`). You connect with `db init` / `db update` / `db migrate`. If `init` succeeds and queries fail, the issue is `DATABASE_URL`, not `init`.
|
|
293
284
|
3. **Treating inferred PSL as the final contract.** `contract infer` produces a starting point. Don't `db sign` against a contract you haven't read.
|
|
294
285
|
4. **Forgetting to emit after editing the contract.** The contract artefacts (`contract.json`, `contract.d.ts`) are stale until you run `contract emit`. If the type-checker says a model "doesn't exist", you skipped emit.
|
|
295
|
-
5. **Setting `DATABASE_URL` in `prisma.config.ts` instead of `.env`.**
|
|
286
|
+
5. **Setting `DATABASE_URL` in `prisma.config.ts` instead of `.env`.** Nothing reads `.env` on its own: the scaffolded `prisma.config.ts` starts with `import 'dotenv/config'`, and that import is what loads `.env` into `process.env` before the config (and the CLI running it) reads `process.env['DATABASE_URL']`. Keep the import; a config without it sees no `.env` values. Hardcoding the URL leaks credentials and bypasses per-environment overrides. See `references/runtime.md`.
|
|
296
287
|
6. **Hand-editing `contract.json` or `contract.d.ts`.** They're emitted artefacts; the next `contract emit` overwrites your changes. Edit the source instead.
|
|
297
288
|
7. **Using `--out` for `contract infer`.** The flag is `--output`.
|
|
298
289
|
|
|
@@ -317,7 +308,7 @@ This skill is intentionally body-only; `prisma orm init --help`, `contract infer
|
|
|
317
308
|
- [ ] Confirmed the user's target (`postgres` / `mongodb`) and authoring mode (`psl` / `typescript`).
|
|
318
309
|
- [ ] **First-touch orientation:** read `prisma.config.ts`, the contract source, `db.ts`, and `.env` before proposing anything — didn't assume what the scaffold tool / teammate left in place.
|
|
319
310
|
- [ ] **Greenfield path:** ran `prisma orm init` from the project directory — no positional project-name argument.
|
|
320
|
-
- [ ] **All paths:** the project ended up in the canonical `src/prisma/contract.{prisma,ts}` + `src/prisma/db.ts` + `migrations/app/` layout
|
|
311
|
+
- [ ] **All paths (application projects):** the project ended up in the canonical `src/prisma/contract.{prisma,ts}` + `src/prisma/db.ts` + `migrations/app/` layout (what `init` scaffolds by default). An extension or aggregate-root package keeps its own `src/contract.{prisma,ts}` + `migrations/<timestamp>_<slug>/` layout — do not relocate it.
|
|
321
312
|
- [ ] **Brownfield path:** ran `contract infer --db "$DATABASE_URL" --output src/prisma/contract.prisma`, reviewed the result, then `contract emit` + `db sign`.
|
|
322
313
|
- [ ] Set `DATABASE_URL` in `.env` and confirmed the value is reachable.
|
|
323
314
|
- [ ] Initialised the DB (`db init` greenfield / first-touch orientation) or signed the marker (`db sign` brownfield).
|