prisma-generator-express 1.52.0 → 1.54.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
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
|
- Router generator with middleware support (before/after hooks per operation)
|
|
14
14
|
- POST read endpoints for all read operations (for complex queries exceeding URL length limits)
|
|
15
|
-
- Express-only progressive read streaming over Server-Sent Events (SSE), using manual stages or auto-include splitting for supported relation reads
|
|
15
|
+
- Express-only progressive read streaming over Server-Sent Events (SSE), using manual stages or auto-include splitting for supported relation reads, including deep `findMany` / `findManyPaginated` auto-include paths
|
|
16
16
|
- Express-only standalone materialized view router for read-only access to registered PostgreSQL materialized views
|
|
17
17
|
- OpenAPI 3.1 spec (JSON and YAML endpoints registered automatically per router)
|
|
18
18
|
- Documentation helpers for contract view and Scalar UI (require manual mounting)
|
|
@@ -74,7 +74,7 @@ Some operations require newer versions:
|
|
|
74
74
|
|
|
75
75
|
The Hono target v1 is tested on Node.js runtimes only. See [Cloudflare Workers and edge runtimes](#cloudflare-workers-and-edge-runtimes).
|
|
76
76
|
|
|
77
|
-
Progressive Endpoint Composition over Server-Sent Events is currently supported by the Express target only. Express supports both manual staged streaming and auto-include streaming for supported relation reads, including single-record reads and
|
|
77
|
+
Progressive Endpoint Composition over Server-Sent Events is currently supported by the Express target only. Express supports both manual staged streaming and auto-include streaming for supported relation reads, including single-record reads and deep `findMany` / `findManyPaginated` reads within the configured planner limits. Fastify and Hono continue to support normal JSON read and write routes.
|
|
78
78
|
|
|
79
79
|
### Database provider support
|
|
80
80
|
|
|
@@ -1562,7 +1562,7 @@ Progressive SSE has two modes:
|
|
|
1562
1562
|
|
|
1563
1563
|
Manual mode is explicit staged data loading. You define stages yourself and each stage decides what query to run and which field path to patch.
|
|
1564
1564
|
|
|
1565
|
-
Auto-include mode is generated relation loading. The router keeps the normal GET endpoint, runs the root query first, then loads supported included relations as separate follow-up queries. For single-record reads, relation paths are streamed as field patches. For `findMany`, direct root
|
|
1565
|
+
Auto-include mode is generated relation loading. The router keeps the normal GET endpoint, runs the root query first, then loads supported included relations as separate follow-up queries. For single-record reads, relation paths are streamed as field patches. For `findMany` and `findManyPaginated`, root rows are streamed first, direct root relation stages are streamed as index-aligned relation batches, and deeper nested relation stages are streamed as locator-based nested relation batches. The terminal `result` event still contains the fully assembled payload.
|
|
1566
1566
|
|
|
1567
1567
|
### Request format
|
|
1568
1568
|
|
|
@@ -1601,8 +1601,9 @@ Auto-include progressive SSE supports these Express GET read operations:
|
|
|
1601
1601
|
- `findFirst`
|
|
1602
1602
|
- `findFirstOrThrow`
|
|
1603
1603
|
- `findMany`
|
|
1604
|
+
- `findManyPaginated`
|
|
1604
1605
|
|
|
1605
|
-
`findMany` support
|
|
1606
|
+
`findMany` and `findManyPaginated` support deep relation loading by flattening parent rows at each relation path, with fallback cases listed in [Auto-include behavior and limits](#auto-include-behavior-and-limits).
|
|
1606
1607
|
|
|
1607
1608
|
If auto-include is configured on an unsupported operation, the router either falls back to single-result SSE or sends an SSE error depending on `fallback`.
|
|
1608
1609
|
|
|
@@ -1630,18 +1631,33 @@ Nested field event:
|
|
|
1630
1631
|
{ "type": "field", "key": "profile.appliedTo", "value": [] }
|
|
1631
1632
|
```
|
|
1632
1633
|
|
|
1633
|
-
Root array event for `findMany` auto-include:
|
|
1634
|
+
Root array event for `findMany` / `findManyPaginated` auto-include:
|
|
1634
1635
|
|
|
1635
1636
|
```json
|
|
1636
1637
|
{ "type": "rootArray", "data": [{ "id": "user-1" }, { "id": "user-2" }] }
|
|
1637
1638
|
```
|
|
1638
1639
|
|
|
1639
|
-
Relation batch event for `findMany` auto-include:
|
|
1640
|
+
Relation batch event for a direct root relation in `findMany` / `findManyPaginated` auto-include:
|
|
1640
1641
|
|
|
1641
1642
|
```json
|
|
1642
1643
|
{ "type": "relationBatch", "relationPath": "profile", "values": [{ "id": "profile-1" }, null] }
|
|
1643
1644
|
```
|
|
1644
1645
|
|
|
1646
|
+
Nested relation batch event for a depth-2-or-deeper relation in `findMany` / `findManyPaginated` auto-include:
|
|
1647
|
+
|
|
1648
|
+
```json
|
|
1649
|
+
{
|
|
1650
|
+
"type": "nestedRelationBatch",
|
|
1651
|
+
"relationPath": "companies.users",
|
|
1652
|
+
"depth": 2,
|
|
1653
|
+
"attachments": [
|
|
1654
|
+
{ "locator": [0, "companies", 0], "value": [{ "id": "user-1" }] }
|
|
1655
|
+
]
|
|
1656
|
+
}
|
|
1657
|
+
```
|
|
1658
|
+
|
|
1659
|
+
Each `attachments[].locator` is walked from `rootArray.data` to the parent object. The leaf field to assign is the last segment of `relationPath`. For example, `relationPath: "companies.users"` and `locator: [0, "companies", 0]` means `rootArray.data[0].companies[0].users = value`.
|
|
1660
|
+
|
|
1645
1661
|
Final result event:
|
|
1646
1662
|
|
|
1647
1663
|
```json
|
|
@@ -1656,7 +1672,9 @@ Error event:
|
|
|
1656
1672
|
|
|
1657
1673
|
For single-record progressive responses, the final `result.data` is the accumulated object built from all applied patches, unless a manual stage returns a stop result.
|
|
1658
1674
|
|
|
1659
|
-
For `findMany` auto-include responses, `rootArray.data` is the source of truth for root row identity and order. Each `relationBatch.values` array is index-aligned with `rootArray.data`, so `values[i]` belongs to `rootArray.data[i]`. The terminal `result.data` is the fully merged array.
|
|
1675
|
+
For `findMany` auto-include responses, `rootArray.data` is the source of truth for root row identity and order. Each depth-1 `relationBatch.values` array is index-aligned with `rootArray.data`, so `values[i]` belongs to `rootArray.data[i]`. Each depth-2-or-deeper `nestedRelationBatch.attachments` array carries locator/value pairs that can be applied to the accumulated root rows immediately. The terminal `result.data` is the fully merged array and can be used as a final reconcile.
|
|
1676
|
+
|
|
1677
|
+
For `findManyPaginated` auto-include responses, `pageMeta` is sent before `rootArray`. The terminal `result.data` has the normal paginated shape: `{ data, total, hasMore }`.
|
|
1660
1678
|
|
|
1661
1679
|
### Manual staged mode
|
|
1662
1680
|
|
|
@@ -1923,7 +1941,7 @@ On single-record reads, auto-include sends root scalar fields first, then sends
|
|
|
1923
1941
|
|
|
1924
1942
|
The final `result` event contains the assembled object.
|
|
1925
1943
|
|
|
1926
|
-
For `findMany`, auto-include sends the root rows first, then sends one relation batch event
|
|
1944
|
+
For `findMany`, auto-include sends the root rows first, then sends one relation batch event for each supported direct root relation stage. Depth-2-or-deeper stages send `nestedRelationBatch` events with locators pointing to the parent object inside the accumulated root rows:
|
|
1927
1945
|
|
|
1928
1946
|
```ts
|
|
1929
1947
|
const listConfig = {
|
|
@@ -1963,7 +1981,7 @@ const response = await fetch(`/user?${params}`, {
|
|
|
1963
1981
|
})
|
|
1964
1982
|
```
|
|
1965
1983
|
|
|
1966
|
-
Example `findMany` auto-include event sequence:
|
|
1984
|
+
Example shallow `findMany` auto-include event sequence:
|
|
1967
1985
|
|
|
1968
1986
|
```json
|
|
1969
1987
|
{ "type": "rootArray", "data": [{ "id": "user-1" }, { "id": "user-2" }] }
|
|
@@ -1977,6 +1995,78 @@ Example `findMany` auto-include event sequence:
|
|
|
1977
1995
|
{ "type": "result", "data": [{ "id": "user-1", "profile": { "id": "profile-1", "displayName": "Alice" } }, { "id": "user-2", "profile": null }] }
|
|
1978
1996
|
```
|
|
1979
1997
|
|
|
1998
|
+
Deep `findMany` and `findManyPaginated` requests use the same planner. Nested stages are loaded by flattening the parent rows at each path, then emitted as locator-based attachment batches:
|
|
1999
|
+
|
|
2000
|
+
```ts
|
|
2001
|
+
const params = encodeQueryParams({
|
|
2002
|
+
take: 20,
|
|
2003
|
+
include: {
|
|
2004
|
+
companies: {
|
|
2005
|
+
include: {
|
|
2006
|
+
users: {
|
|
2007
|
+
include: {
|
|
2008
|
+
profile: {
|
|
2009
|
+
select: {
|
|
2010
|
+
id: true,
|
|
2011
|
+
displayName: true,
|
|
2012
|
+
},
|
|
2013
|
+
},
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
},
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
2019
|
+
})
|
|
2020
|
+
|
|
2021
|
+
const response = await fetch(`/organization/paginated?${params}`, {
|
|
2022
|
+
headers: {
|
|
2023
|
+
Accept: 'text/event-stream',
|
|
2024
|
+
'x-api-variant': 'list',
|
|
2025
|
+
},
|
|
2026
|
+
})
|
|
2027
|
+
```
|
|
2028
|
+
|
|
2029
|
+
Example deep event sequence:
|
|
2030
|
+
|
|
2031
|
+
```json
|
|
2032
|
+
{ "type": "pageMeta", "total": 120, "hasMore": true }
|
|
2033
|
+
```
|
|
2034
|
+
|
|
2035
|
+
```json
|
|
2036
|
+
{ "type": "rootArray", "data": [{ "id": "org-1" }, { "id": "org-2" }] }
|
|
2037
|
+
```
|
|
2038
|
+
|
|
2039
|
+
```json
|
|
2040
|
+
{ "type": "relationBatch", "relationPath": "companies", "values": [[{ "id": "company-1" }], []] }
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
```json
|
|
2044
|
+
{
|
|
2045
|
+
"type": "nestedRelationBatch",
|
|
2046
|
+
"relationPath": "companies.users",
|
|
2047
|
+
"depth": 2,
|
|
2048
|
+
"attachments": [
|
|
2049
|
+
{ "locator": [0, "companies", 0], "value": [{ "id": "user-1" }] }
|
|
2050
|
+
]
|
|
2051
|
+
}
|
|
2052
|
+
```
|
|
2053
|
+
|
|
2054
|
+
```json
|
|
2055
|
+
{
|
|
2056
|
+
"type": "nestedRelationBatch",
|
|
2057
|
+
"relationPath": "companies.users.profile",
|
|
2058
|
+
"depth": 3,
|
|
2059
|
+
"attachments": [
|
|
2060
|
+
{ "locator": [0, "companies", 0, "users", 0], "value": { "id": "profile-1", "displayName": "Alice" } }
|
|
2061
|
+
]
|
|
2062
|
+
}
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
```json
|
|
2066
|
+
{ "type": "result", "data": { "data": [{ "id": "org-1", "companies": [{ "id": "company-1", "users": [{ "id": "user-1", "profile": { "id": "profile-1", "displayName": "Alice" } }] }] }, { "id": "org-2", "companies": [] }], "total": 120, "hasMore": true } }
|
|
2067
|
+
```
|
|
2068
|
+
|
|
2069
|
+
|
|
1980
2070
|
### Auto-include behavior and limits
|
|
1981
2071
|
|
|
1982
2072
|
Auto-include is designed for supported Prisma `include` and relation `select` trees on reads.
|
|
@@ -1988,22 +2078,28 @@ Supported root operations:
|
|
|
1988
2078
|
- `findFirst`
|
|
1989
2079
|
- `findFirstOrThrow`
|
|
1990
2080
|
- `findMany`
|
|
2081
|
+
- `findManyPaginated`
|
|
1991
2082
|
|
|
1992
2083
|
Supported single-record relation shapes:
|
|
1993
2084
|
|
|
1994
2085
|
- direct to-one relation includes/selects
|
|
1995
2086
|
- direct to-many relation includes/selects
|
|
1996
2087
|
- to-many relation args such as `where`, `orderBy`, `take`, `skip`, `cursor`, and `distinct`
|
|
1997
|
-
- nested
|
|
2088
|
+
- nested relation loading through to-one parents
|
|
2089
|
+
|
|
2090
|
+
Single-record auto-include falls back when a nested stage crosses a to-many parent. Direct to-many loading is still supported, but nested loading under that array is not handled by the single-record progressive runtime.
|
|
1998
2091
|
|
|
1999
|
-
Supported `findMany` relation shapes:
|
|
2092
|
+
Supported `findMany` and `findManyPaginated` relation shapes:
|
|
2000
2093
|
|
|
2001
|
-
- direct
|
|
2002
|
-
- direct
|
|
2094
|
+
- direct and nested to-one relation includes/selects
|
|
2095
|
+
- direct and nested to-many relation includes/selects
|
|
2003
2096
|
- relation-level `where` and `orderBy`
|
|
2004
2097
|
- single-column link fields only
|
|
2098
|
+
- nested depth up to the configured planner limit
|
|
2005
2099
|
|
|
2006
|
-
`findMany`
|
|
2100
|
+
For `findMany` and `findManyPaginated`, each stage loads children with a batched query over the flattened parent rows at that stage's `parentPath`. Direct root stages stream `relationBatch` events. Depth-2-or-deeper stages stream `nestedRelationBatch` events with locator/value attachments, then also appear in the terminal `result` event.
|
|
2101
|
+
|
|
2102
|
+
`findMany` and `findManyPaginated` auto-include apply configured pagination limits to the root query before loading relation batches. If the client omits `take`, `pagination.defaultLimit` is applied when configured. If the client sends a large `take`, `pagination.maxLimit` is enforced before the root query runs.
|
|
2007
2103
|
|
|
2008
2104
|
Current MVP fallback cases include:
|
|
2009
2105
|
|
|
@@ -2013,13 +2109,12 @@ Current MVP fallback cases include:
|
|
|
2013
2109
|
- `select` and `omit` at the same level
|
|
2014
2110
|
- relation filters/order/cursor in the root query
|
|
2015
2111
|
- relation filters/order/cursor inside staged relation queries when unsupported
|
|
2016
|
-
- nested relation loading through a to-many parent
|
|
2017
2112
|
- omitted required link fields needed to stitch parent and child records
|
|
2018
2113
|
- planner limits for maximum depth or stage count
|
|
2019
|
-
-
|
|
2020
|
-
- `findMany`
|
|
2021
|
-
- `findMany` to-many relation stages using `take`, `skip`, `cursor`, or `distinct`
|
|
2022
|
-
- `
|
|
2114
|
+
- single-record nested relation loading through a to-many parent
|
|
2115
|
+
- `findMany` / `findManyPaginated` relation stages with composite link fields
|
|
2116
|
+
- `findMany` / `findManyPaginated` to-many relation stages using `take`, `skip`, `cursor`, or `distinct`
|
|
2117
|
+
- `createManyAndReturn` and `updateManyAndReturn`
|
|
2023
2118
|
|
|
2024
2119
|
When fallback happens:
|
|
2025
2120
|
|
|
@@ -2028,7 +2123,7 @@ When fallback happens:
|
|
|
2028
2123
|
|
|
2029
2124
|
If `fallback` is omitted, the default behavior is equivalent to `'singleResult'`.
|
|
2030
2125
|
|
|
2031
|
-
`findMany` auto-include
|
|
2126
|
+
`findMany` and `findManyPaginated` auto-include use a batched relation loading strategy. They do not preserve Prisma's per-parent semantics for to-many `take`, `skip`, `cursor`, or `distinct`, so those cases fall back instead of returning silently different data.
|
|
2032
2127
|
|
|
2033
2128
|
Auto-include does not require `resolveContext` or `progressiveStages`.
|
|
2034
2129
|
|
|
@@ -2071,6 +2166,20 @@ let rows: Array<Record<string, unknown>> = []
|
|
|
2071
2166
|
let fields: Record<string, unknown> = {}
|
|
2072
2167
|
let data: unknown = undefined
|
|
2073
2168
|
|
|
2169
|
+
const lastSegment = (path: string) => {
|
|
2170
|
+
const parts = path.split('.')
|
|
2171
|
+
return parts[parts.length - 1] ?? path
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
const walk = (source: Array<Record<string, unknown>>, locator: Array<number | string>) => {
|
|
2175
|
+
let cursor: unknown = source[locator[0] as number]
|
|
2176
|
+
for (let i = 1; i < locator.length; i++) {
|
|
2177
|
+
if (cursor == null) return null
|
|
2178
|
+
cursor = (cursor as Record<string | number, unknown>)[locator[i]]
|
|
2179
|
+
}
|
|
2180
|
+
return cursor
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2074
2183
|
while (true) {
|
|
2075
2184
|
const { value, done } = await reader.read()
|
|
2076
2185
|
if (done) break
|
|
@@ -2097,12 +2206,24 @@ while (true) {
|
|
|
2097
2206
|
}
|
|
2098
2207
|
|
|
2099
2208
|
if (event.type === 'relationBatch') {
|
|
2209
|
+
const field = lastSegment(event.relationPath)
|
|
2100
2210
|
rows = rows.map((row, index) => ({
|
|
2101
2211
|
...row,
|
|
2102
|
-
[
|
|
2212
|
+
[field]: event.values[index],
|
|
2103
2213
|
}))
|
|
2104
2214
|
}
|
|
2105
2215
|
|
|
2216
|
+
if (event.type === 'nestedRelationBatch') {
|
|
2217
|
+
const field = lastSegment(event.relationPath)
|
|
2218
|
+
for (const attachment of event.attachments) {
|
|
2219
|
+
const parent = walk(rows, attachment.locator)
|
|
2220
|
+
if (parent && typeof parent === 'object' && !Array.isArray(parent)) {
|
|
2221
|
+
const record = parent as Record<string, unknown>
|
|
2222
|
+
record[field] = attachment.value
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2106
2227
|
if (event.type === 'result') {
|
|
2107
2228
|
data = event.data
|
|
2108
2229
|
}
|
|
@@ -2118,6 +2239,8 @@ For React Query, include the variant and mode in the query key:
|
|
|
2118
2239
|
|
|
2119
2240
|
Do not reuse the same query key as the JSON endpoint because the same URL can return different shapes depending on `x-api-variant`.
|
|
2120
2241
|
|
|
2242
|
+
For deep `findMany` / `findManyPaginated` auto-include, apply `nestedRelationBatch` events for progressive rendering and still treat the final `result` event as the authoritative nested payload for reconciliation. Direct root relation stages emit `relationBatch`; depth-2-or-deeper relation stages emit `nestedRelationBatch`.
|
|
2243
|
+
|
|
2121
2244
|
### Runtime notes
|
|
2122
2245
|
|
|
2123
2246
|
The SSE response sets:
|
|
@@ -237,7 +237,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
237
237
|
const isAutoIncludeReadable =
|
|
238
238
|
baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
|
|
239
239
|
baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
|
|
240
|
-
baseOp === 'findMany'
|
|
240
|
+
baseOp === 'findMany' || baseOp === 'findManyPaginated'
|
|
241
241
|
|
|
242
242
|
if (!isAutoIncludeReadable) {
|
|
243
243
|
if (progressiveConfig.fallback === 'error') {
|
|
@@ -263,7 +263,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
263
263
|
res,
|
|
264
264
|
ctx,
|
|
265
265
|
args,
|
|
266
|
-
baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany',
|
|
266
|
+
baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findManyPaginated',
|
|
267
267
|
modelName: '${modelName}',
|
|
268
268
|
delegateKey: '${delegateKey}',
|
|
269
269
|
models: relationModels,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-generator-express",
|
|
3
3
|
"description": "Prisma generator for Express, Fastify, and Hono CRUD APIs with OpenAPI documentation",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.54.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -269,15 +269,9 @@ function walk(
|
|
|
269
269
|
isPlainObject(relationArgs.omit)
|
|
270
270
|
|
|
271
271
|
if (hasNestedProjection) {
|
|
272
|
-
const stagesBeforeRecursion = ctx.stages.length
|
|
273
|
-
|
|
274
272
|
const nested = walk(ctx, relation.type, relationPath, relationArgs, depth + 1)
|
|
275
273
|
if (nested.unsupportedReason) return nested
|
|
276
274
|
|
|
277
|
-
if (relation.isList && ctx.stages.length > stagesBeforeRecursion) {
|
|
278
|
-
return { unsupportedReason: 'nested relation through to-many parent not supported in MVP' }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
275
|
if (nested.projectionAfterStrip) {
|
|
282
276
|
ctx.stages[stageIndex].stageArgs.select = nested.projectionAfterStrip
|
|
283
277
|
}
|
|
@@ -9,12 +9,15 @@ import {
|
|
|
9
9
|
sendSSEProgress,
|
|
10
10
|
sendSSERootArray,
|
|
11
11
|
sendSSERelationBatch,
|
|
12
|
+
sendSSENestedRelationBatch,
|
|
13
|
+
sendSSEPageMeta,
|
|
12
14
|
runSingleResultSSE,
|
|
13
15
|
emitTerminalSSEError,
|
|
14
16
|
setByPath,
|
|
15
17
|
getDelegate,
|
|
16
18
|
getExtendedClient,
|
|
17
19
|
applyPaginationLimits,
|
|
20
|
+
countForPagination,
|
|
18
21
|
mapError,
|
|
19
22
|
type OperationContext,
|
|
20
23
|
type PrismaDelegate,
|
|
@@ -39,6 +42,7 @@ export type AutoIncludeBaseOp =
|
|
|
39
42
|
| 'findFirst'
|
|
40
43
|
| 'findFirstOrThrow'
|
|
41
44
|
| 'findMany'
|
|
45
|
+
| 'findManyPaginated'
|
|
42
46
|
|
|
43
47
|
export type RunAutoIncludeOptions = {
|
|
44
48
|
req: Request
|
|
@@ -54,6 +58,15 @@ export type RunAutoIncludeOptions = {
|
|
|
54
58
|
signal?: AbortSignal
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
type RowPair = {
|
|
62
|
+
internal: Record<string, unknown>
|
|
63
|
+
public: Record<string, unknown>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type ParentEntry = RowPair & {
|
|
67
|
+
locator: Array<number | string>
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
function readPath(source: Record<string, unknown>, path: string): unknown {
|
|
58
71
|
if (path === '') return source
|
|
59
72
|
const parts = path.split('.')
|
|
@@ -196,9 +209,6 @@ function findManyUnsupportedReason(plan: AutoIncludePlan): string | null {
|
|
|
196
209
|
if (rel.parentLinkFields.length !== 1 || rel.childLinkFields.length !== 1) {
|
|
197
210
|
return 'auto-progressive fallback: composite link fields not supported for findMany batched auto-include'
|
|
198
211
|
}
|
|
199
|
-
if (stage.depth > 1) {
|
|
200
|
-
return 'auto-progressive fallback: nested relations not supported for findMany batched auto-include'
|
|
201
|
-
}
|
|
202
212
|
if (rel.isList) {
|
|
203
213
|
const sa = stage.stageArgs
|
|
204
214
|
if (
|
|
@@ -214,6 +224,67 @@ function findManyUnsupportedReason(plan: AutoIncludePlan): string | null {
|
|
|
214
224
|
return null
|
|
215
225
|
}
|
|
216
226
|
|
|
227
|
+
function singleUnsupportedReason(plan: AutoIncludePlan): string | null {
|
|
228
|
+
const stagesByPath = new Map(plan.stages.map((s) => [s.relationPath, s]))
|
|
229
|
+
for (const stage of plan.stages) {
|
|
230
|
+
let parentPath = stage.parentPath
|
|
231
|
+
while (parentPath) {
|
|
232
|
+
const parentStage = stagesByPath.get(parentPath)
|
|
233
|
+
if (parentStage?.relationField.isList) {
|
|
234
|
+
return 'auto-progressive fallback: nested relation through to-many parent is not supported for single-result auto-include'
|
|
235
|
+
}
|
|
236
|
+
const dot = parentPath.lastIndexOf('.')
|
|
237
|
+
parentPath = dot === -1 ? '' : parentPath.slice(0, dot)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function collectParentEntries(
|
|
244
|
+
rootPairs: RowPair[],
|
|
245
|
+
parentPath: string,
|
|
246
|
+
): ParentEntry[] {
|
|
247
|
+
const initial: ParentEntry[] = rootPairs.map((p, i) => ({
|
|
248
|
+
internal: p.internal,
|
|
249
|
+
public: p.public,
|
|
250
|
+
locator: [i],
|
|
251
|
+
}))
|
|
252
|
+
if (parentPath === '') return initial
|
|
253
|
+
|
|
254
|
+
let entries = initial
|
|
255
|
+
for (const segment of parentPath.split('.')) {
|
|
256
|
+
const next: ParentEntry[] = []
|
|
257
|
+
for (const entry of entries) {
|
|
258
|
+
const internalValue = entry.internal[segment]
|
|
259
|
+
const publicValue = entry.public[segment]
|
|
260
|
+
if (Array.isArray(internalValue) && Array.isArray(publicValue)) {
|
|
261
|
+
const length = Math.min(internalValue.length, publicValue.length)
|
|
262
|
+
for (let i = 0; i < length; i++) {
|
|
263
|
+
const internalItem = internalValue[i]
|
|
264
|
+
const publicItem = publicValue[i]
|
|
265
|
+
if (isPlainObject(internalItem) && isPlainObject(publicItem)) {
|
|
266
|
+
next.push({
|
|
267
|
+
internal: internalItem,
|
|
268
|
+
public: publicItem,
|
|
269
|
+
locator: [...entry.locator, segment, i],
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
if (isPlainObject(internalValue) && isPlainObject(publicValue)) {
|
|
276
|
+
next.push({
|
|
277
|
+
internal: internalValue,
|
|
278
|
+
public: publicValue,
|
|
279
|
+
locator: [...entry.locator, segment],
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
entries = next
|
|
284
|
+
}
|
|
285
|
+
return entries
|
|
286
|
+
}
|
|
287
|
+
|
|
217
288
|
async function runOneStageSingle(options: {
|
|
218
289
|
extended: unknown
|
|
219
290
|
models: Record<string, ModelRelationMap>
|
|
@@ -300,7 +371,7 @@ async function runAutoIncludeSingle(
|
|
|
300
371
|
|
|
301
372
|
let rootResult: unknown
|
|
302
373
|
try {
|
|
303
|
-
rootResult = await rootDelegate[baseOp as Exclude<AutoIncludeBaseOp, 'findMany'>](plan.rootArgs)
|
|
374
|
+
rootResult = await rootDelegate[baseOp as Exclude<AutoIncludeBaseOp, 'findMany' | 'findManyPaginated'>](plan.rootArgs)
|
|
304
375
|
} catch (err) {
|
|
305
376
|
if (isClientGone()) return
|
|
306
377
|
console.error('[auto-progressive] root query failed:', err)
|
|
@@ -470,16 +541,12 @@ async function runOneStageMany(options: {
|
|
|
470
541
|
extended: unknown
|
|
471
542
|
models: Record<string, ModelRelationMap>
|
|
472
543
|
stage: AutoIncludeStage
|
|
473
|
-
|
|
474
|
-
publicRows: Record<string, unknown>[]
|
|
544
|
+
parentEntries: ParentEntry[]
|
|
475
545
|
internalFieldPaths: string[]
|
|
476
546
|
res: Response
|
|
477
547
|
isAborted: () => boolean
|
|
478
548
|
}): Promise<void> {
|
|
479
|
-
const {
|
|
480
|
-
extended, models, stage, internalRows, publicRows,
|
|
481
|
-
internalFieldPaths, res, isAborted,
|
|
482
|
-
} = options
|
|
549
|
+
const { extended, models, stage, parentEntries, internalFieldPaths, res, isAborted } = options
|
|
483
550
|
|
|
484
551
|
if (isAborted()) return
|
|
485
552
|
|
|
@@ -492,7 +559,17 @@ async function runOneStageMany(options: {
|
|
|
492
559
|
throw new Error('Target model not in relation metadata: ' + rel.type)
|
|
493
560
|
}
|
|
494
561
|
|
|
495
|
-
|
|
562
|
+
if (parentEntries.length === 0) {
|
|
563
|
+
if (stage.depth === 1) {
|
|
564
|
+
sendSSERelationBatch(res, stage.relationPath, [])
|
|
565
|
+
} else {
|
|
566
|
+
sendSSENestedRelationBatch(res, stage.relationPath, stage.depth, [])
|
|
567
|
+
}
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const internalParents = parentEntries.map((p) => p.internal)
|
|
572
|
+
const distinctValues = collectDistinctParentValues(internalParents, parentKey)
|
|
496
573
|
const delegate: PrismaDelegate = getDelegate(extended, targetModel.delegateKey)
|
|
497
574
|
|
|
498
575
|
const children: unknown[] = []
|
|
@@ -515,11 +592,11 @@ async function runOneStageMany(options: {
|
|
|
515
592
|
: internalFieldPaths
|
|
516
593
|
|
|
517
594
|
const grouped = groupRelatedRows(children, childKey)
|
|
518
|
-
const publicValues: unknown[] = new Array(
|
|
595
|
+
const publicValues: unknown[] = new Array(parentEntries.length)
|
|
519
596
|
|
|
520
|
-
for (let i = 0; i <
|
|
521
|
-
const
|
|
522
|
-
const fkVal =
|
|
597
|
+
for (let i = 0; i < parentEntries.length; i++) {
|
|
598
|
+
const entry = parentEntries[i]
|
|
599
|
+
const fkVal = entry.internal[parentKey]
|
|
523
600
|
let internalVal: unknown
|
|
524
601
|
|
|
525
602
|
if (fkVal === undefined || fkVal === null) {
|
|
@@ -535,13 +612,131 @@ async function runOneStageMany(options: {
|
|
|
535
612
|
|
|
536
613
|
const publicVal = buildPublicForStage(internalVal, effectivePaths, stage.relationPath)
|
|
537
614
|
|
|
538
|
-
|
|
539
|
-
|
|
615
|
+
entry.internal[stage.relationName] = internalVal
|
|
616
|
+
entry.public[stage.relationName] = publicVal
|
|
540
617
|
publicValues[i] = publicVal
|
|
541
618
|
}
|
|
542
619
|
|
|
543
620
|
if (isAborted()) return
|
|
544
|
-
|
|
621
|
+
|
|
622
|
+
if (stage.depth === 1) {
|
|
623
|
+
sendSSERelationBatch(res, stage.relationPath, publicValues)
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const attachments = parentEntries.map((entry, i) => ({
|
|
628
|
+
locator: entry.locator,
|
|
629
|
+
value: publicValues[i],
|
|
630
|
+
}))
|
|
631
|
+
sendSSENestedRelationBatch(res, stage.relationPath, stage.depth, attachments)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function buildRootPairs(
|
|
635
|
+
rootRows: unknown[],
|
|
636
|
+
internalFieldPaths: string[],
|
|
637
|
+
): { publicRows: Record<string, unknown>[]; rootPairs: RowPair[] } {
|
|
638
|
+
const n = rootRows.length
|
|
639
|
+
const publicRows: Record<string, unknown>[] = new Array(n)
|
|
640
|
+
const rootPairs: RowPair[] = new Array(n)
|
|
641
|
+
for (let i = 0; i < n; i++) {
|
|
642
|
+
const row = rootRows[i]
|
|
643
|
+
if (!isPlainObject(row)) {
|
|
644
|
+
const internalEmpty: Record<string, unknown> = {}
|
|
645
|
+
const publicEmpty: Record<string, unknown> = {}
|
|
646
|
+
publicRows[i] = publicEmpty
|
|
647
|
+
rootPairs[i] = { internal: internalEmpty, public: publicEmpty }
|
|
648
|
+
continue
|
|
649
|
+
}
|
|
650
|
+
const internalCopy: Record<string, unknown> = { ...row }
|
|
651
|
+
const publicCopy: Record<string, unknown> = { ...row }
|
|
652
|
+
stripInternalAtScope(publicCopy, internalFieldPaths, '')
|
|
653
|
+
publicRows[i] = publicCopy
|
|
654
|
+
rootPairs[i] = { internal: internalCopy, public: publicCopy }
|
|
655
|
+
}
|
|
656
|
+
return { publicRows, rootPairs }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function processFindManyStages(args: {
|
|
660
|
+
extended: unknown
|
|
661
|
+
models: Record<string, ModelRelationMap>
|
|
662
|
+
plan: AutoIncludePlan
|
|
663
|
+
rootPairs: RowPair[]
|
|
664
|
+
res: Response
|
|
665
|
+
signal?: AbortSignal
|
|
666
|
+
}): Promise<string | null> {
|
|
667
|
+
const { extended, models, plan, rootPairs, res, signal } = args
|
|
668
|
+
const groups = groupStagesByDepth(plan.stages)
|
|
669
|
+
let completed = 0
|
|
670
|
+
let stageErrorMessage: string | null = null
|
|
671
|
+
const isAborted = () =>
|
|
672
|
+
stageErrorMessage !== null ||
|
|
673
|
+
signal?.aborted === true ||
|
|
674
|
+
res.writableEnded ||
|
|
675
|
+
res.destroyed
|
|
676
|
+
|
|
677
|
+
for (const group of groups) {
|
|
678
|
+
if (signal?.aborted === true || res.writableEnded || res.destroyed) return stageErrorMessage
|
|
679
|
+
if (stageErrorMessage) break
|
|
680
|
+
|
|
681
|
+
await runConcurrent(group, STAGE_CONCURRENCY, async (stage) => {
|
|
682
|
+
if (isAborted()) return
|
|
683
|
+
const parentEntries = collectParentEntries(rootPairs, stage.parentPath)
|
|
684
|
+
try {
|
|
685
|
+
await runOneStageMany({
|
|
686
|
+
extended,
|
|
687
|
+
models,
|
|
688
|
+
stage,
|
|
689
|
+
parentEntries,
|
|
690
|
+
internalFieldPaths: plan.internalFieldPaths,
|
|
691
|
+
res,
|
|
692
|
+
isAborted,
|
|
693
|
+
})
|
|
694
|
+
} catch (err) {
|
|
695
|
+
if (isAborted()) return
|
|
696
|
+
console.error('[auto-progressive] stage failed:', stage.relationPath, err)
|
|
697
|
+
stageErrorMessage = mapError(err).message
|
|
698
|
+
return
|
|
699
|
+
}
|
|
700
|
+
if (isAborted()) return
|
|
701
|
+
completed++
|
|
702
|
+
sendSSEProgress(res, stage.relationPath, completed, plan.stages.length)
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
return stageErrorMessage
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function runPaginatedRoot(
|
|
709
|
+
extended: unknown,
|
|
710
|
+
rootDelegate: PrismaDelegate,
|
|
711
|
+
delegateKey: string,
|
|
712
|
+
rootArgs: Record<string, unknown>,
|
|
713
|
+
distinctCountLimit: number | undefined,
|
|
714
|
+
): Promise<{ data: unknown[]; count: number }> {
|
|
715
|
+
const txClient = extended as {
|
|
716
|
+
$transaction?: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T>
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (typeof txClient.$transaction === 'function') {
|
|
720
|
+
try {
|
|
721
|
+
const result = await txClient.$transaction(async (tx: unknown) => {
|
|
722
|
+
const txDelegate = getDelegate(tx, delegateKey)
|
|
723
|
+
const d = await txDelegate.findMany(rootArgs)
|
|
724
|
+
const t = await countForPagination(txDelegate, rootArgs, undefined, undefined, distinctCountLimit)
|
|
725
|
+
return { d, t }
|
|
726
|
+
})
|
|
727
|
+
return { data: result.d as unknown[], count: result.t }
|
|
728
|
+
} catch (err) {
|
|
729
|
+
const e = err as { code?: string }
|
|
730
|
+
if (e?.code !== 'P2028') throw err
|
|
731
|
+
console.warn('[auto-progressive] Interactive transactions not available, paginated queries are non-atomic')
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const [data, count] = await Promise.all([
|
|
736
|
+
rootDelegate.findMany(rootArgs),
|
|
737
|
+
countForPagination(rootDelegate, rootArgs, undefined, undefined, distinctCountLimit),
|
|
738
|
+
])
|
|
739
|
+
return { data: data as unknown[], count }
|
|
545
740
|
}
|
|
546
741
|
|
|
547
742
|
async function runAutoIncludeMany(
|
|
@@ -582,78 +777,105 @@ async function runAutoIncludeMany(
|
|
|
582
777
|
return
|
|
583
778
|
}
|
|
584
779
|
|
|
585
|
-
const
|
|
586
|
-
const publicRows: Record<string, unknown>[] = new Array(rootResult.length)
|
|
780
|
+
const { publicRows, rootPairs } = buildRootPairs(rootResult, plan.internalFieldPaths)
|
|
587
781
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
782
|
+
sendSSERootArray(res, publicRows)
|
|
783
|
+
sendSSEProgress(res, 'root', 0, plan.stages.length)
|
|
784
|
+
|
|
785
|
+
const stageError = await processFindManyStages({
|
|
786
|
+
extended, models, plan, rootPairs, res, signal,
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
if (isClientGone()) return
|
|
790
|
+
|
|
791
|
+
if (stageError) {
|
|
792
|
+
if (!res.writableEnded && !res.destroyed) {
|
|
793
|
+
sendSSEError(res, stageError)
|
|
594
794
|
}
|
|
595
|
-
|
|
596
|
-
const publicCopy: Record<string, unknown> = { ...row }
|
|
597
|
-
stripInternalAtScope(publicCopy, plan.internalFieldPaths, '')
|
|
598
|
-
internalRows[i] = internalCopy
|
|
599
|
-
publicRows[i] = publicCopy
|
|
795
|
+
return
|
|
600
796
|
}
|
|
601
797
|
|
|
602
|
-
|
|
603
|
-
|
|
798
|
+
if (res.writableEnded || res.destroyed) return
|
|
799
|
+
sendSSEResult(res, publicRows)
|
|
800
|
+
} catch (err) {
|
|
801
|
+
if (isClientGone()) return
|
|
802
|
+
console.error('[auto-progressive] many dispatch error:', err)
|
|
803
|
+
if (!res.writableEnded && !res.destroyed) {
|
|
804
|
+
sendSSEError(res, mapError(err).message)
|
|
805
|
+
}
|
|
806
|
+
} finally {
|
|
807
|
+
endSSE(res, keepalive)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
604
810
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
signal?.aborted === true ||
|
|
611
|
-
res.writableEnded ||
|
|
612
|
-
res.destroyed
|
|
811
|
+
async function runAutoIncludePaginated(
|
|
812
|
+
options: RunAutoIncludeOptions,
|
|
813
|
+
plan: AutoIncludePlan,
|
|
814
|
+
): Promise<void> {
|
|
815
|
+
const { res, ctx, delegateKey, models, signal } = options
|
|
613
816
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (stageErrorMessage) break
|
|
817
|
+
const isClientGone = () =>
|
|
818
|
+
signal?.aborted === true || res.writableEnded || res.destroyed
|
|
617
819
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
820
|
+
let keepalive: IntervalHandle | null = null
|
|
821
|
+
try {
|
|
822
|
+
initSSE(res)
|
|
823
|
+
keepalive = startSSEKeepalive(res)
|
|
824
|
+
if (isClientGone()) return
|
|
825
|
+
|
|
826
|
+
const extended = await getExtendedClient(ctx)
|
|
827
|
+
if (isClientGone()) return
|
|
828
|
+
|
|
829
|
+
const rootDelegate = getDelegate(extended, delegateKey)
|
|
830
|
+
const rootArgs = applyPaginationLimits(plan.rootArgs, ctx.paginationConfig)
|
|
831
|
+
const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
|
|
832
|
+
|
|
833
|
+
let rootRows: unknown[]
|
|
834
|
+
let total: number
|
|
835
|
+
try {
|
|
836
|
+
const { data, count } = await runPaginatedRoot(
|
|
837
|
+
extended, rootDelegate, delegateKey, rootArgs, distinctCountLimit,
|
|
838
|
+
)
|
|
839
|
+
rootRows = data
|
|
840
|
+
total = count
|
|
841
|
+
} catch (err) {
|
|
842
|
+
if (isClientGone()) return
|
|
843
|
+
console.error('[auto-progressive] root findManyPaginated failed:', err)
|
|
844
|
+
sendSSEError(res, mapError(err).message)
|
|
845
|
+
return
|
|
641
846
|
}
|
|
642
847
|
|
|
643
848
|
if (isClientGone()) return
|
|
644
849
|
|
|
645
|
-
|
|
850
|
+
const skip = typeof rootArgs.skip === 'number' ? rootArgs.skip : 0
|
|
851
|
+
const takeRaw = typeof rootArgs.take === 'number' ? rootArgs.take : rootRows.length
|
|
852
|
+
const absTake = Math.abs(takeRaw)
|
|
853
|
+
const hasMore = rootRows.length >= absTake && skip + rootRows.length < total
|
|
854
|
+
|
|
855
|
+
const { publicRows, rootPairs } = buildRootPairs(rootRows, plan.internalFieldPaths)
|
|
856
|
+
|
|
857
|
+
sendSSEPageMeta(res, total, hasMore)
|
|
858
|
+
sendSSERootArray(res, publicRows)
|
|
859
|
+
sendSSEProgress(res, 'root', 0, plan.stages.length)
|
|
860
|
+
|
|
861
|
+
const stageError = await processFindManyStages({
|
|
862
|
+
extended, models, plan, rootPairs, res, signal,
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
if (isClientGone()) return
|
|
866
|
+
|
|
867
|
+
if (stageError) {
|
|
646
868
|
if (!res.writableEnded && !res.destroyed) {
|
|
647
|
-
sendSSEError(res,
|
|
869
|
+
sendSSEError(res, stageError)
|
|
648
870
|
}
|
|
649
871
|
return
|
|
650
872
|
}
|
|
651
873
|
|
|
652
874
|
if (res.writableEnded || res.destroyed) return
|
|
653
|
-
sendSSEResult(res, publicRows)
|
|
875
|
+
sendSSEResult(res, { data: publicRows, total, hasMore })
|
|
654
876
|
} catch (err) {
|
|
655
877
|
if (isClientGone()) return
|
|
656
|
-
console.error('[auto-progressive]
|
|
878
|
+
console.error('[auto-progressive] paginated dispatch error:', err)
|
|
657
879
|
if (!res.writableEnded && !res.destroyed) {
|
|
658
880
|
sendSSEError(res, mapError(err).message)
|
|
659
881
|
}
|
|
@@ -682,9 +904,12 @@ export async function runAutoIncludeProgressive(
|
|
|
682
904
|
return handleAutoIncludeFallback(options, plan.unsupportedReason)
|
|
683
905
|
}
|
|
684
906
|
|
|
685
|
-
if (options.baseOp === 'findMany') {
|
|
907
|
+
if (options.baseOp === 'findMany' || options.baseOp === 'findManyPaginated') {
|
|
686
908
|
const reason = findManyUnsupportedReason(plan)
|
|
687
909
|
if (reason) return handleAutoIncludeFallback(options, reason)
|
|
910
|
+
} else {
|
|
911
|
+
const reason = singleUnsupportedReason(plan)
|
|
912
|
+
if (reason) return handleAutoIncludeFallback(options, reason)
|
|
688
913
|
}
|
|
689
914
|
|
|
690
915
|
if (plan.stages.length === 0) {
|
|
@@ -698,5 +923,8 @@ export async function runAutoIncludeProgressive(
|
|
|
698
923
|
if (options.baseOp === 'findMany') {
|
|
699
924
|
return runAutoIncludeMany(options, plan)
|
|
700
925
|
}
|
|
926
|
+
if (options.baseOp === 'findManyPaginated') {
|
|
927
|
+
return runAutoIncludePaginated(options, plan)
|
|
928
|
+
}
|
|
701
929
|
return runAutoIncludeSingle(options, plan)
|
|
702
930
|
}
|
|
@@ -503,6 +503,23 @@ export function sendSSERelationBatch(
|
|
|
503
503
|
return sendSSE(res, { type: 'relationBatch', relationPath, values })
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
export function sendSSENestedRelationBatch(
|
|
507
|
+
res: SseWritable,
|
|
508
|
+
relationPath: string,
|
|
509
|
+
depth: number,
|
|
510
|
+
attachments: Array<{ locator: Array<number | string>; value: unknown }>,
|
|
511
|
+
): boolean {
|
|
512
|
+
return sendSSE(res, { type: 'nestedRelationBatch', relationPath, depth, attachments })
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function sendSSEPageMeta(
|
|
516
|
+
res: SseWritable,
|
|
517
|
+
total: number,
|
|
518
|
+
hasMore: boolean,
|
|
519
|
+
): boolean {
|
|
520
|
+
return sendSSE(res, { type: 'pageMeta', total, hasMore })
|
|
521
|
+
}
|
|
522
|
+
|
|
506
523
|
export function sendSSEError(res: SseWritable, message: string): boolean {
|
|
507
524
|
if (res.writableEnded || res.destroyed) return false
|
|
508
525
|
try {
|
|
@@ -253,7 +253,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
253
253
|
const isAutoIncludeReadable =
|
|
254
254
|
baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
|
|
255
255
|
baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
|
|
256
|
-
baseOp === 'findMany'
|
|
256
|
+
baseOp === 'findMany' || baseOp === 'findManyPaginated'
|
|
257
257
|
|
|
258
258
|
if (!isAutoIncludeReadable) {
|
|
259
259
|
if (progressiveConfig.fallback === 'error') {
|
|
@@ -279,7 +279,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
279
279
|
res,
|
|
280
280
|
ctx,
|
|
281
281
|
args,
|
|
282
|
-
baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany',
|
|
282
|
+
baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findManyPaginated',
|
|
283
283
|
modelName: '${modelName}',
|
|
284
284
|
delegateKey: '${delegateKey}',
|
|
285
285
|
models: relationModels,
|