parse-mobx
Version:
A wrapper for ParseJS SDK to make Parse Objects observable in Mobx
1,067 lines (1,066 loc) • 30.3 kB
JavaScript
"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);