prisma-generator-express 1.59.0 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/encodeQueryParams.js +19 -23
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +37 -0
- package/dist/copy/operationDefinitions.js +412 -0
- package/dist/copy/operationDefinitions.js.map +1 -0
- package/dist/generators/generateFastifyHandler.js +10 -41
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +10 -41
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateModelMetadata.d.ts +8 -0
- package/dist/generators/generateModelMetadata.js +77 -0
- package/dist/generators/generateModelMetadata.js.map +1 -0
- package/dist/generators/generateOperationCore.js +20 -30
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +1 -1
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +11 -57
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.js +64 -177
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +59 -169
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +52 -152
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.js +7 -30
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.js +9 -74
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +7 -0
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/writeFileSafely.js +5 -12
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +12 -2
- package/src/client/encodeQueryParams.ts +23 -36
- package/src/copy/autoIncludePlanner.ts +14 -23
- package/src/copy/autoIncludeRuntime.ts +117 -228
- package/src/copy/buildModelOpenApi.ts +248 -628
- package/src/copy/concurrency.ts +20 -0
- package/src/copy/docsRenderer.ts +63 -333
- package/src/copy/errorMapper.ts +126 -0
- package/src/copy/guardHelpers.ts +56 -0
- package/src/copy/materializedCount.ts +68 -0
- package/src/copy/materializedRouter.ts +33 -29
- package/src/copy/operationDefinitions.ts +359 -35
- package/src/copy/operationRuntime.ts +11 -605
- package/src/copy/pagination.ts +151 -0
- package/src/copy/scalarTypes.ts +2 -0
- package/src/copy/sse.ts +296 -0
- package/src/generators/generateFastifyHandler.ts +13 -47
- package/src/generators/generateHonoHandler.ts +13 -47
- package/src/generators/generateModelMetadata.ts +92 -0
- package/src/generators/generateOperationCore.ts +19 -32
- package/src/generators/generateQueryBuilderHelper.ts +1 -1
- package/src/generators/generateRouteConfigType.ts +9 -60
- package/src/generators/generateRouter.ts +88 -180
- package/src/generators/generateRouterFastify.ts +65 -172
- package/src/generators/generateRouterHono.ts +58 -155
- package/src/generators/generateUnifiedHandler.ts +8 -33
- package/src/generators/generateUnifiedScalarUI.ts +9 -91
- package/src/index.ts +13 -1
- package/src/utils/copyFiles.ts +7 -0
- package/src/utils/writeFileSafely.ts +5 -11
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DMMF } from '@prisma/generator-helper'
|
|
2
|
+
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
3
|
+
import { importExt } from '../utils/importExt'
|
|
4
|
+
|
|
5
|
+
function exampleValueForType(fieldType: string): unknown {
|
|
6
|
+
switch (fieldType) {
|
|
7
|
+
case 'String': return 'example'
|
|
8
|
+
case 'Int': return 1
|
|
9
|
+
case 'BigInt': return '1'
|
|
10
|
+
case 'Float': return 1.0
|
|
11
|
+
case 'Decimal': return '1.0'
|
|
12
|
+
case 'Boolean': return true
|
|
13
|
+
case 'DateTime': return '2025-01-01T00:00:00.000Z'
|
|
14
|
+
case 'Json': return {}
|
|
15
|
+
case 'Bytes': return 'base64data'
|
|
16
|
+
default: return 'example'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GenerateModelMetadataOptions {
|
|
21
|
+
model: DMMF.Model
|
|
22
|
+
enums: DMMF.DatamodelEnum[]
|
|
23
|
+
importStyle: ImportStyle
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function generateModelMetadata(options: GenerateModelMetadataOptions): string {
|
|
27
|
+
const ext = importExt(options.importStyle)
|
|
28
|
+
const { model, enums } = options
|
|
29
|
+
const modelName = model.name
|
|
30
|
+
|
|
31
|
+
const fieldsMeta = model.fields.map((f: any) => ({
|
|
32
|
+
name: f.name,
|
|
33
|
+
kind: f.kind,
|
|
34
|
+
type: f.type,
|
|
35
|
+
isList: f.isList,
|
|
36
|
+
isRequired: f.isRequired,
|
|
37
|
+
hasDefaultValue: f.hasDefaultValue,
|
|
38
|
+
isUpdatedAt: Boolean(f.isUpdatedAt),
|
|
39
|
+
documentation: f.documentation ?? null,
|
|
40
|
+
isId: Boolean(f.isId),
|
|
41
|
+
isUnique: Boolean(f.isUnique),
|
|
42
|
+
relationFromFields: f.relationFromFields,
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const referencedEnumTypes = new Set(
|
|
46
|
+
model.fields.filter((f: any) => f.kind === 'enum').map((f: any) => f.type),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const enumsMeta = enums
|
|
50
|
+
.filter((e) => referencedEnumTypes.has(e.name))
|
|
51
|
+
.map((e) => ({
|
|
52
|
+
name: e.name,
|
|
53
|
+
values: e.values.map((v) => ({ name: v.name })),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
const exampleValues: Record<string, unknown> = {}
|
|
57
|
+
for (const f of model.fields as any[]) {
|
|
58
|
+
if (f.isId || f.isUnique || f.kind === 'scalar' || f.kind === 'enum') {
|
|
59
|
+
if (f.kind === 'enum') {
|
|
60
|
+
const enumDef = enums.find((e) => e.name === f.type)
|
|
61
|
+
exampleValues[f.name] = enumDef?.values[0]?.name || 'VALUE'
|
|
62
|
+
} else {
|
|
63
|
+
exampleValues[f.name] = exampleValueForType(f.type)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const compoundId =
|
|
69
|
+
model.primaryKey && model.primaryKey.fields.length > 1
|
|
70
|
+
? { fields: model.primaryKey.fields }
|
|
71
|
+
: null
|
|
72
|
+
|
|
73
|
+
const compoundUniques = ((model as any).uniqueIndexes || [])
|
|
74
|
+
.filter((idx: any) => idx.fields && idx.fields.length > 1)
|
|
75
|
+
.map((idx: any) => ({
|
|
76
|
+
name: idx.name || idx.fields.join('_'),
|
|
77
|
+
fields: idx.fields,
|
|
78
|
+
}))
|
|
79
|
+
|
|
80
|
+
return `import type { FieldMeta, EnumMeta } from '../docsRenderer${ext}'
|
|
81
|
+
|
|
82
|
+
export const MODEL_FIELDS: FieldMeta[] = ${JSON.stringify(fieldsMeta, null, 2)}
|
|
83
|
+
|
|
84
|
+
export const MODEL_ENUMS: EnumMeta[] = ${JSON.stringify(enumsMeta, null, 2)}
|
|
85
|
+
|
|
86
|
+
export const COMPOUND_ID: { fields: string[] } | null = ${JSON.stringify(compoundId)}
|
|
87
|
+
|
|
88
|
+
export const COMPOUND_UNIQUES: { name: string; fields: string[] }[] = ${JSON.stringify(compoundUniques)}
|
|
89
|
+
|
|
90
|
+
export const EXAMPLE_VALUES: Record<string, unknown> = ${JSON.stringify(exampleValues, null, 2)}
|
|
91
|
+
`
|
|
92
|
+
}
|
|
@@ -10,28 +10,21 @@ export interface ModelCoreOptions {
|
|
|
10
10
|
findManyPaginatedMode: FindManyPaginatedMode
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
type WriteOpDecision =
|
|
14
|
-
| { mode: 'normal'; method: string }
|
|
15
|
-
| { mode: 'redirect'; method: string }
|
|
16
|
-
| { mode: 'throw' }
|
|
13
|
+
type WriteOpDecision = { throw?: true; method: string }
|
|
17
14
|
|
|
18
15
|
function decideWriteOp(
|
|
19
16
|
name: string,
|
|
20
17
|
defaultMethod: string,
|
|
21
18
|
strategy: WriteStrategy,
|
|
22
19
|
): WriteOpDecision {
|
|
23
|
-
if (strategy === '
|
|
24
|
-
return {
|
|
20
|
+
if (strategy === 'throwOnNonReturning' && (name === 'createMany' || name === 'updateMany')) {
|
|
21
|
+
return { throw: true, method: defaultMethod }
|
|
25
22
|
}
|
|
26
|
-
if (strategy === '
|
|
27
|
-
if (name === 'createMany'
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return { mode: 'normal', method: defaultMethod }
|
|
23
|
+
if (strategy === 'forceReturn') {
|
|
24
|
+
if (name === 'createMany') return { method: 'createManyAndReturn' }
|
|
25
|
+
if (name === 'updateMany') return { method: 'updateManyAndReturn' }
|
|
31
26
|
}
|
|
32
|
-
|
|
33
|
-
if (name === 'updateMany') return { mode: 'redirect', method: 'updateManyAndReturn' }
|
|
34
|
-
return { mode: 'normal', method: defaultMethod }
|
|
27
|
+
return { method: defaultMethod }
|
|
35
28
|
}
|
|
36
29
|
|
|
37
30
|
function renderPaginatedBody(modelNameLower: string, mode: FindManyPaginatedMode): string {
|
|
@@ -112,7 +105,7 @@ export async function ${op}(ctx: OperationContext): Promise<unknown> {
|
|
|
112
105
|
const writeHandlers = writeOps.map((op) => {
|
|
113
106
|
const decision = decideWriteOp(op.name, op.method, writeStrategy)
|
|
114
107
|
|
|
115
|
-
if (decision.
|
|
108
|
+
if (decision.throw) {
|
|
116
109
|
return `
|
|
117
110
|
export async function ${op.name}(_ctx: OperationContext): Promise<unknown> {
|
|
118
111
|
throw new HttpError(501, '${op.name} is disabled by writeStrategy="${writeStrategy}"')
|
|
@@ -141,16 +134,15 @@ ${validationLines}
|
|
|
141
134
|
return `import {
|
|
142
135
|
OperationContext,
|
|
143
136
|
PrismaClientLike,
|
|
144
|
-
HttpError,
|
|
145
137
|
getExtendedClient,
|
|
146
138
|
getDelegate,
|
|
147
139
|
validateBody,
|
|
148
140
|
requireBodyField,
|
|
149
|
-
applyPaginationLimits,
|
|
150
|
-
assertGuard,
|
|
151
|
-
countForPagination,
|
|
152
|
-
mapError,
|
|
153
141
|
} from '../operationRuntime${ext}'
|
|
142
|
+
import { HttpError, mapError } from '../errorMapper${ext}'
|
|
143
|
+
import { applyPaginationLimits, countForPagination } from '../pagination${ext}'
|
|
144
|
+
import { assertGuard } from '../guardHelpers${ext}'
|
|
145
|
+
import { mapLimited } from '../concurrency${ext}'
|
|
154
146
|
|
|
155
147
|
export async function findMany(ctx: OperationContext): Promise<unknown> {
|
|
156
148
|
const rawQuery = ctx.parsedQuery || {}
|
|
@@ -248,20 +240,15 @@ export async function updateEach(
|
|
|
248
240
|
const CONCURRENCY = 8
|
|
249
241
|
const results: Array<{ status: 'ok'; data: unknown } | { status: 'error'; error: string }> =
|
|
250
242
|
new Array(items.length)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
results[i] = { status: 'ok', data: await delegate.update(items[i]) }
|
|
259
|
-
} catch (err) {
|
|
260
|
-
results[i] = { status: 'error', error: mapError(err).message }
|
|
261
|
-
}
|
|
243
|
+
|
|
244
|
+
await mapLimited(items, CONCURRENCY, async (item, i) => {
|
|
245
|
+
try {
|
|
246
|
+
results[i] = { status: 'ok', data: await delegate.update(item) }
|
|
247
|
+
} catch (err) {
|
|
248
|
+
results[i] = { status: 'error', error: mapError(err).message }
|
|
262
249
|
}
|
|
263
250
|
})
|
|
264
|
-
|
|
251
|
+
|
|
265
252
|
return results
|
|
266
253
|
}
|
|
267
254
|
`
|
|
@@ -3,7 +3,7 @@ import { GeneratorOptions } from '@prisma/generator-helper'
|
|
|
3
3
|
export function generateQueryBuilderHelper(options: GeneratorOptions): string {
|
|
4
4
|
const schemaPath = options.schemaPath
|
|
5
5
|
? JSON.stringify(options.schemaPath)
|
|
6
|
-
: "
|
|
6
|
+
: "resolve(process.cwd(), 'prisma/schema.prisma')"
|
|
7
7
|
|
|
8
8
|
return `import { spawn } from 'child_process'
|
|
9
9
|
import { resolve, join, dirname } from 'path'
|
|
@@ -1,62 +1,14 @@
|
|
|
1
1
|
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
2
2
|
import { importExt } from '../utils/importExt'
|
|
3
3
|
import type { Target } from '../constants'
|
|
4
|
-
import {
|
|
4
|
+
import { OPERATION_METADATA, READ_OPERATION_NAMES } from '../copy/operationDefinitions'
|
|
5
5
|
|
|
6
|
-
const ROUTER_OPERATIONS =
|
|
7
|
-
'
|
|
8
|
-
|
|
9
|
-
'findFirst',
|
|
10
|
-
'findFirstOrThrow',
|
|
11
|
-
'findMany',
|
|
12
|
-
'findManyPaginated',
|
|
13
|
-
'count',
|
|
14
|
-
'aggregate',
|
|
15
|
-
'groupBy',
|
|
16
|
-
'create',
|
|
17
|
-
'createMany',
|
|
18
|
-
'createManyAndReturn',
|
|
19
|
-
'update',
|
|
20
|
-
'updateMany',
|
|
21
|
-
'updateManyAndReturn',
|
|
22
|
-
'upsert',
|
|
23
|
-
'delete',
|
|
24
|
-
'deleteMany',
|
|
25
|
-
] as const
|
|
6
|
+
const ROUTER_OPERATIONS = OPERATION_METADATA
|
|
7
|
+
.filter((m) => m.name !== 'updateEach')
|
|
8
|
+
.map((m) => m.name)
|
|
26
9
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const READ_OPERATIONS: ReadonlySet<RouterOperation> = new Set<RouterOperation>([
|
|
30
|
-
'findUnique',
|
|
31
|
-
'findUniqueOrThrow',
|
|
32
|
-
'findFirst',
|
|
33
|
-
'findFirstOrThrow',
|
|
34
|
-
'findMany',
|
|
35
|
-
'findManyPaginated',
|
|
36
|
-
'count',
|
|
37
|
-
'aggregate',
|
|
38
|
-
'groupBy',
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
const ROUTER_OP_TO_SHAPE_OP: Record<RouterOperation, string> = {
|
|
42
|
-
findUnique: 'findUnique',
|
|
43
|
-
findUniqueOrThrow: 'findUniqueOrThrow',
|
|
44
|
-
findFirst: 'findFirst',
|
|
45
|
-
findFirstOrThrow: 'findFirstOrThrow',
|
|
46
|
-
findMany: 'findMany',
|
|
47
|
-
findManyPaginated: 'findManyPaginated',
|
|
48
|
-
count: 'count',
|
|
49
|
-
aggregate: 'aggregate',
|
|
50
|
-
groupBy: 'groupBy',
|
|
51
|
-
create: 'create',
|
|
52
|
-
createMany: 'createMany',
|
|
53
|
-
createManyAndReturn: 'createManyAndReturn',
|
|
54
|
-
update: 'update',
|
|
55
|
-
updateMany: 'updateMany',
|
|
56
|
-
updateManyAndReturn: 'updateManyAndReturn',
|
|
57
|
-
upsert: 'upsert',
|
|
58
|
-
delete: 'delete',
|
|
59
|
-
deleteMany: 'deleteMany',
|
|
10
|
+
function capitalize(str: string): string {
|
|
11
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
60
12
|
}
|
|
61
13
|
|
|
62
14
|
function requestTypeFor(target: Target): string {
|
|
@@ -120,17 +72,14 @@ export function generateRouteConfigType(
|
|
|
120
72
|
)
|
|
121
73
|
}
|
|
122
74
|
|
|
123
|
-
const shapeOps =
|
|
124
|
-
(v, i, a) => a.indexOf(v) === i,
|
|
125
|
-
)
|
|
75
|
+
const shapeOps = Array.from(new Set(ROUTER_OPERATIONS))
|
|
126
76
|
const opShapeImports = shapeOps
|
|
127
77
|
.map((op) => `${m}${capitalize(op)}ShapeInput`)
|
|
128
78
|
.join(',\n ')
|
|
129
79
|
|
|
130
80
|
const overrides = ROUTER_OPERATIONS.map((routerOp) => {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const isRead = READ_OPERATIONS.has(routerOp)
|
|
81
|
+
const c = capitalize(routerOp)
|
|
82
|
+
const isRead = READ_OPERATION_NAMES.has(routerOp)
|
|
134
83
|
const lines = [
|
|
135
84
|
` before?: ${beforeRef}[]`,
|
|
136
85
|
` after?: ${afterRef}[]`,
|
|
@@ -3,6 +3,60 @@ import { generateRouteConfigType } from './generateRouteConfigType'
|
|
|
3
3
|
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
4
4
|
import { importExt } from '../utils/importExt'
|
|
5
5
|
import { WriteStrategy, FindManyPaginatedMode } from '../constants'
|
|
6
|
+
import { OPERATION_METADATA } from '../copy/operationDefinitions'
|
|
7
|
+
|
|
8
|
+
interface OpEmitContext {
|
|
9
|
+
modelName: string
|
|
10
|
+
basePath: string
|
|
11
|
+
postReadsEnabled: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function pathExpr(basePath: string, suffix: string): string {
|
|
15
|
+
if (!suffix) return basePath || '/'
|
|
16
|
+
if (!basePath) return `'${suffix}'`
|
|
17
|
+
return `\`\${basePath}${suffix}\``
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function emitReadOp(
|
|
21
|
+
meta: (typeof OPERATION_METADATA)[number],
|
|
22
|
+
modelName: string,
|
|
23
|
+
): string {
|
|
24
|
+
const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
|
|
25
|
+
const handlerName = `${modelName}${c}`
|
|
26
|
+
const pathValue = pathExpr('basePath', meta.pathSuffix)
|
|
27
|
+
|
|
28
|
+
const postReadBlock = meta.supportsPostRead
|
|
29
|
+
? ` if (postReadsEnabled) {
|
|
30
|
+
const postPath = ${meta.name === 'findMany' ? "basePath ? `${basePath}/read` : '/read'" : `path`}
|
|
31
|
+
router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, respond)
|
|
32
|
+
}`
|
|
33
|
+
: ''
|
|
34
|
+
|
|
35
|
+
return ` if (isEnabled(config.${meta.configKey})) {
|
|
36
|
+
const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
37
|
+
const { before = [], after = [] } = opConfig
|
|
38
|
+
const path = ${pathValue}
|
|
39
|
+
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'), ${handlerName} as RequestHandler, ...after, respond)
|
|
40
|
+
${postReadBlock}
|
|
41
|
+
}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function emitWriteOp(
|
|
45
|
+
meta: (typeof OPERATION_METADATA)[number],
|
|
46
|
+
modelName: string,
|
|
47
|
+
): string {
|
|
48
|
+
const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
|
|
49
|
+
const handlerName = `${modelName}${c}`
|
|
50
|
+
const pathValue = pathExpr('basePath', meta.pathSuffix)
|
|
51
|
+
const respondFn = meta.successStatus === 201 ? 'respondCreated' : 'respond'
|
|
52
|
+
|
|
53
|
+
return ` if (isEnabled(config.${meta.configKey})) {
|
|
54
|
+
const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
55
|
+
const { before = [], after = [] } = opConfig
|
|
56
|
+
const path = ${pathValue}
|
|
57
|
+
router.${meta.method}(path, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, ${respondFn})
|
|
58
|
+
}`
|
|
59
|
+
}
|
|
6
60
|
|
|
7
61
|
export function generateRouterFunction({
|
|
8
62
|
model,
|
|
@@ -27,57 +81,35 @@ export function generateRouterFunction({
|
|
|
27
81
|
const delegateKey = modelName.charAt(0).toLowerCase() + modelName.slice(1)
|
|
28
82
|
const routerFunctionName = `${modelName}Router`
|
|
29
83
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
kind: f.kind,
|
|
33
|
-
type: f.type,
|
|
34
|
-
isList: f.isList,
|
|
35
|
-
isRequired: f.isRequired,
|
|
36
|
-
hasDefaultValue: f.hasDefaultValue,
|
|
37
|
-
isUpdatedAt: f.isUpdatedAt ?? false,
|
|
38
|
-
documentation: f.documentation,
|
|
39
|
-
relationFromFields: f.relationFromFields,
|
|
40
|
-
}))
|
|
41
|
-
|
|
42
|
-
const referencedEnumTypes = new Set(
|
|
43
|
-
model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
|
|
84
|
+
const handlerImports = OPERATION_METADATA.filter(
|
|
85
|
+
(m) => m.name !== 'updateEach',
|
|
44
86
|
)
|
|
87
|
+
.map(
|
|
88
|
+
(m) =>
|
|
89
|
+
` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
|
|
90
|
+
)
|
|
91
|
+
.join('\n')
|
|
45
92
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
93
|
+
const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
|
|
94
|
+
const writeOps = OPERATION_METADATA.filter(
|
|
95
|
+
(m) => m.kind === 'write' || m.kind === 'batch',
|
|
96
|
+
).filter((m) => m.name !== 'updateEach')
|
|
97
|
+
|
|
98
|
+
const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
|
|
99
|
+
const writeOpBlocks = writeOps
|
|
100
|
+
.map((m) => emitWriteOp(m, modelName))
|
|
101
|
+
.join('\n\n')
|
|
52
102
|
|
|
53
103
|
return `import express from 'express'
|
|
54
104
|
import type { Request, Response, NextFunction, RequestHandler } from 'express'
|
|
55
105
|
import { startQueryBuilder } from '../queryBuilder${ext}'
|
|
56
106
|
import {
|
|
57
|
-
|
|
58
|
-
${modelName}FindUniqueOrThrow,
|
|
59
|
-
${modelName}FindFirst,
|
|
60
|
-
${modelName}FindFirstOrThrow,
|
|
61
|
-
${modelName}FindMany,
|
|
62
|
-
${modelName}FindManyPaginated,
|
|
63
|
-
${modelName}Create,
|
|
64
|
-
${modelName}CreateMany,
|
|
65
|
-
${modelName}CreateManyAndReturn,
|
|
66
|
-
${modelName}Update,
|
|
67
|
-
${modelName}UpdateMany,
|
|
68
|
-
${modelName}UpdateManyAndReturn,
|
|
69
|
-
${modelName}Upsert,
|
|
70
|
-
${modelName}Delete,
|
|
71
|
-
${modelName}DeleteMany,
|
|
72
|
-
${modelName}Aggregate,
|
|
73
|
-
${modelName}Count,
|
|
74
|
-
${modelName}GroupBy,
|
|
107
|
+
${handlerImports}
|
|
75
108
|
} from './${modelName}Handlers${ext}'
|
|
76
109
|
import * as core from './${modelName}Core${ext}'
|
|
77
110
|
import type {
|
|
78
111
|
RouteConfig,
|
|
79
112
|
QueryBuilderConfig,
|
|
80
|
-
WriteStrategy,
|
|
81
113
|
FindManyPaginatedMode,
|
|
82
114
|
PaginationConfig,
|
|
83
115
|
} from '../routeConfig.target${ext}'
|
|
@@ -86,30 +118,26 @@ import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
|
|
|
86
118
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
87
119
|
import { validateCountSourceWhere } from '../routeConfig${ext}'
|
|
88
120
|
import type { OperationContext } from '../operationRuntime${ext}'
|
|
121
|
+
import { transformResult } from '../operationRuntime${ext}'
|
|
122
|
+
import { HttpError, mapError } from '../errorMapper${ext}'
|
|
123
|
+
import { mergePaginationConfig } from '../pagination${ext}'
|
|
89
124
|
import {
|
|
90
|
-
transformResult,
|
|
91
125
|
acceptsEventStream,
|
|
92
126
|
runProgressiveEndpoint,
|
|
93
127
|
runSingleResultSSE,
|
|
94
128
|
emitTerminalSSEError,
|
|
95
129
|
removeReqCloseListener,
|
|
96
|
-
|
|
97
|
-
mapError,
|
|
98
|
-
HttpError,
|
|
99
|
-
} from '../operationRuntime${ext}'
|
|
130
|
+
} from '../sse${ext}'
|
|
100
131
|
import { relationModels } from '../relationModels${ext}'
|
|
101
132
|
import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
|
|
133
|
+
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
102
134
|
|
|
103
135
|
${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
|
|
104
136
|
const _env = getEnv()
|
|
105
137
|
|
|
106
|
-
const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
|
|
107
138
|
const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
|
|
108
139
|
const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
|
|
109
140
|
|
|
110
|
-
const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
|
|
111
|
-
const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
|
|
112
|
-
|
|
113
141
|
type OperationConfigLike = {
|
|
114
142
|
before?: RequestHandler[]
|
|
115
143
|
after?: RequestHandler[]
|
|
@@ -183,7 +211,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
183
211
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
184
212
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
185
213
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
186
|
-
{ format: 'json', writeStrategy:
|
|
214
|
+
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
187
215
|
)
|
|
188
216
|
}
|
|
189
217
|
return _openApiJsonCache
|
|
@@ -196,7 +224,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
196
224
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
197
225
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
198
226
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
199
|
-
{ format: 'yaml', writeStrategy:
|
|
227
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
200
228
|
) as string
|
|
201
229
|
}
|
|
202
230
|
return _openApiYamlCache
|
|
@@ -374,12 +402,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
374
402
|
}
|
|
375
403
|
|
|
376
404
|
const respond: RequestHandler = (_req, res) => {
|
|
405
|
+
if (res.headersSent || res.writableEnded) return
|
|
377
406
|
const data = readLocals(res).data
|
|
378
407
|
if (data === undefined) return res.status(500).json({ message: 'No data set by handler' })
|
|
379
408
|
return res.json(transformResult(data))
|
|
380
409
|
}
|
|
381
410
|
|
|
382
411
|
const respondCreated: RequestHandler = (_req, res) => {
|
|
412
|
+
if (res.headersSent || res.writableEnded) return
|
|
383
413
|
const data = readLocals(res).data
|
|
384
414
|
if (data === undefined) return res.status(500).json({ message: 'No data set by handler' })
|
|
385
415
|
return res.status(201).json(transformResult(data))
|
|
@@ -396,127 +426,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
396
426
|
})
|
|
397
427
|
}
|
|
398
428
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const path = basePath ? \`\${basePath}/first\` : '/first'
|
|
403
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst, 'findFirst'), ${modelName}FindFirst as RequestHandler, ...after, respond)
|
|
404
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirst as RequestHandler, ...after, respond)
|
|
405
|
-
}
|
|
406
|
-
if (isEnabled(config.findFirstOrThrow)) {
|
|
407
|
-
const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
408
|
-
const { before = [], after = [] } = opConfig
|
|
409
|
-
const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
|
|
410
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow, 'findFirstOrThrow'), ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
|
|
411
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
|
|
412
|
-
}
|
|
413
|
-
if (isEnabled(config.findManyPaginated)) {
|
|
414
|
-
const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
415
|
-
const { before = [], after = [] } = opConfig
|
|
416
|
-
const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
|
|
417
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated, 'findManyPaginated'), ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
|
|
418
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
|
|
419
|
-
}
|
|
420
|
-
if (isEnabled(config.aggregate)) {
|
|
421
|
-
const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
422
|
-
const { before = [], after = [] } = opConfig
|
|
423
|
-
const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
|
|
424
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate, 'aggregate'), ${modelName}Aggregate as RequestHandler, ...after, respond)
|
|
425
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Aggregate as RequestHandler, ...after, respond)
|
|
426
|
-
}
|
|
427
|
-
if (isEnabled(config.count)) {
|
|
428
|
-
const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
429
|
-
const { before = [], after = [] } = opConfig
|
|
430
|
-
const path = basePath ? \`\${basePath}/count\` : '/count'
|
|
431
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count, 'count'), ${modelName}Count as RequestHandler, ...after, respond)
|
|
432
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Count as RequestHandler, ...after, respond)
|
|
433
|
-
}
|
|
434
|
-
if (isEnabled(config.groupBy)) {
|
|
435
|
-
const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
436
|
-
const { before = [], after = [] } = opConfig
|
|
437
|
-
const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
|
|
438
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy, 'groupBy'), ${modelName}GroupBy as RequestHandler, ...after, respond)
|
|
439
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}GroupBy as RequestHandler, ...after, respond)
|
|
440
|
-
}
|
|
441
|
-
if (isEnabled(config.findUniqueOrThrow)) {
|
|
442
|
-
const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
443
|
-
const { before = [], after = [] } = opConfig
|
|
444
|
-
const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
|
|
445
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow, 'findUniqueOrThrow'), ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
|
|
446
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
|
|
447
|
-
}
|
|
448
|
-
if (isEnabled(config.findUnique)) {
|
|
449
|
-
const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
450
|
-
const { before = [], after = [] } = opConfig
|
|
451
|
-
const path = basePath ? \`\${basePath}/unique\` : '/unique'
|
|
452
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique, 'findUnique'), ${modelName}FindUnique as RequestHandler, ...after, respond)
|
|
453
|
-
if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUnique as RequestHandler, ...after, respond)
|
|
454
|
-
}
|
|
455
|
-
if (isEnabled(config.findMany)) {
|
|
456
|
-
const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
457
|
-
const { before = [], after = [] } = opConfig
|
|
458
|
-
const path = basePath || '/'
|
|
459
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany, 'findMany'), ${modelName}FindMany as RequestHandler, ...after, respond)
|
|
460
|
-
if (postReadsEnabled) {
|
|
461
|
-
const postPath = basePath ? \`\${basePath}/read\` : '/read'
|
|
462
|
-
router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindMany as RequestHandler, ...after, respond)
|
|
463
|
-
}
|
|
464
|
-
}
|
|
429
|
+
${readOpBlocks}
|
|
430
|
+
|
|
431
|
+
${writeOpBlocks}
|
|
465
432
|
|
|
466
|
-
if (isEnabled(config.createManyAndReturn)) {
|
|
467
|
-
const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
468
|
-
const { before = [], after = [] } = opConfig
|
|
469
|
-
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
470
|
-
router.post(path, setShape(opConfig), ...before, ${modelName}CreateManyAndReturn as RequestHandler, ...after, respondCreated)
|
|
471
|
-
}
|
|
472
|
-
if (isEnabled(config.createMany)) {
|
|
473
|
-
const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
474
|
-
const { before = [], after = [] } = opConfig
|
|
475
|
-
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
476
|
-
router.post(path, setShape(opConfig), ...before, ${modelName}CreateMany as RequestHandler, ...after, respondCreated)
|
|
477
|
-
}
|
|
478
|
-
if (isEnabled(config.create)) {
|
|
479
|
-
const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
480
|
-
const { before = [], after = [] } = opConfig
|
|
481
|
-
const path = basePath || '/'
|
|
482
|
-
router.post(path, setShape(opConfig), ...before, ${modelName}Create as RequestHandler, ...after, respondCreated)
|
|
483
|
-
}
|
|
484
|
-
if (isEnabled(config.updateManyAndReturn)) {
|
|
485
|
-
const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
486
|
-
const { before = [], after = [] } = opConfig
|
|
487
|
-
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
488
|
-
router.put(path, setShape(opConfig), ...before, ${modelName}UpdateManyAndReturn as RequestHandler, ...after, respond)
|
|
489
|
-
}
|
|
490
|
-
if (isEnabled(config.updateMany)) {
|
|
491
|
-
const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
492
|
-
const { before = [], after = [] } = opConfig
|
|
493
|
-
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
494
|
-
router.put(path, setShape(opConfig), ...before, ${modelName}UpdateMany as RequestHandler, ...after, respond)
|
|
495
|
-
}
|
|
496
|
-
if (isEnabled(config.update)) {
|
|
497
|
-
const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
498
|
-
const { before = [], after = [] } = opConfig
|
|
499
|
-
const path = basePath || '/'
|
|
500
|
-
router.put(path, setShape(opConfig), ...before, ${modelName}Update as RequestHandler, ...after, respond)
|
|
501
|
-
}
|
|
502
|
-
if (isEnabled(config.upsert)) {
|
|
503
|
-
const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
504
|
-
const { before = [], after = [] } = opConfig
|
|
505
|
-
const path = basePath || '/'
|
|
506
|
-
router.patch(path, setShape(opConfig), ...before, ${modelName}Upsert as RequestHandler, ...after, respond)
|
|
507
|
-
}
|
|
508
|
-
if (isEnabled(config.deleteMany)) {
|
|
509
|
-
const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
510
|
-
const { before = [], after = [] } = opConfig
|
|
511
|
-
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
512
|
-
router.delete(path, setShape(opConfig), ...before, ${modelName}DeleteMany as RequestHandler, ...after, respond)
|
|
513
|
-
}
|
|
514
|
-
if (isEnabled(config.delete)) {
|
|
515
|
-
const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
516
|
-
const { before = [], after = [] } = opConfig
|
|
517
|
-
const path = basePath || '/'
|
|
518
|
-
router.delete(path, setShape(opConfig), ...before, ${modelName}Delete as RequestHandler, ...after, respond)
|
|
519
|
-
}
|
|
520
433
|
if (config.updateEach) {
|
|
521
434
|
const opConfig: OperationConfigLike = (config.updateEach as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
522
435
|
if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
|
|
@@ -533,6 +446,9 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
533
446
|
...before,
|
|
534
447
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
535
448
|
try {
|
|
449
|
+
if (!Array.isArray(req.body)) {
|
|
450
|
+
throw new HttpError(400, 'updateEach body must be an array of { where, data } items')
|
|
451
|
+
}
|
|
536
452
|
const atomic = req.get('x-batch-atomic') === 'true'
|
|
537
453
|
readLocals(res).data = await core.updateEach(buildContext(req, res), atomic)
|
|
538
454
|
next()
|
|
@@ -546,15 +462,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
546
462
|
}
|
|
547
463
|
|
|
548
464
|
router.use((err: unknown, _req: Request, res: Response, next: NextFunction) => {
|
|
549
|
-
|
|
550
|
-
if (err instanceof HttpError) {
|
|
551
|
-
httpError = err
|
|
552
|
-
} else if (err && typeof err === 'object' && typeof (err as { status?: number }).status === 'number') {
|
|
553
|
-
const e = err as { status: number; message?: string }
|
|
554
|
-
httpError = new HttpError(e.status, e.message || 'Internal server error')
|
|
555
|
-
} else {
|
|
556
|
-
httpError = mapError(err)
|
|
557
|
-
}
|
|
465
|
+
const httpError = mapError(err)
|
|
558
466
|
if (!res.headersSent) return res.status(httpError.status).json({ message: httpError.message })
|
|
559
467
|
next(err)
|
|
560
468
|
})
|
|
@@ -562,4 +470,4 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
562
470
|
return router
|
|
563
471
|
}
|
|
564
472
|
`
|
|
565
|
-
}
|
|
473
|
+
}
|