prisma-generator-express 1.40.0 → 1.41.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 +444 -12
- package/dist/generators/generateOperationCore.d.ts +3 -1
- package/dist/generators/generateOperationCore.js +266 -160
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +3 -1
- package/dist/generators/generateRouteConfigType.js +36 -31
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +4 -2
- package/dist/generators/generateRouter.js +130 -119
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +3 -1
- package/dist/generators/generateRouterFastify.js +12 -10
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +3 -1
- package/dist/generators/generateRouterHono.js +12 -9
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedDocs.d.ts +2 -1
- package/dist/generators/generateUnifiedDocs.js +6 -4
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/index.js +16 -21
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +2 -1
- package/dist/utils/copyFiles.js +39 -34
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +2 -0
- package/dist/utils/importExt.js +11 -0
- package/dist/utils/importExt.js.map +1 -0
- package/dist/utils/resolveImportStyle.d.ts +3 -0
- package/dist/utils/resolveImportStyle.js +211 -0
- package/dist/utils/resolveImportStyle.js.map +1 -0
- package/dist/utils/writeFileSafely.js +6 -9
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/routeConfig.express.ts +39 -5
- package/src/copy/routeConfig.fastify.ts +8 -4
- package/src/copy/routeConfig.hono.ts +7 -3
- package/src/copy/routeConfig.ts +42 -2
- package/src/generators/generateOperationCore.ts +273 -169
- package/src/generators/generateRouteConfigType.ts +42 -35
- package/src/generators/generateRouter.ts +134 -121
- package/src/generators/generateRouterFastify.ts +14 -9
- package/src/generators/generateRouterHono.ts +14 -8
- package/src/generators/generateUnifiedDocs.ts +8 -3
- package/src/index.ts +25 -47
- package/src/utils/copyFiles.ts +45 -45
- package/src/utils/importExt.ts +7 -0
- package/src/utils/resolveImportStyle.ts +187 -0
- package/src/utils/writeFileSafely.ts +6 -22
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { DMMF } from '@prisma/generator-helper'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
3
|
+
import { importExt } from '../utils/importExt'
|
|
4
|
+
|
|
5
|
+
export function generateOperationRuntime(importStyle: ImportStyle): string {
|
|
6
|
+
const ext = importExt(importStyle)
|
|
7
|
+
return `import { sanitizeKeys } from './misc${ext}'
|
|
8
|
+
import type {
|
|
9
|
+
ProgressivePatch,
|
|
10
|
+
ProgressiveStopResult,
|
|
11
|
+
ProgressiveStageResult,
|
|
12
|
+
ProgressiveStageContext,
|
|
13
|
+
ProgressiveStage,
|
|
14
|
+
} from './routeConfig${ext}'
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
ProgressivePatch,
|
|
18
|
+
ProgressiveStopResult,
|
|
19
|
+
ProgressiveStageResult,
|
|
20
|
+
ProgressiveStageContext,
|
|
21
|
+
ProgressiveStage,
|
|
22
|
+
}
|
|
5
23
|
|
|
6
24
|
export interface PaginationConfig {
|
|
7
25
|
defaultLimit?: number
|
|
@@ -67,88 +85,41 @@ const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
|
|
|
67
85
|
|
|
68
86
|
export function mapError(error: unknown): HttpError {
|
|
69
87
|
if (error instanceof HttpError) return error
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
error &&
|
|
73
|
-
typeof error === 'object' &&
|
|
74
|
-
'name' in error &&
|
|
75
|
-
error.name === 'ShapeError'
|
|
76
|
-
) {
|
|
88
|
+
if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'ShapeError') {
|
|
77
89
|
return new HttpError(400, (error as any).message)
|
|
78
90
|
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
error &&
|
|
82
|
-
typeof error === 'object' &&
|
|
83
|
-
'name' in error &&
|
|
84
|
-
error.name === 'CallerError'
|
|
85
|
-
) {
|
|
91
|
+
if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'CallerError') {
|
|
86
92
|
return new HttpError(400, (error as any).message)
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
error &&
|
|
91
|
-
typeof error === 'object' &&
|
|
92
|
-
'name' in error &&
|
|
93
|
-
error.name === 'PolicyError'
|
|
94
|
-
) {
|
|
94
|
+
if (error && typeof error === 'object' && 'name' in error && (error as any).name === 'PolicyError') {
|
|
95
95
|
return new HttpError(403, (error as any).message)
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
error &&
|
|
100
|
-
typeof error === 'object' &&
|
|
101
|
-
'issues' in error &&
|
|
102
|
-
'name' in error &&
|
|
103
|
-
(error as any).name === 'ZodError'
|
|
104
|
-
) {
|
|
97
|
+
if (error && typeof error === 'object' && 'issues' in error && 'name' in error && (error as any).name === 'ZodError') {
|
|
105
98
|
const issues = (error as any).issues
|
|
106
|
-
const message = Array.isArray(issues)
|
|
107
|
-
? issues.map((i: any) => i.message).join('; ')
|
|
108
|
-
: (error as any).message
|
|
99
|
+
const message = Array.isArray(issues) ? issues.map((i: any) => i.message).join('; ') : (error as any).message
|
|
109
100
|
return new HttpError(400, message)
|
|
110
101
|
}
|
|
111
|
-
|
|
112
102
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
113
103
|
const code = (error as any).code as string
|
|
114
104
|
const mapped = PRISMA_ERROR_MAP[code]
|
|
115
105
|
if (mapped) {
|
|
116
106
|
const detail = (error as any).message
|
|
117
|
-
|
|
118
|
-
? mapped.message + ': ' + detail
|
|
119
|
-
: mapped.message
|
|
120
|
-
return new HttpError(mapped.status, message)
|
|
107
|
+
return new HttpError(mapped.status, detail ? mapped.message + ': ' + detail : mapped.message)
|
|
121
108
|
}
|
|
122
109
|
if (typeof code === 'string' && code.startsWith('P')) {
|
|
123
110
|
const msg = (error as any).message || 'Database operation failed'
|
|
124
|
-
console.warn(
|
|
125
|
-
'[prisma-generator-express] Unmapped Prisma error code:',
|
|
126
|
-
code,
|
|
127
|
-
msg,
|
|
128
|
-
)
|
|
111
|
+
console.warn('[prisma-generator-express] Unmapped Prisma error code:', code, msg)
|
|
129
112
|
return new HttpError(500, msg)
|
|
130
113
|
}
|
|
131
114
|
}
|
|
132
|
-
|
|
133
115
|
if (error && typeof error === 'object' && 'name' in error) {
|
|
134
116
|
const name = (error as any).name
|
|
135
|
-
if (name === 'PrismaClientValidationError')
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (name === '
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (name === 'PrismaClientInitializationError') {
|
|
142
|
-
return new HttpError(503, (error as any).message || 'Database connection failed')
|
|
143
|
-
}
|
|
144
|
-
if (name === 'PrismaClientRustPanicError') {
|
|
145
|
-
return new HttpError(500, (error as any).message || 'Internal database engine error')
|
|
146
|
-
}
|
|
147
|
-
if (name === 'PrismaClientUnknownRequestError') {
|
|
148
|
-
return new HttpError(500, (error as any).message || 'Unknown database error')
|
|
149
|
-
}
|
|
117
|
+
if (name === 'PrismaClientValidationError') return new HttpError(400, (error as any).message || 'Invalid query parameters')
|
|
118
|
+
if (name === 'PrismaClientKnownRequestError') return new HttpError(400, (error as any).message || 'Database request error')
|
|
119
|
+
if (name === 'PrismaClientInitializationError') return new HttpError(503, (error as any).message || 'Database connection failed')
|
|
120
|
+
if (name === 'PrismaClientRustPanicError') return new HttpError(500, (error as any).message || 'Internal database engine error')
|
|
121
|
+
if (name === 'PrismaClientUnknownRequestError') return new HttpError(500, (error as any).message || 'Unknown database error')
|
|
150
122
|
}
|
|
151
|
-
|
|
152
123
|
const msg = error instanceof Error ? error.message : String(error)
|
|
153
124
|
console.error('[prisma-generator-express] Unhandled error:', error)
|
|
154
125
|
return new HttpError(500, msg || 'Internal server error')
|
|
@@ -176,14 +147,10 @@ export async function getExtendedClient(ctx: OperationContext): Promise<any> {
|
|
|
176
147
|
if (!base) {
|
|
177
148
|
throw new HttpError(500, 'PrismaClient not found on request. Set req.prisma in middleware.')
|
|
178
149
|
}
|
|
179
|
-
|
|
180
150
|
await _prismasqlReady
|
|
181
|
-
|
|
182
151
|
if (!_speedExtension) return base
|
|
183
|
-
|
|
184
152
|
const connector = ctx.postgres || ctx.sqlite
|
|
185
153
|
if (!connector) return base
|
|
186
|
-
|
|
187
154
|
if (typeof connector === 'object' && connector !== null) {
|
|
188
155
|
const innerMap = _extendedClients.get(connector)
|
|
189
156
|
if (innerMap) {
|
|
@@ -191,14 +158,12 @@ export async function getExtendedClient(ctx: OperationContext): Promise<any> {
|
|
|
191
158
|
if (cached) return cached
|
|
192
159
|
}
|
|
193
160
|
}
|
|
194
|
-
|
|
195
161
|
try {
|
|
196
162
|
const extended = base.$extends(_speedExtension({
|
|
197
163
|
postgres: ctx.postgres,
|
|
198
164
|
sqlite: ctx.sqlite,
|
|
199
165
|
debug: process.env.DEBUG === 'true',
|
|
200
166
|
}))
|
|
201
|
-
|
|
202
167
|
if (typeof connector === 'object' && connector !== null) {
|
|
203
168
|
let innerMap = _extendedClients.get(connector)
|
|
204
169
|
if (!innerMap) {
|
|
@@ -207,7 +172,6 @@ export async function getExtendedClient(ctx: OperationContext): Promise<any> {
|
|
|
207
172
|
}
|
|
208
173
|
innerMap.set(base, extended)
|
|
209
174
|
}
|
|
210
|
-
|
|
211
175
|
return extended
|
|
212
176
|
} catch (error) {
|
|
213
177
|
console.warn('[speedExtension] Failed to initialize, using base client:', error)
|
|
@@ -228,25 +192,18 @@ export function requireBodyField(body: Record<string, any>, field: string): void
|
|
|
228
192
|
}
|
|
229
193
|
}
|
|
230
194
|
|
|
231
|
-
export function applyPaginationLimits(
|
|
232
|
-
query: Record<string, any>,
|
|
233
|
-
config?: PaginationConfig,
|
|
234
|
-
): Record<string, any> {
|
|
195
|
+
export function applyPaginationLimits(query: Record<string, any>, config?: PaginationConfig): Record<string, any> {
|
|
235
196
|
if (!config) return query
|
|
236
|
-
|
|
237
197
|
const result = { ...query }
|
|
238
|
-
|
|
239
198
|
if (result.take === undefined && config.defaultLimit !== undefined) {
|
|
240
199
|
result.take = config.defaultLimit
|
|
241
200
|
}
|
|
242
|
-
|
|
243
201
|
if (config.maxLimit !== undefined && result.take !== undefined) {
|
|
244
202
|
const takeNum = Number(result.take)
|
|
245
203
|
if (Math.abs(takeNum) > config.maxLimit) {
|
|
246
204
|
result.take = takeNum < 0 ? -config.maxLimit : config.maxLimit
|
|
247
205
|
}
|
|
248
206
|
}
|
|
249
|
-
|
|
250
207
|
return result
|
|
251
208
|
}
|
|
252
209
|
|
|
@@ -258,10 +215,7 @@ export function normalizeDistinct(value: unknown): string[] {
|
|
|
258
215
|
|
|
259
216
|
export function assertGuard(delegate: any): void {
|
|
260
217
|
if (typeof delegate.guard !== 'function') {
|
|
261
|
-
throw new HttpError(
|
|
262
|
-
500,
|
|
263
|
-
'Guard shapes require prisma-guard extension on PrismaClient. Install: npm install prisma-guard, then extend your client with guardExtension().',
|
|
264
|
-
)
|
|
218
|
+
throw new HttpError(500, 'Guard shapes require prisma-guard extension on PrismaClient.')
|
|
265
219
|
}
|
|
266
220
|
}
|
|
267
221
|
|
|
@@ -281,14 +235,9 @@ export function buildCountShape(shape: Record<string, any>): Record<string, any>
|
|
|
281
235
|
if (typeof shape === 'function') {
|
|
282
236
|
return (...args: any[]) => keepWhereOnly((shape as Function)(...args))
|
|
283
237
|
}
|
|
284
|
-
|
|
285
238
|
const keys = Object.keys(shape)
|
|
286
239
|
const isSingleShape = keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
|
|
287
|
-
|
|
288
|
-
if (isSingleShape) {
|
|
289
|
-
return keepWhereOnly(shape)
|
|
290
|
-
}
|
|
291
|
-
|
|
240
|
+
if (isSingleShape) return keepWhereOnly(shape)
|
|
292
241
|
const result: Record<string, any> = {}
|
|
293
242
|
for (const [key, variant] of Object.entries(shape)) {
|
|
294
243
|
if (typeof variant === 'function') {
|
|
@@ -312,7 +261,6 @@ export async function countForPagination(
|
|
|
312
261
|
const distinctFields = normalizeDistinct(query.distinct)
|
|
313
262
|
const hasDistinct = distinctFields.length > 0
|
|
314
263
|
const effectiveLimit = distinctCountLimit ?? DISTINCT_COUNT_LIMIT
|
|
315
|
-
|
|
316
264
|
const countShape = shape ? buildCountShape(shape) : undefined
|
|
317
265
|
|
|
318
266
|
if (hasDistinct) {
|
|
@@ -323,30 +271,22 @@ export async function countForPagination(
|
|
|
323
271
|
select: { [selectField]: true },
|
|
324
272
|
take: effectiveLimit + 1,
|
|
325
273
|
}
|
|
326
|
-
|
|
327
274
|
const results = shape
|
|
328
275
|
? await delegate.guard(shape, caller).findMany(distinctArgs)
|
|
329
276
|
: await delegate.findMany(distinctArgs)
|
|
330
|
-
|
|
331
277
|
if (results.length > effectiveLimit) {
|
|
332
|
-
console.warn(
|
|
333
|
-
'[prisma-generator-express] Distinct count exceeds ' +
|
|
334
|
-
effectiveLimit +
|
|
335
|
-
', falling back to approximate total',
|
|
336
|
-
)
|
|
278
|
+
console.warn('[prisma-generator-express] Distinct count exceeds ' + effectiveLimit + ', falling back to approximate total')
|
|
337
279
|
const countArgs: Record<string, any> = {}
|
|
338
280
|
if (query.where) countArgs.where = query.where
|
|
339
281
|
return countShape
|
|
340
282
|
? await delegate.guard(countShape, caller).count(countArgs)
|
|
341
283
|
: await delegate.count(countArgs)
|
|
342
284
|
}
|
|
343
|
-
|
|
344
285
|
return results.length
|
|
345
286
|
}
|
|
346
287
|
|
|
347
288
|
const countArgs: Record<string, any> = {}
|
|
348
289
|
if (query.where) countArgs.where = query.where
|
|
349
|
-
|
|
350
290
|
return countShape
|
|
351
291
|
? await delegate.guard(countShape, caller).count(countArgs)
|
|
352
292
|
: await delegate.count(countArgs)
|
|
@@ -355,12 +295,8 @@ export async function countForPagination(
|
|
|
355
295
|
export function transformResult(value: unknown): unknown {
|
|
356
296
|
if (value === null || value === undefined) return value
|
|
357
297
|
if (typeof value === 'bigint') return value.toString()
|
|
358
|
-
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
if (value instanceof Uint8Array) {
|
|
362
|
-
return Buffer.from(value).toString('base64')
|
|
363
|
-
}
|
|
298
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value.toString('base64')
|
|
299
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString('base64')
|
|
364
300
|
if (value instanceof Date) return value
|
|
365
301
|
if (Array.isArray(value)) return value.map(transformResult)
|
|
366
302
|
if (typeof value === 'object') {
|
|
@@ -374,31 +310,238 @@ export function transformResult(value: unknown): unknown {
|
|
|
374
310
|
}
|
|
375
311
|
return value
|
|
376
312
|
}
|
|
313
|
+
|
|
314
|
+
export function acceptsEventStream(accept: string | undefined): boolean {
|
|
315
|
+
if (!accept) return false
|
|
316
|
+
return accept.toLowerCase().includes('text/event-stream')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
|
|
320
|
+
|
|
321
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
322
|
+
if (value === null || typeof value !== 'object') return false
|
|
323
|
+
if (Array.isArray(value)) return false
|
|
324
|
+
const proto = Object.getPrototypeOf(value)
|
|
325
|
+
return proto === Object.prototype || proto === null
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function setByPath(target: Record<string, unknown>, path: string, value: unknown): boolean {
|
|
329
|
+
const parts = path.split('.')
|
|
330
|
+
if (parts.length === 0) return false
|
|
331
|
+
for (const p of parts) {
|
|
332
|
+
if (p === '' || UNSAFE_PATH_SEGMENTS.has(p)) return false
|
|
333
|
+
}
|
|
334
|
+
let cursor: Record<string, unknown> = target
|
|
335
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
336
|
+
const part = parts[i]
|
|
337
|
+
const next = cursor[part]
|
|
338
|
+
if (!isPlainObject(next)) {
|
|
339
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
340
|
+
console.warn(
|
|
341
|
+
'[progressive] Dropping patch for "' + path +
|
|
342
|
+
'": cannot traverse non-plain-object at segment "' + part + '"',
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
cursor = next
|
|
348
|
+
}
|
|
349
|
+
cursor[parts[parts.length - 1]] = value
|
|
350
|
+
return true
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function removeReqCloseListener(req: any, listener: () => void): void {
|
|
354
|
+
if (typeof req.off === 'function') {
|
|
355
|
+
req.off('close', listener)
|
|
356
|
+
} else if (typeof req.removeListener === 'function') {
|
|
357
|
+
req.removeListener('close', listener)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function initSSE(res: any): void {
|
|
362
|
+
res.statusCode = 200
|
|
363
|
+
res.setHeader('Content-Type', 'text/event-stream')
|
|
364
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform')
|
|
365
|
+
res.setHeader('Connection', 'keep-alive')
|
|
366
|
+
res.setHeader('X-Accel-Buffering', 'no')
|
|
367
|
+
if (typeof res.flushHeaders === 'function') res.flushHeaders()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function flushSSE(res: any): void {
|
|
371
|
+
if (typeof res.flush === 'function') {
|
|
372
|
+
try { res.flush() } catch {}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function sendSSE(res: any, payload: unknown): boolean {
|
|
377
|
+
if (res.writableEnded || res.destroyed) return false
|
|
378
|
+
try {
|
|
379
|
+
res.write('data: ' + JSON.stringify(transformResult(payload)) + '\\n\\n')
|
|
380
|
+
flushSSE(res)
|
|
381
|
+
return true
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error('[progressive] failed to send SSE event:', err)
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function sendSSEProgress(res: any, stage: string, completed: number, total: number): boolean {
|
|
389
|
+
return sendSSE(res, { type: 'progress', stage, completed, total })
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function sendSSEField(res: any, key: string, value: unknown): boolean {
|
|
393
|
+
return sendSSE(res, { type: 'field', key, value })
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function sendSSEResult(res: any, data: unknown): boolean {
|
|
397
|
+
return sendSSE(res, { type: 'result', data })
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function sendSSEError(res: any, message: string): boolean {
|
|
401
|
+
if (res.writableEnded || res.destroyed) return false
|
|
402
|
+
try {
|
|
403
|
+
res.write('data: ' + JSON.stringify({ type: 'error', message }) + '\\n\\n')
|
|
404
|
+
flushSSE(res)
|
|
405
|
+
return true
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.error('[progressive] failed to send SSE error event:', err)
|
|
408
|
+
return false
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function startSSEKeepalive(res: any, intervalMs: number = 15000): any {
|
|
413
|
+
const handle = setInterval(() => {
|
|
414
|
+
if (res.writableEnded || res.destroyed) return
|
|
415
|
+
try {
|
|
416
|
+
res.write(': keepalive\\n\\n')
|
|
417
|
+
flushSSE(res)
|
|
418
|
+
} catch {}
|
|
419
|
+
}, intervalMs)
|
|
420
|
+
if (typeof (handle as any).unref === 'function') (handle as any).unref()
|
|
421
|
+
return handle
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function endSSE(res: any, keepaliveHandle: any): void {
|
|
425
|
+
if (keepaliveHandle) {
|
|
426
|
+
try { clearInterval(keepaliveHandle) } catch {}
|
|
427
|
+
}
|
|
428
|
+
if (!res.writableEnded && !res.destroyed) {
|
|
429
|
+
try { res.end() } catch {}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export interface RunSingleResultSSEOptions {
|
|
434
|
+
req: any
|
|
435
|
+
res: any
|
|
436
|
+
coreQueryFn: () => Promise<unknown>
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Promise<void> {
|
|
440
|
+
const { req, res, coreQueryFn } = options
|
|
441
|
+
let keepalive: any = null
|
|
442
|
+
try {
|
|
443
|
+
initSSE(res)
|
|
444
|
+
keepalive = startSSEKeepalive(res)
|
|
445
|
+
if (req.destroyed) return
|
|
446
|
+
const data = await coreQueryFn()
|
|
447
|
+
if (res.writableEnded || res.destroyed) return
|
|
448
|
+
sendSSEResult(res, data)
|
|
449
|
+
} catch (err) {
|
|
450
|
+
console.error('[progressive] single-result error:', err)
|
|
451
|
+
if (!res.writableEnded && !res.destroyed) {
|
|
452
|
+
sendSSEError(res, 'Internal server error')
|
|
453
|
+
}
|
|
454
|
+
} finally {
|
|
455
|
+
endSSE(res, keepalive)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function isStopResult(value: unknown): value is ProgressiveStopResult<unknown> {
|
|
460
|
+
return typeof value === 'object' && value !== null && (value as any).stop === true
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export interface RunProgressiveOptions {
|
|
464
|
+
req: any
|
|
465
|
+
res: any
|
|
466
|
+
ctx: unknown
|
|
467
|
+
prisma: any
|
|
468
|
+
variant: string
|
|
469
|
+
stages: string[]
|
|
470
|
+
stageRegistry: Record<string, ProgressiveStage<any, any>>
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export async function runProgressiveEndpoint(options: RunProgressiveOptions): Promise<void> {
|
|
474
|
+
const { req, res, ctx, prisma, variant, stages, stageRegistry } = options
|
|
475
|
+
let keepalive: any = null
|
|
476
|
+
const controller = new AbortController()
|
|
477
|
+
const onClose = () => controller.abort()
|
|
478
|
+
if (typeof req.on === 'function') req.on('close', onClose)
|
|
479
|
+
|
|
480
|
+
const accumulated: Record<string, unknown> = {}
|
|
481
|
+
const signal = controller.signal
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
initSSE(res)
|
|
485
|
+
keepalive = startSSEKeepalive(res)
|
|
486
|
+
sendSSEProgress(res, 'start', 0, stages.length)
|
|
487
|
+
|
|
488
|
+
for (let i = 0; i < stages.length; i++) {
|
|
489
|
+
if (res.writableEnded || res.destroyed || signal.aborted) return
|
|
490
|
+
const stageName = stages[i]
|
|
491
|
+
const stage = stageRegistry[stageName]
|
|
492
|
+
if (!stage) throw new Error('Missing progressive stage: ' + stageName)
|
|
493
|
+
|
|
494
|
+
const result = await stage({ ctx, req, res, prisma, variant, accumulated, signal })
|
|
495
|
+
if (res.writableEnded || res.destroyed) return
|
|
496
|
+
|
|
497
|
+
if (isStopResult(result)) {
|
|
498
|
+
sendSSEResult(res, result.data)
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const patches = Array.isArray(result) ? result : result ? [result] : []
|
|
503
|
+
for (const patch of patches) {
|
|
504
|
+
if (!patch || typeof patch !== 'object') continue
|
|
505
|
+
if (typeof (patch as any).key !== 'string') continue
|
|
506
|
+
if (!('value' in patch)) continue
|
|
507
|
+
const p = patch as ProgressivePatch
|
|
508
|
+
const applied = setByPath(accumulated, p.key, p.value)
|
|
509
|
+
if (applied) sendSSEField(res, p.key, p.value)
|
|
510
|
+
}
|
|
511
|
+
sendSSEProgress(res, stageName, i + 1, stages.length)
|
|
512
|
+
}
|
|
513
|
+
if (res.writableEnded || res.destroyed) return
|
|
514
|
+
sendSSEResult(res, accumulated)
|
|
515
|
+
} catch (err) {
|
|
516
|
+
console.error('[progressive] stage error:', err)
|
|
517
|
+
if (!res.writableEnded && !res.destroyed) {
|
|
518
|
+
sendSSEError(res, 'Could not load progressive response')
|
|
519
|
+
}
|
|
520
|
+
} finally {
|
|
521
|
+
removeReqCloseListener(req, onClose)
|
|
522
|
+
endSSE(res, keepalive)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
377
525
|
`
|
|
378
526
|
}
|
|
379
527
|
|
|
380
528
|
export interface ModelCoreOptions {
|
|
381
529
|
model: DMMF.Model
|
|
530
|
+
importStyle: ImportStyle
|
|
382
531
|
}
|
|
383
532
|
|
|
384
533
|
export function generateModelCore(options: ModelCoreOptions): string {
|
|
534
|
+
const ext = importExt(options.importStyle)
|
|
385
535
|
const modelName = options.model.name
|
|
386
|
-
const modelNameLower =
|
|
387
|
-
modelName.charAt(0).toLowerCase() + modelName.slice(1)
|
|
536
|
+
const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
|
|
388
537
|
|
|
389
538
|
const standardReadOps = [
|
|
390
|
-
'findFirst',
|
|
391
|
-
'
|
|
392
|
-
'findUniqueOrThrow',
|
|
393
|
-
'findFirstOrThrow',
|
|
394
|
-
'count',
|
|
395
|
-
'aggregate',
|
|
396
|
-
'groupBy',
|
|
539
|
+
'findFirst', 'findUnique', 'findUniqueOrThrow', 'findFirstOrThrow',
|
|
540
|
+
'count', 'aggregate', 'groupBy',
|
|
397
541
|
]
|
|
398
542
|
|
|
399
543
|
const standardReadHandlers = standardReadOps
|
|
400
|
-
.map(
|
|
401
|
-
(op) => `
|
|
544
|
+
.map((op) => `
|
|
402
545
|
export async function ${op}(ctx: OperationContext): Promise<unknown> {
|
|
403
546
|
const query = ctx.parsedQuery || {}
|
|
404
547
|
const extended = await getExtendedClient(ctx)
|
|
@@ -407,53 +550,24 @@ export async function ${op}(ctx: OperationContext): Promise<unknown> {
|
|
|
407
550
|
return (extended as any).${modelNameLower}.guard(ctx.guardShape, ctx.guardCaller).${op}(query)
|
|
408
551
|
}
|
|
409
552
|
return (extended as any).${modelNameLower}.${op}(query)
|
|
410
|
-
}
|
|
411
|
-
)
|
|
553
|
+
}`)
|
|
412
554
|
.join('\n')
|
|
413
555
|
|
|
414
556
|
const writeOps = [
|
|
415
557
|
{ name: 'create', method: 'create', requiredFields: ['data'] },
|
|
416
558
|
{ name: 'createMany', method: 'createMany', requiredFields: ['data'] },
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
name: 'update',
|
|
424
|
-
method: 'update',
|
|
425
|
-
requiredFields: ['where', 'data'],
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: 'updateMany',
|
|
429
|
-
method: 'updateMany',
|
|
430
|
-
requiredFields: ['where', 'data'],
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: 'updateManyAndReturn',
|
|
434
|
-
method: 'updateManyAndReturn',
|
|
435
|
-
requiredFields: ['where', 'data'],
|
|
436
|
-
},
|
|
559
|
+
{ name: 'createManyAndReturn', method: 'createManyAndReturn', requiredFields: ['data'] },
|
|
560
|
+
{ name: 'update', method: 'update', requiredFields: ['where', 'data'] },
|
|
561
|
+
{ name: 'updateMany', method: 'updateMany', requiredFields: ['where', 'data'] },
|
|
562
|
+
{ name: 'updateManyAndReturn', method: 'updateManyAndReturn', requiredFields: ['where', 'data'] },
|
|
437
563
|
{ name: 'deleteUnique', method: 'delete', requiredFields: ['where'] },
|
|
438
|
-
{
|
|
439
|
-
|
|
440
|
-
method: 'deleteMany',
|
|
441
|
-
requiredFields: ['where'],
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
name: 'upsert',
|
|
445
|
-
method: 'upsert',
|
|
446
|
-
requiredFields: ['where', 'create', 'update'],
|
|
447
|
-
},
|
|
564
|
+
{ name: 'deleteMany', method: 'deleteMany', requiredFields: ['where'] },
|
|
565
|
+
{ name: 'upsert', method: 'upsert', requiredFields: ['where', 'create', 'update'] },
|
|
448
566
|
]
|
|
449
567
|
|
|
450
|
-
const writeHandlers = writeOps
|
|
451
|
-
.map((
|
|
452
|
-
|
|
453
|
-
.map((field) => ` requireBodyField(body, '${field}')`)
|
|
454
|
-
.join('\n')
|
|
455
|
-
|
|
456
|
-
return `
|
|
568
|
+
const writeHandlers = writeOps.map((op) => {
|
|
569
|
+
const validationLines = op.requiredFields.map((field) => ` requireBodyField(body, '${field}')`).join('\n')
|
|
570
|
+
return `
|
|
457
571
|
export async function ${op.name}(ctx: OperationContext): Promise<unknown> {
|
|
458
572
|
const body = validateBody(ctx.body)
|
|
459
573
|
${validationLines}
|
|
@@ -464,8 +578,7 @@ ${validationLines}
|
|
|
464
578
|
}
|
|
465
579
|
return (extended as any).${modelNameLower}.${op.method}(body)
|
|
466
580
|
}`
|
|
467
|
-
|
|
468
|
-
.join('\n')
|
|
581
|
+
}).join('\n')
|
|
469
582
|
|
|
470
583
|
return `import {
|
|
471
584
|
OperationContext,
|
|
@@ -475,7 +588,7 @@ ${validationLines}
|
|
|
475
588
|
applyPaginationLimits,
|
|
476
589
|
assertGuard,
|
|
477
590
|
countForPagination,
|
|
478
|
-
} from '../operationRuntime'
|
|
591
|
+
} from '../operationRuntime${ext}'
|
|
479
592
|
|
|
480
593
|
export async function findMany(ctx: OperationContext): Promise<unknown> {
|
|
481
594
|
const rawQuery = ctx.parsedQuery || {}
|
|
@@ -501,18 +614,14 @@ export async function findManyPaginated(
|
|
|
501
614
|
const distinctCountLimit = ctx.paginationConfig?.distinctCountLimit
|
|
502
615
|
const delegate = (extended as any).${modelNameLower}
|
|
503
616
|
|
|
504
|
-
if (shape)
|
|
505
|
-
assertGuard(delegate)
|
|
506
|
-
}
|
|
617
|
+
if (shape) assertGuard(delegate)
|
|
507
618
|
|
|
508
619
|
let items: any[]
|
|
509
620
|
let total: number
|
|
510
621
|
|
|
511
622
|
if (shape || typeof extended.$transaction !== 'function') {
|
|
512
623
|
const [data, count] = await Promise.all([
|
|
513
|
-
shape
|
|
514
|
-
? delegate.guard(shape, caller).findMany(query)
|
|
515
|
-
: delegate.findMany(query),
|
|
624
|
+
shape ? delegate.guard(shape, caller).findMany(query) : delegate.findMany(query),
|
|
516
625
|
countForPagination(delegate, query, shape, caller, distinctCountLimit),
|
|
517
626
|
])
|
|
518
627
|
items = data
|
|
@@ -527,13 +636,8 @@ export async function findManyPaginated(
|
|
|
527
636
|
items = txResult.d
|
|
528
637
|
total = txResult.t
|
|
529
638
|
} catch (txError: any) {
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
txError?.code === 'P2028'
|
|
533
|
-
) {
|
|
534
|
-
console.warn(
|
|
535
|
-
'[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic',
|
|
536
|
-
)
|
|
639
|
+
if (txError?.message?.includes?.('interactive transactions') || txError?.code === 'P2028') {
|
|
640
|
+
console.warn('[prisma-generator-express] Interactive transactions not available, pagination queries are non-atomic')
|
|
537
641
|
items = await delegate.findMany(query)
|
|
538
642
|
total = await countForPagination(delegate, query, undefined, undefined, distinctCountLimit)
|
|
539
643
|
} else {
|