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.
Files changed (49) hide show
  1. package/README.md +255 -16
  2. package/dist/constants.d.ts +1 -0
  3. package/dist/generators/generateFastifyHandler.d.ts +4 -0
  4. package/dist/generators/generateFastifyHandler.js +78 -0
  5. package/dist/generators/generateFastifyHandler.js.map +1 -0
  6. package/dist/generators/generateOperationCore.d.ts +6 -0
  7. package/dist/generators/generateOperationCore.js +534 -0
  8. package/dist/generators/generateOperationCore.js.map +1 -0
  9. package/dist/generators/generateQueryBuilderHelper.js +85 -69
  10. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  11. package/dist/generators/generateRouter.js +1 -25
  12. package/dist/generators/generateRouter.js.map +1 -1
  13. package/dist/generators/generateRouterFastify.d.ts +5 -0
  14. package/dist/generators/generateRouterFastify.js +512 -0
  15. package/dist/generators/generateRouterFastify.js.map +1 -0
  16. package/dist/generators/generateUnifiedDocs.d.ts +2 -1
  17. package/dist/generators/generateUnifiedDocs.js +147 -82
  18. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  19. package/dist/generators/generateUnifiedHandler.d.ts +0 -1
  20. package/dist/generators/generateUnifiedHandler.js +47 -516
  21. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  22. package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
  23. package/dist/generators/generateUnifiedScalarUI.js +127 -1324
  24. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  25. package/dist/index.js +33 -8
  26. package/dist/index.js.map +1 -1
  27. package/dist/utils/copyFiles.d.ts +2 -1
  28. package/dist/utils/copyFiles.js +64 -38
  29. package/dist/utils/copyFiles.js.map +1 -1
  30. package/dist/utils/writeFileSafely.js +3 -0
  31. package/dist/utils/writeFileSafely.js.map +1 -1
  32. package/package.json +4 -1
  33. package/src/client/encodeQueryParams.ts +1 -1
  34. package/src/constants.ts +2 -0
  35. package/src/copy/createOutputValidatorMiddleware.ts +9 -12
  36. package/src/copy/docsRenderer.ts +1285 -0
  37. package/src/copy/parseQueryParams.ts +4 -8
  38. package/src/copy/routeConfig.ts +10 -4
  39. package/src/generators/generateFastifyHandler.ts +86 -0
  40. package/src/generators/generateOperationCore.ts +545 -0
  41. package/src/generators/generateQueryBuilderHelper.ts +86 -70
  42. package/src/generators/generateRouter.ts +1 -25
  43. package/src/generators/generateRouterFastify.ts +522 -0
  44. package/src/generators/generateUnifiedDocs.ts +164 -81
  45. package/src/generators/generateUnifiedHandler.ts +45 -533
  46. package/src/generators/generateUnifiedScalarUI.ts +134 -1323
  47. package/src/index.ts +45 -9
  48. package/src/utils/copyFiles.ts +79 -45
  49. package/src/utils/writeFileSafely.ts +4 -0
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage](https://img.shields.io/codecov/c/github/multipliedtwice/prisma-generator-express/main.svg)](https://codecov.io/gh/multipliedtwice/prisma-generator-express)
6
6
  [![npm](https://img.shields.io/npm/l/prisma-generator-express.svg)](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` via middleware
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
- AND isDeleted = false
424
- AND isActive = true
425
- AND title LIKE '%demo%'
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, include, and omit in shapes
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
- Configure default and maximum page sizes:
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. Both settings apply to `findMany` and `findManyPaginated`.
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 applies to `take` and `skip`. Use `encodeQueryParams` on the client side to avoid encoding issues.
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,
@@ -1 +1,2 @@
1
1
  export declare const GENERATOR_NAME = "prisma-generator-express";
2
+ export type Target = 'express' | 'fastify';
@@ -0,0 +1,4 @@
1
+ import { DMMF } from '@prisma/generator-helper';
2
+ export declare function generateFastifyHandler(options: {
3
+ model: DMMF.Model;
4
+ }): string;