prisma-generator-express 1.57.0 → 1.58.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 +111 -29
- package/dist/copy/misc.js +25 -6
- package/dist/copy/misc.js.map +1 -1
- package/dist/generators/generateFastifyHandler.js +11 -0
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +14 -20
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.js +7 -1
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateOperationCore.js +58 -17
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +12 -7
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.js +57 -32
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +235 -191
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +121 -87
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/autoIncludeRuntime.ts +9 -5
- package/src/copy/buildModelOpenApi.ts +96 -0
- package/src/copy/docsRenderer.ts +577 -174
- package/src/copy/materializedRouter.ts +40 -1
- package/src/copy/misc.ts +23 -6
- package/src/copy/operationDefinitions.ts +10 -0
- package/src/copy/operationRuntime.ts +28 -9
- package/src/copy/routeConfig.express.ts +9 -9
- package/src/copy/routeConfig.hono.ts +63 -5
- package/src/copy/routeConfig.ts +44 -22
- package/src/generators/generateFastifyHandler.ts +12 -0
- package/src/generators/generateHonoHandler.ts +15 -20
- package/src/generators/generateImportPrismaStatement.ts +10 -1
- package/src/generators/generateOperationCore.ts +58 -17
- package/src/generators/generateRouteConfigType.ts +13 -8
- package/src/generators/generateRouter.ts +57 -32
- package/src/generators/generateRouterFastify.ts +235 -191
- package/src/generators/generateRouterHono.ts +121 -87
- package/src/index.ts +6 -4
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Supports **Express**, **Fastify**, and **Hono** targets via the `target` configu
|
|
|
41
41
|
- [POST read endpoints](#post-read-endpoints)
|
|
42
42
|
- [Materialized views router (Express)](#materialized-views-router-express)
|
|
43
43
|
- [Progressive Endpoint Composition (Express SSE)](#progressive-endpoint-composition-express-sse)
|
|
44
|
+
- [updateEach (Express, Fastify, Hono, internal batch)](#updateeach-express-fastify-hono-internal-batch)
|
|
44
45
|
- [Response shaping: select, include, omit](#response-shaping-select-include-omit)
|
|
45
46
|
- [BigInt and Decimal handling](#bigint-and-decimal-handling)
|
|
46
47
|
- [Pagination](#pagination)
|
|
@@ -349,15 +350,14 @@ PrismaClient is injected via `c.set('prisma', prismaInstance)` in middleware tha
|
|
|
349
350
|
|
|
350
351
|
### Hooks (Hono)
|
|
351
352
|
|
|
352
|
-
Hono hooks are native Hono middleware
|
|
353
|
+
Hono route hooks are generated pre/post handler hooks, not native Hono middleware chains. A hook continues by returning `void`. It short-circuits by returning a `Response`, and errors by throwing, including `HTTPException`.
|
|
353
354
|
|
|
354
355
|
```ts
|
|
355
|
-
import type {
|
|
356
|
+
import type { HonoBeforeHook } from './generated/routeConfig.target'
|
|
356
357
|
|
|
357
|
-
const auth:
|
|
358
|
+
const auth: HonoBeforeHook = async (c) => {
|
|
358
359
|
const token = c.req.header('authorization')
|
|
359
360
|
if (!token) return c.json({ message: 'Unauthorized' }, 401)
|
|
360
|
-
await next()
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
const userConfig = {
|
|
@@ -367,7 +367,7 @@ const userConfig = {
|
|
|
367
367
|
}
|
|
368
368
|
```
|
|
369
369
|
|
|
370
|
-
|
|
370
|
+
`before` hooks run before the generated handler. `after` hooks run after the generated handler. Do not use `await next()` in generated Hono route hooks. Use normal `app.use()` middleware outside the generated router when you need native Hono downstream/after-`next()` behavior.
|
|
371
371
|
|
|
372
372
|
### HTTPException normalization
|
|
373
373
|
|
|
@@ -375,13 +375,13 @@ Throwing Hono's `HTTPException` from a hook short-circuits to a JSON error respo
|
|
|
375
375
|
|
|
376
376
|
```ts
|
|
377
377
|
import { HTTPException } from 'hono/http-exception'
|
|
378
|
+
import type { HonoBeforeHook } from './generated/routeConfig.target'
|
|
378
379
|
|
|
379
|
-
const auth:
|
|
380
|
+
const auth: HonoBeforeHook = async (c) => {
|
|
380
381
|
const token = c.req.header('authorization')
|
|
381
382
|
if (!token) {
|
|
382
383
|
throw new HTTPException(401, { message: 'Unauthorized' })
|
|
383
384
|
}
|
|
384
|
-
await next()
|
|
385
385
|
}
|
|
386
386
|
```
|
|
387
387
|
|
|
@@ -423,7 +423,7 @@ Both `Bindings` (what the runtime injects) and `Variables` (what your middleware
|
|
|
423
423
|
|
|
424
424
|
### Query Builder
|
|
425
425
|
|
|
426
|
-
The Query Builder playground is Node-only and **not auto-started** by the Hono target. The generated `?ui=playground` route can render the playground iframe, but the Hono router
|
|
426
|
+
The Query Builder playground is Node-only and **not auto-started** by the Hono target. The generated `?ui=playground` route can render the playground iframe, but the Hono router never starts the Query Builder server, even when query builder config is present. Start `prisma-query-builder-ui` manually in a separate process and point the config to that server when needed.
|
|
427
427
|
|
|
428
428
|
### Query string differences
|
|
429
429
|
|
|
@@ -437,8 +437,8 @@ The `encodeQueryParams` client utility does not emit duplicate keys, so this onl
|
|
|
437
437
|
| ------ | ------- | ------- | ---- |
|
|
438
438
|
| Generated function | `ModelRouter(config)` returns `express.Router` | `ModelRoutes(fastify, config)` registers on instance | `ModelRouter(config)` returns `Hono` instance |
|
|
439
439
|
| Mounting | `app.use('/', ModelRouter(config))` | `fastify.register(async (i) => { await ModelRoutes(i, config) })` | `app.route('/', ModelRouter(config))` |
|
|
440
|
-
| Hook types | `RequestHandler[]` | `FastifyHookHandler[]` | `
|
|
441
|
-
| Hook signature | `(req, res, next)` | `(request, reply)` | `(c
|
|
440
|
+
| Hook types | `RequestHandler[]` | `FastifyHookHandler[]` | `HonoBeforeHook[]` / `HonoAfterHook[]` |
|
|
441
|
+
| Hook signature | `(req, res, next)` | `(request, reply)` | `(c) => Response \| void` |
|
|
442
442
|
| Guard resolveVariant | `express.Request` | `FastifyRequest` | Hono `Context` |
|
|
443
443
|
| PrismaClient injection | `req.prisma = prisma` | `request.prisma = prisma` | `c.set('prisma', prisma)` |
|
|
444
444
|
| Error handling | Express error middleware | `setErrorHandler` | `app.onError` |
|
|
@@ -487,10 +487,13 @@ Fastify hooks receive `(request: FastifyRequest, reply: FastifyReply)`. If a hoo
|
|
|
487
487
|
```ts
|
|
488
488
|
const userConfig = {
|
|
489
489
|
findMany: {
|
|
490
|
-
before: [async (c
|
|
490
|
+
before: [async (c) => {
|
|
491
|
+
const token = c.req.header('authorization')
|
|
492
|
+
if (!token) return c.json({ message: 'Unauthorized' }, 401)
|
|
493
|
+
}],
|
|
491
494
|
},
|
|
492
495
|
create: {
|
|
493
|
-
before: [async (c
|
|
496
|
+
before: [async (c) => { /* auth + validation */ }],
|
|
494
497
|
},
|
|
495
498
|
findUnique: {},
|
|
496
499
|
}
|
|
@@ -498,7 +501,7 @@ const userConfig = {
|
|
|
498
501
|
app.route('/', UserRouter(userConfig))
|
|
499
502
|
```
|
|
500
503
|
|
|
501
|
-
Hono hooks
|
|
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.
|
|
502
505
|
|
|
503
506
|
Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
|
|
504
507
|
|
|
@@ -1300,7 +1303,7 @@ All write operations accept the full Prisma args object as the JSON request body
|
|
|
1300
1303
|
|
|
1301
1304
|
Write operations that return records (`create`, `update`, `delete`, `upsert`, `createManyAndReturn`, `updateManyAndReturn`) support `select`, `include`, and `omit` in the request body to control the response shape. When `writeStrategy = "forceReturn"`, the generated `createMany` and `updateMany` endpoints are rewritten to returning methods and also support `select`, `include`, and `omit`.
|
|
1302
1305
|
|
|
1303
|
-
For Express, mount `express.json()` before the router so request bodies are parsed. For Hono, malformed JSON bodies are rejected with 400 (`{ "message": "
|
|
1306
|
+
For Express, mount `express.json()` before the router so request bodies are parsed. For Hono, malformed JSON bodies and non-object bodies are rejected with 400 (`{ "message": "Request body must be a JSON object" }`) before reaching the handler.
|
|
1304
1307
|
|
|
1305
1308
|
### Bulk operations
|
|
1306
1309
|
|
|
@@ -1472,7 +1475,8 @@ Supported view options:
|
|
|
1472
1475
|
| `schema` | `string` | Optional schema name |
|
|
1473
1476
|
| `defaultLimit` | `number` | Default page size for this view |
|
|
1474
1477
|
| `maxLimit` | `number` | Maximum page size for this view |
|
|
1475
|
-
| `orderBy` | `string \| object` | Deterministic sort column |
|
|
1478
|
+
| `orderBy` | `string \| object` | Deterministic default sort column |
|
|
1479
|
+
| `allowedOrderBy` | `string[]` | Optional allowlist for client-provided `orderBy` query fields |
|
|
1476
1480
|
| `authorize` | `function` | Optional per-view authorization hook |
|
|
1477
1481
|
|
|
1478
1482
|
`orderBy` can be a string:
|
|
@@ -1562,7 +1566,9 @@ GET /materialized/jobAdStats?take=100&skip=200
|
|
|
1562
1566
|
|
|
1563
1567
|
`take` is clamped to the configured max limit. `skip` is clamped to zero or greater.
|
|
1564
1568
|
|
|
1565
|
-
|
|
1569
|
+
Clients may provide an `orderBy` query parameter. If `allowedOrderBy` is set, client-provided `orderBy` must be one of those fields. If `allowedOrderBy` is omitted, any valid SQL identifier is accepted and quoted.
|
|
1570
|
+
|
|
1571
|
+
When `skip > 0`, the view must define `orderBy` or receive a valid client `orderBy`. This prevents unstable offset pagination.
|
|
1566
1572
|
|
|
1567
1573
|
```ts
|
|
1568
1574
|
views: {
|
|
@@ -1578,7 +1584,7 @@ views: {
|
|
|
1578
1584
|
|
|
1579
1585
|
### Identifier safety
|
|
1580
1586
|
|
|
1581
|
-
Only registered view names can be queried. Database identifiers such as `schema`, `relation`,
|
|
1587
|
+
Only registered view names can be queried. Database identifiers such as `schema`, `relation`, `orderBy.field`, and `allowedOrderBy` entries must match this pattern:
|
|
1582
1588
|
|
|
1583
1589
|
```txt
|
|
1584
1590
|
^[A-Za-z_][A-Za-z0-9_]*$
|
|
@@ -2141,6 +2147,8 @@ Example deep event sequence:
|
|
|
2141
2147
|
|
|
2142
2148
|
Auto-include is designed for supported Prisma `include` and relation `select` trees on reads.
|
|
2143
2149
|
|
|
2150
|
+
When `findManyPaginatedMode = "transaction"`, the root `findMany` and the total count run inside one interactive transaction, so `data` and `total` are mutually consistent. Relation stages, however, load **after** the transaction commits and are not part of it — relation batches can reflect writes committed between the root transaction and the stage queries.
|
|
2151
|
+
|
|
2144
2152
|
Supported root operations:
|
|
2145
2153
|
|
|
2146
2154
|
- `findUnique`
|
|
@@ -2330,6 +2338,67 @@ The server sends keepalive comments periodically:
|
|
|
2330
2338
|
|
|
2331
2339
|
If compression middleware is used, configure it to skip `text/event-stream`, or ensure `res.flush()` is available so events are flushed promptly.
|
|
2332
2340
|
|
|
2341
|
+
## updateEach (Express, Fastify, Hono, internal batch)
|
|
2342
|
+
|
|
2343
|
+
`updateEach` applies many independent per-row updates in one request. Each item is passed to Prisma `update`.
|
|
2344
|
+
|
|
2345
|
+
```http
|
|
2346
|
+
POST /{modelname}/each
|
|
2347
|
+
```
|
|
2348
|
+
|
|
2349
|
+
Request body is a JSON array of Prisma `update` args. Each item should contain `{ where, data }`:
|
|
2350
|
+
|
|
2351
|
+
```json
|
|
2352
|
+
[
|
|
2353
|
+
{ "where": { "id": "a" }, "data": { "status": "done" } },
|
|
2354
|
+
{ "where": { "id": "b" }, "data": { "status": "failed" } }
|
|
2355
|
+
]
|
|
2356
|
+
```
|
|
2357
|
+
|
|
2358
|
+
By default each row runs independently and the response is a per-row result array:
|
|
2359
|
+
|
|
2360
|
+
```json
|
|
2361
|
+
[
|
|
2362
|
+
{ "status": "ok", "data": { "id": "a", "status": "done" } },
|
|
2363
|
+
{ "status": "error", "error": "Record not found" }
|
|
2364
|
+
]
|
|
2365
|
+
```
|
|
2366
|
+
|
|
2367
|
+
Send header `x-batch-atomic: true` to run all rows inside a single interactive transaction instead. In atomic mode any failing row rolls back the whole batch and the request errors; the endpoint requires a Prisma client with transaction support.
|
|
2368
|
+
|
|
2369
|
+
Batch size is capped to protect the database connection pool:
|
|
2370
|
+
|
|
2371
|
+
| Mode | Maximum items | Execution |
|
|
2372
|
+
| ---- | ------------- | --------- |
|
|
2373
|
+
| Non-atomic | 1000 | Bounded worker pool |
|
|
2374
|
+
| Atomic | 100 | Sequential updates inside one transaction |
|
|
2375
|
+
|
|
2376
|
+
Requests above the limit return 400.
|
|
2377
|
+
|
|
2378
|
+
### Enabling
|
|
2379
|
+
|
|
2380
|
+
`updateEach` is **opt-in only** on Express, Fastify, and Hono. It is **not** enabled by `enableAll: true`. Add it explicitly:
|
|
2381
|
+
|
|
2382
|
+
```ts
|
|
2383
|
+
app.use('/', UserRouter({
|
|
2384
|
+
updateEach: {
|
|
2385
|
+
before: [requireInternalAuth],
|
|
2386
|
+
},
|
|
2387
|
+
}))
|
|
2388
|
+
```
|
|
2389
|
+
|
|
2390
|
+
Only `before` and `after` hooks are configurable. It has no `shape`, `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.
|
|
2391
|
+
|
|
2392
|
+
When enabled, `updateEach` is included in generated OpenAPI output as `POST /{modelname}/each`. It remains excluded from `enableAll: true`.
|
|
2393
|
+
|
|
2394
|
+
### Guard and security
|
|
2395
|
+
|
|
2396
|
+
`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.
|
|
2397
|
+
|
|
2398
|
+
Caller resolution still runs before hooks on all three targets, so a `before` hook can read the resolved caller for its own authorization logic. This is separate from guard shapes and does not enable guard enforcement for `updateEach`.
|
|
2399
|
+
|
|
2400
|
+
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.
|
|
2401
|
+
|
|
2333
2402
|
## Response shaping: select, include, omit
|
|
2334
2403
|
|
|
2335
2404
|
Read and single-record write operations support three response shaping parameters:
|
|
@@ -2354,7 +2423,7 @@ On the client side, `encodeQueryParams` handles BigInt serialization automatical
|
|
|
2354
2423
|
|
|
2355
2424
|
`findManyPaginated` returns `{ data, total, hasMore }`. Execution is controlled by the schema-wide `findManyPaginatedMode` generator option. The default is `"promiseAll"`, which runs `findMany` and `count` concurrently with `Promise.all`. This is faster but not atomic under concurrent writes. `"transaction"` runs both queries inside an interactive transaction and returns `500` if transaction support is missing.
|
|
2356
2425
|
|
|
2357
|
-
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.
|
|
2426
|
+
The `hasMore` field is reliable for forward offset pagination (`skip` + positive `take`) only. When `take` is `0`, `hasMore` is `false`. When using cursor-based pagination or negative `take` (backward pagination), `hasMore` may be inaccurate.
|
|
2358
2427
|
|
|
2359
2428
|
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 a guard shape is configured together with `distinct`, the total falls back to a guarded non-distinct count so the internal count query does not need to reuse the public read projection.
|
|
2360
2429
|
|
|
@@ -2371,7 +2440,7 @@ UserRouter({
|
|
|
2371
2440
|
})
|
|
2372
2441
|
```
|
|
2373
2442
|
|
|
2374
|
-
`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`.
|
|
2443
|
+
`pagination.defaultLimit` is applied when the client omits `take`. It is not applied when a guard shape controls pagination. `pagination.maxLimit` caps `take` by absolute value even when a guard shape is present. `pagination.distinctCountLimit` overrides the default 100,000 row threshold for distinct count estimation. All settings apply to `findMany` and `findManyPaginated`.
|
|
2375
2444
|
|
|
2376
2445
|
### Materialized count source
|
|
2377
2446
|
|
|
@@ -2416,7 +2485,7 @@ UserRouter({
|
|
|
2416
2485
|
|
|
2417
2486
|
The materialized-view count source is used only when the request has no dynamic `where`, no `distinct`, and no guard shape. If any of those are present, the handler falls back to the normal delegate count so `total` stays consistent with the filtered data.
|
|
2418
2487
|
|
|
2419
|
-
The materialized count query uses PostgreSQL-style `$N` placeholders and `LIMIT 1`, so it is intended for PostgreSQL and CockroachDB-style clients. The optional `countSource.where` supports flat equality and `null` only. Operators and nested objects are
|
|
2488
|
+
The materialized count query uses PostgreSQL-style `$N` placeholders and `LIMIT 1`, so it is intended for PostgreSQL and CockroachDB-style clients. The optional `countSource.where` supports flat equality and `null` only. Operators, arrays, and nested objects are rejected at router construction.
|
|
2420
2489
|
|
|
2421
2490
|
Example with a static filter on the count view:
|
|
2422
2491
|
|
|
@@ -2443,7 +2512,7 @@ All errors are returned as JSON with a `message` field:
|
|
|
2443
2512
|
{ "message": "Unique constraint violation" }
|
|
2444
2513
|
```
|
|
2445
2514
|
|
|
2446
|
-
Each generated router installs error handling (Express middleware, Fastify `setErrorHandler`, or Hono `app.onError`) 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.
|
|
2515
|
+
Each generated router installs error handling (Express middleware, Fastify `setErrorHandler`, or Hono `app.onError`) 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. In production, unmapped/internal 500-level errors return a generic `Internal server error` message. Client-error details such as validation or conflict messages may still be included.
|
|
2447
2516
|
|
|
2448
2517
|
For the Hono target, thrown `HTTPException` instances are caught by `app.onError` and converted to `{ "message": err.message }` with the exception's status code. Custom response bodies attached to `HTTPException` are not preserved — see [HTTPException normalization](#httpexception-normalization).
|
|
2449
2518
|
|
|
@@ -2581,7 +2650,7 @@ type Env = {
|
|
|
2581
2650
|
const prisma = new PrismaClient()
|
|
2582
2651
|
|
|
2583
2652
|
const userConfig = {
|
|
2584
|
-
findMany: { before: [async (c
|
|
2653
|
+
findMany: { before: [async (c) => { /* auth */ }] },
|
|
2585
2654
|
create: {},
|
|
2586
2655
|
findUnique: {},
|
|
2587
2656
|
}
|
|
@@ -2623,7 +2692,7 @@ app.get('/docs', generateCombinedDocs({
|
|
|
2623
2692
|
| `/docs/{modelname}?ui=yaml` | Raw YAML |
|
|
2624
2693
|
| `/docs/{modelname}?ui=playground` | Query playground |
|
|
2625
2694
|
|
|
2626
|
-
The `?ui=playground` endpoint requires `prisma-query-builder-ui`. For Express and Fastify, the builder is auto-started in development. For Hono, the builder
|
|
2695
|
+
The `?ui=playground` endpoint requires `prisma-query-builder-ui`. For Express and Fastify, the builder is auto-started in development. For Hono, the router never starts the builder; start it manually in a separate process (see [Query Builder](#query-builder)).
|
|
2627
2696
|
|
|
2628
2697
|
Disable in production via `NODE_ENV=production` or `DISABLE_OPENAPI=true`. Override with `disableOpenApi: false` in config to force-enable.
|
|
2629
2698
|
|
|
@@ -2745,7 +2814,7 @@ For the Express target, GET read endpoints can also stream SSE events when the r
|
|
|
2745
2814
|
|
|
2746
2815
|
## Skipping models
|
|
2747
2816
|
|
|
2748
|
-
Add `/// generator off` to a model's documentation to skip generation:
|
|
2817
|
+
Add `/// generator off` to a model's documentation to skip generation. The marker must be on its own documentation line:
|
|
2749
2818
|
|
|
2750
2819
|
```prisma
|
|
2751
2820
|
/// generator off
|
|
@@ -2766,6 +2835,7 @@ generator express {
|
|
|
2766
2835
|
target = "express"
|
|
2767
2836
|
writeStrategy = "regular"
|
|
2768
2837
|
findManyPaginatedMode = "promiseAll"
|
|
2838
|
+
dropGuard = false
|
|
2769
2839
|
}
|
|
2770
2840
|
```
|
|
2771
2841
|
|
|
@@ -2774,6 +2844,9 @@ generator express {
|
|
|
2774
2844
|
| `target` | `"express"`, `"fastify"`, `"hono"` | `"express"` | Selects the generated router target. |
|
|
2775
2845
|
| `writeStrategy` | `"regular"`, `"throwOnNonReturning"`, `"forceReturn"` | `"regular"` | Controls only `createMany` and `updateMany`. See [Write strategy](#write-strategy). |
|
|
2776
2846
|
| `findManyPaginatedMode` | `"promiseAll"`, `"transaction"` | `"promiseAll"` | Controls whether generated `findManyPaginated` handlers run data and count with `Promise.all` or inside an interactive transaction. See [findManyPaginated execution mode](#findmanypaginated-execution-mode). |
|
|
2847
|
+
| `dropGuard` | `true`, `false` | `false` | When `true`, generated routers never pass guard shapes to Prisma. Runtime `E2E=true` also disables guard in emitted routers, even when generator `dropGuard` is `false`. Route-level and operation-level `dropGuard` config fields do not exist. |
|
|
2848
|
+
|
|
2849
|
+
> Route-level and operation-level `dropGuard` config fields were removed because they were never read at runtime. Use generator `dropGuard = true` or `E2E === 'true'`.
|
|
2777
2850
|
|
|
2778
2851
|
### Express
|
|
2779
2852
|
|
|
@@ -2843,6 +2916,7 @@ interface RouteConfig<TCtx = unknown> {
|
|
|
2843
2916
|
upsert?: OperationConfig
|
|
2844
2917
|
delete?: OperationConfig
|
|
2845
2918
|
deleteMany?: OperationConfig
|
|
2919
|
+
updateEach?: UpdateEachConfig
|
|
2846
2920
|
}
|
|
2847
2921
|
|
|
2848
2922
|
interface OperationConfig {
|
|
@@ -2857,6 +2931,11 @@ interface ReadOperationConfig<TCtx = unknown> extends OperationConfig {
|
|
|
2857
2931
|
progressiveStages?: Record<string, ProgressiveStage<TCtx>>
|
|
2858
2932
|
}
|
|
2859
2933
|
|
|
2934
|
+
interface UpdateEachConfig {
|
|
2935
|
+
before?: RequestHandler[]
|
|
2936
|
+
after?: RequestHandler[]
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2860
2939
|
type ManualProgressiveVariantConfig = {
|
|
2861
2940
|
enabled?: boolean
|
|
2862
2941
|
mode?: 'manual'
|
|
@@ -2938,19 +3017,22 @@ The Hono config is identical except for hook and resolver types:
|
|
|
2938
3017
|
|
|
2939
3018
|
```ts
|
|
2940
3019
|
interface OperationConfig {
|
|
2941
|
-
before?:
|
|
2942
|
-
after?:
|
|
3020
|
+
before?: HonoBeforeHook[]
|
|
3021
|
+
after?: HonoAfterHook[]
|
|
2943
3022
|
shape?: Record<string, any>
|
|
2944
3023
|
pagination?: Partial<PaginationConfig>
|
|
2945
3024
|
}
|
|
2946
3025
|
|
|
2947
|
-
type
|
|
3026
|
+
type HonoBeforeHook<Env extends { Variables: any } = any> = (
|
|
3027
|
+
c: Context<Env>,
|
|
3028
|
+
) => Promise<Response | void> | Response | void
|
|
3029
|
+
|
|
3030
|
+
type HonoAfterHook<Env extends { Variables: any } = any> = (
|
|
2948
3031
|
c: Context<Env>,
|
|
2949
|
-
next: Next,
|
|
2950
3032
|
) => Promise<Response | void> | Response | void
|
|
2951
3033
|
```
|
|
2952
3034
|
|
|
2953
|
-
The `guard.resolveVariant` callback receives Hono's `Context`.
|
|
3035
|
+
The `guard.resolveVariant` callback receives Hono's `Context`. Hono route hooks return `void` to continue, return a `Response` to short-circuit, or throw `HTTPException` to error. They do not receive `next`. Use normal Hono `app.use()` middleware outside the generated router when native middleware `next()` semantics are required.
|
|
2954
3036
|
|
|
2955
3037
|
The Hono router does not auto-start the Query Builder. Set `queryBuilder: false` to make the playground route return 404, or run `prisma-query-builder-ui` manually for development.
|
|
2956
3038
|
|
package/dist/copy/misc.js
CHANGED
|
@@ -25,16 +25,35 @@ function isSafeKey(key) {
|
|
|
25
25
|
}
|
|
26
26
|
function sanitizeKeys(value) {
|
|
27
27
|
if (Array.isArray(value)) {
|
|
28
|
-
|
|
28
|
+
let mutated = false;
|
|
29
|
+
const out = new Array(value.length);
|
|
30
|
+
for (let i = 0; i < value.length; i++) {
|
|
31
|
+
const sanitized = sanitizeKeys(value[i]);
|
|
32
|
+
if (sanitized !== value[i])
|
|
33
|
+
mutated = true;
|
|
34
|
+
out[i] = sanitized;
|
|
35
|
+
}
|
|
36
|
+
return (mutated ? out : value);
|
|
29
37
|
}
|
|
30
38
|
if (isPlainObject(value)) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
const keys = Object.keys(value);
|
|
40
|
+
let hasUnsafe = false;
|
|
41
|
+
let childrenMutated = false;
|
|
42
|
+
const sanitizedChildren = {};
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
if (!isSafeKey(key)) {
|
|
45
|
+
hasUnsafe = true;
|
|
34
46
|
continue;
|
|
35
|
-
|
|
47
|
+
}
|
|
48
|
+
const original = value[key];
|
|
49
|
+
const sanitized = sanitizeKeys(original);
|
|
50
|
+
if (sanitized !== original)
|
|
51
|
+
childrenMutated = true;
|
|
52
|
+
sanitizedChildren[key] = sanitized;
|
|
36
53
|
}
|
|
37
|
-
|
|
54
|
+
if (!hasUnsafe && !childrenMutated)
|
|
55
|
+
return value;
|
|
56
|
+
return sanitizedChildren;
|
|
38
57
|
}
|
|
39
58
|
return value;
|
|
40
59
|
}
|
package/dist/copy/misc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misc.js","sourceRoot":"","sources":["../../src/copy/misc.ts"],"names":[],"mappings":";;;AAIA,sCAKC;AAID,8BAEC;AAED,
|
|
1
|
+
{"version":3,"file":"misc.js","sourceRoot":"","sources":["../../src/copy/misc.ts"],"names":[],"mappings":"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"}
|
|
@@ -63,6 +63,16 @@ export async function ${exportName}(
|
|
|
63
63
|
ext.resultStatus = ${statusCode}
|
|
64
64
|
}`;
|
|
65
65
|
}).join('\n');
|
|
66
|
+
const updateEachExportName = `${modelName}UpdateEach`;
|
|
67
|
+
const updateEachHandler = `
|
|
68
|
+
export async function ${updateEachExportName}(
|
|
69
|
+
request: FastifyRequest,
|
|
70
|
+
_reply: FastifyReply,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const atomic = request.headers['x-batch-atomic'] === 'true'
|
|
73
|
+
const data = await core.updateEach(buildContext(request), atomic)
|
|
74
|
+
;(request as FastifyExtended).resultData = data
|
|
75
|
+
}`;
|
|
66
76
|
return `import type { FastifyRequest, FastifyReply } from 'fastify'
|
|
67
77
|
import * as core from './${modelName}Core${ext}'
|
|
68
78
|
import type { OperationContext, FindManyPaginatedMode } from '../operationRuntime${ext}'
|
|
@@ -96,6 +106,7 @@ function buildContext(request: FastifyRequest): OperationContext {
|
|
|
96
106
|
}
|
|
97
107
|
${readHandlers}
|
|
98
108
|
${writeHandlers}
|
|
109
|
+
${updateEachHandler}
|
|
99
110
|
`;
|
|
100
111
|
}
|
|
101
112
|
//# sourceMappingURL=generateFastifyHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateFastifyHandler.js","sourceRoot":"","sources":["../../src/generators/generateFastifyHandler.ts"],"names":[],"mappings":";;AA0CA,
|
|
1
|
+
{"version":3,"file":"generateFastifyHandler.js","sourceRoot":"","sources":["../../src/generators/generateFastifyHandler.ts"],"names":[],"mappings":";;AA0CA,wDAiFC;AAzHD,kDAA8C;AAE9C,MAAM,aAAa,GAA2B;IAC5C,MAAM,EAAE,cAAc;CACvB,CAAA;AAED,SAAS,UAAU,CAAC,EAAU;IAC5B,OAAO,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED,MAAM,QAAQ,GAAG;IACf,UAAU;IACV,WAAW;IACX,kBAAkB;IAClB,YAAY;IACZ,mBAAmB;IACnB,mBAAmB;IACnB,WAAW;IACX,OAAO;IACP,SAAS;CACV,CAAA;AAED,MAAM,SAAS,GAAG;IAChB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,QAAQ;IACR,YAAY;CACb,CAAA;AAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,QAAQ;IACR,YAAY;IACZ,qBAAqB;CACtB,CAAC,CAAA;AAEF,SAAgB,sBAAsB,CAAC,OAGtC;IACC,MAAM,GAAG,GAAG,IAAA,qBAAS,EAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAA;IAEpC,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5E,OAAO;wBACa,UAAU;;;;4BAIN,UAAU,CAAC,EAAE,CAAC;;EAExC,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACzC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5E,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;QAElD,OAAO;wBACa,UAAU;;;;4BAIN,UAAU,CAAC,EAAE,CAAC;;;uBAGnB,UAAU;EAC/B,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,oBAAoB,GAAG,GAAG,SAAS,YAAY,CAAA;IACrD,MAAM,iBAAiB,GAAG;wBACJ,oBAAoB;;;;;;;EAO1C,CAAA;IAEA,OAAO;2BACkB,SAAS,OAAO,GAAG;mFACqC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BpF,YAAY;EACZ,aAAa;EACb,iBAAiB;CAClB,CAAA;AACD,CAAC"}
|
|
@@ -41,7 +41,7 @@ function generateHonoHandler(options) {
|
|
|
41
41
|
const readHandlers = READ_OPS.map((op) => {
|
|
42
42
|
const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`;
|
|
43
43
|
return `
|
|
44
|
-
export async function ${exportName}(c:
|
|
44
|
+
export async function ${exportName}(c: HandlerContext): Promise<void> {
|
|
45
45
|
const data = await core.${coreFnName(op)}(buildContext(c))
|
|
46
46
|
c.set('resultData', data)
|
|
47
47
|
}`;
|
|
@@ -50,33 +50,27 @@ export async function ${exportName}(c: Context<HonoEnv>): Promise<void> {
|
|
|
50
50
|
const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`;
|
|
51
51
|
const statusCode = CREATED_OPS.has(op) ? 201 : 200;
|
|
52
52
|
return `
|
|
53
|
-
export async function ${exportName}(c:
|
|
53
|
+
export async function ${exportName}(c: HandlerContext): Promise<void> {
|
|
54
54
|
const data = await core.${coreFnName(op)}(buildContext(c))
|
|
55
55
|
c.set('resultData', data)
|
|
56
56
|
c.set('resultStatus', ${statusCode})
|
|
57
57
|
}`;
|
|
58
58
|
}).join('\n');
|
|
59
|
+
const updateEachExportName = `${modelName}UpdateEach`;
|
|
60
|
+
const updateEachHandler = `
|
|
61
|
+
export async function ${updateEachExportName}(c: HandlerContext): Promise<void> {
|
|
62
|
+
const atomic = c.req.header('x-batch-atomic') === 'true'
|
|
63
|
+
const data = await core.updateEach(buildContext(c), atomic)
|
|
64
|
+
c.set('resultData', data)
|
|
65
|
+
}`;
|
|
59
66
|
return `import type { Context } from 'hono'
|
|
60
67
|
import * as core from './${modelName}Core${ext}'
|
|
61
|
-
import type { OperationContext
|
|
62
|
-
|
|
63
|
-
type HonoVariables = {
|
|
64
|
-
prisma: unknown
|
|
65
|
-
postgres?: unknown
|
|
66
|
-
sqlite?: unknown
|
|
67
|
-
parsedQuery?: Record<string, unknown>
|
|
68
|
-
body?: unknown
|
|
69
|
-
routeConfig?: { pagination?: OperationContext['paginationConfig'] }
|
|
70
|
-
guardShape?: Record<string, unknown>
|
|
71
|
-
guardCaller?: string
|
|
72
|
-
findManyPaginatedMode?: FindManyPaginatedMode
|
|
73
|
-
resultData?: unknown
|
|
74
|
-
resultStatus?: number
|
|
75
|
-
}
|
|
68
|
+
import type { OperationContext } from '../operationRuntime${ext}'
|
|
69
|
+
import type { HonoInternalVariables } from '../routeConfig.target${ext}'
|
|
76
70
|
|
|
77
|
-
type
|
|
71
|
+
type HandlerContext = Context<{ Variables: HonoInternalVariables }>
|
|
78
72
|
|
|
79
|
-
function buildContext(c:
|
|
73
|
+
function buildContext(c: HandlerContext): OperationContext {
|
|
80
74
|
return {
|
|
81
75
|
prisma: c.get('prisma'),
|
|
82
76
|
postgres: c.get('postgres'),
|
|
@@ -86,11 +80,11 @@ function buildContext(c: Context<HonoEnv>): OperationContext {
|
|
|
86
80
|
guardShape: c.get('guardShape'),
|
|
87
81
|
guardCaller: c.get('guardCaller'),
|
|
88
82
|
paginationConfig: c.get('routeConfig')?.pagination,
|
|
89
|
-
findManyPaginatedMode: c.get('findManyPaginatedMode'),
|
|
90
83
|
}
|
|
91
84
|
}
|
|
92
85
|
${readHandlers}
|
|
93
86
|
${writeHandlers}
|
|
87
|
+
${updateEachHandler}
|
|
94
88
|
`;
|
|
95
89
|
}
|
|
96
90
|
//# sourceMappingURL=generateHonoHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateHonoHandler.js","sourceRoot":"","sources":["../../src/generators/generateHonoHandler.ts"],"names":[],"mappings":";;AA0CA,
|
|
1
|
+
{"version":3,"file":"generateHonoHandler.js","sourceRoot":"","sources":["../../src/generators/generateHonoHandler.ts"],"names":[],"mappings":";;AA0CA,kDA2DC;AAnGD,kDAA8C;AAE9C,MAAM,aAAa,GAA2B;IAC5C,MAAM,EAAE,cAAc;CACvB,CAAA;AAED,SAAS,UAAU,CAAC,EAAU;IAC5B,OAAO,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED,MAAM,QAAQ,GAAG;IACf,UAAU;IACV,WAAW;IACX,kBAAkB;IAClB,YAAY;IACZ,mBAAmB;IACnB,mBAAmB;IACnB,WAAW;IACX,OAAO;IACP,SAAS;CACV,CAAA;AAED,MAAM,SAAS,GAAG;IAChB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,QAAQ;IACR,YAAY;CACb,CAAA;AAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,QAAQ;IACR,YAAY;IACZ,qBAAqB;CACtB,CAAC,CAAA;AAEF,SAAgB,mBAAmB,CAAC,OAGnC;IACC,MAAM,GAAG,GAAG,IAAA,qBAAS,EAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAA;IAEpC,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5E,OAAO;wBACa,UAAU;4BACN,UAAU,CAAC,EAAE,CAAC;;EAExC,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACzC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5E,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;QAElD,OAAO;wBACa,UAAU;4BACN,UAAU,CAAC,EAAE,CAAC;;0BAEhB,UAAU;EAClC,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,oBAAoB,GAAG,GAAG,SAAS,YAAY,CAAA;IACrD,MAAM,iBAAiB,GAAG;wBACJ,oBAAoB;;;;EAI1C,CAAA;IAEA,OAAO;2BACkB,SAAS,OAAO,GAAG;4DACc,GAAG;mEACI,GAAG;;;;;;;;;;;;;;;;EAgBpE,YAAY;EACZ,aAAa;EACb,iBAAiB;CAClB,CAAA;AACD,CAAC"}
|
|
@@ -89,8 +89,14 @@ function getGuardShapesImport(options, modelName) {
|
|
|
89
89
|
if (!outputValue)
|
|
90
90
|
return null;
|
|
91
91
|
const shapesFilePath = path_1.default.join(guard.output.value, 'shapes.ts');
|
|
92
|
-
if (!fs.existsSync(shapesFilePath))
|
|
92
|
+
if (!fs.existsSync(shapesFilePath)) {
|
|
93
|
+
console.warn('[prisma-generator-express] prisma-guard generator detected but "' +
|
|
94
|
+
shapesFilePath +
|
|
95
|
+
'" was not found. Guard shapes will not be imported for model "' +
|
|
96
|
+
modelName +
|
|
97
|
+
'". Declare the "guard" generator before "express" in schema.prisma and re-run prisma generate.');
|
|
93
98
|
return null;
|
|
99
|
+
}
|
|
94
100
|
const fromDir = path_1.default.join(outputValue, modelName);
|
|
95
101
|
const shapesPath = path_1.default.join(guard.output.value, 'shapes');
|
|
96
102
|
return getRelativeImportPath(fromDir, shapesPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateImportPrismaStatement.js","sourceRoot":"","sources":["../../src/generators/generateImportPrismaStatement.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,sDAoBC;AAmBD,
|
|
1
|
+
{"version":3,"file":"generateImportPrismaStatement.js","sourceRoot":"","sources":["../../src/generators/generateImportPrismaStatement.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,sDAoBC;AAmBD,oDA2BC;AA3FD,uCAAwB;AACxB,gDAAuB;AAEvB,SAAS,mBAAmB,CAAC,OAAyB;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IAC3E,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IAEzB,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAC7C,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,kBAAkB;QACzC,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,gBAAgB;QACvC,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,eAAe,CACzC,CAAA;IACD,OAAO,UAAU,IAAI,IAAI,CAAA;AAC3B,CAAC;AAED,SAAS,qBAAqB,CAAC,OAAe,EAAE,gBAAwB;IACtE,IAAI,kBAAkB,GAAG,cAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;IACjE,kBAAkB,GAAG,kBAAkB,CAAC,KAAK,CAAC,cAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5E,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC,kBAAkB,GAAG,IAAI,GAAG,kBAAkB,CAAA;IAChD,CAAC;IACD,OAAO,kBAAkB,CAAA;AAC3B,CAAC;AAED,SAAgB,qBAAqB,CACnC,gBAAkC,EAClC,SAAiB;IAEjB,MAAM,eAAe,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAA;IAE7D,IAAI,CAAC,eAAe,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,4JAA4J,CAC7J,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAA;IAC5D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACvD,CAAC;IAED,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAEvD,OAAO,qBAAqB,CAAC,aAAa,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC3E,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAyB;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAC7C,CAAC,GAAG,EAAE,EAAE,CACN,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CACvE,CAAA;IACD,IAAI,UAAU;QAAE,OAAO,UAAU,CAAA;IAEjC,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAC3C,CAAC,GAAG,EAAE,EAAE,CACN,CAAC,CAAC,GAAG,CAAC,MAAM;QACZ,CAAC,kBAAkB,IAAI,GAAG,CAAC,MAAM;YAC/B,cAAc,IAAI,GAAG,CAAC,MAAM;YAC5B,gBAAgB,IAAI,GAAG,CAAC,MAAM,CAAC,CACpC,CAAA;IACD,OAAO,QAAQ,IAAI,IAAI,CAAA;AACzB,CAAC;AAED,SAAgB,oBAAoB,CAClC,OAAyB,EACzB,SAAiB;IAEjB,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK;QAAE,OAAO,IAAI,CAAA;IAE/C,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IAE3D,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAA;IACnD,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAA;IAE7B,MAAM,cAAc,GAAG,cAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACjE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CACV,kEAAkE;YAChE,cAAc;YACd,gEAAgE;YAChE,SAAS;YACT,gGAAgG,CACnG,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IACjD,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC1D,OAAO,qBAAqB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;AACnD,CAAC"}
|
|
@@ -158,7 +158,7 @@ ${paginatedBody}
|
|
|
158
158
|
const skip = (typeof query.skip === 'number' ? query.skip : 0)
|
|
159
159
|
const takeRaw = (typeof query.take === 'number' ? query.take : items.length)
|
|
160
160
|
const absTake = Math.abs(takeRaw)
|
|
161
|
-
const hasMore = items.length >= absTake && skip + items.length < total
|
|
161
|
+
const hasMore = absTake > 0 && items.length >= absTake && skip + items.length < total
|
|
162
162
|
|
|
163
163
|
return { data: items, total, hasMore }
|
|
164
164
|
}
|
|
@@ -167,35 +167,76 @@ export async function updateEach(
|
|
|
167
167
|
ctx: OperationContext,
|
|
168
168
|
atomic: boolean,
|
|
169
169
|
): Promise<unknown> {
|
|
170
|
-
const
|
|
171
|
-
if (!Array.isArray(
|
|
170
|
+
const rawBody = ctx.body
|
|
171
|
+
if (!Array.isArray(rawBody)) {
|
|
172
172
|
throw new HttpError(400, 'updateEach body must be an array of { where, data } items')
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
174
|
+
|
|
175
|
+
const MAX_ITEMS_NON_ATOMIC = 1000
|
|
176
|
+
const MAX_ITEMS_ATOMIC = 100
|
|
177
|
+
|
|
178
|
+
if (atomic && rawBody.length > MAX_ITEMS_ATOMIC) {
|
|
179
|
+
throw new HttpError(
|
|
180
|
+
400,
|
|
181
|
+
'atomic updateEach body exceeds max size of ' + MAX_ITEMS_ATOMIC + ' items',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
if (!atomic && rawBody.length > MAX_ITEMS_NON_ATOMIC) {
|
|
185
|
+
throw new HttpError(
|
|
186
|
+
400,
|
|
187
|
+
'updateEach body exceeds max size of ' + MAX_ITEMS_NON_ATOMIC + ' items',
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const items = rawBody.map((item, index) => {
|
|
192
|
+
const sanitized = validateBody(item)
|
|
193
|
+
if (!('where' in sanitized) || sanitized.where === undefined) {
|
|
194
|
+
throw new HttpError(400, 'updateEach item at index ' + index + ' is missing "where"')
|
|
195
|
+
}
|
|
196
|
+
if (!('data' in sanitized) || sanitized.data === undefined) {
|
|
197
|
+
throw new HttpError(400, 'updateEach item at index ' + index + ' is missing "data"')
|
|
198
|
+
}
|
|
199
|
+
return sanitized
|
|
200
|
+
})
|
|
201
|
+
const extended = await getExtendedClient(ctx)
|
|
176
202
|
|
|
177
203
|
if (atomic) {
|
|
178
|
-
|
|
204
|
+
const txClient = extended as PrismaClientLike
|
|
205
|
+
if (typeof txClient.$transaction !== 'function') {
|
|
179
206
|
throw new HttpError(500, 'Atomic updateEach requires transaction support on the Prisma client')
|
|
180
207
|
}
|
|
181
|
-
const runInteractive =
|
|
208
|
+
const runInteractive = txClient.$transaction as unknown as <T>(
|
|
182
209
|
fn: (tx: unknown) => Promise<T>,
|
|
183
210
|
) => Promise<T>
|
|
184
211
|
return runInteractive(async (tx) => {
|
|
185
212
|
const txDelegate = getDelegate(tx, '${modelNameLower}')
|
|
186
|
-
|
|
213
|
+
const out: unknown[] = new Array(items.length)
|
|
214
|
+
for (let i = 0; i < items.length; i++) {
|
|
215
|
+
out[i] = await txDelegate.update(items[i])
|
|
216
|
+
}
|
|
217
|
+
return out
|
|
187
218
|
})
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
const delegate = getDelegate(
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
const delegate = getDelegate(extended, '${modelNameLower}')
|
|
222
|
+
const CONCURRENCY = 8
|
|
223
|
+
const results: Array<{ status: 'ok'; data: unknown } | { status: 'error'; error: string }> =
|
|
224
|
+
new Array(items.length)
|
|
225
|
+
let cursor = 0
|
|
226
|
+
const workerCount = Math.min(CONCURRENCY, items.length)
|
|
227
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
228
|
+
for (;;) {
|
|
229
|
+
const i = cursor++
|
|
230
|
+
if (i >= items.length) return
|
|
231
|
+
try {
|
|
232
|
+
results[i] = { status: 'ok', data: await delegate.update(items[i]) }
|
|
233
|
+
} catch (err) {
|
|
234
|
+
results[i] = { status: 'error', error: mapError(err).message }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
await Promise.all(workers)
|
|
239
|
+
return results
|
|
199
240
|
}
|
|
200
241
|
`;
|
|
201
242
|
}
|