prisma-generator-express 1.19.0 → 1.20.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator for Hono CRUD API with OpenAPI documentation",
4
- "version": "1.19.0",
4
+ "version": "1.20.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
@@ -71,11 +71,9 @@
71
71
  ],
72
72
  "files": [
73
73
  "dist/**/*",
74
- "src/copy/**/*",
75
- "src/client/**/*",
76
- "!**/.DS_Store",
77
- "README.md",
78
- "LICENSE"
74
+ "src/**/*",
75
+ "../../README.md",
76
+ "../../LICENSE"
79
77
  ],
80
78
  "release": {
81
79
  "branches": [
package/src/bin.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '.'
@@ -0,0 +1 @@
1
+ export const GENERATOR_NAME = 'prisma-generator-express'
@@ -0,0 +1,78 @@
1
+ import { GeneratorOptions } from '@prisma/generator-helper'
2
+ import path from 'path'
3
+
4
+ function findClientGenerator(options: GeneratorOptions) {
5
+ const byName = options.otherGenerators.find((gen) => gen.name === 'client')
6
+ if (byName) return byName
7
+
8
+ const byProvider = options.otherGenerators.find(
9
+ (gen) =>
10
+ gen.provider.value === 'prisma-client-js' ||
11
+ gen.provider.value === '@prisma/client' ||
12
+ gen.provider.value === 'prisma-client',
13
+ )
14
+ if (byProvider) return byProvider
15
+
16
+ const withOutput = options.otherGenerators.find(
17
+ (gen) =>
18
+ gen.output?.value?.includes('prisma') ||
19
+ gen.output?.value?.includes('client'),
20
+ )
21
+ return withOutput || null
22
+ }
23
+
24
+ function getRelativeImportPath(
25
+ fromDir: string,
26
+ clientOutputPath: string,
27
+ ): string {
28
+ let relativeImportPath = path.relative(fromDir, clientOutputPath)
29
+ relativeImportPath = relativeImportPath.split(path.sep).join(path.posix.sep)
30
+ if (!relativeImportPath.startsWith('.')) {
31
+ relativeImportPath = './' + relativeImportPath
32
+ }
33
+ return relativeImportPath
34
+ }
35
+
36
+ export function generateImportPrismaStatement(
37
+ generatorOptions: GeneratorOptions,
38
+ ): string {
39
+ const clientGenerator = findClientGenerator(generatorOptions)
40
+
41
+ if (!clientGenerator || !clientGenerator.output?.value) {
42
+ throw new Error(
43
+ 'Prisma client generator not found. Ensure a generator with provider "prisma-client-js" exists in your schema.',
44
+ )
45
+ }
46
+
47
+ const outputValue = generatorOptions.generator.output?.value
48
+ if (!outputValue) {
49
+ throw new Error('Generator output path not defined.')
50
+ }
51
+
52
+ const subDir = path.join(outputValue, '_relative')
53
+ const outputPath = getRelativeImportPath(subDir, clientGenerator.output.value)
54
+
55
+ return `import { Prisma, PrismaClient } from '${outputPath}';\n`
56
+ }
57
+
58
+ export function getRelativeClientPath(
59
+ generatorOptions: GeneratorOptions,
60
+ modelName: string,
61
+ ): string {
62
+ const clientGenerator = findClientGenerator(generatorOptions)
63
+
64
+ if (!clientGenerator || !clientGenerator.output?.value) {
65
+ throw new Error(
66
+ 'Prisma client generator not found. Ensure a generator with provider "prisma-client-js" exists in your schema.',
67
+ )
68
+ }
69
+
70
+ const outputValue = generatorOptions.generator.output?.value
71
+ if (!outputValue) {
72
+ throw new Error('Generator output path not defined.')
73
+ }
74
+
75
+ const routerDirPath = path.join(outputValue, modelName)
76
+
77
+ return getRelativeImportPath(routerDirPath, clientGenerator.output.value)
78
+ }
@@ -0,0 +1,138 @@
1
+ import { GeneratorOptions } from '@prisma/generator-helper'
2
+
3
+ export function generateQueryBuilderHelper(options: GeneratorOptions): string {
4
+ const schemaPath = options.schemaPath
5
+ ? JSON.stringify(options.schemaPath)
6
+ : "require('path').resolve(process.cwd(), 'prisma/schema.prisma')"
7
+
8
+ return `import { spawn } from 'child_process'
9
+ import { resolve, join, dirname } from 'path'
10
+ import { existsSync, readFileSync } from 'fs'
11
+ import { createRequire } from 'module'
12
+ import type { ChildProcess } from 'child_process'
13
+
14
+ let _process: ChildProcess | null = null
15
+ let _stopping = false
16
+ let _cleanupRegistered = false
17
+
18
+ export interface QueryBuilderOptions {
19
+ port?: number
20
+ host?: string
21
+ schemaPath?: string
22
+ databaseUrl?: string
23
+ }
24
+
25
+ function findCliPath(): string | null {
26
+ try {
27
+ const req = createRequire(resolve(process.cwd(), 'package.json'))
28
+ const pkgJsonPath = req.resolve('prisma-query-builder-ui/package.json')
29
+ const pkgDir = dirname(pkgJsonPath)
30
+ const cliPath = join(pkgDir, 'bin', 'cli.js')
31
+ if (existsSync(cliPath)) return cliPath
32
+ } catch {}
33
+
34
+ let dir = process.cwd()
35
+ const root = resolve(dir, '/')
36
+ while (dir !== root) {
37
+ const candidate = join(dir, 'node_modules', 'prisma-query-builder-ui', 'bin', 'cli.js')
38
+ if (existsSync(candidate)) return candidate
39
+ dir = dirname(dir)
40
+ }
41
+
42
+ return null
43
+ }
44
+
45
+ export function startQueryBuilder(options: QueryBuilderOptions = {}): void {
46
+ if (_process) return
47
+
48
+ const env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
49
+
50
+ if (env.NODE_ENV === 'production') return
51
+
52
+ const cliPath = findCliPath()
53
+ if (!cliPath) {
54
+ console.warn('[query-builder] prisma-query-builder-ui not found. Install: npm install prisma-query-builder-ui')
55
+ return
56
+ }
57
+
58
+ const port = options.port || 5173
59
+ const host = options.host || 'localhost'
60
+ const schemaPath = options.schemaPath || ${schemaPath}
61
+ const databaseUrl = options.databaseUrl || env.DATABASE_URL || ''
62
+
63
+ if (!existsSync(schemaPath)) {
64
+ console.error('[query-builder] Schema file not found: ' + schemaPath)
65
+ return
66
+ }
67
+
68
+ let schemaContent: string
69
+ try {
70
+ schemaContent = readFileSync(schemaPath, 'utf-8')
71
+ } catch (err) {
72
+ console.error('[query-builder] Failed to read schema:', err)
73
+ return
74
+ }
75
+
76
+ const schemaCwd = dirname(resolve(schemaPath))
77
+
78
+ _process = spawn(process.execPath, [cliPath], {
79
+ stdio: 'inherit',
80
+ env: {
81
+ ...env,
82
+ PORT: String(port),
83
+ HOST: host,
84
+ PRISMA_QUERY_BUILDER_MODE: 'embedded',
85
+ DISABLE_PERSISTENCE: 'true',
86
+ PRISMA_QUERY_BUILDER_SCHEMA_CONTENT: schemaContent,
87
+ PRISMA_QUERY_BUILDER_CWD: schemaCwd,
88
+ DATABASE_URL: databaseUrl,
89
+ },
90
+ })
91
+
92
+ _process.on('error', (err) => {
93
+ console.error('[query-builder] Failed to start:', err.message)
94
+ _process = null
95
+ })
96
+
97
+ _process.on('exit', (code) => {
98
+ const wasStopping = _stopping
99
+ _stopping = false
100
+ _process = null
101
+ if (!wasStopping && code !== 0) {
102
+ console.warn('[query-builder] Process exited with code ' + code)
103
+ }
104
+ })
105
+
106
+ if (!_cleanupRegistered) {
107
+ _cleanupRegistered = true
108
+
109
+ process.on('exit', () => {
110
+ stopQueryBuilder()
111
+ })
112
+
113
+ const handleSigint = () => {
114
+ stopQueryBuilder()
115
+ process.removeListener('SIGINT', handleSigint)
116
+ process.kill(process.pid, 'SIGINT')
117
+ }
118
+
119
+ const handleSigterm = () => {
120
+ stopQueryBuilder()
121
+ process.removeListener('SIGTERM', handleSigterm)
122
+ process.kill(process.pid, 'SIGTERM')
123
+ }
124
+
125
+ process.on('SIGINT', handleSigint)
126
+ process.on('SIGTERM', handleSigterm)
127
+ }
128
+
129
+ console.log('[query-builder] Starting on http://' + host + ':' + port)
130
+ }
131
+
132
+ export function stopQueryBuilder(): void {
133
+ if (!_process || _process.killed) return
134
+ _stopping = true
135
+ _process.kill()
136
+ }
137
+ `
138
+ }
@@ -0,0 +1,352 @@
1
+ import { DMMF } from '@prisma/generator-helper'
2
+
3
+ export function generateRouterFunction({
4
+ model,
5
+ enums,
6
+ relativeClientPath,
7
+ }: {
8
+ model: DMMF.Model
9
+ enums: DMMF.DatamodelEnum[]
10
+ relativeClientPath: string
11
+ }): string {
12
+ const modelName = model.name
13
+ const modelNameLower = modelName.toLowerCase()
14
+ const routerFunctionName = `${modelName}Router`
15
+
16
+ const fieldsMeta = model.fields.map((f) => ({
17
+ name: f.name,
18
+ kind: f.kind,
19
+ type: f.type,
20
+ isList: f.isList,
21
+ isRequired: f.isRequired,
22
+ hasDefaultValue: f.hasDefaultValue,
23
+ isUpdatedAt: f.isUpdatedAt ?? false,
24
+ documentation: f.documentation,
25
+ relationFromFields: f.relationFromFields,
26
+ }))
27
+
28
+ const referencedEnumTypes = new Set(
29
+ model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
30
+ )
31
+
32
+ const enumsMeta = enums
33
+ .filter((e) => referencedEnumTypes.has(e.name))
34
+ .map((e) => ({
35
+ name: e.name,
36
+ values: e.values.map((v) => ({ name: v.name })),
37
+ }))
38
+
39
+ return `import express, { Request, Response, NextFunction, RequestHandler } from 'express'
40
+ import type { PrismaClient } from '${relativeClientPath}'
41
+ import {
42
+ ${modelName}FindUnique,
43
+ ${modelName}FindUniqueOrThrow,
44
+ ${modelName}FindFirst,
45
+ ${modelName}FindFirstOrThrow,
46
+ ${modelName}FindMany,
47
+ ${modelName}FindManyPaginated,
48
+ ${modelName}Create,
49
+ ${modelName}CreateMany,
50
+ ${modelName}CreateManyAndReturn,
51
+ ${modelName}Update,
52
+ ${modelName}UpdateMany,
53
+ ${modelName}UpdateManyAndReturn,
54
+ ${modelName}Upsert,
55
+ ${modelName}Delete,
56
+ ${modelName}DeleteMany,
57
+ ${modelName}Aggregate,
58
+ ${modelName}Count,
59
+ ${modelName}GroupBy
60
+ } from './${modelName}Handlers'
61
+ import type { RouteConfig } from '../routeConfig'
62
+ import { parseQueryParams } from '../parseQueryParams'
63
+ import { buildModelOpenApi } from '../buildModelOpenApi'
64
+
65
+ const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
66
+
67
+ const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
68
+
69
+ const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
70
+
71
+ const defaultOpConfig = {
72
+ before: [] as RequestHandler[],
73
+ after: [] as RequestHandler[],
74
+ }
75
+
76
+ function normalizePrefix(p: string): string {
77
+ if (!p) return ''
78
+ let result = p
79
+ if (!result.startsWith('/')) result = '/' + result
80
+ while (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1)
81
+ if (result === '/') return ''
82
+ return result
83
+ }
84
+
85
+ function transformResult(value: unknown): unknown {
86
+ if (value === null || value === undefined) return value
87
+ if (typeof value === 'bigint') return value.toString()
88
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
89
+ return value.toString('base64')
90
+ }
91
+ if (value instanceof Uint8Array) {
92
+ let binary = ''
93
+ for (let i = 0; i < value.length; i++) binary += String.fromCharCode(value[i])
94
+ return btoa(binary)
95
+ }
96
+ if (value instanceof Date) return value
97
+ if (Array.isArray(value)) return value.map(transformResult)
98
+ if (typeof value === 'object') {
99
+ const proto = Object.getPrototypeOf(value)
100
+ if (proto !== Object.prototype && proto !== null) return value
101
+ const out: Record<string, unknown> = {}
102
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
103
+ out[k] = transformResult(v)
104
+ }
105
+ return out
106
+ }
107
+ return value
108
+ }
109
+
110
+ function isQueryBuilderEnabled(config: RouteConfig): boolean {
111
+ if (config.queryBuilder === false) return false
112
+ if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
113
+ if (_env.NODE_ENV === 'production') return false
114
+ return true
115
+ }
116
+
117
+ function getQueryBuilderConfig(config: RouteConfig) {
118
+ if (config.queryBuilder === false) return null
119
+ if (typeof config.queryBuilder === 'object') return config.queryBuilder
120
+ return {}
121
+ }
122
+
123
+ export function ${routerFunctionName}(config: RouteConfig = {}) {
124
+ const router = express.Router()
125
+
126
+ router.use(express.json())
127
+
128
+ const customPrefix = normalizePrefix(config.customUrlPrefix || '')
129
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
130
+ const basePath = customPrefix + modelPrefix
131
+
132
+ const openApiDisabled = config.disableOpenApi === true
133
+ || (config.disableOpenApi !== false && (
134
+ _env.DISABLE_OPENAPI === 'true'
135
+ || _env.NODE_ENV === 'production'
136
+ ))
137
+
138
+ const qbEnabled = isQueryBuilderEnabled(config)
139
+
140
+ if (qbEnabled) {
141
+ const qbConfig = getQueryBuilderConfig(config)
142
+ if (qbConfig) {
143
+ import('../queryBuilder').then(mod => mod.startQueryBuilder(qbConfig)).catch(() => {})
144
+ }
145
+ }
146
+
147
+ const parseQuery: RequestHandler = (req, res, next) => {
148
+ const rawQuery = req.query
149
+ if (rawQuery && Object.keys(rawQuery).length > 0) {
150
+ res.locals.parsedQuery = parseQueryParams(rawQuery as Record<string, unknown>)
151
+ }
152
+ next()
153
+ }
154
+
155
+ const setShape = (opConfig: any): RequestHandler => {
156
+ return (req, res, next) => {
157
+ res.locals.routeConfig = config
158
+ if (opConfig.shape) {
159
+ res.locals.guardShape = opConfig.shape
160
+ const caller = config.guard?.resolveVariant?.(req)
161
+ ?? req.get(config.guard?.variantHeader || 'x-api-variant')
162
+ ?? undefined
163
+ if (caller) {
164
+ res.locals.guardCaller = caller
165
+ }
166
+ }
167
+ next()
168
+ }
169
+ }
170
+
171
+ const respond: RequestHandler = (_req, res) => {
172
+ const data = res.locals.data
173
+ if (data === undefined) {
174
+ return res.status(500).json({ message: 'No data set by handler' })
175
+ }
176
+ return res.json(transformResult(data))
177
+ }
178
+
179
+ const respondCreated: RequestHandler = (_req, res) => {
180
+ const data = res.locals.data
181
+ if (data === undefined) {
182
+ return res.status(500).json({ message: 'No data set by handler' })
183
+ }
184
+ return res.status(201).json(transformResult(data))
185
+ }
186
+
187
+ if (!openApiDisabled) {
188
+ const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
189
+ const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
190
+
191
+ router.get(openapiJsonPath, (_req, res) => {
192
+ const spec = buildModelOpenApi(
193
+ '${modelName}',
194
+ MODEL_FIELDS as any,
195
+ MODEL_ENUMS as any,
196
+ config,
197
+ { format: 'json' }
198
+ )
199
+ res.json(spec)
200
+ })
201
+
202
+ router.get(openapiYamlPath, (_req, res) => {
203
+ const spec = buildModelOpenApi(
204
+ '${modelName}',
205
+ MODEL_FIELDS as any,
206
+ MODEL_ENUMS as any,
207
+ config,
208
+ { format: 'yaml' }
209
+ )
210
+ res.type('application/yaml').send(spec as string)
211
+ })
212
+ }
213
+
214
+ if (config.enableAll || config.findFirst) {
215
+ const opConfig = config.findFirst || defaultOpConfig
216
+ const { before = [], after = [] } = opConfig
217
+ const path = basePath ? \`\${basePath}/first\` : '/first'
218
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindFirst as RequestHandler, ...after, respond)
219
+ }
220
+
221
+ if (config.enableAll || config.findFirstOrThrow) {
222
+ const opConfig = config.findFirstOrThrow || defaultOpConfig
223
+ const { before = [], after = [] } = opConfig
224
+ const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
225
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
226
+ }
227
+
228
+ if (config.enableAll || config.findManyPaginated) {
229
+ const opConfig = config.findManyPaginated || defaultOpConfig
230
+ const { before = [], after = [] } = opConfig
231
+ const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
232
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
233
+ }
234
+
235
+ if (config.enableAll || config.aggregate) {
236
+ const opConfig = config.aggregate || defaultOpConfig
237
+ const { before = [], after = [] } = opConfig
238
+ const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
239
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}Aggregate as RequestHandler, ...after, respond)
240
+ }
241
+
242
+ if (config.enableAll || config.count) {
243
+ const opConfig = config.count || defaultOpConfig
244
+ const { before = [], after = [] } = opConfig
245
+ const path = basePath ? \`\${basePath}/count\` : '/count'
246
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}Count as RequestHandler, ...after, respond)
247
+ }
248
+
249
+ if (config.enableAll || config.groupBy) {
250
+ const opConfig = config.groupBy || defaultOpConfig
251
+ const { before = [], after = [] } = opConfig
252
+ const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
253
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}GroupBy as RequestHandler, ...after, respond)
254
+ }
255
+
256
+ if (config.enableAll || config.findUniqueOrThrow) {
257
+ const opConfig = config.findUniqueOrThrow || defaultOpConfig
258
+ const { before = [], after = [] } = opConfig
259
+ const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
260
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
261
+ }
262
+
263
+ if (config.enableAll || config.findUnique) {
264
+ const opConfig = config.findUnique || defaultOpConfig
265
+ const { before = [], after = [] } = opConfig
266
+ const path = basePath ? \`\${basePath}/unique\` : '/unique'
267
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindUnique as RequestHandler, ...after, respond)
268
+ }
269
+
270
+ if (config.enableAll || config.findMany) {
271
+ const opConfig = config.findMany || defaultOpConfig
272
+ const { before = [], after = [] } = opConfig
273
+ const path = basePath || '/'
274
+ router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindMany as RequestHandler, ...after, respond)
275
+ }
276
+
277
+ if (config.enableAll || config.createManyAndReturn) {
278
+ const opConfig = config.createManyAndReturn || defaultOpConfig
279
+ const { before = [], after = [] } = opConfig
280
+ const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
281
+ router.post(path, setShape(opConfig), ...before, ${modelName}CreateManyAndReturn as RequestHandler, ...after, respondCreated)
282
+ }
283
+
284
+ if (config.enableAll || config.createMany) {
285
+ const opConfig = config.createMany || defaultOpConfig
286
+ const { before = [], after = [] } = opConfig
287
+ const path = basePath ? \`\${basePath}/many\` : '/many'
288
+ router.post(path, setShape(opConfig), ...before, ${modelName}CreateMany as RequestHandler, ...after, respondCreated)
289
+ }
290
+
291
+ if (config.enableAll || config.create) {
292
+ const opConfig = config.create || defaultOpConfig
293
+ const { before = [], after = [] } = opConfig
294
+ const path = basePath || '/'
295
+ router.post(path, setShape(opConfig), ...before, ${modelName}Create as RequestHandler, ...after, respondCreated)
296
+ }
297
+
298
+ if (config.enableAll || config.updateManyAndReturn) {
299
+ const opConfig = config.updateManyAndReturn || defaultOpConfig
300
+ const { before = [], after = [] } = opConfig
301
+ const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
302
+ router.put(path, setShape(opConfig), ...before, ${modelName}UpdateManyAndReturn as RequestHandler, ...after, respond)
303
+ }
304
+
305
+ if (config.enableAll || config.updateMany) {
306
+ const opConfig = config.updateMany || defaultOpConfig
307
+ const { before = [], after = [] } = opConfig
308
+ const path = basePath ? \`\${basePath}/many\` : '/many'
309
+ router.put(path, setShape(opConfig), ...before, ${modelName}UpdateMany as RequestHandler, ...after, respond)
310
+ }
311
+
312
+ if (config.enableAll || config.update) {
313
+ const opConfig = config.update || defaultOpConfig
314
+ const { before = [], after = [] } = opConfig
315
+ const path = basePath || '/'
316
+ router.put(path, setShape(opConfig), ...before, ${modelName}Update as RequestHandler, ...after, respond)
317
+ }
318
+
319
+ if (config.enableAll || config.upsert) {
320
+ const opConfig = config.upsert || defaultOpConfig
321
+ const { before = [], after = [] } = opConfig
322
+ const path = basePath || '/'
323
+ router.patch(path, setShape(opConfig), ...before, ${modelName}Upsert as RequestHandler, ...after, respond)
324
+ }
325
+
326
+ if (config.enableAll || config.deleteMany) {
327
+ const opConfig = config.deleteMany || defaultOpConfig
328
+ const { before = [], after = [] } = opConfig
329
+ const path = basePath ? \`\${basePath}/many\` : '/many'
330
+ router.delete(path, setShape(opConfig), ...before, ${modelName}DeleteMany as RequestHandler, ...after, respond)
331
+ }
332
+
333
+ if (config.enableAll || config.delete) {
334
+ const opConfig = config.delete || defaultOpConfig
335
+ const { before = [], after = [] } = opConfig
336
+ const path = basePath || '/'
337
+ router.delete(path, setShape(opConfig), ...before, ${modelName}Delete as RequestHandler, ...after, respond)
338
+ }
339
+
340
+ router.use((err: any, _req: Request, res: Response, next: NextFunction) => {
341
+ const status = typeof err.status === 'number' ? err.status : 500
342
+ const message = err.message || 'Internal server error'
343
+ if (!res.headersSent) {
344
+ return res.status(status).json({ message })
345
+ }
346
+ next(err)
347
+ })
348
+
349
+ return router
350
+ }
351
+ `
352
+ }