prisma-generator-express 1.35.0 → 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.
@@ -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
@@ -62,6 +62,7 @@ import {
62
62
  } from './${modelName}Handlers'
63
63
  import type { RouteConfig } from '../routeConfig.target'
64
64
  import { parseQueryParams } from '../parseQueryParams'
65
+ import { sanitizeKeys } from '../misc'
65
66
  import { buildModelOpenApi } from '../buildModelOpenApi'
66
67
  import { transformResult } from '../operationRuntime'
67
68
 
@@ -113,6 +114,8 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
113
114
  || _env.NODE_ENV === 'production'
114
115
  ))
115
116
 
117
+ const postReadsEnabled = !config.disablePostReads
118
+
116
119
  const qbEnabled = isQueryBuilderEnabled(config)
117
120
 
118
121
  if (qbEnabled) {
@@ -130,6 +133,14 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
130
133
  next()
131
134
  }
132
135
 
136
+ const parseBodyAsQuery: RequestHandler = (req, res, next) => {
137
+ if (!req.body || typeof req.body !== 'object' || Array.isArray(req.body)) {
138
+ return next({ status: 400, message: 'Request body must be a JSON object' })
139
+ }
140
+ res.locals.parsedQuery = sanitizeKeys(req.body)
141
+ next()
142
+ }
143
+
133
144
  const setShape = (opConfig: any): RequestHandler => {
134
145
  return (req, res, next) => {
135
146
  res.locals.routeConfig = config
@@ -194,6 +205,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
194
205
  const { before = [], after = [] } = opConfig
195
206
  const path = basePath ? \`\${basePath}/first\` : '/first'
196
207
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindFirst as RequestHandler, ...after, respond)
208
+ if (postReadsEnabled) {
209
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirst as RequestHandler, ...after, respond)
210
+ }
197
211
  }
198
212
 
199
213
  if (config.enableAll || config.findFirstOrThrow) {
@@ -201,6 +215,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
201
215
  const { before = [], after = [] } = opConfig
202
216
  const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
203
217
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
218
+ if (postReadsEnabled) {
219
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
220
+ }
204
221
  }
205
222
 
206
223
  if (config.enableAll || config.findManyPaginated) {
@@ -208,6 +225,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
208
225
  const { before = [], after = [] } = opConfig
209
226
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
210
227
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
228
+ if (postReadsEnabled) {
229
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
230
+ }
211
231
  }
212
232
 
213
233
  if (config.enableAll || config.aggregate) {
@@ -215,6 +235,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
215
235
  const { before = [], after = [] } = opConfig
216
236
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
217
237
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}Aggregate as RequestHandler, ...after, respond)
238
+ if (postReadsEnabled) {
239
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Aggregate as RequestHandler, ...after, respond)
240
+ }
218
241
  }
219
242
 
220
243
  if (config.enableAll || config.count) {
@@ -222,6 +245,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
222
245
  const { before = [], after = [] } = opConfig
223
246
  const path = basePath ? \`\${basePath}/count\` : '/count'
224
247
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}Count as RequestHandler, ...after, respond)
248
+ if (postReadsEnabled) {
249
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Count as RequestHandler, ...after, respond)
250
+ }
225
251
  }
226
252
 
227
253
  if (config.enableAll || config.groupBy) {
@@ -229,6 +255,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
229
255
  const { before = [], after = [] } = opConfig
230
256
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
231
257
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}GroupBy as RequestHandler, ...after, respond)
258
+ if (postReadsEnabled) {
259
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}GroupBy as RequestHandler, ...after, respond)
260
+ }
232
261
  }
233
262
 
234
263
  if (config.enableAll || config.findUniqueOrThrow) {
@@ -236,6 +265,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
236
265
  const { before = [], after = [] } = opConfig
237
266
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
238
267
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
268
+ if (postReadsEnabled) {
269
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
270
+ }
239
271
  }
240
272
 
241
273
  if (config.enableAll || config.findUnique) {
@@ -243,6 +275,9 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
243
275
  const { before = [], after = [] } = opConfig
244
276
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
245
277
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindUnique as RequestHandler, ...after, respond)
278
+ if (postReadsEnabled) {
279
+ router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUnique as RequestHandler, ...after, respond)
280
+ }
246
281
  }
247
282
 
248
283
  if (config.enableAll || config.findMany) {
@@ -250,6 +285,10 @@ export function ${routerFunctionName}(config: RouteConfig = {}) {
250
285
  const { before = [], after = [] } = opConfig
251
286
  const path = basePath || '/'
252
287
  router.get(path, parseQuery, setShape(opConfig), ...before, ${prefix}FindMany as RequestHandler, ...after, respond)
288
+ if (postReadsEnabled) {
289
+ const postPath = basePath ? \`\${basePath}/read\` : '/read'
290
+ router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindMany as RequestHandler, ...after, respond)
291
+ }
253
292
  }
254
293
 
255
294
  if (config.enableAll || config.createManyAndReturn) {