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.
- package/README.md +186 -11
- package/dist/constants.d.ts +2 -0
- package/dist/generators/generateFastifyHandler.js +3 -1
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +3 -1
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -0
- package/dist/generators/generateOperationCore.js +68 -39
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +4 -1
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +4 -1
- package/dist/generators/generateRouter.js +20 -7
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +4 -1
- package/dist/generators/generateRouterFastify.js +23 -9
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +4 -1
- package/dist/generators/generateRouterHono.js +29 -16
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.d.ts +2 -1
- package/dist/generators/generateUnifiedScalarUI.js +13 -10
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +5 -1
- package/src/copy/autoIncludeRuntime.ts +60 -35
- package/src/copy/buildModelOpenApi.ts +144 -23
- package/src/copy/docsRenderer.ts +125 -98
- package/src/copy/operationRuntime.ts +94 -9
- package/src/copy/routeConfig.express.ts +8 -0
- package/src/copy/routeConfig.fastify.ts +8 -0
- package/src/copy/routeConfig.hono.ts +9 -1
- package/src/copy/routeConfig.ts +23 -5
- package/src/generators/generateFastifyHandler.ts +3 -1
- package/src/generators/generateHonoHandler.ts +3 -1
- package/src/generators/generateOperationCore.ts +84 -39
- package/src/generators/generateRouteConfigType.ts +5 -2
- package/src/generators/generateRouter.ts +24 -6
- package/src/generators/generateRouterFastify.ts +27 -8
- package/src/generators/generateRouterHono.ts +33 -15
- package/src/generators/generateUnifiedScalarUI.ts +15 -11
- 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
|
|
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).${
|
|
135
|
+
return delegate.guard(ctx.guardShape, ctx.guardCaller).${method}(body)
|
|
57
136
|
}
|
|
58
|
-
return delegate.${
|
|
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
|
|
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
|
|
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 {
|
|
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?:
|
|
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
|
-
|
|
217
|
-
|
|
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 {
|
|
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?:
|
|
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
|
|
138
|
-
if (
|
|
139
|
-
fx.routeConfig = { pagination:
|
|
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 {
|
|
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
|
|
154
|
-
if (
|
|
155
|
-
c.set('routeConfig', { pagination:
|
|
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
|
|