prisma-generator-express 1.28.0 → 1.29.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 +244 -14
- package/dist/constants.d.ts +1 -0
- package/dist/generators/generateFastifyHandler.d.ts +4 -0
- package/dist/generators/generateFastifyHandler.js +78 -0
- package/dist/generators/generateFastifyHandler.js.map +1 -0
- package/dist/generators/generateOperationCore.d.ts +6 -0
- package/dist/generators/generateOperationCore.js +534 -0
- package/dist/generators/generateOperationCore.js.map +1 -0
- package/dist/generators/generateQueryBuilderHelper.js +85 -69
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRouter.js +1 -25
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +5 -0
- package/dist/generators/generateRouterFastify.js +512 -0
- package/dist/generators/generateRouterFastify.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +2 -1
- package/dist/generators/generateUnifiedDocs.js +147 -82
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +0 -1
- package/dist/generators/generateUnifiedHandler.js +47 -516
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
- package/dist/generators/generateUnifiedScalarUI.js +127 -1324
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +33 -8
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +2 -1
- package/dist/utils/copyFiles.js +64 -38
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/writeFileSafely.js +3 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +4 -1
- package/src/client/encodeQueryParams.ts +1 -1
- package/src/constants.ts +2 -0
- package/src/copy/createOutputValidatorMiddleware.ts +9 -12
- package/src/copy/docsRenderer.ts +1285 -0
- package/src/copy/parseQueryParams.ts +4 -8
- package/src/copy/routeConfig.ts +10 -4
- package/src/generators/generateFastifyHandler.ts +86 -0
- package/src/generators/generateOperationCore.ts +545 -0
- package/src/generators/generateQueryBuilderHelper.ts +86 -70
- package/src/generators/generateRouter.ts +1 -25
- package/src/generators/generateRouterFastify.ts +522 -0
- package/src/generators/generateUnifiedDocs.ts +164 -81
- package/src/generators/generateUnifiedHandler.ts +45 -533
- package/src/generators/generateUnifiedScalarUI.ts +134 -1323
- package/src/index.ts +45 -9
- package/src/utils/copyFiles.ts +79 -45
- package/src/utils/writeFileSafely.ts +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateFastifyHandler.js","sourceRoot":"","sources":["../../src/generators/generateFastifyHandler.ts"],"names":[],"mappings":";;AAgCA,wDAqDC;AAnFD,MAAM,QAAQ,GAAG;IACf,UAAU;IACV,WAAW;IACX,kBAAkB;IAClB,YAAY;IACZ,mBAAmB;IACnB,mBAAmB;IACnB,WAAW;IACX,OAAO;IACP,SAAS;CACV,CAAA;AAED,MAAM,SAAS,GAAG;IAChB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,QAAQ;IACR,QAAQ;IACR,YAAY;CACb,CAAA;AAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,QAAQ;IACR,YAAY;IACZ,qBAAqB;CACtB,CAAC,CAAA;AAEF,SAAgB,sBAAsB,CAAC,OAEtC;IACC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAA;IAEpC,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAE5E,OAAO;wBACa,UAAU;;;;4BAIN,EAAE;;EAE5B,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACzC,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5E,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;QAElD,OAAO;wBACa,UAAU;;;;4BAIN,EAAE;;qCAEO,UAAU;EAC7C,CAAA;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,OAAO;2BACkB,SAAS;;;;;;;;;;;;;;;;EAgBlC,YAAY;EACZ,aAAa;CACd,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateOperationRuntime = generateOperationRuntime;
|
|
4
|
+
exports.generateModelCore = generateModelCore;
|
|
5
|
+
function generateOperationRuntime() {
|
|
6
|
+
return `import { sanitizeKeys } from './misc.js'
|
|
7
|
+
|
|
8
|
+
export interface PaginationConfig {
|
|
9
|
+
defaultLimit?: number
|
|
10
|
+
maxLimit?: number
|
|
11
|
+
distinctCountLimit?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface OperationContext {
|
|
15
|
+
prisma: any
|
|
16
|
+
postgres?: any
|
|
17
|
+
sqlite?: any
|
|
18
|
+
parsedQuery?: Record<string, unknown>
|
|
19
|
+
body?: unknown
|
|
20
|
+
guardShape?: Record<string, unknown>
|
|
21
|
+
guardCaller?: string
|
|
22
|
+
paginationConfig?: PaginationConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DISTINCT_COUNT_LIMIT = 100000
|
|
26
|
+
|
|
27
|
+
export class HttpError extends Error {
|
|
28
|
+
status: number
|
|
29
|
+
constructor(status: number, message: string) {
|
|
30
|
+
super(message)
|
|
31
|
+
this.name = 'HttpError'
|
|
32
|
+
this.status = status
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
|
|
37
|
+
P2000: { status: 400, message: 'Value too long for column' },
|
|
38
|
+
P2001: { status: 404, message: 'Record not found' },
|
|
39
|
+
P2002: { status: 409, message: 'Unique constraint violation' },
|
|
40
|
+
P2003: { status: 400, message: 'Foreign key constraint failed' },
|
|
41
|
+
P2004: { status: 400, message: 'Constraint failed on the database' },
|
|
42
|
+
P2005: { status: 400, message: 'Invalid field value' },
|
|
43
|
+
P2006: { status: 400, message: 'Invalid value provided' },
|
|
44
|
+
P2007: { status: 400, message: 'Data validation error' },
|
|
45
|
+
P2008: { status: 400, message: 'Failed to parse the query' },
|
|
46
|
+
P2009: { status: 400, message: 'Failed to validate the query' },
|
|
47
|
+
P2010: { status: 500, message: 'Raw query failed' },
|
|
48
|
+
P2011: { status: 400, message: 'Null constraint violation' },
|
|
49
|
+
P2012: { status: 400, message: 'Missing required value' },
|
|
50
|
+
P2013: { status: 400, message: 'Missing required argument' },
|
|
51
|
+
P2014: { status: 400, message: 'Required relation violation' },
|
|
52
|
+
P2015: { status: 404, message: 'Related record not found' },
|
|
53
|
+
P2016: { status: 400, message: 'Query interpretation error' },
|
|
54
|
+
P2017: { status: 400, message: 'Records not connected' },
|
|
55
|
+
P2018: { status: 404, message: 'Required connected record not found' },
|
|
56
|
+
P2019: { status: 400, message: 'Input error' },
|
|
57
|
+
P2020: { status: 400, message: 'Value out of range for the field type' },
|
|
58
|
+
P2021: { status: 500, message: 'Table does not exist in the database' },
|
|
59
|
+
P2022: { status: 500, message: 'Column does not exist in the database' },
|
|
60
|
+
P2023: { status: 500, message: 'Inconsistent column data' },
|
|
61
|
+
P2024: { status: 503, message: 'Connection pool timeout' },
|
|
62
|
+
P2025: { status: 404, message: 'Record not found' },
|
|
63
|
+
P2026: { status: 501, message: 'Feature not supported by the current database provider' },
|
|
64
|
+
P2028: { status: 500, message: 'Transaction API error' },
|
|
65
|
+
P2030: { status: 400, message: 'Cannot find a fulltext index for the search' },
|
|
66
|
+
P2033: { status: 400, message: 'Number out of range for the field type' },
|
|
67
|
+
P2034: { status: 409, message: 'Transaction conflict, please retry' },
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function mapError(error: unknown): HttpError {
|
|
71
|
+
if (error instanceof HttpError) return error
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
error &&
|
|
75
|
+
typeof error === 'object' &&
|
|
76
|
+
'name' in error &&
|
|
77
|
+
error.name === 'ShapeError'
|
|
78
|
+
) {
|
|
79
|
+
return new HttpError(400, (error as any).message)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
error &&
|
|
84
|
+
typeof error === 'object' &&
|
|
85
|
+
'name' in error &&
|
|
86
|
+
error.name === 'CallerError'
|
|
87
|
+
) {
|
|
88
|
+
return new HttpError(400, (error as any).message)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
error &&
|
|
93
|
+
typeof error === 'object' &&
|
|
94
|
+
'name' in error &&
|
|
95
|
+
error.name === 'PolicyError'
|
|
96
|
+
) {
|
|
97
|
+
return new HttpError(403, (error as any).message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
error &&
|
|
102
|
+
typeof error === 'object' &&
|
|
103
|
+
'issues' in error &&
|
|
104
|
+
'name' in error &&
|
|
105
|
+
(error as any).name === 'ZodError'
|
|
106
|
+
) {
|
|
107
|
+
const issues = (error as any).issues
|
|
108
|
+
const message = Array.isArray(issues)
|
|
109
|
+
? issues.map((i: any) => i.message).join('; ')
|
|
110
|
+
: (error as any).message
|
|
111
|
+
return new HttpError(400, message)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
115
|
+
const code = (error as any).code as string
|
|
116
|
+
const mapped = PRISMA_ERROR_MAP[code]
|
|
117
|
+
if (mapped) {
|
|
118
|
+
return new HttpError(mapped.status, mapped.message)
|
|
119
|
+
}
|
|
120
|
+
if (typeof code === 'string' && code.startsWith('P')) {
|
|
121
|
+
console.warn(
|
|
122
|
+
'[prisma-generator-express] Unmapped Prisma error code:',
|
|
123
|
+
code,
|
|
124
|
+
(error as any).message || '',
|
|
125
|
+
)
|
|
126
|
+
return new HttpError(500, 'Database operation failed')
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (error && typeof error === 'object' && 'name' in error) {
|
|
131
|
+
const name = (error as any).name
|
|
132
|
+
if (name === 'PrismaClientValidationError') {
|
|
133
|
+
return new HttpError(400, 'Invalid query parameters')
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.error('[prisma-generator-express] Unhandled error:', error)
|
|
138
|
+
return new HttpError(500, 'Internal server error')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let _speedExtension: ((opts: any) => any) | null = null
|
|
142
|
+
|
|
143
|
+
const _prismasqlModule = 'prisma-' + 'sql'
|
|
144
|
+
const _prismasqlReady = (async () => {
|
|
145
|
+
try {
|
|
146
|
+
const mod = await import(_prismasqlModule)
|
|
147
|
+
_speedExtension = mod.speedExtension ?? mod.default?.speedExtension ?? null
|
|
148
|
+
} catch (err: any) {
|
|
149
|
+
const code = err?.code
|
|
150
|
+
if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
|
|
151
|
+
console.warn('[prisma-generator-express] prisma-sql initialization failed:', err)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
})()
|
|
155
|
+
|
|
156
|
+
const _extendedClients = new WeakMap<object, WeakMap<object, any>>()
|
|
157
|
+
|
|
158
|
+
export async function getExtendedClient(ctx: OperationContext): Promise<any> {
|
|
159
|
+
const base = ctx.prisma
|
|
160
|
+
if (!base) {
|
|
161
|
+
throw new HttpError(500, 'PrismaClient not found on request. Set req.prisma in middleware.')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
await _prismasqlReady
|
|
165
|
+
|
|
166
|
+
if (!_speedExtension) return base
|
|
167
|
+
|
|
168
|
+
const connector = ctx.postgres || ctx.sqlite
|
|
169
|
+
if (!connector) return base
|
|
170
|
+
|
|
171
|
+
if (typeof connector === 'object' && connector !== null) {
|
|
172
|
+
const innerMap = _extendedClients.get(connector)
|
|
173
|
+
if (innerMap) {
|
|
174
|
+
const cached = innerMap.get(base)
|
|
175
|
+
if (cached) return cached
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const extended = base.$extends(_speedExtension({
|
|
181
|
+
postgres: ctx.postgres,
|
|
182
|
+
sqlite: ctx.sqlite,
|
|
183
|
+
debug: process.env.DEBUG === 'true',
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
if (typeof connector === 'object' && connector !== null) {
|
|
187
|
+
let innerMap = _extendedClients.get(connector)
|
|
188
|
+
if (!innerMap) {
|
|
189
|
+
innerMap = new WeakMap<object, any>()
|
|
190
|
+
_extendedClients.set(connector, innerMap)
|
|
191
|
+
}
|
|
192
|
+
innerMap.set(base, extended)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return extended
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn('[speedExtension] Failed to initialize, using base client:', error)
|
|
198
|
+
return base
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function validateBody(body: unknown): Record<string, any> {
|
|
203
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
204
|
+
throw new HttpError(400, 'Request body must be a JSON object')
|
|
205
|
+
}
|
|
206
|
+
return sanitizeKeys(body as Record<string, any>)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function requireBodyField(body: Record<string, any>, field: string): void {
|
|
210
|
+
if (!(field in body) || body[field] === undefined) {
|
|
211
|
+
throw new HttpError(400, 'Missing required field: ' + field)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function applyPaginationLimits(
|
|
216
|
+
query: Record<string, any>,
|
|
217
|
+
config?: PaginationConfig,
|
|
218
|
+
): Record<string, any> {
|
|
219
|
+
if (!config) return query
|
|
220
|
+
|
|
221
|
+
const result = { ...query }
|
|
222
|
+
|
|
223
|
+
if (result.take === undefined && config.defaultLimit !== undefined) {
|
|
224
|
+
result.take = config.defaultLimit
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (config.maxLimit !== undefined && result.take !== undefined) {
|
|
228
|
+
const takeNum = Number(result.take)
|
|
229
|
+
if (Math.abs(takeNum) > config.maxLimit) {
|
|
230
|
+
result.take = takeNum < 0 ? -config.maxLimit : config.maxLimit
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return result
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function normalizeDistinct(value: unknown): string[] {
|
|
238
|
+
if (typeof value === 'string') return [value]
|
|
239
|
+
if (Array.isArray(value)) return value.filter((v): v is string => typeof v === 'string')
|
|
240
|
+
return []
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function assertGuard(delegate: any): void {
|
|
244
|
+
if (typeof delegate.guard !== 'function') {
|
|
245
|
+
throw new HttpError(
|
|
246
|
+
500,
|
|
247
|
+
'Guard shapes require prisma-guard extension on PrismaClient. Install: npm install prisma-guard, then extend your client with guardExtension().',
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const GUARD_SHAPE_CONFIG_KEYS = new Set([
|
|
253
|
+
'data', 'create', 'update', 'where', 'include', 'select', 'orderBy',
|
|
254
|
+
'cursor', 'take', 'skip', 'distinct', 'having', '_count', '_avg',
|
|
255
|
+
'_sum', '_min', '_max', 'by',
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
function keepWhereOnly(obj: Record<string, any>): Record<string, any> {
|
|
259
|
+
const result: Record<string, any> = {}
|
|
260
|
+
if ('where' in obj) result.where = obj.where
|
|
261
|
+
return result
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function buildCountShape(shape: Record<string, any>): Record<string, any> {
|
|
265
|
+
if (typeof shape === 'function') {
|
|
266
|
+
return (...args: any[]) => keepWhereOnly((shape as Function)(...args))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const keys = Object.keys(shape)
|
|
270
|
+
const isSingleShape = keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
|
|
271
|
+
|
|
272
|
+
if (isSingleShape) {
|
|
273
|
+
return keepWhereOnly(shape)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const result: Record<string, any> = {}
|
|
277
|
+
for (const [key, variant] of Object.entries(shape)) {
|
|
278
|
+
if (typeof variant === 'function') {
|
|
279
|
+
result[key] = (...args: any[]) => keepWhereOnly(variant(...args))
|
|
280
|
+
} else if (typeof variant === 'object' && variant !== null) {
|
|
281
|
+
result[key] = keepWhereOnly(variant)
|
|
282
|
+
} else {
|
|
283
|
+
result[key] = variant
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return result
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function countForPagination(
|
|
290
|
+
delegate: any,
|
|
291
|
+
query: Record<string, any>,
|
|
292
|
+
shape: Record<string, any> | undefined,
|
|
293
|
+
caller: string | undefined,
|
|
294
|
+
distinctCountLimit?: number,
|
|
295
|
+
): Promise<number> {
|
|
296
|
+
const distinctFields = normalizeDistinct(query.distinct)
|
|
297
|
+
const hasDistinct = distinctFields.length > 0
|
|
298
|
+
const effectiveLimit = distinctCountLimit ?? DISTINCT_COUNT_LIMIT
|
|
299
|
+
|
|
300
|
+
const countShape = shape ? buildCountShape(shape) : undefined
|
|
301
|
+
|
|
302
|
+
if (hasDistinct) {
|
|
303
|
+
const selectField = distinctFields[0]
|
|
304
|
+
const distinctArgs: Record<string, any> = {
|
|
305
|
+
where: query.where,
|
|
306
|
+
distinct: distinctFields,
|
|
307
|
+
select: { [selectField]: true },
|
|
308
|
+
take: effectiveLimit + 1,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const results = shape
|
|
312
|
+
? await delegate.guard(shape, caller).findMany(distinctArgs)
|
|
313
|
+
: await delegate.findMany(distinctArgs)
|
|
314
|
+
|
|
315
|
+
if (results.length > effectiveLimit) {
|
|
316
|
+
console.warn(
|
|
317
|
+
'[prisma-generator-express] Distinct count exceeds ' +
|
|
318
|
+
effectiveLimit +
|
|
319
|
+
', falling back to approximate total',
|
|
320
|
+
)
|
|
321
|
+
const countArgs: Record<string, any> = {}
|
|
322
|
+
if (query.where) countArgs.where = query.where
|
|
323
|
+
return countShape
|
|
324
|
+
? await delegate.guard(countShape, caller).count(countArgs)
|
|
325
|
+
: await delegate.count(countArgs)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return results.length
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const countArgs: Record<string, any> = {}
|
|
332
|
+
if (query.where) countArgs.where = query.where
|
|
333
|
+
|
|
334
|
+
return countShape
|
|
335
|
+
? await delegate.guard(countShape, caller).count(countArgs)
|
|
336
|
+
: await delegate.count(countArgs)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function transformResult(value: unknown): unknown {
|
|
340
|
+
if (value === null || value === undefined) return value
|
|
341
|
+
if (typeof value === 'bigint') return value.toString()
|
|
342
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
|
|
343
|
+
return value.toString('base64')
|
|
344
|
+
}
|
|
345
|
+
if (value instanceof Uint8Array) {
|
|
346
|
+
return Buffer.from(value).toString('base64')
|
|
347
|
+
}
|
|
348
|
+
if (value instanceof Date) return value
|
|
349
|
+
if (Array.isArray(value)) return value.map(transformResult)
|
|
350
|
+
if (typeof value === 'object') {
|
|
351
|
+
const proto = Object.getPrototypeOf(value)
|
|
352
|
+
if (proto !== Object.prototype && proto !== null) return value
|
|
353
|
+
const out: Record<string, unknown> = {}
|
|
354
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
355
|
+
out[k] = transformResult(v)
|
|
356
|
+
}
|
|
357
|
+
return out
|
|
358
|
+
}
|
|
359
|
+
return value
|
|
360
|
+
}
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
function generateModelCore(options) {
|
|
364
|
+
const modelName = options.model.name;
|
|
365
|
+
const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
366
|
+
const standardReadOps = [
|
|
367
|
+
'findFirst',
|
|
368
|
+
'findUnique',
|
|
369
|
+
'findUniqueOrThrow',
|
|
370
|
+
'findFirstOrThrow',
|
|
371
|
+
'count',
|
|
372
|
+
'aggregate',
|
|
373
|
+
'groupBy',
|
|
374
|
+
];
|
|
375
|
+
const standardReadHandlers = standardReadOps
|
|
376
|
+
.map((op) => `
|
|
377
|
+
export async function ${op}(ctx: OperationContext): Promise<unknown> {
|
|
378
|
+
const query = ctx.parsedQuery || {}
|
|
379
|
+
const extended = await getExtendedClient(ctx)
|
|
380
|
+
if (ctx.guardShape) {
|
|
381
|
+
assertGuard((extended as any).${modelNameLower})
|
|
382
|
+
return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).${op}(query)
|
|
383
|
+
}
|
|
384
|
+
return (extended as any).${modelNameLower}.${op}(query)
|
|
385
|
+
}`)
|
|
386
|
+
.join('\n');
|
|
387
|
+
const writeOps = [
|
|
388
|
+
{ name: 'create', method: 'create', requiredFields: ['data'] },
|
|
389
|
+
{ name: 'createMany', method: 'createMany', requiredFields: ['data'] },
|
|
390
|
+
{
|
|
391
|
+
name: 'createManyAndReturn',
|
|
392
|
+
method: 'createManyAndReturn',
|
|
393
|
+
requiredFields: ['data'],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'update',
|
|
397
|
+
method: 'update',
|
|
398
|
+
requiredFields: ['where', 'data'],
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'updateMany',
|
|
402
|
+
method: 'updateMany',
|
|
403
|
+
requiredFields: ['where', 'data'],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'updateManyAndReturn',
|
|
407
|
+
method: 'updateManyAndReturn',
|
|
408
|
+
requiredFields: ['where', 'data'],
|
|
409
|
+
},
|
|
410
|
+
{ name: 'delete', method: 'delete', requiredFields: ['where'] },
|
|
411
|
+
{
|
|
412
|
+
name: 'deleteMany',
|
|
413
|
+
method: 'deleteMany',
|
|
414
|
+
requiredFields: ['where'],
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: 'upsert',
|
|
418
|
+
method: 'upsert',
|
|
419
|
+
requiredFields: ['where', 'create', 'update'],
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
const writeHandlers = writeOps
|
|
423
|
+
.map((op) => {
|
|
424
|
+
const validationLines = op.requiredFields
|
|
425
|
+
.map((field) => ` requireBodyField(body, '${field}')`)
|
|
426
|
+
.join('\n');
|
|
427
|
+
return `
|
|
428
|
+
export async function ${op.name}(ctx: OperationContext): Promise<unknown> {
|
|
429
|
+
const body = validateBody(ctx.body)
|
|
430
|
+
${validationLines}
|
|
431
|
+
const extended = await getExtendedClient(ctx)
|
|
432
|
+
if (ctx.guardShape) {
|
|
433
|
+
assertGuard((extended as any).${modelNameLower})
|
|
434
|
+
return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).${op.method}(body)
|
|
435
|
+
}
|
|
436
|
+
return (extended as any).${modelNameLower}.${op.method}(body)
|
|
437
|
+
}`;
|
|
438
|
+
})
|
|
439
|
+
.join('\n');
|
|
440
|
+
return `import {
|
|
441
|
+
OperationContext,
|
|
442
|
+
getExtendedClient,
|
|
443
|
+
validateBody,
|
|
444
|
+
requireBodyField,
|
|
445
|
+
applyPaginationLimits,
|
|
446
|
+
assertGuard,
|
|
447
|
+
countForPagination,
|
|
448
|
+
} from '../operationRuntime.js'
|
|
449
|
+
|
|
450
|
+
export async function findMany(ctx: OperationContext): Promise<unknown> {
|
|
451
|
+
const rawQuery = ctx.parsedQuery || {}
|
|
452
|
+
const query = applyPaginationLimits(rawQuery, ctx.paginationConfig)
|
|
453
|
+
const extended = await getExtendedClient(ctx)
|
|
454
|
+
if (ctx.guardShape) {
|
|
455
|
+
assertGuard((extended as any).${modelNameLower})
|
|
456
|
+
return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).findMany(query)
|
|
457
|
+
}
|
|
458
|
+
return (extended as any).${modelNameLower}.findMany(query)
|
|
459
|
+
}
|
|
460
|
+
${standardReadHandlers}
|
|
461
|
+
${writeHandlers}
|
|
462
|
+
|
|
463
|
+
export async function findManyPaginated(
|
|
464
|
+
ctx: OperationContext,
|
|
465
|
+
): Promise<{ data: unknown[]; total: number; hasMore: boolean }> {
|
|
466
|
+
const rawQuery = ctx.parsedQuery || {}
|
|
467
|
+
const query = applyPaginationLimits(rawQuery, ctx.paginationConfig)
|
|
468
|
+
const extended = await getExtendedClient(ctx)
|
|
469
|
+
const shape = ctx.guardShape
|
|
470
|
+
const caller = ctx.guardCaller
|
|
471
|
+
const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
|
|
472
|
+
|
|
473
|
+
if (shape) {
|
|
474
|
+
assertGuard((extended as any).${modelNameLower})
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
let items: any[]
|
|
478
|
+
let total: number
|
|
479
|
+
|
|
480
|
+
if (typeof extended.$transaction === 'function') {
|
|
481
|
+
try {
|
|
482
|
+
const txResult = await extended.$transaction(async (tx: any) => {
|
|
483
|
+
const d = shape
|
|
484
|
+
? await tx.${modelNameLower}.guard(shape, caller).findMany(query)
|
|
485
|
+
: await tx.${modelNameLower}.findMany(query)
|
|
486
|
+
const t = await countForPagination(tx.${modelNameLower}, query, shape, caller, distinctCountLimit)
|
|
487
|
+
return { d, t }
|
|
488
|
+
})
|
|
489
|
+
items = txResult.d
|
|
490
|
+
total = txResult.t
|
|
491
|
+
} catch (txError: any) {
|
|
492
|
+
if (
|
|
493
|
+
txError?.message?.includes?.('interactive transactions') ||
|
|
494
|
+
txError?.code === 'P2028'
|
|
495
|
+
) {
|
|
496
|
+
console.warn(
|
|
497
|
+
'[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic',
|
|
498
|
+
)
|
|
499
|
+
items = shape
|
|
500
|
+
? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
|
|
501
|
+
: await (extended as any).${modelNameLower}.findMany(query)
|
|
502
|
+
total = await countForPagination(
|
|
503
|
+
(extended as any).${modelNameLower},
|
|
504
|
+
query,
|
|
505
|
+
shape,
|
|
506
|
+
caller,
|
|
507
|
+
distinctCountLimit,
|
|
508
|
+
)
|
|
509
|
+
} else {
|
|
510
|
+
throw txError
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
items = shape
|
|
515
|
+
? await (extended as any).${modelNameLower}.guard(shape, caller).findMany(query)
|
|
516
|
+
: await (extended as any).${modelNameLower}.findMany(query)
|
|
517
|
+
total = await countForPagination(
|
|
518
|
+
(extended as any).${modelNameLower},
|
|
519
|
+
query,
|
|
520
|
+
shape,
|
|
521
|
+
caller,
|
|
522
|
+
distinctCountLimit,
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const skip = (query.skip as number) ?? 0
|
|
527
|
+
const absTake = Math.abs((query.take as number) ?? items.length)
|
|
528
|
+
const hasMore = items.length >= absTake && skip + items.length < total
|
|
529
|
+
|
|
530
|
+
return { data: items, total, hasMore }
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
//# sourceMappingURL=generateOperationCore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateOperationCore.js","sourceRoot":"","sources":["../../src/generators/generateOperationCore.ts"],"names":[],"mappings":";;AAEA,4DAqWC;AAMD,8CAmLC;AA9hBD,SAAgB,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmWR,CAAA;AACD,CAAC;AAMD,SAAgB,iBAAiB,CAAC,OAAyB;IACzD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAA;IACpC,MAAM,cAAc,GAClB,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAExD,MAAM,eAAe,GAAG;QACtB,WAAW;QACX,YAAY;QACZ,mBAAmB;QACnB,kBAAkB;QAClB,OAAO;QACP,WAAW;QACX,SAAS;KACV,CAAA;IAED,MAAM,oBAAoB,GAAG,eAAe;SACzC,GAAG,CACF,CAAC,EAAE,EAAE,EAAE,CAAC;wBACU,EAAE;;;;oCAIU,cAAc;+BACnB,cAAc,2CAA2C,EAAE;;6BAE7D,cAAc,IAAI,EAAE;EAC/C,CACG;SACA,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,QAAQ,GAAG;QACf,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE;QAC9D,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE;QACtE;YACE,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,qBAAqB;YAC7B,cAAc,EAAE,CAAC,MAAM,CAAC;SACzB;QACD;YACE,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;SAClC;QACD;YACE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,YAAY;YACpB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;SAClC;QACD;YACE,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,qBAAqB;YAC7B,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;SAClC;QACD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE;QAC/D;YACE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,YAAY;YACpB,cAAc,EAAE,CAAC,OAAO,CAAC;SAC1B;QACD;YACE,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC;SAC9C;KACF,CAAA;IAED,MAAM,aAAa,GAAG,QAAQ;SAC3B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACV,MAAM,eAAe,GAAG,EAAE,CAAC,cAAc;aACtC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,6BAA6B,KAAK,IAAI,CAAC;aACtD,IAAI,CAAC,IAAI,CAAC,CAAA;QAEb,OAAO;wBACW,EAAE,CAAC,IAAI;;EAE7B,eAAe;;;oCAGmB,cAAc;+BACnB,cAAc,2CAA2C,EAAE,CAAC,MAAM;;6BAEpE,cAAc,IAAI,EAAE,CAAC,MAAM;EACtD,CAAA;IACE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,OAAO;;;;;;;;;;;;;;;oCAe2B,cAAc;+BACnB,cAAc;;6BAEhB,cAAc;;EAEzC,oBAAoB;EACpB,aAAa;;;;;;;;;;;;;oCAaqB,cAAc;;;;;;;;;;uBAU3B,cAAc;uBACd,cAAc;gDACW,cAAc;;;;;;;;;;;;;;sCAcxB,cAAc;sCACd,cAAc;;8BAEtB,cAAc;;;;;;;;;;;;kCAYV,cAAc;kCACd,cAAc;;0BAEtB,cAAc;;;;;;;;;;;;;;CAcvC,CAAA;AACD,CAAC"}
|