prisma-generator-express 1.27.0 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -16
- package/dist/constants.d.ts +1 -0
- package/dist/generators/generateFastifyHandler.d.ts +4 -0
- package/dist/generators/generateFastifyHandler.js +78 -0
- package/dist/generators/generateFastifyHandler.js.map +1 -0
- package/dist/generators/generateOperationCore.d.ts +6 -0
- package/dist/generators/generateOperationCore.js +534 -0
- package/dist/generators/generateOperationCore.js.map +1 -0
- package/dist/generators/generateQueryBuilderHelper.js +85 -69
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRouter.js +1 -25
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +5 -0
- package/dist/generators/generateRouterFastify.js +512 -0
- package/dist/generators/generateRouterFastify.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +2 -1
- package/dist/generators/generateUnifiedDocs.js +147 -82
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +0 -1
- package/dist/generators/generateUnifiedHandler.js +47 -516
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
- package/dist/generators/generateUnifiedScalarUI.js +127 -1324
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +33 -8
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +2 -1
- package/dist/utils/copyFiles.js +64 -38
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/writeFileSafely.js +3 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +4 -1
- package/src/client/encodeQueryParams.ts +1 -1
- package/src/constants.ts +2 -0
- package/src/copy/createOutputValidatorMiddleware.ts +9 -12
- package/src/copy/docsRenderer.ts +1285 -0
- package/src/copy/parseQueryParams.ts +4 -8
- package/src/copy/routeConfig.ts +10 -4
- package/src/generators/generateFastifyHandler.ts +86 -0
- package/src/generators/generateOperationCore.ts +545 -0
- package/src/generators/generateQueryBuilderHelper.ts +86 -70
- package/src/generators/generateRouter.ts +1 -25
- package/src/generators/generateRouterFastify.ts +522 -0
- package/src/generators/generateUnifiedDocs.ts +164 -81
- package/src/generators/generateUnifiedHandler.ts +45 -533
- package/src/generators/generateUnifiedScalarUI.ts +134 -1323
- package/src/index.ts +45 -9
- package/src/utils/copyFiles.ts +79 -45
- package/src/utils/writeFileSafely.ts +4 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://codecov.io/gh/multipliedtwice/prisma-generator-express)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
Prisma generator that creates Express CRUD API routes with OpenAPI documentation from your Prisma schema.
|
|
8
|
+
Prisma generator that creates Express or Fastify CRUD API routes with OpenAPI documentation from your Prisma schema.
|
|
9
9
|
|
|
10
10
|
Running `npx prisma generate` produces:
|
|
11
11
|
|
|
@@ -16,12 +16,15 @@ Running `npx prisma generate` produces:
|
|
|
16
16
|
- Client-side query parameter encoder
|
|
17
17
|
- Guard/variant shape enforcement via prisma-guard integration
|
|
18
18
|
|
|
19
|
+
Supports both **Express** and **Fastify** targets via the `target` configuration option.
|
|
20
|
+
|
|
19
21
|
## Table of contents
|
|
20
22
|
|
|
21
23
|
- [Compatibility](#compatibility)
|
|
22
24
|
- [Installation](#installation)
|
|
23
25
|
- [Setup](#setup)
|
|
24
|
-
- [Usage](#usage)
|
|
26
|
+
- [Usage (Express)](#usage-express)
|
|
27
|
+
- [Usage (Fastify)](#usage-fastify)
|
|
25
28
|
- [Selective routes with middleware](#selective-routes-with-middleware)
|
|
26
29
|
- [Guard shapes (prisma-guard integration)](#guard-shapes-prisma-guard-integration)
|
|
27
30
|
- [Request body format](#request-body-format)
|
|
@@ -53,6 +56,13 @@ Some operations require newer versions:
|
|
|
53
56
|
| `omit` parameter | 6.2.0 | Returns 400 on versions 6.0.x–6.1.x |
|
|
54
57
|
| `updateManyAndReturn` | 6.2.0 | PostgreSQL, CockroachDB, SQLite only |
|
|
55
58
|
|
|
59
|
+
### Framework support
|
|
60
|
+
|
|
61
|
+
| Framework | Target value | Generated output |
|
|
62
|
+
| --------- | ------------ | ---------------- |
|
|
63
|
+
| Express | `"express"` | `express.Router()` factory function per model |
|
|
64
|
+
| Fastify | `"fastify"` | Fastify plugin function per model |
|
|
65
|
+
|
|
56
66
|
### Database provider support
|
|
57
67
|
|
|
58
68
|
Most operations work across all Prisma-supported providers. Exceptions:
|
|
@@ -66,16 +76,25 @@ Most operations work across all Prisma-supported providers. Exceptions:
|
|
|
66
76
|
Operations not supported by your database provider return `501 Not Implemented` at runtime. The generator emits handlers for all operations regardless of provider — use selective route configuration to expose only supported operations.
|
|
67
77
|
|
|
68
78
|
## Installation
|
|
79
|
+
|
|
69
80
|
```bash
|
|
70
81
|
npm install -D prisma-generator-express
|
|
71
82
|
```
|
|
72
83
|
|
|
73
|
-
Peer dependencies:
|
|
84
|
+
Peer dependencies for Express:
|
|
85
|
+
|
|
74
86
|
```bash
|
|
75
87
|
npm install @prisma/client express
|
|
76
88
|
```
|
|
77
89
|
|
|
90
|
+
Peer dependencies for Fastify:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @prisma/client fastify
|
|
94
|
+
```
|
|
95
|
+
|
|
78
96
|
Optional peer dependencies:
|
|
97
|
+
|
|
79
98
|
```bash
|
|
80
99
|
npm install prisma-sql # SQL optimization
|
|
81
100
|
npm install prisma-guard zod # Guard shape enforcement
|
|
@@ -85,6 +104,7 @@ npm install prisma-query-builder-ui # Visual query playground
|
|
|
85
104
|
## Setup
|
|
86
105
|
|
|
87
106
|
Add the generator to your `schema.prisma`:
|
|
107
|
+
|
|
88
108
|
```prisma
|
|
89
109
|
generator client {
|
|
90
110
|
provider = "prisma-client-js"
|
|
@@ -95,14 +115,27 @@ generator express {
|
|
|
95
115
|
}
|
|
96
116
|
```
|
|
97
117
|
|
|
118
|
+
To generate Fastify routes instead of Express, set the `target` config:
|
|
119
|
+
|
|
120
|
+
```prisma
|
|
121
|
+
generator express {
|
|
122
|
+
provider = "prisma-generator-express"
|
|
123
|
+
target = "fastify"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Valid `target` values are `"express"` (default) and `"fastify"`.
|
|
128
|
+
|
|
98
129
|
The generator detects the Prisma client generator automatically. All standard provider values are supported: `prisma-client-js`, `@prisma/client`, and `prisma-client`.
|
|
99
130
|
|
|
100
131
|
Generate:
|
|
132
|
+
|
|
101
133
|
```bash
|
|
102
134
|
npx prisma generate
|
|
103
135
|
```
|
|
104
136
|
|
|
105
|
-
## Usage
|
|
137
|
+
## Usage (Express)
|
|
138
|
+
|
|
106
139
|
```ts
|
|
107
140
|
import express from 'express'
|
|
108
141
|
import { PrismaClient } from '@prisma/client'
|
|
@@ -127,7 +160,54 @@ app.listen(3000, () => {
|
|
|
127
160
|
})
|
|
128
161
|
```
|
|
129
162
|
|
|
163
|
+
## Usage (Fastify)
|
|
164
|
+
|
|
165
|
+
When `target = "fastify"`, each model produces a Fastify plugin function instead of an Express router.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import Fastify from 'fastify'
|
|
169
|
+
import { PrismaClient } from '@prisma/client'
|
|
170
|
+
import { UserRoutes } from './generated/User/UserRouter'
|
|
171
|
+
|
|
172
|
+
const prisma = new PrismaClient()
|
|
173
|
+
const fastify = Fastify()
|
|
174
|
+
|
|
175
|
+
fastify.decorateRequest('prisma', null)
|
|
176
|
+
|
|
177
|
+
fastify.addHook('onRequest', async (request) => {
|
|
178
|
+
request.prisma = prisma
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const userConfig = {
|
|
182
|
+
enableAll: true,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fastify.register(async (instance) => {
|
|
186
|
+
await UserRoutes(instance, userConfig)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
fastify.listen({ port: 3000 }, () => {
|
|
190
|
+
console.log('Server is running on http://localhost:3000')
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The generated function signature is `async function ModelRoutes(fastify: FastifyInstance, config?: RouteConfig)`. It registers routes directly on the provided Fastify instance.
|
|
195
|
+
|
|
196
|
+
### Key differences between Express and Fastify targets
|
|
197
|
+
|
|
198
|
+
| Aspect | Express | Fastify |
|
|
199
|
+
| ------ | ------- | ------- |
|
|
200
|
+
| Generated function | `ModelRouter(config)` returns `express.Router` | `ModelRoutes(fastify, config)` registers on instance |
|
|
201
|
+
| Mounting | `app.use('/', ModelRouter(config))` | `fastify.register(async (i) => { await ModelRoutes(i, config) })` |
|
|
202
|
+
| Hook types | `before`/`after` are Express `RequestHandler[]` | `before`/`after` are `FastifyHookHandler[]` |
|
|
203
|
+
| Guard resolveVariant | Receives `express.Request` | Receives `FastifyRequest` |
|
|
204
|
+
| Request data | `req.prisma`, `res.locals` | `request.prisma`, `request` properties |
|
|
205
|
+
| Error handling | Express error middleware on the router | Fastify `setErrorHandler` on the instance |
|
|
206
|
+
|
|
130
207
|
## Selective routes with middleware
|
|
208
|
+
|
|
209
|
+
### Express
|
|
210
|
+
|
|
131
211
|
```ts
|
|
132
212
|
const userConfig = {
|
|
133
213
|
findMany: {
|
|
@@ -142,18 +222,42 @@ const userConfig = {
|
|
|
142
222
|
app.use('/', UserRouter(userConfig))
|
|
143
223
|
```
|
|
144
224
|
|
|
225
|
+
### Fastify
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const userConfig = {
|
|
229
|
+
findMany: {
|
|
230
|
+
before: [async (request, reply) => { /* auth check */ }],
|
|
231
|
+
},
|
|
232
|
+
create: {
|
|
233
|
+
before: [async (request, reply) => { /* auth + validation */ }],
|
|
234
|
+
},
|
|
235
|
+
findUnique: {},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fastify.register(async (instance) => {
|
|
239
|
+
await UserRoutes(instance, userConfig)
|
|
240
|
+
})
|
|
241
|
+
```
|
|
242
|
+
|
|
145
243
|
Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
|
|
146
244
|
|
|
245
|
+
Fastify hooks receive `(request: FastifyRequest, reply: FastifyReply)`. If a hook sends a reply (via `reply.send()`), subsequent hooks and the handler are skipped.
|
|
246
|
+
|
|
147
247
|
## Guard shapes (prisma-guard integration)
|
|
148
248
|
|
|
149
249
|
prisma-generator-express integrates with [prisma-guard](https://github.com/multipliedtwice/prisma-guard) to enforce input validation, query shape restrictions, and tenant isolation on generated routes. When a `shape` is configured on an operation, the handler calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`.
|
|
150
250
|
|
|
251
|
+
Guard shapes work identically for both Express and Fastify targets. The only difference is the type of the `resolveVariant` callback parameter (`Request` vs `FastifyRequest`).
|
|
252
|
+
|
|
151
253
|
### Guard setup
|
|
152
254
|
|
|
153
255
|
Install prisma-guard and add its generator to your schema:
|
|
256
|
+
|
|
154
257
|
```bash
|
|
155
258
|
npm install prisma-guard zod
|
|
156
259
|
```
|
|
260
|
+
|
|
157
261
|
```prisma
|
|
158
262
|
generator client {
|
|
159
263
|
provider = "prisma-client-js"
|
|
@@ -172,6 +276,7 @@ generator express {
|
|
|
172
276
|
Run `npx prisma generate` to emit both the express routes and the guard artifacts.
|
|
173
277
|
|
|
174
278
|
Extend PrismaClient with the guard extension and attach it to requests:
|
|
279
|
+
|
|
175
280
|
```ts
|
|
176
281
|
import express from 'express'
|
|
177
282
|
import { PrismaClient } from '@prisma/client'
|
|
@@ -209,7 +314,7 @@ If prisma-guard is not installed or the client is not extended with the guard ex
|
|
|
209
314
|
|
|
210
315
|
Each operation config accepts an optional `shape` property. When present, the generated handler:
|
|
211
316
|
|
|
212
|
-
1. Stores the shape on `res.locals.guardShape
|
|
317
|
+
1. Stores the shape on the request context via middleware (Express: `res.locals.guardShape`, Fastify: `request.guardShape`)
|
|
213
318
|
2. Resolves the caller from `config.guard.resolveVariant(req)`, then from the configured header (default `x-api-variant`), falling back to `undefined`
|
|
214
319
|
3. Calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`
|
|
215
320
|
|
|
@@ -218,6 +323,7 @@ When `shape` is absent, the handler calls Prisma directly with no guard enforcem
|
|
|
218
323
|
### Single shape per operation
|
|
219
324
|
|
|
220
325
|
A single shape object restricts what the client can do on that operation. No caller routing is needed.
|
|
326
|
+
|
|
221
327
|
```ts
|
|
222
328
|
const userConfig = {
|
|
223
329
|
findMany: {
|
|
@@ -259,6 +365,7 @@ In this example:
|
|
|
259
365
|
### Shape value types in data
|
|
260
366
|
|
|
261
367
|
Each field in a `data` shape accepts one of four value types:
|
|
368
|
+
|
|
262
369
|
```ts
|
|
263
370
|
import { force } from 'prisma-guard'
|
|
264
371
|
|
|
@@ -285,6 +392,7 @@ const config = {
|
|
|
285
392
|
### Named shapes (variant-based routing)
|
|
286
393
|
|
|
287
394
|
Different API consumers often need different shapes for the same operation. Named shapes use a caller value to route to the correct shape.
|
|
395
|
+
|
|
288
396
|
```ts
|
|
289
397
|
const userConfig = {
|
|
290
398
|
findMany: {
|
|
@@ -320,6 +428,7 @@ app.use('/', UserRouter(userConfig))
|
|
|
320
428
|
```
|
|
321
429
|
|
|
322
430
|
The client sends the variant in the configured header:
|
|
431
|
+
|
|
323
432
|
```ts
|
|
324
433
|
// Admin frontend
|
|
325
434
|
fetch('/user', {
|
|
@@ -337,7 +446,9 @@ If the caller is missing or doesn't match any key, the request is rejected with
|
|
|
337
446
|
### Custom caller resolution
|
|
338
447
|
|
|
339
448
|
Use `resolveVariant` for caller logic beyond a simple header:
|
|
449
|
+
|
|
340
450
|
```ts
|
|
451
|
+
// Express
|
|
341
452
|
const userConfig = {
|
|
342
453
|
findMany: {
|
|
343
454
|
shape: {
|
|
@@ -354,11 +465,30 @@ const userConfig = {
|
|
|
354
465
|
}
|
|
355
466
|
```
|
|
356
467
|
|
|
468
|
+
```ts
|
|
469
|
+
// Fastify
|
|
470
|
+
const userConfig = {
|
|
471
|
+
findMany: {
|
|
472
|
+
shape: {
|
|
473
|
+
admin: { /* ... */ },
|
|
474
|
+
public: { /* ... */ },
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
guard: {
|
|
478
|
+
resolveVariant: (request) => {
|
|
479
|
+
if (request.user?.role === 'admin') return 'admin'
|
|
480
|
+
return 'public'
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
357
486
|
`resolveVariant` takes priority over the header. If both are configured, the header is checked only when `resolveVariant` returns `undefined`.
|
|
358
487
|
|
|
359
488
|
### Parameterized caller patterns
|
|
360
489
|
|
|
361
490
|
Caller keys support parameterized path patterns:
|
|
491
|
+
|
|
362
492
|
```ts
|
|
363
493
|
const projectConfig = {
|
|
364
494
|
update: {
|
|
@@ -380,6 +510,7 @@ const projectConfig = {
|
|
|
380
510
|
```
|
|
381
511
|
|
|
382
512
|
The client sends the full path:
|
|
513
|
+
|
|
383
514
|
```ts
|
|
384
515
|
fetch('/project', {
|
|
385
516
|
method: 'PUT',
|
|
@@ -399,6 +530,7 @@ Exact matches are checked first. Parameters (`:id`) are routing-only and are not
|
|
|
399
530
|
### Forced where conditions
|
|
400
531
|
|
|
401
532
|
Literal values in `where` shapes are forced server-side and cannot be overridden by the client:
|
|
533
|
+
|
|
402
534
|
```ts
|
|
403
535
|
import { force } from 'prisma-guard'
|
|
404
536
|
|
|
@@ -418,11 +550,12 @@ const projectConfig = {
|
|
|
418
550
|
```
|
|
419
551
|
|
|
420
552
|
A request with `{ where: { title: { contains: 'demo' } } }` produces:
|
|
553
|
+
|
|
421
554
|
```
|
|
422
555
|
WHERE status = 'published'
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
556
|
+
AND isDeleted = false
|
|
557
|
+
AND isActive = true
|
|
558
|
+
AND title LIKE '%demo%'
|
|
426
559
|
```
|
|
427
560
|
|
|
428
561
|
The client cannot bypass the forced conditions.
|
|
@@ -430,6 +563,7 @@ The client cannot bypass the forced conditions.
|
|
|
430
563
|
### Logical combinators (AND, OR, NOT)
|
|
431
564
|
|
|
432
565
|
Where shapes support `AND`, `OR`, and `NOT`. The combinator value defines which fields are allowed inside it:
|
|
566
|
+
|
|
433
567
|
```ts
|
|
434
568
|
const config = {
|
|
435
569
|
findMany: {
|
|
@@ -448,6 +582,7 @@ const config = {
|
|
|
448
582
|
```
|
|
449
583
|
|
|
450
584
|
Client sends:
|
|
585
|
+
|
|
451
586
|
```json
|
|
452
587
|
{
|
|
453
588
|
"where": {
|
|
@@ -464,6 +599,7 @@ The forced `status = 'published'` is always merged as an AND condition. Forced v
|
|
|
464
599
|
### Relation filters in where
|
|
465
600
|
|
|
466
601
|
Where shapes support relation-level filters. To-many relations use `some`, `every`, `none`. To-one relations use `is`, `isNot`.
|
|
602
|
+
|
|
467
603
|
```ts
|
|
468
604
|
const userConfig = {
|
|
469
605
|
findMany: {
|
|
@@ -484,9 +620,10 @@ const userConfig = {
|
|
|
484
620
|
|
|
485
621
|
The client can filter by `title` inside the relation, but `published = true` is always enforced.
|
|
486
622
|
|
|
487
|
-
### Select
|
|
623
|
+
### Select and include in shapes
|
|
488
624
|
|
|
489
625
|
Shapes can restrict which response fields and relations the client may request:
|
|
626
|
+
|
|
490
627
|
```ts
|
|
491
628
|
const userConfig = {
|
|
492
629
|
findMany: {
|
|
@@ -513,9 +650,14 @@ The client can only select from the whitelisted fields and relations. Attempting
|
|
|
513
650
|
|
|
514
651
|
`select` and `include` are mutually exclusive at the same level in both the shape and the client request.
|
|
515
652
|
|
|
653
|
+
For read operations, the shape's `select` or `include` serves two roles: it whitelists what the client is allowed to request, and it provides the default projection when the client omits `select`/`include` from the request. If the client sends a request without `select` or `include`, the shape's projection is automatically applied — the client does not need to duplicate the field list. If the client does send `select` or `include`, it is validated against the shape as a whitelist.
|
|
654
|
+
|
|
655
|
+
This means a single shape declaration like the example above defines both the security boundary (which fields are allowed) and the default API response shape (which fields are returned when the client doesn't specify).
|
|
656
|
+
|
|
516
657
|
### Nested include with forced where and pagination
|
|
517
658
|
|
|
518
659
|
Nested includes on to-many relations support `where`, `orderBy`, `cursor`, `take`, and `skip`:
|
|
660
|
+
|
|
519
661
|
```ts
|
|
520
662
|
import { force } from 'prisma-guard'
|
|
521
663
|
|
|
@@ -547,6 +689,7 @@ const userConfig = {
|
|
|
547
689
|
### Mutation return projection
|
|
548
690
|
|
|
549
691
|
Write operations that return records (`create`, `update`, `upsert`, `delete`, `createManyAndReturn`, `updateManyAndReturn`) support `select` and `include` in the shape:
|
|
692
|
+
|
|
550
693
|
```ts
|
|
551
694
|
const userConfig = {
|
|
552
695
|
create: {
|
|
@@ -573,9 +716,12 @@ const userConfig = {
|
|
|
573
716
|
|
|
574
717
|
The client can include `include` or `select` in the request body. If the shape does not define projection, the client cannot request one. Batch methods (`createMany`, `updateMany`, `deleteMany`) do not support projection.
|
|
575
718
|
|
|
719
|
+
For mutations, projection shapes only validate and constrain client-requested projections by default — if the client omits `select`/`include`, Prisma returns the full record. This differs from read operations, where the shape's projection is automatically applied as default. Enable `enforceProjection` in the prisma-guard generator config to always apply mutation projection shapes.
|
|
720
|
+
|
|
576
721
|
### Upsert
|
|
577
722
|
|
|
578
723
|
Upsert uses `create` and `update` shape keys instead of `data`:
|
|
724
|
+
|
|
579
725
|
```ts
|
|
580
726
|
import { force } from 'prisma-guard'
|
|
581
727
|
|
|
@@ -602,6 +748,7 @@ All three (`where`, `create`, `update`) are required. Using `data` instead of `c
|
|
|
602
748
|
### Bulk mutation safety
|
|
603
749
|
|
|
604
750
|
`updateMany`, `updateManyAndReturn`, and `deleteMany` require `where` in the shape:
|
|
751
|
+
|
|
605
752
|
```ts
|
|
606
753
|
const userConfig = {
|
|
607
754
|
deleteMany: {
|
|
@@ -623,6 +770,7 @@ A shape without `where` on these methods is rejected. Empty resolved where at ru
|
|
|
623
770
|
### Tenant isolation with guard shapes
|
|
624
771
|
|
|
625
772
|
When the guard extension is configured with scope context, tenant filters are injected automatically into all top-level operations on scoped models. Guard shapes and scope work together:
|
|
773
|
+
|
|
626
774
|
```prisma
|
|
627
775
|
/// @scope-root
|
|
628
776
|
model Tenant {
|
|
@@ -638,6 +786,7 @@ model Project {
|
|
|
638
786
|
tenant Tenant @relation(fields: [tenantId], references: [id])
|
|
639
787
|
}
|
|
640
788
|
```
|
|
789
|
+
|
|
641
790
|
```ts
|
|
642
791
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
643
792
|
import { guard } from './generated/guard/client'
|
|
@@ -703,6 +852,7 @@ Guard errors are mapped to HTTP status codes by the generated error-handling mid
|
|
|
703
852
|
All errors return `{ "message": "..." }` in the response body.
|
|
704
853
|
|
|
705
854
|
### Complete guard example
|
|
855
|
+
|
|
706
856
|
```ts
|
|
707
857
|
import express from 'express'
|
|
708
858
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
@@ -796,7 +946,7 @@ app.listen(3000)
|
|
|
796
946
|
In this setup:
|
|
797
947
|
|
|
798
948
|
- Admins can filter by any allowed field, include relations, and take up to 200 rows
|
|
799
|
-
- Viewers can only see published, non-deleted projects with a restricted field set
|
|
949
|
+
- Viewers can only see published, non-deleted projects with a restricted field set — the `select` shape automatically applies as the default projection, so viewer clients don't need to send `select` in the request
|
|
800
950
|
- Create: admins set any allowed field; viewers always create drafts with priority 1
|
|
801
951
|
- Delete: only admins can delete; viewers hitting the delete endpoint get a `CallerError` because there is no `viewer` shape for delete
|
|
802
952
|
- Tenant isolation is automatic — every query is scoped to the tenant from `x-tenant-id`
|
|
@@ -804,6 +954,7 @@ In this setup:
|
|
|
804
954
|
## Request body format
|
|
805
955
|
|
|
806
956
|
All write operations accept the full Prisma args object as the JSON request body. The body must be a JSON object — sending `null`, arrays, or other non-object values returns 400.
|
|
957
|
+
|
|
807
958
|
```ts
|
|
808
959
|
// Create
|
|
809
960
|
{ "data": { "name": "Alice", "email": "alice@example.com" }, "select": { "id": true } }
|
|
@@ -829,6 +980,7 @@ Write operations that return records (create, update, delete, upsert, createMany
|
|
|
829
980
|
`deleteMany`, `updateMany`, and `updateManyAndReturn` require a `where` field in the request body. Requests without `where` are rejected with 400 to prevent accidental mass operations. Sending `{ "where": {} }` is valid and matches all records — this protection catches accidental omission, not intentional broad operations.
|
|
830
981
|
|
|
831
982
|
## Query encoding (client side)
|
|
983
|
+
|
|
832
984
|
```ts
|
|
833
985
|
import { encodeQueryParams } from './generated/client/encodeQueryParams'
|
|
834
986
|
|
|
@@ -855,6 +1007,8 @@ Read and single-record write operations support three response shaping parameter
|
|
|
855
1007
|
|
|
856
1008
|
The `omit` parameter requires Prisma 6.2.0+. On versions 6.0.x–6.1.x, requests using `omit` return 400.
|
|
857
1009
|
|
|
1010
|
+
When using guard shapes, the shape's `select` or `include` defines both the whitelist and the default projection for read operations. See [Select and include in shapes](#select-and-include-in-shapes).
|
|
1011
|
+
|
|
858
1012
|
## BigInt and Decimal handling
|
|
859
1013
|
|
|
860
1014
|
BigInt and Decimal values are serialized as strings in JSON responses. Buffer and Uint8Array values are serialized as base64 strings. The OpenAPI spec documents BigInt and Decimal fields as `type: string`.
|
|
@@ -867,27 +1021,32 @@ On the client side, `encodeQueryParams` handles BigInt serialization automatical
|
|
|
867
1021
|
|
|
868
1022
|
The `hasMore` field is reliable for forward offset pagination (`skip` + `take`) only. When using cursor-based pagination or negative `take` (backward pagination), `hasMore` may be inaccurate.
|
|
869
1023
|
|
|
870
|
-
|
|
1024
|
+
When `distinct` is used with `findManyPaginated`, the total count is determined by executing a distinct query up to the configured limit (default: 100,000 rows). If the number of distinct values exceeds this limit, the total falls back to an approximate non-distinct count. When guard shapes are active, the distinct counting query respects the guard's where restrictions.
|
|
1025
|
+
|
|
1026
|
+
Configure default and maximum page sizes and the distinct count limit:
|
|
1027
|
+
|
|
871
1028
|
```ts
|
|
872
1029
|
UserRouter({
|
|
873
1030
|
findManyPaginated: {},
|
|
874
1031
|
pagination: {
|
|
875
1032
|
defaultLimit: 20,
|
|
876
1033
|
maxLimit: 100,
|
|
1034
|
+
distinctCountLimit: 50000,
|
|
877
1035
|
},
|
|
878
1036
|
})
|
|
879
1037
|
```
|
|
880
1038
|
|
|
881
|
-
`pagination.defaultLimit` is applied when the client omits `take`. `pagination.maxLimit` caps `take` by absolute value.
|
|
1039
|
+
`pagination.defaultLimit` is applied when the client omits `take`. `pagination.maxLimit` caps `take` by absolute value. `pagination.distinctCountLimit` overrides the default 100,000 row threshold for distinct count estimation. All settings apply to `findMany` and `findManyPaginated`.
|
|
882
1040
|
|
|
883
1041
|
## Error handling
|
|
884
1042
|
|
|
885
1043
|
All errors are returned as JSON with a `message` field:
|
|
1044
|
+
|
|
886
1045
|
```json
|
|
887
1046
|
{ "message": "Unique constraint violation" }
|
|
888
1047
|
```
|
|
889
1048
|
|
|
890
|
-
Each generated router installs an error-handling middleware that normalizes errors. Prisma error codes are mapped to appropriate HTTP status codes. Guard errors are mapped as follows: `ShapeError` and `CallerError` → 400, `PolicyError` → 403.
|
|
1049
|
+
Each generated router installs an error-handling middleware (Express) or error handler (Fastify) that normalizes errors. Prisma error codes are mapped to appropriate HTTP status codes. Guard errors are mapped as follows: `ShapeError` and `CallerError` → 400, `PolicyError` → 403.
|
|
891
1050
|
|
|
892
1051
|
| Status | Description |
|
|
893
1052
|
| ------ | ------------------------------------------ |
|
|
@@ -918,7 +1077,10 @@ Actual paths depend on `customUrlPrefix` and `addModelPrefix` configuration.
|
|
|
918
1077
|
|
|
919
1078
|
### Manual (generated helpers, require mounting)
|
|
920
1079
|
|
|
921
|
-
The generator produces helper functions that you mount yourself. Pass the same config object used for the router to keep docs and runtime in sync
|
|
1080
|
+
The generator produces helper functions that you mount yourself. Pass the same config object used for the router to keep docs and runtime in sync.
|
|
1081
|
+
|
|
1082
|
+
#### Express
|
|
1083
|
+
|
|
922
1084
|
```ts
|
|
923
1085
|
import {
|
|
924
1086
|
generateCombinedDocs,
|
|
@@ -955,6 +1117,46 @@ app.get(
|
|
|
955
1117
|
)
|
|
956
1118
|
```
|
|
957
1119
|
|
|
1120
|
+
#### Fastify
|
|
1121
|
+
|
|
1122
|
+
```ts
|
|
1123
|
+
import {
|
|
1124
|
+
generateCombinedDocs,
|
|
1125
|
+
registerModelDocs,
|
|
1126
|
+
} from './generated/combinedDocs'
|
|
1127
|
+
|
|
1128
|
+
const userConfig = {
|
|
1129
|
+
findMany: { before: [async (request, reply) => { /* auth */ }] },
|
|
1130
|
+
create: {},
|
|
1131
|
+
findUnique: {},
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const postConfig = {
|
|
1135
|
+
enableAll: true,
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
fastify.register(async (instance) => {
|
|
1139
|
+
await UserRoutes(instance, userConfig)
|
|
1140
|
+
})
|
|
1141
|
+
|
|
1142
|
+
fastify.register(async (instance) => {
|
|
1143
|
+
await PostRoutes(instance, postConfig)
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1146
|
+
registerModelDocs(fastify, '/docs', {
|
|
1147
|
+
User: userConfig,
|
|
1148
|
+
Post: postConfig,
|
|
1149
|
+
})
|
|
1150
|
+
|
|
1151
|
+
fastify.get('/docs', generateCombinedDocs({
|
|
1152
|
+
title: 'My API',
|
|
1153
|
+
modelConfigs: {
|
|
1154
|
+
User: userConfig,
|
|
1155
|
+
Post: postConfig,
|
|
1156
|
+
},
|
|
1157
|
+
}))
|
|
1158
|
+
```
|
|
1159
|
+
|
|
958
1160
|
| Endpoint | Description |
|
|
959
1161
|
| ----------------------------- | ----------------------- |
|
|
960
1162
|
| `/docs` | Combined index page |
|
|
@@ -969,6 +1171,7 @@ Disable in production via `NODE_ENV=production` or `DISABLE_OPENAPI=true`. Overr
|
|
|
969
1171
|
### Spec paths and mount prefixes
|
|
970
1172
|
|
|
971
1173
|
Use `specBasePath` to set the base path for OpenAPI spec and docs independently of route registration:
|
|
1174
|
+
|
|
972
1175
|
```ts
|
|
973
1176
|
const userConfig = {
|
|
974
1177
|
enableAll: true,
|
|
@@ -984,7 +1187,8 @@ When `specBasePath` is not set, `customUrlPrefix` is used for both runtime route
|
|
|
984
1187
|
|
|
985
1188
|
When `prisma-sql` is installed, the generated handlers automatically attempt to use its `speedExtension` for optimized SQL execution. The extension activates only when a database connector is provided on the request object.
|
|
986
1189
|
|
|
987
|
-
Set `req.postgres` or `req.sqlite` in your middleware to activate the extension:
|
|
1190
|
+
Set `req.postgres` or `req.sqlite` (Express) / `request.postgres` or `request.sqlite` (Fastify) in your middleware to activate the extension:
|
|
1191
|
+
|
|
988
1192
|
```ts
|
|
989
1193
|
import { PrismaClient } from '@prisma/client'
|
|
990
1194
|
import postgres from 'postgres'
|
|
@@ -992,18 +1196,25 @@ import postgres from 'postgres'
|
|
|
992
1196
|
const prisma = new PrismaClient()
|
|
993
1197
|
const sql = postgres(process.env.DATABASE_URL!)
|
|
994
1198
|
|
|
1199
|
+
// Express
|
|
995
1200
|
app.use((req, res, next) => {
|
|
996
1201
|
req.prisma = prisma
|
|
997
1202
|
req.postgres = sql
|
|
998
1203
|
next()
|
|
999
1204
|
})
|
|
1205
|
+
|
|
1206
|
+
// Fastify
|
|
1207
|
+
fastify.addHook('onRequest', async (request) => {
|
|
1208
|
+
request.prisma = prisma
|
|
1209
|
+
request.postgres = sql
|
|
1210
|
+
})
|
|
1000
1211
|
```
|
|
1001
1212
|
|
|
1002
1213
|
Without a connector on the request, the handlers use the standard PrismaClient. Set `DEBUG=true` in the environment to enable prisma-sql debug logging.
|
|
1003
1214
|
|
|
1004
1215
|
## Query parameter parsing
|
|
1005
1216
|
|
|
1006
|
-
GET query values are parsed server-side. Strings starting with `{`, `[`, or `"` are JSON-parsed. The strings `true`, `false`, `null` are converted to their JS equivalents. Numeric conversion only
|
|
1217
|
+
GET query values are parsed server-side. Strings starting with `{`, `[`, or `"` are JSON-parsed. The strings `true`, `false`, `null` are converted to their JS equivalents. Numeric conversion applies only to `take` and `skip`, and only when the value is a valid integer (e.g., `"10"` is parsed, `"10.5"` and `""` are not). Use `encodeQueryParams` on the client side to avoid encoding issues.
|
|
1007
1218
|
|
|
1008
1219
|
## Router schema
|
|
1009
1220
|
|
|
@@ -1033,6 +1244,7 @@ Paths shown are relative suffixes. Actual paths include the model prefix (e.g.,
|
|
|
1033
1244
|
## Skipping models
|
|
1034
1245
|
|
|
1035
1246
|
Add `/// generator off` to a model's documentation to skip generation:
|
|
1247
|
+
|
|
1036
1248
|
```prisma
|
|
1037
1249
|
/// generator off
|
|
1038
1250
|
model InternalLog {
|
|
@@ -1041,6 +1253,9 @@ model InternalLog {
|
|
|
1041
1253
|
```
|
|
1042
1254
|
|
|
1043
1255
|
## Configuration
|
|
1256
|
+
|
|
1257
|
+
### Express
|
|
1258
|
+
|
|
1044
1259
|
```ts
|
|
1045
1260
|
interface RouteConfig {
|
|
1046
1261
|
enableAll?: boolean
|
|
@@ -1067,6 +1282,7 @@ interface RouteConfig {
|
|
|
1067
1282
|
pagination?: {
|
|
1068
1283
|
defaultLimit?: number
|
|
1069
1284
|
maxLimit?: number
|
|
1285
|
+
distinctCountLimit?: number // default: 100000
|
|
1070
1286
|
}
|
|
1071
1287
|
|
|
1072
1288
|
// per-operation config
|
|
@@ -1105,11 +1321,33 @@ interface QueryBuilderConfig {
|
|
|
1105
1321
|
}
|
|
1106
1322
|
```
|
|
1107
1323
|
|
|
1324
|
+
### Fastify
|
|
1325
|
+
|
|
1326
|
+
The Fastify config is identical except for hook and resolver types:
|
|
1327
|
+
|
|
1328
|
+
```ts
|
|
1329
|
+
interface OperationConfig {
|
|
1330
|
+
before?: FastifyHookHandler[]
|
|
1331
|
+
after?: FastifyHookHandler[]
|
|
1332
|
+
shape?: Record<string, any>
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
type FastifyHookHandler = (
|
|
1336
|
+
request: FastifyRequest,
|
|
1337
|
+
reply: FastifyReply,
|
|
1338
|
+
) => Promise<void> | void
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
The `guard.resolveVariant` callback receives `FastifyRequest` instead of `Request`.
|
|
1342
|
+
|
|
1343
|
+
### Shared options
|
|
1344
|
+
|
|
1108
1345
|
`customUrlPrefix` is normalized to ensure a leading slash and strip trailing slashes.
|
|
1109
1346
|
|
|
1110
1347
|
`specBasePath` controls the base path used in OpenAPI spec paths and docs examples, independent of `customUrlPrefix`.
|
|
1111
1348
|
|
|
1112
1349
|
`openApiServers` sets the `servers` array in the OpenAPI spec:
|
|
1350
|
+
|
|
1113
1351
|
```ts
|
|
1114
1352
|
UserRouter({
|
|
1115
1353
|
enableAll: true,
|
|
@@ -1120,6 +1358,7 @@ UserRouter({
|
|
|
1120
1358
|
```
|
|
1121
1359
|
|
|
1122
1360
|
`openApiSecuritySchemes` and `openApiSecurity` set the security configuration in the OpenAPI spec:
|
|
1361
|
+
|
|
1123
1362
|
```ts
|
|
1124
1363
|
UserRouter({
|
|
1125
1364
|
enableAll: true,
|
package/dist/constants.d.ts
CHANGED