UNPKG

@datorama/akita

Version:

State Management Tailored-Made for JS Applications

1,879 lines (1,844 loc) 155 kB
import { __rest, __decorate, __metadata } from 'tslib'; import { BehaviorSubject, of, Subject, ReplaySubject, from, isObservable, combineLatest, merge, EMPTY } from 'rxjs'; import { tap, distinctUntilChanged, map, filter, switchMap, skip, delay, take, debounceTime, pairwise, auditTime } from 'rxjs/operators'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template T * @param {?} value * @return {?} */ function isArray(value) { return Array.isArray(value); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template T * @param {?} arr * @return {?} */ function isEmpty(arr) { if (isArray(arr)) { return arr.length === 0; } return false; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template E * @param {?} entities * @param {?} idKey * @param {?} preAddEntity * @return {?} */ function toEntitiesObject(entities, idKey, preAddEntity) { /** @type {?} */ const acc = { entities: {}, ids: [] }; for (const entity of entities) { // evaluate the middleware first to support dynamic ids /** @type {?} */ const current = preAddEntity(entity); acc.entities[current[idKey]] = current; acc.ids.push(current[idKey]); } return acc; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template E * @param {?} entities * @param {?} id * @return {?} */ function hasEntity(entities, id) { return entities.hasOwnProperty(id); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template E * @param {?} state * @return {?} */ function hasActiveState(state) { return state.hasOwnProperty('active'); } // @internal /** * @param {?} active * @return {?} */ function isMultiActiveState(active) { return isArray(active); } // @internal /** * @template E * @param {?} __0 * @return {?} */ function resolveActiveEntity({ active, ids, entities }) { if (isMultiActiveState(active)) { return getExitingActives(active, ids); } if (hasEntity(entities, active) === false) { return null; } return active; } // @internal /** * @param {?} currentActivesIds * @param {?} newIds * @return {?} */ function getExitingActives(currentActivesIds, newIds) { /** @type {?} */ const filtered = currentActivesIds.filter((/** * @param {?} id * @return {?} */ id => newIds.indexOf(id) > -1)); /** Return the same reference if nothing has changed */ if (filtered.length === currentActivesIds.length) { return currentActivesIds; } return filtered; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template Entity * @param {?} state * @return {?} */ function isEntityState(state) { return state.entities && state.ids; } // @internal /** * @template E * @param {?} entities * @param {?} preAddEntity * @return {?} */ function applyMiddleware(entities, preAddEntity) { /** @type {?} */ let mapped = {}; for (const id of Object.keys(entities)) { mapped[id] = preAddEntity(entities[id]); } return mapped; } // @internal /** * @template S, E * @param {?} __0 * @return {?} */ function setEntities({ state, entities, idKey, preAddEntity, isNativePreAdd }) { /** @type {?} */ let newEntities; /** @type {?} */ let newIds; if (isArray(entities)) { /** @type {?} */ const resolve = toEntitiesObject(entities, idKey, preAddEntity); newEntities = resolve.entities; newIds = resolve.ids; } else if (isEntityState(entities)) { newEntities = isNativePreAdd ? entities.entities : applyMiddleware(entities.entities, preAddEntity); newIds = entities.ids; } else { // it's an object newEntities = isNativePreAdd ? entities : applyMiddleware(entities, preAddEntity); newIds = Object.keys(newEntities).map((/** * @param {?} id * @return {?} */ id => (isNaN((/** @type {?} */ (id))) ? id : Number(id)))); } /** @type {?} */ const newState = Object.assign({}, state, { entities: newEntities, ids: newIds, loading: false }); if (hasActiveState(state)) { newState.active = resolveActiveEntity((/** @type {?} */ (newState))); } return newState; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal class AkitaError extends Error { /** * @param {?} message */ constructor(message) { super(message); } } // @internal /** * @param {?} name * @param {?} className * @return {?} */ function assertStoreHasName(name, className) { if (!name) { console.error(`@StoreConfig({ name }) is missing in ${className}`); } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const currentAction = { type: null, entityIds: null, skip: false }; /** @type {?} */ let customActionActive = false; /** * @return {?} */ function resetCustomAction() { customActionActive = false; } // public API for custom actions. Custom action always wins /** * @param {?} type * @param {?=} entityIds * @return {?} */ function logAction(type, entityIds) { setAction(type, entityIds); customActionActive = true; } /** * @param {?} type * @param {?=} entityIds * @return {?} */ function setAction(type, entityIds) { if (customActionActive === false) { currentAction.type = type; currentAction.entityIds = entityIds; } } /** * @param {?=} skip * @return {?} */ function setSkipAction(skip$$1 = true) { currentAction.skip = skip$$1; } /** * @param {?} action * @param {?=} entityIds * @return {?} */ function action(action, entityIds) { return (/** * @param {?} target * @param {?} propertyKey * @param {?} descriptor * @return {?} */ function (target, propertyKey, descriptor) { /** @type {?} */ const originalMethod = descriptor.value; descriptor.value = (/** * @param {...?} args * @return {?} */ function (...args) { logAction(action, entityIds); return originalMethod.apply(this, args); }); return descriptor; }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** @type {?} */ const transactionFinished = new Subject(); // @internal /** @type {?} */ const transactionInProcess = new BehaviorSubject(false); // @internal /** @type {?} */ const transactionManager = { activeTransactions: 0, batchTransaction: null }; // @internal /** * @return {?} */ function startBatch() { if (!isTransactionInProcess()) { transactionManager.batchTransaction = new Subject(); } transactionManager.activeTransactions++; transactionInProcess.next(true); } // @internal /** * @return {?} */ function endBatch() { if (--transactionManager.activeTransactions === 0) { transactionManager.batchTransaction.next(true); transactionManager.batchTransaction.complete(); transactionInProcess.next(false); transactionFinished.next(true); } } // @internal /** * @return {?} */ function isTransactionInProcess() { return transactionManager.activeTransactions > 0; } // @internal /** * @return {?} */ function commit() { return transactionManager.batchTransaction ? transactionManager.batchTransaction.asObservable() : of(true); } /** * A logical transaction. * Use this transaction to optimize the dispatch of all the stores. * The following code will update the store, BUT emits only once * * \@example * applyTransaction(() => { * this.todosStore.add(new Todo(1, title)); * this.todosStore.add(new Todo(2, title)); * }); * * @template T * @param {?} action * @param {?=} thisArg * @return {?} */ function applyTransaction(action$$1, thisArg = undefined) { startBatch(); try { return action$$1.apply(thisArg); } finally { logAction('@Transaction'); endBatch(); } } /** * A logical transaction. * Use this transaction to optimize the dispatch of all the stores. * * The following code will update the store, BUT emits only once. * * \@example * \@transaction * addTodos() { * this.todosStore.add(new Todo(1, title)); * this.todosStore.add(new Todo(2, title)); * } * * * @return {?} */ function transaction() { return (/** * @param {?} target * @param {?} propertyKey * @param {?} descriptor * @return {?} */ function (target, propertyKey, descriptor) { /** @type {?} */ const originalMethod = descriptor.value; descriptor.value = (/** * @param {...?} args * @return {?} */ function (...args) { return applyTransaction((/** * @return {?} */ () => { return originalMethod.apply(this, args); }), this); }); return descriptor; }); } /** * * RxJS custom operator that wraps the callback inside transaction * * \@example * * return http.get().pipe( * withTransaction(response > { * store.setActive(1); * store.update(); * store.updateEntity(1, {}); * }) * ) * * @template T * @param {?} transactionFn * @return {?} */ function withTransaction(transactionFn) { return (/** * @param {?} source * @return {?} */ function (source) { return source.pipe(tap((/** * @param {?} value * @return {?} */ value => applyTransaction((/** * @return {?} */ () => transactionFn(value)))))); }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} o * @return {?} */ function deepFreeze(o) { Object.freeze(o); /** @type {?} */ const oIsFunction = typeof o === 'function'; /** @type {?} */ const hasOwnProp = Object.prototype.hasOwnProperty; Object.getOwnPropertyNames(o).forEach((/** * @param {?} prop * @return {?} */ function (prop) { if (hasOwnProp.call(o, prop) && (oIsFunction ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments' : true) && o[prop] !== null && (typeof o[prop] === 'object' || typeof o[prop] === 'function') && !Object.isFrozen(o[prop])) { deepFreeze(o[prop]); } })); return o; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const configKey = 'akitaConfig'; /** * @param {?} metadata * @return {?} */ function StoreConfig(metadata) { return (/** * @param {?} constructor * @return {?} */ function (constructor) { constructor[configKey] = { idKey: 'id' }; for (let i = 0, keys = Object.keys(metadata); i < keys.length; i++) { /** @type {?} */ const key = keys[i]; /* name is preserved read only key */ if (key === 'name') { constructor[configKey]['storeName'] = metadata[key]; } else { constructor[configKey][key] = metadata[key]; } } }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ let CONFIG = { resettable: false }; /** * @param {?} config * @return {?} */ function akitaConfig(config) { CONFIG = Object.assign({}, CONFIG, config); } // @internal /** * @return {?} */ function getAkitaConfig() { return CONFIG; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} value * @return {?} */ function toBoolean(value) { return value != null && `${value}` !== 'false'; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} value * @return {?} */ function isPlainObject(value) { return toBoolean(value) && value.constructor.name === 'Object'; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} value * @return {?} */ function isFunction(value) { return typeof value === 'function'; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** @type {?} */ const $$deleteStore = new Subject(); // @internal /** @type {?} */ const $$addStore = new ReplaySubject(50, 5000); // @internal /** @type {?} */ const $$updateStore = new Subject(); // @internal /** * @param {?} storeName * @return {?} */ function dispatchDeleted(storeName) { $$deleteStore.next(storeName); } // @internal /** * @param {?} storeName * @return {?} */ function dispatchAdded(storeName) { $$addStore.next(storeName); } // @internal /** * @param {?} storeName * @return {?} */ function dispatchUpdate(storeName) { $$updateStore.next(storeName); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ let __DEV__ = true; /** * @return {?} */ function enableAkitaProdMode() { __DEV__ = false; } // @internal /** * @return {?} */ function isDev() { return __DEV__; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const isBrowser = typeof window !== 'undefined'; /** @type {?} */ const isNativeScript = typeof global !== 'undefined' && typeof ((/** @type {?} */ (global))).__runtimeVersion !== 'undefined'; // @internal /** @type {?} */ const isNotBrowser = !isBrowser && !isNativeScript; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** @type {?} */ const __stores__ = {}; // @internal /** @type {?} */ const __queries__ = {}; if (isBrowser && isDev()) { ((/** @type {?} */ (window))).$$stores = __stores__; ((/** @type {?} */ (window))).$$queries = __queries__; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * * Store for managing any type of data * * \@example * * export interface SessionState { * token: string; * userDetails: UserDetails * } * * export function createInitialState(): SessionState { * return { * token: '', * userDetails: null * }; * } * * \@StoreConfig({ name: 'session' }) * export class SessionStore extends Store<SessionState> { * constructor() { * super(createInitialState()); * } * } * @template S */ class Store { /** * @param {?} initialState * @param {?=} options */ constructor(initialState, options = {}) { this.options = options; this.inTransaction = false; this.cache = { active: new BehaviorSubject(false), ttl: null }; this.onInit((/** @type {?} */ (initialState))); } /** * Set the loading state * * \@example * * store.setLoading(true) * * @param {?=} loading * @return {?} */ setLoading(loading = false) { if (loading !== ((/** @type {?} */ (this._value()))).loading) { isDev() && setAction('Set Loading'); this._setState((/** * @param {?} state * @return {?} */ state => ((/** @type {?} */ (Object.assign({}, state, { loading })))))); } } /** * * Set whether the data is cached * * \@example * * store.setHasCache(true) * store.setHasCache(false) * store.setHasCache(true, { restartTTL: true }) * * @param {?} hasCache * @param {?=} options * @return {?} */ setHasCache(hasCache, options = { restartTTL: false }) { if (hasCache !== this.cache.active.value) { this.cache.active.next(hasCache); } if (options.restartTTL) { /** @type {?} */ const ttlConfig = this.cacheConfig && this.cacheConfig.ttl; if (ttlConfig) { if (this.cache.ttl !== null) { clearTimeout(this.cache.ttl); } this.cache.ttl = (/** @type {?} */ (setTimeout((/** * @return {?} */ () => this.setHasCache(false)), ttlConfig))); } } } /** * Set the error state * * \@example * * store.setError({text: 'unable to load data' }) * * @template T * @param {?} error * @return {?} */ setError(error) { if (error !== ((/** @type {?} */ (this._value()))).error) { isDev() && setAction('Set Error'); this._setState((/** * @param {?} state * @return {?} */ state => ((/** @type {?} */ (Object.assign({}, state, { error })))))); } } // @internal /** * @template R * @param {?} project * @return {?} */ _select(project) { return this.store.asObservable().pipe(map(project), distinctUntilChanged()); } // @internal /** * @return {?} */ _value() { return this.storeValue; } // @internal /** * @return {?} */ _cache() { return this.cache.active; } // @internal /** * @return {?} */ get config() { return this.constructor[configKey] || {}; } // @internal /** * @return {?} */ get storeName() { return ((/** @type {?} */ (this.config))).storeName || ((/** @type {?} */ (this.options))).storeName || this.options.name; } // @internal /** * @return {?} */ get deepFreeze() { return this.config.deepFreezeFn || this.options.deepFreezeFn || deepFreeze; } // @internal /** * @return {?} */ get cacheConfig() { return this.config.cache || this.options.cache; } // @internal /** * @return {?} */ get resettable() { return this.config.resettable || this.options.resettable; } // @internal /** * @param {?} newStateFn * @param {?=} _dispatchAction * @return {?} */ _setState(newStateFn, _dispatchAction = true) { this.storeValue = __DEV__ ? this.deepFreeze(newStateFn(this._value())) : newStateFn(this._value()); if (!this.store) { this.store = new BehaviorSubject(this.storeValue); return; } if (isTransactionInProcess()) { this.handleTransaction(); return; } this.dispatch(this.storeValue, _dispatchAction); } /** * * Reset the current store back to the initial value * * \@example * * store.reset() * * @return {?} */ reset() { if (this.isResettable()) { isDev() && setAction('Reset'); this._setState((/** * @return {?} */ () => Object.assign({}, this._initialState))); this.setHasCache(false); } else { isDev() && console.warn(`You need to enable the reset functionality`); } } /** * @param {?} stateOrCallback * @return {?} */ update(stateOrCallback) { isDev() && setAction('Update'); this._setState((/** * @param {?} state * @return {?} */ state => { /** @type {?} */ const newState = isFunction(stateOrCallback) ? stateOrCallback(state) : stateOrCallback; /** @type {?} */ const merged = this.akitaPreUpdate(state, (/** @type {?} */ (Object.assign({}, state, newState)))); return isPlainObject(state) ? merged : new ((/** @type {?} */ (state))).constructor(merged); })); } /** * @param {?} newOptions * @return {?} */ updateStoreConfig(newOptions) { this.options = Object.assign({}, this.options, newOptions); } // @internal /** * @param {?} _ * @param {?} nextState * @return {?} */ akitaPreUpdate(_, nextState) { return nextState; } /** * @return {?} */ ngOnDestroy() { this.destroy(); } /** * * Destroy the store * * \@example * * store.destroy() * * @return {?} */ destroy() { if (isNotBrowser) return; if (!((/** @type {?} */ (window))).hmrEnabled && this === __stores__[this.storeName]) { delete __stores__[this.storeName]; dispatchDeleted(this.storeName); this.setHasCache(false); this.cache.active.complete(); } } /** * @private * @param {?} initialState * @return {?} */ onInit(initialState) { __stores__[this.storeName] = this; this._setState((/** * @return {?} */ () => initialState)); dispatchAdded(this.storeName); if (this.isResettable()) { this._initialState = initialState; } isDev() && assertStoreHasName(this.storeName, this.constructor.name); } /** * @private * @param {?} state * @param {?=} _dispatchAction * @return {?} */ dispatch(state, _dispatchAction = true) { this.store.next(state); if (_dispatchAction) { dispatchUpdate(this.storeName); resetCustomAction(); } } /** * @private * @return {?} */ watchTransaction() { commit().subscribe((/** * @return {?} */ () => { this.inTransaction = false; this.dispatch(this._value()); })); } /** * @private * @return {?} */ isResettable() { if (this.resettable === false) { return false; } return this.resettable || getAkitaConfig().resettable; } /** * @private * @return {?} */ handleTransaction() { if (!this.inTransaction) { this.watchTransaction(); this.inTransaction = true; } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} v * @return {?} */ function isNil(v) { return v === null || v === undefined; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} value * @return {?} */ function isObject(value) { /** @type {?} */ const type = typeof value; return value != null && (type == 'object' || type == 'function'); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} idOrOptions * @param {?} ids * @param {?} currentActive * @return {?} */ function getActiveEntities(idOrOptions, ids, currentActive) { /** @type {?} */ let result; if (isArray(idOrOptions)) { result = idOrOptions; } else { if (isObject(idOrOptions)) { if (isNil(currentActive)) return; ((/** @type {?} */ (idOrOptions))) = Object.assign({ wrap: true }, idOrOptions); /** @type {?} */ const currentIdIndex = ids.indexOf((/** @type {?} */ (currentActive))); if (((/** @type {?} */ (idOrOptions))).prev) { /** @type {?} */ const isFirst = currentIdIndex === 0; if (isFirst && !((/** @type {?} */ (idOrOptions))).wrap) return; result = isFirst ? ids[ids.length - 1] : ((/** @type {?} */ (ids[currentIdIndex - 1]))); } else if (((/** @type {?} */ (idOrOptions))).next) { /** @type {?} */ const isLast = ids.length === currentIdIndex + 1; if (isLast && !((/** @type {?} */ (idOrOptions))).wrap) return; result = isLast ? ids[0] : ((/** @type {?} */ (ids[currentIdIndex + 1]))); } } else { if (idOrOptions === currentActive) return; result = (/** @type {?} */ (idOrOptions)); } } return result; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template S, E * @param {?} __0 * @return {?} */ function addEntities({ state, entities, idKey, options = {}, preAddEntity }) { /** @type {?} */ let newEntities = {}; /** @type {?} */ let newIds = []; /** @type {?} */ let hasNewEntities = false; for (const entity of entities) { if (hasEntity(state.entities, entity[idKey]) === false) { // evaluate the middleware first to support dynamic ids /** @type {?} */ const current = preAddEntity(entity); /** @type {?} */ const entityId = current[idKey]; newEntities[entityId] = current; if (options.prepend) newIds.unshift(entityId); else newIds.push(entityId); hasNewEntities = true; } } return hasNewEntities ? { newState: Object.assign({}, state, { entities: Object.assign({}, state.entities, newEntities), ids: options.prepend ? [...newIds, ...state.ids] : [...state.ids, ...newIds] }), newIds } : null; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template T * @param {?} value * @return {?} */ function coerceArray(value) { if (isNil(value)) { return []; } return Array.isArray(value) ? value : [value]; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template S, E * @param {?} __0 * @return {?} */ function removeEntities({ state, ids }) { if (isNil(ids)) return removeAllEntities(state); /** @type {?} */ const entities = state.entities; /** @type {?} */ let newEntities = {}; for (const id of state.ids) { if (ids.includes(id) === false) { newEntities[id] = entities[id]; } } /** @type {?} */ const newState = Object.assign({}, state, { entities: newEntities, ids: state.ids.filter((/** * @param {?} current * @return {?} */ current => ids.includes(current) === false)) }); if (hasActiveState(state)) { newState.active = resolveActiveEntity(newState); } return newState; } // @internal /** * @template S * @param {?} state * @return {?} */ function removeAllEntities(state) { return Object.assign({}, state, { entities: {}, ids: [], active: isMultiActiveState(state.active) ? [] : null }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** @type {?} */ const getInitialEntitiesState = (/** * @return {?} */ () => ((/** @type {?} */ ({ entities: {}, ids: [], loading: true, error: null })))); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} val * @return {?} */ function isDefined(val) { return isNil(val) === false; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @template S, E * @param {?} __0 * @return {?} */ function updateEntities({ state, ids, idKey, newStateOrFn, preUpdateEntity }) { /** @type {?} */ const updatedEntities = {}; /** @type {?} */ let isUpdatingIdKey = false; /** @type {?} */ let idToUpdate; for (const id of ids) { // if the entity doesn't exist don't do anything if (hasEntity(state.entities, id) === false) { continue; } /** @type {?} */ const oldEntity = state.entities[id]; /** @type {?} */ const newState = isFunction(newStateOrFn) ? newStateOrFn(oldEntity) : newStateOrFn; /** @type {?} */ const isIdChanged = newState.hasOwnProperty(idKey) && newState[idKey] !== oldEntity[idKey]; /** @type {?} */ let newEntity; idToUpdate = id; if (isIdChanged) { isUpdatingIdKey = true; idToUpdate = newState[idKey]; } /** @type {?} */ const merged = Object.assign({}, oldEntity, newState); if (isPlainObject(oldEntity)) { newEntity = merged; } else { /** * In case that new state is class of it's own, there's * a possibility that it will be different than the old * class. * For example, Old state is an instance of animal class * and new state is instance of person class. * To avoid run over new person class with the old animal * class we check if the new state is a class of it's own. * If so, use it. Otherwise, use the old state class */ if (isPlainObject(newState)) { newEntity = new ((/** @type {?} */ (oldEntity))).constructor(merged); } else { newEntity = new ((/** @type {?} */ (newState))).constructor(merged); } } updatedEntities[idToUpdate] = preUpdateEntity(oldEntity, newEntity); } /** @type {?} */ let updatedIds = state.ids; /** @type {?} */ let stateEntities = state.entities; if (isUpdatingIdKey) { const [id] = ids; const _a = state.entities, _b = id, deletedEntity = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); stateEntities = rest; updatedIds = state.ids.map((/** * @param {?} current * @return {?} */ current => (current === id ? idToUpdate : current))); } return Object.assign({}, state, { entities: Object.assign({}, stateEntities, updatedEntities), ids: updatedIds }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // @internal /** * @param {?} value * @return {?} */ function isUndefined(value) { return value === undefined; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @enum {number} */ const EntityActions = { Set: 0, Add: 1, Update: 2, Remove: 3, }; EntityActions[EntityActions.Set] = 'Set'; EntityActions[EntityActions.Add] = 'Add'; EntityActions[EntityActions.Update] = 'Update'; EntityActions[EntityActions.Remove] = 'Remove'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const DEFAULT_ID_KEY = 'id'; var _a; /** * * Store for managing a collection of entities * * \@example * * export interface WidgetsState extends EntityState<Widget> { } * * \@StoreConfig({ name: 'widgets' }) * export class WidgetsStore extends EntityStore<WidgetsState> { * constructor() { * super(); * } * } * * * @template S, EntityType, IDType */ class EntityStore extends Store { /** * @param {?=} initialState * @param {?=} options */ constructor(initialState = {}, options = {}) { super(Object.assign({}, getInitialEntitiesState(), initialState), options); this.options = options; this.entityActions = new Subject(); } // @internal /** * @return {?} */ get selectEntityAction$() { return this.entityActions.asObservable(); } // @internal /** * @return {?} */ get idKey() { return ((/** @type {?} */ (this.config))).idKey || this.options.idKey || DEFAULT_ID_KEY; } /** * * Replace current collection with provided collection * * \@example * * this.store.set([Entity, Entity]) * this.store.set({ids: [], entities: {}}) * this.store.set({ 1: {}, 2: {}}) * * @param {?} entities * @return {?} */ set(entities) { if (isNil(entities)) return; isDev() && setAction('Set Entity'); /** @type {?} */ const isNativePreAdd = this.akitaPreAddEntity === EntityStore.prototype.akitaPreAddEntity; this._setState((/** * @param {?} state * @return {?} */ state => setEntities({ state, entities, idKey: this.idKey, preAddEntity: this.akitaPreAddEntity, isNativePreAdd }))); this.setHasCache(true, { restartTTL: true }); if (this.hasInitialUIState()) { this.handleUICreation(); } this.entityActions.next({ type: EntityActions.Set, ids: this.ids }); } /** * Add entities * * \@example * * this.store.add([Entity, Entity]) * this.store.add(Entity) * this.store.add(Entity, { prepend: true }) * * this.store.add(Entity, { loading: false }) * @param {?} entities * @param {?=} options * @return {?} */ add(entities, options = { loading: false }) { /** @type {?} */ const collection = coerceArray(entities); if (isEmpty(collection)) return; /** @type {?} */ const data = addEntities({ state: this._value(), preAddEntity: this.akitaPreAddEntity, entities: collection, idKey: this.idKey, options }); if (data) { isDev() && setAction('Add Entity'); data.newState.loading = options.loading; this._setState((/** * @return {?} */ () => data.newState)); if (this.hasInitialUIState()) { this.handleUICreation(true); } this.entityActions.next({ type: EntityActions.Add, ids: data.newIds }); } } /** * @param {?} idsOrFnOrState * @param {?=} newStateOrFn * @return {?} */ update(idsOrFnOrState, newStateOrFn) { if (isUndefined(newStateOrFn)) { super.update((/** @type {?} */ (idsOrFnOrState))); return; } /** @type {?} */ let ids = []; if (isFunction(idsOrFnOrState)) { // We need to filter according the predicate function ids = this.ids.filter((/** * @param {?} id * @return {?} */ id => ((/** @type {?} */ (idsOrFnOrState)))(this.entities[id]))); } else { // If it's nil we want all of them ids = isNil(idsOrFnOrState) ? this.ids : coerceArray((/** @type {?} */ (idsOrFnOrState))); } if (isEmpty(ids)) return; isDev() && setAction('Update Entity', ids); this._setState((/** * @param {?} state * @return {?} */ state => updateEntities({ idKey: this.idKey, ids, preUpdateEntity: this.akitaPreUpdateEntity, state, newStateOrFn }))); this.entityActions.next({ type: EntityActions.Update, ids }); } /** * * Create or update * * \@example * * store.upsert(1, { active: true }) * store.upsert([2, 3], { active: true }) * store.upsert([2, 3], entity => ({ isOpen: !entity.isOpen})) * * @param {?} ids * @param {?} newState * @param {?=} options * @return {?} */ upsert(ids, newState, options = {}) { /** @type {?} */ const toArray = coerceArray(ids); /** @type {?} */ const predicate = (/** * @param {?} isUpdate * @return {?} */ isUpdate => (/** * @param {?} id * @return {?} */ id => hasEntity(this.entities, id) === isUpdate)); /** @type {?} */ const isClassBased = isFunction(options.baseClass); /** @type {?} */ const updateIds = toArray.filter(predicate(true)); /** @type {?} */ const newEntities = toArray.filter(predicate(false)).map((/** * @param {?} id * @return {?} */ id => { /** @type {?} */ let entity = isFunction(newState) ? newState((/** @type {?} */ ({}))) : newState; /** @type {?} */ const withId = Object.assign({}, ((/** @type {?} */ (entity))), { [this.idKey]: id }); if (isClassBased) { return new options.baseClass(withId); } return withId; })); // it can be any of the three types this.update((/** @type {?} */ (updateIds)), (/** @type {?} */ (newState))); this.add(newEntities); isDev() && logAction('Upsert Entity'); } /** * * Upsert entity collection (idKey must be present) * * \@example * * store.upsertMany([ { id: 1 }, { id: 2 }]); * * store.upsertMany([ { id: 1 }, { id: 2 }], { loading: true }); * store.upsertMany([ { id: 1 }, { id: 2 }], { baseClass: Todo }); * * @param {?} entities * @param {?=} options * @return {?} */ upsertMany(entities, options = {}) { /** @type {?} */ const addedIds = []; /** @type {?} */ const updatedIds = []; /** @type {?} */ const updatedEntities = {}; // Update the state directly to optimize performance for (const entity of entities) { /** @type {?} */ const withPreCheckHook = this.akitaPreCheckEntity(entity); /** @type {?} */ const id = withPreCheckHook[this.idKey]; if (hasEntity(this.entities, id)) { /** @type {?} */ const prev = this._value().entities[id]; /** @type {?} */ const merged = Object.assign({}, this._value().entities[id], withPreCheckHook); /** @type {?} */ const next = options.baseClass ? new options.baseClass(merged) : merged; /** @type {?} */ const withHook = this.akitaPreUpdateEntity(prev, next); /** @type {?} */ const nextId = withHook[this.idKey]; updatedEntities[nextId] = withHook; updatedIds.push(nextId); } else { /** @type {?} */ const newEntity = options.baseClass ? new options.baseClass(withPreCheckHook) : withPreCheckHook; /** @type {?} */ const withHook = this.akitaPreAddEntity(newEntity); /** @type {?} */ const nextId = withHook[this.idKey]; addedIds.push(nextId); updatedEntities[nextId] = withHook; } } isDev() && logAction('Upsert Many'); this._setState((/** * @param {?} state * @return {?} */ state => (Object.assign({}, state, { ids: addedIds.length ? [...state.ids, ...addedIds] : state.ids, entities: Object.assign({}, state.entities, updatedEntities), loading: !!options.loading })))); updatedIds.length && this.entityActions.next({ type: EntityActions.Update, ids: updatedIds }); addedIds.length && this.entityActions.next({ type: EntityActions.Add, ids: addedIds }); if (addedIds.length && this.hasUIStore()) { this.handleUICreation(true); } } /** * * Replace one or more entities (except the id property) * * * \@example * * this.store.replace(5, newEntity) * this.store.replace([1,2,3], newEntity) * @param {?} ids * @param {?} newState * @return {?} */ replace(ids, newState) { /** @type {?} */ const toArray = coerceArray(ids); if (isEmpty(toArray)) return; /** @type {?} */ let replaced = {}; for (const id of toArray) { newState[this.idKey] = id; replaced[id] = newState; } isDev() && setAction('Replace Entity', ids); this._setState((/** * @param {?} state * @return {?} */ state => (Object.assign({}, state, { entities: Object.assign({}, state.entities, replaced) })))); } /** * * Move entity inside the collection * * * \@example * * this.store.move(fromIndex, toIndex) * @param {?} from * @param {?} to * @return {?} */ move(from$$1, to) { /** @type {?} */ const ids = this.ids.slice(); ids.splice(to < 0 ? ids.length + to : to, 0, ids.splice(from$$1, 1)[0]); isDev() && setAction('Move Entity'); this._setState((/** * @param {?} state * @return {?} */ state => (Object.assign({}, state, { entities: Object.assign({}, state.entities), ids })))); } /** * @param {?=} idsOrFn * @return {?} */ remove(idsOrFn) { if (isEmpty(this.ids)) return; /** @type {?} */ const idPassed = isDefined(idsOrFn); // null means remove all /** @type {?} */ let ids = []; if (isFunction(idsOrFn)) { ids = this.ids.filter((/** * @param {?} entityId * @return {?} */ entityId => idsOrFn(this.entities[entityId]))); } else { ids = idPassed ? coerceArray(idsOrFn) : null; } if (isEmpty(ids)) return; isDev() && setAction('Remove Entity', ids); this._setState((/** * @param {?} state * @return {?} */ (state) => removeEntities({ state, ids }))); if (ids === null) { this.setHasCache(false); } this.handleUIRemove(ids); this.entityActions.next({ type: EntityActions.Remove, ids }); } /** * * Update the active entity * * \@example * * this.store.updateActive({ completed: true }) * this.store.updateActive(active => { * return { * config: { * ..active.config, * date * } * } * }) * @param {?} newStateOrCallback * @return {?} */ updateActive(newStateOrCallback) { /** @type {?} */ const ids = coerceArray(this.active); isDev() && setAction('Update Active', ids); this.update(ids, (/** @type {?} */ (newStateOrCallback))); } /** * @param {?} idOrOptions * @return {?} */ setActive(idOrOptions) { /** @type {?} */ const active = getActiveEntities(idOrOptions, this.ids, this.active); if (active === undefined) { return; } isDev() && setAction('Set Active', active); this._setActive(active); } /** * Add active entities * * \@example * * store.addActive(2); * store.addActive([3, 4, 5]); * @template T * @param {?} ids * @return {?} */ addActive(ids) { /** @type {?} */ const toArray = coerceArray(ids); if (isEmpty(toArray)) return; /** @type {?} */ const everyExist = toArray.every((/** * @param {?} id * @return {?} */ id => this.active.indexOf(id) > -1)); if (everyExist) return; isDev() && setAction('Add Active', ids); this._setState((/** * @param {?} state * @return {?} */ state => { /** * Protect against case that one of the items in the array exist * @type {?} */ const uniques = Array.from(new Set([...((/** @type {?} */ (state.active))), ...toArray])); return Object.assign({}, state, { active: uniques }); })); } /** * Remove active entities * * \@example * * store.removeActive(2) * store.removeActive([3, 4, 5]) * @template T * @param {?} ids * @return {?} */ removeActive(ids) { /** @type {?} */ const toArray = coerceArray(ids); if (isEmpty(toArray)) return; /** @type {?} */ const someExist = toArray.some((/** * @param {?} id * @return {?} */ id => this.active.indexOf(id) > -1)); if (!someExist) return; isDev() && setAction('Remove Active', ids); this._setState((/** * @param {?} state * @return {?} */ state => { return Object.assign({}, state, { active: Array.isArray(state.active) ? state.active.filter((/** * @param {?} currentId * @return {?} */ currentId => toArray.indexOf(currentId) === -1)) : null }); })); } /** * Toggle active entities * * \@example * * store.toggle(2) * store.toggle([3, 4, 5]) * @template T * @param {?} ids * @return {?} */ toggleActive(ids) { /** @type {?} */ const toArray = coerceArray(ids); /** @type {?} */ const filterExists = (/** * @param {?} remove * @return {?} */ remove => (/** * @param {?} id * @return {?} */ id => this.active.includes(id) === remove)); /** @type {?} */ const remove = toArray.filter(filterExists(true)); /** @type {?} */ const add = toArray.filter(filterExists(false)); this.removeActive(remove); this.addActive(add); isDev() && logAction('Toggle Active'); } /** * * Create sub UI store for managing Entity's UI state * * \@example * * export type ProductUI = { * isLoading: boolean; * isOpen: boolean * } * * interface ProductsUIState extends EntityState<ProductUI> {} * * export class ProductsStore EntityStore<ProductsState, Product> { * ui: EntityUIStore<ProductsUIState, ProductUI>; * * constructor() { * super(); * this.createUIStore(); * } * * } * @param {?=} initialState * @param {?=} storeConfig * @return {?} */ createUIStore(initialState = {}, storeConfig = {}) { /** @type {?} */ const defaults = { name: `UI/${this.storeName}`, idKey: this.idKey }; this.ui = new EntityUIStore(initialState, Object.assign({}, defaults, storeConfig)); return this.ui; } // @internal /** * @return {?} */ destroy() { super.destroy(); if (this.ui instanceof EntityStore) { this.ui.destroy(); } this.entityActions.complete();