Skip to content

Commit

Permalink
refactor: general code cleanup and removing old functions.
Browse files Browse the repository at this point in the history
- remove `inArray` and `notInArray`.
- move integer size constants to internal modules.
- revert `toString` function to `stringify`.
- simplify `typeOf` implementation to work with all types.
  • Loading branch information
kofrasa committed Dec 6, 2024
1 parent ecfff7b commit a4d8152
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 271 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## 6.6.0 / 2024-11-30
## 6.5.1 / 2024-12-06

**Improvements**

Expand Down
16 changes: 14 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ import { Cursor } from "./cursor";
import { Source } from "./lazy";
import { Query } from "./query";
import { AnyObject } from "./types";
import { createUpdater, update } from "./updater";
import { createUpdater } from "./updater";

export { Aggregator } from "./aggregator";
export { Query } from "./query";
export { createUpdater, update } from "./updater";
export { createUpdater } from "./updater";

/**
* Updates the given object with the expression.
*
* @param obj The object to update.
* @param expr The update expressions.
* @param arrayFilters Filters to apply to nested items.
* @param conditions Conditions to validate before performing update.
* @param options Update options to override defaults.
* @returns {string[]} A list of modified field paths in the object.
*/
export const update = createUpdater();

/**
* Performs a query on a collection and returns a cursor object.
Expand Down
3 changes: 1 addition & 2 deletions src/operators/_predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
compare as mingoCmp,
ensureArray,
flatten,
inArray,
intersection,
isArray,
isBoolean,
Expand Down Expand Up @@ -252,7 +251,7 @@ export function $all(
for (const query of queries) {
// no need to check all the queries.
if (!matched) break;
if (isObject(query) && inArray(Object.keys(query), "$elemMatch")) {
if (isObject(query) && Object.keys(query).includes("$elemMatch")) {
matched = $elemMatch(values, query["$elemMatch"] as AnyObject, options);
} else if (isRegExp(query)) {
matched = values.some(s => typeof s === "string" && query.test(s));
Expand Down
12 changes: 9 additions & 3 deletions src/operators/expression/type/_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { computeValue, Options } from "../../../core";
import { Any, AnyObject } from "../../../types";
import { isDate, isNil, isNumber, isString } from "../../../util";

export const MAX_INT = 2147483647;
export const MIN_INT = -2147483648;
export const MAX_LONG = Number.MAX_SAFE_INTEGER;
export const MIN_LONG = Number.MIN_SAFE_INTEGER;

export class TypeConvertError extends Error {
constructor(message: string) {
super(message);
Expand All @@ -12,9 +17,8 @@ export function toInteger(
obj: AnyObject,
expr: Any,
options: Options,
max: number,
min: number,
typename: string
max: number
): number | null {
const val = computeValue(obj, expr, null, options) as
| string
Expand All @@ -37,5 +41,7 @@ export function toInteger(
}
}

throw new TypeConvertError(`cannot convert '${val}' to ${typename}`);
throw new TypeConvertError(
`cannot convert '${val}' to ${max == MAX_INT ? "int" : "long"}`
);
}
7 changes: 2 additions & 5 deletions src/operators/expression/type/toInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import { ExpressionOperator, Options } from "../../../core";
import { Any, AnyObject } from "../../../types";
import { MAX_INT, MIN_INT } from "../../../util";
import { toInteger } from "./_internal";
import { MAX_INT, MIN_INT, toInteger } from "./_internal";

/**
* Converts a value to an integer. If the value cannot be converted to an integer, $toInt errors. If the value is null or missing, $toInt returns null.
Expand All @@ -16,6 +15,4 @@ export const $toInt: ExpressionOperator = (
obj: AnyObject,
expr: Any,
options: Options
): number | null => {
return toInteger(obj, expr, options, MAX_INT, MIN_INT, "int");
};
): number | null => toInteger(obj, expr, options, MIN_INT, MAX_INT);
7 changes: 2 additions & 5 deletions src/operators/expression/type/toLong.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import { ExpressionOperator, Options } from "../../../core";
import { Any, AnyObject } from "../../../types";
import { MAX_LONG, MIN_LONG } from "../../../util";
import { toInteger } from "./_internal";
import { MAX_LONG, MIN_LONG, toInteger } from "./_internal";

/**
* Converts a value to a long. If the value cannot be converted to a long, $toLong errors. If the value is null or missing, $toLong returns null.
Expand All @@ -16,6 +15,4 @@ export const $toLong: ExpressionOperator = (
obj: AnyObject,
expr: Any,
options: Options
): number | null => {
return toInteger(obj, expr, options, MAX_LONG, MIN_LONG, "long");
};
): number | null => toInteger(obj, expr, options, MIN_LONG, MAX_LONG);
3 changes: 2 additions & 1 deletion src/operators/expression/type/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import { computeValue, ExpressionOperator, Options } from "../../../core";
import { Any, AnyObject } from "../../../types";
import { isNumber, isRegExp, MAX_INT, MIN_INT, typeOf } from "../../../util";
import { isNumber, isRegExp, typeOf } from "../../../util";
import { MAX_INT, MIN_INT } from "./_internal";

export const $type: ExpressionOperator = (
obj: AnyObject,
Expand Down
46 changes: 8 additions & 38 deletions src/operators/pipeline/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,22 @@ import {
ensureArray,
filterMissing,
has,
inArray,
into,
isArray,
isEmpty,
isMissing,
isNil,
isNumber,
isObject,
isOperator,
isPrimitive,
isString,
notInArray,
merge,
removeValue,
resolveGraph,
setValue
} from "../../util";

const DESCRIPTORS = new Set<Any>(Array.from([0, 1, false, true]));

/**
* Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document.
*
Expand All @@ -49,18 +48,14 @@ export const $project: PipelineOperator = (

// result collection
const expressionKeys = Object.keys(expr);
let idOnlyExcluded = false;

// validate inclusion and exclusion
validateExpression(expr, options);

const ID_KEY = options.idKey;

if (inArray(expressionKeys, ID_KEY)) {
const id = expr[ID_KEY];
idOnlyExcluded = id === 0 && expressionKeys.length === 1;
} else {
// if not specified the add the ID field
if (!expressionKeys.includes(ID_KEY)) {
// if not specified then add the ID field
expressionKeys.push(ID_KEY);
}

Expand Down Expand Up @@ -102,7 +97,7 @@ function processObject(
// expression to associate with key
const subExpr = expr[key];

if (key !== options.idKey && inArray([0, false], subExpr)) {
if (key !== options.idKey && (subExpr === 0 || subExpr === false)) {
foundExclusion = true;
}

Expand All @@ -111,7 +106,7 @@ function processObject(
value = obj[key];
} else if (isString(subExpr)) {
value = computeValue(obj, subExpr, key, options);
} else if (inArray([1, true], subExpr)) {
} else if (subExpr === 1 || subExpr === true) {
// For direct projections, we use the resolved object value
} else if (isArray(subExpr)) {
value = subExpr.map(v => {
Expand Down Expand Up @@ -185,7 +180,7 @@ function processObject(
}

// if computed add/or remove accordingly
if (notInArray([0, 1, false, true], subExpr)) {
if (!DESCRIPTORS.has(subExpr)) {
if (value === undefined) {
removeValue(newObj, key, { descendArray: true });
} else {
Expand Down Expand Up @@ -234,28 +229,3 @@ function validateExpression(expr: AnyObject, options: Options): void {
);
}
}

/**
* Deep merge objects or arrays. When the inputs have unmergeable types, the right hand value is returned.
* If inputs are arrays and options.flatten is set, elements in the same position are merged together.
* Remaining elements are appended to the target object.
*
* @param target Target object to merge into.
* @param input Source object to merge from.
*/
function merge(target: Any, input: Any): Any {
// take care of missing inputs
if (isMissing(target) || isNil(target)) return input;
if (isMissing(input) || isNil(input)) return target;
if (isPrimitive(target) || isPrimitive(input)) return input;
if (isArray(target) && isArray(input)) {
assert(
target.length === input.length,
"arrays must be of equal length to merge."
);
}
for (const k in input as AnyObject) {
target[k] = merge(target[k], input[k]);
}
return target;
}
12 changes: 5 additions & 7 deletions src/operators/pipeline/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { CollationSpec, Options, PipelineOperator } from "../../core";
import { Iterator } from "../../lazy";
import { Any, AnyObject, Comparator } from "../../types";
import {
assert,
compare,
groupBy,
into,
isEmpty,
isObject,
isString,
Expand Down Expand Up @@ -48,12 +48,10 @@ export const $sort: PipelineOperator = (
const sortedKeys = Array.from(groups.keys()).sort(cmp);
if (sortKeys[key] === -1) sortedKeys.reverse();

// reuse collection so the data is available for the next iteration of the sort modifiers.
coll = [];
sortedKeys.reduce(
(acc: Any[], key: Any) => into(acc, groups.get(key)),
coll
);
// modify collection in place.
let i = 0;
for (const k of sortedKeys) for (const v of groups.get(k)) coll[i++] = v;
assert(i == coll.length, "bug: counter must match collection size.");
}
return coll;
});
Expand Down
17 changes: 6 additions & 11 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import { getOperator, initOptions, Options, QueryOperator } from "./core";
import { Cursor } from "./cursor";
import { Source } from "./lazy";
import { Any, AnyObject, Callback, Predicate } from "./types";
import {
assert,
inArray,
isObject,
isOperator,
MingoError,
normalize
} from "./util";
import { assert, isObject, isOperator, MingoError, normalize } from "./util";

const TOP_LEVEL_OPS = new Set(
Array.from(["$and", "$or", "$nor", "$expr", "$jsonSchema"])
);

/**
* An object used to filter input documents
Expand Down Expand Up @@ -41,9 +38,7 @@ export class Query {
for (const [field, expr] of Object.entries(this.#condition)) {
if ("$where" === field) {
Object.assign(whereOperator, { field: field, expr: expr });
} else if (
inArray(["$and", "$or", "$nor", "$expr", "$jsonSchema"], field)
) {
} else if (TOP_LEVEL_OPS.has(field)) {
this.processOperator(field, field, expr);
} else {
// normalize expression
Expand Down
15 changes: 2 additions & 13 deletions src/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,6 @@ export function createUpdater(defaultOptions?: UpdateOptions): Updater {
}

/**
* Updates the given object with the expression.
*
* @param obj The object to update.
* @param expr The update expressions.
* @param arrayFilters Filters to apply to nested items.
* @param conditions Conditions to validate before performing update.
* @param options Update options to override defaults.
* @returns {string[]} A list of modified field paths in the object.
* @deprecated Use {@link update}.
*/
export const update = createUpdater();
/**
* @deprecated Alias to {@link update}
*/
export const updateObject = update;
export const updateObject = createUpdater();
Loading

0 comments on commit a4d8152

Please sign in to comment.