ngssm-store 21.0.0 → 21.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,85 +1,497 @@
1
- # State management
1
+ # ngssm-store
2
2
 
3
- This is a simple custom adaptation and implementation of the **redux** pattern.
3
+ A lightweight, production-ready state management library for Angular applications based on the Redux pattern. Provides centralized state management with full support for reducers, effects, and modern Angular signals.
4
4
 
5
- ```mermaid
6
- graph TB;
7
- B["Store (State manager)"]
8
- C[/Actions queue/]
9
- A["State Observers: <br/> <ul> <li>Components</li> <li>Directives</li> <li>Guards</li></ul>"]
10
-
11
- subgraph G[Action processors]
12
- D[<b>Reducers</b> <br/> Updates state synchronously taking state immutability into account]
13
- E[<b>Effects</b> <br/> No update of the state. <br/> Call to remote services <br/> Actions dispatch...]
14
- end
5
+ ## Overview
15
6
 
16
- C --- B
17
- A -- Dispatch actions --> B
18
- B -- Publish state --> A
19
- B -- Apply action on state --> D
20
- B -- Process action --> E
21
- D -- Updated state --> B
22
- E -- Dispatch actions --> B
7
+ `ngssm-store` is a simple yet powerful custom implementation of the Redux pattern designed specifically for Angular. It leverages Angular's dependency injection, RxJS for reactive updates, and modern Angular signals for optimal reactivity.
23
8
 
24
- style A text-align:left
25
- style C fill:lightgray
9
+ ### Key Features
10
+
11
+ - **Centralized State Management**: Single source of truth for application state
12
+ - **Redux Pattern**: Actions → Reducers → State → Effects → Actions
13
+ - **Dual Reactivity**: Both RxJS observables and Angular Signals support
14
+ - **Immutable State**: Uses `immutability-helper` to ensure state immutability
15
+ - **Effect System**: Side effects, async operations, and action chaining
16
+ - **Feature States**: Modular state management with feature-based organization
17
+ - **Action Queue**: Sequential action processing for predictable state updates
18
+ - **Logging & Debugging**: Built-in logging system for monitoring state changes
19
+ - **TypeScript Support**: Fully typed for better developer experience
20
+ - **Dependency Injection**: Leverages Angular's DI system
21
+
22
+ ## Architecture
23
+
24
+ ### Redux Flow
25
+
26
+ ```
27
+ Component/Effect
28
+ ↓ (dispatch)
29
+ Action → Store → Action Queue
30
+
31
+ Process Next Action
32
+
33
+ Apply to Reducers
34
+
35
+ Update State Immutably
36
+
37
+ Publish New State
38
+
39
+ Process Effects
40
+
41
+ (Can dispatch actions)
42
+ ```
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ npm install ngssm-store
26
48
  ```
27
49
 
28
- ## Store Overview
50
+ ### Peer Dependencies
51
+
52
+ - `@angular/core` >= 20.0.0
53
+ - `@angular/common` >= 20.0.0
54
+ - `immutability-helper` >= 3.1.1
55
+
56
+ ## Setup
57
+
58
+ ### Global Provider
29
59
 
30
- ### Action dispatching
60
+ Initialize the store in your application bootstrapping:
31
61
 
32
- ```mermaid
33
- sequenceDiagram
34
- actor O as Component/Effect
35
- participant S as Store
36
- participant Q as Actions Queue
37
- participant E as Event loop
38
- O->>S: dispatch action
39
- S->>Q: add action
40
- S->>E: add message to process next action
62
+ ```typescript
63
+ import { provideNgssmStore } from 'ngssm-store';
64
+
65
+ bootstrapApplication(AppComponent, {
66
+ providers: [
67
+ provideNgssmStore(),
68
+ ]
69
+ });
41
70
  ```
42
71
 
43
- > An action dispatched by a component or an effect is not processed immediately. The store uses the `setTimeout` function to process "later" the action.
72
+ ## Core Concepts
44
73
 
45
- ### Action processing
74
+ ### Actions
46
75
 
47
- ```mermaid
48
- sequenceDiagram
49
- participant L as Event loop
50
- participant S as Store
51
- participant Q as Actions Queue
52
- actor O as State Observer
53
- participant R as Reducer
54
- participant E as Effect
55
- L->>S: doProcessNextAction
56
- S->>Q: get next available action
57
- alt There is an action to process
58
- loop For all registered reducers for the current sction
59
- S->>R: update state
60
- end
76
+ Actions are plain objects that describe what happened. They must have a `type` property:
61
77
 
62
- S->>O: publish new state
78
+ ```typescript
79
+ export interface Action {
80
+ type: string;
81
+ }
63
82
 
64
- loop For all registered effects for the current action
65
- S->>E: process action
66
- end
83
+ // Example action class
84
+ export class IncrementCounterAction implements Action {
85
+ readonly type = 'INCREMENT_COUNTER';
86
+
87
+ constructor(public readonly amount: number = 1) {}
88
+ }
67
89
 
68
- S->>L: add message to process next action
69
- end
90
+ export class LoadUsersAction implements Action {
91
+ readonly type = 'LOAD_USERS';
92
+ }
70
93
  ```
71
94
 
72
- ## Dependencies
95
+ ### Reducers
96
+
97
+ Reducers are pure functions that take the current state and an action, then return a new state. They must be immutable:
98
+
99
+ ```typescript
100
+ import { Reducer, State, Action } from 'ngssm-store';
101
+ import update from 'immutability-helper';
102
+
103
+ @Injectable()
104
+ export class CounterReducer implements Reducer {
105
+ processedActions = ['INCREMENT_COUNTER', 'DECREMENT_COUNTER'];
106
+
107
+ updateState(state: State, action: Action): State {
108
+ switch (action.type) {
109
+ case 'INCREMENT_COUNTER':
110
+ return update(state, {
111
+ counter: {
112
+ value: { $apply: (v: number) => v + (action as IncrementCounterAction).amount }
113
+ }
114
+ });
115
+ case 'DECREMENT_COUNTER':
116
+ return update(state, {
117
+ counter: { value: { $set: state.counter.value - 1 } }
118
+ });
119
+ default:
120
+ return state;
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Effects
127
+
128
+ Effects handle side effects like API calls, logging, and dispatching new actions. They don't modify state:
129
+
130
+ ```typescript
131
+ import { Effect, ActionDispatcher, State, Action } from 'ngssm-store';
132
+
133
+ @Injectable()
134
+ export class UserEffect implements Effect {
135
+ processedActions = ['LOAD_USERS'];
136
+
137
+ private readonly userService = inject(UserService);
138
+
139
+ constructor(private injector: EnvironmentInjector) {}
140
+
141
+ processAction(dispatcher: ActionDispatcher, state: State, action: Action): void {
142
+ if (action.type === 'LOAD_USERS') {
143
+ this.userService.getUsers().subscribe((users) => {
144
+ dispatcher.dispatchAction(new SetUsersAction(users));
145
+ });
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### State
152
+
153
+ The state is a plain object containing all application data. Structure it hierarchically:
154
+
155
+ ```typescript
156
+ export interface State {
157
+ counter?: {
158
+ value: number;
159
+ };
160
+ users?: {
161
+ list: User[];
162
+ loading: boolean;
163
+ error?: Error;
164
+ };
165
+ settings?: {
166
+ theme: string;
167
+ language: string;
168
+ };
169
+ }
170
+ ```
171
+
172
+ ## Usage
173
+
174
+ ### Dispatching Actions
175
+
176
+ ```typescript
177
+ import { Store } from 'ngssm-store';
178
+
179
+ @Component({
180
+ selector: 'app-counter',
181
+ template: `
182
+ <p>Count: {{ count() }}</p>
183
+ <button (click)="increment()">Increment</button>
184
+ <button (click)="decrement()">Decrement</button>
185
+ `
186
+ })
187
+ export class CounterComponent {
188
+ private store = inject(Store);
189
+
190
+ count = createSignal((state) => state.counter?.value ?? 0);
191
+
192
+ increment() {
193
+ this.store.dispatchAction(new IncrementCounterAction(1));
194
+ }
195
+
196
+ decrement() {
197
+ this.store.dispatchAction(new DecrementCounterAction());
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Accessing State with Signals
203
+
204
+ Use the signal-based API for reactive components:
205
+
206
+ ```typescript
207
+ import { createSignal } from 'ngssm-store';
208
+
209
+ @Component({
210
+ selector: 'app-dashboard',
211
+ template: `
212
+ <div>
213
+ <p>Total Users: {{ userCount() }}</p>
214
+ <div *ngIf="loading()">Loading...</div>
215
+ <ul>
216
+ <li *ngFor="let user of users()">{{ user.name }}</li>
217
+ </ul>
218
+ </div>`
219
+ })
220
+ export class DashboardComponent {
221
+ private store = inject(Store);
222
+
223
+ users = createSignal((state) => state.users?.list ?? []);
224
+ loading = createSignal((state) => state.users?.loading ?? false);
225
+ userCount = createSignal((state) => (state.users?.list ?? []).length);
226
+ }
227
+ ```
228
+
229
+ ### Accessing State with RxJS
230
+
231
+ For components that need RxJS integration:
232
+
233
+ ```typescript
234
+ @Component({
235
+ selector: 'app-user-list',
236
+ template: `
237
+ <ul>
238
+ <li *ngFor="let user of users$ | async">{{ user.name }}</li>
239
+ </ul>`
240
+ })
241
+ export class UserListComponent {
242
+ private store = inject(Store);
243
+
244
+ users$ = this.store.state$.pipe(
245
+ map((state) => state.users?.list ?? [])
246
+ );
247
+ }
248
+ ```
249
+
250
+ ### Direct State Access
251
+
252
+ Access the current state directly:
253
+
254
+ ```typescript
255
+ @Component(...)
256
+ export class MyComponent {
257
+ private store = inject(Store);
258
+
259
+ getCurrentState() {
260
+ const currentState = this.store.state(); // Signal access
261
+ // or
262
+ const viaObservable = this.store.state$; // Observable access
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Tracking Processed Actions
268
+
269
+ Monitor which action was last processed:
270
+
271
+ ```typescript
272
+ @Component(...)
273
+ export class MyComponent {
274
+ private store = inject(Store);
275
+
276
+ lastAction = createSignal((state) => this.store.processedAction().type);
277
+
278
+ // Or with RxJS
279
+ lastAction$ = this.store.processedAction$.pipe(map(a => a.type));
280
+ }
281
+ ```
282
+
283
+ ## Registering Reducers and Effects
73
284
 
74
- - [angular](https://github.com/angular/angular): the library uses the dependency injection system provided by **angular**,
75
- - [rxjs](https://rxjs.dev/): the publish/subscribe pattern is implemented with **rxjs**,
76
- - [immutability-helper](https://github.com/kolodny/immutability-helper): used by the reducers to update safely the state
285
+ ### Single Reducer/Effect
77
286
 
78
- > The state must be immutable. But, to simplify the implementation, it is the responsibility of the user to be sure that the state instance is never updated.
287
+ ```typescript
288
+ import { provideReducer, provideEffect } from 'ngssm-store';
289
+
290
+ bootstrapApplication(AppComponent, {
291
+ providers: [
292
+ provideNgssmStore(),
293
+ provideReducer(CounterReducer),
294
+ provideEffect(UserEffect)
295
+ ]
296
+ });
297
+ ```
298
+
299
+ ### Multiple Reducers/Effects
300
+
301
+ ```typescript
302
+ import { provideReducers, provideEffects } from 'ngssm-store';
303
+
304
+ bootstrapApplication(AppComponent, {
305
+ providers: [
306
+ provideNgssmStore(),
307
+ provideReducers(
308
+ CounterReducer,
309
+ UserReducer,
310
+ SettingsReducer
311
+ ),
312
+ provideEffects(
313
+ UserEffect,
314
+ NotificationEffect
315
+ )
316
+ ]
317
+ });
318
+ ```
319
+
320
+ ### Effect Functions
321
+
322
+ Effect functions are the modern replacement for the `Effect` interface. They are executed in an injection context, allowing you to use Angular's `inject` function for dependency injection:
323
+
324
+ ```typescript
325
+ import { provideEffectFunc } from 'ngssm-store';
326
+ import { inject } from '@angular/core';
327
+
328
+ bootstrapApplication(AppComponent, {
329
+ providers: [
330
+ provideNgssmStore(),
331
+ provideEffectFunc('LOAD_USERS', (state, action) => {
332
+ const userService = inject(UserService);
333
+ const dispatcher = inject(ACTION_DISPATCHER);
334
+
335
+ userService.getUsers().subscribe((users) => {
336
+ dispatcher.dispatchAction(new SetUsersAction(users));
337
+ });
338
+ })
339
+ ]
340
+ });
341
+ ```
342
+
343
+ Effect functions should be preferred over the legacy `Effect` interface implementation as they are more concise and leverage Angular's dependency injection system.
344
+
345
+ ## Feature States
346
+
347
+ Organize state by feature for better modularity:
348
+
349
+ ```typescript
350
+ import { NgSsmFeatureState } from 'ngssm-store';
351
+
352
+ @NgSsmFeatureState({
353
+ featureStateKey: 'products',
354
+ initialState: {
355
+ list: [],
356
+ loading: false,
357
+ selectedId: null
358
+ }
359
+ })
360
+ export class ProductFeatureState {}
361
+
362
+ // Access in components
363
+ products = createSignal((state) => state.products?.list ?? []);
364
+ ```
365
+
366
+ ## State Initializers
367
+
368
+ Initialize state with data from external sources:
369
+
370
+ ```typescript
371
+ import { StateInitializer } from 'ngssm-store';
372
+
373
+ @Injectable()
374
+ export class AppInitializer implements StateInitializer {
375
+ private configService = inject(ConfigService);
376
+
377
+ initializeState(state: State): State {
378
+ const config = this.configService.getConfig();
379
+ return update(state, {
380
+ settings: { $set: config }
381
+ });
382
+ }
383
+ }
384
+
385
+ // Provide it
386
+ bootstrapApplication(AppComponent, {
387
+ providers: [
388
+ provideNgssmStore(),
389
+ { provide: NGSSM_STATE_INITIALIZER, useClass: AppInitializer }
390
+ ]
391
+ });
392
+ ```
393
+
394
+ ## Action Processing
395
+
396
+ ### Sequential Processing
397
+
398
+ Actions are processed sequentially using an action queue:
399
+
400
+ 1. Action dispatched
401
+ 2. Added to queue
402
+ 3. Store schedules processing (via setTimeout by default)
403
+ 4. Reducers update state
404
+ 5. State published to all subscribers
405
+ 6. Effects process action (can dispatch new actions)
406
+ 7. Next action processed
407
+
408
+ ### Macro Tasks vs Micro Tasks
409
+
410
+ By default, the store uses `setTimeout` (macro-tasks) for action processing. You can switch to micro-tasks (Promises) if needed:
411
+
412
+ ```typescript
413
+ @Injectable()
414
+ export class Store {
415
+ // Set to false for micro-tasks (Promise.resolve())
416
+ public useMacroTasks = true;
417
+ }
418
+ ```
419
+
420
+ ## Best Practices
421
+
422
+ 1. **Keep Actions Simple**: Actions should be serializable plain objects
423
+ 2. **Pure Reducers**: Never mutate state directly; always use `immutability-helper`
424
+ 3. **Avoid Side Effects in Reducers**: Use Effects for side effects
425
+ 4. **Type Your State**: Define clear State interfaces
426
+ 5. **Use Signals for Performance**: Prefer `createSignal` over RxJS when possible in new code
427
+ 6. **Single Responsibility**: One reducer per feature/domain
428
+ 7. **Logging**: Use the Logger service for debugging
429
+ 8. **Action Names**: Use clear, descriptive action type names (FEATURE_ACTION_NAME pattern)
430
+ 9. **Immutability**: Never modify state objects in reducers
431
+ 10. **Error Handling**: Handle errors in effects and dispatch error actions
432
+
433
+ ## API Reference
434
+
435
+ ### Store Class
436
+
437
+ - `state(): Signal<State>` - Get current state as a Signal
438
+ - `state$: Observable<State>` - Get state as an Observable
439
+ - `processedAction(): Signal<Action>` - Get last processed action as a Signal
440
+ - `processedAction$: Observable<Action>` - Get last processed action as Observable
441
+ - `dispatchAction(action: Action): void` - Dispatch an action
442
+ - `dispatchActionType(actionType: string): void` - Dispatch by action type string
443
+
444
+ ### Helper Functions
445
+
446
+ - `createSignal<T>(selector: (state: State) => T): Signal<T>` - Create a derived signal from state
447
+ - `provideNgssmStore()` - Initialize the store
448
+ - `provideReducer(reducer)` - Register a single reducer
449
+ - `provideReducers(...reducers)` - Register multiple reducers
450
+ - `provideEffect(effect)` - Register a single effect
451
+ - `provideEffects(...effects)` - Register multiple effects
452
+ - `provideEffectFunc(actionType, func)` - Register an effect function
453
+
454
+ ### Interfaces
455
+
456
+ - `Action` - Action interface with `type` property
457
+ - `Reducer` - Reducer interface with `processedActions` and `updateState()`
458
+ - `Effect` - Effect interface with `processedActions` and `processAction()`
459
+ - `State` - Base state type (empty object by default)
460
+ - `StateInitializer` - Interface for initializing state
461
+ - `ActionDispatcher` - Interface for dispatching actions
462
+
463
+ ## Debugging
464
+
465
+ ### Logging
466
+
467
+ Enable logging to monitor state changes and action processing:
468
+
469
+ ```typescript
470
+ import { Logger } from 'ngssm-store';
471
+
472
+ constructor(private logger: Logger) {
473
+ this.logger.information('Component initialized');
474
+ }
475
+ ```
476
+
477
+ ### DevTools Integration
478
+
479
+ The store can be integrated with Redux DevTools for advanced debugging (requires additional setup).
480
+
481
+ ## Performance Considerations
482
+
483
+ - **Signals**: Prefer signals over observables for better performance in modern Angular
484
+ - **Selectors**: Use `createSignal` with specific selectors to minimize re-renders
485
+ - **Memoization**: Consider memoizing expensive selector functions
486
+ - **Action Batching**: Dispatch related actions together to reduce re-renders
487
+
488
+ ## Dependencies
79
489
 
80
- - [schematics](https://www.npmjs.com/package/@angular-devkit/schematics): schematics are provided to create feature state, components, reducers, effects, actions...
81
- - [mermaid.js](https://mermaid-js.github.io/mermaid/#/) for the documentation.
490
+ - **Angular Core**: Dependency injection, signals, lifecycle management
491
+ - **RxJS**: Reactive state and action streams
492
+ - **immutability-helper**: Safe state immutability patterns
493
+ - **TypeScript**: Full type safety
82
494
 
83
- ## Schematics
495
+ ## License
84
496
 
85
- See [ngssm-schematics](/projects/ngssm-schematics/README.md) for schematics used to create feature state, action, reducer, effect...
497
+ MIT
@@ -10,9 +10,7 @@ import { TestBed } from '@angular/core/testing';
10
10
  * Provides methods to apply a SetCachedItemAction, set the status, or set the value of a cached item.
11
11
  */
12
12
  class NgssmCachedItemSetter {
13
- constructor() {
14
- this.store = inject(Store);
15
- }
13
+ store = inject(Store);
16
14
  /**
17
15
  * Applies a SetCachedItemAction to the StoreMock, updating the cache state.
18
16
  * @param action The SetCachedItemAction to apply.
@@ -95,10 +93,10 @@ class NgssmCachedItemSetter {
95
93
  }
96
94
  return this;
97
95
  }
98
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgssmCachedItemSetter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
99
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgssmCachedItemSetter }); }
96
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NgssmCachedItemSetter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
97
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NgssmCachedItemSetter });
100
98
  }
101
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgssmCachedItemSetter, decorators: [{
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NgssmCachedItemSetter, decorators: [{
102
100
  type: Injectable
103
101
  }] });
104
102
  const ngssmCachedItemSetter = () => TestBed.inject(NgssmCachedItemSetter);
@@ -1 +1 @@
1
- {"version":3,"file":"ngssm-store-caching-testing.mjs","sources":["../../../projects/ngssm-store/caching/testing/src/ngssm-cached-item-setter.ts","../../../projects/ngssm-store/caching/testing/src/provide-ngssm-caching-testing.ts","../../../projects/ngssm-store/caching/testing/ngssm-store-caching-testing.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\n\nimport { Store } from 'ngssm-store';\nimport { CachedItemStatus, selectNgssmCachedItem, SetCachedItemAction, updateNgssmCachingState } from 'ngssm-store/caching';\nimport { StoreMock } from 'ngssm-store/testing';\n\n/**\n * Utility service for setting and updating cached items in the StoreMock during tests.\n * Provides methods to apply a SetCachedItemAction, set the status, or set the value of a cached item.\n */\n@Injectable()\nexport class NgssmCachedItemSetter {\n public readonly store = inject(Store) as unknown as StoreMock;\n\n /**\n * Applies a SetCachedItemAction to the StoreMock, updating the cache state.\n * @param action The SetCachedItemAction to apply.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public apply<T = unknown>(action: SetCachedItemAction<T>): NgssmCachedItemSetter {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [action.cachedItemKey]: {\n $set: {\n status: action.status,\n item: action.cachedItem,\n error: action.error\n }\n }\n }\n });\n\n return this;\n }\n\n /**\n * Sets the status of a cached item in the StoreMock.\n * If the key does not exist in cache, it is created with an undefined value.\n *\n * @param cachedItemKey The key of the cached item.\n * @param status The new status to set.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public setCachedItemStatus(cachedItemKey: string, status: CachedItemStatus): NgssmCachedItemSetter {\n if (selectNgssmCachedItem(this.store.stateValue, cachedItemKey)) {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n status: { $set: status }\n }\n }\n });\n } else {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n $set: {\n status\n }\n }\n }\n });\n }\n\n return this;\n }\n\n /**\n * Sets the value of a cached item in the StoreMock.\n * If the key does not exist in cache, it is created with the status CachedItemStatus.notSet.\n *\n * @param cachedItemKey The key of the cached item.\n * @param value The value to set.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public setCachedItemValue<T>(cachedItemKey: string, value?: T): NgssmCachedItemSetter {\n if (selectNgssmCachedItem(this.store.stateValue, cachedItemKey)) {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n item: { $set: value }\n }\n }\n });\n } else {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n $set: {\n status: CachedItemStatus.notSet,\n item: value\n }\n }\n }\n });\n }\n\n return this;\n }\n}\n\nexport const ngssmCachedItemSetter = () => TestBed.inject(NgssmCachedItemSetter);\n","import { EnvironmentProviders, inject, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\n\nimport { Logger, Store } from 'ngssm-store';\nimport { StoreMock } from 'ngssm-store/testing';\nimport { NgssmCachingStateSpecification } from 'ngssm-store/caching';\n\nimport { NgssmCachedItemSetter } from './ngssm-cached-item-setter';\n\n/**\n * App initializer that sets up the NgssmCaching state in the StoreMock for testing purposes.\n * Throws an error if StoreMock is not registered.\n */\nexport const ngssmCachingStateInitializer = () => {\n const logger = inject(Logger);\n logger.information('[ngssm-caching-testing] Initialization of state');\n const store = inject(Store);\n if (!(store instanceof StoreMock)) {\n throw new Error('StoreMock is not registered.');\n }\n\n store.stateValue = {\n ...store.stateValue,\n [NgssmCachingStateSpecification.featureStateKey]: NgssmCachingStateSpecification.initialState\n };\n};\n\n/**\n * Provides environment providers for NgssmCaching testing, including the state initializer.\n */\nexport const provideNgssmCachingTesting = (): EnvironmentProviders => {\n return makeEnvironmentProviders([provideAppInitializer(ngssmCachingStateInitializer), NgssmCachedItemSetter]);\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngssm-store-caching-testing.mjs","sources":["../../../projects/ngssm-store/caching/testing/src/ngssm-cached-item-setter.ts","../../../projects/ngssm-store/caching/testing/src/provide-ngssm-caching-testing.ts","../../../projects/ngssm-store/caching/testing/ngssm-store-caching-testing.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\n\nimport { Store } from 'ngssm-store';\nimport { CachedItemStatus, selectNgssmCachedItem, SetCachedItemAction, updateNgssmCachingState } from 'ngssm-store/caching';\nimport { StoreMock } from 'ngssm-store/testing';\n\n/**\n * Utility service for setting and updating cached items in the StoreMock during tests.\n * Provides methods to apply a SetCachedItemAction, set the status, or set the value of a cached item.\n */\n@Injectable()\nexport class NgssmCachedItemSetter {\n public readonly store = inject(Store) as unknown as StoreMock;\n\n /**\n * Applies a SetCachedItemAction to the StoreMock, updating the cache state.\n * @param action The SetCachedItemAction to apply.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public apply<T = unknown>(action: SetCachedItemAction<T>): NgssmCachedItemSetter {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [action.cachedItemKey]: {\n $set: {\n status: action.status,\n item: action.cachedItem,\n error: action.error\n }\n }\n }\n });\n\n return this;\n }\n\n /**\n * Sets the status of a cached item in the StoreMock.\n * If the key does not exist in cache, it is created with an undefined value.\n *\n * @param cachedItemKey The key of the cached item.\n * @param status The new status to set.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public setCachedItemStatus(cachedItemKey: string, status: CachedItemStatus): NgssmCachedItemSetter {\n if (selectNgssmCachedItem(this.store.stateValue, cachedItemKey)) {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n status: { $set: status }\n }\n }\n });\n } else {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n $set: {\n status\n }\n }\n }\n });\n }\n\n return this;\n }\n\n /**\n * Sets the value of a cached item in the StoreMock.\n * If the key does not exist in cache, it is created with the status CachedItemStatus.notSet.\n *\n * @param cachedItemKey The key of the cached item.\n * @param value The value to set.\n * @returns The NgssmCachedItemSetter instance for chaining.\n */\n public setCachedItemValue<T>(cachedItemKey: string, value?: T): NgssmCachedItemSetter {\n if (selectNgssmCachedItem(this.store.stateValue, cachedItemKey)) {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n item: { $set: value }\n }\n }\n });\n } else {\n this.store.stateValue = updateNgssmCachingState(this.store.stateValue, {\n caches: {\n [cachedItemKey]: {\n $set: {\n status: CachedItemStatus.notSet,\n item: value\n }\n }\n }\n });\n }\n\n return this;\n }\n}\n\nexport const ngssmCachedItemSetter = () => TestBed.inject(NgssmCachedItemSetter);\n","import { EnvironmentProviders, inject, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\n\nimport { Logger, Store } from 'ngssm-store';\nimport { StoreMock } from 'ngssm-store/testing';\nimport { NgssmCachingStateSpecification } from 'ngssm-store/caching';\n\nimport { NgssmCachedItemSetter } from './ngssm-cached-item-setter';\n\n/**\n * App initializer that sets up the NgssmCaching state in the StoreMock for testing purposes.\n * Throws an error if StoreMock is not registered.\n */\nexport const ngssmCachingStateInitializer = () => {\n const logger = inject(Logger);\n logger.information('[ngssm-caching-testing] Initialization of state');\n const store = inject(Store);\n if (!(store instanceof StoreMock)) {\n throw new Error('StoreMock is not registered.');\n }\n\n store.stateValue = {\n ...store.stateValue,\n [NgssmCachingStateSpecification.featureStateKey]: NgssmCachingStateSpecification.initialState\n };\n};\n\n/**\n * Provides environment providers for NgssmCaching testing, including the state initializer.\n */\nexport const provideNgssmCachingTesting = (): EnvironmentProviders => {\n return makeEnvironmentProviders([provideAppInitializer(ngssmCachingStateInitializer), NgssmCachedItemSetter]);\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":"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"}
@@ -33,19 +33,24 @@ var CachedItemStatus;
33
33
  * This action is dispatched to update the cache with a new or modified item, along with its status and any error information.
34
34
  */
35
35
  class SetCachedItemAction {
36
+ cachedItemKey;
37
+ cachedItem;
38
+ status;
39
+ error;
40
+ type = NgssmCachingActionType.setCachedItem;
36
41
  constructor(cachedItemKey, cachedItem, status = CachedItemStatus.set, error) {
37
42
  this.cachedItemKey = cachedItemKey;
38
43
  this.cachedItem = cachedItem;
39
44
  this.status = status;
40
45
  this.error = error;
41
- this.type = NgssmCachingActionType.setCachedItem;
42
46
  }
43
47
  }
44
48
 
45
49
  class UnsetCachedItemAction {
50
+ cachedItemKey;
51
+ type = NgssmCachingActionType.unsetCachedItem;
46
52
  constructor(cachedItemKey) {
47
53
  this.cachedItemKey = cachedItemKey;
48
- this.type = NgssmCachingActionType.unsetCachedItem;
49
54
  }
50
55
  }
51
56
 
@@ -54,10 +59,10 @@ const updateNgssmCachingState = (state, command) => update(state, {
54
59
  [NgssmCachingStateSpecification.featureStateKey]: command
55
60
  });
56
61
  let NgssmCachingStateSpecification = class NgssmCachingStateSpecification {
57
- static { this.featureStateKey = 'ngssm-caching-state'; }
58
- static { this.initialState = {
62
+ static featureStateKey = 'ngssm-caching-state';
63
+ static initialState = {
59
64
  caches: {}
60
- }; }
65
+ };
61
66
  };
62
67
  NgssmCachingStateSpecification = __decorate([
63
68
  NgSsmFeatureState({
@@ -69,9 +74,7 @@ NgssmCachingStateSpecification = __decorate([
69
74
  const selectNgssmCachedItem = (state, key) => selectNgssmCachingState(state).caches[key];
70
75
 
71
76
  class CachedItemReducer {
72
- constructor() {
73
- this.processedActions = [NgssmCachingActionType.setCachedItem, NgssmCachingActionType.unsetCachedItem];
74
- }
77
+ processedActions = [NgssmCachingActionType.setCachedItem, NgssmCachingActionType.unsetCachedItem];
75
78
  updateState(state, action) {
76
79
  switch (action.type) {
77
80
  case NgssmCachingActionType.setCachedItem: {
@@ -100,10 +103,10 @@ class CachedItemReducer {
100
103
  }
101
104
  return state;
102
105
  }
103
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CachedItemReducer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
104
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CachedItemReducer }); }
106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CachedItemReducer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
107
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CachedItemReducer });
105
108
  }
106
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CachedItemReducer, decorators: [{
109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CachedItemReducer, decorators: [{
107
110
  type: Injectable
108
111
  }] });
109
112