UNPKG

ngx-airbrush

Version:

Angular client for jsonapi/Graphiti backends

1,753 lines (1,739 loc) 67.2 kB
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