* Apply new mutations to this transaction, intelligently merging with existing mutations * * When mutations operate on the same item (same globalKey), they are merged according to * the following rules: * * - **insert + update** → insert (merge changes, keep empty original) * - **i
(mutations: Array<PendingMutation<any>>)
| 334 | * @param mutations - Array of new mutations to apply |
| 335 | */ |
| 336 | applyMutations(mutations: Array<PendingMutation<any>>): void { |
| 337 | // Merge via a globalKey-keyed map rather than a findIndex scan per |
| 338 | // mutation, which is O(n²) for bulk operations (e.g. inserting many rows |
| 339 | // in one call). Map preserves insertion order, matching the previous |
| 340 | // replace-in-place / remove / append semantics. |
| 341 | const merged = new Map<string, PendingMutation<any>>() |
| 342 | for (const mutation of this.mutations) { |
| 343 | merged.set(mutation.globalKey, mutation) |
| 344 | } |
| 345 | |
| 346 | for (const newMutation of mutations) { |
| 347 | const existingMutation = merged.get(newMutation.globalKey) |
| 348 | |
| 349 | if (existingMutation) { |
| 350 | const mergeResult = mergePendingMutations(existingMutation, newMutation) |
| 351 | |
| 352 | if (mergeResult === null) { |
| 353 | // Remove the mutation (e.g., delete after insert cancels both) |
| 354 | merged.delete(newMutation.globalKey) |
| 355 | } else { |
| 356 | // Replace with merged mutation |
| 357 | merged.set(newMutation.globalKey, mergeResult) |
| 358 | } |
| 359 | } else { |
| 360 | // Insert new mutation |
| 361 | merged.set(newMutation.globalKey, newMutation) |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | // Rebuild in place to preserve the array's identity for external holders |
| 366 | this.mutations.length = 0 |
| 367 | for (const mutation of merged.values()) { |
| 368 | this.mutations.push(mutation) |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | /** |
| 373 | * Rollback the transaction and any conflicting transactions |