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.
@@ -157,7 +157,7 @@ async function runRootQuery(
157
157
  }
158
158
 
159
159
  async function runStage(
160
- extended: unknown,
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(extended, targetModel.delegateKey)
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
- extended,
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
- const locals = readLocals(res)
281
- const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
282
- if (merged) {
283
- locals.routeConfig = { pagination: merged }
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
+ }