@datorama/akita
Version:
State Management Tailored-Made for JS Applications
1,879 lines (1,844 loc) • 155 kB
JavaScript
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();