412 lines
9.7 KiB
TypeScript
412 lines
9.7 KiB
TypeScript
import {
|
|
BaseOperation,
|
|
EqualsOperation,
|
|
Options,
|
|
createTester,
|
|
Tester,
|
|
createQueryOperation,
|
|
QueryOperation,
|
|
Operation,
|
|
Query,
|
|
NamedGroupOperation,
|
|
numericalOperation,
|
|
containsOperation,
|
|
NamedOperation
|
|
} from "./core";
|
|
import { Key, comparable, isFunction, isArray } from "./utils";
|
|
|
|
class $Ne extends BaseOperation<any> {
|
|
readonly propop = true;
|
|
private _test: Tester;
|
|
init() {
|
|
this._test = createTester(this.params, this.options.compare);
|
|
}
|
|
reset() {
|
|
super.reset();
|
|
this.keep = true;
|
|
}
|
|
next(item: any) {
|
|
if (this._test(item)) {
|
|
this.done = true;
|
|
this.keep = false;
|
|
}
|
|
}
|
|
}
|
|
// https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
|
|
class $ElemMatch extends BaseOperation<Query<any>> {
|
|
readonly propop = true;
|
|
private _queryOperation: QueryOperation<any>;
|
|
init() {
|
|
if (!this.params || typeof this.params !== "object") {
|
|
throw new Error(`Malformed query. $elemMatch must by an object.`);
|
|
}
|
|
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();
|
|
|
|
const child = item[i];
|
|
this._queryOperation.next(child, i, item, false);
|
|
this.keep = this.keep || this._queryOperation.keep;
|
|
}
|
|
this.done = true;
|
|
} else {
|
|
this.done = false;
|
|
this.keep = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
class $Not extends BaseOperation<Query<any>> {
|
|
readonly propop = true;
|
|
private _queryOperation: QueryOperation<any>;
|
|
init() {
|
|
this._queryOperation = createQueryOperation(
|
|
this.params,
|
|
this.owneryQuery,
|
|
this.options
|
|
);
|
|
}
|
|
reset() {
|
|
super.reset();
|
|
this._queryOperation.reset();
|
|
}
|
|
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 BaseOperation<any> {
|
|
readonly propop = true;
|
|
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;
|
|
// }
|
|
}
|
|
}
|
|
|
|
const assertGroupNotEmpty = (values: any[]) => {
|
|
if (values.length === 0) {
|
|
throw new Error(`$and/$or/$nor must be a nonempty array`);
|
|
}
|
|
};
|
|
|
|
class $Or extends BaseOperation<any> {
|
|
readonly propop = false;
|
|
private _ops: Operation<any>[];
|
|
init() {
|
|
assertGroupNotEmpty(this.params);
|
|
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: any, key: Key, owner: any) {
|
|
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 {
|
|
readonly propop = false;
|
|
next(item: any, key: Key, owner: any) {
|
|
super.next(item, key, owner);
|
|
this.keep = !this.keep;
|
|
}
|
|
}
|
|
|
|
class $In extends BaseOperation<any> {
|
|
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.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 BaseOperation<any> {
|
|
readonly propop = true;
|
|
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 BaseOperation<boolean> {
|
|
readonly propop = true;
|
|
next(item: any, key: Key, owner: any) {
|
|
if (owner.hasOwnProperty(key) === this.params) {
|
|
this.done = true;
|
|
this.keep = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
class $And extends NamedGroupOperation {
|
|
readonly propop = false;
|
|
constructor(
|
|
params: Query<any>[],
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) {
|
|
super(
|
|
params,
|
|
owneryQuery,
|
|
options,
|
|
params.map(query => createQueryOperation(query, owneryQuery, options)),
|
|
name
|
|
);
|
|
|
|
assertGroupNotEmpty(params);
|
|
}
|
|
next(item: any, key: Key, owner: any, root: boolean) {
|
|
this.childrenNext(item, key, owner, root);
|
|
}
|
|
}
|
|
|
|
class $All extends NamedGroupOperation {
|
|
readonly propop = true;
|
|
constructor(
|
|
params: Query<any>[],
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) {
|
|
super(
|
|
params,
|
|
owneryQuery,
|
|
options,
|
|
params.map(query => createQueryOperation(query, owneryQuery, options)),
|
|
name
|
|
);
|
|
}
|
|
next(item: any, key: Key, owner: any, root: boolean) {
|
|
this.childrenNext(item, key, owner, root);
|
|
}
|
|
}
|
|
|
|
export const $eq = (params: any, owneryQuery: Query<any>, options: Options) =>
|
|
new EqualsOperation(params, owneryQuery, options);
|
|
export const $ne = (
|
|
params: any,
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => new $Ne(params, owneryQuery, options, name);
|
|
export const $or = (
|
|
params: Query<any>[],
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => new $Or(params, owneryQuery, options, name);
|
|
export const $nor = (
|
|
params: Query<any>[],
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => new $Nor(params, owneryQuery, options, name);
|
|
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
|
|
) => {
|
|
return new $In(params, owneryQuery, options, name);
|
|
};
|
|
|
|
export const $lt = numericalOperation(params => b => b < params);
|
|
export const $lte = numericalOperation(params => b => b <= params);
|
|
export const $gt = numericalOperation(params => b => b > params);
|
|
export const $gte = numericalOperation(params => b => b >= params);
|
|
export const $mod = (
|
|
[mod, equalsValue]: number[],
|
|
owneryQuery: Query<any>,
|
|
options: Options
|
|
) =>
|
|
new EqualsOperation(
|
|
b => 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
|
|
);
|
|
export const $not = (
|
|
params: any,
|
|
owneryQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => 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
|
|
};
|
|
|
|
export const $type = (
|
|
clazz: Function | string,
|
|
owneryQuery: Query<any>,
|
|
options: 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
|
|
);
|
|
export const $and = (
|
|
params: Query<any>[],
|
|
ownerQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => new $And(params, ownerQuery, options, name);
|
|
|
|
export const $all = (
|
|
params: Query<any>[],
|
|
ownerQuery: Query<any>,
|
|
options: Options,
|
|
name: string
|
|
) => new $All(params, ownerQuery, options, name);
|
|
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;
|
|
|
|
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);
|
|
};
|