prisma-generator-express 1.45.0 → 1.46.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 (70) hide show
  1. package/README.md +155 -30
  2. package/dist/client/encodeQueryParams.js +4 -0
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +4 -2
  5. package/dist/copy/misc.js +35 -24
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/generators/generateFastifyHandler.js +2 -4
  8. package/dist/generators/generateFastifyHandler.js.map +1 -1
  9. package/dist/generators/generateHonoHandler.js +2 -4
  10. package/dist/generators/generateHonoHandler.js.map +1 -1
  11. package/dist/generators/generateImportPrismaStatement.d.ts +0 -1
  12. package/dist/generators/generateImportPrismaStatement.js +2 -20
  13. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  14. package/dist/generators/generateOperationCore.js +1 -1
  15. package/dist/generators/generateQueryBuilderHelper.js +9 -0
  16. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  17. package/dist/generators/generateRelationMeta.js +0 -10
  18. package/dist/generators/generateRelationMeta.js.map +1 -1
  19. package/dist/generators/generateRouteConfigType.js +33 -12
  20. package/dist/generators/generateRouteConfigType.js.map +1 -1
  21. package/dist/generators/generateRouter.d.ts +0 -1
  22. package/dist/generators/generateRouter.js +75 -70
  23. package/dist/generators/generateRouter.js.map +1 -1
  24. package/dist/generators/generateRouterFastify.js +83 -89
  25. package/dist/generators/generateRouterFastify.js.map +1 -1
  26. package/dist/generators/generateRouterHono.js +257 -237
  27. package/dist/generators/generateRouterHono.js.map +1 -1
  28. package/dist/generators/generateUnifiedDocs.d.ts +2 -2
  29. package/dist/generators/generateUnifiedDocs.js +90 -252
  30. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  31. package/dist/generators/generateUnifiedHandler.js +2 -4
  32. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  33. package/dist/index.js +16 -8
  34. package/dist/index.js.map +1 -1
  35. package/dist/utils/copyFiles.js +3 -2
  36. package/dist/utils/copyFiles.js.map +1 -1
  37. package/dist/utils/strings.d.ts +0 -1
  38. package/dist/utils/strings.js +0 -9
  39. package/dist/utils/strings.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/client/encodeQueryParams.ts +7 -15
  42. package/src/copy/autoIncludePlanner.ts +4 -17
  43. package/src/copy/autoIncludeRuntime.ts +11 -19
  44. package/src/copy/buildModelOpenApi.ts +11 -14
  45. package/src/copy/docsRenderer.ts +8 -14
  46. package/src/copy/misc.ts +28 -23
  47. package/src/copy/operationRuntime.ts +61 -43
  48. package/src/copy/parseQueryParams.ts +5 -14
  49. package/src/copy/routeConfig.express.ts +24 -18
  50. package/src/copy/routeConfig.fastify.ts +1 -1
  51. package/src/copy/routeConfig.hono.ts +34 -6
  52. package/src/copy/routeConfig.ts +2 -2
  53. package/src/generators/generateFastifyHandler.ts +2 -5
  54. package/src/generators/generateHonoHandler.ts +2 -5
  55. package/src/generators/generateImportPrismaStatement.ts +3 -35
  56. package/src/generators/generateOperationCore.ts +1 -1
  57. package/src/generators/generateQueryBuilderHelper.ts +9 -0
  58. package/src/generators/generateRelationMeta.ts +0 -10
  59. package/src/generators/generateRouteConfigType.ts +34 -10
  60. package/src/generators/generateRouter.ts +75 -71
  61. package/src/generators/generateRouterFastify.ts +83 -89
  62. package/src/generators/generateRouterHono.ts +257 -237
  63. package/src/generators/generateUnifiedDocs.ts +89 -267
  64. package/src/generators/generateUnifiedHandler.ts +2 -4
  65. package/src/index.ts +45 -14
  66. package/src/utils/copyFiles.ts +2 -2
  67. package/src/utils/strings.ts +0 -8
  68. package/src/copy/createOutputValidatorMiddleware.ts +0 -47
  69. package/src/copy/createValidatorMiddleware.ts +0 -62
  70. package/src/copy/transformZod.ts +0 -139
@@ -1,5 +1,4 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
- import { toCamelCase } from '../utils/strings'
3
2
  import { generateRouteConfigType } from './generateRouteConfigType'
4
3
  import { ImportStyle } from '../utils/resolveImportStyle'
5
4
  import { importExt } from '../utils/importExt'
@@ -17,9 +16,8 @@ export function generateHonoRouterFunction({
17
16
  }): string {
18
17
  const ext = importExt(importStyle)
19
18
  const modelName = model.name
20
- const prefix = toCamelCase(modelName)
21
19
  const modelNameLower = modelName.toLowerCase()
22
- const routerFunctionName = `${prefix}Router`
20
+ const routerFunctionName = `${modelName}Router`
23
21
 
24
22
  const fieldsMeta = model.fields.map((f) => ({
25
23
  name: f.name,
@@ -46,350 +44,372 @@ export function generateHonoRouterFunction({
46
44
 
47
45
  return `import { Hono } from 'hono'
48
46
  import type { Context, Next } from 'hono'
47
+ import type { ContentfulStatusCode } from 'hono/utils/http-status'
49
48
  import { HTTPException } from 'hono/http-exception'
49
+ import { startQueryBuilder } from '../queryBuilder${ext}'
50
50
  import {
51
- ${prefix}FindUnique,
52
- ${prefix}FindUniqueOrThrow,
53
- ${prefix}FindFirst,
54
- ${prefix}FindFirstOrThrow,
55
- ${prefix}FindMany,
56
- ${prefix}FindManyPaginated,
57
- ${prefix}Create,
58
- ${prefix}CreateMany,
59
- ${prefix}CreateManyAndReturn,
60
- ${prefix}Update,
61
- ${prefix}UpdateMany,
62
- ${prefix}UpdateManyAndReturn,
63
- ${prefix}Upsert,
64
- ${prefix}Delete,
65
- ${prefix}DeleteMany,
66
- ${prefix}Aggregate,
67
- ${prefix}Count,
68
- ${prefix}GroupBy,
51
+ ${modelName}FindUnique,
52
+ ${modelName}FindUniqueOrThrow,
53
+ ${modelName}FindFirst,
54
+ ${modelName}FindFirstOrThrow,
55
+ ${modelName}FindMany,
56
+ ${modelName}FindManyPaginated,
57
+ ${modelName}Create,
58
+ ${modelName}CreateMany,
59
+ ${modelName}CreateManyAndReturn,
60
+ ${modelName}Update,
61
+ ${modelName}UpdateMany,
62
+ ${modelName}UpdateManyAndReturn,
63
+ ${modelName}Upsert,
64
+ ${modelName}Delete,
65
+ ${modelName}DeleteMany,
66
+ ${modelName}Aggregate,
67
+ ${modelName}Count,
68
+ ${modelName}GroupBy,
69
69
  } from './${modelName}Handlers${ext}'
70
- import type { RouteConfig, HonoHookHandler } from '../routeConfig.target${ext}'
70
+ import type {
71
+ RouteConfig,
72
+ HonoHookHandler,
73
+ HonoEnvBase,
74
+ HonoInternalVariables,
75
+ GeneratedHonoEnv,
76
+ } from '../routeConfig.target${ext}'
71
77
  import { parseQueryParams } from '../parseQueryParams${ext}'
72
- import { sanitizeKeys } from '../misc${ext}'
78
+ import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
73
79
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
74
- import { mapError, transformResult, HttpError, type OperationContext } from '../operationRuntime${ext}'
80
+ import { mapError, transformResult, type OperationContext } from '../operationRuntime${ext}'
75
81
 
76
82
  ${generateRouteConfigType(modelName, 'HonoHookHandler', guardShapesImport, importStyle, 'hono')}
77
-
78
- type HonoVariables = {
79
- prisma: unknown
80
- postgres?: unknown
81
- sqlite?: unknown
82
- parsedQuery?: Record<string, unknown>
83
- body?: unknown
84
- routeConfig?: { pagination?: OperationContext['paginationConfig'] }
85
- guardShape?: Record<string, unknown>
86
- guardCaller?: string
87
- resultData?: unknown
88
- resultStatus?: number
89
- }
90
-
91
- type HonoEnv = { Variables: HonoVariables }
92
-
93
- const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
83
+ const _env = getEnv()
94
84
 
95
85
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
96
86
 
97
87
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
98
88
 
99
- type OperationConfigLike = {
100
- before?: HonoHookHandler[]
101
- after?: HonoHookHandler[]
89
+ type OperationConfigLike<TEnv extends HonoEnvBase> = {
90
+ before?: HonoHookHandler<TEnv>[]
91
+ after?: HonoHookHandler<TEnv>[]
102
92
  shape?: Record<string, unknown>
103
93
  }
104
94
 
105
- const defaultOpConfig: OperationConfigLike = {
106
- before: [],
107
- after: [],
95
+ const defaultOpConfig = Object.freeze({
96
+ before: Object.freeze([]),
97
+ after: Object.freeze([]),
98
+ }) as unknown as OperationConfigLike<HonoEnvBase>
99
+
100
+ type HandlerContext = Context<{ Variables: HonoInternalVariables }>
101
+
102
+ function isQueryBuilderEnabled(config: RouteConfig): boolean {
103
+ if (config.queryBuilder === false) return false
104
+ if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
105
+ if (_env.NODE_ENV === 'production') return false
106
+ return true
108
107
  }
109
108
 
110
- function normalizePrefix(p: string): string {
111
- if (!p) return ''
112
- let result = p
113
- if (!result.startsWith('/')) result = '/' + result
114
- while (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1)
115
- if (result === '/') return ''
116
- return result
109
+ function getQueryBuilderConfig(config: RouteConfig) {
110
+ if (config.queryBuilder === false) return null
111
+ if (typeof config.queryBuilder === 'object') return config.queryBuilder
112
+ return {}
117
113
  }
118
114
 
119
- async function safeParseBody(c: Context<HonoEnv>): Promise<unknown> {
120
- try {
121
- return await c.req.json()
122
- } catch {
123
- throw new HttpError(400, 'Invalid JSON in request body')
115
+ async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
116
+ const raw = c.req.query() as Record<string, unknown>
117
+ if (raw && Object.keys(raw).length > 0) {
118
+ c.set('parsedQuery', parseQueryParams(raw) as Record<string, unknown>)
124
119
  }
125
120
  }
126
121
 
127
- export function ${routerFunctionName}<TCtx = unknown>(
128
- config: ${modelName}RouteConfig<TCtx> = {},
129
- ): Hono<HonoEnv> {
130
- const app = new Hono<HonoEnv>()
131
-
132
- const customPrefix = normalizePrefix(config.customUrlPrefix || '')
133
- const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
134
- const basePath = customPrefix + modelPrefix
135
-
136
- const openApiDisabled = config.disableOpenApi === true
137
- || (config.disableOpenApi !== false && (
138
- _env.DISABLE_OPENAPI === 'true'
139
- || _env.NODE_ENV === 'production'
140
- ))
141
-
142
- const postReadsEnabled = !config.disablePostReads
143
-
144
- app.onError((err, c) => {
145
- if (err instanceof HTTPException) {
146
- return c.json({ message: err.message }, err.status)
147
- }
148
- const httpError = mapError(err)
149
- return c.json({ message: httpError.message }, httpError.status as Parameters<typeof c.json>[1])
150
- })
151
-
152
- const parseQueryMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
153
- const raw = c.req.query()
154
- if (raw && Object.keys(raw).length > 0) {
155
- c.set(
156
- 'parsedQuery',
157
- parseQueryParams(raw as Record<string, unknown>) as Record<string, unknown>,
158
- )
159
- }
160
- await next()
122
+ async function parseBodyAsQueryMiddleware(c: HandlerContext): Promise<void> {
123
+ let body: unknown
124
+ try {
125
+ body = await c.req.json()
126
+ } catch {
127
+ throw new HTTPException(400, { message: 'Request body must be a JSON object' })
161
128
  }
162
-
163
- const parseBodyAsQueryMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
164
- const body = await safeParseBody(c)
165
- if (!body || typeof body !== 'object' || Array.isArray(body)) {
166
- throw new HttpError(400, 'Request body must be a JSON object')
167
- }
168
- c.set('parsedQuery', sanitizeKeys(body as Record<string, unknown>))
169
- await next()
129
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
130
+ throw new HTTPException(400, { message: 'Request body must be a JSON object' })
170
131
  }
132
+ c.set('parsedQuery', sanitizeKeys(body as Record<string, unknown>))
133
+ }
171
134
 
172
- const parseBodyMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
173
- const body = await safeParseBody(c)
174
- c.set('body', body)
175
- await next()
135
+ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
136
+ let body: unknown
137
+ try {
138
+ body = await c.req.json()
139
+ } catch {
140
+ throw new HTTPException(400, { message: 'Request body must be a JSON object' })
141
+ }
142
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
143
+ throw new HTTPException(400, { message: 'Request body must be a JSON object' })
176
144
  }
145
+ c.set('body', sanitizeKeys(body as Record<string, unknown>))
146
+ }
177
147
 
178
- const setContextMw = (opConfig: OperationConfigLike) => async (c: Context<HonoEnv>, next: Next): Promise<void> => {
148
+ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
149
+ config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>,
150
+ opConfig: OperationConfigLike<TEnv>,
151
+ ) {
152
+ return (c: Context<GeneratedHonoEnv<TEnv>>): void => {
179
153
  const paginationConfig = (config as { pagination?: OperationContext['paginationConfig'] }).pagination
180
154
  if (paginationConfig) {
181
155
  c.set('routeConfig', { pagination: paginationConfig })
182
156
  }
157
+ const headerName = config.guard?.variantHeader || 'x-api-variant'
158
+ const headerValue = c.req.header(headerName)
159
+ const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
160
+ if (caller) c.set('guardCaller', caller)
183
161
  if (opConfig.shape) {
184
162
  c.set('guardShape', opConfig.shape)
185
- const headerName = config.guard?.variantHeader || 'x-api-variant'
186
- const caller = config.guard?.resolveVariant?.(c as Context)
187
- ?? c.req.header(headerName)
188
- ?? undefined
189
- if (caller) {
190
- c.set('guardCaller', caller)
191
- }
192
163
  }
193
- await next()
194
164
  }
165
+ }
195
166
 
196
- const sendResultMw = async (c: Context<HonoEnv>, _next: Next): Promise<Response> => {
197
- const data = c.get('resultData')
198
- const status = c.get('resultStatus') ?? 200
199
- if (data === undefined) {
200
- return c.json({ message: 'No data set by handler' }, 500)
167
+ async function runHooks<TEnv extends HonoEnvBase>(
168
+ hooks: HonoHookHandler<TEnv>[],
169
+ c: Context<GeneratedHonoEnv<TEnv>>,
170
+ ): Promise<Response | undefined> {
171
+ for (const hook of hooks) {
172
+ let advanced = false
173
+ const next: Next = async () => {
174
+ advanced = true
175
+ }
176
+ const result = await hook(c, next)
177
+ if (result instanceof Response) return result
178
+ if (!advanced) {
179
+ if (_env.NODE_ENV !== 'production') {
180
+ console.warn(
181
+ '[hono-router] Hook returned without calling next() or returning a Response. ' +
182
+ 'Use \`return c.json(...)\` to short-circuit, or \`await next()\` to continue.',
183
+ )
184
+ }
185
+ return c.body(null) ?? undefined
201
186
  }
202
- return c.json(
203
- transformResult(data) as Parameters<typeof c.json>[0],
204
- status as Parameters<typeof c.json>[1],
205
- )
206
187
  }
188
+ return undefined
189
+ }
207
190
 
208
- const wrap = (fn: (c: Context<HonoEnv>) => Promise<void>) =>
209
- async (c: Context<HonoEnv>, next: Next): Promise<void> => {
210
- await fn(c)
211
- await next()
212
- }
191
+ function sendResult(c: HandlerContext): Response {
192
+ const data = c.get('resultData')
193
+ const status = (c.get('resultStatus') as number | undefined) ?? 200
194
+ if (data === undefined) {
195
+ throw new HTTPException(500, { message: 'No data set by handler' })
196
+ }
197
+ return c.json(transformResult(data) as Record<string, unknown>, status as ContentfulStatusCode)
198
+ }
213
199
 
214
- if (!openApiDisabled) {
215
- const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
216
- const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
200
+ function sendError(c: HandlerContext, error: unknown): Response {
201
+ const httpError = mapError(error)
202
+ return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
203
+ }
204
+
205
+ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
206
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
207
+
208
+ const customPrefix = normalizePrefix(config.customUrlPrefix || '')
209
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
210
+ const basePath = customPrefix + modelPrefix
211
+
212
+ const openApiDisabled = config.disableOpenApi === true
213
+ || (config.disableOpenApi !== false && (
214
+ _env.DISABLE_OPENAPI === 'true'
215
+ || _env.NODE_ENV === 'production'
216
+ ))
217
217
 
218
- app.get(openapiJsonPath, (c) => {
219
- const spec = buildModelOpenApi(
218
+ const postReadsEnabled = !config.disablePostReads
219
+
220
+ const openApiJsonSpec = openApiDisabled
221
+ ? null
222
+ : buildModelOpenApi(
220
223
  '${modelName}',
221
224
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
222
225
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
223
- config,
226
+ config as RouteConfig,
224
227
  { format: 'json' },
225
228
  )
226
- return c.json(spec as Parameters<typeof c.json>[0])
227
- })
228
-
229
- app.get(openapiYamlPath, (c) => {
230
- const yaml = buildModelOpenApi(
229
+ const openApiYamlSpec = openApiDisabled
230
+ ? null
231
+ : buildModelOpenApi(
231
232
  '${modelName}',
232
233
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
233
234
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
234
- config,
235
+ config as RouteConfig,
235
236
  { format: 'yaml' },
236
- ) as string
237
- return c.body(yaml, 200, { 'Content-Type': 'application/yaml' })
237
+ )
238
+
239
+ if (isQueryBuilderEnabled(config as RouteConfig)) {
240
+ const qbConfig = getQueryBuilderConfig(config as RouteConfig)
241
+ if (qbConfig) {
242
+ try {
243
+ startQueryBuilder(qbConfig)
244
+ } catch (err) {
245
+ if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
246
+ }
247
+ }
248
+ }
249
+
250
+ app.onError((err, c) => {
251
+ if (err instanceof HTTPException) {
252
+ return c.json({ message: err.message }, err.status as ContentfulStatusCode)
253
+ }
254
+ return sendError(c as HandlerContext, err)
255
+ })
256
+
257
+ if (!openApiDisabled) {
258
+ const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
259
+ const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
260
+ app.get(openapiJsonPath, (c) => c.json(openApiJsonSpec as Record<string, unknown>))
261
+ app.get(openapiYamlPath, (c) => {
262
+ c.header('Content-Type', 'application/yaml')
263
+ return c.body(openApiYamlSpec as string)
238
264
  })
239
265
  }
240
266
 
241
- if (config.enableAll || config.findFirst) {
242
- const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
243
- const { before = [], after = [] } = opConfig
244
- const path = basePath ? \`\${basePath}/first\` : '/first'
245
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirst), ...after, sendResultMw)
246
- if (postReadsEnabled) {
247
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirst), ...after, sendResultMw)
267
+ const handleRead = (
268
+ opConfig: OperationConfigLike<TEnv>,
269
+ handlerFn: (c: HandlerContext) => Promise<void>,
270
+ parseFn: (c: HandlerContext) => Promise<void>,
271
+ ) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
272
+ try {
273
+ await parseFn(c)
274
+ makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
275
+ const { before = [], after = [] } = opConfig
276
+ const beforeResp = await runHooks<TEnv>(before, c)
277
+ if (beforeResp) return beforeResp
278
+ await handlerFn(c)
279
+ const afterResp = await runHooks<TEnv>(after, c)
280
+ if (afterResp) return afterResp
281
+ return sendResult(c)
282
+ } catch (error: unknown) {
283
+ return sendError(c, error)
248
284
  }
249
285
  }
250
286
 
251
- if (config.enableAll || config.findFirstOrThrow) {
252
- const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
253
- const { before = [], after = [] } = opConfig
254
- const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
255
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirstOrThrow), ...after, sendResultMw)
256
- if (postReadsEnabled) {
257
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirstOrThrow), ...after, sendResultMw)
287
+ const handleWrite = (
288
+ opConfig: OperationConfigLike<TEnv>,
289
+ handlerFn: (c: HandlerContext) => Promise<void>,
290
+ ) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
291
+ try {
292
+ await parseWriteBodyMiddleware(c)
293
+ makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
294
+ const { before = [], after = [] } = opConfig
295
+ const beforeResp = await runHooks<TEnv>(before, c)
296
+ if (beforeResp) return beforeResp
297
+ await handlerFn(c)
298
+ const afterResp = await runHooks<TEnv>(after, c)
299
+ if (afterResp) return afterResp
300
+ return sendResult(c)
301
+ } catch (error: unknown) {
302
+ return sendError(c, error)
258
303
  }
259
304
  }
260
305
 
306
+ const opFor = <K extends keyof ${modelName}RouteConfig<TCtx, TPrisma, TEnv>>(key: K): OperationConfigLike<TEnv> => {
307
+ return (config[key] as unknown as OperationConfigLike<TEnv> | undefined)
308
+ ?? (defaultOpConfig as OperationConfigLike<TEnv>)
309
+ }
310
+
311
+ if (config.enableAll || config.findFirst) {
312
+ const opConfig = opFor('findFirst')
313
+ const path = basePath ? \`\${basePath}/first\` : '/first'
314
+ app.get(path, handleRead(opConfig, ${modelName}FindFirst, parseQueryMiddleware))
315
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirst, parseBodyAsQueryMiddleware))
316
+ }
317
+ if (config.enableAll || config.findFirstOrThrow) {
318
+ const opConfig = opFor('findFirstOrThrow')
319
+ const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
320
+ app.get(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseQueryMiddleware))
321
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryMiddleware))
322
+ }
261
323
  if (config.enableAll || config.findManyPaginated) {
262
- const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
263
- const { before = [], after = [] } = opConfig
324
+ const opConfig = opFor('findManyPaginated')
264
325
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
265
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindManyPaginated), ...after, sendResultMw)
266
- if (postReadsEnabled) {
267
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindManyPaginated), ...after, sendResultMw)
268
- }
326
+ app.get(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseQueryMiddleware))
327
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryMiddleware))
269
328
  }
270
-
271
329
  if (config.enableAll || config.aggregate) {
272
- const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
273
- const { before = [], after = [] } = opConfig
330
+ const opConfig = opFor('aggregate')
274
331
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
275
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Aggregate), ...after, sendResultMw)
276
- if (postReadsEnabled) {
277
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Aggregate), ...after, sendResultMw)
278
- }
332
+ app.get(path, handleRead(opConfig, ${modelName}Aggregate, parseQueryMiddleware))
333
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Aggregate, parseBodyAsQueryMiddleware))
279
334
  }
280
-
281
335
  if (config.enableAll || config.count) {
282
- const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
283
- const { before = [], after = [] } = opConfig
336
+ const opConfig = opFor('count')
284
337
  const path = basePath ? \`\${basePath}/count\` : '/count'
285
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Count), ...after, sendResultMw)
286
- if (postReadsEnabled) {
287
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Count), ...after, sendResultMw)
288
- }
338
+ app.get(path, handleRead(opConfig, ${modelName}Count, parseQueryMiddleware))
339
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Count, parseBodyAsQueryMiddleware))
289
340
  }
290
-
291
341
  if (config.enableAll || config.groupBy) {
292
- const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
293
- const { before = [], after = [] } = opConfig
342
+ const opConfig = opFor('groupBy')
294
343
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
295
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}GroupBy), ...after, sendResultMw)
296
- if (postReadsEnabled) {
297
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}GroupBy), ...after, sendResultMw)
298
- }
344
+ app.get(path, handleRead(opConfig, ${modelName}GroupBy, parseQueryMiddleware))
345
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}GroupBy, parseBodyAsQueryMiddleware))
299
346
  }
300
-
301
347
  if (config.enableAll || config.findUniqueOrThrow) {
302
- const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
303
- const { before = [], after = [] } = opConfig
348
+ const opConfig = opFor('findUniqueOrThrow')
304
349
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
305
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUniqueOrThrow), ...after, sendResultMw)
306
- if (postReadsEnabled) {
307
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUniqueOrThrow), ...after, sendResultMw)
308
- }
350
+ app.get(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseQueryMiddleware))
351
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryMiddleware))
309
352
  }
310
-
311
353
  if (config.enableAll || config.findUnique) {
312
- const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
313
- const { before = [], after = [] } = opConfig
354
+ const opConfig = opFor('findUnique')
314
355
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
315
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUnique), ...after, sendResultMw)
316
- if (postReadsEnabled) {
317
- app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUnique), ...after, sendResultMw)
318
- }
356
+ app.get(path, handleRead(opConfig, ${modelName}FindUnique, parseQueryMiddleware))
357
+ if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUnique, parseBodyAsQueryMiddleware))
319
358
  }
320
-
321
359
  if (config.enableAll || config.findMany) {
322
- const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
323
- const { before = [], after = [] } = opConfig
360
+ const opConfig = opFor('findMany')
324
361
  const path = basePath || '/'
325
- app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindMany), ...after, sendResultMw)
362
+ app.get(path, handleRead(opConfig, ${modelName}FindMany, parseQueryMiddleware))
326
363
  if (postReadsEnabled) {
327
364
  const postPath = basePath ? \`\${basePath}/read\` : '/read'
328
- app.post(postPath, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindMany), ...after, sendResultMw)
365
+ app.post(postPath, handleRead(opConfig, ${modelName}FindMany, parseBodyAsQueryMiddleware))
329
366
  }
330
367
  }
331
368
 
332
369
  if (config.enableAll || config.createManyAndReturn) {
333
- const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
334
- const { before = [], after = [] } = opConfig
370
+ const opConfig = opFor('createManyAndReturn')
335
371
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
336
- app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}CreateManyAndReturn), ...after, sendResultMw)
372
+ app.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
337
373
  }
338
-
339
374
  if (config.enableAll || config.createMany) {
340
- const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
341
- const { before = [], after = [] } = opConfig
375
+ const opConfig = opFor('createMany')
342
376
  const path = basePath ? \`\${basePath}/many\` : '/many'
343
- app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}CreateMany), ...after, sendResultMw)
377
+ app.post(path, handleWrite(opConfig, ${modelName}CreateMany))
344
378
  }
345
-
346
379
  if (config.enableAll || config.create) {
347
- const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
348
- const { before = [], after = [] } = opConfig
380
+ const opConfig = opFor('create')
349
381
  const path = basePath || '/'
350
- app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Create), ...after, sendResultMw)
382
+ app.post(path, handleWrite(opConfig, ${modelName}Create))
351
383
  }
352
-
353
384
  if (config.enableAll || config.updateManyAndReturn) {
354
- const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
355
- const { before = [], after = [] } = opConfig
385
+ const opConfig = opFor('updateManyAndReturn')
356
386
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
357
- app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}UpdateManyAndReturn), ...after, sendResultMw)
387
+ app.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
358
388
  }
359
-
360
389
  if (config.enableAll || config.updateMany) {
361
- const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
362
- const { before = [], after = [] } = opConfig
390
+ const opConfig = opFor('updateMany')
363
391
  const path = basePath ? \`\${basePath}/many\` : '/many'
364
- app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}UpdateMany), ...after, sendResultMw)
392
+ app.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
365
393
  }
366
-
367
394
  if (config.enableAll || config.update) {
368
- const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
369
- const { before = [], after = [] } = opConfig
395
+ const opConfig = opFor('update')
370
396
  const path = basePath || '/'
371
- app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Update), ...after, sendResultMw)
397
+ app.put(path, handleWrite(opConfig, ${modelName}Update))
372
398
  }
373
-
374
399
  if (config.enableAll || config.upsert) {
375
- const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
376
- const { before = [], after = [] } = opConfig
400
+ const opConfig = opFor('upsert')
377
401
  const path = basePath || '/'
378
- app.patch(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Upsert), ...after, sendResultMw)
402
+ app.patch(path, handleWrite(opConfig, ${modelName}Upsert))
379
403
  }
380
-
381
404
  if (config.enableAll || config.deleteMany) {
382
- const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
383
- const { before = [], after = [] } = opConfig
405
+ const opConfig = opFor('deleteMany')
384
406
  const path = basePath ? \`\${basePath}/many\` : '/many'
385
- app.delete(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}DeleteMany), ...after, sendResultMw)
407
+ app.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
386
408
  }
387
-
388
409
  if (config.enableAll || config.delete) {
389
- const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
390
- const { before = [], after = [] } = opConfig
410
+ const opConfig = opFor('delete')
391
411
  const path = basePath || '/'
392
- app.delete(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Delete), ...after, sendResultMw)
412
+ app.delete(path, handleWrite(opConfig, ${modelName}Delete))
393
413
  }
394
414
 
395
415
  return app