tinybase 1.0.4 → 1.1.0-beta.2

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.
@@ -33,6 +33,20 @@ import {Store} from './store.d';
33
33
  */
34
34
  export type CheckpointIds = [Ids, Id | undefined, Ids];
35
35
 
36
+ /**
37
+ * The CheckpointCallback type describes a function that takes a Checkpoint's
38
+ * Id.
39
+ *
40
+ * A CheckpointCallback is provided when using the forEachCheckpoint method,
41
+ * so that you can do something based on every Checkpoint in the Checkpoints
42
+ * object. See that method for specific examples.
43
+ *
44
+ * @param checkpointId The Id of the Checkpoint that the callback can operate
45
+ * on.
46
+ * @category Callback
47
+ */
48
+ export type CheckpointCallback = (checkpointId: Id, label?: string) => void;
49
+
36
50
  /**
37
51
  * The CheckpointIdsListener type describes a function that is used to listen to
38
52
  * changes to the checkpoint Ids in a Checkpoints object.
@@ -353,6 +367,36 @@ export interface Checkpoints {
353
367
  */
354
368
  getCheckpointIds(): CheckpointIds;
355
369
 
370
+ /**
371
+ * The forEachCheckpoint method takes a function that it will then call for
372
+ * each Checkpoint in a specified Checkpoints object.
373
+ *
374
+ * This method is useful for iterating over the structure of the Checkpoints
375
+ * object in a functional style. The `checkpointCallback` parameter is a
376
+ * CheckpointCallback function that will be called with the Id of each
377
+ * Checkpoint.
378
+ *
379
+ * @param checkpointCallback The function that should be called for every
380
+ * Checkpoint.
381
+ * @example
382
+ * This example iterates over each Checkpoint in a Checkpoints object.
383
+ *
384
+ * ```js
385
+ * const store = createStore().setTables({pets: {fido: {sold: false}}});
386
+ * const checkpoints = createCheckpoints(store);
387
+ * store.setCell('pets', 'fido', 'sold', true);
388
+ * checkpoints.addCheckpoint('sale');
389
+ *
390
+ * checkpoints.forEachCheckpoint((checkpointId, label) => {
391
+ * console.log(`${checkpointId}:${label}`);
392
+ * });
393
+ * // -> '0:'
394
+ * // -> '1:sale'
395
+ * ```
396
+ * @category Iterator
397
+ */
398
+ forEachCheckpoint(checkpointCallback: CheckpointCallback): void;
399
+
356
400
  /**
357
401
  * The hasCheckpoint method returns a boolean indicating whether a given
358
402
  * Checkpoint exists in the Checkpoints object.
@@ -1 +1 @@
1
- const e=(e,t)=>e.includes(t),t=(e,t)=>e.forEach(t),n=e=>e.length,s=e=>0==n(e),o=e=>e.slice(1),r=e=>null==e,l=(e,t,n)=>r(e)?n?.():t(e),c=(e,t)=>e?.has(t)??!1,i=e=>r(e)||0==(e=>e.size)(e),d=(e,t)=>e?.forEach(t),a=(e,t)=>e?.delete(t),p=e=>new Map(e),h=(e,t)=>e?.get(t),u=(e,t,n)=>r(n)?(a(e,t),e):e?.set(t,n),g=(e,t,n,s)=>(c(e,t)||(s?.(n),e.set(t,n)),h(e,t)),C=e=>new Set(e),k=(e,t,r)=>n(r)<2?((e,t)=>e?.add(t))(s(r)?e:g(e,r[0],C()),t):k(g(e,r[0],p()),t,o(r)),f=e=>{const n=(r,c,...i)=>l(r,(r=>s(i)?e(r,c):t([i[0],null],(e=>n(h(r,e),c,...o(i))))));return n},L=Object.freeze,w=(e=>{const t=new WeakMap;return n=>(t.has(n)||t.set(n,e(n)),t.get(n))})((o=>{let w,v,S,z=100,E=p(),I=1;const M=C(),b=p(),[j,x,y]=(e=>{let s,o=0;const c=[],i=p();return[(t,n,r=[])=>{s??=e();const l=c.pop()??""+o++;return u(i,l,[t,n,r]),k(n,l,r),l},(e,t=[],...n)=>f(d)(e,(e=>l(h(i,e),(([e])=>e(s,...t,...n)))),...t),e=>l(h(i,e),(([,t,s])=>(f(a)(t,e,...s),u(i,e),n(c)<1e3&&c.push(e),s)),(()=>[])),(e,o,c)=>l(h(i,e),(([e,,l])=>{const i=(...d)=>{const a=n(d);a==n(l)?e(s,...d,...c(d)):r(l[a])?t(o[a](...d),(e=>i(...d,e))):i(...d,l[a])};i()}))]})((()=>R)),B=p(),F=p(),O=[],T=[],W=(e,t)=>{I=0,o.transaction((()=>d(h(B,t),((t,n)=>d(t,((t,s)=>d(t,((t,l)=>r(t[e])?o.delCell(n,s,l,!0):o.setCell(n,s,l,t[e]))))))))),I=1},m=e=>{u(B,e),u(F,e),x(b,[e])},q=(e,s)=>t(((e,t)=>e.splice(0,t))(e,s??n(e)),m),A=()=>q(O,n(O)-z),D=o.addCellListener(null,null,null,((e,t,n,s,o,r)=>{if(I){l(w,(()=>{O.push(w),A(),q(T),w=void 0,S=1}));const e=g(E,t,p()),c=g(e,n,p()),d=g(c,s,[void 0,void 0],(e=>e[0]=r));d[1]=o,d[0]===d[1]&&i(u(c,s))&&i(u(e,n))&&i(u(E,t))&&(w=O.pop(),S=1),K()}})),G=(e="")=>(r(w)&&(w=""+v++,u(B,w,E),P(w,e),E=p(),S=1),w),H=()=>{s(O)||(T.unshift(G()),W(0,w),w=O.pop(),S=1)},J=()=>{s(T)||(O.push(w),w=T.shift(),W(1,w),S=1)},K=()=>{S&&(x(M),S=0)},N=e=>{const t=G(e);return K(),t},P=(e,t)=>(Q(e)&&h(F,e)!==t&&(u(F,e,t),x(b,[e])),R),Q=e=>c(B,e),R={setSize:e=>(z=e,A(),R),addCheckpoint:N,setCheckpoint:P,getStore:()=>o,getCheckpointIds:()=>[[...O],w,[...T]],hasCheckpoint:Q,getCheckpoint:e=>h(F,e),goBackward:()=>(H(),K(),R),goForward:()=>(J(),K(),R),goTo:t=>{const n=e(O,t)?H:e(T,t)?J:null;for(;!r(n)&&t!=w;)n();return K(),R},addCheckpointIdsListener:e=>j(e,M),addCheckpointListener:(e,t)=>j(t,b,[e]),delListener:e=>(y(e),R),clear:()=>(q(O),q(T),r(w)||m(w),w=void 0,v=0,N(),R),destroy:()=>{o.delListener(D)},getListenerStats:()=>({})};return L(R.clear())}));export{w as createCheckpoints};
1
+ const e=(e,t)=>e.includes(t),t=(e,t)=>e.forEach(t),n=e=>e.length,s=e=>0==n(e),o=e=>e.slice(1),r=(e,t)=>e.push(t),c=e=>e.pop(),l=e=>null==e,i=(e,t,n)=>l(e)?n?.():t(e),d=(e,t)=>e?.has(t)??!1,a=e=>l(e)||0==(e=>e.size)(e),h=(e,t)=>e?.forEach(t),u=(e,t)=>e?.delete(t),p=e=>new Map(e),C=(e,t)=>e?.get(t),g=(e,t,n)=>l(n)?(u(e,t),e):e?.set(t,n),k=(e,t,n,s)=>(d(e,t)||(s?.(n),e.set(t,n)),C(e,t)),f=e=>new Set(e),L=(e,t,r)=>n(r)<2?((e,t)=>e?.add(t))(s(r)?e:k(e,r[0],f()),t):L(k(e,r[0],p()),t,o(r)),v=e=>{const n=(r,c,...l)=>i(r,(r=>s(l)?e(r,c):t([l[0],null],(e=>n(C(r,e),c,...o(l))))));return n},w=Object.freeze,S=(e=>{const t=new WeakMap;return n=>(t.has(n)||t.set(n,e(n)),t.get(n))})((o=>{let S,z,E,I=100,M=p(),b=1;const j=f(),x=p(),[y,B,F]=(e=>{let s,o=0;const d=[],a=p();return[(t,n,r=[])=>{s??=e();const l=c(d)??""+o++;return g(a,l,[t,n,r]),L(n,l,r),l},(e,t=[],...n)=>v(h)(e,(e=>i(C(a,e),(([e])=>e(s,...t,...n)))),...t),e=>i(C(a,e),(([,t,s])=>(v(u)(t,e,...s),g(a,e),n(d)<1e3&&r(d,e),s)),(()=>[])),(e,o,r)=>i(C(a,e),(([e,,c])=>{const i=(...d)=>{const a=n(d);a==n(c)?e(s,...d,...r(d)):l(c[a])?t(o[a](...d),(e=>i(...d,e))):i(...d,c[a])};i()}))]})((()=>V)),O=p(),T=p(),W=[],m=[],q=(e,t)=>{b=0,o.transaction((()=>h(C(O,t),((t,n)=>h(t,((t,s)=>h(t,((t,r)=>l(t[e])?o.delCell(n,s,r,!0):o.setCell(n,s,r,t[e]))))))))),b=1},A=e=>{g(O,e),g(T,e),B(x,[e])},D=(e,s)=>t(((e,t)=>e.splice(0,t))(e,s??n(e)),A),G=()=>D(W,n(W)-I),H=o.addCellListener(null,null,null,((e,t,n,s,o,l)=>{if(b){i(S,(()=>{r(W,S),G(),D(m),S=void 0,E=1}));const e=k(M,t,p()),d=k(e,n,p()),h=k(d,s,[void 0,void 0],(e=>e[0]=l));h[1]=o,h[0]===h[1]&&a(g(d,s))&&a(g(e,n))&&a(g(M,t))&&(S=c(W),E=1),P()}})),J=(e="")=>(l(S)&&(S=""+z++,g(O,S,M),R(S,e),M=p(),E=1),S),K=()=>{s(W)||(m.unshift(J()),q(0,S),S=c(W),E=1)},N=()=>{s(m)||(r(W,S),S=m.shift(),q(1,S),E=1)},P=()=>{E&&(B(j),E=0)},Q=e=>{const t=J(e);return P(),t},R=(e,t)=>(U(e)&&C(T,e)!==t&&(g(T,e,t),B(x,[e])),V),U=e=>d(O,e),V={setSize:e=>(I=e,G(),V),addCheckpoint:Q,setCheckpoint:R,getStore:()=>o,getCheckpointIds:()=>[[...W],S,[...m]],forEachCheckpoint:e=>{return t=e,h(T,((e,n)=>t(n,e)));var t},hasCheckpoint:U,getCheckpoint:e=>C(T,e),goBackward:()=>(K(),P(),V),goForward:()=>(N(),P(),V),goTo:t=>{const n=e(W,t)?K:e(m,t)?N:null;for(;!l(n)&&t!=S;)n();return P(),V},addCheckpointIdsListener:e=>y(e,j),addCheckpointListener:(e,t)=>y(t,x,[e]),delListener:e=>(F(e),V),clear:()=>(D(W),D(m),l(S)||A(S),S=void 0,z=0,Q(),V),destroy:()=>{o.delListener(H)},getListenerStats:()=>({})};return w(V.clear())}));export{S as createCheckpoints};
Binary file
@@ -33,6 +33,20 @@ import {Store} from './store.d';
33
33
  */
34
34
  export type CheckpointIds = [Ids, Id | undefined, Ids];
35
35
 
36
+ /**
37
+ * The CheckpointCallback type describes a function that takes a Checkpoint's
38
+ * Id.
39
+ *
40
+ * A CheckpointCallback is provided when using the forEachCheckpoint method,
41
+ * so that you can do something based on every Checkpoint in the Checkpoints
42
+ * object. See that method for specific examples.
43
+ *
44
+ * @param checkpointId The Id of the Checkpoint that the callback can operate
45
+ * on.
46
+ * @category Callback
47
+ */
48
+ export type CheckpointCallback = (checkpointId: Id, label?: string) => void;
49
+
36
50
  /**
37
51
  * The CheckpointIdsListener type describes a function that is used to listen to
38
52
  * changes to the checkpoint Ids in a Checkpoints object.
@@ -353,6 +367,36 @@ export interface Checkpoints {
353
367
  */
354
368
  getCheckpointIds(): CheckpointIds;
355
369
 
370
+ /**
371
+ * The forEachCheckpoint method takes a function that it will then call for
372
+ * each Checkpoint in a specified Checkpoints object.
373
+ *
374
+ * This method is useful for iterating over the structure of the Checkpoints
375
+ * object in a functional style. The `checkpointCallback` parameter is a
376
+ * CheckpointCallback function that will be called with the Id of each
377
+ * Checkpoint.
378
+ *
379
+ * @param checkpointCallback The function that should be called for every
380
+ * Checkpoint.
381
+ * @example
382
+ * This example iterates over each Checkpoint in a Checkpoints object.
383
+ *
384
+ * ```js
385
+ * const store = createStore().setTables({pets: {fido: {sold: false}}});
386
+ * const checkpoints = createCheckpoints(store);
387
+ * store.setCell('pets', 'fido', 'sold', true);
388
+ * checkpoints.addCheckpoint('sale');
389
+ *
390
+ * checkpoints.forEachCheckpoint((checkpointId, label) => {
391
+ * console.log(`${checkpointId}:${label}`);
392
+ * });
393
+ * // -> '0:'
394
+ * // -> '1:sale'
395
+ * ```
396
+ * @category Iterator
397
+ */
398
+ forEachCheckpoint(checkpointCallback: CheckpointCallback): void;
399
+
356
400
  /**
357
401
  * The hasCheckpoint method returns a boolean indicating whether a given
358
402
  * Checkpoint exists in the Checkpoints object.
@@ -5,6 +5,8 @@ const arrayIsEmpty = (array) => arrayLength(array) == 0;
5
5
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
6
6
  const arrayFromSecond = (ids) => ids.slice(1);
7
7
  const arrayClear = (array, to) => array.splice(0, to);
8
+ const arrayPush = (array, value) => array.push(value);
9
+ const arrayPop = (array) => array.pop();
8
10
 
9
11
  const isUndefined = (thing) => thing == void 0;
10
12
  const ifNotUndefined = (value, then, otherwise) =>
@@ -22,6 +24,8 @@ const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);
22
24
 
23
25
  const mapNew = (entries) => new Map(entries);
24
26
  const mapGet = (map, key) => map?.get(key);
27
+ const mapForEach = (map, cb) =>
28
+ collForEach(map, (value, key) => cb(key, value));
25
29
  const mapSet = (map, key, value) =>
26
30
  isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
27
31
  const mapEnsure = (map, key, defaultValue, onWillAdd) => {
@@ -74,7 +78,7 @@ const getListenerFunctions = (getThing) => {
74
78
  const allListeners = mapNew();
75
79
  const addListener = (listener, deepSet, idOrNulls = []) => {
76
80
  thing ??= getThing();
77
- const id = listenerPool.pop() ?? '' + nextId++;
81
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
78
82
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
79
83
  addDeepSet(deepSet, id, idOrNulls);
80
84
  return id;
@@ -95,7 +99,7 @@ const getListenerFunctions = (getThing) => {
95
99
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
96
100
  mapSet(allListeners, id);
97
101
  if (arrayLength(listenerPool) < 1e3) {
98
- listenerPool.push(id);
102
+ arrayPush(listenerPool, id);
99
103
  }
100
104
  return idOrNulls;
101
105
  },
@@ -171,7 +175,7 @@ const createCheckpoints = getCreateFunction((store) => {
171
175
  (_store, tableId, rowId, cellId, newCell, oldCell) => {
172
176
  if (listening) {
173
177
  ifNotUndefined(currentId, () => {
174
- backwardIds.push(currentId);
178
+ arrayPush(backwardIds, currentId);
175
179
  trimBackwardsIds();
176
180
  clearCheckpointIds(forwardIds);
177
181
  currentId = void 0;
@@ -190,7 +194,7 @@ const createCheckpoints = getCreateFunction((store) => {
190
194
  if (collIsEmpty(mapSet(row, cellId))) {
191
195
  if (collIsEmpty(mapSet(table, rowId))) {
192
196
  if (collIsEmpty(mapSet(delta, tableId))) {
193
- currentId = backwardIds.pop();
197
+ currentId = arrayPop(backwardIds);
194
198
  checkpointsChanged = 1;
195
199
  }
196
200
  }
@@ -214,13 +218,13 @@ const createCheckpoints = getCreateFunction((store) => {
214
218
  if (!arrayIsEmpty(backwardIds)) {
215
219
  forwardIds.unshift(addCheckpointImpl());
216
220
  updateStore(0, currentId);
217
- currentId = backwardIds.pop();
221
+ currentId = arrayPop(backwardIds);
218
222
  checkpointsChanged = 1;
219
223
  }
220
224
  };
221
225
  const goForwardImpl = () => {
222
226
  if (!arrayIsEmpty(forwardIds)) {
223
- backwardIds.push(currentId);
227
+ arrayPush(backwardIds, currentId);
224
228
  currentId = forwardIds.shift();
225
229
  updateStore(1, currentId);
226
230
  checkpointsChanged = 1;
@@ -251,6 +255,8 @@ const createCheckpoints = getCreateFunction((store) => {
251
255
  };
252
256
  const getStore = () => store;
253
257
  const getCheckpointIds = () => [[...backwardIds], currentId, [...forwardIds]];
258
+ const forEachCheckpoint = (checkpointCallback) =>
259
+ mapForEach(labels, checkpointCallback);
254
260
  const hasCheckpoint = (checkpointId) => collHas(deltas, checkpointId);
255
261
  const getCheckpoint = (checkpointId) => mapGet(labels, checkpointId);
256
262
  const goBackward = () => {
@@ -307,6 +313,7 @@ const createCheckpoints = getCreateFunction((store) => {
307
313
  setCheckpoint,
308
314
  getStore,
309
315
  getCheckpointIds,
316
+ forEachCheckpoint,
310
317
  hasCheckpoint,
311
318
  getCheckpoint,
312
319
  goBackward,
@@ -11,7 +11,7 @@
11
11
  * @module indexes
12
12
  */
13
13
 
14
- import {GetCell, Store} from './store.d';
14
+ import {GetCell, RowCallback, Store} from './store.d';
15
15
  import {Id, IdOrNull, Ids, SortKey} from './common.d';
16
16
 
17
17
  /**
@@ -43,6 +43,42 @@ export type Index = {[sliceId: Id]: Slice};
43
43
  */
44
44
  export type Slice = Ids;
45
45
 
46
+ /**
47
+ * The IndexCallback type describes a function that takes an Index's Id and a
48
+ * callback to loop over each Slice within it.
49
+ *
50
+ * A IndexCallback is provided when using the forEachIndex method, so that you
51
+ * can do something based on every Index in the Indexes object. See that method
52
+ * for specific examples.
53
+ *
54
+ * @param indexId The Id of the Index that the callback can operate on.
55
+ * @param forEachRow A function that will let you iterate over the Slice objects
56
+ * in this Index.
57
+ * @category Callback
58
+ */
59
+ export type IndexCallback = (
60
+ indexId: Id,
61
+ forEachSlice: (sliceCallback: SliceCallback) => void,
62
+ ) => void;
63
+
64
+ /**
65
+ * The SliceCallback type describes a function that takes a Slice's Id and a
66
+ * callback to loop over each Row within it.
67
+ *
68
+ * A SliceCallback is provided when using the forEachSlice method, so that you
69
+ * can do something based on every Slice in an Index. See that method for
70
+ * specific examples.
71
+ *
72
+ * @param sliceId The Id of the Slice that the callback can operate on.
73
+ * @param forEachRow A function that will let you iterate over the Row objects
74
+ * in this Slice.
75
+ * @category Callback
76
+ */
77
+ export type SliceCallback = (
78
+ sliceId: Id,
79
+ forEachRow: (rowCallback: RowCallback) => void,
80
+ ) => void;
81
+
46
82
  /**
47
83
  * The SliceIdsListener type describes a function that is used to listen to
48
84
  * changes to the Slice Ids in an Index.
@@ -362,6 +398,83 @@ export interface Indexes {
362
398
  */
363
399
  getIndexIds(): Ids;
364
400
 
401
+ /**
402
+ * The forEachIndex method takes a function that it will then call for each
403
+ * Index in a specified Indexes object.
404
+ *
405
+ * This method is useful for iterating over the structure of the Indexes
406
+ * object in a functional style. The `indexCallback` parameter is a
407
+ * IndexCallback function that will be called with the Id of each Index, and
408
+ * with a function that can then be used to iterate over each Slice of the
409
+ * Index, should you wish.
410
+ *
411
+ * @param indexCallback The function that should be called for every Index.
412
+ * @example
413
+ * This example iterates over each Index in an Indexes object, and lists each
414
+ * Slice Id within them.
415
+ *
416
+ * ```js
417
+ * const store = createStore().setTable('pets', {
418
+ * fido: {species: 'dog', color: 'brown'},
419
+ * felix: {species: 'cat', color: 'black'},
420
+ * cujo: {species: 'dog', color: 'black'},
421
+ * });
422
+ * const indexes = createIndexes(store)
423
+ * .setIndexDefinition('bySpecies', 'pets', 'species')
424
+ * .setIndexDefinition('byColor', 'pets', 'color');
425
+ *
426
+ * indexes.forEachIndex((indexId, forEachSlice) => {
427
+ * console.log(indexId);
428
+ * forEachSlice((sliceId) => console.log(`- ${sliceId}`));
429
+ * });
430
+ * // -> 'bySpecies'
431
+ * // -> '- dog'
432
+ * // -> '- cat'
433
+ * // -> 'byColor'
434
+ * // -> '- brown'
435
+ * // -> '- black'
436
+ * ```
437
+ * @category Iterator
438
+ */
439
+ forEachIndex(indexCallback: IndexCallback): void;
440
+
441
+ /**
442
+ * The forEachSlice method takes a function that it will then call for each
443
+ * Slice in a specified Index.
444
+ *
445
+ * This method is useful for iterating over the Slice structure of the Index
446
+ * in a functional style. The `rowCallback` parameter is a RowCallback
447
+ * function that will be called with the Id and value of each Row in the
448
+ * Slice.
449
+ *
450
+ * @param indexId The Id of the Index to iterate over.
451
+ * @param sliceCallback The function that should be called for every Slice.
452
+ * @example
453
+ * This example iterates over each Row in a Slice, and lists its Id.
454
+ *
455
+ * ```js
456
+ * const store = createStore().setTable('pets', {
457
+ * fido: {species: 'dog'},
458
+ * felix: {species: 'cat'},
459
+ * cujo: {species: 'dog'},
460
+ * });
461
+ * const indexes = createIndexes(store);
462
+ * indexes.setIndexDefinition('bySpecies', 'pets', 'species');
463
+ *
464
+ * indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
465
+ * console.log(sliceId);
466
+ * forEachRow((rowId) => console.log(`- ${rowId}`));
467
+ * });
468
+ * // -> 'dog'
469
+ * // -> '- fido'
470
+ * // -> '- cujo'
471
+ * // -> 'cat'
472
+ * // -> '- felix'
473
+ * ```
474
+ * @category Iterator
475
+ */
476
+ forEachSlice(indexId: Id, sliceCallback: SliceCallback): void;
477
+
365
478
  /**
366
479
  * The hasIndex method returns a boolean indicating whether a given Index
367
480
  * exists in the Indexes object.
@@ -12,6 +12,8 @@ const arrayLength = (array) => array.length;
12
12
  const arrayIsEmpty = (array) => arrayLength(array) == 0;
13
13
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
14
14
  const arrayFromSecond = (ids) => ids.slice(1);
15
+ const arrayPush = (array, value) => array.push(value);
16
+ const arrayPop = (array) => array.pop();
15
17
 
16
18
  const isUndefined = (thing) => thing == void 0;
17
19
  const ifNotUndefined = (value, then, otherwise) =>
@@ -57,6 +59,7 @@ const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
57
59
  const storeListenerIds = mapNew();
58
60
  const getStore = () => store;
59
61
  const getThingIds = () => mapKeys(tableIds);
62
+ const forEachThing = (cb) => mapForEach(things, cb);
60
63
  const hasThing = (id) => collHas(things, id);
61
64
  const getTableId = (id) => mapGet(tableIds, id);
62
65
  const getThing = (id) => mapGet(things, id);
@@ -147,6 +150,7 @@ const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
147
150
  return [
148
151
  getStore,
149
152
  getThingIds,
153
+ forEachThing,
150
154
  hasThing,
151
155
  getTableId,
152
156
  getThing,
@@ -201,7 +205,7 @@ const getListenerFunctions = (getThing) => {
201
205
  const allListeners = mapNew();
202
206
  const addListener = (listener, deepSet, idOrNulls = []) => {
203
207
  thing ??= getThing();
204
- const id = listenerPool.pop() ?? '' + nextId++;
208
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
205
209
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
206
210
  addDeepSet(deepSet, id, idOrNulls);
207
211
  return id;
@@ -222,7 +226,7 @@ const getListenerFunctions = (getThing) => {
222
226
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
223
227
  mapSet(allListeners, id);
224
228
  if (arrayLength(listenerPool) < 1e3) {
225
- listenerPool.push(id);
229
+ arrayPush(listenerPool, id);
226
230
  }
227
231
  return idOrNulls;
228
232
  },
@@ -254,6 +258,7 @@ const createIndexes = getCreateFunction((store) => {
254
258
  const [
255
259
  getStore,
256
260
  getIndexIds,
261
+ forEachIndexImpl,
257
262
  hasIndex,
258
263
  getTableId,
259
264
  getIndex,
@@ -363,6 +368,26 @@ const createIndexes = getCreateFunction((store) => {
363
368
  );
364
369
  return indexes;
365
370
  };
371
+ const forEachIndex = (indexCallback) =>
372
+ forEachIndexImpl((indexId, slices) =>
373
+ indexCallback(indexId, (sliceCallback) =>
374
+ forEachSliceImpl(indexId, sliceCallback, slices),
375
+ ),
376
+ );
377
+ const forEachSlice = (indexId, sliceCallback) =>
378
+ forEachSliceImpl(indexId, sliceCallback, getIndex(indexId));
379
+ const forEachSliceImpl = (indexId, sliceCallback, slices) => {
380
+ const tableId = getTableId(indexId);
381
+ collForEach(slices, (rowIds, sliceId) =>
382
+ sliceCallback(sliceId, (rowCallback) =>
383
+ collForEach(rowIds, (rowId) =>
384
+ rowCallback(rowId, (cellCallback) =>
385
+ store.forEachCell(tableId, rowId, cellCallback),
386
+ ),
387
+ ),
388
+ ),
389
+ );
390
+ };
366
391
  const delIndexDefinition = (indexId) => {
367
392
  delDefinition(indexId);
368
393
  return indexes;
@@ -387,6 +412,8 @@ const createIndexes = getCreateFunction((store) => {
387
412
  delIndexDefinition,
388
413
  getStore,
389
414
  getIndexIds,
415
+ forEachIndex,
416
+ forEachSlice,
390
417
  hasIndex,
391
418
  hasSlice,
392
419
  getTableId,
@@ -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,38 @@ 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
+
481
527
  /**
482
528
  * The hasMetric method returns a boolean indicating whether a given Metric
483
529
  * exists in the Metrics object, and has a value.
@@ -13,6 +13,8 @@ const arrayLength = (array) => array.length;
13
13
  const arrayIsEmpty = (array) => arrayLength(array) == 0;
14
14
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
15
15
  const arrayFromSecond = (ids) => ids.slice(1);
16
+ const arrayPush = (array, value) => array.push(value);
17
+ const arrayPop = (array) => array.pop();
16
18
 
17
19
  const mathMax = Math.max;
18
20
  const mathMin = Math.min;
@@ -62,6 +64,7 @@ const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
62
64
  const storeListenerIds = mapNew();
63
65
  const getStore = () => store;
64
66
  const getThingIds = () => mapKeys(tableIds);
67
+ const forEachThing = (cb) => mapForEach(things, cb);
65
68
  const hasThing = (id) => collHas(things, id);
66
69
  const getTableId = (id) => mapGet(tableIds, id);
67
70
  const getThing = (id) => mapGet(things, id);
@@ -152,6 +155,7 @@ const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
152
155
  return [
153
156
  getStore,
154
157
  getThingIds,
158
+ forEachThing,
155
159
  hasThing,
156
160
  getTableId,
157
161
  getThing,
@@ -204,7 +208,7 @@ const getListenerFunctions = (getThing) => {
204
208
  const allListeners = mapNew();
205
209
  const addListener = (listener, deepSet, idOrNulls = []) => {
206
210
  thing ??= getThing();
207
- const id = listenerPool.pop() ?? '' + nextId++;
211
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
208
212
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
209
213
  addDeepSet(deepSet, id, idOrNulls);
210
214
  return id;
@@ -225,7 +229,7 @@ const getListenerFunctions = (getThing) => {
225
229
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
226
230
  mapSet(allListeners, id);
227
231
  if (arrayLength(listenerPool) < 1e3) {
228
- listenerPool.push(id);
232
+ arrayPush(listenerPool, id);
229
233
  }
230
234
  return idOrNulls;
231
235
  },
@@ -296,6 +300,7 @@ const createMetrics = getCreateFunction((store) => {
296
300
  const [
297
301
  getStore,
298
302
  getMetricIds,
303
+ forEachMetric,
299
304
  hasMetric,
300
305
  getTableId,
301
306
  getMetric,
@@ -381,6 +386,7 @@ const createMetrics = getCreateFunction((store) => {
381
386
  delMetricDefinition,
382
387
  getStore,
383
388
  getMetricIds,
389
+ forEachMetric,
384
390
  hasMetric,
385
391
  getTableId,
386
392
  getMetric,
@@ -11,7 +11,7 @@
11
11
  * @module relationships
12
12
  */
13
13
 
14
- import {GetCell, Store} from './store.d';
14
+ import {GetCell, RowCallback, Store} from './store.d';
15
15
  import {Id, IdOrNull, Ids} from './common.d';
16
16
 
17
17
  /**
@@ -39,6 +39,25 @@ export type Relationship = {
39
39
  linkedRowIds: {[firstRowId: Id]: Ids};
40
40
  };
41
41
 
42
+ /**
43
+ * The RelationshipCallback type describes a function that takes a
44
+ * Relationship's Id and a callback to loop over each local Row within it.
45
+ *
46
+ * A RelationshipCallback is provided when using the forEachRelationship method,
47
+ * so that you can do something based on every Relationship in the Relationships
48
+ * object. See that method for specific examples.
49
+ *
50
+ * @param relationshipId The Id of the Relationship that the callback can
51
+ * operate on.
52
+ * @param forEachRow A function that will let you iterate over the local Row
53
+ * objects in this Relationship.
54
+ * @category Callback
55
+ */
56
+ export type RelationshipCallback = (
57
+ relationshipId: Id,
58
+ forEachRow: (rowCallback: RowCallback) => void,
59
+ ) => void;
60
+
42
61
  /**
43
62
  * The RemoteRowIdListener type describes a function that is used to listen to
44
63
  * changes to the remote Row Id end of a Relationship.
@@ -405,6 +424,49 @@ export interface Relationships {
405
424
  */
406
425
  getRelationshipIds(): Ids;
407
426
 
427
+ /**
428
+ * The forEachRelationship method takes a function that it will then call for
429
+ * each Relationship in a specified Relationships object.
430
+ *
431
+ * This method is useful for iterating over the structure of the Relationships
432
+ * object in a functional style. The `relationshipCallback` parameter is a
433
+ * RelationshipCallback function that will be called with the Id of each
434
+ * Relationship, and with a function that can then be used to iterate over
435
+ * each local Row involved in the Relationship.
436
+ *
437
+ * @param relationshipCallback The function that should be called for every
438
+ * Relationship.
439
+ * @example
440
+ * This example iterates over each Relationship in a Relationships object, and
441
+ * lists each Row Id within them.
442
+ *
443
+ * ```js
444
+ * const store = createStore().setTable('pets', {
445
+ * fido: {species: 'dog', next: 'felix'},
446
+ * felix: {species: 'cat', next: 'cujo'},
447
+ * cujo: {species: 'dog'},
448
+ * });
449
+ * const relationships = createRelationships(store)
450
+ * .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
451
+ * .setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
452
+ *
453
+ * relationships.forEachRelationship((relationshipId, forEachRow) => {
454
+ * console.log(relationshipId);
455
+ * forEachRow((rowId) => console.log(`- ${rowId}`));
456
+ * });
457
+ * // -> 'petSpecies'
458
+ * // -> '- fido'
459
+ * // -> '- felix'
460
+ * // -> '- cujo'
461
+ * // -> 'petSequence'
462
+ * // -> '- fido'
463
+ * // -> '- felix'
464
+ * // -> '- cujo'
465
+ * ```
466
+ * @category Iterator
467
+ */
468
+ forEachRelationship(relationshipCallback: RelationshipCallback): void;
469
+
408
470
  /**
409
471
  * The hasRelationship method returns a boolean indicating whether a given
410
472
  * Relationship exists in the Relationships object.