prisma-generator-express 1.65.2 → 1.67.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 (106) hide show
  1. package/README.md +51 -3480
  2. package/dist/client/encodeQueryParams.js +3 -1
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +1 -0
  5. package/dist/copy/misc.js +14 -0
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/copy/operationDefinitions.js +69 -6
  8. package/dist/copy/operationDefinitions.js.map +1 -1
  9. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  10. package/dist/generators/generateFastifyHandler.js +15 -5
  11. package/dist/generators/generateFastifyHandler.js.map +1 -1
  12. package/dist/generators/generateHonoHandler.d.ts +2 -2
  13. package/dist/generators/generateHonoHandler.js +12 -5
  14. package/dist/generators/generateHonoHandler.js.map +1 -1
  15. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  16. package/dist/generators/generateImportPrismaStatement.js +6 -0
  17. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  18. package/dist/generators/generateModelMetadata.js +20 -10
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateOperationCore.d.ts +3 -3
  21. package/dist/generators/generateOperationCore.js +51 -16
  22. package/dist/generators/generateOperationCore.js.map +1 -1
  23. package/dist/generators/generateQueryBuilderHelper.d.ts +1 -1
  24. package/dist/generators/generateQueryBuilderHelper.js +2 -4
  25. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  26. package/dist/generators/generateRelationMeta.js +2 -2
  27. package/dist/generators/generateRelationMeta.js.map +1 -1
  28. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  29. package/dist/generators/generateRouteConfigType.js +29 -11
  30. package/dist/generators/generateRouteConfigType.js.map +1 -1
  31. package/dist/generators/generateRouter.d.ts +7 -4
  32. package/dist/generators/generateRouter.js +107 -14
  33. package/dist/generators/generateRouter.js.map +1 -1
  34. package/dist/generators/generateRouterFastify.d.ts +7 -4
  35. package/dist/generators/generateRouterFastify.js +56 -28
  36. package/dist/generators/generateRouterFastify.js.map +1 -1
  37. package/dist/generators/generateRouterHono.d.ts +7 -4
  38. package/dist/generators/generateRouterHono.js +84 -53
  39. package/dist/generators/generateRouterHono.js.map +1 -1
  40. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  41. package/dist/generators/generateUnifiedDocs.js +15 -8
  42. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  43. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  44. package/dist/generators/generateUnifiedHandler.js +11 -3
  45. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  46. package/dist/guardOptions.js +12 -7
  47. package/dist/guardOptions.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +22 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/utils/copyFiles.js +22 -6
  52. package/dist/utils/copyFiles.js.map +1 -1
  53. package/dist/utils/importExt.d.ts +1 -1
  54. package/dist/utils/pathCasing.d.ts +4 -0
  55. package/dist/utils/pathCasing.js +40 -0
  56. package/dist/utils/pathCasing.js.map +1 -0
  57. package/dist/utils/resolveImportStyle.js +13 -5
  58. package/dist/utils/resolveImportStyle.js.map +1 -1
  59. package/dist/utils/writeFileSafely.js.map +1 -1
  60. package/package.json +16 -3
  61. package/src/bin.ts +1 -1
  62. package/src/client/encodeQueryParams.ts +39 -35
  63. package/src/constants.ts +1 -1
  64. package/src/copy/autoIncludePlanner.ts +93 -27
  65. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  66. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  67. package/src/copy/buildModelOpenApi.ts +153 -50
  68. package/src/copy/concurrency.ts +10 -8
  69. package/src/copy/errorMapper.ts +68 -19
  70. package/src/copy/guardHelpers.ts +30 -9
  71. package/src/copy/materializedCount.ts +17 -5
  72. package/src/copy/misc.ts +88 -68
  73. package/src/copy/ndjson.ts +72 -0
  74. package/src/copy/operationDefinitions.ts +99 -21
  75. package/src/copy/operationRuntime.ts +100 -12
  76. package/src/copy/pagination.ts +25 -11
  77. package/src/copy/parseQueryParams.ts +47 -48
  78. package/src/copy/projectionDefaults.ts +6 -4
  79. package/src/copy/routeConfig.express.ts +16 -4
  80. package/src/copy/routeConfig.fastify.ts +16 -2
  81. package/src/copy/routeConfig.hono.ts +19 -9
  82. package/src/copy/routeConfig.ts +174 -39
  83. package/src/copy/scalarTypes.ts +7 -2
  84. package/src/copy/sse.ts +72 -19
  85. package/src/generators/generateFastifyHandler.ts +24 -13
  86. package/src/generators/generateHonoHandler.ts +21 -13
  87. package/src/generators/generateImportPrismaStatement.ts +93 -81
  88. package/src/generators/generateModelMetadata.ts +24 -12
  89. package/src/generators/generateOperationCore.ts +81 -33
  90. package/src/generators/generateQueryBuilderHelper.ts +20 -4
  91. package/src/generators/generateRelationMeta.ts +23 -11
  92. package/src/generators/generateRouteConfigType.ts +39 -13
  93. package/src/generators/generateRouter.ts +114 -17
  94. package/src/generators/generateRouterFastify.ts +67 -31
  95. package/src/generators/generateRouterHono.ts +108 -60
  96. package/src/generators/generateUnifiedDocs.ts +29 -8
  97. package/src/generators/generateUnifiedHandler.ts +16 -8
  98. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  99. package/src/guardOptions.ts +105 -92
  100. package/src/index.ts +46 -4
  101. package/src/utils/copyFiles.ts +199 -168
  102. package/src/utils/importExt.ts +2 -2
  103. package/src/utils/pathCasing.ts +42 -0
  104. package/src/utils/resolveImportStyle.ts +37 -15
  105. package/src/utils/strings.ts +2 -2
  106. package/src/utils/writeFileSafely.ts +6 -2
@@ -55,22 +55,59 @@ type BuildOptions = {
55
55
  description?: string
56
56
  version?: string
57
57
  writeStrategy?: WriteStrategy
58
+ pathSegment?: string
58
59
  }
59
60
 
60
- const WHERE_PROP: SchemaObject = { type: 'object', description: 'Filter conditions' }
61
- const TAKE_PROP: SchemaObject = { type: 'integer', description: 'Limit results' }
61
+ const WHERE_PROP: SchemaObject = {
62
+ type: 'object',
63
+ description: 'Filter conditions',
64
+ }
65
+ const TAKE_PROP: SchemaObject = {
66
+ type: 'integer',
67
+ description: 'Limit results',
68
+ }
62
69
  const SKIP_PROP: SchemaObject = { type: 'integer', description: 'Skip results' }
63
- const CURSOR_PROP: SchemaObject = { type: 'object', description: 'Cursor for pagination' }
64
- const ORDERBY_PROP: SchemaObject = { description: 'Sort order (object or array of objects)' }
65
- const SELECT_PROP: SchemaObject = { type: 'object', description: 'Select fields' }
66
- const INCLUDE_PROP: SchemaObject = { type: 'object', description: 'Include relations' }
67
- const OMIT_PROP: SchemaObject = { type: 'object', description: 'Omit fields from response' }
68
- const DISTINCT_PROP: SchemaObject = { description: 'Distinct fields (string or array of strings)' }
69
- const AGG_COUNT: SchemaObject = { description: 'Count aggregate (true or field selection object)' }
70
- const AGG_AVG: SchemaObject = { type: 'object', description: 'Average aggregate (field selection object)' }
71
- const AGG_SUM: SchemaObject = { type: 'object', description: 'Sum aggregate (field selection object)' }
72
- const AGG_MIN: SchemaObject = { type: 'object', description: 'Min aggregate (field selection object)' }
73
- const AGG_MAX: SchemaObject = { type: 'object', description: 'Max aggregate (field selection object)' }
70
+ const CURSOR_PROP: SchemaObject = {
71
+ type: 'object',
72
+ description: 'Cursor for pagination',
73
+ }
74
+ const ORDERBY_PROP: SchemaObject = {
75
+ description: 'Sort order (object or array of objects)',
76
+ }
77
+ const SELECT_PROP: SchemaObject = {
78
+ type: 'object',
79
+ description: 'Select fields',
80
+ }
81
+ const INCLUDE_PROP: SchemaObject = {
82
+ type: 'object',
83
+ description: 'Include relations',
84
+ }
85
+ const OMIT_PROP: SchemaObject = {
86
+ type: 'object',
87
+ description: 'Omit fields from response',
88
+ }
89
+ const DISTINCT_PROP: SchemaObject = {
90
+ description: 'Distinct fields (string or array of strings)',
91
+ }
92
+ const AGG_COUNT: SchemaObject = {
93
+ description: 'Count aggregate (true or field selection object)',
94
+ }
95
+ const AGG_AVG: SchemaObject = {
96
+ type: 'object',
97
+ description: 'Average aggregate (field selection object)',
98
+ }
99
+ const AGG_SUM: SchemaObject = {
100
+ type: 'object',
101
+ description: 'Sum aggregate (field selection object)',
102
+ }
103
+ const AGG_MIN: SchemaObject = {
104
+ type: 'object',
105
+ description: 'Min aggregate (field selection object)',
106
+ }
107
+ const AGG_MAX: SchemaObject = {
108
+ type: 'object',
109
+ description: 'Max aggregate (field selection object)',
110
+ }
74
111
 
75
112
  const PROJECTION_PROPS: Record<string, SchemaObject> = {
76
113
  select: SELECT_PROP,
@@ -330,8 +367,18 @@ function applyWriteStrategy(
330
367
  verb: string
331
368
  targetOp: string
332
369
  }> = [
333
- { method: 'post', successCode: '201', verb: 'Create', targetOp: 'createManyAndReturn' },
334
- { method: 'put', successCode: '200', verb: 'Update', targetOp: 'updateManyAndReturn' },
370
+ {
371
+ method: 'post',
372
+ successCode: '201',
373
+ verb: 'Create',
374
+ targetOp: 'createManyAndReturn',
375
+ },
376
+ {
377
+ method: 'put',
378
+ successCode: '200',
379
+ verb: 'Update',
380
+ targetOp: 'updateManyAndReturn',
381
+ },
335
382
  ]
336
383
 
337
384
  for (const entry of forceReturnOps) {
@@ -341,9 +388,11 @@ function applyWriteStrategy(
341
388
  target,
342
389
  entry.successCode,
343
390
  entry.verb + ' many ' + modelName + ' (forceReturn)',
344
- 'writeStrategy="forceReturn": this endpoint silently invokes ' + entry.targetOp +
345
- ' and returns the ' + (entry.verb === 'Create' ? 'created' : 'updated') +
346
- ' records instead of { count }.',
391
+ 'writeStrategy="forceReturn": this endpoint silently invokes ' +
392
+ entry.targetOp +
393
+ ' and returns the ' +
394
+ (entry.verb === 'Create' ? 'created' : 'updated') +
395
+ ' records instead of { count }.',
347
396
  )
348
397
  }
349
398
  }
@@ -382,7 +431,9 @@ export function buildModelOpenApi(
382
431
  const basePath =
383
432
  normalizePrefix(prefixSource) +
384
433
  removeTrailingSlash(
385
- config.addModelPrefix !== false ? `/${modelName.toLowerCase()}` : '',
434
+ config.addModelPrefix !== false
435
+ ? `/${options.pathSegment ?? modelName.toLowerCase()}`
436
+ : '',
386
437
  )
387
438
 
388
439
  const referencedEnumTypes = new Set(
@@ -794,20 +845,47 @@ function generatePaths(
794
845
  config: RouteConfig,
795
846
  fields: ModelField[],
796
847
  ) {
797
- const postReads = !config.disablePostReads
848
+ const postReadsFor = (op: string): boolean => {
849
+ const raw: unknown = (config as Record<string, unknown>)[op]
850
+ const opValue =
851
+ raw && typeof raw === 'object'
852
+ ? (raw as { disablePostReads?: boolean }).disablePostReads
853
+ : undefined
854
+ return !(opValue ?? config.disablePostReads ?? false)
855
+ }
798
856
 
799
- const createInputRef = { $ref: `#/components/schemas/${modelName}CreateInput` }
800
- const updateInputRef = { $ref: `#/components/schemas/${modelName}UpdateInput` }
801
- const createManyInputRef = { $ref: `#/components/schemas/${modelName}CreateManyInput` }
802
- const updateManyMutationRef = { $ref: `#/components/schemas/${modelName}UpdateManyMutationInput` }
857
+ const createInputRef = {
858
+ $ref: `#/components/schemas/${modelName}CreateInput`,
859
+ }
860
+ const updateInputRef = {
861
+ $ref: `#/components/schemas/${modelName}UpdateInput`,
862
+ }
863
+ const createManyInputRef = {
864
+ $ref: `#/components/schemas/${modelName}CreateManyInput`,
865
+ }
866
+ const updateManyMutationRef = {
867
+ $ref: `#/components/schemas/${modelName}UpdateManyMutationInput`,
868
+ }
803
869
  const responseRef = { $ref: `#/components/schemas/${modelName}Response` }
804
- const nullableResponseSchema = { oneOf: [responseRef, { type: 'null' as const }] }
805
- const batchCountRef = { $ref: `#/components/schemas/${modelName}BatchCountResponse` }
870
+ const nullableResponseSchema = {
871
+ oneOf: [responseRef, { type: 'null' as const }],
872
+ }
873
+ const batchCountRef = {
874
+ $ref: `#/components/schemas/${modelName}BatchCountResponse`,
875
+ }
806
876
  const listRef = { $ref: `#/components/schemas/${modelName}ListResponse` }
807
- const aggregateRef = { $ref: `#/components/schemas/${modelName}AggregateResponse` }
808
- const groupByItemRef = { $ref: `#/components/schemas/${modelName}GroupByItem` }
809
- const updateEachItemRef = { $ref: `#/components/schemas/${modelName}UpdateEachItemInput` }
810
- const updateEachResponseRef = { $ref: `#/components/schemas/${modelName}UpdateEachResponse` }
877
+ const aggregateRef = {
878
+ $ref: `#/components/schemas/${modelName}AggregateResponse`,
879
+ }
880
+ const groupByItemRef = {
881
+ $ref: `#/components/schemas/${modelName}GroupByItem`,
882
+ }
883
+ const updateEachItemRef = {
884
+ $ref: `#/components/schemas/${modelName}UpdateEachItemInput`,
885
+ }
886
+ const updateEachResponseRef = {
887
+ $ref: `#/components/schemas/${modelName}UpdateEachResponse`,
888
+ }
811
889
 
812
890
  if (opEnabled(config, 'findMany')) {
813
891
  const meta = OPERATION_BY_NAME['findMany']
@@ -830,7 +908,7 @@ function generatePaths(
830
908
  addErrorResponses(op, meta.errors)
831
909
  addPath(spec, opPath(basePath, 'findMany'), 'get', op)
832
910
 
833
- if (postReads) {
911
+ if (postReadsFor('findMany')) {
834
912
  addPostReadOperation(
835
913
  spec,
836
914
  postReadPath(basePath, 'findMany'),
@@ -862,7 +940,7 @@ function generatePaths(
862
940
  addErrorResponses(op, meta.errors)
863
941
  addPath(spec, opPath(basePath, 'findUnique'), 'get', op)
864
942
 
865
- if (postReads) {
943
+ if (postReadsFor('findUnique')) {
866
944
  addPostReadOperation(
867
945
  spec,
868
946
  postReadPath(basePath, 'findUnique'),
@@ -893,7 +971,7 @@ function generatePaths(
893
971
  addErrorResponses(op, meta.errors)
894
972
  addPath(spec, opPath(basePath, 'findUniqueOrThrow'), 'get', op)
895
973
 
896
- if (postReads) {
974
+ if (postReadsFor('findUniqueOrThrow')) {
897
975
  addPostReadOperation(
898
976
  spec,
899
977
  postReadPath(basePath, 'findUniqueOrThrow'),
@@ -924,7 +1002,7 @@ function generatePaths(
924
1002
  addErrorResponses(op, meta.errors)
925
1003
  addPath(spec, opPath(basePath, 'findFirst'), 'get', op)
926
1004
 
927
- if (postReads) {
1005
+ if (postReadsFor('findFirst')) {
928
1006
  addPostReadOperation(
929
1007
  spec,
930
1008
  postReadPath(basePath, 'findFirst'),
@@ -955,7 +1033,7 @@ function generatePaths(
955
1033
  addErrorResponses(op, meta.errors)
956
1034
  addPath(spec, opPath(basePath, 'findFirstOrThrow'), 'get', op)
957
1035
 
958
- if (postReads) {
1036
+ if (postReadsFor('findFirstOrThrow')) {
959
1037
  addPostReadOperation(
960
1038
  spec,
961
1039
  postReadPath(basePath, 'findFirstOrThrow'),
@@ -987,7 +1065,7 @@ function generatePaths(
987
1065
  addErrorResponses(op, meta.errors)
988
1066
  addPath(spec, opPath(basePath, 'findManyPaginated'), 'get', op)
989
1067
 
990
- if (postReads) {
1068
+ if (postReadsFor('findManyPaginated')) {
991
1069
  addPostReadOperation(
992
1070
  spec,
993
1071
  postReadPath(basePath, 'findManyPaginated'),
@@ -1327,7 +1405,8 @@ function generatePaths(
1327
1405
  },
1328
1406
  {
1329
1407
  type: 'object',
1330
- description: 'Per-field count object when select is provided',
1408
+ description:
1409
+ 'Per-field count object when select is provided',
1331
1410
  },
1332
1411
  ],
1333
1412
  },
@@ -1339,7 +1418,7 @@ function generatePaths(
1339
1418
  addErrorResponses(op, meta.errors)
1340
1419
  addPath(spec, opPath(basePath, 'count'), 'get', op)
1341
1420
 
1342
- if (postReads) {
1421
+ if (postReadsFor('count')) {
1343
1422
  addPostReadOperation(
1344
1423
  spec,
1345
1424
  postReadPath(basePath, 'count'),
@@ -1380,7 +1459,7 @@ function generatePaths(
1380
1459
  addErrorResponses(op, meta.errors)
1381
1460
  addPath(spec, opPath(basePath, 'aggregate'), 'get', op)
1382
1461
 
1383
- if (postReads) {
1462
+ if (postReadsFor('aggregate')) {
1384
1463
  addPostReadOperation(
1385
1464
  spec,
1386
1465
  postReadPath(basePath, 'aggregate'),
@@ -1416,7 +1495,7 @@ function generatePaths(
1416
1495
  addErrorResponses(op, meta.errors)
1417
1496
  addPath(spec, opPath(basePath, 'groupBy'), 'get', op)
1418
1497
 
1419
- if (postReads) {
1498
+ if (postReadsFor('groupBy')) {
1420
1499
  addPostReadOperation(
1421
1500
  spec,
1422
1501
  postReadPath(basePath, 'groupBy'),
@@ -1581,12 +1660,30 @@ function mapFieldToSchema(field: ModelField): SchemaObject | RefObject {
1581
1660
  }
1582
1661
 
1583
1662
  function nestedListRelationOps(fieldType: string): SchemaObject {
1584
- const createInput: SchemaObject = { type: 'object', description: `${fieldType} create input` }
1585
- const uniqueId: SchemaObject = { type: 'object', description: 'Unique identifier' }
1586
- const uniqueConnect: SchemaObject = { type: 'object', description: 'Unique identifier to connect' }
1587
- const uniqueDisconnect: SchemaObject = { type: 'object', description: 'Unique identifier to disconnect' }
1588
- const uniqueDelete: SchemaObject = { type: 'object', description: 'Unique identifier to delete' }
1589
- const whereFilter: SchemaObject = { type: 'object', description: 'Where filter' }
1663
+ const createInput: SchemaObject = {
1664
+ type: 'object',
1665
+ description: `${fieldType} create input`,
1666
+ }
1667
+ const uniqueId: SchemaObject = {
1668
+ type: 'object',
1669
+ description: 'Unique identifier',
1670
+ }
1671
+ const uniqueConnect: SchemaObject = {
1672
+ type: 'object',
1673
+ description: 'Unique identifier to connect',
1674
+ }
1675
+ const uniqueDisconnect: SchemaObject = {
1676
+ type: 'object',
1677
+ description: 'Unique identifier to disconnect',
1678
+ }
1679
+ const uniqueDelete: SchemaObject = {
1680
+ type: 'object',
1681
+ description: 'Unique identifier to delete',
1682
+ }
1683
+ const whereFilter: SchemaObject = {
1684
+ type: 'object',
1685
+ description: 'Where filter',
1686
+ }
1590
1687
  const whereCreatePair: SchemaObject = {
1591
1688
  type: 'object',
1592
1689
  description: '{ where, create } pair',
@@ -1648,12 +1745,16 @@ function nestedSingleRelationOps(fieldType: string): SchemaObject {
1648
1745
  description: '{ where, create } pair',
1649
1746
  properties: { where: { type: 'object' }, create: { type: 'object' } },
1650
1747
  },
1651
- disconnect: { type: 'boolean', description: 'Disconnect the related record' },
1748
+ disconnect: {
1749
+ type: 'boolean',
1750
+ description: 'Disconnect the related record',
1751
+ },
1652
1752
  delete: { type: 'boolean', description: 'Delete the related record' },
1653
1753
  update: { type: 'object', description: `${fieldType} update input` },
1654
1754
  upsert: {
1655
1755
  type: 'object',
1656
- description: '{ create, update } pair — create if not exists, update if exists',
1756
+ description:
1757
+ '{ create, update } pair — create if not exists, update if exists',
1657
1758
  properties: {
1658
1759
  create: { type: 'object', description: `${fieldType} create input` },
1659
1760
  update: { type: 'object', description: `${fieldType} update input` },
@@ -1668,7 +1769,9 @@ function mapFieldToWriteSchema(
1668
1769
  mode: 'create' | 'update',
1669
1770
  ): SchemaObject | RefObject {
1670
1771
  if (field.kind === 'object') {
1671
- return field.isList ? nestedListRelationOps(field.type) : nestedSingleRelationOps(field.type)
1772
+ return field.isList
1773
+ ? nestedListRelationOps(field.type)
1774
+ : nestedSingleRelationOps(field.type)
1672
1775
  }
1673
1776
 
1674
1777
  let baseSchema: SchemaObject | RefObject
@@ -1733,4 +1836,4 @@ function mapScalarType(type: string): SchemaObject {
1733
1836
  },
1734
1837
  }
1735
1838
  return typeMap[type] || { type: 'string' }
1736
- }
1839
+ }
@@ -8,13 +8,15 @@ export async function mapLimited<T>(
8
8
  const workerCount = Math.min(limit, items.length)
9
9
  const workers: Promise<void>[] = []
10
10
  for (let w = 0; w < workerCount; w++) {
11
- workers.push((async () => {
12
- for (;;) {
13
- const i = index++
14
- if (i >= items.length) return
15
- await fn(items[i], i)
16
- }
17
- })())
11
+ workers.push(
12
+ (async () => {
13
+ for (;;) {
14
+ const i = index++
15
+ if (i >= items.length) return
16
+ await fn(items[i], i)
17
+ }
18
+ })(),
19
+ )
18
20
  }
19
21
  await Promise.all(workers)
20
- }
22
+ }
@@ -36,10 +36,19 @@ const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
36
36
  P2023: { status: 500, message: 'Inconsistent column data' },
37
37
  P2024: { status: 503, message: 'Connection pool timeout' },
38
38
  P2025: { status: 404, message: 'Record not found' },
39
- P2026: { status: 501, message: 'Feature not supported by the current database provider' },
40
- P2027: { status: 400, message: 'Multiple errors occurred during transaction execution' },
39
+ P2026: {
40
+ status: 501,
41
+ message: 'Feature not supported by the current database provider',
42
+ },
43
+ P2027: {
44
+ status: 400,
45
+ message: 'Multiple errors occurred during transaction execution',
46
+ },
41
47
  P2028: { status: 500, message: 'Transaction API error' },
42
- P2030: { status: 400, message: 'Cannot find a fulltext index for the search' },
48
+ P2030: {
49
+ status: 400,
50
+ message: 'Cannot find a fulltext index for the search',
51
+ },
43
52
  P2033: { status: 400, message: 'Number out of range for the field type' },
44
53
  P2034: { status: 409, message: 'Transaction conflict, please retry' },
45
54
  }
@@ -59,11 +68,20 @@ function asErrorShape(error: unknown): ErrorShape {
59
68
  }
60
69
 
61
70
  function isProduction(): boolean {
62
- return typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production'
71
+ return (
72
+ typeof process !== 'undefined' &&
73
+ process.env &&
74
+ process.env.NODE_ENV === 'production'
75
+ )
63
76
  }
64
77
 
65
78
  function isHttpStatus(value: unknown): value is number {
66
- return typeof value === 'number' && Number.isInteger(value) && value >= 400 && value <= 599
79
+ return (
80
+ typeof value === 'number' &&
81
+ Number.isInteger(value) &&
82
+ value >= 400 &&
83
+ value <= 599
84
+ )
67
85
  }
68
86
 
69
87
  export function mapError(error: unknown): HttpError {
@@ -77,18 +95,27 @@ export function mapError(error: unknown): HttpError {
77
95
  ? e.statusCode
78
96
  : null
79
97
  if (duckStatus !== null) {
80
- const detail = typeof e.message === 'string' && e.message ? e.message : 'Internal server error'
98
+ const detail =
99
+ typeof e.message === 'string' && e.message
100
+ ? e.message
101
+ : 'Internal server error'
81
102
  return new HttpError(duckStatus, detail)
82
103
  }
83
104
 
84
- if (e.name === 'ShapeError') return new HttpError(400, e.message || 'Shape validation failed')
85
- if (e.name === 'CallerError') return new HttpError(400, e.message || 'Caller validation failed')
86
- if (e.name === 'PolicyError') return new HttpError(403, e.message || 'Policy denied')
105
+ if (e.name === 'ShapeError')
106
+ return new HttpError(400, e.message || 'Shape validation failed')
107
+ if (e.name === 'CallerError')
108
+ return new HttpError(400, e.message || 'Caller validation failed')
109
+ if (e.name === 'PolicyError')
110
+ return new HttpError(403, e.message || 'Policy denied')
87
111
  if (e.name === 'ZodError') {
88
112
  const issues = e.issues
89
113
  const message = Array.isArray(issues)
90
- ? (issues as Array<{ message?: string }>).map((i) => i.message ?? '').filter(Boolean).join('; ')
91
- : (e.message || 'Validation failed')
114
+ ? (issues as Array<{ message?: string }>)
115
+ .map((i) => i.message ?? '')
116
+ .filter(Boolean)
117
+ .join('; ')
118
+ : e.message || 'Validation failed'
92
119
  return new HttpError(400, message)
93
120
  }
94
121
  if (typeof e.code === 'string') {
@@ -98,7 +125,9 @@ export function mapError(error: unknown): HttpError {
98
125
  const shouldStripDetail = isProd && mapped.status >= 500
99
126
  return new HttpError(
100
127
  mapped.status,
101
- !shouldStripDetail && detail ? mapped.message + ': ' + detail : mapped.message,
128
+ !shouldStripDetail && detail
129
+ ? mapped.message + ': ' + detail
130
+ : mapped.message,
102
131
  )
103
132
  }
104
133
  if (e.code.startsWith('P')) {
@@ -108,19 +137,39 @@ export function mapError(error: unknown): HttpError {
108
137
  }
109
138
  }
110
139
  if (typeof e.name === 'string') {
111
- if (e.name === 'PrismaClientValidationError') return new HttpError(400, e.message || 'Invalid query parameters')
112
- if (e.name === 'PrismaClientKnownRequestError') return new HttpError(400, e.message || 'Database request error')
140
+ if (e.name === 'PrismaClientValidationError')
141
+ return new HttpError(400, e.message || 'Invalid query parameters')
142
+ if (e.name === 'PrismaClientKnownRequestError')
143
+ return new HttpError(400, e.message || 'Database request error')
113
144
  if (e.name === 'PrismaClientInitializationError') {
114
- return new HttpError(503, isProd ? 'Service unavailable' : (e.message || 'Database connection failed'))
145
+ return new HttpError(
146
+ 503,
147
+ isProd
148
+ ? 'Service unavailable'
149
+ : e.message || 'Database connection failed',
150
+ )
115
151
  }
116
152
  if (e.name === 'PrismaClientRustPanicError') {
117
- return new HttpError(500, isProd ? 'Internal server error' : (e.message || 'Internal database engine error'))
153
+ return new HttpError(
154
+ 500,
155
+ isProd
156
+ ? 'Internal server error'
157
+ : e.message || 'Internal database engine error',
158
+ )
118
159
  }
119
160
  if (e.name === 'PrismaClientUnknownRequestError') {
120
- return new HttpError(500, isProd ? 'Internal server error' : (e.message || 'Unknown database error'))
161
+ return new HttpError(
162
+ 500,
163
+ isProd
164
+ ? 'Internal server error'
165
+ : e.message || 'Unknown database error',
166
+ )
121
167
  }
122
168
  }
123
169
  const msg = error instanceof Error ? error.message : String(error)
124
170
  console.error(LOG_PREFIX, 'Unhandled error:', error)
125
- return new HttpError(500, isProd ? 'Internal server error' : (msg || 'Internal server error'))
126
- }
171
+ return new HttpError(
172
+ 500,
173
+ isProd ? 'Internal server error' : msg || 'Internal server error',
174
+ )
175
+ }
@@ -3,12 +3,17 @@ import { HttpError } from './errorMapper'
3
3
  export type PrismaDelegateLike = {
4
4
  count: (args?: unknown) => Promise<unknown>
5
5
  findMany: (args?: unknown) => Promise<unknown>
6
- guard?: (shape: Record<string, unknown>, caller?: string) => PrismaDelegateLike
6
+ guard?: (
7
+ shape: Record<string, unknown>,
8
+ caller?: string,
9
+ ) => PrismaDelegateLike
7
10
  }
8
11
 
9
- export function assertGuard(
10
- delegate: PrismaDelegateLike,
11
- ): asserts delegate is PrismaDelegateLike & { guard: NonNullable<PrismaDelegateLike['guard']> } {
12
+ export function assertGuard<T extends PrismaDelegateLike>(
13
+ delegate: T,
14
+ ): asserts delegate is T & {
15
+ guard: NonNullable<T['guard']>
16
+ } {
12
17
  if (typeof delegate.guard !== 'function') {
13
18
  throw new HttpError(
14
19
  500,
@@ -18,9 +23,24 @@ export function assertGuard(
18
23
  }
19
24
 
20
25
  export const GUARD_SHAPE_CONFIG_KEYS = new Set([
21
- 'data', 'create', 'update', 'where', 'include', 'select', 'orderBy',
22
- 'cursor', 'take', 'skip', 'distinct', 'having', '_count', '_avg',
23
- '_sum', '_min', '_max', 'by',
26
+ 'data',
27
+ 'create',
28
+ 'update',
29
+ 'where',
30
+ 'include',
31
+ 'select',
32
+ 'orderBy',
33
+ 'cursor',
34
+ 'take',
35
+ 'skip',
36
+ 'distinct',
37
+ 'having',
38
+ '_count',
39
+ '_avg',
40
+ '_sum',
41
+ '_min',
42
+ '_max',
43
+ 'by',
24
44
  ])
25
45
 
26
46
  function keepWhereOnly(obj: Record<string, unknown>): Record<string, unknown> {
@@ -39,7 +59,8 @@ export function buildCountShape(
39
59
  return (...args: unknown[]) => keepWhereOnly(fn(...args))
40
60
  }
41
61
  const keys = Object.keys(shape)
42
- const isSingleShape = keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
62
+ const isSingleShape =
63
+ keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
43
64
  if (isSingleShape) return keepWhereOnly(shape)
44
65
  const result: Record<string, unknown> = {}
45
66
  for (const [key, variant] of Object.entries(shape)) {
@@ -53,4 +74,4 @@ export function buildCountShape(
53
74
  }
54
75
  }
55
76
  return result
56
- }
77
+ }
@@ -4,7 +4,10 @@ import type { PaginationCountSource } from './routeConfig'
4
4
  export const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/
5
5
 
6
6
  type PrismaRawClient = {
7
- $queryRawUnsafe?: <T = unknown>(sql: string, ...values: unknown[]) => Promise<T>
7
+ $queryRawUnsafe?: <T = unknown>(
8
+ sql: string,
9
+ ...values: unknown[]
10
+ ) => Promise<T>
8
11
  }
9
12
 
10
13
  export function quoteIdent(name: string): string {
@@ -47,7 +50,10 @@ export async function countFromMaterializedView(
47
50
  ): Promise<number> {
48
51
  const raw = client as PrismaRawClient
49
52
  if (typeof raw.$queryRawUnsafe !== 'function') {
50
- throw new HttpError(500, 'Materialized count source requires $queryRawUnsafe on the Prisma client')
53
+ throw new HttpError(
54
+ 500,
55
+ 'Materialized count source requires $queryRawUnsafe on the Prisma client',
56
+ )
51
57
  }
52
58
  const column = source.column ?? 'total'
53
59
  const where = buildMaterializedCountWhere(source.where)
@@ -58,11 +64,17 @@ export async function countFromMaterializedView(
58
64
  buildMaterializedCountFqn(source) +
59
65
  where.sql +
60
66
  ' LIMIT 1'
61
- const rows = await raw.$queryRawUnsafe<Array<{ total: unknown }>>(sql, ...where.values)
67
+ const rows = await raw.$queryRawUnsafe<Array<{ total: unknown }>>(
68
+ sql,
69
+ ...where.values,
70
+ )
62
71
  const value = rows[0]?.total
63
72
  const total = Number(value)
64
73
  if (!Number.isFinite(total)) {
65
- throw new HttpError(500, 'Materialized count source did not return a numeric total')
74
+ throw new HttpError(
75
+ 500,
76
+ 'Materialized count source did not return a numeric total',
77
+ )
66
78
  }
67
79
  return Math.trunc(total)
68
- }
80
+ }