prisma-generator-express 1.16.7 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +399 -194
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/client/encodeQueryParams.d.ts +1 -0
- package/dist/client/encodeQueryParams.js +33 -0
- package/dist/client/encodeQueryParams.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/copy/misc.d.ts +5 -0
- package/dist/copy/misc.js +52 -0
- package/dist/copy/misc.js.map +1 -0
- package/dist/generators/generateImportPrismaStatement.d.ts +3 -0
- package/dist/generators/generateImportPrismaStatement.js +55 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -0
- package/dist/generators/generateQueryBuilderHelper.d.ts +2 -0
- package/dist/generators/generateQueryBuilderHelper.js +139 -0
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -0
- package/dist/generators/generateRouter.d.ts +6 -0
- package/dist/generators/generateRouter.js +340 -0
- package/dist/generators/generateRouter.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +1 -0
- package/dist/generators/generateUnifiedDocs.js +171 -0
- package/dist/generators/generateUnifiedDocs.js.map +1 -0
- package/dist/generators/generateUnifiedHandler.d.ts +6 -0
- package/dist/generators/generateUnifiedHandler.js +444 -0
- package/dist/generators/generateUnifiedHandler.js.map +1 -0
- package/dist/generators/generateUnifiedScalarUI.d.ts +5 -0
- package/dist/generators/generateUnifiedScalarUI.js +1390 -0
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/copyFiles.d.ts +6 -0
- package/dist/utils/copyFiles.js +123 -21
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/strings.d.ts +2 -0
- package/dist/utils/writeFileSafely.d.ts +10 -0
- package/dist/utils/writeFileSafely.js +86 -14
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +64 -31
- package/src/client/encodeQueryParams.ts +56 -0
- package/src/copy/buildModelOpenApi.ts +1569 -0
- package/src/copy/createOutputValidatorMiddleware.ts +1 -1
- package/src/copy/createValidatorMiddleware.ts +1 -1
- package/src/copy/misc.ts +22 -1
- package/src/copy/operationDefinitions.ts +96 -0
- package/src/copy/parseQueryParams.ts +36 -21
- package/src/copy/routeConfig.ts +69 -27
- package/src/copy/transformZod.ts +15 -16
- package/dist/generator.js +0 -168
- package/dist/generator.js.map +0 -1
- package/dist/helpers/generateAggregate.js +0 -51
- package/dist/helpers/generateAggregate.js.map +0 -1
- package/dist/helpers/generateCount.js +0 -50
- package/dist/helpers/generateCount.js.map +0 -1
- package/dist/helpers/generateCreate.js +0 -49
- package/dist/helpers/generateCreate.js.map +0 -1
- package/dist/helpers/generateCreateMany.js +0 -49
- package/dist/helpers/generateCreateMany.js.map +0 -1
- package/dist/helpers/generateDelete.js +0 -49
- package/dist/helpers/generateDelete.js.map +0 -1
- package/dist/helpers/generateDeleteMany.js +0 -49
- package/dist/helpers/generateDeleteMany.js.map +0 -1
- package/dist/helpers/generateFindFirst.js +0 -56
- package/dist/helpers/generateFindFirst.js.map +0 -1
- package/dist/helpers/generateFindMany.js +0 -56
- package/dist/helpers/generateFindMany.js.map +0 -1
- package/dist/helpers/generateFindUnique.js +0 -56
- package/dist/helpers/generateFindUnique.js.map +0 -1
- package/dist/helpers/generateGroupBy.js +0 -51
- package/dist/helpers/generateGroupBy.js.map +0 -1
- package/dist/helpers/generateImportPrismaStatement.js +0 -25
- package/dist/helpers/generateImportPrismaStatement.js.map +0 -1
- package/dist/helpers/generateRouteFile.js +0 -192
- package/dist/helpers/generateRouteFile.js.map +0 -1
- package/dist/helpers/generateUpdate.js +0 -49
- package/dist/helpers/generateUpdate.js.map +0 -1
- package/dist/helpers/generateUpdateMany.js +0 -49
- package/dist/helpers/generateUpdateMany.js.map +0 -1
- package/dist/helpers/generateUpsert.js +0 -49
- package/dist/helpers/generateUpsert.js.map +0 -1
- package/dist/utils/formatFile.js +0 -26
- package/dist/utils/formatFile.js.map +0 -1
- package/src/bin.ts +0 -2
- package/src/constants.ts +0 -1
- package/src/copy/encodeQueryParams.spec.ts +0 -303
- package/src/copy/encodeQueryParams.ts +0 -44
- package/src/copy/misc.spec.ts +0 -62
- package/src/copy/parseQueryParams.spec.ts +0 -187
- package/src/copy/transformZod.spec.ts +0 -763
- package/src/generator.ts +0 -188
- package/src/helpers/generateAggregate.ts +0 -59
- package/src/helpers/generateCount.ts +0 -58
- package/src/helpers/generateCreate.ts +0 -56
- package/src/helpers/generateCreateMany.ts +0 -55
- package/src/helpers/generateDelete.ts +0 -57
- package/src/helpers/generateDeleteMany.ts +0 -57
- package/src/helpers/generateFindFirst.ts +0 -62
- package/src/helpers/generateFindMany.ts +0 -62
- package/src/helpers/generateFindUnique.ts +0 -62
- package/src/helpers/generateGroupBy.ts +0 -60
- package/src/helpers/generateImportPrismaStatement.ts +0 -38
- package/src/helpers/generateRouteFile.ts +0 -195
- package/src/helpers/generateUpdate.ts +0 -56
- package/src/helpers/generateUpdateMany.ts +0 -56
- package/src/helpers/generateUpsert.ts +0 -57
- package/src/utils/copyFiles.ts +0 -27
- package/src/utils/formatFile.ts +0 -22
- package/src/utils/strings.ts +0 -7
- package/src/utils/writeFileSafely.ts +0 -29
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRouterFunction = generateRouterFunction;
|
|
4
|
+
function generateRouterFunction({ model, enums, relativeClientPath, }) {
|
|
5
|
+
const modelName = model.name;
|
|
6
|
+
const modelNameLower = modelName.toLowerCase();
|
|
7
|
+
const routerFunctionName = `${modelName}Router`;
|
|
8
|
+
const fieldsMeta = model.fields.map((f) => ({
|
|
9
|
+
name: f.name,
|
|
10
|
+
kind: f.kind,
|
|
11
|
+
type: f.type,
|
|
12
|
+
isList: f.isList,
|
|
13
|
+
isRequired: f.isRequired,
|
|
14
|
+
hasDefaultValue: f.hasDefaultValue,
|
|
15
|
+
isUpdatedAt: f.isUpdatedAt ?? false,
|
|
16
|
+
documentation: f.documentation,
|
|
17
|
+
relationFromFields: f.relationFromFields,
|
|
18
|
+
}));
|
|
19
|
+
const referencedEnumTypes = new Set(model.fields.filter((f) => f.kind === 'enum').map((f) => f.type));
|
|
20
|
+
const enumsMeta = enums
|
|
21
|
+
.filter((e) => referencedEnumTypes.has(e.name))
|
|
22
|
+
.map((e) => ({
|
|
23
|
+
name: e.name,
|
|
24
|
+
values: e.values.map((v) => ({ name: v.name })),
|
|
25
|
+
}));
|
|
26
|
+
return `import express, { Request, Response, NextFunction, RequestHandler } from 'express'
|
|
27
|
+
import type { PrismaClient } from '${relativeClientPath}'
|
|
28
|
+
import {
|
|
29
|
+
${modelName}FindUnique,
|
|
30
|
+
${modelName}FindUniqueOrThrow,
|
|
31
|
+
${modelName}FindFirst,
|
|
32
|
+
${modelName}FindFirstOrThrow,
|
|
33
|
+
${modelName}FindMany,
|
|
34
|
+
${modelName}FindManyPaginated,
|
|
35
|
+
${modelName}Create,
|
|
36
|
+
${modelName}CreateMany,
|
|
37
|
+
${modelName}CreateManyAndReturn,
|
|
38
|
+
${modelName}Update,
|
|
39
|
+
${modelName}UpdateMany,
|
|
40
|
+
${modelName}UpdateManyAndReturn,
|
|
41
|
+
${modelName}Upsert,
|
|
42
|
+
${modelName}Delete,
|
|
43
|
+
${modelName}DeleteMany,
|
|
44
|
+
${modelName}Aggregate,
|
|
45
|
+
${modelName}Count,
|
|
46
|
+
${modelName}GroupBy
|
|
47
|
+
} from './${modelName}Handlers'
|
|
48
|
+
import type { RouteConfig } from '../routeConfig'
|
|
49
|
+
import { parseQueryParams } from '../parseQueryParams'
|
|
50
|
+
import { buildModelOpenApi } from '../buildModelOpenApi'
|
|
51
|
+
|
|
52
|
+
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
53
|
+
|
|
54
|
+
const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
|
|
55
|
+
|
|
56
|
+
const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
|
|
57
|
+
|
|
58
|
+
const defaultOpConfig = {
|
|
59
|
+
before: [] as RequestHandler[],
|
|
60
|
+
after: [] as RequestHandler[],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizePrefix(p: string): string {
|
|
64
|
+
if (!p) return ''
|
|
65
|
+
let result = p
|
|
66
|
+
if (!result.startsWith('/')) result = '/' + result
|
|
67
|
+
while (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1)
|
|
68
|
+
if (result === '/') return ''
|
|
69
|
+
return result
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function transformResult(value: unknown): unknown {
|
|
73
|
+
if (value === null || value === undefined) return value
|
|
74
|
+
if (typeof value === 'bigint') return value.toString()
|
|
75
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
|
|
76
|
+
return value.toString('base64')
|
|
77
|
+
}
|
|
78
|
+
if (value instanceof Uint8Array) {
|
|
79
|
+
let binary = ''
|
|
80
|
+
for (let i = 0; i < value.length; i++) binary += String.fromCharCode(value[i])
|
|
81
|
+
return btoa(binary)
|
|
82
|
+
}
|
|
83
|
+
if (value instanceof Date) return value
|
|
84
|
+
if (Array.isArray(value)) return value.map(transformResult)
|
|
85
|
+
if (typeof value === 'object') {
|
|
86
|
+
const proto = Object.getPrototypeOf(value)
|
|
87
|
+
if (proto !== Object.prototype && proto !== null) return value
|
|
88
|
+
const out: Record<string, unknown> = {}
|
|
89
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
90
|
+
out[k] = transformResult(v)
|
|
91
|
+
}
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
return value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isQueryBuilderEnabled(config: RouteConfig): boolean {
|
|
98
|
+
if (config.queryBuilder === false) return false
|
|
99
|
+
if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
|
|
100
|
+
if (_env.NODE_ENV === 'production') return false
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getQueryBuilderConfig(config: RouteConfig) {
|
|
105
|
+
if (config.queryBuilder === false) return null
|
|
106
|
+
if (typeof config.queryBuilder === 'object') return config.queryBuilder
|
|
107
|
+
return {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function ${routerFunctionName}(config: RouteConfig = {}) {
|
|
111
|
+
const router = express.Router()
|
|
112
|
+
|
|
113
|
+
router.use(express.json())
|
|
114
|
+
|
|
115
|
+
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
116
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
|
|
117
|
+
const basePath = customPrefix + modelPrefix
|
|
118
|
+
|
|
119
|
+
const openApiDisabled = config.disableOpenApi === true
|
|
120
|
+
|| (config.disableOpenApi !== false && (
|
|
121
|
+
_env.DISABLE_OPENAPI === 'true'
|
|
122
|
+
|| _env.NODE_ENV === 'production'
|
|
123
|
+
))
|
|
124
|
+
|
|
125
|
+
const qbEnabled = isQueryBuilderEnabled(config)
|
|
126
|
+
|
|
127
|
+
if (qbEnabled) {
|
|
128
|
+
const qbConfig = getQueryBuilderConfig(config)
|
|
129
|
+
if (qbConfig) {
|
|
130
|
+
import('../queryBuilder').then(mod => mod.startQueryBuilder(qbConfig)).catch(() => {})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const parseQuery: RequestHandler = (req, res, next) => {
|
|
135
|
+
const rawQuery = req.query
|
|
136
|
+
if (rawQuery && Object.keys(rawQuery).length > 0) {
|
|
137
|
+
res.locals.parsedQuery = parseQueryParams(rawQuery as Record<string, unknown>)
|
|
138
|
+
}
|
|
139
|
+
next()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const setShape = (opConfig: any): RequestHandler => {
|
|
143
|
+
return (req, res, next) => {
|
|
144
|
+
res.locals.routeConfig = config
|
|
145
|
+
if (opConfig.shape) {
|
|
146
|
+
res.locals.guardShape = opConfig.shape
|
|
147
|
+
const caller = config.guard?.resolveVariant?.(req)
|
|
148
|
+
?? req.get(config.guard?.variantHeader || 'x-api-variant')
|
|
149
|
+
?? undefined
|
|
150
|
+
if (caller) {
|
|
151
|
+
res.locals.guardCaller = caller
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
next()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const respond: RequestHandler = (_req, res) => {
|
|
159
|
+
const data = res.locals.data
|
|
160
|
+
if (data === undefined) {
|
|
161
|
+
return res.status(500).json({ message: 'No data set by handler' })
|
|
162
|
+
}
|
|
163
|
+
return res.json(transformResult(data))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const respondCreated: RequestHandler = (_req, res) => {
|
|
167
|
+
const data = res.locals.data
|
|
168
|
+
if (data === undefined) {
|
|
169
|
+
return res.status(500).json({ message: 'No data set by handler' })
|
|
170
|
+
}
|
|
171
|
+
return res.status(201).json(transformResult(data))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!openApiDisabled) {
|
|
175
|
+
const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
|
|
176
|
+
const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
|
|
177
|
+
|
|
178
|
+
router.get(openapiJsonPath, (_req, res) => {
|
|
179
|
+
const spec = buildModelOpenApi(
|
|
180
|
+
'${modelName}',
|
|
181
|
+
MODEL_FIELDS as any,
|
|
182
|
+
MODEL_ENUMS as any,
|
|
183
|
+
config,
|
|
184
|
+
{ format: 'json' }
|
|
185
|
+
)
|
|
186
|
+
res.json(spec)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
router.get(openapiYamlPath, (_req, res) => {
|
|
190
|
+
const spec = buildModelOpenApi(
|
|
191
|
+
'${modelName}',
|
|
192
|
+
MODEL_FIELDS as any,
|
|
193
|
+
MODEL_ENUMS as any,
|
|
194
|
+
config,
|
|
195
|
+
{ format: 'yaml' }
|
|
196
|
+
)
|
|
197
|
+
res.type('application/yaml').send(spec as string)
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (config.enableAll || config.findFirst) {
|
|
202
|
+
const opConfig = config.findFirst || defaultOpConfig
|
|
203
|
+
const { before = [], after = [] } = opConfig
|
|
204
|
+
const path = basePath ? \`\${basePath}/first\` : '/first'
|
|
205
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindFirst as RequestHandler, ...after, respond)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (config.enableAll || config.findFirstOrThrow) {
|
|
209
|
+
const opConfig = config.findFirstOrThrow || defaultOpConfig
|
|
210
|
+
const { before = [], after = [] } = opConfig
|
|
211
|
+
const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
|
|
212
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (config.enableAll || config.findManyPaginated) {
|
|
216
|
+
const opConfig = config.findManyPaginated || defaultOpConfig
|
|
217
|
+
const { before = [], after = [] } = opConfig
|
|
218
|
+
const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
|
|
219
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (config.enableAll || config.aggregate) {
|
|
223
|
+
const opConfig = config.aggregate || defaultOpConfig
|
|
224
|
+
const { before = [], after = [] } = opConfig
|
|
225
|
+
const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
|
|
226
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}Aggregate as RequestHandler, ...after, respond)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (config.enableAll || config.count) {
|
|
230
|
+
const opConfig = config.count || defaultOpConfig
|
|
231
|
+
const { before = [], after = [] } = opConfig
|
|
232
|
+
const path = basePath ? \`\${basePath}/count\` : '/count'
|
|
233
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}Count as RequestHandler, ...after, respond)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (config.enableAll || config.groupBy) {
|
|
237
|
+
const opConfig = config.groupBy || defaultOpConfig
|
|
238
|
+
const { before = [], after = [] } = opConfig
|
|
239
|
+
const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
|
|
240
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}GroupBy as RequestHandler, ...after, respond)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (config.enableAll || config.findUniqueOrThrow) {
|
|
244
|
+
const opConfig = config.findUniqueOrThrow || defaultOpConfig
|
|
245
|
+
const { before = [], after = [] } = opConfig
|
|
246
|
+
const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
|
|
247
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (config.enableAll || config.findUnique) {
|
|
251
|
+
const opConfig = config.findUnique || defaultOpConfig
|
|
252
|
+
const { before = [], after = [] } = opConfig
|
|
253
|
+
const path = basePath ? \`\${basePath}/unique\` : '/unique'
|
|
254
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindUnique as RequestHandler, ...after, respond)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (config.enableAll || config.findMany) {
|
|
258
|
+
const opConfig = config.findMany || defaultOpConfig
|
|
259
|
+
const { before = [], after = [] } = opConfig
|
|
260
|
+
const path = basePath || '/'
|
|
261
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, ${modelName}FindMany as RequestHandler, ...after, respond)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (config.enableAll || config.createManyAndReturn) {
|
|
265
|
+
const opConfig = config.createManyAndReturn || defaultOpConfig
|
|
266
|
+
const { before = [], after = [] } = opConfig
|
|
267
|
+
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
268
|
+
router.post(path, setShape(opConfig), ...before, ${modelName}CreateManyAndReturn as RequestHandler, ...after, respondCreated)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (config.enableAll || config.createMany) {
|
|
272
|
+
const opConfig = config.createMany || defaultOpConfig
|
|
273
|
+
const { before = [], after = [] } = opConfig
|
|
274
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
275
|
+
router.post(path, setShape(opConfig), ...before, ${modelName}CreateMany as RequestHandler, ...after, respondCreated)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (config.enableAll || config.create) {
|
|
279
|
+
const opConfig = config.create || defaultOpConfig
|
|
280
|
+
const { before = [], after = [] } = opConfig
|
|
281
|
+
const path = basePath || '/'
|
|
282
|
+
router.post(path, setShape(opConfig), ...before, ${modelName}Create as RequestHandler, ...after, respondCreated)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (config.enableAll || config.updateManyAndReturn) {
|
|
286
|
+
const opConfig = config.updateManyAndReturn || defaultOpConfig
|
|
287
|
+
const { before = [], after = [] } = opConfig
|
|
288
|
+
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
289
|
+
router.put(path, setShape(opConfig), ...before, ${modelName}UpdateManyAndReturn as RequestHandler, ...after, respond)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (config.enableAll || config.updateMany) {
|
|
293
|
+
const opConfig = config.updateMany || defaultOpConfig
|
|
294
|
+
const { before = [], after = [] } = opConfig
|
|
295
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
296
|
+
router.put(path, setShape(opConfig), ...before, ${modelName}UpdateMany as RequestHandler, ...after, respond)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (config.enableAll || config.update) {
|
|
300
|
+
const opConfig = config.update || defaultOpConfig
|
|
301
|
+
const { before = [], after = [] } = opConfig
|
|
302
|
+
const path = basePath || '/'
|
|
303
|
+
router.put(path, setShape(opConfig), ...before, ${modelName}Update as RequestHandler, ...after, respond)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (config.enableAll || config.upsert) {
|
|
307
|
+
const opConfig = config.upsert || defaultOpConfig
|
|
308
|
+
const { before = [], after = [] } = opConfig
|
|
309
|
+
const path = basePath || '/'
|
|
310
|
+
router.patch(path, setShape(opConfig), ...before, ${modelName}Upsert as RequestHandler, ...after, respond)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (config.enableAll || config.deleteMany) {
|
|
314
|
+
const opConfig = config.deleteMany || defaultOpConfig
|
|
315
|
+
const { before = [], after = [] } = opConfig
|
|
316
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
317
|
+
router.delete(path, setShape(opConfig), ...before, ${modelName}DeleteMany as RequestHandler, ...after, respond)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (config.enableAll || config.delete) {
|
|
321
|
+
const opConfig = config.delete || defaultOpConfig
|
|
322
|
+
const { before = [], after = [] } = opConfig
|
|
323
|
+
const path = basePath || '/'
|
|
324
|
+
router.delete(path, setShape(opConfig), ...before, ${modelName}Delete as RequestHandler, ...after, respond)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
router.use((err: any, _req: Request, res: Response, next: NextFunction) => {
|
|
328
|
+
const status = typeof err.status === 'number' ? err.status : 500
|
|
329
|
+
const message = err.message || 'Internal server error'
|
|
330
|
+
if (!res.headersSent) {
|
|
331
|
+
return res.status(status).json({ message })
|
|
332
|
+
}
|
|
333
|
+
next(err)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
return router
|
|
337
|
+
}
|
|
338
|
+
`;
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=generateRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateRouter.js","sourceRoot":"","sources":["../../src/generators/generateRouter.ts"],"names":[],"mappings":";;AAEA,wDA6VC;AA7VD,SAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,KAAK,EACL,kBAAkB,GAKnB;IACC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAA;IAC5B,MAAM,cAAc,GAAG,SAAS,CAAC,WAAW,EAAE,CAAA;IAC9C,MAAM,kBAAkB,GAAG,GAAG,SAAS,QAAQ,CAAA;IAE/C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,eAAe,EAAE,CAAC,CAAC,eAAe;QAClC,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,KAAK;QACnC,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;KACzC,CAAC,CAAC,CAAA;IAEH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CACjC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CACjE,CAAA;IAED,MAAM,SAAS,GAAG,KAAK;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC9C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;KAChD,CAAC,CAAC,CAAA;IAEL,OAAO;qCAC4B,kBAAkB;;IAEnD,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;YACD,SAAS;;;;;;;uBAOE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;;sBAEpC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsDtC,kBAAkB;;;;;;4DAMwB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAgE/D,SAAS;;;;;;;;;;;WAWT,SAAS;;;;;;;;;;;;;;kEAc8C,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;kEAOT,SAAS;;;;;;;uDAOpB,SAAS;;;;;;;uDAOT,SAAS;;;;;;;uDAOT,SAAS;;;;;;;sDAOV,SAAS;;;;;;;sDAOT,SAAS;;;;;;;sDAOT,SAAS;;;;;;;wDAOP,SAAS;;;;;;;yDAOR,SAAS;;;;;;;yDAOT,SAAS;;;;;;;;;;;;;;CAcjE,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateUnifiedDocs(models: string[]): string;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateUnifiedDocs = generateUnifiedDocs;
|
|
4
|
+
function generateUnifiedDocs(models) {
|
|
5
|
+
const imports = models
|
|
6
|
+
.map((model) => `import { ${model}Docs } from './${model}/${model}Docs'`)
|
|
7
|
+
.join('\n');
|
|
8
|
+
return `${imports}
|
|
9
|
+
import { Request, Response, RequestHandler } from 'express'
|
|
10
|
+
import type { RouteConfig } from './routeConfig'
|
|
11
|
+
|
|
12
|
+
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
13
|
+
|
|
14
|
+
const docsHandlers: Record<string, (config: any) => (req: Request, res: Response) => any> = {
|
|
15
|
+
${models.map((model) => ` ${model}: ${model}Docs`).join(',\n')}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type DocsUI = 'docs' | 'scalar' | 'json' | 'yaml' | 'playground'
|
|
19
|
+
|
|
20
|
+
interface ModelDocsConfig extends RouteConfig {
|
|
21
|
+
docsTitle?: string
|
|
22
|
+
docsUi?: DocsUI
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CombinedDocsConfig {
|
|
26
|
+
disableOpenApi?: boolean
|
|
27
|
+
title?: string
|
|
28
|
+
description?: string
|
|
29
|
+
basePath?: string
|
|
30
|
+
version?: string
|
|
31
|
+
modelConfigs: {
|
|
32
|
+
[modelName: string]: ModelDocsConfig
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function escapeHtml(input: string) {
|
|
37
|
+
return input
|
|
38
|
+
.replaceAll('&', '&')
|
|
39
|
+
.replaceAll('<', '<')
|
|
40
|
+
.replaceAll('>', '>')
|
|
41
|
+
.replaceAll('"', '"')
|
|
42
|
+
.replaceAll("'", ''')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function removeTrailingSlash(p: string): string {
|
|
46
|
+
if (p === '/') return ''
|
|
47
|
+
return p.endsWith('/') ? p.slice(0, -1) : p
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isOpenApiDisabled(disableOpenApi?: boolean) {
|
|
51
|
+
if (disableOpenApi === true) return true
|
|
52
|
+
if (disableOpenApi === false) return false
|
|
53
|
+
return _env.DISABLE_OPENAPI === 'true' || _env.NODE_ENV === 'production'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isPlaygroundAvailable(config?: ModelDocsConfig) {
|
|
57
|
+
if (_env.NODE_ENV === 'production') return false
|
|
58
|
+
if (!config) return true
|
|
59
|
+
if (config.queryBuilder === false) return false
|
|
60
|
+
if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function generateCombinedDocs(config: CombinedDocsConfig) {
|
|
65
|
+
const title = config.title || 'API Documentation'
|
|
66
|
+
const description = config.description || ''
|
|
67
|
+
const version = config.version || ''
|
|
68
|
+
|
|
69
|
+
return (req: Request, res: Response) => {
|
|
70
|
+
const registeredModels = Object.keys(config.modelConfigs).filter((m) => {
|
|
71
|
+
const cfg = config.modelConfigs[m]
|
|
72
|
+
return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? config.disableOpenApi)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (registeredModels.length === 0) {
|
|
76
|
+
return res.status(404).send('OpenAPI documentation is disabled')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const basePath = removeTrailingSlash(config.basePath || '/docs')
|
|
80
|
+
const generatedAt = new Date().toISOString()
|
|
81
|
+
|
|
82
|
+
const html = \`<!DOCTYPE html>
|
|
83
|
+
<html lang="en">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="utf-8" />
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
87
|
+
<title>\${escapeHtml(title)}</title>
|
|
88
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
89
|
+
</head>
|
|
90
|
+
<body class="m-0 bg-white text-gray-900 font-serif leading-normal">
|
|
91
|
+
<div class="max-w-[980px] mx-auto px-7 pt-10 pb-16">
|
|
92
|
+
<div class="border-b-2 border-gray-900 pb-3.5 mb-[18px]">
|
|
93
|
+
<div class="text-[28px] font-bold tracking-wide">\${escapeHtml(title)}</div>
|
|
94
|
+
\${description ? '<div class="mt-1.5 text-gray-500 text-sm">' + escapeHtml(description) + '</div>' : ''}
|
|
95
|
+
<div class="mt-3 flex gap-x-5 text-[13px] text-gray-500">
|
|
96
|
+
\${version ? '<div>Version: ' + escapeHtml(version) + '</div>' : ''}
|
|
97
|
+
<div>Generated: \${escapeHtml(generatedAt)}</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="mt-[22px]">
|
|
102
|
+
<h2 class="m-0 mb-2.5 text-lg border-t border-gray-300 pt-3.5">Models</h2>
|
|
103
|
+
<table class="w-full border-collapse text-[13px]">
|
|
104
|
+
<thead>
|
|
105
|
+
<tr>
|
|
106
|
+
<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Model</th>
|
|
107
|
+
<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Documentation</th>
|
|
108
|
+
<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Views</th>
|
|
109
|
+
</tr>
|
|
110
|
+
</thead>
|
|
111
|
+
<tbody>
|
|
112
|
+
\${registeredModels.map((m) => {
|
|
113
|
+
const lower = m.toLowerCase()
|
|
114
|
+
const docsUrl = \\\`\\\${basePath}/\\\${lower}\\\`
|
|
115
|
+
const scalarUrl = \\\`\\\${basePath}/\\\${lower}?ui=scalar\\\`
|
|
116
|
+
const jsonUrl = \\\`\\\${basePath}/\\\${lower}?ui=json\\\`
|
|
117
|
+
const yamlUrl = \\\`\\\${basePath}/\\\${lower}?ui=yaml\\\`
|
|
118
|
+
const playgroundUrl = \\\`\\\${basePath}/\\\${lower}?ui=playground\\\`
|
|
119
|
+
const modelCfg = config.modelConfigs[m]
|
|
120
|
+
const modelPlayground = isPlaygroundAvailable(modelCfg)
|
|
121
|
+
const playgroundLink = modelPlayground
|
|
122
|
+
? \\\`, <a href="\\\${playgroundUrl}" class="text-inherit underline">playground</a>\\\`
|
|
123
|
+
: ''
|
|
124
|
+
return \\\`
|
|
125
|
+
<tr>
|
|
126
|
+
<td class="text-left py-2 px-2 border-b border-gray-300 align-top">\\\${escapeHtml(m)}</td>
|
|
127
|
+
<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>
|
|
128
|
+
<td class="text-left py-2 px-2 border-b border-gray-300 align-top">
|
|
129
|
+
<a href="\\\${scalarUrl}" class="text-inherit underline">scalar</a>,
|
|
130
|
+
<a href="\\\${jsonUrl}" class="text-inherit underline">json</a>,
|
|
131
|
+
<a href="\\\${yamlUrl}" class="text-inherit underline">yaml</a>\\\${playgroundLink}
|
|
132
|
+
</td>
|
|
133
|
+
</tr>
|
|
134
|
+
\\\`
|
|
135
|
+
}).join('')}
|
|
136
|
+
</tbody>
|
|
137
|
+
</table>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</body>
|
|
141
|
+
</html>\`
|
|
142
|
+
|
|
143
|
+
res.type('html').send(html)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function registerModelDocs(
|
|
148
|
+
app: any,
|
|
149
|
+
basePath: string = '/docs',
|
|
150
|
+
configs: CombinedDocsConfig['modelConfigs'] = {},
|
|
151
|
+
options?: { disableOpenApi?: boolean }
|
|
152
|
+
) {
|
|
153
|
+
const normalizedBase = removeTrailingSlash(basePath)
|
|
154
|
+
const registeredModels = Object.keys(configs).filter((m) => {
|
|
155
|
+
const cfg = configs[m]
|
|
156
|
+
return m in docsHandlers && !isOpenApiDisabled(cfg?.disableOpenApi ?? options?.disableOpenApi)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (registeredModels.length === 0) return
|
|
160
|
+
|
|
161
|
+
registeredModels.forEach((model) => {
|
|
162
|
+
const handler = docsHandlers[model]
|
|
163
|
+
const cfg = configs[model] || {}
|
|
164
|
+
const path = \\\`\\\${normalizedBase}/\\\${model.toLowerCase()}\\\`
|
|
165
|
+
console.log(\\\` Registered docs: \\\${path}\\\`)
|
|
166
|
+
app.get(path, handler(cfg))
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=generateUnifiedDocs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateUnifiedDocs.js","sourceRoot":"","sources":["../../src/generators/generateUnifiedDocs.ts"],"names":[],"mappings":";;AAAA,kDAuKC;AAvKD,SAAgB,mBAAmB,CAAC,MAAgB;IAClD,MAAM,OAAO,GAAG,MAAM;SACnB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,KAAK,kBAAkB,KAAK,IAAI,KAAK,OAAO,CAAC;SACxE,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,OAAO,GAAG,OAAO;;;;;;;EAOjB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0J9D,CAAA;AACD,CAAC"}
|