prisma-generator-express 1.23.0 → 1.25.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/bin.d.ts +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/generators/generateRouter.js +5 -5
- package/dist/generators/generateUnifiedDocs.js +74 -65
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.js +26 -13
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.js +13 -13
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +1 -5
- package/dist/utils/copyFiles.js +2 -17
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/strings.d.ts +1 -1
- package/dist/utils/strings.js +2 -2
- package/dist/utils/strings.js.map +1 -1
- package/dist/utils/writeFileSafely.js +0 -6
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +1 -1
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +1 -1
- package/src/copy/buildModelOpenApi.ts +90 -19
- package/src/copy/parseQueryParams.ts +2 -2
- package/src/generators/generateImportPrismaStatement.ts +1 -1
- package/src/generators/generateRouter.ts +5 -5
- package/src/generators/generateUnifiedDocs.ts +75 -65
- package/src/generators/generateUnifiedHandler.ts +26 -13
- package/src/generators/generateUnifiedScalarUI.ts +13 -13
- package/src/index.ts +9 -9
- package/src/utils/copyFiles.ts +2 -25
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +1 -9
|
@@ -54,12 +54,6 @@ async function writeFileSafely({ content, options, model, operation, }) {
|
|
|
54
54
|
}
|
|
55
55
|
let filePath;
|
|
56
56
|
switch (operation) {
|
|
57
|
-
case 'cacheConfig':
|
|
58
|
-
filePath = path.join(outputPath, 'cacheConfig.ts');
|
|
59
|
-
break;
|
|
60
|
-
case 'types/inputs':
|
|
61
|
-
filePath = path.join(outputPath, 'types', 'inputs.ts');
|
|
62
|
-
break;
|
|
63
57
|
case 'combinedDocs':
|
|
64
58
|
filePath = path.join(outputPath, 'combinedDocs.ts');
|
|
65
59
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeFileSafely.js","sourceRoot":"","sources":["../../src/utils/writeFileSafely.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,
|
|
1
|
+
{"version":3,"file":"writeFileSafely.js","sourceRoot":"","sources":["../../src/utils/writeFileSafely.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,0CAqDC;AAxED,uCAAwB;AACxB,2CAA4B;AAC5B,wDAA+B;AAS/B,IAAI,gBAAqD,CAAA;AAEzD,KAAK,UAAU,kBAAkB;IAC/B,IAAI,gBAAgB,KAAK,SAAS;QAAE,OAAO,gBAAgB,CAAA;IAC3D,gBAAgB,GAAG,MAAM,kBAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC9D,OAAO,gBAAgB,CAAA;AACzB,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,EACpC,OAAO,EACP,OAAO,EACP,KAAK,EACL,SAAS,GACQ;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAA;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC5C,CAAC;IAED,IAAI,QAAgB,CAAA;IAEpB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,cAAc;YACjB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAA;YACnD,MAAK;QAEP,KAAK,cAAc;YACjB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAA;YACnD,MAAK;QAEP;YACE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,SAAS,EAAE,CAAC,CAAA;YAC/D,CAAC;YACD,QAAQ,GAAG,IAAI,CAAC,IAAI,CAClB,UAAU,EACV,KAAK,CAAC,IAAI,EACV,GAAG,KAAK,CAAC,IAAI,GAAG,SAAS,KAAK,CAC/B,CAAA;IACL,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5C,CAAC;IAED,IAAI,gBAAwB,CAAA;IAC5B,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,MAAM,kBAAkB,EAAE,CAAA;QAClD,gBAAgB,GAAG,MAAM,kBAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;YAChD,GAAG,eAAe;YAClB,MAAM,EAAE,YAAY;SACrB,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CACV,sCAAsC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,uBAAuB,CACrF,CAAA;QACD,gBAAgB,GAAG,OAAO,CAAA;IAC5B,CAAC;IAED,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAA;AAC9C,CAAC"}
|
package/package.json
CHANGED
package/src/bin.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import '.'
|
|
2
|
+
import './index.js'
|
|
@@ -123,6 +123,45 @@ function queryParam(
|
|
|
123
123
|
return param
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
function scalarUpdateOperations(
|
|
127
|
+
baseSchema: SchemaObject | RefObject,
|
|
128
|
+
fieldType: string,
|
|
129
|
+
fieldKind: string,
|
|
130
|
+
): SchemaObject {
|
|
131
|
+
const ops: Record<string, SchemaObject | RefObject> = { set: baseSchema }
|
|
132
|
+
|
|
133
|
+
if (fieldKind === 'scalar' && NUMERIC_SCALAR_TYPES.has(fieldType)) {
|
|
134
|
+
ops.increment = baseSchema
|
|
135
|
+
ops.decrement = baseSchema
|
|
136
|
+
ops.multiply = baseSchema
|
|
137
|
+
ops.divide = baseSchema
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
oneOf: [
|
|
142
|
+
baseSchema,
|
|
143
|
+
{ type: 'object', properties: ops },
|
|
144
|
+
],
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function listScalarUpdateOperations(
|
|
149
|
+
itemSchema: SchemaObject | RefObject,
|
|
150
|
+
): SchemaObject {
|
|
151
|
+
return {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
set: { type: 'array', items: itemSchema },
|
|
155
|
+
push: {
|
|
156
|
+
oneOf: [
|
|
157
|
+
itemSchema as SchemaObject,
|
|
158
|
+
{ type: 'array', items: itemSchema },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
126
165
|
export function buildModelOpenApi(
|
|
127
166
|
modelName: string,
|
|
128
167
|
modelFields: ModelField[],
|
|
@@ -214,7 +253,24 @@ function generateOperationSchemas(
|
|
|
214
253
|
}
|
|
215
254
|
})
|
|
216
255
|
|
|
217
|
-
const
|
|
256
|
+
const relationFkFields = new Set(
|
|
257
|
+
fields
|
|
258
|
+
.filter((f) => f.kind === 'object' && f.relationFromFields?.length)
|
|
259
|
+
.flatMap((f) => f.relationFromFields!),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const requiredCreateScalars = fields
|
|
263
|
+
.filter(
|
|
264
|
+
(f) =>
|
|
265
|
+
(f.kind === 'scalar' || f.kind === 'enum') &&
|
|
266
|
+
f.isRequired &&
|
|
267
|
+
!f.hasDefaultValue &&
|
|
268
|
+
!f.isUpdatedAt &&
|
|
269
|
+
!relationFkFields.has(f.name),
|
|
270
|
+
)
|
|
271
|
+
.map((f) => f.name)
|
|
272
|
+
|
|
273
|
+
const requiredCreateManyScalars = fields
|
|
218
274
|
.filter(
|
|
219
275
|
(f) =>
|
|
220
276
|
(f.kind === 'scalar' || f.kind === 'enum') &&
|
|
@@ -226,34 +282,34 @@ function generateOperationSchemas(
|
|
|
226
282
|
|
|
227
283
|
const createInputSchema: SchemaObject = {
|
|
228
284
|
type: 'object',
|
|
229
|
-
properties: fieldsToWriteProperties(fields),
|
|
285
|
+
properties: fieldsToWriteProperties(fields, 'create'),
|
|
230
286
|
}
|
|
231
|
-
if (
|
|
232
|
-
createInputSchema.required = [...
|
|
287
|
+
if (requiredCreateScalars.length > 0) {
|
|
288
|
+
createInputSchema.required = [...requiredCreateScalars]
|
|
233
289
|
}
|
|
234
290
|
|
|
235
291
|
spec.components.schemas[`${modelName}CreateInput`] = createInputSchema
|
|
236
292
|
|
|
237
293
|
spec.components.schemas[`${modelName}UpdateInput`] = {
|
|
238
294
|
type: 'object',
|
|
239
|
-
properties: fieldsToWriteProperties(fields),
|
|
295
|
+
properties: fieldsToWriteProperties(fields, 'update'),
|
|
240
296
|
}
|
|
241
297
|
|
|
242
298
|
const createManyInputSchema: SchemaObject = {
|
|
243
299
|
type: 'object',
|
|
244
|
-
properties: fieldsToBulkWriteProperties(fields),
|
|
300
|
+
properties: fieldsToBulkWriteProperties(fields, 'create'),
|
|
245
301
|
description:
|
|
246
302
|
'Scalar-only input for bulk create. Nested relation writes are not supported in createMany operations.',
|
|
247
303
|
}
|
|
248
|
-
if (
|
|
249
|
-
createManyInputSchema.required = [...
|
|
304
|
+
if (requiredCreateManyScalars.length > 0) {
|
|
305
|
+
createManyInputSchema.required = [...requiredCreateManyScalars]
|
|
250
306
|
}
|
|
251
307
|
|
|
252
308
|
spec.components.schemas[`${modelName}CreateManyInput`] = createManyInputSchema
|
|
253
309
|
|
|
254
310
|
spec.components.schemas[`${modelName}UpdateManyMutationInput`] = {
|
|
255
311
|
type: 'object',
|
|
256
|
-
properties: fieldsToBulkWriteProperties(fields),
|
|
312
|
+
properties: fieldsToBulkWriteProperties(fields, 'update'),
|
|
257
313
|
description:
|
|
258
314
|
'Scalar-only input for bulk update. Nested relation writes are not supported in updateMany operations.',
|
|
259
315
|
}
|
|
@@ -1085,21 +1141,23 @@ function fieldsToProperties(
|
|
|
1085
1141
|
|
|
1086
1142
|
function fieldsToWriteProperties(
|
|
1087
1143
|
fields: ModelField[],
|
|
1144
|
+
mode: 'create' | 'update',
|
|
1088
1145
|
): Record<string, SchemaObject | RefObject> {
|
|
1089
1146
|
const props: Record<string, SchemaObject | RefObject> = {}
|
|
1090
1147
|
for (const field of fields) {
|
|
1091
|
-
props[field.name] = mapFieldToWriteSchema(field)
|
|
1148
|
+
props[field.name] = mapFieldToWriteSchema(field, mode)
|
|
1092
1149
|
}
|
|
1093
1150
|
return props
|
|
1094
1151
|
}
|
|
1095
1152
|
|
|
1096
1153
|
function fieldsToBulkWriteProperties(
|
|
1097
1154
|
fields: ModelField[],
|
|
1155
|
+
mode: 'create' | 'update',
|
|
1098
1156
|
): Record<string, SchemaObject | RefObject> {
|
|
1099
1157
|
const props: Record<string, SchemaObject | RefObject> = {}
|
|
1100
1158
|
for (const field of fields) {
|
|
1101
1159
|
if (field.kind === 'object') continue
|
|
1102
|
-
props[field.name] = mapFieldToWriteSchema(field)
|
|
1160
|
+
props[field.name] = mapFieldToWriteSchema(field, mode)
|
|
1103
1161
|
}
|
|
1104
1162
|
return props
|
|
1105
1163
|
}
|
|
@@ -1164,7 +1222,10 @@ function mapFieldToSchema(field: ModelField): SchemaObject | RefObject {
|
|
|
1164
1222
|
return schema
|
|
1165
1223
|
}
|
|
1166
1224
|
|
|
1167
|
-
function mapFieldToWriteSchema(
|
|
1225
|
+
function mapFieldToWriteSchema(
|
|
1226
|
+
field: ModelField,
|
|
1227
|
+
mode: 'create' | 'update',
|
|
1228
|
+
): SchemaObject | RefObject {
|
|
1168
1229
|
if (field.kind === 'object') {
|
|
1169
1230
|
if (field.isList) {
|
|
1170
1231
|
return {
|
|
@@ -1382,21 +1443,31 @@ function mapFieldToWriteSchema(field: ModelField): SchemaObject | RefObject {
|
|
|
1382
1443
|
}
|
|
1383
1444
|
}
|
|
1384
1445
|
|
|
1385
|
-
let
|
|
1446
|
+
let baseSchema: SchemaObject | RefObject
|
|
1386
1447
|
|
|
1387
1448
|
switch (field.kind) {
|
|
1388
1449
|
case 'scalar':
|
|
1389
|
-
|
|
1450
|
+
baseSchema = mapScalarType(field.type)
|
|
1390
1451
|
break
|
|
1391
1452
|
case 'enum':
|
|
1392
|
-
|
|
1453
|
+
baseSchema = { $ref: `#/components/schemas/${field.type}` }
|
|
1393
1454
|
break
|
|
1394
1455
|
default:
|
|
1395
|
-
|
|
1456
|
+
baseSchema = { type: 'string' }
|
|
1396
1457
|
}
|
|
1397
1458
|
|
|
1459
|
+
let schema: SchemaObject | RefObject
|
|
1460
|
+
|
|
1398
1461
|
if (field.isList) {
|
|
1399
|
-
|
|
1462
|
+
if (mode === 'update') {
|
|
1463
|
+
schema = listScalarUpdateOperations(baseSchema)
|
|
1464
|
+
} else {
|
|
1465
|
+
schema = { type: 'array', items: baseSchema }
|
|
1466
|
+
}
|
|
1467
|
+
} else if (mode === 'update') {
|
|
1468
|
+
schema = scalarUpdateOperations(baseSchema, field.type, field.kind)
|
|
1469
|
+
} else {
|
|
1470
|
+
schema = baseSchema
|
|
1400
1471
|
}
|
|
1401
1472
|
|
|
1402
1473
|
if (!field.isRequired && !field.isList) {
|
|
@@ -1410,7 +1481,7 @@ function mapFieldToWriteSchema(field: ModelField): SchemaObject | RefObject {
|
|
|
1410
1481
|
description: field.documentation,
|
|
1411
1482
|
}
|
|
1412
1483
|
} else if (!('$ref' in schema)) {
|
|
1413
|
-
schema.description = field.documentation
|
|
1484
|
+
(schema as SchemaObject).description = field.documentation
|
|
1414
1485
|
}
|
|
1415
1486
|
}
|
|
1416
1487
|
|
|
@@ -1566,4 +1637,4 @@ function toYaml(obj: any, indent = 0): string {
|
|
|
1566
1637
|
}
|
|
1567
1638
|
|
|
1568
1639
|
return yaml
|
|
1569
|
-
}
|
|
1640
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isObject, isSafeKey, sanitizeKeys } from './misc'
|
|
1
|
+
import { isObject, isSafeKey, sanitizeKeys } from './misc.js'
|
|
2
2
|
|
|
3
3
|
type QueryParams =
|
|
4
4
|
| string
|
|
@@ -48,7 +48,7 @@ export const parseQueryParams = (params: QueryParams): unknown => {
|
|
|
48
48
|
if (typeof raw === 'string') {
|
|
49
49
|
parsedParams[key] = parseQueryValue(raw, key)
|
|
50
50
|
} else {
|
|
51
|
-
parsedParams[key] = raw
|
|
51
|
+
parsedParams[key] = sanitizeKeys(raw)
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
return parsedParams
|
|
@@ -57,10 +57,10 @@ import {
|
|
|
57
57
|
${modelName}Aggregate,
|
|
58
58
|
${modelName}Count,
|
|
59
59
|
${modelName}GroupBy
|
|
60
|
-
} from './${modelName}Handlers'
|
|
61
|
-
import type { RouteConfig } from '../routeConfig'
|
|
62
|
-
import { parseQueryParams } from '../parseQueryParams'
|
|
63
|
-
import { buildModelOpenApi } from '../buildModelOpenApi'
|
|
60
|
+
} from './${modelName}Handlers.js'
|
|
61
|
+
import type { RouteConfig } from '../routeConfig.js'
|
|
62
|
+
import { parseQueryParams } from '../parseQueryParams.js'
|
|
63
|
+
import { buildModelOpenApi } from '../buildModelOpenApi.js'
|
|
64
64
|
|
|
65
65
|
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
66
66
|
|
|
@@ -140,7 +140,7 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
|
|
|
140
140
|
if (qbEnabled) {
|
|
141
141
|
const qbConfig = getQueryBuilderConfig(config)
|
|
142
142
|
if (qbConfig) {
|
|
143
|
-
import('../queryBuilder').then(mod => mod.startQueryBuilder(qbConfig)).catch(() => {})
|
|
143
|
+
import('../queryBuilder.js').then(mod => mod.startQueryBuilder(qbConfig)).catch((err) => { if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err) })
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -3,14 +3,18 @@ export function generateUnifiedDocs(models: string[]): string {
|
|
|
3
3
|
.map((model) => `import { ${model}Docs } from './${model}/${model}Docs'`)
|
|
4
4
|
.join('\n')
|
|
5
5
|
|
|
6
|
+
const handlersEntries = models
|
|
7
|
+
.map((model) => ` ${model}: ${model}Docs`)
|
|
8
|
+
.join(',\n')
|
|
9
|
+
|
|
6
10
|
return `${imports}
|
|
7
|
-
import { Request, Response
|
|
11
|
+
import { Request, Response } from 'express'
|
|
8
12
|
import type { RouteConfig } from './routeConfig'
|
|
9
13
|
|
|
10
14
|
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
11
15
|
|
|
12
16
|
const docsHandlers: Record<string, (config: any) => (req: Request, res: Response) => any> = {
|
|
13
|
-
${
|
|
17
|
+
${handlersEntries}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
type DocsUI = 'docs' | 'scalar' | 'json' | 'yaml' | 'playground'
|
|
@@ -77,66 +81,72 @@ export function generateCombinedDocs(config: CombinedDocsConfig) {
|
|
|
77
81
|
const basePath = removeTrailingSlash(config.basePath || '/docs')
|
|
78
82
|
const generatedAt = new Date().toISOString()
|
|
79
83
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</
|
|
139
|
-
</
|
|
84
|
+
const modelRows = registeredModels.map((m) => {
|
|
85
|
+
const lower = m.toLowerCase()
|
|
86
|
+
const docsUrl = basePath + '/' + lower
|
|
87
|
+
const scalarUrl = docsUrl + '?ui=scalar'
|
|
88
|
+
const jsonUrl = docsUrl + '?ui=json'
|
|
89
|
+
const yamlUrl = docsUrl + '?ui=yaml'
|
|
90
|
+
const playgroundUrl = docsUrl + '?ui=playground'
|
|
91
|
+
const modelCfg = config.modelConfigs[m]
|
|
92
|
+
const modelPlayground = isPlaygroundAvailable(modelCfg)
|
|
93
|
+
const playgroundLink = modelPlayground
|
|
94
|
+
? ', <a href="' + playgroundUrl + '" class="text-inherit underline">playground</a>'
|
|
95
|
+
: ''
|
|
96
|
+
return '<tr>' +
|
|
97
|
+
'<td class="text-left py-2 px-2 border-b border-gray-300 align-top">' + escapeHtml(m) + '</td>' +
|
|
98
|
+
'<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>' +
|
|
99
|
+
'<td class="text-left py-2 px-2 border-b border-gray-300 align-top">' +
|
|
100
|
+
'<a href="' + scalarUrl + '" class="text-inherit underline">scalar</a>, ' +
|
|
101
|
+
'<a href="' + jsonUrl + '" class="text-inherit underline">json</a>, ' +
|
|
102
|
+
'<a href="' + yamlUrl + '" class="text-inherit underline">yaml</a>' +
|
|
103
|
+
playgroundLink +
|
|
104
|
+
'</td>' +
|
|
105
|
+
'</tr>'
|
|
106
|
+
}).join('')
|
|
107
|
+
|
|
108
|
+
const descriptionHtml = description
|
|
109
|
+
? '<div class="mt-1.5 text-gray-500 text-sm">' + escapeHtml(description) + '</div>'
|
|
110
|
+
: ''
|
|
111
|
+
const versionHtml = version
|
|
112
|
+
? '<div>Version: ' + escapeHtml(version) + '</div>'
|
|
113
|
+
: ''
|
|
114
|
+
|
|
115
|
+
const html =
|
|
116
|
+
'<!DOCTYPE html>' +
|
|
117
|
+
'<html lang="en">' +
|
|
118
|
+
'<head>' +
|
|
119
|
+
'<meta charset="utf-8" />' +
|
|
120
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />' +
|
|
121
|
+
'<title>' + escapeHtml(title) + '</title>' +
|
|
122
|
+
'<script src="https://cdn.tailwindcss.com"></' + 'script>' +
|
|
123
|
+
'</head>' +
|
|
124
|
+
'<body class="m-0 bg-white text-gray-900 font-serif leading-normal">' +
|
|
125
|
+
'<div class="max-w-[980px] mx-auto px-7 pt-10 pb-16">' +
|
|
126
|
+
'<div class="border-b-2 border-gray-900 pb-3.5 mb-[18px]">' +
|
|
127
|
+
'<div class="text-[28px] font-bold tracking-wide">' + escapeHtml(title) + '</div>' +
|
|
128
|
+
descriptionHtml +
|
|
129
|
+
'<div class="mt-3 flex gap-x-5 text-[13px] text-gray-500">' +
|
|
130
|
+
versionHtml +
|
|
131
|
+
'<div>Generated: ' + escapeHtml(generatedAt) + '</div>' +
|
|
132
|
+
'</div>' +
|
|
133
|
+
'</div>' +
|
|
134
|
+
'<div class="mt-[22px]">' +
|
|
135
|
+
'<h2 class="m-0 mb-2.5 text-lg border-t border-gray-300 pt-3.5">Models</h2>' +
|
|
136
|
+
'<table class="w-full border-collapse text-[13px]">' +
|
|
137
|
+
'<thead>' +
|
|
138
|
+
'<tr>' +
|
|
139
|
+
'<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Model</th>' +
|
|
140
|
+
'<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Documentation</th>' +
|
|
141
|
+
'<th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Views</th>' +
|
|
142
|
+
'</tr>' +
|
|
143
|
+
'</thead>' +
|
|
144
|
+
'<tbody>' + modelRows + '</tbody>' +
|
|
145
|
+
'</table>' +
|
|
146
|
+
'</div>' +
|
|
147
|
+
'</div>' +
|
|
148
|
+
'</body>' +
|
|
149
|
+
'</html>'
|
|
140
150
|
|
|
141
151
|
res.type('html').send(html)
|
|
142
152
|
}
|
|
@@ -159,9 +169,9 @@ export function registerModelDocs(
|
|
|
159
169
|
registeredModels.forEach((model) => {
|
|
160
170
|
const handler = docsHandlers[model]
|
|
161
171
|
const cfg = configs[model] || {}
|
|
162
|
-
const
|
|
163
|
-
console.log(
|
|
164
|
-
app.get(
|
|
172
|
+
const docPath = normalizedBase + '/' + model.toLowerCase()
|
|
173
|
+
console.log(' Registered docs: ' + docPath)
|
|
174
|
+
app.get(docPath, handler(cfg))
|
|
165
175
|
})
|
|
166
176
|
}
|
|
167
177
|
`
|
|
@@ -13,7 +13,7 @@ export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
|
|
|
13
13
|
prismaImportStatement.match(/from ['"](.+?)['"]/)?.[1] || ''
|
|
14
14
|
|
|
15
15
|
return `
|
|
16
|
-
import {
|
|
16
|
+
import { PrismaClient } from '${importPath}'
|
|
17
17
|
import { Request, Response, NextFunction } from 'express'
|
|
18
18
|
import { sanitizeKeys } from '../misc'
|
|
19
19
|
|
|
@@ -152,6 +152,7 @@ function handleError(error: unknown, next: NextFunction): void {
|
|
|
152
152
|
return
|
|
153
153
|
}
|
|
154
154
|
if (typeof code === 'string' && code.startsWith('P')) {
|
|
155
|
+
console.warn('[prisma-generator-express] Unmapped Prisma error code:', code, (error as any).message || '')
|
|
155
156
|
next(new HttpError(500, 'Database operation failed'))
|
|
156
157
|
return
|
|
157
158
|
}
|
|
@@ -348,10 +349,14 @@ function generateWriteHandlers(
|
|
|
348
349
|
method: string
|
|
349
350
|
requiredFields?: string[]
|
|
350
351
|
}[] = [
|
|
351
|
-
{ name: 'Create', method: 'create' },
|
|
352
|
-
{ name: 'CreateMany', method: 'createMany' },
|
|
353
|
-
{
|
|
354
|
-
|
|
352
|
+
{ name: 'Create', method: 'create', requiredFields: ['data'] },
|
|
353
|
+
{ name: 'CreateMany', method: 'createMany', requiredFields: ['data'] },
|
|
354
|
+
{
|
|
355
|
+
name: 'CreateManyAndReturn',
|
|
356
|
+
method: 'createManyAndReturn',
|
|
357
|
+
requiredFields: ['data'],
|
|
358
|
+
},
|
|
359
|
+
{ name: 'Update', method: 'update', requiredFields: ['where', 'data'] },
|
|
355
360
|
{
|
|
356
361
|
name: 'UpdateMany',
|
|
357
362
|
method: 'updateMany',
|
|
@@ -362,9 +367,13 @@ function generateWriteHandlers(
|
|
|
362
367
|
method: 'updateManyAndReturn',
|
|
363
368
|
requiredFields: ['where', 'data'],
|
|
364
369
|
},
|
|
365
|
-
{ name: 'Delete', method: 'delete' },
|
|
370
|
+
{ name: 'Delete', method: 'delete', requiredFields: ['where'] },
|
|
366
371
|
{ name: 'DeleteMany', method: 'deleteMany', requiredFields: ['where'] },
|
|
367
|
-
{
|
|
372
|
+
{
|
|
373
|
+
name: 'Upsert',
|
|
374
|
+
method: 'upsert',
|
|
375
|
+
requiredFields: ['where', 'create', 'update'],
|
|
376
|
+
},
|
|
368
377
|
]
|
|
369
378
|
|
|
370
379
|
return (
|
|
@@ -414,6 +423,14 @@ async function countForPagination(
|
|
|
414
423
|
const countShape = shape ? buildCountShape(shape) : undefined
|
|
415
424
|
|
|
416
425
|
if (hasDistinct) {
|
|
426
|
+
if (shape) {
|
|
427
|
+
const countArgs: Record<string, any> = {}
|
|
428
|
+
if (query.where) countArgs.where = query.where
|
|
429
|
+
return countShape
|
|
430
|
+
? await delegate.guard(countShape, caller).count(countArgs)
|
|
431
|
+
: await delegate.count(countArgs)
|
|
432
|
+
}
|
|
433
|
+
|
|
417
434
|
const selectField = distinctFields[0]
|
|
418
435
|
const distinctArgs: Record<string, any> = {
|
|
419
436
|
where: query.where,
|
|
@@ -422,17 +439,13 @@ async function countForPagination(
|
|
|
422
439
|
take: DISTINCT_COUNT_LIMIT + 1,
|
|
423
440
|
}
|
|
424
441
|
|
|
425
|
-
const results =
|
|
426
|
-
? await delegate.guard(shape, caller).findMany(distinctArgs)
|
|
427
|
-
: await delegate.findMany(distinctArgs)
|
|
442
|
+
const results = await delegate.findMany(distinctArgs)
|
|
428
443
|
|
|
429
444
|
if (results.length > DISTINCT_COUNT_LIMIT) {
|
|
430
445
|
console.warn('[prisma-generator-express] Distinct count exceeds ' + DISTINCT_COUNT_LIMIT + ', falling back to approximate total')
|
|
431
446
|
const countArgs: Record<string, any> = {}
|
|
432
447
|
if (query.where) countArgs.where = query.where
|
|
433
|
-
return
|
|
434
|
-
? await delegate.guard(countShape, caller).count(countArgs)
|
|
435
|
-
: await delegate.count(countArgs)
|
|
448
|
+
return await delegate.count(countArgs)
|
|
436
449
|
}
|
|
437
450
|
|
|
438
451
|
return results.length
|
|
@@ -86,10 +86,10 @@ export function generateScalarUIHandler(options: {
|
|
|
86
86
|
fields: idx.fields,
|
|
87
87
|
}))
|
|
88
88
|
|
|
89
|
-
return `import
|
|
90
|
-
import { buildModelOpenApi } from '../buildModelOpenApi'
|
|
91
|
-
import type { RouteConfig } from '../routeConfig'
|
|
92
|
-
import { OPERATION_DEFS, isOperationEnabled } from '../operationDefinitions'
|
|
89
|
+
return `import { Request, Response } from 'express'
|
|
90
|
+
import { buildModelOpenApi } from '../buildModelOpenApi.js'
|
|
91
|
+
import type { RouteConfig } from '../routeConfig.js'
|
|
92
|
+
import { OPERATION_DEFS, isOperationEnabled } from '../operationDefinitions.js'
|
|
93
93
|
|
|
94
94
|
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
95
95
|
|
|
@@ -1359,19 +1359,19 @@ function renderDocs(modelName: string, config: DocsConfig) {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
|
|
1361
1361
|
export function ${modelName}Docs(config: DocsConfig = {}) {
|
|
1362
|
-
return (
|
|
1362
|
+
return (req: Request, res: Response) => {
|
|
1363
1363
|
const disabled = isOpenApiDisabled(config.disableOpenApi)
|
|
1364
|
-
if (disabled) return
|
|
1364
|
+
if (disabled) return res.status(404).send('OpenAPI documentation is disabled in production')
|
|
1365
1365
|
|
|
1366
|
-
const rawUi =
|
|
1366
|
+
const rawUi = (req.query['ui'] as string | undefined) || config.docsUi || 'docs'
|
|
1367
1367
|
const validUis: DocsUI[] = ['docs', 'scalar', 'json', 'yaml', 'playground']
|
|
1368
1368
|
const ui: DocsUI = validUis.includes(rawUi as DocsUI) ? (rawUi as DocsUI) : 'docs'
|
|
1369
1369
|
|
|
1370
1370
|
if (ui === 'playground') {
|
|
1371
1371
|
if (!isPlaygroundAvailable(config)) {
|
|
1372
|
-
return
|
|
1372
|
+
return res.status(404).send('Query builder is disabled')
|
|
1373
1373
|
}
|
|
1374
|
-
return
|
|
1374
|
+
return res.type('html').send(renderPlayground('${modelName}', config))
|
|
1375
1375
|
}
|
|
1376
1376
|
|
|
1377
1377
|
if (ui === 'yaml') {
|
|
@@ -1382,7 +1382,7 @@ export function ${modelName}Docs(config: DocsConfig = {}) {
|
|
|
1382
1382
|
config,
|
|
1383
1383
|
{ format: 'yaml' }
|
|
1384
1384
|
)
|
|
1385
|
-
return
|
|
1385
|
+
return res.type('application/yaml').send(yaml as string)
|
|
1386
1386
|
}
|
|
1387
1387
|
|
|
1388
1388
|
const spec = buildModelOpenApi(
|
|
@@ -1393,16 +1393,16 @@ export function ${modelName}Docs(config: DocsConfig = {}) {
|
|
|
1393
1393
|
{ format: 'json' }
|
|
1394
1394
|
)
|
|
1395
1395
|
|
|
1396
|
-
if (ui === 'json') return
|
|
1396
|
+
if (ui === 'json') return res.json(spec)
|
|
1397
1397
|
|
|
1398
1398
|
const pageTitle = config.docsTitle || \`${modelName} API\`
|
|
1399
1399
|
|
|
1400
1400
|
if (ui === 'scalar') {
|
|
1401
|
-
return
|
|
1401
|
+
return res.type('html').send(renderScalar('${modelName}', spec, pageTitle, config.scalarCdnUrl))
|
|
1402
1402
|
}
|
|
1403
1403
|
|
|
1404
1404
|
const html = renderDocs('${modelName}', config)
|
|
1405
|
-
return
|
|
1405
|
+
return res.type('html').send(html)
|
|
1406
1406
|
}
|
|
1407
1407
|
}
|
|
1408
1408
|
`
|
package/src/index.ts
CHANGED
|
@@ -3,18 +3,18 @@ import {
|
|
|
3
3
|
GeneratorOptions,
|
|
4
4
|
DMMF,
|
|
5
5
|
} from '@prisma/generator-helper'
|
|
6
|
-
import { generateUnifiedHandler } from './generators/generateUnifiedHandler'
|
|
7
|
-
import { generateRouterFunction } from './generators/generateRouter'
|
|
8
|
-
import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
|
|
9
|
-
import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
|
|
10
|
-
import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
|
|
6
|
+
import { generateUnifiedHandler } from './generators/generateUnifiedHandler.js'
|
|
7
|
+
import { generateRouterFunction } from './generators/generateRouter.js'
|
|
8
|
+
import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI.js'
|
|
9
|
+
import { generateUnifiedDocs } from './generators/generateUnifiedDocs.js'
|
|
10
|
+
import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper.js'
|
|
11
11
|
import {
|
|
12
12
|
generateImportPrismaStatement,
|
|
13
13
|
getRelativeClientPath,
|
|
14
|
-
} from './generators/generateImportPrismaStatement'
|
|
15
|
-
import { writeFileSafely } from './utils/writeFileSafely'
|
|
16
|
-
import { copyFiles } from './utils/copyFiles'
|
|
17
|
-
import { GENERATOR_NAME } from './constants'
|
|
14
|
+
} from './generators/generateImportPrismaStatement.js'
|
|
15
|
+
import { writeFileSafely } from './utils/writeFileSafely.js'
|
|
16
|
+
import { copyFiles } from './utils/copyFiles.js'
|
|
17
|
+
import { GENERATOR_NAME } from './constants.js'
|
|
18
18
|
|
|
19
19
|
generatorHandler({
|
|
20
20
|
onManifest() {
|