prisma-generator-express 1.62.1 → 1.62.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/generateRouter.js +54 -17
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +89 -18
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +89 -14
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/utils/copyFiles.js +1 -0
- package/dist/utils/copyFiles.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/autoIncludeRuntimeGuarded.ts +3 -32
- package/src/copy/projectionDefaults.ts +473 -0
- package/src/generators/generateRouter.ts +55 -24
- package/src/generators/generateRouterFastify.ts +98 -20
- package/src/generators/generateRouterHono.ts +90 -14
- package/src/utils/copyFiles.ts +1 -0
|
@@ -157,7 +157,7 @@ async function runRootQuery(
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
async function runStage(
|
|
160
|
-
|
|
160
|
+
base: unknown,
|
|
161
161
|
models: Record<string, ModelRelationMap>,
|
|
162
162
|
stage: GuardedAutoIncludeStage,
|
|
163
163
|
parentValue: unknown,
|
|
@@ -196,38 +196,9 @@ async function runStage(
|
|
|
196
196
|
[childKey]: { in: [linkVal] },
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
const stageDelegate = getDelegate(
|
|
199
|
+
const stageDelegate = getDelegate(base, targetModel.delegateKey)
|
|
200
200
|
const g = guarded(stageDelegate, stage.stageShape, caller)
|
|
201
201
|
|
|
202
|
-
if (stage.relationPath === 'featureFlags' || stage.relationPath === 'companies') {
|
|
203
|
-
const raw = rel.isList
|
|
204
|
-
? await stageDelegate.findMany(stageArgs)
|
|
205
|
-
: await stageDelegate.findFirst(stageArgs)
|
|
206
|
-
|
|
207
|
-
const guardedResult = rel.isList
|
|
208
|
-
? await g.findMany(stageArgs)
|
|
209
|
-
: await g.findFirst(stageArgs)
|
|
210
|
-
|
|
211
|
-
console.dir(
|
|
212
|
-
{
|
|
213
|
-
relationPath: stage.relationPath,
|
|
214
|
-
targetDelegateKey: targetModel.delegateKey,
|
|
215
|
-
parentKey,
|
|
216
|
-
childKey,
|
|
217
|
-
linkVal,
|
|
218
|
-
stageArgs,
|
|
219
|
-
stageShape: stage.stageShape,
|
|
220
|
-
rawCount: Array.isArray(raw) ? raw.length : raw ? 1 : 0,
|
|
221
|
-
guardedCount: Array.isArray(guardedResult) ? guardedResult.length : guardedResult ? 1 : 0,
|
|
222
|
-
raw,
|
|
223
|
-
guardedResult,
|
|
224
|
-
},
|
|
225
|
-
{ depth: null },
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
return guardedResult
|
|
229
|
-
}
|
|
230
|
-
|
|
231
202
|
if (rel.isList) {
|
|
232
203
|
return g.findMany(stageArgs)
|
|
233
204
|
}
|
|
@@ -297,7 +268,7 @@ async function runGuardedAutoIncludeSingle(
|
|
|
297
268
|
if (isAborted()) return
|
|
298
269
|
try {
|
|
299
270
|
const stageResult = await runStage(
|
|
300
|
-
|
|
271
|
+
ctx.prisma,
|
|
301
272
|
models,
|
|
302
273
|
stage,
|
|
303
274
|
internal,
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { isPlainObject } from './misc'
|
|
2
|
+
|
|
3
|
+
const SHAPE_KEYS = new Set([
|
|
4
|
+
'where',
|
|
5
|
+
'select',
|
|
6
|
+
'include',
|
|
7
|
+
'orderBy',
|
|
8
|
+
'cursor',
|
|
9
|
+
'take',
|
|
10
|
+
'skip',
|
|
11
|
+
'distinct',
|
|
12
|
+
'having',
|
|
13
|
+
'_count',
|
|
14
|
+
'_avg',
|
|
15
|
+
'_sum',
|
|
16
|
+
'_min',
|
|
17
|
+
'_max',
|
|
18
|
+
'by',
|
|
19
|
+
'data',
|
|
20
|
+
'create',
|
|
21
|
+
'update',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const COMBINATOR_KEYS = new Set(['AND', 'OR', 'NOT'])
|
|
25
|
+
const TO_ONE_RELATION_OPS = new Set(['is', 'isNot'])
|
|
26
|
+
const TO_MANY_RELATION_OPS = new Set(['some', 'every', 'none'])
|
|
27
|
+
const ALL_RELATION_OPS = new Set([
|
|
28
|
+
...TO_ONE_RELATION_OPS,
|
|
29
|
+
...TO_MANY_RELATION_OPS,
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
const FORCED_MARKER = Symbol.for('prisma-guard.forced')
|
|
33
|
+
|
|
34
|
+
function isForcedValue(v: unknown): v is { value: unknown } {
|
|
35
|
+
return (
|
|
36
|
+
v !== null &&
|
|
37
|
+
typeof v === 'object' &&
|
|
38
|
+
(v as Record<PropertyKey, unknown>)[FORCED_MARKER] === true
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ContextResolver = () => unknown | Promise<unknown>
|
|
43
|
+
|
|
44
|
+
export type OpKind =
|
|
45
|
+
| 'read'
|
|
46
|
+
| 'readUnique'
|
|
47
|
+
| 'create'
|
|
48
|
+
| 'createMany'
|
|
49
|
+
| 'update'
|
|
50
|
+
| 'updateMany'
|
|
51
|
+
| 'upsert'
|
|
52
|
+
| 'delete'
|
|
53
|
+
| 'deleteMany'
|
|
54
|
+
| 'noop'
|
|
55
|
+
|
|
56
|
+
interface WhereForced {
|
|
57
|
+
conditions: Record<string, unknown>
|
|
58
|
+
relations: Record<string, Record<string, WhereForced>>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function emptyForced(): WhereForced {
|
|
62
|
+
return { conditions: {}, relations: {} }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hasForced(f: WhereForced): boolean {
|
|
66
|
+
return (
|
|
67
|
+
Object.keys(f.conditions).length > 0 ||
|
|
68
|
+
Object.keys(f.relations).length > 0
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isDirectGuardShape(input: Record<string, unknown>): boolean {
|
|
73
|
+
const keys = Object.keys(input)
|
|
74
|
+
if (keys.length === 0) return true
|
|
75
|
+
return keys.every((k) => SHAPE_KEYS.has(k))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function callShapeFn(
|
|
79
|
+
fn: (ctx: unknown) => unknown,
|
|
80
|
+
resolveContext: ContextResolver | undefined,
|
|
81
|
+
): Promise<Record<string, unknown> | null> {
|
|
82
|
+
if (!resolveContext) return null
|
|
83
|
+
const ctx = await resolveContext()
|
|
84
|
+
const result = fn(ctx)
|
|
85
|
+
if (!isPlainObject(result)) return null
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function resolveShape(
|
|
90
|
+
input: unknown,
|
|
91
|
+
caller: string | undefined,
|
|
92
|
+
resolveContext: ContextResolver | undefined,
|
|
93
|
+
): Promise<Record<string, unknown> | null> {
|
|
94
|
+
if (!input) return null
|
|
95
|
+
|
|
96
|
+
if (typeof input === 'function') {
|
|
97
|
+
return callShapeFn(input as (ctx: unknown) => unknown, resolveContext)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!isPlainObject(input)) return null
|
|
101
|
+
if (isDirectGuardShape(input)) return input
|
|
102
|
+
|
|
103
|
+
let entry: unknown = undefined
|
|
104
|
+
if (caller !== undefined && caller in input) entry = input[caller]
|
|
105
|
+
if (entry === undefined && 'default' in input) entry = input['default']
|
|
106
|
+
if (entry === undefined) return null
|
|
107
|
+
|
|
108
|
+
if (typeof entry === 'function') {
|
|
109
|
+
return callShapeFn(entry as (ctx: unknown) => unknown, resolveContext)
|
|
110
|
+
}
|
|
111
|
+
if (!isPlainObject(entry)) return null
|
|
112
|
+
return entry
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildDefaultCount(config: unknown): unknown {
|
|
116
|
+
if (config === true) return true
|
|
117
|
+
if (!isPlainObject(config)) return true
|
|
118
|
+
const selectVal = config.select
|
|
119
|
+
if (!isPlainObject(selectVal)) return true
|
|
120
|
+
const result: Record<string, unknown> = {}
|
|
121
|
+
for (const key of Object.keys(selectVal)) result[key] = true
|
|
122
|
+
return { select: result }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildRelSkeleton(
|
|
126
|
+
config: Record<string, unknown>,
|
|
127
|
+
): Record<string, unknown> {
|
|
128
|
+
const skel: Record<string, unknown> = {}
|
|
129
|
+
if (isPlainObject(config.select)) {
|
|
130
|
+
skel.select = buildDefaultProjectionInput(config.select)
|
|
131
|
+
}
|
|
132
|
+
if (isPlainObject(config.include)) {
|
|
133
|
+
skel.include = buildDefaultProjectionInput(config.include)
|
|
134
|
+
}
|
|
135
|
+
return skel
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildDefaultProjectionInput(
|
|
139
|
+
config: Record<string, unknown>,
|
|
140
|
+
): Record<string, unknown> {
|
|
141
|
+
const result: Record<string, unknown> = {}
|
|
142
|
+
for (const [key, value] of Object.entries(config)) {
|
|
143
|
+
if (key === '_count') {
|
|
144
|
+
result[key] = buildDefaultCount(value)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
if (value === true) {
|
|
148
|
+
result[key] = true
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (isPlainObject(value)) {
|
|
152
|
+
result[key] = buildRelSkeleton(value)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildDefaultProjectionBody(
|
|
159
|
+
shape: Record<string, unknown>,
|
|
160
|
+
): Record<string, unknown> | null {
|
|
161
|
+
if (isPlainObject(shape.select)) {
|
|
162
|
+
return { select: buildDefaultProjectionInput(shape.select) }
|
|
163
|
+
}
|
|
164
|
+
if (isPlainObject(shape.include)) {
|
|
165
|
+
return { include: buildDefaultProjectionInput(shape.include) }
|
|
166
|
+
}
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function extractForcedFromWhereConfig(
|
|
171
|
+
whereConfig: Record<string, unknown>,
|
|
172
|
+
): WhereForced {
|
|
173
|
+
const forced = emptyForced()
|
|
174
|
+
|
|
175
|
+
for (const [key, value] of Object.entries(whereConfig)) {
|
|
176
|
+
if (COMBINATOR_KEYS.has(key)) continue
|
|
177
|
+
|
|
178
|
+
if (isForcedValue(value)) {
|
|
179
|
+
forced.conditions[key] = value.value
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isPlainObject(value)) {
|
|
184
|
+
const relOps: Record<string, WhereForced> = {}
|
|
185
|
+
let hasRelOp = false
|
|
186
|
+
|
|
187
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
188
|
+
if (!ALL_RELATION_OPS.has(op)) continue
|
|
189
|
+
if (opValue === null) continue
|
|
190
|
+
if (!isPlainObject(opValue)) continue
|
|
191
|
+
const nested = extractForcedFromWhereConfig(opValue)
|
|
192
|
+
if (hasForced(nested)) {
|
|
193
|
+
relOps[op] = nested
|
|
194
|
+
hasRelOp = true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (hasRelOp) {
|
|
199
|
+
forced.relations[key] = relOps
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const forcedOps: Record<string, unknown> = {}
|
|
204
|
+
let hasForcedOp = false
|
|
205
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
206
|
+
if (isForcedValue(opValue)) {
|
|
207
|
+
forcedOps[op] = opValue.value
|
|
208
|
+
hasForcedOp = true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (hasForcedOp) {
|
|
212
|
+
forced.conditions[key] = forcedOps
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return forced
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function extractForcedFromDataConfig(
|
|
221
|
+
dataConfig: Record<string, unknown>,
|
|
222
|
+
): Record<string, unknown> {
|
|
223
|
+
const forced: Record<string, unknown> = {}
|
|
224
|
+
for (const [key, value] of Object.entries(dataConfig)) {
|
|
225
|
+
if (isForcedValue(value)) {
|
|
226
|
+
forced[key] = value.value
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
if (typeof value === 'function') continue
|
|
230
|
+
if (value === true) continue
|
|
231
|
+
if (isPlainObject(value)) continue
|
|
232
|
+
forced[key] = value
|
|
233
|
+
}
|
|
234
|
+
return forced
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function mergeWhereForced(
|
|
238
|
+
where: Record<string, unknown> | undefined,
|
|
239
|
+
forced: WhereForced,
|
|
240
|
+
): Record<string, unknown> {
|
|
241
|
+
if (!hasForced(forced)) return where ?? {}
|
|
242
|
+
|
|
243
|
+
let result: Record<string, unknown> = where ? { ...where } : {}
|
|
244
|
+
|
|
245
|
+
for (const [relName, opMap] of Object.entries(forced.relations)) {
|
|
246
|
+
if (!isPlainObject(result[relName])) {
|
|
247
|
+
result[relName] = {}
|
|
248
|
+
}
|
|
249
|
+
const relObj = { ...(result[relName] as Record<string, unknown>) }
|
|
250
|
+
for (const [op, nestedForced] of Object.entries(opMap)) {
|
|
251
|
+
relObj[op] = mergeWhereForced(
|
|
252
|
+
isPlainObject(relObj[op])
|
|
253
|
+
? (relObj[op] as Record<string, unknown>)
|
|
254
|
+
: undefined,
|
|
255
|
+
nestedForced,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
result[relName] = relObj
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (Object.keys(forced.conditions).length > 0) {
|
|
262
|
+
const remaining: Record<string, unknown> = {}
|
|
263
|
+
for (const [field, forcedValue] of Object.entries(forced.conditions)) {
|
|
264
|
+
const existing = result[field]
|
|
265
|
+
|
|
266
|
+
if (existing === undefined) {
|
|
267
|
+
remaining[field] = forcedValue
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (isPlainObject(existing) && isPlainObject(forcedValue)) {
|
|
272
|
+
result[field] = { ...existing, ...forcedValue }
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (isPlainObject(existing) && !isPlainObject(forcedValue)) {
|
|
277
|
+
result[field] = { ...existing, equals: forcedValue }
|
|
278
|
+
continue
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!isPlainObject(existing) && isPlainObject(forcedValue)) {
|
|
282
|
+
result[field] = { equals: existing, ...forcedValue }
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
remaining[field] = forcedValue
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (Object.keys(remaining).length > 0) {
|
|
290
|
+
if (Object.keys(result).length === 0) {
|
|
291
|
+
result = remaining
|
|
292
|
+
} else {
|
|
293
|
+
result = { AND: [result, remaining] }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function mergeUniqueWhereForced(
|
|
302
|
+
where: Record<string, unknown> | undefined,
|
|
303
|
+
forced: WhereForced,
|
|
304
|
+
): Record<string, unknown> {
|
|
305
|
+
if (!hasForced(forced)) return where ?? {}
|
|
306
|
+
|
|
307
|
+
const result: Record<string, unknown> = where ? { ...where } : {}
|
|
308
|
+
|
|
309
|
+
for (const [key, value] of Object.entries(forced.conditions)) {
|
|
310
|
+
if (!(key in result)) {
|
|
311
|
+
result[key] = value
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
314
|
+
if (isPlainObject(result[key]) && isPlainObject(value)) {
|
|
315
|
+
result[key] = {
|
|
316
|
+
...(result[key] as Record<string, unknown>),
|
|
317
|
+
...(value as Record<string, unknown>),
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
result[key] = value
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return result
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function applyProjectionToTarget(
|
|
328
|
+
target: Record<string, unknown>,
|
|
329
|
+
projection: Record<string, unknown>,
|
|
330
|
+
): void {
|
|
331
|
+
if ('select' in target || 'include' in target) return
|
|
332
|
+
Object.assign(target, projection)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function mergeForcedData(
|
|
336
|
+
targetData: unknown,
|
|
337
|
+
forced: Record<string, unknown>,
|
|
338
|
+
): Record<string, unknown> | unknown[] {
|
|
339
|
+
if (Array.isArray(targetData)) {
|
|
340
|
+
return targetData.map((item) => {
|
|
341
|
+
if (!isPlainObject(item)) return { ...forced }
|
|
342
|
+
return { ...item, ...forced }
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
if (isPlainObject(targetData)) {
|
|
346
|
+
return { ...targetData, ...forced }
|
|
347
|
+
}
|
|
348
|
+
return { ...forced }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
interface WhereMergeOptions {
|
|
352
|
+
targetContainer: Record<string, unknown>
|
|
353
|
+
whereKey: string
|
|
354
|
+
forced: WhereForced
|
|
355
|
+
isUnique: boolean
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function applyForcedWhere(opts: WhereMergeOptions): void {
|
|
359
|
+
if (!hasForced(opts.forced)) return
|
|
360
|
+
const existing = opts.targetContainer[opts.whereKey]
|
|
361
|
+
const merged = opts.isUnique
|
|
362
|
+
? mergeUniqueWhereForced(
|
|
363
|
+
isPlainObject(existing)
|
|
364
|
+
? (existing as Record<string, unknown>)
|
|
365
|
+
: undefined,
|
|
366
|
+
opts.forced,
|
|
367
|
+
)
|
|
368
|
+
: mergeWhereForced(
|
|
369
|
+
isPlainObject(existing)
|
|
370
|
+
? (existing as Record<string, unknown>)
|
|
371
|
+
: undefined,
|
|
372
|
+
opts.forced,
|
|
373
|
+
)
|
|
374
|
+
opts.targetContainer[opts.whereKey] = merged
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export async function applyDroppedGuard(
|
|
378
|
+
shape: unknown,
|
|
379
|
+
caller: string | undefined,
|
|
380
|
+
resolveContext: ContextResolver | undefined,
|
|
381
|
+
opKind: OpKind,
|
|
382
|
+
targets: {
|
|
383
|
+
readQuery?: Record<string, unknown>
|
|
384
|
+
writeBody?: Record<string, unknown>
|
|
385
|
+
},
|
|
386
|
+
ensureReadTarget: () => Record<string, unknown>,
|
|
387
|
+
ensureWriteTarget: () => Record<string, unknown>,
|
|
388
|
+
): Promise<void> {
|
|
389
|
+
const resolved = await resolveShape(shape, caller, resolveContext)
|
|
390
|
+
if (!resolved) return
|
|
391
|
+
|
|
392
|
+
const projection = buildDefaultProjectionBody(resolved)
|
|
393
|
+
|
|
394
|
+
if (opKind === 'read' || opKind === 'readUnique') {
|
|
395
|
+
const isUnique = opKind === 'readUnique'
|
|
396
|
+
const shapeWhere = isPlainObject(resolved.where)
|
|
397
|
+
? extractForcedFromWhereConfig(resolved.where)
|
|
398
|
+
: emptyForced()
|
|
399
|
+
|
|
400
|
+
if (projection || hasForced(shapeWhere)) {
|
|
401
|
+
const target = targets.readQuery ?? ensureReadTarget()
|
|
402
|
+
if (projection) applyProjectionToTarget(target, projection)
|
|
403
|
+
if (hasForced(shapeWhere)) {
|
|
404
|
+
applyForcedWhere({
|
|
405
|
+
targetContainer: target,
|
|
406
|
+
whereKey: 'where',
|
|
407
|
+
forced: shapeWhere,
|
|
408
|
+
isUnique,
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (opKind === 'noop') return
|
|
416
|
+
|
|
417
|
+
const shapeWhere = isPlainObject(resolved.where)
|
|
418
|
+
? extractForcedFromWhereConfig(resolved.where)
|
|
419
|
+
: emptyForced()
|
|
420
|
+
|
|
421
|
+
const forcedData =
|
|
422
|
+
opKind === 'create' || opKind === 'createMany' || opKind === 'update' || opKind === 'updateMany'
|
|
423
|
+
? isPlainObject(resolved.data)
|
|
424
|
+
? extractForcedFromDataConfig(resolved.data)
|
|
425
|
+
: {}
|
|
426
|
+
: {}
|
|
427
|
+
|
|
428
|
+
const forcedCreate =
|
|
429
|
+
opKind === 'upsert' && isPlainObject(resolved.create)
|
|
430
|
+
? extractForcedFromDataConfig(resolved.create)
|
|
431
|
+
: {}
|
|
432
|
+
|
|
433
|
+
const forcedUpdate =
|
|
434
|
+
opKind === 'upsert' && isPlainObject(resolved.update)
|
|
435
|
+
? extractForcedFromDataConfig(resolved.update)
|
|
436
|
+
: {}
|
|
437
|
+
|
|
438
|
+
const needsBody =
|
|
439
|
+
projection !== null ||
|
|
440
|
+
hasForced(shapeWhere) ||
|
|
441
|
+
Object.keys(forcedData).length > 0 ||
|
|
442
|
+
Object.keys(forcedCreate).length > 0 ||
|
|
443
|
+
Object.keys(forcedUpdate).length > 0
|
|
444
|
+
|
|
445
|
+
if (!needsBody) return
|
|
446
|
+
|
|
447
|
+
const target = targets.writeBody ?? ensureWriteTarget()
|
|
448
|
+
|
|
449
|
+
if (projection) applyProjectionToTarget(target, projection)
|
|
450
|
+
|
|
451
|
+
if (hasForced(shapeWhere)) {
|
|
452
|
+
const isUnique =
|
|
453
|
+
opKind === 'update' || opKind === 'delete' || opKind === 'upsert'
|
|
454
|
+
applyForcedWhere({
|
|
455
|
+
targetContainer: target,
|
|
456
|
+
whereKey: 'where',
|
|
457
|
+
forced: shapeWhere,
|
|
458
|
+
isUnique,
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (Object.keys(forcedData).length > 0) {
|
|
463
|
+
target.data = mergeForcedData(target.data, forcedData)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (Object.keys(forcedCreate).length > 0) {
|
|
467
|
+
target.create = mergeForcedData(target.create, forcedCreate)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (Object.keys(forcedUpdate).length > 0) {
|
|
471
|
+
target.update = mergeForcedData(target.update, forcedUpdate)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -5,12 +5,6 @@ import { importExt } from '../utils/importExt'
|
|
|
5
5
|
import { WriteStrategy, FindManyPaginatedMode } from '../constants'
|
|
6
6
|
import { OPERATION_METADATA } from '../copy/operationDefinitions'
|
|
7
7
|
|
|
8
|
-
interface OpEmitContext {
|
|
9
|
-
modelName: string
|
|
10
|
-
basePath: string
|
|
11
|
-
postReadsEnabled: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
function pathExpr(basePath: string, suffix: string): string {
|
|
15
9
|
if (!suffix) return basePath || '/'
|
|
16
10
|
if (!basePath) return `'${suffix}'`
|
|
@@ -28,7 +22,7 @@ function emitReadOp(
|
|
|
28
22
|
const postReadBlock = meta.supportsPostRead
|
|
29
23
|
? ` if (postReadsEnabled) {
|
|
30
24
|
const postPath = ${meta.name === 'findMany' ? "basePath ? `${basePath}/read` : '/read'" : `path`}
|
|
31
|
-
router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, respond)
|
|
25
|
+
router.post(postPath, parseBodyAsQuery, setShape(opConfig, 'read'), ...before, ${handlerName} as RequestHandler, ...after, respond)
|
|
32
26
|
}`
|
|
33
27
|
: ''
|
|
34
28
|
|
|
@@ -36,7 +30,7 @@ function emitReadOp(
|
|
|
36
30
|
const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
37
31
|
const { before = [], after = [] } = opConfig
|
|
38
32
|
const path = ${pathValue}
|
|
39
|
-
router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'), ${handlerName} as RequestHandler, ...after, respond)
|
|
33
|
+
router.get(path, parseQuery, setShape(opConfig, 'read'), ...before, maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'), ${handlerName} as RequestHandler, ...after, respond)
|
|
40
34
|
${postReadBlock}
|
|
41
35
|
}`
|
|
42
36
|
}
|
|
@@ -54,7 +48,7 @@ function emitWriteOp(
|
|
|
54
48
|
const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
|
|
55
49
|
const { before = [], after = [] } = opConfig
|
|
56
50
|
const path = ${pathValue}
|
|
57
|
-
router.${meta.method}(path, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, ${respondFn})
|
|
51
|
+
router.${meta.method}(path, setShape(opConfig, 'write'), ...before, ${handlerName} as RequestHandler, ...after, ${respondFn})
|
|
58
52
|
}`
|
|
59
53
|
}
|
|
60
54
|
|
|
@@ -114,7 +108,7 @@ import type {
|
|
|
114
108
|
PaginationConfig,
|
|
115
109
|
} from '../routeConfig.target${ext}'
|
|
116
110
|
import { parseQueryParams } from '../parseQueryParams${ext}'
|
|
117
|
-
import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
|
|
111
|
+
import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject } from '../misc${ext}'
|
|
118
112
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
119
113
|
import { validateCountSourceWhere } from '../routeConfig${ext}'
|
|
120
114
|
import type { OperationContext } from '../operationRuntime${ext}'
|
|
@@ -130,6 +124,10 @@ import {
|
|
|
130
124
|
} from '../sse${ext}'
|
|
131
125
|
import { relationModels } from '../relationModels${ext}'
|
|
132
126
|
import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
|
|
127
|
+
import {
|
|
128
|
+
resolveDroppedGuardProjection,
|
|
129
|
+
applyProjectionToTarget,
|
|
130
|
+
} from '../projectionDefaults${ext}'
|
|
133
131
|
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
134
132
|
|
|
135
133
|
${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
|
|
@@ -258,6 +256,11 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
258
256
|
}
|
|
259
257
|
}
|
|
260
258
|
|
|
259
|
+
const buildResolveContext = (req: Request): (() => unknown | Promise<unknown>) | undefined => {
|
|
260
|
+
if (typeof config.resolveContext !== 'function') return undefined
|
|
261
|
+
return () => (config.resolveContext as (r: Request) => unknown | Promise<unknown>)(req)
|
|
262
|
+
}
|
|
263
|
+
|
|
261
264
|
const parseQuery: RequestHandler = (req, res, next) => {
|
|
262
265
|
const rawQuery = req.query
|
|
263
266
|
if (rawQuery && Object.keys(rawQuery).length > 0) {
|
|
@@ -275,19 +278,47 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
|
|
|
275
278
|
next()
|
|
276
279
|
}
|
|
277
280
|
|
|
278
|
-
const setShape = (opConfig: OperationConfigLike): RequestHandler => {
|
|
279
|
-
return (req, res, next) => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
const setShape = (opConfig: OperationConfigLike, kind: 'read' | 'write'): RequestHandler => {
|
|
282
|
+
return async (req, res, next) => {
|
|
283
|
+
try {
|
|
284
|
+
const locals = readLocals(res)
|
|
285
|
+
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
286
|
+
if (merged) {
|
|
287
|
+
locals.routeConfig = { pagination: merged }
|
|
288
|
+
}
|
|
289
|
+
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
290
|
+
const headerValue = req.get(headerName)
|
|
291
|
+
const caller = config.guard?.resolveVariant?.(req) ?? headerValue ?? undefined
|
|
292
|
+
if (caller) locals.guardCaller = caller
|
|
293
|
+
if (opConfig.shape) {
|
|
294
|
+
if (!DROP_GUARD) {
|
|
295
|
+
locals.guardShape = opConfig.shape
|
|
296
|
+
} else {
|
|
297
|
+
const projection = await resolveDroppedGuardProjection(
|
|
298
|
+
opConfig.shape,
|
|
299
|
+
caller,
|
|
300
|
+
buildResolveContext(req),
|
|
301
|
+
)
|
|
302
|
+
if (projection) {
|
|
303
|
+
if (kind === 'read') {
|
|
304
|
+
if (!locals.parsedQuery) locals.parsedQuery = {}
|
|
305
|
+
applyProjectionToTarget(locals.parsedQuery, projection)
|
|
306
|
+
} else {
|
|
307
|
+
if (!isPlainObject(req.body)) {
|
|
308
|
+
req.body = {}
|
|
309
|
+
}
|
|
310
|
+
applyProjectionToTarget(
|
|
311
|
+
req.body as Record<string, unknown>,
|
|
312
|
+
projection,
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
next()
|
|
319
|
+
} catch (err) {
|
|
320
|
+
next(mapError(err))
|
|
284
321
|
}
|
|
285
|
-
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
286
|
-
const headerValue = req.get(headerName)
|
|
287
|
-
const caller = config.guard?.resolveVariant?.(req) ?? headerValue ?? undefined
|
|
288
|
-
if (caller) locals.guardCaller = caller
|
|
289
|
-
if (opConfig.shape && !DROP_GUARD) locals.guardShape = opConfig.shape
|
|
290
|
-
next()
|
|
291
322
|
}
|
|
292
323
|
}
|
|
293
324
|
|
|
@@ -442,7 +473,7 @@ ${writeOpBlocks}
|
|
|
442
473
|
const path = basePath ? \`\${basePath}/each\` : '/each'
|
|
443
474
|
router.post(
|
|
444
475
|
path,
|
|
445
|
-
setShape(opConfig),
|
|
476
|
+
setShape(opConfig, 'write'),
|
|
446
477
|
...before,
|
|
447
478
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
448
479
|
try {
|
|
@@ -470,4 +501,4 @@ ${writeOpBlocks}
|
|
|
470
501
|
return router
|
|
471
502
|
}
|
|
472
503
|
`
|
|
473
|
-
}
|
|
504
|
+
}
|