UNPKG

parse

Version:
1,470 lines (1,409 loc) 87.8 kB
"use strict"; var _WeakMap = require("@babel/runtime-corejs3/core-js-stable/weak-map"); var _Object$defineProperty2 = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); _Object$defineProperty2(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); var _create = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/create")); var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); var _for = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/symbol/for")); var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify")); var _freeze = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/freeze")); var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys")); var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _getPrototypeOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/get-prototype-of")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _some = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/some")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array")); var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/define-property")); var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _CoreManager = _interopRequireDefault(require("./CoreManager")); var _canBeSerialized = _interopRequireDefault(require("./canBeSerialized")); var _decode = _interopRequireDefault(require("./decode")); var _encode = _interopRequireDefault(require("./encode")); var _escape = _interopRequireDefault(require("./escape")); var _ParseACL = _interopRequireDefault(require("./ParseACL")); var _parseDate = _interopRequireDefault(require("./parseDate")); var _ParseError = _interopRequireDefault(require("./ParseError")); var _ParseFile = _interopRequireDefault(require("./ParseFile")); var _promiseUtils = require("./promiseUtils"); var _LocalDatastoreUtils = require("./LocalDatastoreUtils"); var _uuid = _interopRequireDefault(require("./uuid")); var _ParseOp = require("./ParseOp"); var _ParseRelation = _interopRequireDefault(require("./ParseRelation")); var SingleInstanceStateController = _interopRequireWildcard(require("./SingleInstanceStateController")); var _unique = _interopRequireDefault(require("./unique")); var UniqueInstanceStateController = _interopRequireWildcard(require("./UniqueInstanceStateController")); var _unsavedChildren = _interopRequireDefault(require("./unsavedChildren")); function _interopRequireWildcard(e, t) { if ("function" == typeof _WeakMap) var r = new _WeakMap(), n = new _WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = _Object$defineProperty2) && _Object$getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } // Mapping of class names to constructors, so we can populate objects from the // server with appropriate subclasses of ParseObject const classMap = (0, _create.default)(null); // Global counter for generating unique Ids for non-single-instance objects let objectCount = 0; // On web clients, objects are single-instance: any two objects with the same Id // will have the same attributes. However, this may be dangerous default // behavior in a server scenario let singleInstance = !_CoreManager.default.get('IS_NODE'); if (singleInstance) { _CoreManager.default.setObjectStateController(SingleInstanceStateController); } else { _CoreManager.default.setObjectStateController(UniqueInstanceStateController); } function getServerUrlPath() { let serverUrl = _CoreManager.default.get('SERVER_URL'); if (serverUrl[serverUrl.length - 1] !== '/') { serverUrl += '/'; } const url = serverUrl.replace(/https?:\/\//, ''); return url.substr((0, _indexOf.default)(url).call(url, '/')); } /** * Creates a new model with defined attributes. * * <p>You won't normally call this method directly. It is recommended that * you use a subclass of <code>Parse.Object</code> instead, created by calling * <code>extend</code>.</p> * * <p>However, if you don't want to use a subclass, or aren't sure which * subclass is appropriate, you can use this form:<pre> * var object = new Parse.Object("ClassName"); * </pre> * That is basically equivalent to:<pre> * var MyClass = Parse.Object.extend("ClassName"); * var object = new MyClass(); * </pre></p> * * @alias Parse.Object */ class ParseObject { /** * @param {string} className The class name for the object * @param {object} attributes The initial set of data to store in the object. * @param {object} options The options for this object instance. * @param {boolean} [options.ignoreValidation] Set to `true` ignore any attribute validation errors. */ constructor(className, attributes, options) { /** * The ID of this object, unique within its class. * * @property {string} id */ (0, _defineProperty2.default)(this, "id", void 0); (0, _defineProperty2.default)(this, "_localId", void 0); (0, _defineProperty2.default)(this, "_objCount", void 0); (0, _defineProperty2.default)(this, "className", void 0); // Enable legacy initializers if (typeof this.initialize === 'function') { this.initialize.apply(this, arguments); } let toSet = null; this._objCount = objectCount++; if (typeof className === 'string') { this.className = className; if (attributes && typeof attributes === 'object') { toSet = attributes; } } else if (className && typeof className === 'object') { this.className = className.className; toSet = {}; for (const attr in className) { if (attr !== 'className') { toSet[attr] = className[attr]; } } if (attributes && typeof attributes === 'object') { options = attributes; } } if (toSet) { try { this.set(toSet, options); } catch (_) { throw new Error("Can't create an invalid Parse Object"); } } if (_CoreManager.default.get('NODE_LOGGING')) { this[(0, _for.default)('nodejs.util.inspect.custom')] = function () { return `ParseObject: className: ${this.className}, id: ${this.id}\nAttributes: ${(0, _stringify.default)(this.attributes, null, 2)}`; }; } } /* Prototype getters / setters */ get attributes() { const stateController = _CoreManager.default.getObjectStateController(); return (0, _freeze.default)(stateController.estimateAttributes(this._getStateIdentifier())); } /** * The first time this object was saved on the server. * * @property {Date} createdAt * @returns {Date} */ get createdAt() { return this._getServerData().createdAt; } /** * The last time this object was updated on the server. * * @property {Date} updatedAt * @returns {Date} */ get updatedAt() { return this._getServerData().updatedAt; } /* Private methods */ /** * Returns a local or server Id used uniquely identify this object * * @returns {string} */ _getId() { if (typeof this.id === 'string') { return this.id; } if (typeof this._localId === 'string') { return this._localId; } const localId = 'local' + (0, _uuid.default)(); this._localId = localId; return localId; } /** * Returns a unique identifier used to pull data from the State Controller. * * @returns {Parse.Object|object} */ _getStateIdentifier() { if (singleInstance) { let id = this.id; if (!id) { id = this._getId(); } return { id, className: this.className }; } else { return this; } } _getServerData() { const stateController = _CoreManager.default.getObjectStateController(); return stateController.getServerData(this._getStateIdentifier()); } _clearServerData() { const serverData = this._getServerData(); const unset = {}; for (const attr in serverData) { unset[attr] = undefined; } const stateController = _CoreManager.default.getObjectStateController(); stateController.setServerData(this._getStateIdentifier(), unset); } _getPendingOps() { const stateController = _CoreManager.default.getObjectStateController(); return stateController.getPendingOps(this._getStateIdentifier()); } /** * @param {Array<string>} [keysToClear] - if specified, only ops matching * these fields will be cleared */ _clearPendingOps(keysToClear) { const pending = this._getPendingOps(); const latest = pending[pending.length - 1]; const keys = keysToClear || (0, _keys.default)(latest); (0, _forEach.default)(keys).call(keys, key => { delete latest[key]; }); } _getDirtyObjectAttributes() { const attributes = this.attributes; const stateController = _CoreManager.default.getObjectStateController(); const objectCache = stateController.getObjectCache(this._getStateIdentifier()); const dirty = {}; for (const attr in attributes) { const val = attributes[attr]; if (val && typeof val === 'object' && !(val instanceof ParseObject) && !(val instanceof _ParseFile.default) && !(val instanceof _ParseRelation.default)) { // Due to the way browsers construct maps, the key order will not change // unless the object is changed try { const json = (0, _encode.default)(val, false, true); const stringified = (0, _stringify.default)(json); if (objectCache[attr] !== stringified) { dirty[attr] = val; } } catch (_) { // Error occurred, possibly by a nested unsaved pointer in a mutable container // No matter how it happened, it indicates a change in the attribute dirty[attr] = val; } } } return dirty; } _toFullJSON(seen, offline) { const json = this.toJSON(seen, offline); json.__type = 'Object'; json.className = this.className; return json; } _getSaveJSON() { const pending = this._getPendingOps(); const dirtyObjects = this._getDirtyObjectAttributes(); const json = {}; for (var attr in dirtyObjects) { let isDotNotation = false; for (let i = 0; i < pending.length; i += 1) { for (const field in pending[i]) { // Dot notation operations are handled later if ((0, _includes.default)(field).call(field, '.')) { const fieldName = field.split('.')[0]; if (fieldName === attr) { isDotNotation = true; break; } } } } if (!isDotNotation) { json[attr] = new _ParseOp.SetOp(dirtyObjects[attr]).toJSON(); } } for (attr in pending[0]) { json[attr] = pending[0][attr].toJSON(); } return json; } _getSaveParams() { let method = this.id ? 'PUT' : 'POST'; const body = this._getSaveJSON(); let path = 'classes/' + this.className; if (_CoreManager.default.get('ALLOW_CUSTOM_OBJECT_ID')) { if (!this.createdAt) { method = 'POST'; body.objectId = this.id; } else { method = 'PUT'; path += '/' + this.id; } } else if (this.id) { path += '/' + this.id; } else if (this.className === '_User') { path = 'users'; } return { method, body, path }; } _finishFetch(serverData) { if (!this.id && serverData.objectId) { this.id = serverData.objectId; } const stateController = _CoreManager.default.getObjectStateController(); stateController.initializeState(this._getStateIdentifier()); const decoded = {}; for (const attr in serverData) { if (attr === 'ACL') { decoded[attr] = new _ParseACL.default(serverData[attr]); } else if (attr !== 'objectId') { decoded[attr] = (0, _decode.default)(serverData[attr]); if (decoded[attr] instanceof _ParseRelation.default) { decoded[attr]._ensureParentAndKey(this, attr); } } } if (decoded.createdAt && typeof decoded.createdAt === 'string') { decoded.createdAt = (0, _parseDate.default)(decoded.createdAt); } if (decoded.updatedAt && typeof decoded.updatedAt === 'string') { decoded.updatedAt = (0, _parseDate.default)(decoded.updatedAt); } if (!decoded.updatedAt && decoded.createdAt) { decoded.updatedAt = decoded.createdAt; } stateController.commitServerChanges(this._getStateIdentifier(), decoded); } _setExisted(existed) { const stateController = _CoreManager.default.getObjectStateController(); const state = stateController.getState(this._getStateIdentifier()); if (state) { state.existed = existed; } } _migrateId(serverId) { if (this._localId && serverId) { if (singleInstance) { const stateController = _CoreManager.default.getObjectStateController(); const oldState = stateController.removeState(this._getStateIdentifier()); this.id = serverId; delete this._localId; if (oldState) { stateController.initializeState(this._getStateIdentifier(), oldState); } } else { this.id = serverId; delete this._localId; } } } _handleSaveResponse(response, status) { const changes = {}; const stateController = _CoreManager.default.getObjectStateController(); const pending = stateController.popPendingState(this._getStateIdentifier()); for (var attr in pending) { if (pending[attr] instanceof _ParseOp.RelationOp) { changes[attr] = pending[attr].applyTo(undefined, this, attr); } else if (!(attr in response)) { // Only SetOps and UnsetOps should not come back with results changes[attr] = pending[attr].applyTo(undefined); } } for (attr in response) { if ((attr === 'createdAt' || attr === 'updatedAt') && typeof response[attr] === 'string') { changes[attr] = (0, _parseDate.default)(response[attr]); } else if (attr === 'ACL') { changes[attr] = new _ParseACL.default(response[attr]); } else if (attr !== 'objectId') { const val = (0, _decode.default)(response[attr]); if (val && (0, _getPrototypeOf.default)(val) === Object.prototype) { changes[attr] = { ...this.attributes[attr], ...val }; } else { changes[attr] = val; } if (changes[attr] instanceof _ParseOp.UnsetOp) { changes[attr] = undefined; } } } if (changes.createdAt && !changes.updatedAt) { changes.updatedAt = changes.createdAt; } this._migrateId(response.objectId); if (status !== 201) { this._setExisted(true); } stateController.commitServerChanges(this._getStateIdentifier(), changes); } _handleSaveError() { const stateController = _CoreManager.default.getObjectStateController(); stateController.mergeFirstPendingState(this._getStateIdentifier()); } static _getClassMap() { return classMap; } static _getRequestOptions() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const requestOptions = {}; if (Object.prototype.toString.call(options) !== '[object Object]') { throw new Error('request options must be of type Object'); } const { hasOwn } = Object; if (hasOwn(options, 'useMasterKey')) { requestOptions.useMasterKey = !!options.useMasterKey; } if (hasOwn(options, 'useMaintenanceKey')) { requestOptions.useMaintenanceKey = !!options.useMaintenanceKey; } if (hasOwn(options, 'sessionToken') && typeof options.sessionToken === 'string') { requestOptions.sessionToken = options.sessionToken; } if (hasOwn(options, 'installationId') && typeof options.installationId === 'string') { requestOptions.installationId = options.installationId; } if (hasOwn(options, 'transaction') && typeof options.transaction === 'boolean') { requestOptions.transaction = options.transaction; } if (hasOwn(options, 'batchSize') && typeof options.batchSize === 'number') { requestOptions.batchSize = options.batchSize; } if (hasOwn(options, 'context') && typeof options.context === 'object') { requestOptions.context = options.context; } if (hasOwn(options, 'include')) { requestOptions.include = ParseObject.handleIncludeOptions(options); } if (hasOwn(options, 'usePost')) { requestOptions.usePost = options.usePost; } if (hasOwn(options, 'json')) { requestOptions.json = options.json; } return requestOptions; } /* Public methods */ initialize() { // NOOP } /** * Returns a JSON version of the object suitable for saving to Parse. * * @param seen * @param offline * @returns {object} */ toJSON(seen, offline) { const seenEntry = this.id ? this.className + ':' + this.id : this; seen = seen || [seenEntry]; const json = {}; const attrs = this.attributes; for (const attr in attrs) { if ((attr === 'createdAt' || attr === 'updatedAt') && attrs[attr].toJSON) { json[attr] = attrs[attr].toJSON(); } else { json[attr] = (0, _encode.default)(attrs[attr], false, false, seen, offline); } } const pending = this._getPendingOps(); for (const attr in pending[0]) { if ((0, _indexOf.default)(attr).call(attr, '.') < 0) { json[attr] = pending[0][attr].toJSON(offline); } } if (this.id) { json.objectId = this.id; } return json; } /** * Determines whether this ParseObject is equal to another ParseObject * * @param {object} other - An other object ot compare * @returns {boolean} */ equals(other) { if (this === other) { return true; } return other instanceof ParseObject && this.className === other.className && this.id === other.id && typeof this.id !== 'undefined'; } /** * Returns true if this object has been modified since its last * save/refresh. If an attribute is specified, it returns true only if that * particular attribute has been modified since the last save/refresh. * * @param {string} attr An attribute name (optional). * @returns {boolean} */ dirty(attr) { if (!this.id) { return true; } const pendingOps = this._getPendingOps(); const dirtyObjects = this._getDirtyObjectAttributes(); if (attr) { if (Object.hasOwn(dirtyObjects, attr)) { return true; } for (let i = 0; i < pendingOps.length; i++) { if (Object.hasOwn(pendingOps[i], attr)) { return true; } } return false; } if ((0, _keys.default)(pendingOps[0]).length !== 0) { return true; } if ((0, _keys.default)(dirtyObjects).length !== 0) { return true; } return false; } /** * Returns an array of keys that have been modified since last save/refresh * * @returns {string[]} */ dirtyKeys() { const pendingOps = this._getPendingOps(); const keys = {}; for (let i = 0; i < pendingOps.length; i++) { for (const attr in pendingOps[i]) { keys[attr] = true; } } const dirtyObjects = this._getDirtyObjectAttributes(); for (const attr in dirtyObjects) { keys[attr] = true; } return (0, _keys.default)(keys); } /** * Returns true if the object has been fetched. * * @returns {boolean} */ isDataAvailable() { const serverData = this._getServerData(); return !!(0, _keys.default)(serverData).length; } /** * Gets a Pointer referencing this Object. * * @returns {Pointer} */ toPointer() { if (!this.id) { throw new Error('Cannot create a pointer to an unsaved ParseObject'); } return { __type: 'Pointer', className: this.className, objectId: this.id }; } /** * Gets a Pointer referencing this Object. * * @returns {Pointer} */ toOfflinePointer() { if (!this._localId) { throw new Error('Cannot create a offline pointer to a saved ParseObject'); } return { __type: 'Object', className: this.className, _localId: this._localId }; } /** * Gets the value of an attribute. * * @param {string} attr The string name of an attribute. * @returns {*} */ get(attr) { return this.attributes[attr]; } /** * Gets a relation on the given class for the attribute. * * @param {string} attr The attribute to get the relation for. * @returns {Parse.Relation} */ relation(attr) { const value = this.get(attr); if (value) { if (!(value instanceof _ParseRelation.default)) { throw new Error('Called relation() on non-relation field ' + attr); } value._ensureParentAndKey(this, attr); return value; } return new _ParseRelation.default(this, attr); } /** * Gets the HTML-escaped value of an attribute. * * @param {string} attr The string name of an attribute. * @returns {string} */ escape(attr) { let val = this.attributes[attr]; if (val == null) { return ''; } if (typeof val !== 'string') { if (typeof val.toString !== 'function') { return ''; } val = val.toString(); } return (0, _escape.default)(val); } /** * Returns <code>true</code> if the attribute contains a value that is not * null or undefined. * * @param {string} attr The string name of the attribute. * @returns {boolean} */ has(attr) { const attributes = this.attributes; if (Object.hasOwn(attributes, attr)) { return attributes[attr] != null; } return false; } /** * Sets a hash of model attributes on the object. * * <p>You can call it with an object containing keys and values, with one * key and value, or dot notation. For example:<pre> * gameTurn.set({ * player: player1, * diceRoll: 2 * }, { * error: function(gameTurnAgain, error) { * // The set failed validation. * } * }); * * game.set("currentPlayer", player2, { * error: function(gameTurnAgain, error) { * // The set failed validation. * } * }); * * game.set("finished", true);</pre></p> * * game.set("player.score", 10);</pre></p> * * @param {(string|object)} key The key to set. * @param {(string|object)} value The value to give it. * @param {object} options A set of options for the set. * The only supported option is <code>error</code>. * @returns {Parse.Object} Returns the object, so you can chain this call. */ set(key, value, options) { let changes = {}; const newOps = {}; if (key && typeof key === 'object') { changes = key; options = value; } else if (typeof key === 'string') { changes[key] = value; } else { return this; } options = options || {}; let readonly = []; if (typeof this.constructor.readOnlyAttributes === 'function') { readonly = (0, _concat.default)(readonly).call(readonly, this.constructor.readOnlyAttributes()); } for (const k in changes) { if (k === 'createdAt' || k === 'updatedAt') { // This property is read-only, but for legacy reasons we silently // ignore it continue; } if ((0, _indexOf.default)(readonly).call(readonly, k) > -1) { throw new Error('Cannot modify readonly attribute: ' + k); } if (options.unset) { newOps[k] = new _ParseOp.UnsetOp(); } else if (changes[k] instanceof _ParseOp.Op) { newOps[k] = changes[k]; } else if (changes[k] && typeof changes[k] === 'object' && typeof changes[k].__op === 'string') { newOps[k] = (0, _ParseOp.opFromJSON)(changes[k]); } else if (k === 'objectId' || k === 'id') { if (typeof changes[k] === 'string') { this.id = changes[k]; } } else if (k === 'ACL' && typeof changes[k] === 'object' && !(changes[k] instanceof _ParseACL.default)) { newOps[k] = new _ParseOp.SetOp(new _ParseACL.default(changes[k])); } else if (changes[k] instanceof _ParseRelation.default) { const relation = new _ParseRelation.default(this, k); relation.targetClassName = changes[k].targetClassName; newOps[k] = new _ParseOp.SetOp(relation); } else { newOps[k] = new _ParseOp.SetOp(changes[k]); } } const currentAttributes = this.attributes; // Calculate new values const newValues = {}; for (const attr in newOps) { if (newOps[attr] instanceof _ParseOp.RelationOp) { newValues[attr] = newOps[attr].applyTo(currentAttributes[attr], this, attr); } else if (!(newOps[attr] instanceof _ParseOp.UnsetOp)) { newValues[attr] = newOps[attr].applyTo(currentAttributes[attr]); } } // Validate changes if (!options.ignoreValidation) { const validationError = this.validate(newValues); if (validationError) { throw validationError; } } // Consolidate Ops const pendingOps = this._getPendingOps(); const last = pendingOps.length - 1; const stateController = _CoreManager.default.getObjectStateController(); for (const attr in newOps) { const nextOp = newOps[attr].mergeWith(pendingOps[last][attr]); stateController.setPendingOp(this._getStateIdentifier(), attr, nextOp); } return this; } /** * Remove an attribute from the model. This is a noop if the attribute doesn't * exist. * * @param {string} attr The string name of an attribute. * @param options * @returns {Parse.Object} Returns the object, so you can chain this call. */ unset(attr, options) { options = options || {}; options.unset = true; return this.set(attr, null, options); } /** * Atomically increments the value of the given attribute the next time the * object is saved. If no amount is specified, 1 is used by default. * * @param attr {String} The key. * @param amount {Number} The amount to increment by (optional). * @returns {Parse.Object} Returns the object, so you can chain this call. */ increment(attr, amount) { if (typeof amount === 'undefined') { amount = 1; } if (typeof amount !== 'number') { throw new Error('Cannot increment by a non-numeric amount.'); } return this.set(attr, new _ParseOp.IncrementOp(amount)); } /** * Atomically decrements the value of the given attribute the next time the * object is saved. If no amount is specified, 1 is used by default. * * @param attr {String} The key. * @param amount {Number} The amount to decrement by (optional). * @returns {Parse.Object} Returns the object, so you can chain this call. */ decrement(attr, amount) { if (typeof amount === 'undefined') { amount = 1; } if (typeof amount !== 'number') { throw new Error('Cannot decrement by a non-numeric amount.'); } return this.set(attr, new _ParseOp.IncrementOp(amount * -1)); } /** * Atomically add an object to the end of the array associated with a given * key. * * @param attr {String} The key. * @param item {} The item to add. * @returns {Parse.Object} Returns the object, so you can chain this call. */ add(attr, item) { return this.set(attr, new _ParseOp.AddOp([item])); } /** * Atomically add the objects to the end of the array associated with a given * key. * * @param attr {String} The key. * @param items {Object[]} The items to add. * @returns {Parse.Object} Returns the object, so you can chain this call. */ addAll(attr, items) { return this.set(attr, new _ParseOp.AddOp(items)); } /** * Atomically add an object to the array associated with a given key, only * if it is not already present in the array. The position of the insert is * not guaranteed. * * @param attr {String} The key. * @param item {} The object to add. * @returns {Parse.Object} Returns the object, so you can chain this call. */ addUnique(attr, item) { return this.set(attr, new _ParseOp.AddUniqueOp([item])); } /** * Atomically add the objects to the array associated with a given key, only * if it is not already present in the array. The position of the insert is * not guaranteed. * * @param attr {String} The key. * @param items {Object[]} The objects to add. * @returns {Parse.Object} Returns the object, so you can chain this call. */ addAllUnique(attr, items) { return this.set(attr, new _ParseOp.AddUniqueOp(items)); } /** * Atomically remove all instances of an object from the array associated * with a given key. * * @param attr {String} The key. * @param item {} The object to remove. * @returns {Parse.Object} Returns the object, so you can chain this call. */ remove(attr, item) { return this.set(attr, new _ParseOp.RemoveOp([item])); } /** * Atomically remove all instances of the objects from the array associated * with a given key. * * @param attr {String} The key. * @param items {Object[]} The object to remove. * @returns {Parse.Object} Returns the object, so you can chain this call. */ removeAll(attr, items) { return this.set(attr, new _ParseOp.RemoveOp(items)); } /** * Returns an instance of a subclass of Parse.Op describing what kind of * modification has been performed on this field since the last time it was * saved. For example, after calling object.increment("x"), calling * object.op("x") would return an instance of Parse.Op.Increment. * * @param attr {String} The key. * @returns {Parse.Op | undefined} The operation, or undefined if none. */ op(attr) { const pending = this._getPendingOps(); for (let i = 0; i < pending.length; i += 1) { if (pending[i][attr]) { return pending[i][attr]; } } } /** * Creates a new model with identical attributes to this one. * * @returns {Parse.Object} */ clone() { const clone = new this.constructor(this.className); let attributes = this.attributes; if (typeof this.constructor.readOnlyAttributes === 'function') { const readonly = this.constructor.readOnlyAttributes() || []; // Attributes are frozen, so we have to rebuild an object, // rather than delete readonly keys const copy = {}; for (const a in attributes) { if ((0, _indexOf.default)(readonly).call(readonly, a) < 0) { copy[a] = attributes[a]; } } attributes = copy; } if (clone.set) { clone.set(attributes); } return clone; } /** * Creates a new instance of this object. Not to be confused with clone() * * @returns {Parse.Object} */ newInstance() { const clone = new this.constructor(this.className); clone.id = this.id; if (singleInstance) { // Just return an object with the right id return clone; } const stateController = _CoreManager.default.getObjectStateController(); if (stateController) { stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier()); } return clone; } /** * Returns true if this object has never been saved to Parse. * * @returns {boolean} */ isNew() { return !this.id; } /** * Returns true if this object was created by the Parse server when the * object might have already been there (e.g. in the case of a Facebook * login) * * @returns {boolean} */ existed() { if (!this.id) { return false; } const stateController = _CoreManager.default.getObjectStateController(); const state = stateController.getState(this._getStateIdentifier()); if (state) { return state.existed; } return false; } /** * Returns true if this object exists on the Server * * @param {object} options * Valid options are:<ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * </ul> * @returns {Promise<boolean>} A boolean promise that is fulfilled if object exists. */ async exists(options) { if (!this.id) { return false; } try { const ParseQuery = _CoreManager.default.getParseQuery(); const query = new ParseQuery(this.className); await query.get(this.id, options); return true; } catch (e) { if (e.code === _ParseError.default.OBJECT_NOT_FOUND) { return false; } throw e; } } /** * Checks if the model is currently in a valid state. * * @returns {boolean} */ isValid() { return !this.validate(this.attributes); } /** * You should not call this function directly unless you subclass * <code>Parse.Object</code>, in which case you can override this method * to provide additional validation on <code>set</code> and * <code>save</code>. Your implementation should return * * @param {object} attrs The current data to validate. * @returns {Parse.Error|boolean} False if the data is valid. An error object otherwise. * @see Parse.Object#set */ validate(attrs) { if (Object.hasOwn(attrs, 'ACL') && !(attrs.ACL instanceof _ParseACL.default)) { return new _ParseError.default(_ParseError.default.OTHER_CAUSE, 'ACL must be a Parse ACL.'); } for (const key in attrs) { if (!/^[A-Za-z_][0-9A-Za-z_.]*$/.test(key)) { return new _ParseError.default(_ParseError.default.INVALID_KEY_NAME, `Invalid key name: ${key}`); } } return false; } /** * Returns the ACL for this object. * * @returns {Parse.ACL|null} An instance of Parse.ACL. * @see Parse.Object#get */ getACL() { const acl = this.get('ACL'); if (acl instanceof _ParseACL.default) { return acl; } return null; } /** * Sets the ACL to be used for this object. * * @param {Parse.ACL} acl An instance of Parse.ACL. * @param {object} options * @returns {Parse.Object} Returns the object, so you can chain this call. * @see Parse.Object#set */ setACL(acl, options) { return this.set('ACL', acl, options); } /** * Clears any (or specific) changes to this object made since the last call to save() * * @param {string} [keys] - specify which fields to revert */ revert() { let keysToRevert; for (var _len = arguments.length, keys = new Array(_len), _key = 0; _key < _len; _key++) { keys[_key] = arguments[_key]; } if (keys.length) { keysToRevert = []; for (const key of keys) { if (typeof key === 'string') { keysToRevert.push(key); } else { throw new Error('Parse.Object#revert expects either no, or a list of string, arguments.'); } } } this._clearPendingOps(keysToRevert); } /** * Clears all attributes on a model * * @returns {Parse.Object} Returns the object, so you can chain this call. */ clear() { const attributes = this.attributes; const erasable = {}; let readonly = ['createdAt', 'updatedAt']; if (typeof this.constructor.readOnlyAttributes === 'function') { readonly = (0, _concat.default)(readonly).call(readonly, this.constructor.readOnlyAttributes()); } for (const attr in attributes) { if ((0, _indexOf.default)(readonly).call(readonly, attr) < 0) { erasable[attr] = true; } } return this.set(erasable, { unset: true }); } /** * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden. * * @param {object} options * Valid options are:<ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>include: The name(s) of the key(s) to include. Can be a string, an array of strings, * or an array of array of strings. * <li>context: A dictionary that is accessible in Cloud Code `beforeFind` trigger. * </ul> * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ fetch(options) { const fetchOptions = ParseObject._getRequestOptions(options); const controller = _CoreManager.default.getObjectController(); return controller.fetch(this, true, fetchOptions); } /** * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden. * * Includes nested Parse.Objects for the provided key. You can use dot * notation to specify which fields in the included object are also fetched. * * @param {string | Array<string | Array<string>>} keys The name(s) of the key(s) to include. * @param {object} options * Valid options are:<ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * </ul> * @returns {Promise} A promise that is fulfilled when the fetch * completes. */ fetchWithInclude(keys, options) { options = options || {}; options.include = keys; return this.fetch(options); } /** * Saves this object to the server at some unspecified time in the future, * even if Parse is currently inaccessible. * * Use this when you may not have a solid network connection, and don't need to know when the save completes. * If there is some problem with the object such that it can't be saved, it will be silently discarded. * * Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse. * They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is * available. Objects saved this way will persist even after the app is closed, in which case they will be sent the * next time the app is opened. * * @param {object} [options] * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. * Valid options are: * <ul> * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>cascadeSave: If `false`, nested objects will not be saved (default is `true`). * <li>context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. * </ul> * @returns {Promise} A promise that is fulfilled when the save * completes. */ async saveEventually(options) { try { await this.save(null, options); } catch (e) { if (e.code === _ParseError.default.CONNECTION_FAILED) { await _CoreManager.default.getEventuallyQueue().save(this, options); _CoreManager.default.getEventuallyQueue().poll(); } } return this; } /** * Set a hash of model attributes, and save the model to the server. * updatedAt will be updated when the request returns. * You can either call it as:<pre> * object.save();</pre> * or<pre> * object.save(attrs);</pre> * or<pre> * object.save(null, options);</pre> * or<pre> * object.save(attrs, options);</pre> * or<pre> * object.save(key, value);</pre> * or<pre> * object.save(key, value, options);</pre> * * Example 1: <pre> * gameTurn.save({ * player: "Jake Cutter", * diceRoll: 2 * }).then(function(gameTurnAgain) { * // The save was successful. * }, function(error) { * // The save failed. Error is an instance of Parse.Error. * });</pre> * * Example 2: <pre> * gameTurn.save("player", "Jake Cutter");</pre> * * @param {string | object | null} [arg1] * Valid options are:<ul> * <li>`Object` - Key/value pairs to update on the object.</li> * <li>`String` Key - Key of attribute to update (requires arg2 to also be string)</li> * <li>`null` - Passing null for arg1 allows you to save the object with options passed in arg2.</li> * </ul> * @param {string | object} [arg2] * <ul> * <li>`String` Value - If arg1 was passed as a key, arg2 is the value that should be set on that key.</li> * <li>`Object` Options - Valid options are: * <ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>cascadeSave: If `false`, nested objects will not be saved (default is `true`). * <li>context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. * </ul> * </li> * </ul> * @param {object} [arg3] * Used to pass option parameters to method if arg1 and arg2 were both passed as strings. * Valid options are: * <ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>cascadeSave: If `false`, nested objects will not be saved (default is `true`). * <li>context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers. * </ul> * @returns {Promise} A promise that is fulfilled when the save * completes. */ async save(arg1, arg2, arg3) { let attrs; let options; if (typeof arg1 === 'object' || typeof arg1 === 'undefined') { attrs = arg1; if (typeof arg2 === 'object') { options = arg2; } } else { attrs = {}; attrs[arg1] = arg2; options = arg3; } options = options || {}; if (attrs) { this.set(attrs, options); } const saveOptions = ParseObject._getRequestOptions(options); const controller = _CoreManager.default.getObjectController(); const unsaved = options.cascadeSave !== false ? (0, _unsavedChildren.default)(this) : null; if (unsaved && unsaved.length && options.transaction === true && (0, _some.default)(unsaved).call(unsaved, el => el instanceof ParseObject)) { const unsavedFiles = []; const unsavedObjects = []; (0, _forEach.default)(unsaved).call(unsaved, el => { if (el instanceof _ParseFile.default) unsavedFiles.push(el);else unsavedObjects.push(el); }); unsavedObjects.push(this); const filePromise = unsavedFiles.length ? controller.save(unsavedFiles, saveOptions) : _promise.default.resolve(); return filePromise.then(() => controller.save(unsavedObjects, saveOptions)).then(savedOjbects => savedOjbects.pop()); } await controller.save(unsaved, saveOptions); return controller.save(this, saveOptions); } /** * Deletes this object from the server at some unspecified time in the future, * even if Parse is currently inaccessible. * * Use this when you may not have a solid network connection, * and don't need to know when the delete completes. If there is some problem with the object * such that it can't be deleted, the request will be silently discarded. * * Delete instructions made with this method will be stored locally in an on-disk cache until they can be transmitted * to Parse. They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection * is available. Delete requests will persist even after the app is closed, in which case they will be sent the * next time the app is opened. * * @param {object} [options] * Valid options are:<ul> * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. * </ul> * @returns {Promise} A promise that is fulfilled when the destroy * completes. */ async destroyEventually(options) { try { await this.destroy(options); } catch (e) { if (e.code === _ParseError.default.CONNECTION_FAILED) { await _CoreManager.default.getEventuallyQueue().destroy(this, options); _CoreManager.default.getEventuallyQueue().poll(); } } return this; } /** * Destroy this model on the server if it was already persisted. * * @param {object} options * Valid options are:<ul> * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to * be used for this request. * <li>sessionToken: A valid session token, used for making a request on * behalf of a specific user. * <li>context: A dictionary that is accessible in Cloud Code `beforeDelete` and `afterDelete` triggers. * </ul> * @returns {Promise} A promise that is fulfilled when the destroy * completes. */ destroy(options) { if (!this.id) { return _promise.default.resolve(undefined); } const destroyOptions = ParseObject._getRequestOptions(options); return _CoreManager.default.getObjectController().destroy(this, destroyOptions); } /** * Asynchronously stores the object and every object it points to in the local datastore, * recursively, using a default pin name: _default. * * If those other objects have not been fetched from Parse, they will not be stored. * However, if they have changed data, all the changes will be retained. * * <pre> * await object.pin(); * </pre> * * To retrieve object: * <code>query.fromLocalDatastore()</code> or <code>query.fromPin()</code> * * @returns {Promise} A promise that is fulfilled when the pin completes. */ pin() { return ParseObject.pinAllWithName(_LocalDatastoreUtils.DEFAULT_PIN, [this]); } /** * Asynchronously removes the object and every object it points to in the local datastore, * recursively, using a default pin name: _default. * * <pre> * await object.unPin(); * </pre> * * @returns {Promise} A promise that is fulfilled when the unPin completes. */ unPin() { return ParseObject.unPinAllWithName(_LocalDatastoreUtils.DEFAULT_PIN, [this]); } /** * Asynchronously returns if the object is pinned * * <pre> * const isPinned = await object.isPinned(); * </pre> * * @returns {Promise<boolean>} A boolean promise that is fulfilled if object is pinned. */ async isPinned() { const localDatastore = _CoreManager.default.getLocalDatastore(); if (!localDatastore.isEnabled) { return _promise.default.reject('Parse.enableLocalDatastore() must be called first'); } const objectKey = localDatastore.getKeyForObject(this); const pin = await localDatastore.fromPinWithName(objectKey); return pin.length > 0; } /** * Asynchronously stores the objects and every object they point to in the local datastore, recursively. * * If those other objects have not been fetched from Parse, they will not be stored. * However, if they have changed data, all the changes will be retained. * * <pre> * await object.pinWithName(name); * </pre> * * To retrieve object: * <code>query.fromLocalDatastore()</code> or <code>query.fromPinWithName(name)</code> * * @param {string} name Name of Pin. * @returns {Promise} A promise that is fulfilled when the pin completes. */ pinWithName(name) { return ParseObject.pinAllWithName(name, [this]); } /** * Asynchronously removes the object and every object it points to in the local datastore, recursively. * * <pre> * await object.unPinWithName(name); * </pre> * * @param {string} name Name of Pin. * @returns {Promise} A promise that is fulfilled when the unPin completes. */ unPinWithName(name) { return ParseObject.unPinAllWithName(name, [this]); } /** * Asynchronously loads data from the local datastore into this object. * * <pre> * await object.fetchFromLocalDatastore(); * </pre> * * You can create an unfetched poin