prisma-generator-express 1.56.4 → 1.58.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 +286 -29
- package/dist/copy/misc.js +25 -6
- package/dist/copy/misc.js.map +1 -1
- package/dist/generators/generateFastifyHandler.js +11 -0
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +14 -20
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.js +43 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateOperationCore.js +58 -17
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +44 -15
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +2 -1
- package/dist/generators/generateRouter.js +60 -34
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +2 -1
- package/dist/generators/generateRouterFastify.js +238 -193
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +2 -1
- package/dist/generators/generateRouterHono.js +124 -89
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/autoIncludeRuntime.ts +9 -5
- package/src/copy/buildModelOpenApi.ts +96 -0
- package/src/copy/docsRenderer.ts +577 -174
- package/src/copy/materializedRouter.ts +40 -1
- package/src/copy/misc.ts +23 -6
- package/src/copy/operationDefinitions.ts +10 -0
- package/src/copy/operationRuntime.ts +28 -9
- package/src/copy/routeConfig.express.ts +9 -9
- package/src/copy/routeConfig.hono.ts +63 -5
- package/src/copy/routeConfig.ts +44 -20
- package/src/generators/generateFastifyHandler.ts +12 -0
- package/src/generators/generateHonoHandler.ts +15 -20
- package/src/generators/generateImportPrismaStatement.ts +13 -0
- package/src/generators/generateOperationCore.ts +58 -17
- package/src/generators/generateRouteConfigType.ts +52 -17
- package/src/generators/generateRouter.ts +61 -33
- package/src/generators/generateRouterFastify.ts +239 -192
- package/src/generators/generateRouterHono.ts +125 -88
- package/src/index.ts +25 -5
|
@@ -11,6 +11,7 @@ export function generateHonoRouterFunction({
|
|
|
11
11
|
importStyle,
|
|
12
12
|
writeStrategy,
|
|
13
13
|
findManyPaginatedMode,
|
|
14
|
+
dropGuard,
|
|
14
15
|
}: {
|
|
15
16
|
model: DMMF.Model
|
|
16
17
|
enums: DMMF.DatamodelEnum[]
|
|
@@ -18,6 +19,7 @@ export function generateHonoRouterFunction({
|
|
|
18
19
|
importStyle: ImportStyle
|
|
19
20
|
writeStrategy: WriteStrategy
|
|
20
21
|
findManyPaginatedMode: FindManyPaginatedMode
|
|
22
|
+
dropGuard: boolean
|
|
21
23
|
}): string {
|
|
22
24
|
const ext = importExt(importStyle)
|
|
23
25
|
const modelName = model.name
|
|
@@ -48,10 +50,9 @@ export function generateHonoRouterFunction({
|
|
|
48
50
|
}))
|
|
49
51
|
|
|
50
52
|
return `import { Hono } from 'hono'
|
|
51
|
-
import type { Context
|
|
53
|
+
import type { Context } from 'hono'
|
|
52
54
|
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
|
53
55
|
import { HTTPException } from 'hono/http-exception'
|
|
54
|
-
import { startQueryBuilder } from '../queryBuilder${ext}'
|
|
55
56
|
import {
|
|
56
57
|
${modelName}FindUnique,
|
|
57
58
|
${modelName}FindUniqueOrThrow,
|
|
@@ -71,40 +72,41 @@ import {
|
|
|
71
72
|
${modelName}Aggregate,
|
|
72
73
|
${modelName}Count,
|
|
73
74
|
${modelName}GroupBy,
|
|
75
|
+
${modelName}UpdateEach,
|
|
74
76
|
} from './${modelName}Handlers${ext}'
|
|
75
77
|
import type {
|
|
76
78
|
RouteConfig,
|
|
77
|
-
|
|
79
|
+
HonoBeforeHook,
|
|
80
|
+
HonoAfterHook,
|
|
78
81
|
HonoEnvBase,
|
|
79
82
|
HonoInternalVariables,
|
|
80
83
|
GeneratedHonoEnv,
|
|
81
84
|
WriteStrategy,
|
|
82
|
-
FindManyPaginatedMode,
|
|
83
85
|
PaginationConfig,
|
|
84
86
|
} from '../routeConfig.target${ext}'
|
|
85
87
|
import { parseQueryParams } from '../parseQueryParams${ext}'
|
|
86
|
-
import {
|
|
88
|
+
import { normalizePrefix, getEnv, sanitizeKeys } from '../misc${ext}'
|
|
87
89
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
90
|
+
import { validateCountSourceWhere } from '../routeConfig${ext}'
|
|
88
91
|
import {
|
|
89
92
|
mapError,
|
|
90
93
|
transformResult,
|
|
91
94
|
mergePaginationConfig,
|
|
92
|
-
type OperationContext,
|
|
93
95
|
} from '../operationRuntime${ext}'
|
|
94
96
|
|
|
95
|
-
${generateRouteConfigType(modelName, '
|
|
97
|
+
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
|
|
96
98
|
const _env = getEnv()
|
|
97
99
|
|
|
98
100
|
const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
|
|
99
|
-
const
|
|
101
|
+
const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
|
|
100
102
|
|
|
101
103
|
const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
|
|
102
104
|
|
|
103
105
|
const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
|
|
104
106
|
|
|
105
107
|
type OperationConfigLike<TEnv extends HonoEnvBase> = {
|
|
106
|
-
before?:
|
|
107
|
-
after?:
|
|
108
|
+
before?: HonoBeforeHook<TEnv>[]
|
|
109
|
+
after?: HonoAfterHook<TEnv>[]
|
|
108
110
|
shape?: Record<string, unknown>
|
|
109
111
|
pagination?: Partial<PaginationConfig>
|
|
110
112
|
}
|
|
@@ -114,20 +116,7 @@ const defaultOpConfig = Object.freeze({
|
|
|
114
116
|
after: Object.freeze([]),
|
|
115
117
|
}) as unknown as OperationConfigLike<HonoEnvBase>
|
|
116
118
|
|
|
117
|
-
type HandlerContext = Context<{ Variables: HonoInternalVariables
|
|
118
|
-
|
|
119
|
-
function isQueryBuilderEnabled(config: RouteConfig): boolean {
|
|
120
|
-
if (config.queryBuilder === false) return false
|
|
121
|
-
if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
|
|
122
|
-
if (_env.NODE_ENV === 'production') return false
|
|
123
|
-
return true
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function getQueryBuilderConfig(config: RouteConfig) {
|
|
127
|
-
if (config.queryBuilder === false) return null
|
|
128
|
-
if (typeof config.queryBuilder === 'object') return config.queryBuilder
|
|
129
|
-
return {}
|
|
130
|
-
}
|
|
119
|
+
type HandlerContext = Context<{ Variables: HonoInternalVariables }>
|
|
131
120
|
|
|
132
121
|
async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
|
|
133
122
|
const raw = c.req.query() as Record<string, unknown>
|
|
@@ -159,7 +148,20 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
|
159
148
|
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
160
149
|
throw new HTTPException(400, { message: 'Request body must be a JSON object' })
|
|
161
150
|
}
|
|
162
|
-
c.set('body',
|
|
151
|
+
c.set('body', body)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function parseUpdateEachBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
155
|
+
let body: unknown
|
|
156
|
+
try {
|
|
157
|
+
body = await c.req.json()
|
|
158
|
+
} catch {
|
|
159
|
+
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
160
|
+
}
|
|
161
|
+
if (!Array.isArray(body)) {
|
|
162
|
+
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
163
|
+
}
|
|
164
|
+
c.set('body', body)
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
@@ -171,37 +173,34 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
171
173
|
if (merged) {
|
|
172
174
|
c.set('routeConfig', { pagination: merged })
|
|
173
175
|
}
|
|
174
|
-
;(c as unknown as HandlerContext).set('findManyPaginatedMode', FIND_MANY_PAGINATED_MODE)
|
|
175
176
|
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
176
177
|
const headerValue = c.req.header(headerName)
|
|
177
178
|
const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
|
|
178
179
|
if (caller) c.set('guardCaller', caller)
|
|
179
|
-
if (opConfig.shape) {
|
|
180
|
+
if (opConfig.shape && !DROP_GUARD) {
|
|
180
181
|
c.set('guardShape', opConfig.shape)
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
async function
|
|
186
|
-
hooks:
|
|
186
|
+
async function runBeforeHooks<TEnv extends HonoEnvBase>(
|
|
187
|
+
hooks: HonoBeforeHook<TEnv>[],
|
|
187
188
|
c: Context<GeneratedHonoEnv<TEnv>>,
|
|
188
189
|
): Promise<Response | undefined> {
|
|
189
190
|
for (const hook of hooks) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const result = await hook(c)
|
|
192
|
+
if (result instanceof Response) return result
|
|
193
|
+
}
|
|
194
|
+
return undefined
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function runAfterHooks<TEnv extends HonoEnvBase>(
|
|
198
|
+
hooks: HonoAfterHook<TEnv>[],
|
|
199
|
+
c: Context<GeneratedHonoEnv<TEnv>>,
|
|
200
|
+
): Promise<Response | undefined> {
|
|
201
|
+
for (const hook of hooks) {
|
|
202
|
+
const result = await hook(c)
|
|
195
203
|
if (result instanceof Response) return result
|
|
196
|
-
if (!advanced) {
|
|
197
|
-
if (_env.NODE_ENV !== 'production') {
|
|
198
|
-
console.warn(
|
|
199
|
-
'[hono-router] Hook returned without calling next() or returning a Response. ' +
|
|
200
|
-
'Use \`return c.json(...)\` to short-circuit, or \`await next()\` to continue.',
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
return c.body(null) ?? undefined
|
|
204
|
-
}
|
|
205
204
|
}
|
|
206
205
|
return undefined
|
|
207
206
|
}
|
|
@@ -216,69 +215,81 @@ function sendResult(c: HandlerContext): Response {
|
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
function sendError(c: HandlerContext, error: unknown): Response {
|
|
218
|
+
if (error instanceof HTTPException) {
|
|
219
|
+
return c.json({ message: error.message }, error.status as ContentfulStatusCode)
|
|
220
|
+
}
|
|
219
221
|
const httpError = mapError(error)
|
|
220
222
|
return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
|
|
226
|
+
validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
|
|
227
|
+
validateCountSourceWhere(
|
|
228
|
+
(config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
|
|
229
|
+
'${modelName} findManyPaginated pagination',
|
|
230
|
+
)
|
|
231
|
+
|
|
224
232
|
const app = new Hono<GeneratedHonoEnv<TEnv>>()
|
|
225
233
|
|
|
234
|
+
const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
|
|
235
|
+
|
|
226
236
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
227
237
|
const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
|
|
228
238
|
const basePath = customPrefix + modelPrefix
|
|
229
239
|
|
|
230
240
|
const openApiDisabled = config.disableOpenApi === true
|
|
231
241
|
|| (config.disableOpenApi !== false && (
|
|
232
|
-
_env.
|
|
233
|
-
|| _env.
|
|
242
|
+
_env.NODE_ENV === 'production'
|
|
243
|
+
|| _env.DISABLE_OPENAPI === 'true'
|
|
234
244
|
))
|
|
235
245
|
|
|
236
246
|
const postReadsEnabled = !config.disablePostReads
|
|
237
247
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
let _openApiJsonCache: unknown = undefined
|
|
249
|
+
const getOpenApiJson = (): unknown => {
|
|
250
|
+
if (_openApiJsonCache === undefined) {
|
|
251
|
+
_openApiJsonCache = buildModelOpenApi(
|
|
241
252
|
'${modelName}',
|
|
242
253
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
243
254
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
244
255
|
config as RouteConfig,
|
|
245
256
|
{ format: 'json', writeStrategy: WRITE_STRATEGY },
|
|
246
257
|
)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
258
|
+
}
|
|
259
|
+
return _openApiJsonCache
|
|
260
|
+
}
|
|
261
|
+
let _openApiYamlCache: string | undefined = undefined
|
|
262
|
+
const getOpenApiYaml = (): string => {
|
|
263
|
+
if (_openApiYamlCache === undefined) {
|
|
264
|
+
_openApiYamlCache = buildModelOpenApi(
|
|
250
265
|
'${modelName}',
|
|
251
266
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
252
267
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
253
268
|
config as RouteConfig,
|
|
254
269
|
{ format: 'yaml', writeStrategy: WRITE_STRATEGY },
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
if (isQueryBuilderEnabled(config as RouteConfig)) {
|
|
258
|
-
const qbConfig = getQueryBuilderConfig(config as RouteConfig)
|
|
259
|
-
if (qbConfig) {
|
|
260
|
-
try {
|
|
261
|
-
startQueryBuilder(qbConfig)
|
|
262
|
-
} catch (err) {
|
|
263
|
-
if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
|
|
264
|
-
}
|
|
270
|
+
) as string
|
|
265
271
|
}
|
|
272
|
+
return _openApiYamlCache
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (config.queryBuilder && config.queryBuilder !== false && _env.NODE_ENV !== 'production') {
|
|
276
|
+
console.warn(
|
|
277
|
+
'[${modelName}Router] queryBuilder config is present but Hono target does not auto-start it. ' +
|
|
278
|
+
'Run \`npx prisma-query-builder-ui\` in a separate process.',
|
|
279
|
+
)
|
|
266
280
|
}
|
|
267
281
|
|
|
268
282
|
app.onError((err, c) => {
|
|
269
|
-
if (err instanceof HTTPException) {
|
|
270
|
-
return c.json({ message: err.message }, err.status as ContentfulStatusCode)
|
|
271
|
-
}
|
|
272
283
|
return sendError(c as HandlerContext, err)
|
|
273
284
|
})
|
|
274
285
|
|
|
275
286
|
if (!openApiDisabled) {
|
|
276
287
|
const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
|
|
277
288
|
const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
|
|
278
|
-
app.get(openapiJsonPath, (c) => c.json(
|
|
289
|
+
app.get(openapiJsonPath, (c) => c.json(getOpenApiJson() as Record<string, unknown>))
|
|
279
290
|
app.get(openapiYamlPath, (c) => {
|
|
280
291
|
c.header('Content-Type', 'application/yaml')
|
|
281
|
-
return c.body(
|
|
292
|
+
return c.body(getOpenApiYaml())
|
|
282
293
|
})
|
|
283
294
|
}
|
|
284
295
|
|
|
@@ -291,10 +302,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
291
302
|
await parseFn(c as unknown as HandlerContext)
|
|
292
303
|
makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
|
|
293
304
|
const { before = [], after = [] } = opConfig
|
|
294
|
-
const beforeResp = await
|
|
305
|
+
const beforeResp = await runBeforeHooks<TEnv>(before, c)
|
|
295
306
|
if (beforeResp) return beforeResp
|
|
296
307
|
await handlerFn(c as unknown as HandlerContext)
|
|
297
|
-
const afterResp = await
|
|
308
|
+
const afterResp = await runAfterHooks<TEnv>(after, c)
|
|
298
309
|
if (afterResp) return afterResp
|
|
299
310
|
return sendResult(c as unknown as HandlerContext)
|
|
300
311
|
} catch (error: unknown) {
|
|
@@ -310,10 +321,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
310
321
|
await parseWriteBodyMiddleware(c as unknown as HandlerContext)
|
|
311
322
|
makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
|
|
312
323
|
const { before = [], after = [] } = opConfig
|
|
313
|
-
const beforeResp = await
|
|
324
|
+
const beforeResp = await runBeforeHooks<TEnv>(before, c)
|
|
314
325
|
if (beforeResp) return beforeResp
|
|
315
326
|
await handlerFn(c as unknown as HandlerContext)
|
|
316
|
-
const afterResp = await
|
|
327
|
+
const afterResp = await runAfterHooks<TEnv>(after, c)
|
|
317
328
|
if (afterResp) return afterResp
|
|
318
329
|
return sendResult(c as unknown as HandlerContext)
|
|
319
330
|
} catch (error: unknown) {
|
|
@@ -326,55 +337,55 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
326
337
|
?? (defaultOpConfig as OperationConfigLike<TEnv>)
|
|
327
338
|
}
|
|
328
339
|
|
|
329
|
-
if (config.
|
|
340
|
+
if (isEnabled(config.findFirst)) {
|
|
330
341
|
const opConfig = opFor('findFirst')
|
|
331
342
|
const path = basePath ? \`\${basePath}/first\` : '/first'
|
|
332
343
|
app.get(path, handleRead(opConfig, ${modelName}FindFirst, parseQueryMiddleware))
|
|
333
344
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirst, parseBodyAsQueryMiddleware))
|
|
334
345
|
}
|
|
335
|
-
if (config.
|
|
346
|
+
if (isEnabled(config.findFirstOrThrow)) {
|
|
336
347
|
const opConfig = opFor('findFirstOrThrow')
|
|
337
348
|
const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
|
|
338
349
|
app.get(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseQueryMiddleware))
|
|
339
350
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryMiddleware))
|
|
340
351
|
}
|
|
341
|
-
if (config.
|
|
352
|
+
if (isEnabled(config.findManyPaginated)) {
|
|
342
353
|
const opConfig = opFor('findManyPaginated')
|
|
343
354
|
const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
|
|
344
355
|
app.get(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseQueryMiddleware))
|
|
345
356
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryMiddleware))
|
|
346
357
|
}
|
|
347
|
-
if (config.
|
|
358
|
+
if (isEnabled(config.aggregate)) {
|
|
348
359
|
const opConfig = opFor('aggregate')
|
|
349
360
|
const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
|
|
350
361
|
app.get(path, handleRead(opConfig, ${modelName}Aggregate, parseQueryMiddleware))
|
|
351
362
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Aggregate, parseBodyAsQueryMiddleware))
|
|
352
363
|
}
|
|
353
|
-
if (config.
|
|
364
|
+
if (isEnabled(config.count)) {
|
|
354
365
|
const opConfig = opFor('count')
|
|
355
366
|
const path = basePath ? \`\${basePath}/count\` : '/count'
|
|
356
367
|
app.get(path, handleRead(opConfig, ${modelName}Count, parseQueryMiddleware))
|
|
357
368
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Count, parseBodyAsQueryMiddleware))
|
|
358
369
|
}
|
|
359
|
-
if (config.
|
|
370
|
+
if (isEnabled(config.groupBy)) {
|
|
360
371
|
const opConfig = opFor('groupBy')
|
|
361
372
|
const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
|
|
362
373
|
app.get(path, handleRead(opConfig, ${modelName}GroupBy, parseQueryMiddleware))
|
|
363
374
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}GroupBy, parseBodyAsQueryMiddleware))
|
|
364
375
|
}
|
|
365
|
-
if (config.
|
|
376
|
+
if (isEnabled(config.findUniqueOrThrow)) {
|
|
366
377
|
const opConfig = opFor('findUniqueOrThrow')
|
|
367
378
|
const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
|
|
368
379
|
app.get(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseQueryMiddleware))
|
|
369
380
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryMiddleware))
|
|
370
381
|
}
|
|
371
|
-
if (config.
|
|
382
|
+
if (isEnabled(config.findUnique)) {
|
|
372
383
|
const opConfig = opFor('findUnique')
|
|
373
384
|
const path = basePath ? \`\${basePath}/unique\` : '/unique'
|
|
374
385
|
app.get(path, handleRead(opConfig, ${modelName}FindUnique, parseQueryMiddleware))
|
|
375
386
|
if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUnique, parseBodyAsQueryMiddleware))
|
|
376
387
|
}
|
|
377
|
-
if (config.
|
|
388
|
+
if (isEnabled(config.findMany)) {
|
|
378
389
|
const opConfig = opFor('findMany')
|
|
379
390
|
const path = basePath || '/'
|
|
380
391
|
app.get(path, handleRead(opConfig, ${modelName}FindMany, parseQueryMiddleware))
|
|
@@ -384,52 +395,78 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
384
395
|
}
|
|
385
396
|
}
|
|
386
397
|
|
|
387
|
-
if (config.
|
|
398
|
+
if (isEnabled(config.createManyAndReturn)) {
|
|
388
399
|
const opConfig = opFor('createManyAndReturn')
|
|
389
400
|
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
390
401
|
app.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
|
|
391
402
|
}
|
|
392
|
-
if (config.
|
|
403
|
+
if (isEnabled(config.createMany)) {
|
|
393
404
|
const opConfig = opFor('createMany')
|
|
394
405
|
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
395
406
|
app.post(path, handleWrite(opConfig, ${modelName}CreateMany))
|
|
396
407
|
}
|
|
397
|
-
if (config.
|
|
408
|
+
if (isEnabled(config.create)) {
|
|
398
409
|
const opConfig = opFor('create')
|
|
399
410
|
const path = basePath || '/'
|
|
400
411
|
app.post(path, handleWrite(opConfig, ${modelName}Create))
|
|
401
412
|
}
|
|
402
|
-
if (config.
|
|
413
|
+
if (isEnabled(config.updateManyAndReturn)) {
|
|
403
414
|
const opConfig = opFor('updateManyAndReturn')
|
|
404
415
|
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
405
416
|
app.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
|
|
406
417
|
}
|
|
407
|
-
if (config.
|
|
418
|
+
if (isEnabled(config.updateMany)) {
|
|
408
419
|
const opConfig = opFor('updateMany')
|
|
409
420
|
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
410
421
|
app.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
|
|
411
422
|
}
|
|
412
|
-
if (config.
|
|
423
|
+
if (isEnabled(config.update)) {
|
|
413
424
|
const opConfig = opFor('update')
|
|
414
425
|
const path = basePath || '/'
|
|
415
426
|
app.put(path, handleWrite(opConfig, ${modelName}Update))
|
|
416
427
|
}
|
|
417
|
-
if (config.
|
|
428
|
+
if (isEnabled(config.upsert)) {
|
|
418
429
|
const opConfig = opFor('upsert')
|
|
419
430
|
const path = basePath || '/'
|
|
420
431
|
app.patch(path, handleWrite(opConfig, ${modelName}Upsert))
|
|
421
432
|
}
|
|
422
|
-
if (config.
|
|
433
|
+
if (isEnabled(config.deleteMany)) {
|
|
423
434
|
const opConfig = opFor('deleteMany')
|
|
424
435
|
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
425
436
|
app.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
|
|
426
437
|
}
|
|
427
|
-
if (config.
|
|
438
|
+
if (isEnabled(config.delete)) {
|
|
428
439
|
const opConfig = opFor('delete')
|
|
429
440
|
const path = basePath || '/'
|
|
430
441
|
app.delete(path, handleWrite(opConfig, ${modelName}Delete))
|
|
431
442
|
}
|
|
432
443
|
|
|
444
|
+
if (config.updateEach) {
|
|
445
|
+
const opConfig = (config.updateEach as unknown as OperationConfigLike<TEnv> | undefined) ?? (defaultOpConfig as OperationConfigLike<TEnv>)
|
|
446
|
+
if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
|
|
447
|
+
console.warn(
|
|
448
|
+
'[${modelName}Router] updateEach is enabled without a before hook. ' +
|
|
449
|
+
'This endpoint bypasses guard shapes and should be protected by authentication middleware.',
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
const path = basePath ? \`\${basePath}/each\` : '/each'
|
|
453
|
+
app.post(path, async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
454
|
+
try {
|
|
455
|
+
await parseUpdateEachBodyMiddleware(c as unknown as HandlerContext)
|
|
456
|
+
makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
|
|
457
|
+
const { before = [], after = [] } = opConfig
|
|
458
|
+
const beforeResp = await runBeforeHooks<TEnv>(before, c)
|
|
459
|
+
if (beforeResp) return beforeResp
|
|
460
|
+
await ${modelName}UpdateEach(c as unknown as HandlerContext)
|
|
461
|
+
const afterResp = await runAfterHooks<TEnv>(after, c)
|
|
462
|
+
if (afterResp) return afterResp
|
|
463
|
+
return sendResult(c as unknown as HandlerContext)
|
|
464
|
+
} catch (error: unknown) {
|
|
465
|
+
return sendError(c as unknown as HandlerContext, error)
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
|
|
433
470
|
return app
|
|
434
471
|
}
|
|
435
472
|
`
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
FindManyPaginatedMode,
|
|
33
33
|
} from './constants'
|
|
34
34
|
|
|
35
|
-
const GENERATOR_OFF_RE =
|
|
35
|
+
const GENERATOR_OFF_RE = /^\s*generator off\s*$/m
|
|
36
36
|
|
|
37
37
|
function getTarget(options: GeneratorOptions): Target {
|
|
38
38
|
const raw = String(
|
|
@@ -79,6 +79,17 @@ function getFindManyPaginatedMode(
|
|
|
79
79
|
)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function getDropGuard(options: GeneratorOptions): boolean {
|
|
83
|
+
const raw = (options.generator.config as Record<string, unknown>).dropGuard
|
|
84
|
+
if (raw === undefined || raw === null || raw === '') return false
|
|
85
|
+
const lower = String(raw).toLowerCase()
|
|
86
|
+
if (lower === 'true' || lower === '1') return true
|
|
87
|
+
if (lower === 'false' || lower === '0') return false
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Invalid dropGuard "${raw}". Expected "true" or "false".`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
82
93
|
function validateClientGeneratorPresent(options: GeneratorOptions): void {
|
|
83
94
|
getRelativeClientPath(
|
|
84
95
|
options,
|
|
@@ -90,7 +101,7 @@ generatorHandler({
|
|
|
90
101
|
onManifest() {
|
|
91
102
|
return {
|
|
92
103
|
version: require('../package.json').version,
|
|
93
|
-
defaultOutput: '../generated/
|
|
104
|
+
defaultOutput: '../generated/output',
|
|
94
105
|
prettyName: GENERATOR_NAME,
|
|
95
106
|
}
|
|
96
107
|
},
|
|
@@ -99,12 +110,13 @@ generatorHandler({
|
|
|
99
110
|
const target = getTarget(options)
|
|
100
111
|
const writeStrategy = getWriteStrategy(options)
|
|
101
112
|
const findManyPaginatedMode = getFindManyPaginatedMode(options)
|
|
113
|
+
const dropGuard = getDropGuard(options)
|
|
102
114
|
|
|
103
115
|
const manifestDefaultAbs = path.resolve(
|
|
104
116
|
__dirname,
|
|
105
117
|
'..',
|
|
106
118
|
'generated',
|
|
107
|
-
|
|
119
|
+
'output',
|
|
108
120
|
)
|
|
109
121
|
const currentOutput = options.generator.output?.value
|
|
110
122
|
const isUnsetOrManifestDefault =
|
|
@@ -124,6 +136,9 @@ generatorHandler({
|
|
|
124
136
|
console.log(` Import style: ${importStyle}`)
|
|
125
137
|
console.log(` Write strategy: ${writeStrategy}`)
|
|
126
138
|
console.log(` findManyPaginated mode: ${findManyPaginatedMode}`)
|
|
139
|
+
console.log(
|
|
140
|
+
` Drop guard (generator): ${dropGuard}${dropGuard ? '' : ' (runtime E2E=true will also drop guard)'}`,
|
|
141
|
+
)
|
|
127
142
|
|
|
128
143
|
if (options.dmmf.datamodel.models.length > 0) {
|
|
129
144
|
validateClientGeneratorPresent(options)
|
|
@@ -151,7 +166,9 @@ generatorHandler({
|
|
|
151
166
|
}
|
|
152
167
|
modelNames.push(model.name)
|
|
153
168
|
|
|
154
|
-
const guardShapesImport =
|
|
169
|
+
const guardShapesImport = dropGuard
|
|
170
|
+
? null
|
|
171
|
+
: getGuardShapesImport(options, model.name)
|
|
155
172
|
|
|
156
173
|
await writeFileSafely({
|
|
157
174
|
content: generateModelCore({
|
|
@@ -181,6 +198,7 @@ generatorHandler({
|
|
|
181
198
|
importStyle,
|
|
182
199
|
writeStrategy,
|
|
183
200
|
findManyPaginatedMode,
|
|
201
|
+
dropGuard,
|
|
184
202
|
})
|
|
185
203
|
: target === 'hono'
|
|
186
204
|
? generateHonoRouterFunction({
|
|
@@ -190,6 +208,7 @@ generatorHandler({
|
|
|
190
208
|
importStyle,
|
|
191
209
|
writeStrategy,
|
|
192
210
|
findManyPaginatedMode,
|
|
211
|
+
dropGuard,
|
|
193
212
|
})
|
|
194
213
|
: generateRouterFunction({
|
|
195
214
|
model: model as DMMF.Model,
|
|
@@ -198,6 +217,7 @@ generatorHandler({
|
|
|
198
217
|
importStyle,
|
|
199
218
|
writeStrategy,
|
|
200
219
|
findManyPaginatedMode,
|
|
220
|
+
dropGuard,
|
|
201
221
|
})
|
|
202
222
|
|
|
203
223
|
await writeFileSafely({
|
|
@@ -258,4 +278,4 @@ generatorHandler({
|
|
|
258
278
|
console.log(`✓ ${modelNames.length} models (${target})`)
|
|
259
279
|
console.log('')
|
|
260
280
|
},
|
|
261
|
-
})
|
|
281
|
+
})
|