prisma-generator-express 1.57.0 → 1.58.1

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 (42) hide show
  1. package/README.md +111 -29
  2. package/dist/copy/misc.js +25 -6
  3. package/dist/copy/misc.js.map +1 -1
  4. package/dist/generators/generateFastifyHandler.js +11 -0
  5. package/dist/generators/generateFastifyHandler.js.map +1 -1
  6. package/dist/generators/generateHonoHandler.js +14 -20
  7. package/dist/generators/generateHonoHandler.js.map +1 -1
  8. package/dist/generators/generateImportPrismaStatement.js +7 -1
  9. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  10. package/dist/generators/generateOperationCore.js +58 -17
  11. package/dist/generators/generateOperationCore.js.map +1 -1
  12. package/dist/generators/generateRouteConfigType.js +12 -7
  13. package/dist/generators/generateRouteConfigType.js.map +1 -1
  14. package/dist/generators/generateRouter.js +57 -32
  15. package/dist/generators/generateRouter.js.map +1 -1
  16. package/dist/generators/generateRouterFastify.js +235 -191
  17. package/dist/generators/generateRouterFastify.js.map +1 -1
  18. package/dist/generators/generateRouterHono.d.ts +2 -3
  19. package/dist/generators/generateRouterHono.js +131 -89
  20. package/dist/generators/generateRouterHono.js.map +1 -1
  21. package/dist/index.js +8 -6
  22. package/dist/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/copy/autoIncludeRuntime.ts +9 -5
  25. package/src/copy/buildModelOpenApi.ts +96 -0
  26. package/src/copy/docsRenderer.ts +577 -174
  27. package/src/copy/materializedRouter.ts +40 -1
  28. package/src/copy/misc.ts +23 -6
  29. package/src/copy/operationDefinitions.ts +10 -0
  30. package/src/copy/operationRuntime.ts +28 -9
  31. package/src/copy/routeConfig.express.ts +9 -9
  32. package/src/copy/routeConfig.hono.ts +63 -5
  33. package/src/copy/routeConfig.ts +44 -22
  34. package/src/generators/generateFastifyHandler.ts +12 -0
  35. package/src/generators/generateHonoHandler.ts +15 -20
  36. package/src/generators/generateImportPrismaStatement.ts +10 -1
  37. package/src/generators/generateOperationCore.ts +58 -17
  38. package/src/generators/generateRouteConfigType.ts +13 -8
  39. package/src/generators/generateRouter.ts +57 -32
  40. package/src/generators/generateRouterFastify.ts +235 -191
  41. package/src/generators/generateRouterHono.ts +131 -91
  42. package/src/index.ts +11 -7
@@ -1,5 +1,9 @@
1
1
  import type { RouteConfig, WriteStrategy } from './routeConfig'
2
- import { OPERATION_DEFS, isOperationEnabled, READ_OPERATION_NAMES } from './operationDefinitions'
2
+ import {
3
+ OPERATION_DEFS,
4
+ isOperationEnabled,
5
+ READ_OPERATION_NAMES,
6
+ } from './operationDefinitions'
3
7
  import { getEnv, normalizePrefix } from './misc'
4
8
 
5
9
  const _env = getEnv()
@@ -53,13 +57,24 @@ interface OpDetail {
53
57
  const DEFAULT_SCALAR_CDN = 'https://cdn.jsdelivr.net/npm/@scalar/api-reference'
54
58
  const PRISM_CSS = 'https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css'
55
59
  const PRISM_JS = 'https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js'
56
- const PRISM_JSON = 'https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-json.min.js'
60
+ const PRISM_JSON =
61
+ 'https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-json.min.js'
57
62
 
58
63
  const OP_DETAIL_MAP: Record<string, OpDetail> = {
59
64
  findMany: {
60
65
  transport: 'GET query params',
61
66
  required: [],
62
- optional: ['where', 'select', 'include', 'omit', 'orderBy', 'cursor', 'take', 'skip', 'distinct'],
67
+ optional: [
68
+ 'where',
69
+ 'select',
70
+ 'include',
71
+ 'omit',
72
+ 'orderBy',
73
+ 'cursor',
74
+ 'take',
75
+ 'skip',
76
+ 'distinct',
77
+ ],
63
78
  responseDesc: 'Array of records',
64
79
  errors: [400, 403, 500, 501, 503],
65
80
  supportsSelect: true,
@@ -92,7 +107,17 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
92
107
  findFirst: {
93
108
  transport: 'GET query params',
94
109
  required: [],
95
- optional: ['where', 'select', 'include', 'omit', 'orderBy', 'cursor', 'take', 'skip', 'distinct'],
110
+ optional: [
111
+ 'where',
112
+ 'select',
113
+ 'include',
114
+ 'omit',
115
+ 'orderBy',
116
+ 'cursor',
117
+ 'take',
118
+ 'skip',
119
+ 'distinct',
120
+ ],
96
121
  responseDesc: 'Single record or null',
97
122
  errors: [400, 403, 500, 501, 503],
98
123
  supportsSelect: true,
@@ -103,7 +128,17 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
103
128
  findFirstOrThrow: {
104
129
  transport: 'GET query params',
105
130
  required: [],
106
- optional: ['where', 'select', 'include', 'omit', 'orderBy', 'cursor', 'take', 'skip', 'distinct'],
131
+ optional: [
132
+ 'where',
133
+ 'select',
134
+ 'include',
135
+ 'omit',
136
+ 'orderBy',
137
+ 'cursor',
138
+ 'take',
139
+ 'skip',
140
+ 'distinct',
141
+ ],
107
142
  responseDesc: 'Single record',
108
143
  errors: [400, 403, 404, 500, 501, 503],
109
144
  supportsSelect: true,
@@ -114,13 +149,24 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
114
149
  findManyPaginated: {
115
150
  transport: 'GET query params',
116
151
  required: [],
117
- optional: ['where', 'select', 'include', 'omit', 'orderBy', 'cursor', 'take', 'skip', 'distinct'],
152
+ optional: [
153
+ 'where',
154
+ 'select',
155
+ 'include',
156
+ 'omit',
157
+ 'orderBy',
158
+ 'cursor',
159
+ 'take',
160
+ 'skip',
161
+ 'distinct',
162
+ ],
118
163
  responseDesc: '{ data: Record[], total: number, hasMore: boolean }',
119
164
  errors: [400, 403, 409, 500, 501, 503],
120
165
  supportsSelect: true,
121
166
  supportsInclude: true,
122
167
  supportsOmit: true,
123
- notes: 'Wraps findMany with total count. hasMore is reliable for forward offset pagination (skip + take) only. Distinct count over 100k falls back to approximate total. 409 possible on transaction conflict.',
168
+ notes:
169
+ 'Wraps findMany with total count. hasMore is reliable for forward offset pagination (skip + take) only. Distinct count over 100k falls back to approximate total. 409 possible on transaction conflict.',
124
170
  },
125
171
  create: {
126
172
  transport: 'POST JSON body',
@@ -142,7 +188,8 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
142
188
  supportsSelect: false,
143
189
  supportsInclude: false,
144
190
  supportsOmit: false,
145
- notes: 'data is an array of scalar-only inputs. Nested relation writes are not supported. skipDuplicates silently ignores conflicts (not supported on all providers).',
191
+ notes:
192
+ 'data is an array of scalar-only inputs. Nested relation writes are not supported. skipDuplicates silently ignores conflicts (not supported on all providers).',
146
193
  },
147
194
  createManyAndReturn: {
148
195
  transport: 'POST JSON body',
@@ -153,7 +200,8 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
153
200
  supportsSelect: true,
154
201
  supportsInclude: true,
155
202
  supportsOmit: true,
156
- notes: 'Like createMany but returns created records. data items are scalar-only. Requires Prisma 5.14.0+, PostgreSQL/CockroachDB/SQLite only. The order of returned records is not guaranteed.',
203
+ notes:
204
+ 'Like createMany but returns created records. data items are scalar-only. Requires Prisma 5.14.0+, PostgreSQL/CockroachDB/SQLite only. The order of returned records is not guaranteed.',
157
205
  },
158
206
  update: {
159
207
  transport: 'PUT JSON body',
@@ -164,7 +212,21 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
164
212
  supportsSelect: true,
165
213
  supportsInclude: true,
166
214
  supportsOmit: true,
167
- notes: '404 when the record to update is not found. 409 on unique constraint violation or transaction conflict.',
215
+ notes:
216
+ '404 when the record to update is not found. 409 on unique constraint violation or transaction conflict.',
217
+ },
218
+ updateEach: {
219
+ transport: 'POST JSON body (array)',
220
+ required: ['array of { where, data }'],
221
+ optional: [],
222
+ responseDesc:
223
+ 'Non-atomic: per-row { status, data } / { status, error } array. Atomic: array of records.',
224
+ errors: [400, 403, 409, 500, 501, 503],
225
+ supportsSelect: false,
226
+ supportsInclude: false,
227
+ supportsOmit: false,
228
+ notes:
229
+ 'Internal batch endpoint. Bypasses guard shapes. Not enabled by enableAll. Non-atomic max 1000 items, atomic max 100 items. Header x-batch-atomic:true switches to transactional mode.',
168
230
  },
169
231
  updateMany: {
170
232
  transport: 'PUT JSON body',
@@ -175,7 +237,8 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
175
237
  supportsSelect: false,
176
238
  supportsInclude: false,
177
239
  supportsOmit: false,
178
- notes: 'Updates all matching records with scalar-only data. Nested relation writes are not supported. Returns count, not records. 409 on unique constraint violation.',
240
+ notes:
241
+ 'Updates all matching records with scalar-only data. Nested relation writes are not supported. Returns count, not records. 409 on unique constraint violation.',
179
242
  },
180
243
  updateManyAndReturn: {
181
244
  transport: 'PUT JSON body',
@@ -186,7 +249,8 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
186
249
  supportsSelect: true,
187
250
  supportsInclude: true,
188
251
  supportsOmit: true,
189
- notes: 'Like updateMany but returns updated records. data is scalar-only. Requires Prisma 6.2.0+, PostgreSQL/CockroachDB/SQLite only. 409 on unique constraint violation.',
252
+ notes:
253
+ 'Like updateMany but returns updated records. data is scalar-only. Requires Prisma 6.2.0+, PostgreSQL/CockroachDB/SQLite only. 409 on unique constraint violation.',
190
254
  },
191
255
  upsert: {
192
256
  transport: 'PATCH JSON body',
@@ -230,13 +294,26 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
230
294
  supportsSelect: false,
231
295
  supportsInclude: false,
232
296
  supportsOmit: false,
233
- notes: 'select here means count-specific field selection, not record field selection.',
297
+ notes:
298
+ 'select here means count-specific field selection, not record field selection.',
234
299
  },
235
300
  aggregate: {
236
301
  transport: 'GET query params',
237
302
  required: [],
238
- optional: ['where', 'orderBy', 'cursor', 'take', 'skip', '_count', '_avg', '_sum', '_min', '_max'],
239
- responseDesc: 'Object with requested aggregate fields (_count, _avg, _sum, _min, _max)',
303
+ optional: [
304
+ 'where',
305
+ 'orderBy',
306
+ 'cursor',
307
+ 'take',
308
+ 'skip',
309
+ '_count',
310
+ '_avg',
311
+ '_sum',
312
+ '_min',
313
+ '_max',
314
+ ],
315
+ responseDesc:
316
+ 'Object with requested aggregate fields (_count, _avg, _sum, _min, _max)',
240
317
  errors: [400, 403, 500, 501, 503],
241
318
  supportsSelect: false,
242
319
  supportsInclude: false,
@@ -246,13 +323,26 @@ const OP_DETAIL_MAP: Record<string, OpDetail> = {
246
323
  groupBy: {
247
324
  transport: 'GET query params',
248
325
  required: ['by'],
249
- optional: ['where', 'orderBy', 'having', 'take', 'skip', '_count', '_avg', '_sum', '_min', '_max'],
250
- responseDesc: 'Array of objects, each with grouped field values and requested aggregates',
326
+ optional: [
327
+ 'where',
328
+ 'orderBy',
329
+ 'having',
330
+ 'take',
331
+ 'skip',
332
+ '_count',
333
+ '_avg',
334
+ '_sum',
335
+ '_min',
336
+ '_max',
337
+ ],
338
+ responseDesc:
339
+ 'Array of objects, each with grouped field values and requested aggregates',
251
340
  errors: [400, 403, 500, 501, 503],
252
341
  supportsSelect: false,
253
342
  supportsInclude: false,
254
343
  supportsOmit: false,
255
- notes: 'by is a JSON-encoded array of scalar field names. orderBy is required when using skip or take. Response contains only the by-fields plus requested aggregates.',
344
+ notes:
345
+ 'by is a JSON-encoded array of scalar field names. orderBy is required when using skip or take. Response contains only the by-fields plus requested aggregates.',
256
346
  },
257
347
  }
258
348
 
@@ -268,7 +358,8 @@ function detailForOp(opName: string, writeStrategy?: WriteStrategy): OpDetail {
268
358
  supportsSelect: true,
269
359
  supportsInclude: true,
270
360
  supportsOmit: true,
271
- notes: 'writeStrategy="forceReturn": silently invokes createManyAndReturn and returns the created records instead of { count }.',
361
+ notes:
362
+ 'writeStrategy="forceReturn": silently invokes createManyAndReturn and returns the created records instead of { count }.',
272
363
  }
273
364
  }
274
365
  if (opName === 'updateMany') {
@@ -279,14 +370,18 @@ function detailForOp(opName: string, writeStrategy?: WriteStrategy): OpDetail {
279
370
  supportsSelect: true,
280
371
  supportsInclude: true,
281
372
  supportsOmit: true,
282
- notes: 'writeStrategy="forceReturn": silently invokes updateManyAndReturn and returns the updated records instead of { count }.',
373
+ notes:
374
+ 'writeStrategy="forceReturn": silently invokes updateManyAndReturn and returns the updated records instead of { count }.',
283
375
  }
284
376
  }
285
377
  }
286
378
  return base
287
379
  }
288
380
 
289
- function isOpHiddenByStrategy(opName: string, writeStrategy?: WriteStrategy): boolean {
381
+ function isOpHiddenByStrategy(
382
+ opName: string,
383
+ writeStrategy?: WriteStrategy,
384
+ ): boolean {
290
385
  if (writeStrategy !== 'throwOnNonReturning') return false
291
386
  return opName === 'createMany' || opName === 'updateMany'
292
387
  }
@@ -295,7 +390,9 @@ function exampleValue(ctx: DocsModelContext, fieldName: string): unknown {
295
390
  return ctx.exampleValues[fieldName] ?? 'example'
296
391
  }
297
392
 
298
- function compoundWhereExample(ctx: DocsModelContext): Record<string, any> | null {
393
+ function compoundWhereExample(
394
+ ctx: DocsModelContext,
395
+ ): Record<string, any> | null {
299
396
  if (ctx.compoundId) {
300
397
  const keyName = ctx.compoundId.fields.join('_')
301
398
  const val: Record<string, unknown> = {}
@@ -320,7 +417,11 @@ export function isOpenApiDisabled(disableOpenApi?: boolean): boolean {
320
417
 
321
418
  export function isPlaygroundAvailable(config: DocsConfig): boolean {
322
419
  if (config.queryBuilder === false) return false
323
- if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
420
+ if (
421
+ typeof config.queryBuilder === 'object' &&
422
+ config.queryBuilder.enabled === false
423
+ )
424
+ return false
324
425
  if (_env.NODE_ENV === 'production') return false
325
426
  return true
326
427
  }
@@ -338,7 +439,12 @@ function safeJsonForHtml(obj: unknown): string {
338
439
  return JSON.stringify(obj).replace(/</g, '\\u003c')
339
440
  }
340
441
 
341
- export function renderScalar(_modelName: string, spec: unknown, title: string, cdnUrl?: string): string {
442
+ export function renderScalar(
443
+ _modelName: string,
444
+ spec: unknown,
445
+ title: string,
446
+ cdnUrl?: string,
447
+ ): string {
342
448
  const scalarSrc = cdnUrl || DEFAULT_SCALAR_CDN
343
449
  return `<!DOCTYPE html>
344
450
  <html lang="en">
@@ -354,8 +460,14 @@ export function renderScalar(_modelName: string, spec: unknown, title: string, c
354
460
  </html>`
355
461
  }
356
462
 
357
- export function renderPlayground(modelName: string, config: DocsConfig): string {
358
- const qbConfig = (typeof config.queryBuilder === 'object' && config.queryBuilder) ? config.queryBuilder : {}
463
+ export function renderPlayground(
464
+ modelName: string,
465
+ config: DocsConfig,
466
+ ): string {
467
+ const qbConfig =
468
+ typeof config.queryBuilder === 'object' && config.queryBuilder
469
+ ? config.queryBuilder
470
+ : {}
359
471
  const host = qbConfig.host || 'localhost'
360
472
  const port = qbConfig.port || 5173
361
473
  const baseUrl = `http://${host}:${port}`
@@ -444,16 +556,33 @@ function isEnumField(f: FieldMeta): boolean {
444
556
  function scalarFilterOperators(scalarType: string): string[] {
445
557
  if (scalarType === 'String') {
446
558
  return [
447
- 'equals', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte',
448
- 'contains', 'startsWith', 'endsWith', 'mode', 'not',
559
+ 'equals',
560
+ 'in',
561
+ 'notIn',
562
+ 'lt',
563
+ 'lte',
564
+ 'gt',
565
+ 'gte',
566
+ 'contains',
567
+ 'startsWith',
568
+ 'endsWith',
569
+ 'mode',
570
+ 'not',
449
571
  ]
450
572
  }
451
- if (scalarType === 'Int' || scalarType === 'BigInt' || scalarType === 'Float' || scalarType === 'Decimal') {
573
+ if (
574
+ scalarType === 'Int' ||
575
+ scalarType === 'BigInt' ||
576
+ scalarType === 'Float' ||
577
+ scalarType === 'Decimal'
578
+ ) {
452
579
  return ['equals', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte', 'not']
453
580
  }
454
- if (scalarType === 'DateTime') return ['equals', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte', 'not']
581
+ if (scalarType === 'DateTime')
582
+ return ['equals', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte', 'not']
455
583
  if (scalarType === 'Boolean') return ['equals', 'not']
456
- if (scalarType === 'Json') return ['equals', 'path', 'string_contains', 'array_contains', 'not']
584
+ if (scalarType === 'Json')
585
+ return ['equals', 'path', 'string_contains', 'array_contains', 'not']
457
586
  if (scalarType === 'Bytes') return ['equals', 'in', 'notIn', 'not']
458
587
  return ['equals', 'in', 'notIn', 'not']
459
588
  }
@@ -471,8 +600,10 @@ function whereFieldKind(f: FieldMeta): string {
471
600
 
472
601
  function whereFieldShape(f: FieldMeta): string {
473
602
  const kind = whereFieldKind(f)
474
- if (kind === 'relation-list') return '{ some?: RelatedWhere, every?: RelatedWhere, none?: RelatedWhere }'
475
- if (kind === 'relation-single') return '{ is?: RelatedWhere, isNot?: RelatedWhere }'
603
+ if (kind === 'relation-list')
604
+ return '{ some?: RelatedWhere, every?: RelatedWhere, none?: RelatedWhere }'
605
+ if (kind === 'relation-single')
606
+ return '{ is?: RelatedWhere, isNot?: RelatedWhere }'
476
607
  if (kind === 'scalar-list') return '{ has?, hasEvery?, hasSome?, isEmpty? }'
477
608
  if (kind === 'scalar') return 'scalar value OR filter object'
478
609
  if (kind === 'enum') return 'enum value OR enum filter'
@@ -493,11 +624,19 @@ function describeFieldType(f: FieldMeta): string {
493
624
  }
494
625
 
495
626
  function jsonBlock(v: unknown): string {
496
- return '<pre class="my-2 rounded-xl !p-3 overflow-auto text-xs"><code class="language-json">' + escapeHtml(JSON.stringify(v, null, 2)) + '</code></pre>'
627
+ return (
628
+ '<pre class="my-2 rounded-xl !p-3 overflow-auto text-xs"><code class="language-json">' +
629
+ escapeHtml(JSON.stringify(v, null, 2)) +
630
+ '</code></pre>'
631
+ )
497
632
  }
498
633
 
499
634
  function codeBlock(text: string): string {
500
- return '<pre class="my-2 rounded-xl !p-3 overflow-auto text-xs"><code class="language-javascript">' + escapeHtml(text) + '</code></pre>'
635
+ return (
636
+ '<pre class="my-2 rounded-xl !p-3 overflow-auto text-xs"><code class="language-javascript">' +
637
+ escapeHtml(text) +
638
+ '</code></pre>'
639
+ )
501
640
  }
502
641
 
503
642
  function anchors(): { id: string; label: string }[] {
@@ -521,7 +660,8 @@ function anchors(): { id: string; label: string }[] {
521
660
  function buildExampleBasePath(modelName: string, config: DocsConfig): string {
522
661
  const prefixSource = config.specBasePath ?? config.customUrlPrefix ?? ''
523
662
  const prefix = normalizePrefix(prefixSource)
524
- const modelPrefix = config.addModelPrefix !== false ? '/' + modelName.toLowerCase() : ''
663
+ const modelPrefix =
664
+ config.addModelPrefix !== false ? '/' + modelName.toLowerCase() : ''
525
665
  return prefix + modelPrefix
526
666
  }
527
667
 
@@ -544,16 +684,21 @@ export function renderDocs(
544
684
 
545
685
  const postReadsEnabled = !config.disablePostReads
546
686
 
547
- const scalarFields = ctx.fields.filter((f) => isScalarField(f) || isEnumField(f))
687
+ const scalarFields = ctx.fields.filter(
688
+ (f) => isScalarField(f) || isEnumField(f),
689
+ )
548
690
  const relationFields = ctx.fields.filter((f) => isRelationField(f))
549
691
 
550
692
  const uniqueFields = ctx.fields.filter((f) => f.isId || f.isUnique)
551
- const requiredCreateFields = scalarFields.filter((sf) => sf.isRequired && !sf.hasDefaultValue && !sf.isUpdatedAt)
693
+ const requiredCreateFields = scalarFields.filter(
694
+ (sf) => sf.isRequired && !sf.hasDefaultValue && !sf.isUpdatedAt,
695
+ )
552
696
  const listRelations = relationFields.filter((f) => f.isList)
553
697
  const singleRelations = relationFields.filter((f) => !f.isList)
554
698
 
555
- const getOps = OPERATION_DEFS
556
- .filter((d) => isOperationEnabled(config as Record<string, any>, d))
699
+ const getOps = OPERATION_DEFS.filter((d) =>
700
+ isOperationEnabled(config as Record<string, any>, d),
701
+ )
557
702
  .filter((d) => !isOpHiddenByStrategy(d.name, writeStrategy))
558
703
  .map((d) => {
559
704
  const detail = detailForOp(d.name, writeStrategy)
@@ -561,7 +706,11 @@ export function renderDocs(
561
706
  op: d.name,
562
707
  method: d.method.toUpperCase(),
563
708
  path: buildFullPath(exampleBasePath, d.pathSuffix),
564
- transport: detail ? detail.transport : d.method === 'get' ? 'GET query params' : 'JSON body',
709
+ transport: detail
710
+ ? detail.transport
711
+ : d.method === 'get'
712
+ ? 'GET query params'
713
+ : 'JSON body',
565
714
  responseDesc: detail ? detail.responseDesc : '',
566
715
  errors: detail ? detail.errors.join(', ') : '',
567
716
  required: detail ? detail.required : [],
@@ -574,34 +723,40 @@ export function renderDocs(
574
723
  })
575
724
 
576
725
  const postReadOps = postReadsEnabled
577
- ? OPERATION_DEFS
578
- .filter((d) => READ_OPERATION_NAMES.has(d.name) && isOperationEnabled(config as Record<string, any>, d))
579
- .map((d) => {
580
- const detail = OP_DETAIL_MAP[d.name]
581
- const postPath = d.name === 'findMany'
726
+ ? OPERATION_DEFS.filter(
727
+ (d) =>
728
+ READ_OPERATION_NAMES.has(d.name) &&
729
+ isOperationEnabled(config as Record<string, any>, d),
730
+ ).map((d) => {
731
+ const detail = OP_DETAIL_MAP[d.name]
732
+ const postPath =
733
+ d.name === 'findMany'
582
734
  ? buildFullPath(exampleBasePath, '/read')
583
735
  : buildFullPath(exampleBasePath, d.pathSuffix)
584
- return {
585
- op: d.name + ' (POST)',
586
- method: 'POST',
587
- path: postPath,
588
- transport: 'POST JSON body',
589
- responseDesc: detail ? detail.responseDesc : '',
590
- errors: detail ? detail.errors.join(', ') : '',
591
- required: detail ? detail.required : [],
592
- optional: detail ? detail.optional : [],
593
- supportsSelect: detail ? detail.supportsSelect : false,
594
- supportsInclude: detail ? detail.supportsInclude : false,
595
- supportsOmit: detail ? detail.supportsOmit : false,
596
- notes: 'POST alternative for complex queries exceeding URL length limits. Same args as GET but in request body.',
597
- }
598
- })
736
+ return {
737
+ op: d.name + ' (POST)',
738
+ method: 'POST',
739
+ path: postPath,
740
+ transport: 'POST JSON body',
741
+ responseDesc: detail ? detail.responseDesc : '',
742
+ errors: detail ? detail.errors.join(', ') : '',
743
+ required: detail ? detail.required : [],
744
+ optional: detail ? detail.optional : [],
745
+ supportsSelect: detail ? detail.supportsSelect : false,
746
+ supportsInclude: detail ? detail.supportsInclude : false,
747
+ supportsOmit: detail ? detail.supportsOmit : false,
748
+ notes:
749
+ 'POST alternative for complex queries exceeding URL length limits. Same args as GET but in request body.',
750
+ }
751
+ })
599
752
  : []
600
753
 
601
754
  const ops = [...getOps, ...postReadOps]
602
755
 
603
756
  const firstUnique = uniqueFields[0]
604
- const firstUniqueExample = firstUnique ? exampleValue(ctx, firstUnique.name) : null
757
+ const firstUniqueExample = firstUnique
758
+ ? exampleValue(ctx, firstUnique.name)
759
+ : null
605
760
  const compoundWhere = !firstUnique ? compoundWhereExample(ctx) : null
606
761
  const uniqueWhereExample = firstUnique
607
762
  ? { [firstUnique.name]: firstUniqueExample }
@@ -609,8 +764,8 @@ export function renderDocs(
609
764
 
610
765
  const firstFilterFieldName = firstUnique
611
766
  ? firstUnique.name
612
- : (ctx.compoundId ? ctx.compoundId.fields[0] : null)
613
- || (ctx.compoundUniques.length > 0 ? ctx.compoundUniques[0].fields[0] : null)
767
+ : (ctx.compoundId ? ctx.compoundId.fields[0] : null) ||
768
+ (ctx.compoundUniques.length > 0 ? ctx.compoundUniques[0].fields[0] : null)
614
769
 
615
770
  const firstStringField = scalarFields.find((f) => f.type === 'String')
616
771
  const firstBooleanField = scalarFields.find((f) => f.type === 'Boolean')
@@ -618,13 +773,20 @@ export function renderDocs(
618
773
  const whereExample: Record<string, any> = {}
619
774
  const andClauses: Record<string, any>[] = []
620
775
  if (firstFilterFieldName) {
621
- andClauses.push({ [firstFilterFieldName]: { equals: exampleValue(ctx, firstFilterFieldName) } })
776
+ andClauses.push({
777
+ [firstFilterFieldName]: {
778
+ equals: exampleValue(ctx, firstFilterFieldName),
779
+ },
780
+ })
622
781
  }
623
782
  if (firstStringField) {
624
- andClauses.push({ [firstStringField.name]: { contains: 'example', mode: 'insensitive' } })
783
+ andClauses.push({
784
+ [firstStringField.name]: { contains: 'example', mode: 'insensitive' },
785
+ })
625
786
  }
626
787
  if (andClauses.length > 0) whereExample.AND = andClauses
627
- if (firstBooleanField) whereExample.OR = [{ [firstBooleanField.name]: { equals: true } }]
788
+ if (firstBooleanField)
789
+ whereExample.OR = [{ [firstBooleanField.name]: { equals: true } }]
628
790
 
629
791
  const selectExample: any = {}
630
792
  for (const f of scalarFields.slice(0, 10)) selectExample[f.name] = true
@@ -649,24 +811,38 @@ export function renderDocs(
649
811
 
650
812
  const findManyFetchExample =
651
813
  'import { encodeQueryParams } from "./client/encodeQueryParams"\n\n' +
652
- 'const params = encodeQueryParams(' + JSON.stringify(findManyQueryArgs, null, 2) + ')\n\n' +
653
- 'const res = await fetch(BASE_URL + "' + exampleBasePath + '?" + params)\n' +
814
+ 'const params = encodeQueryParams(' +
815
+ JSON.stringify(findManyQueryArgs, null, 2) +
816
+ ')\n\n' +
817
+ 'const res = await fetch(BASE_URL + "' +
818
+ exampleBasePath +
819
+ '?" + params)\n' +
654
820
  'const data = await res.json()'
655
821
 
656
822
  const findManyPostFetchExample =
657
- 'const res = await fetch(BASE_URL + "' + exampleBasePath + '/read", {\n' +
823
+ 'const res = await fetch(BASE_URL + "' +
824
+ exampleBasePath +
825
+ '/read", {\n' +
658
826
  ' method: "POST",\n' +
659
827
  ' headers: { "Content-Type": "application/json" },\n' +
660
- ' body: JSON.stringify(' + JSON.stringify(findManyQueryArgs, null, 2) + ')\n' +
828
+ ' body: JSON.stringify(' +
829
+ JSON.stringify(findManyQueryArgs, null, 2) +
830
+ ')\n' +
661
831
  '})\n' +
662
832
  'const data = await res.json()'
663
833
 
664
834
  const findUniqueFetchExample = uniqueWhereExample
665
835
  ? 'const params = encodeQueryParams({\n' +
666
- ' where: ' + JSON.stringify(uniqueWhereExample) + ',\n' +
667
- ' include: ' + JSON.stringify(includeExample) + '\n' +
836
+ ' where: ' +
837
+ JSON.stringify(uniqueWhereExample) +
838
+ ',\n' +
839
+ ' include: ' +
840
+ JSON.stringify(includeExample) +
841
+ '\n' +
668
842
  '})\n\n' +
669
- 'const res = await fetch(BASE_URL + "' + exampleBasePath + '/unique?" + params)\n' +
843
+ 'const res = await fetch(BASE_URL + "' +
844
+ exampleBasePath +
845
+ '/unique?" + params)\n' +
670
846
  'const data = await res.json()'
671
847
  : null
672
848
 
@@ -676,10 +852,14 @@ export function renderDocs(
676
852
  }
677
853
 
678
854
  const createFetchExample =
679
- 'const res = await fetch(BASE_URL + "' + exampleBasePath + '", {\n' +
855
+ 'const res = await fetch(BASE_URL + "' +
856
+ exampleBasePath +
857
+ '", {\n' +
680
858
  ' method: "POST",\n' +
681
859
  ' headers: { "Content-Type": "application/json" },\n' +
682
- ' body: JSON.stringify(' + JSON.stringify(createBodyExample, null, 2) + ')\n' +
860
+ ' body: JSON.stringify(' +
861
+ JSON.stringify(createBodyExample, null, 2) +
862
+ ')\n' +
683
863
  '})\n' +
684
864
  'const created = await res.json()'
685
865
 
@@ -687,25 +867,36 @@ export function renderDocs(
687
867
  ? { where: uniqueWhereExample, data: {} }
688
868
  : null
689
869
  if (updateBodyExample) {
690
- const firstEditableString = scalarFields.find((sf) => sf.type === 'String' && !sf.isId)
691
- if (firstEditableString) updateBodyExample.data[firstEditableString.name] = 'updated'
870
+ const firstEditableString = scalarFields.find(
871
+ (sf) => sf.type === 'String' && !sf.isId,
872
+ )
873
+ if (firstEditableString)
874
+ updateBodyExample.data[firstEditableString.name] = 'updated'
692
875
  }
693
876
 
694
877
  const updateFetchExample = updateBodyExample
695
- ? 'const res = await fetch(BASE_URL + "' + exampleBasePath + '", {\n' +
878
+ ? 'const res = await fetch(BASE_URL + "' +
879
+ exampleBasePath +
880
+ '", {\n' +
696
881
  ' method: "PUT",\n' +
697
882
  ' headers: { "Content-Type": "application/json" },\n' +
698
- ' body: JSON.stringify(' + JSON.stringify(updateBodyExample, null, 2) + ')\n' +
883
+ ' body: JSON.stringify(' +
884
+ JSON.stringify(updateBodyExample, null, 2) +
885
+ ')\n' +
699
886
  '})\n' +
700
887
  'const updated = await res.json()'
701
888
  : null
702
889
 
703
890
  const deleteFetchExample = uniqueWhereExample
704
- ? 'const res = await fetch(BASE_URL + "' + exampleBasePath + '", {\n' +
891
+ ? 'const res = await fetch(BASE_URL + "' +
892
+ exampleBasePath +
893
+ '", {\n' +
705
894
  ' method: "DELETE",\n' +
706
895
  ' headers: { "Content-Type": "application/json" },\n' +
707
896
  ' body: JSON.stringify({\n' +
708
- ' where: ' + JSON.stringify(uniqueWhereExample) + '\n' +
897
+ ' where: ' +
898
+ JSON.stringify(uniqueWhereExample) +
899
+ '\n' +
709
900
  ' })\n' +
710
901
  '})\n' +
711
902
  'const deleted = await res.json()'
@@ -715,10 +906,16 @@ export function renderDocs(
715
906
 
716
907
  const guardFetchExample = uniqueWhereExample
717
908
  ? 'const params = encodeQueryParams({\n' +
718
- ' where: ' + JSON.stringify(uniqueWhereExample) + '\n' +
909
+ ' where: ' +
910
+ JSON.stringify(uniqueWhereExample) +
911
+ '\n' +
719
912
  '})\n\n' +
720
- 'const res = await fetch(BASE_URL + "' + exampleBasePath + '/unique?" + params, {\n' +
721
- ' headers: { "' + guardVariantHeader + '": "admin" }\n' +
913
+ 'const res = await fetch(BASE_URL + "' +
914
+ exampleBasePath +
915
+ '/unique?" + params, {\n' +
916
+ ' headers: { "' +
917
+ guardVariantHeader +
918
+ '": "admin" }\n' +
722
919
  '})\n' +
723
920
  'const data = await res.json()'
724
921
  : null
@@ -726,7 +923,9 @@ export function renderDocs(
726
923
  const writeFieldRows = ctx.fields.map((f) => {
727
924
  let writeContract = ''
728
925
  if (isRelationField(f)) {
729
- writeContract = f.isList ? 'Nested list write object' : 'Nested single write object'
926
+ writeContract = f.isList
927
+ ? 'Nested list write object'
928
+ : 'Nested single write object'
730
929
  } else if (f.hasDefaultValue || f.isUpdatedAt) {
731
930
  writeContract = 'Optional on create (has default)'
732
931
  } else if (f.isRequired) {
@@ -789,7 +988,8 @@ export function renderDocs(
789
988
  groupBy: {
790
989
  by: 'ScalarFieldEnum[] (required)',
791
990
  where: 'WhereInput',
792
- orderBy: 'OrderByWithAggregationInput | OrderByWithAggregationInput[] (required when using skip or take)',
991
+ orderBy:
992
+ 'OrderByWithAggregationInput | OrderByWithAggregationInput[] (required when using skip or take)',
793
993
  having: 'ScalarWhereWithAggregatesInput',
794
994
  take: 'number',
795
995
  skip: 'number',
@@ -899,64 +1099,176 @@ export function renderDocs(
899
1099
  ]
900
1100
 
901
1101
  const errorRows = [
902
- { status: '400', description: 'Bad request', causes: 'Invalid JSON body, invalid query parameters, query validation failure, guard shape rejection, field value out of range, non-object request body.' },
903
- { status: '403', description: 'Forbidden', causes: 'Guard policy rejected the operation.' },
904
- { status: '404', description: 'Not found', causes: 'Record not found. Only from OrThrow operations, update, and delete.' },
905
- { status: '409', description: 'Conflict', causes: 'Unique constraint violation on create/update/upsert, or transaction conflict (e.g. in findManyPaginated).' },
906
- { status: '500', description: 'Internal server error', causes: 'Database error, table/column missing, raw query failure, unhandled error, or findManyPaginatedMode="transaction" without transaction support on the Prisma client.' },
907
- { status: '501', description: 'Not implemented', causes: 'Database provider does not support the requested feature, or writeStrategy disabled the endpoint.' },
908
- { status: '503', description: 'Service unavailable', causes: 'Database connection pool timeout.' },
1102
+ {
1103
+ status: '400',
1104
+ description: 'Bad request',
1105
+ causes:
1106
+ 'Invalid JSON body, invalid query parameters, query validation failure, guard shape rejection, field value out of range, non-object request body.',
1107
+ },
1108
+ {
1109
+ status: '403',
1110
+ description: 'Forbidden',
1111
+ causes: 'Guard policy rejected the operation.',
1112
+ },
1113
+ {
1114
+ status: '404',
1115
+ description: 'Not found',
1116
+ causes:
1117
+ 'Record not found. Only from OrThrow operations, update, and delete.',
1118
+ },
1119
+ {
1120
+ status: '409',
1121
+ description: 'Conflict',
1122
+ causes:
1123
+ 'Unique constraint violation on create/update/upsert, or transaction conflict (e.g. in findManyPaginated).',
1124
+ },
1125
+ {
1126
+ status: '500',
1127
+ description: 'Internal server error',
1128
+ causes:
1129
+ 'Database error, table/column missing, raw query failure, unhandled error, or findManyPaginatedMode="transaction" without transaction support on the Prisma client.',
1130
+ },
1131
+ {
1132
+ status: '501',
1133
+ description: 'Not implemented',
1134
+ causes:
1135
+ 'Database provider does not support the requested feature, or writeStrategy disabled the endpoint.',
1136
+ },
1137
+ {
1138
+ status: '503',
1139
+ description: 'Service unavailable',
1140
+ causes: 'Database connection pool timeout.',
1141
+ },
909
1142
  ]
910
1143
 
911
1144
  const hasPlayground = isPlaygroundAvailable(config)
912
1145
 
913
- const chipClass = 'inline-block border border-gray-200 bg-gray-50 rounded-full py-[5px] px-2.5 text-xs no-underline text-inherit hover:border-gray-400'
1146
+ const chipClass =
1147
+ 'inline-block border border-gray-200 bg-gray-50 rounded-full py-[5px] px-2.5 text-xs no-underline text-inherit hover:border-gray-400'
914
1148
 
915
1149
  const openApiLinks =
916
- '<a class="' + chipClass + ' !bg-gray-900 !text-white !border-gray-900" href="?ui=docs">Docs</a>' +
917
- '<a class="' + chipClass + '" href="?ui=scalar">Scalar</a>' +
918
- '<a class="' + chipClass + '" href="?ui=json">JSON</a>' +
919
- '<a class="' + chipClass + '" href="?ui=yaml">YAML</a>' +
920
- (hasPlayground ? '<a class="' + chipClass + '" href="?ui=playground">Playground</a>' : '')
921
-
922
- const writeStrategyBanner = !writeStrategy || writeStrategy === 'regular'
923
- ? ''
924
- : '<div class="mt-3 p-3 rounded-xl border border-amber-300 bg-amber-50 text-xs">' +
925
- '<strong>writeStrategy = ' + escapeHtml(writeStrategy) + '.</strong> ' +
926
- (writeStrategy === 'throwOnNonReturning'
927
- ? 'POST /many (createMany) and PUT /many (updateMany) are disabled and respond with 501. Use the /many/return variants.'
928
- : 'POST /many silently invokes createManyAndReturn; PUT /many silently invokes updateManyAndReturn. Both return arrays of records instead of { count } and accept select/include/omit.') +
929
- '</div>'
930
-
931
- const tocHtml = '<ol class="m-0 pl-[18px]">' + anchors().map((a) => '<li class="my-1.5"><a href="#' + escapeHtml(a.id) + '" class="text-inherit">' + escapeHtml(a.label) + '</a></li>').join('') + '</ol>'
932
-
933
- const whereRows = ctx.fields.map((f) => {
934
- const kind = whereFieldKind(f)
935
- const shape = whereFieldShape(f)
936
- const filterOps =
937
- kind === 'scalar'
938
- ? scalarFilterOperators(String(f.type)).map((x) => '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' + escapeHtml(x) + '</span>').join(' ')
939
- : kind === 'enum'
940
- ? ['equals', 'in', 'notIn', 'not'].map((x) => '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' + escapeHtml(x) + '</span>').join(' ')
941
- : kind === 'scalar-list'
942
- ? listFilterOperators().map((x) => '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' + escapeHtml(x) + '</span>').join(' ')
943
- : kind === 'relation-single'
944
- ? ['is', 'isNot'].map((x) => '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' + escapeHtml(x) + '</span>').join(' ')
945
- : kind === 'relation-list'
946
- ? ['some', 'every', 'none'].map((x) => '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' + escapeHtml(x) + '</span>').join(' ')
947
- : '<span class="text-gray-500">n/a</span>'
948
-
949
- const doc = f.documentation ? '<div class="text-gray-500 mt-1.5">' + escapeHtml(String(f.documentation)) + '</div>' : ''
950
-
951
- return (
952
- '<tr>' +
953
- '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' + escapeHtml(f.name) + '</td>' +
954
- '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' + escapeHtml(kind) + '</td>' +
955
- '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' + escapeHtml(describeFieldType(f)) + '</td>' +
956
- '<td class="text-left p-2 border-b border-gray-200 align-top"><div class="text-gray-500 font-mono">' + escapeHtml(shape) + '</div><div class="mt-2">' + filterOps + '</div>' + doc + '</td>' +
957
- '</tr>'
958
- )
959
- }).join('')
1150
+ '<a class="' +
1151
+ chipClass +
1152
+ ' !bg-gray-900 !text-white !border-gray-900" href="?ui=docs">Docs</a>' +
1153
+ '<a class="' +
1154
+ chipClass +
1155
+ '" href="?ui=scalar">Scalar</a>' +
1156
+ '<a class="' +
1157
+ chipClass +
1158
+ '" href="?ui=json">JSON</a>' +
1159
+ '<a class="' +
1160
+ chipClass +
1161
+ '" href="?ui=yaml">YAML</a>' +
1162
+ (hasPlayground
1163
+ ? '<a class="' + chipClass + '" href="?ui=playground">Playground</a>'
1164
+ : '')
1165
+
1166
+ const writeStrategyBanner =
1167
+ !writeStrategy || writeStrategy === 'regular'
1168
+ ? ''
1169
+ : '<div class="mt-3 p-3 rounded-xl border border-amber-300 bg-amber-50 text-xs">' +
1170
+ '<strong>writeStrategy = ' +
1171
+ escapeHtml(writeStrategy) +
1172
+ '.</strong> ' +
1173
+ (writeStrategy === 'throwOnNonReturning'
1174
+ ? 'POST /many (createMany) and PUT /many (updateMany) are disabled and respond with 501. Use the /many/return variants.'
1175
+ : 'POST /many silently invokes createManyAndReturn; PUT /many silently invokes updateManyAndReturn. Both return arrays of records instead of { count } and accept select/include/omit.') +
1176
+ '</div>'
1177
+
1178
+ const tocHtml =
1179
+ '<ol class="m-0 pl-[18px]">' +
1180
+ anchors()
1181
+ .map(
1182
+ (a) =>
1183
+ '<li class="my-1.5"><a href="#' +
1184
+ escapeHtml(a.id) +
1185
+ '" class="text-inherit">' +
1186
+ escapeHtml(a.label) +
1187
+ '</a></li>',
1188
+ )
1189
+ .join('') +
1190
+ '</ol>'
1191
+
1192
+ const whereRows = ctx.fields
1193
+ .map((f) => {
1194
+ const kind = whereFieldKind(f)
1195
+ const shape = whereFieldShape(f)
1196
+ const filterOps =
1197
+ kind === 'scalar'
1198
+ ? scalarFilterOperators(String(f.type))
1199
+ .map(
1200
+ (x) =>
1201
+ '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' +
1202
+ escapeHtml(x) +
1203
+ '</span>',
1204
+ )
1205
+ .join(' ')
1206
+ : kind === 'enum'
1207
+ ? ['equals', 'in', 'notIn', 'not']
1208
+ .map(
1209
+ (x) =>
1210
+ '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' +
1211
+ escapeHtml(x) +
1212
+ '</span>',
1213
+ )
1214
+ .join(' ')
1215
+ : kind === 'scalar-list'
1216
+ ? listFilterOperators()
1217
+ .map(
1218
+ (x) =>
1219
+ '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' +
1220
+ escapeHtml(x) +
1221
+ '</span>',
1222
+ )
1223
+ .join(' ')
1224
+ : kind === 'relation-single'
1225
+ ? ['is', 'isNot']
1226
+ .map(
1227
+ (x) =>
1228
+ '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' +
1229
+ escapeHtml(x) +
1230
+ '</span>',
1231
+ )
1232
+ .join(' ')
1233
+ : kind === 'relation-list'
1234
+ ? ['some', 'every', 'none']
1235
+ .map(
1236
+ (x) =>
1237
+ '<span class="inline-block border border-gray-200 bg-gray-50 rounded-full py-0.5 px-2 text-[11px] mr-1.5 mb-0.5 font-mono">' +
1238
+ escapeHtml(x) +
1239
+ '</span>',
1240
+ )
1241
+ .join(' ')
1242
+ : '<span class="text-gray-500">n/a</span>'
1243
+
1244
+ const doc = f.documentation
1245
+ ? '<div class="text-gray-500 mt-1.5">' +
1246
+ escapeHtml(String(f.documentation)) +
1247
+ '</div>'
1248
+ : ''
1249
+
1250
+ return (
1251
+ '<tr>' +
1252
+ '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' +
1253
+ escapeHtml(f.name) +
1254
+ '</td>' +
1255
+ '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' +
1256
+ escapeHtml(kind) +
1257
+ '</td>' +
1258
+ '<td class="text-left p-2 border-b border-gray-200 align-top font-mono">' +
1259
+ escapeHtml(describeFieldType(f)) +
1260
+ '</td>' +
1261
+ '<td class="text-left p-2 border-b border-gray-200 align-top"><div class="text-gray-500 font-mono">' +
1262
+ escapeHtml(shape) +
1263
+ '</div><div class="mt-2">' +
1264
+ filterOps +
1265
+ '</div>' +
1266
+ doc +
1267
+ '</td>' +
1268
+ '</tr>'
1269
+ )
1270
+ })
1271
+ .join('')
960
1272
 
961
1273
  const whereCoreShapes = {
962
1274
  where: {
@@ -993,40 +1305,92 @@ export function renderDocs(
993
1305
  ]
994
1306
 
995
1307
  const nestedWriteListOps = [
996
- { key: 'create', desc: 'Create new related records inline. Accepts a single object or array.' },
997
- { key: 'connect', desc: 'Connect existing records by unique identifier. Accepts a single object or array.' },
998
- { key: 'connectOrCreate', desc: 'Connect if exists, create if not. Each item: { where, create }.' },
999
- { key: 'createMany', desc: 'Bulk create related records. Shape: { data: [...], skipDuplicates?: boolean }.' },
1000
- { key: 'set', desc: 'Replace all connections. Provide an array of unique identifiers.' },
1001
- { key: 'disconnect', desc: 'Disconnect related records without deleting them.' },
1308
+ {
1309
+ key: 'create',
1310
+ desc: 'Create new related records inline. Accepts a single object or array.',
1311
+ },
1312
+ {
1313
+ key: 'connect',
1314
+ desc: 'Connect existing records by unique identifier. Accepts a single object or array.',
1315
+ },
1316
+ {
1317
+ key: 'connectOrCreate',
1318
+ desc: 'Connect if exists, create if not. Each item: { where, create }.',
1319
+ },
1320
+ {
1321
+ key: 'createMany',
1322
+ desc: 'Bulk create related records. Shape: { data: [...], skipDuplicates?: boolean }.',
1323
+ },
1324
+ {
1325
+ key: 'set',
1326
+ desc: 'Replace all connections. Provide an array of unique identifiers.',
1327
+ },
1328
+ {
1329
+ key: 'disconnect',
1330
+ desc: 'Disconnect related records without deleting them.',
1331
+ },
1002
1332
  { key: 'delete', desc: 'Delete related records by unique identifier.' },
1003
- { key: 'update', desc: 'Update related records. Each item: { where, data }.' },
1004
- { key: 'updateMany', desc: 'Bulk update related records matching a filter. Each item: { where, data }.' },
1005
- { key: 'deleteMany', desc: 'Bulk delete related records matching a filter.' },
1006
- { key: 'upsert', desc: 'Create or update related records. Each item: { where, create, update }.' },
1333
+ {
1334
+ key: 'update',
1335
+ desc: 'Update related records. Each item: { where, data }.',
1336
+ },
1337
+ {
1338
+ key: 'updateMany',
1339
+ desc: 'Bulk update related records matching a filter. Each item: { where, data }.',
1340
+ },
1341
+ {
1342
+ key: 'deleteMany',
1343
+ desc: 'Bulk delete related records matching a filter.',
1344
+ },
1345
+ {
1346
+ key: 'upsert',
1347
+ desc: 'Create or update related records. Each item: { where, create, update }.',
1348
+ },
1007
1349
  ]
1008
1350
 
1009
1351
  const nestedWriteSingleOps = [
1010
1352
  { key: 'create', desc: 'Create a new related record inline.' },
1011
- { key: 'connect', desc: 'Connect an existing record by unique identifier.' },
1012
- { key: 'connectOrCreate', desc: 'Connect if exists, create if not. Shape: { where, create }.' },
1013
- { key: 'disconnect', desc: 'Disconnect the related record (set relation to null). Pass true.' },
1353
+ {
1354
+ key: 'connect',
1355
+ desc: 'Connect an existing record by unique identifier.',
1356
+ },
1357
+ {
1358
+ key: 'connectOrCreate',
1359
+ desc: 'Connect if exists, create if not. Shape: { where, create }.',
1360
+ },
1361
+ {
1362
+ key: 'disconnect',
1363
+ desc: 'Disconnect the related record (set relation to null). Pass true.',
1364
+ },
1014
1365
  { key: 'delete', desc: 'Delete the related record. Pass true.' },
1015
- { key: 'update', desc: 'Update the related record inline with update input.' },
1016
- { key: 'upsert', desc: 'Create the related record if it does not exist, update if it does. Shape: { create, update }.' },
1366
+ {
1367
+ key: 'update',
1368
+ desc: 'Update the related record inline with update input.',
1369
+ },
1370
+ {
1371
+ key: 'upsert',
1372
+ desc: 'Create the related record if it does not exist, update if it does. Shape: { create, update }.',
1373
+ },
1017
1374
  ]
1018
1375
 
1019
1376
  const guardShapeInfo = [
1020
1377
  'When a guard shape is configured on an operation, prisma-guard validates and enforces allowed query patterns before the query reaches the database.',
1021
- 'Named shapes route to different guard configs based on a caller value resolved from the <span class="font-mono">' + escapeHtml(guardVariantHeader) + '</span> header or a custom resolver function.',
1378
+ 'Named shapes route to different guard configs based on a caller value resolved from the <span class="font-mono">' +
1379
+ escapeHtml(guardVariantHeader) +
1380
+ '</span> header or a custom resolver function.',
1022
1381
  'Forced values (literals instead of true) are injected server-side and cannot be overridden by the client.',
1023
1382
  ]
1024
1383
 
1025
- const writeStrategyNotes = !writeStrategy || writeStrategy === 'regular'
1026
- ? []
1027
- : writeStrategy === 'throwOnNonReturning'
1028
- ? ['<strong>writeStrategy="throwOnNonReturning":</strong> the createMany (POST /many) and updateMany (PUT /many) endpoints are disabled and return 501. Use the corresponding <span class="font-mono">/many/return</span> endpoints instead.']
1029
- : ['<strong>writeStrategy="forceReturn":</strong> the createMany (POST /many) and updateMany (PUT /many) endpoints silently invoke createManyAndReturn and updateManyAndReturn respectively. They return arrays of records instead of <span class="font-mono">{ count }</span>, and their request bodies accept <span class="font-mono">select</span>, <span class="font-mono">include</span>, and <span class="font-mono">omit</span>.']
1384
+ const writeStrategyNotes =
1385
+ !writeStrategy || writeStrategy === 'regular'
1386
+ ? []
1387
+ : writeStrategy === 'throwOnNonReturning'
1388
+ ? [
1389
+ '<strong>writeStrategy="throwOnNonReturning":</strong> the createMany (POST /many) and updateMany (PUT /many) endpoints are disabled and return 501. Use the corresponding <span class="font-mono">/many/return</span> endpoints instead.',
1390
+ ]
1391
+ : [
1392
+ '<strong>writeStrategy="forceReturn":</strong> the createMany (POST /many) and updateMany (PUT /many) endpoints silently invoke createManyAndReturn and updateManyAndReturn respectively. They return arrays of records instead of <span class="font-mono">{ count }</span>, and their request bodies accept <span class="font-mono">select</span>, <span class="font-mono">include</span>, and <span class="font-mono">omit</span>.',
1393
+ ]
1030
1394
 
1031
1395
  const runtimeNotes = [
1032
1396
  '<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.',
@@ -1046,7 +1410,8 @@ export function renderDocs(
1046
1410
  ...writeStrategyNotes,
1047
1411
  ]
1048
1412
 
1049
- const noUniqueFieldNote = '<div class="bg-gray-50 border border-gray-200 rounded-xl p-3 text-xs text-gray-500">This model has no unique or id fields suitable for a generated example. Use the unique constraint from your schema.</div>'
1413
+ const noUniqueFieldNote =
1414
+ '<div class="bg-gray-50 border border-gray-200 rounded-xl p-3 text-xs text-gray-500">This model has no unique or id fields suitable for a generated example. Use the unique constraint from your schema.</div>'
1050
1415
 
1051
1416
  const thClass = 'text-left p-2 border-b border-gray-200 align-top font-black'
1052
1417
  const tdClass = 'text-left p-2 border-b border-gray-200 align-top'
@@ -1101,7 +1466,9 @@ export function renderDocs(
1101
1466
  </tr>
1102
1467
  </thead>
1103
1468
  <tbody>
1104
- ${ops.map((o) => `
1469
+ ${ops
1470
+ .map(
1471
+ (o) => `
1105
1472
  <tr>
1106
1473
  <td class="${tdClass} font-mono">${escapeHtml(o.op)}</td>
1107
1474
  <td class="${tdClass} font-mono">${escapeHtml(o.method)}</td>
@@ -1112,7 +1479,9 @@ export function renderDocs(
1112
1479
  <td class="${tdClass} font-mono">${escapeHtml(o.errors)}</td>
1113
1480
  <td class="${tdClass} text-gray-500">${escapeHtml(o.notes)}</td>
1114
1481
  </tr>
1115
- `).join('')}
1482
+ `,
1483
+ )
1484
+ .join('')}
1116
1485
  </tbody>
1117
1486
  </table>
1118
1487
  </div>
@@ -1167,13 +1536,17 @@ export function renderDocs(
1167
1536
  </tr>
1168
1537
  </thead>
1169
1538
  <tbody>
1170
- ${writeFieldRows.map((r) => `
1539
+ ${writeFieldRows
1540
+ .map(
1541
+ (r) => `
1171
1542
  <tr>
1172
1543
  <td class="${tdClass} font-mono">${escapeHtml(r.name)}</td>
1173
1544
  <td class="${tdClass} font-mono">${escapeHtml(r.type)}</td>
1174
1545
  <td class="${tdClass}">${escapeHtml(r.writeContract)}</td>
1175
1546
  </tr>
1176
- `).join('')}
1547
+ `,
1548
+ )
1549
+ .join('')}
1177
1550
  </tbody>
1178
1551
  </table>
1179
1552
  </div>
@@ -1237,9 +1610,22 @@ export function renderDocs(
1237
1610
  <div>
1238
1611
  <h3 class="mt-3.5 mb-2 text-sm">7.1 List relation operations</h3>
1239
1612
  <div class="${calloutClass}">
1240
- ${listRelations.length > 0
1241
- ? '<div class="text-xs text-gray-500 mb-2">Relations: ' + listRelations.map((f) => '<span class="font-mono">' + escapeHtml(f.name) + '</span> (' + escapeHtml(String(f.type)) + '[])').join(', ') + '</div>'
1242
- : '<div class="text-xs text-gray-500 mb-2">This model has no list relations.</div>'}
1613
+ ${
1614
+ listRelations.length > 0
1615
+ ? '<div class="text-xs text-gray-500 mb-2">Relations: ' +
1616
+ listRelations
1617
+ .map(
1618
+ (f) =>
1619
+ '<span class="font-mono">' +
1620
+ escapeHtml(f.name) +
1621
+ '</span> (' +
1622
+ escapeHtml(String(f.type)) +
1623
+ '[])',
1624
+ )
1625
+ .join(', ') +
1626
+ '</div>'
1627
+ : '<div class="text-xs text-gray-500 mb-2">This model has no list relations.</div>'
1628
+ }
1243
1629
  <table class="w-full border-collapse text-xs">
1244
1630
  <tbody>
1245
1631
  ${nestedWriteListOps.map((op) => '<tr><td class="py-1.5 pr-2 align-top font-mono border-b border-gray-200">' + escapeHtml(op.key) + '</td><td class="py-1.5 border-b border-gray-200">' + escapeHtml(op.desc) + '</td></tr>').join('')}
@@ -1251,9 +1637,22 @@ export function renderDocs(
1251
1637
  <div>
1252
1638
  <h3 class="mt-3.5 mb-2 text-sm">7.2 Single relation operations</h3>
1253
1639
  <div class="${calloutClass}">
1254
- ${singleRelations.length > 0
1255
- ? '<div class="text-xs text-gray-500 mb-2">Relations: ' + singleRelations.map((f) => '<span class="font-mono">' + escapeHtml(f.name) + '</span> (' + escapeHtml(String(f.type)) + ')').join(', ') + '</div>'
1256
- : '<div class="text-xs text-gray-500 mb-2">This model has no single relations.</div>'}
1640
+ ${
1641
+ singleRelations.length > 0
1642
+ ? '<div class="text-xs text-gray-500 mb-2">Relations: ' +
1643
+ singleRelations
1644
+ .map(
1645
+ (f) =>
1646
+ '<span class="font-mono">' +
1647
+ escapeHtml(f.name) +
1648
+ '</span> (' +
1649
+ escapeHtml(String(f.type)) +
1650
+ ')',
1651
+ )
1652
+ .join(', ') +
1653
+ '</div>'
1654
+ : '<div class="text-xs text-gray-500 mb-2">This model has no single relations.</div>'
1655
+ }
1257
1656
  <table class="w-full border-collapse text-xs">
1258
1657
  <tbody>
1259
1658
  ${nestedWriteSingleOps.map((op) => '<tr><td class="py-1.5 pr-2 align-top font-mono border-b border-gray-200">' + escapeHtml(op.key) + '</td><td class="py-1.5 border-b border-gray-200">' + escapeHtml(op.desc) + '</td></tr>').join('')}
@@ -1287,13 +1686,17 @@ export function renderDocs(
1287
1686
  </tr>
1288
1687
  </thead>
1289
1688
  <tbody>
1290
- ${errorRows.map((r) => `
1689
+ ${errorRows
1690
+ .map(
1691
+ (r) => `
1291
1692
  <tr>
1292
1693
  <td class="${tdClass} font-mono">${escapeHtml(r.status)}</td>
1293
1694
  <td class="${tdClass}">${escapeHtml(r.description)}</td>
1294
1695
  <td class="${tdClass} text-gray-500">${escapeHtml(r.causes)}</td>
1295
1696
  </tr>
1296
- `).join('')}
1697
+ `,
1698
+ )
1699
+ .join('')}
1297
1700
  </tbody>
1298
1701
  </table>
1299
1702
 
@@ -1346,4 +1749,4 @@ export function renderDocs(
1346
1749
  </html>`
1347
1750
 
1348
1751
  return html
1349
- }
1752
+ }