mongo-dot-notation
Version:
Transform objects to MongoDB update instructions
113 lines (112 loc) • 3.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.flatten = void 0;
const operator_1 = require("./operator");
const mergeOptions = (options) => (Object.assign({ array: false, skipEmptyObjects: false }, options));
/**
* Transforms a given object into the MongoDB's update instructions.
* @param value the input object to transform
*
* @example
* ```ts
* flatten({
* first_name: $rename('firstName'),
* lastName: 'Doe',
* updatedOn: $timestamp(),
* address: {
* primary: true
* },
* access: $push(new Date()),
* });
*
* // equivalent to:
* {
* "$rename": {
* "first_name": "firstName"
* },
* "$set": {
* "lastName": "Doe",
* "address.primary": true
* },
* "$currentDate": {
* "updatedOn": {
* "$type": "timestamp"
* }
* },
* "$push": {
* "access": new Date()
* }
* }
* ```
*
* @example
* ```ts
* // update arrays
* flatten({ scores: [{ value: 4 }, {value: 2}] }, { array: true} );
*
* // equivalent to:
* {
* "$set": {
* "scores.0.value": 4,
* "scores.1.value": 2,
* }
* }
* ```
*/
const flatten = (value, options) => {
if (isAtomic(value) || Array.isArray(value)) {
return value;
}
const keyValues = Object.entries(value);
if (keyValues.length === 0) {
return value;
}
const d = dot(mergeOptions(options));
return keyValues.reduce((acc, [key, value]) => d(acc, key, value), {});
};
exports.flatten = flatten;
const mergeInner = (obj, field, inner, value) => (Object.assign(Object.assign({}, obj), { [field]: Object.assign(Object.assign({}, obj[field]), { [inner]: value }) }));
const dot = (options) => {
const merge = (instructions, operator, field, value) => {
if (!(0, operator_1.isOperator)(value)) {
return mergeInner(instructions, operator, field, value);
}
if ((0, operator_1.getType)(value) === 'merge') {
const mergeValue = (0, operator_1.getValue)(value);
return isNullOrUndefined(mergeValue)
? instructions
: dotMerge(instructions, `${field}.${operator}`, mergeValue);
}
return mergeInner(instructions, (0, operator_1.getType)(value), `${field}.${operator}`, (0, operator_1.getValue)(value));
};
const visit = (instructions, field, value) => {
const tail = isAtomic(value) || (Array.isArray(value) && !options.array);
if (tail) {
return merge(instructions, '$set', field, value);
}
if ((0, operator_1.isOperator)(value)) {
return merge(instructions, (0, operator_1.getType)(value), field, (0, operator_1.getValue)(value));
}
const keyValues = Object.entries(value);
if (keyValues.length === 0) {
const ignoreValue = (Array.isArray(value) && options.array) || options.skipEmptyObjects;
return ignoreValue ? instructions : merge(instructions, '$set', field, value);
}
return keyValues.reduce((acc, [key, value]) => visit(acc, `${field}.${key}`, value), instructions);
};
return visit;
};
const dotMerge = dot({ array: true, skipEmptyObjects: true });
const isAtomic = (value) => isPrimitive(value) || isBsonType(value);
const TYPEOF_PRIMITIVES = ['number', 'string', 'boolean', 'symbol', 'bigint'];
/* istanbul ignore next */
const INSTANCEOF_PRIMITIVES = [Date, RegExp, typeof Buffer !== undefined ? Buffer : []]
.flat()
.filter(Boolean);
const isPrimitive = (value) => {
return (isNullOrUndefined(value) ||
TYPEOF_PRIMITIVES.some((type) => typeof value === type) ||
INSTANCEOF_PRIMITIVES.some((type) => value instanceof type));
};
const isNullOrUndefined = (value) => value === null || typeof value === 'undefined';
const isBsonType = (value) => '_bsontype' in value;