@angular-architects/ngrx-toolkit 20.0.1 → 20.0.2

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.
Files changed (84) hide show
  1. package/eslint.config.cjs +43 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +37 -0
  6. package/redux-connector/docs/README.md +131 -0
  7. package/redux-connector/index.ts +6 -0
  8. package/redux-connector/ng-package.json +5 -0
  9. package/redux-connector/src/lib/create-redux.ts +102 -0
  10. package/redux-connector/src/lib/model.ts +89 -0
  11. package/redux-connector/src/lib/rxjs-interop/redux-method.ts +66 -0
  12. package/redux-connector/src/lib/signal-redux-store.ts +59 -0
  13. package/redux-connector/src/lib/util.ts +22 -0
  14. package/src/index.ts +43 -0
  15. package/src/lib/assertions/assertions.ts +9 -0
  16. package/src/lib/devtools/features/with-disabled-name-indicies.ts +31 -0
  17. package/src/lib/devtools/features/with-glitch-tracking.ts +35 -0
  18. package/src/lib/devtools/features/with-mapper.ts +34 -0
  19. package/src/lib/devtools/internal/current-action-names.ts +1 -0
  20. package/src/lib/devtools/internal/default-tracker.ts +60 -0
  21. package/src/lib/devtools/internal/devtools-feature.ts +37 -0
  22. package/src/lib/devtools/internal/devtools-syncer.service.ts +202 -0
  23. package/src/lib/devtools/internal/glitch-tracker.service.ts +61 -0
  24. package/src/lib/devtools/internal/models.ts +29 -0
  25. package/src/lib/devtools/provide-devtools-config.ts +32 -0
  26. package/src/lib/devtools/rename-devtools-name.ts +21 -0
  27. package/src/lib/devtools/tests/action-name.spec.ts +48 -0
  28. package/src/lib/devtools/tests/basic.spec.ts +111 -0
  29. package/src/lib/devtools/tests/connecting.spec.ts +37 -0
  30. package/src/lib/devtools/tests/helpers.spec.ts +43 -0
  31. package/src/lib/devtools/tests/naming.spec.ts +216 -0
  32. package/src/lib/devtools/tests/provide-devtools-config.spec.ts +25 -0
  33. package/src/lib/devtools/tests/types.spec.ts +19 -0
  34. package/src/lib/devtools/tests/update-state.spec.ts +29 -0
  35. package/src/lib/devtools/tests/with-devtools.spec.ts +5 -0
  36. package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +272 -0
  37. package/src/lib/devtools/tests/with-mapper.spec.ts +69 -0
  38. package/src/lib/devtools/update-state.ts +38 -0
  39. package/src/lib/devtools/with-dev-tools-stub.ts +6 -0
  40. package/src/lib/devtools/with-devtools.ts +81 -0
  41. package/src/lib/immutable-state/deep-freeze.ts +43 -0
  42. package/src/lib/immutable-state/is-dev-mode.ts +6 -0
  43. package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +278 -0
  44. package/src/lib/immutable-state/with-immutable-state.ts +150 -0
  45. package/src/lib/shared/prettify.ts +3 -0
  46. package/src/lib/shared/signal-store-models.ts +30 -0
  47. package/src/lib/shared/throw-if-null.ts +7 -0
  48. package/src/lib/storage-sync/features/with-indexed-db.ts +81 -0
  49. package/src/lib/storage-sync/features/with-local-storage.ts +58 -0
  50. package/src/lib/storage-sync/internal/indexeddb.service.ts +124 -0
  51. package/src/lib/storage-sync/internal/local-storage.service.ts +19 -0
  52. package/src/lib/storage-sync/internal/models.ts +62 -0
  53. package/src/lib/storage-sync/internal/session-storage.service.ts +18 -0
  54. package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +99 -0
  55. package/src/lib/storage-sync/tests/with-storage-async.spec.ts +305 -0
  56. package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +273 -0
  57. package/src/lib/storage-sync/with-storage-sync.ts +236 -0
  58. package/src/lib/with-call-state.spec.ts +42 -0
  59. package/src/lib/with-call-state.ts +195 -0
  60. package/src/lib/with-conditional.spec.ts +125 -0
  61. package/src/lib/with-conditional.ts +74 -0
  62. package/src/lib/with-data-service.spec.ts +564 -0
  63. package/src/lib/with-data-service.ts +433 -0
  64. package/src/lib/with-feature-factory.spec.ts +69 -0
  65. package/src/lib/with-feature-factory.ts +56 -0
  66. package/src/lib/with-pagination.spec.ts +135 -0
  67. package/src/lib/with-pagination.ts +373 -0
  68. package/src/lib/with-redux.spec.ts +258 -0
  69. package/src/lib/with-redux.ts +387 -0
  70. package/src/lib/with-reset.spec.ts +112 -0
  71. package/src/lib/with-reset.ts +62 -0
  72. package/src/lib/with-undo-redo.spec.ts +274 -0
  73. package/src/lib/with-undo-redo.ts +200 -0
  74. package/src/test-setup.ts +6 -0
  75. package/tsconfig.json +29 -0
  76. package/tsconfig.lib.json +17 -0
  77. package/tsconfig.lib.prod.json +9 -0
  78. package/tsconfig.spec.json +17 -0
  79. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +0 -119
  80. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +0 -1
  81. package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -1789
  82. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  83. package/index.d.ts +0 -949
  84. package/redux-connector/index.d.ts +0 -59
@@ -0,0 +1,43 @@
1
+ const nx = require('@nx/eslint-plugin');
2
+ const baseConfig = require('../../eslint.config.cjs');
3
+
4
+ module.exports = [
5
+ ...baseConfig,
6
+ {
7
+ files: ['**/*.json'],
8
+ rules: {
9
+ '@nx/dependency-checks': [
10
+ 'error',
11
+ {
12
+ ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'],
13
+ },
14
+ ],
15
+ },
16
+ languageOptions: {
17
+ parser: require('jsonc-eslint-parser'),
18
+ },
19
+ },
20
+ ...nx.configs['flat/angular'],
21
+ ...nx.configs['flat/angular-template'],
22
+ {
23
+ files: ['**/*.ts'],
24
+ rules: {
25
+ '@angular-eslint/directive-selector': [
26
+ 'error',
27
+ {
28
+ type: 'attribute',
29
+ prefix: 'lib',
30
+ style: 'camelCase',
31
+ },
32
+ ],
33
+ '@angular-eslint/component-selector': [
34
+ 'error',
35
+ {
36
+ type: 'element',
37
+ prefix: 'lib',
38
+ style: 'kebab-case',
39
+ },
40
+ ],
41
+ },
42
+ }
43
+ ];
package/jest.config.ts ADDED
@@ -0,0 +1,22 @@
1
+ export default {
2
+ displayName: 'ngrx-toolkit',
3
+ setupFiles: ['core-js'],
4
+ preset: '../../jest.preset.js',
5
+ setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
6
+ coverageDirectory: '../../coverage/libs/ngrx-toolkit',
7
+ transform: {
8
+ '^.+\\.(ts|mjs|js|html)$': [
9
+ 'jest-preset-angular',
10
+ {
11
+ tsconfig: '<rootDir>/tsconfig.spec.json',
12
+ stringifyContentPathRegex: '\\.(html|svg)$',
13
+ },
14
+ ],
15
+ },
16
+ transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
17
+ snapshotSerializers: [
18
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
19
+ 'jest-preset-angular/build/serializers/ng-snapshot',
20
+ 'jest-preset-angular/build/serializers/html-comment',
21
+ ],
22
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/libs/ngrx-toolkit",
4
+ "lib": {
5
+ "entryFile": "src/index.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-architects/ngrx-toolkit",
3
- "version": "20.0.1",
3
+ "version": "20.0.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "GitHub",
@@ -18,23 +18,6 @@
18
18
  "optional": true
19
19
  }
20
20
  },
21
- "dependencies": {
22
- "tslib": "^2.3.0"
23
- },
24
- "sideEffects": false,
25
- "module": "fesm2022/angular-architects-ngrx-toolkit.mjs",
26
- "typings": "index.d.ts",
27
- "exports": {
28
- "./package.json": {
29
- "default": "./package.json"
30
- },
31
- ".": {
32
- "types": "./index.d.ts",
33
- "default": "./fesm2022/angular-architects-ngrx-toolkit.mjs"
34
- },
35
- "./redux-connector": {
36
- "types": "./redux-connector/index.d.ts",
37
- "default": "./fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs"
38
- }
39
- }
40
- }
21
+ "dependencies": {},
22
+ "sideEffects": false
23
+ }
package/project.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "ngrx-toolkit",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/ngrx-toolkit/src",
5
+ "prefix": "ngrx-toolkit",
6
+ "projectType": "library",
7
+ "tags": [],
8
+ "targets": {
9
+ "build": {
10
+ "executor": "@nx/angular:package",
11
+ "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
12
+ "options": {
13
+ "project": "libs/ngrx-toolkit/ng-package.json"
14
+ },
15
+ "configurations": {
16
+ "production": {
17
+ "tsConfig": "libs/ngrx-toolkit/tsconfig.lib.prod.json"
18
+ },
19
+ "development": {
20
+ "tsConfig": "libs/ngrx-toolkit/tsconfig.lib.json"
21
+ }
22
+ },
23
+ "defaultConfiguration": "production"
24
+ },
25
+ "test": {
26
+ "executor": "@nx/jest:jest",
27
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
28
+ "options": {
29
+ "jestConfig": "libs/ngrx-toolkit/jest.config.ts"
30
+ }
31
+ },
32
+ "lint": {
33
+ "executor": "@nx/eslint:lint",
34
+ "outputs": ["{options.outputFile}"]
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,131 @@
1
+ # Redux Connector for the NgRx Signal Store `createReduxState()`
2
+
3
+ The Redux Connector turns any `signalStore()` into a Global State Management Slice following the Redux pattern. It is available as secondary entry point, i.e. `import { createReduxState } from '@angular-architects/ngrx-toolkit/redux-connector'` and has a dependency to `@ngrx/store`.
4
+
5
+ It supports:
6
+
7
+ ✅ Well-known NgRx Store Actions \
8
+ ✅ Global Action `dispatch()` \
9
+ ✅ Angular Lazy Loading \
10
+ ✅ Auto-generated `provideNamedStore()` & `injectNamedStore()` Functions \
11
+ ✅ Global Action to Store Method Mappers \
12
+
13
+ - [Redux Connector for the NgRx Signal Store `createReduxState()`](#redux-connector-for-the-ngrx-signal-store-createreduxstate)
14
+ - [Use a present Signal Store](#use-a-present-signal-store)
15
+ - [Use well-known NgRx Store Actions](#use-well-known-ngrx-store-actions)
16
+ - [Map Actions to Methods](#map-actions-to-methods)
17
+ - [Register an Angular Dependency Injection Provider](#register-an-angular-dependency-injection-provider)
18
+ - [Use the Store in your Component](#use-the-store-in-your-component)
19
+
20
+ ## Use a present Signal Store
21
+
22
+ ```typescript
23
+ export const FlightStore = signalStore(
24
+ // State
25
+ withEntities({ entity: type<Flight>(), collection: 'flight' }),
26
+ withEntities({ entity: type<number>(), collection: 'hide' }),
27
+ // Selectors
28
+ withComputed(({ flightEntities, hideEntities }) => ({
29
+ filteredFlights: computed(() => flightEntities().filter((flight) => !hideEntities().includes(flight.id))),
30
+ flightCount: computed(() => flightEntities().length),
31
+ })),
32
+ // Updater
33
+ withMethods((store) => ({
34
+ setFlights: (state: { flights: Flight[] }) => patchState(store, setAllEntities(state.flights, { collection: 'flight' })),
35
+ updateFlight: (state: { flight: Flight }) => patchState(store, updateEntity({ id: state.flight.id, changes: state.flight }, { collection: 'flight' })),
36
+ clearFlights: () => patchState(store, removeAllEntities({ collection: 'flight' })),
37
+ })),
38
+ // Effects
39
+ withMethods((store, flightService = inject(FlightService)) => ({
40
+ loadFlights: reduxMethod<FlightFilter, { flights: Flight[] }>(
41
+ pipe(
42
+ switchMap((filter) => from(flightService.load({ from: filter.from, to: filter.to }))),
43
+ map((flights) => ({ flights })),
44
+ ),
45
+ store.setFlights,
46
+ ),
47
+ })),
48
+ );
49
+ ```
50
+
51
+ ## Use well-known NgRx Store Actions
52
+
53
+ ```typescript
54
+ export const ticketActions = createActionGroup({
55
+ source: 'tickets',
56
+ events: {
57
+ 'flights load': props<FlightFilter>(),
58
+ 'flights loaded': props<{ flights: Flight[] }>(),
59
+ 'flights loaded by passenger': props<{ flights: Flight[] }>(),
60
+ 'flight update': props<{ flight: Flight }>(),
61
+ 'flights clear': emptyProps(),
62
+ },
63
+ });
64
+ ```
65
+
66
+ ## Map Actions to Methods
67
+
68
+ ```typescript
69
+ export const { provideFlightStore, injectFlightStore } = createReduxState('flight', FlightStore, (store) =>
70
+ withActionMappers(
71
+ mapAction(
72
+ // Filtered Action
73
+ ticketActions.flightsLoad,
74
+ // Side-Effect
75
+ store.loadFlights,
76
+ // Result Action
77
+ ticketActions.flightsLoaded,
78
+ ),
79
+ mapAction(
80
+ // Filtered Actions
81
+ ticketActions.flightsLoaded,
82
+ ticketActions.flightsLoadedByPassenger,
83
+ // State Updater Method (like Reducers)
84
+ store.setFlights,
85
+ ),
86
+ mapAction(ticketActions.flightUpdate, store.updateFlight),
87
+ mapAction(ticketActions.flightsClear, store.clearFlights),
88
+ ),
89
+ );
90
+ ```
91
+
92
+ ## Register an Angular Dependency Injection Provider
93
+
94
+ ```typescript
95
+ export const appRoutes: Route[] = [
96
+ {
97
+ path: 'flight-search-redux-connector',
98
+ providers: [provideFlightStore()],
99
+ component: FlightSearchReducConnectorComponent,
100
+ },
101
+ ];
102
+ ```
103
+
104
+ ## Use the Store in your Component
105
+
106
+ ```typescript
107
+ @Component({
108
+ standalone: true,
109
+ imports: [JsonPipe, RouterLink, FormsModule, FlightCardComponent],
110
+ selector: 'demo-flight-search-redux-connector',
111
+ templateUrl: './flight-search.component.html',
112
+ })
113
+ export class FlightSearchReducConnectorComponent {
114
+ private store = injectFlightStore();
115
+
116
+ protected flights = this.store.flightEntities;
117
+
118
+ protected search() {
119
+ this.store.dispatch(
120
+ ticketActions.flightsLoad({
121
+ from: this.localState.filter.from(),
122
+ to: this.localState.filter.to(),
123
+ }),
124
+ );
125
+ }
126
+
127
+ protected reset(): void {
128
+ this.store.dispatch(ticketActions.flightsClear());
129
+ }
130
+ }
131
+ ```
@@ -0,0 +1,6 @@
1
+ export {
2
+ createReduxState,
3
+ mapAction,
4
+ withActionMappers,
5
+ } from './src/lib/create-redux';
6
+ export { reduxMethod } from './src/lib/rxjs-interop/redux-method';
@@ -0,0 +1,5 @@
1
+ {
2
+ "lib": {
3
+ "entryFile": "index.ts"
4
+ }
5
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ inject,
3
+ makeEnvironmentProviders,
4
+ provideEnvironmentInitializer,
5
+ } from '@angular/core';
6
+ import { ActionCreator, ActionType } from '@ngrx/store/src/models';
7
+ import {
8
+ CreateReduxState,
9
+ ExtractActionTypes,
10
+ MapperTypes,
11
+ ServiceWithDecorator,
12
+ Store,
13
+ } from './model';
14
+ import { SignalReduxStore, injectReduxDispatch } from './signal-redux-store';
15
+ import { capitalize, isActionCreator } from './util';
16
+
17
+ export function mapAction<Creators extends readonly ActionCreator[]>(
18
+ ...args: [
19
+ ...creators: Creators,
20
+ storeMethod: (action: ActionType<Creators[number]>) => unknown,
21
+ ]
22
+ ): MapperTypes<Creators>;
23
+ export function mapAction<Creators extends readonly ActionCreator[], T>(
24
+ ...args: [
25
+ ...creators: Creators,
26
+ storeMethod: (
27
+ action: ActionType<Creators[number]>,
28
+ resultMethod: (input: T) => unknown,
29
+ ) => unknown,
30
+ resultMethod: (input: T) => unknown,
31
+ ]
32
+ ): MapperTypes<Creators>;
33
+ export function mapAction<Creators extends readonly ActionCreator[]>(
34
+ ...args: [
35
+ ...creators: Creators,
36
+ storeMethod: (action: ActionType<Creators[number]>) => unknown,
37
+ resultMethod?: (input: unknown) => unknown,
38
+ ]
39
+ ): MapperTypes<Creators> {
40
+ let resultMethod = args.pop() as unknown as
41
+ | ((input: unknown) => unknown)
42
+ | undefined;
43
+ let storeMethod = args.pop() as unknown as (
44
+ action: ActionType<Creators[number]>,
45
+ ) => unknown;
46
+
47
+ if (isActionCreator(storeMethod)) {
48
+ args.push(storeMethod);
49
+ storeMethod = resultMethod || storeMethod;
50
+ resultMethod = undefined;
51
+ }
52
+
53
+ const types = (args as unknown as Creators).map(
54
+ (creator) => creator.type,
55
+ ) as unknown as ExtractActionTypes<Creators>;
56
+
57
+ return {
58
+ types,
59
+ storeMethod,
60
+ resultMethod,
61
+ };
62
+ }
63
+
64
+ export function withActionMappers(
65
+ ...mappers: MapperTypes<ActionCreator[]>[]
66
+ ): MapperTypes<ActionCreator[]>[] {
67
+ return mappers;
68
+ }
69
+
70
+ export function createReduxState<StoreName extends string, STORE extends Store>(
71
+ storeName: StoreName,
72
+ signalStore: STORE,
73
+ withActionMappers: (
74
+ store: InstanceType<STORE>,
75
+ ) => MapperTypes<ActionCreator[]>[],
76
+ ): CreateReduxState<StoreName, STORE> {
77
+ const isRootProvider =
78
+ (signalStore as ServiceWithDecorator)?.ɵprov?.providedIn === 'root';
79
+ return {
80
+ [`provide${capitalize(storeName)}Store`]: (connectReduxDevtools = false) =>
81
+ makeEnvironmentProviders([
82
+ isRootProvider ? [] : signalStore,
83
+ provideEnvironmentInitializer(() => {
84
+ const initializerFn = (
85
+ (
86
+ signalReduxStore = inject(SignalReduxStore),
87
+ store = inject(signalStore),
88
+ ) =>
89
+ () => {
90
+ if (connectReduxDevtools) {
91
+ // addStoreToReduxDevtools(store, storeName, false);
92
+ }
93
+ signalReduxStore.connectFeatureStore(withActionMappers(store));
94
+ }
95
+ )();
96
+ return initializerFn();
97
+ }),
98
+ ]),
99
+ [`inject${capitalize(storeName)}Store`]: () =>
100
+ Object.assign(inject(signalStore), { dispatch: injectReduxDispatch() }),
101
+ } as CreateReduxState<StoreName, STORE>;
102
+ }
@@ -0,0 +1,89 @@
1
+ import { EnvironmentProviders, Signal, Type } from '@angular/core';
2
+ import { DeepSignal } from '@ngrx/signals/src/deep-signal';
3
+ import {
4
+ SignalStoreFeatureResult,
5
+ StateSignals,
6
+ } from '@ngrx/signals/src/signal-store-models';
7
+ import {
8
+ Action,
9
+ ActionCreator,
10
+ ActionType,
11
+ Prettify,
12
+ } from '@ngrx/store/src/models';
13
+ import { Observable, Unsubscribable } from 'rxjs';
14
+
15
+ export type IncludePropType<
16
+ T,
17
+ V,
18
+ WithNevers = {
19
+ [K in keyof T]: Exclude<T[K], undefined> extends V
20
+ ? T[K] extends Record<string, unknown>
21
+ ? IncludePropType<T[K], V>
22
+ : T[K]
23
+ : never;
24
+ },
25
+ > = Prettify<
26
+ Pick<
27
+ WithNevers,
28
+ {
29
+ [K in keyof WithNevers]: WithNevers[K] extends never
30
+ ? never
31
+ : K extends string
32
+ ? K
33
+ : never;
34
+ }[keyof WithNevers]
35
+ >
36
+ >;
37
+
38
+ export type Store = Type<
39
+ Record<string, unknown> & StateSignals<SignalStoreFeatureResult['state']>
40
+ >;
41
+
42
+ export type CreateReduxState<StoreName extends string, STORE extends Store> = {
43
+ [K in StoreName as `provide${Capitalize<K>}Store`]: (
44
+ connectReduxDevtools?: boolean,
45
+ ) => EnvironmentProviders;
46
+ } & {
47
+ [K in StoreName as `inject${Capitalize<K>}Store`]: () => InjectableReduxSlice<STORE>;
48
+ };
49
+
50
+ export type Selectors<STORE extends Store> = IncludePropType<
51
+ InstanceType<STORE>,
52
+ Signal<unknown> | DeepSignal<unknown>
53
+ >;
54
+ export type Dispatch = {
55
+ dispatch: (
56
+ input: Action | Observable<Action> | Signal<Action>,
57
+ ) => Unsubscribable;
58
+ };
59
+ export type InjectableReduxSlice<STORE extends Store> = Selectors<STORE> &
60
+ Dispatch;
61
+
62
+ export type ExtractActionTypes<Creators extends readonly ActionCreator[]> = {
63
+ [Key in keyof Creators]: Creators[Key] extends ActionCreator<infer T>
64
+ ? T
65
+ : never;
66
+ };
67
+
68
+ export interface ActionMethod<T, V extends Action = Action> {
69
+ (action: V): T;
70
+ }
71
+
72
+ export interface StoreMethod<
73
+ Creators extends readonly ActionCreator[],
74
+ ResultState = unknown,
75
+ > {
76
+ (action: ActionType<Creators[number]>): ResultState;
77
+ }
78
+
79
+ export interface MapperTypes<Creators extends readonly ActionCreator[]> {
80
+ types: ExtractActionTypes<Creators>;
81
+ storeMethod: StoreMethod<Creators>;
82
+ resultMethod?: (...args: unknown[]) => unknown;
83
+ }
84
+
85
+ export type ServiceWithDecorator = {
86
+ ɵprov?: {
87
+ providedIn?: string;
88
+ };
89
+ };
@@ -0,0 +1,66 @@
1
+ import { Injector, Signal, inject } from '@angular/core';
2
+ import { rxMethod } from '@ngrx/signals/rxjs-interop';
3
+ import { Observable, Unsubscribable, map, pipe } from 'rxjs';
4
+
5
+ type RxMethodInput<Input> = Input | Observable<Input> | Signal<Input>;
6
+
7
+ type RxMethodRef = {
8
+ destroy: () => void;
9
+ };
10
+
11
+ type RxMethod<Input, MethodInput = Input, MethodResult = unknown> = ((
12
+ input: RxMethodInput<Input>,
13
+ resultMethod: (input: MethodInput) => MethodResult,
14
+ ) => RxMethodRef) &
15
+ RxMethodRef;
16
+
17
+ export function reduxMethod<Input, MethodInput = Input>(
18
+ generator: (source$: Observable<Input>) => Observable<MethodInput>,
19
+ config?: { injector?: Injector },
20
+ ): RxMethod<Input, MethodInput>;
21
+ export function reduxMethod<Input, MethodInput = Input, MethodResult = unknown>(
22
+ generator: (source$: Observable<Input>) => Observable<MethodInput>,
23
+ resultMethod: (input: MethodInput) => MethodResult,
24
+ config?: {
25
+ injector?: Injector;
26
+ },
27
+ ): RxMethod<Input, MethodInput, MethodResult>;
28
+ export function reduxMethod<Input, MethodInput = Input, MethodResult = unknown>(
29
+ generator: (source$: Observable<Input>) => Observable<MethodInput>,
30
+ resultMethodOrConfig?:
31
+ | ((input: MethodInput) => MethodResult)
32
+ | {
33
+ injector?: Injector;
34
+ },
35
+ config?: {
36
+ injector?: Injector;
37
+ },
38
+ ): RxMethod<Input, MethodInput, MethodResult> {
39
+ const injector = inject(Injector);
40
+
41
+ if (typeof resultMethodOrConfig === 'function') {
42
+ let unsubscribable: Unsubscribable;
43
+ const inputResultFn = ((
44
+ input: RxMethodInput<Input>,
45
+ resultMethod = resultMethodOrConfig,
46
+ ) => {
47
+ const rxMethodWithResult = rxMethod<Input>(
48
+ pipe(generator, map(resultMethod)),
49
+ {
50
+ ...(config || {}),
51
+ injector: config?.injector || injector,
52
+ },
53
+ );
54
+ const rxWithInput = rxMethodWithResult(input);
55
+ unsubscribable = { unsubscribe: rxWithInput.destroy.bind(rxWithInput) };
56
+
57
+ return rxWithInput;
58
+ }) as RxMethod<Input, MethodInput, MethodResult>;
59
+
60
+ inputResultFn.destroy = () => unsubscribable?.unsubscribe();
61
+
62
+ return inputResultFn;
63
+ }
64
+
65
+ return rxMethod<Input>(generator, resultMethodOrConfig);
66
+ }
@@ -0,0 +1,59 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { rxMethod } from '@ngrx/signals/rxjs-interop';
3
+ import { Action, ActionCreator } from '@ngrx/store';
4
+ import { pipe, tap } from 'rxjs';
5
+ import { MapperTypes } from './model';
6
+ import { isUnsubscribable } from './util';
7
+
8
+ @Injectable({
9
+ providedIn: 'root',
10
+ })
11
+ export class SignalReduxStore {
12
+ private mapperDict: Record<
13
+ string,
14
+ {
15
+ storeMethod: (...args: unknown[]) => unknown;
16
+ resultMethod?: (...args: unknown[]) => unknown;
17
+ }
18
+ > = {};
19
+
20
+ dispatch = rxMethod<Action>(
21
+ pipe(
22
+ tap((action: Action) => {
23
+ const callbacks = this.mapperDict[action.type];
24
+ if (callbacks?.storeMethod) {
25
+ if (
26
+ isUnsubscribable(callbacks.storeMethod) &&
27
+ callbacks.resultMethod
28
+ ) {
29
+ return callbacks.storeMethod(action, (a: Action) => {
30
+ const resultAction = callbacks.resultMethod?.(a) as Action;
31
+ this.dispatch(resultAction);
32
+ });
33
+ }
34
+
35
+ return callbacks?.storeMethod(action);
36
+ }
37
+
38
+ return;
39
+ }),
40
+ ),
41
+ );
42
+
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ connectFeatureStore(mappers: MapperTypes<ActionCreator<any, any>[]>[]): void {
45
+ mappers.forEach((mapper) =>
46
+ mapper.types.forEach(
47
+ (action) =>
48
+ (this.mapperDict[action] = {
49
+ storeMethod: mapper.storeMethod,
50
+ resultMethod: mapper.resultMethod,
51
+ }),
52
+ ),
53
+ );
54
+ }
55
+ }
56
+
57
+ export function injectReduxDispatch() {
58
+ return inject(SignalReduxStore).dispatch;
59
+ }
@@ -0,0 +1,22 @@
1
+ import { ActionCreator } from '@ngrx/store';
2
+ import { Unsubscribable } from 'rxjs';
3
+
4
+ export function isUnsubscribable<F extends (...args: unknown[]) => unknown>(
5
+ fn: F | (F & Unsubscribable),
6
+ ): fn is F & Unsubscribable {
7
+ return !!(fn as F & Unsubscribable)?.unsubscribe;
8
+ }
9
+
10
+ export function capitalize(str: string): string {
11
+ return str ? str[0].toUpperCase() + str.substring(1) : str;
12
+ }
13
+
14
+ export function isActionCreator(action: unknown): action is ActionCreator {
15
+ return Boolean(
16
+ typeof action === 'function' &&
17
+ action &&
18
+ 'type' in action &&
19
+ action.type &&
20
+ typeof action.type === 'string',
21
+ );
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ export { withDisabledNameIndices } from './lib/devtools/features/with-disabled-name-indicies';
2
+ export { withGlitchTracking } from './lib/devtools/features/with-glitch-tracking';
3
+ export { withMapper } from './lib/devtools/features/with-mapper';
4
+ export {
5
+ ReduxDevtoolsConfig,
6
+ provideDevtoolsConfig,
7
+ } from './lib/devtools/provide-devtools-config';
8
+ export { renameDevtoolsName } from './lib/devtools/rename-devtools-name';
9
+ export { patchState, updateState } from './lib/devtools/update-state';
10
+ export { withDevToolsStub } from './lib/devtools/with-dev-tools-stub';
11
+ export { withDevtools } from './lib/devtools/with-devtools';
12
+
13
+ export {
14
+ createEffects,
15
+ createReducer,
16
+ noPayload,
17
+ payload,
18
+ withRedux,
19
+ } from './lib/with-redux';
20
+
21
+ export * from './lib/with-call-state';
22
+ export * from './lib/with-data-service';
23
+ export * from './lib/with-pagination';
24
+ export { setResetState, withReset } from './lib/with-reset';
25
+ export * from './lib/with-undo-redo';
26
+
27
+ export { withImmutableState } from './lib/immutable-state/with-immutable-state';
28
+ export { withIndexedDB } from './lib/storage-sync/features/with-indexed-db';
29
+
30
+ /**
31
+ * @deprecated Use {@link withIndexedDB} instead.
32
+ */
33
+ export { withIndexedDB as withIndexeddb } from './lib/storage-sync/features/with-indexed-db';
34
+ export {
35
+ withLocalStorage,
36
+ withSessionStorage,
37
+ } from './lib/storage-sync/features/with-local-storage';
38
+ export {
39
+ SyncConfig,
40
+ withStorageSync,
41
+ } from './lib/storage-sync/with-storage-sync';
42
+ export { emptyFeature, withConditional } from './lib/with-conditional';
43
+ export { withFeatureFactory } from './lib/with-feature-factory';
@@ -0,0 +1,9 @@
1
+ import { ActionsFnSpecs } from '../with-redux';
2
+
3
+ export function assertActionFnSpecs(
4
+ obj: unknown,
5
+ ): asserts obj is ActionsFnSpecs {
6
+ if (!obj || typeof obj !== 'object') {
7
+ throw new Error('%o is not an Action Specification');
8
+ }
9
+ }