prisma-generator-express 1.42.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/generators/generateFastifyHandler.d.ts +2 -0
  2. package/dist/generators/generateFastifyHandler.js +21 -6
  3. package/dist/generators/generateFastifyHandler.js.map +1 -1
  4. package/dist/generators/generateHonoHandler.d.ts +2 -0
  5. package/dist/generators/generateHonoHandler.js +8 -6
  6. package/dist/generators/generateHonoHandler.js.map +1 -1
  7. package/dist/generators/generateOperationCore.d.ts +0 -1
  8. package/dist/generators/generateOperationCore.js +34 -546
  9. package/dist/generators/generateOperationCore.js.map +1 -1
  10. package/dist/generators/generateRelationMeta.d.ts +13 -0
  11. package/dist/generators/generateRelationMeta.js +110 -0
  12. package/dist/generators/generateRelationMeta.js.map +1 -0
  13. package/dist/generators/generateRouteConfigType.js +13 -5
  14. package/dist/generators/generateRouteConfigType.js.map +1 -1
  15. package/dist/generators/generateRouter.js +83 -19
  16. package/dist/generators/generateRouter.js.map +1 -1
  17. package/dist/generators/generateRouterFastify.js +127 -384
  18. package/dist/generators/generateRouterFastify.js.map +1 -1
  19. package/dist/generators/generateRouterHono.js +48 -36
  20. package/dist/generators/generateRouterHono.js.map +1 -1
  21. package/dist/generators/generateUnifiedHandler.d.ts +2 -0
  22. package/dist/generators/generateUnifiedHandler.js +28 -10
  23. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  24. package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
  25. package/dist/generators/generateUnifiedScalarUI.js +19 -16
  26. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  27. package/dist/index.js +21 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/copyFiles.js +12 -0
  30. package/dist/utils/copyFiles.js.map +1 -1
  31. package/dist/utils/writeFileSafely.js +2 -2
  32. package/dist/utils/writeFileSafely.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/copy/autoIncludePlanner.ts +354 -0
  35. package/src/copy/autoIncludeRuntime.ts +362 -0
  36. package/src/copy/operationRuntime.ts +614 -0
  37. package/src/copy/routeConfig.express.ts +5 -3
  38. package/src/copy/routeConfig.fastify.ts +2 -2
  39. package/src/copy/routeConfig.hono.ts +3 -3
  40. package/src/copy/routeConfig.ts +20 -9
  41. package/src/generators/generateFastifyHandler.ts +23 -6
  42. package/src/generators/generateHonoHandler.ts +10 -6
  43. package/src/generators/generateOperationCore.ts +34 -546
  44. package/src/generators/generateRelationMeta.ts +160 -0
  45. package/src/generators/generateRouteConfigType.ts +13 -6
  46. package/src/generators/generateRouter.ts +83 -19
  47. package/src/generators/generateRouterFastify.ts +127 -384
  48. package/src/generators/generateRouterHono.ts +48 -36
  49. package/src/generators/generateUnifiedHandler.ts +30 -10
  50. package/src/generators/generateUnifiedScalarUI.ts +21 -16
  51. package/src/index.ts +31 -13
  52. package/src/utils/copyFiles.ts +13 -0
  53. package/src/utils/writeFileSafely.ts +2 -2
@@ -0,0 +1,362 @@
1
+ import type { Request, Response } from 'express'
2
+ import {
3
+ initSSE,
4
+ endSSE,
5
+ startSSEKeepalive,
6
+ sendSSEField,
7
+ sendSSEResult,
8
+ sendSSEError,
9
+ sendSSEProgress,
10
+ runSingleResultSSE,
11
+ emitTerminalSSEError,
12
+ setByPath,
13
+ getDelegate,
14
+ getExtendedClient,
15
+ type OperationContext,
16
+ type PrismaDelegate,
17
+ } from './operationRuntime'
18
+ import {
19
+ planAutoInclude,
20
+ type ModelRelationMap,
21
+ type AutoIncludeStage,
22
+ } from './autoIncludePlanner'
23
+ import type { AutoIncludeProgressiveVariantConfig } from './routeConfig'
24
+
25
+ const STAGE_CONCURRENCY = 4
26
+ type IntervalHandle = ReturnType<typeof setInterval>
27
+
28
+ export type RunAutoIncludeOptions = {
29
+ req: Request
30
+ res: Response
31
+ ctx: OperationContext
32
+ args: Record<string, unknown>
33
+ baseOp: 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow'
34
+ modelName: string
35
+ delegateKey: string
36
+ models: Record<string, ModelRelationMap>
37
+ variantConfig: AutoIncludeProgressiveVariantConfig
38
+ coreQueryFn: () => Promise<unknown>
39
+ signal?: AbortSignal
40
+ }
41
+
42
+ function isObject(v: unknown): v is Record<string, unknown> {
43
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
44
+ }
45
+
46
+ function readPath(source: Record<string, unknown>, path: string): unknown {
47
+ if (path === '') return source
48
+ const parts = path.split('.')
49
+ let cursor: unknown = source
50
+ for (const part of parts) {
51
+ if (!isObject(cursor)) return undefined
52
+ cursor = cursor[part]
53
+ }
54
+ return cursor
55
+ }
56
+
57
+ function stripInternalAtScope(
58
+ target: Record<string, unknown>,
59
+ internalPaths: string[],
60
+ scopePath: string,
61
+ ): void {
62
+ for (const fullPath of internalPaths) {
63
+ if (scopePath === '') {
64
+ if (!fullPath.includes('.')) {
65
+ delete target[fullPath]
66
+ }
67
+ continue
68
+ }
69
+ if (!fullPath.startsWith(scopePath + '.')) continue
70
+ const relative = fullPath.slice(scopePath.length + 1)
71
+ if (relative.includes('.')) continue
72
+ delete target[relative]
73
+ }
74
+ }
75
+
76
+ function mergeWhere(
77
+ userWhere: unknown,
78
+ linkFilter: Record<string, unknown>,
79
+ ): Record<string, unknown> {
80
+ if (!isObject(userWhere) || Object.keys(userWhere).length === 0) return linkFilter
81
+ return { AND: [userWhere, linkFilter] }
82
+ }
83
+
84
+ function buildLinkFilter(
85
+ stage: AutoIncludeStage,
86
+ parentValue: Record<string, unknown>,
87
+ ): Record<string, unknown> | null {
88
+ const filter: Record<string, unknown> = {}
89
+ const rel = stage.relationField
90
+ for (let i = 0; i < rel.parentLinkFields.length; i++) {
91
+ const parentKey = rel.parentLinkFields[i]
92
+ const childKey = rel.childLinkFields[i]
93
+ const value = parentValue[parentKey]
94
+ if (value === undefined || value === null) return null
95
+ filter[childKey] = value
96
+ }
97
+ return filter
98
+ }
99
+
100
+ function emptyResultFor(isList: boolean): unknown {
101
+ return isList ? [] : null
102
+ }
103
+
104
+ function buildPublicForStage(
105
+ result: unknown,
106
+ internalFieldPaths: string[],
107
+ scopePath: string,
108
+ ): unknown {
109
+ if (Array.isArray(result)) {
110
+ return result.map((item) => {
111
+ if (isObject(item)) {
112
+ const copy: Record<string, unknown> = { ...item }
113
+ stripInternalAtScope(copy, internalFieldPaths, scopePath)
114
+ return copy
115
+ }
116
+ return item
117
+ })
118
+ }
119
+ if (isObject(result)) {
120
+ const copy: Record<string, unknown> = { ...result }
121
+ stripInternalAtScope(copy, internalFieldPaths, scopePath)
122
+ return copy
123
+ }
124
+ return result
125
+ }
126
+
127
+ async function runOneStage(options: {
128
+ extended: unknown
129
+ models: Record<string, ModelRelationMap>
130
+ stage: AutoIncludeStage
131
+ internal: Record<string, unknown>
132
+ publicState: Record<string, unknown>
133
+ internalFieldPaths: string[]
134
+ res: Response
135
+ isAborted: () => boolean
136
+ }): Promise<void> {
137
+ const { extended, models, stage, internal, publicState, internalFieldPaths, res, isAborted } = options
138
+ if (isAborted()) return
139
+
140
+ const parentRaw = readPath(internal, stage.parentPath)
141
+ if (!isObject(parentRaw)) {
142
+ if (stage.parentPath !== '') {
143
+ return
144
+ }
145
+ const empty = emptyResultFor(stage.relationField.isList)
146
+ const applied = setByPath(publicState, stage.relationPath, empty)
147
+ if (applied) sendSSEField(res, stage.relationPath, empty)
148
+ return
149
+ }
150
+
151
+ const linkFilter = buildLinkFilter(stage, parentRaw)
152
+ if (!linkFilter) {
153
+ const empty = emptyResultFor(stage.relationField.isList)
154
+ const appliedInternal = setByPath(internal, stage.relationPath, empty)
155
+ const appliedPublic = setByPath(publicState, stage.relationPath, empty)
156
+ if (appliedInternal && appliedPublic) {
157
+ sendSSEField(res, stage.relationPath, empty)
158
+ }
159
+ return
160
+ }
161
+
162
+ const targetModel = models[stage.relationField.type]
163
+ if (!targetModel) {
164
+ throw new Error('Target model not in relation metadata: ' + stage.relationField.type)
165
+ }
166
+
167
+ const finalArgs: Record<string, unknown> = { ...stage.stageArgs }
168
+ finalArgs.where = mergeWhere(stage.stageArgs.where, linkFilter)
169
+
170
+ const delegate: PrismaDelegate = getDelegate(extended, targetModel.delegateKey)
171
+ const method: 'findMany' | 'findFirst' = stage.relationField.isList ? 'findMany' : 'findFirst'
172
+ const result = await delegate[method](finalArgs)
173
+
174
+ if (isAborted()) return
175
+
176
+ const appliedInternal = setByPath(internal, stage.relationPath, result)
177
+ if (!appliedInternal) {
178
+ throw new Error('Failed to apply internal patch for ' + stage.relationPath)
179
+ }
180
+
181
+ const publicResult = buildPublicForStage(result, internalFieldPaths, stage.relationPath)
182
+
183
+ const appliedPublic = setByPath(publicState, stage.relationPath, publicResult)
184
+ if (!appliedPublic) {
185
+ throw new Error('Failed to apply public patch for ' + stage.relationPath)
186
+ }
187
+
188
+ sendSSEField(res, stage.relationPath, publicResult)
189
+ }
190
+
191
+ function groupStagesByDepth(stages: AutoIncludeStage[]): AutoIncludeStage[][] {
192
+ const byDepth = new Map<number, AutoIncludeStage[]>()
193
+ for (const s of stages) {
194
+ const arr = byDepth.get(s.depth)
195
+ if (arr) arr.push(s)
196
+ else byDepth.set(s.depth, [s])
197
+ }
198
+ return Array.from(byDepth.keys())
199
+ .sort((a, b) => a - b)
200
+ .map((d) => byDepth.get(d) as AutoIncludeStage[])
201
+ }
202
+
203
+ async function runConcurrent<T>(
204
+ items: T[],
205
+ limit: number,
206
+ fn: (item: T) => Promise<void>,
207
+ ): Promise<void> {
208
+ let index = 0
209
+ const workers: Promise<void>[] = []
210
+ const workerCount = Math.min(limit, items.length)
211
+ for (let w = 0; w < workerCount; w++) {
212
+ workers.push((async () => {
213
+ for (;;) {
214
+ const i = index++
215
+ if (i >= items.length) return
216
+ await fn(items[i])
217
+ }
218
+ })())
219
+ }
220
+ await Promise.all(workers)
221
+ }
222
+
223
+ export async function runAutoIncludeProgressive(
224
+ options: RunAutoIncludeOptions,
225
+ ): Promise<void> {
226
+ const { req, res, ctx, args, baseOp, modelName, delegateKey, models, variantConfig, coreQueryFn, signal } = options
227
+
228
+ const isClientGone = () =>
229
+ signal?.aborted === true || res.writableEnded || res.destroyed
230
+
231
+ if (ctx.guardShape) {
232
+ if (variantConfig.fallback === 'error') {
233
+ emitTerminalSSEError(res, 'auto-progressive fallback: guard shape disables auto-include')
234
+ return
235
+ }
236
+ return runSingleResultSSE({ req, res, coreQueryFn })
237
+ }
238
+
239
+ const plan = planAutoInclude({
240
+ rootModelName: modelName,
241
+ models,
242
+ args,
243
+ })
244
+
245
+ if (plan.unsupportedReason) {
246
+ if (variantConfig.fallback === 'error') {
247
+ emitTerminalSSEError(res, plan.unsupportedReason)
248
+ return
249
+ }
250
+ return runSingleResultSSE({ req, res, coreQueryFn })
251
+ }
252
+
253
+ if (plan.stages.length === 0) {
254
+ return runSingleResultSSE({ req, res, coreQueryFn })
255
+ }
256
+
257
+ let keepalive: IntervalHandle | null = null
258
+ try {
259
+ initSSE(res)
260
+ keepalive = startSSEKeepalive(res)
261
+ if (isClientGone()) return
262
+
263
+ const extended = await getExtendedClient(ctx)
264
+ if (isClientGone()) return
265
+
266
+ const rootDelegate = getDelegate(extended, delegateKey)
267
+
268
+ let rootResult: unknown
269
+ try {
270
+ rootResult = await rootDelegate[baseOp](plan.rootArgs)
271
+ } catch (err) {
272
+ if (isClientGone()) return
273
+ const code = (err as { code?: string } | null)?.code
274
+ const isOrThrow = baseOp === 'findUniqueOrThrow' || baseOp === 'findFirstOrThrow'
275
+ if (isOrThrow && code === 'P2025') {
276
+ sendSSEError(res, 'Record not found')
277
+ return
278
+ }
279
+ console.error('[auto-progressive] root query failed:', err)
280
+ sendSSEError(res, 'Root query failed')
281
+ return
282
+ }
283
+
284
+ if (isClientGone()) return
285
+
286
+ if (rootResult === null || !isObject(rootResult)) {
287
+ sendSSEResult(res, null)
288
+ return
289
+ }
290
+
291
+ const internal: Record<string, unknown> = { ...rootResult }
292
+ const publicRoot: Record<string, unknown> = { ...rootResult }
293
+ stripInternalAtScope(publicRoot, plan.internalFieldPaths, '')
294
+
295
+ const publicState: Record<string, unknown> = { ...publicRoot }
296
+ for (const [k, v] of Object.entries(publicRoot)) {
297
+ if (isClientGone()) return
298
+ sendSSEField(res, k, v)
299
+ }
300
+
301
+ if (isClientGone()) return
302
+ sendSSEProgress(res, 'root', 0, plan.stages.length)
303
+
304
+ const groups = groupStagesByDepth(plan.stages)
305
+ let completed = 0
306
+ let stageErrorMessage: string | null = null
307
+ const isAborted = () =>
308
+ stageErrorMessage !== null ||
309
+ signal?.aborted === true ||
310
+ res.writableEnded ||
311
+ res.destroyed
312
+
313
+ for (const group of groups) {
314
+ if (isClientGone()) return
315
+ if (stageErrorMessage) break
316
+
317
+ await runConcurrent(group, STAGE_CONCURRENCY, async (stage) => {
318
+ if (isAborted()) return
319
+ try {
320
+ await runOneStage({
321
+ extended,
322
+ models,
323
+ stage,
324
+ internal,
325
+ publicState,
326
+ internalFieldPaths: plan.internalFieldPaths,
327
+ res,
328
+ isAborted,
329
+ })
330
+ } catch (err) {
331
+ if (isAborted()) return
332
+ console.error('[auto-progressive] stage failed:', stage.relationPath, err)
333
+ stageErrorMessage = 'Could not load progressive response'
334
+ return
335
+ }
336
+ if (isAborted()) return
337
+ completed++
338
+ sendSSEProgress(res, stage.relationPath, completed, plan.stages.length)
339
+ })
340
+ }
341
+
342
+ if (isClientGone()) return
343
+
344
+ if (stageErrorMessage) {
345
+ if (!res.writableEnded && !res.destroyed) {
346
+ sendSSEError(res, stageErrorMessage)
347
+ }
348
+ return
349
+ }
350
+
351
+ if (res.writableEnded || res.destroyed) return
352
+ sendSSEResult(res, publicState)
353
+ } catch (err) {
354
+ if (isClientGone()) return
355
+ console.error('[auto-progressive] dispatch error:', err)
356
+ if (!res.writableEnded && !res.destroyed) {
357
+ sendSSEError(res, 'Internal server error')
358
+ }
359
+ } finally {
360
+ endSSE(res, keepalive)
361
+ }
362
+ }