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.
Files changed (110) hide show
  1. package/README.md +399 -194
  2. package/dist/bin.d.ts +2 -0
  3. package/dist/bin.js +1 -1
  4. package/dist/bin.js.map +1 -1
  5. package/dist/client/encodeQueryParams.d.ts +1 -0
  6. package/dist/client/encodeQueryParams.js +33 -0
  7. package/dist/client/encodeQueryParams.js.map +1 -0
  8. package/dist/constants.d.ts +1 -0
  9. package/dist/copy/misc.d.ts +5 -0
  10. package/dist/copy/misc.js +52 -0
  11. package/dist/copy/misc.js.map +1 -0
  12. package/dist/generators/generateImportPrismaStatement.d.ts +3 -0
  13. package/dist/generators/generateImportPrismaStatement.js +55 -0
  14. package/dist/generators/generateImportPrismaStatement.js.map +1 -0
  15. package/dist/generators/generateQueryBuilderHelper.d.ts +2 -0
  16. package/dist/generators/generateQueryBuilderHelper.js +139 -0
  17. package/dist/generators/generateQueryBuilderHelper.js.map +1 -0
  18. package/dist/generators/generateRouter.d.ts +6 -0
  19. package/dist/generators/generateRouter.js +340 -0
  20. package/dist/generators/generateRouter.js.map +1 -0
  21. package/dist/generators/generateUnifiedDocs.d.ts +1 -0
  22. package/dist/generators/generateUnifiedDocs.js +171 -0
  23. package/dist/generators/generateUnifiedDocs.js.map +1 -0
  24. package/dist/generators/generateUnifiedHandler.d.ts +6 -0
  25. package/dist/generators/generateUnifiedHandler.js +444 -0
  26. package/dist/generators/generateUnifiedHandler.js.map +1 -0
  27. package/dist/generators/generateUnifiedScalarUI.d.ts +5 -0
  28. package/dist/generators/generateUnifiedScalarUI.js +1390 -0
  29. package/dist/generators/generateUnifiedScalarUI.js.map +1 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +80 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/utils/copyFiles.d.ts +6 -0
  34. package/dist/utils/copyFiles.js +123 -21
  35. package/dist/utils/copyFiles.js.map +1 -1
  36. package/dist/utils/strings.d.ts +2 -0
  37. package/dist/utils/writeFileSafely.d.ts +10 -0
  38. package/dist/utils/writeFileSafely.js +86 -14
  39. package/dist/utils/writeFileSafely.js.map +1 -1
  40. package/package.json +64 -31
  41. package/src/client/encodeQueryParams.ts +56 -0
  42. package/src/copy/buildModelOpenApi.ts +1569 -0
  43. package/src/copy/createOutputValidatorMiddleware.ts +1 -1
  44. package/src/copy/createValidatorMiddleware.ts +1 -1
  45. package/src/copy/misc.ts +22 -1
  46. package/src/copy/operationDefinitions.ts +96 -0
  47. package/src/copy/parseQueryParams.ts +36 -21
  48. package/src/copy/routeConfig.ts +69 -27
  49. package/src/copy/transformZod.ts +15 -16
  50. package/dist/generator.js +0 -168
  51. package/dist/generator.js.map +0 -1
  52. package/dist/helpers/generateAggregate.js +0 -51
  53. package/dist/helpers/generateAggregate.js.map +0 -1
  54. package/dist/helpers/generateCount.js +0 -50
  55. package/dist/helpers/generateCount.js.map +0 -1
  56. package/dist/helpers/generateCreate.js +0 -49
  57. package/dist/helpers/generateCreate.js.map +0 -1
  58. package/dist/helpers/generateCreateMany.js +0 -49
  59. package/dist/helpers/generateCreateMany.js.map +0 -1
  60. package/dist/helpers/generateDelete.js +0 -49
  61. package/dist/helpers/generateDelete.js.map +0 -1
  62. package/dist/helpers/generateDeleteMany.js +0 -49
  63. package/dist/helpers/generateDeleteMany.js.map +0 -1
  64. package/dist/helpers/generateFindFirst.js +0 -56
  65. package/dist/helpers/generateFindFirst.js.map +0 -1
  66. package/dist/helpers/generateFindMany.js +0 -56
  67. package/dist/helpers/generateFindMany.js.map +0 -1
  68. package/dist/helpers/generateFindUnique.js +0 -56
  69. package/dist/helpers/generateFindUnique.js.map +0 -1
  70. package/dist/helpers/generateGroupBy.js +0 -51
  71. package/dist/helpers/generateGroupBy.js.map +0 -1
  72. package/dist/helpers/generateImportPrismaStatement.js +0 -25
  73. package/dist/helpers/generateImportPrismaStatement.js.map +0 -1
  74. package/dist/helpers/generateRouteFile.js +0 -192
  75. package/dist/helpers/generateRouteFile.js.map +0 -1
  76. package/dist/helpers/generateUpdate.js +0 -49
  77. package/dist/helpers/generateUpdate.js.map +0 -1
  78. package/dist/helpers/generateUpdateMany.js +0 -49
  79. package/dist/helpers/generateUpdateMany.js.map +0 -1
  80. package/dist/helpers/generateUpsert.js +0 -49
  81. package/dist/helpers/generateUpsert.js.map +0 -1
  82. package/dist/utils/formatFile.js +0 -26
  83. package/dist/utils/formatFile.js.map +0 -1
  84. package/src/bin.ts +0 -2
  85. package/src/constants.ts +0 -1
  86. package/src/copy/encodeQueryParams.spec.ts +0 -303
  87. package/src/copy/encodeQueryParams.ts +0 -44
  88. package/src/copy/misc.spec.ts +0 -62
  89. package/src/copy/parseQueryParams.spec.ts +0 -187
  90. package/src/copy/transformZod.spec.ts +0 -763
  91. package/src/generator.ts +0 -188
  92. package/src/helpers/generateAggregate.ts +0 -59
  93. package/src/helpers/generateCount.ts +0 -58
  94. package/src/helpers/generateCreate.ts +0 -56
  95. package/src/helpers/generateCreateMany.ts +0 -55
  96. package/src/helpers/generateDelete.ts +0 -57
  97. package/src/helpers/generateDeleteMany.ts +0 -57
  98. package/src/helpers/generateFindFirst.ts +0 -62
  99. package/src/helpers/generateFindMany.ts +0 -62
  100. package/src/helpers/generateFindUnique.ts +0 -62
  101. package/src/helpers/generateGroupBy.ts +0 -60
  102. package/src/helpers/generateImportPrismaStatement.ts +0 -38
  103. package/src/helpers/generateRouteFile.ts +0 -195
  104. package/src/helpers/generateUpdate.ts +0 -56
  105. package/src/helpers/generateUpdateMany.ts +0 -56
  106. package/src/helpers/generateUpsert.ts +0 -57
  107. package/src/utils/copyFiles.ts +0 -27
  108. package/src/utils/formatFile.ts +0 -22
  109. package/src/utils/strings.ts +0 -7
  110. 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('&', '&amp;')
39
+ .replaceAll('<', '&lt;')
40
+ .replaceAll('>', '&gt;')
41
+ .replaceAll('"', '&quot;')
42
+ .replaceAll("'", '&#039;')
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"}
@@ -0,0 +1,6 @@
1
+ import { DMMF } from '@prisma/generator-helper';
2
+ export interface UnifiedHandlerOptions {
3
+ model: DMMF.Model;
4
+ prismaImportStatement: string;
5
+ }
6
+ export declare function generateUnifiedHandler(options: UnifiedHandlerOptions): string;