|
4 | 4 | import { FieldInfo, PrismaWriteActionType, PrismaWriteActions } from '../types'; |
5 | 5 | import { resolveField } from './model-meta'; |
6 | 6 | import { ModelMeta } from './types'; |
7 | | -import { Enumerable, ensureArray, getModelFields } from './utils'; |
| 7 | +import { enumerate, getModelFields } from './utils'; |
8 | 8 |
|
9 | 9 | type NestingPathItem = { field?: FieldInfo; where: any; unique: boolean }; |
10 | 10 |
|
@@ -34,33 +34,25 @@ export type VisitorContext = { |
34 | 34 | export type NestedWriterVisitorCallback = { |
35 | 35 | create?: (model: string, args: any[], context: VisitorContext) => Promise<void>; |
36 | 36 |
|
37 | | - connectOrCreate?: ( |
38 | | - model: string, |
39 | | - args: Enumerable<{ where: object; create: any }>, |
40 | | - context: VisitorContext |
41 | | - ) => Promise<void>; |
| 37 | + connectOrCreate?: (model: string, args: { where: object; create: any }, context: VisitorContext) => Promise<void>; |
42 | 38 |
|
43 | | - connect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>; |
| 39 | + connect?: (model: string, args: object, context: VisitorContext) => Promise<void>; |
44 | 40 |
|
45 | | - disconnect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>; |
| 41 | + disconnect?: (model: string, args: object, context: VisitorContext) => Promise<void>; |
46 | 42 |
|
47 | | - update?: (model: string, args: Enumerable<{ where: object; data: any }>, context: VisitorContext) => Promise<void>; |
| 43 | + update?: (model: string, args: { where: object; data: any }, context: VisitorContext) => Promise<void>; |
48 | 44 |
|
49 | | - updateMany?: ( |
50 | | - model: string, |
51 | | - args: Enumerable<{ where?: object; data: any }>, |
52 | | - context: VisitorContext |
53 | | - ) => Promise<void>; |
| 45 | + updateMany?: (model: string, args: { where?: object; data: any }, context: VisitorContext) => Promise<void>; |
54 | 46 |
|
55 | 47 | upsert?: ( |
56 | 48 | model: string, |
57 | | - args: Enumerable<{ where: object; create: any; update: any }>, |
| 49 | + args: { where: object; create: any; update: any }, |
58 | 50 | context: VisitorContext |
59 | 51 | ) => Promise<void>; |
60 | 52 |
|
61 | | - delete?: (model: string, args: Enumerable<object> | boolean, context: VisitorContext) => Promise<void>; |
| 53 | + delete?: (model: string, args: object | boolean, context: VisitorContext) => Promise<void>; |
62 | 54 |
|
63 | | - deleteMany?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>; |
| 55 | + deleteMany?: (model: string, args: any | object, context: VisitorContext) => Promise<void>; |
64 | 56 |
|
65 | 57 | field?: (field: FieldInfo, action: PrismaWriteActionType, data: any, context: VisitorContext) => Promise<void>; |
66 | 58 | }; |
@@ -115,126 +107,162 @@ export class NestedWriteVisitor { |
115 | 107 | return; |
116 | 108 | } |
117 | 109 |
|
118 | | - const fieldContainers: any[] = []; |
119 | 110 | const isToOneUpdate = field?.isDataModel && !field.isArray; |
120 | 111 | const context = { parent, field, nestingPath: [...nestingPath] }; |
121 | 112 |
|
122 | 113 | // visit payload |
123 | 114 | switch (action) { |
124 | 115 | case 'create': |
125 | 116 | context.nestingPath.push({ field, where: {}, unique: false }); |
126 | | - if (this.callback.create) { |
127 | | - await this.callback.create(model, data, context); |
| 117 | + for (const item of enumerate(data)) { |
| 118 | + if (this.callback.create) { |
| 119 | + await this.callback.create(model, item, context); |
| 120 | + } |
| 121 | + await this.visitSubPayload(model, action, item, context.nestingPath); |
128 | 122 | } |
129 | | - fieldContainers.push(...ensureArray(data)); |
130 | 123 | break; |
131 | 124 |
|
132 | 125 | case 'createMany': |
133 | 126 | // skip the 'data' layer so as to keep consistency with 'create' |
134 | 127 | if (data.data) { |
135 | 128 | context.nestingPath.push({ field, where: {}, unique: false }); |
136 | | - if (this.callback.create) { |
137 | | - await this.callback.create(model, data.data, context); |
| 129 | + for (const item of enumerate(data.data)) { |
| 130 | + if (this.callback.create) { |
| 131 | + await this.callback.create(model, item, context); |
| 132 | + } |
| 133 | + await this.visitSubPayload(model, action, item, context.nestingPath); |
138 | 134 | } |
139 | | - fieldContainers.push(...ensureArray(data.data)); |
140 | 135 | } |
141 | 136 | break; |
142 | 137 |
|
143 | 138 | case 'connectOrCreate': |
144 | 139 | context.nestingPath.push({ field, where: data.where, unique: true }); |
145 | | - if (this.callback.connectOrCreate) { |
146 | | - await this.callback.connectOrCreate(model, data, context); |
| 140 | + for (const item of enumerate(data)) { |
| 141 | + if (this.callback.connectOrCreate) { |
| 142 | + await this.callback.connectOrCreate(model, item, context); |
| 143 | + } |
| 144 | + await this.visitSubPayload(model, action, item.create, context.nestingPath); |
147 | 145 | } |
148 | | - fieldContainers.push(...ensureArray(data).map((d) => d.create)); |
149 | 146 | break; |
150 | 147 |
|
151 | 148 | case 'connect': |
152 | | - context.nestingPath.push({ field, where: data, unique: true }); |
153 | 149 | if (this.callback.connect) { |
154 | | - await this.callback.connect(model, data, context); |
| 150 | + for (const item of enumerate(data)) { |
| 151 | + const newContext = { |
| 152 | + ...context, |
| 153 | + nestingPath: [...context.nestingPath, { field, where: item, unique: true }], |
| 154 | + }; |
| 155 | + await this.callback.connect(model, item, newContext); |
| 156 | + } |
155 | 157 | } |
156 | 158 | break; |
157 | 159 |
|
158 | 160 | case 'disconnect': |
159 | 161 | // disconnect has two forms: |
160 | 162 | // if relation is to-many, the payload is a unique filter object |
161 | 163 | // if relation is to-one, the payload can only be boolean `true` |
162 | | - context.nestingPath.push({ field, where: data, unique: typeof data === 'object' }); |
163 | 164 | if (this.callback.disconnect) { |
164 | | - await this.callback.disconnect(model, data, context); |
| 165 | + for (const item of enumerate(data)) { |
| 166 | + const newContext = { |
| 167 | + ...context, |
| 168 | + nestingPath: [ |
| 169 | + ...context.nestingPath, |
| 170 | + { field, where: item, unique: typeof item === 'object' }, |
| 171 | + ], |
| 172 | + }; |
| 173 | + await this.callback.disconnect(model, item, newContext); |
| 174 | + } |
165 | 175 | } |
166 | 176 | break; |
167 | 177 |
|
168 | 178 | case 'update': |
169 | 179 | context.nestingPath.push({ field, where: data.where, unique: false }); |
170 | | - if (this.callback.update) { |
171 | | - await this.callback.update(model, data, context); |
| 180 | + for (const item of enumerate(data)) { |
| 181 | + if (this.callback.update) { |
| 182 | + await this.callback.update(model, item, context); |
| 183 | + } |
| 184 | + const payload = isToOneUpdate ? item : item.data; |
| 185 | + await this.visitSubPayload(model, action, payload, context.nestingPath); |
172 | 186 | } |
173 | | - fieldContainers.push(...ensureArray(data).map((d) => (isToOneUpdate ? d : d.data))); |
174 | 187 | break; |
175 | 188 |
|
176 | 189 | case 'updateMany': |
177 | 190 | context.nestingPath.push({ field, where: data.where, unique: false }); |
178 | | - if (this.callback.updateMany) { |
179 | | - await this.callback.updateMany(model, data, context); |
| 191 | + for (const item of enumerate(data)) { |
| 192 | + if (this.callback.updateMany) { |
| 193 | + await this.callback.updateMany(model, item, context); |
| 194 | + } |
| 195 | + await this.visitSubPayload(model, action, item, context.nestingPath); |
180 | 196 | } |
181 | | - fieldContainers.push(...ensureArray(data)); |
182 | 197 | break; |
183 | 198 |
|
184 | | - case 'upsert': |
| 199 | + case 'upsert': { |
185 | 200 | context.nestingPath.push({ field, where: data.where, unique: true }); |
186 | | - if (this.callback.upsert) { |
187 | | - await this.callback.upsert(model, data, context); |
| 201 | + for (const item of enumerate(data)) { |
| 202 | + if (this.callback.upsert) { |
| 203 | + await this.callback.upsert(model, item, context); |
| 204 | + } |
| 205 | + await this.visitSubPayload(model, action, item.create, context.nestingPath); |
| 206 | + await this.visitSubPayload(model, action, item.update, context.nestingPath); |
188 | 207 | } |
189 | | - fieldContainers.push(...ensureArray(data).map((d) => d.create)); |
190 | | - fieldContainers.push(...ensureArray(data).map((d) => d.update)); |
191 | 208 | break; |
| 209 | + } |
192 | 210 |
|
193 | | - case 'delete': |
194 | | - context.nestingPath.push({ field, where: data.where, unique: false }); |
| 211 | + case 'delete': { |
195 | 212 | if (this.callback.delete) { |
196 | | - await this.callback.delete(model, data, context); |
| 213 | + context.nestingPath.push({ field, where: data.where, unique: false }); |
| 214 | + for (const item of enumerate(data)) { |
| 215 | + await this.callback.delete(model, item, context); |
| 216 | + } |
197 | 217 | } |
198 | 218 | break; |
| 219 | + } |
199 | 220 |
|
200 | 221 | case 'deleteMany': |
201 | | - context.nestingPath.push({ field, where: data.where, unique: false }); |
202 | 222 | if (this.callback.deleteMany) { |
203 | | - await this.callback.deleteMany(model, data, context); |
| 223 | + context.nestingPath.push({ field, where: data.where, unique: false }); |
| 224 | + for (const item of enumerate(data)) { |
| 225 | + await this.callback.deleteMany(model, item, context); |
| 226 | + } |
204 | 227 | } |
205 | 228 | break; |
206 | 229 |
|
207 | 230 | default: { |
208 | 231 | throw new Error(`unhandled action type ${action}`); |
209 | 232 | } |
210 | 233 | } |
| 234 | + } |
211 | 235 |
|
212 | | - for (const fieldContainer of fieldContainers) { |
213 | | - for (const field of getModelFields(fieldContainer)) { |
214 | | - const fieldInfo = resolveField(this.modelMeta, model, field); |
215 | | - if (!fieldInfo) { |
216 | | - continue; |
217 | | - } |
| 236 | + private async visitSubPayload( |
| 237 | + model: string, |
| 238 | + action: PrismaWriteActionType, |
| 239 | + payload: any, |
| 240 | + nestingPath: NestingPathItem[] |
| 241 | + ) { |
| 242 | + for (const field of getModelFields(payload)) { |
| 243 | + const fieldInfo = resolveField(this.modelMeta, model, field); |
| 244 | + if (!fieldInfo) { |
| 245 | + continue; |
| 246 | + } |
218 | 247 |
|
219 | | - if (fieldInfo.isDataModel) { |
220 | | - // recurse into nested payloads |
221 | | - for (const [subAction, subData] of Object.entries<any>(fieldContainer[field])) { |
222 | | - if (this.isPrismaWriteAction(subAction) && subData) { |
223 | | - await this.doVisit(fieldInfo.type, subAction, subData, fieldContainer[field], fieldInfo, [ |
224 | | - ...context.nestingPath, |
225 | | - ]); |
226 | | - } |
227 | | - } |
228 | | - } else { |
229 | | - // visit plain field |
230 | | - if (this.callback.field) { |
231 | | - await this.callback.field(fieldInfo, action, fieldContainer[field], { |
232 | | - parent: fieldContainer, |
233 | | - nestingPath: [...context.nestingPath], |
234 | | - field: fieldInfo, |
235 | | - }); |
| 248 | + if (fieldInfo.isDataModel) { |
| 249 | + // recurse into nested payloads |
| 250 | + for (const [subAction, subData] of Object.entries<any>(payload[field])) { |
| 251 | + if (this.isPrismaWriteAction(subAction) && subData) { |
| 252 | + await this.doVisit(fieldInfo.type, subAction, subData, payload[field], fieldInfo, [ |
| 253 | + ...nestingPath, |
| 254 | + ]); |
236 | 255 | } |
237 | 256 | } |
| 257 | + } else { |
| 258 | + // visit plain field |
| 259 | + if (this.callback.field) { |
| 260 | + await this.callback.field(fieldInfo, action, payload[field], { |
| 261 | + parent: payload, |
| 262 | + nestingPath, |
| 263 | + field: fieldInfo, |
| 264 | + }); |
| 265 | + } |
238 | 266 | } |
239 | 267 | } |
240 | 268 | } |
|
0 commit comments