tinybase 1.1.0-beta.1 → 1.2.0-beta.1

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.
@@ -37,7 +37,7 @@ export type Metric = number;
37
37
  export type MetricCallback = (metricId: Id, metric?: Metric) => void;
38
38
 
39
39
  /**
40
- * The Aggregate type describes a custom function that takes an array or numbers
40
+ * The Aggregate type describes a custom function that takes an array of numbers
41
41
  * and returns an aggregate that is used as a Metric.
42
42
  *
43
43
  * There are a number of common predefined aggregators, such as for counting,
@@ -88,6 +88,18 @@ export type Row = {[cellId: Id]: Cell};
88
88
  */
89
89
  export type Cell = string | number | boolean;
90
90
 
91
+ /**
92
+ * The CellOrUndefined type is a data structure representing the data in a
93
+ * single cell or the value `undefined`.
94
+ *
95
+ * This is used when describing a Cell that is present _or_ that is not present
96
+ * - such as when it has been deleted, or when describing a previous state where
97
+ * the Cell value has since been added.
98
+ *
99
+ * @category Store
100
+ */
101
+ export type CellOrUndefined = Cell | undefined;
102
+
91
103
  /**
92
104
  * The TableCallback type describes a function that takes a Table's Id and a
93
105
  * callback to loop over each Row within it.
@@ -149,7 +161,7 @@ export type CellCallback = (cellId: Id, cell: Cell) => void;
149
161
  * @param cell The current value of the Cell to map to a new value.
150
162
  * @category Callback
151
163
  */
152
- export type MapCell = (cell: Cell | undefined) => Cell;
164
+ export type MapCell = (cell: CellOrUndefined) => Cell;
153
165
 
154
166
  /**
155
167
  * The GetCell type describes a function that takes a Id and returns the Cell
@@ -162,7 +174,7 @@ export type MapCell = (cell: Cell | undefined) => Cell;
162
174
  * @param cellId The Id of the Cell to fetch the value for.
163
175
  * @category Callback
164
176
  */
165
- export type GetCell = (cellId: Id) => Cell | undefined;
177
+ export type GetCell = (cellId: Id) => CellOrUndefined;
166
178
 
167
179
  /**
168
180
  * The TablesListener type describes a function that is used to listen to
@@ -390,8 +402,8 @@ export type GetCellChange = (tableId: Id, rowId: Id, cellId: Id) => CellChange;
390
402
  */
391
403
  export type CellChange = [
392
404
  changed: boolean,
393
- oldCell: Cell | undefined,
394
- newCell: Cell | undefined,
405
+ oldCell: CellOrUndefined,
406
+ newCell: CellOrUndefined,
395
407
  ];
396
408
 
397
409
  /**
@@ -461,6 +473,59 @@ export type CellSchema =
461
473
  default?: boolean;
462
474
  };
463
475
 
476
+ /**
477
+ * The ChangedCells type describes the Cell values that have been changed during
478
+ * a transaction, primarily used so that you can indicate whether the
479
+ * transaction should be rolled back.
480
+ *
481
+ * A ChangedCells object is provided to the `doRollback` callback when using the
482
+ * transaction method. See that method for specific examples.
483
+ *
484
+ * This type is a nested structure of Table, Row, and Cell objects, much like
485
+ * the Tables object, but one which provides both the old and new Cell values in
486
+ * a two-part array. These are describing the state of each changed Cell in
487
+ * Store at the _start_ of the transaction, and by the _end_ of the transaction.
488
+ *
489
+ * Hence, an `undefined` value for the first item in the array means that the
490
+ * Cell was added during the transaction. An `undefined` value for the second
491
+ * item in the array means that the Cell was removed during the transaction. An
492
+ * array with two different Cell values indicates that it was changed. The
493
+ * two-part array will never contain two items of the same value (including two
494
+ * `undefined` values), even if, during the transaction, a Cell was changed to a
495
+ * different value and then changed back.
496
+ *
497
+ * @category Transaction
498
+ */
499
+ export type ChangedCells = {
500
+ [tableId: Id]: {
501
+ [rowId: Id]: {
502
+ [cellId: Id]: [CellOrUndefined, CellOrUndefined];
503
+ };
504
+ };
505
+ };
506
+ /**
507
+ * The InvalidCells type describes the invalid Cell values that have been
508
+ * attempted during a transaction, primarily used so that you can indicate
509
+ * whether the transaction should be rolled back.
510
+ *
511
+ * An InvalidCells object is provided to the `doRollback` callback when using
512
+ * the transaction method. See that method for specific examples.
513
+ *
514
+ * This type is a nested structure of Table, Row, and Cell objects, much like
515
+ * the Tables object, but one for which Cell values are listed in array
516
+ * (much like the InvalidCellListener type) so that multiple failed attempts to
517
+ * change a Cell during the transaction are described.
518
+ *
519
+ * @category Transaction
520
+ */
521
+ export type InvalidCells = {
522
+ [tableId: Id]: {
523
+ [rowId: Id]: {
524
+ [cellId: Id]: any[];
525
+ };
526
+ };
527
+ };
528
+
464
529
  /**
465
530
  * The StoreListenerStats type describes the number of listeners registered with
466
531
  * the Store, and can be used for debugging purposes.
@@ -897,7 +962,7 @@ export interface Store {
897
962
  * ```
898
963
  * @category Getter
899
964
  */
900
- getCell(tableId: Id, rowId: Id, cellId: Id): Cell | undefined;
965
+ getCell(tableId: Id, rowId: Id, cellId: Id): CellOrUndefined;
901
966
 
902
967
  /**
903
968
  * The hasTables method returns a boolean indicating whether any Table objects
@@ -1603,7 +1668,15 @@ export interface Store {
1603
1668
  * Transactions can be nested. Relevant listeners will be called only when the
1604
1669
  * outermost one completes.
1605
1670
  *
1671
+ * The second, optional parameter, `doRollback` is a callback that you can use
1672
+ * to rollback the transaction if it did not complete to your satisfaction. It
1673
+ * is called with `changedCells` and `invalidCells` parameters, which inform
1674
+ * you of the net changes that have been made during the transaction, and any
1675
+ * invalid attempts to do so, respectively.
1676
+ *
1606
1677
  * @param actions The function to be executed as a transaction.
1678
+ * @param doRollback An optional callback that should return `true` if you
1679
+ * want to rollback the transaction at the end.
1607
1680
  * @returns Whatever value the provided transaction function returns.
1608
1681
  * @example
1609
1682
  * This example makes changes to two Cells, first outside, and secondly
@@ -1653,9 +1726,46 @@ export interface Store {
1653
1726
  * // -> undefined
1654
1727
  * // No net change during the transaction, so the listener is not called.
1655
1728
  * ```
1656
- * @category Setter
1729
+ * @example
1730
+ * This example makes multiple changes to the Store, including some attempts
1731
+ * to update a Cell with invalid values. The `doRollback` callback receives
1732
+ * information about the changes and invalid attempts, and then judges that
1733
+ * the transaction should be rolled back to its original state.
1734
+ *
1735
+ * ```js
1736
+ * const store = createStore().setTables({
1737
+ * pets: {fido: {species: 'dog', color: 'brown'}},
1738
+ * });
1739
+ *
1740
+ * store.transaction(
1741
+ * () => {
1742
+ * store.setCell('pets', 'fido', 'color', 'black');
1743
+ * store.setCell('pets', 'fido', 'eyes', ['left', 'right']);
1744
+ * store.setCell('pets', 'fido', 'info', {sold: null});
1745
+ * },
1746
+ * (changedCells, invalidCells) => {
1747
+ * console.log(store.getTables());
1748
+ * // -> {pets: {fido: {species: 'dog', color: 'black'}}}
1749
+ * console.log(changedCells);
1750
+ * // -> {pets: {fido: {color: ['brown', 'black']}}}
1751
+ * console.log(invalidCells);
1752
+ * // -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
1753
+ * return invalidCells['pets'] != null;
1754
+ * },
1755
+ * );
1756
+ *
1757
+ * console.log(store.getTables());
1758
+ * // -> {pets: {fido: {species: 'dog', color: 'brown'}}}
1759
+ * ```
1760
+ * @category Transaction
1657
1761
  */
1658
- transaction<Return>(actions: () => Return): Return;
1762
+ transaction<Return>(
1763
+ actions: () => Return,
1764
+ doRollback?: (
1765
+ changedCells: ChangedCells,
1766
+ invalidCells: InvalidCells,
1767
+ ) => boolean,
1768
+ ): Return;
1659
1769
 
1660
1770
  /**
1661
1771
  * The forEachTable method takes a function that it will then call for each
@@ -2433,6 +2543,21 @@ export interface Store {
2433
2543
  * The changes made by mutator listeners do not fire other mutating listeners,
2434
2544
  * though they will fire non-mutator listeners.
2435
2545
  *
2546
+ * Special note should be made for how the listener will be called when a
2547
+ * Schema is present. The listener will be called:
2548
+ *
2549
+ * - if a Table is being updated that is not specified in the Schema
2550
+ * - if a Cell is of the wrong type specified in the Schema
2551
+ * - if a Cell is omitted and is not defaulted in the Schema
2552
+ * - if an empty Row is provided and there are no Cell defaults in the Schema
2553
+ *
2554
+ * The listener will not be called if Cell that is defaulted in the Schema is
2555
+ * not provided, as long as all of the Cells that are _not_ defaulted _are_
2556
+ * provided.
2557
+ *
2558
+ * To help understand all of these schema-based conditions, please see the
2559
+ * Schema example below.
2560
+ *
2436
2561
  * @param tableId The Id of the Table to listen to, or `null` as a wildcard.
2437
2562
  * @param rowId The Id of the Row to listen to, or `null` as a wildcard.
2438
2563
  * @param cellId The Id of the Cell to listen to, or `null` as a wildcard.
@@ -2468,7 +2593,9 @@ export interface Store {
2468
2593
  * ```
2469
2594
  * @example
2470
2595
  * This example registers a listener that responds to any invalid changes to
2471
- * any Cell.
2596
+ * any Cell - in a Store _without_ a Schema. Note also how it then responds to
2597
+ * cases where an empty or invalid Row objects, or Table objects, or Tables
2598
+ * objects are provided.
2472
2599
  *
2473
2600
  * ```js
2474
2601
  * const store = createStore().setTables({
@@ -2490,6 +2617,91 @@ export interface Store {
2490
2617
  * store.setTable('sales', {fido: {date: new Date()}});
2491
2618
  * // -> 'Invalid date cell in fido row in sales table'
2492
2619
  *
2620
+ * store.setRow('pets', 'felix', {});
2621
+ * // -> 'Invalid undefined cell in felix row in pets table'
2622
+ *
2623
+ * store.setRow('filter', 'name', /[a-z]?/);
2624
+ * // -> 'Invalid undefined cell in name row in filter table'
2625
+ *
2626
+ * store.setRow('sales', '2021', {forecast: undefined});
2627
+ * // -> 'Invalid forecast cell in 2021 row in sales table'
2628
+ *
2629
+ * store.addRow('filter', /[0-9]?/);
2630
+ * // -> 'Invalid undefined cell in undefined row in filter table'
2631
+ *
2632
+ * store.setTable('raw', {});
2633
+ * // -> 'Invalid undefined cell in undefined row in raw table'
2634
+ *
2635
+ * store.setTable('raw', ['row1', 'row2']);
2636
+ * // -> 'Invalid undefined cell in undefined row in raw table'
2637
+ *
2638
+ * store.setTables(['table1', 'table2']);
2639
+ * // -> 'Invalid undefined cell in undefined row in undefined table'
2640
+ *
2641
+ * store.delListener(listenerId);
2642
+ * ```
2643
+ * @example
2644
+ * This example registers a listener that responds to any invalid changes to
2645
+ * any Cell - in a Store _with_ a Schema. Note how it responds to cases where
2646
+ * missing parameters are provided for optional, and defaulted Cell values in
2647
+ * a Row.
2648
+ *
2649
+ * ```js
2650
+ * const store = createStore().setSchema({
2651
+ * pets: {
2652
+ * species: {type: 'string'},
2653
+ * color: {type: 'string', default: 'unknown'},
2654
+ * },
2655
+ * });
2656
+ *
2657
+ * const listenerId = store.addInvalidCellListener(
2658
+ * null,
2659
+ * null,
2660
+ * null,
2661
+ * (store, tableId, rowId, cellId) => {
2662
+ * console.log(
2663
+ * `Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
2664
+ * );
2665
+ * },
2666
+ * );
2667
+ *
2668
+ * store.setRow('sales', 'fido', {price: 5});
2669
+ * // -> 'Invalid price cell in fido row in sales table'
2670
+ * // The listener is called, because the sales Table is not in the schema
2671
+ *
2672
+ * store.setRow('pets', 'felix', {species: true});
2673
+ * // -> 'Invalid species cell in felix row in pets table'
2674
+ * // The listener is called, because species is invalid...
2675
+ * console.log(store.getRow('pets', 'felix'));
2676
+ * // -> {color: 'unknown'}
2677
+ * // ...even though a Row was set with the default value
2678
+ *
2679
+ * store.setRow('pets', 'fido', {color: 'brown'});
2680
+ * // -> 'Invalid species cell in fido row in pets table'
2681
+ * // The listener is called, because species is missing and not defaulted...
2682
+ * console.log(store.getRow('pets', 'fido'));
2683
+ * // -> {color: 'brown'}
2684
+ * // ...even though a Row was set
2685
+ *
2686
+ * store.setRow('pets', 'rex', {species: 'dog'});
2687
+ * console.log(store.getRow('pets', 'rex'));
2688
+ * // -> {species: 'dog', color: 'unknown'}
2689
+ * // The listener is not called, because color is defaulted
2690
+ *
2691
+ * store.delTables().setSchema({
2692
+ * pets: {
2693
+ * species: {type: 'string'},
2694
+ * color: {type: 'string'},
2695
+ * },
2696
+ * });
2697
+ *
2698
+ * store.setRow('pets', 'cujo', {});
2699
+ * // -> 'Invalid species cell in cujo row in pets table'
2700
+ * // -> 'Invalid color cell in cujo row in pets table'
2701
+ * // -> 'Invalid undefined cell in cujo row in pets table'
2702
+ * // The listener is called multiple times, because neither Cell is defaulted
2703
+ * // and the Row as a whole is empty
2704
+ *
2493
2705
  * store.delListener(listenerId);
2494
2706
  * ```
2495
2707
  * @example