Skip to content

Commit c2b7aa3

Browse files
committed
[indexes] Add forEachIndex and forEachSlice methods
1 parent f6b3b50 commit c2b7aa3

File tree

4 files changed

+210
-8
lines changed

4 files changed

+210
-8
lines changed

src/indexes.d.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* @module indexes
1212
*/
1313

14-
import {GetCell, Store} from './store.d';
14+
import {GetCell, RowCallback, Store} from './store.d';
1515
import {Id, IdOrNull, Ids, SortKey} from './common.d';
1616

1717
/**
@@ -43,6 +43,42 @@ export type Index = {[sliceId: Id]: Slice};
4343
*/
4444
export type Slice = Ids;
4545

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+
4682
/**
4783
* The SliceIdsListener type describes a function that is used to listen to
4884
* changes to the Slice Ids in an Index.
@@ -362,6 +398,82 @@ export interface Indexes {
362398
*/
363399
getIndexIds(): Ids;
364400

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 called with the Id of each Index, and with
408+
* a function that can then be used to iterate over each Slice of the Index,
409+
* 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 a 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 called with the Id and value of each Row in the Slice.
448+
*
449+
* @param indexId The Id of the Index to iterate over.
450+
* @param sliceCallback The function that should be called for every Slice.
451+
* @example
452+
* This example iterates over each Row in a Slice, and lists its Id.
453+
*
454+
* ```js
455+
* const store = createStore().setTable('pets', {
456+
* fido: {species: 'dog'},
457+
* felix: {species: 'cat'},
458+
* cujo: {species: 'dog'},
459+
* });
460+
* const indexes = createIndexes(store);
461+
* indexes.setIndexDefinition('bySpecies', 'pets', 'species');
462+
*
463+
* indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
464+
* console.log(sliceId);
465+
* forEachRow((rowId) => console.log(`- ${rowId}`));
466+
* });
467+
* // -> 'dog'
468+
* // -> '- fido'
469+
* // -> '- cujo'
470+
* // -> 'cat'
471+
* // -> '- felix'
472+
* ```
473+
* @category Iterator
474+
*/
475+
forEachSlice(indexId: Id, sliceCallback: SliceCallback): void;
476+
365477
/**
366478
* The hasIndex method returns a boolean indicating whether a given Index
367479
* exists in the Indexes object.

src/indexes.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {Id, IdOrNull, Ids, SortKey} from './common.d';
44
import {IdMap, mapForEach, mapGet, mapKeys, mapNew, mapSet} from './common/map';
55
import {IdSet, IdSet2, IdSet3, setAdd, setNew} from './common/set';
66
import {
7+
IndexCallback,
78
Indexes,
89
IndexesListenerStats,
10+
SliceCallback,
911
SliceIdsListener,
1012
SliceRowIdsListener,
1113
createIndexes as createIndexesDecl,
@@ -37,7 +39,7 @@ export const createIndexes: typeof createIndexesDecl = getCreateFunction(
3739
const [
3840
getStore,
3941
getIndexIds,
40-
_forEachIndex,
42+
forEachIndexImpl,
4143
hasIndex,
4244
getTableId,
4345
getIndex,
@@ -178,6 +180,33 @@ export const createIndexes: typeof createIndexesDecl = getCreateFunction(
178180
return indexes;
179181
};
180182

183+
const forEachIndex = (indexCallback: IndexCallback) =>
184+
forEachIndexImpl((indexId, slices) =>
185+
indexCallback(indexId, (sliceCallback) =>
186+
forEachSliceImpl(indexId, sliceCallback, slices),
187+
),
188+
);
189+
190+
const forEachSlice = (indexId: Id, sliceCallback: SliceCallback) =>
191+
forEachSliceImpl(indexId, sliceCallback, getIndex(indexId) as IdSet2);
192+
193+
const forEachSliceImpl = (
194+
indexId: Id,
195+
sliceCallback: SliceCallback,
196+
slices: IdSet2,
197+
) => {
198+
const tableId = getTableId(indexId);
199+
collForEach(slices, (rowIds, sliceId) =>
200+
sliceCallback(sliceId, (rowCallback) =>
201+
collForEach(rowIds, (rowId) =>
202+
rowCallback(rowId, (cellCallback) =>
203+
store.forEachCell(tableId, rowId, cellCallback),
204+
),
205+
),
206+
),
207+
);
208+
};
209+
181210
const delIndexDefinition = (indexId: Id): Indexes => {
182211
delDefinition(indexId);
183212
return indexes;
@@ -218,6 +247,8 @@ export const createIndexes: typeof createIndexesDecl = getCreateFunction(
218247

219248
getStore,
220249
getIndexIds,
250+
forEachIndex,
251+
forEachSlice,
221252
hasIndex,
222253
hasSlice,
223254
getTableId,

test/unit/common.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,17 @@ export const getMetricsObject = (
293293

294294
export const getIndexesObject = (indexes: Indexes): IdMap<IdMap<Ids>> => {
295295
const indexesObject: IdMap<IdMap<Ids>> = {};
296-
indexes.getIndexIds().forEach((indexId) => {
296+
indexes.forEachIndex((indexId) => {
297297
indexesObject[indexId] = {};
298-
indexes.getSliceIds(indexId).forEach((sliceId) => {
299-
indexesObject[indexId][sliceId] = indexes.getSliceRowIds(
300-
indexId,
301-
sliceId,
298+
indexes
299+
.getSliceIds(indexId)
300+
.forEach(
301+
(sliceId) =>
302+
(indexesObject[indexId][sliceId] = indexes.getSliceRowIds(
303+
indexId,
304+
sliceId,
305+
)),
302306
);
303-
});
304307
});
305308
return indexesObject;
306309
};

test/unit/indexes.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,62 @@ describe('Miscellaneous', () => {
15551555
expectNoChanges(listener);
15561556
});
15571557

1558+
describe('forEach', () => {
1559+
test('forEachIndex', () => {
1560+
indexes
1561+
.setIndexDefinition('i1', 't1')
1562+
.setIndexDefinition('i2', 't1', 'c2');
1563+
setCells();
1564+
const eachIndex: any = {};
1565+
indexes.forEachIndex((indexId, forEachSlice) => {
1566+
const eachSlice: any = {};
1567+
forEachSlice((sliceId, forEachRow) => {
1568+
const eachRow: any = {};
1569+
forEachRow((rowId, forEachCell) => {
1570+
const eachCell: any = {};
1571+
forEachCell((cellId, cell) => (eachCell[cellId] = cell));
1572+
eachRow[rowId] = eachCell;
1573+
});
1574+
eachSlice[sliceId] = eachRow;
1575+
});
1576+
eachIndex[indexId] = eachSlice;
1577+
});
1578+
expect(eachIndex).toEqual({
1579+
i1: {
1580+
'': {
1581+
r1: {c1: 'one', c2: 'odd'},
1582+
r2: {c1: 'two', c2: 'even'},
1583+
r3: {c1: 'three', c2: 'odd'},
1584+
r4: {c1: 'four', c2: 'even'},
1585+
},
1586+
},
1587+
i2: {
1588+
even: {r2: {c1: 'two', c2: 'even'}, r4: {c1: 'four', c2: 'even'}},
1589+
odd: {r1: {c1: 'one', c2: 'odd'}, r3: {c1: 'three', c2: 'odd'}},
1590+
},
1591+
});
1592+
});
1593+
1594+
test('forEachSlice', () => {
1595+
indexes.setIndexDefinition('i2', 't1', 'c2');
1596+
setCells();
1597+
const eachSlice: any = {};
1598+
indexes.forEachSlice('i2', (sliceId, forEachRow) => {
1599+
const eachRow: any = {};
1600+
forEachRow((rowId, forEachCell) => {
1601+
const eachCell: any = {};
1602+
forEachCell((cellId, cell) => (eachCell[cellId] = cell));
1603+
eachRow[rowId] = eachCell;
1604+
});
1605+
eachSlice[sliceId] = eachRow;
1606+
});
1607+
expect(eachSlice).toEqual({
1608+
even: {r2: {c1: 'two', c2: 'even'}, r4: {c1: 'four', c2: 'even'}},
1609+
odd: {r1: {c1: 'one', c2: 'odd'}, r3: {c1: 'three', c2: 'odd'}},
1610+
});
1611+
});
1612+
});
1613+
15581614
test('are things present', () => {
15591615
expect(indexes.hasIndex('i1')).toEqual(false);
15601616
indexes.setIndexDefinition('i1', 't1', 'c2');

0 commit comments

Comments
 (0)