prisma-generator-express 1.56.4 → 1.58.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.
- package/README.md +286 -29
- package/dist/copy/misc.js +25 -6
- package/dist/copy/misc.js.map +1 -1
- package/dist/generators/generateFastifyHandler.js +11 -0
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +14 -20
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.js +43 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateOperationCore.js +58 -17
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +44 -15
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +2 -1
- package/dist/generators/generateRouter.js +60 -34
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +2 -1
- package/dist/generators/generateRouterFastify.js +238 -193
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +2 -1
- package/dist/generators/generateRouterHono.js +124 -89
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/autoIncludeRuntime.ts +9 -5
- package/src/copy/buildModelOpenApi.ts +96 -0
- package/src/copy/docsRenderer.ts +577 -174
- package/src/copy/materializedRouter.ts +40 -1
- package/src/copy/misc.ts +23 -6
- package/src/copy/operationDefinitions.ts +10 -0
- package/src/copy/operationRuntime.ts +28 -9
- package/src/copy/routeConfig.express.ts +9 -9
- package/src/copy/routeConfig.hono.ts +63 -5
- package/src/copy/routeConfig.ts +44 -20
- package/src/generators/generateFastifyHandler.ts +12 -0
- package/src/generators/generateHonoHandler.ts +15 -20
- package/src/generators/generateImportPrismaStatement.ts +13 -0
- package/src/generators/generateOperationCore.ts +58 -17
- package/src/generators/generateRouteConfigType.ts +52 -17
- package/src/generators/generateRouter.ts +61 -33
- package/src/generators/generateRouterFastify.ts +239 -192
- package/src/generators/generateRouterHono.ts +125 -88
- package/src/index.ts +25 -5
package/src/copy/docsRenderer.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { RouteConfig, WriteStrategy } from './routeConfig'
|
|
2
|
-
import {
|
|
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 =
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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: [
|
|
239
|
-
|
|
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: [
|
|
250
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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(
|
|
358
|
-
|
|
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',
|
|
448
|
-
'
|
|
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 (
|
|
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')
|
|
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')
|
|
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')
|
|
475
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
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
|
-
|
|
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({
|
|
776
|
+
andClauses.push({
|
|
777
|
+
[firstFilterFieldName]: {
|
|
778
|
+
equals: exampleValue(ctx, firstFilterFieldName),
|
|
779
|
+
},
|
|
780
|
+
})
|
|
622
781
|
}
|
|
623
782
|
if (firstStringField) {
|
|
624
|
-
andClauses.push({
|
|
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)
|
|
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(' +
|
|
653
|
-
|
|
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 + "' +
|
|
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(' +
|
|
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: ' +
|
|
667
|
-
|
|
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 + "' +
|
|
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 + "' +
|
|
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(' +
|
|
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(
|
|
691
|
-
|
|
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 + "' +
|
|
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(' +
|
|
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 + "' +
|
|
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: ' +
|
|
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: ' +
|
|
909
|
+
' where: ' +
|
|
910
|
+
JSON.stringify(uniqueWhereExample) +
|
|
911
|
+
'\n' +
|
|
719
912
|
'})\n\n' +
|
|
720
|
-
'const res = await fetch(BASE_URL + "' +
|
|
721
|
-
|
|
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
|
|
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:
|
|
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
|
-
{
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
{
|
|
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 =
|
|
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="' +
|
|
917
|
-
|
|
918
|
-
'
|
|
919
|
-
'<a class="' +
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
'</
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
?
|
|
941
|
-
:
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
'
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
{
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
{
|
|
1001
|
-
|
|
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
|
-
{
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
{
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
-
{
|
|
1016
|
-
|
|
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">' +
|
|
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 =
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
1241
|
-
|
|
1242
|
-
|
|
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
|
-
${
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|