1 | const typeChecker = (type) => {
|
2 | const typeString = "[object " + type + "]";
|
3 | return function (value) {
|
4 | return getClassName(value) === typeString;
|
5 | };
|
6 | };
|
7 | const getClassName = value => Object.prototype.toString.call(value);
|
8 | const 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 | };
|
20 | const isArray = typeChecker("Array");
|
21 | const isObject = typeChecker("Object");
|
22 | const isFunction = typeChecker("Function");
|
23 | const 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 | };
|
31 | const 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 |
|
66 |
|
67 |
|
68 | const walkKeyPathValues = (item, keyPath, next, depth, key, owner) => {
|
69 | const currentKey = keyPath[depth];
|
70 |
|
71 |
|
72 | if (isArray(item) && isNaN(Number(currentKey))) {
|
73 | for (let i = 0, { length } = item; i < length; i++) {
|
74 |
|
75 |
|
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 | };
|
86 | class 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 | }
|
99 | class 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 |
|
134 | this.done = done;
|
135 | this.success = success;
|
136 | }
|
137 | }
|
138 | class QueryOperation extends GroupOperation {
|
139 | |
140 |
|
141 | next(item, key, parent) {
|
142 | this.childrenNext(item, key, parent);
|
143 | }
|
144 | }
|
145 | class 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 | }
|
162 | const 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 | };
|
176 | class 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 | }
|
187 | const createEqualsOperation = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options);
|
188 | class NopeOperation extends BaseOperation {
|
189 | next() {
|
190 | this.done = true;
|
191 | this.success = false;
|
192 | }
|
193 | }
|
194 | const numericalOperationCreator = (createNumericalOperation) => (params, owneryQuery, options) => {
|
195 | if (params == null) {
|
196 | return new NopeOperation(params, owneryQuery, options);
|
197 | }
|
198 | return createNumericalOperation(params, owneryQuery, options);
|
199 | };
|
200 | const 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 | });
|
207 | const 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 | };
|
214 | const containsOperation = (query) => {
|
215 | for (const key in query) {
|
216 | if (key.charAt(0) === "$")
|
217 | return true;
|
218 | }
|
219 | return false;
|
220 | };
|
221 | const 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 | };
|
233 | const 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 | };
|
245 | const 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 |
|
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 | };
|
266 | const 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 |
|
278 | class $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 |
|
294 | class $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 | }
|
314 | class $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 | }
|
327 | class $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 | }
|
354 | class $Nor extends $Or {
|
355 | next(item, key, owner) {
|
356 | super.next(item, key, owner);
|
357 | this.success = !this.success;
|
358 | }
|
359 | }
|
360 | class $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 | }
|
384 | class $Nin extends $In {
|
385 | next(item, key, owner) {
|
386 | super.next(item, key, owner);
|
387 | this.success = !this.success;
|
388 | }
|
389 | }
|
390 | class $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 | }
|
398 | class $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 | }
|
406 | const $eq = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options);
|
407 | const $ne = (params, owneryQuery, options) => new $Ne(params, owneryQuery, options);
|
408 | const $or = (params, owneryQuery, options) => new $Or(params, owneryQuery, options);
|
409 | const $nor = (params, owneryQuery, options) => new $Nor(params, owneryQuery, options);
|
410 | const $elemMatch = (params, owneryQuery, options) => new $ElemMatch(params, owneryQuery, options);
|
411 | const $nin = (params, owneryQuery, options) => new $Nin(params, owneryQuery, options);
|
412 | const $in = (params, owneryQuery, options) => new $In(params, owneryQuery, options);
|
413 | const $lt = numericalOperation(params => b => b < params);
|
414 | const $lte = numericalOperation(params => b => b <= params);
|
415 | const $gt = numericalOperation(params => b => b > params);
|
416 | const $gte = numericalOperation(params => b => b >= params);
|
417 | const $mod = ([mod, equalsValue], owneryQuery, options) => new EqualsOperation(b => comparable(b) % mod === equalsValue, owneryQuery, options);
|
418 | const $exists = (params, owneryQuery, options) => new $Exists(params, owneryQuery, options);
|
419 | const $regex = (pattern, owneryQuery, options) => new EqualsOperation(new RegExp(pattern, owneryQuery.$options), owneryQuery, options);
|
420 | const $not = (params, owneryQuery, options) => new $Not(params, owneryQuery, options);
|
421 | const $type = (clazz, owneryQuery, options) => new EqualsOperation(b => (b != null ? b instanceof clazz || b.constructor === clazz : false), owneryQuery, options);
|
422 | const $and = (params, ownerQuery, options) => new $And(params, ownerQuery, options);
|
423 | const $all = $and;
|
424 | const $size = (params, ownerQuery, options) => new EqualsOperation(b => b && b.length === params, ownerQuery, options);
|
425 | const $options = () => null;
|
426 | const $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 |
|
440 | var defaultOperations = 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 |
|
465 | const createDefaultQueryTester = (query, { compare, operations } = {}) => {
|
466 | return createQueryTester(query, {
|
467 | compare: compare,
|
468 | operations: Object.assign({}, defaultOperations, operations)
|
469 | });
|
470 | };
|
471 |
|
472 | export default createDefaultQueryTester;
|
473 | export { $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 |
|