prisma-generator-express 1.20.0 → 1.21.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 (2) hide show
  1. package/README.md +657 -12
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -16,6 +16,30 @@ 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
+ ## Table of contents
20
+
21
+ - [Compatibility](#compatibility)
22
+ - [Installation](#installation)
23
+ - [Setup](#setup)
24
+ - [Usage](#usage)
25
+ - [Selective routes with middleware](#selective-routes-with-middleware)
26
+ - [Guard shapes (prisma-guard integration)](#guard-shapes-prisma-guard-integration)
27
+ - [Request body format](#request-body-format)
28
+ - [Query encoding (client side)](#query-encoding-client-side)
29
+ - [Response shaping: select, include, omit](#response-shaping-select-include-omit)
30
+ - [BigInt and Decimal handling](#bigint-and-decimal-handling)
31
+ - [Pagination](#pagination)
32
+ - [Error handling](#error-handling)
33
+ - [Security](#security)
34
+ - [Documentation endpoints](#documentation-endpoints)
35
+ - [prisma-sql integration](#prisma-sql-integration)
36
+ - [Query parameter parsing](#query-parameter-parsing)
37
+ - [Router schema](#router-schema)
38
+ - [Skipping models](#skipping-models)
39
+ - [Configuration](#configuration)
40
+ - [Environment variables](#environment-variables)
41
+ - [License](#license)
42
+
19
43
  ## Compatibility
20
44
 
21
45
  ### Prisma version
@@ -54,7 +78,7 @@ npm install @prisma/client express
54
78
  Optional peer dependencies:
55
79
  ```bash
56
80
  npm install prisma-sql # SQL optimization
57
- npm install prisma-guard # Guard shape enforcement
81
+ npm install prisma-guard zod # Guard shape enforcement
58
82
  npm install prisma-query-builder-ui # Visual query playground
59
83
  ```
60
84
 
@@ -120,30 +144,171 @@ app.use('/', UserRouter(userConfig))
120
144
 
121
145
  Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
122
146
 
123
- ## Guard shapes (variant-based field access)
147
+ ## Guard shapes (prisma-guard integration)
148
+
149
+ 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)`.
124
150
 
125
- Guard shapes require the `prisma-guard` package for runtime enforcement.
151
+ ### Guard setup
126
152
 
127
- ### Setup
153
+ Install prisma-guard and add its generator to your schema:
128
154
  ```bash
129
- npm install prisma-guard
155
+ npm install prisma-guard zod
156
+ ```
157
+ ```prisma
158
+ generator client {
159
+ provider = "prisma-client-js"
160
+ }
161
+
162
+ generator guard {
163
+ provider = "prisma-guard"
164
+ output = "generated/guard"
165
+ }
166
+
167
+ generator express {
168
+ provider = "prisma-generator-express"
169
+ }
130
170
  ```
131
171
 
132
- Extend your PrismaClient with the guard extension:
172
+ Run `npx prisma generate` to emit both the express routes and the guard artifacts.
173
+
174
+ Extend PrismaClient with the guard extension and attach it to requests:
133
175
  ```ts
176
+ import express from 'express'
134
177
  import { PrismaClient } from '@prisma/client'
135
- import { guardExtension } from 'prisma-guard'
178
+ import { guard } from './generated/guard/client'
179
+ import { UserRouter } from './generated/User/UserRouter'
180
+
181
+ const prisma = new PrismaClient().$extends(
182
+ guard.extension(() => ({
183
+ // scope context, caller, or any other values
184
+ }))
185
+ )
136
186
 
137
- const prisma = new PrismaClient().$extends(guardExtension())
187
+ const app = express()
188
+
189
+ app.use((req, res, next) => {
190
+ req.prisma = prisma
191
+ next()
192
+ })
193
+
194
+ app.use('/', UserRouter({
195
+ findMany: {
196
+ shape: {
197
+ where: { name: { contains: true } },
198
+ take: { max: 50, default: 20 },
199
+ },
200
+ },
201
+ }))
202
+
203
+ app.listen(3000)
138
204
  ```
139
205
 
140
- ### Configuration
206
+ If prisma-guard is not installed or the client is not extended with the guard extension, requests to guarded routes return 500 with the message: `Guard shapes require prisma-guard extension on PrismaClient. Install: npm install prisma-guard, then extend your client with guardExtension().`
207
+
208
+ ### How guard integration works
209
+
210
+ Each operation config accepts an optional `shape` property. When present, the generated handler:
211
+
212
+ 1. Stores the shape on `res.locals.guardShape` via middleware
213
+ 2. Resolves the caller from `config.guard.resolveVariant(req)`, then from the configured header (default `x-api-variant`), falling back to `undefined`
214
+ 3. Calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`
215
+
216
+ When `shape` is absent, the handler calls Prisma directly with no guard enforcement.
217
+
218
+ ### Single shape per operation
219
+
220
+ A single shape object restricts what the client can do on that operation. No caller routing is needed.
221
+ ```ts
222
+ const userConfig = {
223
+ findMany: {
224
+ shape: {
225
+ where: { email: { contains: true }, role: { equals: true } },
226
+ orderBy: { createdAt: true },
227
+ take: { max: 100, default: 25 },
228
+ skip: true,
229
+ },
230
+ },
231
+ create: {
232
+ shape: {
233
+ data: { email: true, name: true, role: 'user' },
234
+ },
235
+ },
236
+ update: {
237
+ shape: {
238
+ data: { name: true },
239
+ where: { id: { equals: true } },
240
+ },
241
+ },
242
+ delete: {
243
+ shape: {
244
+ where: { id: { equals: true } },
245
+ },
246
+ },
247
+ }
248
+
249
+ app.use('/', UserRouter(userConfig))
250
+ ```
251
+
252
+ In this example:
253
+
254
+ - `findMany` allows filtering by `email` (contains) and `role` (equals), sorting by `createdAt`, pagination via `take`/`skip`. All other where fields, orderBy fields, and include/select are rejected.
255
+ - `create` accepts `email` and `name` from the client. `role` is forced to `'user'` regardless of what the client sends.
256
+ - `update` only allows changing `name`, and requires a unique `id` in `where`.
257
+ - `delete` requires a unique `id` in `where`.
258
+
259
+ ### Shape value types in data
260
+
261
+ Each field in a `data` shape accepts one of four value types:
262
+ ```ts
263
+ import { force } from 'prisma-guard'
264
+
265
+ const config = {
266
+ create: {
267
+ shape: {
268
+ data: {
269
+ email: true, // client-controlled, @zod chains apply
270
+ name: true, // client-controlled
271
+ role: 'member', // forced to 'member', client cannot override
272
+ isActive: force(true), // forced to boolean true (force() needed to distinguish from client-controlled)
273
+ bio: (base) => base.max(500), // client-controlled with inline validation override
274
+ },
275
+ },
276
+ },
277
+ }
278
+ ```
279
+
280
+ - `true` — client provides the value; `@zod` schema directives from the Prisma schema apply
281
+ - literal value — server forces this value; client input is ignored
282
+ - `force(value)` — same as literal, but required when the forced value is literally `true` (since bare `true` means client-controlled)
283
+ - `(base) => schema` — client provides the value; the function receives the base Zod type and returns a refined schema, bypassing `@zod` chains
284
+
285
+ ### Named shapes (variant-based routing)
286
+
287
+ Different API consumers often need different shapes for the same operation. Named shapes use a caller value to route to the correct shape.
141
288
  ```ts
142
289
  const userConfig = {
143
290
  findMany: {
144
291
  shape: {
145
- admin: { select: { id: true, email: true, role: true } },
146
- public: { select: { id: true, email: true } },
292
+ admin: {
293
+ where: { email: { contains: true }, role: { equals: true }, isActive: { equals: true } },
294
+ include: { posts: true, profile: true },
295
+ take: { max: 200 },
296
+ },
297
+ public: {
298
+ where: { name: { contains: true } },
299
+ select: { id: true, name: true },
300
+ take: { max: 20, default: 10 },
301
+ },
302
+ },
303
+ },
304
+ create: {
305
+ shape: {
306
+ admin: {
307
+ data: { email: true, name: true, role: true, isActive: true },
308
+ },
309
+ editor: {
310
+ data: { email: true, name: true, role: 'member' },
311
+ },
147
312
  },
148
313
  },
149
314
  guard: {
@@ -154,7 +319,487 @@ const userConfig = {
154
319
  app.use('/', UserRouter(userConfig))
155
320
  ```
156
321
 
157
- When a guard shape is configured on an operation, the variant is resolved from the configured header (default: `x-api-variant`) or a custom `resolveVariant` function. The resolved variant selects which shape config to apply. If prisma-guard is not installed or the client is not extended with the guard extension, requests to guarded routes return 500 with an actionable error message.
322
+ The client sends the variant in the configured header:
323
+ ```ts
324
+ // Admin frontend
325
+ fetch('/user', {
326
+ headers: { 'x-api-variant': 'admin' },
327
+ })
328
+
329
+ // Public frontend
330
+ fetch('/user', {
331
+ headers: { 'x-api-variant': 'public' },
332
+ })
333
+ ```
334
+
335
+ If the caller is missing or doesn't match any key, the request is rejected with 400 (`CallerError`).
336
+
337
+ ### Custom caller resolution
338
+
339
+ Use `resolveVariant` for caller logic beyond a simple header:
340
+ ```ts
341
+ const userConfig = {
342
+ findMany: {
343
+ shape: {
344
+ admin: { /* ... */ },
345
+ public: { /* ... */ },
346
+ },
347
+ },
348
+ guard: {
349
+ resolveVariant: (req) => {
350
+ if (req.user?.role === 'admin') return 'admin'
351
+ return 'public'
352
+ },
353
+ },
354
+ }
355
+ ```
356
+
357
+ `resolveVariant` takes priority over the header. If both are configured, the header is checked only when `resolveVariant` returns `undefined`.
358
+
359
+ ### Parameterized caller patterns
360
+
361
+ Caller keys support parameterized path patterns:
362
+ ```ts
363
+ const projectConfig = {
364
+ update: {
365
+ shape: {
366
+ '/admin/projects/:id': {
367
+ data: { title: true, status: true, priority: true },
368
+ where: { id: { equals: true } },
369
+ },
370
+ '/editor/projects/:id': {
371
+ data: { title: true },
372
+ where: { id: { equals: true } },
373
+ },
374
+ },
375
+ },
376
+ guard: {
377
+ variantHeader: 'x-caller',
378
+ },
379
+ }
380
+ ```
381
+
382
+ The client sends the full path:
383
+ ```ts
384
+ fetch('/project', {
385
+ method: 'PUT',
386
+ headers: {
387
+ 'x-caller': '/admin/projects/abc123',
388
+ 'Content-Type': 'application/json',
389
+ },
390
+ body: JSON.stringify({
391
+ where: { id: { equals: 'abc123' } },
392
+ data: { title: 'Updated', status: 'active' },
393
+ }),
394
+ })
395
+ ```
396
+
397
+ Exact matches are checked first. Parameters (`:id`) are routing-only and are not extracted.
398
+
399
+ ### Forced where conditions
400
+
401
+ Literal values in `where` shapes are forced server-side and cannot be overridden by the client:
402
+ ```ts
403
+ import { force } from 'prisma-guard'
404
+
405
+ const projectConfig = {
406
+ findMany: {
407
+ shape: {
408
+ where: {
409
+ status: { equals: 'published' }, // always filter to published
410
+ isDeleted: { equals: false }, // always exclude deleted
411
+ isActive: { equals: force(true) }, // force() needed for boolean true
412
+ title: { contains: true }, // client-controlled
413
+ },
414
+ take: { max: 50 },
415
+ },
416
+ },
417
+ }
418
+ ```
419
+
420
+ A request with `{ where: { title: { contains: 'demo' } } }` produces:
421
+ ```
422
+ WHERE status = 'published'
423
+ AND isDeleted = false
424
+ AND isActive = true
425
+ AND title LIKE '%demo%'
426
+ ```
427
+
428
+ The client cannot bypass the forced conditions.
429
+
430
+ ### Logical combinators (AND, OR, NOT)
431
+
432
+ Where shapes support `AND`, `OR`, and `NOT`. The combinator value defines which fields are allowed inside it:
433
+ ```ts
434
+ const config = {
435
+ findMany: {
436
+ shape: {
437
+ where: {
438
+ OR: {
439
+ title: { contains: true },
440
+ description: { contains: true },
441
+ },
442
+ status: { equals: 'published' }, // forced, always applied
443
+ },
444
+ take: { max: 50 },
445
+ },
446
+ },
447
+ }
448
+ ```
449
+
450
+ Client sends:
451
+ ```json
452
+ {
453
+ "where": {
454
+ "OR": [
455
+ { "title": { "contains": "demo" } },
456
+ { "description": { "contains": "demo" } }
457
+ ]
458
+ }
459
+ }
460
+ ```
461
+
462
+ The forced `status = 'published'` is always merged as an AND condition. Forced values inside combinators are lifted to the top-level query, regardless of the combinator type.
463
+
464
+ ### Relation filters in where
465
+
466
+ Where shapes support relation-level filters. To-many relations use `some`, `every`, `none`. To-one relations use `is`, `isNot`.
467
+ ```ts
468
+ const userConfig = {
469
+ findMany: {
470
+ shape: {
471
+ where: {
472
+ posts: {
473
+ some: {
474
+ title: { contains: true },
475
+ published: { equals: true }, // forced inside the relation
476
+ },
477
+ },
478
+ },
479
+ take: { max: 50 },
480
+ },
481
+ },
482
+ }
483
+ ```
484
+
485
+ The client can filter by `title` inside the relation, but `published = true` is always enforced.
486
+
487
+ ### Select, include, and omit in shapes
488
+
489
+ Shapes can restrict which response fields and relations the client may request:
490
+ ```ts
491
+ const userConfig = {
492
+ findMany: {
493
+ shape: {
494
+ where: { role: { equals: true } },
495
+ select: {
496
+ id: true,
497
+ email: true,
498
+ name: true,
499
+ posts: {
500
+ select: { id: true, title: true },
501
+ },
502
+ _count: {
503
+ select: { posts: true },
504
+ },
505
+ },
506
+ take: { max: 50 },
507
+ },
508
+ },
509
+ }
510
+ ```
511
+
512
+ The client can only select from the whitelisted fields and relations. Attempting to select unlisted fields (e.g. `passwordHash`) is rejected.
513
+
514
+ `select` and `include` are mutually exclusive at the same level in both the shape and the client request.
515
+
516
+ ### Nested include with forced where and pagination
517
+
518
+ Nested includes on to-many relations support `where`, `orderBy`, `cursor`, `take`, and `skip`:
519
+ ```ts
520
+ import { force } from 'prisma-guard'
521
+
522
+ const userConfig = {
523
+ findMany: {
524
+ shape: {
525
+ include: {
526
+ posts: {
527
+ where: { isDeleted: { equals: false } }, // forced: never return deleted posts
528
+ orderBy: { createdAt: true },
529
+ take: { max: 20, default: 10 },
530
+ skip: true,
531
+ },
532
+ profile: true, // simple include, no constraints
533
+ _count: {
534
+ select: {
535
+ posts: {
536
+ where: { isDeleted: { equals: false } }, // count only non-deleted
537
+ },
538
+ },
539
+ },
540
+ },
541
+ take: { max: 50 },
542
+ },
543
+ },
544
+ }
545
+ ```
546
+
547
+ ### Mutation return projection
548
+
549
+ Write operations that return records (`create`, `update`, `upsert`, `delete`, `createManyAndReturn`, `updateManyAndReturn`) support `select` and `include` in the shape:
550
+ ```ts
551
+ const userConfig = {
552
+ create: {
553
+ shape: {
554
+ data: { email: true, name: true },
555
+ include: {
556
+ profile: true,
557
+ },
558
+ },
559
+ },
560
+ update: {
561
+ shape: {
562
+ data: { name: true },
563
+ where: { id: { equals: true } },
564
+ select: {
565
+ id: true,
566
+ name: true,
567
+ updatedAt: true,
568
+ },
569
+ },
570
+ },
571
+ }
572
+ ```
573
+
574
+ 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
+
576
+ ### Upsert
577
+
578
+ Upsert uses `create` and `update` shape keys instead of `data`:
579
+ ```ts
580
+ import { force } from 'prisma-guard'
581
+
582
+ const projectConfig = {
583
+ upsert: {
584
+ shape: {
585
+ where: { id: { equals: true } },
586
+ create: {
587
+ title: true,
588
+ status: 'draft',
589
+ isActive: force(true),
590
+ },
591
+ update: {
592
+ title: true,
593
+ },
594
+ select: { id: true, title: true, status: true },
595
+ },
596
+ },
597
+ }
598
+ ```
599
+
600
+ All three (`where`, `create`, `update`) are required. Using `data` instead of `create`/`update` is rejected.
601
+
602
+ ### Bulk mutation safety
603
+
604
+ `updateMany`, `updateManyAndReturn`, and `deleteMany` require `where` in the shape:
605
+ ```ts
606
+ const userConfig = {
607
+ deleteMany: {
608
+ shape: {
609
+ where: { isActive: { equals: true }, role: { equals: true } },
610
+ },
611
+ },
612
+ updateMany: {
613
+ shape: {
614
+ data: { isActive: true },
615
+ where: { role: { equals: true } },
616
+ },
617
+ },
618
+ }
619
+ ```
620
+
621
+ A shape without `where` on these methods is rejected. Empty resolved where at runtime is also rejected.
622
+
623
+ ### Tenant isolation with guard shapes
624
+
625
+ 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:
626
+ ```prisma
627
+ /// @scope-root
628
+ model Tenant {
629
+ id String @id @default(cuid())
630
+ name String
631
+ projects Project[]
632
+ }
633
+
634
+ model Project {
635
+ id String @id @default(cuid())
636
+ title String
637
+ tenantId String
638
+ tenant Tenant @relation(fields: [tenantId], references: [id])
639
+ }
640
+ ```
641
+ ```ts
642
+ import { AsyncLocalStorage } from 'node:async_hooks'
643
+ import { guard } from './generated/guard/client'
644
+
645
+ const store = new AsyncLocalStorage<{ tenantId: string }>()
646
+
647
+ const prisma = new PrismaClient().$extends(
648
+ guard.extension(() => ({
649
+ Tenant: store.getStore()?.tenantId,
650
+ }))
651
+ )
652
+
653
+ app.use((req, res, next) => {
654
+ const tenantId = req.headers['x-tenant-id'] as string
655
+ store.run({ tenantId }, () => {
656
+ req.prisma = prisma
657
+ next()
658
+ })
659
+ })
660
+
661
+ app.use('/', ProjectRouter({
662
+ findMany: {
663
+ shape: {
664
+ where: { title: { contains: true } },
665
+ take: { max: 50 },
666
+ },
667
+ },
668
+ create: {
669
+ shape: {
670
+ data: { title: true },
671
+ },
672
+ },
673
+ }))
674
+ ```
675
+
676
+ The scope extension handles tenant isolation at the query level:
677
+
678
+ - Reads: `AND tenantId = ?` is injected into where
679
+ - Creates: `tenantId` is injected into data (the scope FK does not need to be in the data shape)
680
+ - Updates/deletes: `tenantId` condition is merged into where, scope FK is stripped from data
681
+ - Upsert: scope condition in where, FK injected into create data, FK stripped from update data
682
+
683
+ The data shape for `create` above only lists `title`. The `tenantId` field is injected by the scope extension automatically — the create completeness check accounts for scope foreign keys.
684
+
685
+ ### Supported shape keys
686
+
687
+ For reads: `where`, `include`, `select`, `orderBy`, `cursor`, `take`, `skip`, `distinct`, `_count`, `_avg`, `_sum`, `_min`, `_max`, `by`, `having`
688
+
689
+ For writes: `data`, `where`, `select`, `include` (select/include only on methods that return records)
690
+
691
+ For upsert: `where`, `create`, `update`, `select`, `include`
692
+
693
+ ### Guard error handling
694
+
695
+ Guard errors are mapped to HTTP status codes by the generated error-handling middleware:
696
+
697
+ | Error type | HTTP status | When |
698
+ | ------------- | ----------- | ----------------------------------------------------------------- |
699
+ | `ShapeError` | 400 | Invalid shape config, unknown fields, body validation, type errors |
700
+ | `CallerError` | 400 | Missing/unknown/ambiguous caller, caller in body |
701
+ | `PolicyError` | 403 | Scope denied, missing tenant context, rejected findUnique |
702
+
703
+ All errors return `{ "message": "..." }` in the response body.
704
+
705
+ ### Complete guard example
706
+ ```ts
707
+ import express from 'express'
708
+ import { AsyncLocalStorage } from 'node:async_hooks'
709
+ import { PrismaClient } from '@prisma/client'
710
+ import { guard } from './generated/guard/client'
711
+ import { force } from 'prisma-guard'
712
+ import { UserRouter } from './generated/User/UserRouter'
713
+ import { ProjectRouter } from './generated/Project/ProjectRouter'
714
+
715
+ const store = new AsyncLocalStorage<{ tenantId: string; role: string }>()
716
+
717
+ const prisma = new PrismaClient().$extends(
718
+ guard.extension(() => ({
719
+ Tenant: store.getStore()?.tenantId,
720
+ }))
721
+ )
722
+
723
+ const app = express()
724
+
725
+ app.use((req, res, next) => {
726
+ const tenantId = req.headers['x-tenant-id'] as string
727
+ const role = req.headers['x-role'] as string || 'viewer'
728
+ store.run({ tenantId, role }, () => {
729
+ req.prisma = prisma
730
+ next()
731
+ })
732
+ })
733
+
734
+ app.use('/', ProjectRouter({
735
+ findMany: {
736
+ shape: {
737
+ admin: {
738
+ where: { title: { contains: true }, status: { equals: true } },
739
+ include: { members: true },
740
+ orderBy: { createdAt: true },
741
+ take: { max: 200 },
742
+ skip: true,
743
+ },
744
+ viewer: {
745
+ where: {
746
+ title: { contains: true },
747
+ status: { equals: 'published' },
748
+ isDeleted: { equals: false },
749
+ },
750
+ select: { id: true, title: true, createdAt: true },
751
+ take: { max: 50, default: 20 },
752
+ },
753
+ },
754
+ },
755
+ create: {
756
+ shape: {
757
+ admin: {
758
+ data: { title: true, status: true, priority: true },
759
+ include: { members: true },
760
+ },
761
+ viewer: {
762
+ data: { title: true, status: 'draft', priority: 1 },
763
+ },
764
+ },
765
+ },
766
+ update: {
767
+ shape: {
768
+ admin: {
769
+ data: { title: true, status: true, priority: true },
770
+ where: { id: { equals: true } },
771
+ },
772
+ viewer: {
773
+ data: { title: true },
774
+ where: { id: { equals: true } },
775
+ },
776
+ },
777
+ },
778
+ delete: {
779
+ shape: {
780
+ admin: {
781
+ where: { id: { equals: true } },
782
+ },
783
+ },
784
+ },
785
+ guard: {
786
+ resolveVariant: (req) => {
787
+ const ctx = store.getStore()
788
+ return ctx?.role === 'admin' ? 'admin' : 'viewer'
789
+ },
790
+ },
791
+ }))
792
+
793
+ app.listen(3000)
794
+ ```
795
+
796
+ In this setup:
797
+
798
+ - 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
800
+ - Create: admins set any allowed field; viewers always create drafts with priority 1
801
+ - Delete: only admins can delete; viewers hitting the delete endpoint get a `CallerError` because there is no `viewer` shape for delete
802
+ - Tenant isolation is automatic — every query is scoped to the tenant from `x-tenant-id`
158
803
 
159
804
  ## Request body format
160
805
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator for Hono CRUD API with OpenAPI documentation",
4
- "version": "1.20.0",
4
+ "version": "1.21.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",