redux-sparse-kit 0.0.0-stage → 0.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.
@@ -0,0 +1,212 @@
1
+ import { Action, Reducer, StoreEnhancer } from "redux";
2
+ //#region src/segment.d.ts
3
+ /**
4
+ * The state that is always present in the store, outside of any segment.
5
+ * Register it once so that segment epics can read it:
6
+ *
7
+ * ```ts
8
+ * declare module 'redux-sparse-kit' {
9
+ * interface StaticState extends RootStaticState {}
10
+ * }
11
+ * ```
12
+ */
13
+ interface StaticState {}
14
+ /**
15
+ * What an idle segment (one no longer held by anything) does until it is
16
+ * evicted:
17
+ * - `keep-running` - its reducer and epics keep running, so its state stays
18
+ * current.
19
+ * - `pause-epics` - its reducer keeps running, its epics are stopped and
20
+ * started again when the segment is held again.
21
+ * - `freeze` - its epics are stopped and its reducer is skipped, so its state
22
+ * is kept exactly as it was.
23
+ */
24
+ type WhileIdle = "keep-running" | "pause-epics" | "freeze";
25
+ /**
26
+ * An epic as far as the core is concerned: a function the epic runner starts.
27
+ * The `redux-sparse-kit/observable` entry point runs redux-observable epics.
28
+ */
29
+ type SegmentEpic = (...args: any[]) => unknown;
30
+ /**
31
+ * A reducer, the epics that drive it and the segments it depends on, injected
32
+ * together and evicted together. Its `State` is `never` when it has no
33
+ * reducer: it then owns no slice of the state.
34
+ */
35
+ interface Segment<Key extends string = string, State = any, Deps extends readonly AnySegment[] = readonly AnySegment[]> {
36
+ /**
37
+ * Names the segment, and is the top-level key of the state its reducer
38
+ * owns when it has one.
39
+ */
40
+ readonly key: Key;
41
+ readonly reducer: unknown extends State ? Reducer<any> | undefined : [State] extends [never] ? undefined : Reducer<State>;
42
+ /** Segments that must be present for as long as this one is. */
43
+ readonly deps: Deps;
44
+ readonly epics: readonly SegmentEpic[];
45
+ /** Overrides the store's idle timeout for this segment, in milliseconds. */
46
+ readonly idleTimeout: number | undefined;
47
+ /** Overrides the store's idle behaviour for this segment. */
48
+ readonly whileIdle: WhileIdle | undefined;
49
+ }
50
+ /**
51
+ * Any segment, with or without a reducer. Spelled out rather than written as
52
+ * `Segment<string, any>`, which a segment without a reducer would not match.
53
+ */
54
+ interface AnySegment {
55
+ readonly key: string;
56
+ readonly reducer: Reducer<any> | undefined;
57
+ readonly deps: readonly AnySegment[];
58
+ readonly epics: readonly SegmentEpic[];
59
+ readonly idleTimeout: number | undefined;
60
+ readonly whileIdle: WhileIdle | undefined;
61
+ }
62
+ type SliceKeyOf<S> = S extends Segment<infer Key, infer State, any> ? ([State] extends [never] ? never : Key) : never;
63
+ /**
64
+ * The state slice each of the given segments contributes to the store;
65
+ * segments without a reducer contribute none.
66
+ */
67
+ type StateOfSegments<Segments extends AnySegment> = { [S in Segments as SliceKeyOf<S>]: S extends Segment<string, infer State, any> ? State : never; };
68
+ type OwnSliceOf<Key extends string, State> = [State] extends [never] ? {} : { [K in Key]: State; };
69
+ /**
70
+ * The state a segment may read: the static state, its own slice and the
71
+ * slices of its direct dependencies.
72
+ */
73
+ type ScopeStateOf<S extends AnySegment> = StaticState & StateOfSegments<S | S["deps"][number]>;
74
+ type EpicStateOf<Epic> = Epic extends ((action$: any, state$: infer State$, ...rest: any[]) => any) ? State$ extends {
75
+ readonly value: infer State;
76
+ } ? State : unknown : unknown;
77
+ declare const segmentEpicError: unique symbol;
78
+ /**
79
+ * Shown when an epic declares a state the segment cannot guarantee: it reads
80
+ * a segment that is neither the segment itself nor one of its dependencies.
81
+ */
82
+ interface SegmentEpicStateError<Readable, Declared> {
83
+ readonly [segmentEpicError]: "The epic reads state outside the segment and its deps";
84
+ readonly readable: Readable;
85
+ readonly declared: Declared;
86
+ }
87
+ /** An epic typed with a `void`, `unknown` or `any` state does not read it. */
88
+ type ReadsState<State> = [State] extends [void] ? false : unknown extends State ? false : true;
89
+ type ValidateEpics<Epics extends readonly unknown[], Scope> = { [I in keyof Epics]: ReadsState<EpicStateOf<Epics[I]>> extends false ? unknown : Scope extends EpicStateOf<Epics[I]> ? unknown : SegmentEpicStateError<Scope, EpicStateOf<Epics[I]>>; };
90
+ interface SegmentOptions<Key extends string, State, Deps extends readonly AnySegment[], Epics extends readonly SegmentEpic[]> {
91
+ key: Key;
92
+ /** Omitted for a segment that only runs epics or only bundles its deps. */
93
+ reducer?: Reducer<State>;
94
+ deps?: Deps;
95
+ epics?: Epics & ValidateEpics<Epics, StaticState & StateOfSegments<Deps[number]> & OwnSliceOf<Key, State>>;
96
+ idleTimeout?: number;
97
+ whileIdle?: WhileIdle;
98
+ }
99
+ /**
100
+ * Whether a segment with a reducer has been defined under this key. Lets dev
101
+ * checks tell a read of a segment's slice from a read of static state.
102
+ */
103
+ declare const isSegmentKey: (key: string) => boolean;
104
+ /**
105
+ * Defines a segment: a reducer, the epics that drive it and the segments it
106
+ * depends on, injected together and evicted together. Each part is optional.
107
+ *
108
+ * A dependency has to be defined before the segment that lists it, so the
109
+ * dependency graph cannot contain a cycle.
110
+ */
111
+ declare function defineSegment<const Key extends string, State = never, const Deps extends readonly AnySegment[] = [], const Epics extends readonly SegmentEpic[] = []>(options: SegmentOptions<Key, State, Deps, Epics>): Segment<Key, State, Deps>;
112
+ //#endregion
113
+ //#region src/sparseEnhancer.d.ts
114
+ /**
115
+ * Actions the sparse store dispatches. Reducers outside the segments can
116
+ * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an
117
+ * evicted segment.
118
+ */
119
+ declare const SparseActionTypes: {
120
+ /** Segment slices with the keys in `payload.keys` were added to the state. */
121
+ readonly INJECT: "@@sparse/INJECT";
122
+ /** Segment slices with the keys in `payload.keys` are removed by this action. */
123
+ readonly EVICT: "@@sparse/EVICT";
124
+ };
125
+ interface SparseAction extends Action<(typeof SparseActionTypes)[keyof typeof SparseActionTypes]> {
126
+ payload: {
127
+ keys: readonly string[];
128
+ };
129
+ }
130
+ type SparseEvent = {
131
+ type: "inject";
132
+ keys: readonly string[];
133
+ } | {
134
+ type: "evict";
135
+ keys: readonly string[];
136
+ } | {
137
+ type: "idle";
138
+ key: string;
139
+ } | {
140
+ type: "revive";
141
+ key: string;
142
+ };
143
+ interface SparseStats {
144
+ /** Keys of present segments that something holds. */
145
+ active: string[];
146
+ /** Keys of present segments waiting for their idle timeout. */
147
+ idle: string[];
148
+ }
149
+ interface SparseController {
150
+ /**
151
+ * Holds the given segments, injecting the ones that are not present yet
152
+ * together with their dependencies. Returns a function that releases the
153
+ * hold; calling it more than once has no further effect.
154
+ */
155
+ acquire(...segments: readonly AnySegment[]): () => void;
156
+ /** Whether a segment with this key is present in the store. */
157
+ has(segmentOrKey: AnySegment | string): boolean;
158
+ /**
159
+ * Runs `fn`, deferring injections and evictions it causes until it returns,
160
+ * so that they reach the store as a single action of each kind.
161
+ */
162
+ batch<T>(fn: () => T): T;
163
+ /** Listens to segments being injected, going idle, revived and evicted. */
164
+ subscribe(listener: (event: SparseEvent) => void): () => void;
165
+ /** The keys of the present segments, by state. */
166
+ stats(): SparseStats;
167
+ }
168
+ interface SparseStoreExt {
169
+ readonly sparse: SparseController;
170
+ }
171
+ /**
172
+ * Starts segment epics. `redux-sparse-kit/observable` provides one for
173
+ * redux-observable.
174
+ */
175
+ interface EpicRunner {
176
+ /** Starts `epic` and returns a function that stops it. */
177
+ run(epic: SegmentEpic, segment: AnySegment): () => void;
178
+ }
179
+ interface SparseOptions {
180
+ /** Required as soon as a held segment has epics. */
181
+ epicRunner?: EpicRunner;
182
+ /**
183
+ * How long a segment nothing holds stays present before it is evicted, in
184
+ * milliseconds. `0` evicts it right away, `Infinity` never. Segments can
185
+ * override it. Defaults to one minute.
186
+ */
187
+ idleTimeout?: number;
188
+ /**
189
+ * What an idle segment does until it is evicted. Segments can override it.
190
+ * Defaults to `keep-running`.
191
+ */
192
+ whileIdle?: WhileIdle;
193
+ /**
194
+ * How many idle segments may wait for their idle timeout at once. Past it,
195
+ * the ones idle the longest are evicted right away. Defaults to `Infinity`.
196
+ */
197
+ maxIdle?: number;
198
+ /**
199
+ * Stops the idle clock while the page is hidden, so that time spent in a
200
+ * background tab does not count towards the idle timeout. Defaults to `true`.
201
+ */
202
+ pauseWhenHidden?: boolean;
203
+ }
204
+ /**
205
+ * A store enhancer that lets segments be injected into and evicted from a
206
+ * running store. The root reducer passed to `createStore` keeps owning the
207
+ * static state; segment slices live next to it under their keys.
208
+ */
209
+ declare function sparseEnhancer(options?: SparseOptions): StoreEnhancer<SparseStoreExt>;
210
+ //#endregion
211
+ export { StaticState as _, SparseEvent as a, isSegmentKey as b, SparseStoreExt as c, ScopeStateOf as d, Segment as f, StateOfSegments as g, SegmentOptions as h, SparseController as i, sparseEnhancer as l, SegmentEpicStateError as m, SparseAction as n, SparseOptions as o, SegmentEpic as p, SparseActionTypes as r, SparseStats as s, EpicRunner as t, AnySegment as u, WhileIdle as v, defineSegment as y };
212
+ //# sourceMappingURL=sparseEnhancer-CIcdhh-P.d.mts.map
@@ -0,0 +1,212 @@
1
+ import { Action, Reducer, StoreEnhancer } from "redux";
2
+ //#region src/segment.d.ts
3
+ /**
4
+ * The state that is always present in the store, outside of any segment.
5
+ * Register it once so that segment epics can read it:
6
+ *
7
+ * ```ts
8
+ * declare module 'redux-sparse-kit' {
9
+ * interface StaticState extends RootStaticState {}
10
+ * }
11
+ * ```
12
+ */
13
+ interface StaticState {}
14
+ /**
15
+ * What an idle segment (one no longer held by anything) does until it is
16
+ * evicted:
17
+ * - `keep-running` - its reducer and epics keep running, so its state stays
18
+ * current.
19
+ * - `pause-epics` - its reducer keeps running, its epics are stopped and
20
+ * started again when the segment is held again.
21
+ * - `freeze` - its epics are stopped and its reducer is skipped, so its state
22
+ * is kept exactly as it was.
23
+ */
24
+ type WhileIdle = "keep-running" | "pause-epics" | "freeze";
25
+ /**
26
+ * An epic as far as the core is concerned: a function the epic runner starts.
27
+ * The `redux-sparse-kit/observable` entry point runs redux-observable epics.
28
+ */
29
+ type SegmentEpic = (...args: any[]) => unknown;
30
+ /**
31
+ * A reducer, the epics that drive it and the segments it depends on, injected
32
+ * together and evicted together. Its `State` is `never` when it has no
33
+ * reducer: it then owns no slice of the state.
34
+ */
35
+ interface Segment<Key extends string = string, State = any, Deps extends readonly AnySegment[] = readonly AnySegment[]> {
36
+ /**
37
+ * Names the segment, and is the top-level key of the state its reducer
38
+ * owns when it has one.
39
+ */
40
+ readonly key: Key;
41
+ readonly reducer: unknown extends State ? Reducer<any> | undefined : [State] extends [never] ? undefined : Reducer<State>;
42
+ /** Segments that must be present for as long as this one is. */
43
+ readonly deps: Deps;
44
+ readonly epics: readonly SegmentEpic[];
45
+ /** Overrides the store's idle timeout for this segment, in milliseconds. */
46
+ readonly idleTimeout: number | undefined;
47
+ /** Overrides the store's idle behaviour for this segment. */
48
+ readonly whileIdle: WhileIdle | undefined;
49
+ }
50
+ /**
51
+ * Any segment, with or without a reducer. Spelled out rather than written as
52
+ * `Segment<string, any>`, which a segment without a reducer would not match.
53
+ */
54
+ interface AnySegment {
55
+ readonly key: string;
56
+ readonly reducer: Reducer<any> | undefined;
57
+ readonly deps: readonly AnySegment[];
58
+ readonly epics: readonly SegmentEpic[];
59
+ readonly idleTimeout: number | undefined;
60
+ readonly whileIdle: WhileIdle | undefined;
61
+ }
62
+ type SliceKeyOf<S> = S extends Segment<infer Key, infer State, any> ? ([State] extends [never] ? never : Key) : never;
63
+ /**
64
+ * The state slice each of the given segments contributes to the store;
65
+ * segments without a reducer contribute none.
66
+ */
67
+ type StateOfSegments<Segments extends AnySegment> = { [S in Segments as SliceKeyOf<S>]: S extends Segment<string, infer State, any> ? State : never; };
68
+ type OwnSliceOf<Key extends string, State> = [State] extends [never] ? {} : { [K in Key]: State; };
69
+ /**
70
+ * The state a segment may read: the static state, its own slice and the
71
+ * slices of its direct dependencies.
72
+ */
73
+ type ScopeStateOf<S extends AnySegment> = StaticState & StateOfSegments<S | S["deps"][number]>;
74
+ type EpicStateOf<Epic> = Epic extends ((action$: any, state$: infer State$, ...rest: any[]) => any) ? State$ extends {
75
+ readonly value: infer State;
76
+ } ? State : unknown : unknown;
77
+ declare const segmentEpicError: unique symbol;
78
+ /**
79
+ * Shown when an epic declares a state the segment cannot guarantee: it reads
80
+ * a segment that is neither the segment itself nor one of its dependencies.
81
+ */
82
+ interface SegmentEpicStateError<Readable, Declared> {
83
+ readonly [segmentEpicError]: "The epic reads state outside the segment and its deps";
84
+ readonly readable: Readable;
85
+ readonly declared: Declared;
86
+ }
87
+ /** An epic typed with a `void`, `unknown` or `any` state does not read it. */
88
+ type ReadsState<State> = [State] extends [void] ? false : unknown extends State ? false : true;
89
+ type ValidateEpics<Epics extends readonly unknown[], Scope> = { [I in keyof Epics]: ReadsState<EpicStateOf<Epics[I]>> extends false ? unknown : Scope extends EpicStateOf<Epics[I]> ? unknown : SegmentEpicStateError<Scope, EpicStateOf<Epics[I]>>; };
90
+ interface SegmentOptions<Key extends string, State, Deps extends readonly AnySegment[], Epics extends readonly SegmentEpic[]> {
91
+ key: Key;
92
+ /** Omitted for a segment that only runs epics or only bundles its deps. */
93
+ reducer?: Reducer<State>;
94
+ deps?: Deps;
95
+ epics?: Epics & ValidateEpics<Epics, StaticState & StateOfSegments<Deps[number]> & OwnSliceOf<Key, State>>;
96
+ idleTimeout?: number;
97
+ whileIdle?: WhileIdle;
98
+ }
99
+ /**
100
+ * Whether a segment with a reducer has been defined under this key. Lets dev
101
+ * checks tell a read of a segment's slice from a read of static state.
102
+ */
103
+ declare const isSegmentKey: (key: string) => boolean;
104
+ /**
105
+ * Defines a segment: a reducer, the epics that drive it and the segments it
106
+ * depends on, injected together and evicted together. Each part is optional.
107
+ *
108
+ * A dependency has to be defined before the segment that lists it, so the
109
+ * dependency graph cannot contain a cycle.
110
+ */
111
+ declare function defineSegment<const Key extends string, State = never, const Deps extends readonly AnySegment[] = [], const Epics extends readonly SegmentEpic[] = []>(options: SegmentOptions<Key, State, Deps, Epics>): Segment<Key, State, Deps>;
112
+ //#endregion
113
+ //#region src/sparseEnhancer.d.ts
114
+ /**
115
+ * Actions the sparse store dispatches. Reducers outside the segments can
116
+ * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an
117
+ * evicted segment.
118
+ */
119
+ declare const SparseActionTypes: {
120
+ /** Segment slices with the keys in `payload.keys` were added to the state. */
121
+ readonly INJECT: "@@sparse/INJECT";
122
+ /** Segment slices with the keys in `payload.keys` are removed by this action. */
123
+ readonly EVICT: "@@sparse/EVICT";
124
+ };
125
+ interface SparseAction extends Action<(typeof SparseActionTypes)[keyof typeof SparseActionTypes]> {
126
+ payload: {
127
+ keys: readonly string[];
128
+ };
129
+ }
130
+ type SparseEvent = {
131
+ type: "inject";
132
+ keys: readonly string[];
133
+ } | {
134
+ type: "evict";
135
+ keys: readonly string[];
136
+ } | {
137
+ type: "idle";
138
+ key: string;
139
+ } | {
140
+ type: "revive";
141
+ key: string;
142
+ };
143
+ interface SparseStats {
144
+ /** Keys of present segments that something holds. */
145
+ active: string[];
146
+ /** Keys of present segments waiting for their idle timeout. */
147
+ idle: string[];
148
+ }
149
+ interface SparseController {
150
+ /**
151
+ * Holds the given segments, injecting the ones that are not present yet
152
+ * together with their dependencies. Returns a function that releases the
153
+ * hold; calling it more than once has no further effect.
154
+ */
155
+ acquire(...segments: readonly AnySegment[]): () => void;
156
+ /** Whether a segment with this key is present in the store. */
157
+ has(segmentOrKey: AnySegment | string): boolean;
158
+ /**
159
+ * Runs `fn`, deferring injections and evictions it causes until it returns,
160
+ * so that they reach the store as a single action of each kind.
161
+ */
162
+ batch<T>(fn: () => T): T;
163
+ /** Listens to segments being injected, going idle, revived and evicted. */
164
+ subscribe(listener: (event: SparseEvent) => void): () => void;
165
+ /** The keys of the present segments, by state. */
166
+ stats(): SparseStats;
167
+ }
168
+ interface SparseStoreExt {
169
+ readonly sparse: SparseController;
170
+ }
171
+ /**
172
+ * Starts segment epics. `redux-sparse-kit/observable` provides one for
173
+ * redux-observable.
174
+ */
175
+ interface EpicRunner {
176
+ /** Starts `epic` and returns a function that stops it. */
177
+ run(epic: SegmentEpic, segment: AnySegment): () => void;
178
+ }
179
+ interface SparseOptions {
180
+ /** Required as soon as a held segment has epics. */
181
+ epicRunner?: EpicRunner;
182
+ /**
183
+ * How long a segment nothing holds stays present before it is evicted, in
184
+ * milliseconds. `0` evicts it right away, `Infinity` never. Segments can
185
+ * override it. Defaults to one minute.
186
+ */
187
+ idleTimeout?: number;
188
+ /**
189
+ * What an idle segment does until it is evicted. Segments can override it.
190
+ * Defaults to `keep-running`.
191
+ */
192
+ whileIdle?: WhileIdle;
193
+ /**
194
+ * How many idle segments may wait for their idle timeout at once. Past it,
195
+ * the ones idle the longest are evicted right away. Defaults to `Infinity`.
196
+ */
197
+ maxIdle?: number;
198
+ /**
199
+ * Stops the idle clock while the page is hidden, so that time spent in a
200
+ * background tab does not count towards the idle timeout. Defaults to `true`.
201
+ */
202
+ pauseWhenHidden?: boolean;
203
+ }
204
+ /**
205
+ * A store enhancer that lets segments be injected into and evicted from a
206
+ * running store. The root reducer passed to `createStore` keeps owning the
207
+ * static state; segment slices live next to it under their keys.
208
+ */
209
+ declare function sparseEnhancer(options?: SparseOptions): StoreEnhancer<SparseStoreExt>;
210
+ //#endregion
211
+ export { StaticState as _, SparseEvent as a, isSegmentKey as b, SparseStoreExt as c, ScopeStateOf as d, Segment as f, StateOfSegments as g, SegmentOptions as h, SparseController as i, sparseEnhancer as l, SegmentEpicStateError as m, SparseAction as n, SparseOptions as o, SegmentEpic as p, SparseActionTypes as r, SparseStats as s, EpicRunner as t, AnySegment as u, WhileIdle as v, defineSegment as y };
212
+ //# sourceMappingURL=sparseEnhancer-CIcdhh-P.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,114 @@
1
1
  {
2
2
  "name": "redux-sparse-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Dynamic reducers and epics for large Redux stores: only mounted features run on dispatch",
5
+ "keywords": [
6
+ "code-splitting",
7
+ "dynamic-reducers",
8
+ "lazy-loading",
9
+ "performance",
10
+ "react-redux",
11
+ "redux",
12
+ "redux-observable"
13
+ ],
14
+ "homepage": "https://github.com/veksa/redux-sparse-kit#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/veksa/redux-sparse-kit/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Aleksandr Khizhnyi",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/veksa/redux-sparse-kit.git"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "sideEffects": false,
28
+ "main": "./dist/index.js",
29
+ "module": "./dist/index.mjs",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ "./package.json": "./package.json",
33
+ ".": {
34
+ "import": {
35
+ "types": "./dist/index.d.mts",
36
+ "default": "./dist/index.mjs"
37
+ },
38
+ "require": {
39
+ "types": "./dist/index.d.ts",
40
+ "default": "./dist/index.js"
41
+ }
42
+ },
43
+ "./observable": {
44
+ "import": {
45
+ "types": "./dist/observable.d.mts",
46
+ "default": "./dist/observable.mjs"
47
+ },
48
+ "require": {
49
+ "types": "./dist/observable.d.ts",
50
+ "default": "./dist/observable.js"
51
+ }
52
+ },
53
+ "./react": {
54
+ "import": {
55
+ "types": "./dist/react.d.mts",
56
+ "default": "./dist/react.mjs"
57
+ },
58
+ "require": {
59
+ "types": "./dist/react.d.ts",
60
+ "default": "./dist/react.js"
61
+ }
62
+ }
63
+ },
64
+ "scripts": {
65
+ "build": "yarn clean && tsdown",
66
+ "test": "vitest run",
67
+ "test:watch": "vitest",
68
+ "test:coverage": "vitest run --coverage",
69
+ "test:types": "tsc --noEmit -p _type-tests/tsconfig.json",
70
+ "lint": "oxlint src/",
71
+ "fmt": "oxfmt",
72
+ "fmt:check": "oxfmt --check",
73
+ "compile": "tsc --noEmit",
74
+ "clean": "rimraf dist"
75
+ },
76
+ "devDependencies": {
77
+ "@reduxjs/toolkit": "^2.8.0",
78
+ "@testing-library/dom": "^10.4.1",
79
+ "@testing-library/react": "^16.3.2",
80
+ "@types/node": "^24.0.3",
81
+ "@types/react": "^19.0.12",
82
+ "@types/react-dom": "^19.0.4",
83
+ "@veksa/react-redux": "9.3.3",
84
+ "@vitest/coverage-v8": "^4.1.10",
85
+ "expect-type": "^1.2.1",
86
+ "jsdom": "^30.0.0",
87
+ "oxfmt": "^0.61.0",
88
+ "oxlint": "^1.76.0",
89
+ "react": "^19.0.0",
90
+ "react-dom": "^19.0.0",
91
+ "react-redux": "^9.3.0",
92
+ "redux": "^5.0.1",
93
+ "redux-observable": "3.0.0-rc.3",
94
+ "rimraf": "^6.0.1",
95
+ "rxjs": "^7.8.2",
96
+ "tsdown": "^0.22.14",
97
+ "typescript": "7.0.2",
98
+ "vitest": "^4.1.10"
99
+ },
100
+ "peerDependencies": {
101
+ "react": ">=18.0.0",
102
+ "redux": "^5.0.0",
103
+ "rxjs": "^7.0.0"
104
+ },
105
+ "peerDependenciesMeta": {
106
+ "react": {
107
+ "optional": true
108
+ },
109
+ "rxjs": {
110
+ "optional": true
111
+ }
112
+ },
113
+ "packageManager": "yarn@4.18.0"
114
+ }