UNPKG

parse-mobx

Version:

A wrapper for ParseJS SDK to make Parse Objects observable in Mobx

1,067 lines (1,066 loc) 30.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MobxStore = exports.ParseMobx = exports.configureParseMobx = void 0; const mobx_1 = require("mobx"); // eslint-disable-next-line @typescript-eslint/no-unused-vars const config_1 = require("./config"); // Re-export configuration function var config_2 = require("./config"); Object.defineProperty(exports, "configureParseMobx", { enumerable: true, get: function () { return config_2.configureParseMobx; } }); /** * Parse Mobx Class */ class ParseMobx { /** * ParseMobx Class constructor * @param {Parse.Object} obj */ constructor(obj) { /** * Will set to true if the object is being saved. * @type {boolean} */ Object.defineProperty(this, "loading", { enumerable: true, configurable: true, writable: true, value: false }); /** * Contains the observable attributes. * @type {Parse.Attributes} * @private */ Object.defineProperty(this, "attributes", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * The parse Object * @type {Parse.Object} * @private */ Object.defineProperty(this, "parseObj", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * Parse Object Id. * @type {string} * @private */ Object.defineProperty(this, "id", { enumerable: true, configurable: true, writable: true, value: void 0 }); (0, mobx_1.makeObservable)(this); // make sure objects are saved. if (obj.isNew()) { throw new Error(`Only Saved Parse objects can be converted to ParseMobx objects. not saved object: ${obj.className}`); } // keep a ref of parse object. this.parseObj = obj; // copy id this.id = obj.id; this.attributes = { createdAt: obj.get('createdAt') }; // store props to be observed. const observableObject = {}; for (const key in obj.attributes) { const attribute = obj.attributes[key]; if (attribute.constructor.name === 'ParseObjectSubclass') { this.attributes[key] = new ParseMobx(attribute); } else if (Array.isArray(attribute)) { observableObject[key] = attribute.map((el) => el.constructor.name === 'ParseObjectSubclass' ? new ParseMobx(el) : el.constructor.name !== 'ParseRelation' && el.constructor.name !== 'ParseACL' ? el : null); } else if (attribute.constructor.name !== 'ParseRelation' && attribute.constructor.name !== 'ParseACL' && key !== 'createdAt') { observableObject[key] = attribute; } } (0, mobx_1.extendObservable)(this.attributes, observableObject); } /** * Convert a ParseObject or array of ParseObjects to ParseMobx object or array of ParseMobx objects. * @param param * @static * @returns {ParseMobx | ParseMobx[] | ((obj: Parse.Object) => any) | null} */ static toParseMobx(param) { return typeof param === 'function' ? (obj) => param(new ParseMobx(obj)) : Array.isArray(param) ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore param.map((obj) => new ParseMobx(obj)) : param ? new ParseMobx(param) : null; } /** * Delete an Item from a list of observable ParseMobx * @param {ParseMobx[]} list * @param {ParseMobx} item * @static */ static deleteListItemById(list, item) { list.splice(list.findIndex((obj) => obj.getId() === item.getId()), 1); } /** * Update an Item in the list of parse mobx observable list. * @param {ParseMobx[]} list * @param {ParseMobx} item * @deprecated * @static */ static updateListItem(list, item) { list[list.findIndex((obj) => obj.getId() === item.getId())] = item; } /** * Atomically add an object to the end of the array associated with a given key. * @param {string} attr * @param {any} item * @returns {this} */ add(attr, item) { this.checkDefined(attr, []); this.parseObj.add(attr, item); this.attributes[attr].push(item); return this; } /** * Atomically add the objects to the end of the array associated with a given key. * @param {string} attr * @param {any[]} items * @returns {this} */ addAll(attr, items) { this.checkDefined(attr, []); this.parseObj.addAll(attr, items); this.attributes[attr] = this.attributes[attr].concat(items); return this; } /** * 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 {string} attr * @param {any[]} items * @returns {this} */ addAllUnique(attr, items) { this.checkDefined(attr, []); if (this.checkType(attr, 'Array')) { items.forEach((item) => { if (this.attributes[attr].indexOf(item) === -1) { item.constructor.name === 'ParseObjectSubclass' ? this.attributes[attr].push(new ParseMobx(item)) : this.attributes[attr].push(item); } }); } this.parseObj.addAllUnique(attr, items); return this; } /** * 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 {string} key * @param value * @returns {this} */ addUnique(key, value) { this.checkDefined(key, []); if (this.checkType(key, 'Array')) { if (this.attributes[key].indexOf(value) === -1) { value.constructor.name === 'ParseObjectSubclass' ? this.attributes[key].push(new ParseMobx(value)) : this.attributes[key].push(value); } } this.parseObj.addUnique(key, value); return this; } /** * Clear Parse Object * @param options * @returns {any} */ clear(options) { return this.parseObj.clear(options); } /** * * @returns {ParseMobx} */ clone() { return new ParseMobx(this.parseObj.clone()); } /** * Destroy Object on the server. * @param {Parse.Object.DestroyOptions | undefined} options * @returns {Promise<Parse.Object>} */ destroy(options) { return this.parseObj.destroy(options); } /** * Eventually Destroy an object on the server * @param {Parse.Object.DestroyOptions | undefined} options * @returns {Promise<Parse.Object>} */ destroyEventually(options) { return this.parseObj.destroyEventually(options); } /** * * @param {string | undefined} attr * @returns {boolean} */ dirty(attr) { return this.parseObj.dirty(attr); } /** * * @returns {string[]} */ dirtyKeys() { return this.parseObj.dirtyKeys(); } /** * * @param {T} other * @returns {boolean} */ equals(other) { return this.parseObj.equals(other); } /** * * @param {string} attr * @returns {string} */ escape(attr) { return this.parseObj.escape(attr); } /** * * @returns {boolean} */ existed() { return this.parseObj.existed(); } /** * * @param {Parse.RequestOptions | undefined} options * @returns {Promise<boolean>} */ exists(options) { return this.parseObj.exists(options); } /** * * @param {Parse.Object.FetchOptions | undefined} options * @returns {Promise<this>} */ fetch(options) { return new Promise((resolve, reject) => { this.parseObj .fetch(options) .then((newParseObj) => resolve(new ParseMobx(newParseObj))) .catch(reject); }); } /** * * @returns {Promise<Parse.Object>} */ fetchFromLocalDatastore() { return this.parseObj.fetchFromLocalDatastore(); } /** * * @param {(K[] | K)[] | K} keys * @param {Parse.RequestOptions | undefined} options * @returns {Promise<this>} */ fetchWithInclude(keys, options) { return new Promise((resolve, reject) => { this.parseObj .fetchWithInclude(keys, options) .then((newParseObj) => resolve(new ParseMobx(newParseObj))) .catch(reject); }); } /** * * @param {string} key * @returns {any} */ get(key) { return this.attributes[key]; } /** * Return the id of the parse object. * @returns {string} */ getId() { return this.id; } /** * * @returns {Parse.ACL | undefined} */ getACL() { return this.parseObj.getACL(); } /** * * @param {string} attr * @returns {boolean} */ has(attr) { return this.parseObj.has(attr); } /** * * @param {string} attr * @param {number | undefined} amount * @returns {false | this} */ increment(attr, amount) { // set 0 to attr if undefined. this.checkDefined(attr, 0); if (this.checkType(attr, 'Number')) { this.attributes[attr] += amount || 0; } this.parseObj.increment(attr, amount); return this; } /** * * @param {string} attr * @param {number | undefined} amount * @returns {false | this} */ decrement(attr, amount) { this.checkDefined(attr, 0); if (this.checkType(attr, 'Number')) { this.attributes[attr] -= amount || 0; } this.parseObj.decrement(attr, amount); return this; } /** * */ initialize() { return this.parseObj.initialize(); } /** * * @returns {boolean} */ isDataAvailable() { return this.parseObj.isDataAvailable(); } /** * * @returns {boolean} */ isNew() { return this.parseObj.isNew(); } /** * * @returns {Promise<boolean>} */ isPinned() { return this.parseObj.isPinned(); } /** * * @returns {boolean} */ isValid() { return this.parseObj.isValid(); } /** * * @returns {ParseMobx} */ newInstance() { return new ParseMobx(this.parseObj.newInstance()); } /** * * @param {string} attr * @returns {any} */ op(attr) { return this.parseObj.op(attr); } /** * * @returns {Promise<void>} */ pin() { return this.parseObj.pin(); } /** * * @param {string} name * @returns {Promise<void>} */ pinWithName(name) { return this.parseObj.pinWithName(name); } /** * * @param {K} attr * @returns {Parse.Relation<any, R>} */ relation(attr) { return this.parseObj.relation(attr); } /** * * @param {string} key * @param value * @returns {this} */ remove(key, value) { this.checkDefined(key, []); if (this.checkType(key, 'Array')) { if (this.attributes[key].indexOf(value) !== -1) { this.attributes[key].splice(this.attributes[key].indexOf(value), 1); } } this.parseObj.remove(key, value); return this; } /** * * @param {string} attr * @param {any[]} items * @returns {this} */ removeAll(attr, items) { this.checkDefined(attr, []); if (this.checkType(attr, 'Array')) { items.forEach((item) => { if (this.attributes[attr].indexOf(item) !== -1) { this.attributes[attr].splice(this.attributes[attr].indexOf(item), 1); } }); } this.parseObj.removeAll(attr, items); return this; } /** * * @param {string} keys * @returns {ParseMobx} */ revert(...keys) { this.parseObj.revert(...keys); for (const key in keys) { this.attributes[key] = this.parseObj.get(key); } } /** * * @param {Parse.Object.SaveOptions} options * @returns {Promise<this>} */ save(options) { this.loading = true; return new Promise((resolve, reject) => { this.parseObj .save(options) .then(() => { (0, mobx_1.runInAction)(() => { this.loading = false; this.set('updatedAt', new Date().toISOString(), undefined); resolve(this); }); }) .catch((error) => { (0, mobx_1.runInAction)(() => { this.loading = false; reject(error); }); }); }); } /** * * @param {Parse.Object.SaveOptions | undefined} options * @returns {Promise<this>} */ saveEventually(options) { this.loading = true; return new Promise((resolve, reject) => { this.parseObj .saveEventually(options) .then(() => { (0, mobx_1.runInAction)(() => { this.loading = false; this.set('updatedAt', new Date().toISOString(), undefined); resolve(this); }); }) .catch((error) => { (0, mobx_1.runInAction)(() => { this.loading = false; reject(error); }); }); }); } /** * * @param {string} key * @param value * @param {Parse.Object.SetOptions} options * @returns {this} */ set(key, value, options) { if (value.constructor.name === 'ParseRelation') { throw new Error('You can not add relations with set'); } if (value.constructor.name === 'ParseACL') { throw new Error('Please use setACL() instead'); } if (typeof this.attributes[key] !== 'undefined') { // if it is parse subclass, create parse object. if (value.constructor.name === 'ParseObjectSubclass') { this.attributes[key] = new ParseMobx(value); } else { this.attributes[key] = value; } } else { const objToExtend = {}; objToExtend[key] = value; (0, mobx_1.extendObservable)(this.attributes, objToExtend); } this.parseObj.set(key, value, options); return this; } /** * * @param {Parse.ACL} acl * @param {Parse.SuccessFailureOptions | undefined} options * @returns {false | this} */ setACL(acl, options) { this.parseObj.setACL(acl, options); return this; } /** * * @returns {Parse.Object.ToJSON<Parse.Attributes> & Parse.JSONBaseAttributes} */ toJSON() { return this.parseObj.toJSON(); } /** * * @returns {Parse.Pointer} */ toPointer() { return this.parseObj.toPointer(); } /** * Creates an offline pointer to this object * @returns {Parse.Pointer} */ toOfflinePointer() { const parseObjAny = this.parseObj; if (parseObjAny.toOfflinePointer) { return parseObjAny.toOfflinePointer(); } // Fallback to regular pointer if offline pointer is not available return this.parseObj.toPointer(); } /** * * @returns {Promise<void>} */ unPin() { return this.parseObj.unPin(); } /** * * @param {string} name * @returns {Promise<void>} */ unPinWithName(name) { return this.parseObj.unPinWithName(name); } /** * * @param {string} attr * @param options * @returns {this} */ unset(attr, options) { this.parseObj.unset(attr, options); if (this.attributes[attr]) { delete this.attributes[attr]; } return this; } /** * * @param {Parse.Attributes} attrs * @param {Parse.SuccessFailureOptions | undefined} options * @returns {false | Parse.Error} */ validate(attrs, options) { return this.parseObj.validate(attrs, options); } /** * * @returns {Parse.Object} */ getParseObject() { return this.parseObj; } /** * Clear pending operations on the Parse object */ _clearPendingOps() { const parseObjAny = this.parseObj; if (parseObjAny._clearPendingOps) { return parseObjAny._clearPendingOps(); } } /** * Get the internal Parse object ID * @returns {string} */ _getId() { const parseObjAny = this.parseObj; if (parseObjAny._getId) { return parseObjAny._getId(); } return this.parseObj.id || ''; } /** * Get the internal state identifier * @returns {any} */ _getStateIdentifier() { const parseObjAny = this.parseObj; if (parseObjAny._getStateIdentifier) { return parseObjAny._getStateIdentifier(); } return null; } /** * Check if a value is undefined and create the attribute for it with a default value/ * @param {string} key * @param initValue * @private */ checkDefined(key, initValue) { if (typeof this.attributes[key] === 'undefined') { const objToExtend = {}; objToExtend[key] = initValue; (0, mobx_1.extendObservable)(this.attributes, objToExtend); } } /** * returns the type of attribute's value * @param {string} key * @param {string} type * @returns {boolean} * @private */ checkType(key, type) { return this.attributes[key].constructor.name === type; } } exports.ParseMobx = ParseMobx; __decorate([ mobx_1.observable ], ParseMobx.prototype, "loading", void 0); __decorate([ mobx_1.observable ], ParseMobx.prototype, "attributes", void 0); __decorate([ mobx_1.action ], ParseMobx.prototype, "add", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "addAll", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "addAllUnique", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "addUnique", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "increment", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "remove", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "removeAll", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "revert", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "save", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "saveEventually", null); __decorate([ mobx_1.action ], ParseMobx.prototype, "set", null); /** * MobxStore Class */ class MobxStore { /** * Class constructor * @param {string} parseClassName */ constructor(parseClassName) { /** * Contains the observable parseMobx objects * @type {ParseMobx[]} */ Object.defineProperty(this, "objects", { enumerable: true, configurable: true, writable: true, value: [] }); /** * Contains the parse error object * @type {Parse.Error} */ Object.defineProperty(this, "parseError", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * Return the loading state of fetching objects, adding a new object or saving objects. * @type {boolean} */ Object.defineProperty(this, "loading", { enumerable: true, configurable: true, writable: true, value: false }); /** * If liveQuery subscription is open, the value will be false. * @type {boolean} */ Object.defineProperty(this, "subscriptionOpen", { enumerable: true, configurable: true, writable: true, value: false }); /** * The parse class name. * @type {string} * @private */ Object.defineProperty(this, "parseClassName", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * The subscription object. * @type {Parse.LiveQuerySubscription} * @private */ Object.defineProperty(this, "subscription", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * * @param {ParseMobx} object * @private * @returns {ParseMobx} */ Object.defineProperty(this, "createCallback", { enumerable: true, configurable: true, writable: true, value: (object) => { return object; } }); /** * * @param {ParseMobx} object * @private * @returns {ParseMobx} */ Object.defineProperty(this, "updateCallback", { enumerable: true, configurable: true, writable: true, value: (object) => { return object; } }); /** * * @param {ParseMobx} object * @private * @returns {ParseMobx} */ Object.defineProperty(this, "enterCallback", { enumerable: true, configurable: true, writable: true, value: (object) => { return object; } }); /** * * @param {ParseMobx} object * @private * @returns {ParseMobx} */ Object.defineProperty(this, "leaveCallback", { enumerable: true, configurable: true, writable: true, value: (object) => { return object; } }); /** * * @param {ParseMobx} object * @private * @returns {ParseMobx} */ Object.defineProperty(this, "deleteCallback", { enumerable: true, configurable: true, writable: true, value: (object) => { return object; } }); this.parseClassName = parseClassName; (0, mobx_1.makeObservable)(this); } /** * Fetch Objects from parse server and update the Objects list * @param {Parse.Query} parseQuery */ fetchObjects(parseQuery) { (async () => { try { this.loading = true; const Parse = (0, config_1.getParseInstance)(); const query = parseQuery || new Parse.Query(this.parseClassName); const objects = await query.find(); (0, mobx_1.runInAction)(() => { this.loading = false; this.objects = ParseMobx.toParseMobx(objects); }); } catch (error) { this.loading = false; this.parseError = error; } })(); } /** * Create and save an object to the list of observable objects. * @param {Attributes} params * @param {CreateObjectOptions} options */ createObject(params, options) { (async () => { this.loading = true; try { const Parse = (0, config_1.getParseInstance)(); const newObject = new Parse.Object(this.parseClassName); for (const key in params) { newObject.set(key, params[key]); } (options === null || options === void 0 ? void 0 : options.saveEventually) ? await newObject.saveEventually() : await newObject.save(); (0, mobx_1.runInAction)(() => { this.loading = false; if (options === null || options === void 0 ? void 0 : options.updateList) { this.objects.push(ParseMobx.toParseMobx(newObject)); } }); } catch (error) { this.loading = false; this.parseError = error; } })(); } /** * Clear error observable */ clearError() { this.parseError = undefined; } /** * Delete an object on the server and update hte objects list. * @param {ParseMobx} obj * @param {DeleteObjectOptions} options */ deleteObject(obj, options) { (async () => { try { this.loading = true; obj.getParseObject(); (options === null || options === void 0 ? void 0 : options.deleteEventually) ? await obj.destroyEventually() : await obj.destroy(); (0, mobx_1.runInAction)(() => { this.loading = false; ParseMobx.deleteListItemById(this.objects, obj); }); } catch (error) { this.parseError = error; } })(); } /** * Subscribe to liveQuery * @param {Parse.Query} parseQuery */ subscribe(parseQuery) { (async () => { if (this.subscription) return false; // don't listen twice const query = parseQuery || new Parse.Query(this.parseClassName); this.subscription = (await query.subscribe()); if (this.subscription) { this.subscription.on('open', () => { (0, mobx_1.runInAction)(() => { this.subscriptionOpen = true; }); }); this.subscription.on('create', (object) => this.createCallback(new ParseMobx(object))); this.subscription.on('update', (object) => this.updateCallback(new ParseMobx(object))); this.subscription.on('enter', (object) => this.enterCallback(new ParseMobx(object))); this.subscription.on('leave', (object) => this.leaveCallback(new ParseMobx(object))); this.subscription.on('delete', (object) => this.deleteCallback(new ParseMobx(object))); this.subscription.on('close', () => { (0, mobx_1.runInAction)(() => { this.subscriptionOpen = false; }); }); } })(); } /** * Register a callback for onCreate * @param {EventCallback} callback */ onCreate(callback) { this.createCallback = callback; } /** * Register a callback for onUpdate * @param {EventCallback} callback */ onUpdate(callback) { this.updateCallback = callback; } /** * Register a callback for onEnter * @param {EventCallback} callback */ onEnter(callback) { this.enterCallback = callback; } /** * Register a callback for onLeave * @param {EventCallback} callback */ onLeave(callback) { this.leaveCallback = callback; } /** * Register a callback for onDelete * @param {EventCallback} callback */ onDelete(callback) { this.deleteCallback = callback; } /** * Unsubscribe from LiveQuery */ unsubscribe() { if (this.subscription) { this.subscription.unsubscribe(); this.subscription = undefined; } } } exports.MobxStore = MobxStore; __decorate([ mobx_1.observable ], MobxStore.prototype, "objects", void 0); __decorate([ mobx_1.observable ], MobxStore.prototype, "parseError", void 0); __decorate([ mobx_1.observable ], MobxStore.prototype, "loading", void 0); __decorate([ mobx_1.observable ], MobxStore.prototype, "subscriptionOpen", void 0); __decorate([ mobx_1.action ], MobxStore.prototype, "fetchObjects", null); __decorate([ mobx_1.action ], MobxStore.prototype, "createObject", null); __decorate([ mobx_1.action ], MobxStore.prototype, "clearError", null); __decorate([ mobx_1.action ], MobxStore.prototype, "deleteObject", null); __decorate([ mobx_1.action ], MobxStore.prototype, "subscribe", null);