From 81baa9daaf4013cd7dd72b9c2424b02eed8bdbaa Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Mon, 11 Mar 2024 22:53:33 +0200 Subject: [PATCH] refactor: move metadata into internals (#2062) This commit includes several updates: 1) Moves metadata interfaces and functions to the `@ngxs/store/internals` subpackage. 2) Transfers interfaces and objects from `plugin_api.ts` to the `@ngxs/store/plugins` subpackage. 3) Adds the `@deprecated` annotation to functions and types that were exported from `public_to_deprecate.ts`. --- .../devtools-plugin/src/devtools.module.ts | 3 +- .../devtools-plugin/src/devtools.plugin.ts | 6 +- packages/form-plugin/src/directive.ts | 3 +- packages/form-plugin/src/form.module.ts | 3 +- packages/form-plugin/src/form.plugin.ts | 2 +- packages/hmr-plugin/src/hmr-manager.ts | 12 +- .../hmr-plugin/src/internal/hmr-lifecycle.ts | 7 +- packages/logger-plugin/src/action-logger.ts | 3 +- packages/logger-plugin/src/logger.module.ts | 4 +- packages/logger-plugin/src/logger.plugin.ts | 4 +- packages/storage-plugin/src/storage.module.ts | 3 +- packages/storage-plugin/src/storage.plugin.ts | 6 +- .../src}/custom-rxjs-subjects.ts | 4 +- packages/store/internals/src/index.ts | 19 +-- packages/store/internals/src/initial-state.ts | 14 +- packages/store/internals/src/memoize.ts | 2 +- packages/store/internals/src/metadata.ts | 70 +++++++++ .../src/ngxs-app-bootstrapped-state.ts | 14 ++ .../store/internals/src/ngxs-bootstrapper.ts | 23 --- packages/store/internals/src/state-stream.ts | 30 ++++ packages/store/internals/src/state-token.ts | 21 +++ packages/store/internals/src/symbols.ts | 92 ++++++++++- .../testing/tests/ngxs.setup.spec.ts | 6 +- packages/store/plugins/ng-package.json | 7 + .../{src/actions => plugins/src}/actions.ts | 4 +- packages/store/plugins/src/index.ts | 3 + packages/store/plugins/src/symbols.ts | 23 +++ .../store/{src/utils => plugins/src}/utils.ts | 43 +---- packages/store/src/actions-stream.ts | 4 +- packages/store/src/actions/symbols.ts | 16 -- packages/store/src/configs/messages.config.ts | 4 +- packages/store/src/decorators/action.ts | 9 +- .../store/src/decorators/select/symbols.ts | 5 +- .../store/src/decorators/selector-options.ts | 5 +- .../store/src/decorators/selector/symbols.ts | 5 +- packages/store/src/decorators/state.ts | 40 +++-- .../dev-features/ngxs-development.module.ts | 9 +- .../ngxs-unhandled-actions-logger.ts | 3 +- packages/store/src/dev-features/symbols.ts | 4 +- packages/store/src/internal/dispatcher.ts | 2 +- packages/store/src/internal/internals.ts | 147 +++--------------- .../src/internal/lifecycle-state-manager.ts | 9 +- .../src/internal/state-context-factory.ts | 5 +- packages/store/src/internal/state-factory.ts | 90 ++++++----- packages/store/src/internal/state-stream.ts | 31 +--- packages/store/src/operators/of-action.ts | 2 +- packages/store/src/plugin-manager.ts | 2 +- packages/store/src/plugin_api.ts | 15 +- packages/store/src/public_api.ts | 9 +- packages/store/src/public_to_deprecate.ts | 82 +++++----- .../src/selectors/selector-checks.util.ts | 5 +- .../store/src/selectors/selector-metadata.ts | 21 +-- .../store/src/selectors/selector-models.ts | 8 +- .../src/selectors/selector-types.util.ts | 4 +- .../store/src/selectors/selector-utils.ts | 40 ++--- .../src/standalone-features/initializers.ts | 7 +- .../store/src/standalone-features/plugin.ts | 3 +- .../src/standalone-features/root-providers.ts | 8 +- packages/store/src/state-token/state-token.ts | 25 --- packages/store/src/state-token/symbols.ts | 7 - packages/store/src/store.ts | 9 +- packages/store/src/symbols.ts | 73 +++------ packages/store/src/utils/store-validators.ts | 12 +- packages/store/tests/actions-stream.spec.ts | 4 +- packages/store/tests/ensure-store.spec.ts | 40 +++-- .../store/tests/release-resources.spec.ts | 5 +- packages/store/tests/utils/utils.spec.ts | 78 +--------- .../websocket-plugin/src/websocket-handler.ts | 3 +- tsconfig.base.json | 1 + 69 files changed, 617 insertions(+), 665 deletions(-) rename packages/store/{src/internal => internals/src}/custom-rxjs-subjects.ts (96%) create mode 100644 packages/store/internals/src/metadata.ts create mode 100644 packages/store/internals/src/ngxs-app-bootstrapped-state.ts delete mode 100644 packages/store/internals/src/ngxs-bootstrapper.ts create mode 100644 packages/store/internals/src/state-stream.ts create mode 100644 packages/store/internals/src/state-token.ts create mode 100644 packages/store/plugins/ng-package.json rename packages/store/{src/actions => plugins/src}/actions.ts (62%) create mode 100644 packages/store/plugins/src/index.ts create mode 100644 packages/store/plugins/src/symbols.ts rename packages/store/{src/utils => plugins/src}/utils.ts (55%) delete mode 100644 packages/store/src/state-token/state-token.ts delete mode 100644 packages/store/src/state-token/symbols.ts diff --git a/packages/devtools-plugin/src/devtools.module.ts b/packages/devtools-plugin/src/devtools.module.ts index 2902848c7..4239299a8 100644 --- a/packages/devtools-plugin/src/devtools.module.ts +++ b/packages/devtools-plugin/src/devtools.module.ts @@ -5,7 +5,8 @@ import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; -import { NGXS_PLUGINS, withNgxsPlugin } from '@ngxs/store'; +import { withNgxsPlugin } from '@ngxs/store'; +import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsDevtoolsOptions, NGXS_DEVTOOLS_OPTIONS } from './symbols'; import { NgxsReduxDevtoolsPlugin } from './devtools.plugin'; diff --git a/packages/devtools-plugin/src/devtools.plugin.ts b/packages/devtools-plugin/src/devtools.plugin.ts index 8c8d5cc5a..551a9d216 100644 --- a/packages/devtools-plugin/src/devtools.plugin.ts +++ b/packages/devtools-plugin/src/devtools.plugin.ts @@ -1,11 +1,11 @@ import { Inject, Injectable, Injector, NgZone, OnDestroy, ɵglobal } from '@angular/core'; +import { Store } from '@ngxs/store'; import { InitState, getActionTypeFromInstance, NgxsNextPluginFn, - NgxsPlugin, - Store -} from '@ngxs/store'; + NgxsPlugin +} from '@ngxs/store/plugins'; import { tap, catchError } from 'rxjs/operators'; import { diff --git a/packages/form-plugin/src/directive.ts b/packages/form-plugin/src/directive.ts index bf3746dca..5861777f1 100644 --- a/packages/form-plugin/src/directive.ts +++ b/packages/form-plugin/src/directive.ts @@ -1,6 +1,7 @@ import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit } from '@angular/core'; import { FormGroup, FormGroupDirective } from '@angular/forms'; -import { Actions, getValue, ofActionDispatched, Store } from '@ngxs/store'; +import { Actions, ofActionDispatched, Store } from '@ngxs/store'; +import { getValue } from '@ngxs/store/plugins'; import { Observable, ReplaySubject } from 'rxjs'; import { debounceTime, distinctUntilChanged, filter, takeUntil } from 'rxjs/operators'; import { diff --git a/packages/form-plugin/src/form.module.ts b/packages/form-plugin/src/form.module.ts index e271536bb..eb0fcae1f 100644 --- a/packages/form-plugin/src/form.module.ts +++ b/packages/form-plugin/src/form.module.ts @@ -4,7 +4,8 @@ import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; -import { NGXS_PLUGINS, withNgxsPlugin } from '@ngxs/store'; +import { withNgxsPlugin } from '@ngxs/store'; +import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsFormPlugin } from './form.plugin'; import { NgxsFormDirective } from './directive'; diff --git a/packages/form-plugin/src/form.plugin.ts b/packages/form-plugin/src/form.plugin.ts index 58ae73d50..921738bec 100644 --- a/packages/form-plugin/src/form.plugin.ts +++ b/packages/form-plugin/src/form.plugin.ts @@ -5,7 +5,7 @@ import { NgxsNextPluginFn, NgxsPlugin, setValue -} from '@ngxs/store'; +} from '@ngxs/store/plugins'; import { ResetForm, SetFormDirty, diff --git a/packages/hmr-plugin/src/hmr-manager.ts b/packages/hmr-plugin/src/hmr-manager.ts index bc7efe74c..8ead1ce52 100644 --- a/packages/hmr-plugin/src/hmr-manager.ts +++ b/packages/hmr-plugin/src/hmr-manager.ts @@ -1,5 +1,5 @@ import { ApplicationRef, ComponentRef, NgModuleRef } from '@angular/core'; -import { InitialState, NgxsBootstrapper } from '@ngxs/store/internals'; +import { ɵInitialState, ɵNgxsAppBootstrappedState } from '@ngxs/store/internals'; import { HmrStorage } from './internal/hmr-storage'; import { @@ -31,22 +31,22 @@ export class HmrManager>, S = NgxsHmrSnaps return this.ngModule.injector.get(ApplicationRef); } - private get bootstrap(): NgxsBootstrapper { - return this.ngModule.injector.get(NgxsBootstrapper); + private get appBootstrappedState() { + return this.ngModule.injector.get(ɵNgxsAppBootstrappedState); } public async hmrModule( bootstrapFn: BootstrapModuleFn, tick: () => void ): Promise> { - InitialState.set(this.storage.snapshot); + ɵInitialState.set(this.storage.snapshot); this.ngModule = await bootstrapFn(); this.context = new HmrStateContextFactory(this.ngModule); this.lifecycle = this.createLifecycle(); tick(); - InitialState.pop(); + ɵInitialState.pop(); return this.ngModule; } @@ -71,7 +71,7 @@ export class HmrManager>, S = NgxsHmrSnaps private createLifecycle(): HmrLifecycle { return new HmrLifecycle( this.ngModule.instance, - this.bootstrap, + this.appBootstrappedState, this.storage, this.context, this.optionsBuilder diff --git a/packages/hmr-plugin/src/internal/hmr-lifecycle.ts b/packages/hmr-plugin/src/internal/hmr-lifecycle.ts index 3d51e2e09..dc19b7985 100644 --- a/packages/hmr-plugin/src/internal/hmr-lifecycle.ts +++ b/packages/hmr-plugin/src/internal/hmr-lifecycle.ts @@ -1,4 +1,4 @@ -import { NgxsBootstrapper } from '@ngxs/store/internals'; +import { ɵNgxsAppBootstrappedState } from '@ngxs/store/internals'; import { Observable, Subscription } from 'rxjs'; import { StateContext } from '@ngxs/store'; @@ -11,7 +11,7 @@ import { HmrStorage } from './hmr-storage'; export class HmrLifecycle>, S> { constructor( private ngAppModule: T, - private bootstrap: NgxsBootstrapper, + private appBootstrappedState: ɵNgxsAppBootstrappedState, private storage: HmrStorage, private context: HmrStateContextFactory, private options: HmrOptionBuilder @@ -52,10 +52,9 @@ export class HmrLifecycle>, S> { private stateEventLoop(callback: HmrCallback): void { if (!this.storage.hasData()) return; - const appBootstrapped$: Observable = this.bootstrap.appBootstrapped$; const state$: Observable = this.context.store.select(state => state); - appBootstrapped$.subscribe(() => { + this.appBootstrappedState.subscribe(() => { let eventId: number; const storeEventId: Subscription = state$.subscribe(() => { // setTimeout used for zone detection after set hmr state diff --git a/packages/logger-plugin/src/action-logger.ts b/packages/logger-plugin/src/action-logger.ts index 706c7ca5f..e15e4c680 100644 --- a/packages/logger-plugin/src/action-logger.ts +++ b/packages/logger-plugin/src/action-logger.ts @@ -1,4 +1,5 @@ -import { getActionTypeFromInstance, Store } from '@ngxs/store'; +import { Store } from '@ngxs/store'; +import { getActionTypeFromInstance } from '@ngxs/store/plugins'; import { formatTime } from './internals'; import { LogWriter } from './log-writer'; diff --git a/packages/logger-plugin/src/logger.module.ts b/packages/logger-plugin/src/logger.module.ts index 4cd889a33..bb4be3b1e 100644 --- a/packages/logger-plugin/src/logger.module.ts +++ b/packages/logger-plugin/src/logger.module.ts @@ -5,7 +5,9 @@ import { NgModule, makeEnvironmentProviders } from '@angular/core'; -import { NGXS_PLUGINS, withNgxsPlugin } from '@ngxs/store'; +import { withNgxsPlugin } from '@ngxs/store'; +import { NGXS_PLUGINS } from '@ngxs/store/plugins'; + import { NgxsLoggerPlugin } from './logger.plugin'; import { NgxsLoggerPluginOptions, NGXS_LOGGER_PLUGIN_OPTIONS } from './symbols'; diff --git a/packages/logger-plugin/src/logger.plugin.ts b/packages/logger-plugin/src/logger.plugin.ts index 49530fc9a..90a25208e 100644 --- a/packages/logger-plugin/src/logger.plugin.ts +++ b/packages/logger-plugin/src/logger.plugin.ts @@ -1,6 +1,8 @@ import { Inject, Injectable, Injector } from '@angular/core'; -import { NgxsNextPluginFn, NgxsPlugin, Store } from '@ngxs/store'; +import { Store } from '@ngxs/store'; +import { NgxsNextPluginFn, NgxsPlugin } from '@ngxs/store/plugins'; import { catchError, tap } from 'rxjs/operators'; + import { ActionLogger } from './action-logger'; import { LogWriter } from './log-writer'; import { NgxsLoggerPluginOptions, NGXS_LOGGER_PLUGIN_OPTIONS } from './symbols'; diff --git a/packages/storage-plugin/src/storage.module.ts b/packages/storage-plugin/src/storage.module.ts index 2cfdd79f9..221d32103 100644 --- a/packages/storage-plugin/src/storage.module.ts +++ b/packages/storage-plugin/src/storage.module.ts @@ -7,7 +7,8 @@ import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; -import { NGXS_PLUGINS, withNgxsPlugin } from '@ngxs/store'; +import { withNgxsPlugin } from '@ngxs/store'; +import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsStoragePluginOptions, STORAGE_ENGINE, diff --git a/packages/storage-plugin/src/storage.plugin.ts b/packages/storage-plugin/src/storage.plugin.ts index 6b0b836d7..b32e5dd57 100644 --- a/packages/storage-plugin/src/storage.plugin.ts +++ b/packages/storage-plugin/src/storage.plugin.ts @@ -1,6 +1,6 @@ import { PLATFORM_ID, Inject, Injectable } from '@angular/core'; import { isPlatformServer } from '@angular/common'; -import { PlainObject } from '@ngxs/store/internals'; +import { ɵPlainObject } from '@ngxs/store/internals'; import { NgxsPlugin, setValue, @@ -9,7 +9,7 @@ import { UpdateState, actionMatcher, NgxsNextPluginFn -} from '@ngxs/store'; +} from '@ngxs/store/plugins'; import { ɵDEFAULT_STATE_KEY, ɵFinalNgxsStoragePluginOptions, @@ -124,7 +124,7 @@ export class NgxsStoragePlugin implements NgxsPlugin { } return accumulator; }, - {} + <ɵPlainObject>{} ); } diff --git a/packages/store/src/internal/custom-rxjs-subjects.ts b/packages/store/internals/src/custom-rxjs-subjects.ts similarity index 96% rename from packages/store/src/internal/custom-rxjs-subjects.ts rename to packages/store/internals/src/custom-rxjs-subjects.ts index c56c760a0..c805cf6b0 100644 --- a/packages/store/src/internal/custom-rxjs-subjects.ts +++ b/packages/store/internals/src/custom-rxjs-subjects.ts @@ -53,7 +53,7 @@ function orderedQueueOperation(operation: (...args: TArgs) * When `subject` is a standard `Subject` the second subscriber would recieve `end` and then `start`. * When `subject` is a `OrderedSubject` the second subscriber would recieve `start` and then `end`. */ -export class OrderedSubject extends Subject { +export class ɵOrderedSubject extends Subject { private _orderedNext = orderedQueueOperation((value?: T) => super.next(value)); next(value?: T): void { @@ -76,7 +76,7 @@ export class OrderedSubject extends Subject { * When `subject` is a standard `BehaviorSubject` the second subscriber would recieve `end` and then `start`. * When `subject` is a `OrderedBehaviorSubject` the second subscriber would recieve `start` and then `end`. */ -export class OrderedBehaviorSubject extends BehaviorSubject { +export class ɵOrderedBehaviorSubject extends BehaviorSubject { private _orderedNext = orderedQueueOperation((value: T) => super.next(value)); private _currentValue: T; diff --git a/packages/store/internals/src/index.ts b/packages/store/internals/src/index.ts index b5ed6e4a0..712788d7a 100644 --- a/packages/store/internals/src/index.ts +++ b/packages/store/internals/src/index.ts @@ -1,12 +1,9 @@ -export { NgxsBootstrapper } from './ngxs-bootstrapper'; -export { memoize } from './memoize'; -export { INITIAL_STATE_TOKEN, InitialState } from './initial-state'; -export { - PlainObjectOf, - PlainObject, - ɵStateClass, - ɵMETA_KEY, - ɵMETA_OPTIONS_KEY, - ɵSELECTOR_META_KEY -} from './symbols'; +export * from './symbols'; +export * from './metadata'; +export { ɵmemoize } from './memoize'; +export { StateToken } from './state-token'; +export { ɵINITIAL_STATE_TOKEN, ɵInitialState } from './initial-state'; +export { ɵNgxsAppBootstrappedState } from './ngxs-app-bootstrapped-state'; export { ɵNGXS_STATE_CONTEXT_FACTORY, ɵNGXS_STATE_FACTORY } from './internal-tokens'; +export { ɵOrderedSubject, ɵOrderedBehaviorSubject } from './custom-rxjs-subjects'; +export { ɵStateStream } from './state-stream'; diff --git a/packages/store/internals/src/initial-state.ts b/packages/store/internals/src/initial-state.ts index ed8c12cca..b939ebcae 100644 --- a/packages/store/internals/src/initial-state.ts +++ b/packages/store/internals/src/initial-state.ts @@ -1,29 +1,29 @@ import { InjectionToken } from '@angular/core'; -import { PlainObject } from './symbols'; +import { ɵPlainObject } from './symbols'; declare const ngDevMode: boolean; const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; -export class InitialState { - private static _value: PlainObject = {}; +export class ɵInitialState { + private static _value: ɵPlainObject = {}; - static set(state: PlainObject) { + static set(state: ɵPlainObject) { this._value = state; } - static pop(): PlainObject { + static pop(): ɵPlainObject { const state = this._value; this._value = {}; return state; } } -export const INITIAL_STATE_TOKEN = new InjectionToken( +export const ɵINITIAL_STATE_TOKEN = new InjectionToken<ɵPlainObject>( NG_DEV_MODE ? 'INITIAL_STATE_TOKEN' : '', { providedIn: 'root', - factory: () => InitialState.pop() + factory: () => ɵInitialState.pop() } ); diff --git a/packages/store/internals/src/memoize.ts b/packages/store/internals/src/memoize.ts index f6270f41c..5cb3a0577 100644 --- a/packages/store/internals/src/memoize.ts +++ b/packages/store/internals/src/memoize.ts @@ -28,7 +28,7 @@ function areArgumentsShallowlyEqual( * * @ignore */ -export function memoize any>( +export function ɵmemoize any>( func: T, equalityCheck = defaultEqualityCheck ): T { diff --git a/packages/store/internals/src/metadata.ts b/packages/store/internals/src/metadata.ts new file mode 100644 index 000000000..32894a148 --- /dev/null +++ b/packages/store/internals/src/metadata.ts @@ -0,0 +1,70 @@ +import { + ɵMETA_KEY, + ɵSELECTOR_META_KEY, + ɵMetaDataModel, + ɵStateClassInternal, + ɵSelectorMetaDataModel, + ɵRuntimeSelectorContext +} from './symbols'; + +/** + * Ensures metadata is attached to the class and returns it. + * + * @ignore + */ +export function ɵensureStoreMetadata(target: ɵStateClassInternal): ɵMetaDataModel { + if (!target.hasOwnProperty(ɵMETA_KEY)) { + const defaultMetadata: ɵMetaDataModel = { + name: null, + actions: {}, + defaults: {}, + path: null, + makeRootSelector(context: ɵRuntimeSelectorContext) { + return context.getStateGetter(defaultMetadata.name); + }, + children: [] + }; + + Object.defineProperty(target, ɵMETA_KEY, { value: defaultMetadata }); + } + return ɵgetStoreMetadata(target); +} + +/** + * Get the metadata attached to the state class if it exists. + * + * @ignore + */ +export function ɵgetStoreMetadata(target: ɵStateClassInternal): ɵMetaDataModel { + return target[ɵMETA_KEY]!; +} + +/** + * Ensures metadata is attached to the selector and returns it. + * + * @ignore + */ +export function ɵensureSelectorMetadata(target: Function): ɵSelectorMetaDataModel { + if (!target.hasOwnProperty(ɵSELECTOR_META_KEY)) { + const defaultMetadata: ɵSelectorMetaDataModel = { + makeRootSelector: null, + originalFn: null, + containerClass: null, + selectorName: null, + getSelectorOptions: () => ({}) + }; + + Object.defineProperty(target, ɵSELECTOR_META_KEY, { value: defaultMetadata }); + } + + return ɵgetSelectorMetadata(target); +} + +/** + * Get the metadata attached to the selector if it exists. + * + * @ignore + */ +export function ɵgetSelectorMetadata(target: any): ɵSelectorMetaDataModel { + return target[ɵSELECTOR_META_KEY]; +} diff --git a/packages/store/internals/src/ngxs-app-bootstrapped-state.ts b/packages/store/internals/src/ngxs-app-bootstrapped-state.ts new file mode 100644 index 000000000..d2e570213 --- /dev/null +++ b/packages/store/internals/src/ngxs-app-bootstrapped-state.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { ReplaySubject } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class ɵNgxsAppBootstrappedState extends ReplaySubject { + constructor() { + super(1); + } + + bootstrap(): void { + this.next(true); + this.complete(); + } +} diff --git a/packages/store/internals/src/ngxs-bootstrapper.ts b/packages/store/internals/src/ngxs-bootstrapper.ts deleted file mode 100644 index 7cd1d5aec..000000000 --- a/packages/store/internals/src/ngxs-bootstrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable, ReplaySubject } from 'rxjs'; - -@Injectable({ providedIn: 'root' }) -export class NgxsBootstrapper { - /** - * Use `ReplaySubject`, thus we can get cached value even if the stream is completed - */ - private bootstrap$ = new ReplaySubject(1); - - get appBootstrapped$(): Observable { - return this.bootstrap$.asObservable(); - } - - /** - * This event will be emitted after attaching `ComponentRef` of the root component - * to the tree of views, that's a signal that application has been fully rendered - */ - bootstrap(): void { - this.bootstrap$.next(true); - this.bootstrap$.complete(); - } -} diff --git a/packages/store/internals/src/state-stream.ts b/packages/store/internals/src/state-stream.ts new file mode 100644 index 000000000..4605a88cf --- /dev/null +++ b/packages/store/internals/src/state-stream.ts @@ -0,0 +1,30 @@ +import { Injectable, OnDestroy, Signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; + +import { ɵPlainObject } from './symbols'; +import { ɵOrderedBehaviorSubject } from './custom-rxjs-subjects'; + +/** + * BehaviorSubject of the entire state. + * @ignore + */ +@Injectable({ providedIn: 'root' }) +export class ɵStateStream extends ɵOrderedBehaviorSubject<ɵPlainObject> implements OnDestroy { + readonly state: Signal<ɵPlainObject> = toSignal(this, { + manualCleanup: true, + requireSync: true + }); + + constructor() { + super({}); + } + + ngOnDestroy(): void { + // The StateStream should never emit values once the root view is removed, + // such as when the `NgModuleRef.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. + this.complete(); + } +} diff --git a/packages/store/internals/src/state-token.ts b/packages/store/internals/src/state-token.ts new file mode 100644 index 000000000..02bdd516b --- /dev/null +++ b/packages/store/internals/src/state-token.ts @@ -0,0 +1,21 @@ +import { ɵensureSelectorMetadata } from './metadata'; +import type { ɵTokenName, ɵSelectFromRootState, ɵRuntimeSelectorContext } from './symbols'; + +export class StateToken { + constructor(private readonly _name: ɵTokenName) { + const selectorMetadata = ɵensureSelectorMetadata(this); + selectorMetadata.makeRootSelector = ( + runtimeContext: ɵRuntimeSelectorContext + ): ɵSelectFromRootState => { + return runtimeContext.getStateGetter(this._name); + }; + } + + getName(): string { + return this._name; + } + + toString(): string { + return `StateToken[${this._name}]`; + } +} diff --git a/packages/store/internals/src/symbols.ts b/packages/store/internals/src/symbols.ts index 733fbdb38..2e842c795 100644 --- a/packages/store/internals/src/symbols.ts +++ b/packages/store/internals/src/symbols.ts @@ -1,8 +1,10 @@ -export interface PlainObject { +import type { StateToken } from './state-token'; + +export interface ɵPlainObject { [key: string]: any; } -export interface PlainObjectOf { +export interface ɵPlainObjectOf { [key: string]: T; } @@ -18,3 +20,89 @@ export const ɵMETA_OPTIONS_KEY = 'NGXS_OPTIONS_META'; // such as decorated with the `@Selector` or provided through the // `createSelector` function. export const ɵSELECTOR_META_KEY = 'NGXS_SELECTOR_META'; + +export interface ɵStateToken { + new (name: ɵTokenName): U; + getName(): string; + toString(): string; +} + +type RequireGeneric = T extends void ? 'You must provide a type parameter' : U; + +export type ɵTokenName = string & RequireGeneric; + +export type ɵExtractTokenType

= P extends StateToken ? T : never; + +/** + * Options that can be provided to the store. + */ +export interface ɵStoreOptions { + /** + * Name of the state. Required. + */ + name: string | StateToken; + + /** + * Default values for the state. If not provided, uses empty object. + */ + defaults?: T; + + /** + * Sub states for the given state. + */ + children?: ɵStateClass[]; +} + +// inspired from https://stackoverflow.com/a/43674389 +export interface ɵStateClassInternal extends ɵStateClass { + [ɵMETA_KEY]?: ɵMetaDataModel; + [ɵMETA_OPTIONS_KEY]?: ɵStoreOptions; +} + +export interface ɵMetaDataModel { + name: string | null; + actions: ɵPlainObjectOf<ɵActionHandlerMetaData[]>; + defaults: any; + path: string | null; + makeRootSelector: ɵSelectorFactory | null; + children?: ɵStateClassInternal[]; +} + +export interface ɵSelectorMetaDataModel { + makeRootSelector: ɵSelectorFactory | null; + originalFn: Function | null; + containerClass: any; + selectorName: string | null; + getSelectorOptions: () => ɵSharedSelectorOptions; +} + +export interface ɵSharedSelectorOptions { + injectContainerState?: boolean; + suppressErrors?: boolean; +} + +export interface ɵRuntimeSelectorContext { + getStateGetter(key: any): (state: any) => any; + getSelectorOptions(localOptions?: ɵSharedSelectorOptions): ɵSharedSelectorOptions; +} + +export type ɵSelectFromRootState = (rootState: any) => any; +export type ɵSelectorFactory = ( + runtimeContext: ɵRuntimeSelectorContext +) => ɵSelectFromRootState; + +/** + * Actions that can be provided in a action decorator. + */ +export interface ɵActionOptions { + /** + * Cancel the previous uncompleted observable(s). + */ + cancelUncompleted?: boolean; +} + +export interface ɵActionHandlerMetaData { + fn: string | symbol; + options: ɵActionOptions; + type: string; +} diff --git a/packages/store/internals/testing/tests/ngxs.setup.spec.ts b/packages/store/internals/testing/tests/ngxs.setup.spec.ts index 98bb4251b..ee139bf64 100644 --- a/packages/store/internals/testing/tests/ngxs.setup.spec.ts +++ b/packages/store/internals/testing/tests/ngxs.setup.spec.ts @@ -1,6 +1,6 @@ import { NgxsAfterBootstrap, NgxsOnInit, State, StateContext } from '@ngxs/store'; import { NgxsTestBed } from '@ngxs/store/internals/testing'; -import { InitialState } from '@ngxs/store/internals'; +import { ɵInitialState } from '@ngxs/store/internals'; import { Injectable } from '@angular/core'; describe('Full testing NGXS States with NgxsTestBed', () => { @@ -53,7 +53,7 @@ describe('Full testing NGXS States with NgxsTestBed', () => { defaultsState: { app: { count: 0 }, foo: 'bar' } }, before: () => { - InitialState.set({ app: { count: 1 } }); + ɵInitialState.set({ app: { count: 1 } }); } }); @@ -119,7 +119,7 @@ describe('Full testing NGXS States with NgxsTestBed', () => { }); it('with initial state', () => { - InitialState.set({ defaultValue: 2 }); + ɵInitialState.set({ defaultValue: 2 }); const { store } = NgxsTestBed.configureTestingStates({ states: [MyState] diff --git a/packages/store/plugins/ng-package.json b/packages/store/plugins/ng-package.json new file mode 100644 index 000000000..f3fb18c4f --- /dev/null +++ b/packages/store/plugins/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "src/index.ts", + "flatModuleFile": "ngxs-store-plugins" + } +} diff --git a/packages/store/src/actions/actions.ts b/packages/store/plugins/src/actions.ts similarity index 62% rename from packages/store/src/actions/actions.ts rename to packages/store/plugins/src/actions.ts index d605ddfd7..65dcdb2af 100644 --- a/packages/store/src/actions/actions.ts +++ b/packages/store/plugins/src/actions.ts @@ -1,4 +1,4 @@ -import { PlainObject } from '@ngxs/store/internals'; +import { ɵPlainObject } from '@ngxs/store/internals'; /** * Init action @@ -13,5 +13,5 @@ export class InitState { export class UpdateState { static readonly type = '@@UPDATE_STATE'; - constructor(public addedStates?: PlainObject) {} + constructor(readonly addedStates?: ɵPlainObject) {} } diff --git a/packages/store/plugins/src/index.ts b/packages/store/plugins/src/index.ts new file mode 100644 index 000000000..1596c2996 --- /dev/null +++ b/packages/store/plugins/src/index.ts @@ -0,0 +1,3 @@ +export { InitState, UpdateState } from './actions'; +export { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn, NgxsNextPluginFn } from './symbols'; +export { getActionTypeFromInstance, actionMatcher, setValue, getValue } from './utils'; diff --git a/packages/store/plugins/src/symbols.ts b/packages/store/plugins/src/symbols.ts new file mode 100644 index 000000000..1505cca29 --- /dev/null +++ b/packages/store/plugins/src/symbols.ts @@ -0,0 +1,23 @@ +import { InjectionToken } from '@angular/core'; + +declare const ngDevMode: boolean; + +const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; + +// The injection token is used to resolve to custom NGXS plugins provided +// at the root level through either `{provide}` scheme or `withNgxsPlugin`. +export const NGXS_PLUGINS = new InjectionToken(NG_DEV_MODE ? 'NGXS_PLUGINS' : ''); + +export type NgxsPluginFn = (state: any, mutation: any, next: NgxsNextPluginFn) => any; + +export type NgxsNextPluginFn = (state: any, mutation: any) => any; + +/** + * Plugin interface + */ +export interface NgxsPlugin { + /** + * Handle the state/action before its submitted to the state handlers. + */ + handle(state: any, action: any, next: NgxsNextPluginFn): any; +} diff --git a/packages/store/src/utils/utils.ts b/packages/store/plugins/src/utils.ts similarity index 55% rename from packages/store/src/utils/utils.ts rename to packages/store/plugins/src/utils.ts index 631c30eaf..c6e035c7f 100644 --- a/packages/store/src/utils/utils.ts +++ b/packages/store/plugins/src/utils.ts @@ -3,11 +3,7 @@ * @ignore */ export function getActionTypeFromInstance(action: any): string | undefined { - if (action.constructor && action.constructor.type) { - return action.constructor.type; - } else { - return action.type; - } + return action.constructor?.type || action.type; } /** @@ -60,40 +56,3 @@ export const setValue = (obj: any, prop: string, val: any) => { */ export const getValue = (obj: any, prop: string): any => prop.split('.').reduce((acc: any, part: string) => acc && acc[part], obj); - -/** - * Simple object check. - * - * isObject({a:1}) //=> true - * isObject(1) //=> false - * - * @ignore - */ -export const isObject = (item: any) => { - return item && typeof item === 'object' && !Array.isArray(item); -}; - -/** - * Deep merge two objects. - * - * mergeDeep({a:1, b:{x: 1, y:2}}, {b:{x: 3}, c:4}) //=> {a:1, b:{x:3, y:2}, c:4} - * - * @param base base object onto which `sources` will be applied - */ -export const mergeDeep = (base: any, ...sources: any[]): any => { - if (!sources.length) return base; - const source = sources.shift(); - - if (isObject(base) && isObject(source)) { - for (const key in source) { - if (isObject(source[key])) { - if (!base[key]) Object.assign(base, { [key]: {} }); - mergeDeep(base[key], source[key]); - } else { - Object.assign(base, { [key]: source[key] }); - } - } - } - - return mergeDeep(base, ...sources); -}; diff --git a/packages/store/src/actions-stream.ts b/packages/store/src/actions-stream.ts index 163189994..cb7313727 100644 --- a/packages/store/src/actions-stream.ts +++ b/packages/store/src/actions-stream.ts @@ -1,10 +1,10 @@ import { Injectable, OnDestroy } from '@angular/core'; +import { ɵOrderedSubject } from '@ngxs/store/internals'; import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; import { leaveNgxs } from './operators/leave-ngxs'; import { InternalNgxsExecutionStrategy } from './execution/internal-ngxs-execution-strategy'; -import { OrderedSubject } from './internal/custom-rxjs-subjects'; /** * Status of a dispatched action @@ -26,7 +26,7 @@ export interface ActionContext { * Internal Action stream that is emitted anytime an action is dispatched. */ @Injectable({ providedIn: 'root' }) -export class InternalActions extends OrderedSubject implements OnDestroy { +export class InternalActions extends ɵOrderedSubject implements OnDestroy { ngOnDestroy(): void { this.complete(); } diff --git a/packages/store/src/actions/symbols.ts b/packages/store/src/actions/symbols.ts index 08378376d..05ee3c96b 100644 --- a/packages/store/src/actions/symbols.ts +++ b/packages/store/src/actions/symbols.ts @@ -5,19 +5,3 @@ export interface ActionDef { } export type ActionType = ActionDef | { type: string }; - -/** - * Actions that can be provided in a action decorator. - */ -export interface ActionOptions { - /** - * Cancel the previous uncompleted observable(s). - */ - cancelUncompleted?: boolean; -} - -export interface ActionHandlerMetaData { - fn: string | symbol; - options: ActionOptions; - type: string; -} diff --git a/packages/store/src/configs/messages.config.ts b/packages/store/src/configs/messages.config.ts index bbb716dac..e8ab4a47f 100644 --- a/packages/store/src/configs/messages.config.ts +++ b/packages/store/src/configs/messages.config.ts @@ -1,4 +1,4 @@ -import { PlainObject } from '@ngxs/store/internals'; +import { ɵPlainObject } from '@ngxs/store/internals'; export function throwStateNameError(name: string): never { throw new Error( @@ -42,7 +42,7 @@ export function getUndecoratedStateInIvyWarningMessage(name: string): string { return `'${name}' class should be decorated with @Injectable() right after the @State() decorator`; } -export function getInvalidInitializationOrderMessage(addedStates?: PlainObject) { +export function getInvalidInitializationOrderMessage(addedStates?: ɵPlainObject) { let message = 'You have an invalid state initialization order. This typically occurs when `NgxsModule.forFeature`\n' + 'or `provideStates` is called before `NgxsModule.forRoot` or `provideStore`.\n' + diff --git a/packages/store/src/decorators/action.ts b/packages/store/src/decorators/action.ts index a3e54b6bf..341d66887 100644 --- a/packages/store/src/decorators/action.ts +++ b/packages/store/src/decorators/action.ts @@ -1,5 +1,6 @@ -import { ensureStoreMetadata } from '../internal/internals'; -import { ActionType, ActionOptions } from '../actions/symbols'; +import { ɵActionOptions, ɵensureStoreMetadata } from '@ngxs/store/internals'; + +import { ActionType } from '../actions/symbols'; import { throwActionDecoratorError } from '../configs/messages.config'; /** @@ -7,7 +8,7 @@ import { throwActionDecoratorError } from '../configs/messages.config'; */ export function Action( actions: ActionType | ActionType[], - options?: ActionOptions + options?: ɵActionOptions ): MethodDecorator { return (target: any, name: string | symbol): void => { // Caretaker note: we have still left the `typeof` condition in order to avoid @@ -20,7 +21,7 @@ export function Action( } } - const meta = ensureStoreMetadata(target.constructor); + const meta = ɵensureStoreMetadata(target.constructor); if (!Array.isArray(actions)) { actions = [actions]; diff --git a/packages/store/src/decorators/select/symbols.ts b/packages/store/src/decorators/select/symbols.ts index 30d278a71..c1af07c5d 100644 --- a/packages/store/src/decorators/select/symbols.ts +++ b/packages/store/src/decorators/select/symbols.ts @@ -1,9 +1,8 @@ import { Observable } from 'rxjs'; +import { ɵExtractTokenType, StateToken } from '@ngxs/store/internals'; import { propGetter } from '../../internal/internals'; import { SelectFactory } from './select-factory'; -import { StateToken } from '../../state-token/state-token'; -import { ExtractTokenType } from '../../state-token/symbols'; import { throwSelectFactoryNotConnectedError } from '../../configs/messages.config'; const DOLLAR_CHAR_CODE = 36; @@ -39,7 +38,7 @@ export function removeDollarAtTheEnd(name: string): string { export type PropertyType = T extends StateToken - ? Observable> + ? Observable<ɵExtractTokenType> : T extends (...args: any[]) => any ? Observable> : any; diff --git a/packages/store/src/decorators/selector-options.ts b/packages/store/src/decorators/selector-options.ts index ca79a8c3d..5d773c676 100644 --- a/packages/store/src/decorators/selector-options.ts +++ b/packages/store/src/decorators/selector-options.ts @@ -1,10 +1,11 @@ -import { SharedSelectorOptions } from '../internal/internals'; +import { ɵSharedSelectorOptions } from '@ngxs/store/internals'; + import { selectorOptionsMetaAccessor } from '../selectors/selector-metadata'; /** * Decorator for setting selector options at a method or class level. */ -export function SelectorOptions(options: SharedSelectorOptions) { +export function SelectorOptions(options: ɵSharedSelectorOptions) { return ( function decorate( target: any, diff --git a/packages/store/src/decorators/selector/symbols.ts b/packages/store/src/decorators/selector/symbols.ts index f4c64d2a9..508c32c7c 100644 --- a/packages/store/src/decorators/selector/symbols.ts +++ b/packages/store/src/decorators/selector/symbols.ts @@ -1,10 +1,9 @@ -import { StateToken } from '../../state-token/state-token'; -import { ExtractTokenType } from '../../state-token/symbols'; +import { StateToken, ɵExtractTokenType } from '@ngxs/store/internals'; export type SelectorSpec = [T] extends [never] ? (...states: any[]) => any : T extends StateToken - ? (state: ExtractTokenType) => U + ? (state: ɵExtractTokenType) => U : (...states: any[]) => any; export type SelectorType = ( diff --git a/packages/store/src/decorators/state.ts b/packages/store/src/decorators/state.ts index 1cc37af85..aa77bce47 100644 --- a/packages/store/src/decorators/state.ts +++ b/packages/store/src/decorators/state.ts @@ -1,24 +1,30 @@ -import { ɵStateClass, ɵMETA_KEY, ɵMETA_OPTIONS_KEY } from '@ngxs/store/internals'; +import { + ɵStateClass, + ɵMETA_KEY, + ɵMETA_OPTIONS_KEY, + ɵMetaDataModel, + ɵStateClassInternal, + ɵStoreOptions, + ɵensureStoreMetadata +} from '@ngxs/store/internals'; -import { StoreOptions } from '../symbols'; import { ensureStateNameIsValid } from '../utils/store-validators'; -import { ensureStoreMetadata, MetaDataModel, StateClassInternal } from '../internal/internals'; interface MutateMetaOptions { - meta: MetaDataModel; - inheritedStateClass: StateClassInternal; - optionsWithInheritance: StoreOptions; + meta: ɵMetaDataModel; + inheritedStateClass: ɵStateClassInternal; + optionsWithInheritance: ɵStoreOptions; } /** * Decorates a class with ngxs state information. */ -export function State(options: StoreOptions) { +export function State(options: ɵStoreOptions) { return (target: ɵStateClass): void => { - const stateClass: StateClassInternal = target; - const meta: MetaDataModel = ensureStoreMetadata(stateClass); - const inheritedStateClass: StateClassInternal = Object.getPrototypeOf(stateClass); - const optionsWithInheritance: StoreOptions = getStateOptions( + const stateClass: ɵStateClassInternal = target; + const meta: ɵMetaDataModel = ɵensureStoreMetadata(stateClass); + const inheritedStateClass: ɵStateClassInternal = Object.getPrototypeOf(stateClass); + const optionsWithInheritance: ɵStoreOptions = getStateOptions( inheritedStateClass, options ); @@ -28,12 +34,12 @@ export function State(options: StoreOptions) { } function getStateOptions( - inheritedStateClass: StateClassInternal, - options: StoreOptions -): StoreOptions { - const inheritanceOptions: Partial> = + inheritedStateClass: ɵStateClassInternal, + options: ɵStoreOptions +): ɵStoreOptions { + const inheritanceOptions: Partial<ɵStoreOptions> = inheritedStateClass[ɵMETA_OPTIONS_KEY] || {}; - return { ...inheritanceOptions, ...options } as StoreOptions; + return { ...inheritanceOptions, ...options } as ɵStoreOptions; } function mutateMetaData(params: MutateMetaOptions): void { @@ -47,7 +53,7 @@ function mutateMetaData(params: MutateMetaOptions): void { } if (inheritedStateClass.hasOwnProperty(ɵMETA_KEY)) { - const inheritedMeta: Partial = inheritedStateClass[ɵMETA_KEY] || {}; + const inheritedMeta: Partial<ɵMetaDataModel> = inheritedStateClass[ɵMETA_KEY] || {}; meta.actions = { ...meta.actions, ...inheritedMeta.actions }; } diff --git a/packages/store/src/dev-features/ngxs-development.module.ts b/packages/store/src/dev-features/ngxs-development.module.ts index 34a682b66..3670263a6 100644 --- a/packages/store/src/dev-features/ngxs-development.module.ts +++ b/packages/store/src/dev-features/ngxs-development.module.ts @@ -1,4 +1,4 @@ -import { ModuleWithProviders, NgModule } from '@angular/core'; +import { ModuleWithProviders, NgModule, makeEnvironmentProviders } from '@angular/core'; import { NgxsDevelopmentOptions, NGXS_DEVELOPMENT_OPTIONS } from './symbols'; import { NgxsUnhandledActionsLogger } from './ngxs-unhandled-actions-logger'; @@ -15,3 +15,10 @@ export class NgxsDevelopmentModule { }; } } + +export function provideNgxsDevelopmentOptions(options: NgxsDevelopmentOptions) { + return makeEnvironmentProviders([ + NgxsUnhandledActionsLogger, + { provide: NGXS_DEVELOPMENT_OPTIONS, useValue: options } + ]); +} diff --git a/packages/store/src/dev-features/ngxs-unhandled-actions-logger.ts b/packages/store/src/dev-features/ngxs-unhandled-actions-logger.ts index f6d6ea2d1..b9b24c30d 100644 --- a/packages/store/src/dev-features/ngxs-unhandled-actions-logger.ts +++ b/packages/store/src/dev-features/ngxs-unhandled-actions-logger.ts @@ -1,8 +1,7 @@ import { Inject, Injectable } from '@angular/core'; +import { InitState, UpdateState, getActionTypeFromInstance } from '@ngxs/store/plugins'; import { ActionType } from '../actions/symbols'; -import { getActionTypeFromInstance } from '../utils/utils'; -import { InitState, UpdateState } from '../actions/actions'; import { NgxsDevelopmentOptions, NGXS_DEVELOPMENT_OPTIONS } from './symbols'; @Injectable() diff --git a/packages/store/src/dev-features/symbols.ts b/packages/store/src/dev-features/symbols.ts index 9ffb23871..2f4b35053 100644 --- a/packages/store/src/dev-features/symbols.ts +++ b/packages/store/src/dev-features/symbols.ts @@ -2,6 +2,8 @@ import { InjectionToken } from '@angular/core'; import { ActionType } from '../actions/symbols'; +const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; + export interface NgxsDevelopmentOptions { // This allows setting only `true` because there's no reason to set `false`. // Developers may just skip importing the development module at all. @@ -13,7 +15,7 @@ export interface NgxsDevelopmentOptions { } export const NGXS_DEVELOPMENT_OPTIONS = new InjectionToken( - 'NGXS_DEVELOPMENT_OPTIONS', + NG_DEV_MODE ? 'NGXS_DEVELOPMENT_OPTIONS' : '', { providedIn: 'root', factory: () => ({ warnOnUnhandledActions: true }) diff --git a/packages/store/src/internal/dispatcher.ts b/packages/store/src/internal/dispatcher.ts index bf7829ed6..219f2655d 100644 --- a/packages/store/src/internal/dispatcher.ts +++ b/packages/store/src/internal/dispatcher.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { getActionTypeFromInstance } from '@ngxs/store/plugins'; import { EMPTY, forkJoin, Observable, of, Subject, throwError } from 'rxjs'; import { exhaustMap, filter, shareReplay, take } from 'rxjs/operators'; @@ -8,7 +9,6 @@ import { ActionContext, ActionStatus, InternalActions } from '../actions-stream' import { StateStream } from './state-stream'; import { PluginManager } from '../plugin-manager'; import { InternalNgxsExecutionStrategy } from '../execution/internal-ngxs-execution-strategy'; -import { getActionTypeFromInstance } from '../utils/utils'; /** * Internal Action result stream that is emitted when an action is completed. diff --git a/packages/store/src/internal/internals.ts b/packages/store/src/internal/internals.ts index d3dcdbf4a..fc9b0c293 100644 --- a/packages/store/src/internal/internals.ts +++ b/packages/store/src/internal/internals.ts @@ -1,23 +1,15 @@ +import { Observable } from 'rxjs'; import { - PlainObjectOf, - ɵStateClass, ɵMETA_KEY, - ɵMETA_OPTIONS_KEY, - ɵSELECTOR_META_KEY + ɵPlainObjectOf, + ɵStateClassInternal, + ɵActionHandlerMetaData } from '@ngxs/store/internals'; -import { Observable } from 'rxjs'; -import { NgxsConfig, StoreOptions } from '../symbols'; -import { ActionHandlerMetaData } from '../actions/symbols'; - -// inspired from https://stackoverflow.com/a/43674389 -export interface StateClassInternal extends ɵStateClass { - [ɵMETA_KEY]?: MetaDataModel; - [ɵMETA_OPTIONS_KEY]?: StoreOptions; -} +import { NgxsConfig } from '../symbols'; -export type StateKeyGraph = PlainObjectOf; -export type StatesByName = PlainObjectOf; +export type StateKeyGraph = ɵPlainObjectOf; +export type StatesByName = ɵPlainObjectOf<ɵStateClassInternal>; export interface StateOperations { getState(): T; @@ -27,40 +19,10 @@ export interface StateOperations { dispatch(actionOrActions: any | any[]): Observable; } -export interface MetaDataModel { - name: string | null; - actions: PlainObjectOf; - defaults: any; - path: string | null; - makeRootSelector: SelectorFactory | null; - children?: StateClassInternal[]; -} - -export interface RuntimeSelectorContext { - getStateGetter(key: any): (state: any) => any; - getSelectorOptions(localOptions?: SharedSelectorOptions): SharedSelectorOptions; -} - -export type SelectFromRootState = (rootState: any) => any; -export type SelectorFactory = (runtimeContext: RuntimeSelectorContext) => SelectFromRootState; - -export interface SharedSelectorOptions { - injectContainerState?: boolean; - suppressErrors?: boolean; -} - -export interface SelectorMetaDataModel { - makeRootSelector: SelectorFactory | null; - originalFn: Function | null; - containerClass: any; - selectorName: string | null; - getSelectorOptions: () => SharedSelectorOptions; -} - export interface MappedStore { name: string; isInitialised: boolean; - actions: PlainObjectOf; + actions: ɵPlainObjectOf<ɵActionHandlerMetaData[]>; defaults: any; instance: any; path: string; @@ -71,68 +33,6 @@ export interface StatesAndDefaults { states: MappedStore[]; } -/** - * Ensures metadata is attached to the class and returns it. - * - * @ignore - */ -export function ensureStoreMetadata(target: StateClassInternal): MetaDataModel { - if (!target.hasOwnProperty(ɵMETA_KEY)) { - const defaultMetadata: MetaDataModel = { - name: null, - actions: {}, - defaults: {}, - path: null, - makeRootSelector(context: RuntimeSelectorContext) { - return context.getStateGetter(defaultMetadata.name); - }, - children: [] - }; - - Object.defineProperty(target, ɵMETA_KEY, { value: defaultMetadata }); - } - return getStoreMetadata(target); -} - -/** - * Get the metadata attached to the state class if it exists. - * - * @ignore - */ -export function getStoreMetadata(target: StateClassInternal): MetaDataModel { - return target[ɵMETA_KEY]!; -} - -/** - * Ensures metadata is attached to the selector and returns it. - * - * @ignore - */ -export function ensureSelectorMetadata(target: Function): SelectorMetaDataModel { - if (!target.hasOwnProperty(ɵSELECTOR_META_KEY)) { - const defaultMetadata: SelectorMetaDataModel = { - makeRootSelector: null, - originalFn: null, - containerClass: null, - selectorName: null, - getSelectorOptions: () => ({}) - }; - - Object.defineProperty(target, ɵSELECTOR_META_KEY, { value: defaultMetadata }); - } - - return getSelectorMetadata(target); -} - -/** - * Get the metadata attached to the selector if it exists. - * - * @ignore - */ -export function getSelectorMetadata(target: any): SelectorMetaDataModel { - return target[ɵSELECTOR_META_KEY]; -} - /** * Get a deeply nested value. Example: * @@ -179,7 +79,7 @@ function fastPropGetter(paths: string[]): (x: any) => any { * @ignore */ export function propGetter(paths: string[], config: NgxsConfig) { - if (config && config.compatibility && config.compatibility.strictContentSecurityPolicy) { + if (config?.compatibility?.strictContentSecurityPolicy) { return compliantPropGetter(paths); } else { return fastPropGetter(paths); @@ -204,8 +104,8 @@ export function propGetter(paths: string[], config: NgxsConfig) { * * @ignore */ -export function buildGraph(stateClasses: StateClassInternal[]): StateKeyGraph { - const findName = (stateClass: StateClassInternal) => { +export function buildGraph(stateClasses: ɵStateClassInternal[]): StateKeyGraph { + const findName = (stateClass: ɵStateClassInternal) => { const meta = stateClasses.find(g => g === stateClass); // Caretaker note: we have still left the `typeof` condition in order to avoid @@ -220,7 +120,7 @@ export function buildGraph(stateClasses: StateClassInternal[]): StateKeyGraph { }; return stateClasses.reduce( - (result: StateKeyGraph, stateClass: StateClassInternal) => { + (result: StateKeyGraph, stateClass: ɵStateClassInternal) => { const { name, children } = stateClass[ɵMETA_KEY]!; result[name!] = (children || []).map(findName); return result; @@ -239,9 +139,11 @@ export function buildGraph(stateClasses: StateClassInternal[]): StateKeyGraph { * * @ignore */ -export function nameToState(states: StateClassInternal[]): PlainObjectOf { - return states.reduce>( - (result: PlainObjectOf, stateClass: StateClassInternal) => { +export function nameToState( + states: ɵStateClassInternal[] +): ɵPlainObjectOf<ɵStateClassInternal> { + return states.reduce<ɵPlainObjectOf<ɵStateClassInternal>>( + (result: ɵPlainObjectOf<ɵStateClassInternal>, stateClass: ɵStateClassInternal) => { const meta = stateClass[ɵMETA_KEY]!; result[meta.name!] = stateClass; return result; @@ -272,8 +174,8 @@ export function nameToState(states: StateClassInternal[]): PlainObjectOf = {} -): PlainObjectOf { + newObj: ɵPlainObjectOf = {} +): ɵPlainObjectOf { const visit = (child: StateKeyGraph, keyToFind: string): string | null => { for (const key in child) { if (child.hasOwnProperty(key) && child[key].indexOf(keyToFind) >= 0) { @@ -315,7 +217,7 @@ export function findFullParentPath( */ export function topologicalSort(graph: StateKeyGraph): string[] { const sorted: string[] = []; - const visited: PlainObjectOf = {}; + const visited: ɵPlainObjectOf = {}; const visit = (name: string, ancestors: string[] = []) => { if (!Array.isArray(ancestors)) { @@ -350,12 +252,3 @@ export function topologicalSort(graph: StateKeyGraph): string[] { return sorted.reverse(); } - -/** - * Returns if the parameter is a object or not. - * - * @ignore - */ -export function isObject(obj: any) { - return (typeof obj === 'object' && obj !== null) || typeof obj === 'function'; -} diff --git a/packages/store/src/internal/lifecycle-state-manager.ts b/packages/store/src/internal/lifecycle-state-manager.ts index da7231b50..2fb68d8e3 100644 --- a/packages/store/src/internal/lifecycle-state-manager.ts +++ b/packages/store/src/internal/lifecycle-state-manager.ts @@ -1,5 +1,6 @@ import { Injectable, OnDestroy } from '@angular/core'; -import { NgxsBootstrapper } from '@ngxs/store/internals'; +import { ɵNgxsAppBootstrappedState } from '@ngxs/store/internals'; +import { getValue, InitState, UpdateState } from '@ngxs/store/plugins'; import { EMPTY, ReplaySubject } from 'rxjs'; import { catchError, @@ -12,9 +13,7 @@ import { } from 'rxjs/operators'; import { Store } from '../store'; -import { getValue } from '../utils/utils'; import { InternalErrorReporter } from './error-handler'; -import { InitState, UpdateState } from '../actions/actions'; import { StateContextFactory } from './state-context-factory'; import { InternalStateOperations } from './state-operations'; import { MappedStore, StatesAndDefaults } from './internals'; @@ -34,7 +33,7 @@ export class LifecycleStateManager implements OnDestroy { private _internalErrorReporter: InternalErrorReporter, private _internalStateOperations: InternalStateOperations, private _stateContextFactory: StateContextFactory, - private _bootstrapper: NgxsBootstrapper + private _appBootstrappedState: ɵNgxsAppBootstrappedState ) {} ngOnDestroy(): void { @@ -67,7 +66,7 @@ export class LifecycleStateManager implements OnDestroy { .pipe( filter(() => !!results), tap(() => this._invokeInitOnStates(results!.states)), - mergeMap(() => this._bootstrapper.appBootstrapped$), + mergeMap(() => this._appBootstrappedState), filter(appBootstrapped => !!appBootstrapped), catchError(error => { // The `SafeSubscriber` (which is used by most RxJS operators) re-throws diff --git a/packages/store/src/internal/state-context-factory.ts b/packages/store/src/internal/state-context-factory.ts index 9e2962482..536698b08 100644 --- a/packages/store/src/internal/state-context-factory.ts +++ b/packages/store/src/internal/state-context-factory.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; - +import { getValue, setValue } from '@ngxs/store/plugins'; import { ExistingState, StateOperator, isStateOperator } from '@ngxs/store/operators'; +import { Observable } from 'rxjs'; import { StateContext } from '../symbols'; import { MappedStore, StateOperations } from '../internal/internals'; -import { setValue, getValue } from '../utils/utils'; import { InternalStateOperations } from '../internal/state-operations'; import { simplePatch } from './state-operators'; diff --git a/packages/store/src/internal/state-factory.ts b/packages/store/src/internal/state-factory.ts index f222657dc..ed2f6cc15 100644 --- a/packages/store/src/internal/state-factory.ts +++ b/packages/store/src/internal/state-factory.ts @@ -7,6 +7,18 @@ import { OnDestroy, ɵisPromise } from '@angular/core'; +import { + ɵmemoize, + ɵMETA_KEY, + ɵPlainObjectOf, + ɵMetaDataModel, + ɵgetStoreMetadata, + ɵStateClassInternal, + ɵINITIAL_STATE_TOKEN, + ɵSharedSelectorOptions, + ɵRuntimeSelectorContext +} from '@ngxs/store/internals'; +import { getActionTypeFromInstance, getValue, setValue } from '@ngxs/store/plugins'; import { forkJoin, from, @@ -26,27 +38,19 @@ import { shareReplay, takeUntil } from 'rxjs/operators'; -import { INITIAL_STATE_TOKEN, PlainObjectOf, memoize, ɵMETA_KEY } from '@ngxs/store/internals'; import { NgxsConfig } from '../symbols'; import { buildGraph, findFullParentPath, - isObject, MappedStore, - MetaDataModel, nameToState, propGetter, - StateClassInternal, StateKeyGraph, StatesAndDefaults, StatesByName, - topologicalSort, - RuntimeSelectorContext, - SharedSelectorOptions, - getStoreMetadata + topologicalSort } from './internals'; -import { getActionTypeFromInstance, getValue, setValue } from '../utils/utils'; import { ofActionDispatched } from '../operators/of-action'; import { ActionContext, ActionStatus, InternalActions } from '../actions-stream'; import { InternalDispatchedActionResults } from '../internal/dispatcher'; @@ -57,6 +61,20 @@ import { NgxsUnhandledActionsLogger } from '../dev-features/ngxs-unhandled-actio const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; +function cloneDefaults(defaults: any): any { + let value = defaults === undefined ? {} : defaults; + + if (defaults) { + if (Array.isArray(defaults)) { + value = defaults.slice(); + } else if (typeof defaults === 'object') { + value = { ...defaults }; + } + } + + return value; +} + /** * The `StateFactory` class adds root and feature states to the graph. * This extracts state names from state classes, checks if they already @@ -84,7 +102,7 @@ export class StateFactory implements OnDestroy { private _actionResults: InternalDispatchedActionResults, private _stateContextFactory: StateContextFactory, @Optional() - @Inject(INITIAL_STATE_TOKEN) + @Inject(ɵINITIAL_STATE_TOKEN) private _initialState: any ) {} @@ -100,13 +118,13 @@ export class StateFactory implements OnDestroy { return this._parentFactory ? this._parentFactory.statesByName : this._statesByName; } - private _statePaths: PlainObjectOf = {}; + private _statePaths: ɵPlainObjectOf = {}; - private get statePaths(): PlainObjectOf { + private get statePaths(): ɵPlainObjectOf { return this._parentFactory ? this._parentFactory.statePaths : this._statePaths; } - getRuntimeSelectorContext = memoize(() => { + getRuntimeSelectorContext = ɵmemoize(() => { // eslint-disable-next-line @typescript-eslint/no-this-alias const stateFactory = this; @@ -115,7 +133,7 @@ export class StateFactory implements OnDestroy { return path ? propGetter(path.split('.'), stateFactory._config) : null; } - const context: RuntimeSelectorContext = this._parentFactory + const context: ɵRuntimeSelectorContext = this._parentFactory ? this._parentFactory.getRuntimeSelectorContext() : { getStateGetter(key: string) { @@ -133,7 +151,7 @@ export class StateFactory implements OnDestroy { return getter ? getter(...args) : undefined; }; }, - getSelectorOptions(localOptions?: SharedSelectorOptions) { + getSelectorOptions(localOptions?: ɵSharedSelectorOptions) { const globalSelectorOptions = stateFactory._config.selectorOptions; return { ...globalSelectorOptions, @@ -144,20 +162,6 @@ export class StateFactory implements OnDestroy { return context; }); - private static _cloneDefaults(defaults: any): any { - let value = defaults; - - if (Array.isArray(defaults)) { - value = defaults.slice(); - } else if (isObject(defaults)) { - value = { ...defaults }; - } else if (defaults === undefined) { - value = {}; - } - - return value; - } - ngOnDestroy(): void { this._actionsSubscription?.unsubscribe(); } @@ -165,7 +169,7 @@ export class StateFactory implements OnDestroy { /** * Add a new state to the global defs. */ - add(stateClasses: StateClassInternal[]): MappedStore[] { + add(stateClasses: ɵStateClassInternal[]): MappedStore[] { if (NG_DEV_MODE) { ensureStatesAreDecorated(stateClasses); } @@ -175,14 +179,14 @@ export class StateFactory implements OnDestroy { const stateGraph: StateKeyGraph = buildGraph(newStates); const sortedStates: string[] = topologicalSort(stateGraph); - const paths: PlainObjectOf = findFullParentPath(stateGraph); - const nameGraph: PlainObjectOf = nameToState(newStates); + const paths: ɵPlainObjectOf = findFullParentPath(stateGraph); + const nameGraph: ɵPlainObjectOf<ɵStateClassInternal> = nameToState(newStates); const bootstrappedStores: MappedStore[] = []; for (const name of sortedStates) { - const stateClass: StateClassInternal = nameGraph[name]; + const stateClass: ɵStateClassInternal = nameGraph[name]; const path: string = paths[name]; - const meta: MetaDataModel = stateClass[ɵMETA_KEY]!; + const meta: ɵMetaDataModel = stateClass[ɵMETA_KEY]!; this.addRuntimeInfoToMeta(meta, path); @@ -200,7 +204,7 @@ export class StateFactory implements OnDestroy { isInitialised: false, actions: meta.actions, instance: this._injector.get(stateClass), - defaults: StateFactory._cloneDefaults(meta.defaults) + defaults: cloneDefaults(meta.defaults) }; // ensure our store hasn't already been added @@ -219,8 +223,8 @@ export class StateFactory implements OnDestroy { /** * Add a set of states to the store and return the defaults */ - addAndReturnDefaults(stateClasses: StateClassInternal[]): StatesAndDefaults { - const classes: StateClassInternal[] = stateClasses || []; + addAndReturnDefaults(stateClasses: ɵStateClassInternal[]): StatesAndDefaults { + const classes: ɵStateClassInternal[] = stateClasses || []; const mappedStores: MappedStore[] = this.add(classes); const defaults = mappedStores.reduce( @@ -302,7 +306,7 @@ export class StateFactory implements OnDestroy { if (ɵisPromise(value)) { return from(value); } - if (isObservable(value)) { + if (isObservable(value)) { return value; } return of(value); @@ -349,14 +353,14 @@ export class StateFactory implements OnDestroy { return forkJoin(results); } - private addToStatesMap(stateClasses: StateClassInternal[]): { - newStates: StateClassInternal[]; + private addToStatesMap(stateClasses: ɵStateClassInternal[]): { + newStates: ɵStateClassInternal[]; } { - const newStates: StateClassInternal[] = []; + const newStates: ɵStateClassInternal[] = []; const statesMap: StatesByName = this.statesByName; for (const stateClass of stateClasses) { - const stateName = getStoreMetadata(stateClass).name!; + const stateName = ɵgetStoreMetadata(stateClass).name!; if (NG_DEV_MODE) { ensureStateNameIsUnique(stateName, stateClass, statesMap); } @@ -370,7 +374,7 @@ export class StateFactory implements OnDestroy { return { newStates }; } - private addRuntimeInfoToMeta(meta: MetaDataModel, path: string): void { + private addRuntimeInfoToMeta(meta: ɵMetaDataModel, path: string): void { this.statePaths[meta.name!] = path; // TODO: v4 - we plan to get rid of the path property because it is non-deterministic // we can do this when we get rid of the incorrectly exposed getStoreMetadata diff --git a/packages/store/src/internal/state-stream.ts b/packages/store/src/internal/state-stream.ts index 264386c91..d36d6eef9 100644 --- a/packages/store/src/internal/state-stream.ts +++ b/packages/store/src/internal/state-stream.ts @@ -1,31 +1,8 @@ -import { Injectable, OnDestroy, Signal } from '@angular/core'; -import { toSignal } from '@angular/core/rxjs-interop'; - -import { PlainObject } from '@ngxs/store/internals'; - -import { OrderedBehaviorSubject } from './custom-rxjs-subjects'; +import { Injectable } from '@angular/core'; +import { ɵStateStream } from '@ngxs/store/internals'; /** - * BehaviorSubject of the entire state. - * @ignore + * @deprecated use `ɵStateStream` from `@ngxs/store/internals`. */ @Injectable({ providedIn: 'root' }) -export class StateStream extends OrderedBehaviorSubject implements OnDestroy { - readonly state: Signal = toSignal(this, { - manualCleanup: true, - requireSync: true - }); - - constructor() { - super({}); - } - - ngOnDestroy(): void { - // The StateStream should never emit values once the root view is removed, - // such as when the `NgModuleRef.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. - this.complete(); - } -} +export class StateStream extends ɵStateStream {} diff --git a/packages/store/src/operators/of-action.ts b/packages/store/src/operators/of-action.ts index 588a38d2b..9a2eb329d 100644 --- a/packages/store/src/operators/of-action.ts +++ b/packages/store/src/operators/of-action.ts @@ -1,8 +1,8 @@ +import { getActionTypeFromInstance } from '@ngxs/store/plugins'; import { OperatorFunction, Observable } from 'rxjs'; import { map, filter } from 'rxjs/operators'; import { ActionType } from '../actions/symbols'; -import { getActionTypeFromInstance } from '../utils/utils'; import { ActionContext, ActionStatus } from '../actions-stream'; type TupleKeys = Exclude; diff --git a/packages/store/src/plugin-manager.ts b/packages/store/src/plugin-manager.ts index 04d4e74a9..6b242b0b6 100644 --- a/packages/store/src/plugin-manager.ts +++ b/packages/store/src/plugin-manager.ts @@ -1,5 +1,5 @@ import { Inject, Injectable, Optional, SkipSelf } from '@angular/core'; -import { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn } from './symbols'; +import { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn } from '@ngxs/store/plugins'; @Injectable() export class PluginManager { diff --git a/packages/store/src/plugin_api.ts b/packages/store/src/plugin_api.ts index 26e3c06e0..54f75e291 100644 --- a/packages/store/src/plugin_api.ts +++ b/packages/store/src/plugin_api.ts @@ -1,5 +1,12 @@ -export { NgxsModule } from './module'; -export { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn, NgxsNextPluginFn } from './symbols'; export { StateStream } from './internal/state-stream'; -export { getActionTypeFromInstance, setValue, getValue } from './utils/utils'; -export { InitState, UpdateState } from './actions/actions'; +export { + InitState, + UpdateState, + getActionTypeFromInstance, + setValue, + getValue, + NGXS_PLUGINS, + NgxsPlugin, + NgxsPluginFn, + NgxsNextPluginFn +} from '@ngxs/store/plugins'; diff --git a/packages/store/src/public_api.ts b/packages/store/src/public_api.ts index 27bdf166e..ea81bfeb8 100644 --- a/packages/store/src/public_api.ts +++ b/packages/store/src/public_api.ts @@ -40,11 +40,9 @@ export { NgxsSimpleChange } from './symbols'; export { Selector } from './decorators/selector/selector'; -export { getActionTypeFromInstance, actionMatcher } from './utils/utils'; export { NgxsExecutionStrategy } from './execution/symbols'; -export { ActionType, ActionOptions } from './actions/symbols'; +export { ActionType } from './actions/symbols'; export { NoopNgxsExecutionStrategy } from './execution/noop-ngxs-execution-strategy'; -export { StateToken } from './state-token/state-token'; export { NgxsDevelopmentOptions } from './dev-features/symbols'; export { NgxsDevelopmentModule } from './dev-features/ngxs-development.module'; @@ -60,3 +58,8 @@ export { } from './selectors'; export * from './standalone-features'; + +export { StateToken } from '@ngxs/store/internals'; +export { ɵActionOptions as ActionOptions } from '@ngxs/store/internals'; + +export { getActionTypeFromInstance, actionMatcher } from '@ngxs/store/plugins'; diff --git a/packages/store/src/public_to_deprecate.ts b/packages/store/src/public_to_deprecate.ts index 310c04102..8fe75ae04 100644 --- a/packages/store/src/public_to_deprecate.ts +++ b/packages/store/src/public_to_deprecate.ts @@ -1,45 +1,57 @@ import { - getSelectorMetadata as getSelectorMetadataInternal, - getStoreMetadata as getStoreMetadataInternal, - ensureStoreMetadata as ensureStoreMetadataInternal, - ensureSelectorMetadata as ensureSelectorMetadataInternal, - StateClassInternal, - SharedSelectorOptions -} from './internal/internals'; -import { PlainObjectOf } from '../internals/src/symbols'; -import { ActionHandlerMetaData } from './actions/symbols'; + ɵActionHandlerMetaData, + ɵMetaDataModel, + ɵPlainObjectOf, + ɵSelectorMetaDataModel, + ɵStateClassInternal, + ɵSharedSelectorOptions, + ɵgetSelectorMetadata, + ɵgetStoreMetadata, + ɵensureStoreMetadata, + ɵensureSelectorMetadata +} from '@ngxs/store/internals'; -interface MetaDataModel { - name: string | null; - actions: PlainObjectOf; - defaults: any; - path: string | null; - // selectFromAppState: SelectFromState | null; - // makeRootSelector: SelectorFactory | null; // Don't expose new stuff - children?: StateClassInternal[]; -} - -interface SelectorMetaDataModel { - // selectFromAppState: SelectFromState | null; - // makeRootSelector: SelectorFactory | null; // Don't expose new stuff - originalFn: Function | null; - containerClass: any; - selectorName: string | null; - getSelectorOptions: () => SharedSelectorOptions; -} +/** + * @deprecated will be removed after v4 + */ +export type StateClassInternal = ɵStateClassInternal; +/** + * @deprecated will be removed after v4 + */ +export type PlainObjectOf = ɵPlainObjectOf; +/** + * @deprecated will be removed after v4 + */ +export type ActionHandlerMetaData = ɵActionHandlerMetaData; +/** + * @deprecated will be removed after v4 + */ +export type SharedSelectorOptions = ɵSharedSelectorOptions; -export function ensureStoreMetadata(target: StateClassInternal): MetaDataModel { - return ensureStoreMetadataInternal(target); +/** + * @deprecated will be removed after v4 + */ +export function ensureStoreMetadata(target: ɵStateClassInternal): ɵMetaDataModel { + return ɵensureStoreMetadata(target); } -export function getStoreMetadata(target: StateClassInternal): MetaDataModel { - return getStoreMetadataInternal(target); +/** + * @deprecated will be removed after v4 + */ +export function getStoreMetadata(target: ɵStateClassInternal): ɵMetaDataModel { + return ɵgetStoreMetadata(target); } -export function ensureSelectorMetadata(target: Function): SelectorMetaDataModel { - return ensureSelectorMetadataInternal(target); +/** + * @deprecated will be removed after v4 + */ +export function ensureSelectorMetadata(target: Function): ɵSelectorMetaDataModel { + return ɵensureSelectorMetadata(target); } -export function getSelectorMetadata(target: any): SelectorMetaDataModel { - return getSelectorMetadataInternal(target); +/** + * @deprecated will be removed after v4 + */ +export function getSelectorMetadata(target: any): ɵSelectorMetaDataModel { + return ɵgetSelectorMetadata(target); } diff --git a/packages/store/src/selectors/selector-checks.util.ts b/packages/store/src/selectors/selector-checks.util.ts index 9d1b6ded8..a9a7e0f82 100644 --- a/packages/store/src/selectors/selector-checks.util.ts +++ b/packages/store/src/selectors/selector-checks.util.ts @@ -1,4 +1,5 @@ -import { getSelectorMetadata, getStoreMetadata } from '../internal/internals'; +import { ɵgetSelectorMetadata, ɵgetStoreMetadata } from '@ngxs/store/internals'; + import { SelectorDef } from './selector-types.util'; export function ensureValidSelector( @@ -8,7 +9,7 @@ export function ensureValidSelector( const noun = context.noun || 'selector'; const prefix = context.prefix ? context.prefix + ': ' : ''; ensureValueProvided(selector, { noun, prefix: context.prefix }); - const metadata = getSelectorMetadata(selector) || getStoreMetadata(selector as any); + const metadata = ɵgetSelectorMetadata(selector) || ɵgetStoreMetadata(selector as any); if (!metadata) { throw new Error(`${prefix}The value provided as the ${noun} is not a valid selector.`); } diff --git a/packages/store/src/selectors/selector-metadata.ts b/packages/store/src/selectors/selector-metadata.ts index 6b210aa99..7103a1e51 100644 --- a/packages/store/src/selectors/selector-metadata.ts +++ b/packages/store/src/selectors/selector-metadata.ts @@ -1,17 +1,18 @@ import { - ensureSelectorMetadata, - SelectorMetaDataModel, - SharedSelectorOptions -} from '../internal/internals'; + ɵSelectorMetaDataModel, + ɵSharedSelectorOptions, + ɵensureSelectorMetadata +} from '@ngxs/store/internals'; + import { CreationMetadata } from './selector-models'; const SELECTOR_OPTIONS_META_KEY = 'NGXS_SELECTOR_OPTIONS_META'; export const selectorOptionsMetaAccessor = { - getOptions: (target: any): SharedSelectorOptions => { + getOptions: (target: any): ɵSharedSelectorOptions => { return (target && (target)[SELECTOR_OPTIONS_META_KEY]) || {}; }, - defineOptions: (target: any, options: SharedSelectorOptions) => { + defineOptions: (target: any, options: ɵSharedSelectorOptions) => { if (!target) return; (target)[SELECTOR_OPTIONS_META_KEY] = options; } @@ -21,7 +22,7 @@ export function setupSelectorMetadata any>( originalFn: T, creationMetadata: Partial | undefined ) { - const selectorMetaData = ensureSelectorMetadata(originalFn); + const selectorMetaData = ɵensureSelectorMetadata(originalFn); selectorMetaData.originalFn = originalFn; let getExplicitSelectorOptions = () => ({}); if (creationMetadata) { @@ -37,9 +38,9 @@ export function setupSelectorMetadata any>( } function getLocalSelectorOptions( - selectorMetaData: SelectorMetaDataModel, - explicitOptions: SharedSelectorOptions -): SharedSelectorOptions { + selectorMetaData: ɵSelectorMetaDataModel, + explicitOptions: ɵSharedSelectorOptions +): ɵSharedSelectorOptions { return { ...(selectorOptionsMetaAccessor.getOptions(selectorMetaData.containerClass) || {}), ...(selectorOptionsMetaAccessor.getOptions(selectorMetaData.originalFn) || {}), diff --git a/packages/store/src/selectors/selector-models.ts b/packages/store/src/selectors/selector-models.ts index 9a84ce88a..2a38ad661 100644 --- a/packages/store/src/selectors/selector-models.ts +++ b/packages/store/src/selectors/selector-models.ts @@ -1,12 +1,12 @@ -import { SharedSelectorOptions, SelectFromRootState } from '../internal/internals'; +import { ɵSharedSelectorOptions, ɵSelectFromRootState } from '@ngxs/store/internals'; export interface CreationMetadata { containerClass: any; selectorName: string; - getSelectorOptions?: () => SharedSelectorOptions; + getSelectorOptions?: () => ɵSharedSelectorOptions; } export interface RuntimeSelectorInfo { - selectorOptions: SharedSelectorOptions; - argumentSelectorFunctions: SelectFromRootState[]; + selectorOptions: ɵSharedSelectorOptions; + argumentSelectorFunctions: ɵSelectFromRootState[]; } diff --git a/packages/store/src/selectors/selector-types.util.ts b/packages/store/src/selectors/selector-types.util.ts index 209307ce1..17a07c7fe 100644 --- a/packages/store/src/selectors/selector-types.util.ts +++ b/packages/store/src/selectors/selector-types.util.ts @@ -1,6 +1,4 @@ -import { ɵStateClass } from '@ngxs/store/internals'; - -import { StateToken } from '../state-token/state-token'; +import { ɵStateClass, StateToken } from '@ngxs/store/internals'; export type SelectorFunc = (...arg: any[]) => TModel; diff --git a/packages/store/src/selectors/selector-utils.ts b/packages/store/src/selectors/selector-utils.ts index 38a6f4af8..d9e8df444 100644 --- a/packages/store/src/selectors/selector-utils.ts +++ b/packages/store/src/selectors/selector-utils.ts @@ -1,21 +1,21 @@ -import { memoize } from '@ngxs/store/internals'; - import { - getSelectorMetadata, - getStoreMetadata, - SelectorMetaDataModel, - SharedSelectorOptions, - RuntimeSelectorContext, - SelectorFactory -} from '../internal/internals'; + ɵmemoize, + ɵRuntimeSelectorContext, + ɵSelectorFactory, + ɵgetStoreMetadata, + ɵgetSelectorMetadata, + ɵSelectorMetaDataModel, + ɵSharedSelectorOptions +} from '@ngxs/store/internals'; + import { CreationMetadata, RuntimeSelectorInfo } from './selector-models'; export function createRootSelectorFactory any>( - selectorMetaData: SelectorMetaDataModel, + selectorMetaData: ɵSelectorMetaDataModel, selectors: any[] | undefined, memoizedSelectorFn: T -): SelectorFactory { - return (context: RuntimeSelectorContext) => { +): ɵSelectorFactory { + return (context: ɵRuntimeSelectorContext) => { const { argumentSelectorFunctions, selectorOptions } = getRuntimeSelectorInfo( context, selectorMetaData, @@ -50,19 +50,19 @@ export function createMemoizedSelectorFn any>( const wrappedFn = function wrappedSelectorFn(...args: any[]) { const returnValue = originalFn.apply(containerClass, args); if (returnValue instanceof Function) { - const innerMemoizedFn = memoize.apply(null, [returnValue]); + const innerMemoizedFn = ɵmemoize.apply(null, [returnValue]); return innerMemoizedFn; } return returnValue; } as T; - const memoizedFn = memoize(wrappedFn); + const memoizedFn = ɵmemoize(wrappedFn); Object.setPrototypeOf(memoizedFn, originalFn); return memoizedFn; } function getRuntimeSelectorInfo( - context: RuntimeSelectorContext, - selectorMetaData: SelectorMetaDataModel, + context: ɵRuntimeSelectorContext, + selectorMetaData: ɵSelectorMetaDataModel, selectors: any[] | undefined = [] ): RuntimeSelectorInfo { const localSelectorOptions = selectorMetaData.getSelectorOptions(); @@ -85,7 +85,7 @@ function getRuntimeSelectorInfo( function getSelectorsToApply( selectors: any[] | undefined = [], - selectorOptions: SharedSelectorOptions, + selectorOptions: ɵSharedSelectorOptions, containerClass: any ) { const selectorsToApply = []; @@ -93,7 +93,7 @@ function getSelectorsToApply( selectors.length === 0 || selectorOptions.injectContainerState; if (containerClass && canInjectContainerState) { // If we are on a state class, add it as the first selector parameter - const metadata = getStoreMetadata(containerClass); + const metadata = ɵgetStoreMetadata(containerClass); if (metadata) { selectorsToApply.push(containerClass); } @@ -108,7 +108,7 @@ function getSelectorsToApply( * This function gets the factory function to create the selector to get the selected slice from the app state * @ignore */ -export function getRootSelectorFactory(selector: any): SelectorFactory { - const metadata = getSelectorMetadata(selector) || getStoreMetadata(selector); +export function getRootSelectorFactory(selector: any): ɵSelectorFactory { + const metadata = ɵgetSelectorMetadata(selector) || ɵgetStoreMetadata(selector); return (metadata && metadata.makeRootSelector) || (() => selector); } diff --git a/packages/store/src/standalone-features/initializers.ts b/packages/store/src/standalone-features/initializers.ts index 75c619d36..e3ddc95c6 100644 --- a/packages/store/src/standalone-features/initializers.ts +++ b/packages/store/src/standalone-features/initializers.ts @@ -1,10 +1,11 @@ import { ENVIRONMENT_INITIALIZER, InjectionToken, Provider, inject } from '@angular/core'; +import { ɵStateClassInternal } from '@ngxs/store/internals'; import { Store } from '../store'; import { InitState, UpdateState } from '../plugin_api'; import { FEATURE_STATE_TOKEN, ROOT_STATE_TOKEN } from '../symbols'; import { StateFactory } from '../internal/state-factory'; -import { StateClassInternal, StatesAndDefaults } from '../internal/internals'; +import { StatesAndDefaults } from '../internal/internals'; import { SelectFactory } from '../decorators/select/select-factory'; import { InternalStateOperations } from '../internal/state-operations'; import { LifecycleStateManager } from '../internal/lifecycle-state-manager'; @@ -53,8 +54,8 @@ export function featureStatesInitializer(): void { // Since FEATURE_STATE_TOKEN is a multi token, we need to // flatten it [[Feature1State, Feature2State], [Feature3State]]. - const flattenedStates: StateClassInternal[] = states.reduce( - (total: StateClassInternal[], values: StateClassInternal[]) => total.concat(values), + const flattenedStates: ɵStateClassInternal[] = states.reduce( + (total: ɵStateClassInternal[], values: ɵStateClassInternal[]) => total.concat(values), [] ); diff --git a/packages/store/src/standalone-features/plugin.ts b/packages/store/src/standalone-features/plugin.ts index 39dce8a26..b11daf9c1 100644 --- a/packages/store/src/standalone-features/plugin.ts +++ b/packages/store/src/standalone-features/plugin.ts @@ -1,6 +1,5 @@ import { EnvironmentProviders, Type, makeEnvironmentProviders } from '@angular/core'; - -import { NGXS_PLUGINS, NgxsPlugin } from '../symbols'; +import { NGXS_PLUGINS, NgxsPlugin } from '@ngxs/store/plugins'; /** * This function registers a custom global plugin for the state. diff --git a/packages/store/src/standalone-features/root-providers.ts b/packages/store/src/standalone-features/root-providers.ts index 112a44165..bcf9f79e3 100644 --- a/packages/store/src/standalone-features/root-providers.ts +++ b/packages/store/src/standalone-features/root-providers.ts @@ -1,9 +1,9 @@ import { APP_BOOTSTRAP_LISTENER, Provider, inject } from '@angular/core'; import { - NgxsBootstrapper, ɵStateClass, ɵNGXS_STATE_CONTEXT_FACTORY, - ɵNGXS_STATE_FACTORY + ɵNGXS_STATE_FACTORY, + ɵNgxsAppBootstrappedState } from '@ngxs/store/internals'; import { PluginManager } from '../plugin-manager'; @@ -31,8 +31,8 @@ export function getRootProviders( { provide: APP_BOOTSTRAP_LISTENER, useFactory: () => { - const bootstrapper = inject(NgxsBootstrapper); - return () => bootstrapper.bootstrap(); + const appBootstrappedState = inject(ɵNgxsAppBootstrappedState); + return () => appBootstrappedState.bootstrap(); }, multi: true }, diff --git a/packages/store/src/state-token/state-token.ts b/packages/store/src/state-token/state-token.ts deleted file mode 100644 index 9f6d47b51..000000000 --- a/packages/store/src/state-token/state-token.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TokenName } from './symbols'; -import { - ensureSelectorMetadata, - RuntimeSelectorContext, - SelectFromRootState -} from '../internal/internals'; - -export class StateToken { - constructor(private readonly name: TokenName) { - const selectorMetadata = ensureSelectorMetadata(this); - selectorMetadata.makeRootSelector = ( - runtimeContext: RuntimeSelectorContext - ): SelectFromRootState => { - return runtimeContext.getStateGetter(this.name); - }; - } - - getName(): string { - return this.name; - } - - toString(): string { - return `StateToken[${this.name}]`; - } -} diff --git a/packages/store/src/state-token/symbols.ts b/packages/store/src/state-token/symbols.ts deleted file mode 100644 index ee11c6924..000000000 --- a/packages/store/src/state-token/symbols.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { StateToken } from './state-token'; - -type RequireGeneric = T extends void ? 'You must provide a type parameter' : U; - -export type TokenName = string & RequireGeneric; - -export type ExtractTokenType

= P extends StateToken ? T : never; diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index f22a6bb39..7944c5f9c 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -2,7 +2,7 @@ import { Inject, Injectable, Optional, Signal, Type, computed } from '@angular/core'; import { Observable, of, Subscription, throwError } from 'rxjs'; import { catchError, distinctUntilChanged, map, shareReplay, take } from 'rxjs/operators'; -import { INITIAL_STATE_TOKEN, PlainObject } from '@ngxs/store/internals'; +import { ɵINITIAL_STATE_TOKEN, ɵPlainObject, StateToken } from '@ngxs/store/internals'; import { InternalNgxsExecutionStrategy } from './execution/internal-ngxs-execution-strategy'; import { InternalStateOperations } from './internal/state-operations'; @@ -10,7 +10,6 @@ import { getRootSelectorFactory } from './selectors/selector-utils'; import { StateStream } from './internal/state-stream'; import { leaveNgxs } from './operators/leave-ngxs'; import { NgxsConfig } from './symbols'; -import { StateToken } from './state-token/state-token'; import { StateFactory } from './internal/state-factory'; import { TypedSelector } from './selectors'; @@ -33,7 +32,7 @@ export class Store { private _internalExecutionStrategy: InternalNgxsExecutionStrategy, private _stateFactory: StateFactory, @Optional() - @Inject(INITIAL_STATE_TOKEN) + @Inject(ɵINITIAL_STATE_TOKEN) initialStateValue: any ) { this.initStateStream(initialStateValue); @@ -133,11 +132,11 @@ export class Store { } private initStateStream(initialStateValue: any): void { - const value: PlainObject = this._stateStream.value; + const value: ɵPlainObject = this._stateStream.value; const storeIsEmpty: boolean = !value || Object.keys(value).length === 0; if (storeIsEmpty) { const defaultStateNotEmpty: boolean = Object.keys(this._config.defaultsState).length > 0; - const storeValues: PlainObject = defaultStateNotEmpty + const storeValues: ɵPlainObject = defaultStateNotEmpty ? { ...this._config.defaultsState, ...initialStateValue } : initialStateValue; diff --git a/packages/store/src/symbols.ts b/packages/store/src/symbols.ts index 830ad2a73..ea790d91f 100644 --- a/packages/store/src/symbols.ts +++ b/packages/store/src/symbols.ts @@ -1,14 +1,11 @@ import { Injectable, InjectionToken, Type, inject } from '@angular/core'; import { Observable } from 'rxjs'; -import { PlainObject, ɵStateClass } from '@ngxs/store/internals'; import { StateOperator } from '@ngxs/store/operators'; +import { ɵPlainObject, ɵSharedSelectorOptions, ɵStateClass } from '@ngxs/store/internals'; -import { mergeDeep } from './utils/utils'; -import { DispatchOutsideZoneNgxsExecutionStrategy } from './execution/dispatch-outside-zone-ngxs-execution-strategy'; import { NgxsExecutionStrategy } from './execution/symbols'; -import { SharedSelectorOptions } from './internal/internals'; -import { StateToken } from './state-token/state-token'; +import { DispatchOutsideZoneNgxsExecutionStrategy } from './execution/dispatch-outside-zone-ngxs-execution-strategy'; const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; @@ -26,10 +23,6 @@ export const FEATURE_STATE_TOKEN = new InjectionToken> NG_DEV_MODE ? 'FEATURE_STATE_TOKEN' : '' ); -// The injection token is used to resolve to custom NGXS plugins provided -// at the root level through either `{provide}` scheme or `withNgxsPlugin`. -export const NGXS_PLUGINS = new InjectionToken(NG_DEV_MODE ? 'NGXS_PLUGINS' : ''); - // The injection token is used to resolve to options provided at the root // level through either `NgxsModule.forRoot` or `provideStore`. export const NGXS_OPTIONS = new InjectionToken( @@ -40,14 +33,23 @@ export type NgxsLifeCycle = Partial & Partial & Partial; -export type NgxsPluginFn = (state: any, mutation: any, next: NgxsNextPluginFn) => any; - /** * The NGXS config settings. */ @Injectable({ providedIn: 'root', - useFactory: () => mergeDeep(new NgxsConfig(), inject(NGXS_OPTIONS)) + useFactory: (): NgxsConfig => { + const defaultConfig = new NgxsConfig(); + const config = inject(NGXS_OPTIONS); + return { + ...defaultConfig, + ...config, + selectorOptions: { + ...defaultConfig.selectorOptions, + ...config.selectorOptions + } + }; + } }) export class NgxsConfig { /** @@ -67,6 +69,8 @@ export class NgxsConfig { * (default: false) */ strictContentSecurityPolicy: boolean; + } = { + strictContentSecurityPolicy: false }; /** * Determines the execution context to perform async operations inside. An implementation can be @@ -79,28 +83,21 @@ export class NgxsConfig { * `NoopNgxsExecutionStrategy` that doesn't interact with zones. * (default: null) */ - executionStrategy: Type; + executionStrategy: Type = DispatchOutsideZoneNgxsExecutionStrategy; /** * Defining the default state before module initialization * This is convenient if we need to create a define our own set of states. * @deprecated will be removed after v4 * (default: {}) */ - defaultsState: PlainObject = {}; + defaultsState: ɵPlainObject = {}; /** * Defining shared selector options */ - selectorOptions: SharedSelectorOptions = { + selectorOptions: ɵSharedSelectorOptions = { injectContainerState: true, // TODO: default is true in v3, will change in v4 suppressErrors: true // TODO: default is true in v3, will change in v4 }; - - constructor() { - this.compatibility = { - strictContentSecurityPolicy: false - }; - this.executionStrategy = DispatchOutsideZoneNgxsExecutionStrategy; - } } export { StateOperator }; @@ -130,38 +127,6 @@ export interface StateContext { dispatch(actions: any | any[]): Observable; } -export type NgxsNextPluginFn = (state: any, mutation: any) => any; - -/** - * Plugin interface - */ -export interface NgxsPlugin { - /** - * Handle the state/action before its submitted to the state handlers. - */ - handle(state: any, action: any, next: NgxsNextPluginFn): any; -} - -/** - * Options that can be provided to the store. - */ -export interface StoreOptions { - /** - * Name of the state. Required. - */ - name: string | StateToken; - - /** - * Default values for the state. If not provided, uses empty object. - */ - defaults?: T; - - /** - * Sub states for the given state. - */ - children?: ɵStateClass[]; -} - /** * Represents a basic change from a previous to a new value for a single state instance. * Passed as a value in a NgxsSimpleChanges object to the ngxsOnChanges hook. diff --git a/packages/store/src/utils/store-validators.ts b/packages/store/src/utils/store-validators.ts index 5874d9bfd..d233932e2 100644 --- a/packages/store/src/utils/store-validators.ts +++ b/packages/store/src/utils/store-validators.ts @@ -1,4 +1,6 @@ -import { getStoreMetadata, StateClassInternal, StatesByName } from '../internal/internals'; +import { ɵStateClassInternal, ɵgetStoreMetadata } from '@ngxs/store/internals'; + +import { StatesByName } from '../internal/internals'; import { throwStateDecoratorError, throwStateNameError, @@ -18,7 +20,7 @@ export function ensureStateNameIsValid(name: string | null): void | never { export function ensureStateNameIsUnique( stateName: string, - state: StateClassInternal, + state: ɵStateClassInternal, statesByName: StatesByName ): void | never { const existingState = statesByName[stateName]; @@ -27,9 +29,9 @@ export function ensureStateNameIsUnique( } } -export function ensureStatesAreDecorated(stateClasses: StateClassInternal[]): void | never { - stateClasses.forEach((stateClass: StateClassInternal) => { - if (!getStoreMetadata(stateClass)) { +export function ensureStatesAreDecorated(stateClasses: ɵStateClassInternal[]): void | never { + stateClasses.forEach((stateClass: ɵStateClassInternal) => { + if (!ɵgetStoreMetadata(stateClass)) { throwStateDecoratorError(stateClass.name); } }); diff --git a/packages/store/tests/actions-stream.spec.ts b/packages/store/tests/actions-stream.spec.ts index f125504d0..7fcde1c4e 100644 --- a/packages/store/tests/actions-stream.spec.ts +++ b/packages/store/tests/actions-stream.spec.ts @@ -1,8 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { NgxsModule, ActionStatus, Actions } from '@ngxs/store'; +import { ɵOrderedSubject } from '@ngxs/store/internals'; import { Subject } from 'rxjs'; -import { OrderedSubject } from '../src/internal/custom-rxjs-subjects'; import { InternalActions } from '../src/actions-stream'; describe('The Actions stream', () => { @@ -34,7 +34,7 @@ describe('The Actions stream', () => { it('should rather use OrderedSubject because it preserves the order of dispatch for subscribers', () => { // Arrange - const statuses$ = new OrderedSubject(); + const statuses$ = new ɵOrderedSubject(); const callsRecorded = []; // Act diff --git a/packages/store/tests/ensure-store.spec.ts b/packages/store/tests/ensure-store.spec.ts index 01b873167..57ca0038f 100644 --- a/packages/store/tests/ensure-store.spec.ts +++ b/packages/store/tests/ensure-store.spec.ts @@ -1,23 +1,19 @@ +import { State, Action, Selector, NgxsModule, SelectorOptions } from '@ngxs/store'; import { - State, - Action, - getStoreMetadata, - getSelectorMetadata, - Selector, - NgxsModule, - SelectorOptions -} from '@ngxs/store'; + ɵSelectorMetaDataModel, + ɵgetSelectorMetadata, + ɵgetStoreMetadata +} from '@ngxs/store/internals'; import { Injectable } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { SelectorMetaDataModel } from '../src/internal/internals'; import { getRootSelectorFactory } from '../src/selectors/selector-utils'; describe('Ensure metadata', () => { it('should return undefined if not a state class', () => { class MyState {} - expect(getStoreMetadata(MyState)).toBeUndefined(); - expect(getSelectorMetadata(MyState)).toBeUndefined(); + expect(ɵgetStoreMetadata(MyState)).toBeUndefined(); + expect(ɵgetSelectorMetadata(MyState)).toBeUndefined(); }); describe('Ensure store for plugins', () => { @@ -63,7 +59,7 @@ describe('Ensure metadata', () => { }); it('should get the meta data from the CountState', () => { - expect(getStoreMetadata(CountState)).toEqual({ + expect(ɵgetStoreMetadata(CountState)).toEqual({ name: 'count', actions: { increment: [ @@ -80,7 +76,7 @@ describe('Ensure metadata', () => { }); it('should get the meta data from the MyCounterState', () => { - expect(getStoreMetadata(MyCounterState)).toEqual({ + expect(ɵgetStoreMetadata(MyCounterState)).toEqual({ name: 'myCounter', actions: { decrement: [{ fn: 'decrement', options: {}, type: 'decrement' }] }, defaults: 1, @@ -91,12 +87,12 @@ describe('Ensure metadata', () => { }); it('should get the selector meta data from the CountState, MyCounterState', () => { - expect(getSelectorMetadata(CountState)).toBeUndefined(); - expect(getSelectorMetadata(MyCounterState)).toBeUndefined(); + expect(ɵgetSelectorMetadata(CountState)).toBeUndefined(); + expect(ɵgetSelectorMetadata(MyCounterState)).toBeUndefined(); }); it('should get the selector meta data from the CountState.selectFn', () => { - const metadata = getSelectorMetadata(CountState.selectFn); + const metadata = <ɵSelectorMetaDataModel>ɵgetSelectorMetadata(CountState.selectFn); expect(metadata.selectorName).toEqual('selectFn'); expect(metadata.containerClass).toEqual(CountState); @@ -109,12 +105,12 @@ describe('Ensure metadata', () => { }); it('should get the selector meta data from the CountState.canInheritSelectFn, MyCounterState.canInheritSelectFn', () => { - const countMetadata = ( - getSelectorMetadata(CountState.canInheritSelectFn) + const countMetadata = <ɵSelectorMetaDataModel>( + ɵgetSelectorMetadata(CountState.canInheritSelectFn) ); - const myCounterMetadata = ( - getSelectorMetadata(MyCounterState.canInheritSelectFn) + const myCounterMetadata = <ɵSelectorMetaDataModel>( + ɵgetSelectorMetadata(MyCounterState.canInheritSelectFn) ); expect(countMetadata.selectorName).toEqual('canInheritSelectFn'); @@ -153,8 +149,8 @@ describe('Ensure metadata', () => { } } - const metadata = ( - getSelectorMetadata(SuperCountState.canInheritSelectFn) + const metadata = <ɵSelectorMetaDataModel>( + ɵgetSelectorMetadata(SuperCountState.canInheritSelectFn) ); expect(metadata.containerClass).toEqual(SuperCountState); diff --git a/packages/store/tests/release-resources.spec.ts b/packages/store/tests/release-resources.spec.ts index 3b31d57d8..fa0f7bdb8 100644 --- a/packages/store/tests/release-resources.spec.ts +++ b/packages/store/tests/release-resources.spec.ts @@ -4,7 +4,6 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { NgxsModule, Store } from '@ngxs/store'; import { freshPlatform } from '@ngxs/store/internals/testing'; -import { NgxsConfig } from '../src/symbols'; import { NoopErrorHandler } from './helpers/utils'; import { SelectFactory } from '../src/decorators/select/select-factory'; @@ -29,7 +28,9 @@ describe('Release NGXS resources', () => { // Assert expect(SelectFactory.store).toBeInstanceOf(Store); - expect(SelectFactory.config).toBeInstanceOf(NgxsConfig); + expect(SelectFactory.config!.compatibility).toEqual({ + strictContentSecurityPolicy: false + }); ngModuleRef.destroy(); diff --git a/packages/store/tests/utils/utils.spec.ts b/packages/store/tests/utils/utils.spec.ts index 9a1b1e336..d953b78bd 100644 --- a/packages/store/tests/utils/utils.spec.ts +++ b/packages/store/tests/utils/utils.spec.ts @@ -1,7 +1,7 @@ -import { PlainObject } from '@ngxs/store/internals'; +import { setValue } from '@ngxs/store/plugins'; +import { ɵPlainObject } from '@ngxs/store/internals'; import { propGetter } from '../../src/internal/internals'; -import { setValue, isObject, mergeDeep } from '../../src/utils/utils'; import { NgxsConfig } from '../../src/symbols'; describe('utils', () => { @@ -58,7 +58,7 @@ describe('utils', () => { describe('propGetter', () => { it('strictContentSecurityPolicy: false', () => { const config: NgxsConfig = new NgxsConfig(); - const target: PlainObject = { a: { b: { c: 100 } } }; + const target: ɵPlainObject = { a: { b: { c: 100 } } }; expect(propGetter(['a', 'b', 'c'], config)(target)).toEqual(100); expect(propGetter(['a', 'b'], config)(target)).toEqual({ c: 100 }); @@ -74,80 +74,10 @@ describe('utils', () => { } }; - const target: PlainObject = { a: { b: { c: 100 } } }; + const target: ɵPlainObject = { a: { b: { c: 100 } } }; expect(propGetter(['a', 'b', 'c'], config)(target)).toEqual(100); expect(propGetter(['a', 'b'], config)(target)).toEqual({ c: 100 }); }); }); - - describe('isObject', () => { - it('should correctly identify objects', () => { - const object = { a: 1 }; - const constructedObject = new Object(1); - const array = [1, 2, 3]; - const string = 'asdasd'; - const number = 12; - - expect(isObject(object)).toBeTruthy(); - expect(isObject(constructedObject)).toBeTruthy(); - expect(isObject(array)).toBeFalsy(); - expect(isObject(string)).toBeFalsy(); - expect(isObject(number)).toBeFalsy(); - - expect(isObject(null)).toBeFalsy(); - expect(isObject(undefined)).toBeFalsy(); - }); - }); - - describe('mergeDeep', () => { - it('should merge properties from two objects', () => { - const base = { a: 1, b: 2 }; - const source = { c: 3 }; - - expect(mergeDeep(base, source)).toEqual({ a: 1, b: 2, c: 3 }); - }); - - it('should merge properties from multiple objects', () => { - const base = { a: 1, b: 2 }; - const sources = [{ c: 3 }, { d: 4 }, { e: 5 }]; - - expect(mergeDeep(base, sources[0], sources[1], sources[2])).toEqual({ - a: 1, - b: 2, - c: 3, - d: 4, - e: 5 - }); - }); - - it('should merge subproperties from two objects ', () => { - const base = { a: 1, b: { x: 2, z: { c: 1 } } }; - const source = { b: { y: 3, z: { d: 2 }, w: { f: 1 } } }; - - expect(mergeDeep(base, source)).toEqual({ - a: 1, - b: { x: 2, y: 3, z: { c: 1, d: 2 }, w: { f: 1 } } - }); - }); - - it('should overwrite properties of base object with source object', () => { - const base = { a: 1, b: 2 }; - const source = { c: 3, b: 4 }; - - expect(mergeDeep(base, source)).toEqual({ a: 1, b: 4, c: 3 }); - }); - - it('should overwrite properties in the correct order', () => { - const base = { a: 1, b: 2 }; - const sources = [{ c: 3, b: 4, d: 6 }, { c: 5 }, { b: 7 }]; - - expect(mergeDeep(base, sources[0], sources[1], sources[2])).toEqual({ - a: 1, - b: 7, - c: 5, - d: 6 - }); - }); - }); }); diff --git a/packages/websocket-plugin/src/websocket-handler.ts b/packages/websocket-plugin/src/websocket-handler.ts index 2491d62a9..9f54c1c69 100644 --- a/packages/websocket-plugin/src/websocket-handler.ts +++ b/packages/websocket-plugin/src/websocket-handler.ts @@ -1,5 +1,6 @@ import { Injectable, Inject, OnDestroy, NgZone } from '@angular/core'; -import { Actions, Store, getValue, ofActionDispatched } from '@ngxs/store'; +import { Actions, Store, ofActionDispatched } from '@ngxs/store'; +import { getValue } from '@ngxs/store/plugins'; import { ReplaySubject, Subject, fromEvent } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 6c2f7a0e8..f1a6b096a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,6 +32,7 @@ "@ngxs/store/internals": ["packages/store/internals/src/index.ts"], "@ngxs/store/internals/testing": ["packages/store/internals/testing/src/index.ts"], "@ngxs/store/operators": ["packages/store/operators/src/index.ts"], + "@ngxs/store/plugins": ["packages/store/plugins/src/index.ts"], "@ngxs/websocket-plugin": ["packages/websocket-plugin/index.ts"] } }