|
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