diff --git a/libs/common/src/lib/create-store.ts b/libs/common/src/lib/create-store.ts index 6aa2a9c7..d52ea365 100644 --- a/libs/common/src/lib/create-store.ts +++ b/libs/common/src/lib/create-store.ts @@ -47,7 +47,7 @@ export function createStore(selectableState: { // Listen to the Actions stream and update state actionSubscription = actions$.subscribe((action) => { - const nextState = getReducerManager().reducer(appState.get() ?? {}, action); + const nextState = getReducerManager().reducer(appState.get(), action); appState.set(nextState); }); } @@ -121,7 +121,12 @@ export function createStore(selectableState: { } return { - hasUndoExtension, + set hasUndoExtension(v: boolean) { + hasUndoExtension = v; + }, + get hasUndoExtension(): boolean { + return hasUndoExtension; + }, actions$, dispatch, appState, // TODO select? diff --git a/libs/mini-rx-store/src/lib/extensions/redux-devtools.extension.ts b/libs/mini-rx-store/src/lib/extensions/redux-devtools.extension.ts index 5de74f5b..8e2fffee 100644 --- a/libs/mini-rx-store/src/lib/extensions/redux-devtools.extension.ts +++ b/libs/mini-rx-store/src/lib/extensions/redux-devtools.extension.ts @@ -1,5 +1,5 @@ import { AbstractReduxDevtoolsExtension, Action, AppState } from '@mini-rx/common'; -import { actions$, appState } from '../store-core'; +import { actions$, storeCore } from '../store-core'; import { Observable } from 'rxjs'; export class ReduxDevtoolsExtension extends AbstractReduxDevtoolsExtension { @@ -8,10 +8,10 @@ export class ReduxDevtoolsExtension extends AbstractReduxDevtoolsExtension { } readState(): AppState { - return appState.get()!; + return storeCore.appState.get(); } updateState(state: AppState): void { - appState.set(state); + storeCore.appState.set(state); } } diff --git a/libs/mini-rx-store/src/lib/state.ts b/libs/mini-rx-store/src/lib/state.ts index 06e62f28..94b5063f 100644 --- a/libs/mini-rx-store/src/lib/state.ts +++ b/libs/mini-rx-store/src/lib/state.ts @@ -1,5 +1,4 @@ -import { BehaviorSubject, Observable, pipe } from 'rxjs'; -import { filter, map, distinctUntilChanged } from 'rxjs/operators'; +import { BehaviorSubject, distinctUntilChanged, filter, map, Observable, pipe } from 'rxjs'; function createSelectFn(state$: Observable) { function select(): Observable; diff --git a/libs/mini-rx-store/src/lib/store-core.ts b/libs/mini-rx-store/src/lib/store-core.ts index 59a56b23..6ecc4ae9 100644 --- a/libs/mini-rx-store/src/lib/store-core.ts +++ b/libs/mini-rx-store/src/lib/store-core.ts @@ -1,146 +1,6 @@ -import { Subscription } from 'rxjs'; import { createState } from './state'; -import { - AppState, - createActionsOnQueue, - createMiniRxActionType, - createReducerManager, - createRxEffectForStore, - ExtensionId, - MetaReducer, - miniRxError, - OperationType, - Reducer, - ReducerManager, - sortExtensions, - StoreConfig, - StoreExtension, -} from '@mini-rx/common'; +import { AppState, createRxEffectForStore, createStore } from '@mini-rx/common'; -function createStore() { - let hasUndoExtension = false; - let actionSubscription: Subscription | undefined; - - // REDUCER MANAGER - let reducerManager: ReducerManager | undefined; - - function getReducerManager(): ReducerManager { - if (!reducerManager) { - reducerManager = createReducerManager(); - } - return reducerManager; - } - - // ACTIONS - const {actions$, dispatch} = createActionsOnQueue(); - - // APP STATE - const appState = createState(); - - // Wire up the Redux Store: subscribe to the actions stream, calc next state for every action - // Called by `configureStore` and `addReducer` - function initStore(): void { - if (actionSubscription) { - return; - } - - // Listen to the Actions stream and update state - actionSubscription = actions$.subscribe((action) => { - const nextState: AppState = getReducerManager().reducer(appState.get() ?? {}, action); - appState.set(nextState); - }); - } - - function configureStore(config: StoreConfig = {}) { - initStore(); - - if (getReducerManager().hasFeatureReducers()) { - miniRxError( - '`configureStore` detected reducers. Did you instantiate FeatureStores before calling `configureStore`?' - ); - } - - if (config.metaReducers) { - getReducerManager().addMetaReducers(...config.metaReducers); - } - - if (config.extensions) { - sortExtensions(config.extensions).forEach((extension) => addExtension(extension)); - } - - if (config.reducers) { - getReducerManager().setFeatureReducers(config.reducers); - } - - if (config.initialState) { - appState.set(config.initialState); - } - - dispatch({type: createMiniRxActionType(OperationType.INIT)}); - - return { - dispatch, - feature: addFeature, - effect, - select: appState.select - } - } - - function addFeature( - featureKey: string, - reducer: Reducer, - config: { - metaReducers?: MetaReducer[]; - initialState?: StateType; - } = {} - ): void { - initStore(); - getReducerManager().addFeatureReducer( - featureKey, - reducer, - config.metaReducers, - config.initialState - ); - dispatch({type: createMiniRxActionType(OperationType.INIT, featureKey)}); - } - - function removeFeature(featureKey: string): void { - getReducerManager().removeFeatureReducer(featureKey); - dispatch({type: createMiniRxActionType(OperationType.DESTROY, featureKey)}); - } - - const effect = createRxEffectForStore(dispatch); - - function addExtension(extension: StoreExtension): void { - const metaReducer: MetaReducer | void = extension.init(); - - if (metaReducer) { - getReducerManager().addMetaReducers(metaReducer); - } - - hasUndoExtension = extension.id === ExtensionId.UNDO; - } - - // Used for testing - function destroy(): void { - actionSubscription?.unsubscribe(); - actionSubscription = undefined; - reducerManager = undefined; - } - - return { - hasUndoExtension, - actions$, - dispatch, - appState, - addFeature, - removeFeature, - effect, - addExtension, - configureStore, - destroy - } -} - -export const storeCore = createStore(); -export const actions$ = storeCore.actions$ +export const storeCore = createStore(createState({})); +export const actions$ = storeCore.actions$; +export const rxEffect = createRxEffectForStore(storeCore.dispatch); diff --git a/libs/mini-rx-store/src/lib/store.ts b/libs/mini-rx-store/src/lib/store.ts index 7980d342..e6088000 100644 --- a/libs/mini-rx-store/src/lib/store.ts +++ b/libs/mini-rx-store/src/lib/store.ts @@ -1,7 +1,5 @@ import { Observable } from 'rxjs'; -import { - storeCore -} from './store-core'; +import { rxEffect, storeCore } from './store-core'; import { Action, Reducer, @@ -35,7 +33,7 @@ export function configureStore(config: StoreConfig): Store | never { feature: storeCore.addFeature, select: storeCore.appState.select, dispatch: storeCore.dispatch, - effect: storeCore.effect + effect: rxEffect, }; } miniRxError('`configureStore` was called multiple times.'); diff --git a/libs/signal-store/src/lib/component-store.ts b/libs/signal-store/src/lib/component-store.ts index 9da9718f..942eb5d4 100644 --- a/libs/signal-store/src/lib/component-store.ts +++ b/libs/signal-store/src/lib/component-store.ts @@ -16,7 +16,7 @@ import { undo, UpdateStateCallback, } from '@mini-rx/common'; -import { createSelectableSignalState } from './selectable-signal-state'; +import { createSelectableSignal, createSelectableWritableSignal } from './selectable-signal-state'; import { ComponentStoreLike } from './models'; import { createRxEffectFn } from './rx-effect'; import { createConnectFn } from './connect'; @@ -37,9 +37,9 @@ export class ComponentStore implements ComponentStoreL private actionsOnQueue = createActionsOnQueue(); - private _state: WritableSignal = signal(this.initialState); + private _state = createSelectableWritableSignal(signal(this.initialState)); get state(): StateType { - return this._state(); + return this._state.get(); } private updateState: UpdateStateCallback = ( @@ -78,7 +78,7 @@ export class ComponentStore implements ComponentStoreL setState = createUpdateFn(this.updateState); connect = createConnectFn(this.updateState); rxEffect = createRxEffectFn(); - select = createSelectableSignalState(this._state).select; + select = this._state.select; private destroy(): void { // Dispatch an action really just for logging via LoggerExtension diff --git a/libs/signal-store/src/lib/extensions/redux-devtools.extension.ts b/libs/signal-store/src/lib/extensions/redux-devtools.extension.ts index 554a1dce..57614ad6 100644 --- a/libs/signal-store/src/lib/extensions/redux-devtools.extension.ts +++ b/libs/signal-store/src/lib/extensions/redux-devtools.extension.ts @@ -1,18 +1,17 @@ import { AbstractReduxDevtoolsExtension, Action, AppState } from '@mini-rx/common'; import { Observable } from 'rxjs'; -import { actions$, select, updateAppState } from '../store-core'; +import { storeCore } from '../store-core'; export class ReduxDevtoolsExtension extends AbstractReduxDevtoolsExtension { get actions$(): Observable { - return actions$; + return storeCore.actions$; } readState(): AppState { - const signalState = select(); - return signalState(); + return storeCore.appState.get(); } updateState(state: AppState): void { - updateAppState(state); + storeCore.appState.set(state); } } diff --git a/libs/signal-store/src/lib/feature-store.ts b/libs/signal-store/src/lib/feature-store.ts index 9656cdfc..ade27274 100644 --- a/libs/signal-store/src/lib/feature-store.ts +++ b/libs/signal-store/src/lib/feature-store.ts @@ -13,8 +13,8 @@ import { undo, UpdateStateCallback, } from '@mini-rx/common'; -import { addFeature, dispatch, hasUndoExtension, removeFeature, select } from './store-core'; -import { createSelectableSignalState } from './selectable-signal-state'; +import { storeCore } from './store-core'; +import { createSelectableSignal } from './selectable-signal-state'; import { ComponentStoreLike } from './models'; import { createRxEffectFn } from './rx-effect'; import { createConnectFn } from './connect'; @@ -27,7 +27,9 @@ export class FeatureStore implements ComponentStoreLik return this._featureKey; } - private _state: Signal = select((state) => state[this.featureKey]); + private _state: Signal = storeCore.appState.select( + (state) => state[this.featureKey] + ); get state(): StateType { return this._state(); } @@ -37,7 +39,7 @@ export class FeatureStore implements ComponentStoreLik operationType: OperationType, name: string | undefined ): MiniRxAction => { - return dispatch({ + return storeCore.dispatch({ type: createMiniRxActionType(operationType, this.featureKey, name), stateOrCallback, featureId: this.featureId, @@ -48,7 +50,7 @@ export class FeatureStore implements ComponentStoreLik this.featureId = generateId(); this._featureKey = generateFeatureKey(featureKey, config.multi); - addFeature( + storeCore.addFeature( this._featureKey, createFeatureStoreReducer(this.featureId, initialState) ); @@ -57,18 +59,18 @@ export class FeatureStore implements ComponentStoreLik } undo(action: Action): void { - hasUndoExtension - ? dispatch(undo(action)) + storeCore.hasUndoExtension + ? storeCore.dispatch(undo(action)) : miniRxError('UndoExtension is not initialized.'); } setState = createUpdateFn(this.updateState); connect = createConnectFn(this.updateState); rxEffect = createRxEffectFn(); - select = createSelectableSignalState(this._state).select; + select = createSelectableSignal(this._state).select; private destroy(): void { - removeFeature(this._featureKey); + storeCore.removeFeature(this._featureKey); } } diff --git a/libs/signal-store/src/lib/modules/store.module.ts b/libs/signal-store/src/lib/modules/store.module.ts index 8ba09935..04dbef16 100644 --- a/libs/signal-store/src/lib/modules/store.module.ts +++ b/libs/signal-store/src/lib/modules/store.module.ts @@ -1,7 +1,7 @@ import { inject, ModuleWithProviders, NgModule } from '@angular/core'; import { Actions, AppState, FeatureConfig, Reducer, StoreConfig } from '@mini-rx/common'; import { Store } from '../store'; -import { actions$, addFeature } from '../store-core'; +import { storeCore } from '../store-core'; import { FEATURE_CONFIGS, FEATURE_NAMES, @@ -27,7 +27,7 @@ export class StoreFeatureModule { const configs: FeatureConfig[] = inject(FEATURE_CONFIGS); featureNames.forEach((featureName, index) => { - addFeature(featureName, reducers[index], configs[index]); + storeCore.addFeature(featureName, reducers[index], configs[index]); }); } } @@ -46,7 +46,7 @@ export class StoreModule { }, { provide: Actions, - useValue: actions$, + useValue: storeCore.actions$, }, ], }; diff --git a/libs/signal-store/src/lib/providers.ts b/libs/signal-store/src/lib/providers.ts index 5083ba83..c971f614 100644 --- a/libs/signal-store/src/lib/providers.ts +++ b/libs/signal-store/src/lib/providers.ts @@ -15,7 +15,7 @@ import { Reducer, StoreConfig, } from '@mini-rx/common'; -import { actions$, addFeature, rxEffect } from './store-core'; +import { storeCore, rxEffect } from './store-core'; import { Store } from './store'; import { globalCsConfig } from './component-store'; import { @@ -54,7 +54,7 @@ export function provideStore(config: StoreConfig): EnvironmentProviders { }, { provide: Actions, - useValue: actions$, + useValue: storeCore.actions$, }, { provide: STORE_PROVIDER, useFactory: rootStoreProviderFactory }, { @@ -74,7 +74,7 @@ function featureProviderFactory(): void { const configs = inject(FEATURE_CONFIGS); featureNames.forEach((featureName, index) => { - addFeature(featureName, reducers[index], configs[index]); + storeCore.addFeature(featureName, reducers[index], configs[index]); }); } diff --git a/libs/signal-store/src/lib/selectable-signal-state.ts b/libs/signal-store/src/lib/selectable-signal-state.ts index a250d6b6..b0841952 100644 --- a/libs/signal-store/src/lib/selectable-signal-state.ts +++ b/libs/signal-store/src/lib/selectable-signal-state.ts @@ -1,10 +1,10 @@ -import { computed, Signal } from '@angular/core'; +import { computed, Signal, WritableSignal } from '@angular/core'; import { isSignalSelector, SignalSelector } from './signal-selector'; import { defaultSignalEquality } from './utils'; type StateSelector = (state: T) => R; -export function createSelectableSignalState(state: Signal) { +function createSelectFn(state: Signal) { function select(): Signal; function select(mapFn: SignalSelector): Signal; function select(mapFn: StateSelector): Signal; @@ -25,7 +25,28 @@ export function createSelectableSignalState(state: Sig ); } + return select; +} + +export function createSelectableSignal(state: Signal) { + return { + select: createSelectFn(state), + get: () => { + return state(); + }, + }; +} + +export function createSelectableWritableSignal( + state: WritableSignal +) { return { - select, + select: createSelectFn(state), + get: (): StateType => { + return state(); + }, + set: (v: StateType): void => { + state.set(v); + }, }; } diff --git a/libs/signal-store/src/lib/spec/_spec-helpers.ts b/libs/signal-store/src/lib/spec/_spec-helpers.ts index 5e46f3df..00426855 100644 --- a/libs/signal-store/src/lib/spec/_spec-helpers.ts +++ b/libs/signal-store/src/lib/spec/_spec-helpers.ts @@ -1,9 +1,9 @@ import { Action, ComponentStoreExtension, ExtensionId, MetaReducer } from '@mini-rx/common'; -import { destroy } from '../store-core'; +import { storeCore } from '../store-core'; import { v4 as uuid } from 'uuid'; export function destroyStore() { - destroy(); + storeCore.destroy(); } export interface UserState { diff --git a/libs/signal-store/src/lib/spec/feature-store.spec.ts b/libs/signal-store/src/lib/spec/feature-store.spec.ts index a62f3afb..bc027379 100644 --- a/libs/signal-store/src/lib/spec/feature-store.spec.ts +++ b/libs/signal-store/src/lib/spec/feature-store.spec.ts @@ -6,6 +6,7 @@ import { counterInitialState, counterReducer, CounterState, + destroyStore, userState, UserState, } from './_spec-helpers'; @@ -161,6 +162,9 @@ function setupCounterFeatureStore(): void { } describe('FeatureStore', () => { + beforeEach(() => { + destroyStore(); + }); it('should initialize the feature', () => { setupUserFeatureStore(); const selectedState = userFeatureStore.select(); diff --git a/libs/signal-store/src/lib/spec/store.spec.ts b/libs/signal-store/src/lib/spec/store.spec.ts index dc22da62..05205f1e 100644 --- a/libs/signal-store/src/lib/spec/store.spec.ts +++ b/libs/signal-store/src/lib/spec/store.spec.ts @@ -25,7 +25,7 @@ import { } from './_spec-helpers'; import { TestBed } from '@angular/core/testing'; import { StoreModule } from '../modules/store.module'; -import { addFeature, removeFeature, rxEffect } from '../store-core'; +import { storeCore, rxEffect } from '../store-core'; interface ActionWithPayload extends Action { payload?: any; @@ -460,7 +460,7 @@ describe('Store', () => { const spy = jest.fn(); actions.subscribe(spy); - addFeature('products', (state) => state); + storeCore.addFeature('products', (state) => state); expect(spy).toHaveBeenCalledWith({ type: '@mini-rx/products/init' }); }); @@ -799,11 +799,11 @@ describe('Store', () => { it('should add and remove reducers', () => { const featureKey = 'tempCounter'; - addFeature(featureKey, counterReducer); + storeCore.addFeature(featureKey, counterReducer); const selectedState = store.select((state) => state); expect(selectedState()).toEqual({ tempCounter: counterInitialState }); - removeFeature(featureKey); + storeCore.removeFeature(featureKey); expect(selectedState()).toEqual({}); }); }); diff --git a/libs/signal-store/src/lib/store-core.ts b/libs/signal-store/src/lib/store-core.ts index 2d3802ab..b132a36f 100644 --- a/libs/signal-store/src/lib/store-core.ts +++ b/libs/signal-store/src/lib/store-core.ts @@ -1,119 +1,6 @@ -import { signal, WritableSignal } from '@angular/core'; -import { Subscription } from 'rxjs'; -import { - AppState, - createActionsOnQueue, - createMiniRxActionType, - createReducerManager, - createRxEffectForStore, - ExtensionId, - MetaReducer, - OperationType, - Reducer, - ReducerManager, - sortExtensions, - StoreConfig, - StoreExtension, -} from '@mini-rx/common'; -import { createSelectableSignalState } from './selectable-signal-state'; +import { signal } from '@angular/core'; +import { AppState, createRxEffectForStore, createStore } from '@mini-rx/common'; +import { createSelectableWritableSignal } from './selectable-signal-state'; -export let hasUndoExtension = false; -let actionSubscription: Subscription | undefined; - -// REDUCER MANAGER -let reducerManager: ReducerManager | undefined; -function getReducerManager(): ReducerManager { - if (!reducerManager) { - reducerManager = createReducerManager(); - } - return reducerManager; -} - -// ACTIONS -export const { actions$, dispatch } = createActionsOnQueue(); - -// APP STATE -const appState: WritableSignal = signal({}); -export const { select } = createSelectableSignalState(appState); - -// Wire up the Redux Store: subscribe to the actions stream, calc next state for every action -// Called by `configureStore` and `addReducer` -function initStore(): void { - if (actionSubscription) { - return; - } - - // Listen to the Actions stream and update state - actionSubscription = actions$.subscribe((action) => { - const nextState: AppState = getReducerManager().reducer(appState(), action); - appState.set(nextState); - }); -} - -export function configureStore(config: StoreConfig = {}): void { - initStore(); - - if (config.metaReducers) { - getReducerManager().addMetaReducers(...config.metaReducers); - } - - if (config.extensions) { - sortExtensions(config.extensions).forEach((extension) => addExtension(extension)); - } - - if (config.reducers) { - getReducerManager().setFeatureReducers(config.reducers); - } - - if (config.initialState) { - appState.set(config.initialState); - } - - dispatch({ type: createMiniRxActionType(OperationType.INIT) }); -} - -export function addFeature( - featureKey: string, - reducer: Reducer, - config: { - metaReducers?: MetaReducer[]; - initialState?: StateType; - } = {} -): void { - initStore(); - getReducerManager().addFeatureReducer( - featureKey, - reducer, - config.metaReducers, - config.initialState - ); - dispatch({ type: createMiniRxActionType(OperationType.INIT, featureKey) }); -} - -export function removeFeature(featureKey: string): void { - getReducerManager().removeFeatureReducer(featureKey); - dispatch({ type: createMiniRxActionType(OperationType.DESTROY, featureKey) }); -} - -export const rxEffect = createRxEffectForStore(dispatch); - -function addExtension(extension: StoreExtension): void { - const metaReducer: MetaReducer | void = extension.init(); - - if (metaReducer) { - getReducerManager().addMetaReducers(metaReducer); - } - - hasUndoExtension = extension.id === ExtensionId.UNDO; -} - -export function updateAppState(state: AppState): void { - appState.set(state); -} - -// Used for testing -export function destroy(): void { - actionSubscription?.unsubscribe(); - actionSubscription = undefined; - reducerManager = undefined; -} +export const storeCore = createStore(createSelectableWritableSignal(signal({}))); +export const rxEffect = createRxEffectForStore(storeCore.dispatch); diff --git a/libs/signal-store/src/lib/store.ts b/libs/signal-store/src/lib/store.ts index cde45f84..b0d03ad2 100644 --- a/libs/signal-store/src/lib/store.ts +++ b/libs/signal-store/src/lib/store.ts @@ -1,11 +1,11 @@ import { AppState, StoreConfig } from '@mini-rx/common'; -import { configureStore, dispatch, select } from './store-core'; +import { storeCore } from './store-core'; export class Store { - dispatch = dispatch; - select = select; + dispatch = storeCore.dispatch; + select = storeCore.appState.select; constructor(config: StoreConfig) { - configureStore(config); + storeCore.configureStore(config); } }