src/helpers/Helper.js
import { stringToCamel, stringToSnake } from '../utilities/FormatUtility';
import { fieldsToSnake, toJsonApiStub } from '../utilities/ClassUtility';
import Client from '../Client';
import { HelperFactory, helperFactory } from '../utilities/factories/HelperFactory';
import { flattenJsonApi } from '../utilities/JsonApiUtility';
/**
* The base class for helper classes
*/
class Helper {
/**
* Initializer for the helper function
*/
initializer() {
/** @type {Object} */
this._meta = {};
this._relationships = {};
}
/** @type {Object} */
get meta() {
return this._meta;
}
/** @type {Object} */
set meta(_meta) {
this._meta = _meta;
}
/** @type {Object} - Placeholder for relationships */
get relationships() {
const relationships = {};
Object.keys(this._relationships).forEach(k => (relationships[k] = { data: toJsonApiStub(this._relationships[k]) }));
let _return;
if (!(Object.keys(relationships).length === 0 && relationships.constructor === Object)) {
_return = { relationships };
}
return _return;
}
/** @type {String} - Create an object that will be used for JSON.stringify. Expect extending
* functions to implement this appropriately.
*/
static get type() {
return 'none';
}
/** @type {String} - Create an object that will be used for JSON.stringify. */
get type() {
return this.constructor.type;
}
/**
* Remove this object from storage
*/
async clear() {
return Client.instance.storageUtility.clear(this.type, this.id);
}
/**
* Deserialies the relationship stubs prior to synching with database
* @param {Object} json - Must be in JsonAPI Format
*/
deserializeRelationshipStubs(json) {
let relationships;
if (json && (json.relationships || (json.data && json.data.relationships))) {
// Create a clone as to not modify existing json
// this._relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
} else {
return;
}
Object.keys(relationships).forEach((key) => {
let data;
if (relationships[key] && typeof relationships[key].data !== 'undefined') {
// data can be an object or null
({ data } = relationships[key]);
} else if (typeof relationships[key] !== 'undefined') {
// An object is either already assigned
data = relationships[key];
}
if (!this._relationships[key]) {
if (Array.isArray(data)) {
this._relationships[key] = [];
data.forEach((obj) => {
if (!HelperFactory.map[obj.type]) {
// return a flattened version of the object as-is
this._relationships[key].push(flattenJsonApi(obj));
} else {
this._relationships[key].push(new HelperFactory.map[obj.type](obj));
}
});
} else if (data && data.type) {
if (HelperFactory.map[data.type]) {
this._relationships[key] = new HelperFactory.map[data.type](data);
}
} else if (typeof data !== 'undefined') {
this._relationships[key] = data;
}
}
});
}
/**
* Get the relationship from storage or if it's already associated based on the type and id.
*
* @param {Object} obj - Get relationship based on json api relationship object
* @param {Number} obj.id - The object id
* @param {String} obj.type - The object type
* @return {Promise} - Returns a Promise with the object from the relationship
*/
async getRelationship(obj) {
const storage = Client.instance.storageUtility;
return new Promise((resolve) => {
if (this[obj.type] && this[obj.type].find(o => (o.id === obj.id))) {
resolve(this[obj.type].find(o => (o.id === obj.id)));
} else {
storage.get(obj.type, obj)
.then(s => this[`_${obj.type}`].push(s))
.then(s => resolve(s));
}
});
}
/**
* Synchronize the relationships from storage to the current object
*
* @return {Promise}
*/
async syncRelationships(json) {
let relationships;
if (json && (json.relationships || (json.data && json.data.relationships))) {
// Create a clone as to not modify existing json
// this._relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
relationships = json.relationships || json.data.relationships;
} else {
return Promise.resolve();
}
// // Do relationships exist?
// if (!this._relationships) {
// return Promise.resolve();
// }
// count relationships
const storage = Client.instance.storageUtility;
return Promise.all(
Object.keys(relationships).map((key) => {
let _promise = Promise.resolve(data);
let data;
if (relationships[key] && typeof relationships[key].data !== 'undefined') {
// data can be an object or null
({ data } = relationships[key]);
} else if (typeof relationships[key] !== 'undefined') {
// An object is either already assigned
data = relationships[key];
}
// Setup getters/setters dynamically
/** @type {Any} - This is an attribute to link relationships */
const self = this;
if (self._relationships[key] && !self[stringToCamel(key)]) {
// delete self._relationships[key];
Object.defineProperty(self, stringToCamel(key), {
get: () => self._relationships[key],
set: v => (self._relationships[key] = v),
configurable: true
});
}
// Associate real data with relationships
if (Array.isArray(data)) {
/** @type {Object[]} */
if (!this._relationships[key]) {
this._relationships[key] = [];
}
_promise = Promise.all(
data.map(obj => storage.get(obj.type).then((stor) => {
// Point to the object in storage if it exists there
const _obj = (Object.keys(stor).length !== 0 && obj.id && stor[obj.id]) ? stor[obj.id] : obj;
const i = this._relationships[key].findIndex((o => _obj.id && o && o.id === _obj.id));
if (i !== -1) {
// If this already exists in _relationships, update it
this._relationships[key][i] = _obj;
} else {
// Otherwise simply add it
this._relationships[key].push(stor[_obj.id]);
}
}))
);
} else if (data && data.type) {
_promise = storage.get(data.type, { id: data.id }).then((v) => {
if (Object.keys(v).length !== 0) {
if (data.id && v[data.id]) {
this._relationships[key] = v[data.id];
} else if (data.id && v.type) {
this._relationships[key] = v;
} else if (data.id && data.type) {
helperFactory.get({ id: data.id, type: data.type }, data.type)
.then(obj => (this._relationships[key] = obj));
} else {
this._relationships[key] = v;
}
} else {
this._relationships[key] = data;
}
});
} else if (this._relationships[key] && this._relationships[key].id && this._relationships[key].type) {
// make sure that if the server set this as null, we know the difference between this and it never being set
this._relationships[key] = helperFactory.get(this._relationships[key], this._relationships[key].type);
_promise = Promise.resolve(this._relationships[key]);
} else {
this._relationships[key] = data;
_promise = Promise.resolve(this._relationships[key]);
}
return _promise;
})
);
}
/**
* Load this object from storage.
*/
async load() {
return Client.instance.storageUtility.get(this.type, { id: this.id })
.then(d => Object.assign(this, d));
}
/**
* Store this object into storage
*/
async store() {
return Client.instance.storageUtility.set(this.type, this, { id: this.id });
}
/**
* Default toJSON for json api format
* @return {Object} - returns json useable for json api
*/
toJSON() {
const obj = {};
if (this.id) {
obj.id = this.id;
}
obj.type = this.type;
obj.attributes = {};
Object.keys(this).forEach((key) => {
if (['id', 'type'].indexOf(key) === -1 && key.charAt(0) !== '_') {
if (this[key] && typeof this[key].toJSON === 'function') {
obj.attributes[stringToSnake(key)] = this[key].toJSON();
} else {
obj.attributes[stringToSnake(key)] = this[key];
}
}
});
Object.assign(obj, fieldsToSnake(this.relationships));
return obj;
}
}
export default Helper;