UNPKG

leancloud-storage

Version:
733 lines (645 loc) 20.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find")); var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _ = require('underscore'); module.exports = function (AV) { /** * @private * @class * A AV.Op is an atomic operation that can be applied to a field in a * AV.Object. For example, calling <code>object.set("foo", "bar")</code> * is an example of a AV.Op.Set. Calling <code>object.unset("foo")</code> * is a AV.Op.Unset. These operations are stored in a AV.Object and * sent to the server as part of <code>object.save()</code> operations. * Instances of AV.Op should be immutable. * * You should not create subclasses of AV.Op or instantiate AV.Op * directly. */ AV.Op = function () { this._initialize.apply(this, arguments); }; _.extend(AV.Op.prototype, /** @lends AV.Op.prototype */ { _initialize: function _initialize() {} }); _.extend(AV.Op, { /** * To create a new Op, call AV.Op._extend(); * @private */ _extend: AV._extend, // A map of __op string to decoder function. _opDecoderMap: {}, /** * Registers a function to convert a json object with an __op field into an * instance of a subclass of AV.Op. * @private */ _registerDecoder: function _registerDecoder(opName, decoder) { AV.Op._opDecoderMap[opName] = decoder; }, /** * Converts a json object into an instance of a subclass of AV.Op. * @private */ _decode: function _decode(json) { var decoder = AV.Op._opDecoderMap[json.__op]; if (decoder) { return decoder(json); } else { return undefined; } } }); /* * Add a handler for Batch ops. */ AV.Op._registerDecoder('Batch', function (json) { var op = null; AV._arrayEach(json.ops, function (nextOp) { nextOp = AV.Op._decode(nextOp); op = nextOp._mergeWithPrevious(op); }); return op; }); /** * @private * @class * A Set operation indicates that either the field was changed using * AV.Object.set, or it is a mutable container that was detected as being * changed. */ AV.Op.Set = AV.Op._extend( /** @lends AV.Op.Set.prototype */ { _initialize: function _initialize(value) { this._value = value; }, /** * Returns the new value of this field after the set. */ value: function value() { return this._value; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return AV._encode(this.value()); }, _mergeWithPrevious: function _mergeWithPrevious(previous) { return this; }, _estimate: function _estimate(oldValue) { return this.value(); } }); /** * A sentinel value that is returned by AV.Op.Unset._estimate to * indicate the field should be deleted. Basically, if you find _UNSET as a * value in your object, you should remove that key. */ AV.Op._UNSET = {}; /** * @private * @class * An Unset operation indicates that this field has been deleted from the * object. */ AV.Op.Unset = AV.Op._extend( /** @lends AV.Op.Unset.prototype */ { /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'Delete' }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { return this; }, _estimate: function _estimate(oldValue) { return AV.Op._UNSET; } }); AV.Op._registerDecoder('Delete', function (json) { return new AV.Op.Unset(); }); /** * @private * @class * An Increment is an atomic operation where the numeric value for the field * will be increased by a given amount. */ AV.Op.Increment = AV.Op._extend( /** @lends AV.Op.Increment.prototype */ { _initialize: function _initialize(amount) { this._amount = amount; }, /** * Returns the amount to increment by. * @return {Number} the amount to increment by. */ amount: function amount() { return this._amount; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'Increment', amount: this._amount }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(this.amount()); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(previous.value() + this.amount()); } else if (previous instanceof AV.Op.Increment) { return new AV.Op.Increment(this.amount() + previous.amount()); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { if (!oldValue) { return this.amount(); } return oldValue + this.amount(); } }); AV.Op._registerDecoder('Increment', function (json) { return new AV.Op.Increment(json.amount); }); /** * @private * @class * BitAnd is an atomic operation where the given value will be bit and to the * value than is stored in this field. */ AV.Op.BitAnd = AV.Op._extend( /** @lends AV.Op.BitAnd.prototype */ { _initialize: function _initialize(value) { this._value = value; }, value: function value() { return this._value; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'BitAnd', value: this.value() }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(0); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(previous.value() & this.value()); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { return oldValue & this.value(); } }); AV.Op._registerDecoder('BitAnd', function (json) { return new AV.Op.BitAnd(json.value); }); /** * @private * @class * BitOr is an atomic operation where the given value will be bit and to the * value than is stored in this field. */ AV.Op.BitOr = AV.Op._extend( /** @lends AV.Op.BitOr.prototype */ { _initialize: function _initialize(value) { this._value = value; }, value: function value() { return this._value; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'BitOr', value: this.value() }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(this.value()); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(previous.value() | this.value()); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { return oldValue | this.value(); } }); AV.Op._registerDecoder('BitOr', function (json) { return new AV.Op.BitOr(json.value); }); /** * @private * @class * BitXor is an atomic operation where the given value will be bit and to the * value than is stored in this field. */ AV.Op.BitXor = AV.Op._extend( /** @lends AV.Op.BitXor.prototype */ { _initialize: function _initialize(value) { this._value = value; }, value: function value() { return this._value; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'BitXor', value: this.value() }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(this.value()); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(previous.value() ^ this.value()); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { return oldValue ^ this.value(); } }); AV.Op._registerDecoder('BitXor', function (json) { return new AV.Op.BitXor(json.value); }); /** * @private * @class * Add is an atomic operation where the given objects will be appended to the * array that is stored in this field. */ AV.Op.Add = AV.Op._extend( /** @lends AV.Op.Add.prototype */ { _initialize: function _initialize(objects) { this._objects = objects; }, /** * Returns the objects to be added to the array. * @return {Array} The objects to be added to the array. */ objects: function objects() { return this._objects; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'Add', objects: AV._encode(this.objects()) }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(this.objects()); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(this._estimate(previous.value())); } else if (previous instanceof AV.Op.Add) { var _context; return new AV.Op.Add((0, _concat.default)(_context = previous.objects()).call(_context, this.objects())); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { if (!oldValue) { return _.clone(this.objects()); } else { return (0, _concat.default)(oldValue).call(oldValue, this.objects()); } } }); AV.Op._registerDecoder('Add', function (json) { return new AV.Op.Add(AV._decode(json.objects)); }); /** * @private * @class * AddUnique is an atomic operation where the given items will be appended to * the array that is stored in this field only if they were not already * present in the array. */ AV.Op.AddUnique = AV.Op._extend( /** @lends AV.Op.AddUnique.prototype */ { _initialize: function _initialize(objects) { this._objects = _.uniq(objects); }, /** * Returns the objects to be added to the array. * @return {Array} The objects to be added to the array. */ objects: function objects() { return this._objects; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'AddUnique', objects: AV._encode(this.objects()) }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return new AV.Op.Set(this.objects()); } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(this._estimate(previous.value())); } else if (previous instanceof AV.Op.AddUnique) { return new AV.Op.AddUnique(this._estimate(previous.objects())); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { if (!oldValue) { return _.clone(this.objects()); } else { // We can't just take the _.uniq(_.union(...)) of oldValue and // this.objects, because the uniqueness may not apply to oldValue // (especially if the oldValue was set via .set()) var newValue = _.clone(oldValue); AV._arrayEach(this.objects(), function (obj) { if (obj instanceof AV.Object && obj.id) { var matchingObj = (0, _find.default)(_).call(_, newValue, function (anObj) { return anObj instanceof AV.Object && anObj.id === obj.id; }); if (!matchingObj) { newValue.push(obj); } else { var index = (0, _indexOf.default)(_).call(_, newValue, matchingObj); newValue[index] = obj; } } else if (!_.contains(newValue, obj)) { newValue.push(obj); } }); return newValue; } } }); AV.Op._registerDecoder('AddUnique', function (json) { return new AV.Op.AddUnique(AV._decode(json.objects)); }); /** * @private * @class * Remove is an atomic operation where the given objects will be removed from * the array that is stored in this field. */ AV.Op.Remove = AV.Op._extend( /** @lends AV.Op.Remove.prototype */ { _initialize: function _initialize(objects) { this._objects = _.uniq(objects); }, /** * Returns the objects to be removed from the array. * @return {Array} The objects to be removed from the array. */ objects: function objects() { return this._objects; }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { return { __op: 'Remove', objects: AV._encode(this.objects()) }; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { return previous; } else if (previous instanceof AV.Op.Set) { return new AV.Op.Set(this._estimate(previous.value())); } else if (previous instanceof AV.Op.Remove) { return new AV.Op.Remove(_.union(previous.objects(), this.objects())); } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue) { if (!oldValue) { return []; } else { var newValue = _.difference(oldValue, this.objects()); // If there are saved AV Objects being removed, also remove them. AV._arrayEach(this.objects(), function (obj) { if (obj instanceof AV.Object && obj.id) { newValue = _.reject(newValue, function (other) { return other instanceof AV.Object && other.id === obj.id; }); } }); return newValue; } } }); AV.Op._registerDecoder('Remove', function (json) { return new AV.Op.Remove(AV._decode(json.objects)); }); /** * @private * @class * A Relation operation indicates that the field is an instance of * AV.Relation, and objects are being added to, or removed from, that * relation. */ AV.Op.Relation = AV.Op._extend( /** @lends AV.Op.Relation.prototype */ { _initialize: function _initialize(adds, removes) { this._targetClassName = null; var self = this; var pointerToId = function pointerToId(object) { if (object instanceof AV.Object) { if (!object.id) { throw new Error("You can't add an unsaved AV.Object to a relation."); } if (!self._targetClassName) { self._targetClassName = object.className; } if (self._targetClassName !== object.className) { throw new Error('Tried to create a AV.Relation with 2 different types: ' + self._targetClassName + ' and ' + object.className + '.'); } return object.id; } return object; }; this.relationsToAdd = _.uniq((0, _map.default)(_).call(_, adds, pointerToId)); this.relationsToRemove = _.uniq((0, _map.default)(_).call(_, removes, pointerToId)); }, /** * Returns an array of unfetched AV.Object that are being added to the * relation. * @return {Array} */ added: function added() { var self = this; return (0, _map.default)(_).call(_, this.relationsToAdd, function (objectId) { var object = AV.Object._create(self._targetClassName); object.id = objectId; return object; }); }, /** * Returns an array of unfetched AV.Object that are being removed from * the relation. * @return {Array} */ removed: function removed() { var self = this; return (0, _map.default)(_).call(_, this.relationsToRemove, function (objectId) { var object = AV.Object._create(self._targetClassName); object.id = objectId; return object; }); }, /** * Returns a JSON version of the operation suitable for sending to AV. * @return {Object} */ toJSON: function toJSON() { var adds = null; var removes = null; var self = this; var idToPointer = function idToPointer(id) { return { __type: 'Pointer', className: self._targetClassName, objectId: id }; }; var pointers = null; if (this.relationsToAdd.length > 0) { pointers = (0, _map.default)(_).call(_, this.relationsToAdd, idToPointer); adds = { __op: 'AddRelation', objects: pointers }; } if (this.relationsToRemove.length > 0) { pointers = (0, _map.default)(_).call(_, this.relationsToRemove, idToPointer); removes = { __op: 'RemoveRelation', objects: pointers }; } if (adds && removes) { return { __op: 'Batch', ops: [adds, removes] }; } return adds || removes || {}; }, _mergeWithPrevious: function _mergeWithPrevious(previous) { if (!previous) { return this; } else if (previous instanceof AV.Op.Unset) { throw new Error("You can't modify a relation after deleting it."); } else if (previous instanceof AV.Op.Relation) { if (previous._targetClassName && previous._targetClassName !== this._targetClassName) { throw new Error('Related object must be of class ' + previous._targetClassName + ', but ' + this._targetClassName + ' was passed in.'); } var newAdd = _.union(_.difference(previous.relationsToAdd, this.relationsToRemove), this.relationsToAdd); var newRemove = _.union(_.difference(previous.relationsToRemove, this.relationsToAdd), this.relationsToRemove); var newRelation = new AV.Op.Relation(newAdd, newRemove); newRelation._targetClassName = this._targetClassName; return newRelation; } else { throw new Error('Op is invalid after previous op.'); } }, _estimate: function _estimate(oldValue, object, key) { if (!oldValue) { var relation = new AV.Relation(object, key); relation.targetClassName = this._targetClassName; } else if (oldValue instanceof AV.Relation) { if (this._targetClassName) { if (oldValue.targetClassName) { if (oldValue.targetClassName !== this._targetClassName) { throw new Error('Related object must be a ' + oldValue.targetClassName + ', but a ' + this._targetClassName + ' was passed in.'); } } else { oldValue.targetClassName = this._targetClassName; } } return oldValue; } else { throw new Error('Op is invalid after previous op.'); } } }); AV.Op._registerDecoder('AddRelation', function (json) { return new AV.Op.Relation(AV._decode(json.objects), []); }); AV.Op._registerDecoder('RemoveRelation', function (json) { return new AV.Op.Relation([], AV._decode(json.objects)); }); };