UNPKG

15.8 kBJavaScriptView Raw
1const typeChecker = (type) => {
2 const typeString = "[object " + type + "]";
3 return function (value) {
4 return getClassName(value) === typeString;
5 };
6};
7const getClassName = value => Object.prototype.toString.call(value);
8const comparable = (value) => {
9 if (value instanceof Date) {
10 return value.getTime();
11 }
12 else if (isArray(value)) {
13 return value.map(comparable);
14 }
15 else if (value && typeof value.toJSON === "function") {
16 return value.toJSON();
17 }
18 return value;
19};
20const isArray = typeChecker("Array");
21const isObject = typeChecker("Object");
22const isFunction = typeChecker("Function");
23const isVanillaObject = value => {
24 return (value &&
25 (value.constructor === Object ||
26 value.constructor === Array ||
27 value.constructor.toString() === "function Object() { [native code] }" ||
28 value.constructor.toString() === "function Array() { [native code] }") &&
29 !value.toJSON);
30};
31const equals = (a, b) => {
32 if (a == null && a == b) {
33 return true;
34 }
35 if (a === b) {
36 return true;
37 }
38 if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) {
39 return false;
40 }
41 if (isArray(a)) {
42 if (a.length !== b.length) {
43 return false;
44 }
45 for (let i = 0, { length } = a; i < length; i++) {
46 if (!equals(a[i], b[i]))
47 return false;
48 }
49 return true;
50 }
51 else if (isObject(a)) {
52 if (Object.keys(a).length !== Object.keys(b).length) {
53 return false;
54 }
55 for (const key in a) {
56 if (!equals(a[key], b[key]))
57 return false;
58 }
59 return true;
60 }
61 return false;
62};
63
64/**
65 * Walks through each value given the context - used for nested operations. E.g:
66 * { "person.address": { $eq: "blarg" }}
67 */
68const walkKeyPathValues = (item, keyPath, next, depth, key, owner) => {
69 const currentKey = keyPath[depth];
70 // if array, then try matching. Might fall through for cases like:
71 // { $eq: [1, 2, 3] }, [ 1, 2, 3 ].
72 if (isArray(item) && isNaN(Number(currentKey))) {
73 for (let i = 0, { length } = item; i < length; i++) {
74 // if FALSE is returned, then terminate walker. For operations, this simply
75 // means that the search critera was met.
76 if (!walkKeyPathValues(item[i], keyPath, next, depth, i, item)) {
77 return false;
78 }
79 }
80 }
81 if (depth === keyPath.length || item == null) {
82 return next(item, key, owner);
83 }
84 return walkKeyPathValues(item[currentKey], keyPath, next, depth + 1, currentKey, item);
85};
86class BaseOperation {
87 constructor(params, owneryQuery, options) {
88 this.params = params;
89 this.owneryQuery = owneryQuery;
90 this.options = options;
91 this.init();
92 }
93 init() { }
94 reset() {
95 this.done = false;
96 this.success = false;
97 }
98}
99class GroupOperation extends BaseOperation {
100 constructor(params, owneryQuery, options, _children) {
101 super(params, owneryQuery, options);
102 this._children = _children;
103 }
104 /**
105 */
106 reset() {
107 this.success = false;
108 this.done = false;
109 for (let i = 0, { length } = this._children; i < length; i++) {
110 this._children[i].reset();
111 }
112 }
113 /**
114 */
115 childrenNext(item, key, owner) {
116 let done = true;
117 let success = true;
118 for (let i = 0, { length } = this._children; i < length; i++) {
119 const childOperation = this._children[i];
120 childOperation.next(item, key, owner);
121 if (!childOperation.success) {
122 success = false;
123 }
124 if (childOperation.done) {
125 if (!childOperation.success) {
126 break;
127 }
128 }
129 else {
130 done = false;
131 }
132 }
133 // console.log("DONE", this.params, done, success);
134 this.done = done;
135 this.success = success;
136 }
137}
138class QueryOperation extends GroupOperation {
139 /**
140 */
141 next(item, key, parent) {
142 this.childrenNext(item, key, parent);
143 }
144}
145class NestedOperation extends GroupOperation {
146 constructor(keyPath, params, owneryQuery, options, children) {
147 super(params, owneryQuery, options, children);
148 this.keyPath = keyPath;
149 /**
150 */
151 this._nextNestedValue = (value, key, owner) => {
152 this.childrenNext(value, key, owner);
153 return !this.done;
154 };
155 }
156 /**
157 */
158 next(item, key, parent) {
159 walkKeyPathValues(item, this.keyPath, this._nextNestedValue, 0, key, parent);
160 }
161}
162const createTester = (a, compare) => {
163 if (a instanceof Function) {
164 return a;
165 }
166 if (a instanceof RegExp) {
167 return b => {
168 const result = typeof b === "string" && a.test(b);
169 a.lastIndex = 0;
170 return result;
171 };
172 }
173 const comparableA = comparable(a);
174 return b => compare(comparableA, comparable(b));
175};
176class EqualsOperation extends BaseOperation {
177 init() {
178 this._test = createTester(this.params, this.options.compare);
179 }
180 next(item, key, parent) {
181 if (this._test(item, key, parent)) {
182 this.done = true;
183 this.success = true;
184 }
185 }
186}
187const createEqualsOperation = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options);
188class NopeOperation extends BaseOperation {
189 next() {
190 this.done = true;
191 this.success = false;
192 }
193}
194const numericalOperationCreator = (createNumericalOperation) => (params, owneryQuery, options) => {
195 if (params == null) {
196 return new NopeOperation(params, owneryQuery, options);
197 }
198 return createNumericalOperation(params, owneryQuery, options);
199};
200const numericalOperation = (createTester) => numericalOperationCreator((params, owneryQuery, options) => {
201 const typeofParams = typeof comparable(params);
202 const test = createTester(params);
203 return new EqualsOperation(b => {
204 return typeof comparable(b) === typeofParams && test(b);
205 }, owneryQuery, options);
206});
207const createOperation = (name, params, parentQuery, options) => {
208 const operationCreator = options.operations[name];
209 if (!operationCreator) {
210 throw new Error(`Unsupported operation: ${name}`);
211 }
212 return operationCreator(params, parentQuery, options);
213};
214const containsOperation = (query) => {
215 for (const key in query) {
216 if (key.charAt(0) === "$")
217 return true;
218 }
219 return false;
220};
221const createNestedOperation = (keyPath, nestedQuery, owneryQuery, options) => {
222 if (containsOperation(nestedQuery)) {
223 const [selfOperations, nestedOperations] = createQueryOperations(nestedQuery, options);
224 if (nestedOperations.length) {
225 throw new Error(`Property queries must contain only operations, or exact objects.`);
226 }
227 return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, selfOperations);
228 }
229 return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, [
230 new EqualsOperation(nestedQuery, owneryQuery, options)
231 ]);
232};
233const createQueryOperation = (query, owneryQuery, options) => {
234 const [selfOperations, nestedOperations] = createQueryOperations(query, options);
235 const ops = [];
236 if (selfOperations.length) {
237 ops.push(new NestedOperation([], query, owneryQuery, options, selfOperations));
238 }
239 ops.push(...nestedOperations);
240 if (ops.length === 1) {
241 return ops[0];
242 }
243 return new QueryOperation(query, owneryQuery, options, ops);
244};
245const createQueryOperations = (query, options) => {
246 const selfOperations = [];
247 const nestedOperations = [];
248 if (!isVanillaObject(query)) {
249 selfOperations.push(new EqualsOperation(query, query, options));
250 return [selfOperations, nestedOperations];
251 }
252 for (const key in query) {
253 if (key.charAt(0) === "$") {
254 const op = createOperation(key, query[key], query, options);
255 // probably just a flag for another operation (like $options)
256 if (op != null) {
257 selfOperations.push(op);
258 }
259 }
260 else {
261 nestedOperations.push(createNestedOperation(key.split("."), query[key], query, options));
262 }
263 }
264 return [selfOperations, nestedOperations];
265};
266const createQueryTester = (query, { compare, operations } = {}) => {
267 const operation = createQueryOperation(query, null, {
268 compare: compare || equals,
269 operations: Object.assign({}, operations || {})
270 });
271 return (item, key, owner) => {
272 operation.reset();
273 operation.next(item, key, owner);
274 return operation.success;
275 };
276};
277
278class $Ne extends BaseOperation {
279 init() {
280 this._test = createTester(this.params, this.options.compare);
281 }
282 reset() {
283 super.reset();
284 this.success = true;
285 }
286 next(item) {
287 if (this._test(item)) {
288 this.done = true;
289 this.success = false;
290 }
291 }
292}
293// https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
294class $ElemMatch extends BaseOperation {
295 init() {
296 this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
297 }
298 reset() {
299 this._queryOperation.reset();
300 }
301 next(item, key, owner) {
302 this._queryOperation.reset();
303 if (isArray(owner)) {
304 this._queryOperation.next(item, key, owner);
305 this.done = this._queryOperation.done || key === owner.length - 1;
306 this.success = this._queryOperation.success;
307 }
308 else {
309 this.done = true;
310 this.success = false;
311 }
312 }
313}
314class $Not extends BaseOperation {
315 init() {
316 this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
317 }
318 reset() {
319 this._queryOperation.reset();
320 }
321 next(item, key, owner) {
322 this._queryOperation.next(item, key, owner);
323 this.done = this._queryOperation.done;
324 this.success = !this._queryOperation.success;
325 }
326}
327class $Or extends BaseOperation {
328 init() {
329 this._ops = this.params.map(op => createQueryOperation(op, null, this.options));
330 }
331 reset() {
332 this.done = false;
333 this.success = false;
334 for (let i = 0, { length } = this._ops; i < length; i++) {
335 this._ops[i].reset();
336 }
337 }
338 next(item, key, owner) {
339 let done = false;
340 let success = false;
341 for (let i = 0, { length } = this._ops; i < length; i++) {
342 const op = this._ops[i];
343 op.next(item, key, owner);
344 if (op.success) {
345 done = true;
346 success = op.success;
347 break;
348 }
349 }
350 this.success = success;
351 this.done = done;
352 }
353}
354class $Nor extends $Or {
355 next(item, key, owner) {
356 super.next(item, key, owner);
357 this.success = !this.success;
358 }
359}
360class $In extends BaseOperation {
361 init() {
362 this._testers = this.params.map(value => {
363 if (containsOperation(value)) {
364 throw new Error(`cannot nest $ under ${this.constructor.name.toLowerCase()}`);
365 }
366 return createTester(value, this.options.compare);
367 });
368 }
369 next(item, key, owner) {
370 let done = false;
371 let success = false;
372 for (let i = 0, { length } = this._testers; i < length; i++) {
373 const test = this._testers[i];
374 if (test(item)) {
375 done = true;
376 success = true;
377 break;
378 }
379 }
380 this.success = success;
381 this.done = done;
382 }
383}
384class $Nin extends $In {
385 next(item, key, owner) {
386 super.next(item, key, owner);
387 this.success = !this.success;
388 }
389}
390class $Exists extends BaseOperation {
391 next(item, key, owner) {
392 if (owner.hasOwnProperty(key) === this.params) {
393 this.done = true;
394 this.success = true;
395 }
396 }
397}
398class $And extends GroupOperation {
399 constructor(params, owneryQuery, options) {
400 super(params, owneryQuery, options, params.map(query => createQueryOperation(query, owneryQuery, options)));
401 }
402 next(item, key, owner) {
403 this.childrenNext(item, key, owner);
404 }
405}
406const $eq = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options);
407const $ne = (params, owneryQuery, options) => new $Ne(params, owneryQuery, options);
408const $or = (params, owneryQuery, options) => new $Or(params, owneryQuery, options);
409const $nor = (params, owneryQuery, options) => new $Nor(params, owneryQuery, options);
410const $elemMatch = (params, owneryQuery, options) => new $ElemMatch(params, owneryQuery, options);
411const $nin = (params, owneryQuery, options) => new $Nin(params, owneryQuery, options);
412const $in = (params, owneryQuery, options) => new $In(params, owneryQuery, options);
413const $lt = numericalOperation(params => b => b < params);
414const $lte = numericalOperation(params => b => b <= params);
415const $gt = numericalOperation(params => b => b > params);
416const $gte = numericalOperation(params => b => b >= params);
417const $mod = ([mod, equalsValue], owneryQuery, options) => new EqualsOperation(b => comparable(b) % mod === equalsValue, owneryQuery, options);
418const $exists = (params, owneryQuery, options) => new $Exists(params, owneryQuery, options);
419const $regex = (pattern, owneryQuery, options) => new EqualsOperation(new RegExp(pattern, owneryQuery.$options), owneryQuery, options);
420const $not = (params, owneryQuery, options) => new $Not(params, owneryQuery, options);
421const $type = (clazz, owneryQuery, options) => new EqualsOperation(b => (b != null ? b instanceof clazz || b.constructor === clazz : false), owneryQuery, options);
422const $and = (params, ownerQuery, options) => new $And(params, ownerQuery, options);
423const $all = $and;
424const $size = (params, ownerQuery, options) => new EqualsOperation(b => b && b.length === params, ownerQuery, options);
425const $options = () => null;
426const $where = (params, ownerQuery, options) => {
427 let test;
428 if (isFunction(params)) {
429 test = params;
430 }
431 else if (!process.env.CSP_ENABLED) {
432 test = new Function("obj", "return " + params);
433 }
434 else {
435 throw new Error(`In CSP mode, sift does not support strings in "$where" condition`);
436 }
437 return new EqualsOperation(b => test.bind(b)(b), ownerQuery, options);
438};
439
440var defaultOperations = /*#__PURE__*/Object.freeze({
441 __proto__: null,
442 $eq: $eq,
443 $ne: $ne,
444 $or: $or,
445 $nor: $nor,
446 $elemMatch: $elemMatch,
447 $nin: $nin,
448 $in: $in,
449 $lt: $lt,
450 $lte: $lte,
451 $gt: $gt,
452 $gte: $gte,
453 $mod: $mod,
454 $exists: $exists,
455 $regex: $regex,
456 $not: $not,
457 $type: $type,
458 $and: $and,
459 $all: $all,
460 $size: $size,
461 $options: $options,
462 $where: $where
463});
464
465const createDefaultQueryTester = (query, { compare, operations } = {}) => {
466 return createQueryTester(query, {
467 compare: compare,
468 operations: Object.assign({}, defaultOperations, operations)
469 });
470};
471
472export default createDefaultQueryTester;
473export { $all, $and, $elemMatch, $eq, $exists, $gt, $gte, $in, $lt, $lte, $mod, $ne, $nin, $nor, $not, $options, $or, $regex, $size, $type, $where, EqualsOperation, createEqualsOperation, createQueryTester };
474//# sourceMappingURL=index.js.map