prisma-generator-express 1.41.0 → 1.43.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 (45) hide show
  1. package/dist/generators/generateFastifyHandler.js +17 -4
  2. package/dist/generators/generateFastifyHandler.js.map +1 -1
  3. package/dist/generators/generateHonoHandler.js +4 -4
  4. package/dist/generators/generateOperationCore.d.ts +0 -1
  5. package/dist/generators/generateOperationCore.js +34 -546
  6. package/dist/generators/generateOperationCore.js.map +1 -1
  7. package/dist/generators/generateRelationMeta.d.ts +13 -0
  8. package/dist/generators/generateRelationMeta.js +106 -0
  9. package/dist/generators/generateRelationMeta.js.map +1 -0
  10. package/dist/generators/generateRouteConfigType.js +6 -6
  11. package/dist/generators/generateRouteConfigType.js.map +1 -1
  12. package/dist/generators/generateRouter.js +141 -60
  13. package/dist/generators/generateRouter.js.map +1 -1
  14. package/dist/generators/generateRouterFastify.js +127 -384
  15. package/dist/generators/generateRouterFastify.js.map +1 -1
  16. package/dist/generators/generateRouterHono.js +48 -36
  17. package/dist/generators/generateRouterHono.js.map +1 -1
  18. package/dist/generators/generateUnifiedHandler.js +24 -8
  19. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  20. package/dist/index.js +21 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/utils/copyFiles.js +12 -0
  23. package/dist/utils/copyFiles.js.map +1 -1
  24. package/dist/utils/writeFileSafely.js +3 -0
  25. package/dist/utils/writeFileSafely.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/copy/autoIncludePlanner.ts +299 -0
  28. package/src/copy/autoIncludeRuntime.ts +307 -0
  29. package/src/copy/operationRuntime.ts +603 -0
  30. package/src/copy/routeConfig.express.ts +5 -3
  31. package/src/copy/routeConfig.fastify.ts +2 -2
  32. package/src/copy/routeConfig.hono.ts +3 -3
  33. package/src/copy/routeConfig.ts +20 -9
  34. package/src/generators/generateFastifyHandler.ts +17 -4
  35. package/src/generators/generateHonoHandler.ts +4 -4
  36. package/src/generators/generateOperationCore.ts +34 -546
  37. package/src/generators/generateRelationMeta.ts +154 -0
  38. package/src/generators/generateRouteConfigType.ts +7 -7
  39. package/src/generators/generateRouter.ts +141 -60
  40. package/src/generators/generateRouterFastify.ts +127 -384
  41. package/src/generators/generateRouterHono.ts +48 -36
  42. package/src/generators/generateUnifiedHandler.ts +24 -8
  43. package/src/index.ts +25 -7
  44. package/src/utils/copyFiles.ts +13 -0
  45. package/src/utils/writeFileSafely.ts +3 -0
@@ -2,529 +2,6 @@ import { DMMF } from '@prisma/generator-helper'
2
2
  import { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
4
 
5
- export function generateOperationRuntime(importStyle: ImportStyle): string {
6
- const ext = importExt(importStyle)
7
- return `import { sanitizeKeys } from './misc${ext}'
8
- import type {
9
- ProgressivePatch,
10
- ProgressiveStopResult,
11
- ProgressiveStageResult,
12
- ProgressiveStageContext,
13
- ProgressiveStage,
14
- } from './routeConfig${ext}'
15
-
16
- export type {
17
- ProgressivePatch,
18
- ProgressiveStopResult,
19
- ProgressiveStageResult,
20
- ProgressiveStageContext,
21
- ProgressiveStage,
22
- }
23
-
24
- export interface PaginationConfig {
25
- defaultLimit?: number
26
- maxLimit?: number
27
- distinctCountLimit?: number
28
- }
29
-
30
- export interface OperationContext {
31
- prisma: any
32
- postgres?: any
33
- sqlite?: any
34
- parsedQuery?: Record<string, unknown>
35
- body?: unknown
36
- guardShape?: Record<string, unknown>
37
- guardCaller?: string
38
- paginationConfig?: PaginationConfig
39
- }
40
-
41
- export const DISTINCT_COUNT_LIMIT = 100000
42
-
43
- export class HttpError extends Error {
44
- status: number
45
- constructor(status: number, message: string) {
46
- super(message)
47
- this.name = 'HttpError'
48
- this.status = status
49
- }
50
- }
51
-
52
- const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
53
- P2000: { status: 400, message: 'Value too long for column' },
54
- P2001: { status: 404, message: 'Record not found' },
55
- P2002: { status: 409, message: 'Unique constraint violation' },
56
- P2003: { status: 400, message: 'Foreign key constraint failed' },
57
- P2004: { status: 400, message: 'Constraint failed on the database' },
58
- P2005: { status: 400, message: 'Invalid field value' },
59
- P2006: { status: 400, message: 'Invalid value provided' },
60
- P2007: { status: 400, message: 'Data validation error' },
61
- P2008: { status: 400, message: 'Failed to parse the query' },
62
- P2009: { status: 400, message: 'Failed to validate the query' },
63
- P2010: { status: 500, message: 'Raw query failed' },
64
- P2011: { status: 400, message: 'Null constraint violation' },
65
- P2012: { status: 400, message: 'Missing required value' },
66
- P2013: { status: 400, message: 'Missing required argument' },
67
- P2014: { status: 400, message: 'Required relation violation' },
68
- P2015: { status: 404, message: 'Related record not found' },
69
- P2016: { status: 400, message: 'Query interpretation error' },
70
- P2017: { status: 400, message: 'Records not connected' },
71
- P2018: { status: 404, message: 'Required connected record not found' },
72
- P2019: { status: 400, message: 'Input error' },
73
- P2020: { status: 400, message: 'Value out of range for the field type' },
74
- P2021: { status: 500, message: 'Table does not exist in the database' },
75
- P2022: { status: 500, message: 'Column does not exist in the database' },
76
- P2023: { status: 500, message: 'Inconsistent column data' },
77
- P2024: { status: 503, message: 'Connection pool timeout' },
78
- P2025: { status: 404, message: 'Record not found' },
79
- P2026: { status: 501, message: 'Feature not supported by the current database provider' },
80
- P2028: { status: 500, message: 'Transaction API error' },
81
- P2030: { status: 400, message: 'Cannot find a fulltext index for the search' },
82
- P2033: { status: 400, message: 'Number out of range for the field type' },
83
- P2034: { status: 409, message: 'Transaction conflict, please retry' },
84
- }
85
-
86
- export function mapError(error: unknown): HttpError {
87
- if (error instanceof HttpError) return error
88
- if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'ShapeError') {
89
- return new HttpError(400, (error as any).message)
90
- }
91
- if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'CallerError') {
92
- return new HttpError(400, (error as any).message)
93
- }
94
- if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'PolicyError') {
95
- return new HttpError(403, (error as any).message)
96
- }
97
- if (error && typeof error === 'object' && 'issues' in error && 'name' in error && (error as any).name === 'ZodError') {
98
- const issues = (error as any).issues
99
- const message = Array.isArray(issues) ? issues.map((i: any) => i.message).join('; ') : (error as any).message
100
- return new HttpError(400, message)
101
- }
102
- if (error && typeof error === 'object' && 'code' in error) {
103
- const code = (error as any).code as string
104
- const mapped = PRISMA_ERROR_MAP[code]
105
- if (mapped) {
106
- const detail = (error as any).message
107
- return new HttpError(mapped.status, detail ? mapped.message + ': ' + detail : mapped.message)
108
- }
109
- if (typeof code === 'string' && code.startsWith('P')) {
110
- const msg = (error as any).message || 'Database operation failed'
111
- console.warn('[prisma-generator-express] Unmapped Prisma error code:', code, msg)
112
- return new HttpError(500, msg)
113
- }
114
- }
115
- if (error && typeof error === 'object' && 'name' in error) {
116
- const name = (error as any).name
117
- if (name === 'PrismaClientValidationError') return new HttpError(400, (error as any).message || 'Invalid query parameters')
118
- if (name === 'PrismaClientKnownRequestError') return new HttpError(400, (error as any).message || 'Database request error')
119
- if (name === 'PrismaClientInitializationError') return new HttpError(503, (error as any).message || 'Database connection failed')
120
- if (name === 'PrismaClientRustPanicError') return new HttpError(500, (error as any).message || 'Internal database engine error')
121
- if (name === 'PrismaClientUnknownRequestError') return new HttpError(500, (error as any).message || 'Unknown database error')
122
- }
123
- const msg = error instanceof Error ? error.message : String(error)
124
- console.error('[prisma-generator-express] Unhandled error:', error)
125
- return new HttpError(500, msg || 'Internal server error')
126
- }
127
-
128
- let _speedExtension: ((opts: any) => any) | null = null
129
-
130
- const _prismasqlModule = 'prisma-' + 'sql'
131
- const _prismasqlReady = (async () => {
132
- try {
133
- const mod = await import(_prismasqlModule)
134
- _speedExtension = mod.speedExtension ?? mod.default?.speedExtension ?? null
135
- } catch (err: any) {
136
- const code = err?.code
137
- if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
138
- console.warn('[prisma-generator-express] prisma-sql initialization failed:', err)
139
- }
140
- }
141
- })()
142
-
143
- const _extendedClients = new WeakMap<object, WeakMap<object, any>>()
144
-
145
- export async function getExtendedClient(ctx: OperationContext): Promise<any> {
146
- const base = ctx.prisma
147
- if (!base) {
148
- throw new HttpError(500, 'PrismaClient not found on request. Set req.prisma in middleware.')
149
- }
150
- await _prismasqlReady
151
- if (!_speedExtension) return base
152
- const connector = ctx.postgres || ctx.sqlite
153
- if (!connector) return base
154
- if (typeof connector === 'object' && connector !== null) {
155
- const innerMap = _extendedClients.get(connector)
156
- if (innerMap) {
157
- const cached = innerMap.get(base)
158
- if (cached) return cached
159
- }
160
- }
161
- try {
162
- const extended = base.$extends(_speedExtension({
163
- postgres: ctx.postgres,
164
- sqlite: ctx.sqlite,
165
- debug: process.env.DEBUG === 'true',
166
- }))
167
- if (typeof connector === 'object' && connector !== null) {
168
- let innerMap = _extendedClients.get(connector)
169
- if (!innerMap) {
170
- innerMap = new WeakMap<object, any>()
171
- _extendedClients.set(connector, innerMap)
172
- }
173
- innerMap.set(base, extended)
174
- }
175
- return extended
176
- } catch (error) {
177
- console.warn('[speedExtension] Failed to initialize, using base client:', error)
178
- return base
179
- }
180
- }
181
-
182
- export function validateBody(body: unknown): Record<string, any> {
183
- if (!body || typeof body !== 'object' || Array.isArray(body)) {
184
- throw new HttpError(400, 'Request body must be a JSON object')
185
- }
186
- return sanitizeKeys(body as Record<string, any>)
187
- }
188
-
189
- export function requireBodyField(body: Record<string, any>, field: string): void {
190
- if (!(field in body) || body[field] === undefined) {
191
- throw new HttpError(400, 'Missing required field: ' + field)
192
- }
193
- }
194
-
195
- export function applyPaginationLimits(query: Record<string, any>, config?: PaginationConfig): Record<string, any> {
196
- if (!config) return query
197
- const result = { ...query }
198
- if (result.take === undefined && config.defaultLimit !== undefined) {
199
- result.take = config.defaultLimit
200
- }
201
- if (config.maxLimit !== undefined && result.take !== undefined) {
202
- const takeNum = Number(result.take)
203
- if (Math.abs(takeNum) > config.maxLimit) {
204
- result.take = takeNum < 0 ? -config.maxLimit : config.maxLimit
205
- }
206
- }
207
- return result
208
- }
209
-
210
- export function normalizeDistinct(value: unknown): string[] {
211
- if (typeof value === 'string') return [value]
212
- if (Array.isArray(value)) return value.filter((v): v is string => typeof v === 'string')
213
- return []
214
- }
215
-
216
- export function assertGuard(delegate: any): void {
217
- if (typeof delegate.guard !== 'function') {
218
- throw new HttpError(500, 'Guard shapes require prisma-guard extension on PrismaClient.')
219
- }
220
- }
221
-
222
- const GUARD_SHAPE_CONFIG_KEYS = new Set([
223
- 'data', 'create', 'update', 'where', 'include', 'select', 'orderBy',
224
- 'cursor', 'take', 'skip', 'distinct', 'having', '_count', '_avg',
225
- '_sum', '_min', '_max', 'by',
226
- ])
227
-
228
- function keepWhereOnly(obj: Record<string, any>): Record<string, any> {
229
- const result: Record<string, any> = {}
230
- if ('where' in obj) result.where = obj.where
231
- return result
232
- }
233
-
234
- export function buildCountShape(shape: Record<string, any>): Record<string, any> {
235
- if (typeof shape === 'function') {
236
- return (...args: any[]) => keepWhereOnly((shape as Function)(...args))
237
- }
238
- const keys = Object.keys(shape)
239
- const isSingleShape = keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
240
- if (isSingleShape) return keepWhereOnly(shape)
241
- const result: Record<string, any> = {}
242
- for (const [key, variant] of Object.entries(shape)) {
243
- if (typeof variant === 'function') {
244
- result[key] = (...args: any[]) => keepWhereOnly(variant(...args))
245
- } else if (typeof variant === 'object' && variant !== null) {
246
- result[key] = keepWhereOnly(variant)
247
- } else {
248
- result[key] = variant
249
- }
250
- }
251
- return result
252
- }
253
-
254
- export async function countForPagination(
255
- delegate: any,
256
- query: Record<string, any>,
257
- shape: Record<string, any> | undefined,
258
- caller: string | undefined,
259
- distinctCountLimit?: number,
260
- ): Promise<number> {
261
- const distinctFields = normalizeDistinct(query.distinct)
262
- const hasDistinct = distinctFields.length > 0
263
- const effectiveLimit = distinctCountLimit ?? DISTINCT_COUNT_LIMIT
264
- const countShape = shape ? buildCountShape(shape) : undefined
265
-
266
- if (hasDistinct) {
267
- const selectField = distinctFields[0]
268
- const distinctArgs: Record<string, any> = {
269
- where: query.where,
270
- distinct: distinctFields,
271
- select: { [selectField]: true },
272
- take: effectiveLimit + 1,
273
- }
274
- const results = shape
275
- ? await delegate.guard(shape, caller).findMany(distinctArgs)
276
- : await delegate.findMany(distinctArgs)
277
- if (results.length > effectiveLimit) {
278
- console.warn('[prisma-generator-express] Distinct count exceeds ' + effectiveLimit + ', falling back to approximate total')
279
- const countArgs: Record<string, any> = {}
280
- if (query.where) countArgs.where = query.where
281
- return countShape
282
- ? await delegate.guard(countShape, caller).count(countArgs)
283
- : await delegate.count(countArgs)
284
- }
285
- return results.length
286
- }
287
-
288
- const countArgs: Record<string, any> = {}
289
- if (query.where) countArgs.where = query.where
290
- return countShape
291
- ? await delegate.guard(countShape, caller).count(countArgs)
292
- : await delegate.count(countArgs)
293
- }
294
-
295
- export function transformResult(value: unknown): unknown {
296
- if (value === null || value === undefined) return value
297
- if (typeof value === 'bigint') return value.toString()
298
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value.toString('base64')
299
- if (value instanceof Uint8Array) return Buffer.from(value).toString('base64')
300
- if (value instanceof Date) return value
301
- if (Array.isArray(value)) return value.map(transformResult)
302
- if (typeof value === 'object') {
303
- const proto = Object.getPrototypeOf(value)
304
- if (proto !== Object.prototype && proto !== null) return value
305
- const out: Record<string, unknown> = {}
306
- for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
307
- out[k] = transformResult(v)
308
- }
309
- return out
310
- }
311
- return value
312
- }
313
-
314
- export function acceptsEventStream(accept: string | undefined): boolean {
315
- if (!accept) return false
316
- return accept.toLowerCase().includes('text/event-stream')
317
- }
318
-
319
- const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
320
-
321
- function isPlainObject(value: unknown): value is Record<string, unknown> {
322
- if (value === null || typeof value !== 'object') return false
323
- if (Array.isArray(value)) return false
324
- const proto = Object.getPrototypeOf(value)
325
- return proto === Object.prototype || proto === null
326
- }
327
-
328
- export function setByPath(target: Record<string, unknown>, path: string, value: unknown): boolean {
329
- const parts = path.split('.')
330
- if (parts.length === 0) return false
331
- for (const p of parts) {
332
- if (p === '' || UNSAFE_PATH_SEGMENTS.has(p)) return false
333
- }
334
- let cursor: Record<string, unknown> = target
335
- for (let i = 0; i < parts.length - 1; i++) {
336
- const part = parts[i]
337
- const next = cursor[part]
338
- if (!isPlainObject(next)) {
339
- if (process.env.NODE_ENV !== 'production') {
340
- console.warn(
341
- '[progressive] Dropping patch for "' + path +
342
- '": cannot traverse non-plain-object at segment "' + part + '"',
343
- )
344
- }
345
- return false
346
- }
347
- cursor = next
348
- }
349
- cursor[parts[parts.length - 1]] = value
350
- return true
351
- }
352
-
353
- function removeReqCloseListener(req: any, listener: () => void): void {
354
- if (typeof req.off === 'function') {
355
- req.off('close', listener)
356
- } else if (typeof req.removeListener === 'function') {
357
- req.removeListener('close', listener)
358
- }
359
- }
360
-
361
- export function initSSE(res: any): void {
362
- res.statusCode = 200
363
- res.setHeader('Content-Type', 'text/event-stream')
364
- res.setHeader('Cache-Control', 'no-cache, no-transform')
365
- res.setHeader('Connection', 'keep-alive')
366
- res.setHeader('X-Accel-Buffering', 'no')
367
- if (typeof res.flushHeaders === 'function') res.flushHeaders()
368
- }
369
-
370
- export function flushSSE(res: any): void {
371
- if (typeof res.flush === 'function') {
372
- try { res.flush() } catch {}
373
- }
374
- }
375
-
376
- export function sendSSE(res: any, payload: unknown): boolean {
377
- if (res.writableEnded || res.destroyed) return false
378
- try {
379
- res.write('data: ' + JSON.stringify(transformResult(payload)) + '\\n\\n')
380
- flushSSE(res)
381
- return true
382
- } catch (err) {
383
- console.error('[progressive] failed to send SSE event:', err)
384
- return false
385
- }
386
- }
387
-
388
- export function sendSSEProgress(res: any, stage: string, completed: number, total: number): boolean {
389
- return sendSSE(res, { type: 'progress', stage, completed, total })
390
- }
391
-
392
- export function sendSSEField(res: any, key: string, value: unknown): boolean {
393
- return sendSSE(res, { type: 'field', key, value })
394
- }
395
-
396
- export function sendSSEResult(res: any, data: unknown): boolean {
397
- return sendSSE(res, { type: 'result', data })
398
- }
399
-
400
- export function sendSSEError(res: any, message: string): boolean {
401
- if (res.writableEnded || res.destroyed) return false
402
- try {
403
- res.write('data: ' + JSON.stringify({ type: 'error', message }) + '\\n\\n')
404
- flushSSE(res)
405
- return true
406
- } catch (err) {
407
- console.error('[progressive] failed to send SSE error event:', err)
408
- return false
409
- }
410
- }
411
-
412
- export function startSSEKeepalive(res: any, intervalMs: number = 15000): any {
413
- const handle = setInterval(() => {
414
- if (res.writableEnded || res.destroyed) return
415
- try {
416
- res.write(': keepalive\\n\\n')
417
- flushSSE(res)
418
- } catch {}
419
- }, intervalMs)
420
- if (typeof (handle as any).unref === 'function') (handle as any).unref()
421
- return handle
422
- }
423
-
424
- export function endSSE(res: any, keepaliveHandle: any): void {
425
- if (keepaliveHandle) {
426
- try { clearInterval(keepaliveHandle) } catch {}
427
- }
428
- if (!res.writableEnded && !res.destroyed) {
429
- try { res.end() } catch {}
430
- }
431
- }
432
-
433
- export interface RunSingleResultSSEOptions {
434
- req: any
435
- res: any
436
- coreQueryFn: () => Promise<unknown>
437
- }
438
-
439
- export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Promise<void> {
440
- const { req, res, coreQueryFn } = options
441
- let keepalive: any = null
442
- try {
443
- initSSE(res)
444
- keepalive = startSSEKeepalive(res)
445
- if (req.destroyed) return
446
- const data = await coreQueryFn()
447
- if (res.writableEnded || res.destroyed) return
448
- sendSSEResult(res, data)
449
- } catch (err) {
450
- console.error('[progressive] single-result error:', err)
451
- if (!res.writableEnded && !res.destroyed) {
452
- sendSSEError(res, 'Internal server error')
453
- }
454
- } finally {
455
- endSSE(res, keepalive)
456
- }
457
- }
458
-
459
- function isStopResult(value: unknown): value is ProgressiveStopResult<unknown> {
460
- return typeof value === 'object' && value !== null && (value as any).stop === true
461
- }
462
-
463
- export interface RunProgressiveOptions {
464
- req: any
465
- res: any
466
- ctx: unknown
467
- prisma: any
468
- variant: string
469
- stages: string[]
470
- stageRegistry: Record<string, ProgressiveStage<any, any>>
471
- }
472
-
473
- export async function runProgressiveEndpoint(options: RunProgressiveOptions): Promise<void> {
474
- const { req, res, ctx, prisma, variant, stages, stageRegistry } = options
475
- let keepalive: any = null
476
- const controller = new AbortController()
477
- const onClose = () => controller.abort()
478
- if (typeof req.on === 'function') req.on('close', onClose)
479
-
480
- const accumulated: Record<string, unknown> = {}
481
- const signal = controller.signal
482
-
483
- try {
484
- initSSE(res)
485
- keepalive = startSSEKeepalive(res)
486
- sendSSEProgress(res, 'start', 0, stages.length)
487
-
488
- for (let i = 0; i < stages.length; i++) {
489
- if (res.writableEnded || res.destroyed || signal.aborted) return
490
- const stageName = stages[i]
491
- const stage = stageRegistry[stageName]
492
- if (!stage) throw new Error('Missing progressive stage: ' + stageName)
493
-
494
- const result = await stage({ ctx, req, res, prisma, variant, accumulated, signal })
495
- if (res.writableEnded || res.destroyed) return
496
-
497
- if (isStopResult(result)) {
498
- sendSSEResult(res, result.data)
499
- return
500
- }
501
-
502
- const patches = Array.isArray(result) ? result : result ? [result] : []
503
- for (const patch of patches) {
504
- if (!patch || typeof patch !== 'object') continue
505
- if (typeof (patch as any).key !== 'string') continue
506
- if (!('value' in patch)) continue
507
- const p = patch as ProgressivePatch
508
- const applied = setByPath(accumulated, p.key, p.value)
509
- if (applied) sendSSEField(res, p.key, p.value)
510
- }
511
- sendSSEProgress(res, stageName, i + 1, stages.length)
512
- }
513
- if (res.writableEnded || res.destroyed) return
514
- sendSSEResult(res, accumulated)
515
- } catch (err) {
516
- console.error('[progressive] stage error:', err)
517
- if (!res.writableEnded && !res.destroyed) {
518
- sendSSEError(res, 'Could not load progressive response')
519
- }
520
- } finally {
521
- removeReqCloseListener(req, onClose)
522
- endSSE(res, keepalive)
523
- }
524
- }
525
- `
526
- }
527
-
528
5
  export interface ModelCoreOptions {
529
6
  model: DMMF.Model
530
7
  importStyle: ImportStyle
@@ -545,11 +22,12 @@ export function generateModelCore(options: ModelCoreOptions): string {
545
22
  export async function ${op}(ctx: OperationContext): Promise<unknown> {
546
23
  const query = ctx.parsedQuery || {}
547
24
  const extended = await getExtendedClient(ctx)
25
+ const delegate = getDelegate(extended, '${modelNameLower}')
548
26
  if (ctx.guardShape) {
549
- assertGuard((extended as any).${modelNameLower})
550
- return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).${op}(query)
27
+ assertGuard(delegate)
28
+ return delegate.guard(ctx.guardShape, ctx.guardCaller).${op}(query)
551
29
  }
552
- return (extended as any).${modelNameLower}.${op}(query)
30
+ return delegate.${op}(query)
553
31
  }`)
554
32
  .join('\n')
555
33
 
@@ -572,17 +50,19 @@ export async function ${op.name}(ctx: OperationContext): Promise<unknown> {
572
50
  const body = validateBody(ctx.body)
573
51
  ${validationLines}
574
52
  const extended = await getExtendedClient(ctx)
53
+ const delegate = getDelegate(extended, '${modelNameLower}')
575
54
  if (ctx.guardShape) {
576
- assertGuard((extended as any).${modelNameLower})
577
- return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).${op.method}(body)
55
+ assertGuard(delegate)
56
+ return delegate.guard(ctx.guardShape, ctx.guardCaller).${op.method}(body)
578
57
  }
579
- return (extended as any).${modelNameLower}.${op.method}(body)
58
+ return delegate.${op.method}(body)
580
59
  }`
581
60
  }).join('\n')
582
61
 
583
62
  return `import {
584
63
  OperationContext,
585
64
  getExtendedClient,
65
+ getDelegate,
586
66
  validateBody,
587
67
  requireBodyField,
588
68
  applyPaginationLimits,
@@ -594,11 +74,12 @@ export async function findMany(ctx: OperationContext): Promise<unknown> {
594
74
  const rawQuery = ctx.parsedQuery || {}
595
75
  const query = applyPaginationLimits(rawQuery, ctx.paginationConfig)
596
76
  const extended = await getExtendedClient(ctx)
77
+ const delegate = getDelegate(extended, '${modelNameLower}')
597
78
  if (ctx.guardShape) {
598
- assertGuard((extended as any).${modelNameLower})
599
- return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).findMany(query)
79
+ assertGuard(delegate)
80
+ return delegate.guard(ctx.guardShape, ctx.guardCaller).findMany(query)
600
81
  }
601
- return (extended as any).${modelNameLower}.findMany(query)
82
+ return delegate.findMany(query)
602
83
  }
603
84
  ${standardReadHandlers}
604
85
  ${writeHandlers}
@@ -612,33 +93,39 @@ export async function findManyPaginated(
612
93
  const shape = ctx.guardShape
613
94
  const caller = ctx.guardCaller
614
95
  const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
615
- const delegate = (extended as any).${modelNameLower}
96
+ const delegate = getDelegate(extended, '${modelNameLower}')
616
97
 
617
98
  if (shape) assertGuard(delegate)
618
99
 
619
- let items: any[]
100
+ let items: unknown[]
620
101
  let total: number
621
102
 
622
- if (shape || typeof extended.$transaction !== 'function') {
103
+ const txClient = extended as { $transaction?: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T> }
104
+
105
+ if (shape || typeof txClient.$transaction !== 'function') {
623
106
  const [data, count] = await Promise.all([
624
- shape ? delegate.guard(shape, caller).findMany(query) : delegate.findMany(query),
107
+ shape
108
+ ? (delegate.guard as NonNullable<typeof delegate.guard>)(shape, caller).findMany(query)
109
+ : delegate.findMany(query),
625
110
  countForPagination(delegate, query, shape, caller, distinctCountLimit),
626
111
  ])
627
- items = data
112
+ items = data as unknown[]
628
113
  total = count
629
114
  } else {
630
115
  try {
631
- const txResult = await extended.$transaction(async (tx: any) => {
632
- const d = await tx.${modelNameLower}.findMany(query)
633
- const t = await countForPagination(tx.${modelNameLower}, query, undefined, undefined, distinctCountLimit)
116
+ const txResult = await txClient.$transaction(async (tx: unknown) => {
117
+ const txDelegate = getDelegate(tx, '${modelNameLower}')
118
+ const d = await txDelegate.findMany(query)
119
+ const t = await countForPagination(txDelegate, query, undefined, undefined, distinctCountLimit)
634
120
  return { d, t }
635
121
  })
636
- items = txResult.d
122
+ items = txResult.d as unknown[]
637
123
  total = txResult.t
638
- } catch (txError: any) {
639
- if (txError?.message?.includes?.('interactive transactions') || txError?.code === 'P2028') {
124
+ } catch (txError: unknown) {
125
+ const txe = txError as { message?: string; code?: string }
126
+ if ((typeof txe?.message === 'string' && txe.message.includes('interactive transactions')) || txe?.code === 'P2028') {
640
127
  console.warn('[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic')
641
- items = await delegate.findMany(query)
128
+ items = (await delegate.findMany(query)) as unknown[]
642
129
  total = await countForPagination(delegate, query, undefined, undefined, distinctCountLimit)
643
130
  } else {
644
131
  throw txError
@@ -646,8 +133,9 @@ export async function findManyPaginated(
646
133
  }
647
134
  }
648
135
 
649
- const skip = (query.skip as number) ?? 0
650
- const absTake = Math.abs((query.take as number) ?? items.length)
136
+ const skip = (typeof query.skip === 'number' ? query.skip : 0)
137
+ const takeRaw = (typeof query.take === 'number' ? query.take : items.length)
138
+ const absTake = Math.abs(takeRaw)
651
139
  const hasMore = items.length >= absTake && skip + items.length < total
652
140
 
653
141
  return { data: items, total, hasMore }