tinybase 1.0.2 → 1.1.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.
Files changed (60) hide show
  1. package/lib/checkpoints.d.ts +945 -0
  2. package/lib/checkpoints.js +1 -0
  3. package/lib/checkpoints.js.gz +0 -0
  4. package/lib/common.d.ts +115 -0
  5. package/lib/common.js +1 -0
  6. package/lib/common.js.gz +0 -0
  7. package/lib/debug/checkpoints.d.ts +66 -0
  8. package/lib/debug/checkpoints.js +332 -0
  9. package/lib/debug/common.js +3 -0
  10. package/lib/debug/indexes.d.ts +167 -6
  11. package/lib/debug/indexes.js +431 -0
  12. package/lib/debug/metrics.d.ts +72 -0
  13. package/lib/debug/metrics.js +401 -0
  14. package/lib/debug/persisters.js +191 -0
  15. package/lib/debug/relationships.d.ts +86 -1
  16. package/lib/debug/relationships.js +434 -0
  17. package/lib/debug/store.d.ts +187 -5
  18. package/lib/debug/store.js +783 -0
  19. package/lib/debug/tinybase.js +144 -39
  20. package/lib/indexes.d.ts +939 -0
  21. package/lib/indexes.js +1 -0
  22. package/lib/indexes.js.gz +0 -0
  23. package/lib/metrics.d.ts +829 -0
  24. package/lib/metrics.js +1 -0
  25. package/lib/metrics.js.gz +0 -0
  26. package/lib/persisters.d.ts +711 -0
  27. package/lib/persisters.js +1 -0
  28. package/lib/persisters.js.gz +0 -0
  29. package/lib/relationships.d.ts +1201 -0
  30. package/lib/relationships.js +1 -0
  31. package/lib/relationships.js.gz +0 -0
  32. package/lib/store.d.ts +2688 -0
  33. package/lib/store.js +1 -0
  34. package/lib/store.js.gz +0 -0
  35. package/lib/tinybase.d.ts +13 -0
  36. package/lib/tinybase.js +1 -0
  37. package/lib/tinybase.js.gz +0 -0
  38. package/lib/ui-react.d.ts +7185 -0
  39. package/lib/ui-react.js +1 -0
  40. package/lib/ui-react.js.gz +0 -0
  41. package/lib/umd/checkpoints.js +1 -0
  42. package/lib/umd/checkpoints.js.gz +0 -0
  43. package/lib/umd/common.js +1 -0
  44. package/lib/umd/common.js.gz +0 -0
  45. package/lib/umd/indexes.js +1 -0
  46. package/lib/umd/indexes.js.gz +0 -0
  47. package/lib/umd/metrics.js +1 -0
  48. package/lib/umd/metrics.js.gz +0 -0
  49. package/lib/umd/persisters.js +1 -0
  50. package/lib/umd/persisters.js.gz +0 -0
  51. package/lib/umd/relationships.js +1 -0
  52. package/lib/umd/relationships.js.gz +0 -0
  53. package/lib/umd/store.js +1 -0
  54. package/lib/umd/store.js.gz +0 -0
  55. package/lib/umd/tinybase.js +1 -0
  56. package/lib/umd/tinybase.js.gz +0 -0
  57. package/lib/umd/ui-react.js +1 -0
  58. package/lib/umd/ui-react.js.gz +0 -0
  59. package/package.json +14 -14
  60. package/readme.md +2 -2
@@ -22,6 +22,20 @@ import {Id, IdOrNull, Ids} from './common.d';
22
22
  */
23
23
  export type Metric = number;
24
24
 
25
+ /**
26
+ * The MetricCallback type describes a function that takes a Metric's Id and a
27
+ * callback to loop over each Row within it.
28
+ *
29
+ * A MetricCallback is provided when using the forEachMetric method, so that you
30
+ * can do something based on every Metric in the Metrics object. See that method
31
+ * for specific examples.
32
+ *
33
+ * @param metricId The Id of the Metric that the callback can operate on.
34
+ * @param metric The value of the Metric.
35
+ * @category Callback
36
+ */
37
+ export type MetricCallback = (metricId: Id, metric?: Metric) => void;
38
+
25
39
  /**
26
40
  * The Aggregate type describes a custom function that takes an array or numbers
27
41
  * and returns an aggregate that is used as a Metric.
@@ -478,6 +492,64 @@ export interface Metrics {
478
492
  */
479
493
  getMetricIds(): Ids;
480
494
 
495
+ /**
496
+ * The forEachMetric method takes a function that it will then call for each
497
+ * Metric in the Metrics object.
498
+ *
499
+ * This method is useful for iterating over all the Metrics in a functional
500
+ * style. The `metricCallback` parameter is a MetricCallback function that
501
+ * will be called with the Id of each Metric and its value.
502
+ *
503
+ * @param metricCallback The function that should be called for every Metric.
504
+ * @example
505
+ * This example iterates over each Metric in a Metrics object.
506
+ *
507
+ * ```js
508
+ * const store = createStore().setTable('species', {
509
+ * dog: {price: 5},
510
+ * cat: {price: 4},
511
+ * worm: {price: 1},
512
+ * });
513
+ * const metrics = createMetrics(store)
514
+ * .setMetricDefinition('highestPrice', 'species', 'max', 'price')
515
+ * .setMetricDefinition('lowestPrice', 'species', 'min', 'price');
516
+ *
517
+ * metrics.forEachMetric((metricId, metric) => {
518
+ * console.log([metricId, metric]);
519
+ * });
520
+ * // -> ['highestPrice', 5]
521
+ * // -> ['lowestPrice', 1]
522
+ * ```
523
+ * @category Iterator
524
+ */
525
+ forEachMetric(metricCallback: MetricCallback): void;
526
+
527
+ /**
528
+ * The hasMetric method returns a boolean indicating whether a given Metric
529
+ * exists in the Metrics object, and has a value.
530
+ *
531
+ * @param metricId The Id of a possible Metric in the Metrics object.
532
+ * @returns Whether a Metric with that Id exists.
533
+ * @example
534
+ * This example shows two simple Metric existence checks.
535
+ *
536
+ * ```js
537
+ * const store = createStore();
538
+ * const metrics = createMetrics(store);
539
+ * metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
540
+ *
541
+ * console.log(metrics.hasMetric('lowestPrice'));
542
+ * // -> false
543
+ * console.log(metrics.hasMetric('highestPrice'));
544
+ * // -> false
545
+ * store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
546
+ * console.log(metrics.hasMetric('highestPrice'));
547
+ * // -> true
548
+ * ```
549
+ * @category Getter
550
+ */
551
+ hasMetric(metricId: Id): boolean;
552
+
481
553
  /**
482
554
  * The getTableId method returns the Id of the underlying Table that is
483
555
  * backing a Metric.
@@ -0,0 +1,401 @@
1
+ const getTypeOf = (thing) => typeof thing;
2
+ const EMPTY_STRING = '';
3
+ const STRING = getTypeOf(EMPTY_STRING);
4
+ const FUNCTION = getTypeOf(getTypeOf);
5
+ const SUM = 'sum';
6
+ const AVG = 'avg';
7
+ const MIN = 'min';
8
+ const MAX = 'max';
9
+
10
+ const arrayForEach = (array, cb) => array.forEach(cb);
11
+ const arraySum = (array) => arrayReduce(array, (i, j) => i + j, 0);
12
+ const arrayLength = (array) => array.length;
13
+ const arrayIsEmpty = (array) => arrayLength(array) == 0;
14
+ const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
15
+ const arrayFromSecond = (ids) => ids.slice(1);
16
+ const arrayPush = (array, value) => array.push(value);
17
+ const arrayPop = (array) => array.pop();
18
+
19
+ const mathMax = Math.max;
20
+ const mathMin = Math.min;
21
+ const isFiniteNumber = isFinite;
22
+ const isUndefined = (thing) => thing == void 0;
23
+ const ifNotUndefined = (value, then, otherwise) =>
24
+ isUndefined(value) ? otherwise?.() : then(value);
25
+ const isString = (thing) => getTypeOf(thing) == STRING;
26
+ const isFunction = (thing) => getTypeOf(thing) == FUNCTION;
27
+ const getUndefined = () => void 0;
28
+
29
+ const collSizeN = (collSizer) => (coll) =>
30
+ arrayReduce(collValues(coll), (total, coll2) => total + collSizer(coll2), 0);
31
+ const collSize = (coll) => coll.size;
32
+ const collSize2 = collSizeN(collSize);
33
+ const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false;
34
+ const collIsEmpty = (coll) => isUndefined(coll) || collSize(coll) == 0;
35
+ const collValues = (coll) => [...(coll?.values() ?? [])];
36
+ const collClear = (coll) => coll.clear();
37
+ const collForEach = (coll, cb) => coll?.forEach(cb);
38
+ const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);
39
+
40
+ const mapNew = (entries) => new Map(entries);
41
+ const mapKeys = (map) => [...(map?.keys() ?? [])];
42
+ const mapGet = (map, key) => map?.get(key);
43
+ const mapForEach = (map, cb) =>
44
+ collForEach(map, (value, key) => cb(key, value));
45
+ const mapSet = (map, key, value) =>
46
+ isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
47
+ const mapEnsure = (map, key, defaultValue, onWillAdd) => {
48
+ if (!collHas(map, key)) {
49
+ onWillAdd?.(defaultValue);
50
+ map.set(key, defaultValue);
51
+ }
52
+ return mapGet(map, key);
53
+ };
54
+
55
+ const setNew = (entries) => new Set(entries);
56
+ const setAdd = (set, value) => set?.add(value);
57
+
58
+ const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
59
+ const hasRow = store.hasRow;
60
+ const tableIds = mapNew();
61
+ const things = mapNew();
62
+ const allRowValues = mapNew();
63
+ const allSortKeys = mapNew();
64
+ const storeListenerIds = mapNew();
65
+ const getStore = () => store;
66
+ const getThingIds = () => mapKeys(tableIds);
67
+ const forEachThing = (cb) => mapForEach(things, cb);
68
+ const hasThing = (id) => collHas(things, id);
69
+ const getTableId = (id) => mapGet(tableIds, id);
70
+ const getThing = (id) => mapGet(things, id);
71
+ const setThing = (id, thing) => mapSet(things, id, thing);
72
+ const removeStoreListeners = (id) =>
73
+ ifNotUndefined(mapGet(storeListenerIds, id), (listenerIds) => {
74
+ collForEach(listenerIds, store.delListener);
75
+ mapSet(storeListenerIds, id);
76
+ });
77
+ const setDefinition = (id, tableId, onChanged, getRowValue, getSortKey) => {
78
+ const changedRowValues = mapNew();
79
+ const changedSortKeys = mapNew();
80
+ mapSet(tableIds, id, tableId);
81
+ if (!collHas(things, id)) {
82
+ mapSet(things, id, getDefaultThing());
83
+ mapSet(allRowValues, id, mapNew());
84
+ mapSet(allSortKeys, id, mapNew());
85
+ }
86
+ const rowValues = mapGet(allRowValues, id);
87
+ const sortKeys = mapGet(allSortKeys, id);
88
+ const processRow = (rowId) => {
89
+ const getCell = (cellId) => store.getCell(tableId, rowId, cellId);
90
+ const oldRowValue = mapGet(rowValues, rowId);
91
+ const newRowValue = hasRow(tableId, rowId)
92
+ ? validateRowValue(getRowValue(getCell, rowId))
93
+ : void 0;
94
+ if (oldRowValue != newRowValue) {
95
+ mapSet(changedRowValues, rowId, [oldRowValue, newRowValue]);
96
+ }
97
+ if (!isUndefined(getSortKey)) {
98
+ const oldSortKey = mapGet(sortKeys, rowId);
99
+ const newSortKey = hasRow(tableId, rowId)
100
+ ? getSortKey(getCell, rowId)
101
+ : void 0;
102
+ if (oldSortKey != newSortKey) {
103
+ mapSet(changedSortKeys, rowId, newSortKey);
104
+ }
105
+ }
106
+ };
107
+ const processTable = (force) => {
108
+ onChanged(
109
+ () => {
110
+ collForEach(changedRowValues, ([, newRowValue], rowId) =>
111
+ mapSet(rowValues, rowId, newRowValue),
112
+ );
113
+ collForEach(changedSortKeys, (newSortKey, rowId) =>
114
+ mapSet(sortKeys, rowId, newSortKey),
115
+ );
116
+ },
117
+ changedRowValues,
118
+ changedSortKeys,
119
+ rowValues,
120
+ sortKeys,
121
+ force,
122
+ );
123
+ collClear(changedRowValues);
124
+ collClear(changedSortKeys);
125
+ };
126
+ mapForEach(rowValues, processRow);
127
+ if (store.hasTable(tableId)) {
128
+ arrayForEach(store.getRowIds(tableId), (rowId) => {
129
+ if (!collHas(rowValues, rowId)) {
130
+ processRow(rowId);
131
+ }
132
+ });
133
+ }
134
+ processTable(true);
135
+ removeStoreListeners(id);
136
+ mapSet(
137
+ storeListenerIds,
138
+ id,
139
+ setNew([
140
+ store.addRowListener(tableId, null, (_store, _tableId, rowId) =>
141
+ processRow(rowId),
142
+ ),
143
+ store.addTableListener(tableId, () => processTable()),
144
+ ]),
145
+ );
146
+ };
147
+ const delDefinition = (id) => {
148
+ mapSet(tableIds, id);
149
+ mapSet(things, id);
150
+ mapSet(allRowValues, id);
151
+ mapSet(allSortKeys, id);
152
+ removeStoreListeners(id);
153
+ };
154
+ const destroy = () => mapForEach(storeListenerIds, delDefinition);
155
+ return [
156
+ getStore,
157
+ getThingIds,
158
+ forEachThing,
159
+ hasThing,
160
+ getTableId,
161
+ getThing,
162
+ setThing,
163
+ setDefinition,
164
+ delDefinition,
165
+ destroy,
166
+ ];
167
+ };
168
+ const getRowCellFunction = (getRowCell, defaultCellValue) =>
169
+ isString(getRowCell)
170
+ ? (getCell) => getCell(getRowCell)
171
+ : getRowCell ?? (() => defaultCellValue ?? EMPTY_STRING);
172
+ const getCreateFunction = (getFunction) => {
173
+ const getFunctionsByStore = /* @__PURE__ */ new WeakMap();
174
+ return (store) => {
175
+ if (!getFunctionsByStore.has(store)) {
176
+ getFunctionsByStore.set(store, getFunction(store));
177
+ }
178
+ return getFunctionsByStore.get(store);
179
+ };
180
+ };
181
+
182
+ const addDeepSet = (deepSet, value, ids) =>
183
+ arrayLength(ids) < 2
184
+ ? setAdd(
185
+ arrayIsEmpty(ids) ? deepSet : mapEnsure(deepSet, ids[0], setNew()),
186
+ value,
187
+ )
188
+ : addDeepSet(
189
+ mapEnsure(deepSet, ids[0], mapNew()),
190
+ value,
191
+ arrayFromSecond(ids),
192
+ );
193
+ const forDeepSet = (valueDo) => {
194
+ const deep = (deepIdSet, arg, ...ids) =>
195
+ ifNotUndefined(deepIdSet, (deepIdSet2) =>
196
+ arrayIsEmpty(ids)
197
+ ? valueDo(deepIdSet2, arg)
198
+ : arrayForEach([ids[0], null], (id) =>
199
+ deep(mapGet(deepIdSet2, id), arg, ...arrayFromSecond(ids)),
200
+ ),
201
+ );
202
+ return deep;
203
+ };
204
+ const getListenerFunctions = (getThing) => {
205
+ let thing;
206
+ let nextId = 0;
207
+ const listenerPool = [];
208
+ const allListeners = mapNew();
209
+ const addListener = (listener, deepSet, idOrNulls = []) => {
210
+ thing ??= getThing();
211
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
212
+ mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
213
+ addDeepSet(deepSet, id, idOrNulls);
214
+ return id;
215
+ };
216
+ const callListeners = (deepSet, ids = [], ...extraArgs) =>
217
+ forDeepSet(collForEach)(
218
+ deepSet,
219
+ (id) =>
220
+ ifNotUndefined(mapGet(allListeners, id), ([listener]) =>
221
+ listener(thing, ...ids, ...extraArgs),
222
+ ),
223
+ ...ids,
224
+ );
225
+ const delListener = (id) =>
226
+ ifNotUndefined(
227
+ mapGet(allListeners, id),
228
+ ([, deepSet, idOrNulls]) => {
229
+ forDeepSet(collDel)(deepSet, id, ...idOrNulls);
230
+ mapSet(allListeners, id);
231
+ if (arrayLength(listenerPool) < 1e3) {
232
+ arrayPush(listenerPool, id);
233
+ }
234
+ return idOrNulls;
235
+ },
236
+ () => [],
237
+ );
238
+ const callListener = (id, idNullGetters, extraArgsGetter) =>
239
+ ifNotUndefined(mapGet(allListeners, id), ([listener, , idOrNulls]) => {
240
+ const callWithIds = (...ids) => {
241
+ const index = arrayLength(ids);
242
+ index == arrayLength(idOrNulls)
243
+ ? listener(thing, ...ids, ...extraArgsGetter(ids))
244
+ : isUndefined(idOrNulls[index])
245
+ ? arrayForEach(idNullGetters[index](...ids), (id2) =>
246
+ callWithIds(...ids, id2),
247
+ )
248
+ : callWithIds(...ids, idOrNulls[index]);
249
+ };
250
+ callWithIds();
251
+ });
252
+ return [addListener, callListeners, delListener, callListener];
253
+ };
254
+
255
+ const object = Object;
256
+ const objFreeze = object.freeze;
257
+
258
+ const aggregators = mapNew([
259
+ [
260
+ AVG,
261
+ [
262
+ (numbers, length) => arraySum(numbers) / length,
263
+ (metric, add, length) => metric + (add - metric) / (length + 1),
264
+ (metric, remove, length) => metric + (metric - remove) / (length - 1),
265
+ (metric, add, remove, length) => metric + (add - remove) / length,
266
+ ],
267
+ ],
268
+ [
269
+ MAX,
270
+ [
271
+ (numbers) => mathMax(...numbers),
272
+ (metric, add) => mathMax(add, metric),
273
+ (metric, remove) => (remove == metric ? void 0 : metric),
274
+ (metric, add, remove) =>
275
+ remove == metric ? void 0 : mathMax(add, metric),
276
+ ],
277
+ ],
278
+ [
279
+ MIN,
280
+ [
281
+ (numbers) => mathMin(...numbers),
282
+ (metric, add) => mathMin(add, metric),
283
+ (metric, remove) => (remove == metric ? void 0 : metric),
284
+ (metric, add, remove) =>
285
+ remove == metric ? void 0 : mathMin(add, metric),
286
+ ],
287
+ ],
288
+ [
289
+ SUM,
290
+ [
291
+ (numbers) => arraySum(numbers),
292
+ (metric, add) => metric + add,
293
+ (metric, remove) => metric - remove,
294
+ (metric, add, remove) => metric - remove + add,
295
+ ],
296
+ ],
297
+ ]);
298
+ const createMetrics = getCreateFunction((store) => {
299
+ const metricListeners = mapNew();
300
+ const [
301
+ getStore,
302
+ getMetricIds,
303
+ forEachMetric,
304
+ hasMetric,
305
+ getTableId,
306
+ getMetric,
307
+ setMetric,
308
+ setDefinition,
309
+ delDefinition,
310
+ destroy,
311
+ ] = getDefinableFunctions(store, getUndefined, (value) =>
312
+ isNaN(value) ||
313
+ isUndefined(value) ||
314
+ value === true ||
315
+ value === false ||
316
+ value === EMPTY_STRING
317
+ ? void 0
318
+ : value * 1,
319
+ );
320
+ const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
321
+ () => metrics,
322
+ );
323
+ const setMetricDefinition = (
324
+ metricId,
325
+ tableId,
326
+ aggregate,
327
+ getNumber,
328
+ aggregateAdd,
329
+ aggregateRemove,
330
+ aggregateReplace,
331
+ ) => {
332
+ const metricAggregators = isFunction(aggregate)
333
+ ? [aggregate, aggregateAdd, aggregateRemove, aggregateReplace]
334
+ : mapGet(aggregators, aggregate) ?? mapGet(aggregators, SUM);
335
+ setDefinition(
336
+ metricId,
337
+ tableId,
338
+ (change, changedNumbers, _changedSortKeys, numbers, _sortKeys, force) => {
339
+ let newMetric = getMetric(metricId);
340
+ let length = collSize(numbers);
341
+ const [aggregate2, aggregateAdd2, aggregateRemove2, aggregateReplace2] =
342
+ metricAggregators;
343
+ force = force || isUndefined(newMetric);
344
+ collForEach(changedNumbers, ([oldNumber, newNumber]) => {
345
+ if (!force) {
346
+ newMetric = isUndefined(oldNumber)
347
+ ? aggregateAdd2?.(newMetric, newNumber, length++)
348
+ : isUndefined(newNumber)
349
+ ? aggregateRemove2?.(newMetric, oldNumber, length--)
350
+ : aggregateReplace2?.(newMetric, newNumber, oldNumber, length);
351
+ }
352
+ force = force || isUndefined(newMetric);
353
+ });
354
+ change();
355
+ if (collIsEmpty(numbers)) {
356
+ newMetric = void 0;
357
+ } else if (force) {
358
+ newMetric = aggregate2(collValues(numbers), collSize(numbers));
359
+ }
360
+ if (!isFiniteNumber(newMetric)) {
361
+ newMetric = void 0;
362
+ }
363
+ const oldMetric = getMetric(metricId);
364
+ if (newMetric != oldMetric) {
365
+ setMetric(metricId, newMetric);
366
+ callListeners(metricListeners, [metricId], newMetric, oldMetric);
367
+ }
368
+ },
369
+ getRowCellFunction(getNumber, 1),
370
+ );
371
+ return metrics;
372
+ };
373
+ const delMetricDefinition = (metricId) => {
374
+ delDefinition(metricId);
375
+ return metrics;
376
+ };
377
+ const addMetricListener = (metricId, listener) =>
378
+ addListener(listener, metricListeners, [metricId]);
379
+ const delListener = (listenerId) => {
380
+ delListenerImpl(listenerId);
381
+ return metrics;
382
+ };
383
+ const getListenerStats = () => ({metric: collSize2(metricListeners)});
384
+ const metrics = {
385
+ setMetricDefinition,
386
+ delMetricDefinition,
387
+ getStore,
388
+ getMetricIds,
389
+ forEachMetric,
390
+ hasMetric,
391
+ getTableId,
392
+ getMetric,
393
+ addMetricListener,
394
+ delListener,
395
+ destroy,
396
+ getListenerStats,
397
+ };
398
+ return objFreeze(metrics);
399
+ });
400
+
401
+ export {createMetrics};
@@ -0,0 +1,191 @@
1
+ import {promises, watch} from 'fs';
2
+
3
+ const UTF8 = 'utf8';
4
+
5
+ const isUndefined = (thing) => thing == void 0;
6
+ const ifNotUndefined = (value, then, otherwise) =>
7
+ isUndefined(value) ? otherwise?.() : then(value);
8
+
9
+ const object = Object;
10
+ const objFreeze = object.freeze;
11
+
12
+ const createCustomPersister = (
13
+ store,
14
+ getPersisted,
15
+ setPersisted,
16
+ startListeningToPersisted,
17
+ stopListeningToPersisted,
18
+ ) => {
19
+ let tablesListenerId;
20
+ let loadSave = 0;
21
+ let loads = 0;
22
+ let saves = 0;
23
+ const persister = {
24
+ load: async (initialTables) => {
25
+ /* istanbul ignore else */
26
+ if (loadSave != 2) {
27
+ loadSave = 1;
28
+ {
29
+ loads++;
30
+ }
31
+ const body = await getPersisted();
32
+ if (!isUndefined(body) && body != '') {
33
+ store.setJson(body);
34
+ } else {
35
+ store.setTables(initialTables);
36
+ }
37
+ loadSave = 0;
38
+ }
39
+ return persister;
40
+ },
41
+ startAutoLoad: async (initialTables) => {
42
+ persister.stopAutoLoad();
43
+ await persister.load(initialTables);
44
+ startListeningToPersisted(persister.load);
45
+ return persister;
46
+ },
47
+ stopAutoLoad: () => {
48
+ stopListeningToPersisted();
49
+ return persister;
50
+ },
51
+ save: async () => {
52
+ /* istanbul ignore else */
53
+ if (loadSave != 1) {
54
+ loadSave = 2;
55
+ {
56
+ saves++;
57
+ }
58
+ await setPersisted(store.getJson());
59
+ loadSave = 0;
60
+ }
61
+ return persister;
62
+ },
63
+ startAutoSave: async () => {
64
+ await persister.stopAutoSave().save();
65
+ tablesListenerId = store.addTablesListener(() => persister.save());
66
+ return persister;
67
+ },
68
+ stopAutoSave: () => {
69
+ ifNotUndefined(tablesListenerId, store.delListener);
70
+ return persister;
71
+ },
72
+ getStore: () => store,
73
+ destroy: () => persister.stopAutoLoad().stopAutoSave(),
74
+ getStats: () => ({loads, saves}),
75
+ };
76
+ return objFreeze(persister);
77
+ };
78
+
79
+ const STORAGE = 'storage';
80
+ const WINDOW = globalThis.window;
81
+ const getStoragePersister = (store, storageName, storage) => {
82
+ let listener;
83
+ const getPersisted = async () => storage.getItem(storageName);
84
+ const setPersisted = async (json) => storage.setItem(storageName, json);
85
+ const startListeningToPersisted = (didChange) => {
86
+ listener = (event) => {
87
+ if (event.storageArea === storage && event.key === storageName) {
88
+ didChange();
89
+ }
90
+ };
91
+ WINDOW.addEventListener(STORAGE, listener);
92
+ };
93
+ const stopListeningToPersisted = () => {
94
+ WINDOW.removeEventListener(STORAGE, listener);
95
+ listener = void 0;
96
+ };
97
+ return createCustomPersister(
98
+ store,
99
+ getPersisted,
100
+ setPersisted,
101
+ startListeningToPersisted,
102
+ stopListeningToPersisted,
103
+ );
104
+ };
105
+ const createLocalPersister = (store, storageName) =>
106
+ getStoragePersister(store, storageName, localStorage);
107
+ const createSessionPersister = (store, storageName) =>
108
+ getStoragePersister(store, storageName, sessionStorage);
109
+
110
+ const createFilePersister = (store, filePath) => {
111
+ let watcher;
112
+ const getPersisted = async () => {
113
+ try {
114
+ return await promises.readFile(filePath, UTF8);
115
+ } catch {}
116
+ };
117
+ const setPersisted = async (json) => {
118
+ try {
119
+ await promises.writeFile(filePath, json, UTF8);
120
+ } catch {}
121
+ };
122
+ const startListeningToPersisted = (didChange) => {
123
+ watcher = watch(filePath, didChange);
124
+ };
125
+ const stopListeningToPersisted = () => {
126
+ watcher?.close();
127
+ watcher = void 0;
128
+ };
129
+ return createCustomPersister(
130
+ store,
131
+ getPersisted,
132
+ setPersisted,
133
+ startListeningToPersisted,
134
+ stopListeningToPersisted,
135
+ );
136
+ };
137
+
138
+ const getETag = (response) => response.headers.get('ETag');
139
+ const createRemotePersister = (
140
+ store,
141
+ loadUrl,
142
+ saveUrl,
143
+ autoLoadIntervalSeconds,
144
+ ) => {
145
+ let interval;
146
+ let lastEtag;
147
+ const getPersisted = async () => {
148
+ const response = await fetch(loadUrl);
149
+ lastEtag = getETag(response);
150
+ return response.text();
151
+ };
152
+ const setPersisted = async (json) =>
153
+ await fetch(saveUrl, {
154
+ method: 'POST',
155
+ headers: {'Content-Type': 'application/json'},
156
+ body: json,
157
+ });
158
+ const startListeningToPersisted = (didChange) => {
159
+ interval = setInterval(async () => {
160
+ const response = await fetch(loadUrl, {method: 'HEAD'});
161
+ const currentEtag = getETag(response);
162
+ if (
163
+ !isUndefined(lastEtag) &&
164
+ !isUndefined(currentEtag) &&
165
+ currentEtag != lastEtag
166
+ ) {
167
+ lastEtag = currentEtag;
168
+ didChange();
169
+ }
170
+ }, autoLoadIntervalSeconds * 1e3);
171
+ };
172
+ const stopListeningToPersisted = () => {
173
+ ifNotUndefined(interval, clearInterval);
174
+ interval = void 0;
175
+ };
176
+ return createCustomPersister(
177
+ store,
178
+ getPersisted,
179
+ setPersisted,
180
+ startListeningToPersisted,
181
+ stopListeningToPersisted,
182
+ );
183
+ };
184
+
185
+ export {
186
+ createCustomPersister,
187
+ createFilePersister,
188
+ createLocalPersister,
189
+ createRemotePersister,
190
+ createSessionPersister,
191
+ };