@ngxs/store
Version:
351 lines (336 loc) • 14 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, DestroyRef, Injectable, untracked } from '@angular/core';
import { BehaviorSubject, Subject, Observable } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
// This key is used to store metadata on state classes,
// such as actions and other related information.
const ɵMETA_KEY = 'NGXS_META';
// This key is used to store options on state classes
// provided through the `@State` decorator.
const ɵMETA_OPTIONS_KEY = 'NGXS_OPTIONS_META';
// This key is used to store selector metadata on selector functions,
// such as decorated with the `@Selector` or provided through the
// `createSelector` function.
const ɵSELECTOR_META_KEY = 'NGXS_SELECTOR_META';
// Property reads are not minified.
// It's smaller to read it once and use a function.
const _hasOwnProperty = Object.prototype.hasOwnProperty;
const ɵhasOwnProperty = (target, key) => _hasOwnProperty.call(target, key);
const ɵdefineProperty = Object.defineProperty;
/**
* Ensures metadata is attached to the class and returns it.
*
* @ignore
*/
function ɵensureStoreMetadata(target) {
if (!ɵhasOwnProperty(target, ɵMETA_KEY)) {
const defaultMetadata = {
name: null,
actions: {},
defaults: {},
path: null,
makeRootSelector(context) {
return context.getStateGetter(defaultMetadata.name);
},
children: []
};
ɵdefineProperty(target, ɵMETA_KEY, { value: defaultMetadata });
}
return ɵgetStoreMetadata(target);
}
/**
* Get the metadata attached to the state class if it exists.
*
* @ignore
*/
function ɵgetStoreMetadata(target) {
return target[ɵMETA_KEY];
}
/**
* Ensures metadata is attached to the selector and returns it.
*
* @ignore
*/
function ɵensureSelectorMetadata(target) {
if (!ɵhasOwnProperty(target, ɵSELECTOR_META_KEY)) {
const defaultMetadata = {
makeRootSelector: null,
originalFn: null,
containerClass: null,
selectorName: null,
getSelectorOptions: () => ({})
};
ɵdefineProperty(target, ɵSELECTOR_META_KEY, { value: defaultMetadata });
}
return ɵgetSelectorMetadata(target);
}
/**
* Get the metadata attached to the selector if it exists.
*
* @ignore
*/
function ɵgetSelectorMetadata(target) {
return target[ɵSELECTOR_META_KEY];
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can
// determine equality as fast as possible.
const length = prev.length;
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
}
/**
* Memoize a function on its last inputs only.
* Originally from: https://github.com/reduxjs/reselect/blob/master/src/index.js
*
* @ignore
*/
function ɵmemoize(func, equalityCheck = Object.is) {
let lastArgs = null;
let lastResult = null;
// we reference arguments instead of spreading them for performance reasons
function memoized() {
// eslint-disable-next-line prefer-rest-params
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
// eslint-disable-next-line prefer-rest-params, prefer-spread
lastResult = func.apply(null, arguments);
}
// eslint-disable-next-line prefer-rest-params
lastArgs = arguments;
return lastResult;
}
memoized.reset = function () {
// The hidden (for now) ability to reset the memoization
lastArgs = null;
lastResult = null;
};
return memoized;
}
class StateToken {
_name;
constructor(_name) {
this._name = _name;
const selectorMetadata = ɵensureSelectorMetadata(this);
selectorMetadata.makeRootSelector = (runtimeContext) => {
return runtimeContext.getStateGetter(this._name);
};
}
getName() {
return this._name;
}
toString() {
return `StateToken[${this._name}]`;
}
}
class ɵInitialState {
static _value = {};
static set(state) {
this._value = state;
}
static pop() {
const state = this._value;
this._value = {};
return state;
}
}
const ɵINITIAL_STATE_TOKEN = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'INITIAL_STATE_TOKEN' : '', {
providedIn: 'root',
factory: () => ɵInitialState.pop()
});
class ɵNgxsAppBootstrappedState extends BehaviorSubject {
constructor() {
super(false);
const destroyRef = inject(DestroyRef);
// Complete the subject once the root injector is destroyed to ensure
// there are no active subscribers that would receive events or perform
// any actions after the application is destroyed.
destroyRef.onDestroy(() => this.complete());
}
bootstrap() {
this.next(true);
}
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
// These tokens are internal and can change at any point.
const ɵNGXS_STATE_FACTORY = /* @__PURE__ */ new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ɵNGXS_STATE_FACTORY' : '');
const ɵNGXS_STATE_CONTEXT_FACTORY = /* @__PURE__ */ new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ɵNGXS_STATE_CONTEXT_FACTORY' : '');
/**
* This wraps the provided function, and will enforce the following:
* - The calls will execute in the order that they are made
* - A call will only be initiated when the previous call has completed
* - If there is a call currently executing then the new call will be added
* to the queue and the function will return immediately
*
* NOTE: The following assumptions about the operation must hold true:
* - The operation is synchronous in nature
* - If any asynchronous side effects of the call exist, it should not
* have any bearing on the correctness of the next call in the queue
* - The operation has a void return
* - The caller should not assume that the call has completed upon
* return of the function
* - The caller can assume that all the queued calls will complete
* within the current microtask
* - The only way that a call will encounter another call in the queue
* would be if the call at the front of the queue initiated this call
* as part of its synchronous execution
*/
function orderedQueueOperation(operation) {
const callsQueue = [];
let busyPushingNext = false;
return function callOperation(...args) {
if (busyPushingNext) {
callsQueue.unshift(args);
return;
}
busyPushingNext = true;
operation(...args);
while (callsQueue.length > 0) {
const nextCallArgs = callsQueue.pop();
nextCallArgs && operation(...nextCallArgs);
}
busyPushingNext = false;
};
}
/**
* Custom Subject that ensures that subscribers are notified of values in the order that they arrived.
* A standard Subject does not have this guarantee.
* For example, given the following code:
* ```typescript
* const subject = new Subject<string>();
subject.subscribe(value => {
if (value === 'start') subject.next('end');
});
subject.subscribe(value => { });
subject.next('start');
* ```
* When `subject` is a standard `Subject<T>` the second subscriber would recieve `end` and then `start`.
* When `subject` is a `OrderedSubject<T>` the second subscriber would recieve `start` and then `end`.
*/
class ɵOrderedSubject extends Subject {
_orderedNext = orderedQueueOperation((value) => super.next(value));
next(value) {
this._orderedNext(value);
}
}
/**
* Custom BehaviorSubject that ensures that subscribers are notified of values in the order that they arrived.
* A standard BehaviorSubject does not have this guarantee.
* For example, given the following code:
* ```typescript
* const subject = new BehaviorSubject<string>();
subject.subscribe(value => {
if (value === 'start') subject.next('end');
});
subject.subscribe(value => { });
subject.next('start');
* ```
* When `subject` is a standard `BehaviorSubject<T>` the second subscriber would recieve `end` and then `start`.
* When `subject` is a `OrderedBehaviorSubject<T>` the second subscriber would recieve `start` and then `end`.
*/
class ɵOrderedBehaviorSubject extends BehaviorSubject {
_orderedNext = orderedQueueOperation((value) => super.next(value));
_currentValue;
constructor(value) {
super(value);
this._currentValue = value;
}
getValue() {
return this._currentValue;
}
next(value) {
this._currentValue = value;
this._orderedNext(value);
}
}
function ɵwrapObserverCalls(invokeFn) {
return (source) => {
return new Observable(subscriber => {
return source.subscribe({
next(value) {
invokeFn(() => subscriber.next(value));
},
error(error) {
invokeFn(() => subscriber.error(error));
},
complete() {
invokeFn(() => subscriber.complete());
}
});
});
};
}
/**
* BehaviorSubject of the entire state.
* @ignore
*/
class ɵStateStream extends ɵOrderedBehaviorSubject {
state = toSignal(this.pipe(ɵwrapObserverCalls(untracked)), {
manualCleanup: true,
requireSync: true
});
constructor() {
super({});
// Complete the subject once the root injector is destroyed to ensure
// there are no active subscribers that would receive events or perform
// any actions after the application is destroyed.
// The `StateStream` should never emit values once the root view is removed,
// such as when the `ApplicationRef.destroy()` method is called. This is crucial
// for preventing memory leaks in server-side rendered apps, where a new `StateStream`
// is created for each HTTP request. If users forget to unsubscribe from `store.select`
// or `store.subscribe`, it can result in significant memory leaks in SSR apps.
inject(DestroyRef).onDestroy(() => this.complete());
}
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
class ɵNgxsActionRegistry {
// Instead of going over the states list every time an action is dispatched,
// we are constructing a map of action types to lists of action metadata.
// If the `@@Init` action is handled in two different states, the action
// metadata list will contain two objects that have the state `instance` and
// method names to be used as action handlers (decorated with `@Action(InitState)`).
_actionTypeToHandlersMap = new Map();
constructor() {
inject(DestroyRef).onDestroy(() => this._actionTypeToHandlersMap.clear());
}
get(type) {
return this._actionTypeToHandlersMap.get(type);
}
register(type, handler) {
const handlers = this._actionTypeToHandlersMap.get(type) ?? new Set();
handlers.add(handler);
this._actionTypeToHandlersMap.set(type, handlers);
return () => {
const handlers = this._actionTypeToHandlersMap.get(type);
handlers.delete(handler);
};
}
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Generated bundle index. Do not edit.
*/
export { StateToken, ɵINITIAL_STATE_TOKEN, ɵInitialState, ɵMETA_KEY, ɵMETA_OPTIONS_KEY, ɵNGXS_STATE_CONTEXT_FACTORY, ɵNGXS_STATE_FACTORY, ɵNgxsActionRegistry, ɵNgxsAppBootstrappedState, ɵOrderedBehaviorSubject, ɵOrderedSubject, ɵSELECTOR_META_KEY, ɵStateStream, ɵdefineProperty, ɵensureSelectorMetadata, ɵensureStoreMetadata, ɵgetSelectorMetadata, ɵgetStoreMetadata, ɵhasOwnProperty, ɵmemoize, ɵwrapObserverCalls };
//# sourceMappingURL=ngxs-store-internals.mjs.map