prisma-generator-express 1.65.2 → 1.67.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 +51 -3480
- 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.js +69 -6
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +15 -5
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +12 -5
- package/dist/generators/generateHonoHandler.js.map +1 -1
- 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/generateModelMetadata.js +20 -10
- 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/generateQueryBuilderHelper.d.ts +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +2 -4
- package/dist/generators/generateQueryBuilderHelper.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 +107 -14
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +56 -28
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +84 -53
- package/dist/generators/generateRouterHono.js.map +1 -1
- 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 +11 -3
- 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 +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +22 -6
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- 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.map +1 -1
- package/package.json +16 -3
- 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 +153 -50
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +99 -21
- package/src/copy/operationRuntime.ts +100 -12
- package/src/copy/pagination.ts +25 -11
- 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 +19 -9
- package/src/copy/routeConfig.ts +174 -39
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +24 -13
- package/src/generators/generateHonoHandler.ts +21 -13
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateModelMetadata.ts +24 -12
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateQueryBuilderHelper.ts +20 -4
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +114 -17
- package/src/generators/generateRouterFastify.ts +67 -31
- package/src/generators/generateRouterHono.ts +108 -60
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +16 -8
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +46 -4
- package/src/utils/copyFiles.ts +199 -168
- package/src/utils/importExt.ts +2 -2
- 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 +6 -2
|
@@ -32,13 +32,15 @@ function findOppositeField(
|
|
|
32
32
|
const target = models.find((m) => m.name === targetModelName)
|
|
33
33
|
if (!target) return null
|
|
34
34
|
const isSelfRelation = targetModelName === selfModelName
|
|
35
|
-
return
|
|
36
|
-
(
|
|
37
|
-
f
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
return (
|
|
36
|
+
target.fields.find(
|
|
37
|
+
(f) =>
|
|
38
|
+
f.kind === 'object' &&
|
|
39
|
+
f.relationName === relationName &&
|
|
40
|
+
f.type === selfModelName &&
|
|
41
|
+
!(isSelfRelation && f.name === selfFieldName),
|
|
42
|
+
) || null
|
|
43
|
+
)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function computeRelation(
|
|
@@ -61,7 +63,13 @@ function computeRelation(
|
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
const opposite = findOppositeField(
|
|
66
|
+
const opposite = findOppositeField(
|
|
67
|
+
models,
|
|
68
|
+
field.type,
|
|
69
|
+
field.relationName,
|
|
70
|
+
selfModelName,
|
|
71
|
+
field.name,
|
|
72
|
+
)
|
|
65
73
|
if (opposite) {
|
|
66
74
|
const oppFrom = (opposite.relationFromFields ?? []) as string[]
|
|
67
75
|
const oppTo = (opposite.relationToFields ?? []) as string[]
|
|
@@ -118,7 +126,9 @@ export interface GenerateRelationMetaOptions {
|
|
|
118
126
|
importStyle: ImportStyle
|
|
119
127
|
}
|
|
120
128
|
|
|
121
|
-
export function generateRelationMeta(
|
|
129
|
+
export function generateRelationMeta(
|
|
130
|
+
options: GenerateRelationMetaOptions,
|
|
131
|
+
): string {
|
|
122
132
|
const ext = importExt(options.importStyle)
|
|
123
133
|
const meta = buildModelMeta(options.model, options.allModels)
|
|
124
134
|
return `import type { ModelRelationMap } from '../autoIncludePlanner${ext}'
|
|
@@ -132,7 +142,9 @@ export interface GenerateRelationModelsIndexOptions {
|
|
|
132
142
|
importStyle: ImportStyle
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
export function generateRelationModelsIndex(
|
|
145
|
+
export function generateRelationModelsIndex(
|
|
146
|
+
options: GenerateRelationModelsIndexOptions,
|
|
147
|
+
): string {
|
|
136
148
|
const ext = importExt(options.importStyle)
|
|
137
149
|
const imports = options.modelNames
|
|
138
150
|
.map((n) => `import { ${n}Relations } from './${n}/${n}Relations${ext}'`)
|
|
@@ -147,4 +159,4 @@ export const relationModels: Record<string, ModelRelationMap> = {
|
|
|
147
159
|
${entries}
|
|
148
160
|
}
|
|
149
161
|
`
|
|
150
|
-
}
|
|
162
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
1
|
+
import type { ImportStyle } from '../utils/resolveImportStyle'
|
|
2
2
|
import { importExt } from '../utils/importExt'
|
|
3
3
|
import type { Target } from '../constants'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
OPERATION_METADATA,
|
|
6
|
+
READ_OPERATION_NAMES,
|
|
7
|
+
} from '../copy/operationDefinitions'
|
|
5
8
|
|
|
6
|
-
const ROUTER_OPERATIONS = OPERATION_METADATA
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
const ROUTER_OPERATIONS = OPERATION_METADATA.filter(
|
|
10
|
+
(m) => m.name !== 'updateEach',
|
|
11
|
+
).map((m) => m.name)
|
|
9
12
|
|
|
10
13
|
function capitalize(str: string): string {
|
|
11
14
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
@@ -13,15 +16,15 @@ function capitalize(str: string): string {
|
|
|
13
16
|
|
|
14
17
|
function requestTypeFor(target: Target): string {
|
|
15
18
|
if (target === 'fastify') return `import('fastify').FastifyRequest`
|
|
16
|
-
if (target === 'hono') return `import('hono').Context<TEnv
|
|
19
|
+
if (target === 'hono') return `import('hono').Context<GeneratedHonoEnv<TEnv>>`
|
|
17
20
|
return `import('express').Request`
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
function configGenericsFor(target: Target): string {
|
|
21
24
|
if (target === 'hono') {
|
|
22
|
-
return `<TCtx = unknown, TPrisma =
|
|
25
|
+
return `<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends { Variables: Record<string, unknown> } = { Variables: Record<string, unknown> }>`
|
|
23
26
|
}
|
|
24
|
-
return `<TCtx = unknown, TPrisma =
|
|
27
|
+
return `<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>`
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
function routeConfigBaseFor(target: Target): string {
|
|
@@ -50,6 +53,7 @@ export function generateRouteConfigType(
|
|
|
50
53
|
guardShapesImport: string | null,
|
|
51
54
|
importStyle: ImportStyle,
|
|
52
55
|
target: Target,
|
|
56
|
+
clientImport?: string,
|
|
53
57
|
): string {
|
|
54
58
|
const ext = importExt(importStyle)
|
|
55
59
|
const m = modelName
|
|
@@ -61,14 +65,33 @@ export function generateRouteConfigType(
|
|
|
61
65
|
const afterRef = afterHookRef(target, hookHandlerType)
|
|
62
66
|
const requestType = requestTypeFor(target)
|
|
63
67
|
|
|
64
|
-
const
|
|
68
|
+
const clientPath = clientImport ? `${clientImport}${ext}` : '@prisma/client'
|
|
69
|
+
const delegate = `${m.charAt(0).toLowerCase() + m.slice(1)}`
|
|
70
|
+
const modelDelegate = clientImport
|
|
71
|
+
? `PrismaClient['${delegate}']`
|
|
72
|
+
: `(TPrisma extends Record<'${delegate}', infer D> ? D : never)`
|
|
73
|
+
const overrideImports = `import type { Prisma${clientImport ? ', PrismaClient' : ''} } from '${clientPath}'\nimport type { OperationOverride } from '../operationRuntime${ext}'\n`
|
|
74
|
+
const overrideType = (op: string) => {
|
|
75
|
+
const method = op === 'findManyPaginated' ? 'findMany' : op
|
|
76
|
+
const args = `Prisma.Args<${modelDelegate}, '${method}'>`
|
|
77
|
+
const result = `Prisma.Result<${modelDelegate}, ${args}, '${method}'>`
|
|
78
|
+
const output =
|
|
79
|
+
op === 'findManyPaginated'
|
|
80
|
+
? `{ data: ${result}; total: number; hasMore: boolean }`
|
|
81
|
+
: result
|
|
82
|
+
const methods =
|
|
83
|
+
op === 'findManyPaginated' ? "'findMany' | 'count'" : `'${method}'`
|
|
84
|
+
return `OperationOverride<${args}, ${output}, TCtx, Readonly<{ ${delegate}: Pick<${modelDelegate}, Extract<keyof ${modelDelegate}, ${methods}>> }>>`
|
|
85
|
+
}
|
|
86
|
+
const typeImports = supportsProgressive
|
|
65
87
|
? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n`
|
|
66
88
|
: ''
|
|
67
89
|
|
|
68
90
|
if (!guardShapesImport) {
|
|
69
91
|
return (
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
overrideImports +
|
|
93
|
+
typeImports +
|
|
94
|
+
`export type ${m}RouteConfig${generics} = Omit<${baseConfig}, ${ROUTER_OPERATIONS.map((op) => `'${op}'`).join(' | ')}> & {\n${ROUTER_OPERATIONS.map((op) => ` ${op}?: (Omit<Exclude<${baseConfig}['${op}'], false | undefined>, 'override'> & { override?: ${overrideType(op)} }) | false`).join('\n')}\n}\n`
|
|
72
95
|
)
|
|
73
96
|
}
|
|
74
97
|
|
|
@@ -95,6 +118,8 @@ export function generateRouteConfigType(
|
|
|
95
118
|
const c = capitalize(routerOp)
|
|
96
119
|
const isRead = READ_OPERATION_NAMES.has(routerOp)
|
|
97
120
|
const commonLines = [
|
|
121
|
+
` override?: ${overrideType(routerOp)}`,
|
|
122
|
+
` authorize?: ${beforeRef}`,
|
|
98
123
|
` before?: ${beforeRef}[]`,
|
|
99
124
|
` after?: ${afterRef}[]`,
|
|
100
125
|
` pagination?: Partial<PaginationConfig>`,
|
|
@@ -133,7 +158,8 @@ export function generateRouteConfigType(
|
|
|
133
158
|
const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
|
|
134
159
|
|
|
135
160
|
return (
|
|
136
|
-
|
|
161
|
+
overrideImports +
|
|
162
|
+
typeImports +
|
|
137
163
|
`import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
|
|
138
164
|
`${shapeOrFnAliases}\n\n` +
|
|
139
165
|
`export type ${m}RouteConfig${generics} = Omit<\n` +
|
|
@@ -144,4 +170,4 @@ export function generateRouteConfigType(
|
|
|
144
170
|
` resolveContext?: (request: ${requestType}) => TCtx | Promise<TCtx>\n` +
|
|
145
171
|
`${overrides}\n}\n`
|
|
146
172
|
)
|
|
147
|
-
}
|
|
173
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DMMF } from '@prisma/generator-helper'
|
|
1
|
+
import type { DMMF } from '@prisma/generator-helper'
|
|
2
2
|
import { generateRouteConfigType } from './generateRouteConfigType'
|
|
3
|
-
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
3
|
+
import type { ImportStyle } from '../utils/resolveImportStyle'
|
|
4
4
|
import { importExt } from '../utils/importExt'
|
|
5
|
-
import { WriteStrategy, FindManyPaginatedMode } from '../constants'
|
|
5
|
+
import type { WriteStrategy, FindManyPaginatedMode } from '../constants'
|
|
6
|
+
import { modelPathSegment, type PathCase } from '../utils/pathCasing'
|
|
6
7
|
import { OPERATION_METADATA } from '../copy/operationDefinitions'
|
|
7
8
|
|
|
8
9
|
function pathExpr(basePath: string, suffix: string): string {
|
|
@@ -55,10 +56,11 @@ function emitReadOp(
|
|
|
55
56
|
const opKind = opKindFor(meta.name)
|
|
56
57
|
|
|
57
58
|
const postReadBlock = meta.supportsPostRead
|
|
58
|
-
? ` if (
|
|
59
|
+
? ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) {
|
|
59
60
|
const postPath = ${meta.name === 'findMany' ? "basePath ? `${basePath}/read` : '/read'" : `path`}
|
|
60
61
|
router.post(
|
|
61
62
|
postPath,
|
|
63
|
+
...(opConfig.authorize ? [opConfig.authorize] : []),
|
|
62
64
|
parseBodyAsQuery,
|
|
63
65
|
setShape(opConfig, '${opKind}'),
|
|
64
66
|
...opConfig.operationBefore,
|
|
@@ -77,12 +79,14 @@ function emitReadOp(
|
|
|
77
79
|
const path = ${pathValue}
|
|
78
80
|
router.get(
|
|
79
81
|
path,
|
|
82
|
+
...(opConfig.authorize ? [opConfig.authorize] : []),
|
|
80
83
|
parseQuery,
|
|
81
84
|
setShape(opConfig, '${opKind}'),
|
|
82
85
|
...opConfig.operationBefore,
|
|
83
86
|
requireVariantKey(),
|
|
84
87
|
variantBeforeDispatcher(opConfig),
|
|
85
88
|
maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'),
|
|
89
|
+
${meta.name === 'findMany' ? 'maybeNdjsonFindMany(core.findMany),' : ''}
|
|
86
90
|
${handlerName} as RequestHandler,
|
|
87
91
|
variantAfterDispatcher(opConfig),
|
|
88
92
|
...opConfig.operationAfter,
|
|
@@ -107,6 +111,7 @@ function emitWriteOp(
|
|
|
107
111
|
const path = ${pathValue}
|
|
108
112
|
router.${meta.method}(
|
|
109
113
|
path,
|
|
114
|
+
...(opConfig.authorize ? [opConfig.authorize] : []),
|
|
110
115
|
setShape(opConfig, '${opKind}'),
|
|
111
116
|
...opConfig.operationBefore,
|
|
112
117
|
requireVariantKey(),
|
|
@@ -123,22 +128,26 @@ export function generateRouterFunction({
|
|
|
123
128
|
model,
|
|
124
129
|
enums,
|
|
125
130
|
guardShapesImport,
|
|
131
|
+
clientImport,
|
|
126
132
|
importStyle,
|
|
127
133
|
writeStrategy,
|
|
128
134
|
findManyPaginatedMode,
|
|
129
135
|
dropGuard,
|
|
136
|
+
pathCase,
|
|
130
137
|
}: {
|
|
131
138
|
model: DMMF.Model
|
|
132
139
|
enums: DMMF.DatamodelEnum[]
|
|
133
140
|
guardShapesImport: string | null
|
|
141
|
+
clientImport?: string
|
|
134
142
|
importStyle: ImportStyle
|
|
135
143
|
writeStrategy: WriteStrategy
|
|
136
144
|
findManyPaginatedMode: FindManyPaginatedMode
|
|
137
145
|
dropGuard: boolean
|
|
146
|
+
pathCase: PathCase
|
|
138
147
|
}): string {
|
|
139
148
|
const ext = importExt(importStyle)
|
|
140
149
|
const modelName = model.name
|
|
141
|
-
const
|
|
150
|
+
const modelSegment = modelPathSegment(modelName, pathCase)
|
|
142
151
|
const delegateKey = modelName.charAt(0).toLowerCase() + modelName.slice(1)
|
|
143
152
|
const routerFunctionName = `${modelName}Router`
|
|
144
153
|
|
|
@@ -173,9 +182,10 @@ import type {
|
|
|
173
182
|
QueryBuilderConfig,
|
|
174
183
|
FindManyPaginatedMode,
|
|
175
184
|
PaginationConfig,
|
|
185
|
+
PrismaClientLike,
|
|
176
186
|
} from '../routeConfig.target${ext}'
|
|
177
187
|
import { parseQueryParams } from '../parseQueryParams${ext}'
|
|
178
|
-
import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject } from '../misc${ext}'
|
|
188
|
+
import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject, resolveDropGuardEnv } from '../misc${ext}'
|
|
179
189
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
180
190
|
import {
|
|
181
191
|
normalizeOperation,
|
|
@@ -183,7 +193,10 @@ import {
|
|
|
183
193
|
validateCountSourceWhere,
|
|
184
194
|
validateOperationConfig,
|
|
185
195
|
validateUpdateEachConfig,
|
|
196
|
+
resolvePostReadsEnabled,
|
|
197
|
+
warnIfUnguardedRoutes,
|
|
186
198
|
} from '../routeConfig${ext}'
|
|
199
|
+
import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
|
|
187
200
|
import type { NormalizedOperationConfig } from '../routeConfig${ext}'
|
|
188
201
|
import type { OperationContext } from '../operationRuntime${ext}'
|
|
189
202
|
import { transformResult } from '../operationRuntime${ext}'
|
|
@@ -191,6 +204,7 @@ import { HttpError, mapError } from '../errorMapper${ext}'
|
|
|
191
204
|
import { formatGuardVariantResolutionError } from '../guardVariantError${ext}'
|
|
192
205
|
import type { GuardVariantResolution } from '../guardVariantRouting${ext}'
|
|
193
206
|
import { mergePaginationConfig } from '../pagination${ext}'
|
|
207
|
+
import { acceptsNdjson, runNdjsonFindMany } from '../ndjson${ext}'
|
|
194
208
|
import {
|
|
195
209
|
acceptsEventStream,
|
|
196
210
|
runProgressiveEndpoint,
|
|
@@ -204,13 +218,18 @@ import { applyDroppedGuard } from '../projectionDefaults${ext}'
|
|
|
204
218
|
import type { OpKind } from '../projectionDefaults${ext}'
|
|
205
219
|
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
206
220
|
|
|
207
|
-
${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
|
|
221
|
+
${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express', clientImport)}
|
|
208
222
|
const _env = getEnv()
|
|
209
223
|
|
|
210
224
|
const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
|
|
211
|
-
|
|
225
|
+
// Fixed at generation time. The environment bypass is resolved per router,
|
|
226
|
+
// because it requires the configuration's explicit \`allowE2EGuardBypass: true\`:
|
|
227
|
+
// false or omitted means the environment cannot drop guards for this target.
|
|
228
|
+
const DROP_GUARD = ${dropGuard}
|
|
212
229
|
|
|
213
230
|
type OperationConfigLike = {
|
|
231
|
+
authorize?: RequestHandler
|
|
232
|
+
override?: RuntimeOperationOverride
|
|
214
233
|
before?: RequestHandler[]
|
|
215
234
|
after?: RequestHandler[]
|
|
216
235
|
shape?: unknown
|
|
@@ -233,7 +252,7 @@ type NormalizedOp = NormalizedOperationConfig<RequestHandler, RequestHandler> &
|
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
type ExtendedRequest = Request & {
|
|
236
|
-
prisma?:
|
|
255
|
+
prisma?: PrismaClientLike
|
|
237
256
|
postgres?: unknown
|
|
238
257
|
sqlite?: unknown
|
|
239
258
|
}
|
|
@@ -243,6 +262,8 @@ type LocalsBag = {
|
|
|
243
262
|
routeConfig?: { pagination?: PaginationConfig }
|
|
244
263
|
guardShape?: Record<string, unknown>
|
|
245
264
|
guardCaller?: string
|
|
265
|
+
operationOverride?: RuntimeOperationOverride
|
|
266
|
+
resolveOperationContext?: () => unknown | Promise<unknown>
|
|
246
267
|
guardVariantKey?: string
|
|
247
268
|
guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
|
|
248
269
|
data?: unknown
|
|
@@ -360,7 +381,7 @@ function requireVariantKey(): RequestHandler {
|
|
|
360
381
|
}
|
|
361
382
|
}
|
|
362
383
|
|
|
363
|
-
export function ${routerFunctionName}<TCtx = unknown, TPrisma =
|
|
384
|
+
export function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
|
|
364
385
|
validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
|
|
365
386
|
validateCountSourceWhere(
|
|
366
387
|
(config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
|
|
@@ -370,6 +391,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
370
391
|
const router = express.Router()
|
|
371
392
|
|
|
372
393
|
const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
|
|
394
|
+
warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
|
|
373
395
|
|
|
374
396
|
const opFor = (key: string): NormalizedOp => {
|
|
375
397
|
const raw = (config as unknown as Record<string, unknown>)[key] as OperationConfigLike | undefined
|
|
@@ -378,13 +400,12 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
378
400
|
}
|
|
379
401
|
|
|
380
402
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
381
|
-
const modelPrefix = config.addModelPrefix !== false ? '/${
|
|
403
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
|
|
382
404
|
const basePath = customPrefix + modelPrefix
|
|
383
405
|
|
|
384
406
|
const openApiDisabled = config.disableOpenApi === true
|
|
385
407
|
|| (config.disableOpenApi !== false && (_env.DISABLE_OPENAPI === 'true' || _env.NODE_ENV === 'production'))
|
|
386
408
|
|
|
387
|
-
const postReadsEnabled = !config.disablePostReads
|
|
388
409
|
|
|
389
410
|
let _openApiJsonCache: unknown = undefined
|
|
390
411
|
const getOpenApiJson = (): unknown => {
|
|
@@ -394,7 +415,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
394
415
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
395
416
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
396
417
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
397
|
-
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
418
|
+
{ format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
398
419
|
)
|
|
399
420
|
}
|
|
400
421
|
return _openApiJsonCache
|
|
@@ -407,7 +428,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
407
428
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
408
429
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
409
430
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
410
|
-
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
431
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
411
432
|
) as string
|
|
412
433
|
}
|
|
413
434
|
return _openApiYamlCache
|
|
@@ -429,6 +450,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
429
450
|
const extReq = req as ExtendedRequest
|
|
430
451
|
const locals = readLocals(res)
|
|
431
452
|
return {
|
|
453
|
+
operationOverride: locals.operationOverride,
|
|
454
|
+
resolveOperationContext: locals.resolveOperationContext,
|
|
432
455
|
prisma: extReq.prisma,
|
|
433
456
|
postgres: extReq.postgres,
|
|
434
457
|
sqlite: extReq.sqlite,
|
|
@@ -436,6 +459,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
436
459
|
body: req.body,
|
|
437
460
|
guardShape: locals.guardShape,
|
|
438
461
|
guardCaller: locals.guardCaller,
|
|
462
|
+
guardVariantKey: locals.guardVariantKey,
|
|
439
463
|
paginationConfig: locals.routeConfig?.pagination,
|
|
440
464
|
findManyPaginatedMode: FIND_MANY_PAGINATED_MODE,
|
|
441
465
|
}
|
|
@@ -463,10 +487,25 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
463
487
|
next()
|
|
464
488
|
}
|
|
465
489
|
|
|
490
|
+
/**
|
|
491
|
+
* The environment bypass, and it is OPT-IN for this target.
|
|
492
|
+
*
|
|
493
|
+
* \`PGE_DROP_GUARD=true\` (deprecated alias \`E2E=true\`) downgrading enforcement
|
|
494
|
+
* in a deployed environment is a real hazard: on most platforms it is an
|
|
495
|
+
* ordinary config var, set on staging, copied forward, flagged as
|
|
496
|
+
* security-relevant nowhere. It is honoured only when this configuration
|
|
497
|
+
* states \`allowE2EGuardBypass: true\` — false or omitted means the environment
|
|
498
|
+
* cannot drop guards. The generation-time \`DROP_GUARD\` literal is unchanged.
|
|
499
|
+
*/
|
|
500
|
+
const dropGuard = DROP_GUARD || (config.allowE2EGuardBypass === true && resolveDropGuardEnv(_env))
|
|
501
|
+
|
|
466
502
|
const setShape = (opConfig: NormalizedOp, opKind: OpKind): RequestHandler => {
|
|
467
503
|
return async (req, res, next) => {
|
|
468
504
|
try {
|
|
469
505
|
const locals = readLocals(res)
|
|
506
|
+
locals.operationOverride = opConfig.override
|
|
507
|
+
let context: Promise<unknown> | undefined
|
|
508
|
+
locals.resolveOperationContext = () => context ??= Promise.resolve(config.resolveContext?.(req))
|
|
470
509
|
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
471
510
|
if (merged) {
|
|
472
511
|
locals.routeConfig = { pagination: merged }
|
|
@@ -491,7 +530,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
491
530
|
if (resolvedKey !== undefined) locals.guardVariantKey = resolvedKey
|
|
492
531
|
|
|
493
532
|
if (opConfig.guardShape) {
|
|
494
|
-
if (!
|
|
533
|
+
if (!dropGuard) {
|
|
495
534
|
locals.guardShape = opConfig.guardShape
|
|
496
535
|
} else {
|
|
497
536
|
await applyDroppedGuard(
|
|
@@ -526,6 +565,41 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
526
565
|
}
|
|
527
566
|
}
|
|
528
567
|
|
|
568
|
+
const maybeNdjsonFindMany = (
|
|
569
|
+
coreFn: (ctx: OperationContext) => Promise<unknown>,
|
|
570
|
+
): RequestHandler => {
|
|
571
|
+
return async (req, res, next) => {
|
|
572
|
+
if (res.headersSent || res.writableEnded) return next()
|
|
573
|
+
if (req.method !== 'GET') return next()
|
|
574
|
+
if (!acceptsNdjson(req.headers.accept)) return next()
|
|
575
|
+
|
|
576
|
+
const locals = readLocals(res)
|
|
577
|
+
const rawArgs = (locals.parsedQuery ?? {}) as Record<string, unknown>
|
|
578
|
+
const args = { ...rawArgs }
|
|
579
|
+
const paginationCfg = mergePaginationConfig(config.pagination, undefined)
|
|
580
|
+
const requestedTake = typeof rawArgs.take === 'number' ? rawArgs.take : undefined
|
|
581
|
+
const initialSkip = typeof rawArgs.skip === 'number' ? rawArgs.skip : 0
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
await runNdjsonFindMany({
|
|
585
|
+
res,
|
|
586
|
+
isClosed: () => req.destroyed,
|
|
587
|
+
fetchPage: (skip, pageTake) =>
|
|
588
|
+
coreFn({
|
|
589
|
+
...buildContext(req, res),
|
|
590
|
+
parsedQuery: { ...args, skip, take: pageTake },
|
|
591
|
+
}),
|
|
592
|
+
initialSkip,
|
|
593
|
+
requestedTake,
|
|
594
|
+
defaultLimit: paginationCfg?.defaultLimit,
|
|
595
|
+
maxLimit: paginationCfg?.maxLimit ?? 1000,
|
|
596
|
+
})
|
|
597
|
+
} catch (err) {
|
|
598
|
+
next(err)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
529
603
|
const maybeProgressiveSSE = (
|
|
530
604
|
opConfig: NormalizedOp,
|
|
531
605
|
coreFn: (ctx: OperationContext) => Promise<unknown>,
|
|
@@ -552,6 +626,23 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
552
626
|
}
|
|
553
627
|
|
|
554
628
|
if (progressiveConfig.mode === 'autoInclude') {
|
|
629
|
+
if (progressiveConfig.experimental !== true) {
|
|
630
|
+
if (_env.NODE_ENV !== 'production') {
|
|
631
|
+
console.warn(
|
|
632
|
+
'[prisma-generator-express] auto-include progressive is experimental; set \`experimental: true\` on the progressive config to enable it. Falling back.'
|
|
633
|
+
)
|
|
634
|
+
}
|
|
635
|
+
if (progressiveConfig.fallback === 'error') {
|
|
636
|
+
emitTerminalSSEError(res, 'auto-include requires experimental: true')
|
|
637
|
+
return
|
|
638
|
+
}
|
|
639
|
+
await runSingleResultSSE({
|
|
640
|
+
req,
|
|
641
|
+
res,
|
|
642
|
+
coreQueryFn: () => coreFn(buildContext(req, res)),
|
|
643
|
+
})
|
|
644
|
+
return
|
|
645
|
+
}
|
|
555
646
|
const isAutoIncludeReadable =
|
|
556
647
|
baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
|
|
557
648
|
baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
|
|
@@ -619,11 +710,16 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
619
710
|
}
|
|
620
711
|
|
|
621
712
|
const ctx = await config.resolveContext(req)
|
|
713
|
+
const prismaClient = (req as ExtendedRequest).prisma
|
|
714
|
+
if (!prismaClient) {
|
|
715
|
+
emitTerminalSSEError(res, 'Progressive endpoint requires req.prisma to be set')
|
|
716
|
+
return
|
|
717
|
+
}
|
|
622
718
|
await runProgressiveEndpoint({
|
|
623
719
|
req,
|
|
624
720
|
res,
|
|
625
721
|
ctx,
|
|
626
|
-
prisma:
|
|
722
|
+
prisma: prismaClient,
|
|
627
723
|
variant: variant as string,
|
|
628
724
|
stages: progressiveConfig.stages,
|
|
629
725
|
stageRegistry,
|
|
@@ -679,6 +775,7 @@ ${writeOpBlocks}
|
|
|
679
775
|
const path = basePath ? \`\${basePath}/each\` : '/each'
|
|
680
776
|
router.post(
|
|
681
777
|
path,
|
|
778
|
+
...(opConfig.authorize ? [opConfig.authorize] : []),
|
|
682
779
|
setShape(opConfig, 'noop'),
|
|
683
780
|
...opConfig.operationBefore,
|
|
684
781
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
@@ -707,4 +804,4 @@ ${writeOpBlocks}
|
|
|
707
804
|
return router
|
|
708
805
|
}
|
|
709
806
|
`
|
|
710
|
-
}
|
|
807
|
+
}
|