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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { HttpError, mapError } from './errorMapper'
|
|
2
|
+
import { formatGuardVariantResolutionError } from './guardVariantError'
|
|
3
|
+
import { isPlainObject } from './misc'
|
|
4
|
+
import {
|
|
5
|
+
applyDroppedGuard,
|
|
6
|
+
type ContextResolver,
|
|
7
|
+
type OpKind,
|
|
8
|
+
} from './projectionDefaults'
|
|
9
|
+
import {
|
|
10
|
+
resolveOperationVariantKey,
|
|
11
|
+
resolveGuardShapeOnce,
|
|
12
|
+
type NormalizedGuardRouting,
|
|
13
|
+
type PaginationConfig,
|
|
14
|
+
type FindManyPaginatedMode,
|
|
15
|
+
} from './routeConfig'
|
|
16
|
+
import type { GuardVariantResolution } from './guardVariantRouting'
|
|
17
|
+
import type {
|
|
18
|
+
OperationContext,
|
|
19
|
+
PrismaClientLike,
|
|
20
|
+
RuntimeOperationOverride,
|
|
21
|
+
} from './operationRuntime'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Transport-neutral orchestration shared by the Express, Fastify, Hono and MCP
|
|
25
|
+
* emitters. Five stages: route, prepareGuard, settle, execute, classifyError.
|
|
26
|
+
*
|
|
27
|
+
* The pipeline never reads a request, a header or an authentication. Adapters
|
|
28
|
+
* resolve the caller themselves and pass it in; `caller` is a routing key for
|
|
29
|
+
* variant selection only, never an identity.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Resolves application context at most once per request. */
|
|
33
|
+
export type GetContext<T = unknown> = () => Promise<T>
|
|
34
|
+
|
|
35
|
+
export function memoizeContext<T>(): GetContext<T>
|
|
36
|
+
export function memoizeContext<T>(resolver: () => T | undefined): GetContext<T>
|
|
37
|
+
export function memoizeContext(resolver?: () => unknown): GetContext {
|
|
38
|
+
let cached: Promise<unknown> | undefined
|
|
39
|
+
return () => (cached ??= Promise.resolve(resolver?.()))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One request-scoped argument channel. Backed by adapter request storage
|
|
44
|
+
* (Express `res.locals.parsedQuery` / `req.body`, the Fastify request, Hono
|
|
45
|
+
* context vars). Dropped-guard transformations, framework hooks, overrides and
|
|
46
|
+
* execution all observe the same storage through it, so guard transforms
|
|
47
|
+
* cannot be dropped between stages.
|
|
48
|
+
*
|
|
49
|
+
* `read()` returns the STORED value — `undefined` when nothing was stored —
|
|
50
|
+
* exactly like the storage it fronts; mutation of a stored object is visible
|
|
51
|
+
* to every later stage.
|
|
52
|
+
*/
|
|
53
|
+
export type ArgsChannel = {
|
|
54
|
+
read(): Record<string, unknown> | undefined
|
|
55
|
+
write(next: Record<string, unknown>): void
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runtime narrowing for the adapter boundary: request bags store the client
|
|
60
|
+
* as `unknown` (Express `req.prisma`, the Fastify request, Hono vars), and
|
|
61
|
+
* this is the ONE place that narrows it to the structural client contract the
|
|
62
|
+
* pipeline types require. Missing client is the classic deployment fault —
|
|
63
|
+
* classified 500, same message the operation core raises later.
|
|
64
|
+
*/
|
|
65
|
+
export function requirePrisma(value: unknown): PrismaClientLike {
|
|
66
|
+
if (value === null || typeof value !== 'object') {
|
|
67
|
+
throw new HttpError(
|
|
68
|
+
500,
|
|
69
|
+
'PrismaClient not found on request. Set req.prisma in middleware.',
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
return value as PrismaClientLike
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Ensure-style accessor over a channel: materialises and stores empties. */
|
|
76
|
+
export function ensureArgs(channel: ArgsChannel): Record<string, unknown> {
|
|
77
|
+
const current = channel.read()
|
|
78
|
+
if (current) return current
|
|
79
|
+
const fresh: Record<string, unknown> = {}
|
|
80
|
+
channel.write(fresh)
|
|
81
|
+
return fresh
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Minimal execution policy; `GuardPolicy` itself carries target defaults. */
|
|
85
|
+
export type PipelinePolicy = {
|
|
86
|
+
dropGuard: boolean
|
|
87
|
+
validateResolvedShapes: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type RouteStageOk = {
|
|
91
|
+
ok: true
|
|
92
|
+
variantKey: string | undefined
|
|
93
|
+
caller: string | undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type RouteStageResult = RouteStageOk | { ok: false; failure: HttpError }
|
|
97
|
+
|
|
98
|
+
export type GuardStageOk = {
|
|
99
|
+
ok: true
|
|
100
|
+
guardShape: Record<string, unknown> | undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type GuardStageResult = GuardStageOk | { ok: false; failure: HttpError }
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Variant routing. Synchronous; no application context, no Prisma. A variant
|
|
107
|
+
* failure is stored already classified as 400, so `settleStage` rethrows the
|
|
108
|
+
* same classified error wherever the adapter chooses to settle it.
|
|
109
|
+
*/
|
|
110
|
+
export function routeOperation(input: {
|
|
111
|
+
guardRouting: NormalizedGuardRouting
|
|
112
|
+
caller: string | undefined
|
|
113
|
+
}): RouteStageResult {
|
|
114
|
+
const resolution: GuardVariantResolution = resolveOperationVariantKey(
|
|
115
|
+
input.guardRouting,
|
|
116
|
+
input.caller,
|
|
117
|
+
)
|
|
118
|
+
if (!resolution.ok) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
failure: new HttpError(
|
|
122
|
+
400,
|
|
123
|
+
formatGuardVariantResolutionError(resolution),
|
|
124
|
+
),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const variantKey =
|
|
128
|
+
input.guardRouting.kind === 'named' ? resolution.key : undefined
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
variantKey,
|
|
132
|
+
caller: typeof input.caller === 'string' ? input.caller : undefined,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Guard-shape preparation. Accepts only a successful route result at the type
|
|
138
|
+
* level. Shape handling preserves upstream behaviour: default passes the raw
|
|
139
|
+
* shape through unresolved; resolution happens only when the policy asks for
|
|
140
|
+
* it (`validateResolvedShapes`, always on for MCP) or the guard is dropped
|
|
141
|
+
* (local apply, written through the argument channels).
|
|
142
|
+
*
|
|
143
|
+
* Resolved-shape failures are stored classified as 500; `settleStage`
|
|
144
|
+
* rethrows them unchanged.
|
|
145
|
+
*/
|
|
146
|
+
export async function prepareGuardOperation(
|
|
147
|
+
routed: RouteStageOk,
|
|
148
|
+
input: {
|
|
149
|
+
guardShape: Record<string, unknown> | undefined
|
|
150
|
+
opKind: OpKind
|
|
151
|
+
policy: PipelinePolicy
|
|
152
|
+
getContext: ContextResolver | undefined
|
|
153
|
+
args: ArgsChannel
|
|
154
|
+
writeArgs: ArgsChannel | undefined
|
|
155
|
+
},
|
|
156
|
+
): Promise<GuardStageResult> {
|
|
157
|
+
const { guardShape } = input
|
|
158
|
+
if (!guardShape) return { ok: true, guardShape: undefined }
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
if (input.policy.dropGuard) {
|
|
162
|
+
await applyDroppedGuard(
|
|
163
|
+
guardShape,
|
|
164
|
+
routed.variantKey,
|
|
165
|
+
input.getContext,
|
|
166
|
+
input.opKind,
|
|
167
|
+
{
|
|
168
|
+
readQuery: input.args.read(),
|
|
169
|
+
writeBody: input.writeArgs?.read(),
|
|
170
|
+
},
|
|
171
|
+
() => ensureArgs(input.args),
|
|
172
|
+
input.writeArgs
|
|
173
|
+
? () => ensureArgs(input.writeArgs as ArgsChannel)
|
|
174
|
+
: () => {
|
|
175
|
+
throw new HttpError(
|
|
176
|
+
500,
|
|
177
|
+
'dropped guard requires a write argument channel for this operation',
|
|
178
|
+
)
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
return { ok: true, guardShape: undefined }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let effectiveShape: Record<string, unknown> = guardShape
|
|
185
|
+
if (input.policy.validateResolvedShapes) {
|
|
186
|
+
const resolution = await resolveGuardShapeOnce(
|
|
187
|
+
guardShape,
|
|
188
|
+
routed.variantKey,
|
|
189
|
+
input.getContext,
|
|
190
|
+
)
|
|
191
|
+
if (!resolution.ok) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
failure: new HttpError(
|
|
195
|
+
500,
|
|
196
|
+
'guard shape could not be resolved: ' + resolution.problem,
|
|
197
|
+
),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
effectiveShape = isPlainObject(resolution.shape)
|
|
201
|
+
? resolution.shape
|
|
202
|
+
: guardShape
|
|
203
|
+
}
|
|
204
|
+
return { ok: true, guardShape: effectiveShape }
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return { ok: false, failure: mapError(error) }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Rethrows the stored classified failure without reclassification. Placement
|
|
212
|
+
* is the adapter's behavioural choice (default order, Hono `before-hooks`,
|
|
213
|
+
* MCP before authorize continuation).
|
|
214
|
+
*/
|
|
215
|
+
export function settleStage(
|
|
216
|
+
result: RouteStageResult | GuardStageResult,
|
|
217
|
+
): asserts result is RouteStageOk | GuardStageOk {
|
|
218
|
+
if (!result.ok) throw result.failure
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Shared error classification; encoding stays per transport. */
|
|
222
|
+
export function classifyError(error: unknown): HttpError {
|
|
223
|
+
return mapError(error)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type ExecuteDeps<TPrisma extends PrismaClientLike = PrismaClientLike> = {
|
|
227
|
+
core: (ctx: OperationContext) => Promise<unknown>
|
|
228
|
+
args: ArgsChannel
|
|
229
|
+
body?: unknown
|
|
230
|
+
/** The caller's client; typed so `TPrisma` flows through the shared stage. */
|
|
231
|
+
prisma: TPrisma
|
|
232
|
+
postgres?: unknown
|
|
233
|
+
sqlite?: unknown
|
|
234
|
+
pagination?: PaginationConfig
|
|
235
|
+
override?: RuntimeOperationOverride
|
|
236
|
+
getContext: (() => unknown | Promise<unknown>) | undefined
|
|
237
|
+
findManyPaginatedMode?: FindManyPaginatedMode
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Execution. Reads the argument channel at call time, so every write the
|
|
242
|
+
* dropped-guard transformation made is what reaches the operation core — the
|
|
243
|
+
* same core, pagination, override and transformResult path REST uses.
|
|
244
|
+
*/
|
|
245
|
+
export async function executeOperation<
|
|
246
|
+
TPrisma extends PrismaClientLike = PrismaClientLike,
|
|
247
|
+
>(
|
|
248
|
+
routed: { variantKey?: string; caller?: string },
|
|
249
|
+
guard: { guardShape?: Record<string, unknown> },
|
|
250
|
+
deps: ExecuteDeps<TPrisma>,
|
|
251
|
+
): Promise<unknown> {
|
|
252
|
+
const ctx: OperationContext = {
|
|
253
|
+
prisma: deps.prisma,
|
|
254
|
+
postgres: deps.postgres,
|
|
255
|
+
sqlite: deps.sqlite,
|
|
256
|
+
parsedQuery: deps.args.read() ?? {},
|
|
257
|
+
body: deps.body,
|
|
258
|
+
guardShape: guard.guardShape,
|
|
259
|
+
guardCaller: routed.caller,
|
|
260
|
+
guardVariantKey: routed.variantKey,
|
|
261
|
+
paginationConfig: deps.pagination,
|
|
262
|
+
operationOverride: deps.override,
|
|
263
|
+
resolveOperationContext: deps.getContext,
|
|
264
|
+
findManyPaginatedMode: deps.findManyPaginatedMode,
|
|
265
|
+
}
|
|
266
|
+
return deps.core(ctx)
|
|
267
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveGuardShapeOnce } from './routeConfig'
|
|
2
|
+
import { assertGuard } from './guardHelpers'
|
|
1
3
|
import { HttpError, LOG_PREFIX } from './errorMapper'
|
|
2
4
|
import { sanitizeKeys, isPlainObject } from './misc'
|
|
3
5
|
import type {
|
|
@@ -24,12 +26,15 @@ export type {
|
|
|
24
26
|
|
|
25
27
|
export interface OperationContext {
|
|
26
28
|
prisma: unknown
|
|
29
|
+
operationOverride?: RuntimeOperationOverride
|
|
30
|
+
resolveOperationContext?: () => unknown | Promise<unknown>
|
|
27
31
|
postgres?: unknown
|
|
28
32
|
sqlite?: unknown
|
|
29
33
|
parsedQuery?: Record<string, unknown>
|
|
30
34
|
body?: unknown
|
|
31
35
|
guardShape?: Record<string, unknown>
|
|
32
36
|
guardCaller?: string
|
|
37
|
+
guardVariantKey?: string
|
|
33
38
|
paginationConfig?: PaginationConfig
|
|
34
39
|
findManyPaginatedMode?: FindManyPaginatedMode
|
|
35
40
|
}
|
|
@@ -60,34 +65,53 @@ export type PrismaClientLike = {
|
|
|
60
65
|
$transaction?: <T>(fn: (tx: PrismaClientLike) => Promise<T>) => Promise<T>
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
type SpeedExtensionFactory = (opts: {
|
|
68
|
+
type SpeedExtensionFactory = (opts: {
|
|
69
|
+
postgres?: unknown
|
|
70
|
+
sqlite?: unknown
|
|
71
|
+
debug?: boolean
|
|
72
|
+
}) => unknown
|
|
64
73
|
|
|
65
74
|
let _speedExtension: SpeedExtensionFactory | null = null
|
|
75
|
+
let _prismasqlReady: Promise<void> | undefined
|
|
66
76
|
|
|
67
77
|
const _prismasqlModule = 'prisma-' + 'sql'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Loaded on FIRST USE, not at module load: the copied runtime stays free of
|
|
81
|
+
* top-level side effects, so a bundler can evaluate the import graph without
|
|
82
|
+
* triggering the optional `prisma-sql` lookup.
|
|
83
|
+
*/
|
|
84
|
+
function ensurePrismaSqlReady(): Promise<void> {
|
|
85
|
+
return (_prismasqlReady ??= (async () => {
|
|
86
|
+
try {
|
|
87
|
+
const mod = (await import(_prismasqlModule)) as {
|
|
88
|
+
speedExtension?: SpeedExtensionFactory
|
|
89
|
+
default?: { speedExtension?: SpeedExtensionFactory }
|
|
90
|
+
}
|
|
91
|
+
_speedExtension =
|
|
92
|
+
mod.speedExtension ?? mod.default?.speedExtension ?? null
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const code = (err as { code?: string } | null)?.code
|
|
95
|
+
if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
|
|
96
|
+
console.warn(LOG_PREFIX, 'prisma-sql initialization failed:', err)
|
|
97
|
+
}
|
|
79
98
|
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
99
|
+
})())
|
|
100
|
+
}
|
|
82
101
|
|
|
83
102
|
const _extendedClients = new WeakMap<object, WeakMap<object, unknown>>()
|
|
84
103
|
|
|
85
|
-
export async function getExtendedClient(
|
|
104
|
+
export async function getExtendedClient(
|
|
105
|
+
ctx: OperationContext,
|
|
106
|
+
): Promise<unknown> {
|
|
86
107
|
const base = ctx.prisma as PrismaClientLike | null | undefined
|
|
87
108
|
if (!base) {
|
|
88
|
-
throw new HttpError(
|
|
109
|
+
throw new HttpError(
|
|
110
|
+
500,
|
|
111
|
+
'PrismaClient not found on request. Set req.prisma in middleware.',
|
|
112
|
+
)
|
|
89
113
|
}
|
|
90
|
-
await
|
|
114
|
+
await ensurePrismaSqlReady()
|
|
91
115
|
if (!_speedExtension) return base
|
|
92
116
|
const connector = (ctx.postgres ?? ctx.sqlite) as object | undefined
|
|
93
117
|
if (!connector) return base
|
|
@@ -98,11 +122,13 @@ export async function getExtendedClient(ctx: OperationContext): Promise<unknown>
|
|
|
98
122
|
}
|
|
99
123
|
try {
|
|
100
124
|
if (typeof base.$extends !== 'function') return base
|
|
101
|
-
const extended = base.$extends(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
const extended = base.$extends(
|
|
126
|
+
_speedExtension({
|
|
127
|
+
postgres: ctx.postgres,
|
|
128
|
+
sqlite: ctx.sqlite,
|
|
129
|
+
debug: process.env.DEBUG === 'true',
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
106
132
|
let map = _extendedClients.get(connector)
|
|
107
133
|
if (!map) {
|
|
108
134
|
map = new WeakMap<object, unknown>()
|
|
@@ -111,7 +137,10 @@ export async function getExtendedClient(ctx: OperationContext): Promise<unknown>
|
|
|
111
137
|
map.set(base as unknown as object, extended)
|
|
112
138
|
return extended
|
|
113
139
|
} catch (error) {
|
|
114
|
-
console.warn(
|
|
140
|
+
console.warn(
|
|
141
|
+
'[speedExtension] Failed to initialize, using base client:',
|
|
142
|
+
error,
|
|
143
|
+
)
|
|
115
144
|
return base
|
|
116
145
|
}
|
|
117
146
|
}
|
|
@@ -134,7 +163,10 @@ export function validateBody(body: unknown): Record<string, unknown> {
|
|
|
134
163
|
return sanitizeKeys(body as Record<string, unknown>)
|
|
135
164
|
}
|
|
136
165
|
|
|
137
|
-
export function requireBodyField(
|
|
166
|
+
export function requireBodyField(
|
|
167
|
+
body: Record<string, unknown>,
|
|
168
|
+
field: string,
|
|
169
|
+
): void {
|
|
138
170
|
if (!(field in body) || body[field] === undefined) {
|
|
139
171
|
throw new HttpError(400, 'Missing required field: ' + field)
|
|
140
172
|
}
|
|
@@ -143,7 +175,8 @@ export function requireBodyField(body: Record<string, unknown>, field: string):
|
|
|
143
175
|
export function transformResult(value: unknown): unknown {
|
|
144
176
|
if (value === null || value === undefined) return value
|
|
145
177
|
if (typeof value === 'bigint') return value.toString()
|
|
146
|
-
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
|
|
178
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
|
|
179
|
+
return value.toString('base64')
|
|
147
180
|
if (typeof Buffer !== 'undefined' && value instanceof Uint8Array) {
|
|
148
181
|
return Buffer.from(value).toString('base64')
|
|
149
182
|
}
|
|
@@ -169,4 +202,69 @@ export function transformResult(value: unknown): unknown {
|
|
|
169
202
|
return changed ? out : value
|
|
170
203
|
}
|
|
171
204
|
return value
|
|
172
|
-
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const OPERATION_OVERRIDE_VERSION = 1
|
|
208
|
+
|
|
209
|
+
export type OperationOverride<TInput, TResult, TContext, TPrisma> = {
|
|
210
|
+
invoke(options: {
|
|
211
|
+
input: TInput
|
|
212
|
+
args: TInput
|
|
213
|
+
context: TContext
|
|
214
|
+
prisma: TPrisma
|
|
215
|
+
core: () => Promise<TResult>
|
|
216
|
+
}): TResult | Promise<TResult>
|
|
217
|
+
}['invoke']
|
|
218
|
+
|
|
219
|
+
export type RuntimeOperationOverride = OperationOverride<
|
|
220
|
+
Record<string, unknown>,
|
|
221
|
+
unknown,
|
|
222
|
+
unknown,
|
|
223
|
+
Readonly<
|
|
224
|
+
Record<
|
|
225
|
+
string,
|
|
226
|
+
Readonly<Record<string, (...args: never[]) => Promise<unknown>>>
|
|
227
|
+
>
|
|
228
|
+
>
|
|
229
|
+
>
|
|
230
|
+
|
|
231
|
+
export async function executeOperationOverride<TResult>(
|
|
232
|
+
ctx: OperationContext,
|
|
233
|
+
model: string,
|
|
234
|
+
operation: keyof Omit<PrismaDelegate, 'guard'> | 'findManyPaginated',
|
|
235
|
+
input: Record<string, unknown>,
|
|
236
|
+
core: () => Promise<TResult>,
|
|
237
|
+
): Promise<TResult> {
|
|
238
|
+
if (!ctx.operationOverride) return core()
|
|
239
|
+
if (!ctx.guardShape)
|
|
240
|
+
throw new HttpError(500, 'An operation override requires a guard shape')
|
|
241
|
+
const context = await ctx.resolveOperationContext?.()
|
|
242
|
+
const resolved = await resolveGuardShapeOnce(
|
|
243
|
+
ctx.guardShape,
|
|
244
|
+
ctx.guardVariantKey ?? ctx.guardCaller,
|
|
245
|
+
() => context,
|
|
246
|
+
)
|
|
247
|
+
if (!resolved.ok || !isPlainObject(resolved.shape)) {
|
|
248
|
+
throw new HttpError(
|
|
249
|
+
500,
|
|
250
|
+
'An operation override requires a resolved guard shape',
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
ctx.guardShape = resolved.shape
|
|
254
|
+
const delegate = getDelegate(await getExtendedClient(ctx), model)
|
|
255
|
+
assertGuard(delegate)
|
|
256
|
+
const guarded = delegate.guard(resolved.shape, ctx.guardCaller)
|
|
257
|
+
const methods =
|
|
258
|
+
operation === 'findManyPaginated'
|
|
259
|
+
? (['findMany', 'count'] as const)
|
|
260
|
+
: [operation]
|
|
261
|
+
const exposed: Record<string, (...args: never[]) => Promise<unknown>> = {}
|
|
262
|
+
for (const method of methods) exposed[method] = guarded[method].bind(guarded)
|
|
263
|
+
return (await ctx.operationOverride({
|
|
264
|
+
input,
|
|
265
|
+
args: input,
|
|
266
|
+
context,
|
|
267
|
+
prisma: Object.freeze({ [model]: Object.freeze(exposed) }),
|
|
268
|
+
core,
|
|
269
|
+
})) as TResult
|
|
270
|
+
}
|