From 3d1bf296f97df32f182f95e85e47201a78a4b3ca Mon Sep 17 00:00:00 2001 From: Craig Condon Date: Mon, 14 Feb 2022 22:47:56 -0500 Subject: [PATCH] fix #243 --- src/core.ts | 65 ++++++++++++++++++++++---------------------- src/operations.ts | 68 ++++++++++++++++++++++++++++++---------------- test/basic-test.js | 9 ++++++ 3 files changed, 87 insertions(+), 55 deletions(-) diff --git a/src/core.ts b/src/core.ts index 7b9a011..e09ca7c 100644 --- a/src/core.ts +++ b/src/core.ts @@ -12,10 +12,15 @@ export interface Operation { readonly done: boolean; propop: boolean; reset(); - next(item: TItem, key?: Key, owner?: any); + next(item: TItem, key?: Key, owner?: any, root?: boolean); } -export type Tester = (item: any, key?: Key, owner?: any) => boolean; +export type Tester = ( + item: any, + key?: Key, + owner?: any, + root?: boolean +) => boolean; export interface NamedOperation { name: string; @@ -100,7 +105,7 @@ const walkKeyPathValues = ( } if (depth === keyPath.length || item == null) { - return next(item, key, owner); + return next(item, key, owner, depth === 0); } return walkKeyPathValues( @@ -113,14 +118,16 @@ const walkKeyPathValues = ( ); }; -abstract class BaseOperation implements Operation { +export abstract class BaseOperation + implements Operation { keep: boolean; done: boolean; abstract propop: boolean; constructor( readonly params: TParams, readonly owneryQuery: any, - readonly options: Options + readonly options: Options, + readonly name?: string ) { this.init(); } @@ -129,21 +136,7 @@ abstract class BaseOperation implements Operation { this.done = false; this.keep = false; } - abstract next(item: any, key: Key, parent: any); -} - -export abstract class NamedBaseOperation - extends BaseOperation - implements NamedOperation { - abstract propop: boolean; - constructor( - params: TParams, - owneryQuery: any, - options: Options, - readonly name: string - ) { - super(params, owneryQuery, options); - } + abstract next(item: any, key: Key, parent: any, root: boolean); } abstract class GroupOperation extends BaseOperation { @@ -170,17 +163,19 @@ abstract class GroupOperation extends BaseOperation { } } - abstract next(item: any, key: Key, owner: any); + abstract next(item: any, key: Key, owner: any, root: boolean); /** */ - protected childrenNext(item: any, key: Key, owner: any) { + protected childrenNext(item: any, key: Key, owner: any, root: boolean) { 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.done) { + childOperation.next(item, key, owner, root); + } if (!childOperation.keep) { keep = false; } @@ -216,8 +211,8 @@ export class QueryOperation extends GroupOperation { /** */ - next(item: TItem, key: Key, parent: any) { - this.childrenNext(item, key, parent); + next(item: TItem, key: Key, parent: any, root: boolean) { + this.childrenNext(item, key, parent, root); } } @@ -249,8 +244,13 @@ export class NestedOperation extends GroupOperation { /** */ - private _nextNestedValue = (value: any, key: Key, owner: any) => { - this.childrenNext(value, key, owner); + private _nextNestedValue = ( + value: any, + key: Key, + owner: any, + root: boolean + ) => { + this.childrenNext(value, key, owner, root); return !this.done; }; } @@ -290,7 +290,7 @@ export const createEqualsOperation = ( params: any, owneryQuery: any, options: Options -) => new EqualsOperation(params, owneryQuery, options); +) => new EqualsOperation(params, owneryQuery, options, "$eq"); export class NopeOperation extends BaseOperation { readonly propop = true; @@ -304,7 +304,7 @@ export const numericalOperationCreator = ( createNumericalOperation: OperationCreator ) => (params: any, owneryQuery: any, options: Options, name: string) => { if (params == null) { - return new NopeOperation(params, owneryQuery, options); + return new NopeOperation(params, owneryQuery, options, name); } return createNumericalOperation(params, owneryQuery, options, name); @@ -312,7 +312,7 @@ export const numericalOperationCreator = ( export const numericalOperation = (createTester: (any) => Tester) => numericalOperationCreator( - (params: any, owneryQuery: Query, options: Options) => { + (params: any, owneryQuery: Query, options: Options, name: string) => { const typeofParams = typeof comparable(params); const test = createTester(params); return new EqualsOperation( @@ -320,7 +320,8 @@ export const numericalOperation = (createTester: (any) => Tester) => return typeof comparable(b) === typeofParams && test(b); }, owneryQuery, - options + options, + name ); } ); @@ -427,7 +428,7 @@ const createQueryOperations = ( const selfOperations = []; const nestedOperations = []; if (!isVanillaObject(query)) { - selfOperations.push(new EqualsOperation(query, query, options)); + selfOperations.push(new EqualsOperation(query, query, options, "$eq")); return [selfOperations, nestedOperations]; } for (const key in query) { diff --git a/src/operations.ts b/src/operations.ts index b72e6fa..83c5f5b 100644 --- a/src/operations.ts +++ b/src/operations.ts @@ -1,5 +1,5 @@ import { - NamedBaseOperation, + BaseOperation, EqualsOperation, Options, createTester, @@ -15,7 +15,7 @@ import { } from "./core"; import { Key, comparable, isFunction, isArray } from "./utils"; -class $Ne extends NamedBaseOperation { +class $Ne extends BaseOperation { readonly propop = true; private _test: Tester; init() { @@ -33,7 +33,7 @@ class $Ne extends NamedBaseOperation { } } // https://docs.mongodb.com/manual/reference/operator/query/elemMatch/ -class $ElemMatch extends NamedBaseOperation> { +class $ElemMatch extends BaseOperation> { readonly propop = true; private _queryOperation: QueryOperation; init() { @@ -58,7 +58,7 @@ class $ElemMatch extends NamedBaseOperation> { this._queryOperation.reset(); const child = item[i]; - this._queryOperation.next(child, i, item); + this._queryOperation.next(child, i, item, false); this.keep = this.keep || this._queryOperation.keep; } this.done = true; @@ -69,7 +69,7 @@ class $ElemMatch extends NamedBaseOperation> { } } -class $Not extends NamedBaseOperation> { +class $Not extends BaseOperation> { readonly propop = true; private _queryOperation: QueryOperation; init() { @@ -80,16 +80,17 @@ class $Not extends NamedBaseOperation> { ); } reset() { + super.reset(); this._queryOperation.reset(); } - next(item: any, key: Key, owner: any) { - this._queryOperation.next(item, key, owner); + next(item: any, key: Key, owner: any, root: boolean) { + this._queryOperation.next(item, key, owner, root); this.done = this._queryOperation.done; this.keep = !this._queryOperation.keep; } } -export class $Size extends NamedBaseOperation { +export class $Size extends BaseOperation { readonly propop = true; init() {} next(item) { @@ -110,7 +111,7 @@ const assertGroupNotEmpty = (values: any[]) => { } }; -class $Or extends NamedBaseOperation { +class $Or extends BaseOperation { readonly propop = false; private _ops: Operation[]; init() { @@ -152,15 +153,13 @@ class $Nor extends $Or { } } -class $In extends NamedBaseOperation { +class $In extends BaseOperation { readonly propop = true; private _testers: Tester[]; init() { this._testers = this.params.map(value => { if (containsOperation(value, this.options)) { - throw new Error( - `cannot nest $ under ${this.constructor.name.toLowerCase()}` - ); + throw new Error(`cannot nest $ under ${this.name.toLowerCase()}`); } return createTester(value, this.options.compare); }); @@ -182,15 +181,36 @@ class $In extends NamedBaseOperation { } } -class $Nin extends $In { +class $Nin extends BaseOperation { readonly propop = true; - next(item: any, key: Key, owner: any) { - super.next(item, key, owner); - this.keep = !this.keep; + private _in: $In; + constructor(params: any, ownerQuery: any, options: Options, name: string) { + super(params, ownerQuery, options, name); + this._in = new $In(params, ownerQuery, options, name); + } + next(item: any, key: Key, owner: any, root: boolean) { + this._in.next(item, key, owner); + + if (isArray(owner) && !root) { + if (this._in.keep) { + this.keep = false; + this.done = true; + } else if (key == owner.length - 1) { + this.keep = true; + this.done = true; + } + } else { + this.keep = !this._in.keep; + this.done = true; + } + } + reset() { + super.reset(); + this._in.reset(); } } -class $Exists extends NamedBaseOperation { +class $Exists extends BaseOperation { readonly propop = true; next(item: any, key: Key, owner: any) { if (owner.hasOwnProperty(key) === this.params) { @@ -218,8 +238,8 @@ class $And extends NamedGroupOperation { assertGroupNotEmpty(params); } - next(item: any, key: Key, owner: any) { - this.childrenNext(item, key, owner); + next(item: any, key: Key, owner: any, root: boolean) { + this.childrenNext(item, key, owner, root); } } @@ -239,8 +259,8 @@ class $All extends NamedGroupOperation { name ); } - next(item: any, key: Key, owner: any) { - this.childrenNext(item, key, owner); + next(item: any, key: Key, owner: any, root: boolean) { + this.childrenNext(item, key, owner, root); } } @@ -281,7 +301,9 @@ export const $in = ( owneryQuery: Query, options: Options, name: string -) => new $In(params, owneryQuery, options, name); +) => { + return new $In(params, owneryQuery, options, name); +}; export const $lt = numericalOperation(params => b => b < params); export const $lte = numericalOperation(params => b => b <= params); diff --git a/test/basic-test.js b/test/basic-test.js index c1d69c9..2aaa1a8 100644 --- a/test/basic-test.js +++ b/test/basic-test.js @@ -584,5 +584,14 @@ describe(__filename + "#", function() { } }) ); + + assert.deepEqual(result, [ + { + tags: ["animal", "dog"] + }, + { + tags: ["animal", "cat"] + } + ]); }); });