ngx-airbrush
Version:
Angular client for jsonapi/Graphiti backends
1,753 lines (1,739 loc) • 67.2 kB
JavaScript
import { NgModule } from '@angular/core';
import Jsona from 'jsona';
import { v4 } from 'uuid';
import { map, catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { HttpParams } from '@angular/common/http';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxAirbrushModule {
}
NgxAirbrushModule.decorators = [
{ type: NgModule }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const ServiceMetadata = (/** @type {?} */ (Symbol('ServiceMetadata')));
/** @type {?} */
const ModelMetadata = (/** @type {?} */ (Symbol('ModelMetadata')));
/** @type {?} */
const AttributeMetadata = (/** @type {?} */ (Symbol('AttributeMetadata')));
/** @type {?} */
const BelongsToMetadata = (/** @type {?} */ (Symbol('BelongsToMetadata')));
/** @type {?} */
const HasManyMetadata = (/** @type {?} */ (Symbol('HasManyMetadata')));
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} options
* @return {?}
*/
function ServiceDecorator(options) {
return (/**
* @param {?} target
* @return {?}
*/
(target) => {
Reflect.defineMetadata(ServiceMetadata, options, target);
});
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class TypesStore {
/**
* @private
*/
constructor() {
// tslint:disable-next-line: variable-name
this._types = {};
}
/**
* @return {?}
*/
static getInstance() {
if (!TypesStore.instance) {
TypesStore.instance = new TypesStore();
}
return TypesStore.instance;
}
/**
* @param {?} key
* @param {?} obj
* @return {?}
*/
static add(key, obj) {
TypesStore.getInstance().add(key, obj);
}
/**
* @param {?} key
* @return {?}
*/
static find(key) {
return TypesStore.getInstance().find(key);
}
/**
* @return {?}
*/
static all() {
return TypesStore.getInstance().all;
}
/**
* @param {?} key
* @param {?} obj
* @return {?}
*/
add(key, obj) {
this._types[key] = obj;
}
/**
* @param {?} key
* @return {?}
*/
find(key) {
return this.all[key];
}
/**
* @return {?}
*/
get all() { return this._types; }
}
TypesStore.instance = null;
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} options
* @return {?}
*/
function Model(options) {
return (/**
* @param {?} target
* @return {?}
*/
(target) => {
Reflect.defineMetadata(ModelMetadata, options, target);
TypesStore.add(options.type, target);
});
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class DateConverter {
/**
* @param {?} value
* @return {?}
*/
deserialize(value) {
if (!value) {
return null;
}
return new Date(value);
}
/**
* @param {?} value
* @return {?}
*/
serialize(value) {
if (!value) {
return null;
}
return value.toJSON();
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?=} options
* @return {?}
*/
function Attribute(options = {}) {
return (/**
* @param {?} target
* @param {?} propertyName
* @return {?}
*/
(target, propertyName) => {
/** @type {?} */
const converter = (/**
* @param {?} propType
* @return {?}
*/
(propType) => {
/** @type {?} */
let attrConverter;
if (options.converter) {
attrConverter = options.converter;
}
else if (propType === Date) {
attrConverter = new DateConverter();
}
else {
/** @type {?} */
const datatype = new propType();
if (datatype.serialize && datatype.deserialize) {
attrConverter = datatype;
}
}
return attrConverter;
});
/** @type {?} */
const convert = (/**
* @param {?} propType
* @param {?} value
* @param {?=} serialize
* @return {?}
*/
(propType, value, serialize = false) => {
/** @type {?} */
const attrConverter = converter(propType);
if (attrConverter) {
if (serialize) {
return attrConverter.serialize(value);
}
return attrConverter.deserialize(value);
} // else
return value;
});
/** @type {?} */
const areEquals = (/**
* @param {?} propType
* @param {?} oldValue
* @param {?} newValue
* @return {?}
*/
(propType, oldValue, newValue) => {
/** @type {?} */
const attrConverter = converter(propType);
if (attrConverter) {
if (attrConverter.areEquals) {
return attrConverter.areEquals(oldValue, newValue);
}
// else
/** @type {?} */
const newVal = attrConverter.serialize(newValue);
/** @type {?} */
const oldVal = attrConverter.serialize(oldValue);
return newVal === oldVal;
} // else
return oldValue === newValue;
});
/** @type {?} */
const setupMetadata = (/**
* @return {?}
*/
() => {
/** @type {?} */
const attributes = Reflect.getMetadata(AttributeMetadata, target) || [];
attributes.push({
propertyName,
backendName: options.backendName || propertyName,
readonly: !!options.readonly,
converter: options.converter,
});
Reflect.defineMetadata(AttributeMetadata, attributes, target);
});
/** @type {?} */
const setMetadata = (/**
* @param {?} instance
* @param {?} oldValue
* @param {?} newValue
* @return {?}
*/
(instance, oldValue, newValue) => {
/** @type {?} */
const propType = Reflect.getMetadata('design:type', target, propertyName);
instance[AttributeMetadata] = instance[AttributeMetadata] || {};
instance[AttributeMetadata][propertyName] = {
newValue,
oldValue,
backendName: options.backendName || propertyName,
hasDirtyAttributes: !areEquals(propType, oldValue, newValue),
serializedValue: convert(propType, newValue, true),
};
});
/** @type {?} */
const getter = (/**
* @return {?}
*/
function () {
return this[`_${propertyName}`];
});
/** @type {?} */
const setter = (/**
* @param {?} newVal
* @return {?}
*/
function (newVal) {
/** @type {?} */
const propType = Reflect.getMetadata('design:type', target, propertyName);
/** @type {?} */
const serialized = convert(propType, newVal);
/** @type {?} */
let oldValue = null;
if (this.isModelInitialization() && this.id) {
oldValue = serialized;
}
else {
if (this[AttributeMetadata] && this[AttributeMetadata][propertyName]) {
oldValue = this[AttributeMetadata][propertyName].oldValue;
}
}
this[`_${propertyName}`] = serialized;
setMetadata(this, oldValue, serialized);
});
if (delete target[propertyName]) {
setupMetadata();
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
});
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class JsonapiConverter {
constructor() {
this.jsona = new Jsona();
}
/**
* @param {?} data
* @return {?}
*/
deserialize(data) {
return this.jsona.deserialize(data);
}
/**
* @param {?} data
* @param {?=} includedKeys
* @return {?}
*/
serialize(data, includedKeys) {
if (!data) {
return;
}
// data preparation
this.defineTempIds(data);
/** @type {?} */
const body = (/** @type {?} */ (this.jsona.serialize({ stuff: data, includeNames: includedKeys })));
if (!body) {
return;
}
// Fix relationships and includes
if (!body.data.id) {
delete body.data.id;
}
this.addMethodsInRelations(data, body);
this.deleteMethodsInIncludes(body);
this.renameTempIdKeys(body);
return (/** @type {?} */ (body));
}
/**
* @private
* @param {?} data
* @return {?}
*/
defineTempIds(data) {
this.getRelationsKeys(data).forEach((/**
* @param {?} key
* @return {?}
*/
key => {
/** @type {?} */
const resources = data[key];
this.execOnElementOrArray(resources, (/**
* @param {?} elt
* @return {?}
*/
elt => {
if (elt.__method === 'create') {
elt.id = elt.id || v4();
}
this.defineTempIds(elt);
}));
}));
}
/**
* @private
* @param {?} data
* @param {?} body
* @return {?}
*/
addMethodsInRelations(data, body) {
this.updateMethods(data, body.data);
this.getRelationsKeys(data).filter((/**
* @param {?} key
* @return {?}
*/
key => data[key])).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.execOnElementOrArray(data[key], (/**
* @param {?} elt
* @return {?}
*/
elt => this.updateMethodsInculdes(elt, body.included)));
}));
}
/**
* @private
* @param {?} data
* @param {?} includes
* @return {?}
*/
updateMethodsInculdes(data, includes) {
/** @type {?} */
const found = includes && includes.find((/**
* @param {?} incl
* @return {?}
*/
incl => (incl.id === data.id && incl.type === data.type)));
if (!found) {
return;
}
this.updateMethods(data, found);
this.getRelationsKeys(data).filter((/**
* @param {?} key
* @return {?}
*/
key => data[key])).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.updateMethodsInculdes(data[key], includes);
}));
}
/**
* @private
* @param {?} data
* @param {?} resource
* @return {?}
*/
updateMethods(data, resource) {
/** @type {?} */
const relations = resource.relationships;
if (!relations) {
return;
}
this.getRelationsKeys(data).filter((/**
* @param {?} key
* @return {?}
*/
key => relations[key])).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.execOnElementOrArray(relations[key].data, (/**
* @param {?} elt
* @return {?}
*/
elt => elt.method = data[key].__method), (/**
* @param {?} elt
* @return {?}
*/
elt => {
/** @type {?} */
const method = data[key].find((/**
* @param {?} itm
* @return {?}
*/
itm => itm.id === elt.id)).__method;
elt.method = method;
}));
}));
}
/**
* @private
* @param {?} body
* @return {?}
*/
deleteMethodsInIncludes(body) {
/** @type {?} */
const includes = body.included || [];
includes.forEach((/**
* @param {?} incl
* @return {?}
*/
incl => {
delete incl.attributes.__method;
}));
}
/**
* @private
* @param {?} body
* @return {?}
*/
renameTempIdKeys(body) {
/** @type {?} */
const includes = body.included;
if (!includes) {
return;
}
this.setTempId(body.data, includes);
includes.forEach((/**
* @param {?} elt
* @return {?}
*/
elt => {
this.setTempId(elt, includes);
}));
}
/**
* @private
* @param {?} resource
* @param {?} includes
* @return {?}
*/
setTempId(resource, includes) {
/** @type {?} */
const relations = resource.relationships;
if (!relations) {
return;
}
/** @type {?} */
const keys = Object.keys(relations).filter((/**
* @param {?} key
* @return {?}
*/
key => (relations.hasOwnProperty(key) && relations[key])));
keys.forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.execOnElementOrArray(relations[key].data, (/**
* @param {?} elt
* @return {?}
*/
elt => {
if (elt.method === 'create') {
/** @type {?} */
const included = includes.find((/**
* @param {?} itm
* @return {?}
*/
itm => itm.id === elt.id && itm.type === elt.type));
included['temp-id'] = included.id;
elt['temp-id'] = elt.id;
delete included.id;
delete elt.id;
}
}));
}));
}
/**
* @private
* @param {?} resource
* @return {?}
*/
getRelationsKeys(resource) {
return resource.relationshipNames || [];
}
/**
* @private
* @param {?} elements
* @param {?} callback
* @param {?=} callBackArray
* @return {?}
*/
execOnElementOrArray(elements, callback, callBackArray) {
callBackArray = callBackArray || callback;
if (Array.isArray(elements)) {
elements.forEach((/**
* @param {?} elt
* @return {?}
*/
elt => callBackArray(elt)));
}
else {
callback(elements);
}
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
let getIncludedKeys;
class ResourceMetadataWrapper {
/**
* @param {?} model
*/
constructor(model) {
this.model = model;
}
/**
* @param {?=} options
* @return {?}
*/
allKeys(options) {
return [
...this.attributesKeys(options),
...this.relationshipKeys(options),
];
}
/**
* @param {?=} options
* @return {?}
*/
relationshipKeys(options) {
return [
...this.belongsToKeys(options),
...this.hasManyKeys(options)
];
}
/**
* @param {?=} options
* @return {?}
*/
attributesKeys(options) {
/** @type {?} */
let keys = this.retrieveKeys(AttributeMetadata);
if (options && options.defined) {
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.model[key] !== undefined));
// FIXME: quick fix to delete null values when it's a create
if (!this.model.id) {
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.model[key] !== null));
}
}
return keys;
}
/**
* @param {?=} options
* @return {?}
*/
belongsToKeys(options) {
return this.retrieveKeys(BelongsToMetadata, options);
}
/**
* @param {?=} options
* @return {?}
*/
hasManyKeys(options) {
return this.retrieveKeys(HasManyMetadata, options);
}
/**
* @return {?}
*/
allMetadata() {
return [
...this.attributesMetadata(),
...this.relationshipMetadata(),
];
}
/**
* @return {?}
*/
relationshipMetadata() {
return [
...this.belongsToMetadata(),
...this.hasManyMetadata()
];
}
/**
* @return {?}
*/
attributesMetadata() {
return this.retrieveMetadata(AttributeMetadata);
}
/**
* @return {?}
*/
belongsToMetadata() {
return this.retrieveMetadata(BelongsToMetadata);
}
/**
* @return {?}
*/
hasManyMetadata() {
return this.retrieveMetadata(HasManyMetadata);
}
/**
* @param {?} propertyName
* @return {?}
*/
isSidepostable(propertyName) {
/** @type {?} */
let metadata = Reflect.getMetadata(BelongsToMetadata, this.model);
/** @type {?} */
let found = metadata && metadata.find((/**
* @param {?} rel
* @return {?}
*/
rel => rel.propertyName === propertyName));
if (found) {
return found.sidepostable;
}
metadata = Reflect.getMetadata(HasManyMetadata, this.model);
found = metadata && metadata.find((/**
* @param {?} rel
* @return {?}
*/
rel => rel.propertyName === propertyName));
if (found) {
return found.sidepostable;
}
}
/**
* @param {?} propertyName
* @return {?}
*/
isWritable(propertyName) {
/** @type {?} */
const allMetadata = this.allMetadata();
/** @type {?} */
const found = allMetadata.find((/**
* @param {?} pname
* @return {?}
*/
pname => pname.propertyName === propertyName));
if (found) {
return !found.readonly;
}
}
/**
* @param {?} propertyName
* @return {?}
*/
backendPropName(propertyName) {
/** @type {?} */
const allMetadata = this.allMetadata();
/** @type {?} */
const found = allMetadata.find((/**
* @param {?} pname
* @return {?}
*/
pname => pname.propertyName === propertyName));
if (found) {
return found.backendName;
}
}
/**
* @param {?} propertyName
* @return {?}
*/
frontendPropName(propertyName) {
/** @type {?} */
const allMetadata = this.allMetadata();
// search in backend
/** @type {?} */
let found = allMetadata.find((/**
* @param {?} pname
* @return {?}
*/
pname => pname.backendName === propertyName));
if (found) {
return found.propertyName;
}
// search in frontend
found = allMetadata.find((/**
* @param {?} pname
* @return {?}
*/
pname => pname.propertyName === propertyName));
if (found) {
return propertyName;
}
}
/**
* @private
* @param {?} symbol
* @param {?=} options
* @return {?}
*/
retrieveKeys(symbol, options) {
this.model.modelSerialization = true;
/** @type {?} */
const metadata = this.retrieveMetadata(symbol);
/** @type {?} */
let keys = metadata.map((/**
* @param {?} btm
* @return {?}
*/
btm => btm.propertyName));
if (options && options.defined) {
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.model[key]));
}
this.model.modelSerialization = false;
return keys;
}
/**
* @private
* @param {?} symbol
* @return {?}
*/
retrieveMetadata(symbol) {
return Reflect.getMetadata(symbol, this.model) || [];
}
/**
* @param {?=} options
* @return {?}
*/
getIncludedKeys(options) {
options = (options || {}) && Object.assign({ forBackend: true }, options);
return getIncludedKeys(this.model, this, options);
}
}
getIncludedKeys = (/**
* @param {?} model
* @param {?} wrapper
* @param {?=} options
* @return {?}
*/
(model, wrapper, options) => {
/** @type {?} */
const keyTranslator = options.forBackend ? 'backendPropName' : 'frontendPropName';
/** @type {?} */
let localKeys = wrapper.relationshipKeys({ defined: true }).filter((/**
* @param {?} key
* @return {?}
*/
key => wrapper.isSidepostable(key)));
/** @type {?} */
const childKeys = [];
localKeys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
/** @type {?} */
const resource = model[key];
/** @type {?} */
let cKeys = [];
if (Array.isArray(resource)) {
resource.forEach((/**
* @param {?} elt
* @return {?}
*/
elt => {
/** @type {?} */
const wrap = new ResourceMetadataWrapper(elt);
/** @type {?} */
const keys = getIncludedKeys(elt, wrap, options);
cKeys.push(...keys);
}));
}
else {
/** @type {?} */
const wrap = new ResourceMetadataWrapper(resource);
/** @type {?} */
const keys = getIncludedKeys(resource, wrap, options);
cKeys.push(...keys);
}
cKeys = cKeys.map((/**
* @param {?} ckey
* @return {?}
*/
ckey => `${wrapper[keyTranslator](key)}.${ckey}`));
// remove duplicates
cKeys = cKeys.filter((/**
* @param {?} item
* @param {?} index
* @return {?}
*/
(item, index) => cKeys.indexOf(item) >= index));
childKeys.push(...cKeys);
}));
localKeys = localKeys.map((/**
* @param {?} lkey
* @return {?}
*/
lkey => wrapper[keyTranslator](lkey)));
return [...localKeys, ...childKeys];
});
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class ResourceSerializer {
/**
* @param {?} model
*/
constructor(model) {
this.model = model;
this.mdwrapper = new ResourceMetadataWrapper(model);
}
/**
* @return {?}
*/
toJsonapi() {
/** @type {?} */
const result = this.serialize();
/** @type {?} */
const includeKeys = this.mdwrapper.getIncludedKeys();
/** @type {?} */
const body = new JsonapiConverter().serialize(result, includeKeys);
return body;
}
/**
* @return {?}
*/
serialize() {
/** @type {?} */
const type = this.model.type;
const [attributes, belongstos, hasManys] = this.serializeAll();
/** @type {?} */
const relationshipNames = [];
relationshipNames.push(...Object.keys(belongstos).filter((/**
* @param {?} key
* @return {?}
*/
key => belongstos.hasOwnProperty(key))));
relationshipNames.push(...Object.keys(hasManys).filter((/**
* @param {?} key
* @return {?}
*/
key => hasManys.hasOwnProperty(key))));
/** @type {?} */
const result = Object.assign({ id: this.model.id, type }, attributes, belongstos, hasManys, { relationshipNames });
if (this.model.isRelation) {
result.__method = this.model.relationMethod;
}
return result;
}
/**
* @return {?}
*/
toHash() {
/** @type {?} */
const res = { id: this.model.id, type: this.model.type };
this.mdwrapper.attributesKeys({ defined: true }).forEach((/**
* @param {?} key
* @return {?}
*/
key => res[key] = this.model[key]));
this.mdwrapper.belongsToKeys({ defined: true }).forEach((/**
* @param {?} key
* @return {?}
*/
key => res[key] = this.model[key].toHash()));
this.mdwrapper.hasManyKeys({ defined: true }).forEach((/**
* @param {?} key
* @return {?}
*/
key => {
this.model[key].forEach((/**
* @param {?} elt
* @return {?}
*/
elt => {
res[key] = res[key] || []; // no need to init res[key] if array has no elements.
res[key].push(elt.toHash());
}));
}));
return res;
}
/**
* @private
* @return {?}
*/
serializeAll() {
/** @type {?} */
const attributes = this.serializeAttributes();
/** @type {?} */
const belongstos = this.serializeBelongsTo();
/** @type {?} */
const hasManys = this.serializeHasMany();
return [attributes, belongstos, hasManys];
}
/**
* @private
* @return {?}
*/
serializeAttributes() {
/** @type {?} */
let keys = this.mdwrapper.attributesKeys({ defined: true });
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.mdwrapper.isWritable(key)));
/** @type {?} */
const attributes = this.model[AttributeMetadata];
keys = this.model.id ? keys.filter((/**
* @param {?} key
* @return {?}
*/
key => attributes[key].hasDirtyAttributes)) : keys;
/** @type {?} */
const result = {};
keys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
/** @type {?} */
const beKey = this.mdwrapper.backendPropName(key);
result[beKey] = attributes[key].serializedValue;
}));
return result;
}
/**
* @private
* @return {?}
*/
serializeBelongsTo() {
/** @type {?} */
let keys = this.mdwrapper.belongsToKeys({ defined: true });
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.mdwrapper.isWritable(key)));
// Ignore untouched relationships
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.model[key].hasBeenTouched));
/** @type {?} */
const belongstos = {};
keys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
/** @type {?} */
const sidepostable = this.mdwrapper.isSidepostable(key);
/** @type {?} */
const beKey = this.mdwrapper.backendPropName(key);
belongstos[beKey] = sidepostable ? this.model[key].serialize() : this.model[key].sideload();
if (!belongstos[beKey]) {
delete belongstos[beKey];
}
}));
return belongstos;
}
/**
* @private
* @return {?}
*/
serializeHasMany() {
/** @type {?} */
let keys = this.mdwrapper.hasManyKeys({ defined: true });
keys = keys.filter((/**
* @param {?} key
* @return {?}
*/
key => this.mdwrapper.isWritable(key)));
/** @type {?} */
const hasManys = {};
keys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
/** @type {?} */
const sidepostable = this.mdwrapper.isSidepostable(key);
/** @type {?} */
const beKey = this.mdwrapper.backendPropName(key);
hasManys[beKey] = sidepostable ?
this.model[key].filter((/**
* @param {?} elt
* @return {?}
*/
elt => elt.hasBeenTouched)).map((/**
* @param {?} elt
* @return {?}
*/
elt => elt.serialize())) :
this.model[key].filter((/**
* @param {?} elt
* @return {?}
*/
elt => elt.hasBeenTouched)).map((/**
* @param {?} elt
* @return {?}
*/
elt => elt.sideload()));
if (hasManys[beKey].length === 0) {
delete hasManys[beKey];
}
}));
return hasManys;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @abstract
*/
class ResourceBase {
/**
* @param {?=} data
*/
constructor(data) {
this.modelInitialization = false;
this.modelSerialization = false;
this.__isRelation = false;
if (!data) {
return;
}
this.modelInitialization = true;
this.id = data.id;
this.assign(data);
this.modelInitialization = false;
}
/**
* @private
* @return {?}
*/
get __wrapper() {
if (!this.__mdwrapper) {
this.__mdwrapper = new ResourceMetadataWrapper(this);
}
return this.__mdwrapper;
}
/**
* @private
* @return {?}
*/
get __serializer() {
if (!this.__resSerializer) {
this.__resSerializer = new ResourceSerializer(this);
}
return this.__resSerializer;
}
/**
* @template T
* @param {?} constructor
* @param {?=} data
* @return {?}
*/
static getModel(constructor, data) {
/** @type {?} */
const ret = new JsonapiConverter().deserialize(data);
return new constructor(ret);
}
/**
* @template T
* @param {?} constructor
* @param {?} data
* @param {?=} options
* @return {?}
*/
static getAsRelationship(constructor, data, options) {
if (!data) {
return null;
}
options = Object.assign({ method: 'untouched', modelInit: false }, options);
/** @type {?} */
let model;
if (options.modelInit) {
model = new constructor(data);
}
else {
model = new constructor();
model.id = data.id;
model.assign(data);
}
model.__isRelation = true;
model.__method = options.method;
return model;
}
/**
* @return {?}
*/
isModelInitialization() {
return this.modelInitialization;
}
/**
* @return {?}
*/
get modelOptions() {
return Reflect.getMetadata(ModelMetadata, this.constructor);
}
/**
* @return {?}
*/
get type() {
return this.modelOptions.type;
}
/**
* @return {?}
*/
get hasBeenTouched() {
/** @type {?} */
const touched = this.__isRelation && this.__method !== 'untouched';
return this.hasDirtyAttributes || this.hasDirtyBelongsTos || this.hasDirtyHasManys || touched;
}
/**
* @return {?}
*/
get hasDirtyAttributes() {
/** @type {?} */
const attributesMetadata = this[AttributeMetadata];
for (const propertyName in attributesMetadata) {
if (attributesMetadata.hasOwnProperty(propertyName) && attributesMetadata[propertyName].hasDirtyAttributes) {
return true;
}
}
return false;
}
/**
* @return {?}
*/
get hasDirtyBelongsTos() {
/** @type {?} */
const keys = this.__wrapper.belongsToKeys({ defined: true });
/** @type {?} */
let touched = false;
keys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
touched = touched || this[key].hasBeenTouched;
}));
return touched;
}
/**
* @return {?}
*/
get hasDirtyHasManys() {
/** @type {?} */
const keys = this.__wrapper.hasManyKeys({ defined: true });
/** @type {?} */
let touched = false;
keys.forEach((/**
* @param {?} key
* @return {?}
*/
key => {
touched = touched || this[key].map((/**
* @param {?} elt
* @return {?}
*/
elt => elt.hasBeenTouched)).reduce((/**
* @param {?} res
* @param {?} bool
* @return {?}
*/
(res, bool) => res || bool), false);
}));
return touched;
}
/**
* @return {?}
*/
get isRelation() {
return this.__isRelation;
}
/**
* @return {?}
*/
get relationMethod() {
if (this.__method !== 'untouched') {
return this.__method;
}
if (!this.hasBeenTouched) {
return 'untouched';
}
if (this.id) {
return 'update';
}
return 'create';
}
/**
* @return {?}
*/
rollbackAttributes() {
/** @type {?} */
const attributesMetadata = this[AttributeMetadata];
for (const propertyName in attributesMetadata) {
if (attributesMetadata.hasOwnProperty(propertyName) && attributesMetadata[propertyName].hasDirtyAttributes) {
this[propertyName] = attributesMetadata[propertyName].oldValue;
}
}
}
/**
* @param {?} data
* @return {?}
*/
assign(data) {
/** @type {?} */
const clone = this.translateKeys(data);
Object.assign(this, clone);
if (this.__isRelation) {
this.__method = this.relationMethod;
}
}
/**
* @return {?}
*/
disassociate() {
this.__method = 'disassociate';
}
/**
* @return {?}
*/
destroy() {
if (this.isRelation) {
this.__method = 'destroy';
}
else {
throw new Error('Destroying resource is not supported! Use the service instead.');
}
}
/**
* @return {?}
*/
toJsonapi() {
return this.__serializer.toJsonapi();
}
/**
* @return {?}
*/
toHash() {
return this.__serializer.toHash();
}
/**
* @return {?}
*/
sideload() {
/** @type {?} */
const method = this.relationMethod;
if (method === 'create' || method === 'untouched') {
return;
}
/** @type {?} */
const type = this.modelOptions.type;
return {
id: this.id,
type,
__method: method,
};
}
/**
* @return {?}
*/
serialize() {
return this.__serializer.serialize();
}
/**
* @private
* @param {?} obj
* @return {?}
*/
translateKeys(obj) {
if (obj.toHash) {
obj = obj.toHash();
}
/** @type {?} */
const filtred = {};
for (const oldkey in obj) {
if (obj.hasOwnProperty(oldkey)) {
/** @type {?} */
const newkey = this.__wrapper.frontendPropName(oldkey.toString());
if (newkey) {
filtred[newkey] = obj[oldkey];
}
}
}
return filtred;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?=} options
* @return {?}
*/
function BelongsTo(options = {}) {
return (/**
* @param {?} target
* @param {?} propertyName
* @return {?}
*/
(target, propertyName) => {
/** @type {?} */
const setupMetadata = (/**
* @return {?}
*/
() => {
/** @type {?} */
const belongsTos = Reflect.getMetadata(BelongsToMetadata, target) || [];
belongsTos.push({
propertyName,
backendName: options.backendName || propertyName,
readonly: !!options.readonly,
sidepostable: !!options.sidepostable,
});
Reflect.defineMetadata(BelongsToMetadata, belongsTos, target);
});
/** @type {?} */
const getClass = (/**
* @param {?=} fromVal
* @return {?}
*/
(fromVal) => {
// const klass = options.class();
if (!Array.isArray(options.class)) {
return TypesStore.find(options.class);
}
/** @type {?} */
const klasses = options.class.map((/**
* @param {?} klass
* @return {?}
*/
klass => TypesStore.find(klass)));
if (!fromVal) {
return klasses[0];
}
/** @type {?} */
const type = fromVal.type;
if (!type) {
throw new Error('Object should be a subclass of ResourceBase or should have a type attribute!');
}
/** @type {?} */
const prop = klasses.find((/**
* @param {?} k
* @return {?}
*/
k => {
/** @type {?} */
const obj = new k();
return obj.type === type;
}));
if (!prop) {
throw new Error('Object should be a subclass of ResourceBase or should have a type attribute!');
}
return prop;
});
/** @type {?} */
const updateSideload = (/**
* @param {?} propClass
* @param {?} oldValue
* @param {?} newValue
* @return {?}
*/
(propClass, oldValue, newValue) => {
/** @type {?} */
const noOldId = !(oldValue && oldValue.id);
/** @type {?} */
const noNewId = !(newValue && newValue.id);
/** @type {?} */
const noIds = noOldId && noNewId;
/** @type {?} */
const eqIds = noOldId || noNewId ? false : oldValue.id === newValue.id;
// no ids: can't create a resource. This is a sideload!
if (noIds) {
return null;
}
// new don't exist: disassociate old from the resource
if (noNewId || newValue.deleteRelation) {
oldValue.disassociate();
return oldValue;
}
// same ids: can't update a resource. This is a sideload!
if (eqIds) {
/** @type {?} */
const type1 = oldValue.type;
/** @type {?} */
const type2 = newValue.type;
if (!type2 || type1 === type2) {
return oldValue;
}
}
// no old or different IDs: create new relationship
return ResourceBase.getAsRelationship(propClass, newValue, { method: 'update' });
});
/** @type {?} */
const updateSidepost = (/**
* @param {?} propClass
* @param {?} oldValue
* @param {?} newValue
* @return {?}
*/
(propClass, oldValue, newValue) => {
/** @type {?} */
const oldHaveId = !!(oldValue && oldValue.id);
/** @type {?} */
const newHaveId = !!(newValue && newValue.id);
/** @type {?} */
const newHaveVal = !!(newValue && Object.keys(newValue).filter((/**
* @param {?} key
* @return {?}
*/
key => key !== 'id' && key !== 'type')).length > 0);
/** @type {?} */
const eqIds = oldHaveId && newHaveId ? oldValue.id === newValue.id : false;
// equal ids: update old
if (eqIds) {
if (newValue.deleteRelation) {
oldValue.disassociate();
}
if (newValue.destroyRelation) {
oldValue.destroy();
}
oldValue.assign(newValue);
return oldValue;
}
// new have an id: change old by new
if (newHaveId) {
return ResourceBase.getAsRelationship(propClass, newValue, { method: 'update' });
}
// new don't have id but have value: create new
if (newHaveVal) {
return ResourceBase.getAsRelationship(propClass, newValue, { method: 'create' });
}
// new empty and old null: return null;
if (!oldValue) {
return null;
}
// new empty: disassociate old from the resource
oldValue.disassociate();
return oldValue;
});
/** @type {?} */
const getter = (/**
* @return {?}
*/
function () {
if (!this[`_${propertyName}`] && !this.modelSerialization) {
/** @type {?} */
const propClass = getClass();
this[`_${propertyName}`] = ResourceBase.getAsRelationship(propClass, (/** @type {?} */ ({})));
}
return this[`_${propertyName}`];
});
/** @type {?} */
const setter = (/**
* @param {?} newVal
* @return {?}
*/
function (newVal) {
/** @type {?} */
const propClass = getClass(newVal);
if (this.isModelInitialization()) {
this[`_${propertyName}`] = ResourceBase.getAsRelationship(propClass, newVal, { modelInit: true });
}
else {
/** @type {?} */
const oldVal = this[`_${propertyName}`];
this[`_${propertyName}`] = options.sidepostable ?
updateSidepost(propClass, oldVal, newVal) :
updateSideload(propClass, oldVal, newVal);
}
});
if (delete target[propertyName]) {
setupMetadata();
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
});
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?=} options
* @return {?}
*/
function HasMany(options = {}) {
return (/**
* @param {?} target
* @param {?} propertyName
* @return {?}
*/
(target, propertyName) => {
/** @type {?} */
const setupMetadata = (/**
* @return {?}
*/
() => {
/** @type {?} */
const hasManys = Reflect.getMetadata(HasManyMetadata, target) || [];
hasManys.push({
propertyName,
backendName: options.backendName || propertyName,
readonly: !!options.readonly,
sidepostable: !!options.sidepostable,
});
Reflect.defineMetadata(HasManyMetadata, hasManys, target);
});
/** @type {?} */
const updateSideload = (/**
* @param {?} propClass
* @param {?} oldValues
* @param {?} newValues
* @return {?}
*/
(propClass, oldValues, newValues) => {
if (!newValues) {
return oldValues;
}
/** @type {?} */
const notConcerned = oldValues && oldValues.filter((/**
* @param {?} oldVal
* @return {?}
*/
oldVal => (newValues.findIndex((/**
* @param {?} newVal
* @return {?}
*/
newVal => oldVal.id === newVal.id)) === -1)));
/** @type {?} */
const result = notConcerned || [];
newValues.forEach((/**
* @param {?} newVal
* @return {?}
*/
newVal => {
// no ids: can't create a resource. This is a sideload!
if (!newVal.id) {
return;
}
/** @type {?} */
const found = oldValues && oldValues.find((/**
* @param {?} oldVal
* @return {?}
*/
oldVal => oldVal.id === newVal.id));
// no old or id not found: create new relationship
if (!found) {
result.push(ResourceBase.getAsRelationship(propClass, newVal, { method: 'update' }));
return;
}
// mark for disassociation
if (newVal.deleteRelation) {
found.disassociate();
}
// keep existing relation
result.push(found);
}));
return result;
});
/** @type {?} */
const updateSidepost = (/**
* @param {?} propClass
* @param {?} oldValues
* @param {?} newValues
* @return {?}
*/
(propClass, oldValues, newValues) => {
if (!newValues) {
return oldValues;
}
/** @type {?} */
const notConcerned = oldValues && oldValues.filter((/**
* @param {?} oldVal
* @return {?}
*/
oldVal => (newValues.findIndex((/**
* @param {?} newVal
* @return {?}
*/
newVal => oldVal.id === newVal.id)) === -1)));
/** @type {?} */
const result = notConcerned || [];
newValues.forEach((/**
* @param {?} newVal
* @return {?}
*/
newVal => {
/** @type {?} */
const newHaveId = !!(newVal && newVal.id);
/** @type {?} */
const newHaveVal = !!(newVal && Object.keys(newVal).filter((/**
* @param {?} key
* @return {?}
*/
key => key !== 'id' && key !== 'type')).length > 0);
// new don't have id and value: do nothing!
if (!newHaveId && !newHaveVal) {
return;
}
// new don't have id but have value: create new
if (!newHaveId) {
result.push(ResourceBase.getAsRelationship(propClass, newVal, { method: 'create' }));
return;
}
/** @type {?} */
const found = oldValues && oldValues.find((/**
* @param {?} oldVal
* @return {?}
*/
oldVal => oldVal.id === newVal.id));
// no old or id not found: create new relationship
if (!found) {
result.push(ResourceBase.getAsRelationship(propClass, newVal, { method: 'update' }));
return;
}
// mark for disassociation
if (newVal.deleteRelation || newVal.destroyRelation) {
newVal.destroyRelation ? found.destroy() : found.disassociate();
result.push(found);
return;
}
// keep existing relation
found.assign(newVal);
result.push(found);
}));
return result;
});
/** @type {?} */
const getter = (/**
* @return {?}
*/
function () {
if (!this[`_${propertyName}`] && !this.modelSerialization) {
this[`_${propertyName}`] = [];
}
return this[`_${propertyName}`];
});
/** @type {?} */
const setter = (/**
* @param {?} newVal
* @return {?}
*/
function (newVal) {
// const propClass = options.class();
/** @type {?} */
const propClass = TypesStore.find(options.class);
if (this.isModelInitialization()) {
this[`_${propertyName}`] = newVal.map((/**
* @param {?} elt