modifyjs
Version:
Modify your objects with a mongo syntax.
865 lines (788 loc) • 29.8 kB
JavaScript
;
var underscore = require('underscore');
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
MinimongoError = function MinimongoError(message) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (typeof message === "string" && options.field) {
message += " for field '" + options.field + "'";
}
var e = new Error(message);
e.name = "MinimongoError";
return e;
};
var MinimongoError$1 = MinimongoError;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
// Make sure field names do not contain Mongo restricted
// characters ('.', '$', '\0').
// https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
var invalidCharMsg = {
'.': "contain '.'",
'$': "start with '$'",
'\0': "contain null bytes"
};
function assertIsValidFieldName(key) {
var match = void 0;
if (underscore.isString(key) && (match = key.match(/^\$|\.|\0/))) {
throw MinimongoError$1('Key ' + key + ' must not ' + invalidCharMsg[match[0]]);
}
}
// checks if all field names in an object are valid
function assertHasValidFieldNames(doc) {
if (doc && (typeof doc === 'undefined' ? 'undefined' : _typeof(doc)) === "object") {
JSON.stringify(doc, function (key, value) {
assertIsValidFieldName(key);
return value;
});
}
}
EJSON = {};
EJSONTest = {};
// Add a custom type, using a method of your choice to get to and
// from a basic JSON-able representation. The factory argument
// is a function of JSON-able --> your object
// The type you add must have:
// - A toJSONValue() method, so that Meteor can serialize it
// - a typeName() method, to show how to look it up in our type table.
// It is okay if these methods are monkey-patched on.
// EJSON.clone will use toJSONValue and the given factory to produce
// a clone, but you may specify a method clone() that will be
// used instead.
// Similarly, EJSON.equals will use toJSONValue to make comparisons,
// but you may provide a method equals() instead.
/**
* @summary Add a custom datatype to EJSON.
* @locus Anywhere
* @param {String} name A tag for your custom type; must be unique among custom data types defined in your project, and must match the result of your type's `typeName` method.
* @param {Function} factory A function that deserializes a JSON-compatible value into an instance of your type. This should match the serialization performed by your type's `toJSONValue` method.
*/
var _$2 = { has: underscore.has, isNaN: underscore.isNaN, size: underscore.size, isEmpty: underscore.isEmpty, any: underscore.any, each: underscore.each, all: underscore.all, isArguments: underscore.isArguments, isArray: underscore.isArray };
EJSON.isBinary = function (obj) {
return !!(typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array || obj && obj.$Uint8ArrayPolyfill);
};
/**
* @summary Return true if `a` and `b` are equal to each other. Return false otherwise. Uses the `equals` method on `a` if present, otherwise performs a deep comparison.
* @locus Anywhere
* @param {EJSON} a
* @param {EJSON} b
* @param {Object} [options]
* @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`.
*/
EJSON.equals = function (a, b, options) {
var i;
var keyOrderSensitive = !!(options && options.keyOrderSensitive);
if (a === b) return true;
if (_$2.isNaN(a) && _$2.isNaN(b)) return true; // This differs from the IEEE spec for NaN equality, b/c we don't want
// anything ever with a NaN to be poisoned from becoming equal to anything.
if (!a || !b) // if either one is falsy, they'd have to be === to be equal
return false;
if (!((typeof a === 'undefined' ? 'undefined' : _typeof(a)) === 'object' && (typeof b === 'undefined' ? 'undefined' : _typeof(b)) === 'object')) return false;
if (a instanceof Date && b instanceof Date) return a.valueOf() === b.valueOf();
if (EJSON.isBinary(a) && EJSON.isBinary(b)) {
if (a.length !== b.length) return false;
for (i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
if (typeof a.equals === 'function') return a.equals(b, options);
if (typeof b.equals === 'function') return b.equals(a, options);
if (a instanceof Array) {
if (!(b instanceof Array)) return false;
if (a.length !== b.length) return false;
for (i = 0; i < a.length; i++) {
if (!EJSON.equals(a[i], b[i], options)) return false;
}
return true;
}
// fall back to structural equality of objects
var ret;
if (keyOrderSensitive) {
var bKeys = [];
_$2.each(b, function (val, x) {
bKeys.push(x);
});
i = 0;
ret = _$2.all(a, function (val, x) {
if (i >= bKeys.length) {
return false;
}
if (x !== bKeys[i]) {
return false;
}
if (!EJSON.equals(val, b[bKeys[i]], options)) {
return false;
}
i++;
return true;
});
return ret && i === bKeys.length;
} else {
i = 0;
ret = _$2.all(a, function (val, key) {
if (!_$2.has(b, key)) {
return false;
}
if (!EJSON.equals(val, b[key], options)) {
return false;
}
i++;
return true;
});
return ret && _$2.size(b) === i;
}
};
/**
* @summary Return a deep copy of `val`.
* @locus Anywhere
* @param {EJSON} val A value to copy.
*/
EJSON.clone = function (v) {
var ret;
if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) !== "object") return v;
if (v === null) return null; // null has typeof "object"
if (v instanceof Date) return new Date(v.getTime());
// RegExps are not really EJSON elements (eg we don't define a serialization
// for them), but they're immutable anyway, so we can support them in clone.
if (v instanceof RegExp) return v;
if (EJSON.isBinary(v)) {
ret = EJSON.newBinary(v.length);
for (var i = 0; i < v.length; i++) {
ret[i] = v[i];
}
return ret;
}
// XXX: Use something better than underscore's isArray
if (_$2.isArray(v) || _$2.isArguments(v)) {
// For some reason, _.map doesn't work in this context on Opera (weird test
// failures).
ret = [];
for (i = 0; i < v.length; i++) {
ret[i] = EJSON.clone(v[i]);
}return ret;
}
// handle general user-defined typed Objects if they have a clone method
if (typeof v.clone === 'function') {
return v.clone();
}
// handle other objects
ret = {};
_$2.each(v, function (value, key) {
ret[key] = EJSON.clone(value);
});
return ret;
};
var _f = {
// XXX for _all and _in, consider building 'inquery' at compile time..
_type: function _type(v) {
if (typeof v === "number") return 1;
if (typeof v === "string") return 2;
if (typeof v === "boolean") return 8;
if (isArray(v)) return 4;
if (v === null) return 10;
if (v instanceof RegExp)
// note that typeof(/x/) === "object"
return 11;
if (typeof v === "function") return 13;
if (v instanceof Date) return 9;
if (EJSON.isBinary(v)) return 5;
return 3; // object
// XXX support some/all of these:
// 14, symbol
// 15, javascript code with scope
// 16, 18: 32-bit/64-bit integer
// 17, timestamp
// 255, minkey
// 127, maxkey
},
// deep equality test: use for literal document and array matches
_equal: function _equal(a, b) {
return EJSON.equals(a, b, { keyOrderSensitive: true });
},
// maps a type code to a value that can be used to sort values of
// different types
_typeorder: function _typeorder(t) {
// http://www.mongodb.org/display/DOCS/What+is+the+Compare+Order+for+BSON+Types
// XXX what is the correct sort position for Javascript code?
// ('100' in the matrix below)
// XXX minkey/maxkey
return [-1, // (not a type)
1, // number
2, // string
3, // object
4, // array
5, // binary
-1, // deprecated
6, // ObjectID
7, // bool
8, // Date
0, // null
9, // RegExp
-1, // deprecated
100, // JS code
2, // deprecated (symbol)
100, // JS code
1, // 32-bit int
8, // Mongo timestamp
1 // 64-bit int
][t];
},
// compare two values of unknown type according to BSON ordering
// semantics. (as an extension, consider 'undefined' to be less than
// any other value.) return negative if a is less, positive if b is
// less, or 0 if equal
_cmp: function _cmp(a, b) {
if (a === undefined) return b === undefined ? 0 : -1;
if (b === undefined) return 1;
var ta = this._type(a);
var tb = this._type(b);
var oa = this._typeorder(ta);
var ob = this._typeorder(tb);
if (oa !== ob) return oa < ob ? -1 : 1;
if (ta !== tb)
// XXX need to implement this if we implement Symbol or integers, or
// Timestamp
throw Error("Missing type coercion logic in _cmp");
if (ta === 7) {
// ObjectID
// Convert to string.
ta = tb = 2;
a = a.toHexString();
b = b.toHexString();
}
if (ta === 9) {
// Date
// Convert to millis.
ta = tb = 1;
a = a.getTime();
b = b.getTime();
}
if (ta === 1) // double
return a - b;
if (tb === 2) // string
return a < b ? -1 : a === b ? 0 : 1;
if (ta === 3) {
// Object
// this could be much more efficient in the expected case ...
var to_array = function to_array(obj) {
var ret = [];
for (var key in obj) {
ret.push(key);
ret.push(obj[key]);
}
return ret;
};
return this._cmp(to_array(a), to_array(b));
}
if (ta === 4) {
// Array
for (var i = 0;; i++) {
if (i === a.length) return i === b.length ? 0 : -1;
if (i === b.length) return 1;
var s = this._cmp(a[i], b[i]);
if (s !== 0) return s;
}
}
if (ta === 5) {
// binary
// Surprisingly, a small binary blob is always less than a large one in
// Mongo.
if (a.length !== b.length) return a.length - b.length;
for (i = 0; i < a.length; i++) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return 0;
}
if (ta === 8) {
// boolean
if (a) return b ? 0 : 1;
return b ? -1 : 0;
}
if (ta === 10) // null
return 0;
if (ta === 11) // regexp
throw Error("Sorting not supported on regular expression"); // XXX
// 13: javascript code
// 14: symbol
// 15: javascript code with scope
// 16: 32-bit integer
// 17: timestamp
// 18: 64-bit integer
// 255: minkey
// 127: maxkey
if (ta === 13) // javascript code
throw Error("Sorting not supported on Javascript code"); // XXX
throw Error("Unknown type to sort");
}
};
var _$1 = { isArray: underscore.isArray, each: underscore.each };
// Like _.isArray, but doesn't regard polyfilled Uint8Arrays on old browsers as
// arrays.
// XXX maybe this should be EJSON.isArray
isArray = function isArray(x) {
return _$1.isArray(x) && !EJSON.isBinary(x);
};
// XXX maybe this should be EJSON.isObject, though EJSON doesn't know about
// RegExp
// XXX note that _type(undefined) === 3!!!!
isPlainObject = function isPlainObject(x) {
return x && _f._type(x) === 3;
};
isIndexable = function isIndexable(x) {
return isArray(x) || isPlainObject(x);
};
// Returns true if this is an object with at least one key and all keys begin
// with $. Unless inconsistentOK is set, throws if some keys begin with $ and
// others don't.
isOperatorObject = function isOperatorObject(valueSelector, inconsistentOK) {
if (!isPlainObject(valueSelector)) return false;
var theseAreOperators = undefined;
_$1.each(valueSelector, function (value, selKey) {
var thisIsOperator = selKey.substr(0, 1) === '$';
if (theseAreOperators === undefined) {
theseAreOperators = thisIsOperator;
} else if (theseAreOperators !== thisIsOperator) {
if (!inconsistentOK) throw new Error("Inconsistent operator: " + JSON.stringify(valueSelector));
theseAreOperators = false;
}
});
return !!theseAreOperators; // {} has no operators
};
// string can be converted to integer
isNumericKey = function isNumericKey(s) {
return (/^[0-9]+$/.test(s)
);
};
var _ = { all: underscore.all, each: underscore.each, keys: underscore.keys, has: underscore.has, isObject: isObject };
// XXX need a strategy for passing the binding of $ into this
// function, from the compiled selector
//
// maybe just {key.up.to.just.before.dollarsign: array_index}
//
// XXX atomicity: if one modification fails, do we roll back the whole
// change?
//
// options:
// - isInsert is set when _modify is being called to compute the document to
// insert as part of an upsert operation. We use this primarily to figure
// out when to set the fields in $setOnInsert, if present.
var modify = function (doc, mod, options) {
return LocalCollection._modify(doc, mod, _extends({}, options, { returnInsteadOfReplacing: true }));
};
LocalCollection = window && window.LocalCollection || global && global.LocalCollection || {};
LocalCollection._modify = function (doc, mod, options) {
options = options || {};
if (!isPlainObject(mod)) throw MinimongoError$1("Modifier must be an object");
// Make sure the caller can't mutate our data structures.
mod = EJSON.clone(mod);
var isModifier = isOperatorObject(mod);
var newDoc;
if (!isModifier) {
// replace the whole document
assertHasValidFieldNames(mod);
newDoc = mod;
} else {
// apply modifiers to the doc.
newDoc = EJSON.clone(doc);
_.each(mod, function (operand, op) {
var modFunc = MODIFIERS[op];
// Treat $setOnInsert as $set if this is an insert.
if (!modFunc) throw MinimongoError$1("Invalid modifier specified " + op);
_.each(operand, function (arg, keypath) {
if (keypath === '') {
throw MinimongoError$1("An empty update path is not valid.");
}
var keyparts = keypath.split('.');
if (!underscore.all(keyparts)) {
throw MinimongoError$1("The update path '" + keypath + "' contains an empty field name, which is not allowed.");
}
var noCreate = _.has(NO_CREATE_MODIFIERS, op);
var forbidArray = op === "$rename";
var target = findModTarget(newDoc, keyparts, {
noCreate: NO_CREATE_MODIFIERS[op],
forbidArray: op === "$rename",
arrayIndices: options.arrayIndices
});
var field = keyparts.pop();
modFunc(target, field, arg, keypath, newDoc);
});
});
}
if (options.returnInsteadOfReplacing) {
return newDoc;
} else {
// move new document into place.
_.each(_.keys(doc), function (k) {
// Note: this used to be for (var k in doc) however, this does not
// work right in Opera. Deleting from a doc while iterating over it
// would sometimes cause opera to skip some keys.
if (k !== '_id') delete doc[k];
});
_.each(newDoc, function (v, k) {
doc[k] = v;
});
}
};
// for a.b.c.2.d.e, keyparts should be ['a', 'b', 'c', '2', 'd', 'e'],
// and then you would operate on the 'e' property of the returned
// object.
//
// if options.noCreate is falsey, creates intermediate levels of
// structure as necessary, like mkdir -p (and raises an exception if
// that would mean giving a non-numeric property to an array.) if
// options.noCreate is true, return undefined instead.
//
// may modify the last element of keyparts to signal to the caller that it needs
// to use a different value to index into the returned object (for example,
// ['a', '01'] -> ['a', 1]).
//
// if forbidArray is true, return null if the keypath goes through an array.
//
// if options.arrayIndices is set, use its first element for the (first) '$' in
// the path.
var findModTarget = function findModTarget(doc, keyparts, options) {
options = options || {};
var usedArrayIndex = false;
for (var i = 0; i < keyparts.length; i++) {
var last = i === keyparts.length - 1;
var keypart = keyparts[i];
var indexable = isIndexable(doc);
if (!indexable) {
if (options.noCreate) return undefined;
var e = MinimongoError$1("cannot use the part '" + keypart + "' to traverse " + doc);
e.setPropertyError = true;
throw e;
}
if (doc instanceof Array) {
if (options.forbidArray) return null;
if (keypart === '$') {
if (usedArrayIndex) throw MinimongoError$1("Too many positional (i.e. '$') elements");
if (!options.arrayIndices || !options.arrayIndices.length) {
throw MinimongoError$1("The positional operator did not find the " + "match needed from the query");
}
keypart = options.arrayIndices[0];
usedArrayIndex = true;
} else if (isNumericKey(keypart)) {
keypart = parseInt(keypart);
} else {
if (options.noCreate) return undefined;
throw MinimongoError$1("can't append to array using string field name [" + keypart + "]");
}
if (last)
// handle 'a.01'
keyparts[i] = keypart;
if (options.noCreate && keypart >= doc.length) return undefined;
while (doc.length < keypart) {
doc.push(null);
}if (!last) {
if (doc.length === keypart) doc.push({});else if (_typeof(doc[keypart]) !== "object") throw MinimongoError$1("can't modify field '" + keyparts[i + 1] + "' of list value " + JSON.stringify(doc[keypart]));
}
} else {
assertIsValidFieldName(keypart);
if (!(keypart in doc)) {
if (options.noCreate) return undefined;
if (!last) doc[keypart] = {};
}
}
if (last) return doc;
doc = doc[keypart];
}
// notreached
};
var NO_CREATE_MODIFIERS = {
$unset: true,
$pop: true,
$rename: true,
$pull: true,
$pullAll: true
};
var MODIFIERS = {
$currentDate: function $currentDate(target, field, arg) {
if ((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === "object" && arg.hasOwnProperty("$type")) {
if (arg.$type !== "date") {
throw MinimongoError$1("Minimongo does currently only support the date type " + "in $currentDate modifiers", { field: field });
}
} else if (arg !== true) {
throw MinimongoError$1("Invalid $currentDate modifier", { field: field });
}
target[field] = new Date();
},
$min: function $min(target, field, arg) {
if (typeof arg !== "number") {
throw MinimongoError$1("Modifier $min allowed for numbers only", { field: field });
}
if (field in target) {
if (typeof target[field] !== "number") {
throw MinimongoError$1("Cannot apply $min modifier to non-number", { field: field });
}
if (target[field] > arg) {
target[field] = arg;
}
} else {
target[field] = arg;
}
},
$max: function $max(target, field, arg) {
if (typeof arg !== "number") {
throw MinimongoError$1("Modifier $max allowed for numbers only", { field: field });
}
if (field in target) {
if (typeof target[field] !== "number") {
throw MinimongoError$1("Cannot apply $max modifier to non-number", { field: field });
}
if (target[field] < arg) {
target[field] = arg;
}
} else {
target[field] = arg;
}
},
$inc: function $inc(target, field, arg) {
if (typeof arg !== "number") throw MinimongoError$1("Modifier $inc allowed for numbers only", { field: field });
if (field in target) {
if (typeof target[field] !== "number") throw MinimongoError$1("Cannot apply $inc modifier to non-number", { field: field });
target[field] += arg;
} else {
target[field] = arg;
}
},
$set: function $set(target, field, arg) {
if (!_.isObject(target)) {
// not an array or an object
var e = MinimongoError$1("Cannot set property on non-object field", { field: field });
e.setPropertyError = true;
throw e;
}
if (target === null) {
var e = MinimongoError$1("Cannot set property on null", { field: field });
e.setPropertyError = true;
throw e;
}
assertHasValidFieldNames(arg);
target[field] = arg;
},
$setOnInsert: function $setOnInsert(target, field, arg) {
// converted to `$set` in `_modify`
},
$unset: function $unset(target, field, arg) {
if (target !== undefined) {
if (target instanceof Array) {
if (field in target) target[field] = null;
} else delete target[field];
}
},
$push: function $push(target, field, arg) {
if (target[field] === undefined) target[field] = [];
if (!(target[field] instanceof Array)) throw MinimongoError$1("Cannot apply $push modifier to non-array", { field: field });
if (!(arg && arg.$each)) {
// Simple mode: not $each
assertHasValidFieldNames(arg);
target[field].push(arg);
return;
}
// Fancy mode: $each (and maybe $slice and $sort and $position)
var toPush = arg.$each;
if (!(toPush instanceof Array)) throw MinimongoError$1("$each must be an array", { field: field });
assertHasValidFieldNames(toPush);
// Parse $position
var position = undefined;
if ('$position' in arg) {
if (typeof arg.$position !== "number") throw MinimongoError$1("$position must be a numeric value", { field: field });
// XXX should check to make sure integer
if (arg.$position < 0) throw MinimongoError$1("$position in $push must be zero or positive", { field: field });
position = arg.$position;
}
// Parse $slice.
var slice = undefined;
if ('$slice' in arg) {
if (typeof arg.$slice !== "number") throw MinimongoError$1("$slice must be a numeric value", { field: field });
// XXX should check to make sure integer
if (arg.$slice > 0) throw MinimongoError$1("$slice in $push must be zero or negative", { field: field });
slice = arg.$slice;
}
// Parse $sort.
var sortFunction = undefined;
if (arg.$sort) {
if (slice === undefined) throw MinimongoError$1("$sort requires $slice to be present", { field: field });
// XXX this allows us to use a $sort whose value is an array, but that's
// actually an extension of the Node driver, so it won't work
// server-side. Could be confusing!
// XXX is it correct that we don't do geo-stuff here?
sortFunction = new Minimongo.Sorter(arg.$sort).getComparator();
for (var i = 0; i < toPush.length; i++) {
if (_f._type(toPush[i]) !== 3) {
throw MinimongoError$1("$push like modifiers using $sort " + "require all elements to be objects", { field: field });
}
}
}
// Actually push.
if (position === undefined) {
for (var j = 0; j < toPush.length; j++) {
target[field].push(toPush[j]);
}
} else {
var spliceArguments = [position, 0];
for (var j = 0; j < toPush.length; j++) {
spliceArguments.push(toPush[j]);
}Array.prototype.splice.apply(target[field], spliceArguments);
}
// Actually sort.
if (sortFunction) target[field].sort(sortFunction);
// Actually slice.
if (slice !== undefined) {
if (slice === 0) target[field] = []; // differs from Array.slice!
else target[field] = target[field].slice(slice);
}
},
$pushAll: function $pushAll(target, field, arg) {
if (!((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === "object" && arg instanceof Array)) throw MinimongoError$1("Modifier $pushAll/pullAll allowed for arrays only");
assertHasValidFieldNames(arg);
var x = target[field];
if (x === undefined) target[field] = arg;else if (!(x instanceof Array)) throw MinimongoError$1("Cannot apply $pushAll modifier to non-array", { field: field });else {
for (var i = 0; i < arg.length; i++) {
x.push(arg[i]);
}
}
},
$addToSet: function $addToSet(target, field, arg) {
var isEach = false;
if ((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === "object") {
//check if first key is '$each'
var _keys = Object.keys(arg);
if (_keys[0] === "$each") {
isEach = true;
}
}
var values = isEach ? arg["$each"] : [arg];
assertHasValidFieldNames(values);
var x = target[field];
if (x === undefined) target[field] = values;else if (!(x instanceof Array)) throw MinimongoError$1("Cannot apply $addToSet modifier to non-array", { field: field });else {
_.each(values, function (value) {
for (var i = 0; i < x.length; i++) {
if (_f._equal(value, x[i])) return;
}x.push(value);
});
}
},
$pop: function $pop(target, field, arg) {
if (target === undefined) return;
var x = target[field];
if (x === undefined) return;else if (!(x instanceof Array)) throw MinimongoError$1("Cannot apply $pop modifier to non-array", { field: field });else {
if (typeof arg === 'number' && arg < 0) x.splice(0, 1);else x.pop();
}
},
$pull: function $pull(target, field, arg) {
if (target === undefined) return;
var x = target[field];
if (x === undefined) return;else if (!(x instanceof Array)) throw MinimongoError$1("Cannot apply $pull/pullAll modifier to non-array", { field: field });else {
var out = [];
if (arg != null && (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === "object" && !(arg instanceof Array)) {
// XXX would be much nicer to compile this once, rather than
// for each document we modify.. but usually we're not
// modifying that many documents, so we'll let it slide for
// now
// XXX Minimongo.Matcher isn't up for the job, because we need
// to permit stuff like {$pull: {a: {$gt: 4}}}.. something
// like {$gt: 4} is not normally a complete selector.
// same issue as $elemMatch possibly?
var matcher = new Minimongo.Matcher(arg);
for (var i = 0; i < x.length; i++) {
if (!matcher.documentMatches(x[i]).result) out.push(x[i]);
}
} else {
for (var i = 0; i < x.length; i++) {
if (!_f._equal(x[i], arg)) out.push(x[i]);
}
}
target[field] = out;
}
},
$pullAll: function $pullAll(target, field, arg) {
if (!((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === "object" && arg instanceof Array)) throw MinimongoError$1("Modifier $pushAll/pullAll allowed for arrays only", { field: field });
if (target === undefined) return;
var x = target[field];
if (x === undefined) return;else if (!(x instanceof Array)) throw MinimongoError$1("Cannot apply $pull/pullAll modifier to non-array", { field: field });else {
var out = [];
for (var i = 0; i < x.length; i++) {
var exclude = false;
for (var j = 0; j < arg.length; j++) {
if (_f._equal(x[i], arg[j])) {
exclude = true;
break;
}
}
if (!exclude) out.push(x[i]);
}
target[field] = out;
}
},
$rename: function $rename(target, field, arg, keypath, doc) {
if (keypath === arg)
// no idea why mongo has this restriction..
throw MinimongoError$1("$rename source must differ from target", { field: field });
if (target === null) throw MinimongoError$1("$rename source field invalid", { field: field });
if (typeof arg !== "string") throw MinimongoError$1("$rename target must be a string", { field: field });
if (arg.indexOf('\0') > -1) {
// Null bytes are not allowed in Mongo field names
// https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
throw MinimongoError$1("The 'to' field for $rename cannot contain an embedded null byte", { field: field });
}
if (target === undefined) return;
var v = target[field];
delete target[field];
var keyparts = arg.split('.');
var target2 = findModTarget(doc, keyparts, { forbidArray: true });
if (target2 === null) throw MinimongoError$1("$rename target field invalid", { field: field });
var field2 = keyparts.pop();
target2[field2] = v;
},
$bit: function $bit(target, field, arg) {
// XXX mongo only supports $bit on integers, and we only support
// native javascript numbers (doubles) so far, so we can't support $bit
throw MinimongoError$1("$bit is not supported", { field: field });
}
};
module.exports = modify;