prisma-generator-express 1.66.0 → 1.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -3432
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +4 -1
- package/dist/copy/operationDefinitions.js +133 -9
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +68 -12
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +53 -6
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateHonoOpenApi.d.ts +13 -0
- package/dist/generators/generateHonoOpenApi.js +70 -0
- package/dist/generators/generateHonoOpenApi.js.map +1 -0
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateMcpApp.d.ts +10 -0
- package/dist/generators/generateMcpApp.js +113 -0
- package/dist/generators/generateMcpApp.js.map +1 -0
- package/dist/generators/generateModelMcp.d.ts +9 -0
- package/dist/generators/generateModelMcp.js +145 -0
- package/dist/generators/generateModelMcp.js.map +1 -0
- package/dist/generators/generateModelMetadata.js +29 -15
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +164 -65
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +106 -86
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +59 -414
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateRouterPartsHono.d.ts +14 -0
- package/dist/generators/generateRouterPartsHono.js +180 -0
- package/dist/generators/generateRouterPartsHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +42 -18
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +123 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +1 -1
- package/dist/utils/copyFiles.js +42 -7
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/mcpGate.d.ts +10 -0
- package/dist/utils/mcpGate.js +118 -0
- package/dist/utils/mcpGate.js.map +1 -0
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js +6 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +22 -5
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +229 -176
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/docsRenderer.ts +14 -15
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/mcpRuntime.ts +1017 -0
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +177 -25
- package/src/copy/operationPipeline.ts +267 -0
- package/src/copy/operationRuntime.ts +124 -26
- package/src/copy/operationSchemas.ts +2773 -0
- package/src/copy/pagination.ts +35 -13
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +46 -14
- package/src/copy/routeConfig.ts +175 -40
- package/src/copy/routerParts.ts +524 -0
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +77 -20
- package/src/generators/generateHonoHandler.ts +62 -14
- package/src/generators/generateHonoOpenApi.ts +93 -0
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateMcpApp.ts +133 -0
- package/src/generators/generateModelMcp.ts +208 -0
- package/src/generators/generateModelMetadata.ts +40 -22
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +171 -68
- package/src/generators/generateRouterFastify.ts +117 -89
- package/src/generators/generateRouterHono.ts +84 -464
- package/src/generators/generateRouterPartsHono.ts +220 -0
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +47 -24
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +161 -6
- package/src/utils/copyFiles.ts +235 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/mcpGate.ts +157 -0
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +12 -2
package/src/copy/concurrency.ts
CHANGED
|
@@ -8,13 +8,15 @@ export async function mapLimited<T>(
|
|
|
8
8
|
const workerCount = Math.min(limit, items.length)
|
|
9
9
|
const workers: Promise<void>[] = []
|
|
10
10
|
for (let w = 0; w < workerCount; w++) {
|
|
11
|
-
workers.push(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
workers.push(
|
|
12
|
+
(async () => {
|
|
13
|
+
for (;;) {
|
|
14
|
+
const i = index++
|
|
15
|
+
if (i >= items.length) return
|
|
16
|
+
await fn(items[i], i)
|
|
17
|
+
}
|
|
18
|
+
})(),
|
|
19
|
+
)
|
|
18
20
|
}
|
|
19
21
|
await Promise.all(workers)
|
|
20
|
-
}
|
|
22
|
+
}
|
package/src/copy/docsRenderer.ts
CHANGED
|
@@ -127,7 +127,7 @@ function exampleValue(ctx: DocsModelContext, fieldName: string): unknown {
|
|
|
127
127
|
|
|
128
128
|
function compoundWhereExample(
|
|
129
129
|
ctx: DocsModelContext,
|
|
130
|
-
): Record<string,
|
|
130
|
+
): Record<string, unknown> | null {
|
|
131
131
|
if (ctx.compoundId) {
|
|
132
132
|
const keyName = ctx.compoundId.fields.join('_')
|
|
133
133
|
const val: Record<string, unknown> = {}
|
|
@@ -426,7 +426,7 @@ export function renderDocs(
|
|
|
426
426
|
const singleRelations = relationFields.filter((f) => !f.isList)
|
|
427
427
|
|
|
428
428
|
const getOps = OPERATION_METADATA.filter((meta) =>
|
|
429
|
-
isOperationEnabled(config
|
|
429
|
+
isOperationEnabled(config, meta),
|
|
430
430
|
)
|
|
431
431
|
.filter((meta) => !isOpHiddenByStrategy(meta.name, writeStrategy))
|
|
432
432
|
.map((meta) => {
|
|
@@ -453,9 +453,7 @@ export function renderDocs(
|
|
|
453
453
|
|
|
454
454
|
const postReadOps = postReadsEnabled
|
|
455
455
|
? OPERATION_METADATA.filter((meta) => READ_OPERATION_NAMES.has(meta.name))
|
|
456
|
-
.filter((meta) =>
|
|
457
|
-
isOperationEnabled(config as Record<string, any>, meta),
|
|
458
|
-
)
|
|
456
|
+
.filter((meta) => isOperationEnabled(config, meta))
|
|
459
457
|
.map((meta) => {
|
|
460
458
|
const detail = detailForOp(meta.name)
|
|
461
459
|
const postPath =
|
|
@@ -499,8 +497,8 @@ export function renderDocs(
|
|
|
499
497
|
const firstStringField = scalarFields.find((f) => f.type === 'String')
|
|
500
498
|
const firstBooleanField = scalarFields.find((f) => f.type === 'Boolean')
|
|
501
499
|
|
|
502
|
-
const whereExample: Record<string,
|
|
503
|
-
const andClauses: Record<string,
|
|
500
|
+
const whereExample: Record<string, unknown> = {}
|
|
501
|
+
const andClauses: Record<string, unknown>[] = []
|
|
504
502
|
if (firstFilterFieldName) {
|
|
505
503
|
andClauses.push({
|
|
506
504
|
[firstFilterFieldName]: {
|
|
@@ -517,19 +515,19 @@ export function renderDocs(
|
|
|
517
515
|
if (firstBooleanField)
|
|
518
516
|
whereExample.OR = [{ [firstBooleanField.name]: { equals: true } }]
|
|
519
517
|
|
|
520
|
-
const selectExample:
|
|
518
|
+
const selectExample: Record<string, unknown> = {}
|
|
521
519
|
for (const f of scalarFields.slice(0, 10)) selectExample[f.name] = true
|
|
522
520
|
|
|
523
|
-
const includeExample:
|
|
521
|
+
const includeExample: Record<string, unknown> = {}
|
|
524
522
|
for (const f of relationFields.slice(0, 6)) includeExample[f.name] = true
|
|
525
523
|
|
|
526
|
-
const omitExample:
|
|
524
|
+
const omitExample: Record<string, unknown> = {}
|
|
527
525
|
const omitCandidates = scalarFields.filter((f) => !f.isId && !f.isUnique)
|
|
528
526
|
for (const f of omitCandidates.slice(0, 3)) omitExample[f.name] = true
|
|
529
527
|
|
|
530
528
|
const orderByField = firstUnique ? firstUnique.name : firstFilterFieldName
|
|
531
529
|
|
|
532
|
-
const findManyQueryArgs:
|
|
530
|
+
const findManyQueryArgs: Record<string, unknown> = {
|
|
533
531
|
where: whereExample,
|
|
534
532
|
select: selectExample,
|
|
535
533
|
orderBy: orderByField ? { [orderByField]: 'asc' } : undefined,
|
|
@@ -575,7 +573,7 @@ export function renderDocs(
|
|
|
575
573
|
'const data = await res.json()'
|
|
576
574
|
: null
|
|
577
575
|
|
|
578
|
-
const createBodyExample:
|
|
576
|
+
const createBodyExample: { data: Record<string, unknown> } = { data: {} }
|
|
579
577
|
for (const f of requiredCreateFields.slice(0, 5)) {
|
|
580
578
|
createBodyExample.data[f.name] = exampleValue(ctx, f.name)
|
|
581
579
|
}
|
|
@@ -592,9 +590,10 @@ export function renderDocs(
|
|
|
592
590
|
'})\n' +
|
|
593
591
|
'const created = await res.json()'
|
|
594
592
|
|
|
595
|
-
const updateBodyExample:
|
|
596
|
-
|
|
597
|
-
:
|
|
593
|
+
const updateBodyExample: {
|
|
594
|
+
where: Record<string, unknown>
|
|
595
|
+
data: Record<string, unknown>
|
|
596
|
+
} | null = uniqueWhereExample ? { where: uniqueWhereExample, data: {} } : null
|
|
598
597
|
if (updateBodyExample) {
|
|
599
598
|
const firstEditableString = scalarFields.find(
|
|
600
599
|
(sf) => sf.type === 'String' && !sf.isId,
|
package/src/copy/errorMapper.ts
CHANGED
|
@@ -36,10 +36,19 @@ const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
|
|
|
36
36
|
P2023: { status: 500, message: 'Inconsistent column data' },
|
|
37
37
|
P2024: { status: 503, message: 'Connection pool timeout' },
|
|
38
38
|
P2025: { status: 404, message: 'Record not found' },
|
|
39
|
-
P2026: {
|
|
40
|
-
|
|
39
|
+
P2026: {
|
|
40
|
+
status: 501,
|
|
41
|
+
message: 'Feature not supported by the current database provider',
|
|
42
|
+
},
|
|
43
|
+
P2027: {
|
|
44
|
+
status: 400,
|
|
45
|
+
message: 'Multiple errors occurred during transaction execution',
|
|
46
|
+
},
|
|
41
47
|
P2028: { status: 500, message: 'Transaction API error' },
|
|
42
|
-
P2030: {
|
|
48
|
+
P2030: {
|
|
49
|
+
status: 400,
|
|
50
|
+
message: 'Cannot find a fulltext index for the search',
|
|
51
|
+
},
|
|
43
52
|
P2033: { status: 400, message: 'Number out of range for the field type' },
|
|
44
53
|
P2034: { status: 409, message: 'Transaction conflict, please retry' },
|
|
45
54
|
}
|
|
@@ -59,11 +68,20 @@ function asErrorShape(error: unknown): ErrorShape {
|
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
function isProduction(): boolean {
|
|
62
|
-
return
|
|
71
|
+
return (
|
|
72
|
+
typeof process !== 'undefined' &&
|
|
73
|
+
process.env &&
|
|
74
|
+
process.env.NODE_ENV === 'production'
|
|
75
|
+
)
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
function isHttpStatus(value: unknown): value is number {
|
|
66
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
typeof value === 'number' &&
|
|
81
|
+
Number.isInteger(value) &&
|
|
82
|
+
value >= 400 &&
|
|
83
|
+
value <= 599
|
|
84
|
+
)
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
export function mapError(error: unknown): HttpError {
|
|
@@ -77,18 +95,27 @@ export function mapError(error: unknown): HttpError {
|
|
|
77
95
|
? e.statusCode
|
|
78
96
|
: null
|
|
79
97
|
if (duckStatus !== null) {
|
|
80
|
-
const detail =
|
|
98
|
+
const detail =
|
|
99
|
+
typeof e.message === 'string' && e.message
|
|
100
|
+
? e.message
|
|
101
|
+
: 'Internal server error'
|
|
81
102
|
return new HttpError(duckStatus, detail)
|
|
82
103
|
}
|
|
83
104
|
|
|
84
|
-
if (e.name === 'ShapeError')
|
|
85
|
-
|
|
86
|
-
if (e.name === '
|
|
105
|
+
if (e.name === 'ShapeError')
|
|
106
|
+
return new HttpError(400, e.message || 'Shape validation failed')
|
|
107
|
+
if (e.name === 'CallerError')
|
|
108
|
+
return new HttpError(400, e.message || 'Caller validation failed')
|
|
109
|
+
if (e.name === 'PolicyError')
|
|
110
|
+
return new HttpError(403, e.message || 'Policy denied')
|
|
87
111
|
if (e.name === 'ZodError') {
|
|
88
112
|
const issues = e.issues
|
|
89
113
|
const message = Array.isArray(issues)
|
|
90
|
-
? (issues as Array<{ message?: string }>)
|
|
91
|
-
|
|
114
|
+
? (issues as Array<{ message?: string }>)
|
|
115
|
+
.map((i) => i.message ?? '')
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join('; ')
|
|
118
|
+
: e.message || 'Validation failed'
|
|
92
119
|
return new HttpError(400, message)
|
|
93
120
|
}
|
|
94
121
|
if (typeof e.code === 'string') {
|
|
@@ -98,7 +125,9 @@ export function mapError(error: unknown): HttpError {
|
|
|
98
125
|
const shouldStripDetail = isProd && mapped.status >= 500
|
|
99
126
|
return new HttpError(
|
|
100
127
|
mapped.status,
|
|
101
|
-
!shouldStripDetail && detail
|
|
128
|
+
!shouldStripDetail && detail
|
|
129
|
+
? mapped.message + ': ' + detail
|
|
130
|
+
: mapped.message,
|
|
102
131
|
)
|
|
103
132
|
}
|
|
104
133
|
if (e.code.startsWith('P')) {
|
|
@@ -108,19 +137,39 @@ export function mapError(error: unknown): HttpError {
|
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
139
|
if (typeof e.name === 'string') {
|
|
111
|
-
if (e.name === 'PrismaClientValidationError')
|
|
112
|
-
|
|
140
|
+
if (e.name === 'PrismaClientValidationError')
|
|
141
|
+
return new HttpError(400, e.message || 'Invalid query parameters')
|
|
142
|
+
if (e.name === 'PrismaClientKnownRequestError')
|
|
143
|
+
return new HttpError(400, e.message || 'Database request error')
|
|
113
144
|
if (e.name === 'PrismaClientInitializationError') {
|
|
114
|
-
return new HttpError(
|
|
145
|
+
return new HttpError(
|
|
146
|
+
503,
|
|
147
|
+
isProd
|
|
148
|
+
? 'Service unavailable'
|
|
149
|
+
: e.message || 'Database connection failed',
|
|
150
|
+
)
|
|
115
151
|
}
|
|
116
152
|
if (e.name === 'PrismaClientRustPanicError') {
|
|
117
|
-
return new HttpError(
|
|
153
|
+
return new HttpError(
|
|
154
|
+
500,
|
|
155
|
+
isProd
|
|
156
|
+
? 'Internal server error'
|
|
157
|
+
: e.message || 'Internal database engine error',
|
|
158
|
+
)
|
|
118
159
|
}
|
|
119
160
|
if (e.name === 'PrismaClientUnknownRequestError') {
|
|
120
|
-
return new HttpError(
|
|
161
|
+
return new HttpError(
|
|
162
|
+
500,
|
|
163
|
+
isProd
|
|
164
|
+
? 'Internal server error'
|
|
165
|
+
: e.message || 'Unknown database error',
|
|
166
|
+
)
|
|
121
167
|
}
|
|
122
168
|
}
|
|
123
169
|
const msg = error instanceof Error ? error.message : String(error)
|
|
124
170
|
console.error(LOG_PREFIX, 'Unhandled error:', error)
|
|
125
|
-
return new HttpError(
|
|
126
|
-
|
|
171
|
+
return new HttpError(
|
|
172
|
+
500,
|
|
173
|
+
isProd ? 'Internal server error' : msg || 'Internal server error',
|
|
174
|
+
)
|
|
175
|
+
}
|
package/src/copy/guardHelpers.ts
CHANGED
|
@@ -3,12 +3,17 @@ import { HttpError } from './errorMapper'
|
|
|
3
3
|
export type PrismaDelegateLike = {
|
|
4
4
|
count: (args?: unknown) => Promise<unknown>
|
|
5
5
|
findMany: (args?: unknown) => Promise<unknown>
|
|
6
|
-
guard?: (
|
|
6
|
+
guard?: (
|
|
7
|
+
shape: Record<string, unknown>,
|
|
8
|
+
caller?: string,
|
|
9
|
+
) => PrismaDelegateLike
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
export function assertGuard(
|
|
10
|
-
delegate:
|
|
11
|
-
): asserts delegate is
|
|
12
|
+
export function assertGuard<T extends PrismaDelegateLike>(
|
|
13
|
+
delegate: T,
|
|
14
|
+
): asserts delegate is T & {
|
|
15
|
+
guard: NonNullable<T['guard']>
|
|
16
|
+
} {
|
|
12
17
|
if (typeof delegate.guard !== 'function') {
|
|
13
18
|
throw new HttpError(
|
|
14
19
|
500,
|
|
@@ -18,9 +23,24 @@ export function assertGuard(
|
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export const GUARD_SHAPE_CONFIG_KEYS = new Set([
|
|
21
|
-
'data',
|
|
22
|
-
'
|
|
23
|
-
'
|
|
26
|
+
'data',
|
|
27
|
+
'create',
|
|
28
|
+
'update',
|
|
29
|
+
'where',
|
|
30
|
+
'include',
|
|
31
|
+
'select',
|
|
32
|
+
'orderBy',
|
|
33
|
+
'cursor',
|
|
34
|
+
'take',
|
|
35
|
+
'skip',
|
|
36
|
+
'distinct',
|
|
37
|
+
'having',
|
|
38
|
+
'_count',
|
|
39
|
+
'_avg',
|
|
40
|
+
'_sum',
|
|
41
|
+
'_min',
|
|
42
|
+
'_max',
|
|
43
|
+
'by',
|
|
24
44
|
])
|
|
25
45
|
|
|
26
46
|
function keepWhereOnly(obj: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -39,7 +59,8 @@ export function buildCountShape(
|
|
|
39
59
|
return (...args: unknown[]) => keepWhereOnly(fn(...args))
|
|
40
60
|
}
|
|
41
61
|
const keys = Object.keys(shape)
|
|
42
|
-
const isSingleShape =
|
|
62
|
+
const isSingleShape =
|
|
63
|
+
keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
|
|
43
64
|
if (isSingleShape) return keepWhereOnly(shape)
|
|
44
65
|
const result: Record<string, unknown> = {}
|
|
45
66
|
for (const [key, variant] of Object.entries(shape)) {
|
|
@@ -53,4 +74,4 @@ export function buildCountShape(
|
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
76
|
return result
|
|
56
|
-
}
|
|
77
|
+
}
|
|
@@ -4,7 +4,10 @@ import type { PaginationCountSource } from './routeConfig'
|
|
|
4
4
|
export const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
5
5
|
|
|
6
6
|
type PrismaRawClient = {
|
|
7
|
-
$queryRawUnsafe?: <T = unknown>(
|
|
7
|
+
$queryRawUnsafe?: <T = unknown>(
|
|
8
|
+
sql: string,
|
|
9
|
+
...values: unknown[]
|
|
10
|
+
) => Promise<T>
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export function quoteIdent(name: string): string {
|
|
@@ -47,7 +50,10 @@ export async function countFromMaterializedView(
|
|
|
47
50
|
): Promise<number> {
|
|
48
51
|
const raw = client as PrismaRawClient
|
|
49
52
|
if (typeof raw.$queryRawUnsafe !== 'function') {
|
|
50
|
-
throw new HttpError(
|
|
53
|
+
throw new HttpError(
|
|
54
|
+
500,
|
|
55
|
+
'Materialized count source requires $queryRawUnsafe on the Prisma client',
|
|
56
|
+
)
|
|
51
57
|
}
|
|
52
58
|
const column = source.column ?? 'total'
|
|
53
59
|
const where = buildMaterializedCountWhere(source.where)
|
|
@@ -58,11 +64,17 @@ export async function countFromMaterializedView(
|
|
|
58
64
|
buildMaterializedCountFqn(source) +
|
|
59
65
|
where.sql +
|
|
60
66
|
' LIMIT 1'
|
|
61
|
-
const rows = await raw.$queryRawUnsafe<Array<{ total: unknown }>>(
|
|
67
|
+
const rows = await raw.$queryRawUnsafe<Array<{ total: unknown }>>(
|
|
68
|
+
sql,
|
|
69
|
+
...where.values,
|
|
70
|
+
)
|
|
62
71
|
const value = rows[0]?.total
|
|
63
72
|
const total = Number(value)
|
|
64
73
|
if (!Number.isFinite(total)) {
|
|
65
|
-
throw new HttpError(
|
|
74
|
+
throw new HttpError(
|
|
75
|
+
500,
|
|
76
|
+
'Materialized count source did not return a numeric total',
|
|
77
|
+
)
|
|
66
78
|
}
|
|
67
79
|
return Math.trunc(total)
|
|
68
|
-
}
|
|
80
|
+
}
|