prisma-generator-express 1.34.4 → 1.36.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.
@@ -1 +1 @@
1
- {"version":3,"file":"generateRouterFastify.js","sourceRoot":"","sources":["../../src/generators/generateRouterFastify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generateRouterFastify.js","sourceRoot":"","sources":["../../src/generators/generateRouterFastify.ts"],"names":[],"mappings":"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"}
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.34.4",
4
+ "version": "1.36.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
@@ -69,6 +69,11 @@ function opPath(basePath: string, name: string): string {
69
69
  return `${basePath}${def.pathSuffix}`
70
70
  }
71
71
 
72
+ function postReadPath(basePath: string, name: string): string {
73
+ if (name === 'findMany') return `${basePath}/read`
74
+ return opPath(basePath, name)
75
+ }
76
+
72
77
  function errorRef(): RefObject {
73
78
  return { $ref: '#/components/schemas/ErrorResponse' }
74
79
  }
@@ -162,6 +167,109 @@ function listScalarUpdateOperations(
162
167
  }
163
168
  }
164
169
 
170
+ function findManyBodySchema(): SchemaObject {
171
+ return {
172
+ type: 'object',
173
+ properties: {
174
+ where: { type: 'object', description: 'Filter conditions' },
175
+ orderBy: { description: 'Sort order (object or array of objects)' },
176
+ take: { type: 'integer', description: 'Limit results' },
177
+ skip: { type: 'integer', description: 'Skip results' },
178
+ select: { type: 'object', description: 'Select fields' },
179
+ include: { type: 'object', description: 'Include relations' },
180
+ omit: { type: 'object', description: 'Omit fields from response' },
181
+ cursor: { type: 'object', description: 'Cursor for pagination' },
182
+ distinct: { description: 'Distinct fields (string or array of strings)' },
183
+ },
184
+ }
185
+ }
186
+
187
+ function findUniqueBodySchema(): SchemaObject {
188
+ return {
189
+ type: 'object',
190
+ properties: {
191
+ where: { type: 'object', description: 'Unique selector' },
192
+ select: { type: 'object', description: 'Select fields' },
193
+ include: { type: 'object', description: 'Include relations' },
194
+ omit: { type: 'object', description: 'Omit fields from response' },
195
+ },
196
+ required: ['where'],
197
+ }
198
+ }
199
+
200
+ function countBodySchema(): SchemaObject {
201
+ return {
202
+ type: 'object',
203
+ properties: {
204
+ where: { type: 'object', description: 'Filter conditions' },
205
+ orderBy: { description: 'Sort order' },
206
+ take: { type: 'integer', description: 'Limit results' },
207
+ skip: { type: 'integer', description: 'Skip results' },
208
+ cursor: { type: 'object', description: 'Cursor for pagination' },
209
+ select: { description: 'Count specific fields. When provided, returns per-field counts as an object instead of a single integer.' },
210
+ },
211
+ }
212
+ }
213
+
214
+ function aggregateBodySchema(): SchemaObject {
215
+ return {
216
+ type: 'object',
217
+ properties: {
218
+ where: { type: 'object', description: 'Filter conditions' },
219
+ orderBy: { description: 'Sort order' },
220
+ cursor: { type: 'object', description: 'Cursor for pagination' },
221
+ take: { type: 'integer', description: 'Limit results' },
222
+ skip: { type: 'integer', description: 'Skip results' },
223
+ _count: { description: 'Count aggregate (true or field selection object)' },
224
+ _avg: { type: 'object', description: 'Average aggregate (field selection object)' },
225
+ _sum: { type: 'object', description: 'Sum aggregate (field selection object)' },
226
+ _min: { type: 'object', description: 'Min aggregate (field selection object)' },
227
+ _max: { type: 'object', description: 'Max aggregate (field selection object)' },
228
+ },
229
+ }
230
+ }
231
+
232
+ function groupByBodySchema(): SchemaObject {
233
+ return {
234
+ type: 'object',
235
+ properties: {
236
+ by: { type: 'array', items: { type: 'string' }, description: 'Fields to group by' },
237
+ where: { type: 'object', description: 'Filter conditions' },
238
+ orderBy: { description: 'Sort order. Required when using skip or take.' },
239
+ having: { type: 'object', description: 'Having conditions (filter object)' },
240
+ take: { type: 'integer', description: 'Limit results' },
241
+ skip: { type: 'integer', description: 'Skip results' },
242
+ _count: { description: 'Count aggregate (true or field selection object)' },
243
+ _avg: { type: 'object', description: 'Average aggregate (field selection object)' },
244
+ _sum: { type: 'object', description: 'Sum aggregate (field selection object)' },
245
+ _min: { type: 'object', description: 'Min aggregate (field selection object)' },
246
+ _max: { type: 'object', description: 'Max aggregate (field selection object)' },
247
+ },
248
+ required: ['by'],
249
+ }
250
+ }
251
+
252
+ function getPostReadBodySchema(opName: string): SchemaObject {
253
+ switch (opName) {
254
+ case 'findMany':
255
+ case 'findFirst':
256
+ case 'findFirstOrThrow':
257
+ case 'findManyPaginated':
258
+ return findManyBodySchema()
259
+ case 'findUnique':
260
+ case 'findUniqueOrThrow':
261
+ return findUniqueBodySchema()
262
+ case 'count':
263
+ return countBodySchema()
264
+ case 'aggregate':
265
+ return aggregateBodySchema()
266
+ case 'groupBy':
267
+ return groupByBodySchema()
268
+ default:
269
+ return findManyBodySchema()
270
+ }
271
+ }
272
+
165
273
  export function buildModelOpenApi(
166
274
  modelName: string,
167
275
  modelFields: ModelField[],
@@ -535,6 +643,41 @@ function getGroupByParams() {
535
643
  ]
536
644
  }
537
645
 
646
+ function addPostReadOperation(
647
+ spec: OpenApiSpec,
648
+ path: string,
649
+ modelName: string,
650
+ opName: string,
651
+ summary: string,
652
+ responseSchema: any,
653
+ errorCodes: number[],
654
+ description?: string,
655
+ ) {
656
+ const op: any = {
657
+ tags: [modelName],
658
+ summary: summary + ' (POST)',
659
+ operationId: `${modelName}${opName.charAt(0).toUpperCase() + opName.slice(1)}Post`,
660
+ description: (description ? description + ' ' : '') +
661
+ 'POST alternative for requests with complex query parameters that may exceed URL length limits. Accepts the same arguments as the GET endpoint but as a JSON request body instead of query parameters.',
662
+ requestBody: {
663
+ required: true,
664
+ content: {
665
+ 'application/json': {
666
+ schema: getPostReadBodySchema(opName),
667
+ },
668
+ },
669
+ },
670
+ responses: {
671
+ '200': {
672
+ description: 'Success',
673
+ content: { 'application/json': { schema: responseSchema } },
674
+ },
675
+ },
676
+ }
677
+ addErrorResponses(op, errorCodes)
678
+ addPath(spec, path, 'post', op)
679
+ }
680
+
538
681
  function generatePaths(
539
682
  spec: OpenApiSpec,
540
683
  modelName: string,
@@ -542,6 +685,8 @@ function generatePaths(
542
685
  config: RouteConfig,
543
686
  fields: ModelField[],
544
687
  ) {
688
+ const postReads = !config.disablePostReads
689
+
545
690
  const createInputRef = {
546
691
  $ref: `#/components/schemas/${modelName}CreateInput`,
547
692
  }
@@ -588,6 +733,18 @@ function generatePaths(
588
733
  }
589
734
  addErrorResponses(op, [400, 403, 500, 501, 503])
590
735
  addPath(spec, opPath(basePath, 'findMany'), 'get', op)
736
+
737
+ if (postReads) {
738
+ addPostReadOperation(
739
+ spec,
740
+ postReadPath(basePath, 'findMany'),
741
+ modelName,
742
+ 'findMany',
743
+ `List ${modelName}`,
744
+ { type: 'array', items: responseRef },
745
+ [400, 403, 500, 501, 503],
746
+ )
747
+ }
591
748
  }
592
749
 
593
750
  if (opEnabled(config, 'findUnique')) {
@@ -607,6 +764,19 @@ function generatePaths(
607
764
  }
608
765
  addErrorResponses(op, [400, 403, 500, 501, 503])
609
766
  addPath(spec, opPath(basePath, 'findUnique'), 'get', op)
767
+
768
+ if (postReads) {
769
+ addPostReadOperation(
770
+ spec,
771
+ postReadPath(basePath, 'findUnique'),
772
+ modelName,
773
+ 'findUnique',
774
+ `Get ${modelName} by unique constraint`,
775
+ nullableResponseSchema,
776
+ [400, 403, 500, 501, 503],
777
+ 'Returns null with status 200 when no record matches the unique constraint.',
778
+ )
779
+ }
610
780
  }
611
781
 
612
782
  if (opEnabled(config, 'findUniqueOrThrow')) {
@@ -624,6 +794,18 @@ function generatePaths(
624
794
  }
625
795
  addErrorResponses(op, [400, 403, 404, 500, 501, 503])
626
796
  addPath(spec, opPath(basePath, 'findUniqueOrThrow'), 'get', op)
797
+
798
+ if (postReads) {
799
+ addPostReadOperation(
800
+ spec,
801
+ postReadPath(basePath, 'findUniqueOrThrow'),
802
+ modelName,
803
+ 'findUniqueOrThrow',
804
+ `Get ${modelName} by unique constraint (throws if not found)`,
805
+ responseRef,
806
+ [400, 403, 404, 500, 501, 503],
807
+ )
808
+ }
627
809
  }
628
810
 
629
811
  if (opEnabled(config, 'findFirst')) {
@@ -642,6 +824,19 @@ function generatePaths(
642
824
  }
643
825
  addErrorResponses(op, [400, 403, 500, 501, 503])
644
826
  addPath(spec, opPath(basePath, 'findFirst'), 'get', op)
827
+
828
+ if (postReads) {
829
+ addPostReadOperation(
830
+ spec,
831
+ postReadPath(basePath, 'findFirst'),
832
+ modelName,
833
+ 'findFirst',
834
+ `Get first ${modelName}`,
835
+ nullableResponseSchema,
836
+ [400, 403, 500, 501, 503],
837
+ 'Returns null with status 200 when no record matches.',
838
+ )
839
+ }
645
840
  }
646
841
 
647
842
  if (opEnabled(config, 'findFirstOrThrow')) {
@@ -659,6 +854,18 @@ function generatePaths(
659
854
  }
660
855
  addErrorResponses(op, [400, 403, 404, 500, 501, 503])
661
856
  addPath(spec, opPath(basePath, 'findFirstOrThrow'), 'get', op)
857
+
858
+ if (postReads) {
859
+ addPostReadOperation(
860
+ spec,
861
+ postReadPath(basePath, 'findFirstOrThrow'),
862
+ modelName,
863
+ 'findFirstOrThrow',
864
+ `Get first ${modelName} (throws if not found)`,
865
+ responseRef,
866
+ [400, 403, 404, 500, 501, 503],
867
+ )
868
+ }
662
869
  }
663
870
 
664
871
  if (opEnabled(config, 'findManyPaginated')) {
@@ -678,6 +885,19 @@ function generatePaths(
678
885
  }
679
886
  addErrorResponses(op, [400, 403, 409, 500, 501, 503])
680
887
  addPath(spec, opPath(basePath, 'findManyPaginated'), 'get', op)
888
+
889
+ if (postReads) {
890
+ addPostReadOperation(
891
+ spec,
892
+ postReadPath(basePath, 'findManyPaginated'),
893
+ modelName,
894
+ 'findManyPaginated',
895
+ `List ${modelName} with pagination`,
896
+ listRef,
897
+ [400, 403, 409, 500, 501, 503],
898
+ 'Returns paginated results with total count.',
899
+ )
900
+ }
681
901
  }
682
902
 
683
903
  if (opEnabled(config, 'create')) {
@@ -1074,6 +1294,23 @@ function generatePaths(
1074
1294
  }
1075
1295
  addErrorResponses(op, [400, 403, 500, 501, 503])
1076
1296
  addPath(spec, opPath(basePath, 'count'), 'get', op)
1297
+
1298
+ if (postReads) {
1299
+ addPostReadOperation(
1300
+ spec,
1301
+ postReadPath(basePath, 'count'),
1302
+ modelName,
1303
+ 'count',
1304
+ `Count ${modelName}`,
1305
+ {
1306
+ oneOf: [
1307
+ { type: 'integer', description: 'Total count when select is not provided' },
1308
+ { type: 'object', description: 'Per-field count object when select is provided' },
1309
+ ],
1310
+ },
1311
+ [400, 403, 500, 501, 503],
1312
+ )
1313
+ }
1077
1314
  }
1078
1315
 
1079
1316
  if (opEnabled(config, 'aggregate')) {
@@ -1091,6 +1328,18 @@ function generatePaths(
1091
1328
  }
1092
1329
  addErrorResponses(op, [400, 403, 500, 501, 503])
1093
1330
  addPath(spec, opPath(basePath, 'aggregate'), 'get', op)
1331
+
1332
+ if (postReads) {
1333
+ addPostReadOperation(
1334
+ spec,
1335
+ postReadPath(basePath, 'aggregate'),
1336
+ modelName,
1337
+ 'aggregate',
1338
+ `Aggregate ${modelName}`,
1339
+ aggregateRef,
1340
+ [400, 403, 500, 501, 503],
1341
+ )
1342
+ }
1094
1343
  }
1095
1344
 
1096
1345
  if (opEnabled(config, 'groupBy')) {
@@ -1114,6 +1363,19 @@ function generatePaths(
1114
1363
  }
1115
1364
  addErrorResponses(op, [400, 403, 500, 501, 503])
1116
1365
  addPath(spec, opPath(basePath, 'groupBy'), 'get', op)
1366
+
1367
+ if (postReads) {
1368
+ addPostReadOperation(
1369
+ spec,
1370
+ postReadPath(basePath, 'groupBy'),
1371
+ modelName,
1372
+ 'groupBy',
1373
+ `Group ${modelName}`,
1374
+ { type: 'array', items: groupByItemRef },
1375
+ [400, 403, 500, 501, 503],
1376
+ 'Groups records by the specified fields and returns aggregates.',
1377
+ )
1378
+ }
1117
1379
  }
1118
1380
  }
1119
1381
 
@@ -1,5 +1,5 @@
1
1
  import type { RouteConfig } from './routeConfig'
2
- import { OPERATION_DEFS, isOperationEnabled } from './operationDefinitions'
2
+ import { OPERATION_DEFS, isOperationEnabled, READ_OPERATION_NAMES } from './operationDefinitions'
3
3
 
4
4
  const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
5
5
 
@@ -553,6 +553,8 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
553
553
  const modelLower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
554
554
  const exampleBasePath = buildExampleBasePath(modelName, config)
555
555
 
556
+ const postReadsEnabled = !config.disablePostReads
557
+
556
558
  const scalarFields = ctx.fields.filter((f) => isScalarField(f) || isEnumField(f))
557
559
  const relationFields = ctx.fields.filter((f) => isRelationField(f))
558
560
 
@@ -561,7 +563,7 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
561
563
  const listRelations = relationFields.filter((f) => f.isList)
562
564
  const singleRelations = relationFields.filter((f) => !f.isList)
563
565
 
564
- const ops = OPERATION_DEFS
566
+ const getOps = OPERATION_DEFS
565
567
  .filter((d) => isOperationEnabled(config as Record<string, any>, d))
566
568
  .map((d) => {
567
569
  const detail = OP_DETAIL_MAP[d.name]
@@ -581,6 +583,33 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
581
583
  }
582
584
  })
583
585
 
586
+ const postReadOps = postReadsEnabled
587
+ ? OPERATION_DEFS
588
+ .filter((d) => READ_OPERATION_NAMES.has(d.name) && isOperationEnabled(config as Record<string, any>, d))
589
+ .map((d) => {
590
+ const detail = OP_DETAIL_MAP[d.name]
591
+ const postPath = d.name === 'findMany'
592
+ ? buildFullPath(exampleBasePath, '/read')
593
+ : buildFullPath(exampleBasePath, d.pathSuffix)
594
+ return {
595
+ op: d.name + ' (POST)',
596
+ method: 'POST',
597
+ path: postPath,
598
+ transport: 'POST JSON body',
599
+ responseDesc: detail ? detail.responseDesc : '',
600
+ errors: detail ? detail.errors.join(', ') : '',
601
+ required: detail ? detail.required : [],
602
+ optional: detail ? detail.optional : [],
603
+ supportsSelect: detail ? detail.supportsSelect : false,
604
+ supportsInclude: detail ? detail.supportsInclude : false,
605
+ supportsOmit: detail ? detail.supportsOmit : false,
606
+ notes: 'POST alternative for complex queries exceeding URL length limits. Same args as GET but in request body.',
607
+ }
608
+ })
609
+ : []
610
+
611
+ const ops = [...getOps, ...postReadOps]
612
+
584
613
  const firstUnique = uniqueFields[0]
585
614
  const firstUniqueExample = firstUnique ? exampleValue(ctx, firstUnique.name) : null
586
615
  const compoundWhere = !firstUnique ? compoundWhereExample(ctx) : null
@@ -640,6 +669,14 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
640
669
  'const res = await fetch(BASE_URL + "' + exampleBasePath + '?" + params)\n' +
641
670
  'const data = await res.json()'
642
671
 
672
+ const findManyPostFetchExample =
673
+ 'const res = await fetch(BASE_URL + "' + exampleBasePath + '/read", {\n' +
674
+ ' method: "POST",\n' +
675
+ ' headers: { "Content-Type": "application/json" },\n' +
676
+ ' body: JSON.stringify(' + JSON.stringify(findManyQueryArgs, null, 2) + ')\n' +
677
+ '})\n' +
678
+ 'const data = await res.json()'
679
+
643
680
  const findUniqueFetchExample = uniqueWhereExample
644
681
  ? 'const params = encodeQueryParams({\n' +
645
682
  ' where: ' + JSON.stringify(uniqueWhereExample) + ',\n' +
@@ -844,6 +881,7 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
844
881
  const transportNotes = [
845
882
  'GET endpoints: Prisma args as JSON-encoded query parameter strings via encodeQueryParams.',
846
883
  'POST/PUT/DELETE/PATCH endpoints: Prisma args as JSON request body. Body must be a JSON object.',
884
+ 'POST read endpoints: All read operations also accept POST with the same args as JSON body instead of query params. Use when query parameters exceed URL length limits. findMany uses POST /read, all others use the same path as GET.',
847
885
  'findManyPaginated returns { data, total, hasMore }. hasMore is reliable for forward offset pagination only.',
848
886
  'Batch mutations (createMany, updateMany, deleteMany) return { count }. Batch data inputs are scalar-only — nested relation writes are not supported.',
849
887
  'findUnique and findFirst return null (not 404) when no record matches. Use the OrThrow variants for 404 behavior.',
@@ -967,6 +1005,7 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
967
1005
 
968
1006
  const runtimeNotes = [
969
1007
  '<strong>Query parameter parsing:</strong> GET query values are parsed server-side. Strings starting with <span class="font-mono">{</span>, <span class="font-mono">[</span>, or <span class="font-mono">"</span> are JSON-parsed. The strings <span class="font-mono">true</span>, <span class="font-mono">false</span>, <span class="font-mono">null</span> are converted to their JS equivalents. Numeric conversion only applies to <span class="font-mono">take</span> and <span class="font-mono">skip</span>. Use <span class="font-mono">encodeQueryParams</span> to avoid encoding issues.',
1008
+ '<strong>POST read endpoints:</strong> All read operations accept POST as an alternative transport. The request body is a plain JSON object with the same argument structure as the GET query params — no JSON-string encoding needed. POST reads use native JSON types (numbers, booleans, objects) directly. findMany POST read is at <span class="font-mono">/read</span>; all other read operations use the same path as their GET counterpart. Disable with <span class="font-mono">disablePostReads: true</span> in route config.',
970
1009
  '<strong>Request body validation:</strong> All write endpoints require a JSON object body. Sending <span class="font-mono">null</span>, arrays, or non-object JSON values returns 400.',
971
1010
  '<strong>Documentation in production:</strong> Docs endpoints are disabled by default when <span class="font-mono">NODE_ENV=production</span> or <span class="font-mono">DISABLE_OPENAPI=true</span>. To enable in production, set <span class="font-mono">disableOpenApi: false</span> in the route config.',
972
1011
  '<strong>Paginated query atomicity:</strong> findManyPaginated wraps data + count in a database transaction when available. If interactive transactions are not supported (e.g. some edge adapters), the queries run separately and data/total may be slightly inconsistent under concurrent writes.',
@@ -1236,29 +1275,33 @@ export function renderDocs(modelName: string, config: DocsConfig, ctx: DocsModel
1236
1275
  ${codeBlock(findManyFetchExample)}
1237
1276
  </div>
1238
1277
  <div>
1239
- <h3 class="mt-3.5 mb-2 text-sm">11.2 GETfindUnique</h3>
1278
+ <h3 class="mt-3.5 mb-2 text-sm">11.2 POSTfindMany (read)</h3>
1279
+ ${codeBlock(findManyPostFetchExample)}
1280
+ </div>
1281
+ <div>
1282
+ <h3 class="mt-3.5 mb-2 text-sm">11.3 GET — findUnique</h3>
1240
1283
  ${findUniqueFetchExample
1241
1284
  ? codeBlock(findUniqueFetchExample)
1242
1285
  : noUniqueFieldNote}
1243
1286
  </div>
1244
1287
  <div>
1245
- <h3 class="mt-3.5 mb-2 text-sm">11.3 POST — create</h3>
1288
+ <h3 class="mt-3.5 mb-2 text-sm">11.4 POST — create</h3>
1246
1289
  ${codeBlock(createFetchExample)}
1247
1290
  </div>
1248
1291
  <div>
1249
- <h3 class="mt-3.5 mb-2 text-sm">11.4 PUT — update</h3>
1292
+ <h3 class="mt-3.5 mb-2 text-sm">11.5 PUT — update</h3>
1250
1293
  ${updateFetchExample
1251
1294
  ? codeBlock(updateFetchExample)
1252
1295
  : noUniqueFieldNote}
1253
1296
  </div>
1254
1297
  <div>
1255
- <h3 class="mt-3.5 mb-2 text-sm">11.5 DELETE — delete</h3>
1298
+ <h3 class="mt-3.5 mb-2 text-sm">11.6 DELETE — delete</h3>
1256
1299
  ${deleteFetchExample
1257
1300
  ? codeBlock(deleteFetchExample)
1258
1301
  : noUniqueFieldNote}
1259
1302
  </div>
1260
1303
  <div>
1261
- <h3 class="mt-3.5 mb-2 text-sm">11.6 Guard variant header</h3>
1304
+ <h3 class="mt-3.5 mb-2 text-sm">11.7 Guard variant header</h3>
1262
1305
  ${guardFetchExample
1263
1306
  ? codeBlock(guardFetchExample)
1264
1307
  : noUniqueFieldNote}
@@ -88,9 +88,27 @@ export const OPERATION_DEFS: OperationDef[] = [
88
88
  },
89
89
  ]
90
90
 
91
+ export const READ_OPERATION_NAMES = new Set([
92
+ 'findMany',
93
+ 'findUnique',
94
+ 'findUniqueOrThrow',
95
+ 'findFirst',
96
+ 'findFirstOrThrow',
97
+ 'findManyPaginated',
98
+ 'count',
99
+ 'aggregate',
100
+ 'groupBy',
101
+ ])
102
+
103
+ export function getPostReadPathSuffix(opName: string): string {
104
+ if (opName === 'findMany') return '/read'
105
+ const def = OPERATION_DEFS.find((d) => d.name === opName)
106
+ return def ? def.pathSuffix : ''
107
+ }
108
+
91
109
  export function isOperationEnabled(
92
110
  config: Record<string, any>,
93
111
  def: OperationDef,
94
112
  ): boolean {
95
113
  return !!(config.enableAll || config[def.configKey])
96
- }
114
+ }
@@ -32,6 +32,7 @@ export interface BaseRouteConfig<HookHandler, RequestType> {
32
32
  customUrlPrefix?: string
33
33
  specBasePath?: string
34
34
  disableOpenApi?: boolean
35
+ disablePostReads?: boolean
35
36
  scalarCdnUrl?: string
36
37
 
37
38
  openApiTitle?: string
@@ -499,21 +499,29 @@ export async function findManyPaginated(
499
499
  const shape = ctx.guardShape
500
500
  const caller = ctx.guardCaller
501
501
  const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
502
+ const delegate = (extended as any).${modelNameLower}
502
503
 
503
504
  if (shape) {
504
- assertGuard((extended as any).${modelNameLower})
505
+ assertGuard(delegate)
505
506
  }
506
507
 
507
508
  let items: any[]
508
509
  let total: number
509
510
 
510
- if (typeof extended.$transaction === 'function') {
511
+ if (shape || typeof extended.$transaction !== 'function') {
512
+ const [data, count] = await Promise.all([
513
+ shape
514
+ ? delegate.guard(shape, caller).findMany(query)
515
+ : delegate.findMany(query),
516
+ countForPagination(delegate, query, shape, caller, distinctCountLimit),
517
+ ])
518
+ items = data
519
+ total = count
520
+ } else {
511
521
  try {
512
522
  const txResult = await extended.$transaction(async (tx: any) => {
513
- const d = shape
514
- ? await tx.${modelNameLower}.guard(shape, caller).findMany(query)
515
- : await tx.${modelNameLower}.findMany(query)
516
- const t = await countForPagination(tx.${modelNameLower}, query, shape, caller, distinctCountLimit)
523
+ const d = await tx.${modelNameLower}.findMany(query)
524
+ const t = await countForPagination(tx.${modelNameLower}, query, undefined, undefined, distinctCountLimit)
517
525
  return { d, t }
518
526
  })
519
527
  items = txResult.d
@@ -526,31 +534,12 @@ export async function findManyPaginated(
526
534
  console.warn(
527
535
  '[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic',
528
536
  )
529
- items = shape
530
- ? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
531
- : await (extended as any).${modelNameLower}.findMany(query)
532
- total = await countForPagination(
533
- (extended as any).${modelNameLower},
534
- query,
535
- shape,
536
- caller,
537
- distinctCountLimit,
538
- )
537
+ items = await delegate.findMany(query)
538
+ total = await countForPagination(delegate, query, undefined, undefined, distinctCountLimit)
539
539
  } else {
540
540
  throw txError
541
541
  }
542
542
  }
543
- } else {
544
- items = shape
545
- ? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
546
- : await (extended as any).${modelNameLower}.findMany(query)
547
- total = await countForPagination(
548
- (extended as any).${modelNameLower},
549
- query,
550
- shape,
551
- caller,
552
- distinctCountLimit,
553
- )
554
543
  }
555
544
 
556
545
  const skip = (query.skip as number) ?? 0