prisma-generator-express 1.28.0 → 1.30.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 +244 -14
  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 +73 -39
  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 +88 -44
  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
 
@@ -421,9 +553,9 @@ A request with `{ where: { title: { contains: 'demo' } } }` produces:
421
553
 
422
554
  ```
423
555
  WHERE status = 'published'
424
- AND isDeleted = false
425
- AND isActive = true
426
- AND title LIKE '%demo%'
556
+ AND isDeleted = false
557
+ AND isActive = true
558
+ AND title LIKE '%demo%'
427
559
  ```
428
560
 
429
561
  The client cannot bypass the forced conditions.
@@ -431,6 +563,7 @@ The client cannot bypass the forced conditions.
431
563
  ### Logical combinators (AND, OR, NOT)
432
564
 
433
565
  Where shapes support `AND`, `OR`, and `NOT`. The combinator value defines which fields are allowed inside it:
566
+
434
567
  ```ts
435
568
  const config = {
436
569
  findMany: {
@@ -449,6 +582,7 @@ const config = {
449
582
  ```
450
583
 
451
584
  Client sends:
585
+
452
586
  ```json
453
587
  {
454
588
  "where": {
@@ -465,6 +599,7 @@ The forced `status = 'published'` is always merged as an AND condition. Forced v
465
599
  ### Relation filters in where
466
600
 
467
601
  Where shapes support relation-level filters. To-many relations use `some`, `every`, `none`. To-one relations use `is`, `isNot`.
602
+
468
603
  ```ts
469
604
  const userConfig = {
470
605
  findMany: {
@@ -488,6 +623,7 @@ The client can filter by `title` inside the relation, but `published = true` is
488
623
  ### Select and include in shapes
489
624
 
490
625
  Shapes can restrict which response fields and relations the client may request:
626
+
491
627
  ```ts
492
628
  const userConfig = {
493
629
  findMany: {
@@ -521,6 +657,7 @@ This means a single shape declaration like the example above defines both the se
521
657
  ### Nested include with forced where and pagination
522
658
 
523
659
  Nested includes on to-many relations support `where`, `orderBy`, `cursor`, `take`, and `skip`:
660
+
524
661
  ```ts
525
662
  import { force } from 'prisma-guard'
526
663
 
@@ -552,6 +689,7 @@ const userConfig = {
552
689
  ### Mutation return projection
553
690
 
554
691
  Write operations that return records (`create`, `update`, `upsert`, `delete`, `createManyAndReturn`, `updateManyAndReturn`) support `select` and `include` in the shape:
692
+
555
693
  ```ts
556
694
  const userConfig = {
557
695
  create: {
@@ -583,6 +721,7 @@ For mutations, projection shapes only validate and constrain client-requested pr
583
721
  ### Upsert
584
722
 
585
723
  Upsert uses `create` and `update` shape keys instead of `data`:
724
+
586
725
  ```ts
587
726
  import { force } from 'prisma-guard'
588
727
 
@@ -609,6 +748,7 @@ All three (`where`, `create`, `update`) are required. Using `data` instead of `c
609
748
  ### Bulk mutation safety
610
749
 
611
750
  `updateMany`, `updateManyAndReturn`, and `deleteMany` require `where` in the shape:
751
+
612
752
  ```ts
613
753
  const userConfig = {
614
754
  deleteMany: {
@@ -630,6 +770,7 @@ A shape without `where` on these methods is rejected. Empty resolved where at ru
630
770
  ### Tenant isolation with guard shapes
631
771
 
632
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
+
633
774
  ```prisma
634
775
  /// @scope-root
635
776
  model Tenant {
@@ -645,6 +786,7 @@ model Project {
645
786
  tenant Tenant @relation(fields: [tenantId], references: [id])
646
787
  }
647
788
  ```
789
+
648
790
  ```ts
649
791
  import { AsyncLocalStorage } from 'node:async_hooks'
650
792
  import { guard } from './generated/guard/client'
@@ -710,6 +852,7 @@ Guard errors are mapped to HTTP status codes by the generated error-handling mid
710
852
  All errors return `{ "message": "..." }` in the response body.
711
853
 
712
854
  ### Complete guard example
855
+
713
856
  ```ts
714
857
  import express from 'express'
715
858
  import { AsyncLocalStorage } from 'node:async_hooks'
@@ -811,6 +954,7 @@ In this setup:
811
954
  ## Request body format
812
955
 
813
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
+
814
958
  ```ts
815
959
  // Create
816
960
  { "data": { "name": "Alice", "email": "alice@example.com" }, "select": { "id": true } }
@@ -836,6 +980,7 @@ Write operations that return records (create, update, delete, upsert, createMany
836
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.
837
981
 
838
982
  ## Query encoding (client side)
983
+
839
984
  ```ts
840
985
  import { encodeQueryParams } from './generated/client/encodeQueryParams'
841
986
 
@@ -876,27 +1021,32 @@ On the client side, `encodeQueryParams` handles BigInt serialization automatical
876
1021
 
877
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.
878
1023
 
879
- 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
+
880
1028
  ```ts
881
1029
  UserRouter({
882
1030
  findManyPaginated: {},
883
1031
  pagination: {
884
1032
  defaultLimit: 20,
885
1033
  maxLimit: 100,
1034
+ distinctCountLimit: 50000,
886
1035
  },
887
1036
  })
888
1037
  ```
889
1038
 
890
- `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`.
891
1040
 
892
1041
  ## Error handling
893
1042
 
894
1043
  All errors are returned as JSON with a `message` field:
1044
+
895
1045
  ```json
896
1046
  { "message": "Unique constraint violation" }
897
1047
  ```
898
1048
 
899
- 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.
900
1050
 
901
1051
  | Status | Description |
902
1052
  | ------ | ------------------------------------------ |
@@ -927,7 +1077,10 @@ Actual paths depend on `customUrlPrefix` and `addModelPrefix` configuration.
927
1077
 
928
1078
  ### Manual (generated helpers, require mounting)
929
1079
 
930
- 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
+
931
1084
  ```ts
932
1085
  import {
933
1086
  generateCombinedDocs,
@@ -964,6 +1117,46 @@ app.get(
964
1117
  )
965
1118
  ```
966
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
+
967
1160
  | Endpoint | Description |
968
1161
  | ----------------------------- | ----------------------- |
969
1162
  | `/docs` | Combined index page |
@@ -978,6 +1171,7 @@ Disable in production via `NODE_ENV=production` or `DISABLE_OPENAPI=true`. Overr
978
1171
  ### Spec paths and mount prefixes
979
1172
 
980
1173
  Use `specBasePath` to set the base path for OpenAPI spec and docs independently of route registration:
1174
+
981
1175
  ```ts
982
1176
  const userConfig = {
983
1177
  enableAll: true,
@@ -993,7 +1187,8 @@ When `specBasePath` is not set, `customUrlPrefix` is used for both runtime route
993
1187
 
994
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.
995
1189
 
996
- 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
+
997
1192
  ```ts
998
1193
  import { PrismaClient } from '@prisma/client'
999
1194
  import postgres from 'postgres'
@@ -1001,18 +1196,25 @@ import postgres from 'postgres'
1001
1196
  const prisma = new PrismaClient()
1002
1197
  const sql = postgres(process.env.DATABASE_URL!)
1003
1198
 
1199
+ // Express
1004
1200
  app.use((req, res, next) => {
1005
1201
  req.prisma = prisma
1006
1202
  req.postgres = sql
1007
1203
  next()
1008
1204
  })
1205
+
1206
+ // Fastify
1207
+ fastify.addHook('onRequest', async (request) => {
1208
+ request.prisma = prisma
1209
+ request.postgres = sql
1210
+ })
1009
1211
  ```
1010
1212
 
1011
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.
1012
1214
 
1013
1215
  ## Query parameter parsing
1014
1216
 
1015
- 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.
1016
1218
 
1017
1219
  ## Router schema
1018
1220
 
@@ -1042,6 +1244,7 @@ Paths shown are relative suffixes. Actual paths include the model prefix (e.g.,
1042
1244
  ## Skipping models
1043
1245
 
1044
1246
  Add `/// generator off` to a model's documentation to skip generation:
1247
+
1045
1248
  ```prisma
1046
1249
  /// generator off
1047
1250
  model InternalLog {
@@ -1050,6 +1253,9 @@ model InternalLog {
1050
1253
  ```
1051
1254
 
1052
1255
  ## Configuration
1256
+
1257
+ ### Express
1258
+
1053
1259
  ```ts
1054
1260
  interface RouteConfig {
1055
1261
  enableAll?: boolean
@@ -1076,6 +1282,7 @@ interface RouteConfig {
1076
1282
  pagination?: {
1077
1283
  defaultLimit?: number
1078
1284
  maxLimit?: number
1285
+ distinctCountLimit?: number // default: 100000
1079
1286
  }
1080
1287
 
1081
1288
  // per-operation config
@@ -1114,11 +1321,33 @@ interface QueryBuilderConfig {
1114
1321
  }
1115
1322
  ```
1116
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
+
1117
1345
  `customUrlPrefix` is normalized to ensure a leading slash and strip trailing slashes.
1118
1346
 
1119
1347
  `specBasePath` controls the base path used in OpenAPI spec paths and docs examples, independent of `customUrlPrefix`.
1120
1348
 
1121
1349
  `openApiServers` sets the `servers` array in the OpenAPI spec:
1350
+
1122
1351
  ```ts
1123
1352
  UserRouter({
1124
1353
  enableAll: true,
@@ -1129,6 +1358,7 @@ UserRouter({
1129
1358
  ```
1130
1359
 
1131
1360
  `openApiSecuritySchemes` and `openApiSecurity` set the security configuration in the OpenAPI spec:
1361
+
1132
1362
  ```ts
1133
1363
  UserRouter({
1134
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;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateFastifyHandler = generateFastifyHandler;
4
+ const READ_OPS = [
5
+ 'findMany',
6
+ 'findFirst',
7
+ 'findFirstOrThrow',
8
+ 'findUnique',
9
+ 'findUniqueOrThrow',
10
+ 'findManyPaginated',
11
+ 'aggregate',
12
+ 'count',
13
+ 'groupBy',
14
+ ];
15
+ const WRITE_OPS = [
16
+ 'create',
17
+ 'createMany',
18
+ 'createManyAndReturn',
19
+ 'update',
20
+ 'updateMany',
21
+ 'updateManyAndReturn',
22
+ 'upsert',
23
+ 'delete',
24
+ 'deleteMany',
25
+ ];
26
+ const CREATED_OPS = new Set([
27
+ 'create',
28
+ 'createMany',
29
+ 'createManyAndReturn',
30
+ ]);
31
+ function generateFastifyHandler(options) {
32
+ const modelName = options.model.name;
33
+ const readHandlers = READ_OPS.map((op) => {
34
+ const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`;
35
+ return `
36
+ export async function ${exportName}(
37
+ request: FastifyRequest,
38
+ _reply: FastifyReply,
39
+ ): Promise<void> {
40
+ const data = await core.${op}(buildContext(request))
41
+ ;(request as any).resultData = data
42
+ }`;
43
+ }).join('\n');
44
+ const writeHandlers = WRITE_OPS.map((op) => {
45
+ const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`;
46
+ const statusCode = CREATED_OPS.has(op) ? 201 : 200;
47
+ return `
48
+ export async function ${exportName}(
49
+ request: FastifyRequest,
50
+ _reply: FastifyReply,
51
+ ): Promise<void> {
52
+ const data = await core.${op}(buildContext(request))
53
+ ;(request as any).resultData = data
54
+ ;(request as any).resultStatus = ${statusCode}
55
+ }`;
56
+ }).join('\n');
57
+ return `import type { FastifyRequest, FastifyReply } from 'fastify'
58
+ import * as core from './${modelName}Core.js'
59
+ import type { OperationContext } from '../operationRuntime.js'
60
+
61
+ function buildContext(request: FastifyRequest): OperationContext {
62
+ const req = request as any
63
+ return {
64
+ prisma: req.prisma,
65
+ postgres: req.postgres,
66
+ sqlite: req.sqlite,
67
+ parsedQuery: req.parsedQuery,
68
+ body: request.body,
69
+ guardShape: req.guardShape,
70
+ guardCaller: req.guardCaller,
71
+ paginationConfig: req.routeConfig?.pagination,
72
+ }
73
+ }
74
+ ${readHandlers}
75
+ ${writeHandlers}
76
+ `;
77
+ }
78
+ //# sourceMappingURL=generateFastifyHandler.js.map