parse
Version:
Parse JavaScript SDK
1,470 lines (1,409 loc) • 87.8 kB
JavaScript
"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