Skip to content

Commit a9b0a87

Browse files
author
Bobby
authored
feat: support where clauses with timestamp values
feat: Filter by timestamp
2 parents d188673 + 70e0d88 commit a9b0a87

File tree

2 files changed

+288
-1
lines changed

2 files changed

+288
-1
lines changed

src/reducers/cacheReducer.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ const info = debug('rrf:cache');
9797
* @property {object} meta
9898
*/
9999

100+
const isTimestamp = (a) => a instanceof Object && a.seconds !== undefined;
101+
100102
const PROCESSES = {
101103
'<': (a, b) => a < b,
102104
'<=': (a, b) => a <= b,
@@ -111,6 +113,28 @@ const PROCESSES = {
111113
'*': () => true,
112114
};
113115

116+
const PROCESSES_TIMESTAMP = {
117+
'<': (a, b) =>
118+
a.seconds < b.seconds ||
119+
(a.seconds === b.seconds && a.nanoseconds < b.nanoseconds),
120+
'<=': (a, b) =>
121+
a.seconds < b.seconds ||
122+
(a.seconds === b.seconds && a.nanoseconds <= b.nanoseconds),
123+
'==': (a, b) => a.seconds === b.seconds && a.nanoseconds === b.nanoseconds,
124+
'!=': (a, b) => a.seconds !== b.seconds || a.nanoseconds !== b.nanoseconds,
125+
'>=': (a, b) =>
126+
a.seconds > b.seconds ||
127+
(a.seconds === b.seconds && a.nanoseconds >= b.nanoseconds),
128+
'>': (a, b) =>
129+
a.seconds > b.seconds ||
130+
(a.seconds === b.seconds && a.nanoseconds > b.nanoseconds),
131+
'array-contains': (a, b) => a.includes(b),
132+
in: (a, b) => Array.isArray(b) && b.includes(a),
133+
'array-contains-any': (a, b) => b.some((b1) => a.includes(b1)),
134+
'not-in': (a, b) => !b.includes(a),
135+
'*': () => true,
136+
};
137+
114138
/**
115139
* @name getDocumentTransducer
116140
* @param ids - array of document ids
@@ -185,7 +209,9 @@ const filterTransducers = (where) => {
185209
const clauses = isFlat ? [where] : where;
186210

187211
return clauses.map(([field, op, val]) => {
188-
const fnc = PROCESSES[op] || (() => true);
212+
const fnc = isTimestamp(val)
213+
? PROCESSES_TIMESTAMP[op]
214+
: PROCESSES[op] || (() => true);
189215
return partialRight(map, (collection) =>
190216
filter(Object.values(collection || {}), (doc) => {
191217
let value;

test/unit/reducers/cacheReducer.spec.js

+261
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable prettier/prettier */
12
/* eslint-disable no-console */
23
import reducer from 'reducer';
34
import { actionTypes } from 'constants';
@@ -73,6 +74,266 @@ describe('cacheReducer', () => {
7374
path,
7475
});
7576
});
77+
78+
it('SET_LISTENER returns smaller filtered date', () => {
79+
80+
const thresholdDate = { seconds: 0, nanoseconds: 1 }
81+
82+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
83+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
84+
85+
// Initial seed
86+
const action1 = {
87+
meta: {
88+
collection,
89+
storeAs: 'testStoreAs',
90+
orderBy: ['dateKey'],
91+
fields: ['id', 'other'],
92+
},
93+
payload: {
94+
data: { [doc1.id]: doc1, [doc2.id]: doc2 },
95+
ordered: [doc1, doc2],
96+
fromCache: true,
97+
},
98+
type: actionTypes.LISTENER_RESPONSE,
99+
};
100+
101+
const action2 = {
102+
meta: {
103+
collection,
104+
storeAs: 'testStoreAs2',
105+
where: [['dateKey', '<', thresholdDate]],
106+
orderBy: ['dateKey'],
107+
fields: ['id', 'other'],
108+
},
109+
payload: { name: 'testStoreAs2' },
110+
type: actionTypes.SET_LISTENER,
111+
};
112+
113+
const pass1 = reducer(initialState, action1);
114+
const pass2 = reducer(pass1, action2);
115+
116+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(1);
117+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId1')
118+
});
119+
120+
it('SET_LISTENER returns greater filtered date', () => {
121+
122+
const thresholdDate = { seconds: 0, nanoseconds: 1 }
123+
124+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
125+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
126+
127+
// Initial seed
128+
const action1 = {
129+
meta: {
130+
collection,
131+
storeAs: 'testStoreAs',
132+
orderBy: ['dateKey'],
133+
fields: ['id', 'other'],
134+
},
135+
payload: {
136+
data: { [doc1.id]: doc1, [doc2.id]: doc2 },
137+
ordered: [doc1, doc2],
138+
fromCache: true,
139+
},
140+
type: actionTypes.LISTENER_RESPONSE,
141+
};
142+
143+
const action2 = {
144+
meta: {
145+
collection,
146+
storeAs: 'testStoreAs2',
147+
where: [['dateKey', '>', thresholdDate]],
148+
orderBy: ['dateKey'],
149+
fields: ['id', 'other'],
150+
},
151+
payload: { name: 'testStoreAs2' },
152+
type: actionTypes.SET_LISTENER,
153+
};
154+
155+
const pass1 = reducer(initialState, action1);
156+
const pass2 = reducer(pass1, action2);
157+
158+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(1);
159+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId2')
160+
});
161+
162+
it('SET_LISTENER returns smaller or equal filtered date', () => {
163+
164+
const thresholdDate = { seconds: 1, nanoseconds: 1 }
165+
166+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
167+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
168+
const doc3 = { dateKey: { seconds: 3, nanoseconds: 3 }, other: 'test', id: 'testDocId3', path };
169+
170+
// Initial seed
171+
const action1 = {
172+
meta: {
173+
collection,
174+
storeAs: 'testStoreAs',
175+
orderBy: ['dateKey'],
176+
fields: ['id', 'other'],
177+
},
178+
payload: {
179+
data: { [doc1.id]: doc1, [doc2.id]: doc2, [doc3.id]: doc3 },
180+
ordered: [doc1, doc2],
181+
fromCache: true,
182+
},
183+
type: actionTypes.LISTENER_RESPONSE,
184+
};
185+
186+
const action2 = {
187+
meta: {
188+
collection,
189+
storeAs: 'testStoreAs2',
190+
where: [['dateKey', '<=', thresholdDate]],
191+
orderBy: ['dateKey'],
192+
fields: ['id', 'other'],
193+
},
194+
payload: { name: 'testStoreAs2' },
195+
type: actionTypes.SET_LISTENER,
196+
};
197+
198+
const pass1 = reducer(initialState, action1);
199+
const pass2 = reducer(pass1, action2);
200+
201+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(2);
202+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId1')
203+
expect(pass2.cache.testStoreAs2.docs[1].id).to.eql('testDocId2')
204+
});
205+
206+
it('SET_LISTENER returns greater or equal filtered date', () => {
207+
208+
const thresholdDate = { seconds: 1, nanoseconds: 1 }
209+
210+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
211+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
212+
const doc3 = { dateKey: { seconds: 3, nanoseconds: 3 }, other: 'test', id: 'testDocId3', path };
213+
214+
// Initial seed
215+
const action1 = {
216+
meta: {
217+
collection,
218+
storeAs: 'testStoreAs',
219+
orderBy: ['dateKey'],
220+
fields: ['id', 'other'],
221+
},
222+
payload: {
223+
data: { [doc1.id]: doc1, [doc2.id]: doc2, [doc3.id]: doc3 },
224+
ordered: [doc1, doc2],
225+
fromCache: true,
226+
},
227+
type: actionTypes.LISTENER_RESPONSE,
228+
};
229+
230+
const action2 = {
231+
meta: {
232+
collection,
233+
storeAs: 'testStoreAs2',
234+
where: [['dateKey', '>=', thresholdDate]],
235+
orderBy: ['dateKey'],
236+
fields: ['id', 'other'],
237+
},
238+
payload: { name: 'testStoreAs2' },
239+
type: actionTypes.SET_LISTENER,
240+
};
241+
242+
const pass1 = reducer(initialState, action1);
243+
const pass2 = reducer(pass1, action2);
244+
245+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(2);
246+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId2')
247+
expect(pass2.cache.testStoreAs2.docs[1].id).to.eql('testDocId3')
248+
});
249+
250+
it('SET_LISTENER returns exact filtered date', () => {
251+
252+
const thresholdDate = { seconds: 1, nanoseconds: 1 }
253+
254+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
255+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
256+
const doc3 = { dateKey: { seconds: 3, nanoseconds: 3 }, other: 'test', id: 'testDocId3', path };
257+
258+
// Initial seed
259+
const action1 = {
260+
meta: {
261+
collection,
262+
storeAs: 'testStoreAs',
263+
orderBy: ['dateKey'],
264+
fields: ['id', 'other'],
265+
},
266+
payload: {
267+
data: { [doc1.id]: doc1, [doc2.id]: doc2, [doc3.id]: doc3 },
268+
ordered: [doc1, doc2],
269+
fromCache: true,
270+
},
271+
type: actionTypes.LISTENER_RESPONSE,
272+
};
273+
274+
const action2 = {
275+
meta: {
276+
collection,
277+
storeAs: 'testStoreAs2',
278+
where: [['dateKey', '==', thresholdDate]],
279+
orderBy: ['dateKey'],
280+
fields: ['id', 'other'],
281+
},
282+
payload: { name: 'testStoreAs2' },
283+
type: actionTypes.SET_LISTENER,
284+
};
285+
286+
const pass1 = reducer(initialState, action1);
287+
const pass2 = reducer(pass1, action2);
288+
289+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(1);
290+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId2')
291+
});
292+
293+
it('SET_LISTENER returns different filtered date', () => {
294+
295+
const thresholdDate = { seconds: 1, nanoseconds: 1 }
296+
297+
const doc1 = { dateKey: { seconds: 0, nanoseconds: 0 }, other: 'test', id: 'testDocId1', path };
298+
const doc2 = { dateKey: { seconds: 1, nanoseconds: 1 }, other: 'test', id: 'testDocId2', path };
299+
const doc3 = { dateKey: { seconds: 3, nanoseconds: 3 }, other: 'test', id: 'testDocId3', path };
300+
301+
// Initial seed
302+
const action1 = {
303+
meta: {
304+
collection,
305+
storeAs: 'testStoreAs',
306+
orderBy: ['dateKey'],
307+
fields: ['id', 'other'],
308+
},
309+
payload: {
310+
data: { [doc1.id]: doc1, [doc2.id]: doc2, [doc3.id]: doc3 },
311+
ordered: [doc1, doc2],
312+
fromCache: true,
313+
},
314+
type: actionTypes.LISTENER_RESPONSE,
315+
};
316+
317+
const action2 = {
318+
meta: {
319+
collection,
320+
storeAs: 'testStoreAs2',
321+
where: [['dateKey', '!=', thresholdDate]],
322+
orderBy: ['dateKey'],
323+
fields: ['id', 'other'],
324+
},
325+
payload: { name: 'testStoreAs2' },
326+
type: actionTypes.SET_LISTENER,
327+
};
328+
329+
const pass1 = reducer(initialState, action1);
330+
const pass2 = reducer(pass1, action2);
331+
332+
expect(pass2.cache.testStoreAs2.docs.length).to.eql(2);
333+
expect(pass2.cache.testStoreAs2.docs[0].id).to.eql('testDocId1')
334+
expect(pass2.cache.testStoreAs2.docs[1].id).to.eql('testDocId3')
335+
});
336+
76337
});
77338

78339
describe('query fields', () => {

0 commit comments

Comments
 (0)