prisma-generator-express 1.66.0 → 1.68.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 +188 -3432
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +4 -1
- package/dist/copy/operationDefinitions.js +133 -9
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +68 -12
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +53 -6
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateHonoOpenApi.d.ts +13 -0
- package/dist/generators/generateHonoOpenApi.js +70 -0
- package/dist/generators/generateHonoOpenApi.js.map +1 -0
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateMcpApp.d.ts +10 -0
- package/dist/generators/generateMcpApp.js +113 -0
- package/dist/generators/generateMcpApp.js.map +1 -0
- package/dist/generators/generateModelMcp.d.ts +9 -0
- package/dist/generators/generateModelMcp.js +145 -0
- package/dist/generators/generateModelMcp.js.map +1 -0
- package/dist/generators/generateModelMetadata.js +29 -15
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +164 -65
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +106 -86
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +59 -414
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateRouterPartsHono.d.ts +14 -0
- package/dist/generators/generateRouterPartsHono.js +180 -0
- package/dist/generators/generateRouterPartsHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +42 -18
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +123 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +1 -1
- package/dist/utils/copyFiles.js +42 -7
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/mcpGate.d.ts +10 -0
- package/dist/utils/mcpGate.js +118 -0
- package/dist/utils/mcpGate.js.map +1 -0
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js +6 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +22 -5
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +229 -176
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/docsRenderer.ts +14 -15
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/mcpRuntime.ts +1017 -0
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +177 -25
- package/src/copy/operationPipeline.ts +267 -0
- package/src/copy/operationRuntime.ts +124 -26
- package/src/copy/operationSchemas.ts +2773 -0
- package/src/copy/pagination.ts +35 -13
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +46 -14
- package/src/copy/routeConfig.ts +175 -40
- package/src/copy/routerParts.ts +524 -0
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +77 -20
- package/src/generators/generateHonoHandler.ts +62 -14
- package/src/generators/generateHonoOpenApi.ts +93 -0
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateMcpApp.ts +133 -0
- package/src/generators/generateModelMcp.ts +208 -0
- package/src/generators/generateModelMetadata.ts +40 -22
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +171 -68
- package/src/generators/generateRouterFastify.ts +117 -89
- package/src/generators/generateRouterHono.ts +84 -464
- package/src/generators/generateRouterPartsHono.ts +220 -0
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +47 -24
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +161 -6
- package/src/utils/copyFiles.ts +235 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/mcpGate.ts +157 -0
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +12 -2
package/src/copy/misc.ts
CHANGED
|
@@ -1,68 +1,88 @@
|
|
|
1
|
-
export const isObject = (value: unknown): value is Record<string, unknown> => {
|
|
2
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function isPlainObject(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
if (result
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
export const isObject = (value: unknown): value is Record<string, unknown> => {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function isPlainObject(
|
|
6
|
+
value: unknown,
|
|
7
|
+
): value is Record<string, unknown> {
|
|
8
|
+
if (value === null || typeof value !== 'object') return false
|
|
9
|
+
if (Array.isArray(value)) return false
|
|
10
|
+
const proto = Object.getPrototypeOf(value)
|
|
11
|
+
return proto === Object.prototype || proto === null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
|
|
15
|
+
|
|
16
|
+
export function isSafeKey(key: string): boolean {
|
|
17
|
+
return !UNSAFE_KEYS.has(key)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function sanitizeKeys<T>(value: T): T {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
let mutated = false
|
|
23
|
+
const out: unknown[] = new Array(value.length)
|
|
24
|
+
for (let i = 0; i < value.length; i++) {
|
|
25
|
+
const sanitized = sanitizeKeys(value[i])
|
|
26
|
+
if (sanitized !== value[i]) mutated = true
|
|
27
|
+
out[i] = sanitized
|
|
28
|
+
}
|
|
29
|
+
return (mutated ? out : value) as T
|
|
30
|
+
}
|
|
31
|
+
if (isPlainObject(value)) {
|
|
32
|
+
const keys = Object.keys(value)
|
|
33
|
+
let hasUnsafe = false
|
|
34
|
+
let childrenMutated = false
|
|
35
|
+
const sanitizedChildren: Record<string, unknown> = {}
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (!isSafeKey(key)) {
|
|
38
|
+
hasUnsafe = true
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
const original = (value as Record<string, unknown>)[key]
|
|
42
|
+
const sanitized = sanitizeKeys(original)
|
|
43
|
+
if (sanitized !== original) childrenMutated = true
|
|
44
|
+
sanitizedChildren[key] = sanitized
|
|
45
|
+
}
|
|
46
|
+
if (!hasUnsafe && !childrenMutated) return value
|
|
47
|
+
return sanitizedChildren as T
|
|
48
|
+
}
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normalizePrefix(p: string): string {
|
|
53
|
+
if (!p) return ''
|
|
54
|
+
let result = p
|
|
55
|
+
if (!result.startsWith('/')) result = '/' + result
|
|
56
|
+
while (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1)
|
|
57
|
+
if (result === '/') return ''
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function removeTrailingSlash(path: string): string {
|
|
62
|
+
if (path === '/') return ''
|
|
63
|
+
return path.endsWith('/') ? path.slice(0, -1) : path
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getEnv(): Record<string, string | undefined> {
|
|
67
|
+
return typeof process !== 'undefined' && process.env
|
|
68
|
+
? process.env
|
|
69
|
+
: ({} as Record<string, string | undefined>)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let dropGuardDeprecationWarned = false
|
|
73
|
+
|
|
74
|
+
export function resolveDropGuardEnv(
|
|
75
|
+
env: Record<string, string | undefined>,
|
|
76
|
+
): boolean {
|
|
77
|
+
if (env.PGE_DROP_GUARD === 'true') return true
|
|
78
|
+
if (env.E2E === 'true') {
|
|
79
|
+
if (!dropGuardDeprecationWarned) {
|
|
80
|
+
dropGuardDeprecationWarned = true
|
|
81
|
+
console.warn(
|
|
82
|
+
'[prisma-generator-express] E2E=true guard bypass is deprecated. Use PGE_DROP_GUARD=true.',
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SseWritable } from './sse'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NDJSON chunked streaming for list reads.
|
|
5
|
+
*
|
|
6
|
+
* Contract: one JSON document per line; the final line is
|
|
7
|
+
* `{"done":true,"rows":N}`. Clients parse by splitting on newlines — there is
|
|
8
|
+
* no event vocabulary and no client-side merge algorithm. The same guard
|
|
9
|
+
* shapes, projection defaults, and pagination limits apply as on the JSON
|
|
10
|
+
* endpoint, because every page goes through the same generated core function.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function acceptsNdjson(accept: string | undefined): boolean {
|
|
14
|
+
if (!accept) return false
|
|
15
|
+
return accept
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.split(',')
|
|
18
|
+
.some((entry) => entry.split(';')[0].trim() === 'application/x-ndjson')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function startNdjson(res: SseWritable): void {
|
|
22
|
+
res.setHeader('Content-Type', 'application/x-ndjson')
|
|
23
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform')
|
|
24
|
+
res.setHeader('X-Stream-Protocol-Version', '1')
|
|
25
|
+
res.setHeader('X-Accel-Buffering', 'no')
|
|
26
|
+
res.statusCode = 200
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NdjsonStreamOptions {
|
|
30
|
+
res: SseWritable
|
|
31
|
+
isClosed: () => boolean
|
|
32
|
+
fetchPage: (skip: number, take: number) => Promise<unknown>
|
|
33
|
+
initialSkip: number
|
|
34
|
+
requestedTake?: number
|
|
35
|
+
defaultLimit?: number
|
|
36
|
+
maxLimit: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runNdjsonFindMany(
|
|
40
|
+
options: NdjsonStreamOptions,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
startNdjson(options.res)
|
|
43
|
+
const take = Math.min(
|
|
44
|
+
options.requestedTake ?? options.defaultLimit ?? options.maxLimit,
|
|
45
|
+
options.maxLimit,
|
|
46
|
+
)
|
|
47
|
+
let skip = options.initialSkip
|
|
48
|
+
let written = 0
|
|
49
|
+
|
|
50
|
+
while (
|
|
51
|
+
written < options.maxLimit &&
|
|
52
|
+
!options.isClosed() &&
|
|
53
|
+
!options.res.writableEnded
|
|
54
|
+
) {
|
|
55
|
+
const result = await options.fetchPage(skip, take)
|
|
56
|
+
if (!Array.isArray(result)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'NDJSON streaming requires an array result from the core read',
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
for (const row of result) {
|
|
62
|
+
if (written >= options.maxLimit) break
|
|
63
|
+
options.res.write(JSON.stringify(row) + '\n')
|
|
64
|
+
written++
|
|
65
|
+
}
|
|
66
|
+
if (result.length < take) break
|
|
67
|
+
skip += result.length
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
options.res.write(JSON.stringify({ done: true, rows: written }) + '\n')
|
|
71
|
+
options.res.end()
|
|
72
|
+
}
|
|
@@ -21,11 +21,25 @@ export interface OpMetadata {
|
|
|
21
21
|
argsSchema: readonly string[]
|
|
22
22
|
requiredArgs: readonly string[]
|
|
23
23
|
notes: string
|
|
24
|
+
/**
|
|
25
|
+
* EXPLICIT behaviour annotations, never inferred from `kind`. Mapped to MCP
|
|
26
|
+
* tool hints (readOnlyHint / destructiveHint / idempotentHint) and available
|
|
27
|
+
* for any other transport that wants the same vocabulary. MCP semantics:
|
|
28
|
+
* `destructive: false` means ONLY additive updates — anything that can
|
|
29
|
+
* overwrite or remove existing data (update, upsert, delete families) is
|
|
30
|
+
* destructive.
|
|
31
|
+
*/
|
|
32
|
+
readOnly: boolean
|
|
33
|
+
destructive: boolean
|
|
34
|
+
idempotent: boolean
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
27
38
|
{
|
|
28
39
|
name: 'findMany',
|
|
40
|
+
readOnly: true,
|
|
41
|
+
destructive: false,
|
|
42
|
+
idempotent: true,
|
|
29
43
|
method: 'get',
|
|
30
44
|
pathSuffix: '',
|
|
31
45
|
configKey: 'findMany',
|
|
@@ -40,12 +54,25 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
40
54
|
errors: [400, 403, 500, 501, 503],
|
|
41
55
|
responseDesc: 'Array of records',
|
|
42
56
|
transport: 'GET query params',
|
|
43
|
-
argsSchema: [
|
|
57
|
+
argsSchema: [
|
|
58
|
+
'where',
|
|
59
|
+
'select',
|
|
60
|
+
'include',
|
|
61
|
+
'omit',
|
|
62
|
+
'orderBy',
|
|
63
|
+
'cursor',
|
|
64
|
+
'take',
|
|
65
|
+
'skip',
|
|
66
|
+
'distinct',
|
|
67
|
+
],
|
|
44
68
|
requiredArgs: [],
|
|
45
69
|
notes: 'Pagination limits may apply when configured.',
|
|
46
70
|
},
|
|
47
71
|
{
|
|
48
72
|
name: 'findUnique',
|
|
73
|
+
readOnly: true,
|
|
74
|
+
destructive: false,
|
|
75
|
+
idempotent: true,
|
|
49
76
|
method: 'get',
|
|
50
77
|
pathSuffix: '/unique',
|
|
51
78
|
configKey: 'findUnique',
|
|
@@ -66,6 +93,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
66
93
|
},
|
|
67
94
|
{
|
|
68
95
|
name: 'findUniqueOrThrow',
|
|
96
|
+
readOnly: true,
|
|
97
|
+
destructive: false,
|
|
98
|
+
idempotent: true,
|
|
69
99
|
method: 'get',
|
|
70
100
|
pathSuffix: '/unique/strict',
|
|
71
101
|
configKey: 'findUniqueOrThrow',
|
|
@@ -86,6 +116,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
86
116
|
},
|
|
87
117
|
{
|
|
88
118
|
name: 'findFirst',
|
|
119
|
+
readOnly: true,
|
|
120
|
+
destructive: false,
|
|
121
|
+
idempotent: true,
|
|
89
122
|
method: 'get',
|
|
90
123
|
pathSuffix: '/first',
|
|
91
124
|
configKey: 'findFirst',
|
|
@@ -100,12 +133,25 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
100
133
|
errors: [400, 403, 500, 501, 503],
|
|
101
134
|
responseDesc: 'Single record or null',
|
|
102
135
|
transport: 'GET query params',
|
|
103
|
-
argsSchema: [
|
|
136
|
+
argsSchema: [
|
|
137
|
+
'where',
|
|
138
|
+
'select',
|
|
139
|
+
'include',
|
|
140
|
+
'omit',
|
|
141
|
+
'orderBy',
|
|
142
|
+
'cursor',
|
|
143
|
+
'take',
|
|
144
|
+
'skip',
|
|
145
|
+
'distinct',
|
|
146
|
+
],
|
|
104
147
|
requiredArgs: [],
|
|
105
148
|
notes: 'Returns null (not 404) when no record matches.',
|
|
106
149
|
},
|
|
107
150
|
{
|
|
108
151
|
name: 'findFirstOrThrow',
|
|
152
|
+
readOnly: true,
|
|
153
|
+
destructive: false,
|
|
154
|
+
idempotent: true,
|
|
109
155
|
method: 'get',
|
|
110
156
|
pathSuffix: '/first/strict',
|
|
111
157
|
configKey: 'findFirstOrThrow',
|
|
@@ -120,12 +166,25 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
120
166
|
errors: [400, 403, 404, 500, 501, 503],
|
|
121
167
|
responseDesc: 'Single record',
|
|
122
168
|
transport: 'GET query params',
|
|
123
|
-
argsSchema: [
|
|
169
|
+
argsSchema: [
|
|
170
|
+
'where',
|
|
171
|
+
'select',
|
|
172
|
+
'include',
|
|
173
|
+
'omit',
|
|
174
|
+
'orderBy',
|
|
175
|
+
'cursor',
|
|
176
|
+
'take',
|
|
177
|
+
'skip',
|
|
178
|
+
'distinct',
|
|
179
|
+
],
|
|
124
180
|
requiredArgs: [],
|
|
125
181
|
notes: 'Returns 404 when no record matches.',
|
|
126
182
|
},
|
|
127
183
|
{
|
|
128
184
|
name: 'findManyPaginated',
|
|
185
|
+
readOnly: true,
|
|
186
|
+
destructive: false,
|
|
187
|
+
idempotent: true,
|
|
129
188
|
method: 'get',
|
|
130
189
|
pathSuffix: '/paginated',
|
|
131
190
|
configKey: 'findManyPaginated',
|
|
@@ -140,12 +199,26 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
140
199
|
errors: [400, 403, 409, 500, 501, 503],
|
|
141
200
|
responseDesc: '{ data: Record[], total: number, hasMore: boolean }',
|
|
142
201
|
transport: 'GET query params',
|
|
143
|
-
argsSchema: [
|
|
202
|
+
argsSchema: [
|
|
203
|
+
'where',
|
|
204
|
+
'select',
|
|
205
|
+
'include',
|
|
206
|
+
'omit',
|
|
207
|
+
'orderBy',
|
|
208
|
+
'cursor',
|
|
209
|
+
'take',
|
|
210
|
+
'skip',
|
|
211
|
+
'distinct',
|
|
212
|
+
],
|
|
144
213
|
requiredArgs: [],
|
|
145
|
-
notes:
|
|
214
|
+
notes:
|
|
215
|
+
'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.',
|
|
146
216
|
},
|
|
147
217
|
{
|
|
148
218
|
name: 'count',
|
|
219
|
+
readOnly: true,
|
|
220
|
+
destructive: false,
|
|
221
|
+
idempotent: true,
|
|
149
222
|
method: 'get',
|
|
150
223
|
pathSuffix: '/count',
|
|
151
224
|
configKey: 'count',
|
|
@@ -162,10 +235,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
162
235
|
transport: 'GET query params',
|
|
163
236
|
argsSchema: ['where', 'orderBy', 'cursor', 'take', 'skip', 'select'],
|
|
164
237
|
requiredArgs: [],
|
|
165
|
-
notes:
|
|
238
|
+
notes:
|
|
239
|
+
'select here means count-specific field selection, not record field selection.',
|
|
166
240
|
},
|
|
167
241
|
{
|
|
168
242
|
name: 'aggregate',
|
|
243
|
+
readOnly: true,
|
|
244
|
+
destructive: false,
|
|
245
|
+
idempotent: true,
|
|
169
246
|
method: 'get',
|
|
170
247
|
pathSuffix: '/aggregate',
|
|
171
248
|
configKey: 'aggregate',
|
|
@@ -178,14 +255,29 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
178
255
|
excludeFromEnableAll: false,
|
|
179
256
|
writeStrategyAffected: false,
|
|
180
257
|
errors: [400, 403, 500, 501, 503],
|
|
181
|
-
responseDesc:
|
|
258
|
+
responseDesc:
|
|
259
|
+
'Object with requested aggregate fields (_count, _avg, _sum, _min, _max)',
|
|
182
260
|
transport: 'GET query params',
|
|
183
|
-
argsSchema: [
|
|
261
|
+
argsSchema: [
|
|
262
|
+
'where',
|
|
263
|
+
'orderBy',
|
|
264
|
+
'cursor',
|
|
265
|
+
'take',
|
|
266
|
+
'skip',
|
|
267
|
+
'_count',
|
|
268
|
+
'_avg',
|
|
269
|
+
'_sum',
|
|
270
|
+
'_min',
|
|
271
|
+
'_max',
|
|
272
|
+
],
|
|
184
273
|
requiredArgs: [],
|
|
185
274
|
notes: '_avg, _sum only apply to numeric fields.',
|
|
186
275
|
},
|
|
187
276
|
{
|
|
188
277
|
name: 'groupBy',
|
|
278
|
+
readOnly: true,
|
|
279
|
+
destructive: false,
|
|
280
|
+
idempotent: true,
|
|
189
281
|
method: 'get',
|
|
190
282
|
pathSuffix: '/groupby',
|
|
191
283
|
configKey: 'groupBy',
|
|
@@ -198,14 +290,31 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
198
290
|
excludeFromEnableAll: false,
|
|
199
291
|
writeStrategyAffected: false,
|
|
200
292
|
errors: [400, 403, 500, 501, 503],
|
|
201
|
-
responseDesc:
|
|
293
|
+
responseDesc:
|
|
294
|
+
'Array of objects, each with grouped field values and requested aggregates',
|
|
202
295
|
transport: 'GET query params',
|
|
203
|
-
argsSchema: [
|
|
296
|
+
argsSchema: [
|
|
297
|
+
'by',
|
|
298
|
+
'where',
|
|
299
|
+
'orderBy',
|
|
300
|
+
'having',
|
|
301
|
+
'take',
|
|
302
|
+
'skip',
|
|
303
|
+
'_count',
|
|
304
|
+
'_avg',
|
|
305
|
+
'_sum',
|
|
306
|
+
'_min',
|
|
307
|
+
'_max',
|
|
308
|
+
],
|
|
204
309
|
requiredArgs: ['by'],
|
|
205
|
-
notes:
|
|
310
|
+
notes:
|
|
311
|
+
'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.',
|
|
206
312
|
},
|
|
207
313
|
{
|
|
208
314
|
name: 'create',
|
|
315
|
+
readOnly: false,
|
|
316
|
+
destructive: false,
|
|
317
|
+
idempotent: false,
|
|
209
318
|
method: 'post',
|
|
210
319
|
pathSuffix: '',
|
|
211
320
|
configKey: 'create',
|
|
@@ -226,6 +335,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
226
335
|
},
|
|
227
336
|
{
|
|
228
337
|
name: 'createMany',
|
|
338
|
+
readOnly: false,
|
|
339
|
+
destructive: false,
|
|
340
|
+
idempotent: false,
|
|
229
341
|
method: 'post',
|
|
230
342
|
pathSuffix: '/many',
|
|
231
343
|
configKey: 'createMany',
|
|
@@ -242,10 +354,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
242
354
|
transport: 'POST JSON body',
|
|
243
355
|
argsSchema: ['data', 'skipDuplicates'],
|
|
244
356
|
requiredArgs: ['data'],
|
|
245
|
-
notes:
|
|
357
|
+
notes:
|
|
358
|
+
'data is an array of scalar-only inputs. Nested relation writes are not supported. skipDuplicates silently ignores conflicts (not supported on all providers).',
|
|
246
359
|
},
|
|
247
360
|
{
|
|
248
361
|
name: 'createManyAndReturn',
|
|
362
|
+
readOnly: false,
|
|
363
|
+
destructive: false,
|
|
364
|
+
idempotent: false,
|
|
249
365
|
method: 'post',
|
|
250
366
|
pathSuffix: '/many/return',
|
|
251
367
|
configKey: 'createManyAndReturn',
|
|
@@ -262,10 +378,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
262
378
|
transport: 'POST JSON body',
|
|
263
379
|
argsSchema: ['data', 'skipDuplicates', 'select', 'include', 'omit'],
|
|
264
380
|
requiredArgs: ['data'],
|
|
265
|
-
notes:
|
|
381
|
+
notes:
|
|
382
|
+
'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.',
|
|
266
383
|
},
|
|
267
384
|
{
|
|
268
385
|
name: 'update',
|
|
386
|
+
readOnly: false,
|
|
387
|
+
destructive: true,
|
|
388
|
+
idempotent: false,
|
|
269
389
|
method: 'put',
|
|
270
390
|
pathSuffix: '',
|
|
271
391
|
configKey: 'update',
|
|
@@ -282,10 +402,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
282
402
|
transport: 'PUT JSON body',
|
|
283
403
|
argsSchema: ['where', 'data', 'select', 'include', 'omit'],
|
|
284
404
|
requiredArgs: ['where', 'data'],
|
|
285
|
-
notes:
|
|
405
|
+
notes:
|
|
406
|
+
'404 when the record to update is not found. 409 on unique constraint violation or transaction conflict.',
|
|
286
407
|
},
|
|
287
408
|
{
|
|
288
409
|
name: 'updateMany',
|
|
410
|
+
readOnly: false,
|
|
411
|
+
destructive: true,
|
|
412
|
+
idempotent: false,
|
|
289
413
|
method: 'put',
|
|
290
414
|
pathSuffix: '/many',
|
|
291
415
|
configKey: 'updateMany',
|
|
@@ -302,10 +426,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
302
426
|
transport: 'PUT JSON body',
|
|
303
427
|
argsSchema: ['where', 'data'],
|
|
304
428
|
requiredArgs: ['where', 'data'],
|
|
305
|
-
notes:
|
|
429
|
+
notes:
|
|
430
|
+
'Updates all matching records with scalar-only data. Nested relation writes are not supported. Returns count, not records. 409 on unique constraint violation.',
|
|
306
431
|
},
|
|
307
432
|
{
|
|
308
433
|
name: 'updateManyAndReturn',
|
|
434
|
+
readOnly: false,
|
|
435
|
+
destructive: true,
|
|
436
|
+
idempotent: false,
|
|
309
437
|
method: 'put',
|
|
310
438
|
pathSuffix: '/many/return',
|
|
311
439
|
configKey: 'updateManyAndReturn',
|
|
@@ -322,10 +450,14 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
322
450
|
transport: 'PUT JSON body',
|
|
323
451
|
argsSchema: ['where', 'data', 'select', 'include', 'omit'],
|
|
324
452
|
requiredArgs: ['where', 'data'],
|
|
325
|
-
notes:
|
|
453
|
+
notes:
|
|
454
|
+
'Like updateMany but returns updated records. data is scalar-only. Requires Prisma 6.2.0+, PostgreSQL/CockroachDB/SQLite only. 409 on unique constraint violation.',
|
|
326
455
|
},
|
|
327
456
|
{
|
|
328
457
|
name: 'upsert',
|
|
458
|
+
readOnly: false,
|
|
459
|
+
destructive: true,
|
|
460
|
+
idempotent: true,
|
|
329
461
|
method: 'patch',
|
|
330
462
|
pathSuffix: '',
|
|
331
463
|
configKey: 'upsert',
|
|
@@ -346,6 +478,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
346
478
|
},
|
|
347
479
|
{
|
|
348
480
|
name: 'delete',
|
|
481
|
+
readOnly: false,
|
|
482
|
+
destructive: true,
|
|
483
|
+
idempotent: true,
|
|
349
484
|
method: 'delete',
|
|
350
485
|
pathSuffix: '',
|
|
351
486
|
configKey: 'delete',
|
|
@@ -366,6 +501,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
366
501
|
},
|
|
367
502
|
{
|
|
368
503
|
name: 'deleteMany',
|
|
504
|
+
readOnly: false,
|
|
505
|
+
destructive: true,
|
|
506
|
+
idempotent: true,
|
|
369
507
|
method: 'delete',
|
|
370
508
|
pathSuffix: '/many',
|
|
371
509
|
configKey: 'deleteMany',
|
|
@@ -386,6 +524,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
386
524
|
},
|
|
387
525
|
{
|
|
388
526
|
name: 'updateEach',
|
|
527
|
+
readOnly: false,
|
|
528
|
+
destructive: true,
|
|
529
|
+
idempotent: false,
|
|
389
530
|
method: 'post',
|
|
390
531
|
pathSuffix: '/each',
|
|
391
532
|
configKey: 'updateEach',
|
|
@@ -398,11 +539,13 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
|
|
|
398
539
|
excludeFromEnableAll: true,
|
|
399
540
|
writeStrategyAffected: false,
|
|
400
541
|
errors: [400, 403, 409, 500, 501, 503],
|
|
401
|
-
responseDesc:
|
|
542
|
+
responseDesc:
|
|
543
|
+
'Non-atomic: per-row { status, data } / { status, error } array. Atomic: array of records.',
|
|
402
544
|
transport: 'POST JSON body (array)',
|
|
403
545
|
argsSchema: [],
|
|
404
546
|
requiredArgs: ['array of { where, data }'],
|
|
405
|
-
notes:
|
|
547
|
+
notes:
|
|
548
|
+
'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.',
|
|
406
549
|
},
|
|
407
550
|
]
|
|
408
551
|
|
|
@@ -410,9 +553,12 @@ export const OPERATION_BY_NAME: Record<string, OpMetadata> = Object.fromEntries(
|
|
|
410
553
|
OPERATION_METADATA.map((m) => [m.name, m]),
|
|
411
554
|
)
|
|
412
555
|
|
|
413
|
-
export const READ_OPERATIONS: readonly OpMetadata[] = OPERATION_METADATA.filter(
|
|
556
|
+
export const READ_OPERATIONS: readonly OpMetadata[] = OPERATION_METADATA.filter(
|
|
557
|
+
(m) => m.kind === 'read',
|
|
558
|
+
)
|
|
414
559
|
|
|
415
|
-
export const WRITE_OPERATIONS: readonly OpMetadata[] =
|
|
560
|
+
export const WRITE_OPERATIONS: readonly OpMetadata[] =
|
|
561
|
+
OPERATION_METADATA.filter((m) => m.kind === 'write' || m.kind === 'batch')
|
|
416
562
|
|
|
417
563
|
export const READ_OPERATION_NAMES = new Set(READ_OPERATIONS.map((m) => m.name))
|
|
418
564
|
|
|
@@ -439,10 +585,16 @@ export function getPostReadPathSuffix(opName: string): string {
|
|
|
439
585
|
}
|
|
440
586
|
|
|
441
587
|
export function isOperationEnabled(
|
|
442
|
-
config:
|
|
588
|
+
config: object,
|
|
443
589
|
def: OperationDef | OpMetadata,
|
|
444
590
|
): boolean {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
591
|
+
// Route configs are interfaces without index signatures, so the value is
|
|
592
|
+
// reached through one runtime narrowing here at the single point that reads it.
|
|
593
|
+
if (!config || typeof config !== 'object') {
|
|
594
|
+
return false
|
|
595
|
+
}
|
|
596
|
+
const cfg = config as Record<string, unknown>
|
|
597
|
+
if (cfg[def.configKey] === false) return false
|
|
598
|
+
if (def.excludeFromEnableAll) return !!cfg[def.configKey]
|
|
599
|
+
return !!(cfg.enableAll || cfg[def.configKey])
|
|
600
|
+
}
|