prisma-generator-express 1.45.0 → 1.46.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 (70) hide show
  1. package/README.md +155 -30
  2. package/dist/client/encodeQueryParams.js +4 -0
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +4 -2
  5. package/dist/copy/misc.js +35 -24
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/generators/generateFastifyHandler.js +2 -4
  8. package/dist/generators/generateFastifyHandler.js.map +1 -1
  9. package/dist/generators/generateHonoHandler.js +2 -4
  10. package/dist/generators/generateHonoHandler.js.map +1 -1
  11. package/dist/generators/generateImportPrismaStatement.d.ts +0 -1
  12. package/dist/generators/generateImportPrismaStatement.js +2 -20
  13. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  14. package/dist/generators/generateOperationCore.js +1 -1
  15. package/dist/generators/generateQueryBuilderHelper.js +9 -0
  16. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  17. package/dist/generators/generateRelationMeta.js +0 -10
  18. package/dist/generators/generateRelationMeta.js.map +1 -1
  19. package/dist/generators/generateRouteConfigType.js +33 -12
  20. package/dist/generators/generateRouteConfigType.js.map +1 -1
  21. package/dist/generators/generateRouter.d.ts +0 -1
  22. package/dist/generators/generateRouter.js +75 -70
  23. package/dist/generators/generateRouter.js.map +1 -1
  24. package/dist/generators/generateRouterFastify.js +83 -89
  25. package/dist/generators/generateRouterFastify.js.map +1 -1
  26. package/dist/generators/generateRouterHono.js +257 -237
  27. package/dist/generators/generateRouterHono.js.map +1 -1
  28. package/dist/generators/generateUnifiedDocs.d.ts +2 -2
  29. package/dist/generators/generateUnifiedDocs.js +90 -252
  30. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  31. package/dist/generators/generateUnifiedHandler.js +2 -4
  32. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  33. package/dist/index.js +16 -8
  34. package/dist/index.js.map +1 -1
  35. package/dist/utils/copyFiles.js +3 -2
  36. package/dist/utils/copyFiles.js.map +1 -1
  37. package/dist/utils/strings.d.ts +0 -1
  38. package/dist/utils/strings.js +0 -9
  39. package/dist/utils/strings.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/client/encodeQueryParams.ts +7 -15
  42. package/src/copy/autoIncludePlanner.ts +4 -17
  43. package/src/copy/autoIncludeRuntime.ts +11 -19
  44. package/src/copy/buildModelOpenApi.ts +11 -14
  45. package/src/copy/docsRenderer.ts +8 -14
  46. package/src/copy/misc.ts +28 -23
  47. package/src/copy/operationRuntime.ts +61 -43
  48. package/src/copy/parseQueryParams.ts +5 -14
  49. package/src/copy/routeConfig.express.ts +24 -18
  50. package/src/copy/routeConfig.fastify.ts +1 -1
  51. package/src/copy/routeConfig.hono.ts +34 -6
  52. package/src/copy/routeConfig.ts +2 -2
  53. package/src/generators/generateFastifyHandler.ts +2 -5
  54. package/src/generators/generateHonoHandler.ts +2 -5
  55. package/src/generators/generateImportPrismaStatement.ts +3 -35
  56. package/src/generators/generateOperationCore.ts +1 -1
  57. package/src/generators/generateQueryBuilderHelper.ts +9 -0
  58. package/src/generators/generateRelationMeta.ts +0 -10
  59. package/src/generators/generateRouteConfigType.ts +34 -10
  60. package/src/generators/generateRouter.ts +75 -71
  61. package/src/generators/generateRouterFastify.ts +83 -89
  62. package/src/generators/generateRouterHono.ts +257 -237
  63. package/src/generators/generateUnifiedDocs.ts +89 -267
  64. package/src/generators/generateUnifiedHandler.ts +2 -4
  65. package/src/index.ts +45 -14
  66. package/src/utils/copyFiles.ts +2 -2
  67. package/src/utils/strings.ts +0 -8
  68. package/src/copy/createOutputValidatorMiddleware.ts +0 -47
  69. package/src/copy/createValidatorMiddleware.ts +0 -62
  70. package/src/copy/transformZod.ts +0 -139
@@ -1,314 +1,136 @@
1
- import { Target } from '../constants'
2
1
  import { ImportStyle } from '../utils/resolveImportStyle'
3
2
  import { importExt } from '../utils/importExt'
3
+ import type { Target } from '../constants'
4
4
 
5
5
  export function generateUnifiedDocs(
6
- models: string[],
7
- target: Target = 'express',
8
- importStyle: ImportStyle = 'none',
6
+ modelNames: string[],
7
+ target: Target,
8
+ importStyle: ImportStyle,
9
9
  ): string {
10
10
  const ext = importExt(importStyle)
11
+ if (target === 'fastify') return generateFastify(modelNames, ext)
12
+ if (target === 'hono') return generateHono(modelNames, ext)
13
+ return generateExpress(modelNames, ext)
14
+ }
11
15
 
12
- const imports = models
13
- .map((model) => `import { ${model}Docs } from './${model}/${model}Docs${ext}'`)
14
- .join('\n')
15
-
16
- const handlersEntries = models
17
- .map((model) => ` ${model}: ${model}Docs`)
18
- .join(',\n')
19
-
20
- const frameworkImport =
21
- target === 'fastify'
22
- ? `import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'`
23
- : target === 'hono'
24
- ? `import type { Hono, Context } from 'hono'`
25
- : `import type { Request, Response } from 'express'`
26
-
27
- const routeConfigImport = `import type { RouteConfig } from './routeConfig.target${ext}'`
28
-
29
- const handlerType =
30
- target === 'fastify'
31
- ? `(config: any) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>`
32
- : target === 'hono'
33
- ? `(config: any) => (c: Context) => Response | Promise<Response>`
34
- : `(config: any) => (req: Request, res: Response) => any`
35
-
36
- const combinedDocsReturn =
37
- target === 'fastify'
38
- ? generateFastifyCombinedDocs()
39
- : target === 'hono'
40
- ? generateHonoCombinedDocs()
41
- : generateExpressCombinedDocs()
42
-
43
- const registerDocs =
44
- target === 'fastify'
45
- ? generateFastifyRegisterDocs()
46
- : target === 'hono'
47
- ? generateHonoRegisterDocs()
48
- : generateExpressRegisterDocs()
49
-
50
- return `${imports}
51
- ${frameworkImport}
52
- ${routeConfigImport}
53
-
54
- const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
16
+ const renderIndexFn = `function renderIndex(title: string, modelNames: string[], basePath: string): string {
17
+ const links = modelNames
18
+ .map((n) => '<li><a href="' + basePath + '/' + n.toLowerCase() + '">' + n + '</a></li>')
19
+ .join('')
20
+
21
+ return '<!DOCTYPE html><html><head><meta charset="utf-8" /><title>' + title +
22
+ '</title><script src="https://cdn.tailwindcss.com"></script></head>' +
23
+ '<body class="m-0 bg-white text-gray-900 font-sans">' +
24
+ '<div class="max-w-[1120px] mx-auto px-5 pt-[30px] pb-20">' +
25
+ '<h1 class="text-xl font-black border-b-2 border-gray-900 pb-3">' + title + '</h1>' +
26
+ '<ul class="mt-4 list-disc pl-6 text-sm">' + links + '</ul>' +
27
+ '</div></body></html>'
28
+ }`
55
29
 
56
- const docsHandlers: Record<string, ${handlerType}> = {
57
- ${handlersEntries}
58
- }
30
+ function generateExpress(modelNames: string[], ext: string): string {
31
+ return `import type { Router } from 'express'
32
+ import { getEnv, removeTrailingSlash } from './misc${ext}'
59
33
 
60
- type DocsUI = 'docs' | 'scalar' | 'json' | 'yaml' | 'playground'
34
+ const _env = getEnv()
61
35
 
62
- interface ModelDocsConfig extends RouteConfig {
63
- docsTitle?: string
64
- docsUi?: DocsUI
65
- }
36
+ const MODELS = ${JSON.stringify(modelNames)} as const
66
37
 
67
- export interface CombinedDocsConfig {
68
- disableOpenApi?: boolean
38
+ interface UnifiedDocsConfig {
69
39
  title?: string
70
- description?: string
71
40
  basePath?: string
72
- version?: string
73
- modelConfigs: {
74
- [modelName: string]: ModelDocsConfig
75
- }
41
+ models?: string[]
42
+ disableInProduction?: boolean
76
43
  }
77
44
 
78
- function escapeHtml(input: string) {
79
- return input
80
- .replace(/&/g, '&amp;')
81
- .replace(/</g, '&lt;')
82
- .replace(/>/g, '&gt;')
83
- .replace(/"/g, '&quot;')
84
- .replace(/'/g, '&#039;')
85
- }
86
-
87
- function removeTrailingSlash(p: string): string {
88
- if (p === '/') return ''
89
- return p.endsWith('/') ? p.slice(0, -1) : p
90
- }
91
-
92
- function isOpenApiDisabled(disableOpenApi?: boolean) {
93
- if (disableOpenApi === true) return true
94
- if (disableOpenApi === false) return false
95
- return _env.DISABLE_OPENAPI === 'true' || _env.NODE_ENV === 'production'
96
- }
97
-
98
- function isPlaygroundAvailable(config?: ModelDocsConfig) {
99
- if (_env.NODE_ENV === 'production') return false
100
- if (!config) return true
101
- if (config.queryBuilder === false) return false
102
- if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
103
- return true
104
- }
45
+ export function registerUnifiedDocs(router: Router, config: UnifiedDocsConfig = {}): void {
46
+ const disabled = config.disableInProduction !== false && _env.NODE_ENV === 'production'
47
+ if (disabled) return
105
48
 
106
- function buildCombinedHtml(
107
- config: CombinedDocsConfig,
108
- registeredModels: string[],
109
- ) {
110
- const title = config.title || 'API Documentation'
111
- const description = config.description || ''
112
- const version = config.version || ''
113
49
  const basePath = removeTrailingSlash(config.basePath || '/docs')
114
- const generatedAt = new Date().toISOString()
115
-
116
- const modelRows = registeredModels.map((m) => {
117
- const lower = m.toLowerCase()
118
- const docsUrl = basePath + '/' + lower
119
- const scalarUrl = docsUrl + '?ui=scalar'
120
- const jsonUrl = docsUrl + '?ui=json'
121
- const yamlUrl = docsUrl + '?ui=yaml'
122
- const playgroundUrl = docsUrl + '?ui=playground'
123
- const modelCfg = config.modelConfigs[m]
124
- const modelPlayground = isPlaygroundAvailable(modelCfg)
125
- const playgroundLink = modelPlayground
126
- ? ', <a href="' + playgroundUrl + '" class="text-inherit underline">playground</a>'
127
- : ''
128
- return '<tr>' +
129
- '<td class="text-left py-2 px-2 border-b border-gray-300 align-top">' + escapeHtml(m) + '</td>' +
130
- '<td class="text-left py-2 px-2 border-b border-gray-300 align-top"><a href="' + docsUrl + '" class="text-inherit underline">' + escapeHtml(docsUrl) + '</a></td>' +
131
- '<td class="text-left py-2 px-2 border-b border-gray-300 align-top">' +
132
- '<a href="' + scalarUrl + '" class="text-inherit underline">scalar</a>, ' +
133
- '<a href="' + jsonUrl + '" class="text-inherit underline">json</a>, ' +
134
- '<a href="' + yamlUrl + '" class="text-inherit underline">yaml</a>' +
135
- playgroundLink +
136
- '</td>' +
137
- '</tr>'
138
- }).join('')
139
-
140
- const descriptionHtml = description
141
- ? '<div class="mt-1.5 text-gray-500 text-sm">' + escapeHtml(description) + '</div>'
142
- : ''
143
- const versionHtml = version
144
- ? '<div>Version: ' + escapeHtml(version) + '</div>'
145
- : ''
50
+ const enabled = config.models && config.models.length > 0
51
+ ? MODELS.filter((m) => config.models!.includes(m))
52
+ : MODELS
146
53
 
147
- return '<!DOCTYPE html>' +
148
- '<html lang="en">' +
149
- '<head>' +
150
- '<meta charset="utf-8" />' +
151
- '<meta name="viewport" content="width=device-width, initial-scale=1" />' +
152
- '<title>' + escapeHtml(title) + '</title>' +
153
- '<script src="https://cdn.tailwindcss.com"></' + 'script>' +
154
- '</head>' +
155
- '<body class="m-0 bg-white text-gray-900 font-serif leading-normal">' +
156
- '<div class="max-w-[980px] mx-auto px-7 pt-10 pb-16">' +
157
- '<div class="border-b-2 border-gray-900 pb-3.5 mb-[18px]">' +
158
- '<div class="text-[28px] font-bold tracking-wide">' + escapeHtml(title) + '</div>' +
159
- descriptionHtml +
160
- '<div class="mt-3 flex gap-x-5 text-[13px] text-gray-500">' +
161
- versionHtml +
162
- '<div>Generated: ' + escapeHtml(generatedAt) + '</div>' +
163
- '</div>' +
164
- '</div>' +
165
- '<div class="mt-[22px]">' +
166
- '<h2 class="m-0 mb-2.5 text-lg border-t border-gray-300 pt-3.5">Models</h2>' +
167
- '<table class="w-full border-collapse text-[13px]">' +
168
- '<thead>' +
169
- '<tr>' +
170
- '<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Model</th>' +
171
- '<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Documentation</th>' +
172
- '<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Views</th>' +
173
- '</tr>' +
174
- '</thead>' +
175
- '<tbody>' + modelRows + '</tbody>' +
176
- '</table>' +
177
- '</div>' +
178
- '</div>' +
179
- '</body>' +
180
- '</html>'
181
- }
182
-
183
- function getRegisteredModels(config: CombinedDocsConfig) {
184
- return Object.keys(config.modelConfigs).filter((m) => {
185
- const cfg = config.modelConfigs[m]
186
- return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? config.disableOpenApi)
54
+ router.get(basePath, (_req, res) => {
55
+ res.type('html').send(renderIndex(config.title || 'API Documentation', enabled as string[], basePath))
187
56
  })
188
57
  }
189
58
 
190
- ${combinedDocsReturn}
59
+ ${renderIndexFn}
191
60
 
192
- ${registerDocs}
61
+ export default registerUnifiedDocs
193
62
  `
194
63
  }
195
64
 
196
- function generateExpressCombinedDocs(): string {
197
- return `export function generateCombinedDocs(config: CombinedDocsConfig) {
198
- return (req: Request, res: Response) => {
199
- const registeredModels = getRegisteredModels(config)
65
+ function generateFastify(modelNames: string[], ext: string): string {
66
+ return `import type { FastifyInstance } from 'fastify'
67
+ import { getEnv, removeTrailingSlash } from './misc${ext}'
200
68
 
201
- if (registeredModels.length === 0) {
202
- return res.status(404).send('OpenAPI documentation is disabled')
203
- }
69
+ const _env = getEnv()
204
70
 
205
- const html = buildCombinedHtml(config, registeredModels)
206
- res.type('html').send(html)
207
- }
208
- }`
71
+ const MODELS = ${JSON.stringify(modelNames)} as const
72
+
73
+ interface UnifiedDocsConfig {
74
+ title?: string
75
+ basePath?: string
76
+ models?: string[]
77
+ disableInProduction?: boolean
209
78
  }
210
79
 
211
- function generateFastifyCombinedDocs(): string {
212
- return `export function generateCombinedDocs(config: CombinedDocsConfig) {
213
- return async (request: FastifyRequest, reply: FastifyReply) => {
214
- const registeredModels = getRegisteredModels(config)
80
+ export async function registerUnifiedDocs(
81
+ fastify: FastifyInstance,
82
+ config: UnifiedDocsConfig = {},
83
+ ): Promise<void> {
84
+ const disabled = config.disableInProduction !== false && _env.NODE_ENV === 'production'
85
+ if (disabled) return
215
86
 
216
- if (registeredModels.length === 0) {
217
- return reply.code(404).send('OpenAPI documentation is disabled')
218
- }
87
+ const basePath = removeTrailingSlash(config.basePath || '/docs')
88
+ const enabled = config.models && config.models.length > 0
89
+ ? MODELS.filter((m) => config.models!.includes(m))
90
+ : MODELS
219
91
 
220
- const html = buildCombinedHtml(config, registeredModels)
221
- return reply.type('text/html').send(html)
222
- }
223
- }`
92
+ fastify.get(basePath, async (_request, reply) => {
93
+ reply.type('text/html').send(renderIndex(config.title || 'API Documentation', enabled as string[], basePath))
94
+ })
224
95
  }
225
96
 
226
- function generateHonoCombinedDocs(): string {
227
- return `export function generateCombinedDocs(config: CombinedDocsConfig) {
228
- return (c: Context): Response | Promise<Response> => {
229
- const registeredModels = getRegisteredModels(config)
97
+ ${renderIndexFn}
230
98
 
231
- if (registeredModels.length === 0) {
232
- return c.text('OpenAPI documentation is disabled', 404)
233
- }
234
-
235
- const html = buildCombinedHtml(config, registeredModels)
236
- return c.html(html)
237
- }
238
- }`
99
+ export default registerUnifiedDocs
100
+ `
239
101
  }
240
102
 
241
- function generateExpressRegisterDocs(): string {
242
- return `export function registerModelDocs(
243
- app: any,
244
- basePath: string = '/docs',
245
- configs: CombinedDocsConfig['modelConfigs'] = {},
246
- options?: { disableOpenApi?: boolean }
247
- ) {
248
- const normalizedBase = removeTrailingSlash(basePath)
249
- const registeredModels = Object.keys(configs).filter((m) => {
250
- const cfg = configs[m]
251
- return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? options?.disableOpenApi)
252
- })
103
+ function generateHono(modelNames: string[], ext: string): string {
104
+ return `import type { Hono } from 'hono'
105
+ import { getEnv, removeTrailingSlash } from './misc${ext}'
253
106
 
254
- if (registeredModels.length === 0) return
107
+ const _env = getEnv()
255
108
 
256
- registeredModels.forEach((model) => {
257
- const handler = docsHandlers[model]
258
- const cfg = configs[model] || {}
259
- const docPath = normalizedBase + '/' + model.toLowerCase()
260
- console.log(' Registered docs: ' + docPath)
261
- app.get(docPath, handler(cfg))
262
- })
263
- }`
109
+ const MODELS = ${JSON.stringify(modelNames)} as const
110
+
111
+ interface UnifiedDocsConfig {
112
+ title?: string
113
+ basePath?: string
114
+ models?: string[]
115
+ disableInProduction?: boolean
264
116
  }
265
117
 
266
- function generateFastifyRegisterDocs(): string {
267
- return `export function registerModelDocs(
268
- app: FastifyInstance,
269
- basePath: string = '/docs',
270
- configs: CombinedDocsConfig['modelConfigs'] = {},
271
- options?: { disableOpenApi?: boolean }
272
- ) {
273
- const normalizedBase = removeTrailingSlash(basePath)
274
- const registeredModels = Object.keys(configs).filter((m) => {
275
- const cfg = configs[m]
276
- return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? options?.disableOpenApi)
277
- })
118
+ export function registerUnifiedDocs(app: Hono, config: UnifiedDocsConfig = {}): void {
119
+ const disabled = config.disableInProduction !== false && _env.NODE_ENV === 'production'
120
+ if (disabled) return
278
121
 
279
- if (registeredModels.length === 0) return
122
+ const basePath = removeTrailingSlash(config.basePath || '/docs')
123
+ const enabled = config.models && config.models.length > 0
124
+ ? MODELS.filter((m) => config.models!.includes(m))
125
+ : MODELS
280
126
 
281
- registeredModels.forEach((model) => {
282
- const handler = docsHandlers[model]
283
- const cfg = configs[model] || {}
284
- const docPath = normalizedBase + '/' + model.toLowerCase()
285
- console.log(' Registered docs: ' + docPath)
286
- app.get(docPath, handler(cfg))
127
+ app.get(basePath, (c) => {
128
+ return c.html(renderIndex(config.title || 'API Documentation', enabled as string[], basePath))
287
129
  })
288
- }`
289
130
  }
290
131
 
291
- function generateHonoRegisterDocs(): string {
292
- return `export function registerModelDocs(
293
- app: Hono<any>,
294
- basePath: string = '/docs',
295
- configs: CombinedDocsConfig['modelConfigs'] = {},
296
- options?: { disableOpenApi?: boolean }
297
- ) {
298
- const normalizedBase = removeTrailingSlash(basePath)
299
- const registeredModels = Object.keys(configs).filter((m) => {
300
- const cfg = configs[m]
301
- return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? options?.disableOpenApi)
302
- })
303
-
304
- if (registeredModels.length === 0) return
132
+ ${renderIndexFn}
305
133
 
306
- registeredModels.forEach((model) => {
307
- const handler = docsHandlers[model]
308
- const cfg = configs[model] || {}
309
- const docPath = normalizedBase + '/' + model.toLowerCase()
310
- console.log(' Registered docs: ' + docPath)
311
- app.get(docPath, handler(cfg))
312
- })
313
- }`
134
+ export default registerUnifiedDocs
135
+ `
314
136
  }
@@ -1,5 +1,4 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
- import { toCamelCase } from '../utils/strings'
3
2
  import { ImportStyle } from '../utils/resolveImportStyle'
4
3
  import { importExt } from '../utils/importExt'
5
4
 
@@ -40,10 +39,9 @@ const ALL_OPS = [
40
39
  export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
41
40
  const ext = importExt(options.importStyle)
42
41
  const modelName = options.model.name
43
- const prefix = toCamelCase(modelName)
44
42
 
45
43
  const handlers = ALL_OPS.map((op) => {
46
- const exportName = `${prefix}${op.charAt(0).toUpperCase() + op.slice(1)}`
44
+ const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
47
45
 
48
46
  return `
49
47
  export async function ${exportName}(
@@ -60,7 +58,7 @@ export async function ${exportName}(
60
58
  }`
61
59
  }).join('\n')
62
60
 
63
- return `import { Request, Response, NextFunction } from 'express'
61
+ return `import type { Request, Response, NextFunction } from 'express'
64
62
  import * as core from './${modelName}Core${ext}'
65
63
  import { OperationContext, mapError } from '../operationRuntime${ext}'
66
64
 
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { generatorHandler, GeneratorOptions, DMMF } from '@prisma/generator-helper'
1
+ import {
2
+ generatorHandler,
3
+ GeneratorOptions,
4
+ DMMF,
5
+ } from '@prisma/generator-helper'
2
6
  import path from 'path'
3
7
  import { generateUnifiedHandler } from './generators/generateUnifiedHandler'
4
8
  import { generateFastifyHandler } from './generators/generateFastifyHandler'
@@ -10,17 +14,33 @@ import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
10
14
  import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
11
15
  import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
12
16
  import { generateModelCore } from './generators/generateOperationCore'
13
- import { generateRelationMeta, generateRelationModelsIndex } from './generators/generateRelationMeta'
14
- import { getRelativeClientPath, getGuardShapesImport } from './generators/generateImportPrismaStatement'
17
+ import {
18
+ generateRelationMeta,
19
+ generateRelationModelsIndex,
20
+ } from './generators/generateRelationMeta'
21
+ import {
22
+ getRelativeClientPath,
23
+ getGuardShapesImport,
24
+ } from './generators/generateImportPrismaStatement'
15
25
  import { writeFileSafely } from './utils/writeFileSafely'
16
26
  import { copyFiles } from './utils/copyFiles'
17
27
  import { resolveImportStyle, ImportStyle } from './utils/resolveImportStyle'
18
28
  import { GENERATOR_NAME, Target } from './constants'
19
29
 
30
+ const GENERATOR_OFF_RE = /\bgenerator off\b/
31
+
20
32
  function getTarget(options: GeneratorOptions): Target {
21
- const raw = String((options.generator.config as Record<string, unknown>).target ?? 'express').toLowerCase()
33
+ const raw = String(
34
+ (options.generator.config as Record<string, unknown>).target ?? 'express',
35
+ ).toLowerCase()
22
36
  if (raw === 'express' || raw === 'fastify' || raw === 'hono') return raw
23
- throw new Error(`Invalid target "${raw}". Expected "express", "fastify", or "hono".`)
37
+ throw new Error(
38
+ `Invalid target "${raw}". Expected "express", "fastify", or "hono".`,
39
+ )
40
+ }
41
+
42
+ function validateClientGeneratorPresent(options: GeneratorOptions): void {
43
+ getRelativeClientPath(options, options.dmmf.datamodel.models[0]?.name ?? 'Model')
24
44
  }
25
45
 
26
46
  generatorHandler({
@@ -34,8 +54,9 @@ generatorHandler({
34
54
 
35
55
  async onGenerate(options: GeneratorOptions) {
36
56
  const target = getTarget(options)
37
- const hasExplicitOutput = !!options.generator.output?.fromEnvVar
38
- || (options.generator.config as Record<string, unknown>).output !== undefined
57
+ const hasExplicitOutput =
58
+ !!options.generator.output?.fromEnvVar ||
59
+ (options.generator.config as Record<string, unknown>).output !== undefined
39
60
 
40
61
  if (!hasExplicitOutput) {
41
62
  const schemaDir = path.dirname(options.schemaPath)
@@ -50,24 +71,35 @@ generatorHandler({
50
71
  console.log(` Output: ${options.generator.output?.value}`)
51
72
  console.log(` Import style: ${importStyle}`)
52
73
 
74
+ if (options.dmmf.datamodel.models.length > 0) {
75
+ validateClientGeneratorPresent(options)
76
+ }
77
+
53
78
  await copyFiles(options, target, importStyle)
54
79
 
55
80
  const modelNames: string[] = []
56
- const generateHandler: (opts: { model: DMMF.Model; importStyle: ImportStyle }) => string =
57
- target === 'fastify' ? generateFastifyHandler :
58
- target === 'hono' ? generateHonoHandler :
59
- generateUnifiedHandler
81
+ const generateHandler: (opts: {
82
+ model: DMMF.Model
83
+ importStyle: ImportStyle
84
+ }) => string =
85
+ target === 'fastify'
86
+ ? generateFastifyHandler
87
+ : target === 'hono'
88
+ ? generateHonoHandler
89
+ : generateUnifiedHandler
60
90
 
61
91
  const allModels = options.dmmf.datamodel.models as DMMF.Model[]
62
92
 
63
93
  for (const model of options.dmmf.datamodel.models) {
64
- if (model.documentation && model.documentation.includes('generator off')) {
94
+ if (
95
+ model.documentation &&
96
+ GENERATOR_OFF_RE.test(model.documentation)
97
+ ) {
65
98
  console.log(` Skipping: ${model.name} (generator off)`)
66
99
  continue
67
100
  }
68
101
  modelNames.push(model.name)
69
102
 
70
- const relativeClientPath = getRelativeClientPath(options, model.name)
71
103
  const guardShapesImport = getGuardShapesImport(options, model.name)
72
104
 
73
105
  await writeFileSafely({
@@ -102,7 +134,6 @@ generatorHandler({
102
134
  : generateRouterFunction({
103
135
  model: model as DMMF.Model,
104
136
  enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
105
- relativeClientPath,
106
137
  guardShapesImport,
107
138
  importStyle,
108
139
  })
@@ -42,7 +42,7 @@ function escapeRegex(str: string): string {
42
42
  }
43
43
 
44
44
  const RELATIVE_IMPORT_RE =
45
- /(\b(?:import|export)\b[^'"\n;]*?\bfrom\s+|\bimport\s+|\bimport\s*\(\s*)(['"])(\.{1,2}\/[^'"\n]+)\2/g
45
+ /(\b(?:import|export)\b[^'";]*?\bfrom\s+|\bimport\s+|\bimport\s*\(\s*)(['"])(\.{1,2}\/[^'"\n]+)\2/g
46
46
 
47
47
  const SKIP_EXTENSION_RE = /\.(m?[jt]sx?|json|css|svg|png|jpe?g|gif|webp|wasm)$/i
48
48
 
@@ -79,6 +79,7 @@ function copyFileSync(
79
79
 
80
80
  if (options?.importRewrites) {
81
81
  for (const rewrite of options.importRewrites) {
82
+ if (rewrite.from === rewrite.to) continue
82
83
  content = content.replace(
83
84
  new RegExp('from [\'"]' + escapeRegex(rewrite.from) + '[\'"]', 'g'),
84
85
  "from '" + rewrite.to + "'",
@@ -129,7 +130,6 @@ export async function copyFiles(
129
130
  const err = copyFileSync(copyBase, outputPath, targetConfigFile, importStyle, {
130
131
  required: true,
131
132
  destFilename: 'routeConfig.target.ts',
132
- importRewrites: [{ from: './routeConfig', to: './routeConfig' }],
133
133
  })
134
134
  if (err) errors.push(err)
135
135
 
@@ -1,10 +1,2 @@
1
1
  export const capitalize = (str: string) =>
2
2
  str.charAt(0).toUpperCase() + str.slice(1)
3
-
4
- export function toCamelCase(str: string) {
5
- if (!str) return str
6
- return str
7
- .split('_')
8
- .map((part, i) => i === 0 ? part.toLowerCase() : capitalize(part))
9
- .join('')
10
- }
@@ -1,47 +0,0 @@
1
- import { NextFunction, Response, Request } from 'express'
2
- import { allow, forbid } from './transformZod'
3
- import { ValidatorOptions } from './createValidatorMiddleware'
4
-
5
- /**
6
- * Creates a middleware for validating response data using a Zod schema.
7
- * @param {ValidatorOptions} options - The validation options.
8
- * @param {ZodSchema<any>} options.schema - The Zod schema to validate the response data.
9
- * @param {string[]} [options.allowedPaths] - Paths that are allowed in the response data.
10
- * @param {string[]} [options.forbiddenPaths] - Paths that are forbidden in the response data.
11
- * @returns {function} Express middleware function.
12
- */
13
- export function createOutputValidatorMiddleware({
14
- schema,
15
- allowedPaths,
16
- forbiddenPaths,
17
- }: ValidatorOptions) {
18
- if (allowedPaths) {
19
- schema = allow(schema, allowedPaths)
20
- }
21
-
22
- if (forbiddenPaths) {
23
- schema = forbid(schema, forbiddenPaths)
24
- }
25
-
26
- return (req: Request, res: Response, next: NextFunction) => {
27
- const originalSend = res.send
28
-
29
- res.send = function (data) {
30
- const validationResult = schema.safeParse(data)
31
- if (!validationResult.success) {
32
- const errors = validationResult.error.issues
33
- return originalSend.call(
34
- this,
35
- JSON.stringify({
36
- status: 400,
37
- message: 'Output validation failed',
38
- errors,
39
- }),
40
- )
41
- }
42
- return originalSend.call(this, data)
43
- }
44
-
45
- next()
46
- }
47
- }