You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					529 lines
				
				17 KiB
			
		
		
			
		
	
	
					529 lines
				
				17 KiB
			| 
											3 years ago
										 | const typeChecker = (type) => { | ||
|  |     const typeString = "[object " + type + "]"; | ||
|  |     return function (value) { | ||
|  |         return getClassName(value) === typeString; | ||
|  |     }; | ||
|  | }; | ||
|  | const getClassName = value => Object.prototype.toString.call(value); | ||
|  | const comparable = (value) => { | ||
|  |     if (value instanceof Date) { | ||
|  |         return value.getTime(); | ||
|  |     } | ||
|  |     else if (isArray(value)) { | ||
|  |         return value.map(comparable); | ||
|  |     } | ||
|  |     else if (value && typeof value.toJSON === "function") { | ||
|  |         return value.toJSON(); | ||
|  |     } | ||
|  |     return value; | ||
|  | }; | ||
|  | const isArray = typeChecker("Array"); | ||
|  | const isObject = typeChecker("Object"); | ||
|  | const isFunction = typeChecker("Function"); | ||
|  | const isVanillaObject = value => { | ||
|  |     return (value && | ||
|  |         (value.constructor === Object || | ||
|  |             value.constructor === Array || | ||
|  |             value.constructor.toString() === "function Object() { [native code] }" || | ||
|  |             value.constructor.toString() === "function Array() { [native code] }") && | ||
|  |         !value.toJSON); | ||
|  | }; | ||
|  | const equals = (a, b) => { | ||
|  |     if (a == null && a == b) { | ||
|  |         return true; | ||
|  |     } | ||
|  |     if (a === b) { | ||
|  |         return true; | ||
|  |     } | ||
|  |     if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) { | ||
|  |         return false; | ||
|  |     } | ||
|  |     if (isArray(a)) { | ||
|  |         if (a.length !== b.length) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         for (let i = 0, { length } = a; i < length; i++) { | ||
|  |             if (!equals(a[i], b[i])) | ||
|  |                 return false; | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  |     else if (isObject(a)) { | ||
|  |         if (Object.keys(a).length !== Object.keys(b).length) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         for (const key in a) { | ||
|  |             if (!equals(a[key], b[key])) | ||
|  |                 return false; | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  |     return false; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Walks through each value given the context - used for nested operations. E.g: | ||
|  |  * { "person.address": { $eq: "blarg" }} | ||
|  |  */ | ||
|  | const walkKeyPathValues = (item, keyPath, next, depth, key, owner) => { | ||
|  |     const currentKey = keyPath[depth]; | ||
|  |     // if array, then try matching. Might fall through for cases like:
 | ||
|  |     // { $eq: [1, 2, 3] }, [ 1, 2, 3 ].
 | ||
|  |     if (isArray(item) && isNaN(Number(currentKey))) { | ||
|  |         for (let i = 0, { length } = item; i < length; i++) { | ||
|  |             // if FALSE is returned, then terminate walker. For operations, this simply
 | ||
|  |             // means that the search critera was met.
 | ||
|  |             if (!walkKeyPathValues(item[i], keyPath, next, depth, i, item)) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     if (depth === keyPath.length || item == null) { | ||
|  |         return next(item, key, owner); | ||
|  |     } | ||
|  |     return walkKeyPathValues(item[currentKey], keyPath, next, depth + 1, currentKey, item); | ||
|  | }; | ||
|  | class BaseOperation { | ||
|  |     constructor(params, owneryQuery, options) { | ||
|  |         this.params = params; | ||
|  |         this.owneryQuery = owneryQuery; | ||
|  |         this.options = options; | ||
|  |         this.init(); | ||
|  |     } | ||
|  |     init() { } | ||
|  |     reset() { | ||
|  |         this.done = false; | ||
|  |         this.keep = false; | ||
|  |     } | ||
|  | } | ||
|  | class NamedBaseOperation extends BaseOperation { | ||
|  |     constructor(params, owneryQuery, options, name) { | ||
|  |         super(params, owneryQuery, options); | ||
|  |         this.name = name; | ||
|  |     } | ||
|  | } | ||
|  | class GroupOperation extends BaseOperation { | ||
|  |     constructor(params, owneryQuery, options, children) { | ||
|  |         super(params, owneryQuery, options); | ||
|  |         this.children = children; | ||
|  |     } | ||
|  |     /** | ||
|  |      */ | ||
|  |     reset() { | ||
|  |         this.keep = false; | ||
|  |         this.done = false; | ||
|  |         for (let i = 0, { length } = this.children; i < length; i++) { | ||
|  |             this.children[i].reset(); | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      */ | ||
|  |     childrenNext(item, key, owner) { | ||
|  |         let done = true; | ||
|  |         let keep = true; | ||
|  |         for (let i = 0, { length } = this.children; i < length; i++) { | ||
|  |             const childOperation = this.children[i]; | ||
|  |             childOperation.next(item, key, owner); | ||
|  |             if (!childOperation.keep) { | ||
|  |                 keep = false; | ||
|  |             } | ||
|  |             if (childOperation.done) { | ||
|  |                 if (!childOperation.keep) { | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |             else { | ||
|  |                 done = false; | ||
|  |             } | ||
|  |         } | ||
|  |         this.done = done; | ||
|  |         this.keep = keep; | ||
|  |     } | ||
|  | } | ||
|  | class NamedGroupOperation extends GroupOperation { | ||
|  |     constructor(params, owneryQuery, options, children, name) { | ||
|  |         super(params, owneryQuery, options, children); | ||
|  |         this.name = name; | ||
|  |     } | ||
|  | } | ||
|  | class QueryOperation extends GroupOperation { | ||
|  |     /** | ||
|  |      */ | ||
|  |     next(item, key, parent) { | ||
|  |         this.childrenNext(item, key, parent); | ||
|  |     } | ||
|  | } | ||
|  | class NestedOperation extends GroupOperation { | ||
|  |     constructor(keyPath, params, owneryQuery, options, children) { | ||
|  |         super(params, owneryQuery, options, children); | ||
|  |         this.keyPath = keyPath; | ||
|  |         /** | ||
|  |          */ | ||
|  |         this._nextNestedValue = (value, key, owner) => { | ||
|  |             this.childrenNext(value, key, owner); | ||
|  |             return !this.done; | ||
|  |         }; | ||
|  |     } | ||
|  |     /** | ||
|  |      */ | ||
|  |     next(item, key, parent) { | ||
|  |         walkKeyPathValues(item, this.keyPath, this._nextNestedValue, 0, key, parent); | ||
|  |     } | ||
|  | } | ||
|  | const createTester = (a, compare) => { | ||
|  |     if (a instanceof Function) { | ||
|  |         return a; | ||
|  |     } | ||
|  |     if (a instanceof RegExp) { | ||
|  |         return b => { | ||
|  |             const result = typeof b === "string" && a.test(b); | ||
|  |             a.lastIndex = 0; | ||
|  |             return result; | ||
|  |         }; | ||
|  |     } | ||
|  |     const comparableA = comparable(a); | ||
|  |     return b => compare(comparableA, comparable(b)); | ||
|  | }; | ||
|  | class EqualsOperation extends BaseOperation { | ||
|  |     init() { | ||
|  |         this._test = createTester(this.params, this.options.compare); | ||
|  |     } | ||
|  |     next(item, key, parent) { | ||
|  |         if (!Array.isArray(parent) || parent.hasOwnProperty(key)) { | ||
|  |             if (this._test(item, key, parent)) { | ||
|  |                 this.done = true; | ||
|  |                 this.keep = true; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | const createEqualsOperation = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options); | ||
|  | class NopeOperation extends BaseOperation { | ||
|  |     next() { | ||
|  |         this.done = true; | ||
|  |         this.keep = false; | ||
|  |     } | ||
|  | } | ||
|  | const numericalOperationCreator = (createNumericalOperation) => (params, owneryQuery, options, name) => { | ||
|  |     if (params == null) { | ||
|  |         return new NopeOperation(params, owneryQuery, options); | ||
|  |     } | ||
|  |     return createNumericalOperation(params, owneryQuery, options, name); | ||
|  | }; | ||
|  | const numericalOperation = (createTester) => numericalOperationCreator((params, owneryQuery, options) => { | ||
|  |     const typeofParams = typeof comparable(params); | ||
|  |     const test = createTester(params); | ||
|  |     return new EqualsOperation(b => { | ||
|  |         return typeof comparable(b) === typeofParams && test(b); | ||
|  |     }, owneryQuery, options); | ||
|  | }); | ||
|  | const createNamedOperation = (name, params, parentQuery, options) => { | ||
|  |     const operationCreator = options.operations[name]; | ||
|  |     if (!operationCreator) { | ||
|  |         throw new Error(`Unsupported operation: ${name}`); | ||
|  |     } | ||
|  |     return operationCreator(params, parentQuery, options, name); | ||
|  | }; | ||
|  | const containsOperation = (query) => { | ||
|  |     for (const key in query) { | ||
|  |         if (key.charAt(0) === "$") | ||
|  |             return true; | ||
|  |     } | ||
|  |     return false; | ||
|  | }; | ||
|  | const createNestedOperation = (keyPath, nestedQuery, owneryQuery, options) => { | ||
|  |     if (containsOperation(nestedQuery)) { | ||
|  |         const [selfOperations, nestedOperations] = createQueryOperations(nestedQuery, options); | ||
|  |         if (nestedOperations.length) { | ||
|  |             throw new Error(`Property queries must contain only operations, or exact objects.`); | ||
|  |         } | ||
|  |         return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, selfOperations); | ||
|  |     } | ||
|  |     return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, [ | ||
|  |         new EqualsOperation(nestedQuery, owneryQuery, options) | ||
|  |     ]); | ||
|  | }; | ||
|  | const createQueryOperation = (query, owneryQuery = null, { compare, operations } = {}) => { | ||
|  |     const options = { | ||
|  |         compare: compare || equals, | ||
|  |         operations: Object.assign({}, operations || {}) | ||
|  |     }; | ||
|  |     const [selfOperations, nestedOperations] = createQueryOperations(query, options); | ||
|  |     const ops = []; | ||
|  |     if (selfOperations.length) { | ||
|  |         ops.push(new NestedOperation([], query, owneryQuery, options, selfOperations)); | ||
|  |     } | ||
|  |     ops.push(...nestedOperations); | ||
|  |     if (ops.length === 1) { | ||
|  |         return ops[0]; | ||
|  |     } | ||
|  |     return new QueryOperation(query, owneryQuery, options, ops); | ||
|  | }; | ||
|  | const createQueryOperations = (query, options) => { | ||
|  |     const selfOperations = []; | ||
|  |     const nestedOperations = []; | ||
|  |     if (!isVanillaObject(query)) { | ||
|  |         selfOperations.push(new EqualsOperation(query, query, options)); | ||
|  |         return [selfOperations, nestedOperations]; | ||
|  |     } | ||
|  |     for (const key in query) { | ||
|  |         if (key.charAt(0) === "$") { | ||
|  |             const op = createNamedOperation(key, query[key], query, options); | ||
|  |             // probably just a flag for another operation (like $options)
 | ||
|  |             if (op != null) { | ||
|  |                 selfOperations.push(op); | ||
|  |             } | ||
|  |         } | ||
|  |         else { | ||
|  |             nestedOperations.push(createNestedOperation(key.split("."), query[key], query, options)); | ||
|  |         } | ||
|  |     } | ||
|  |     return [selfOperations, nestedOperations]; | ||
|  | }; | ||
|  | const createOperationTester = (operation) => (item, key, owner) => { | ||
|  |     operation.reset(); | ||
|  |     operation.next(item, key, owner); | ||
|  |     return operation.keep; | ||
|  | }; | ||
|  | const createQueryTester = (query, options = {}) => { | ||
|  |     return createOperationTester(createQueryOperation(query, null, options)); | ||
|  | }; | ||
|  | 
 | ||
|  | class $Ne extends NamedBaseOperation { | ||
|  |     init() { | ||
|  |         this._test = createTester(this.params, this.options.compare); | ||
|  |     } | ||
|  |     reset() { | ||
|  |         super.reset(); | ||
|  |         this.keep = true; | ||
|  |     } | ||
|  |     next(item) { | ||
|  |         if (this._test(item)) { | ||
|  |             this.done = true; | ||
|  |             this.keep = false; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | // https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
 | ||
|  | class $ElemMatch extends NamedBaseOperation { | ||
|  |     init() { | ||
|  |         this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options); | ||
|  |     } | ||
|  |     reset() { | ||
|  |         super.reset(); | ||
|  |         this._queryOperation.reset(); | ||
|  |     } | ||
|  |     next(item) { | ||
|  |         if (isArray(item)) { | ||
|  |             for (let i = 0, { length } = item; i < length; i++) { | ||
|  |                 // reset query operation since item being tested needs to pass _all_ query
 | ||
|  |                 // operations for it to be a success
 | ||
|  |                 this._queryOperation.reset(); | ||
|  |                 // check item
 | ||
|  |                 this._queryOperation.next(item[i], i, item); | ||
|  |                 this.keep = this.keep || this._queryOperation.keep; | ||
|  |             } | ||
|  |             this.done = true; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.done = false; | ||
|  |             this.keep = false; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | class $Not extends NamedBaseOperation { | ||
|  |     init() { | ||
|  |         this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options); | ||
|  |     } | ||
|  |     reset() { | ||
|  |         this._queryOperation.reset(); | ||
|  |     } | ||
|  |     next(item, key, owner) { | ||
|  |         this._queryOperation.next(item, key, owner); | ||
|  |         this.done = this._queryOperation.done; | ||
|  |         this.keep = !this._queryOperation.keep; | ||
|  |     } | ||
|  | } | ||
|  | class $Size extends NamedBaseOperation { | ||
|  |     init() { } | ||
|  |     next(item) { | ||
|  |         if (isArray(item) && item.length === this.params) { | ||
|  |             this.done = true; | ||
|  |             this.keep = true; | ||
|  |         } | ||
|  |         // if (parent && parent.length === this.params) {
 | ||
|  |         //   this.done = true;
 | ||
|  |         //   this.keep = true;
 | ||
|  |         // }
 | ||
|  |     } | ||
|  | } | ||
|  | class $Or extends NamedBaseOperation { | ||
|  |     init() { | ||
|  |         this._ops = this.params.map(op => createQueryOperation(op, null, this.options)); | ||
|  |     } | ||
|  |     reset() { | ||
|  |         this.done = false; | ||
|  |         this.keep = false; | ||
|  |         for (let i = 0, { length } = this._ops; i < length; i++) { | ||
|  |             this._ops[i].reset(); | ||
|  |         } | ||
|  |     } | ||
|  |     next(item, key, owner) { | ||
|  |         let done = false; | ||
|  |         let success = false; | ||
|  |         for (let i = 0, { length } = this._ops; i < length; i++) { | ||
|  |             const op = this._ops[i]; | ||
|  |             op.next(item, key, owner); | ||
|  |             if (op.keep) { | ||
|  |                 done = true; | ||
|  |                 success = op.keep; | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  |         this.keep = success; | ||
|  |         this.done = done; | ||
|  |     } | ||
|  | } | ||
|  | class $Nor extends $Or { | ||
|  |     next(item, key, owner) { | ||
|  |         super.next(item, key, owner); | ||
|  |         this.keep = !this.keep; | ||
|  |     } | ||
|  | } | ||
|  | class $In extends NamedBaseOperation { | ||
|  |     init() { | ||
|  |         this._testers = this.params.map(value => { | ||
|  |             if (containsOperation(value)) { | ||
|  |                 throw new Error(`cannot nest $ under ${this.constructor.name.toLowerCase()}`); | ||
|  |             } | ||
|  |             return createTester(value, this.options.compare); | ||
|  |         }); | ||
|  |     } | ||
|  |     next(item, key, owner) { | ||
|  |         let done = false; | ||
|  |         let success = false; | ||
|  |         for (let i = 0, { length } = this._testers; i < length; i++) { | ||
|  |             const test = this._testers[i]; | ||
|  |             if (test(item)) { | ||
|  |                 done = true; | ||
|  |                 success = true; | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  |         this.keep = success; | ||
|  |         this.done = done; | ||
|  |     } | ||
|  | } | ||
|  | class $Nin extends $In { | ||
|  |     next(item, key, owner) { | ||
|  |         super.next(item, key, owner); | ||
|  |         this.keep = !this.keep; | ||
|  |     } | ||
|  | } | ||
|  | class $Exists extends NamedBaseOperation { | ||
|  |     next(item, key, owner) { | ||
|  |         if (owner.hasOwnProperty(key) === this.params) { | ||
|  |             this.done = true; | ||
|  |             this.keep = true; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | class $And extends NamedGroupOperation { | ||
|  |     constructor(params, owneryQuery, options, name) { | ||
|  |         super(params, owneryQuery, options, params.map(query => createQueryOperation(query, owneryQuery, options)), name); | ||
|  |     } | ||
|  |     next(item, key, owner) { | ||
|  |         this.childrenNext(item, key, owner); | ||
|  |     } | ||
|  | } | ||
|  | const $eq = (params, owneryQuery, options) => new EqualsOperation(params, owneryQuery, options); | ||
|  | const $ne = (params, owneryQuery, options, name) => new $Ne(params, owneryQuery, options, name); | ||
|  | const $or = (params, owneryQuery, options, name) => new $Or(params, owneryQuery, options, name); | ||
|  | const $nor = (params, owneryQuery, options, name) => new $Nor(params, owneryQuery, options, name); | ||
|  | const $elemMatch = (params, owneryQuery, options, name) => new $ElemMatch(params, owneryQuery, options, name); | ||
|  | const $nin = (params, owneryQuery, options, name) => new $Nin(params, owneryQuery, options, name); | ||
|  | const $in = (params, owneryQuery, options, name) => new $In(params, owneryQuery, options, name); | ||
|  | const $lt = numericalOperation(params => b => b < params); | ||
|  | const $lte = numericalOperation(params => b => b <= params); | ||
|  | const $gt = numericalOperation(params => b => b > params); | ||
|  | const $gte = numericalOperation(params => b => b >= params); | ||
|  | const $mod = ([mod, equalsValue], owneryQuery, options) => new EqualsOperation(b => comparable(b) % mod === equalsValue, owneryQuery, options); | ||
|  | const $exists = (params, owneryQuery, options, name) => new $Exists(params, owneryQuery, options, name); | ||
|  | const $regex = (pattern, owneryQuery, options) => new EqualsOperation(new RegExp(pattern, owneryQuery.$options), owneryQuery, options); | ||
|  | const $not = (params, owneryQuery, options, name) => new $Not(params, owneryQuery, options, name); | ||
|  | const typeAliases = { | ||
|  |     number: v => typeof v === "number", | ||
|  |     string: v => typeof v === "string", | ||
|  |     bool: v => typeof v === "boolean", | ||
|  |     array: v => Array.isArray(v), | ||
|  |     null: v => v === null, | ||
|  |     timestamp: v => v instanceof Date | ||
|  | }; | ||
|  | const $type = (clazz, owneryQuery, options) => new EqualsOperation(b => { | ||
|  |     if (typeof clazz === "string") { | ||
|  |         if (!typeAliases[clazz]) { | ||
|  |             throw new Error(`Type alias does not exist`); | ||
|  |         } | ||
|  |         return typeAliases[clazz](b); | ||
|  |     } | ||
|  |     return b != null ? b instanceof clazz || b.constructor === clazz : false; | ||
|  | }, owneryQuery, options); | ||
|  | const $and = (params, ownerQuery, options, name) => new $And(params, ownerQuery, options, name); | ||
|  | const $all = $and; | ||
|  | const $size = (params, ownerQuery, options) => new $Size(params, ownerQuery, options, "$size"); | ||
|  | const $options = () => null; | ||
|  | const $where = (params, ownerQuery, options) => { | ||
|  |     let test; | ||
|  |     if (isFunction(params)) { | ||
|  |         test = params; | ||
|  |     } | ||
|  |     else if (!process.env.CSP_ENABLED) { | ||
|  |         test = new Function("obj", "return " + params); | ||
|  |     } | ||
|  |     else { | ||
|  |         throw new Error(`In CSP mode, sift does not support strings in "$where" condition`); | ||
|  |     } | ||
|  |     return new EqualsOperation(b => test.bind(b)(b), ownerQuery, options); | ||
|  | }; | ||
|  | 
 | ||
|  | var defaultOperations = /*#__PURE__*/Object.freeze({ | ||
|  |     __proto__: null, | ||
|  |     $Size: $Size, | ||
|  |     $eq: $eq, | ||
|  |     $ne: $ne, | ||
|  |     $or: $or, | ||
|  |     $nor: $nor, | ||
|  |     $elemMatch: $elemMatch, | ||
|  |     $nin: $nin, | ||
|  |     $in: $in, | ||
|  |     $lt: $lt, | ||
|  |     $lte: $lte, | ||
|  |     $gt: $gt, | ||
|  |     $gte: $gte, | ||
|  |     $mod: $mod, | ||
|  |     $exists: $exists, | ||
|  |     $regex: $regex, | ||
|  |     $not: $not, | ||
|  |     $type: $type, | ||
|  |     $and: $and, | ||
|  |     $all: $all, | ||
|  |     $size: $size, | ||
|  |     $options: $options, | ||
|  |     $where: $where | ||
|  | }); | ||
|  | 
 | ||
|  | const createDefaultQueryOperation = (query, ownerQuery, { compare, operations } = {}) => { | ||
|  |     return createQueryOperation(query, ownerQuery, { | ||
|  |         compare: compare, | ||
|  |         operations: Object.assign({}, defaultOperations, operations || {}) | ||
|  |     }); | ||
|  | }; | ||
|  | const createDefaultQueryTester = (query, options = {}) => { | ||
|  |     const op = createDefaultQueryOperation(query, null, options); | ||
|  |     return createOperationTester(op); | ||
|  | }; | ||
|  | 
 | ||
|  | export default createDefaultQueryTester; | ||
|  | export { $Size, $all, $and, $elemMatch, $eq, $exists, $gt, $gte, $in, $lt, $lte, $mod, $ne, $nin, $nor, $not, $options, $or, $regex, $size, $type, $where, EqualsOperation, createDefaultQueryOperation, createEqualsOperation, createOperationTester, createQueryOperation, createQueryTester }; | ||
|  | //# sourceMappingURL=index.js.map
 |