prisma-generator-express 1.59.0 → 1.60.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 (64) hide show
  1. package/dist/client/encodeQueryParams.js +19 -23
  2. package/dist/client/encodeQueryParams.js.map +1 -1
  3. package/dist/copy/operationDefinitions.d.ts +37 -0
  4. package/dist/copy/operationDefinitions.js +412 -0
  5. package/dist/copy/operationDefinitions.js.map +1 -0
  6. package/dist/generators/generateFastifyHandler.js +10 -41
  7. package/dist/generators/generateFastifyHandler.js.map +1 -1
  8. package/dist/generators/generateHonoHandler.js +10 -41
  9. package/dist/generators/generateHonoHandler.js.map +1 -1
  10. package/dist/generators/generateModelMetadata.d.ts +8 -0
  11. package/dist/generators/generateModelMetadata.js +77 -0
  12. package/dist/generators/generateModelMetadata.js.map +1 -0
  13. package/dist/generators/generateOperationCore.js +20 -30
  14. package/dist/generators/generateOperationCore.js.map +1 -1
  15. package/dist/generators/generateQueryBuilderHelper.js +1 -1
  16. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  17. package/dist/generators/generateRouteConfigType.js +11 -57
  18. package/dist/generators/generateRouteConfigType.js.map +1 -1
  19. package/dist/generators/generateRouter.js +64 -177
  20. package/dist/generators/generateRouter.js.map +1 -1
  21. package/dist/generators/generateRouterFastify.js +59 -169
  22. package/dist/generators/generateRouterFastify.js.map +1 -1
  23. package/dist/generators/generateRouterHono.js +52 -152
  24. package/dist/generators/generateRouterHono.js.map +1 -1
  25. package/dist/generators/generateUnifiedHandler.js +7 -30
  26. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  27. package/dist/generators/generateUnifiedScalarUI.js +9 -74
  28. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  29. package/dist/index.js +11 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/utils/copyFiles.js +7 -0
  32. package/dist/utils/copyFiles.js.map +1 -1
  33. package/dist/utils/writeFileSafely.js +5 -12
  34. package/dist/utils/writeFileSafely.js.map +1 -1
  35. package/package.json +12 -2
  36. package/src/client/encodeQueryParams.ts +23 -36
  37. package/src/copy/autoIncludePlanner.ts +14 -23
  38. package/src/copy/autoIncludeRuntime.ts +117 -228
  39. package/src/copy/buildModelOpenApi.ts +248 -628
  40. package/src/copy/concurrency.ts +20 -0
  41. package/src/copy/docsRenderer.ts +63 -333
  42. package/src/copy/errorMapper.ts +126 -0
  43. package/src/copy/guardHelpers.ts +56 -0
  44. package/src/copy/materializedCount.ts +68 -0
  45. package/src/copy/materializedRouter.ts +33 -29
  46. package/src/copy/operationDefinitions.ts +359 -35
  47. package/src/copy/operationRuntime.ts +11 -605
  48. package/src/copy/pagination.ts +151 -0
  49. package/src/copy/scalarTypes.ts +2 -0
  50. package/src/copy/sse.ts +296 -0
  51. package/src/generators/generateFastifyHandler.ts +13 -47
  52. package/src/generators/generateHonoHandler.ts +13 -47
  53. package/src/generators/generateModelMetadata.ts +92 -0
  54. package/src/generators/generateOperationCore.ts +19 -32
  55. package/src/generators/generateQueryBuilderHelper.ts +1 -1
  56. package/src/generators/generateRouteConfigType.ts +9 -60
  57. package/src/generators/generateRouter.ts +88 -180
  58. package/src/generators/generateRouterFastify.ts +65 -172
  59. package/src/generators/generateRouterHono.ts +58 -155
  60. package/src/generators/generateUnifiedHandler.ts +8 -33
  61. package/src/generators/generateUnifiedScalarUI.ts +9 -91
  62. package/src/index.ts +13 -1
  63. package/src/utils/copyFiles.ts +7 -0
  64. package/src/utils/writeFileSafely.ts +5 -11
@@ -1,6 +1,8 @@
1
+ import { stringify as yamlStringify } from 'yaml'
1
2
  import type { RouteConfig, WriteStrategy } from './routeConfig'
2
- import { OPERATION_DEFS, isOperationEnabled } from './operationDefinitions'
3
+ import { OPERATION_BY_NAME, isOperationEnabled } from './operationDefinitions'
3
4
  import { normalizePrefix, removeTrailingSlash } from './misc'
5
+ import { NUMERIC_SCALAR_TYPES, STRING_NUMERIC_TYPES } from './scalarTypes'
4
6
 
5
7
  type SchemaObject = {
6
8
  type?: string | string[]
@@ -13,6 +15,7 @@ type SchemaObject = {
13
15
  oneOf?: (SchemaObject | RefObject)[]
14
16
  allOf?: (SchemaObject | RefObject)[]
15
17
  nullable?: boolean
18
+ maxItems?: number
16
19
  }
17
20
 
18
21
  type RefObject = { $ref: string; description?: string }
@@ -54,21 +57,44 @@ type BuildOptions = {
54
57
  writeStrategy?: WriteStrategy
55
58
  }
56
59
 
57
- const OP_MAP = new Map(OPERATION_DEFS.map((d) => [d.name, d]))
58
-
59
- const NUMERIC_SCALAR_TYPES = new Set(['Int', 'BigInt', 'Float', 'Decimal'])
60
+ const WHERE_PROP: SchemaObject = { type: 'object', description: 'Filter conditions' }
61
+ const TAKE_PROP: SchemaObject = { type: 'integer', description: 'Limit results' }
62
+ 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)' }
74
+
75
+ const PROJECTION_PROPS: Record<string, SchemaObject> = {
76
+ select: SELECT_PROP,
77
+ include: INCLUDE_PROP,
78
+ omit: OMIT_PROP,
79
+ }
60
80
 
61
- const STRING_NUMERIC_TYPES = new Set(['BigInt', 'Decimal'])
81
+ const AGGREGATE_PROPS: Record<string, SchemaObject> = {
82
+ _count: AGG_COUNT,
83
+ _avg: AGG_AVG,
84
+ _sum: AGG_SUM,
85
+ _min: AGG_MIN,
86
+ _max: AGG_MAX,
87
+ }
62
88
 
63
89
  function opEnabled(config: RouteConfig, name: string): boolean {
64
- const def = OP_MAP.get(name)
65
- return def ? isOperationEnabled(config as Record<string, any>, def) : false
90
+ const meta = OPERATION_BY_NAME[name]
91
+ return meta ? isOperationEnabled(config as Record<string, any>, meta) : false
66
92
  }
67
93
 
68
94
  function opPath(basePath: string, name: string): string {
69
- const def = OP_MAP.get(name)!
70
- if (!def.pathSuffix) return basePath || '/'
71
- return `${basePath}${def.pathSuffix}`
95
+ const meta = OPERATION_BY_NAME[name]
96
+ if (!meta.pathSuffix) return basePath || '/'
97
+ return `${basePath}${meta.pathSuffix}`
72
98
  }
73
99
 
74
100
  function postReadPath(basePath: string, name: string): string {
@@ -97,7 +123,7 @@ const COMMON_ERRORS: Record<number, string> = {
97
123
  503: 'Service unavailable — database connection pool timeout',
98
124
  }
99
125
 
100
- function addErrorResponses(operation: any, codes: number[]): void {
126
+ function addErrorResponses(operation: any, codes: readonly number[]): void {
101
127
  for (const code of codes) {
102
128
  operation.responses[String(code)] = errorResponse(
103
129
  COMMON_ERRORS[code] || 'Error',
@@ -116,6 +142,10 @@ function queryParam(
116
142
  return param
117
143
  }
118
144
 
145
+ const oneOrMany = (schema: SchemaObject | RefObject): SchemaObject => ({
146
+ oneOf: [schema, { type: 'array', items: schema }],
147
+ })
148
+
119
149
  function scalarUpdateOperations(
120
150
  baseSchema: SchemaObject | RefObject,
121
151
  fieldType: string,
@@ -156,15 +186,13 @@ function findManyBodySchema(): SchemaObject {
156
186
  return {
157
187
  type: 'object',
158
188
  properties: {
159
- where: { type: 'object', description: 'Filter conditions' },
160
- orderBy: { description: 'Sort order (object or array of objects)' },
161
- take: { type: 'integer', description: 'Limit results' },
162
- skip: { type: 'integer', description: 'Skip results' },
163
- select: { type: 'object', description: 'Select fields' },
164
- include: { type: 'object', description: 'Include relations' },
165
- omit: { type: 'object', description: 'Omit fields from response' },
166
- cursor: { type: 'object', description: 'Cursor for pagination' },
167
- distinct: { description: 'Distinct fields (string or array of strings)' },
189
+ where: WHERE_PROP,
190
+ orderBy: ORDERBY_PROP,
191
+ take: TAKE_PROP,
192
+ skip: SKIP_PROP,
193
+ ...PROJECTION_PROPS,
194
+ cursor: CURSOR_PROP,
195
+ distinct: DISTINCT_PROP,
168
196
  },
169
197
  }
170
198
  }
@@ -174,9 +202,7 @@ function findUniqueBodySchema(): SchemaObject {
174
202
  type: 'object',
175
203
  properties: {
176
204
  where: { type: 'object', description: 'Unique selector' },
177
- select: { type: 'object', description: 'Select fields' },
178
- include: { type: 'object', description: 'Include relations' },
179
- omit: { type: 'object', description: 'Omit fields from response' },
205
+ ...PROJECTION_PROPS,
180
206
  },
181
207
  required: ['where'],
182
208
  }
@@ -186,11 +212,11 @@ function countBodySchema(): SchemaObject {
186
212
  return {
187
213
  type: 'object',
188
214
  properties: {
189
- where: { type: 'object', description: 'Filter conditions' },
215
+ where: WHERE_PROP,
190
216
  orderBy: { description: 'Sort order' },
191
- take: { type: 'integer', description: 'Limit results' },
192
- skip: { type: 'integer', description: 'Skip results' },
193
- cursor: { type: 'object', description: 'Cursor for pagination' },
217
+ take: TAKE_PROP,
218
+ skip: SKIP_PROP,
219
+ cursor: CURSOR_PROP,
194
220
  select: {
195
221
  description:
196
222
  'Count specific fields. When provided, returns per-field counts as an object instead of a single integer.',
@@ -203,30 +229,12 @@ function aggregateBodySchema(): SchemaObject {
203
229
  return {
204
230
  type: 'object',
205
231
  properties: {
206
- where: { type: 'object', description: 'Filter conditions' },
232
+ where: WHERE_PROP,
207
233
  orderBy: { description: 'Sort order' },
208
- cursor: { type: 'object', description: 'Cursor for pagination' },
209
- take: { type: 'integer', description: 'Limit results' },
210
- skip: { type: 'integer', description: 'Skip results' },
211
- _count: {
212
- description: 'Count aggregate (true or field selection object)',
213
- },
214
- _avg: {
215
- type: 'object',
216
- description: 'Average aggregate (field selection object)',
217
- },
218
- _sum: {
219
- type: 'object',
220
- description: 'Sum aggregate (field selection object)',
221
- },
222
- _min: {
223
- type: 'object',
224
- description: 'Min aggregate (field selection object)',
225
- },
226
- _max: {
227
- type: 'object',
228
- description: 'Max aggregate (field selection object)',
229
- },
234
+ cursor: CURSOR_PROP,
235
+ take: TAKE_PROP,
236
+ skip: SKIP_PROP,
237
+ ...AGGREGATE_PROPS,
230
238
  },
231
239
  }
232
240
  }
@@ -240,57 +248,34 @@ function groupByBodySchema(): SchemaObject {
240
248
  items: { type: 'string' },
241
249
  description: 'Fields to group by',
242
250
  },
243
- where: { type: 'object', description: 'Filter conditions' },
251
+ where: WHERE_PROP,
244
252
  orderBy: { description: 'Sort order. Required when using skip or take.' },
245
253
  having: {
246
254
  type: 'object',
247
255
  description: 'Having conditions (filter object)',
248
256
  },
249
- take: { type: 'integer', description: 'Limit results' },
250
- skip: { type: 'integer', description: 'Skip results' },
251
- _count: {
252
- description: 'Count aggregate (true or field selection object)',
253
- },
254
- _avg: {
255
- type: 'object',
256
- description: 'Average aggregate (field selection object)',
257
- },
258
- _sum: {
259
- type: 'object',
260
- description: 'Sum aggregate (field selection object)',
261
- },
262
- _min: {
263
- type: 'object',
264
- description: 'Min aggregate (field selection object)',
265
- },
266
- _max: {
267
- type: 'object',
268
- description: 'Max aggregate (field selection object)',
269
- },
257
+ take: TAKE_PROP,
258
+ skip: SKIP_PROP,
259
+ ...AGGREGATE_PROPS,
270
260
  },
271
261
  required: ['by'],
272
262
  }
273
263
  }
274
264
 
265
+ const POST_READ_BODY_SCHEMAS: Record<string, () => SchemaObject> = {
266
+ findMany: findManyBodySchema,
267
+ findFirst: findManyBodySchema,
268
+ findFirstOrThrow: findManyBodySchema,
269
+ findManyPaginated: findManyBodySchema,
270
+ findUnique: findUniqueBodySchema,
271
+ findUniqueOrThrow: findUniqueBodySchema,
272
+ count: countBodySchema,
273
+ aggregate: aggregateBodySchema,
274
+ groupBy: groupByBodySchema,
275
+ }
276
+
275
277
  function getPostReadBodySchema(opName: string): SchemaObject {
276
- switch (opName) {
277
- case 'findMany':
278
- case 'findFirst':
279
- case 'findFirstOrThrow':
280
- case 'findManyPaginated':
281
- return findManyBodySchema()
282
- case 'findUnique':
283
- case 'findUniqueOrThrow':
284
- return findUniqueBodySchema()
285
- case 'count':
286
- return countBodySchema()
287
- case 'aggregate':
288
- return aggregateBodySchema()
289
- case 'groupBy':
290
- return groupByBodySchema()
291
- default:
292
- return findManyBodySchema()
293
- }
278
+ return (POST_READ_BODY_SCHEMAS[opName] ?? findManyBodySchema)()
294
279
  }
295
280
 
296
281
  function applyWriteStrategy(
@@ -335,36 +320,30 @@ function applyWriteStrategy(
335
320
  }
336
321
  const reqSchema = op.requestBody?.content?.['application/json']?.schema
337
322
  if (reqSchema && reqSchema.properties) {
338
- reqSchema.properties.select = {
339
- type: 'object',
340
- description: 'Select fields to return',
341
- }
342
- reqSchema.properties.include = {
343
- type: 'object',
344
- description: 'Include relations to return',
345
- }
346
- reqSchema.properties.omit = {
347
- type: 'object',
348
- description: 'Omit fields from response',
349
- }
323
+ Object.assign(reqSchema.properties, PROJECTION_PROPS)
350
324
  }
351
325
  }
352
326
 
353
- if (node.post) {
354
- injectProjectionAndArrayResponse(
355
- node.post,
356
- '201',
357
- 'Create many ' + modelName + ' (forceReturn)',
358
- 'writeStrategy="forceReturn": this endpoint silently invokes createManyAndReturn and returns the created records instead of { count }.',
359
- )
360
- }
327
+ const forceReturnOps: Array<{
328
+ method: 'post' | 'put'
329
+ successCode: '200' | '201'
330
+ verb: string
331
+ targetOp: string
332
+ }> = [
333
+ { method: 'post', successCode: '201', verb: 'Create', targetOp: 'createManyAndReturn' },
334
+ { method: 'put', successCode: '200', verb: 'Update', targetOp: 'updateManyAndReturn' },
335
+ ]
361
336
 
362
- if (node.put) {
337
+ for (const entry of forceReturnOps) {
338
+ const target = node[entry.method]
339
+ if (!target) continue
363
340
  injectProjectionAndArrayResponse(
364
- node.put,
365
- '200',
366
- 'Update many ' + modelName + ' (forceReturn)',
367
- 'writeStrategy="forceReturn": this endpoint silently invokes updateManyAndReturn and returns the updated records instead of { count }.',
341
+ target,
342
+ entry.successCode,
343
+ 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 }.',
368
347
  )
369
348
  }
370
349
  }
@@ -430,13 +409,11 @@ export function buildModelOpenApi(
430
409
  }
431
410
 
432
411
  generateOperationSchemas(spec, modelName, modelFields)
433
-
434
412
  generatePaths(spec, modelName, basePath, config, modelFields)
435
-
436
413
  applyWriteStrategy(spec, modelName, basePath, options.writeStrategy)
437
414
 
438
415
  if (options.format === 'yaml') {
439
- return toYaml(spec)
416
+ return yamlStringify(spec)
440
417
  }
441
418
  return spec
442
419
  }
@@ -446,12 +423,9 @@ function generateOperationSchemas(
446
423
  modelName: string,
447
424
  fields: ModelField[],
448
425
  ) {
449
- const relatedModels = new Set<string>()
450
- fields.forEach((field) => {
451
- if (field.kind === 'object') {
452
- relatedModels.add(field.type)
453
- }
454
- })
426
+ const relatedModels = new Set(
427
+ fields.filter((f) => f.kind === 'object').map((f) => f.type),
428
+ )
455
429
 
456
430
  relatedModels.forEach((relatedModel) => {
457
431
  if (!spec.components.schemas[`${relatedModel}Response`]) {
@@ -613,11 +587,6 @@ function generateOperationSchemas(
613
587
  (f) => f.kind === 'scalar' && NUMERIC_SCALAR_TYPES.has(f.type),
614
588
  )
615
589
 
616
- const numericFieldSelection: Record<string, SchemaObject> = {}
617
- for (const f of numericFields) {
618
- numericFieldSelection[f.name] = { type: 'boolean' }
619
- }
620
-
621
590
  const avgResultProps: Record<string, SchemaObject> = {}
622
591
  const numericResultProps: Record<string, SchemaObject> = {}
623
592
  for (const f of numericFields) {
@@ -632,15 +601,6 @@ function generateOperationSchemas(
632
601
  : { oneOf: [{ type: 'number' }, { type: 'null' }] }
633
602
  }
634
603
 
635
- const allFieldSelection: Record<string, SchemaObject> = {
636
- _all: { type: 'boolean' },
637
- }
638
- for (const f of fields) {
639
- if (f.kind === 'scalar' || f.kind === 'enum') {
640
- allFieldSelection[f.name] = { type: 'boolean' }
641
- }
642
- }
643
-
644
604
  const countResultProps: Record<string, SchemaObject> = {
645
605
  _all: { type: 'integer' },
646
606
  }
@@ -798,7 +758,7 @@ function addPostReadOperation(
798
758
  opName: string,
799
759
  summary: string,
800
760
  responseSchema: any,
801
- errorCodes: number[],
761
+ errorCodes: readonly number[],
802
762
  description?: string,
803
763
  ) {
804
764
  const op: any = {
@@ -836,40 +796,21 @@ function generatePaths(
836
796
  ) {
837
797
  const postReads = !config.disablePostReads
838
798
 
839
- const createInputRef = {
840
- $ref: `#/components/schemas/${modelName}CreateInput`,
841
- }
842
- const updateInputRef = {
843
- $ref: `#/components/schemas/${modelName}UpdateInput`,
844
- }
845
- const createManyInputRef = {
846
- $ref: `#/components/schemas/${modelName}CreateManyInput`,
847
- }
848
- const updateManyMutationRef = {
849
- $ref: `#/components/schemas/${modelName}UpdateManyMutationInput`,
850
- }
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` }
851
803
  const responseRef = { $ref: `#/components/schemas/${modelName}Response` }
852
- const nullableResponseSchema = {
853
- oneOf: [responseRef, { type: 'null' as const }],
854
- }
855
- const batchCountRef = {
856
- $ref: `#/components/schemas/${modelName}BatchCountResponse`,
857
- }
804
+ const nullableResponseSchema = { oneOf: [responseRef, { type: 'null' as const }] }
805
+ const batchCountRef = { $ref: `#/components/schemas/${modelName}BatchCountResponse` }
858
806
  const listRef = { $ref: `#/components/schemas/${modelName}ListResponse` }
859
- const aggregateRef = {
860
- $ref: `#/components/schemas/${modelName}AggregateResponse`,
861
- }
862
- const groupByItemRef = {
863
- $ref: `#/components/schemas/${modelName}GroupByItem`,
864
- }
865
- const updateEachItemRef = {
866
- $ref: `#/components/schemas/${modelName}UpdateEachItemInput`,
867
- }
868
- const updateEachResponseRef = {
869
- $ref: `#/components/schemas/${modelName}UpdateEachResponse`,
870
- }
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` }
871
811
 
872
812
  if (opEnabled(config, 'findMany')) {
813
+ const meta = OPERATION_BY_NAME['findMany']
873
814
  const op: any = {
874
815
  tags: [modelName],
875
816
  summary: `List ${modelName}`,
@@ -886,7 +827,7 @@ function generatePaths(
886
827
  },
887
828
  },
888
829
  }
889
- addErrorResponses(op, [400, 403, 500, 501, 503])
830
+ addErrorResponses(op, meta.errors)
890
831
  addPath(spec, opPath(basePath, 'findMany'), 'get', op)
891
832
 
892
833
  if (postReads) {
@@ -897,12 +838,13 @@ function generatePaths(
897
838
  'findMany',
898
839
  `List ${modelName}`,
899
840
  { type: 'array', items: responseRef },
900
- [400, 403, 500, 501, 503],
841
+ meta.errors,
901
842
  )
902
843
  }
903
844
  }
904
845
 
905
846
  if (opEnabled(config, 'findUnique')) {
847
+ const meta = OPERATION_BY_NAME['findUnique']
906
848
  const op: any = {
907
849
  tags: [modelName],
908
850
  summary: `Get ${modelName} by unique constraint`,
@@ -917,7 +859,7 @@ function generatePaths(
917
859
  },
918
860
  },
919
861
  }
920
- addErrorResponses(op, [400, 403, 500, 501, 503])
862
+ addErrorResponses(op, meta.errors)
921
863
  addPath(spec, opPath(basePath, 'findUnique'), 'get', op)
922
864
 
923
865
  if (postReads) {
@@ -928,13 +870,14 @@ function generatePaths(
928
870
  'findUnique',
929
871
  `Get ${modelName} by unique constraint`,
930
872
  nullableResponseSchema,
931
- [400, 403, 500, 501, 503],
873
+ meta.errors,
932
874
  'Returns null with status 200 when no record matches the unique constraint.',
933
875
  )
934
876
  }
935
877
  }
936
878
 
937
879
  if (opEnabled(config, 'findUniqueOrThrow')) {
880
+ const meta = OPERATION_BY_NAME['findUniqueOrThrow']
938
881
  const op: any = {
939
882
  tags: [modelName],
940
883
  summary: `Get ${modelName} by unique constraint (throws if not found)`,
@@ -947,7 +890,7 @@ function generatePaths(
947
890
  },
948
891
  },
949
892
  }
950
- addErrorResponses(op, [400, 403, 404, 500, 501, 503])
893
+ addErrorResponses(op, meta.errors)
951
894
  addPath(spec, opPath(basePath, 'findUniqueOrThrow'), 'get', op)
952
895
 
953
896
  if (postReads) {
@@ -958,12 +901,13 @@ function generatePaths(
958
901
  'findUniqueOrThrow',
959
902
  `Get ${modelName} by unique constraint (throws if not found)`,
960
903
  responseRef,
961
- [400, 403, 404, 500, 501, 503],
904
+ meta.errors,
962
905
  )
963
906
  }
964
907
  }
965
908
 
966
909
  if (opEnabled(config, 'findFirst')) {
910
+ const meta = OPERATION_BY_NAME['findFirst']
967
911
  const op: any = {
968
912
  tags: [modelName],
969
913
  summary: `Get first ${modelName}`,
@@ -977,7 +921,7 @@ function generatePaths(
977
921
  },
978
922
  },
979
923
  }
980
- addErrorResponses(op, [400, 403, 500, 501, 503])
924
+ addErrorResponses(op, meta.errors)
981
925
  addPath(spec, opPath(basePath, 'findFirst'), 'get', op)
982
926
 
983
927
  if (postReads) {
@@ -988,13 +932,14 @@ function generatePaths(
988
932
  'findFirst',
989
933
  `Get first ${modelName}`,
990
934
  nullableResponseSchema,
991
- [400, 403, 500, 501, 503],
935
+ meta.errors,
992
936
  'Returns null with status 200 when no record matches.',
993
937
  )
994
938
  }
995
939
  }
996
940
 
997
941
  if (opEnabled(config, 'findFirstOrThrow')) {
942
+ const meta = OPERATION_BY_NAME['findFirstOrThrow']
998
943
  const op: any = {
999
944
  tags: [modelName],
1000
945
  summary: `Get first ${modelName} (throws if not found)`,
@@ -1007,7 +952,7 @@ function generatePaths(
1007
952
  },
1008
953
  },
1009
954
  }
1010
- addErrorResponses(op, [400, 403, 404, 500, 501, 503])
955
+ addErrorResponses(op, meta.errors)
1011
956
  addPath(spec, opPath(basePath, 'findFirstOrThrow'), 'get', op)
1012
957
 
1013
958
  if (postReads) {
@@ -1018,12 +963,13 @@ function generatePaths(
1018
963
  'findFirstOrThrow',
1019
964
  `Get first ${modelName} (throws if not found)`,
1020
965
  responseRef,
1021
- [400, 403, 404, 500, 501, 503],
966
+ meta.errors,
1022
967
  )
1023
968
  }
1024
969
  }
1025
970
 
1026
971
  if (opEnabled(config, 'findManyPaginated')) {
972
+ const meta = OPERATION_BY_NAME['findManyPaginated']
1027
973
  const op: any = {
1028
974
  tags: [modelName],
1029
975
  summary: `List ${modelName} with pagination`,
@@ -1038,7 +984,7 @@ function generatePaths(
1038
984
  },
1039
985
  },
1040
986
  }
1041
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
987
+ addErrorResponses(op, meta.errors)
1042
988
  addPath(spec, opPath(basePath, 'findManyPaginated'), 'get', op)
1043
989
 
1044
990
  if (postReads) {
@@ -1049,13 +995,14 @@ function generatePaths(
1049
995
  'findManyPaginated',
1050
996
  `List ${modelName} with pagination`,
1051
997
  listRef,
1052
- [400, 403, 409, 500, 501, 503],
998
+ meta.errors,
1053
999
  'Returns paginated results with total count.',
1054
1000
  )
1055
1001
  }
1056
1002
  }
1057
1003
 
1058
1004
  if (opEnabled(config, 'create')) {
1005
+ const meta = OPERATION_BY_NAME['create']
1059
1006
  const op: any = {
1060
1007
  tags: [modelName],
1061
1008
  summary: `Create ${modelName}`,
@@ -1068,18 +1015,7 @@ function generatePaths(
1068
1015
  type: 'object',
1069
1016
  properties: {
1070
1017
  data: createInputRef,
1071
- select: {
1072
- type: 'object',
1073
- description: 'Select fields to return',
1074
- },
1075
- include: {
1076
- type: 'object',
1077
- description: 'Include relations to return',
1078
- },
1079
- omit: {
1080
- type: 'object',
1081
- description: 'Omit fields from response',
1082
- },
1018
+ ...PROJECTION_PROPS,
1083
1019
  },
1084
1020
  required: ['data'],
1085
1021
  },
@@ -1093,11 +1029,12 @@ function generatePaths(
1093
1029
  },
1094
1030
  },
1095
1031
  }
1096
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1032
+ addErrorResponses(op, meta.errors)
1097
1033
  addPath(spec, opPath(basePath, 'create'), 'post', op)
1098
1034
  }
1099
1035
 
1100
1036
  if (opEnabled(config, 'createMany')) {
1037
+ const meta = OPERATION_BY_NAME['createMany']
1101
1038
  const op: any = {
1102
1039
  tags: [modelName],
1103
1040
  summary: `Create many ${modelName}`,
@@ -1128,11 +1065,12 @@ function generatePaths(
1128
1065
  },
1129
1066
  },
1130
1067
  }
1131
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1068
+ addErrorResponses(op, meta.errors)
1132
1069
  addPath(spec, opPath(basePath, 'createMany'), 'post', op)
1133
1070
  }
1134
1071
 
1135
1072
  if (opEnabled(config, 'createManyAndReturn')) {
1073
+ const meta = OPERATION_BY_NAME['createManyAndReturn']
1136
1074
  const op: any = {
1137
1075
  tags: [modelName],
1138
1076
  summary: `Create many ${modelName} and return records`,
@@ -1150,18 +1088,7 @@ function generatePaths(
1150
1088
  description:
1151
1089
  'Skip records that would cause unique constraint violations. Not supported on all database providers.',
1152
1090
  },
1153
- select: {
1154
- type: 'object',
1155
- description: 'Select fields to return',
1156
- },
1157
- include: {
1158
- type: 'object',
1159
- description: 'Include relations to return',
1160
- },
1161
- omit: {
1162
- type: 'object',
1163
- description: 'Omit fields from response',
1164
- },
1091
+ ...PROJECTION_PROPS,
1165
1092
  },
1166
1093
  required: ['data'],
1167
1094
  },
@@ -1179,11 +1106,12 @@ function generatePaths(
1179
1106
  },
1180
1107
  },
1181
1108
  }
1182
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1109
+ addErrorResponses(op, meta.errors)
1183
1110
  addPath(spec, opPath(basePath, 'createManyAndReturn'), 'post', op)
1184
1111
  }
1185
1112
 
1186
1113
  if (opEnabled(config, 'update')) {
1114
+ const meta = OPERATION_BY_NAME['update']
1187
1115
  const op: any = {
1188
1116
  tags: [modelName],
1189
1117
  summary: `Update ${modelName}`,
@@ -1197,18 +1125,7 @@ function generatePaths(
1197
1125
  properties: {
1198
1126
  where: { type: 'object' },
1199
1127
  data: updateInputRef,
1200
- select: {
1201
- type: 'object',
1202
- description: 'Select fields to return',
1203
- },
1204
- include: {
1205
- type: 'object',
1206
- description: 'Include relations to return',
1207
- },
1208
- omit: {
1209
- type: 'object',
1210
- description: 'Omit fields from response',
1211
- },
1128
+ ...PROJECTION_PROPS,
1212
1129
  },
1213
1130
  required: ['where', 'data'],
1214
1131
  },
@@ -1222,11 +1139,12 @@ function generatePaths(
1222
1139
  },
1223
1140
  },
1224
1141
  }
1225
- addErrorResponses(op, [400, 403, 404, 409, 500, 501, 503])
1142
+ addErrorResponses(op, meta.errors)
1226
1143
  addPath(spec, opPath(basePath, 'update'), 'put', op)
1227
1144
  }
1228
1145
 
1229
1146
  if (opEnabled(config, 'updateMany')) {
1147
+ const meta = OPERATION_BY_NAME['updateMany']
1230
1148
  const op: any = {
1231
1149
  tags: [modelName],
1232
1150
  summary: `Update many ${modelName}`,
@@ -1253,11 +1171,12 @@ function generatePaths(
1253
1171
  },
1254
1172
  },
1255
1173
  }
1256
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1174
+ addErrorResponses(op, meta.errors)
1257
1175
  addPath(spec, opPath(basePath, 'updateMany'), 'put', op)
1258
1176
  }
1259
1177
 
1260
1178
  if (opEnabled(config, 'updateManyAndReturn')) {
1179
+ const meta = OPERATION_BY_NAME['updateManyAndReturn']
1261
1180
  const op: any = {
1262
1181
  tags: [modelName],
1263
1182
  summary: `Update many ${modelName} and return records`,
@@ -1271,18 +1190,7 @@ function generatePaths(
1271
1190
  properties: {
1272
1191
  where: { type: 'object' },
1273
1192
  data: updateManyMutationRef,
1274
- select: {
1275
- type: 'object',
1276
- description: 'Select fields to return',
1277
- },
1278
- include: {
1279
- type: 'object',
1280
- description: 'Include relations to return',
1281
- },
1282
- omit: {
1283
- type: 'object',
1284
- description: 'Omit fields from response',
1285
- },
1193
+ ...PROJECTION_PROPS,
1286
1194
  },
1287
1195
  required: ['where', 'data'],
1288
1196
  },
@@ -1300,11 +1208,12 @@ function generatePaths(
1300
1208
  },
1301
1209
  },
1302
1210
  }
1303
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1211
+ addErrorResponses(op, meta.errors)
1304
1212
  addPath(spec, opPath(basePath, 'updateManyAndReturn'), 'put', op)
1305
1213
  }
1306
1214
 
1307
1215
  if (opEnabled(config, 'upsert')) {
1216
+ const meta = OPERATION_BY_NAME['upsert']
1308
1217
  const op: any = {
1309
1218
  tags: [modelName],
1310
1219
  summary: `Upsert ${modelName}`,
@@ -1319,18 +1228,7 @@ function generatePaths(
1319
1228
  where: { type: 'object' },
1320
1229
  create: createInputRef,
1321
1230
  update: updateInputRef,
1322
- select: {
1323
- type: 'object',
1324
- description: 'Select fields to return',
1325
- },
1326
- include: {
1327
- type: 'object',
1328
- description: 'Include relations to return',
1329
- },
1330
- omit: {
1331
- type: 'object',
1332
- description: 'Omit fields from response',
1333
- },
1231
+ ...PROJECTION_PROPS,
1334
1232
  },
1335
1233
  required: ['where', 'create', 'update'],
1336
1234
  },
@@ -1344,11 +1242,12 @@ function generatePaths(
1344
1242
  },
1345
1243
  },
1346
1244
  }
1347
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1245
+ addErrorResponses(op, meta.errors)
1348
1246
  addPath(spec, opPath(basePath, 'upsert'), 'patch', op)
1349
1247
  }
1350
1248
 
1351
1249
  if (opEnabled(config, 'delete')) {
1250
+ const meta = OPERATION_BY_NAME['delete']
1352
1251
  const op: any = {
1353
1252
  tags: [modelName],
1354
1253
  summary: `Delete ${modelName}`,
@@ -1361,18 +1260,7 @@ function generatePaths(
1361
1260
  type: 'object',
1362
1261
  properties: {
1363
1262
  where: { type: 'object' },
1364
- select: {
1365
- type: 'object',
1366
- description: 'Select fields to return',
1367
- },
1368
- include: {
1369
- type: 'object',
1370
- description: 'Include relations to return',
1371
- },
1372
- omit: {
1373
- type: 'object',
1374
- description: 'Omit fields from response',
1375
- },
1263
+ ...PROJECTION_PROPS,
1376
1264
  },
1377
1265
  required: ['where'],
1378
1266
  },
@@ -1386,11 +1274,12 @@ function generatePaths(
1386
1274
  },
1387
1275
  },
1388
1276
  }
1389
- addErrorResponses(op, [400, 403, 404, 500, 501, 503])
1277
+ addErrorResponses(op, meta.errors)
1390
1278
  addPath(spec, opPath(basePath, 'delete'), 'delete', op)
1391
1279
  }
1392
1280
 
1393
1281
  if (opEnabled(config, 'deleteMany')) {
1282
+ const meta = OPERATION_BY_NAME['deleteMany']
1394
1283
  const op: any = {
1395
1284
  tags: [modelName],
1396
1285
  summary: `Delete many ${modelName}`,
@@ -1414,11 +1303,12 @@ function generatePaths(
1414
1303
  },
1415
1304
  },
1416
1305
  }
1417
- addErrorResponses(op, [400, 403, 500, 501, 503])
1306
+ addErrorResponses(op, meta.errors)
1418
1307
  addPath(spec, opPath(basePath, 'deleteMany'), 'delete', op)
1419
1308
  }
1420
1309
 
1421
1310
  if (opEnabled(config, 'count')) {
1311
+ const meta = OPERATION_BY_NAME['count']
1422
1312
  const op: any = {
1423
1313
  tags: [modelName],
1424
1314
  summary: `Count ${modelName}`,
@@ -1437,8 +1327,7 @@ function generatePaths(
1437
1327
  },
1438
1328
  {
1439
1329
  type: 'object',
1440
- description:
1441
- 'Per-field count object when select is provided',
1330
+ description: 'Per-field count object when select is provided',
1442
1331
  },
1443
1332
  ],
1444
1333
  },
@@ -1447,7 +1336,7 @@ function generatePaths(
1447
1336
  },
1448
1337
  },
1449
1338
  }
1450
- addErrorResponses(op, [400, 403, 500, 501, 503])
1339
+ addErrorResponses(op, meta.errors)
1451
1340
  addPath(spec, opPath(basePath, 'count'), 'get', op)
1452
1341
 
1453
1342
  if (postReads) {
@@ -1469,12 +1358,13 @@ function generatePaths(
1469
1358
  },
1470
1359
  ],
1471
1360
  },
1472
- [400, 403, 500, 501, 503],
1361
+ meta.errors,
1473
1362
  )
1474
1363
  }
1475
1364
  }
1476
1365
 
1477
1366
  if (opEnabled(config, 'aggregate')) {
1367
+ const meta = OPERATION_BY_NAME['aggregate']
1478
1368
  const op: any = {
1479
1369
  tags: [modelName],
1480
1370
  summary: `Aggregate ${modelName}`,
@@ -1487,7 +1377,7 @@ function generatePaths(
1487
1377
  },
1488
1378
  },
1489
1379
  }
1490
- addErrorResponses(op, [400, 403, 500, 501, 503])
1380
+ addErrorResponses(op, meta.errors)
1491
1381
  addPath(spec, opPath(basePath, 'aggregate'), 'get', op)
1492
1382
 
1493
1383
  if (postReads) {
@@ -1498,12 +1388,13 @@ function generatePaths(
1498
1388
  'aggregate',
1499
1389
  `Aggregate ${modelName}`,
1500
1390
  aggregateRef,
1501
- [400, 403, 500, 501, 503],
1391
+ meta.errors,
1502
1392
  )
1503
1393
  }
1504
1394
  }
1505
1395
 
1506
1396
  if (opEnabled(config, 'groupBy')) {
1397
+ const meta = OPERATION_BY_NAME['groupBy']
1507
1398
  const op: any = {
1508
1399
  tags: [modelName],
1509
1400
  summary: `Group ${modelName}`,
@@ -1522,7 +1413,7 @@ function generatePaths(
1522
1413
  },
1523
1414
  },
1524
1415
  }
1525
- addErrorResponses(op, [400, 403, 500, 501, 503])
1416
+ addErrorResponses(op, meta.errors)
1526
1417
  addPath(spec, opPath(basePath, 'groupBy'), 'get', op)
1527
1418
 
1528
1419
  if (postReads) {
@@ -1533,13 +1424,14 @@ function generatePaths(
1533
1424
  'groupBy',
1534
1425
  `Group ${modelName}`,
1535
1426
  { type: 'array', items: groupByItemRef },
1536
- [400, 403, 500, 501, 503],
1427
+ meta.errors,
1537
1428
  'Groups records by the specified fields and returns aggregates.',
1538
1429
  )
1539
1430
  }
1540
1431
  }
1541
1432
 
1542
1433
  if (opEnabled(config, 'updateEach')) {
1434
+ const meta = OPERATION_BY_NAME['updateEach']
1543
1435
  const op: any = {
1544
1436
  tags: [modelName],
1545
1437
  summary: `Update each ${modelName} (batch)`,
@@ -1578,7 +1470,7 @@ function generatePaths(
1578
1470
  },
1579
1471
  },
1580
1472
  }
1581
- addErrorResponses(op, [400, 403, 409, 500, 501, 503])
1473
+ addErrorResponses(op, meta.errors)
1582
1474
  addPath(spec, opPath(basePath, 'updateEach'), 'post', op)
1583
1475
  }
1584
1476
  }
@@ -1688,225 +1580,95 @@ function mapFieldToSchema(field: ModelField): SchemaObject | RefObject {
1688
1580
  return schema
1689
1581
  }
1690
1582
 
1691
- function mapFieldToWriteSchema(
1692
- field: ModelField,
1693
- mode: 'create' | 'update',
1694
- ): SchemaObject | RefObject {
1695
- if (field.kind === 'object') {
1696
- if (field.isList) {
1697
- return {
1583
+ 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' }
1590
+ const whereCreatePair: SchemaObject = {
1591
+ type: 'object',
1592
+ description: '{ where, create } pair',
1593
+ properties: { where: { type: 'object' }, create: { type: 'object' } },
1594
+ }
1595
+ const whereDataPair: SchemaObject = {
1596
+ type: 'object',
1597
+ description: '{ where, data } pair',
1598
+ properties: { where: { type: 'object' }, data: { type: 'object' } },
1599
+ }
1600
+ const whereCreateUpdateTriple: SchemaObject = {
1601
+ type: 'object',
1602
+ description: '{ where, create, update } triple',
1603
+ properties: {
1604
+ where: { type: 'object' },
1605
+ create: { type: 'object' },
1606
+ update: { type: 'object' },
1607
+ },
1608
+ }
1609
+
1610
+ return {
1611
+ type: 'object',
1612
+ description: `Nested ${fieldType} write operations for list relation`,
1613
+ properties: {
1614
+ create: oneOrMany(createInput),
1615
+ connect: oneOrMany(uniqueConnect),
1616
+ connectOrCreate: oneOrMany(whereCreatePair),
1617
+ createMany: {
1698
1618
  type: 'object',
1699
- description: `Nested ${field.type} write operations for list relation`,
1700
1619
  properties: {
1701
- create: {
1702
- oneOf: [
1703
- { type: 'object', description: `${field.type} create input` },
1704
- {
1705
- type: 'array',
1706
- items: {
1707
- type: 'object',
1708
- description: `${field.type} create input`,
1709
- },
1710
- },
1711
- ],
1712
- },
1713
- connect: {
1714
- oneOf: [
1715
- { type: 'object', description: 'Unique identifier to connect' },
1716
- {
1717
- type: 'array',
1718
- items: {
1719
- type: 'object',
1720
- description: 'Unique identifier to connect',
1721
- },
1722
- },
1723
- ],
1724
- },
1725
- connectOrCreate: {
1726
- oneOf: [
1727
- {
1728
- type: 'object',
1729
- description: '{ where, create } pair',
1730
- properties: {
1731
- where: { type: 'object' },
1732
- create: { type: 'object' },
1733
- },
1734
- },
1735
- {
1736
- type: 'array',
1737
- items: {
1738
- type: 'object',
1739
- properties: {
1740
- where: { type: 'object' },
1741
- create: { type: 'object' },
1742
- },
1743
- },
1744
- },
1745
- ],
1746
- },
1747
- createMany: {
1748
- type: 'object',
1749
- properties: {
1750
- data: {
1751
- type: 'array',
1752
- items: {
1753
- type: 'object',
1754
- description: `${field.type} create input`,
1755
- },
1756
- },
1757
- skipDuplicates: { type: 'boolean' },
1758
- },
1759
- },
1760
- set: {
1761
- type: 'array',
1762
- items: { type: 'object', description: 'Unique identifier' },
1763
- description: 'Replace all connected records',
1764
- },
1765
- disconnect: {
1766
- oneOf: [
1767
- {
1768
- type: 'object',
1769
- description: 'Unique identifier to disconnect',
1770
- },
1771
- {
1772
- type: 'array',
1773
- items: {
1774
- type: 'object',
1775
- description: 'Unique identifier to disconnect',
1776
- },
1777
- },
1778
- ],
1779
- },
1780
- delete: {
1781
- oneOf: [
1782
- { type: 'object', description: 'Unique identifier to delete' },
1783
- {
1784
- type: 'array',
1785
- items: {
1786
- type: 'object',
1787
- description: 'Unique identifier to delete',
1788
- },
1789
- },
1790
- ],
1791
- },
1792
- update: {
1793
- oneOf: [
1794
- {
1795
- type: 'object',
1796
- description: '{ where, data } pair',
1797
- properties: {
1798
- where: { type: 'object' },
1799
- data: { type: 'object' },
1800
- },
1801
- },
1802
- {
1803
- type: 'array',
1804
- items: {
1805
- type: 'object',
1806
- properties: {
1807
- where: { type: 'object' },
1808
- data: { type: 'object' },
1809
- },
1810
- },
1811
- },
1812
- ],
1813
- },
1814
- updateMany: {
1815
- oneOf: [
1816
- {
1817
- type: 'object',
1818
- description: '{ where, data } pair',
1819
- properties: {
1820
- where: { type: 'object' },
1821
- data: { type: 'object' },
1822
- },
1823
- },
1824
- {
1825
- type: 'array',
1826
- items: {
1827
- type: 'object',
1828
- properties: {
1829
- where: { type: 'object' },
1830
- data: { type: 'object' },
1831
- },
1832
- },
1833
- },
1834
- ],
1835
- },
1836
- deleteMany: {
1837
- oneOf: [
1838
- { type: 'object', description: 'Where filter' },
1839
- {
1840
- type: 'array',
1841
- items: { type: 'object', description: 'Where filter' },
1842
- },
1843
- ],
1844
- },
1845
- upsert: {
1846
- oneOf: [
1847
- {
1848
- type: 'object',
1849
- description: '{ where, create, update } triple',
1850
- properties: {
1851
- where: { type: 'object' },
1852
- create: { type: 'object' },
1853
- update: { type: 'object' },
1854
- },
1855
- },
1856
- {
1857
- type: 'array',
1858
- items: {
1859
- type: 'object',
1860
- description: '{ where, create, update } triple',
1861
- properties: {
1862
- where: { type: 'object' },
1863
- create: { type: 'object' },
1864
- update: { type: 'object' },
1865
- },
1866
- },
1867
- },
1868
- ],
1869
- },
1870
- },
1871
- }
1872
- }
1873
- return {
1874
- type: 'object',
1875
- description: `Nested ${field.type} write operations for single relation`,
1876
- properties: {
1877
- create: { type: 'object', description: `${field.type} create input` },
1878
- connect: {
1879
- type: 'object',
1880
- description: 'Unique identifier to connect',
1881
- },
1882
- connectOrCreate: {
1883
- type: 'object',
1884
- description: '{ where, create } pair',
1885
- properties: { where: { type: 'object' }, create: { type: 'object' } },
1886
- },
1887
- disconnect: {
1888
- type: 'boolean',
1889
- description: 'Disconnect the related record',
1620
+ data: { type: 'array', items: createInput },
1621
+ skipDuplicates: { type: 'boolean' },
1890
1622
  },
1891
- delete: { type: 'boolean', description: 'Delete the related record' },
1892
- update: { type: 'object', description: `${field.type} update input` },
1893
- upsert: {
1894
- type: 'object',
1895
- description:
1896
- '{ create, update } pair — create if not exists, update if exists',
1897
- properties: {
1898
- create: {
1899
- type: 'object',
1900
- description: `${field.type} create input`,
1901
- },
1902
- update: {
1903
- type: 'object',
1904
- description: `${field.type} update input`,
1905
- },
1906
- },
1623
+ },
1624
+ set: {
1625
+ type: 'array',
1626
+ items: uniqueId,
1627
+ description: 'Replace all connected records',
1628
+ },
1629
+ disconnect: oneOrMany(uniqueDisconnect),
1630
+ delete: oneOrMany(uniqueDelete),
1631
+ update: oneOrMany(whereDataPair),
1632
+ updateMany: oneOrMany(whereDataPair),
1633
+ deleteMany: oneOrMany(whereFilter),
1634
+ upsert: oneOrMany(whereCreateUpdateTriple),
1635
+ },
1636
+ }
1637
+ }
1638
+
1639
+ function nestedSingleRelationOps(fieldType: string): SchemaObject {
1640
+ return {
1641
+ type: 'object',
1642
+ description: `Nested ${fieldType} write operations for single relation`,
1643
+ properties: {
1644
+ create: { type: 'object', description: `${fieldType} create input` },
1645
+ connect: { type: 'object', description: 'Unique identifier to connect' },
1646
+ connectOrCreate: {
1647
+ type: 'object',
1648
+ description: '{ where, create } pair',
1649
+ properties: { where: { type: 'object' }, create: { type: 'object' } },
1650
+ },
1651
+ disconnect: { type: 'boolean', description: 'Disconnect the related record' },
1652
+ delete: { type: 'boolean', description: 'Delete the related record' },
1653
+ update: { type: 'object', description: `${fieldType} update input` },
1654
+ upsert: {
1655
+ type: 'object',
1656
+ description: '{ create, update } pair — create if not exists, update if exists',
1657
+ properties: {
1658
+ create: { type: 'object', description: `${fieldType} create input` },
1659
+ update: { type: 'object', description: `${fieldType} update input` },
1907
1660
  },
1908
1661
  },
1909
- }
1662
+ },
1663
+ }
1664
+ }
1665
+
1666
+ function mapFieldToWriteSchema(
1667
+ field: ModelField,
1668
+ mode: 'create' | 'update',
1669
+ ): SchemaObject | RefObject {
1670
+ if (field.kind === 'object') {
1671
+ return field.isList ? nestedListRelationOps(field.type) : nestedSingleRelationOps(field.type)
1910
1672
  }
1911
1673
 
1912
1674
  let baseSchema: SchemaObject | RefObject
@@ -1971,146 +1733,4 @@ function mapScalarType(type: string): SchemaObject {
1971
1733
  },
1972
1734
  }
1973
1735
  return typeMap[type] || { type: 'string' }
1974
- }
1975
-
1976
- function yamlEscapeValue(value: unknown, indent: number = 0): string {
1977
- if (value === null) return 'null'
1978
- if (value === undefined) return 'null'
1979
- if (typeof value === 'boolean') return value ? 'true' : 'false'
1980
- if (typeof value === 'number') return String(value)
1981
-
1982
- const str = String(value)
1983
- if (str === '') return "''"
1984
-
1985
- const needsQuote =
1986
- str === '~' ||
1987
- str === '.inf' ||
1988
- str === '-.inf' ||
1989
- str === '.nan' ||
1990
- str === '-' ||
1991
- str === '?' ||
1992
- str.startsWith('- ') ||
1993
- str.startsWith('? ') ||
1994
- /^[!&*|>@`]/.test(str) ||
1995
- str.includes(':') ||
1996
- str.includes('#') ||
1997
- str.includes('{') ||
1998
- str.includes('}') ||
1999
- str.includes('[') ||
2000
- str.includes(']') ||
2001
- str.includes(',') ||
2002
- str.includes('&') ||
2003
- str.includes('*') ||
2004
- str.includes('!') ||
2005
- str.includes('|') ||
2006
- str.includes('>') ||
2007
- str.includes("'") ||
2008
- str.includes('"') ||
2009
- str.includes('%') ||
2010
- str.includes('@') ||
2011
- str.includes('`') ||
2012
- str.startsWith(' ') ||
2013
- str.endsWith(' ') ||
2014
- str === 'true' ||
2015
- str === 'false' ||
2016
- str === 'null' ||
2017
- str === 'yes' ||
2018
- str === 'no' ||
2019
- str === 'on' ||
2020
- str === 'off' ||
2021
- (!isNaN(Number(str)) && str !== '')
2022
-
2023
- if (str.includes('\n')) {
2024
- const blockIndent = ' '.repeat(indent + 1)
2025
- return (
2026
- '|\n' +
2027
- str
2028
- .split('\n')
2029
- .map((l) => blockIndent + l)
2030
- .join('\n')
2031
- )
2032
- }
2033
-
2034
- if (needsQuote) {
2035
- return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
2036
- }
2037
-
2038
- return str
2039
- }
2040
-
2041
- function yamlEscapeKey(key: string): string {
2042
- if (key === '') return "''"
2043
-
2044
- const needsQuote =
2045
- key === '~' ||
2046
- key === '.inf' ||
2047
- key === '-.inf' ||
2048
- key === '.nan' ||
2049
- key === '-' ||
2050
- key === '?' ||
2051
- key.startsWith('- ') ||
2052
- key.startsWith('? ') ||
2053
- /^[!&*|>@`]/.test(key) ||
2054
- key.includes(':') ||
2055
- key.includes('#') ||
2056
- key.includes('{') ||
2057
- key.includes('}') ||
2058
- key.includes('[') ||
2059
- key.includes(']') ||
2060
- key.includes(',') ||
2061
- key.includes('&') ||
2062
- key.includes('*') ||
2063
- key.includes('!') ||
2064
- key.includes('|') ||
2065
- key.includes('>') ||
2066
- key.includes("'") ||
2067
- key.includes('"') ||
2068
- key.includes('%') ||
2069
- key.includes('@') ||
2070
- key.includes('`') ||
2071
- key.includes(' ') ||
2072
- key === 'true' ||
2073
- key === 'false' ||
2074
- key === 'null' ||
2075
- key === 'yes' ||
2076
- key === 'no' ||
2077
- key === 'on' ||
2078
- key === 'off' ||
2079
- (!isNaN(Number(key)) && key !== '')
2080
-
2081
- if (needsQuote) {
2082
- return '"' + key.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
2083
- }
2084
-
2085
- return key
2086
- }
2087
-
2088
- function toYaml(obj: any, indent = 0): string {
2089
- const spaces = ' '.repeat(indent)
2090
- let yaml = ''
2091
-
2092
- if (Array.isArray(obj)) {
2093
- if (obj.length === 0) return `${spaces}[]\n`
2094
- for (const item of obj) {
2095
- if (typeof item === 'object' && item !== null) {
2096
- const inner = toYaml(item, indent + 1).trimStart()
2097
- yaml += `${spaces}- ${inner}`
2098
- } else {
2099
- yaml += `${spaces}- ${yamlEscapeValue(item, indent)}\n`
2100
- }
2101
- }
2102
- } else if (typeof obj === 'object' && obj !== null) {
2103
- if (Object.keys(obj).length === 0) return `${spaces}{}\n`
2104
- for (const [key, value] of Object.entries(obj)) {
2105
- if (value === undefined) continue
2106
- const safeKey = yamlEscapeKey(key)
2107
- if (typeof value === 'object' && value !== null) {
2108
- yaml += `${spaces}${safeKey}:\n${toYaml(value, indent + 1)}`
2109
- } else {
2110
- yaml += `${spaces}${safeKey}: ${yamlEscapeValue(value, indent)}\n`
2111
- }
2112
- }
2113
- }
2114
-
2115
- return yaml
2116
- }
1736
+ }