prisma-generator-express 1.28.0 → 1.30.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 (49) hide show
  1. package/README.md +244 -14
  2. package/dist/constants.d.ts +1 -0
  3. package/dist/generators/generateFastifyHandler.d.ts +4 -0
  4. package/dist/generators/generateFastifyHandler.js +78 -0
  5. package/dist/generators/generateFastifyHandler.js.map +1 -0
  6. package/dist/generators/generateOperationCore.d.ts +6 -0
  7. package/dist/generators/generateOperationCore.js +534 -0
  8. package/dist/generators/generateOperationCore.js.map +1 -0
  9. package/dist/generators/generateQueryBuilderHelper.js +85 -69
  10. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  11. package/dist/generators/generateRouter.js +1 -25
  12. package/dist/generators/generateRouter.js.map +1 -1
  13. package/dist/generators/generateRouterFastify.d.ts +5 -0
  14. package/dist/generators/generateRouterFastify.js +512 -0
  15. package/dist/generators/generateRouterFastify.js.map +1 -0
  16. package/dist/generators/generateUnifiedDocs.d.ts +2 -1
  17. package/dist/generators/generateUnifiedDocs.js +147 -82
  18. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  19. package/dist/generators/generateUnifiedHandler.d.ts +0 -1
  20. package/dist/generators/generateUnifiedHandler.js +47 -516
  21. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  22. package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
  23. package/dist/generators/generateUnifiedScalarUI.js +127 -1324
  24. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  25. package/dist/index.js +33 -8
  26. package/dist/index.js.map +1 -1
  27. package/dist/utils/copyFiles.d.ts +2 -1
  28. package/dist/utils/copyFiles.js +73 -39
  29. package/dist/utils/copyFiles.js.map +1 -1
  30. package/dist/utils/writeFileSafely.js +3 -0
  31. package/dist/utils/writeFileSafely.js.map +1 -1
  32. package/package.json +4 -1
  33. package/src/client/encodeQueryParams.ts +1 -1
  34. package/src/constants.ts +2 -0
  35. package/src/copy/createOutputValidatorMiddleware.ts +9 -12
  36. package/src/copy/docsRenderer.ts +1285 -0
  37. package/src/copy/parseQueryParams.ts +4 -8
  38. package/src/copy/routeConfig.ts +10 -4
  39. package/src/generators/generateFastifyHandler.ts +86 -0
  40. package/src/generators/generateOperationCore.ts +545 -0
  41. package/src/generators/generateQueryBuilderHelper.ts +86 -70
  42. package/src/generators/generateRouter.ts +1 -25
  43. package/src/generators/generateRouterFastify.ts +522 -0
  44. package/src/generators/generateUnifiedDocs.ts +164 -81
  45. package/src/generators/generateUnifiedHandler.ts +45 -533
  46. package/src/generators/generateUnifiedScalarUI.ts +134 -1323
  47. package/src/index.ts +45 -9
  48. package/src/utils/copyFiles.ts +88 -44
  49. package/src/utils/writeFileSafely.ts +4 -0
@@ -2,554 +2,66 @@ import { DMMF } from '@prisma/generator-helper'
2
2
 
3
3
  export interface UnifiedHandlerOptions {
4
4
  model: DMMF.Model
5
- prismaImportStatement: string
6
5
  }
7
6
 
8
- export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
9
- const { model, prismaImportStatement } = options
10
- const modelName = model.name
11
- const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
12
- const importPath =
13
- prismaImportStatement.match(/from ['"](.+?)['"]/)?.[1] || ''
14
-
15
- return `
16
- import { PrismaClient } from '${importPath}'
17
- import { Request, Response, NextFunction } from 'express'
18
- import { sanitizeKeys } from '../misc'
19
-
20
- let _speedExtension: ((opts: any) => any) | null = null
21
-
22
- const _prismasqlModule = 'prisma-' + 'sql'
23
- const _prismasqlReady = (async () => {
24
- try {
25
- const mod = await import(_prismasqlModule)
26
- _speedExtension = mod.speedExtension ?? mod.default?.speedExtension ?? null
27
- } catch (err: any) {
28
- const code = err?.code
29
- if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
30
- console.warn('[prisma-generator-express] prisma-sql initialization failed:', err)
31
- }
32
- }
33
- })()
34
-
35
- const _extendedClients = new WeakMap<object, WeakMap<object, PrismaClient>>()
36
-
37
- const DISTINCT_COUNT_LIMIT = 100000
38
-
39
- export class HttpError extends Error {
40
- status: number
41
- constructor(status: number, message: string) {
42
- super(message)
43
- this.name = 'HttpError'
44
- this.status = status
45
- }
46
- }
47
-
48
- const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
49
- P2000: { status: 400, message: 'Value too long for column' },
50
- P2001: { status: 404, message: 'Record not found' },
51
- P2002: { status: 409, message: 'Unique constraint violation' },
52
- P2003: { status: 400, message: 'Foreign key constraint failed' },
53
- P2004: { status: 400, message: 'Constraint failed on the database' },
54
- P2005: { status: 400, message: 'Invalid field value' },
55
- P2006: { status: 400, message: 'Invalid value provided' },
56
- P2007: { status: 400, message: 'Data validation error' },
57
- P2008: { status: 400, message: 'Failed to parse the query' },
58
- P2009: { status: 400, message: 'Failed to validate the query' },
59
- P2010: { status: 500, message: 'Raw query failed' },
60
- P2011: { status: 400, message: 'Null constraint violation' },
61
- P2012: { status: 400, message: 'Missing required value' },
62
- P2013: { status: 400, message: 'Missing required argument' },
63
- P2014: { status: 400, message: 'Required relation violation' },
64
- P2015: { status: 404, message: 'Related record not found' },
65
- P2016: { status: 400, message: 'Query interpretation error' },
66
- P2017: { status: 400, message: 'Records not connected' },
67
- P2018: { status: 404, message: 'Required connected record not found' },
68
- P2019: { status: 400, message: 'Input error' },
69
- P2020: { status: 400, message: 'Value out of range for the field type' },
70
- P2021: { status: 500, message: 'Table does not exist in the database' },
71
- P2022: { status: 500, message: 'Column does not exist in the database' },
72
- P2023: { status: 500, message: 'Inconsistent column data' },
73
- P2024: { status: 503, message: 'Connection pool timeout' },
74
- P2025: { status: 404, message: 'Record not found' },
75
- P2026: { status: 501, message: 'Feature not supported by the current database provider' },
76
- P2028: { status: 500, message: 'Transaction API error' },
77
- P2030: { status: 400, message: 'Cannot find a fulltext index for the search' },
78
- P2033: { status: 400, message: 'Number out of range for the field type' },
79
- P2034: { status: 409, message: 'Transaction conflict, please retry' },
80
- }
81
-
82
- async function getExtendedClient(req: Request): Promise<PrismaClient> {
83
- const base = (req as any).prisma as PrismaClient
84
- if (!base) {
85
- throw new HttpError(500, 'PrismaClient not found on request. Set req.prisma in middleware.')
86
- }
87
-
88
- await _prismasqlReady
89
-
90
- if (!_speedExtension) return base
91
-
92
- const connector = (req as any).postgres || (req as any).sqlite
93
- if (!connector) return base
94
-
95
- if (typeof connector === 'object' && connector !== null) {
96
- const innerMap = _extendedClients.get(connector)
97
- if (innerMap) {
98
- const cached = innerMap.get(base)
99
- if (cached) return cached
100
- }
101
- }
102
-
103
- try {
104
- const extended = base.$extends(_speedExtension({
105
- postgres: (req as any).postgres,
106
- sqlite: (req as any).sqlite,
107
- debug: process.env.DEBUG === 'true'
108
- })) as unknown as PrismaClient
109
-
110
- if (typeof connector === 'object' && connector !== null) {
111
- let innerMap = _extendedClients.get(connector)
112
- if (!innerMap) {
113
- innerMap = new WeakMap<object, PrismaClient>()
114
- _extendedClients.set(connector, innerMap)
115
- }
116
- innerMap.set(base, extended)
117
- }
118
-
119
- return extended
120
- } catch (error) {
121
- console.warn('[speedExtension] Failed to initialize, using base client:', error)
122
- return base
123
- }
124
- }
125
-
126
- function handleError(error: unknown, next: NextFunction): void {
127
- if (error instanceof HttpError) {
128
- next(error);
129
- return;
130
- }
131
-
132
- if (
133
- error &&
134
- typeof error === "object" &&
135
- "name" in error &&
136
- error.name === "ShapeError"
137
- ) {
138
- next(new HttpError(400, (error as any).message));
139
- return;
140
- }
141
-
142
- if (
143
- error &&
144
- typeof error === "object" &&
145
- "name" in error &&
146
- error.name === "CallerError"
147
- ) {
148
- next(new HttpError(400, (error as any).message));
149
- return;
150
- }
151
-
152
- if (
153
- error &&
154
- typeof error === "object" &&
155
- "name" in error &&
156
- error.name === "PolicyError"
157
- ) {
158
- next(new HttpError(403, (error as any).message));
159
- return;
160
- }
161
-
162
- if (
163
- error &&
164
- typeof error === "object" &&
165
- "issues" in error &&
166
- "name" in error &&
167
- (error as any).name === "ZodError"
168
- ) {
169
- const issues = (error as any).issues;
170
- const message = Array.isArray(issues)
171
- ? issues.map((i: any) => i.message).join("; ")
172
- : (error as any).message;
173
- next(new HttpError(400, message));
174
- return;
175
- }
176
-
177
- if (error && typeof error === "object" && "code" in error) {
178
- const code = (error as any).code as string;
179
- const mapped = PRISMA_ERROR_MAP[code];
180
- if (mapped) {
181
- next(new HttpError(mapped.status, mapped.message));
182
- return;
183
- }
184
- if (typeof code === "string" && code.startsWith("P")) {
185
- console.warn(
186
- "[prisma-generator-express] Unmapped Prisma error code:",
187
- code,
188
- (error as any).message || "",
189
- );
190
- next(new HttpError(500, "Database operation failed"));
191
- return;
192
- }
193
- }
194
-
195
- if (error && typeof error === "object" && "name" in error) {
196
- const name = (error as any).name;
197
- if (name === "PrismaClientValidationError") {
198
- next(new HttpError(400, "Invalid query parameters"));
199
- return;
200
- }
201
- }
202
-
203
- console.error("[prisma-generator-express] Unhandled error:", error);
204
- next(new HttpError(500, "Internal server error"));
205
- }
206
-
207
- function safeParseBody(req: Request): Record<string, any> {
208
- const body = req.body
209
- if (!body || typeof body !== 'object' || Array.isArray(body)) {
210
- throw new HttpError(400, 'Request body must be a JSON object')
211
- }
212
- return sanitizeKeys(body as Record<string, any>)
213
- }
214
-
215
- function requireBodyField(body: Record<string, any>, field: string): void {
216
- if (!(field in body) || body[field] === undefined) {
217
- throw new HttpError(400, 'Missing required field: ' + field)
218
- }
219
- }
220
-
221
- function applyPaginationLimits(query: Record<string, any>, res: Response): Record<string, any> {
222
- const routeConfig = res.locals.routeConfig
223
- const pagination = routeConfig?.pagination
224
- if (!pagination) return query
225
-
226
- const result = { ...query }
227
-
228
- if (result.take === undefined && pagination.defaultLimit !== undefined) {
229
- result.take = pagination.defaultLimit
230
- }
231
-
232
- if (pagination.maxLimit !== undefined && result.take !== undefined) {
233
- const takeNum = Number(result.take)
234
- if (Math.abs(takeNum) > pagination.maxLimit) {
235
- result.take = takeNum < 0 ? -pagination.maxLimit : pagination.maxLimit
236
- }
237
- }
238
-
239
- return result
240
- }
241
-
242
- function normalizeDistinct(value: unknown): string[] {
243
- if (typeof value === 'string') return [value]
244
- if (Array.isArray(value)) return value.filter((v): v is string => typeof v === 'string')
245
- return []
246
- }
247
-
248
- function assertGuard(delegate: any): void {
249
- if (typeof delegate.guard !== 'function') {
250
- throw new HttpError(500, 'Guard shapes require prisma-guard extension on PrismaClient. Install: npm install prisma-guard, then extend your client with guardExtension().')
251
- }
252
- }
253
-
254
- const GUARD_SHAPE_CONFIG_KEYS = new Set([
255
- 'data', 'create', 'update', 'where', 'include', 'select', 'orderBy',
256
- 'cursor', 'take', 'skip', 'distinct', 'having', '_count', '_avg',
257
- '_sum', '_min', '_max', 'by',
258
- ])
7
+ const ALL_OPS = [
8
+ 'findMany',
9
+ 'findFirst',
10
+ 'findFirstOrThrow',
11
+ 'findUnique',
12
+ 'findUniqueOrThrow',
13
+ 'findManyPaginated',
14
+ 'create',
15
+ 'createMany',
16
+ 'createManyAndReturn',
17
+ 'update',
18
+ 'updateMany',
19
+ 'updateManyAndReturn',
20
+ 'upsert',
21
+ 'delete',
22
+ 'deleteMany',
23
+ 'aggregate',
24
+ 'count',
25
+ 'groupBy',
26
+ ]
259
27
 
260
- function keepWhereOnly(obj: Record<string, any>): Record<string, any> {
261
- const result: Record<string, any> = {}
262
- if ('where' in obj) result.where = obj.where
263
- return result
264
- }
265
-
266
- function buildCountShape(shape: Record<string, any>): Record<string, any> {
267
- if (typeof shape === 'function') {
268
- return (...args: any[]) => keepWhereOnly((shape as Function)(...args))
269
- }
270
-
271
- const keys = Object.keys(shape)
272
- const isSingleShape = keys.length === 0 || keys.every(k => GUARD_SHAPE_CONFIG_KEYS.has(k))
273
-
274
- if (isSingleShape) {
275
- return keepWhereOnly(shape)
276
- }
277
-
278
- const result: Record<string, any> = {}
279
- for (const [key, variant] of Object.entries(shape)) {
280
- if (typeof variant === 'function') {
281
- result[key] = (...args: any[]) => keepWhereOnly(variant(...args))
282
- } else if (typeof variant === 'object' && variant !== null) {
283
- result[key] = keepWhereOnly(variant)
284
- } else {
285
- result[key] = variant
286
- }
287
- }
288
- return result
289
- }
290
-
291
- ${generateReadHandlers(modelName, modelNameLower)}
292
-
293
- ${generateWriteHandlers(modelName, modelNameLower)}
294
- `
295
- }
296
-
297
- function generateReadHandlers(
298
- modelName: string,
299
- modelNameLower: string,
300
- ): string {
301
- const standardReadOps = [
302
- 'findFirst',
303
- 'findUnique',
304
- 'findUniqueOrThrow',
305
- 'findFirstOrThrow',
306
- 'count',
307
- 'aggregate',
308
- 'groupBy',
309
- ]
310
-
311
- const standardHandlers = standardReadOps
312
- .map((op) => {
313
- const functionName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
314
-
315
- return `
316
- export async function ${functionName}(
317
- req: Request,
318
- res: Response,
319
- next: NextFunction
320
- ) {
321
- try {
322
- const query = res.locals.parsedQuery || {}
323
- const extended = await getExtendedClient(req)
324
- const shape = res.locals.guardShape
28
+ export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
29
+ const modelName = options.model.name
325
30
 
326
- let data
327
- if (shape) {
328
- assertGuard((extended as any).${modelNameLower})
329
- const caller = res.locals.guardCaller
330
- data = await (extended as any).${modelNameLower}.guard(shape, caller).${op}(query)
331
- } else {
332
- data = await (extended as any).${modelNameLower}.${op}(query)
333
- }
31
+ const handlers = ALL_OPS.map((op) => {
32
+ const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
334
33
 
335
- res.locals.data = data
336
- next()
337
- } catch (error: unknown) {
338
- handleError(error, next)
339
- }
340
- }
341
- `
342
- })
343
- .join('\n')
344
-
345
- const findManyHandler = `
346
- export async function ${modelName}FindMany(
34
+ return `
35
+ export async function ${exportName}(
347
36
  req: Request,
348
37
  res: Response,
349
- next: NextFunction
38
+ next: NextFunction,
350
39
  ) {
351
40
  try {
352
- const rawQuery = res.locals.parsedQuery || {}
353
- const query = applyPaginationLimits(rawQuery, res)
354
- const extended = await getExtendedClient(req)
355
- const shape = res.locals.guardShape
356
-
357
- let data
358
- if (shape) {
359
- assertGuard((extended as any).${modelNameLower})
360
- const caller = res.locals.guardCaller
361
- data = await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
362
- } else {
363
- data = await (extended as any).${modelNameLower}.findMany(query)
364
- }
365
-
366
- res.locals.data = data
367
- next()
368
- } catch (error: unknown) {
369
- handleError(error, next)
370
- }
371
- }
372
- `
373
-
374
- return findManyHandler + '\n' + standardHandlers
375
- }
376
-
377
- function generateWriteHandlers(
378
- modelName: string,
379
- modelNameLower: string,
380
- ): string {
381
- const writeOps: {
382
- name: string
383
- method: string
384
- requiredFields?: string[]
385
- }[] = [
386
- { name: 'Create', method: 'create', requiredFields: ['data'] },
387
- { name: 'CreateMany', method: 'createMany', requiredFields: ['data'] },
388
- {
389
- name: 'CreateManyAndReturn',
390
- method: 'createManyAndReturn',
391
- requiredFields: ['data'],
392
- },
393
- { name: 'Update', method: 'update', requiredFields: ['where', 'data'] },
394
- {
395
- name: 'UpdateMany',
396
- method: 'updateMany',
397
- requiredFields: ['where', 'data'],
398
- },
399
- {
400
- name: 'UpdateManyAndReturn',
401
- method: 'updateManyAndReturn',
402
- requiredFields: ['where', 'data'],
403
- },
404
- { name: 'Delete', method: 'delete', requiredFields: ['where'] },
405
- { name: 'DeleteMany', method: 'deleteMany', requiredFields: ['where'] },
406
- {
407
- name: 'Upsert',
408
- method: 'upsert',
409
- requiredFields: ['where', 'create', 'update'],
410
- },
411
- ]
412
-
413
- return (
414
- writeOps
415
- .map((op) => {
416
- const functionName = `${modelName}${op.name}`
417
- const validationLines = (op.requiredFields || [])
418
- .map((field) => ` requireBodyField(body, '${field}')`)
419
- .join('\n')
420
-
421
- return `
422
- export async function ${functionName}(req: Request, res: Response, next: NextFunction) {
423
- try {
424
- const body = safeParseBody(req)
425
- ${validationLines ? validationLines + '\n' : ''} const extended = await getExtendedClient(req)
426
- const shape = res.locals.guardShape
427
-
428
- let data
429
- if (shape) {
430
- assertGuard((extended as any).${modelNameLower})
431
- const caller = res.locals.guardCaller
432
- data = await (extended as any).${modelNameLower}.guard(shape, caller).${op.method}(body)
433
- } else {
434
- data = await (extended as any).${modelNameLower}.${op.method}(body)
435
- }
436
-
437
- res.locals.data = data
41
+ res.locals.data = await core.${op}(buildContext(req, res))
438
42
  next()
439
43
  } catch (error: unknown) {
440
- handleError(error, next)
44
+ next(mapError(error))
441
45
  }
442
- }
443
- `
444
- })
445
- .join('\n') +
446
- `
447
-
448
- async function countForPagination(
449
- delegate: any,
450
- query: Record<string, any>,
451
- shape: Record<string, any> | undefined,
452
- caller: string | undefined,
453
- ): Promise<number> {
454
- const distinctFields = normalizeDistinct(query.distinct)
455
- const hasDistinct = distinctFields.length > 0
456
-
457
- const countShape = shape ? buildCountShape(shape) : undefined
458
-
459
- if (hasDistinct) {
460
- if (shape) {
461
- const countArgs: Record<string, any> = {}
462
- if (query.where) countArgs.where = query.where
463
- return countShape
464
- ? await delegate.guard(countShape, caller).count(countArgs)
465
- : await delegate.count(countArgs)
466
- }
46
+ }`
47
+ }).join('\n')
467
48
 
468
- const selectField = distinctFields[0]
469
- const distinctArgs: Record<string, any> = {
470
- where: query.where,
471
- distinct: distinctFields,
472
- select: { [selectField]: true },
473
- take: DISTINCT_COUNT_LIMIT + 1,
474
- }
49
+ return `import { Request, Response, NextFunction } from 'express'
50
+ import * as core from './${modelName}Core.js'
51
+ import { OperationContext, mapError } from '../operationRuntime.js'
475
52
 
476
- const results = await delegate.findMany(distinctArgs)
477
-
478
- if (results.length > DISTINCT_COUNT_LIMIT) {
479
- console.warn('[prisma-generator-express] Distinct count exceeds ' + DISTINCT_COUNT_LIMIT + ', falling back to approximate total')
480
- const countArgs: Record<string, any> = {}
481
- if (query.where) countArgs.where = query.where
482
- return await delegate.count(countArgs)
483
- }
484
-
485
- return results.length
486
- }
487
-
488
- const countArgs: Record<string, any> = {}
489
- if (query.where) countArgs.where = query.where
490
-
491
- return countShape
492
- ? await delegate.guard(countShape, caller).count(countArgs)
493
- : await delegate.count(countArgs)
494
- }
495
-
496
- export async function ${modelName}FindManyPaginated(req: Request, res: Response, next: NextFunction) {
497
- try {
498
- const rawQuery = res.locals.parsedQuery || {}
499
- const query = applyPaginationLimits(rawQuery, res)
500
- const extended = await getExtendedClient(req)
501
- const shape = res.locals.guardShape
502
- const caller = res.locals.guardCaller
503
-
504
- if (shape) {
505
- assertGuard((extended as any).${modelNameLower})
506
- }
507
-
508
- let items: any[]
509
- let total: number
510
-
511
- if (typeof extended.$transaction === 'function') {
512
- try {
513
- const txResult = await extended.$transaction(async (tx: any) => {
514
- const d = shape
515
- ? await tx.${modelNameLower}.guard(shape, caller).findMany(query)
516
- : await tx.${modelNameLower}.findMany(query)
517
- const t = await countForPagination(tx.${modelNameLower}, query, shape, caller)
518
- return { d, t }
519
- })
520
- items = txResult.d
521
- total = txResult.t
522
- } catch (txError: any) {
523
- if (
524
- txError?.message?.includes?.('interactive transactions') ||
525
- txError?.code === 'P2028'
526
- ) {
527
- console.warn('[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic')
528
- items = shape
529
- ? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
530
- : await (extended as any).${modelNameLower}.findMany(query)
531
- total = await countForPagination((extended as any).${modelNameLower}, query, shape, caller)
532
- } else {
533
- throw txError
534
- }
535
- }
536
- } else {
537
- items = shape
538
- ? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
539
- : await (extended as any).${modelNameLower}.findMany(query)
540
- total = await countForPagination((extended as any).${modelNameLower}, query, shape, caller)
541
- }
542
-
543
- const skip = (query.skip as number) ?? 0
544
- const absTake = Math.abs((query.take as number) ?? items.length)
545
- const hasMore = items.length >= absTake && skip + items.length < total
546
-
547
- res.locals.data = { data: items, total, hasMore }
548
- next()
549
- } catch (error: unknown) {
550
- handleError(error, next)
53
+ function buildContext(req: Request, res: Response): OperationContext {
54
+ return {
55
+ prisma: (req as any).prisma,
56
+ postgres: (req as any).postgres,
57
+ sqlite: (req as any).sqlite,
58
+ parsedQuery: res.locals.parsedQuery,
59
+ body: req.body,
60
+ guardShape: res.locals.guardShape,
61
+ guardCaller: res.locals.guardCaller,
62
+ paginationConfig: res.locals.routeConfig?.pagination,
551
63
  }
552
64
  }
65
+ ${handlers}
553
66
  `
554
- )
555
67
  }