prisma-generator-express 1.62.3 → 1.63.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 +234 -95
- package/dist/generators/generateRouteConfigType.js +26 -6
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.js +245 -44
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +100 -28
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +104 -36
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/utils/copyFiles.js +2 -0
- package/dist/utils/copyFiles.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/docsRenderer.ts +1 -0
- package/src/copy/guardVariantError.ts +28 -0
- package/src/copy/guardVariantRouting.ts +109 -0
- package/src/copy/projectionDefaults.ts +5 -6
- package/src/copy/routeConfig.hono.ts +15 -5
- package/src/copy/routeConfig.ts +220 -27
- package/src/generators/generateRouteConfigType.ts +37 -6
- package/src/generators/generateRouter.ts +246 -44
- package/src/generators/generateRouterFastify.ts +100 -28
- package/src/generators/generateRouterHono.ts +104 -36
- package/src/utils/copyFiles.ts +2 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Running `npx prisma generate` produces:
|
|
|
12
12
|
- Handler functions for all Prisma operations (findMany, create, update, delete, etc.)
|
|
13
13
|
- Schema-level `findManyPaginated` execution mode selection (`Promise.all` or interactive transaction)
|
|
14
14
|
- Per-route and per-endpoint pagination config, including optional materialized-view count sources
|
|
15
|
-
- Router generator with
|
|
15
|
+
- Router generator with operation-wide and per-variant before/after hooks
|
|
16
16
|
- POST read endpoints for all read operations (for complex queries exceeding URL length limits)
|
|
17
17
|
- Express-only progressive read streaming over Server-Sent Events (SSE), using manual stages or auto-include splitting for supported relation reads, including unguarded deep `findMany` / `findManyPaginated` auto-include paths and guarded single-record auto-include paths
|
|
18
18
|
- Express-only standalone materialized view router for read-only access to registered PostgreSQL materialized views
|
|
@@ -503,13 +503,27 @@ app.route('/', UserRouter(userConfig))
|
|
|
503
503
|
|
|
504
504
|
Hono route hooks return `void` to continue. Return a `Response` (e.g. `c.json({...}, 403)`) or throw `HTTPException` to short-circuit — subsequent hooks and the handler will not run.
|
|
505
505
|
|
|
506
|
+
### Hook execution contract
|
|
507
|
+
|
|
508
|
+
Operation-level hooks run for every successfully routed variant. Variant hooks run only for the selected declared variant key.
|
|
509
|
+
|
|
510
|
+
```text
|
|
511
|
+
operation before-hooks
|
|
512
|
+
variant before-hooks
|
|
513
|
+
generated handler
|
|
514
|
+
variant after-hooks
|
|
515
|
+
operation after-hooks
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
A terminal response or an error stops the remaining hooks in that phase. Operation after-hooks are not cleanup or `finally` handlers and are not guaranteed to run. Caller-routing failures are surfaced after operation before-hooks and before variant before-hooks, preserving operation-wide logging and authentication timing.
|
|
519
|
+
|
|
506
520
|
Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
|
|
507
521
|
|
|
508
522
|
## Guard shapes (prisma-guard integration)
|
|
509
523
|
|
|
510
|
-
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.
|
|
524
|
+
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. Configure either `shape` for the existing guard API or `variants` to co-locate named shapes with per-variant hooks. The handler passes the extracted guard shape map to `prisma.model.guard(shape, caller).method(args)` instead of calling Prisma directly.
|
|
511
525
|
|
|
512
|
-
Guard
|
|
526
|
+
Guard routing works identically across Express, Fastify, and Hono. The only target-specific differences are hook types and the type of the `resolveVariant` callback parameter (`Request` for Express, `FastifyRequest` for Fastify, `Context` for Hono).
|
|
513
527
|
|
|
514
528
|
### Guard setup
|
|
515
529
|
|
|
@@ -579,71 +593,81 @@ If prisma-guard is not installed or the client is not extended with the guard ex
|
|
|
579
593
|
|
|
580
594
|
### How guard integration works
|
|
581
595
|
|
|
582
|
-
Each operation
|
|
596
|
+
Each operation accepts either the existing `shape` property or the new `variants` property:
|
|
583
597
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
598
|
+
- `shape` accepts one direct shape, one context-dependent shape function, or a named shape map.
|
|
599
|
+
- `variants` is a named map whose entries contain one shape plus optional per-variant hooks.
|
|
600
|
+
- `shape` and `variants` cannot both be defined on the same operation.
|
|
601
|
+
- Both may be absent when the operation only needs operation-wide hooks or pagination.
|
|
587
602
|
|
|
588
|
-
|
|
603
|
+
At router construction, the generated router validates and normalizes the descriptor. Variant descriptors are removed before anything reaches prisma-guard, dropped-guard projection logic, progressive planning, or OpenAPI extraction.
|
|
589
604
|
|
|
590
|
-
|
|
605
|
+
For each request, the generated router:
|
|
591
606
|
|
|
592
|
-
|
|
607
|
+
1. Resolves the raw caller from `config.guard.resolveVariant(request)`, then from the configured header (default `x-api-variant`), falling back to `undefined`.
|
|
608
|
+
2. Resolves the declared guard variant key using the same exact/default/parameterized precedence as prisma-guard.
|
|
609
|
+
3. Applies dropped-guard projection defaults immediately for successful routing when guard is globally dropped, preserving what operation before-hooks observe.
|
|
610
|
+
4. Runs operation before-hooks.
|
|
611
|
+
5. Converts a stored routing failure into HTTP 400 before any variant hook or generated handler runs.
|
|
612
|
+
6. Runs hooks for the matched declared key, calls the generated handler, then runs after-hooks in reverse scope order.
|
|
593
613
|
|
|
594
|
-
|
|
614
|
+
The normalized guard shape is stored on request context (`res.locals.guardShape`, `request.guardShape`, or `c.get('guardShape')`). The declared matched key is stored separately from the raw caller. For example, caller `customer/123` matching `customer/:id` stores `customer/:id` as the variant key.
|
|
595
615
|
|
|
596
|
-
|
|
616
|
+
When neither `shape` nor `variants` is configured, the generated handler calls Prisma directly with no guard enforcement.
|
|
597
617
|
|
|
598
|
-
|
|
618
|
+
### Default shape per operation
|
|
599
619
|
|
|
600
|
-
|
|
620
|
+
For one normal shape, use a direct shape object:
|
|
601
621
|
|
|
602
622
|
```ts
|
|
603
623
|
const userConfig = {
|
|
604
624
|
findMany: {
|
|
605
625
|
shape: {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
skip: true,
|
|
611
|
-
},
|
|
626
|
+
where: { email: { contains: true }, role: { equals: true } },
|
|
627
|
+
orderBy: { createdAt: true },
|
|
628
|
+
take: { max: 100, default: 25 },
|
|
629
|
+
skip: true,
|
|
612
630
|
},
|
|
613
631
|
},
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
A context-dependent shape function is also valid:
|
|
636
|
+
|
|
637
|
+
```ts
|
|
638
|
+
const userConfig = {
|
|
639
|
+
findMany: {
|
|
640
|
+
shape: (ctx) => ({
|
|
641
|
+
where: {
|
|
642
|
+
companyId: { equals: ctx.companyId },
|
|
643
|
+
name: { contains: true },
|
|
618
644
|
},
|
|
619
|
-
|
|
645
|
+
take: { max: 50 },
|
|
646
|
+
}),
|
|
620
647
|
},
|
|
621
|
-
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Use a named shape map when only shape routing differs and no per-variant route hooks are needed:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
const userConfig = {
|
|
655
|
+
findMany: {
|
|
622
656
|
shape: {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
657
|
+
admin: {
|
|
658
|
+
where: { email: { contains: true }, role: { equals: true } },
|
|
659
|
+
take: { max: 100 },
|
|
626
660
|
},
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
delete: {
|
|
630
|
-
shape: {
|
|
631
661
|
default: {
|
|
632
|
-
where: {
|
|
662
|
+
where: { name: { contains: true } },
|
|
663
|
+
take: { max: 20, default: 10 },
|
|
633
664
|
},
|
|
634
665
|
},
|
|
635
666
|
},
|
|
636
667
|
}
|
|
637
|
-
|
|
638
|
-
app.use('/', UserRouter(userConfig))
|
|
639
668
|
```
|
|
640
669
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
- `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.
|
|
644
|
-
- `create` accepts `email` and `name` from the client. `role` is forced to `'user'` regardless of what the client sends.
|
|
645
|
-
- `update` only allows changing `name`, and requires a unique `id` in `where`.
|
|
646
|
-
- `delete` requires a unique `id` in `where`.
|
|
670
|
+
`default` is selected when the caller is missing, blank, or unmatched. Without `default`, the request returns 400. Use `variants` instead when hooks must differ by the matched shape.
|
|
647
671
|
|
|
648
672
|
### Shape value types in data
|
|
649
673
|
|
|
@@ -728,6 +752,62 @@ fetch('/user', {
|
|
|
728
752
|
|
|
729
753
|
If the caller is missing or doesn't match any key, the request is rejected with 400 (`CallerError`).
|
|
730
754
|
|
|
755
|
+
### Variant-specific hooks
|
|
756
|
+
|
|
757
|
+
Use `variants` to keep each named shape beside the hooks that belong to it:
|
|
758
|
+
|
|
759
|
+
```ts
|
|
760
|
+
const orderConfig = {
|
|
761
|
+
create: {
|
|
762
|
+
before: [authenticate],
|
|
763
|
+
after: [auditOrderCreation],
|
|
764
|
+
|
|
765
|
+
variants: {
|
|
766
|
+
customer: {
|
|
767
|
+
shape: customerCreateShape,
|
|
768
|
+
before: [bindCustomer],
|
|
769
|
+
after: [sendCustomerConfirmation],
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
seller: {
|
|
773
|
+
shape: sellerCreateShape,
|
|
774
|
+
before: [bindSeller],
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
default: {
|
|
778
|
+
shape: internalCreateShape,
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
guard: {
|
|
784
|
+
resolveVariant: (req) => req.user?.role,
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
The execution order for `customer` is:
|
|
790
|
+
|
|
791
|
+
```text
|
|
792
|
+
authenticate
|
|
793
|
+
bindCustomer
|
|
794
|
+
generated create handler
|
|
795
|
+
sendCustomerConfirmation
|
|
796
|
+
auditOrderCreation
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Important rules:
|
|
800
|
+
|
|
801
|
+
- Operation-level `before` and `after` hooks run for every successfully resolved variant.
|
|
802
|
+
- Variant hooks run only for the selected declared key.
|
|
803
|
+
- `variants[key].shape` is one direct operation shape or one context-dependent shape function. It cannot contain another named map.
|
|
804
|
+
- `shape` and `variants` are mutually exclusive on one operation, but both may be absent.
|
|
805
|
+
- `default` is allowed as a variant key and receives its own hooks when fallback selects it.
|
|
806
|
+
- Reserved guard shape keys such as `where`, `data`, `include`, and `select` cannot be variant names.
|
|
807
|
+
- OpenAPI generation extracts only the shapes; hook descriptors are never exposed as guard configuration.
|
|
808
|
+
|
|
809
|
+
`updateEach` does not support `variants`; it keeps operation-wide hooks only.
|
|
810
|
+
|
|
731
811
|
### Custom caller resolution
|
|
732
812
|
|
|
733
813
|
Use `resolveVariant` for caller logic beyond a simple header. The callback parameter type depends on the target.
|
|
@@ -798,14 +878,20 @@ Caller keys support parameterized path patterns:
|
|
|
798
878
|
```ts
|
|
799
879
|
const projectConfig = {
|
|
800
880
|
update: {
|
|
801
|
-
|
|
881
|
+
variants: {
|
|
802
882
|
'/admin/projects/:id': {
|
|
803
|
-
|
|
804
|
-
|
|
883
|
+
shape: {
|
|
884
|
+
data: { title: true, status: true, priority: true },
|
|
885
|
+
where: { id: true },
|
|
886
|
+
},
|
|
887
|
+
before: [requireAdmin],
|
|
805
888
|
},
|
|
806
889
|
'/editor/projects/:id': {
|
|
807
|
-
|
|
808
|
-
|
|
890
|
+
shape: {
|
|
891
|
+
data: { title: true },
|
|
892
|
+
where: { id: true },
|
|
893
|
+
},
|
|
894
|
+
before: [requireEditor],
|
|
809
895
|
},
|
|
810
896
|
},
|
|
811
897
|
},
|
|
@@ -815,7 +901,7 @@ const projectConfig = {
|
|
|
815
901
|
}
|
|
816
902
|
```
|
|
817
903
|
|
|
818
|
-
The client sends the
|
|
904
|
+
The client sends the concrete caller:
|
|
819
905
|
|
|
820
906
|
```ts
|
|
821
907
|
fetch('/project', {
|
|
@@ -825,13 +911,39 @@ fetch('/project', {
|
|
|
825
911
|
'Content-Type': 'application/json',
|
|
826
912
|
},
|
|
827
913
|
body: JSON.stringify({
|
|
828
|
-
where: { id:
|
|
914
|
+
where: { id: 'abc123' },
|
|
829
915
|
data: { title: 'Updated', status: 'active' },
|
|
830
916
|
}),
|
|
831
917
|
})
|
|
832
918
|
```
|
|
833
919
|
|
|
834
|
-
Exact
|
|
920
|
+
Exact non-blank keys are checked first. Parameter segments match one path segment and are not extracted. Blank and whitespace-only callers do not exact-match or pattern-match; they use `default` or return 400.
|
|
921
|
+
|
|
922
|
+
The router stores the declared matched key, not the concrete caller. In this example:
|
|
923
|
+
|
|
924
|
+
```text
|
|
925
|
+
raw caller: /admin/projects/abc123
|
|
926
|
+
matched key: /admin/projects/:id
|
|
927
|
+
selected hooks: variants['/admin/projects/:id']
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
Express progressive configuration should also use the declared matched key:
|
|
931
|
+
|
|
932
|
+
```ts
|
|
933
|
+
update: {
|
|
934
|
+
variants: {
|
|
935
|
+
'/admin/projects/:id': { shape: adminShape },
|
|
936
|
+
},
|
|
937
|
+
progressive: {
|
|
938
|
+
'/admin/projects/:id': {
|
|
939
|
+
enabled: true,
|
|
940
|
+
mode: 'autoInclude',
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
For backward compatibility, a concrete raw-caller progressive key may be used only when no declared-key entry exists. That fallback emits a deduplicated deprecation warning identifying the declared key to use. Declared-key configuration always takes precedence.
|
|
835
947
|
|
|
836
948
|
### Forced where conditions
|
|
837
949
|
|
|
@@ -1181,7 +1293,11 @@ Guard errors are mapped to HTTP status codes by the generated error handler:
|
|
|
1181
1293
|
| `CallerError` | 400 | Missing/unknown/ambiguous caller, caller in body |
|
|
1182
1294
|
| `PolicyError` | 403 | Scope denied, missing tenant context, rejected findUnique |
|
|
1183
1295
|
|
|
1184
|
-
|
|
1296
|
+
Generated caller routing preserves the same 400 messages for existing named `shape` maps. Missing, unknown, ambiguous, and reserved-key routing failures are stored during shape preparation, operation before-hooks run, and the failure is then surfaced before variant hooks and the handler.
|
|
1297
|
+
|
|
1298
|
+
The new `variants` descriptor is validated when the router is constructed. Invalid configuration such as an empty variants map, a missing entry shape, a reserved variant name, or defining both `shape` and `variants` throws immediately instead of registering the route.
|
|
1299
|
+
|
|
1300
|
+
All request errors return `{ "message": "..." }` in the response body.
|
|
1185
1301
|
|
|
1186
1302
|
### Complete guard example
|
|
1187
1303
|
|
|
@@ -2343,17 +2459,26 @@ Auto-include does not require `resolveContext` or `progressiveStages`.
|
|
|
2343
2459
|
|
|
2344
2460
|
### Hooks and guard behavior
|
|
2345
2461
|
|
|
2346
|
-
For SSE requests:
|
|
2462
|
+
For SSE requests, routing and before-hooks run in the same order as JSON requests:
|
|
2463
|
+
|
|
2464
|
+
```text
|
|
2465
|
+
operation before-hooks
|
|
2466
|
+
variant before-hooks
|
|
2467
|
+
progressive SSE middleware
|
|
2468
|
+
```
|
|
2469
|
+
|
|
2470
|
+
If the progressive middleware starts or completes an SSE response, it does not continue to the normal generated handler. Consequently, variant after-hooks and operation after-hooks do not run for that request. They are not cleanup handlers.
|
|
2471
|
+
|
|
2472
|
+
Additional behavior:
|
|
2347
2473
|
|
|
2348
|
-
- `before` hooks run before streaming starts
|
|
2349
|
-
- `after` hooks do not run, because the SSE middleware handles the response and does not continue to the normal handler
|
|
2350
2474
|
- manual progressive stages receive `req.prisma` directly
|
|
2351
2475
|
- manual progressive stages do not automatically use guard shapes
|
|
2352
|
-
- auto-include mode does not run when an operation has a guard
|
|
2476
|
+
- auto-include mode does not run when an operation has a guard shape; it falls back to single-result SSE or emits an SSE error depending on `fallback`
|
|
2477
|
+
- variants without progressive config use the single-result SSE fallback through the normal generated core read handler, so guard shape behavior matches the JSON endpoint
|
|
2353
2478
|
|
|
2354
|
-
|
|
2479
|
+
Progressive lookup uses the declared matched variant key. A caller such as `/user/123` matched by `/user/:id` should configure `progressive['/user/:id']`. If only a legacy concrete raw-caller key exists, the router may use it as a compatibility fallback and emits a deduplicated deprecation warning.
|
|
2355
2480
|
|
|
2356
|
-
|
|
2481
|
+
Manual stage authors are responsible for using the resolved context and enforcing ownership or tenant constraints in their stage queries.
|
|
2357
2482
|
|
|
2358
2483
|
### Client-side usage
|
|
2359
2484
|
|
|
@@ -2523,7 +2648,7 @@ app.use('/', UserRouter({
|
|
|
2523
2648
|
}))
|
|
2524
2649
|
```
|
|
2525
2650
|
|
|
2526
|
-
Only `before` and `after` hooks are configurable. It has no `shape`, `
|
|
2651
|
+
Only operation-wide `before` and `after` hooks are configurable. It has no `shape`, `variants`, pagination, or progressive config. In development, enabling `updateEach` without a `before` hook may print a warning because this route bypasses guard shapes and should be protected explicitly.
|
|
2527
2652
|
|
|
2528
2653
|
When enabled, `updateEach` is included in generated OpenAPI output as `POST /{modelname}/each`. It remains excluded from `enableAll: true`.
|
|
2529
2654
|
|
|
@@ -2531,7 +2656,7 @@ When enabled, `updateEach` is included in generated OpenAPI output as `POST /{mo
|
|
|
2531
2656
|
|
|
2532
2657
|
`updateEach` does **not** apply prisma-guard shapes on any target, by design. It is intended as a trusted internal batch path, for example worker-to-backend bulk updates, not a public endpoint. Because it bypasses guard, it can write any field the underlying `update` allows.
|
|
2533
2658
|
|
|
2534
|
-
|
|
2659
|
+
Raw caller resolution still runs before hooks on all three targets, so a `before` hook can read the caller for its own authorization logic. No variant key is selected and no variant hook gate runs for `updateEach`. This is separate from guard shapes and does not enable guard enforcement.
|
|
2535
2660
|
|
|
2536
2661
|
Protect it yourself with route middleware (`before`) enforcing authentication or network-level restrictions. A guard `variantHeader` such as `x-api-variant` is **not** a security boundary — it only selects a caller value for hooks to read and is trivially spoofable. Do not expose `/each` to untrusted callers.
|
|
2537
2662
|
|
|
@@ -3055,14 +3180,39 @@ interface RouteConfig<TCtx = unknown> {
|
|
|
3055
3180
|
updateEach?: UpdateEachConfig
|
|
3056
3181
|
}
|
|
3057
3182
|
|
|
3058
|
-
|
|
3183
|
+
type ShapeOrFn<TShape, TCtx> = TShape | ((ctx: TCtx) => TShape)
|
|
3184
|
+
|
|
3185
|
+
type VariantEntry<TShape, TCtx, TBefore, TAfter = TBefore> = {
|
|
3186
|
+
shape: ShapeOrFn<TShape, TCtx>
|
|
3187
|
+
before?: TBefore[]
|
|
3188
|
+
after?: TAfter[]
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
type OperationConfig<
|
|
3192
|
+
TShape = Record<string, any>,
|
|
3193
|
+
TCtx = unknown,
|
|
3194
|
+
> = {
|
|
3059
3195
|
before?: RequestHandler[]
|
|
3060
3196
|
after?: RequestHandler[]
|
|
3061
|
-
shape?: Record<string, any>
|
|
3062
3197
|
pagination?: Partial<PaginationConfig>
|
|
3063
|
-
}
|
|
3198
|
+
} & (
|
|
3199
|
+
| {
|
|
3200
|
+
shape?: ShapeOrFn<TShape, TCtx> | Record<string, ShapeOrFn<TShape, TCtx>>
|
|
3201
|
+
variants?: never
|
|
3202
|
+
}
|
|
3203
|
+
| {
|
|
3204
|
+
shape?: never
|
|
3205
|
+
variants: Record<
|
|
3206
|
+
string,
|
|
3207
|
+
VariantEntry<TShape, TCtx, RequestHandler>
|
|
3208
|
+
>
|
|
3209
|
+
}
|
|
3210
|
+
)
|
|
3064
3211
|
|
|
3065
|
-
|
|
3212
|
+
type ReadOperationConfig<
|
|
3213
|
+
TCtx = unknown,
|
|
3214
|
+
TShape = Record<string, any>,
|
|
3215
|
+
> = OperationConfig<TShape, TCtx> & {
|
|
3066
3216
|
progressive?: Record<string, ProgressiveVariantConfig>
|
|
3067
3217
|
progressiveStages?: Record<string, ProgressiveStage<TCtx>>
|
|
3068
3218
|
}
|
|
@@ -3132,12 +3282,14 @@ interface QueryBuilderConfig {
|
|
|
3132
3282
|
The Fastify config is identical except for hook and resolver types:
|
|
3133
3283
|
|
|
3134
3284
|
```ts
|
|
3135
|
-
|
|
3285
|
+
type OperationConfig<TShape = Record<string, any>> = {
|
|
3136
3286
|
before?: FastifyHookHandler[]
|
|
3137
3287
|
after?: FastifyHookHandler[]
|
|
3138
|
-
shape?: Record<string, any>
|
|
3139
3288
|
pagination?: Partial<PaginationConfig>
|
|
3140
|
-
}
|
|
3289
|
+
} & (
|
|
3290
|
+
| { shape?: TShape | ((ctx: unknown) => TShape) | Record<string, TShape | ((ctx: unknown) => TShape)>; variants?: never }
|
|
3291
|
+
| { shape?: never; variants: Record<string, { shape: TShape | ((ctx: unknown) => TShape); before?: FastifyHookHandler[]; after?: FastifyHookHandler[] }> }
|
|
3292
|
+
)
|
|
3141
3293
|
|
|
3142
3294
|
type FastifyHookHandler = (
|
|
3143
3295
|
request: FastifyRequest,
|
|
@@ -3152,12 +3304,14 @@ The `guard.resolveVariant` callback receives `FastifyRequest` instead of `Reques
|
|
|
3152
3304
|
The Hono config is identical except for hook and resolver types:
|
|
3153
3305
|
|
|
3154
3306
|
```ts
|
|
3155
|
-
|
|
3307
|
+
type OperationConfig<TShape = Record<string, any>> = {
|
|
3156
3308
|
before?: HonoBeforeHook[]
|
|
3157
3309
|
after?: HonoAfterHook[]
|
|
3158
|
-
shape?: Record<string, any>
|
|
3159
3310
|
pagination?: Partial<PaginationConfig>
|
|
3160
|
-
}
|
|
3311
|
+
} & (
|
|
3312
|
+
| { shape?: TShape | ((ctx: unknown) => TShape) | Record<string, TShape | ((ctx: unknown) => TShape)>; variants?: never }
|
|
3313
|
+
| { shape?: never; variants: Record<string, { shape: TShape | ((ctx: unknown) => TShape); before?: HonoBeforeHook[]; after?: HonoAfterHook[] }> }
|
|
3314
|
+
)
|
|
3161
3315
|
|
|
3162
3316
|
type HonoBeforeHook<Env extends { Variables: any } = any> = (
|
|
3163
3317
|
c: Context<Env>,
|
|
@@ -3233,19 +3387,11 @@ Generated routers use this effective guard-drop flag:
|
|
|
3233
3387
|
const DROP_GUARD = GENERATOR_DROP_GUARD || process.env.E2E === 'true'
|
|
3234
3388
|
```
|
|
3235
3389
|
|
|
3236
|
-
When `DROP_GUARD` is true
|
|
3237
|
-
|
|
3238
|
-
```ts
|
|
3239
|
-
delegate.findMany(args)
|
|
3240
|
-
```
|
|
3390
|
+
When `DROP_GUARD` is true, the generated router calls Prisma directly instead of calling `delegate.guard(...)`. Before doing so, it still applies forced values and default projection behavior using vendored, dependency-free generated runtime helpers.
|
|
3241
3391
|
|
|
3242
|
-
is
|
|
3392
|
+
Caller routing is not dropped. Active and dropped modes use the same exact/default/parameterized resolver and the same declared matched key. Missing, unknown, ambiguous, and reserved-key routing failures return 400 after operation before-hooks in both modes.
|
|
3243
3393
|
|
|
3244
|
-
|
|
3245
|
-
delegate.guard(shape, caller).findMany(args)
|
|
3246
|
-
```
|
|
3247
|
-
|
|
3248
|
-
This is global for generated routers. No per-router config is required.
|
|
3394
|
+
Generated dropped/E2E router output has no runtime import from `prisma-guard`, the configured guard runtime import path, or a generated guard bridge. Type-only imports of generated guard shape declarations are allowed because they are erased at build time.
|
|
3249
3395
|
|
|
3250
3396
|
### Production safety
|
|
3251
3397
|
|
|
@@ -3362,27 +3508,20 @@ SQL extension receives normal id filters
|
|
|
3362
3508
|
|
|
3363
3509
|
### Required implementation notes
|
|
3364
3510
|
|
|
3365
|
-
Generated router code
|
|
3511
|
+
Generated router code computes:
|
|
3366
3512
|
|
|
3367
3513
|
```ts
|
|
3368
|
-
const DROP_GUARD =
|
|
3514
|
+
const DROP_GUARD = GENERATOR_DROP_GUARD || process.env.E2E === 'true'
|
|
3369
3515
|
```
|
|
3370
3516
|
|
|
3371
|
-
|
|
3517
|
+
The router always resolves caller routing first. On a successful match:
|
|
3372
3518
|
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
locals.guardShape = opConfig.shape
|
|
3376
|
-
}
|
|
3377
|
-
```
|
|
3519
|
+
- active mode stores the normalized guard shape for `delegate.guard(...)`
|
|
3520
|
+
- dropped mode applies the same matched shape locally before operation before-hooks
|
|
3378
3521
|
|
|
3379
|
-
|
|
3522
|
+
On a routing failure, the router stores the failure, runs operation before-hooks, then returns HTTP 400 before variant hooks or the handler. This ordering is identical across Express, Fastify, and Hono.
|
|
3380
3523
|
|
|
3381
|
-
|
|
3382
|
-
Express
|
|
3383
|
-
Fastify
|
|
3384
|
-
Hono
|
|
3385
|
-
```
|
|
3524
|
+
The dropped runtime must remain self-contained. Generated code must not require `prisma-guard` to be installed or resolvable when `dropGuard=true` or `E2E=true`.
|
|
3386
3525
|
|
|
3387
3526
|
## Environment variables
|
|
3388
3527
|
|
|
@@ -3394,4 +3533,4 @@ Hono
|
|
|
3394
3533
|
|
|
3395
3534
|
## License
|
|
3396
3535
|
|
|
3397
|
-
MIT
|
|
3536
|
+
MIT
|
|
@@ -59,26 +59,46 @@ function generateRouteConfigType(modelName, hookHandlerType, guardShapesImport,
|
|
|
59
59
|
}
|
|
60
60
|
const shapeOps = Array.from(new Set(ROUTER_OPERATIONS));
|
|
61
61
|
const opShapeImports = shapeOps
|
|
62
|
-
.
|
|
62
|
+
.flatMap((op) => {
|
|
63
|
+
const prefix = `${m}${capitalize(op)}Shape`;
|
|
64
|
+
return [prefix, `${prefix}Input`];
|
|
65
|
+
})
|
|
63
66
|
.join(',\n ');
|
|
67
|
+
const shapeOrFnAliases = shapeOps
|
|
68
|
+
.map((op) => {
|
|
69
|
+
const prefix = `${m}${capitalize(op)}Shape`;
|
|
70
|
+
return (`type ${prefix}OrFn<TCtx = unknown> =\n` +
|
|
71
|
+
` | ${prefix}\n` +
|
|
72
|
+
` | ((ctx: TCtx) => ${prefix})`);
|
|
73
|
+
})
|
|
74
|
+
.join('\n\n');
|
|
64
75
|
const overrides = ROUTER_OPERATIONS.map((routerOp) => {
|
|
65
76
|
const c = capitalize(routerOp);
|
|
66
77
|
const isRead = operationDefinitions_1.READ_OPERATION_NAMES.has(routerOp);
|
|
67
|
-
const
|
|
78
|
+
const commonLines = [
|
|
68
79
|
` before?: ${beforeRef}[]`,
|
|
69
80
|
` after?: ${afterRef}[]`,
|
|
70
|
-
` shape?: ${m}${c}ShapeInput<TCtx>`,
|
|
71
81
|
` pagination?: Partial<PaginationConfig>`,
|
|
72
82
|
];
|
|
73
83
|
if (isRead && supportsProgressive) {
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
commonLines.push(` progressive?: Record<string, ProgressiveVariantConfig>`);
|
|
85
|
+
commonLines.push(` progressiveStages?: Record<string, ProgressiveStage<TCtx, TPrisma>>`);
|
|
76
86
|
}
|
|
77
|
-
|
|
87
|
+
const commonConfig = `{\n${commonLines.join('\n')}\n }`;
|
|
88
|
+
const variantsConfig = `Record<string, {\n` +
|
|
89
|
+
` shape: ${m}${c}ShapeOrFn<TCtx>\n` +
|
|
90
|
+
` before?: ${beforeRef}[]\n` +
|
|
91
|
+
` after?: ${afterRef}[]\n` +
|
|
92
|
+
` }>`;
|
|
93
|
+
return (` ${routerOp}?: (${commonConfig} & (\n` +
|
|
94
|
+
` | { shape?: ${m}${c}ShapeInput<TCtx>; variants?: never }\n` +
|
|
95
|
+
` | { shape?: never; variants: ${variantsConfig} }\n` +
|
|
96
|
+
` )) | false`);
|
|
78
97
|
}).join('\n');
|
|
79
98
|
const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ');
|
|
80
99
|
return (progressiveTypeImport +
|
|
81
100
|
`import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
|
|
101
|
+
`${shapeOrFnAliases}\n\n` +
|
|
82
102
|
`export type ${m}RouteConfig${generics} = Omit<\n` +
|
|
83
103
|
` ${baseConfig},\n` +
|
|
84
104
|
` | ${omitKeys}\n` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateRouteConfigType.js","sourceRoot":"","sources":["../../src/generators/generateRouteConfigType.ts"],"names":[],"mappings":";;AA8CA,
|
|
1
|
+
{"version":3,"file":"generateRouteConfigType.js","sourceRoot":"","sources":["../../src/generators/generateRouteConfigType.ts"],"names":[],"mappings":"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"}
|