prisma-generator-express 1.45.1 → 1.47.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/dist/client/encodeQueryParams.js +4 -0
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +4 -2
- package/dist/copy/misc.js +35 -24
- package/dist/copy/misc.js.map +1 -1
- package/dist/generators/generateFastifyHandler.js +2 -4
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +2 -4
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.d.ts +0 -1
- package/dist/generators/generateImportPrismaStatement.js +2 -20
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateOperationCore.js +37 -1
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +9 -0
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +0 -10
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +33 -12
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +0 -1
- package/dist/generators/generateRouter.js +96 -70
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +83 -89
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +257 -237
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedDocs.d.ts +2 -2
- package/dist/generators/generateUnifiedDocs.js +90 -252
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.js +2 -4
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/index.js +16 -8
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +3 -2
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/strings.d.ts +0 -1
- package/dist/utils/strings.js +0 -9
- package/dist/utils/strings.js.map +1 -1
- package/package.json +1 -1
- package/src/client/encodeQueryParams.ts +7 -15
- package/src/copy/autoIncludePlanner.ts +4 -17
- package/src/copy/autoIncludeRuntime.ts +11 -19
- package/src/copy/buildModelOpenApi.ts +11 -14
- package/src/copy/docsRenderer.ts +8 -14
- package/src/copy/misc.ts +28 -23
- package/src/copy/operationRuntime.ts +61 -43
- package/src/copy/parseQueryParams.ts +5 -14
- package/src/copy/routeConfig.express.ts +24 -18
- package/src/copy/routeConfig.fastify.ts +1 -1
- package/src/copy/routeConfig.hono.ts +34 -6
- package/src/copy/routeConfig.ts +3 -2
- package/src/generators/generateFastifyHandler.ts +2 -5
- package/src/generators/generateHonoHandler.ts +2 -5
- package/src/generators/generateImportPrismaStatement.ts +3 -35
- package/src/generators/generateOperationCore.ts +37 -1
- package/src/generators/generateQueryBuilderHelper.ts +9 -0
- package/src/generators/generateRelationMeta.ts +0 -10
- package/src/generators/generateRouteConfigType.ts +34 -10
- package/src/generators/generateRouter.ts +96 -71
- package/src/generators/generateRouterFastify.ts +83 -89
- package/src/generators/generateRouterHono.ts +257 -237
- package/src/generators/generateUnifiedDocs.ts +89 -267
- package/src/generators/generateUnifiedHandler.ts +2 -4
- package/src/index.ts +45 -14
- package/src/utils/copyFiles.ts +2 -2
- package/src/utils/strings.ts +0 -8
- package/src/copy/createOutputValidatorMiddleware.ts +0 -47
- package/src/copy/createValidatorMiddleware.ts +0 -62
- 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
|
-
|
|
7
|
-
target: Target
|
|
8
|
-
importStyle: ImportStyle
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
34
|
+
const _env = getEnv()
|
|
61
35
|
|
|
62
|
-
|
|
63
|
-
docsTitle?: string
|
|
64
|
-
docsUi?: DocsUI
|
|
65
|
-
}
|
|
36
|
+
const MODELS = ${JSON.stringify(modelNames)} as const
|
|
66
37
|
|
|
67
|
-
|
|
68
|
-
disableOpenApi?: boolean
|
|
38
|
+
interface UnifiedDocsConfig {
|
|
69
39
|
title?: string
|
|
70
|
-
description?: string
|
|
71
40
|
basePath?: string
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
[modelName: string]: ModelDocsConfig
|
|
75
|
-
}
|
|
41
|
+
models?: string[]
|
|
42
|
+
disableInProduction?: boolean
|
|
76
43
|
}
|
|
77
44
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.replace(/</g, '<')
|
|
82
|
-
.replace(/>/g, '>')
|
|
83
|
-
.replace(/"/g, '"')
|
|
84
|
-
.replace(/'/g, ''')
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
148
|
-
'
|
|
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
|
-
${
|
|
59
|
+
${renderIndexFn}
|
|
191
60
|
|
|
192
|
-
|
|
61
|
+
export default registerUnifiedDocs
|
|
193
62
|
`
|
|
194
63
|
}
|
|
195
64
|
|
|
196
|
-
function
|
|
197
|
-
return `
|
|
198
|
-
|
|
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
|
-
|
|
202
|
-
return res.status(404).send('OpenAPI documentation is disabled')
|
|
203
|
-
}
|
|
69
|
+
const _env = getEnv()
|
|
204
70
|
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
227
|
-
return `export function generateCombinedDocs(config: CombinedDocsConfig) {
|
|
228
|
-
return (c: Context): Response | Promise<Response> => {
|
|
229
|
-
const registeredModels = getRegisteredModels(config)
|
|
97
|
+
${renderIndexFn}
|
|
230
98
|
|
|
231
|
-
|
|
232
|
-
|
|
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
|
|
242
|
-
return `
|
|
243
|
-
|
|
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
|
-
|
|
107
|
+
const _env = getEnv()
|
|
255
108
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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 = `${
|
|
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 {
|
|
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 {
|
|
14
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
38
|
-
|
|
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: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 (
|
|
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
|
})
|
package/src/utils/copyFiles.ts
CHANGED
|
@@ -42,7 +42,7 @@ function escapeRegex(str: string): string {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const RELATIVE_IMPORT_RE =
|
|
45
|
-
/(\b(?:import|export)\b[^'"
|
|
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
|
|
package/src/utils/strings.ts
CHANGED
|
@@ -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
|
-
}
|