tinybase 1.2.2 → 1.3.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.
@@ -1230,6 +1230,7 @@ const idsChanged = (ids, id, added) =>
1230
1230
  mapSet(ids, id, mapGet(ids, id) == -added ? void 0 : added);
1231
1231
  const createStore = () => {
1232
1232
  let hasSchema;
1233
+ let cellsTouched;
1233
1234
  let nextRowId = 0;
1234
1235
  let transactions = 0;
1235
1236
  const changedTableIds = mapNew();
@@ -1248,6 +1249,7 @@ const createStore = () => {
1248
1249
  const cellIdsListeners = mapNewPair();
1249
1250
  const cellListeners = mapNewPair();
1250
1251
  const invalidCellListeners = mapNewPair();
1252
+ const finishTransactionListeners = mapNewPair(setNew);
1251
1253
  const [addListener, callListeners, delListenerImpl, callListenerImpl] =
1252
1254
  getListenerFunctions(() => store);
1253
1255
  const validateSchema = (schema) =>
@@ -1480,75 +1482,73 @@ const createStore = () => {
1480
1482
  )
1481
1483
  : 0;
1482
1484
  const callListenersForChanges = (mutator) => {
1483
- if (!collIsEmpty(changedCells)) {
1484
- const emptyIdListeners =
1485
- collIsEmpty(cellIdsListeners[mutator]) &&
1486
- collIsEmpty(rowIdsListeners[mutator]) &&
1487
- collIsEmpty(tableIdsListeners[mutator]);
1488
- const emptyOtherListeners =
1489
- collIsEmpty(cellListeners[mutator]) &&
1490
- collIsEmpty(rowListeners[mutator]) &&
1491
- collIsEmpty(tableListeners[mutator]) &&
1492
- collIsEmpty(tablesListeners[mutator]);
1493
- if (!(emptyIdListeners && emptyOtherListeners)) {
1494
- const changes = mutator
1495
- ? [
1496
- mapClone(changedTableIds),
1497
- mapClone2(changedRowIds),
1498
- mapClone(changedCellIds, mapClone2),
1499
- mapClone(changedCells, mapClone2),
1500
- ]
1501
- : [changedTableIds, changedRowIds, changedCellIds, changedCells];
1502
- if (!emptyIdListeners) {
1503
- collForEach(changes[2], (rowCellIds, tableId) =>
1504
- collForEach(rowCellIds, (changedIds, rowId) => {
1505
- if (!collIsEmpty(changedIds)) {
1506
- callListeners(cellIdsListeners[mutator], [tableId, rowId]);
1507
- }
1508
- }),
1509
- );
1510
- collForEach(changes[1], (changedIds, tableId) => {
1485
+ const emptyIdListeners =
1486
+ collIsEmpty(cellIdsListeners[mutator]) &&
1487
+ collIsEmpty(rowIdsListeners[mutator]) &&
1488
+ collIsEmpty(tableIdsListeners[mutator]);
1489
+ const emptyOtherListeners =
1490
+ collIsEmpty(cellListeners[mutator]) &&
1491
+ collIsEmpty(rowListeners[mutator]) &&
1492
+ collIsEmpty(tableListeners[mutator]) &&
1493
+ collIsEmpty(tablesListeners[mutator]);
1494
+ if (!(emptyIdListeners && emptyOtherListeners)) {
1495
+ const changes = mutator
1496
+ ? [
1497
+ mapClone(changedTableIds),
1498
+ mapClone2(changedRowIds),
1499
+ mapClone(changedCellIds, mapClone2),
1500
+ mapClone(changedCells, mapClone2),
1501
+ ]
1502
+ : [changedTableIds, changedRowIds, changedCellIds, changedCells];
1503
+ if (!emptyIdListeners) {
1504
+ collForEach(changes[2], (rowCellIds, tableId) =>
1505
+ collForEach(rowCellIds, (changedIds, rowId) => {
1511
1506
  if (!collIsEmpty(changedIds)) {
1512
- callListeners(rowIdsListeners[mutator], [tableId]);
1507
+ callListeners(cellIdsListeners[mutator], [tableId, rowId]);
1513
1508
  }
1514
- });
1515
- if (!collIsEmpty(changes[0])) {
1516
- callListeners(tableIdsListeners[mutator]);
1509
+ }),
1510
+ );
1511
+ collForEach(changes[1], (changedIds, tableId) => {
1512
+ if (!collIsEmpty(changedIds)) {
1513
+ callListeners(rowIdsListeners[mutator], [tableId]);
1517
1514
  }
1515
+ });
1516
+ if (!collIsEmpty(changes[0])) {
1517
+ callListeners(tableIdsListeners[mutator]);
1518
1518
  }
1519
- if (!emptyOtherListeners) {
1520
- let tablesChanged;
1521
- collForEach(changes[3], (rows, tableId) => {
1522
- let tableChanged;
1523
- collForEach(rows, (cells, rowId) => {
1524
- let rowChanged;
1525
- collForEach(cells, ([oldCell, newCell], cellId) => {
1526
- if (newCell !== oldCell) {
1527
- callListeners(
1528
- cellListeners[mutator],
1529
- [tableId, rowId, cellId],
1530
- newCell,
1531
- oldCell,
1532
- getCellChange,
1533
- );
1534
- tablesChanged = tableChanged = rowChanged = 1;
1535
- }
1536
- });
1537
- if (rowChanged) {
1519
+ }
1520
+ if (!emptyOtherListeners) {
1521
+ let tablesChanged;
1522
+ collForEach(changes[3], (rows, tableId) => {
1523
+ let tableChanged;
1524
+ collForEach(rows, (cells, rowId) => {
1525
+ let rowChanged;
1526
+ collForEach(cells, ([oldCell, newCell], cellId) => {
1527
+ if (newCell !== oldCell) {
1538
1528
  callListeners(
1539
- rowListeners[mutator],
1540
- [tableId, rowId],
1529
+ cellListeners[mutator],
1530
+ [tableId, rowId, cellId],
1531
+ newCell,
1532
+ oldCell,
1541
1533
  getCellChange,
1542
1534
  );
1535
+ tablesChanged = tableChanged = rowChanged = 1;
1543
1536
  }
1544
1537
  });
1545
- if (tableChanged) {
1546
- callListeners(tableListeners[mutator], [tableId], getCellChange);
1538
+ if (rowChanged) {
1539
+ callListeners(
1540
+ rowListeners[mutator],
1541
+ [tableId, rowId],
1542
+ getCellChange,
1543
+ );
1547
1544
  }
1548
1545
  });
1549
- if (tablesChanged) {
1550
- callListeners(tablesListeners[mutator], [], getCellChange);
1546
+ if (tableChanged) {
1547
+ callListeners(tableListeners[mutator], [tableId], getCellChange);
1551
1548
  }
1549
+ });
1550
+ if (tablesChanged) {
1551
+ callListeners(tablesListeners[mutator], [], getCellChange);
1552
1552
  }
1553
1553
  }
1554
1554
  }
@@ -1677,61 +1677,79 @@ const createStore = () => {
1677
1677
  if (transactions == -1) {
1678
1678
  return;
1679
1679
  }
1680
- transactions++;
1680
+ startTransaction();
1681
1681
  const result = actions?.();
1682
- transactions--;
1683
- if (transactions == 0) {
1684
- transactions = 1;
1685
- callInvalidCellListeners(1);
1686
- callListenersForChanges(1);
1687
- transactions = -1;
1688
- if (
1689
- doRollback?.(
1690
- mapToObj(
1691
- changedCells,
1692
- (table) =>
1693
- mapToObj(
1694
- table,
1695
- (row) =>
1696
- mapToObj(
1697
- row,
1698
- (cells) => [...cells],
1699
- ([oldCell, newCell]) => oldCell === newCell,
1700
- ),
1701
- objIsEmpty,
1702
- ),
1703
- objIsEmpty,
1704
- ),
1705
- mapToObj(invalidCells, (map) => mapToObj(map, mapToObj)),
1706
- )
1707
- ) {
1682
+ finishTransaction(doRollback);
1683
+ return result;
1684
+ };
1685
+ const startTransaction = () => {
1686
+ transactions++;
1687
+ return store;
1688
+ };
1689
+ const finishTransaction = (doRollback) => {
1690
+ if (transactions > 0) {
1691
+ transactions--;
1692
+ if (transactions == 0) {
1693
+ cellsTouched = !collIsEmpty(changedCells);
1708
1694
  transactions = 1;
1709
- collForEach(changedCells, (table, tableId) =>
1710
- collForEach(table, (row, rowId) =>
1711
- collForEach(row, ([oldCell], cellId) =>
1712
- isUndefined(oldCell)
1713
- ? delCell(tableId, rowId, cellId, true)
1714
- : setCell(tableId, rowId, cellId, oldCell),
1695
+ callInvalidCellListeners(1);
1696
+ if (cellsTouched) {
1697
+ callListenersForChanges(1);
1698
+ }
1699
+ transactions = -1;
1700
+ if (
1701
+ doRollback?.(
1702
+ mapToObj(
1703
+ changedCells,
1704
+ (table) =>
1705
+ mapToObj(
1706
+ table,
1707
+ (row) =>
1708
+ mapToObj(
1709
+ row,
1710
+ (cells) => [...cells],
1711
+ ([oldCell, newCell]) => oldCell === newCell,
1712
+ ),
1713
+ objIsEmpty,
1714
+ ),
1715
+ objIsEmpty,
1715
1716
  ),
1716
- ),
1717
+ mapToObj(invalidCells, (map) => mapToObj(map, mapToObj)),
1718
+ )
1719
+ ) {
1720
+ transactions = 1;
1721
+ collForEach(changedCells, (table, tableId) =>
1722
+ collForEach(table, (row, rowId) =>
1723
+ collForEach(row, ([oldCell], cellId) =>
1724
+ isUndefined(oldCell)
1725
+ ? delCell(tableId, rowId, cellId, true)
1726
+ : setCell(tableId, rowId, cellId, oldCell),
1727
+ ),
1728
+ ),
1729
+ );
1730
+ transactions = -1;
1731
+ cellsTouched = false;
1732
+ }
1733
+ callListeners(finishTransactionListeners[0], [], cellsTouched);
1734
+ callInvalidCellListeners(0);
1735
+ if (cellsTouched) {
1736
+ callListenersForChanges(0);
1737
+ }
1738
+ callListeners(finishTransactionListeners[1], [], cellsTouched);
1739
+ transactions = 0;
1740
+ arrayForEach(
1741
+ [
1742
+ changedCells,
1743
+ invalidCells,
1744
+ changedTableIds,
1745
+ changedRowIds,
1746
+ changedCellIds,
1747
+ ],
1748
+ collClear,
1717
1749
  );
1718
- transactions = -1;
1719
1750
  }
1720
- callInvalidCellListeners(0);
1721
- callListenersForChanges(0);
1722
- transactions = 0;
1723
- arrayForEach(
1724
- [
1725
- changedCells,
1726
- invalidCells,
1727
- changedTableIds,
1728
- changedRowIds,
1729
- changedCellIds,
1730
- ],
1731
- collClear,
1732
- );
1733
1751
  }
1734
- return result;
1752
+ return store;
1735
1753
  };
1736
1754
  const forEachTable = (tableCallback) =>
1737
1755
  collForEach(tablesMap, (tableMap, tableId) =>
@@ -1773,6 +1791,10 @@ const createStore = () => {
1773
1791
  rowId,
1774
1792
  cellId,
1775
1793
  ]);
1794
+ const addWillFinishTransactionListener = (listener) =>
1795
+ addListener(listener, finishTransactionListeners[0]);
1796
+ const addDidFinishTransactionListener = (listener) =>
1797
+ addListener(listener, finishTransactionListeners[1]);
1776
1798
  const callListener = (listenerId) => {
1777
1799
  callListenerImpl(listenerId, [getTableIds, getRowIds, getCellIds], (ids) =>
1778
1800
  isUndefined(ids[2]) ? [] : arrayPair(getCell(...ids)),
@@ -1791,6 +1813,8 @@ const createStore = () => {
1791
1813
  row: collPairSize(rowListeners, collSize3),
1792
1814
  cellIds: collPairSize(cellIdsListeners, collSize3),
1793
1815
  cell: collPairSize(cellListeners, collSize4),
1816
+ invalidCell: collPairSize(invalidCellListeners, collSize4),
1817
+ transaction: collPairSize(finishTransactionListeners),
1794
1818
  });
1795
1819
  const store = {
1796
1820
  getTables,
@@ -1820,6 +1844,8 @@ const createStore = () => {
1820
1844
  delCell,
1821
1845
  delSchema,
1822
1846
  transaction,
1847
+ startTransaction,
1848
+ finishTransaction,
1823
1849
  forEachTable,
1824
1850
  forEachRow,
1825
1851
  forEachCell,
@@ -1831,6 +1857,8 @@ const createStore = () => {
1831
1857
  addCellIdsListener,
1832
1858
  addCellListener,
1833
1859
  addInvalidCellListener,
1860
+ addWillFinishTransactionListener,
1861
+ addDidFinishTransactionListener,
1834
1862
  callListener,
1835
1863
  delListener,
1836
1864
  getListenerStats,
package/lib/store.d.ts CHANGED
@@ -176,6 +176,32 @@ export type MapCell = (cell: CellOrUndefined) => Cell;
176
176
  */
177
177
  export type GetCell = (cellId: Id) => CellOrUndefined;
178
178
 
179
+ /**
180
+ * The TransactionListener type describes a function that is used to listen to
181
+ * the completion of a transaction for the Store.
182
+ *
183
+ * A TransactionListener is provided when using the
184
+ * addWillFinishTransactionListener and addDidFinishTransactionListener methods.
185
+ * See those methods for specific examples.
186
+ *
187
+ * When called, a TransactionListener is simply given a reference to the Store
188
+ * and a boolean to indicate whether Cell values have been touched during the
189
+ * transaction. The latter flag is intended as a hint about whether non-mutating
190
+ * listeners might be being called at the end of the transaction.
191
+ *
192
+ * Here, 'touched' means that Cell values have either been changed, or changed
193
+ * and then changed back to its original value during the transaction. The
194
+ * exception is a transaction that has been rolled back, for which the value of
195
+ * `cellsTouched` in the listener will be `false` because all changes have been
196
+ * reverted.
197
+ *
198
+ * @param store A reference to the Store that is completing a transaction.
199
+ * @param cellsTouched Whether Cell values have been touched during the
200
+ * transaction.
201
+ * @category Listener
202
+ */
203
+ export type TransactionListener = (store: Store, cellsTouched: boolean) => void;
204
+
179
205
  /**
180
206
  * The TablesListener type describes a function that is used to listen to
181
207
  * changes to the whole Store.
@@ -566,6 +592,14 @@ export type StoreListenerStats = {
566
592
  * The number of CellListeners registered with the Store.
567
593
  */
568
594
  cell?: number;
595
+ /**
596
+ * The number of InvalidCellListeners registered with the Store.
597
+ */
598
+ invalidCell?: number;
599
+ /**
600
+ * The number of TransactionListeners registered with the Store.
601
+ */
602
+ transaction?: number;
569
603
  };
570
604
 
571
605
  /**
@@ -1648,7 +1682,7 @@ export interface Store {
1648
1682
 
1649
1683
  /**
1650
1684
  * The transaction method takes a function that makes multiple mutations to
1651
- * the store, buffering all calls to the relevant listeners until it
1685
+ * the Store, buffering all calls to the relevant listeners until it
1652
1686
  * completes.
1653
1687
  *
1654
1688
  * This method is useful for making bulk changes to the data in a Store, and
@@ -1767,6 +1801,135 @@ export interface Store {
1767
1801
  ) => boolean,
1768
1802
  ): Return;
1769
1803
 
1804
+ /**
1805
+ * The startTransaction allows you to explicitly start a transaction that will
1806
+ * make multiple mutations to the Store, buffering all calls to the relevant
1807
+ * listeners until it completes when you call the finishTransaction method.
1808
+ *
1809
+ * Transactions are useful for making bulk changes to the data in a Store, and
1810
+ * when you don't want listeners to be called as you make each change. Changes
1811
+ * are made silently during the transaction, and listeners relevant to the
1812
+ * changes you have made will instead only be called when the whole
1813
+ * transaction is complete.
1814
+ *
1815
+ * Generally it is preferable to use the transaction method to wrap a block of
1816
+ * code as a transaction. It simply calls both the startTransaction and
1817
+ * finishTransaction methods for you. See that method for several transaction
1818
+ * examples.
1819
+ *
1820
+ * Use this startTransaction method when you have a more 'open-ended'
1821
+ * transaction, such as one containing mutations triggered from other events
1822
+ * that are asynchronous or not occurring inline to your code. You must
1823
+ * remember to also call the finishTransaction method explicitly when it is
1824
+ * done, of course.
1825
+ *
1826
+ * @returns A reference to the Store.
1827
+ * @example
1828
+ * This example makes changes to two Cells, first outside, and secondly
1829
+ * within, a transaction that is explicitly started and finished. In the
1830
+ * second case, the Row listener is only called once.
1831
+ *
1832
+ * ```js
1833
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
1834
+ * store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
1835
+ *
1836
+ * store.setCell('pets', 'fido', 'color', 'brown');
1837
+ * store.setCell('pets', 'fido', 'sold', false);
1838
+ * // -> 'Fido changed'
1839
+ * // -> 'Fido changed'
1840
+ *
1841
+ * store.startTransaction();
1842
+ * store.setCell('pets', 'fido', 'color', 'walnut');
1843
+ * store.setCell('pets', 'fido', 'sold', true);
1844
+ * store.finishTransaction();
1845
+ * // -> 'Fido changed'
1846
+ * ```
1847
+ * @category Transaction
1848
+ */
1849
+ startTransaction(): Store;
1850
+
1851
+ /**
1852
+ * The finishTransaction allows you to explicitly finish a transaction that
1853
+ * has made multiple mutations to the Store, triggering all calls to the
1854
+ * relevant listeners.
1855
+ *
1856
+ * Transactions are useful for making bulk changes to the data in a Store, and
1857
+ * when you don't want listeners to be called as you make each change. Changes
1858
+ * are made silently during the transaction, and listeners relevant to the
1859
+ * changes you have made will instead only be called when the whole
1860
+ * transaction is complete.
1861
+ *
1862
+ * Generally it is preferable to use the transaction method to wrap a block of
1863
+ * code as a transaction. It simply calls both the startTransaction and
1864
+ * finishTransaction methods for you. See that method for several transaction
1865
+ * examples.
1866
+ *
1867
+ * Use this finishTransaction method when you have a more 'open-ended'
1868
+ * transaction, such as one containing mutations triggered from other events
1869
+ * that are asynchronous or not occurring inline to your code. There must have
1870
+ * been a corresponding startTransaction method that this completes, of
1871
+ * course, otherwise this function has no effect.
1872
+ *
1873
+ * @param doRollback An optional callback that should return `true` if you
1874
+ * want to rollback the transaction at the end.
1875
+ * @returns A reference to the Store.
1876
+ * @example
1877
+ * This example makes changes to two Cells, first outside, and secondly
1878
+ * within, a transaction that is explicitly started and finished. In the
1879
+ * second case, the Row listener is only called once.
1880
+ *
1881
+ * ```js
1882
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
1883
+ * store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
1884
+ *
1885
+ * store.setCell('pets', 'fido', 'color', 'brown');
1886
+ * store.setCell('pets', 'fido', 'sold', false);
1887
+ * // -> 'Fido changed'
1888
+ * // -> 'Fido changed'
1889
+ *
1890
+ * store.startTransaction();
1891
+ * store.setCell('pets', 'fido', 'color', 'walnut');
1892
+ * store.setCell('pets', 'fido', 'sold', true);
1893
+ * store.finishTransaction();
1894
+ * // -> 'Fido changed'
1895
+ * ```
1896
+ * @example
1897
+ * This example makes multiple changes to the Store, including some attempts
1898
+ * to update a Cell with invalid values. The `doRollback` callback receives
1899
+ * information about the changes and invalid attempts, and then judges that
1900
+ * the transaction should be rolled back to its original state.
1901
+ *
1902
+ * ```js
1903
+ * const store = createStore().setTables({
1904
+ * pets: {fido: {species: 'dog', color: 'brown'}},
1905
+ * });
1906
+ *
1907
+ * store.startTransaction();
1908
+ * store.setCell('pets', 'fido', 'color', 'black');
1909
+ * store.setCell('pets', 'fido', 'eyes', ['left', 'right']);
1910
+ * store.setCell('pets', 'fido', 'info', {sold: null});
1911
+ * store.finishTransaction((changedCells, invalidCells) => {
1912
+ * console.log(store.getTables());
1913
+ * // -> {pets: {fido: {species: 'dog', color: 'black'}}}
1914
+ * console.log(changedCells);
1915
+ * // -> {pets: {fido: {color: ['brown', 'black']}}}
1916
+ * console.log(invalidCells);
1917
+ * // -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
1918
+ * return invalidCells['pets'] != null;
1919
+ * });
1920
+ *
1921
+ * console.log(store.getTables());
1922
+ * // -> {pets: {fido: {species: 'dog', color: 'brown'}}}
1923
+ * ```
1924
+ * @category Transaction
1925
+ */
1926
+ finishTransaction(
1927
+ doRollback?: (
1928
+ changedCells: ChangedCells,
1929
+ invalidCells: InvalidCells,
1930
+ ) => boolean,
1931
+ ): Store;
1932
+
1770
1933
  /**
1771
1934
  * The forEachTable method takes a function that it will then call for each
1772
1935
  * Table in the Store.
@@ -2742,6 +2905,143 @@ export interface Store {
2742
2905
  mutator?: boolean,
2743
2906
  ): Id;
2744
2907
 
2908
+ /**
2909
+ * The addWillFinishTransactionListener method registers a listener function
2910
+ * with the Store that will be called just before other non-mutating listeners
2911
+ * are called at the end of the transaction.
2912
+ *
2913
+ * This is useful if you need to know that a set of listeners are about to be
2914
+ * called at the end of a transaction, perhaps to batch _their_ consequences
2915
+ * together.
2916
+ *
2917
+ * The provided TransactionListener will receive a reference to the Store and
2918
+ * a boolean to indicate whether Cell values have been touched during the
2919
+ * transaction. The latter flag is intended as a hint about whether
2920
+ * non-mutating listeners might be being called at the end of the transaction.
2921
+ *
2922
+ * Here, 'touched' means that Cell values have either been changed, or changed
2923
+ * and then changed back to its original value during the transaction. The
2924
+ * exception is a transaction that has been rolled back, for which the value
2925
+ * of `cellsTouched` in the listener will be `false` because all changes have
2926
+ * been reverted.
2927
+ *
2928
+ * @returns A unique Id for the listener that can later be used to remove it.
2929
+ * @example
2930
+ * This example registers a listener that is called at the end of the
2931
+ * transaction, just before its listeners will be called. The transactions
2932
+ * shown here variously change, touch, and rollback cells, demonstrating how
2933
+ * the `cellsTouched` parameter in the listener works.
2934
+ *
2935
+ * ```js
2936
+ * const store = createStore().setTables({
2937
+ * pets: {fido: {species: 'dog', color: 'brown'}},
2938
+ * });
2939
+ * const listenerId = store.addWillFinishTransactionListener(
2940
+ * (store, cellsTouched) => console.log(`Cells touched: ${cellsTouched}`),
2941
+ * );
2942
+ * const listenerId2 = store.addTablesListener(() =>
2943
+ * console.log('Tables changed'),
2944
+ * );
2945
+ *
2946
+ * store.transaction(() => store.setCell('pets', 'fido', 'color', 'brown'));
2947
+ * // -> 'Cells touched: false'
2948
+ *
2949
+ * store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
2950
+ * // -> 'Cells touched: true'
2951
+ * // -> 'Tables changed'
2952
+ *
2953
+ * store.transaction(() => {
2954
+ * store.setRow('pets', 'felix', {species: 'cat'});
2955
+ * store.delRow('pets', 'felix');
2956
+ * });
2957
+ * // -> 'Cells touched: true'
2958
+ *
2959
+ * store.transaction(
2960
+ * () => store.setRow('pets', 'fido', {species: 'dog'}),
2961
+ * () => true,
2962
+ * );
2963
+ * // -> 'Cells touched: false'
2964
+ * // Transaction was rolled back.
2965
+ *
2966
+ * store.callListener(listenerId);
2967
+ * // -> 'Cells touched: undefined'
2968
+ * // It is meaningless to call this listener directly.
2969
+ *
2970
+ * store.delListener(listenerId).delListener(listenerId2);
2971
+ * ```
2972
+ * @category Listener
2973
+ */
2974
+ addWillFinishTransactionListener(listener: TransactionListener): Id;
2975
+
2976
+ /**
2977
+ * The addDidFinishTransactionListener method registers a listener function
2978
+ * with the Store that will be called just after other non-mutating listeners
2979
+ * are called at the end of the transaction.
2980
+ *
2981
+ * This is useful if you need to know that a set of listeners have just been
2982
+ * called at the end of a transaction, perhaps to batch _their_ consequences
2983
+ * together.
2984
+ *
2985
+ * The provided TransactionListener will receive a reference to the Store and
2986
+ * a boolean to indicate whether Cell values have been touched during the
2987
+ * transaction. The latter flag is intended as a hint about whether
2988
+ * non-mutating listeners might have been called at the end of the
2989
+ * transaction.
2990
+ *
2991
+ * Here, 'touched' means that Cell values have either been changed, or changed
2992
+ * and then changed back to its original value during the transaction. The
2993
+ * exception is a transaction that has been rolled back, for which the value
2994
+ * of `cellsTouched` in the listener will be `false` because all changes have
2995
+ * been reverted.
2996
+ *
2997
+ * @returns A unique Id for the listener that can later be used to remove it.
2998
+ * @example
2999
+ * This example registers a listener that is called at the end of the
3000
+ * transaction, just after its listeners have been called. The transactions
3001
+ * shown here variously change, touch, and rollback cells, demonstrating how
3002
+ * the `cellsTouched` parameter in the listener works.
3003
+ *
3004
+ * ```js
3005
+ * const store = createStore().setTables({
3006
+ * pets: {fido: {species: 'dog', color: 'brown'}},
3007
+ * });
3008
+ * const listenerId = store.addDidFinishTransactionListener(
3009
+ * (store, cellsTouched) => console.log(`Cells touched: ${cellsTouched}`),
3010
+ * );
3011
+ * const listenerId2 = store.addTablesListener(() =>
3012
+ * console.log('Tables changed'),
3013
+ * );
3014
+ *
3015
+ * store.transaction(() => store.setCell('pets', 'fido', 'color', 'brown'));
3016
+ * // -> 'Cells touched: false'
3017
+ *
3018
+ * store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
3019
+ * // -> 'Tables changed'
3020
+ * // -> 'Cells touched: true'
3021
+ *
3022
+ * store.transaction(() => {
3023
+ * store.setRow('pets', 'felix', {species: 'cat'});
3024
+ * store.delRow('pets', 'felix');
3025
+ * });
3026
+ * // -> 'Cells touched: true'
3027
+ *
3028
+ * store.transaction(
3029
+ * () => store.setRow('pets', 'fido', {species: 'dog'}),
3030
+ * () => true,
3031
+ * );
3032
+ * // -> 'Cells touched: false'
3033
+ * // Transaction was rolled back.
3034
+ *
3035
+ * store.callListener(listenerId);
3036
+ * // -> 'Cells touched: undefined'
3037
+ * // It is meaningless to call this listener directly.
3038
+ *
3039
+ * store.delListener(listenerId).delListener(listenerId2);
3040
+ * ```
3041
+ * @category Listener
3042
+ */
3043
+ addDidFinishTransactionListener(listener: TransactionListener): Id;
3044
+
2745
3045
  /**
2746
3046
  * The callListener method provides a way for you to manually provoke a
2747
3047
  * listener to be called, even if the underlying data hasn't changed.