Support tracking secondary indecies in DataSource
Reviewed By: LukeDefeo Differential Revision: D51026559 fbshipit-source-id: 1f8f40ceedf70dfdc8978e0d6e447a1a58f8f82a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
da5856138d
commit
701ae01501
@@ -46,12 +46,23 @@ type ShiftEvent<T> = {
|
||||
entries: Entry<T>[];
|
||||
amount: number;
|
||||
};
|
||||
type SINewIndexValueEvent<T> = {
|
||||
type: 'siNewIndexValue';
|
||||
indexKey: string;
|
||||
value: T;
|
||||
firstOfKind: boolean;
|
||||
};
|
||||
type ClearEvent = {
|
||||
type: 'clear';
|
||||
};
|
||||
|
||||
type DataEvent<T> =
|
||||
| AppendEvent<T>
|
||||
| UpdateEvent<T>
|
||||
| RemoveEvent<T>
|
||||
| ShiftEvent<T>;
|
||||
| ShiftEvent<T>
|
||||
| SINewIndexValueEvent<T>
|
||||
| ClearEvent;
|
||||
|
||||
type Entry<T> = {
|
||||
value: T;
|
||||
@@ -181,7 +192,7 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
[viewId: string]: DataSourceView<T, KeyType>;
|
||||
};
|
||||
|
||||
public readonly outputEventEmitter = new EventEmitter();
|
||||
private readonly outputEventEmitter = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
keyAttribute: keyof T | undefined,
|
||||
@@ -262,6 +273,10 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
};
|
||||
}
|
||||
|
||||
public secondaryIndicesKeys(): string[] {
|
||||
return [...this._secondaryIndices.keys()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of a specific key in the *records* set.
|
||||
* Returns -1 if the record wansn't found
|
||||
@@ -469,6 +484,7 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
this.shiftOffset = 0;
|
||||
this.idToIndex.clear();
|
||||
this.rebuild();
|
||||
this.emitDataEvent({type: 'clear'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,6 +538,16 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
}
|
||||
}
|
||||
|
||||
public addDataListener<E extends DataEvent<T>['type']>(
|
||||
event: E,
|
||||
cb: (data: Extract<DataEvent<T>, {type: E}>) => void,
|
||||
) {
|
||||
this.outputEventEmitter.addListener(event, cb);
|
||||
return () => {
|
||||
this.outputEventEmitter.removeListener(event, cb);
|
||||
};
|
||||
}
|
||||
|
||||
private assertKeySet() {
|
||||
if (!this.keyAttribute) {
|
||||
throw new Error(
|
||||
@@ -571,6 +597,12 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
} else {
|
||||
a.push(value);
|
||||
}
|
||||
this.emitDataEvent({
|
||||
type: 'siNewIndexValue',
|
||||
indexKey: indexValue,
|
||||
value,
|
||||
firstOfKind: !a,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,11 +663,21 @@ export class DataSource<T extends any, KeyType = never> {
|
||||
return this.getAllRecordsByIndex(indexQuery)[0];
|
||||
}
|
||||
|
||||
public getAllIndexValues(index: IndexDefinition<T>) {
|
||||
const sortedKeys = index.slice().sort();
|
||||
const indexKey = sortedKeys.join(':');
|
||||
const recordsByIndex = this._recordsBySecondaryIndex.get(indexKey);
|
||||
if (!recordsByIndex) {
|
||||
return;
|
||||
}
|
||||
return [...recordsByIndex.keys()];
|
||||
}
|
||||
|
||||
private getSecondaryIndexValueFromRecord(
|
||||
record: T,
|
||||
// assumes keys is already ordered
|
||||
keys: IndexDefinition<T>,
|
||||
): any {
|
||||
): string {
|
||||
return JSON.stringify(
|
||||
Object.fromEntries(keys.map((k) => [k, String(record[k])])),
|
||||
);
|
||||
@@ -993,6 +1035,10 @@ export class DataSourceView<T, KeyType> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'clear':
|
||||
case 'siNewIndexValue': {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('unknown event type');
|
||||
}
|
||||
|
||||
@@ -912,6 +912,13 @@ test('secondary keys - lookup by single key', () => {
|
||||
indices: [['id'], ['title'], ['done']],
|
||||
});
|
||||
|
||||
expect(ds.secondaryIndicesKeys()).toEqual(['id', 'title', 'done']);
|
||||
expect(ds.getAllIndexValues(['id'])).toEqual([
|
||||
JSON.stringify({id: 'cookie'}),
|
||||
JSON.stringify({id: 'coffee'}),
|
||||
JSON.stringify({id: 'bug'}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
ds.getAllRecordsByIndex({
|
||||
title: 'eat a cookie',
|
||||
@@ -938,6 +945,12 @@ test('secondary keys - lookup by single key', () => {
|
||||
}),
|
||||
).toEqual(submitBug);
|
||||
|
||||
expect(ds.getAllIndexValues(['id'])).toEqual([
|
||||
JSON.stringify({id: 'cookie'}),
|
||||
JSON.stringify({id: 'coffee'}),
|
||||
JSON.stringify({id: 'bug'}),
|
||||
]);
|
||||
|
||||
ds.delete(0); // eat Cookie
|
||||
expect(
|
||||
ds.getAllRecordsByIndex({
|
||||
@@ -945,6 +958,13 @@ test('secondary keys - lookup by single key', () => {
|
||||
}),
|
||||
).toEqual([cookie2]);
|
||||
|
||||
// We do not remove empty index values (for now)
|
||||
expect(ds.getAllIndexValues(['id'])).toEqual([
|
||||
JSON.stringify({id: 'cookie'}),
|
||||
JSON.stringify({id: 'coffee'}),
|
||||
JSON.stringify({id: 'bug'}),
|
||||
]);
|
||||
|
||||
// replace submit Bug
|
||||
const n = {
|
||||
id: 'bug',
|
||||
@@ -972,6 +992,12 @@ test('secondary keys - lookup by single key', () => {
|
||||
title: 'eat a cookie',
|
||||
}),
|
||||
).toEqual([cookie2]);
|
||||
|
||||
expect(ds.getAllIndexValues(['id'])).toEqual([
|
||||
JSON.stringify({id: 'cookie'}),
|
||||
JSON.stringify({id: 'coffee'}),
|
||||
JSON.stringify({id: 'bug'}),
|
||||
]);
|
||||
});
|
||||
|
||||
test('secondary keys - lookup by combined keys', () => {
|
||||
@@ -983,6 +1009,13 @@ test('secondary keys - lookup by combined keys', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(ds.secondaryIndicesKeys()).toEqual(['id:title', 'done:title']);
|
||||
expect(ds.getAllIndexValues(['id', 'title'])).toEqual([
|
||||
JSON.stringify({id: 'cookie', title: 'eat a cookie'}),
|
||||
JSON.stringify({id: 'coffee', title: 'drink coffee'}),
|
||||
JSON.stringify({id: 'bug', title: 'submit a bug'}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
ds.getAllRecordsByIndex({
|
||||
id: 'cookie',
|
||||
@@ -1014,6 +1047,13 @@ test('secondary keys - lookup by combined keys', () => {
|
||||
}),
|
||||
).toEqual([eatCookie, cookie2]);
|
||||
|
||||
expect(ds.getAllIndexValues(['id', 'title'])).toEqual([
|
||||
JSON.stringify({id: 'cookie', title: 'eat a cookie'}),
|
||||
JSON.stringify({id: 'coffee', title: 'drink coffee'}),
|
||||
JSON.stringify({id: 'bug', title: 'submit a bug'}),
|
||||
JSON.stringify({id: 'cookie2', title: 'eat a cookie'}),
|
||||
]);
|
||||
|
||||
const upsertedCookie = {
|
||||
id: 'cookie',
|
||||
title: 'eat a cookie',
|
||||
@@ -1041,6 +1081,16 @@ test('secondary keys - lookup by combined keys', () => {
|
||||
}),
|
||||
).toEqual(undefined);
|
||||
|
||||
expect(ds.getAllIndexValues(['id', 'title'])).toEqual([
|
||||
JSON.stringify({id: 'cookie', title: 'eat a cookie'}),
|
||||
JSON.stringify({id: 'coffee', title: 'drink coffee'}),
|
||||
JSON.stringify({id: 'bug', title: 'submit a bug'}),
|
||||
JSON.stringify({id: 'cookie2', title: 'eat a cookie'}),
|
||||
]);
|
||||
|
||||
const clearSub = jest.fn();
|
||||
ds.addDataListener('clear', clearSub);
|
||||
|
||||
ds.clear();
|
||||
expect(
|
||||
ds.getAllRecordsByIndex({
|
||||
@@ -1049,6 +1099,12 @@ test('secondary keys - lookup by combined keys', () => {
|
||||
}),
|
||||
).toEqual([]);
|
||||
|
||||
expect(ds.getAllIndexValues(['id', 'title'])).toEqual([]);
|
||||
expect(clearSub).toBeCalledTimes(1);
|
||||
|
||||
const newIndexValueSub = jest.fn();
|
||||
ds.addDataListener('siNewIndexValue', newIndexValueSub);
|
||||
|
||||
ds.append(cookie2);
|
||||
expect(
|
||||
ds.getAllRecordsByIndex({
|
||||
@@ -1056,4 +1112,23 @@ test('secondary keys - lookup by combined keys', () => {
|
||||
title: 'eat a cookie',
|
||||
}),
|
||||
).toEqual([cookie2]);
|
||||
|
||||
expect(ds.getAllIndexValues(['id', 'title'])).toEqual([
|
||||
JSON.stringify({id: 'cookie2', title: 'eat a cookie'}),
|
||||
]);
|
||||
|
||||
// Because we have 2 indecies
|
||||
expect(newIndexValueSub).toBeCalledTimes(2);
|
||||
expect(newIndexValueSub).toBeCalledWith({
|
||||
type: 'siNewIndexValue',
|
||||
indexKey: JSON.stringify({id: 'cookie2', title: 'eat a cookie'}),
|
||||
firstOfKind: true,
|
||||
value: cookie2,
|
||||
});
|
||||
expect(newIndexValueSub).toBeCalledWith({
|
||||
type: 'siNewIndexValue',
|
||||
indexKey: JSON.stringify({done: 'true', title: 'eat a cookie'}),
|
||||
firstOfKind: true,
|
||||
value: cookie2,
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user