prisma-generator-express 1.51.0 → 1.52.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 single-record relation reads
15
+ - Express-only progressive read streaming over Server-Sent Events (SSE), using manual stages or auto-include splitting for supported relation reads
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 single-record relation reads. Fastify and Hono continue to support normal JSON read and write routes.
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 limited `findMany` reads. Fastify and Hono continue to support normal JSON read and write routes.
78
78
 
79
79
  ### Database provider support
80
80
 
@@ -1558,11 +1558,11 @@ Progressive SSE has two modes:
1558
1558
  | Mode | Config | Best for |
1559
1559
  | ---- | ------ | -------- |
1560
1560
  | Manual stages | `{ stages: [...] }` or `{ mode: 'manual', stages: [...] }` | Custom page-level composition where each stage runs its own query and returns patches |
1561
- | Auto include | `{ mode: 'autoInclude' }` | Single-record reads where the client already sends a Prisma `include` or relation `select` tree and you want relation fields streamed progressively |
1561
+ | Auto include | `{ mode: 'autoInclude' }` | Reads where the client already sends a Prisma `include` or relation `select` tree and you want relation fields streamed progressively |
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 single-record query first, streams root fields, then loads supported included relations as separate follow-up queries and streams each relation path as it resolves.
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 relations are loaded in batches and streamed as index-aligned relation batches.
1566
1566
 
1567
1567
  ### Request format
1568
1568
 
@@ -1594,14 +1594,17 @@ Manual progressive SSE can be configured on Express GET read operations only:
1594
1594
  - `aggregate`
1595
1595
  - `groupBy`
1596
1596
 
1597
- Auto-include progressive SSE only supports single-record read operations:
1597
+ Auto-include progressive SSE supports these Express GET read operations:
1598
1598
 
1599
1599
  - `findUnique`
1600
1600
  - `findUniqueOrThrow`
1601
1601
  - `findFirst`
1602
1602
  - `findFirstOrThrow`
1603
+ - `findMany`
1604
+
1605
+ `findMany` support is intentionally narrower than single-record support. It supports direct root relation loading only, with additional fallback cases listed in [Auto-include behavior and limits](#auto-include-behavior-and-limits).
1603
1606
 
1604
- If auto-include is configured on another operation, the router either falls back to single-result SSE or sends an SSE error depending on `fallback`.
1607
+ 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`.
1605
1608
 
1606
1609
  Write operations do not support progressive SSE.
1607
1610
 
@@ -1627,6 +1630,18 @@ Nested field event:
1627
1630
  { "type": "field", "key": "profile.appliedTo", "value": [] }
1628
1631
  ```
1629
1632
 
1633
+ Root array event for `findMany` auto-include:
1634
+
1635
+ ```json
1636
+ { "type": "rootArray", "data": [{ "id": "user-1" }, { "id": "user-2" }] }
1637
+ ```
1638
+
1639
+ Relation batch event for `findMany` auto-include:
1640
+
1641
+ ```json
1642
+ { "type": "relationBatch", "relationPath": "profile", "values": [{ "id": "profile-1" }, null] }
1643
+ ```
1644
+
1630
1645
  Final result event:
1631
1646
 
1632
1647
  ```json
@@ -1639,7 +1654,9 @@ Error event:
1639
1654
  { "type": "error", "message": "Could not load progressive response" }
1640
1655
  ```
1641
1656
 
1642
- The final `result.data` is the accumulated object built from all applied patches, unless a manual stage returns a stop result.
1657
+ 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
+
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.
1643
1660
 
1644
1661
  ### Manual staged mode
1645
1662
 
@@ -1890,7 +1907,7 @@ const response = await fetch(`/user/unique?${params}`, {
1890
1907
  })
1891
1908
  ```
1892
1909
 
1893
- Auto-include sends root scalar fields first, then sends relation field events as separate relation queries finish:
1910
+ On single-record reads, auto-include sends root scalar fields first, then sends relation field events as separate relation queries finish:
1894
1911
 
1895
1912
  ```json
1896
1913
  { "type": "field", "key": "id", "value": "user-id" }
@@ -1906,9 +1923,63 @@ Auto-include sends root scalar fields first, then sends relation field events as
1906
1923
 
1907
1924
  The final `result` event contains the assembled object.
1908
1925
 
1926
+ For `findMany`, auto-include sends the root rows first, then sends one relation batch event per supported relation stage:
1927
+
1928
+ ```ts
1929
+ const listConfig = {
1930
+ guard: {
1931
+ variantHeader: 'x-api-variant',
1932
+ },
1933
+
1934
+ findMany: {
1935
+ progressive: {
1936
+ list: {
1937
+ enabled: true,
1938
+ mode: 'autoInclude',
1939
+ fallback: 'singleResult',
1940
+ },
1941
+ },
1942
+ },
1943
+ }
1944
+
1945
+ const params = encodeQueryParams({
1946
+ where: { isActive: true },
1947
+ take: 50,
1948
+ include: {
1949
+ profile: {
1950
+ select: {
1951
+ id: true,
1952
+ displayName: true,
1953
+ },
1954
+ },
1955
+ },
1956
+ })
1957
+
1958
+ const response = await fetch(`/user?${params}`, {
1959
+ headers: {
1960
+ Accept: 'text/event-stream',
1961
+ 'x-api-variant': 'list',
1962
+ },
1963
+ })
1964
+ ```
1965
+
1966
+ Example `findMany` auto-include event sequence:
1967
+
1968
+ ```json
1969
+ { "type": "rootArray", "data": [{ "id": "user-1" }, { "id": "user-2" }] }
1970
+ ```
1971
+
1972
+ ```json
1973
+ { "type": "relationBatch", "relationPath": "profile", "values": [{ "id": "profile-1", "displayName": "Alice" }, null] }
1974
+ ```
1975
+
1976
+ ```json
1977
+ { "type": "result", "data": [{ "id": "user-1", "profile": { "id": "profile-1", "displayName": "Alice" } }, { "id": "user-2", "profile": null }] }
1978
+ ```
1979
+
1909
1980
  ### Auto-include behavior and limits
1910
1981
 
1911
- Auto-include is designed for supported Prisma `include` and relation `select` trees on single-record reads.
1982
+ Auto-include is designed for supported Prisma `include` and relation `select` trees on reads.
1912
1983
 
1913
1984
  Supported root operations:
1914
1985
 
@@ -1916,14 +1987,24 @@ Supported root operations:
1916
1987
  - `findUniqueOrThrow`
1917
1988
  - `findFirst`
1918
1989
  - `findFirstOrThrow`
1990
+ - `findMany`
1919
1991
 
1920
- Supported relation shapes:
1992
+ Supported single-record relation shapes:
1921
1993
 
1922
1994
  - direct to-one relation includes/selects
1923
1995
  - direct to-many relation includes/selects
1924
1996
  - to-many relation args such as `where`, `orderBy`, `take`, `skip`, `cursor`, and `distinct`
1925
1997
  - nested to-one relation loading through to-one parents
1926
1998
 
1999
+ Supported `findMany` relation shapes:
2000
+
2001
+ - direct root to-one relation includes/selects
2002
+ - direct root to-many relation includes/selects
2003
+ - relation-level `where` and `orderBy`
2004
+ - single-column link fields only
2005
+
2006
+ `findMany` auto-include applies 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
+
1927
2008
  Current MVP fallback cases include:
1928
2009
 
1929
2010
  - `_count` in `select` or `include`
@@ -1935,6 +2016,10 @@ Current MVP fallback cases include:
1935
2016
  - nested relation loading through a to-many parent
1936
2017
  - omitted required link fields needed to stitch parent and child records
1937
2018
  - planner limits for maximum depth or stage count
2019
+ - `findMany` relation stages with composite link fields
2020
+ - `findMany` nested relation stages deeper than direct root relations
2021
+ - `findMany` to-many relation stages using `take`, `skip`, `cursor`, or `distinct`
2022
+ - `findManyPaginated`, `createManyAndReturn`, and `updateManyAndReturn`
1938
2023
 
1939
2024
  When fallback happens:
1940
2025
 
@@ -1943,6 +2028,8 @@ When fallback happens:
1943
2028
 
1944
2029
  If `fallback` is omitted, the default behavior is equivalent to `'singleResult'`.
1945
2030
 
2031
+ `findMany` auto-include uses a batched relation loading strategy. It does 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
+
1946
2033
  Auto-include does not require `resolveContext` or `progressiveStages`.
1947
2034
 
1948
2035
  ### Hooks and guard behavior
@@ -1980,6 +2067,9 @@ if (!response.body) {
1980
2067
  const reader = response.body.getReader()
1981
2068
  const decoder = new TextDecoder()
1982
2069
  let buffer = ''
2070
+ let rows: Array<Record<string, unknown>> = []
2071
+ let fields: Record<string, unknown> = {}
2072
+ let data: unknown = undefined
1983
2073
 
1984
2074
  while (true) {
1985
2075
  const { value, done } = await reader.read()
@@ -1998,12 +2088,23 @@ while (true) {
1998
2088
 
1999
2089
  const event = JSON.parse(line.slice('data: '.length))
2000
2090
 
2091
+ if (event.type === 'rootArray') {
2092
+ rows = event.data
2093
+ }
2094
+
2001
2095
  if (event.type === 'field') {
2002
- // patch local field state
2096
+ fields[event.key] = event.value
2097
+ }
2098
+
2099
+ if (event.type === 'relationBatch') {
2100
+ rows = rows.map((row, index) => ({
2101
+ ...row,
2102
+ [event.relationPath]: event.values[index],
2103
+ }))
2003
2104
  }
2004
2105
 
2005
2106
  if (event.type === 'result') {
2006
- // replace with final result
2107
+ data = event.data
2007
2108
  }
2008
2109
  }
2009
2110
  }
@@ -234,13 +234,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
234
234
  }
235
235
 
236
236
  if (progressiveConfig.mode === 'autoInclude') {
237
- const isSingleRecordRead =
237
+ const isAutoIncludeReadable =
238
238
  baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
239
- baseOp === 'findFirst' || baseOp === 'findFirstOrThrow'
239
+ baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
240
+ baseOp === 'findMany'
240
241
 
241
- if (!isSingleRecordRead) {
242
+ if (!isAutoIncludeReadable) {
242
243
  if (progressiveConfig.fallback === 'error') {
243
- emitTerminalSSEError(res, 'auto-progressive fallback: operation not single-record')
244
+ emitTerminalSSEError(res, 'auto-progressive fallback: operation not supported by auto-include')
244
245
  return
245
246
  }
246
247
  await runSingleResultSSE({
@@ -262,7 +263,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
262
263
  res,
263
264
  ctx,
264
265
  args,
265
- baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow',
266
+ baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany',
266
267
  modelName: '${modelName}',
267
268
  delegateKey: '${delegateKey}',
268
269
  models: relationModels,
@@ -1 +1 @@
1
- {"version":3,"file":"generateRouter.js","sourceRoot":"","sources":["../../src/generators/generateRouter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generateRouter.js","sourceRoot":"","sources":["../../src/generators/generateRouter.ts"],"names":[],"mappings":"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"}
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.51.0",
4
+ "version": "1.52.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
@@ -7,11 +7,14 @@ import {
7
7
  sendSSEResult,
8
8
  sendSSEError,
9
9
  sendSSEProgress,
10
+ sendSSERootArray,
11
+ sendSSERelationBatch,
10
12
  runSingleResultSSE,
11
13
  emitTerminalSSEError,
12
14
  setByPath,
13
15
  getDelegate,
14
16
  getExtendedClient,
17
+ applyPaginationLimits,
15
18
  mapError,
16
19
  type OperationContext,
17
20
  type PrismaDelegate,
@@ -19,20 +22,30 @@ import {
19
22
  import { isPlainObject } from './misc'
20
23
  import {
21
24
  planAutoInclude,
25
+ type AutoIncludePlan,
22
26
  type ModelRelationMap,
23
27
  type AutoIncludeStage,
24
28
  } from './autoIncludePlanner'
25
29
  import type { AutoIncludeProgressiveVariantConfig } from './routeConfig'
26
30
 
27
31
  const STAGE_CONCURRENCY = 4
32
+ const MAX_IN_CHUNK = 1000
33
+
28
34
  type IntervalHandle = ReturnType<typeof setInterval>
29
35
 
36
+ export type AutoIncludeBaseOp =
37
+ | 'findUnique'
38
+ | 'findUniqueOrThrow'
39
+ | 'findFirst'
40
+ | 'findFirstOrThrow'
41
+ | 'findMany'
42
+
30
43
  export type RunAutoIncludeOptions = {
31
44
  req: Request
32
45
  res: Response
33
46
  ctx: OperationContext
34
47
  args: Record<string, unknown>
35
- baseOp: 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow'
48
+ baseOp: AutoIncludeBaseOp
36
49
  modelName: string
37
50
  delegateKey: string
38
51
  models: Record<string, ModelRelationMap>
@@ -122,7 +135,86 @@ function buildPublicForStage(
122
135
  return result
123
136
  }
124
137
 
125
- async function runOneStage(options: {
138
+ function normalizeKey(v: unknown): string {
139
+ if (v === null || v === undefined) return '\u0000'
140
+ if (typeof v === 'bigint') return 'B' + v.toString()
141
+ if (v instanceof Date) return 'D' + v.getTime().toString()
142
+ if (typeof v === 'object') return 'O' + JSON.stringify(v)
143
+ return typeof v + ':' + String(v)
144
+ }
145
+
146
+ function groupStagesByDepth(stages: AutoIncludeStage[]): AutoIncludeStage[][] {
147
+ const byDepth = new Map<number, AutoIncludeStage[]>()
148
+ for (const s of stages) {
149
+ const arr = byDepth.get(s.depth)
150
+ if (arr) arr.push(s)
151
+ else byDepth.set(s.depth, [s])
152
+ }
153
+ return Array.from(byDepth.keys())
154
+ .sort((a, b) => a - b)
155
+ .map((d) => byDepth.get(d) as AutoIncludeStage[])
156
+ }
157
+
158
+ async function runConcurrent<T>(
159
+ items: T[],
160
+ limit: number,
161
+ fn: (item: T) => Promise<void>,
162
+ ): Promise<void> {
163
+ let index = 0
164
+ const workers: Promise<void>[] = []
165
+ const workerCount = Math.min(limit, items.length)
166
+ for (let w = 0; w < workerCount; w++) {
167
+ workers.push((async () => {
168
+ for (;;) {
169
+ const i = index++
170
+ if (i >= items.length) return
171
+ await fn(items[i])
172
+ }
173
+ })())
174
+ }
175
+ await Promise.all(workers)
176
+ }
177
+
178
+ function handleAutoIncludeFallback(
179
+ options: RunAutoIncludeOptions,
180
+ message: string,
181
+ ): Promise<void> {
182
+ if (options.variantConfig.fallback === 'error') {
183
+ emitTerminalSSEError(options.res, message)
184
+ return Promise.resolve()
185
+ }
186
+ return runSingleResultSSE({
187
+ req: options.req,
188
+ res: options.res,
189
+ coreQueryFn: options.coreQueryFn,
190
+ })
191
+ }
192
+
193
+ function findManyUnsupportedReason(plan: AutoIncludePlan): string | null {
194
+ for (const stage of plan.stages) {
195
+ const rel = stage.relationField
196
+ if (rel.parentLinkFields.length !== 1 || rel.childLinkFields.length !== 1) {
197
+ return 'auto-progressive fallback: composite link fields not supported for findMany batched auto-include'
198
+ }
199
+ if (stage.depth > 1) {
200
+ return 'auto-progressive fallback: nested relations not supported for findMany batched auto-include'
201
+ }
202
+ if (rel.isList) {
203
+ const sa = stage.stageArgs
204
+ if (
205
+ sa.take !== undefined ||
206
+ sa.skip !== undefined ||
207
+ sa.cursor !== undefined ||
208
+ sa.distinct !== undefined
209
+ ) {
210
+ return 'auto-progressive fallback: per-parent take/skip/cursor/distinct on to-many relations not supported for findMany batched auto-include'
211
+ }
212
+ }
213
+ }
214
+ return null
215
+ }
216
+
217
+ async function runOneStageSingle(options: {
126
218
  extended: unknown
127
219
  models: Record<string, ModelRelationMap>
128
220
  stage: AutoIncludeStage
@@ -186,72 +278,15 @@ async function runOneStage(options: {
186
278
  sendSSEField(res, stage.relationPath, publicResult)
187
279
  }
188
280
 
189
- function groupStagesByDepth(stages: AutoIncludeStage[]): AutoIncludeStage[][] {
190
- const byDepth = new Map<number, AutoIncludeStage[]>()
191
- for (const s of stages) {
192
- const arr = byDepth.get(s.depth)
193
- if (arr) arr.push(s)
194
- else byDepth.set(s.depth, [s])
195
- }
196
- return Array.from(byDepth.keys())
197
- .sort((a, b) => a - b)
198
- .map((d) => byDepth.get(d) as AutoIncludeStage[])
199
- }
200
-
201
- async function runConcurrent<T>(
202
- items: T[],
203
- limit: number,
204
- fn: (item: T) => Promise<void>,
205
- ): Promise<void> {
206
- let index = 0
207
- const workers: Promise<void>[] = []
208
- const workerCount = Math.min(limit, items.length)
209
- for (let w = 0; w < workerCount; w++) {
210
- workers.push((async () => {
211
- for (;;) {
212
- const i = index++
213
- if (i >= items.length) return
214
- await fn(items[i])
215
- }
216
- })())
217
- }
218
- await Promise.all(workers)
219
- }
220
-
221
- export async function runAutoIncludeProgressive(
281
+ async function runAutoIncludeSingle(
222
282
  options: RunAutoIncludeOptions,
283
+ plan: AutoIncludePlan,
223
284
  ): Promise<void> {
224
- const { req, res, ctx, args, baseOp, modelName, delegateKey, models, variantConfig, coreQueryFn, signal } = options
285
+ const { res, ctx, baseOp, delegateKey, models, signal } = options
225
286
 
226
287
  const isClientGone = () =>
227
288
  signal?.aborted === true || res.writableEnded || res.destroyed
228
289
 
229
- if (ctx.guardShape) {
230
- if (variantConfig.fallback === 'error') {
231
- emitTerminalSSEError(res, 'auto-progressive fallback: guard shape disables auto-include')
232
- return
233
- }
234
- return runSingleResultSSE({ req, res, coreQueryFn })
235
- }
236
-
237
- const plan = planAutoInclude({
238
- rootModelName: modelName,
239
- models,
240
- args,
241
- })
242
-
243
- if (plan.unsupportedReason) {
244
- if (variantConfig.fallback === 'error') {
245
- emitTerminalSSEError(res, plan.unsupportedReason)
246
- return
247
- }
248
- return runSingleResultSSE({ req, res, coreQueryFn })
249
- }
250
-
251
- if (plan.stages.length === 0) {
252
- return runSingleResultSSE({ req, res, coreQueryFn })
253
- }
254
-
255
290
  let keepalive: IntervalHandle | null = null
256
291
  try {
257
292
  initSSE(res)
@@ -265,7 +300,7 @@ export async function runAutoIncludeProgressive(
265
300
 
266
301
  let rootResult: unknown
267
302
  try {
268
- rootResult = await rootDelegate[baseOp](plan.rootArgs)
303
+ rootResult = await rootDelegate[baseOp as Exclude<AutoIncludeBaseOp, 'findMany'>](plan.rootArgs)
269
304
  } catch (err) {
270
305
  if (isClientGone()) return
271
306
  console.error('[auto-progressive] root query failed:', err)
@@ -309,7 +344,7 @@ export async function runAutoIncludeProgressive(
309
344
  await runConcurrent(group, STAGE_CONCURRENCY, async (stage) => {
310
345
  if (isAborted()) return
311
346
  try {
312
- await runOneStage({
347
+ await runOneStageSingle({
313
348
  extended,
314
349
  models,
315
350
  stage,
@@ -351,4 +386,317 @@ export async function runAutoIncludeProgressive(
351
386
  } finally {
352
387
  endSSE(res, keepalive)
353
388
  }
389
+ }
390
+
391
+ function buildStageQueryArgs(
392
+ stage: AutoIncludeStage,
393
+ childKey: string,
394
+ inChunk: unknown[],
395
+ ): { args: Record<string, unknown>; injectedChildPath: string | null } {
396
+ const baseSelect = isPlainObject(stage.stageArgs.select)
397
+ ? (stage.stageArgs.select as Record<string, unknown>)
398
+ : null
399
+ const baseOmit = isPlainObject(stage.stageArgs.omit)
400
+ ? (stage.stageArgs.omit as Record<string, unknown>)
401
+ : null
402
+
403
+ const finalArgs: Record<string, unknown> = { ...stage.stageArgs }
404
+ finalArgs.where = mergeWhere(stage.stageArgs.where, { [childKey]: { in: inChunk } })
405
+
406
+ let injectedChildPath: string | null = null
407
+
408
+ if (baseSelect) {
409
+ if (baseSelect[childKey] !== true) {
410
+ finalArgs.select = { ...baseSelect, [childKey]: true }
411
+ injectedChildPath = stage.relationPath + '.' + childKey
412
+ }
413
+ }
414
+
415
+ if (baseOmit && baseOmit[childKey] === true) {
416
+ const nextOmit: Record<string, unknown> = {}
417
+ for (const [k, v] of Object.entries(baseOmit)) {
418
+ if (k === childKey) continue
419
+ nextOmit[k] = v
420
+ }
421
+ if (Object.keys(nextOmit).length > 0) {
422
+ finalArgs.omit = nextOmit
423
+ } else {
424
+ delete finalArgs.omit
425
+ }
426
+ injectedChildPath = stage.relationPath + '.' + childKey
427
+ }
428
+
429
+ return { args: finalArgs, injectedChildPath }
430
+ }
431
+
432
+ function collectDistinctParentValues(
433
+ rows: Record<string, unknown>[],
434
+ parentKey: string,
435
+ ): unknown[] {
436
+ const seen = new Set<string>()
437
+ const out: unknown[] = []
438
+ for (const row of rows) {
439
+ const v = row[parentKey]
440
+ if (v === undefined || v === null) continue
441
+ const k = normalizeKey(v)
442
+ if (seen.has(k)) continue
443
+ seen.add(k)
444
+ out.push(v)
445
+ }
446
+ return out
447
+ }
448
+
449
+ function groupRelatedRows(
450
+ children: unknown[],
451
+ childKey: string,
452
+ ): Map<string, unknown[]> {
453
+ const grouped = new Map<string, unknown[]>()
454
+ for (const child of children) {
455
+ if (!isPlainObject(child)) continue
456
+ const k = child[childKey]
457
+ if (k === undefined || k === null) continue
458
+ const key = normalizeKey(k)
459
+ let arr = grouped.get(key)
460
+ if (!arr) {
461
+ arr = []
462
+ grouped.set(key, arr)
463
+ }
464
+ arr.push(child)
465
+ }
466
+ return grouped
467
+ }
468
+
469
+ async function runOneStageMany(options: {
470
+ extended: unknown
471
+ models: Record<string, ModelRelationMap>
472
+ stage: AutoIncludeStage
473
+ internalRows: Record<string, unknown>[]
474
+ publicRows: Record<string, unknown>[]
475
+ internalFieldPaths: string[]
476
+ res: Response
477
+ isAborted: () => boolean
478
+ }): Promise<void> {
479
+ const {
480
+ extended, models, stage, internalRows, publicRows,
481
+ internalFieldPaths, res, isAborted,
482
+ } = options
483
+
484
+ if (isAborted()) return
485
+
486
+ const rel = stage.relationField
487
+ const parentKey = rel.parentLinkFields[0]
488
+ const childKey = rel.childLinkFields[0]
489
+
490
+ const targetModel = models[rel.type]
491
+ if (!targetModel) {
492
+ throw new Error('Target model not in relation metadata: ' + rel.type)
493
+ }
494
+
495
+ const distinctValues = collectDistinctParentValues(internalRows, parentKey)
496
+ const delegate: PrismaDelegate = getDelegate(extended, targetModel.delegateKey)
497
+
498
+ const children: unknown[] = []
499
+ let injectedChildPath: string | null = null
500
+
501
+ for (let i = 0; i < distinctValues.length; i += MAX_IN_CHUNK) {
502
+ if (isAborted()) return
503
+ const chunk = distinctValues.slice(i, i + MAX_IN_CHUNK)
504
+ const { args, injectedChildPath: ip } = buildStageQueryArgs(stage, childKey, chunk)
505
+ if (ip) injectedChildPath = ip
506
+ const partial = await delegate.findMany(args)
507
+ if (isAborted()) return
508
+ if (Array.isArray(partial)) {
509
+ for (const c of partial) children.push(c)
510
+ }
511
+ }
512
+
513
+ const effectivePaths = injectedChildPath
514
+ ? [...internalFieldPaths, injectedChildPath]
515
+ : internalFieldPaths
516
+
517
+ const grouped = groupRelatedRows(children, childKey)
518
+ const publicValues: unknown[] = new Array(internalRows.length)
519
+
520
+ for (let i = 0; i < internalRows.length; i++) {
521
+ const row = internalRows[i]
522
+ const fkVal = row[parentKey]
523
+ let internalVal: unknown
524
+
525
+ if (fkVal === undefined || fkVal === null) {
526
+ internalVal = emptyResultFor(rel.isList)
527
+ } else {
528
+ const matches = grouped.get(normalizeKey(fkVal)) ?? []
529
+ if (rel.isList) {
530
+ internalVal = matches
531
+ } else {
532
+ internalVal = matches.length > 0 ? matches[0] : null
533
+ }
534
+ }
535
+
536
+ const publicVal = buildPublicForStage(internalVal, effectivePaths, stage.relationPath)
537
+
538
+ internalRows[i][stage.relationName] = internalVal
539
+ publicRows[i][stage.relationName] = publicVal
540
+ publicValues[i] = publicVal
541
+ }
542
+
543
+ if (isAborted()) return
544
+ sendSSERelationBatch(res, stage.relationPath, publicValues)
545
+ }
546
+
547
+ async function runAutoIncludeMany(
548
+ options: RunAutoIncludeOptions,
549
+ plan: AutoIncludePlan,
550
+ ): Promise<void> {
551
+ const { res, ctx, delegateKey, models, signal } = options
552
+
553
+ const isClientGone = () =>
554
+ signal?.aborted === true || res.writableEnded || res.destroyed
555
+
556
+ let keepalive: IntervalHandle | null = null
557
+ try {
558
+ initSSE(res)
559
+ keepalive = startSSEKeepalive(res)
560
+ if (isClientGone()) return
561
+
562
+ const extended = await getExtendedClient(ctx)
563
+ if (isClientGone()) return
564
+
565
+ const rootDelegate = getDelegate(extended, delegateKey)
566
+ const rootArgs = applyPaginationLimits(plan.rootArgs, ctx.paginationConfig)
567
+
568
+ let rootResult: unknown
569
+ try {
570
+ rootResult = await rootDelegate.findMany(rootArgs)
571
+ } catch (err) {
572
+ if (isClientGone()) return
573
+ console.error('[auto-progressive] root findMany failed:', err)
574
+ sendSSEError(res, mapError(err).message)
575
+ return
576
+ }
577
+
578
+ if (isClientGone()) return
579
+
580
+ if (!Array.isArray(rootResult)) {
581
+ sendSSEError(res, 'auto-progressive: unexpected non-array root result for findMany')
582
+ return
583
+ }
584
+
585
+ const internalRows: Record<string, unknown>[] = new Array(rootResult.length)
586
+ const publicRows: Record<string, unknown>[] = new Array(rootResult.length)
587
+
588
+ for (let i = 0; i < rootResult.length; i++) {
589
+ const row = rootResult[i]
590
+ if (!isPlainObject(row)) {
591
+ internalRows[i] = {}
592
+ publicRows[i] = {}
593
+ continue
594
+ }
595
+ const internalCopy: Record<string, unknown> = { ...row }
596
+ const publicCopy: Record<string, unknown> = { ...row }
597
+ stripInternalAtScope(publicCopy, plan.internalFieldPaths, '')
598
+ internalRows[i] = internalCopy
599
+ publicRows[i] = publicCopy
600
+ }
601
+
602
+ sendSSERootArray(res, publicRows)
603
+ sendSSEProgress(res, 'root', 0, plan.stages.length)
604
+
605
+ const groups = groupStagesByDepth(plan.stages)
606
+ let completed = 0
607
+ let stageErrorMessage: string | null = null
608
+ const isAborted = () =>
609
+ stageErrorMessage !== null ||
610
+ signal?.aborted === true ||
611
+ res.writableEnded ||
612
+ res.destroyed
613
+
614
+ for (const group of groups) {
615
+ if (isClientGone()) return
616
+ if (stageErrorMessage) break
617
+
618
+ await runConcurrent(group, STAGE_CONCURRENCY, async (stage) => {
619
+ if (isAborted()) return
620
+ try {
621
+ await runOneStageMany({
622
+ extended,
623
+ models,
624
+ stage,
625
+ internalRows,
626
+ publicRows,
627
+ internalFieldPaths: plan.internalFieldPaths,
628
+ res,
629
+ isAborted,
630
+ })
631
+ } catch (err) {
632
+ if (isAborted()) return
633
+ console.error('[auto-progressive] stage failed:', stage.relationPath, err)
634
+ stageErrorMessage = mapError(err).message
635
+ return
636
+ }
637
+ if (isAborted()) return
638
+ completed++
639
+ sendSSEProgress(res, stage.relationPath, completed, plan.stages.length)
640
+ })
641
+ }
642
+
643
+ if (isClientGone()) return
644
+
645
+ if (stageErrorMessage) {
646
+ if (!res.writableEnded && !res.destroyed) {
647
+ sendSSEError(res, stageErrorMessage)
648
+ }
649
+ return
650
+ }
651
+
652
+ if (res.writableEnded || res.destroyed) return
653
+ sendSSEResult(res, publicRows)
654
+ } catch (err) {
655
+ if (isClientGone()) return
656
+ console.error('[auto-progressive] many dispatch error:', err)
657
+ if (!res.writableEnded && !res.destroyed) {
658
+ sendSSEError(res, mapError(err).message)
659
+ }
660
+ } finally {
661
+ endSSE(res, keepalive)
662
+ }
663
+ }
664
+
665
+ export async function runAutoIncludeProgressive(
666
+ options: RunAutoIncludeOptions,
667
+ ): Promise<void> {
668
+ if (options.ctx.guardShape) {
669
+ return handleAutoIncludeFallback(
670
+ options,
671
+ 'auto-progressive fallback: guard shape disables auto-include',
672
+ )
673
+ }
674
+
675
+ const plan = planAutoInclude({
676
+ rootModelName: options.modelName,
677
+ models: options.models,
678
+ args: options.args,
679
+ })
680
+
681
+ if (plan.unsupportedReason) {
682
+ return handleAutoIncludeFallback(options, plan.unsupportedReason)
683
+ }
684
+
685
+ if (options.baseOp === 'findMany') {
686
+ const reason = findManyUnsupportedReason(plan)
687
+ if (reason) return handleAutoIncludeFallback(options, reason)
688
+ }
689
+
690
+ if (plan.stages.length === 0) {
691
+ return runSingleResultSSE({
692
+ req: options.req,
693
+ res: options.res,
694
+ coreQueryFn: options.coreQueryFn,
695
+ })
696
+ }
697
+
698
+ if (options.baseOp === 'findMany') {
699
+ return runAutoIncludeMany(options, plan)
700
+ }
701
+ return runAutoIncludeSingle(options, plan)
354
702
  }
@@ -491,6 +491,18 @@ export function sendSSEResult(res: SseWritable, data: unknown): boolean {
491
491
  return sendSSE(res, { type: 'result', data })
492
492
  }
493
493
 
494
+ export function sendSSERootArray(res: SseWritable, rows: unknown[]): boolean {
495
+ return sendSSE(res, { type: 'rootArray', data: rows })
496
+ }
497
+
498
+ export function sendSSERelationBatch(
499
+ res: SseWritable,
500
+ relationPath: string,
501
+ values: unknown[],
502
+ ): boolean {
503
+ return sendSSE(res, { type: 'relationBatch', relationPath, values })
504
+ }
505
+
494
506
  export function sendSSEError(res: SseWritable, message: string): boolean {
495
507
  if (res.writableEnded || res.destroyed) return false
496
508
  try {
@@ -250,13 +250,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
250
250
  }
251
251
 
252
252
  if (progressiveConfig.mode === 'autoInclude') {
253
- const isSingleRecordRead =
253
+ const isAutoIncludeReadable =
254
254
  baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
255
- baseOp === 'findFirst' || baseOp === 'findFirstOrThrow'
255
+ baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
256
+ baseOp === 'findMany'
256
257
 
257
- if (!isSingleRecordRead) {
258
+ if (!isAutoIncludeReadable) {
258
259
  if (progressiveConfig.fallback === 'error') {
259
- emitTerminalSSEError(res, 'auto-progressive fallback: operation not single-record')
260
+ emitTerminalSSEError(res, 'auto-progressive fallback: operation not supported by auto-include')
260
261
  return
261
262
  }
262
263
  await runSingleResultSSE({
@@ -278,7 +279,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
278
279
  res,
279
280
  ctx,
280
281
  args,
281
- baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow',
282
+ baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow' | 'findMany',
282
283
  modelName: '${modelName}',
283
284
  delegateKey: '${delegateKey}',
284
285
  models: relationModels,