tinybase 1.0.5 → 1.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.
@@ -15,6 +15,8 @@ const arrayIsEmpty = (array) => arrayLength(array) == 0;
15
15
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
16
16
  const arrayFilter = (array, cb) => array.filter(cb);
17
17
  const arrayFromSecond = (ids) => ids.slice(1);
18
+ const arrayPush = (array, value) => array.push(value);
19
+ const arrayPop = (array) => array.pop();
18
20
 
19
21
  const jsonString = (obj) =>
20
22
  JSON.stringify(obj, (_key, value) =>
@@ -125,7 +127,7 @@ const getListenerFunctions = (getThing) => {
125
127
  const allListeners = mapNew();
126
128
  const addListener = (listener, deepSet, idOrNulls = []) => {
127
129
  thing ??= getThing();
128
- const id = listenerPool.pop() ?? '' + nextId++;
130
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
129
131
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
130
132
  addDeepSet(deepSet, id, idOrNulls);
131
133
  return id;
@@ -146,7 +148,7 @@ const getListenerFunctions = (getThing) => {
146
148
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
147
149
  mapSet(allListeners, id);
148
150
  if (arrayLength(listenerPool) < 1e3) {
149
- listenerPool.push(id);
151
+ arrayPush(listenerPool, id);
150
152
  }
151
153
  return idOrNulls;
152
154
  },
@@ -186,8 +188,9 @@ const getCellType = (cell) => {
186
188
  ? type
187
189
  : void 0;
188
190
  };
189
- const validate = (obj, validateChild) => {
190
- if (isUndefined(obj) || !isObject(obj) || objFrozen(obj)) {
191
+ const validate = (obj, validateChild, onInvalidObj) => {
192
+ if (isUndefined(obj) || !isObject(obj) || objIsEmpty(obj) || objFrozen(obj)) {
193
+ onInvalidObj?.();
191
194
  return false;
192
195
  }
193
196
  objForEach(obj, (child, id) => {
@@ -207,8 +210,9 @@ const createStore = () => {
207
210
  const changedRowIds = mapNew();
208
211
  const changedCellIds = mapNew();
209
212
  const changedCells = mapNew();
213
+ const invalidCells = mapNew();
210
214
  const schemaMap = mapNew();
211
- const schemaDefaultRows = mapNew();
215
+ const schemaRowCache = mapNew();
212
216
  const tablesMap = mapNew();
213
217
  const tablesListeners = mapNewPair(setNew);
214
218
  const tableIdsListeners = mapNewPair(setNew);
@@ -217,6 +221,7 @@ const createStore = () => {
217
221
  const rowListeners = mapNewPair();
218
222
  const cellIdsListeners = mapNewPair();
219
223
  const cellListeners = mapNewPair();
224
+ const invalidCellListeners = mapNewPair();
220
225
  const [addListener, callListeners, delListenerImpl, callListenerImpl] =
221
226
  getListenerFunctions(() => store);
222
227
  const validateSchema = (schema) =>
@@ -237,40 +242,57 @@ const createStore = () => {
237
242
  return true;
238
243
  }),
239
244
  );
240
- const validateTables = (tables) => validate(tables, validateTable);
245
+ const validateTables = (tables) =>
246
+ validate(tables, validateTable, cellInvalid);
241
247
  const validateTable = (table, tableId) =>
242
- (!hasSchema || collHas(schemaMap, tableId)) &&
243
- validate(table, (row) => validateRow(tableId, row));
244
- const validateRow = (tableId, row, skipDefaults) =>
248
+ (!hasSchema || collHas(schemaMap, tableId) || cellInvalid(tableId)) &&
245
249
  validate(
246
- skipDefaults ? row : addDefaultsToRow(row, tableId),
250
+ table,
251
+ (row, rowId) => validateRow(tableId, rowId, row),
252
+ () => cellInvalid(tableId),
253
+ );
254
+ const validateRow = (tableId, rowId, row, skipDefaults) =>
255
+ validate(
256
+ skipDefaults ? row : addDefaultsToRow(row, tableId, rowId),
247
257
  (cell, cellId) =>
248
258
  ifNotUndefined(
249
- getValidatedCell(tableId, cellId, cell),
259
+ getValidatedCell(tableId, rowId, cellId, cell),
250
260
  (validCell) => {
251
261
  row[cellId] = validCell;
252
262
  return true;
253
263
  },
254
264
  () => false,
255
265
  ),
266
+ () => cellInvalid(tableId, rowId),
256
267
  );
257
- const getValidatedCell = (tableId, cellId, cell) =>
268
+ const getValidatedCell = (tableId, rowId, cellId, cell) =>
258
269
  hasSchema
259
270
  ? ifNotUndefined(
260
271
  mapGet(mapGet(schemaMap, tableId), cellId),
261
272
  (cellSchema) =>
262
- getCellType(cell) != cellSchema[TYPE] ? cellSchema[DEFAULT] : cell,
273
+ getCellType(cell) != cellSchema[TYPE]
274
+ ? cellInvalid(tableId, rowId, cellId, cell, cellSchema[DEFAULT])
275
+ : cell,
276
+ () => cellInvalid(tableId, rowId, cellId, cell),
263
277
  )
264
278
  : isUndefined(getCellType(cell))
265
- ? void 0
279
+ ? cellInvalid(tableId, rowId, cellId, cell)
266
280
  : cell;
267
- const addDefaultsToRow = (row, tableId) => {
268
- ifNotUndefined(mapGet(schemaDefaultRows, tableId), (defaultRow) =>
269
- objForEach(defaultRow, (cell, cellId) => {
270
- if (!objHas(row, cellId)) {
271
- row[cellId] = cell;
272
- }
273
- }),
281
+ const addDefaultsToRow = (row, tableId, rowId) => {
282
+ ifNotUndefined(
283
+ mapGet(schemaRowCache, tableId),
284
+ ([rowDefaulted, rowNonDefaulted]) => {
285
+ collForEach(rowDefaulted, (cell, cellId) => {
286
+ if (!objHas(row, cellId)) {
287
+ row[cellId] = cell;
288
+ }
289
+ });
290
+ collForEach(rowNonDefaulted, (cellId) => {
291
+ if (!objHas(row, cellId)) {
292
+ cellInvalid(tableId, rowId, cellId);
293
+ }
294
+ });
295
+ },
274
296
  );
275
297
  return row;
276
298
  };
@@ -279,7 +301,8 @@ const createStore = () => {
279
301
  schemaMap,
280
302
  schema,
281
303
  (_schema, tableId, tableSchema) => {
282
- const defaultRow = {};
304
+ const rowDefaulted = mapNew();
305
+ const rowNonDefaulted = setNew();
283
306
  transformMap(
284
307
  mapEnsure(schemaMap, tableId, mapNew()),
285
308
  tableSchema,
@@ -287,15 +310,16 @@ const createStore = () => {
287
310
  mapSet(tableSchemaMap, cellId, cellSchema);
288
311
  ifNotUndefined(
289
312
  cellSchema[DEFAULT],
290
- (def) => (defaultRow[cellId] = def),
313
+ (def) => mapSet(rowDefaulted, cellId, def),
314
+ () => setAdd(rowNonDefaulted, cellId),
291
315
  );
292
316
  },
293
317
  );
294
- mapSet(schemaDefaultRows, tableId, defaultRow);
318
+ mapSet(schemaRowCache, tableId, [rowDefaulted, rowNonDefaulted]);
295
319
  },
296
320
  (_schema, tableId) => {
297
321
  mapSet(schemaMap, tableId);
298
- mapSet(schemaDefaultRows, tableId);
322
+ mapSet(schemaRowCache, tableId);
299
323
  },
300
324
  );
301
325
  const setValidTables = (tables) =>
@@ -335,10 +359,6 @@ const createStore = () => {
335
359
  mapSet(rowMap, cellId, newCell);
336
360
  }
337
361
  };
338
- const setValidRowTransaction = (tableId, rowId, row) =>
339
- transaction(() =>
340
- setValidRow(tableId, getOrCreateTable(tableId), rowId, row),
341
- );
342
362
  const setCellIntoDefaultRow = (tableId, tableMap, rowId, cellId, validCell) =>
343
363
  ifNotUndefined(
344
364
  mapGet(tableMap, rowId),
@@ -348,7 +368,7 @@ const createStore = () => {
348
368
  tableId,
349
369
  tableMap,
350
370
  rowId,
351
- addDefaultsToRow({[cellId]: validCell}, tableId),
371
+ addDefaultsToRow({[cellId]: validCell}, tableId, rowId),
352
372
  ),
353
373
  );
354
374
  const getNewRowId = (tableMap) => {
@@ -364,7 +384,7 @@ const createStore = () => {
364
384
  const delValidRow = (tableId, tableMap, rowId) =>
365
385
  setValidRow(tableId, tableMap, rowId, {}, true);
366
386
  const delValidCell = (tableId, table, rowId, row, cellId, forceDel) => {
367
- const defaultCell = mapGet(schemaDefaultRows, tableId)?.[cellId];
387
+ const defaultCell = mapGet(mapGet(schemaRowCache, tableId)?.[0], cellId);
368
388
  if (!isUndefined(defaultCell) && !forceDel) {
369
389
  return setValidCell(tableId, rowId, row, cellId, defaultCell);
370
390
  }
@@ -398,6 +418,17 @@ const createStore = () => {
398
418
  cellId,
399
419
  oldCell,
400
420
  );
421
+ const cellInvalid = (tableId, rowId, cellId, invalidCell, defaultedCell) => {
422
+ arrayPush(
423
+ mapEnsure(
424
+ mapEnsure(mapEnsure(invalidCells, tableId, mapNew()), rowId, mapNew()),
425
+ cellId,
426
+ [],
427
+ ),
428
+ invalidCell,
429
+ );
430
+ return defaultedCell;
431
+ };
401
432
  const getCellChange = (tableId, rowId, cellId) => {
402
433
  const changedRow = mapGet(mapGet(changedCells, tableId), rowId);
403
434
  const newCell = getCell(tableId, rowId, cellId);
@@ -405,80 +436,103 @@ const createStore = () => {
405
436
  ? [true, mapGet(changedRow, cellId), newCell]
406
437
  : [false, newCell, newCell];
407
438
  };
439
+ const callInvalidCellListeners = (mutator) =>
440
+ !collIsEmpty(invalidCells) && !collIsEmpty(invalidCellListeners[mutator])
441
+ ? collForEach(
442
+ mutator
443
+ ? mapClone(invalidCells, (table) => mapClone(table, mapClone))
444
+ : invalidCells,
445
+ (rows, tableId) =>
446
+ collForEach(rows, (cells, rowId) =>
447
+ collForEach(cells, (invalidCell, cellId) =>
448
+ callListeners(
449
+ invalidCellListeners[mutator],
450
+ [tableId, rowId, cellId],
451
+ invalidCell,
452
+ ),
453
+ ),
454
+ ),
455
+ )
456
+ : 0;
408
457
  const callListenersForChanges = (mutator) => {
409
- const emptyIdListeners =
410
- collIsEmpty(cellIdsListeners[mutator]) &&
411
- collIsEmpty(rowIdsListeners[mutator]) &&
412
- collIsEmpty(tableIdsListeners[mutator]);
413
- const emptyOtherListeners =
414
- collIsEmpty(cellListeners[mutator]) &&
415
- collIsEmpty(rowListeners[mutator]) &&
416
- collIsEmpty(tableListeners[mutator]) &&
417
- collIsEmpty(tablesListeners[mutator]);
418
- if (emptyIdListeners && emptyOtherListeners) {
419
- return;
420
- }
421
- const changes = mutator
422
- ? [
423
- mapClone(changedTableIds),
424
- mapClone(changedRowIds, mapClone),
425
- mapClone(changedCellIds, (table) => mapClone(table, mapClone)),
426
- mapClone(changedCells, (table) => mapClone(table, mapClone)),
427
- ]
428
- : [changedTableIds, changedRowIds, changedCellIds, changedCells];
429
- if (!emptyIdListeners) {
430
- collForEach(changes[2], (rowCellIds, tableId) =>
431
- collForEach(rowCellIds, (changedIds, rowId) => {
432
- if (!collIsEmpty(changedIds)) {
433
- callListeners(cellIdsListeners[mutator], [tableId, rowId]);
458
+ if (!collIsEmpty(changedCells)) {
459
+ const emptyIdListeners =
460
+ collIsEmpty(cellIdsListeners[mutator]) &&
461
+ collIsEmpty(rowIdsListeners[mutator]) &&
462
+ collIsEmpty(tableIdsListeners[mutator]);
463
+ const emptyOtherListeners =
464
+ collIsEmpty(cellListeners[mutator]) &&
465
+ collIsEmpty(rowListeners[mutator]) &&
466
+ collIsEmpty(tableListeners[mutator]) &&
467
+ collIsEmpty(tablesListeners[mutator]);
468
+ if (!(emptyIdListeners && emptyOtherListeners)) {
469
+ const changes = mutator
470
+ ? [
471
+ mapClone(changedTableIds),
472
+ mapClone(changedRowIds, mapClone),
473
+ mapClone(changedCellIds, (table) => mapClone(table, mapClone)),
474
+ mapClone(changedCells, (table) => mapClone(table, mapClone)),
475
+ ]
476
+ : [changedTableIds, changedRowIds, changedCellIds, changedCells];
477
+ if (!emptyIdListeners) {
478
+ collForEach(changes[2], (rowCellIds, tableId) =>
479
+ collForEach(rowCellIds, (changedIds, rowId) => {
480
+ if (!collIsEmpty(changedIds)) {
481
+ callListeners(cellIdsListeners[mutator], [tableId, rowId]);
482
+ }
483
+ }),
484
+ );
485
+ collForEach(changes[1], (changedIds, tableId) => {
486
+ if (!collIsEmpty(changedIds)) {
487
+ callListeners(rowIdsListeners[mutator], [tableId]);
488
+ }
489
+ });
490
+ if (!collIsEmpty(changes[0])) {
491
+ callListeners(tableIdsListeners[mutator]);
434
492
  }
435
- }),
436
- );
437
- collForEach(changes[1], (changedIds, tableId) => {
438
- if (!collIsEmpty(changedIds)) {
439
- callListeners(rowIdsListeners[mutator], [tableId]);
440
493
  }
441
- });
442
- if (!collIsEmpty(changes[0])) {
443
- callListeners(tableIdsListeners[mutator]);
444
- }
445
- }
446
- if (!emptyOtherListeners) {
447
- let tablesChanged;
448
- collForEach(changes[3], (rows, tableId) => {
449
- let tableChanged;
450
- collForEach(rows, (cells, rowId) => {
451
- let rowChanged;
452
- collForEach(cells, (oldCell, cellId) => {
453
- const newCell = getCell(tableId, rowId, cellId);
454
- if (newCell !== oldCell) {
455
- callListeners(
456
- cellListeners[mutator],
457
- [tableId, rowId, cellId],
458
- newCell,
459
- oldCell,
460
- getCellChange,
461
- );
462
- tablesChanged = tableChanged = rowChanged = 1;
494
+ if (!emptyOtherListeners) {
495
+ let tablesChanged;
496
+ collForEach(changes[3], (rows, tableId) => {
497
+ let tableChanged;
498
+ collForEach(rows, (cells, rowId) => {
499
+ let rowChanged;
500
+ collForEach(cells, (oldCell, cellId) => {
501
+ const newCell = getCell(tableId, rowId, cellId);
502
+ if (newCell !== oldCell) {
503
+ callListeners(
504
+ cellListeners[mutator],
505
+ [tableId, rowId, cellId],
506
+ newCell,
507
+ oldCell,
508
+ getCellChange,
509
+ );
510
+ tablesChanged = tableChanged = rowChanged = 1;
511
+ }
512
+ });
513
+ if (rowChanged) {
514
+ callListeners(
515
+ rowListeners[mutator],
516
+ [tableId, rowId],
517
+ getCellChange,
518
+ );
519
+ }
520
+ });
521
+ if (tableChanged) {
522
+ callListeners(tableListeners[mutator], [tableId], getCellChange);
463
523
  }
464
524
  });
465
- if (rowChanged) {
466
- callListeners(
467
- rowListeners[mutator],
468
- [tableId, rowId],
469
- getCellChange,
470
- );
525
+ if (tablesChanged) {
526
+ callListeners(tablesListeners[mutator], [], getCellChange);
471
527
  }
472
- });
473
- if (tableChanged) {
474
- callListeners(tableListeners[mutator], [tableId], getCellChange);
475
528
  }
476
- });
477
- if (tablesChanged) {
478
- callListeners(tablesListeners[mutator], [], getCellChange);
479
529
  }
480
530
  }
481
531
  };
532
+ const fluentTransaction = (actions) => {
533
+ transaction(actions);
534
+ return store;
535
+ };
482
536
  const getTables = () =>
483
537
  mapToObj(tablesMap, (tableMap) => mapToObj(tableMap, mapToObj));
484
538
  const getTableIds = () => mapKeys(tablesMap);
@@ -497,52 +551,52 @@ const createStore = () => {
497
551
  collHas(mapGet(mapGet(tablesMap, tableId), rowId), cellId);
498
552
  const getJson = () => jsonString(tablesMap);
499
553
  const getSchemaJson = () => jsonString(schemaMap);
500
- const setTables = (tables) => {
501
- if (validateTables(tables)) {
502
- transaction(() => setValidTables(tables));
503
- }
504
- return store;
505
- };
506
- const setTable = (tableId, table) => {
507
- if (validateTable(table, tableId)) {
508
- transaction(() => setValidTable(tableId, table));
509
- }
510
- return store;
511
- };
512
- const setRow = (tableId, rowId, row) => {
513
- if (validateRow(tableId, row)) {
514
- setValidRowTransaction(tableId, rowId, row);
515
- }
516
- return store;
517
- };
518
- const addRow = (tableId, row) => {
519
- let rowId = void 0;
520
- if (validateRow(tableId, row)) {
521
- rowId = getNewRowId(mapGet(tablesMap, tableId));
522
- setValidRowTransaction(tableId, rowId, row);
523
- }
524
- return rowId;
525
- };
526
- const setPartialRow = (tableId, rowId, partialRow) => {
527
- if (validateRow(tableId, partialRow, 1)) {
528
- transaction(() => {
554
+ const setTables = (tables) =>
555
+ fluentTransaction(() =>
556
+ validateTables(tables) ? setValidTables(tables) : 0,
557
+ );
558
+ const setTable = (tableId, table) =>
559
+ fluentTransaction(() =>
560
+ validateTable(table, tableId) ? setValidTable(tableId, table) : 0,
561
+ );
562
+ const setRow = (tableId, rowId, row) =>
563
+ fluentTransaction(() =>
564
+ validateRow(tableId, rowId, row)
565
+ ? setValidRow(tableId, getOrCreateTable(tableId), rowId, row)
566
+ : 0,
567
+ );
568
+ const addRow = (tableId, row) =>
569
+ transaction(() => {
570
+ let rowId = void 0;
571
+ if (validateRow(tableId, rowId, row)) {
572
+ setValidRow(
573
+ tableId,
574
+ getOrCreateTable(tableId),
575
+ (rowId = getNewRowId(mapGet(tablesMap, tableId))),
576
+ row,
577
+ );
578
+ }
579
+ return rowId;
580
+ });
581
+ const setPartialRow = (tableId, rowId, partialRow) =>
582
+ fluentTransaction(() => {
583
+ if (validateRow(tableId, rowId, partialRow, 1)) {
529
584
  const table = getOrCreateTable(tableId);
530
585
  objForEach(partialRow, (cell, cellId) =>
531
586
  setCellIntoDefaultRow(tableId, table, rowId, cellId, cell),
532
587
  );
533
- });
534
- }
535
- return store;
536
- };
537
- const setCell = (tableId, rowId, cellId, cell) => {
538
- ifNotUndefined(
539
- getValidatedCell(
540
- tableId,
541
- cellId,
542
- isFunction(cell) ? cell(getCell(tableId, rowId, cellId)) : cell,
543
- ),
544
- (validCell) =>
545
- transaction(() =>
588
+ }
589
+ });
590
+ const setCell = (tableId, rowId, cellId, cell) =>
591
+ fluentTransaction(() =>
592
+ ifNotUndefined(
593
+ getValidatedCell(
594
+ tableId,
595
+ rowId,
596
+ cellId,
597
+ isFunction(cell) ? cell(getCell(tableId, rowId, cellId)) : cell,
598
+ ),
599
+ (validCell) =>
546
600
  setCellIntoDefaultRow(
547
601
  tableId,
548
602
  getOrCreateTable(tableId),
@@ -550,77 +604,74 @@ const createStore = () => {
550
604
  cellId,
551
605
  validCell,
552
606
  ),
553
- ),
607
+ ),
554
608
  );
555
- return store;
556
- };
557
609
  const setJson = (json) => {
558
610
  try {
559
611
  json === EMPTY_OBJECT ? delTables() : setTables(jsonParse(json));
560
612
  } catch {}
561
613
  return store;
562
614
  };
563
- const setSchema = (schema) => {
564
- if ((hasSchema = validateSchema(schema))) {
565
- setValidSchema(schema);
566
- if (!collIsEmpty(tablesMap)) {
567
- const tables = getTables();
568
- delTables();
569
- setTables(tables);
570
- }
571
- }
572
- return store;
573
- };
574
- const delTables = () => {
575
- transaction(() => setValidTables({}));
576
- return store;
577
- };
578
- const delTable = (tableId) => {
579
- if (collHas(tablesMap, tableId)) {
580
- transaction(() => delValidTable(tableId));
581
- }
582
- return store;
583
- };
584
- const delRow = (tableId, rowId) => {
585
- ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) => {
586
- if (collHas(tableMap, rowId)) {
587
- transaction(() => delValidRow(tableId, tableMap, rowId));
615
+ const setSchema = (schema) =>
616
+ fluentTransaction(() => {
617
+ if ((hasSchema = validateSchema(schema))) {
618
+ setValidSchema(schema);
619
+ if (!collIsEmpty(tablesMap)) {
620
+ const tables = getTables();
621
+ delTables();
622
+ setTables(tables);
623
+ }
588
624
  }
589
625
  });
590
- return store;
591
- };
592
- const delCell = (tableId, rowId, cellId, forceDel) => {
593
- ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) =>
594
- ifNotUndefined(mapGet(tableMap, rowId), (rowMap) => {
595
- if (collHas(rowMap, cellId)) {
596
- transaction(() =>
597
- delValidCell(tableId, tableMap, rowId, rowMap, cellId, forceDel),
598
- );
599
- }
600
- }),
626
+ const delTables = () => fluentTransaction(() => setValidTables({}));
627
+ const delTable = (tableId) =>
628
+ fluentTransaction(() =>
629
+ collHas(tablesMap, tableId) ? delValidTable(tableId) : 0,
601
630
  );
602
- return store;
603
- };
604
- const delSchema = () => {
605
- setValidSchema({});
606
- hasSchema = false;
607
- return store;
608
- };
631
+ const delRow = (tableId, rowId) =>
632
+ fluentTransaction(() =>
633
+ ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) =>
634
+ collHas(tableMap, rowId) ? delValidRow(tableId, tableMap, rowId) : 0,
635
+ ),
636
+ );
637
+ const delCell = (tableId, rowId, cellId, forceDel) =>
638
+ fluentTransaction(() =>
639
+ ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) =>
640
+ ifNotUndefined(mapGet(tableMap, rowId), (rowMap) =>
641
+ collHas(rowMap, cellId)
642
+ ? delValidCell(tableId, tableMap, rowId, rowMap, cellId, forceDel)
643
+ : 0,
644
+ ),
645
+ ),
646
+ );
647
+ const delSchema = () =>
648
+ fluentTransaction(() => {
649
+ setValidSchema({});
650
+ hasSchema = false;
651
+ });
609
652
  const transaction = (actions) => {
610
653
  if (transactions == -1) {
611
654
  return;
612
655
  }
613
656
  transactions++;
614
- const result = actions();
657
+ const result = actions?.();
615
658
  transactions--;
616
659
  if (transactions == 0) {
617
660
  transactions = 1;
661
+ callInvalidCellListeners(1);
618
662
  callListenersForChanges(1);
619
663
  transactions = -1;
664
+ callInvalidCellListeners(0);
620
665
  callListenersForChanges(0);
621
666
  transactions = 0;
622
667
  arrayForEach(
623
- [changedCells, changedTableIds, changedRowIds, changedCellIds],
668
+ [
669
+ changedCells,
670
+ invalidCells,
671
+ changedTableIds,
672
+ changedRowIds,
673
+ changedCellIds,
674
+ ],
624
675
  collClear,
625
676
  );
626
677
  }
@@ -660,6 +711,12 @@ const createStore = () => {
660
711
  rowId,
661
712
  cellId,
662
713
  ]);
714
+ const addInvalidCellListener = (tableId, rowId, cellId, listener, mutator) =>
715
+ addListener(listener, invalidCellListeners[mutator ? 1 : 0], [
716
+ tableId,
717
+ rowId,
718
+ cellId,
719
+ ]);
663
720
  const callListener = (listenerId) => {
664
721
  callListenerImpl(listenerId, [getTableIds, getRowIds, getCellIds], (ids) =>
665
722
  isUndefined(ids[2]) ? [] : Array(2).fill(getCell(...ids)),
@@ -717,6 +774,7 @@ const createStore = () => {
717
774
  addRowListener,
718
775
  addCellIdsListener,
719
776
  addCellListener,
777
+ addInvalidCellListener,
720
778
  callListener,
721
779
  delListener,
722
780
  getListenerStats,