tinybase 1.1.0-beta.0 → 1.2.0-beta.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.
@@ -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
@@ -1653,9 +1718,15 @@ export interface Store {
1653
1718
  * // -> undefined
1654
1719
  * // No net change during the transaction, so the listener is not called.
1655
1720
  * ```
1656
- * @category Setter
1721
+ * @category Transaction
1657
1722
  */
1658
- transaction<Return>(actions: () => Return): Return;
1723
+ transaction<Return>(
1724
+ actions: () => Return,
1725
+ doRollback?: (
1726
+ changedCells: ChangedCells,
1727
+ invalidCells: InvalidCells,
1728
+ ) => boolean,
1729
+ ): Return;
1659
1730
 
1660
1731
  /**
1661
1732
  * The forEachTable method takes a function that it will then call for each
@@ -2433,6 +2504,21 @@ export interface Store {
2433
2504
  * The changes made by mutator listeners do not fire other mutating listeners,
2434
2505
  * though they will fire non-mutator listeners.
2435
2506
  *
2507
+ * Special note should be made for how the listener will be called when a
2508
+ * Schema is present. The listener will be called:
2509
+ *
2510
+ * - if a Table is being updated that is not specified in the Schema
2511
+ * - if a Cell is of the wrong type specified in the Schema
2512
+ * - if a Cell is omitted and is not defaulted in the Schema
2513
+ * - if an empty Row is provided and there are no Cell defaults in the Schema
2514
+ *
2515
+ * The listener will not be called if Cell that is defaulted in the Schema is
2516
+ * not provided, as long as all of the Cells that are _not_ defaulted _are_
2517
+ * provided.
2518
+ *
2519
+ * To help understand all of these schema-based conditions, please see the
2520
+ * Schema example below.
2521
+ *
2436
2522
  * @param tableId The Id of the Table to listen to, or `null` as a wildcard.
2437
2523
  * @param rowId The Id of the Row to listen to, or `null` as a wildcard.
2438
2524
  * @param cellId The Id of the Cell to listen to, or `null` as a wildcard.
@@ -2468,7 +2554,9 @@ export interface Store {
2468
2554
  * ```
2469
2555
  * @example
2470
2556
  * This example registers a listener that responds to any invalid changes to
2471
- * any Cell.
2557
+ * any Cell - in a Store _without_ a Schema. Note also how it then responds to
2558
+ * cases where an empty or invalid Row objects, or Table objects, or Tables
2559
+ * objects are provided.
2472
2560
  *
2473
2561
  * ```js
2474
2562
  * const store = createStore().setTables({
@@ -2490,6 +2578,91 @@ export interface Store {
2490
2578
  * store.setTable('sales', {fido: {date: new Date()}});
2491
2579
  * // -> 'Invalid date cell in fido row in sales table'
2492
2580
  *
2581
+ * store.setRow('pets', 'felix', {});
2582
+ * // -> 'Invalid undefined cell in felix row in pets table'
2583
+ *
2584
+ * store.setRow('filter', 'name', /[a-z]?/);
2585
+ * // -> 'Invalid undefined cell in name row in filter table'
2586
+ *
2587
+ * store.setRow('sales', '2021', {forecast: undefined});
2588
+ * // -> 'Invalid forecast cell in 2021 row in sales table'
2589
+ *
2590
+ * store.addRow('filter', /[0-9]?/);
2591
+ * // -> 'Invalid undefined cell in undefined row in filter table'
2592
+ *
2593
+ * store.setTable('raw', {});
2594
+ * // -> 'Invalid undefined cell in undefined row in raw table'
2595
+ *
2596
+ * store.setTable('raw', ['row1', 'row2']);
2597
+ * // -> 'Invalid undefined cell in undefined row in raw table'
2598
+ *
2599
+ * store.setTables(['table1', 'table2']);
2600
+ * // -> 'Invalid undefined cell in undefined row in undefined table'
2601
+ *
2602
+ * store.delListener(listenerId);
2603
+ * ```
2604
+ * @example
2605
+ * This example registers a listener that responds to any invalid changes to
2606
+ * any Cell - in a Store _with_ a Schema. Note how it responds to cases where
2607
+ * missing parameters are provided for optional, and defaulted Cell values in
2608
+ * a Row.
2609
+ *
2610
+ * ```js
2611
+ * const store = createStore().setSchema({
2612
+ * pets: {
2613
+ * species: {type: 'string'},
2614
+ * color: {type: 'string', default: 'unknown'},
2615
+ * },
2616
+ * });
2617
+ *
2618
+ * const listenerId = store.addInvalidCellListener(
2619
+ * null,
2620
+ * null,
2621
+ * null,
2622
+ * (store, tableId, rowId, cellId) => {
2623
+ * console.log(
2624
+ * `Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
2625
+ * );
2626
+ * },
2627
+ * );
2628
+ *
2629
+ * store.setRow('sales', 'fido', {price: 5});
2630
+ * // -> 'Invalid price cell in fido row in sales table'
2631
+ * // The listener is called, because the sales Table is not in the schema
2632
+ *
2633
+ * store.setRow('pets', 'felix', {species: true});
2634
+ * // -> 'Invalid species cell in felix row in pets table'
2635
+ * // The listener is called, because species is invalid...
2636
+ * console.log(store.getRow('pets', 'felix'));
2637
+ * // -> {color: 'unknown'}
2638
+ * // ...even though a Row was set with the default value
2639
+ *
2640
+ * store.setRow('pets', 'fido', {color: 'brown'});
2641
+ * // -> 'Invalid species cell in fido row in pets table'
2642
+ * // The listener is called, because species is missing and not defaulted...
2643
+ * console.log(store.getRow('pets', 'fido'));
2644
+ * // -> {color: 'brown'}
2645
+ * // ...even though a Row was set
2646
+ *
2647
+ * store.setRow('pets', 'rex', {species: 'dog'});
2648
+ * console.log(store.getRow('pets', 'rex'));
2649
+ * // -> {species: 'dog', color: 'unknown'}
2650
+ * // The listener is not called, because color is defaulted
2651
+ *
2652
+ * store.delTables().setSchema({
2653
+ * pets: {
2654
+ * species: {type: 'string'},
2655
+ * color: {type: 'string'},
2656
+ * },
2657
+ * });
2658
+ *
2659
+ * store.setRow('pets', 'cujo', {});
2660
+ * // -> 'Invalid species cell in cujo row in pets table'
2661
+ * // -> 'Invalid color cell in cujo row in pets table'
2662
+ * // -> 'Invalid undefined cell in cujo row in pets table'
2663
+ * // The listener is called multiple times, because neither Cell is defaulted
2664
+ * // and the Row as a whole is empty
2665
+ *
2493
2666
  * store.delListener(listenerId);
2494
2667
  * ```
2495
2668
  * @example