parse
Version:
Parse JavaScript SDK
385 lines (383 loc) • 14.5 kB
JavaScript
"use strict";
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
_Object$defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _set = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set"));
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with"));
var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from"));
var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find"));
var _CoreManager = _interopRequireDefault(require("./CoreManager"));
var _LocalDatastoreController = _interopRequireDefault(require("./LocalDatastoreController"));
var _ParseQuery = _interopRequireDefault(require("./ParseQuery"));
var _LocalDatastoreUtils = require("./LocalDatastoreUtils");
/**
* Provides a local datastore which can be used to store and retrieve <code>Parse.Object</code>. <br />
* To enable this functionality, call <code>Parse.enableLocalDatastore()</code>.
*
* Pin object to add to local datastore
*
* <pre>await object.pin();</pre>
* <pre>await object.pinWithName('pinName');</pre>
*
* Query pinned objects
*
* <pre>query.fromLocalDatastore();</pre>
* <pre>query.fromPin();</pre>
* <pre>query.fromPinWithName();</pre>
*
* <pre>const localObjects = await query.find();</pre>
*
* @class Parse.LocalDatastore
* @static
*/
const LocalDatastore = {
isEnabled: false,
isSyncing: false,
fromPinWithName(name) {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.fromPinWithName(name);
},
async pinWithName(name, value) {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.pinWithName(name, value);
},
async unPinWithName(name) {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.unPinWithName(name);
},
_getAllContents() {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.getAllContents();
},
// Use for testing
async _getRawStorage() {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.getRawStorage();
},
async _clear() {
const controller = _CoreManager.default.getLocalDatastoreController();
return controller.clear();
},
// Pin the object and children recursively
// Saves the object and children key to Pin Name
async _handlePinAllWithName(name, objects) {
const pinName = this.getPinName(name);
const toPinPromises = [];
const objectKeys = [];
for (const parent of objects) {
const children = this._getChildren(parent);
const parentKey = this.getKeyForObject(parent);
const json = parent._toFullJSON(undefined, true);
if (parent._localId) {
json._localId = parent._localId;
}
children[parentKey] = json;
for (const objectKey in children) {
objectKeys.push(objectKey);
toPinPromises.push(this.pinWithName(objectKey, [children[objectKey]]));
}
}
const fromPinPromise = this.fromPinWithName(pinName);
const [pinned] = await _promise.default.all([fromPinPromise, toPinPromises]);
const toPin = [...new _set.default([...(pinned || []), ...objectKeys])];
return this.pinWithName(pinName, toPin);
},
// Removes object and children keys from pin name
// Keeps the object and children pinned
async _handleUnPinAllWithName(name, objects) {
const localDatastore = await this._getAllContents();
const pinName = this.getPinName(name);
const promises = [];
let objectKeys = [];
for (const parent of objects) {
const children = this._getChildren(parent);
const parentKey = this.getKeyForObject(parent);
objectKeys.push(parentKey, ...(0, _keys.default)(children));
}
objectKeys = [...new _set.default(objectKeys)];
let pinned = localDatastore[pinName] || [];
pinned = (0, _filter.default)(pinned).call(pinned, item => !(0, _includes.default)(objectKeys).call(objectKeys, item));
if (pinned.length == 0) {
promises.push(this.unPinWithName(pinName));
delete localDatastore[pinName];
} else {
promises.push(this.pinWithName(pinName, pinned));
localDatastore[pinName] = pinned;
}
for (const objectKey of objectKeys) {
let hasReference = false;
for (const key in localDatastore) {
if (key === _LocalDatastoreUtils.DEFAULT_PIN || (0, _startsWith.default)(key).call(key, _LocalDatastoreUtils.PIN_PREFIX)) {
const pinnedObjects = localDatastore[key] || [];
if ((0, _includes.default)(pinnedObjects).call(pinnedObjects, objectKey)) {
hasReference = true;
break;
}
}
}
if (!hasReference) {
promises.push(this.unPinWithName(objectKey));
}
}
return _promise.default.all(promises);
},
// Retrieve all pointer fields from object recursively
_getChildren(object) {
const encountered = {};
const json = object._toFullJSON(undefined, true);
for (const key in json) {
if (json[key] && json[key].__type && json[key].__type === 'Object') {
this._traverse(json[key], encountered);
}
}
return encountered;
},
_traverse(object, encountered) {
if (!object.objectId) {
return;
} else {
const objectKey = this.getKeyForObject(object);
if (encountered[objectKey]) {
return;
}
encountered[objectKey] = object;
}
for (const key in object) {
let json = object[key];
if (!object[key]) {
json = object;
}
if (json.__type && json.__type === 'Object') {
this._traverse(json, encountered);
}
}
},
// Transform keys in pin name to objects
async _serializeObjectsFromPinName(name) {
var _context;
const localDatastore = await this._getAllContents();
const allObjects = [];
for (const key in localDatastore) {
if ((0, _startsWith.default)(key).call(key, _LocalDatastoreUtils.OBJECT_PREFIX)) {
allObjects.push(localDatastore[key][0]);
}
}
if (!name) {
return allObjects;
}
const pinName = this.getPinName(name);
const pinned = localDatastore[pinName];
if (!(0, _isArray.default)(pinned)) {
return [];
}
const promises = (0, _map.default)(pinned).call(pinned, objectKey => this.fromPinWithName(objectKey));
let objects = await _promise.default.all(promises);
objects = (0, _concat.default)(_context = []).call(_context, ...objects);
return (0, _filter.default)(objects).call(objects, object => object != null);
},
// Replaces object pointers with pinned pointers
// The object pointers may contain old data
// Uses Breadth First Search Algorithm
async _serializeObject(objectKey, localDatastore) {
let LDS = localDatastore;
if (!LDS) {
LDS = await this._getAllContents();
}
if (!LDS[objectKey] || LDS[objectKey].length === 0) {
return null;
}
const root = LDS[objectKey][0];
const queue = [];
const meta = {};
let uniqueId = 0;
meta[uniqueId] = root;
queue.push(uniqueId);
while (queue.length !== 0) {
const nodeId = queue.shift();
const subTreeRoot = meta[nodeId];
for (const field in subTreeRoot) {
const value = subTreeRoot[field];
if (value.__type && value.__type === 'Object') {
const key = this.getKeyForObject(value);
if (LDS[key] && LDS[key].length > 0) {
const pointer = LDS[key][0];
uniqueId++;
meta[uniqueId] = pointer;
subTreeRoot[field] = pointer;
queue.push(uniqueId);
}
}
}
}
return root;
},
// Called when an object is save / fetched
// Update object pin value
async _updateObjectIfPinned(object) {
if (!this.isEnabled) {
return;
}
const objectKey = this.getKeyForObject(object);
const pinned = await this.fromPinWithName(objectKey);
if (!pinned || pinned.length === 0) {
return;
}
return this.pinWithName(objectKey, [object._toFullJSON()]);
},
// Called when object is destroyed
// Unpin object and remove all references from pin names
// TODO: Destroy children?
async _destroyObjectIfPinned(object) {
if (!this.isEnabled) {
return;
}
const localDatastore = await this._getAllContents();
const objectKey = this.getKeyForObject(object);
const pin = localDatastore[objectKey];
if (!pin) {
return;
}
const promises = [this.unPinWithName(objectKey)];
delete localDatastore[objectKey];
for (const key in localDatastore) {
if (key === _LocalDatastoreUtils.DEFAULT_PIN || (0, _startsWith.default)(key).call(key, _LocalDatastoreUtils.PIN_PREFIX)) {
let pinned = localDatastore[key] || [];
if ((0, _includes.default)(pinned).call(pinned, objectKey)) {
pinned = (0, _filter.default)(pinned).call(pinned, item => item !== objectKey);
if (pinned.length == 0) {
promises.push(this.unPinWithName(key));
delete localDatastore[key];
} else {
promises.push(this.pinWithName(key, pinned));
localDatastore[key] = pinned;
}
}
}
}
return _promise.default.all(promises);
},
// Update pin and references of the unsaved object
async _updateLocalIdForObject(localId, object) {
if (!this.isEnabled) {
return;
}
const localKey = `${_LocalDatastoreUtils.OBJECT_PREFIX}${object.className}_${localId}`;
const objectKey = this.getKeyForObject(object);
const unsaved = await this.fromPinWithName(localKey);
if (!unsaved || unsaved.length === 0) {
return;
}
const promises = [this.unPinWithName(localKey), this.pinWithName(objectKey, unsaved)];
const localDatastore = await this._getAllContents();
for (const key in localDatastore) {
if (key === _LocalDatastoreUtils.DEFAULT_PIN || (0, _startsWith.default)(key).call(key, _LocalDatastoreUtils.PIN_PREFIX)) {
let pinned = localDatastore[key] || [];
if ((0, _includes.default)(pinned).call(pinned, localKey)) {
pinned = (0, _filter.default)(pinned).call(pinned, item => item !== localKey);
pinned.push(objectKey);
promises.push(this.pinWithName(key, pinned));
localDatastore[key] = pinned;
}
}
}
return _promise.default.all(promises);
},
/**
* Updates Local Datastore from Server
*
* <pre>
* await Parse.LocalDatastore.updateFromServer();
* </pre>
*
* @function updateFromServer
* @name Parse.LocalDatastore.updateFromServer
* @static
*/
async updateFromServer() {
var _context2;
if (!this.checkIfEnabled() || this.isSyncing) {
return;
}
const localDatastore = await this._getAllContents();
const keys = [];
for (const key in localDatastore) {
if ((0, _startsWith.default)(key).call(key, _LocalDatastoreUtils.OBJECT_PREFIX)) {
keys.push(key);
}
}
if (keys.length === 0) {
return;
}
this.isSyncing = true;
const pointersHash = {};
for (const key of keys) {
// Ignore the OBJECT_PREFIX
let [,, className, objectId] = key.split('_');
// User key is split into [ 'Parse', 'LDS', '', 'User', 'objectId' ]
if (key.split('_').length === 5 && key.split('_')[3] === 'User') {
className = '_User';
objectId = key.split('_')[4];
}
if ((0, _startsWith.default)(objectId).call(objectId, 'local')) {
continue;
}
if (!(className in pointersHash)) {
pointersHash[className] = new _set.default();
}
pointersHash[className].add(objectId);
}
const queryPromises = (0, _map.default)(_context2 = (0, _keys.default)(pointersHash)).call(_context2, className => {
const objectIds = (0, _from.default)(pointersHash[className]);
const query = new _ParseQuery.default(className);
query.limit(objectIds.length);
if (objectIds.length === 1) {
query.equalTo('objectId', objectIds[0]);
} else {
query.containedIn('objectId', objectIds);
}
return (0, _find.default)(query).call(query);
});
try {
const responses = await _promise.default.all(queryPromises);
const objects = (0, _concat.default)([]).apply([], responses);
const pinPromises = (0, _map.default)(objects).call(objects, object => {
const objectKey = this.getKeyForObject(object);
return this.pinWithName(objectKey, object._toFullJSON());
});
await _promise.default.all(pinPromises);
this.isSyncing = false;
} catch (error) {
console.error('Error syncing LocalDatastore: ', error);
this.isSyncing = false;
}
},
getKeyForObject(object) {
const objectId = object.objectId || object._getId();
return `${_LocalDatastoreUtils.OBJECT_PREFIX}${object.className}_${objectId}`;
},
getPinName(pinName) {
if (!pinName || pinName === _LocalDatastoreUtils.DEFAULT_PIN) {
return _LocalDatastoreUtils.DEFAULT_PIN;
}
return _LocalDatastoreUtils.PIN_PREFIX + pinName;
},
checkIfEnabled() {
if (!this.isEnabled) {
console.error('Parse.enableLocalDatastore() must be called first');
}
return this.isEnabled;
}
};
var _default = exports.default = LocalDatastore;
_CoreManager.default.setLocalDatastoreController(_LocalDatastoreController.default);
_CoreManager.default.setLocalDatastore(LocalDatastore);