prisma-generator-express 1.54.0 → 1.56.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 (44) hide show
  1. package/README.md +186 -11
  2. package/dist/constants.d.ts +2 -0
  3. package/dist/generators/generateFastifyHandler.js +3 -1
  4. package/dist/generators/generateFastifyHandler.js.map +1 -1
  5. package/dist/generators/generateHonoHandler.js +3 -1
  6. package/dist/generators/generateHonoHandler.js.map +1 -1
  7. package/dist/generators/generateOperationCore.d.ts +3 -0
  8. package/dist/generators/generateOperationCore.js +68 -39
  9. package/dist/generators/generateOperationCore.js.map +1 -1
  10. package/dist/generators/generateRouteConfigType.js +4 -1
  11. package/dist/generators/generateRouteConfigType.js.map +1 -1
  12. package/dist/generators/generateRouter.d.ts +4 -1
  13. package/dist/generators/generateRouter.js +20 -7
  14. package/dist/generators/generateRouter.js.map +1 -1
  15. package/dist/generators/generateRouterFastify.d.ts +4 -1
  16. package/dist/generators/generateRouterFastify.js +23 -9
  17. package/dist/generators/generateRouterFastify.js.map +1 -1
  18. package/dist/generators/generateRouterHono.d.ts +4 -1
  19. package/dist/generators/generateRouterHono.js +29 -16
  20. package/dist/generators/generateRouterHono.js.map +1 -1
  21. package/dist/generators/generateUnifiedScalarUI.d.ts +2 -1
  22. package/dist/generators/generateUnifiedScalarUI.js +13 -10
  23. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  24. package/dist/index.js +42 -3
  25. package/dist/index.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/constants.ts +5 -1
  28. package/src/copy/autoIncludeRuntime.ts +60 -35
  29. package/src/copy/buildModelOpenApi.ts +144 -23
  30. package/src/copy/docsRenderer.ts +125 -98
  31. package/src/copy/operationRuntime.ts +94 -9
  32. package/src/copy/routeConfig.express.ts +8 -0
  33. package/src/copy/routeConfig.fastify.ts +8 -0
  34. package/src/copy/routeConfig.hono.ts +9 -1
  35. package/src/copy/routeConfig.ts +23 -5
  36. package/src/generators/generateFastifyHandler.ts +3 -1
  37. package/src/generators/generateHonoHandler.ts +3 -1
  38. package/src/generators/generateOperationCore.ts +84 -39
  39. package/src/generators/generateRouteConfigType.ts +5 -2
  40. package/src/generators/generateRouter.ts +24 -6
  41. package/src/generators/generateRouterFastify.ts +27 -8
  42. package/src/generators/generateRouterHono.ts +33 -15
  43. package/src/generators/generateUnifiedScalarUI.ts +15 -11
  44. package/src/index.ts +49 -7
@@ -77,7 +77,7 @@ export async function ${exportName}(
77
77
 
78
78
  return `import type { FastifyRequest, FastifyReply } from 'fastify'
79
79
  import * as core from './${modelName}Core${ext}'
80
- import type { OperationContext } from '../operationRuntime${ext}'
80
+ import type { OperationContext, FindManyPaginatedMode } from '../operationRuntime${ext}'
81
81
 
82
82
  type FastifyExtended = FastifyRequest & {
83
83
  prisma?: unknown
@@ -87,6 +87,7 @@ type FastifyExtended = FastifyRequest & {
87
87
  routeConfig?: { pagination?: OperationContext['paginationConfig'] }
88
88
  guardShape?: Record<string, unknown>
89
89
  guardCaller?: string
90
+ findManyPaginatedMode?: FindManyPaginatedMode
90
91
  resultData?: unknown
91
92
  resultStatus?: number
92
93
  }
@@ -102,6 +103,7 @@ function buildContext(request: FastifyRequest): OperationContext {
102
103
  guardShape: req.guardShape,
103
104
  guardCaller: req.guardCaller,
104
105
  paginationConfig: req.routeConfig?.pagination,
106
+ findManyPaginatedMode: req.findManyPaginatedMode,
105
107
  }
106
108
  }
107
109
  ${readHandlers}
@@ -70,7 +70,7 @@ export async function ${exportName}(c: Context<HonoEnv>): Promise<void> {
70
70
 
71
71
  return `import type { Context } from 'hono'
72
72
  import * as core from './${modelName}Core${ext}'
73
- import type { OperationContext } from '../operationRuntime${ext}'
73
+ import type { OperationContext, FindManyPaginatedMode } from '../operationRuntime${ext}'
74
74
 
75
75
  type HonoVariables = {
76
76
  prisma: unknown
@@ -81,6 +81,7 @@ type HonoVariables = {
81
81
  routeConfig?: { pagination?: OperationContext['paginationConfig'] }
82
82
  guardShape?: Record<string, unknown>
83
83
  guardCaller?: string
84
+ findManyPaginatedMode?: FindManyPaginatedMode
84
85
  resultData?: unknown
85
86
  resultStatus?: number
86
87
  }
@@ -97,6 +98,7 @@ function buildContext(c: Context<HonoEnv>): OperationContext {
97
98
  guardShape: c.get('guardShape'),
98
99
  guardCaller: c.get('guardCaller'),
99
100
  paginationConfig: c.get('routeConfig')?.pagination,
101
+ findManyPaginatedMode: c.get('findManyPaginatedMode'),
100
102
  }
101
103
  }
102
104
  ${readHandlers}
@@ -1,16 +1,82 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
2
  import { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
+ import { WriteStrategy, FindManyPaginatedMode } from '../constants'
4
5
 
5
6
  export interface ModelCoreOptions {
6
7
  model: DMMF.Model
7
8
  importStyle: ImportStyle
9
+ writeStrategy: WriteStrategy
10
+ findManyPaginatedMode: FindManyPaginatedMode
11
+ }
12
+
13
+ type WriteOpDecision =
14
+ | { mode: 'normal'; method: string }
15
+ | { mode: 'redirect'; method: string }
16
+ | { mode: 'throw' }
17
+
18
+ function decideWriteOp(
19
+ name: string,
20
+ defaultMethod: string,
21
+ strategy: WriteStrategy,
22
+ ): WriteOpDecision {
23
+ if (strategy === 'regular') {
24
+ return { mode: 'normal', method: defaultMethod }
25
+ }
26
+ if (strategy === 'throwOnNonReturning') {
27
+ if (name === 'createMany' || name === 'updateMany') {
28
+ return { mode: 'throw' }
29
+ }
30
+ return { mode: 'normal', method: defaultMethod }
31
+ }
32
+ if (name === 'createMany') return { mode: 'redirect', method: 'createManyAndReturn' }
33
+ if (name === 'updateMany') return { mode: 'redirect', method: 'updateManyAndReturn' }
34
+ return { mode: 'normal', method: defaultMethod }
35
+ }
36
+
37
+ function renderPaginatedBody(modelNameLower: string, mode: FindManyPaginatedMode): string {
38
+ if (mode === 'transaction') {
39
+ return `
40
+ const txClient = extended as { $transaction?: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T> }
41
+ if (typeof txClient.$transaction !== 'function') {
42
+ throw new HttpError(500, 'findManyPaginatedMode="transaction" requires transaction support on the Prisma client')
43
+ }
44
+
45
+ const txResult = await txClient.$transaction(async (tx: unknown) => {
46
+ const txDelegate = getDelegate(tx, '${modelNameLower}')
47
+ if (shape) assertGuard(txDelegate)
48
+ const findP = shape
49
+ ? (txDelegate.guard as NonNullable<typeof txDelegate.guard>)(shape, caller).findMany(query)
50
+ : txDelegate.findMany(query)
51
+ const countP = countForPagination(
52
+ txDelegate, query, shape, caller, distinctCountLimit, countSource, tx,
53
+ )
54
+ const [data, count] = await Promise.all([findP, countP])
55
+ return { data, count }
56
+ })
57
+ items = txResult.data as unknown[]
58
+ total = txResult.count`
59
+ }
60
+
61
+ return `
62
+ const delegate = getDelegate(extended, '${modelNameLower}')
63
+ if (shape) assertGuard(delegate)
64
+ const [data, count] = await Promise.all([
65
+ shape
66
+ ? (delegate.guard as NonNullable<typeof delegate.guard>)(shape, caller).findMany(query)
67
+ : delegate.findMany(query),
68
+ countForPagination(delegate, query, shape, caller, distinctCountLimit, countSource, extended),
69
+ ])
70
+ items = data as unknown[]
71
+ total = count`
8
72
  }
9
73
 
10
74
  export function generateModelCore(options: ModelCoreOptions): string {
11
75
  const ext = importExt(options.importStyle)
12
76
  const modelName = options.model.name
13
77
  const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
78
+ const writeStrategy = options.writeStrategy
79
+ const paginatedBody = renderPaginatedBody(modelNameLower, options.findManyPaginatedMode)
14
80
 
15
81
  const standardReadOps = [
16
82
  'findFirst', 'findUnique', 'findUniqueOrThrow', 'findFirstOrThrow',
@@ -44,7 +110,20 @@ export async function ${op}(ctx: OperationContext): Promise<unknown> {
44
110
  ]
45
111
 
46
112
  const writeHandlers = writeOps.map((op) => {
47
- const validationLines = op.requiredFields.map((field) => ` requireBodyField(body, '${field}')`).join('\n')
113
+ const decision = decideWriteOp(op.name, op.method, writeStrategy)
114
+
115
+ if (decision.mode === 'throw') {
116
+ return `
117
+ export async function ${op.name}(_ctx: OperationContext): Promise<unknown> {
118
+ throw new HttpError(501, '${op.name} is disabled by writeStrategy="${writeStrategy}"')
119
+ }`
120
+ }
121
+
122
+ const method = decision.method
123
+ const validationLines = op.requiredFields
124
+ .map((field) => ` requireBodyField(body, '${field}')`)
125
+ .join('\n')
126
+
48
127
  return `
49
128
  export async function ${op.name}(ctx: OperationContext): Promise<unknown> {
50
129
  const body = validateBody(ctx.body)
@@ -53,9 +132,9 @@ ${validationLines}
53
132
  const delegate = getDelegate(extended, '${modelNameLower}')
54
133
  if (ctx.guardShape) {
55
134
  assertGuard(delegate)
56
- return delegate.guard(ctx.guardShape, ctx.guardCaller).${op.method}(body)
135
+ return delegate.guard(ctx.guardShape, ctx.guardCaller).${method}(body)
57
136
  }
58
- return delegate.${op.method}(body)
137
+ return delegate.${method}(body)
59
138
  }`
60
139
  }).join('\n')
61
140
 
@@ -96,45 +175,11 @@ export async function findManyPaginated(
96
175
  const shape = ctx.guardShape
97
176
  const caller = ctx.guardCaller
98
177
  const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
99
- const delegate = getDelegate(extended, '${modelNameLower}')
100
-
101
- if (shape) assertGuard(delegate)
178
+ const countSource = ctx.paginationConfig?.countSource
102
179
 
103
180
  let items: unknown[]
104
181
  let total: number
105
-
106
- const txClient = extended as { $transaction?: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T> }
107
-
108
- if (shape || typeof txClient.$transaction !== 'function') {
109
- const [data, count] = await Promise.all([
110
- shape
111
- ? (delegate.guard as NonNullable<typeof delegate.guard>)(shape, caller).findMany(query)
112
- : delegate.findMany(query),
113
- countForPagination(delegate, query, shape, caller, distinctCountLimit),
114
- ])
115
- items = data as unknown[]
116
- total = count
117
- } else {
118
- try {
119
- const txResult = await txClient.$transaction(async (tx: unknown) => {
120
- const txDelegate = getDelegate(tx, '${modelNameLower}')
121
- const d = await txDelegate.findMany(query)
122
- const t = await countForPagination(txDelegate, query, undefined, undefined, distinctCountLimit)
123
- return { d, t }
124
- })
125
- items = txResult.d as unknown[]
126
- total = txResult.t
127
- } catch (txError: unknown) {
128
- const txe = txError as { message?: string; code?: string }
129
- if (txe?.code === 'P2028') {
130
- console.warn('[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic')
131
- items = (await delegate.findMany(query)) as unknown[]
132
- total = await countForPagination(delegate, query, undefined, undefined, distinctCountLimit)
133
- } else {
134
- throw txError
135
- }
136
- }
137
- }
182
+ ${paginatedBody}
138
183
 
139
184
  const skip = (typeof query.skip === 'number' ? query.skip : 0)
140
185
  const takeRaw = (typeof query.take === 'number' ? query.take : items.length)
@@ -39,7 +39,6 @@ const ROUTER_OP_TO_SHAPE_OP: Record<RouterOperation, string> = {
39
39
  deleteMany: 'deleteMany',
40
40
  }
41
41
 
42
-
43
42
  function requestTypeFor(target: Target): string {
44
43
  if (target === 'fastify') return `import('fastify').FastifyRequest`
45
44
  if (target === 'hono') return `import('hono').Context<TEnv>`
@@ -85,13 +84,15 @@ export function generateRouteConfigType(
85
84
  const requestType = requestTypeFor(target)
86
85
 
87
86
  const progressiveTypeImport = supportsProgressive
88
- ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n\n`
87
+ ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n`
89
88
  : ''
90
89
 
91
90
  if (!guardShapesImport) {
92
91
  return progressiveTypeImport + `export type ${m}RouteConfig${generics} = ${baseConfig}\n`
93
92
  }
94
93
 
94
+ const paginationImport = `import type { PaginationConfig } from '../routeConfig.target${ext}'\n`
95
+
95
96
  const shapeOps = Object.values(ROUTER_OP_TO_SHAPE_OP).filter((v, i, a) => a.indexOf(v) === i)
96
97
  const opShapeImports = shapeOps.map((op) => `${m}${capitalize(op)}ShapeInput`).join(',\n ')
97
98
 
@@ -103,6 +104,7 @@ export function generateRouteConfigType(
103
104
  ` before?: ${hookRef}[]`,
104
105
  ` after?: ${hookRef}[]`,
105
106
  ` shape?: ${m}${c}ShapeInput<TCtx>`,
107
+ ` pagination?: Partial<PaginationConfig>`,
106
108
  ]
107
109
  if (isRead && supportsProgressive) {
108
110
  lines.push(` progressive?: Record<string, ProgressiveVariantConfig>`)
@@ -115,6 +117,7 @@ export function generateRouteConfigType(
115
117
 
116
118
  return (
117
119
  progressiveTypeImport +
120
+ paginationImport +
118
121
  `import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
119
122
  `export type ${m}RouteConfig${generics} = Omit<\n` +
120
123
  ` ${baseConfig},\n` +
@@ -2,17 +2,22 @@ import { DMMF } from '@prisma/generator-helper'
2
2
  import { generateRouteConfigType } from './generateRouteConfigType'
3
3
  import { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
+ import { WriteStrategy, FindManyPaginatedMode } from '../constants'
5
6
 
6
7
  export function generateRouterFunction({
7
8
  model,
8
9
  enums,
9
10
  guardShapesImport,
10
11
  importStyle,
12
+ writeStrategy,
13
+ findManyPaginatedMode,
11
14
  }: {
12
15
  model: DMMF.Model
13
16
  enums: DMMF.DatamodelEnum[]
14
17
  guardShapesImport: string | null
15
18
  importStyle: ImportStyle
19
+ writeStrategy: WriteStrategy
20
+ findManyPaginatedMode: FindManyPaginatedMode
16
21
  }): string {
17
22
  const ext = importExt(importStyle)
18
23
  const modelName = model.name
@@ -67,7 +72,13 @@ import {
67
72
  ${modelName}GroupBy,
68
73
  } from './${modelName}Handlers${ext}'
69
74
  import * as core from './${modelName}Core${ext}'
70
- import type { RouteConfig, QueryBuilderConfig } from '../routeConfig.target${ext}'
75
+ import type {
76
+ RouteConfig,
77
+ QueryBuilderConfig,
78
+ WriteStrategy,
79
+ FindManyPaginatedMode,
80
+ PaginationConfig,
81
+ } from '../routeConfig.target${ext}'
71
82
  import { parseQueryParams } from '../parseQueryParams${ext}'
72
83
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
73
84
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
@@ -79,6 +90,7 @@ import {
79
90
  runSingleResultSSE,
80
91
  emitTerminalSSEError,
81
92
  removeReqCloseListener,
93
+ mergePaginationConfig,
82
94
  mapError,
83
95
  HttpError,
84
96
  } from '../operationRuntime${ext}'
@@ -88,6 +100,9 @@ import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
88
100
  ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
89
101
  const _env = getEnv()
90
102
 
103
+ const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
104
+ const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
105
+
91
106
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
92
107
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
93
108
 
@@ -95,6 +110,7 @@ type OperationConfigLike = {
95
110
  before?: RequestHandler[]
96
111
  after?: RequestHandler[]
97
112
  shape?: Record<string, unknown>
113
+ pagination?: Partial<PaginationConfig>
98
114
  progressive?: Record<string, ProgressiveVariantConfig>
99
115
  progressiveStages?: Record<string, ProgressiveStage<unknown>>
100
116
  }
@@ -107,7 +123,7 @@ type ExtendedRequest = Request & {
107
123
 
108
124
  type LocalsBag = {
109
125
  parsedQuery?: Record<string, unknown>
110
- routeConfig?: { pagination?: OperationContext['paginationConfig'] }
126
+ routeConfig?: { pagination?: PaginationConfig }
111
127
  guardShape?: Record<string, unknown>
112
128
  guardCaller?: string
113
129
  data?: unknown
@@ -154,7 +170,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
154
170
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
155
171
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
156
172
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
157
- { format: 'json' },
173
+ { format: 'json', writeStrategy: WRITE_STRATEGY },
158
174
  )
159
175
  const openApiYamlSpec = openApiDisabled
160
176
  ? null
@@ -163,7 +179,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
163
179
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
164
180
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
165
181
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
166
- { format: 'yaml' },
182
+ { format: 'yaml', writeStrategy: WRITE_STRATEGY },
167
183
  )
168
184
 
169
185
  const qbEnabled = isQueryBuilderEnabled(config)
@@ -190,6 +206,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
190
206
  guardShape: locals.guardShape,
191
207
  guardCaller: locals.guardCaller,
192
208
  paginationConfig: locals.routeConfig?.pagination,
209
+ findManyPaginatedMode: FIND_MANY_PAGINATED_MODE,
193
210
  }
194
211
  }
195
212
 
@@ -213,8 +230,9 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
213
230
  const setShape = (opConfig: OperationConfigLike): RequestHandler => {
214
231
  return (req, res, next) => {
215
232
  const locals = readLocals(res)
216
- if (config.pagination) {
217
- locals.routeConfig = { pagination: config.pagination }
233
+ const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
234
+ if (merged) {
235
+ locals.routeConfig = { pagination: merged }
218
236
  }
219
237
  const headerName = config.guard?.variantHeader || 'x-api-variant'
220
238
  const headerValue = req.get(headerName)
@@ -2,17 +2,22 @@ import { DMMF } from '@prisma/generator-helper'
2
2
  import { generateRouteConfigType } from './generateRouteConfigType'
3
3
  import { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
+ import { WriteStrategy, FindManyPaginatedMode } from '../constants'
5
6
 
6
7
  export function generateFastifyRouterFunction({
7
8
  model,
8
9
  enums,
9
10
  guardShapesImport,
10
11
  importStyle,
12
+ writeStrategy,
13
+ findManyPaginatedMode,
11
14
  }: {
12
15
  model: DMMF.Model
13
16
  enums: DMMF.DatamodelEnum[]
14
17
  guardShapesImport: string | null
15
18
  importStyle: ImportStyle
19
+ writeStrategy: WriteStrategy
20
+ findManyPaginatedMode: FindManyPaginatedMode
16
21
  }): string {
17
22
  const ext = importExt(importStyle)
18
23
  const modelName = model.name
@@ -64,15 +69,24 @@ import {
64
69
  ${modelName}Count,
65
70
  ${modelName}GroupBy,
66
71
  } from './${modelName}Handlers${ext}'
67
- import type { RouteConfig, FastifyHookHandler } from '../routeConfig.target${ext}'
72
+ import type {
73
+ RouteConfig,
74
+ FastifyHookHandler,
75
+ WriteStrategy,
76
+ FindManyPaginatedMode,
77
+ PaginationConfig,
78
+ } from '../routeConfig.target${ext}'
68
79
  import { parseQueryParams } from '../parseQueryParams${ext}'
69
80
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
70
81
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
71
- import { mapError, transformResult, HttpError, type OperationContext } from '../operationRuntime${ext}'
82
+ import { mapError, transformResult, mergePaginationConfig, HttpError, type OperationContext } from '../operationRuntime${ext}'
72
83
 
73
84
  ${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify')}
74
85
  const _env = getEnv()
75
86
 
87
+ const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
88
+ const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
89
+
76
90
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
77
91
 
78
92
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
@@ -81,6 +95,7 @@ type OperationConfigLike = {
81
95
  before?: FastifyHookHandler[]
82
96
  after?: FastifyHookHandler[]
83
97
  shape?: Record<string, unknown>
98
+ pagination?: Partial<PaginationConfig>
84
99
  }
85
100
 
86
101
  type FastifyExtended = FastifyRequest & {
@@ -88,7 +103,7 @@ type FastifyExtended = FastifyRequest & {
88
103
  postgres?: unknown
89
104
  sqlite?: unknown
90
105
  parsedQuery?: Record<string, unknown>
91
- routeConfig?: { pagination?: OperationContext['paginationConfig'] }
106
+ routeConfig?: { pagination?: PaginationConfig }
92
107
  guardShape?: Record<string, unknown>
93
108
  guardCaller?: string
94
109
  resultData?: unknown
@@ -134,9 +149,9 @@ function makeShapeHook(
134
149
  ): (request: FastifyRequest) => void {
135
150
  return (request: FastifyRequest) => {
136
151
  const fx = request as FastifyExtended
137
- const paginationConfig = (config as { pagination?: OperationContext['paginationConfig'] }).pagination
138
- if (paginationConfig) {
139
- fx.routeConfig = { pagination: paginationConfig }
152
+ const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
153
+ if (merged) {
154
+ fx.routeConfig = { pagination: merged }
140
155
  }
141
156
  const headerName = (config.guard?.variantHeader || 'x-api-variant').toLowerCase()
142
157
  const headerValue = request.headers[headerName]
@@ -203,7 +218,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
203
218
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
204
219
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
205
220
  config,
206
- { format: 'json' },
221
+ { format: 'json', writeStrategy: WRITE_STRATEGY },
207
222
  )
208
223
  const openApiYamlSpec = openApiDisabled
209
224
  ? null
@@ -212,7 +227,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
212
227
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
213
228
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
214
229
  config,
215
- { format: 'yaml' },
230
+ { format: 'yaml', writeStrategy: WRITE_STRATEGY },
216
231
  )
217
232
 
218
233
  const qbEnabled = isQueryBuilderEnabled(config)
@@ -228,6 +243,10 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
228
243
  }
229
244
  }
230
245
 
246
+ fastify.addHook('onRequest', async (request: FastifyRequest) => {
247
+ (request as FastifyExtended & { findManyPaginatedMode?: FindManyPaginatedMode }).findManyPaginatedMode = FIND_MANY_PAGINATED_MODE
248
+ })
249
+
231
250
  fastify.setErrorHandler((error: FastifyError, _request: FastifyRequest, reply: FastifyReply) => {
232
251
  const e = error as { status?: number; statusCode?: number; message?: string }
233
252
  const status = e.status ?? e.statusCode ?? 500
@@ -2,17 +2,22 @@ import { DMMF } from '@prisma/generator-helper'
2
2
  import { generateRouteConfigType } from './generateRouteConfigType'
3
3
  import { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
+ import { WriteStrategy, FindManyPaginatedMode } from '../constants'
5
6
 
6
7
  export function generateHonoRouterFunction({
7
8
  model,
8
9
  enums,
9
10
  guardShapesImport,
10
11
  importStyle,
12
+ writeStrategy,
13
+ findManyPaginatedMode,
11
14
  }: {
12
15
  model: DMMF.Model
13
16
  enums: DMMF.DatamodelEnum[]
14
17
  guardShapesImport: string | null
15
18
  importStyle: ImportStyle
19
+ writeStrategy: WriteStrategy
20
+ findManyPaginatedMode: FindManyPaginatedMode
16
21
  }): string {
17
22
  const ext = importExt(importStyle)
18
23
  const modelName = model.name
@@ -73,15 +78,26 @@ import type {
73
78
  HonoEnvBase,
74
79
  HonoInternalVariables,
75
80
  GeneratedHonoEnv,
81
+ WriteStrategy,
82
+ FindManyPaginatedMode,
83
+ PaginationConfig,
76
84
  } from '../routeConfig.target${ext}'
77
85
  import { parseQueryParams } from '../parseQueryParams${ext}'
78
86
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
79
87
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
80
- import { mapError, transformResult, type OperationContext } from '../operationRuntime${ext}'
88
+ import {
89
+ mapError,
90
+ transformResult,
91
+ mergePaginationConfig,
92
+ type OperationContext,
93
+ } from '../operationRuntime${ext}'
81
94
 
82
95
  ${generateRouteConfigType(modelName, 'HonoHookHandler', guardShapesImport, importStyle, 'hono')}
83
96
  const _env = getEnv()
84
97
 
98
+ const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
99
+ const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
100
+
85
101
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
86
102
 
87
103
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
@@ -90,6 +106,7 @@ type OperationConfigLike<TEnv extends HonoEnvBase> = {
90
106
  before?: HonoHookHandler<TEnv>[]
91
107
  after?: HonoHookHandler<TEnv>[]
92
108
  shape?: Record<string, unknown>
109
+ pagination?: Partial<PaginationConfig>
93
110
  }
94
111
 
95
112
  const defaultOpConfig = Object.freeze({
@@ -97,7 +114,7 @@ const defaultOpConfig = Object.freeze({
97
114
  after: Object.freeze([]),
98
115
  }) as unknown as OperationConfigLike<HonoEnvBase>
99
116
 
100
- type HandlerContext = Context<{ Variables: HonoInternalVariables }>
117
+ type HandlerContext = Context<{ Variables: HonoInternalVariables & { findManyPaginatedMode?: FindManyPaginatedMode } }>
101
118
 
102
119
  function isQueryBuilderEnabled(config: RouteConfig): boolean {
103
120
  if (config.queryBuilder === false) return false
@@ -150,10 +167,11 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
150
167
  opConfig: OperationConfigLike<TEnv>,
151
168
  ) {
152
169
  return (c: Context<GeneratedHonoEnv<TEnv>>): void => {
153
- const paginationConfig = (config as { pagination?: OperationContext['paginationConfig'] }).pagination
154
- if (paginationConfig) {
155
- c.set('routeConfig', { pagination: paginationConfig })
170
+ const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
171
+ if (merged) {
172
+ c.set('routeConfig', { pagination: merged })
156
173
  }
174
+ ;(c as unknown as HandlerContext).set('findManyPaginatedMode', FIND_MANY_PAGINATED_MODE)
157
175
  const headerName = config.guard?.variantHeader || 'x-api-variant'
158
176
  const headerValue = c.req.header(headerName)
159
177
  const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
@@ -224,7 +242,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
224
242
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
225
243
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
226
244
  config as RouteConfig,
227
- { format: 'json' },
245
+ { format: 'json', writeStrategy: WRITE_STRATEGY },
228
246
  )
229
247
  const openApiYamlSpec = openApiDisabled
230
248
  ? null
@@ -233,7 +251,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
233
251
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
234
252
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
235
253
  config as RouteConfig,
236
- { format: 'yaml' },
254
+ { format: 'yaml', writeStrategy: WRITE_STRATEGY },
237
255
  )
238
256
 
239
257
  if (isQueryBuilderEnabled(config as RouteConfig)) {
@@ -270,17 +288,17 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
270
288
  parseFn: (c: HandlerContext) => Promise<void>,
271
289
  ) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
272
290
  try {
273
- await parseFn(c)
291
+ await parseFn(c as unknown as HandlerContext)
274
292
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
275
293
  const { before = [], after = [] } = opConfig
276
294
  const beforeResp = await runHooks<TEnv>(before, c)
277
295
  if (beforeResp) return beforeResp
278
- await handlerFn(c)
296
+ await handlerFn(c as unknown as HandlerContext)
279
297
  const afterResp = await runHooks<TEnv>(after, c)
280
298
  if (afterResp) return afterResp
281
- return sendResult(c)
299
+ return sendResult(c as unknown as HandlerContext)
282
300
  } catch (error: unknown) {
283
- return sendError(c, error)
301
+ return sendError(c as unknown as HandlerContext, error)
284
302
  }
285
303
  }
286
304
 
@@ -289,17 +307,17 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
289
307
  handlerFn: (c: HandlerContext) => Promise<void>,
290
308
  ) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
291
309
  try {
292
- await parseWriteBodyMiddleware(c)
310
+ await parseWriteBodyMiddleware(c as unknown as HandlerContext)
293
311
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
294
312
  const { before = [], after = [] } = opConfig
295
313
  const beforeResp = await runHooks<TEnv>(before, c)
296
314
  if (beforeResp) return beforeResp
297
- await handlerFn(c)
315
+ await handlerFn(c as unknown as HandlerContext)
298
316
  const afterResp = await runHooks<TEnv>(after, c)
299
317
  if (afterResp) return afterResp
300
- return sendResult(c)
318
+ return sendResult(c as unknown as HandlerContext)
301
319
  } catch (error: unknown) {
302
- return sendError(c, error)
320
+ return sendError(c as unknown as HandlerContext, error)
303
321
  }
304
322
  }
305
323