@opensaas/stack-core 0.1.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/.turbo/turbo-build.log +4 -0
- package/README.md +447 -0
- package/dist/access/engine.d.ts +73 -0
- package/dist/access/engine.d.ts.map +1 -0
- package/dist/access/engine.js +244 -0
- package/dist/access/engine.js.map +1 -0
- package/dist/access/field-transforms.d.ts +47 -0
- package/dist/access/field-transforms.d.ts.map +1 -0
- package/dist/access/field-transforms.js +2 -0
- package/dist/access/field-transforms.js.map +1 -0
- package/dist/access/index.d.ts +3 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +2 -0
- package/dist/access/index.js.map +1 -0
- package/dist/access/types.d.ts +83 -0
- package/dist/access/types.d.ts.map +1 -0
- package/dist/access/types.js +2 -0
- package/dist/access/types.js.map +1 -0
- package/dist/config/index.d.ts +39 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +38 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +413 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/context/index.d.ts +31 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +524 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/nested-operations.d.ts +10 -0
- package/dist/context/nested-operations.d.ts.map +1 -0
- package/dist/context/nested-operations.js +261 -0
- package/dist/context/nested-operations.js.map +1 -0
- package/dist/fields/index.d.ts +78 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/index.js +381 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/hooks/index.d.ts +58 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +79 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/case-utils.d.ts +49 -0
- package/dist/lib/case-utils.d.ts.map +1 -0
- package/dist/lib/case-utils.js +68 -0
- package/dist/lib/case-utils.js.map +1 -0
- package/dist/lib/case-utils.test.d.ts +2 -0
- package/dist/lib/case-utils.test.d.ts.map +1 -0
- package/dist/lib/case-utils.test.js +101 -0
- package/dist/lib/case-utils.test.js.map +1 -0
- package/dist/utils/password.d.ts +81 -0
- package/dist/utils/password.d.ts.map +1 -0
- package/dist/utils/password.js +132 -0
- package/dist/utils/password.js.map +1 -0
- package/dist/validation/schema.d.ts +17 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +42 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/schema.test.d.ts +2 -0
- package/dist/validation/schema.test.d.ts.map +1 -0
- package/dist/validation/schema.test.js +143 -0
- package/dist/validation/schema.test.js.map +1 -0
- package/docs/type-distribution-fix.md +136 -0
- package/package.json +48 -0
- package/src/access/engine.ts +360 -0
- package/src/access/field-transforms.ts +99 -0
- package/src/access/index.ts +20 -0
- package/src/access/types.ts +103 -0
- package/src/config/index.ts +71 -0
- package/src/config/types.ts +478 -0
- package/src/context/index.ts +814 -0
- package/src/context/nested-operations.ts +412 -0
- package/src/fields/index.ts +438 -0
- package/src/hooks/index.ts +132 -0
- package/src/index.ts +62 -0
- package/src/lib/case-utils.test.ts +127 -0
- package/src/lib/case-utils.ts +74 -0
- package/src/utils/password.ts +147 -0
- package/src/validation/schema.test.ts +171 -0
- package/src/validation/schema.ts +59 -0
- package/tests/access-relationships.test.ts +613 -0
- package/tests/access.test.ts +499 -0
- package/tests/config.test.ts +195 -0
- package/tests/context.test.ts +248 -0
- package/tests/hooks.test.ts +417 -0
- package/tests/password-type-distribution.test.ts +155 -0
- package/tests/password-types.test.ts +147 -0
- package/tests/password.test.ts +249 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import type { OpenSaasConfig, ListConfig, FieldConfig } from '../config/types.js'
|
|
2
|
+
import type { AccessContext } from '../access/types.js'
|
|
3
|
+
import { checkAccess, filterWritableFields, getRelatedListConfig } from '../access/index.js'
|
|
4
|
+
import {
|
|
5
|
+
executeResolveInput,
|
|
6
|
+
executeValidateInput,
|
|
7
|
+
validateFieldRules,
|
|
8
|
+
ValidationError,
|
|
9
|
+
} from '../hooks/index.js'
|
|
10
|
+
import { getDbKey } from '../lib/case-utils.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a field config is a relationship field
|
|
14
|
+
*/
|
|
15
|
+
function isRelationshipField(fieldConfig: FieldConfig | undefined): boolean {
|
|
16
|
+
return fieldConfig?.type === 'relationship'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Process nested create operations
|
|
21
|
+
* Applies hooks and access control to each item being created
|
|
22
|
+
*/
|
|
23
|
+
async function processNestedCreate(
|
|
24
|
+
items: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
25
|
+
relatedListConfig: ListConfig,
|
|
26
|
+
context: AccessContext,
|
|
27
|
+
config: OpenSaasConfig,
|
|
28
|
+
): Promise<Record<string, unknown> | Array<Record<string, unknown>>> {
|
|
29
|
+
const itemsArray = Array.isArray(items) ? items : [items]
|
|
30
|
+
|
|
31
|
+
const processedItems = await Promise.all(
|
|
32
|
+
itemsArray.map(async (item) => {
|
|
33
|
+
// 1. Check create access
|
|
34
|
+
const createAccess = relatedListConfig.access?.operation?.create
|
|
35
|
+
const accessResult = await checkAccess(createAccess, {
|
|
36
|
+
session: context.session,
|
|
37
|
+
context,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (accessResult === false) {
|
|
41
|
+
throw new Error('Access denied: Cannot create related item')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. Execute resolveInput hook
|
|
45
|
+
let resolvedData = await executeResolveInput(relatedListConfig.hooks, {
|
|
46
|
+
operation: 'create',
|
|
47
|
+
resolvedData: item,
|
|
48
|
+
context,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// 3. Execute validateInput hook
|
|
52
|
+
await executeValidateInput(relatedListConfig.hooks, {
|
|
53
|
+
operation: 'create',
|
|
54
|
+
resolvedData,
|
|
55
|
+
context,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 4. Field validation
|
|
59
|
+
const validation = validateFieldRules(resolvedData, relatedListConfig.fields, 'create')
|
|
60
|
+
if (validation.errors.length > 0) {
|
|
61
|
+
throw new ValidationError(validation.errors, validation.fieldErrors)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 5. Filter writable fields
|
|
65
|
+
const filtered = await filterWritableFields(
|
|
66
|
+
resolvedData,
|
|
67
|
+
relatedListConfig.fields,
|
|
68
|
+
'create',
|
|
69
|
+
{
|
|
70
|
+
session: context.session,
|
|
71
|
+
context,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
// 6. Recursively process nested operations in this item
|
|
76
|
+
return await processNestedOperations(
|
|
77
|
+
filtered,
|
|
78
|
+
relatedListConfig.fields,
|
|
79
|
+
config,
|
|
80
|
+
context,
|
|
81
|
+
'create',
|
|
82
|
+
)
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return Array.isArray(items) ? processedItems : processedItems[0]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Process nested connect operations
|
|
91
|
+
* Verifies update access to the items being connected
|
|
92
|
+
*/
|
|
93
|
+
async function processNestedConnect(
|
|
94
|
+
connections: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
95
|
+
relatedListName: string,
|
|
96
|
+
relatedListConfig: ListConfig,
|
|
97
|
+
context: AccessContext,
|
|
98
|
+
prisma: unknown,
|
|
99
|
+
): Promise<Record<string, unknown> | Array<Record<string, unknown>>> {
|
|
100
|
+
const connectionsArray = Array.isArray(connections) ? connections : [connections]
|
|
101
|
+
|
|
102
|
+
// Check update access for each item being connected
|
|
103
|
+
for (const connection of connectionsArray) {
|
|
104
|
+
// Access Prisma model dynamically - required because model names are generated at runtime
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
const model = (prisma as any)[getDbKey(relatedListName)]
|
|
107
|
+
|
|
108
|
+
// Fetch the item to check access
|
|
109
|
+
const item = await model.findUnique({
|
|
110
|
+
where: connection,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
if (!item) {
|
|
114
|
+
throw new Error(`Cannot connect: Item not found`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check update access (connecting modifies the relationship)
|
|
118
|
+
const updateAccess = relatedListConfig.access?.operation?.update
|
|
119
|
+
const accessResult = await checkAccess(updateAccess, {
|
|
120
|
+
session: context.session,
|
|
121
|
+
item,
|
|
122
|
+
context,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
if (accessResult === false) {
|
|
126
|
+
throw new Error('Access denied: Cannot connect to this item')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If access returns a filter, check if item matches
|
|
130
|
+
if (typeof accessResult === 'object') {
|
|
131
|
+
// Simple field matching
|
|
132
|
+
for (const [key, value] of Object.entries(accessResult)) {
|
|
133
|
+
if (typeof value === 'object' && value !== null && 'equals' in value) {
|
|
134
|
+
if (item[key] !== (value as Record<string, unknown>).equals) {
|
|
135
|
+
throw new Error('Access denied: Cannot connect to this item')
|
|
136
|
+
}
|
|
137
|
+
} else if (item[key] !== value) {
|
|
138
|
+
throw new Error('Access denied: Cannot connect to this item')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return connections
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Process nested update operations
|
|
149
|
+
* Applies hooks and access control to updates
|
|
150
|
+
*/
|
|
151
|
+
async function processNestedUpdate(
|
|
152
|
+
updates: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
153
|
+
relatedListName: string,
|
|
154
|
+
relatedListConfig: ListConfig,
|
|
155
|
+
context: AccessContext,
|
|
156
|
+
config: OpenSaasConfig,
|
|
157
|
+
prisma: unknown,
|
|
158
|
+
): Promise<Record<string, unknown> | Array<Record<string, unknown>>> {
|
|
159
|
+
const updatesArray = Array.isArray(updates) ? updates : [updates]
|
|
160
|
+
|
|
161
|
+
const processedUpdates = await Promise.all(
|
|
162
|
+
updatesArray.map(async (update) => {
|
|
163
|
+
// Access Prisma model dynamically - required because model names are generated at runtime
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
|
+
const model = (prisma as any)[getDbKey(relatedListName)]
|
|
166
|
+
|
|
167
|
+
// Fetch the existing item
|
|
168
|
+
const item = await model.findUnique({
|
|
169
|
+
where: (update as Record<string, unknown>).where,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
if (!item) {
|
|
173
|
+
throw new Error('Cannot update: Item not found')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check update access
|
|
177
|
+
const updateAccess = relatedListConfig.access?.operation?.update
|
|
178
|
+
const accessResult = await checkAccess(updateAccess, {
|
|
179
|
+
session: context.session,
|
|
180
|
+
item,
|
|
181
|
+
context,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (accessResult === false) {
|
|
185
|
+
throw new Error('Access denied: Cannot update related item')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Execute resolveInput hook
|
|
189
|
+
const updateData = (update as Record<string, unknown>).data as Record<string, unknown>
|
|
190
|
+
let resolvedData = await executeResolveInput(relatedListConfig.hooks, {
|
|
191
|
+
operation: 'update',
|
|
192
|
+
resolvedData: updateData,
|
|
193
|
+
item,
|
|
194
|
+
context,
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Execute validateInput hook
|
|
198
|
+
await executeValidateInput(relatedListConfig.hooks, {
|
|
199
|
+
operation: 'update',
|
|
200
|
+
resolvedData,
|
|
201
|
+
item,
|
|
202
|
+
context,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Field validation
|
|
206
|
+
const validation = validateFieldRules(resolvedData, relatedListConfig.fields, 'update')
|
|
207
|
+
if (validation.errors.length > 0) {
|
|
208
|
+
throw new ValidationError(validation.errors, validation.fieldErrors)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Filter writable fields
|
|
212
|
+
const filtered = await filterWritableFields(
|
|
213
|
+
resolvedData,
|
|
214
|
+
relatedListConfig.fields,
|
|
215
|
+
'update',
|
|
216
|
+
{
|
|
217
|
+
session: context.session,
|
|
218
|
+
item,
|
|
219
|
+
context,
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// Recursively process nested operations
|
|
224
|
+
const processedData = await processNestedOperations(
|
|
225
|
+
filtered,
|
|
226
|
+
relatedListConfig.fields,
|
|
227
|
+
config,
|
|
228
|
+
context,
|
|
229
|
+
'update',
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
where: (update as Record<string, unknown>).where,
|
|
234
|
+
data: processedData,
|
|
235
|
+
}
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return Array.isArray(updates) ? processedUpdates : processedUpdates[0]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Process nested connectOrCreate operations
|
|
244
|
+
*/
|
|
245
|
+
async function processNestedConnectOrCreate(
|
|
246
|
+
operations: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
247
|
+
relatedListName: string,
|
|
248
|
+
relatedListConfig: ListConfig,
|
|
249
|
+
context: AccessContext,
|
|
250
|
+
config: OpenSaasConfig,
|
|
251
|
+
prisma: unknown,
|
|
252
|
+
): Promise<Record<string, unknown> | Array<Record<string, unknown>>> {
|
|
253
|
+
const operationsArray = Array.isArray(operations) ? operations : [operations]
|
|
254
|
+
|
|
255
|
+
const processedOps = await Promise.all(
|
|
256
|
+
operationsArray.map(async (op) => {
|
|
257
|
+
// Process the create portion through create hooks
|
|
258
|
+
const opRecord = op as Record<string, unknown>
|
|
259
|
+
const processedCreate = await processNestedCreate(
|
|
260
|
+
opRecord.create as Record<string, unknown> | Array<Record<string, unknown>>,
|
|
261
|
+
relatedListConfig,
|
|
262
|
+
context,
|
|
263
|
+
config,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
// Check access for the connect portion (try to find existing item)
|
|
267
|
+
try {
|
|
268
|
+
// Access Prisma model dynamically - required because model names are generated at runtime
|
|
269
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
270
|
+
const model = (prisma as any)[getDbKey(relatedListName)]
|
|
271
|
+
const existingItem = await model.findUnique({
|
|
272
|
+
where: opRecord.where,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
if (existingItem) {
|
|
276
|
+
// Check update access for connection
|
|
277
|
+
const updateAccess = relatedListConfig.access?.operation?.update
|
|
278
|
+
const accessResult = await checkAccess(updateAccess, {
|
|
279
|
+
session: context.session,
|
|
280
|
+
item: existingItem,
|
|
281
|
+
context,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (accessResult === false) {
|
|
285
|
+
throw new Error('Access denied: Cannot connect to existing item')
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
// Item doesn't exist, will use create (already processed)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
where: (op as Record<string, unknown>).where,
|
|
294
|
+
create: processedCreate,
|
|
295
|
+
}
|
|
296
|
+
}),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return Array.isArray(operations) ? processedOps : processedOps[0]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Process all nested operations in a data payload
|
|
304
|
+
* Recursively handles relationship fields with nested writes
|
|
305
|
+
*/
|
|
306
|
+
export async function processNestedOperations(
|
|
307
|
+
data: Record<string, unknown>,
|
|
308
|
+
fieldConfigs: Record<string, FieldConfig>,
|
|
309
|
+
config: OpenSaasConfig,
|
|
310
|
+
context: AccessContext & { prisma: unknown },
|
|
311
|
+
operation: 'create' | 'update',
|
|
312
|
+
depth: number = 0,
|
|
313
|
+
): Promise<Record<string, unknown>> {
|
|
314
|
+
const MAX_DEPTH = 5
|
|
315
|
+
|
|
316
|
+
if (depth >= MAX_DEPTH) {
|
|
317
|
+
return data
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const processed: Record<string, unknown> = {}
|
|
321
|
+
|
|
322
|
+
for (const [fieldName, value] of Object.entries(data)) {
|
|
323
|
+
const fieldConfig = fieldConfigs[fieldName]
|
|
324
|
+
|
|
325
|
+
// If not a relationship field or no value, pass through
|
|
326
|
+
if (!isRelationshipField(fieldConfig) || value === null || value === undefined) {
|
|
327
|
+
processed[fieldName] = value
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Get related list config
|
|
332
|
+
const relationshipField = fieldConfig as { type: 'relationship'; ref: string }
|
|
333
|
+
const relatedConfig = getRelatedListConfig(relationshipField.ref, config)
|
|
334
|
+
if (!relatedConfig) {
|
|
335
|
+
processed[fieldName] = value
|
|
336
|
+
continue
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const { listName: relatedListName, listConfig: relatedListConfig } = relatedConfig
|
|
340
|
+
|
|
341
|
+
// Process different nested operation types
|
|
342
|
+
const nestedOp: Record<string, unknown> = {}
|
|
343
|
+
const valueRecord = value as Record<string, unknown>
|
|
344
|
+
|
|
345
|
+
if (valueRecord.create !== undefined) {
|
|
346
|
+
nestedOp.create = await processNestedCreate(
|
|
347
|
+
valueRecord.create as Record<string, unknown> | Array<Record<string, unknown>>,
|
|
348
|
+
relatedListConfig,
|
|
349
|
+
context,
|
|
350
|
+
config,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (valueRecord.connect !== undefined) {
|
|
355
|
+
nestedOp.connect = await processNestedConnect(
|
|
356
|
+
valueRecord.connect as Record<string, unknown> | Array<Record<string, unknown>>,
|
|
357
|
+
relatedListName,
|
|
358
|
+
relatedListConfig,
|
|
359
|
+
context,
|
|
360
|
+
context.prisma,
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (valueRecord.connectOrCreate !== undefined) {
|
|
365
|
+
nestedOp.connectOrCreate = await processNestedConnectOrCreate(
|
|
366
|
+
valueRecord.connectOrCreate as Record<string, unknown> | Array<Record<string, unknown>>,
|
|
367
|
+
relatedListName,
|
|
368
|
+
relatedListConfig,
|
|
369
|
+
context,
|
|
370
|
+
config,
|
|
371
|
+
context.prisma,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (valueRecord.update !== undefined) {
|
|
376
|
+
nestedOp.update = await processNestedUpdate(
|
|
377
|
+
valueRecord.update as Record<string, unknown> | Array<Record<string, unknown>>,
|
|
378
|
+
relatedListName,
|
|
379
|
+
relatedListConfig,
|
|
380
|
+
context,
|
|
381
|
+
config,
|
|
382
|
+
context.prisma,
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// For other operations, pass through (disconnect, delete, set, etc.)
|
|
387
|
+
// These will be subject to Prisma's own constraints
|
|
388
|
+
if (valueRecord.disconnect !== undefined) {
|
|
389
|
+
nestedOp.disconnect = valueRecord.disconnect
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (valueRecord.delete !== undefined) {
|
|
393
|
+
nestedOp.delete = valueRecord.delete
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (valueRecord.deleteMany !== undefined) {
|
|
397
|
+
nestedOp.deleteMany = valueRecord.deleteMany
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (valueRecord.set !== undefined) {
|
|
401
|
+
nestedOp.set = valueRecord.set
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (valueRecord.updateMany !== undefined) {
|
|
405
|
+
nestedOp.updateMany = valueRecord.updateMany
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
processed[fieldName] = nestedOp
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return processed
|
|
412
|
+
}
|