prisma-generator-express 1.59.0 → 1.60.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 (64) hide show
  1. package/dist/client/encodeQueryParams.js +19 -23
  2. package/dist/client/encodeQueryParams.js.map +1 -1
  3. package/dist/copy/operationDefinitions.d.ts +37 -0
  4. package/dist/copy/operationDefinitions.js +412 -0
  5. package/dist/copy/operationDefinitions.js.map +1 -0
  6. package/dist/generators/generateFastifyHandler.js +10 -41
  7. package/dist/generators/generateFastifyHandler.js.map +1 -1
  8. package/dist/generators/generateHonoHandler.js +10 -41
  9. package/dist/generators/generateHonoHandler.js.map +1 -1
  10. package/dist/generators/generateModelMetadata.d.ts +8 -0
  11. package/dist/generators/generateModelMetadata.js +77 -0
  12. package/dist/generators/generateModelMetadata.js.map +1 -0
  13. package/dist/generators/generateOperationCore.js +20 -30
  14. package/dist/generators/generateOperationCore.js.map +1 -1
  15. package/dist/generators/generateQueryBuilderHelper.js +1 -1
  16. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  17. package/dist/generators/generateRouteConfigType.js +11 -57
  18. package/dist/generators/generateRouteConfigType.js.map +1 -1
  19. package/dist/generators/generateRouter.js +64 -177
  20. package/dist/generators/generateRouter.js.map +1 -1
  21. package/dist/generators/generateRouterFastify.js +59 -169
  22. package/dist/generators/generateRouterFastify.js.map +1 -1
  23. package/dist/generators/generateRouterHono.js +52 -152
  24. package/dist/generators/generateRouterHono.js.map +1 -1
  25. package/dist/generators/generateUnifiedHandler.js +7 -30
  26. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  27. package/dist/generators/generateUnifiedScalarUI.js +9 -74
  28. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  29. package/dist/index.js +11 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/utils/copyFiles.js +7 -0
  32. package/dist/utils/copyFiles.js.map +1 -1
  33. package/dist/utils/writeFileSafely.js +5 -12
  34. package/dist/utils/writeFileSafely.js.map +1 -1
  35. package/package.json +12 -2
  36. package/src/client/encodeQueryParams.ts +23 -36
  37. package/src/copy/autoIncludePlanner.ts +14 -23
  38. package/src/copy/autoIncludeRuntime.ts +117 -228
  39. package/src/copy/buildModelOpenApi.ts +248 -628
  40. package/src/copy/concurrency.ts +20 -0
  41. package/src/copy/docsRenderer.ts +63 -333
  42. package/src/copy/errorMapper.ts +126 -0
  43. package/src/copy/guardHelpers.ts +56 -0
  44. package/src/copy/materializedCount.ts +68 -0
  45. package/src/copy/materializedRouter.ts +33 -29
  46. package/src/copy/operationDefinitions.ts +359 -35
  47. package/src/copy/operationRuntime.ts +11 -605
  48. package/src/copy/pagination.ts +151 -0
  49. package/src/copy/scalarTypes.ts +2 -0
  50. package/src/copy/sse.ts +296 -0
  51. package/src/generators/generateFastifyHandler.ts +13 -47
  52. package/src/generators/generateHonoHandler.ts +13 -47
  53. package/src/generators/generateModelMetadata.ts +92 -0
  54. package/src/generators/generateOperationCore.ts +19 -32
  55. package/src/generators/generateQueryBuilderHelper.ts +1 -1
  56. package/src/generators/generateRouteConfigType.ts +9 -60
  57. package/src/generators/generateRouter.ts +88 -180
  58. package/src/generators/generateRouterFastify.ts +65 -172
  59. package/src/generators/generateRouterHono.ts +58 -155
  60. package/src/generators/generateUnifiedHandler.ts +8 -33
  61. package/src/generators/generateUnifiedScalarUI.ts +9 -91
  62. package/src/index.ts +13 -1
  63. package/src/utils/copyFiles.ts +7 -0
  64. package/src/utils/writeFileSafely.ts +5 -11
@@ -0,0 +1,151 @@
1
+ import { HttpError, LOG_PREFIX } from './errorMapper'
2
+ import { assertGuard, buildCountShape, type PrismaDelegateLike } from './guardHelpers'
3
+ import { countFromMaterializedView } from './materializedCount'
4
+ import { isPlainObject } from './misc'
5
+ import type { PaginationConfig, PaginationCountSource } from './routeConfig'
6
+
7
+ export const DISTINCT_COUNT_LIMIT = 100000
8
+
9
+ export function applyPaginationLimits(
10
+ query: Record<string, unknown>,
11
+ config?: PaginationConfig,
12
+ hasGuardShape?: boolean,
13
+ ): Record<string, unknown> {
14
+ if (!config) return query
15
+ const result: Record<string, unknown> = { ...query }
16
+ if (!hasGuardShape && result.take === undefined && config.defaultLimit !== undefined) {
17
+ result.take = config.defaultLimit
18
+ }
19
+ if (config.maxLimit !== undefined && result.take !== undefined) {
20
+ const takeNum = typeof result.take === 'number' ? result.take : Number(result.take)
21
+ if (!Number.isFinite(takeNum)) {
22
+ throw new HttpError(400, 'Invalid take: must be a finite number')
23
+ }
24
+ if (!Number.isInteger(takeNum)) {
25
+ throw new HttpError(400, 'Invalid take: must be an integer')
26
+ }
27
+ if (Math.abs(takeNum) > config.maxLimit) {
28
+ result.take = takeNum < 0 ? -config.maxLimit : config.maxLimit
29
+ } else {
30
+ result.take = takeNum
31
+ }
32
+ }
33
+ return result
34
+ }
35
+
36
+ function mergeCountSource(
37
+ base: PaginationCountSource | undefined,
38
+ override: PaginationCountSource | undefined,
39
+ ): PaginationCountSource | undefined {
40
+ if (!base && !override) return undefined
41
+ if (!base) return override
42
+ if (!override) return base
43
+
44
+ const overrideType = (override as { type?: string }).type
45
+ const baseType = (base as { type?: string }).type
46
+ const effectiveType = overrideType ?? baseType
47
+
48
+ if (effectiveType === 'delegate' || effectiveType === undefined) {
49
+ return { type: 'delegate' }
50
+ }
51
+
52
+ return {
53
+ ...(base as Record<string, unknown>),
54
+ ...(override as Record<string, unknown>),
55
+ type: effectiveType,
56
+ } as PaginationCountSource
57
+ }
58
+
59
+ export function mergePaginationConfig(
60
+ base: PaginationConfig | undefined,
61
+ override: Partial<PaginationConfig> | undefined,
62
+ ): PaginationConfig | undefined {
63
+ if (!base && !override) return undefined
64
+ const merged: PaginationConfig = {
65
+ ...(base ?? {}),
66
+ ...(override ?? {}),
67
+ }
68
+ const mergedCountSource = mergeCountSource(base?.countSource, override?.countSource)
69
+ if (mergedCountSource) {
70
+ merged.countSource = mergedCountSource
71
+ } else {
72
+ delete merged.countSource
73
+ }
74
+ return merged
75
+ }
76
+
77
+ export function normalizeDistinct(value: unknown): string[] {
78
+ if (typeof value === 'string') return [value]
79
+ if (Array.isArray(value)) return value.filter((v): v is string => typeof v === 'string')
80
+ return []
81
+ }
82
+
83
+ function isEffectivelyEmpty(where: unknown): boolean {
84
+ if (where === undefined || where === null) return true
85
+ if (!isPlainObject(where)) return false
86
+ return Object.keys(where).length === 0
87
+ }
88
+
89
+ export async function countForPagination(
90
+ delegate: PrismaDelegateLike,
91
+ query: Record<string, unknown>,
92
+ shape: Record<string, unknown> | undefined,
93
+ caller: string | undefined,
94
+ distinctCountLimit?: number,
95
+ countSource?: PaginationCountSource,
96
+ rawClient?: unknown,
97
+ ): Promise<number> {
98
+ const normalizedDistinct = normalizeDistinct(query.distinct)
99
+ const whereIsEmpty = isEffectivelyEmpty(query.where)
100
+ const distinctIsEmpty = normalizedDistinct.length === 0
101
+
102
+ if (
103
+ countSource &&
104
+ countSource.type === 'materializedView' &&
105
+ !shape &&
106
+ whereIsEmpty &&
107
+ distinctIsEmpty
108
+ ) {
109
+ return countFromMaterializedView(rawClient ?? delegate, countSource)
110
+ }
111
+
112
+ const hasDistinct = normalizedDistinct.length > 0
113
+ const effectiveLimit = distinctCountLimit ?? DISTINCT_COUNT_LIMIT
114
+ const countShape = shape ? buildCountShape(shape) : undefined
115
+
116
+ const runCount = async (): Promise<number> => {
117
+ const countArgs: Record<string, unknown> = {}
118
+ if (query.where) countArgs.where = query.where
119
+ if (countShape) {
120
+ assertGuard(delegate)
121
+ return (await delegate.guard(
122
+ countShape as Record<string, unknown>,
123
+ caller,
124
+ ).count(countArgs)) as number
125
+ }
126
+ return (await delegate.count(countArgs)) as number
127
+ }
128
+
129
+ if (hasDistinct && shape) return runCount()
130
+
131
+ if (hasDistinct) {
132
+ const selectField = normalizedDistinct[0]
133
+ const distinctArgs: Record<string, unknown> = {
134
+ where: query.where,
135
+ distinct: normalizedDistinct,
136
+ select: { [selectField]: true },
137
+ take: effectiveLimit + 1,
138
+ }
139
+ const results = (await delegate.findMany(distinctArgs)) as unknown[]
140
+ if (results.length > effectiveLimit) {
141
+ console.warn(
142
+ LOG_PREFIX,
143
+ 'Distinct count exceeds ' + effectiveLimit + ', falling back to approximate total',
144
+ )
145
+ return runCount()
146
+ }
147
+ return results.length
148
+ }
149
+
150
+ return runCount()
151
+ }
@@ -0,0 +1,2 @@
1
+ export const NUMERIC_SCALAR_TYPES = new Set(['Int', 'BigInt', 'Float', 'Decimal'])
2
+ export const STRING_NUMERIC_TYPES = new Set(['BigInt', 'Decimal'])
@@ -0,0 +1,296 @@
1
+ import { HttpError, LOG_PREFIX, mapError } from './errorMapper'
2
+ import { isPlainObject } from './misc'
3
+ import { transformResult } from './operationRuntime'
4
+ import type {
5
+ ProgressiveStage,
6
+ ProgressiveStopResult,
7
+ ProgressivePatch,
8
+ } from './routeConfig'
9
+
10
+ export function acceptsEventStream(accept: string | undefined): boolean {
11
+ if (!accept) return false
12
+ return accept
13
+ .toLowerCase()
14
+ .split(',')
15
+ .some((entry) => entry.split(';')[0].trim() === 'text/event-stream')
16
+ }
17
+
18
+ const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
19
+
20
+ export function setByPath(target: Record<string, unknown>, path: string, value: unknown): boolean {
21
+ const parts = path.split('.')
22
+ if (parts.length === 0) return false
23
+ for (const p of parts) {
24
+ if (p === '' || UNSAFE_PATH_SEGMENTS.has(p)) return false
25
+ }
26
+ let cursor: Record<string, unknown> = target
27
+ for (let i = 0; i < parts.length - 1; i++) {
28
+ const part = parts[i]
29
+ const next = cursor[part]
30
+ if (!isPlainObject(next)) {
31
+ if (process.env.NODE_ENV !== 'production') {
32
+ console.warn(
33
+ LOG_PREFIX,
34
+ 'Dropping patch for "' + path +
35
+ '": cannot traverse non-plain-object at segment "' + part + '"',
36
+ )
37
+ }
38
+ return false
39
+ }
40
+ cursor = next
41
+ }
42
+ cursor[parts[parts.length - 1]] = value
43
+ return true
44
+ }
45
+
46
+ export type EventEmitterLike = {
47
+ on?: (event: string, listener: () => void) => unknown
48
+ off?: (event: string, listener: () => void) => unknown
49
+ removeListener?: (event: string, listener: () => void) => unknown
50
+ destroyed?: boolean
51
+ }
52
+
53
+ export type SseWritable = {
54
+ statusCode: number
55
+ setHeader: (name: string, value: string) => unknown
56
+ flushHeaders?: () => unknown
57
+ flush?: () => unknown
58
+ write: (chunk: string) => unknown
59
+ end: () => unknown
60
+ writableEnded: boolean
61
+ destroyed: boolean
62
+ }
63
+
64
+ export function removeReqCloseListener(req: EventEmitterLike, listener: () => void): void {
65
+ if (typeof req.off === 'function') {
66
+ req.off('close', listener)
67
+ } else if (typeof req.removeListener === 'function') {
68
+ req.removeListener('close', listener)
69
+ }
70
+ }
71
+
72
+ export function initSSE(res: SseWritable): void {
73
+ res.statusCode = 200
74
+ res.setHeader('Content-Type', 'text/event-stream')
75
+ res.setHeader('Cache-Control', 'no-cache, no-transform')
76
+ res.setHeader('Connection', 'keep-alive')
77
+ res.setHeader('X-Accel-Buffering', 'no')
78
+ if (typeof res.flushHeaders === 'function') res.flushHeaders()
79
+ }
80
+
81
+ export function flushSSE(res: SseWritable): void {
82
+ if (typeof res.flush === 'function') {
83
+ try { res.flush() } catch {}
84
+ }
85
+ }
86
+
87
+ export function sendSSE(res: SseWritable, payload: unknown): boolean {
88
+ if (res.writableEnded || res.destroyed) return false
89
+ try {
90
+ res.write('data: ' + JSON.stringify(transformResult(payload)) + '\n\n')
91
+ flushSSE(res)
92
+ return true
93
+ } catch (err) {
94
+ console.error(LOG_PREFIX, 'failed to send SSE event:', err)
95
+ return false
96
+ }
97
+ }
98
+
99
+ export function sendSSEProgress(res: SseWritable, stage: string, completed: number, total: number): boolean {
100
+ return sendSSE(res, { type: 'progress', stage, completed, total })
101
+ }
102
+
103
+ export function sendSSEField(res: SseWritable, key: string, value: unknown): boolean {
104
+ return sendSSE(res, { type: 'field', key, value })
105
+ }
106
+
107
+ export function sendSSEResult(res: SseWritable, data: unknown): boolean {
108
+ return sendSSE(res, { type: 'result', data })
109
+ }
110
+
111
+ export function sendSSERootArray(res: SseWritable, rows: unknown[]): boolean {
112
+ return sendSSE(res, { type: 'rootArray', data: rows })
113
+ }
114
+
115
+ export function sendSSERelationBatch(
116
+ res: SseWritable,
117
+ relationPath: string,
118
+ values: unknown[],
119
+ ): boolean {
120
+ return sendSSE(res, { type: 'relationBatch', relationPath, values })
121
+ }
122
+
123
+ export function sendSSENestedRelationBatch(
124
+ res: SseWritable,
125
+ relationPath: string,
126
+ depth: number,
127
+ attachments: Array<{ locator: Array<number | string>; value: unknown }>,
128
+ ): boolean {
129
+ return sendSSE(res, { type: 'nestedRelationBatch', relationPath, depth, attachments })
130
+ }
131
+
132
+ export function sendSSEPageMeta(
133
+ res: SseWritable,
134
+ total: number,
135
+ hasMore: boolean,
136
+ ): boolean {
137
+ return sendSSE(res, { type: 'pageMeta', total, hasMore })
138
+ }
139
+
140
+ export function sendSSEError(res: SseWritable, message: string): boolean {
141
+ if (res.writableEnded || res.destroyed) return false
142
+ try {
143
+ res.write('data: ' + JSON.stringify({ type: 'error', message }) + '\n\n')
144
+ flushSSE(res)
145
+ return true
146
+ } catch (err) {
147
+ console.error(LOG_PREFIX, 'failed to send SSE error event:', err)
148
+ return false
149
+ }
150
+ }
151
+
152
+ export function safeSendError(res: SseWritable, message: string): void {
153
+ if (!res.writableEnded && !res.destroyed) {
154
+ sendSSEError(res, message)
155
+ }
156
+ }
157
+
158
+ type IntervalHandle = ReturnType<typeof setInterval>
159
+
160
+ export function startSSEKeepalive(res: SseWritable, intervalMs: number = 15000): IntervalHandle {
161
+ const handle = setInterval(() => {
162
+ if (res.writableEnded || res.destroyed) return
163
+ try {
164
+ res.write(': keepalive\n\n')
165
+ flushSSE(res)
166
+ } catch {}
167
+ }, intervalMs)
168
+ const maybeUnref = (handle as unknown as { unref?: () => void }).unref
169
+ if (typeof maybeUnref === 'function') maybeUnref.call(handle)
170
+ return handle
171
+ }
172
+
173
+ export function endSSE(res: SseWritable, keepaliveHandle: IntervalHandle | null): void {
174
+ if (keepaliveHandle) {
175
+ clearInterval(keepaliveHandle)
176
+ }
177
+ if (!res.writableEnded && !res.destroyed) {
178
+ try { res.end() } catch {}
179
+ }
180
+ }
181
+
182
+ export function emitTerminalSSEError(res: SseWritable, message: string): void {
183
+ let keepalive: IntervalHandle | null = null
184
+ try {
185
+ initSSE(res)
186
+ keepalive = startSSEKeepalive(res)
187
+ sendSSEError(res, message)
188
+ } finally {
189
+ endSSE(res, keepalive)
190
+ }
191
+ }
192
+
193
+ export interface WithSSEOptions {
194
+ res: SseWritable
195
+ signal?: AbortSignal
196
+ label: string
197
+ }
198
+
199
+ export async function withSSE(
200
+ options: WithSSEOptions,
201
+ fn: () => Promise<void>,
202
+ ): Promise<void> {
203
+ const { res, signal, label } = options
204
+ let keepalive: IntervalHandle | null = null
205
+ const isClientGone = () =>
206
+ signal?.aborted === true || res.writableEnded || res.destroyed
207
+
208
+ try {
209
+ initSSE(res)
210
+ keepalive = startSSEKeepalive(res)
211
+ if (isClientGone()) return
212
+ await fn()
213
+ } catch (err) {
214
+ if (isClientGone()) return
215
+ console.error(LOG_PREFIX, label + ' dispatch error:', err)
216
+ safeSendError(res, mapError(err).message)
217
+ } finally {
218
+ endSSE(res, keepalive)
219
+ }
220
+ }
221
+
222
+ export interface RunSingleResultSSEOptions {
223
+ req: EventEmitterLike
224
+ res: SseWritable
225
+ coreQueryFn: () => Promise<unknown>
226
+ }
227
+
228
+ export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Promise<void> {
229
+ const { req, res, coreQueryFn } = options
230
+ await withSSE({ res, label: 'single-result' }, async () => {
231
+ if (req.destroyed) return
232
+ const data = await coreQueryFn()
233
+ if (res.writableEnded || res.destroyed) return
234
+ sendSSEResult(res, data)
235
+ })
236
+ }
237
+
238
+ function isStopResult(value: unknown): value is ProgressiveStopResult<unknown> {
239
+ return typeof value === 'object' && value !== null && (value as { stop?: unknown }).stop === true
240
+ }
241
+
242
+ export interface RunProgressiveOptions {
243
+ req: EventEmitterLike
244
+ res: SseWritable
245
+ ctx: unknown
246
+ prisma: unknown
247
+ variant: string
248
+ stages: string[]
249
+ stageRegistry: Record<string, ProgressiveStage>
250
+ }
251
+
252
+ export async function runProgressiveEndpoint(options: RunProgressiveOptions): Promise<void> {
253
+ const { req, res, ctx, prisma, variant, stages, stageRegistry } = options
254
+ const controller = new AbortController()
255
+ const onClose = () => controller.abort()
256
+ if (typeof req.on === 'function') req.on('close', onClose)
257
+
258
+ const accumulated: Record<string, unknown> = {}
259
+ const signal = controller.signal
260
+
261
+ try {
262
+ await withSSE({ res, signal, label: 'stage' }, async () => {
263
+ sendSSEProgress(res, 'start', 0, stages.length)
264
+
265
+ for (let i = 0; i < stages.length; i++) {
266
+ if (res.writableEnded || res.destroyed || signal.aborted) return
267
+ const stageName = stages[i]
268
+ const stage = stageRegistry[stageName]
269
+ if (!stage) throw new HttpError(500, 'Missing progressive stage: ' + stageName)
270
+
271
+ const result = await stage({ ctx, req, res, prisma, variant, accumulated, signal })
272
+ if (res.writableEnded || res.destroyed) return
273
+
274
+ if (isStopResult(result)) {
275
+ sendSSEResult(res, result.data)
276
+ return
277
+ }
278
+
279
+ const patches = Array.isArray(result) ? result : result ? [result] : []
280
+ for (const patch of patches) {
281
+ if (!patch || typeof patch !== 'object') continue
282
+ const p = patch as ProgressivePatch
283
+ if (typeof p.key !== 'string') continue
284
+ if (!('value' in p)) continue
285
+ const applied = setByPath(accumulated, p.key, p.value)
286
+ if (applied) sendSSEField(res, p.key, p.value)
287
+ }
288
+ sendSSEProgress(res, stageName, i + 1, stages.length)
289
+ }
290
+ if (res.writableEnded || res.destroyed) return
291
+ sendSSEResult(res, accumulated)
292
+ })
293
+ } finally {
294
+ removeReqCloseListener(req, onClose)
295
+ }
296
+ }
@@ -1,44 +1,7 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
2
  import { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
-
5
- const CORE_NAME_MAP: Record<string, string> = {
6
- delete: 'deleteUnique',
7
- }
8
-
9
- function coreFnName(op: string): string {
10
- return CORE_NAME_MAP[op] || op
11
- }
12
-
13
- const READ_OPS = [
14
- 'findMany',
15
- 'findFirst',
16
- 'findFirstOrThrow',
17
- 'findUnique',
18
- 'findUniqueOrThrow',
19
- 'findManyPaginated',
20
- 'aggregate',
21
- 'count',
22
- 'groupBy',
23
- ]
24
-
25
- const WRITE_OPS = [
26
- 'create',
27
- 'createMany',
28
- 'createManyAndReturn',
29
- 'update',
30
- 'updateMany',
31
- 'updateManyAndReturn',
32
- 'upsert',
33
- 'delete',
34
- 'deleteMany',
35
- ]
36
-
37
- const CREATED_OPS = new Set([
38
- 'create',
39
- 'createMany',
40
- 'createManyAndReturn',
41
- ])
4
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
42
5
 
43
6
  export function generateFastifyHandler(options: {
44
7
  model: DMMF.Model
@@ -47,31 +10,34 @@ export function generateFastifyHandler(options: {
47
10
  const ext = importExt(options.importStyle)
48
11
  const modelName = options.model.name
49
12
 
50
- const readHandlers = READ_OPS.map((op) => {
51
- const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
13
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
14
+ const writeOps = OPERATION_METADATA.filter(
15
+ (m) => (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
16
+ )
17
+
18
+ const readHandlers = readOps.map((meta) => {
19
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
52
20
  return `
53
21
  export async function ${exportName}(
54
22
  request: FastifyRequest,
55
23
  _reply: FastifyReply,
56
24
  ): Promise<void> {
57
- const data = await core.${coreFnName(op)}(buildContext(request))
25
+ const data = await core.${meta.coreName}(buildContext(request))
58
26
  ;(request as FastifyExtended).resultData = data
59
27
  }`
60
28
  }).join('\n')
61
29
 
62
- const writeHandlers = WRITE_OPS.map((op) => {
63
- const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
64
- const statusCode = CREATED_OPS.has(op) ? 201 : 200
65
-
30
+ const writeHandlers = writeOps.map((meta) => {
31
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
66
32
  return `
67
33
  export async function ${exportName}(
68
34
  request: FastifyRequest,
69
35
  _reply: FastifyReply,
70
36
  ): Promise<void> {
71
- const data = await core.${coreFnName(op)}(buildContext(request))
37
+ const data = await core.${meta.coreName}(buildContext(request))
72
38
  const ext = request as FastifyExtended
73
39
  ext.resultData = data
74
- ext.resultStatus = ${statusCode}
40
+ ext.resultStatus = ${meta.successStatus}
75
41
  }`
76
42
  }).join('\n')
77
43
 
@@ -1,44 +1,7 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
2
  import { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
-
5
- const CORE_NAME_MAP: Record<string, string> = {
6
- delete: 'deleteUnique',
7
- }
8
-
9
- function coreFnName(op: string): string {
10
- return CORE_NAME_MAP[op] || op
11
- }
12
-
13
- const READ_OPS = [
14
- 'findMany',
15
- 'findFirst',
16
- 'findFirstOrThrow',
17
- 'findUnique',
18
- 'findUniqueOrThrow',
19
- 'findManyPaginated',
20
- 'aggregate',
21
- 'count',
22
- 'groupBy',
23
- ]
24
-
25
- const WRITE_OPS = [
26
- 'create',
27
- 'createMany',
28
- 'createManyAndReturn',
29
- 'update',
30
- 'updateMany',
31
- 'updateManyAndReturn',
32
- 'upsert',
33
- 'delete',
34
- 'deleteMany',
35
- ]
36
-
37
- const CREATED_OPS = new Set([
38
- 'create',
39
- 'createMany',
40
- 'createManyAndReturn',
41
- ])
4
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
42
5
 
43
6
  export function generateHonoHandler(options: {
44
7
  model: DMMF.Model
@@ -47,24 +10,27 @@ export function generateHonoHandler(options: {
47
10
  const ext = importExt(options.importStyle)
48
11
  const modelName = options.model.name
49
12
 
50
- const readHandlers = READ_OPS.map((op) => {
51
- const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
13
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
14
+ const writeOps = OPERATION_METADATA.filter(
15
+ (m) => (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
16
+ )
17
+
18
+ const readHandlers = readOps.map((meta) => {
19
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
52
20
  return `
53
21
  export async function ${exportName}(c: HandlerContext): Promise<void> {
54
- const data = await core.${coreFnName(op)}(buildContext(c))
22
+ const data = await core.${meta.coreName}(buildContext(c))
55
23
  c.set('resultData', data)
56
24
  }`
57
25
  }).join('\n')
58
26
 
59
- const writeHandlers = WRITE_OPS.map((op) => {
60
- const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
61
- const statusCode = CREATED_OPS.has(op) ? 201 : 200
62
-
27
+ const writeHandlers = writeOps.map((meta) => {
28
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
63
29
  return `
64
30
  export async function ${exportName}(c: HandlerContext): Promise<void> {
65
- const data = await core.${coreFnName(op)}(buildContext(c))
31
+ const data = await core.${meta.coreName}(buildContext(c))
66
32
  c.set('resultData', data)
67
- c.set('resultStatus', ${statusCode})
33
+ c.set('resultStatus', ${meta.successStatus})
68
34
  }`
69
35
  }).join('\n')
70
36