datum-focus
Version:
Data shape, model, metadata, JSON, JSON Schema, GraphQL, MongoDB query and aggregations, iterator generators
210 lines (185 loc) • 4.97 kB
text/typescript
import { isArray } from '../../array';
import { isNumber } from '../../number';
import { isString } from '../../string';
import {
containsOperation, createQueryOperation, createTester, EqualsOperation, NamedBaseOperation, Options, Query, QueryOperation, Tester
} from "./core";
import { comparable, isFunction, Key } from "./utils";
// https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
class $ElemMatch extends NamedBaseOperation<Query<any>> {
private _queryOperation!: QueryOperation<any>;
init() {
this._queryOperation = createQueryOperation(
this.params,
this.owneryQuery,
this.options
);
}
reset() {
super.reset();
this._queryOperation.reset();
}
next(item: any) {
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;
}
}
}
export class $Size extends NamedBaseOperation<any> {
init() { }
next(item: any) {
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 $In extends NamedBaseOperation<any> {
private _testers!: Tester[];
init() {
this._testers = this.params.map((value: any) => {
if (containsOperation(value)) {
throw new Error(
`cannot nest $ under ${this.constructor.name.toLowerCase()}`
);
}
return createTester(value, this.options.compare);
});
}
next(item: any, key: Key, owner: any) {
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: any, key: Key, owner: any) {
super.next(item, key, owner);
this.keep = !this.keep;
}
}
class $Exists extends NamedBaseOperation<boolean> {
next(item: any, key: Key, owner: any) {
if (owner.hasOwnProperty(key) === this.params) {
this.done = true;
this.keep = true;
}
}
}
export const $elemMatch = (
params: any,
owneryQuery: Query<any>,
options: Options,
name: string
) => new $ElemMatch(params, owneryQuery, options, name);
export const $nin = (
params: any,
owneryQuery: Query<any>,
options: Options,
name: string
) => new $Nin(params, owneryQuery, options, name);
export const $in = (
params: any,
owneryQuery: Query<any>,
options: Options,
name: string
) => new $In(params, owneryQuery, options, name);
export const $mod = (
[mod, equalsValue]: number[],
owneryQuery: Query<any>,
options: Options
) =>
new EqualsOperation(
(b: any) => <number>comparable(b) % mod === equalsValue,
owneryQuery,
options
);
export const $exists = (
params: boolean,
owneryQuery: Query<any>,
options: Options,
name: string
) => new $Exists(params, owneryQuery, options, name);
export const $regex = (
pattern: string,
owneryQuery: Query<any>,
options: Options
) =>
new EqualsOperation(
new RegExp(pattern, owneryQuery.$options),
owneryQuery,
options
);
const typeAliases: Record<string, (v: any) => boolean> = {
number: isNumber,
string: isString,
bool: (v: any) => typeof v === "boolean",
array: isArray,
null: (v: any) => v === null,
timestamp: (v: any) => v instanceof Date
};
export const $type = (
clazz: Function | string,
owneryQuery: Query<any>,
options: Options
) =>
new EqualsOperation(
(b: any) => {
if (isString(clazz)) {
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
);
export const $size = (
params: number,
ownerQuery: Query<any>,
options: Options
) => new $Size(params, ownerQuery, options, "$size");
export const $options = () => null;
export const $where = (
params: string | Function,
ownerQuery: Query<any>,
options: Options
) => {
let test: any;
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: any) => test.bind(b)(b), ownerQuery, options);
};