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.
Files changed (34) hide show
  1. package/dist/bin.d.ts +1 -1
  2. package/dist/bin.js +1 -1
  3. package/dist/bin.js.map +1 -1
  4. package/dist/generators/generateRouter.js +5 -5
  5. package/dist/generators/generateUnifiedDocs.js +74 -65
  6. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  7. package/dist/generators/generateUnifiedHandler.js +26 -13
  8. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  9. package/dist/generators/generateUnifiedScalarUI.js +13 -13
  10. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  11. package/dist/index.js +23 -23
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/copyFiles.d.ts +1 -5
  14. package/dist/utils/copyFiles.js +2 -17
  15. package/dist/utils/copyFiles.js.map +1 -1
  16. package/dist/utils/strings.d.ts +1 -1
  17. package/dist/utils/strings.js +2 -2
  18. package/dist/utils/strings.js.map +1 -1
  19. package/dist/utils/writeFileSafely.js +0 -6
  20. package/dist/utils/writeFileSafely.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/bin.ts +1 -1
  23. package/src/client/encodeQueryParams.ts +1 -1
  24. package/src/copy/buildModelOpenApi.ts +90 -19
  25. package/src/copy/parseQueryParams.ts +2 -2
  26. package/src/generators/generateImportPrismaStatement.ts +1 -1
  27. package/src/generators/generateRouter.ts +5 -5
  28. package/src/generators/generateUnifiedDocs.ts +75 -65
  29. package/src/generators/generateUnifiedHandler.ts +26 -13
  30. package/src/generators/generateUnifiedScalarUI.ts +13 -13
  31. package/src/index.ts +9 -9
  32. package/src/utils/copyFiles.ts +2 -25
  33. package/src/utils/strings.ts +2 -2
  34. 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,0CA6DC;AAhFD,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,aAAa;YAChB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAA;YAClD,MAAK;QAEP,KAAK,cAAc;YACjB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CAAA;YACtD,MAAK;QAEP,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"}
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator for Hono CRUD API with OpenAPI documentation",
4
- "version": "1.23.0",
4
+ "version": "1.25.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
package/src/bin.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import '.'
2
+ import './index.js'
@@ -53,4 +53,4 @@ export const encodeQueryParams = (params: Record<string, unknown>): string => {
53
53
  }
54
54
 
55
55
  return entries.join('&')
56
- }
56
+ }
@@ -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 requiredScalars = fields
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 (requiredScalars.length > 0) {
232
- createInputSchema.required = [...requiredScalars]
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 (requiredScalars.length > 0) {
249
- createManyInputSchema.required = [...requiredScalars]
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(field: ModelField): SchemaObject | RefObject {
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 schema: SchemaObject | RefObject
1446
+ let baseSchema: SchemaObject | RefObject
1386
1447
 
1387
1448
  switch (field.kind) {
1388
1449
  case 'scalar':
1389
- schema = mapScalarType(field.type)
1450
+ baseSchema = mapScalarType(field.type)
1390
1451
  break
1391
1452
  case 'enum':
1392
- schema = { $ref: `#/components/schemas/${field.type}` }
1453
+ baseSchema = { $ref: `#/components/schemas/${field.type}` }
1393
1454
  break
1394
1455
  default:
1395
- schema = { type: 'string' }
1456
+ baseSchema = { type: 'string' }
1396
1457
  }
1397
1458
 
1459
+ let schema: SchemaObject | RefObject
1460
+
1398
1461
  if (field.isList) {
1399
- schema = { type: 'array', items: schema }
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
@@ -75,4 +75,4 @@ export function getRelativeClientPath(
75
75
  const routerDirPath = path.join(outputValue, modelName)
76
76
 
77
77
  return getRelativeImportPath(routerDirPath, clientGenerator.output.value)
78
- }
78
+ }
@@ -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, RequestHandler } from 'express'
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
- ${models.map((model) => ` ${model}: ${model}Docs`).join(',\n')}
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 html = \`<!DOCTYPE html>
81
- <html lang="en">
82
- <head>
83
- <meta charset="utf-8" />
84
- <meta name="viewport" content="width=device-width, initial-scale=1" />
85
- <title>\${escapeHtml(title)}</title>
86
- <script src="https://cdn.tailwindcss.com"></script>
87
- </head>
88
- <body class="m-0 bg-white text-gray-900 font-serif leading-normal">
89
- <div class="max-w-[980px] mx-auto px-7 pt-10 pb-16">
90
- <div class="border-b-2 border-gray-900 pb-3.5 mb-[18px]">
91
- <div class="text-[28px] font-bold tracking-wide">\${escapeHtml(title)}</div>
92
- \${description ? '<div class="mt-1.5 text-gray-500 text-sm">' + escapeHtml(description) + '</div>' : ''}
93
- <div class="mt-3 flex gap-x-5 text-[13px] text-gray-500">
94
- \${version ? '<div>Version: ' + escapeHtml(version) + '</div>' : ''}
95
- <div>Generated: \${escapeHtml(generatedAt)}</div>
96
- </div>
97
- </div>
98
-
99
- <div class="mt-[22px]">
100
- <h2 class="m-0 mb-2.5 text-lg border-t border-gray-300 pt-3.5">Models</h2>
101
- <table class="w-full border-collapse text-[13px]">
102
- <thead>
103
- <tr>
104
- <th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Model</th>
105
- <th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Documentation</th>
106
- <th class="text-left py-2 px-2 border-b border-gray-300 align-top font-bold">Views</th>
107
- </tr>
108
- </thead>
109
- <tbody>
110
- \${registeredModels.map((m) => {
111
- const lower = m.toLowerCase()
112
- const docsUrl = \\\`\\\${basePath}/\\\${lower}\\\`
113
- const scalarUrl = \\\`\\\${basePath}/\\\${lower}?ui=scalar\\\`
114
- const jsonUrl = \\\`\\\${basePath}/\\\${lower}?ui=json\\\`
115
- const yamlUrl = \\\`\\\${basePath}/\\\${lower}?ui=yaml\\\`
116
- const playgroundUrl = \\\`\\\${basePath}/\\\${lower}?ui=playground\\\`
117
- const modelCfg = config.modelConfigs[m]
118
- const modelPlayground = isPlaygroundAvailable(modelCfg)
119
- const playgroundLink = modelPlayground
120
- ? \\\`, <a href="\\\${playgroundUrl}" class="text-inherit underline">playground</a>\\\`
121
- : ''
122
- return \\\`
123
- <tr>
124
- <td class="text-left py-2 px-2 border-b border-gray-300 align-top">\\\${escapeHtml(m)}</td>
125
- <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>
126
- <td class="text-left py-2 px-2 border-b border-gray-300 align-top">
127
- <a href="\\\${scalarUrl}" class="text-inherit underline">scalar</a>,
128
- <a href="\\\${jsonUrl}" class="text-inherit underline">json</a>,
129
- <a href="\\\${yamlUrl}" class="text-inherit underline">yaml</a>\\\${playgroundLink}
130
- </td>
131
- </tr>
132
- \\\`
133
- }).join('')}
134
- </tbody>
135
- </table>
136
- </div>
137
- </div>
138
- </body>
139
- </html>\`
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 path = \\\`\\\${normalizedBase}/\\\${model.toLowerCase()}\\\`
163
- console.log(\\\` Registered docs: \\\${path}\\\`)
164
- app.get(path, handler(cfg))
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 { Prisma, PrismaClient } from '${importPath}'
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
- { name: 'CreateManyAndReturn', method: 'createManyAndReturn' },
354
- { name: 'Update', method: 'update' },
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
- { name: 'Upsert', method: 'upsert' },
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 = shape
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 countShape
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 type { Context } from 'hono'
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 (c: Context) => {
1362
+ return (req: Request, res: Response) => {
1363
1363
  const disabled = isOpenApiDisabled(config.disableOpenApi)
1364
- if (disabled) return c.text('OpenAPI documentation is disabled in production', 404)
1364
+ if (disabled) return res.status(404).send('OpenAPI documentation is disabled in production')
1365
1365
 
1366
- const rawUi = c.req.query('ui') || config.docsUi || 'docs'
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 c.text('Query builder is disabled', 404)
1372
+ return res.status(404).send('Query builder is disabled')
1373
1373
  }
1374
- return c.html(renderPlayground('${modelName}', config))
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 c.text(yaml as string, 200, { 'Content-Type': 'application/yaml' })
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 c.json(spec)
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 c.html(renderScalar('${modelName}', spec, pageTitle, config.scalarCdnUrl))
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 c.html(html)
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() {