@bilig/core 0.1.27 → 0.1.29

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.
Files changed (66) hide show
  1. package/dist/cell-mutations-at.d.ts +26 -0
  2. package/dist/cell-mutations-at.js +70 -0
  3. package/dist/cell-mutations-at.js.map +1 -0
  4. package/dist/cell-store.d.ts +1 -0
  5. package/dist/cell-store.js +2 -0
  6. package/dist/cell-store.js.map +1 -1
  7. package/dist/engine/live.d.ts +1 -1
  8. package/dist/engine/live.js +64 -58
  9. package/dist/engine/live.js.map +1 -1
  10. package/dist/engine/runtime-state.d.ts +6 -0
  11. package/dist/engine/runtime-state.js.map +1 -1
  12. package/dist/engine/services/cell-state-service.d.ts +4 -0
  13. package/dist/engine/services/cell-state-service.js +20 -7
  14. package/dist/engine/services/cell-state-service.js.map +1 -1
  15. package/dist/engine/services/formula-binding-service.d.ts +11 -2
  16. package/dist/engine/services/formula-binding-service.js +284 -7
  17. package/dist/engine/services/formula-binding-service.js.map +1 -1
  18. package/dist/engine/services/formula-evaluation-service.d.ts +3 -1
  19. package/dist/engine/services/formula-evaluation-service.js +30 -2
  20. package/dist/engine/services/formula-evaluation-service.js.map +1 -1
  21. package/dist/engine/services/formula-graph-service.d.ts +5 -1
  22. package/dist/engine/services/formula-graph-service.js +21 -4
  23. package/dist/engine/services/formula-graph-service.js.map +1 -1
  24. package/dist/engine/services/lookup-service.d.ts +51 -0
  25. package/dist/engine/services/lookup-service.js +370 -0
  26. package/dist/engine/services/lookup-service.js.map +1 -0
  27. package/dist/engine/services/mutation-history-fast-path.d.ts +17 -0
  28. package/dist/engine/services/mutation-history-fast-path.js +119 -0
  29. package/dist/engine/services/mutation-history-fast-path.js.map +1 -0
  30. package/dist/engine/services/mutation-service.d.ts +26 -2
  31. package/dist/engine/services/mutation-service.js +169 -36
  32. package/dist/engine/services/mutation-service.js.map +1 -1
  33. package/dist/engine/services/mutation-support-service.d.ts +27 -0
  34. package/dist/engine/services/mutation-support-service.js +114 -0
  35. package/dist/engine/services/mutation-support-service.js.map +1 -1
  36. package/dist/engine/services/operation-service.d.ts +3 -1
  37. package/dist/engine/services/operation-service.js +351 -75
  38. package/dist/engine/services/operation-service.js.map +1 -1
  39. package/dist/engine/services/pivot-service.d.ts +7 -0
  40. package/dist/engine/services/pivot-service.js +9 -1
  41. package/dist/engine/services/pivot-service.js.map +1 -1
  42. package/dist/engine/services/recalc-service.d.ts +2 -0
  43. package/dist/engine/services/recalc-service.js +15 -6
  44. package/dist/engine/services/recalc-service.js.map +1 -1
  45. package/dist/engine/services/runtime-scratch-service.d.ts +1 -0
  46. package/dist/engine/services/runtime-scratch-service.js +1 -0
  47. package/dist/engine/services/runtime-scratch-service.js.map +1 -1
  48. package/dist/engine-value-utils.d.ts +2 -0
  49. package/dist/engine-value-utils.js +28 -0
  50. package/dist/engine-value-utils.js.map +1 -1
  51. package/dist/engine.d.ts +12 -0
  52. package/dist/engine.js +35 -3
  53. package/dist/engine.js.map +1 -1
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +2 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/literal-sheet-loader.d.ts +4 -0
  58. package/dist/literal-sheet-loader.js +69 -0
  59. package/dist/literal-sheet-loader.js.map +1 -0
  60. package/dist/wasm-facade.d.ts +2 -0
  61. package/dist/wasm-facade.js +34 -5
  62. package/dist/wasm-facade.js.map +1 -1
  63. package/dist/workbook-store.d.ts +6 -0
  64. package/dist/workbook-store.js +48 -0
  65. package/dist/workbook-store.js.map +1 -1
  66. package/package.json +5 -5
@@ -1,11 +1,24 @@
1
1
  import { Effect } from "effect";
2
- import { ValueTag } from "@bilig/protocol";
2
+ import { formatAddress } from "@bilig/formula";
3
3
  import { makeCellEntity } from "../../entity-ids.js";
4
4
  import { batchOpOrder, compareOpOrder, createBatch, markBatchApplied, } from "../../replica-state.js";
5
- import { emptyValue, literalToValue } from "../../engine-value-utils.js";
5
+ import { emptyValue, writeLiteralToCellStore } from "../../engine-value-utils.js";
6
6
  import { spillDependencyKey, tableDependencyKey } from "../../engine-metadata-utils.js";
7
- import { normalizeDefinedName, pivotKey } from "../../workbook-store.js";
7
+ import { makeCellKey, normalizeDefinedName, pivotKey, } from "../../workbook-store.js";
8
8
  import { EngineMutationError } from "../errors.js";
9
+ const noopVersionStore = {
10
+ get() {
11
+ return undefined;
12
+ },
13
+ set() {
14
+ return;
15
+ },
16
+ };
17
+ const FAST_LITERAL_OVERWRITE_FLAGS = 2 /* CellFlags.HasFormula */ |
18
+ 4 /* CellFlags.JsOnly */ |
19
+ 8 /* CellFlags.InCycle */ |
20
+ 64 /* CellFlags.SpillChild */ |
21
+ 128 /* CellFlags.PivotOutput */;
9
22
  function mutationErrorMessage(message, cause) {
10
23
  return cause instanceof Error && cause.message.length > 0 ? cause.message : message;
11
24
  }
@@ -25,6 +38,30 @@ export function createEngineOperationService(args) {
25
38
  const emitBatch = (batch) => {
26
39
  args.state.batchListeners.forEach((listener) => listener(batch));
27
40
  };
41
+ const entityVersions = args.state.trackReplicaVersions
42
+ ? args.state.entityVersions
43
+ : noopVersionStore;
44
+ const sheetDeleteVersions = args.state.trackReplicaVersions
45
+ ? args.state.sheetDeleteVersions
46
+ : noopVersionStore;
47
+ const setEntityVersionForOp = (op, order) => {
48
+ if (!args.state.trackReplicaVersions) {
49
+ return;
50
+ }
51
+ entityVersions.set(entityKeyForOp(op), order);
52
+ };
53
+ const setCellEntityVersion = (sheetName, address, order) => {
54
+ if (!args.state.trackReplicaVersions) {
55
+ return;
56
+ }
57
+ entityVersions.set(`cell:${sheetName}!${address}`, order);
58
+ };
59
+ const setSheetDeleteVersion = (sheetName, order) => {
60
+ if (!args.state.trackReplicaVersions) {
61
+ return;
62
+ }
63
+ sheetDeleteVersions.set(sheetName, order);
64
+ };
28
65
  const pruneCellIfOrphaned = (cellIndex) => {
29
66
  if (args.collectFormulaDependents(makeCellEntity(cellIndex)).length > 0) {
30
67
  return;
@@ -98,6 +135,11 @@ export function createEngineOperationService(args) {
98
135
  return assertNever(op);
99
136
  }
100
137
  };
138
+ const canFastPathLiteralOverwrite = (cellIndex) => {
139
+ const flags = args.state.workbook.cellStore.flags[cellIndex] ?? 0;
140
+ return ((flags & FAST_LITERAL_OVERWRITE_FLAGS) === 0 &&
141
+ args.state.formulas.get(cellIndex) === undefined);
142
+ };
101
143
  const sheetDeleteBarrierForOp = (op) => {
102
144
  switch (op.kind) {
103
145
  case "upsertWorkbook":
@@ -131,20 +173,19 @@ export function createEngineOperationService(args) {
131
173
  case "upsertSpillRange":
132
174
  case "deleteSpillRange":
133
175
  case "deletePivotTable":
134
- return args.state.sheetDeleteVersions.get(op.sheetName);
176
+ return sheetDeleteVersions.get(op.sheetName);
135
177
  case "setStyleRange":
136
178
  case "setFormatRange":
137
- return args.state.sheetDeleteVersions.get(op.range.sheetName);
179
+ return sheetDeleteVersions.get(op.range.sheetName);
138
180
  case "upsertCellNumberFormat":
139
181
  case "upsertCellStyle":
140
182
  return undefined;
141
183
  case "upsertSheet":
142
- return args.state.sheetDeleteVersions.get(op.name);
184
+ return sheetDeleteVersions.get(op.name);
143
185
  case "renameSheet":
144
- return args.state.sheetDeleteVersions.get(op.oldName);
186
+ return sheetDeleteVersions.get(op.oldName);
145
187
  case "upsertPivotTable":
146
- return (args.state.sheetDeleteVersions.get(op.sheetName) ??
147
- args.state.sheetDeleteVersions.get(op.source.sheetName));
188
+ return (sheetDeleteVersions.get(op.sheetName) ?? sheetDeleteVersions.get(op.source.sheetName));
148
189
  default:
149
190
  return assertNever(op);
150
191
  }
@@ -154,7 +195,7 @@ export function createEngineOperationService(args) {
154
195
  if (sheetDeleteOrder && compareOpOrder(order, sheetDeleteOrder) <= 0) {
155
196
  return false;
156
197
  }
157
- const existingOrder = args.state.entityVersions.get(entityKeyForOp(op));
198
+ const existingOrder = entityVersions.get(entityKeyForOp(op));
158
199
  if (existingOrder && compareOpOrder(order, existingOrder) <= 0) {
159
200
  return false;
160
201
  }
@@ -167,7 +208,7 @@ export function createEngineOperationService(args) {
167
208
  else {
168
209
  args.state.workbook.deleteSpill(op.sheetName, op.address);
169
210
  }
170
- args.state.entityVersions.set(entityKeyForOp(op), order);
211
+ setEntityVersionForOp(op, order);
171
212
  return collectTrackedDependents(args.reverseState.reverseSpillEdges, [
172
213
  spillDependencyKey(op.sheetName, op.address),
173
214
  ]);
@@ -183,17 +224,17 @@ export function createEngineOperationService(args) {
183
224
  rows: op.rows,
184
225
  cols: op.cols,
185
226
  });
186
- args.state.entityVersions.set(entityKeyForOp(op), order);
227
+ setEntityVersionForOp(op, order);
187
228
  };
188
229
  const applyPivotDeleteOp = (op, order) => {
189
230
  const pivot = args.state.workbook.getPivot(op.sheetName, op.address);
190
231
  if (!pivot) {
191
- args.state.entityVersions.set(entityKeyForOp(op), order);
232
+ setEntityVersionForOp(op, order);
192
233
  return [];
193
234
  }
194
235
  const changedPivotOutputs = args.clearOwnedPivot(pivot);
195
236
  args.state.workbook.deletePivot(op.sheetName, op.address);
196
- args.state.entityVersions.set(entityKeyForOp(op), order);
237
+ setEntityVersionForOp(op, order);
197
238
  return changedPivotOutputs;
198
239
  };
199
240
  const applyDerivedOpNow = (op) => {
@@ -216,6 +257,7 @@ export function createEngineOperationService(args) {
216
257
  }
217
258
  };
218
259
  const applyBatchNow = (batch, source, potentialNewCells) => {
260
+ const isRestore = source === "restore";
219
261
  args.beginMutationCollection();
220
262
  let changedInputCount = 0;
221
263
  let formulaChangedCount = 0;
@@ -223,6 +265,7 @@ export function createEngineOperationService(args) {
223
265
  let topologyChanged = false;
224
266
  let sheetDeleted = false;
225
267
  let structuralInvalidation = false;
268
+ let compileMs = 0;
226
269
  const invalidatedRanges = [];
227
270
  const invalidatedRows = [];
228
271
  const invalidatedColumns = [];
@@ -242,24 +285,24 @@ export function createEngineOperationService(args) {
242
285
  switch (op.kind) {
243
286
  case "upsertWorkbook":
244
287
  args.state.workbook.workbookName = op.name;
245
- args.state.entityVersions.set(entityKeyForOp(op), order);
288
+ setEntityVersionForOp(op, order);
246
289
  break;
247
290
  case "setWorkbookMetadata":
248
291
  args.state.workbook.setWorkbookProperty(op.key, op.value);
249
- args.state.entityVersions.set(entityKeyForOp(op), order);
292
+ setEntityVersionForOp(op, order);
250
293
  break;
251
294
  case "setCalculationSettings":
252
295
  args.state.workbook.setCalculationSettings(op.settings);
253
- args.state.entityVersions.set(entityKeyForOp(op), order);
296
+ setEntityVersionForOp(op, order);
254
297
  break;
255
298
  case "setVolatileContext":
256
299
  args.state.workbook.setVolatileContext(op.context);
257
- args.state.entityVersions.set(entityKeyForOp(op), order);
300
+ setEntityVersionForOp(op, order);
258
301
  break;
259
302
  case "upsertSheet": {
260
303
  args.state.workbook.createSheet(op.name, op.order, op.id);
261
- args.state.entityVersions.set(entityKeyForOp(op), order);
262
- const tombstone = args.state.sheetDeleteVersions.get(op.name);
304
+ setEntityVersionForOp(op, order);
305
+ const tombstone = sheetDeleteVersions.get(op.name);
263
306
  if (!tombstone || compareOpOrder(order, tombstone) > 0) {
264
307
  args.state.sheetDeleteVersions.delete(op.name);
265
308
  }
@@ -271,10 +314,12 @@ export function createEngineOperationService(args) {
271
314
  }
272
315
  case "renameSheet": {
273
316
  const renamedSheet = args.state.workbook.renameSheet(op.oldName, op.newName);
274
- args.state.entityVersions.set(`sheet:${op.oldName}`, order);
275
- args.state.entityVersions.set(`sheet:${op.newName}`, order);
276
- args.state.sheetDeleteVersions.set(op.oldName, order);
277
- const renamedTombstone = args.state.sheetDeleteVersions.get(op.newName);
317
+ if (args.state.trackReplicaVersions) {
318
+ entityVersions.set(`sheet:${op.oldName}`, order);
319
+ entityVersions.set(`sheet:${op.newName}`, order);
320
+ }
321
+ setSheetDeleteVersion(op.oldName, order);
322
+ const renamedTombstone = sheetDeleteVersions.get(op.newName);
278
323
  if (!renamedTombstone || compareOpOrder(order, renamedTombstone) > 0) {
279
324
  args.state.sheetDeleteVersions.delete(op.newName);
280
325
  }
@@ -298,8 +343,8 @@ export function createEngineOperationService(args) {
298
343
  changedInputCount += removal.changedInputCount;
299
344
  formulaChangedCount += removal.formulaChangedCount;
300
345
  explicitChangedCount = removal.explicitChangedCount;
301
- args.state.entityVersions.set(entityKeyForOp(op), order);
302
- args.state.sheetDeleteVersions.set(op.name, order);
346
+ setEntityVersionForOp(op, order);
347
+ setSheetDeleteVersion(op.name, order);
303
348
  topologyChanged = true;
304
349
  sheetDeleted = true;
305
350
  structuralInvalidation = true;
@@ -322,7 +367,7 @@ export function createEngineOperationService(args) {
322
367
  topologyChanged = true;
323
368
  structuralInvalidation = true;
324
369
  refreshAllPivots = true;
325
- args.state.entityVersions.set(entityKeyForOp(op), order);
370
+ setEntityVersionForOp(op, order);
326
371
  break;
327
372
  }
328
373
  case "updateRowMetadata":
@@ -332,7 +377,7 @@ export function createEngineOperationService(args) {
332
377
  startIndex: op.start,
333
378
  endIndex: op.start + op.count - 1,
334
379
  });
335
- args.state.entityVersions.set(entityKeyForOp(op), order);
380
+ setEntityVersionForOp(op, order);
336
381
  break;
337
382
  case "updateColumnMetadata":
338
383
  args.state.workbook.setColumnMetadata(op.sheetName, op.start, op.count, op.size, op.hidden);
@@ -341,44 +386,44 @@ export function createEngineOperationService(args) {
341
386
  startIndex: op.start,
342
387
  endIndex: op.start + op.count - 1,
343
388
  });
344
- args.state.entityVersions.set(entityKeyForOp(op), order);
389
+ setEntityVersionForOp(op, order);
345
390
  break;
346
391
  case "setFreezePane":
347
392
  args.state.workbook.setFreezePane(op.sheetName, op.rows, op.cols);
348
393
  structuralInvalidation = true;
349
- args.state.entityVersions.set(entityKeyForOp(op), order);
394
+ setEntityVersionForOp(op, order);
350
395
  break;
351
396
  case "clearFreezePane":
352
397
  args.state.workbook.clearFreezePane(op.sheetName);
353
398
  structuralInvalidation = true;
354
- args.state.entityVersions.set(entityKeyForOp(op), order);
399
+ setEntityVersionForOp(op, order);
355
400
  break;
356
401
  case "setFilter":
357
402
  args.state.workbook.setFilter(op.sheetName, op.range);
358
403
  structuralInvalidation = true;
359
- args.state.entityVersions.set(entityKeyForOp(op), order);
404
+ setEntityVersionForOp(op, order);
360
405
  break;
361
406
  case "clearFilter":
362
407
  args.state.workbook.deleteFilter(op.sheetName, op.range);
363
408
  structuralInvalidation = true;
364
- args.state.entityVersions.set(entityKeyForOp(op), order);
409
+ setEntityVersionForOp(op, order);
365
410
  break;
366
411
  case "setSort":
367
412
  args.state.workbook.setSort(op.sheetName, op.range, op.keys);
368
413
  structuralInvalidation = true;
369
- args.state.entityVersions.set(entityKeyForOp(op), order);
414
+ setEntityVersionForOp(op, order);
370
415
  break;
371
416
  case "clearSort":
372
417
  args.state.workbook.deleteSort(op.sheetName, op.range);
373
418
  structuralInvalidation = true;
374
- args.state.entityVersions.set(entityKeyForOp(op), order);
419
+ setEntityVersionForOp(op, order);
375
420
  break;
376
421
  case "upsertTable": {
377
422
  args.state.workbook.setTable(op.table);
378
423
  const reboundCount = formulaChangedCount;
379
424
  formulaChangedCount = args.rebindTableDependents([tableDependencyKey(op.table.name)], formulaChangedCount);
380
425
  topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
381
- args.state.entityVersions.set(entityKeyForOp(op), order);
426
+ setEntityVersionForOp(op, order);
382
427
  break;
383
428
  }
384
429
  case "deleteTable": {
@@ -386,7 +431,7 @@ export function createEngineOperationService(args) {
386
431
  const reboundCount = formulaChangedCount;
387
432
  formulaChangedCount = args.rebindTableDependents([tableDependencyKey(op.name)], formulaChangedCount);
388
433
  topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
389
- args.state.entityVersions.set(entityKeyForOp(op), order);
434
+ setEntityVersionForOp(op, order);
390
435
  break;
391
436
  }
392
437
  case "upsertSpillRange":
@@ -397,15 +442,18 @@ export function createEngineOperationService(args) {
397
442
  break;
398
443
  }
399
444
  case "setCellValue": {
400
- const existingIndex = args.state.workbook.getCellIndex(op.sheetName, op.address);
401
- if (existingIndex !== undefined) {
402
- changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
445
+ if (!isRestore) {
446
+ const existingIndex = args.state.workbook.getCellIndex(op.sheetName, op.address);
447
+ if (existingIndex !== undefined) {
448
+ changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
449
+ }
403
450
  }
404
451
  const cellIndex = args.ensureCellTracked(op.sheetName, op.address);
405
- changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
406
- topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
407
- const value = literalToValue(op.value, args.state.strings);
408
- args.state.workbook.cellStore.setValue(cellIndex, value, value.tag === ValueTag.String ? value.stringId : 0);
452
+ if (!isRestore) {
453
+ changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
454
+ topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
455
+ }
456
+ writeLiteralToCellStore(args.state.workbook.cellStore, cellIndex, op.value, args.state.strings);
409
457
  args.state.workbook.cellStore.flags[cellIndex] =
410
458
  (args.state.workbook.cellStore.flags[cellIndex] ?? 0) &
411
459
  ~(2 /* CellFlags.HasFormula */ |
@@ -413,72 +461,82 @@ export function createEngineOperationService(args) {
413
461
  8 /* CellFlags.InCycle */ |
414
462
  64 /* CellFlags.SpillChild */ |
415
463
  128 /* CellFlags.PivotOutput */);
416
- pruneCellIfOrphaned(cellIndex);
464
+ if (!isRestore) {
465
+ pruneCellIfOrphaned(cellIndex);
466
+ }
417
467
  changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
418
- explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
419
- args.state.entityVersions.set(entityKeyForOp(op), order);
468
+ if (!isRestore) {
469
+ explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
470
+ setEntityVersionForOp(op, order);
471
+ }
420
472
  break;
421
473
  }
422
474
  case "setCellFormula": {
423
- const existingIndex = args.state.workbook.getCellIndex(op.sheetName, op.address);
424
- if (existingIndex !== undefined) {
425
- changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
475
+ if (!isRestore) {
476
+ const existingIndex = args.state.workbook.getCellIndex(op.sheetName, op.address);
477
+ if (existingIndex !== undefined) {
478
+ changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
479
+ }
426
480
  }
427
481
  const cellIndex = args.ensureCellTracked(op.sheetName, op.address);
428
- changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
429
- const compileStarted = performance.now();
482
+ if (!isRestore) {
483
+ changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
484
+ }
485
+ const compileStarted = isRestore ? 0 : performance.now();
430
486
  try {
431
487
  args.bindFormula(cellIndex, op.sheetName, op.formula);
432
- args.state.setLastMetrics({
433
- ...args.state.getLastMetrics(),
434
- compileMs: performance.now() - compileStarted,
435
- });
488
+ if (!isRestore) {
489
+ compileMs += performance.now() - compileStarted;
490
+ }
436
491
  formulaChangedCount = args.markFormulaChanged(cellIndex, formulaChangedCount);
437
492
  topologyChanged = true;
438
493
  }
439
494
  catch {
440
- args.state.setLastMetrics({
441
- ...args.state.getLastMetrics(),
442
- compileMs: performance.now() - compileStarted,
443
- });
495
+ if (!isRestore) {
496
+ compileMs += performance.now() - compileStarted;
497
+ }
444
498
  topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
445
499
  args.setInvalidFormulaValue(cellIndex);
446
500
  changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
447
501
  }
448
- explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
449
- args.state.entityVersions.set(entityKeyForOp(op), order);
502
+ if (!isRestore) {
503
+ explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
504
+ setEntityVersionForOp(op, order);
505
+ }
450
506
  break;
451
507
  }
452
508
  case "setCellFormat": {
453
509
  const cellIndex = args.ensureCellTracked(op.sheetName, op.address);
454
510
  args.state.workbook.setCellFormat(cellIndex, op.format);
455
- pruneCellIfOrphaned(cellIndex);
456
- explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
457
- args.state.entityVersions.set(entityKeyForOp(op), order);
511
+ if (!isRestore) {
512
+ pruneCellIfOrphaned(cellIndex);
513
+ explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
514
+ setEntityVersionForOp(op, order);
515
+ }
458
516
  break;
459
517
  }
460
518
  case "upsertCellStyle":
461
519
  args.state.workbook.upsertCellStyle(op.style);
462
- args.state.entityVersions.set(entityKeyForOp(op), order);
520
+ setEntityVersionForOp(op, order);
463
521
  break;
464
522
  case "upsertCellNumberFormat":
465
523
  args.state.workbook.upsertCellNumberFormat(op.format);
466
- args.state.entityVersions.set(entityKeyForOp(op), order);
524
+ setEntityVersionForOp(op, order);
467
525
  break;
468
526
  case "setStyleRange":
469
527
  args.state.workbook.setStyleRange(op.range, op.styleId);
470
528
  invalidatedRanges.push(op.range);
471
- args.state.entityVersions.set(entityKeyForOp(op), order);
529
+ setEntityVersionForOp(op, order);
472
530
  break;
473
531
  case "setFormatRange":
474
532
  args.state.workbook.setFormatRange(op.range, op.formatId);
475
533
  invalidatedRanges.push(op.range);
476
- args.state.entityVersions.set(entityKeyForOp(op), order);
534
+ setEntityVersionForOp(op, order);
477
535
  break;
478
536
  case "clearCell": {
479
537
  const cellIndex = args.state.workbook.getCellIndex(op.sheetName, op.address);
480
538
  if (cellIndex === undefined) {
481
- args.state.entityVersions.set(entityKeyForOp(op), order);
539
+ setEntityVersionForOp(op, order);
482
540
  break;
483
541
  }
484
542
  changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(cellIndex), changedInputCount);
@@ -495,7 +553,7 @@ export function createEngineOperationService(args) {
495
553
  pruneCellIfOrphaned(cellIndex);
496
554
  changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
497
555
  explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
498
- args.state.entityVersions.set(entityKeyForOp(op), order);
556
+ setEntityVersionForOp(op, order);
499
557
  break;
500
558
  }
501
559
  case "upsertDefinedName": {
@@ -504,7 +562,7 @@ export function createEngineOperationService(args) {
504
562
  const reboundCount = formulaChangedCount;
505
563
  formulaChangedCount = args.rebindDefinedNameDependents([normalizedName], formulaChangedCount);
506
564
  topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
507
- args.state.entityVersions.set(entityKeyForOp(op), order);
565
+ setEntityVersionForOp(op, order);
508
566
  break;
509
567
  }
510
568
  case "deleteDefinedName": {
@@ -513,7 +571,7 @@ export function createEngineOperationService(args) {
513
571
  const reboundCount = formulaChangedCount;
514
572
  formulaChangedCount = args.rebindDefinedNameDependents([normalizedName], formulaChangedCount);
515
573
  topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
516
- args.state.entityVersions.set(entityKeyForOp(op), order);
574
+ setEntityVersionForOp(op, order);
517
575
  break;
518
576
  }
519
577
  case "upsertPivotTable":
@@ -557,21 +615,25 @@ export function createEngineOperationService(args) {
557
615
  const changedInputArray = args.getChangedInputBuffer().subarray(0, changedInputCount);
558
616
  let recalculated = args.recalculate(args.composeMutationRoots(changedInputCount, formulaChangedCount), changedInputArray);
559
617
  recalculated = args.reconcilePivotOutputs(recalculated, refreshAllPivots);
560
- const changed = args.composeEventChanges(recalculated, explicitChangedCount);
618
+ const changed = isRestore
619
+ ? new Uint32Array()
620
+ : args.composeEventChanges(recalculated, explicitChangedCount);
561
621
  const lastMetrics = {
562
622
  ...args.state.getLastMetrics(),
563
623
  batchId: args.state.getLastMetrics().batchId + 1,
564
624
  changedInputCount: changedInputCount + formulaChangedCount,
625
+ compileMs,
565
626
  };
566
627
  args.state.setLastMetrics(lastMetrics);
567
628
  const event = {
568
629
  kind: "batch",
569
- invalidation: sheetDeleted || structuralInvalidation ? "full" : "cells",
630
+ invalidation: isRestore || sheetDeleted || structuralInvalidation ? "full" : "cells",
570
631
  changedCellIndices: changed,
571
632
  invalidatedRanges,
572
633
  invalidatedRows,
573
634
  invalidatedColumns,
574
635
  metrics: lastMetrics,
636
+ explicitChangedCount,
575
637
  };
576
638
  if (event.invalidation === "full") {
577
639
  args.state.events.emitAllWatched(event);
@@ -587,6 +649,209 @@ export function createEngineOperationService(args) {
587
649
  args.state.redoStack.length = 0;
588
650
  }
589
651
  };
652
+ const applyCellMutationsAtNow = (refs, batch, source, potentialNewCells) => {
653
+ const isRestore = source === "restore";
654
+ args.beginMutationCollection();
655
+ let changedInputCount = 0;
656
+ let formulaChangedCount = 0;
657
+ let explicitChangedCount = 0;
658
+ let topologyChanged = false;
659
+ let compileMs = 0;
660
+ const reservedNewCells = potentialNewCells ?? refs.length;
661
+ args.state.workbook.cellStore.ensureCapacity(args.state.workbook.cellStore.size + reservedNewCells);
662
+ args.resetMaterializedCellScratch(reservedNewCells);
663
+ const sheetNameById = new Map();
664
+ const resolveSheetName = (sheetId) => {
665
+ const cached = sheetNameById.get(sheetId);
666
+ if (cached !== undefined) {
667
+ return cached;
668
+ }
669
+ const sheet = args.state.workbook.getSheetById(sheetId);
670
+ if (!sheet) {
671
+ throw new Error(`Unknown sheet id: ${sheetId}`);
672
+ }
673
+ sheetNameById.set(sheetId, sheet.name);
674
+ return sheet.name;
675
+ };
676
+ args.setBatchMutationDepth(args.getBatchMutationDepth() + 1);
677
+ try {
678
+ args.state.workbook.withBatchedColumnVersionUpdates(() => {
679
+ refs.forEach((ref, refIndex) => {
680
+ const { sheetId, mutation } = ref;
681
+ const order = args.state.trackReplicaVersions ? batchOpOrder(batch, refIndex) : undefined;
682
+ const existingIndex = args.state.workbook.cellKeyToIndex.get(makeCellKey(sheetId, mutation.row, mutation.col));
683
+ switch (mutation.kind) {
684
+ case "setCellValue": {
685
+ if (existingIndex !== undefined && canFastPathLiteralOverwrite(existingIndex)) {
686
+ writeLiteralToCellStore(args.state.workbook.cellStore, existingIndex, mutation.value, args.state.strings);
687
+ changedInputCount = args.markInputChanged(existingIndex, changedInputCount);
688
+ if (!isRestore) {
689
+ explicitChangedCount = args.markExplicitChanged(existingIndex, explicitChangedCount);
690
+ }
691
+ if (!isRestore && args.state.trackReplicaVersions) {
692
+ setCellEntityVersion(resolveSheetName(sheetId), formatAddress(mutation.row, mutation.col), order);
693
+ }
694
+ break;
695
+ }
696
+ if (existingIndex !== undefined) {
697
+ changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
698
+ }
699
+ const cellIndex = args.state.workbook.ensureCellAt(sheetId, mutation.row, mutation.col).cellIndex;
700
+ if (!isRestore) {
701
+ changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
702
+ topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
703
+ }
704
+ writeLiteralToCellStore(args.state.workbook.cellStore, cellIndex, mutation.value, args.state.strings);
705
+ args.state.workbook.cellStore.flags[cellIndex] =
706
+ (args.state.workbook.cellStore.flags[cellIndex] ?? 0) &
707
+ ~(2 /* CellFlags.HasFormula */ |
708
+ 4 /* CellFlags.JsOnly */ |
709
+ 8 /* CellFlags.InCycle */ |
710
+ 64 /* CellFlags.SpillChild */ |
711
+ 128 /* CellFlags.PivotOutput */);
712
+ if (!isRestore) {
713
+ pruneCellIfOrphaned(cellIndex);
714
+ }
715
+ changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
716
+ if (!isRestore) {
717
+ explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
718
+ }
719
+ if (!isRestore && args.state.trackReplicaVersions) {
720
+ setCellEntityVersion(resolveSheetName(sheetId), formatAddress(mutation.row, mutation.col), order);
721
+ }
722
+ break;
723
+ }
724
+ case "setCellFormula": {
725
+ const sheetName = resolveSheetName(sheetId);
726
+ if (!isRestore && existingIndex !== undefined) {
727
+ changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
728
+ }
729
+ const cellIndex = args.state.workbook.ensureCellAt(sheetId, mutation.row, mutation.col).cellIndex;
730
+ if (!isRestore) {
731
+ changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(cellIndex), changedInputCount);
732
+ }
733
+ const compileStarted = isRestore ? 0 : performance.now();
734
+ try {
735
+ args.bindFormula(cellIndex, sheetName, mutation.formula);
736
+ if (!isRestore) {
737
+ compileMs += performance.now() - compileStarted;
738
+ }
739
+ formulaChangedCount = args.markFormulaChanged(cellIndex, formulaChangedCount);
740
+ topologyChanged = true;
741
+ }
742
+ catch {
743
+ if (!isRestore) {
744
+ compileMs += performance.now() - compileStarted;
745
+ }
746
+ topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
747
+ args.setInvalidFormulaValue(cellIndex);
748
+ changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
749
+ }
750
+ if (!isRestore) {
751
+ explicitChangedCount = args.markExplicitChanged(cellIndex, explicitChangedCount);
752
+ }
753
+ if (!isRestore && args.state.trackReplicaVersions) {
754
+ setCellEntityVersion(sheetName, formatAddress(mutation.row, mutation.col), order);
755
+ }
756
+ break;
757
+ }
758
+ case "clearCell": {
759
+ if (existingIndex !== undefined && canFastPathLiteralOverwrite(existingIndex)) {
760
+ args.state.workbook.cellStore.setValue(existingIndex, emptyValue());
761
+ changedInputCount = args.markInputChanged(existingIndex, changedInputCount);
762
+ if (!isRestore) {
763
+ explicitChangedCount = args.markExplicitChanged(existingIndex, explicitChangedCount);
764
+ }
765
+ if (!isRestore && args.state.trackReplicaVersions) {
766
+ setCellEntityVersion(resolveSheetName(sheetId), formatAddress(mutation.row, mutation.col), order);
767
+ }
768
+ break;
769
+ }
770
+ if (existingIndex === undefined) {
771
+ if (!isRestore && args.state.trackReplicaVersions) {
772
+ setCellEntityVersion(resolveSheetName(sheetId), formatAddress(mutation.row, mutation.col), order);
773
+ }
774
+ break;
775
+ }
776
+ changedInputCount = args.markPivotRootsChanged(args.clearPivotForCell(existingIndex), changedInputCount);
777
+ changedInputCount = args.markSpillRootsChanged(args.clearOwnedSpill(existingIndex), changedInputCount);
778
+ topologyChanged = args.removeFormula(existingIndex) || topologyChanged;
779
+ args.state.workbook.cellStore.setValue(existingIndex, emptyValue());
780
+ args.state.workbook.cellStore.flags[existingIndex] =
781
+ (args.state.workbook.cellStore.flags[existingIndex] ?? 0) &
782
+ ~(2 /* CellFlags.HasFormula */ |
783
+ 4 /* CellFlags.JsOnly */ |
784
+ 8 /* CellFlags.InCycle */ |
785
+ 64 /* CellFlags.SpillChild */ |
786
+ 128 /* CellFlags.PivotOutput */);
787
+ if (!isRestore) {
788
+ pruneCellIfOrphaned(existingIndex);
789
+ }
790
+ changedInputCount = args.markInputChanged(existingIndex, changedInputCount);
791
+ if (!isRestore) {
792
+ explicitChangedCount = args.markExplicitChanged(existingIndex, explicitChangedCount);
793
+ }
794
+ if (!isRestore && args.state.trackReplicaVersions) {
795
+ setCellEntityVersion(resolveSheetName(sheetId), formatAddress(mutation.row, mutation.col), order);
796
+ }
797
+ break;
798
+ }
799
+ default:
800
+ assertNever(mutation);
801
+ }
802
+ });
803
+ });
804
+ const reboundCount = formulaChangedCount;
805
+ formulaChangedCount = args.syncDynamicRanges(formulaChangedCount);
806
+ topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
807
+ }
808
+ finally {
809
+ args.setBatchMutationDepth(args.getBatchMutationDepth() - 1);
810
+ args.flushWasmProgramSync();
811
+ }
812
+ markBatchApplied(args.state.replicaState, batch);
813
+ if (refs.length === 0) {
814
+ if (!isRestore) {
815
+ emitBatch(batch);
816
+ }
817
+ return;
818
+ }
819
+ if (topologyChanged) {
820
+ args.rebuildTopoRanks();
821
+ args.detectCycles();
822
+ }
823
+ formulaChangedCount = args.markVolatileFormulasChanged(formulaChangedCount);
824
+ const changedInputArray = args.getChangedInputBuffer().subarray(0, changedInputCount);
825
+ let recalculated = args.recalculate(args.composeMutationRoots(changedInputCount, formulaChangedCount), changedInputArray);
826
+ recalculated = args.reconcilePivotOutputs(recalculated, false);
827
+ const changed = isRestore
828
+ ? new Uint32Array()
829
+ : args.composeEventChanges(recalculated, explicitChangedCount);
830
+ const lastMetrics = {
831
+ ...args.state.getLastMetrics(),
832
+ batchId: args.state.getLastMetrics().batchId + 1,
833
+ changedInputCount: changedInputCount + formulaChangedCount,
834
+ compileMs,
835
+ };
836
+ args.state.setLastMetrics(lastMetrics);
837
+ const event = {
838
+ kind: "batch",
839
+ invalidation: isRestore ? "full" : "cells",
840
+ changedCellIndices: changed,
841
+ invalidatedRanges: [],
842
+ invalidatedRows: [],
843
+ invalidatedColumns: [],
844
+ metrics: lastMetrics,
845
+ explicitChangedCount,
846
+ };
847
+ if (isRestore) {
848
+ args.state.events.emitAllWatched(event);
849
+ return;
850
+ }
851
+ args.state.events.emit(event, changed, (cellIndex) => args.state.workbook.getQualifiedAddress(cellIndex));
852
+ void args.state.getSyncClientConnection()?.send(batch);
853
+ emitBatch(batch);
854
+ };
590
855
  return {
591
856
  applyBatch(batch, source, potentialNewCells) {
592
857
  return Effect.try({
@@ -599,6 +864,17 @@ export function createEngineOperationService(args) {
599
864
  }),
600
865
  });
601
866
  },
867
+ applyCellMutationsAt(refs, batch, source, potentialNewCells) {
868
+ return Effect.try({
869
+ try: () => {
870
+ applyCellMutationsAtNow(refs, batch, source, potentialNewCells);
871
+ },
872
+ catch: (cause) => new EngineMutationError({
873
+ message: mutationErrorMessage(`Failed to apply ${source} cell mutations`, cause),
874
+ cause,
875
+ }),
876
+ });
877
+ },
602
878
  applyDerivedOp(op) {
603
879
  return Effect.try({
604
880
  try: () => applyDerivedOpNow(op),