@hamak/ui-store-impl 0.4.6 → 0.4.16
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/package.json +9 -6
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-test.log +0 -115
- package/CHANGELOG.md +0 -41
- package/project.json +0 -24
- package/src/core/index.ts +0 -3
- package/src/core/middleware-registry.test.ts +0 -247
- package/src/core/middleware-registry.ts +0 -64
- package/src/core/reducer-registry.test.ts +0 -215
- package/src/core/reducer-registry.ts +0 -71
- package/src/core/store-manager.test.ts +0 -288
- package/src/core/store-manager.ts +0 -125
- package/src/extensions/store-extensions.ts +0 -90
- package/src/fs/commands/fs-commands.ts +0 -389
- package/src/fs/commands/structure-commands.ts +0 -105
- package/src/fs/core/fs-adapter.ts +0 -180
- package/src/fs/core/fs-facade.ts +0 -100
- package/src/fs/index.ts +0 -14
- package/src/fs/utils/data-updater.ts +0 -273
- package/src/fs/utils/deep-equal.ts +0 -35
- package/src/index.ts +0 -9
- package/src/middleware/event-bridge-middleware.test.ts +0 -131
- package/src/middleware/event-bridge-middleware.ts +0 -26
- package/src/middleware/index.ts +0 -6
- package/src/middleware/logger-middleware.test.ts +0 -129
- package/src/middleware/logger-middleware.ts +0 -25
- package/src/plugin/index.ts +0 -5
- package/src/plugin/store-plugin-factory.ts +0 -142
- package/tsconfig.es2015.json +0 -24
- package/tsconfig.json +0 -19
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ReducerRegistry Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
6
|
-
import { ReducerRegistry } from './reducer-registry';
|
|
7
|
-
import type { Reducer } from 'redux';
|
|
8
|
-
|
|
9
|
-
describe('ReducerRegistry', () => {
|
|
10
|
-
let registry: ReducerRegistry;
|
|
11
|
-
let onReducerChange: ReturnType<typeof vi.fn>;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
onReducerChange = vi.fn(() => {});
|
|
15
|
-
registry = new ReducerRegistry(onReducerChange);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('register', () => {
|
|
19
|
-
test('should register a reducer', () => {
|
|
20
|
-
const reducer: Reducer = (state = {}) => state;
|
|
21
|
-
|
|
22
|
-
registry.register('test', reducer);
|
|
23
|
-
|
|
24
|
-
expect(registry.has('test')).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('should call onReducerChange when reducer is registered', () => {
|
|
28
|
-
const reducer: Reducer = (state = {}) => state;
|
|
29
|
-
|
|
30
|
-
registry.register('test', reducer);
|
|
31
|
-
|
|
32
|
-
expect(onReducerChange).toHaveBeenCalled();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('should replace existing reducer when replace flag is true', () => {
|
|
36
|
-
const reducer1: Reducer = (state = { value: 1 }) => state;
|
|
37
|
-
const reducer2: Reducer = (state = { value: 2 }) => state;
|
|
38
|
-
|
|
39
|
-
registry.register('test', reducer1);
|
|
40
|
-
registry.register('test', reducer2, true);
|
|
41
|
-
|
|
42
|
-
expect(registry.has('test')).toBe(true);
|
|
43
|
-
expect(onReducerChange).toHaveBeenCalledTimes(2);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('should warn when replacing without replace flag', () => {
|
|
47
|
-
const reducer1: Reducer = (state = {}) => state;
|
|
48
|
-
const reducer2: Reducer = (state = {}) => state;
|
|
49
|
-
|
|
50
|
-
registry.register('test', reducer1);
|
|
51
|
-
|
|
52
|
-
// Should not replace - just warn
|
|
53
|
-
registry.register('test', reducer2, false);
|
|
54
|
-
|
|
55
|
-
// Reducer should still be the first one
|
|
56
|
-
const info = registry.getInfo('test');
|
|
57
|
-
expect(info?.reducer).toBe(reducer1);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('should not call onReducerChange when no callback provided', () => {
|
|
61
|
-
const registryWithoutCallback = new ReducerRegistry();
|
|
62
|
-
const reducer: Reducer = (state = {}) => state;
|
|
63
|
-
|
|
64
|
-
expect(() => registryWithoutCallback.register('test', reducer)).not.toThrow();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('unregister', () => {
|
|
69
|
-
test('should unregister a reducer', () => {
|
|
70
|
-
const reducer: Reducer = (state = {}) => state;
|
|
71
|
-
registry.register('test', reducer);
|
|
72
|
-
|
|
73
|
-
registry.unregister('test');
|
|
74
|
-
|
|
75
|
-
expect(registry.has('test')).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('should call onReducerChange when reducer is unregistered', () => {
|
|
79
|
-
const reducer: Reducer = (state = {}) => state;
|
|
80
|
-
registry.register('test', reducer);
|
|
81
|
-
onReducerChange.mockClear();
|
|
82
|
-
|
|
83
|
-
registry.unregister('test');
|
|
84
|
-
|
|
85
|
-
expect(onReducerChange).toHaveBeenCalled();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('should not throw when unregistering non-existent reducer', () => {
|
|
89
|
-
expect(() => registry.unregister('non-existent')).not.toThrow();
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('has', () => {
|
|
94
|
-
test('should return false for non-existent reducer', () => {
|
|
95
|
-
expect(registry.has('non-existent')).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('should return true for registered reducer', () => {
|
|
99
|
-
const reducer: Reducer = (state = {}) => state;
|
|
100
|
-
registry.register('test', reducer);
|
|
101
|
-
|
|
102
|
-
expect(registry.has('test')).toBe(true);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('getInfo', () => {
|
|
107
|
-
test('should return undefined for non-existent reducer', () => {
|
|
108
|
-
expect(registry.getInfo('non-existent')).toBeUndefined();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('should return registration info for existing key', () => {
|
|
112
|
-
const reducer: Reducer = (state = {}) => state;
|
|
113
|
-
registry.register('test', reducer);
|
|
114
|
-
|
|
115
|
-
const info = registry.getInfo('test');
|
|
116
|
-
expect(info?.reducer).toBe(reducer);
|
|
117
|
-
expect(info?.key).toBe('test');
|
|
118
|
-
expect(info?.registeredAt).toBeInstanceOf(Date);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('getAll', () => {
|
|
123
|
-
test('should return empty object when no reducers registered', () => {
|
|
124
|
-
expect(registry.getAll()).toEqual({});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('should return all reducers as an object', () => {
|
|
128
|
-
const reducer1: Reducer = (state = {}) => state;
|
|
129
|
-
const reducer2: Reducer = (state = {}) => state;
|
|
130
|
-
|
|
131
|
-
registry.register('first', reducer1);
|
|
132
|
-
registry.register('second', reducer2);
|
|
133
|
-
|
|
134
|
-
const all = registry.getAll();
|
|
135
|
-
|
|
136
|
-
expect(all).toEqual({
|
|
137
|
-
first: reducer1,
|
|
138
|
-
second: reducer2,
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe('getAllRegistrations', () => {
|
|
144
|
-
test('should return empty array when no reducers registered', () => {
|
|
145
|
-
expect(registry.getAllRegistrations()).toEqual([]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('should return all registrations with metadata', () => {
|
|
149
|
-
const reducer1: Reducer = (state = {}) => state;
|
|
150
|
-
const reducer2: Reducer = (state = {}) => state;
|
|
151
|
-
|
|
152
|
-
registry.register('first', reducer1);
|
|
153
|
-
registry.register('second', reducer2);
|
|
154
|
-
|
|
155
|
-
const registrations = registry.getAllRegistrations();
|
|
156
|
-
|
|
157
|
-
expect(registrations.length).toBe(2);
|
|
158
|
-
expect(registrations[0]).toHaveProperty('key');
|
|
159
|
-
expect(registrations[0]).toHaveProperty('reducer');
|
|
160
|
-
expect(registrations[0]).toHaveProperty('registeredAt');
|
|
161
|
-
expect(registrations[0].registeredAt).toBeInstanceOf(Date);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('getCombinedReducer', () => {
|
|
166
|
-
test('should return identity reducer when no reducers registered', () => {
|
|
167
|
-
const combined = registry.getCombinedReducer();
|
|
168
|
-
const state = { test: 'value' };
|
|
169
|
-
|
|
170
|
-
expect(combined(state, { type: 'TEST' })).toEqual(state);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('should combine multiple reducers', () => {
|
|
174
|
-
const counterReducer: Reducer = (state = 0, action: any) => {
|
|
175
|
-
if (action.type === 'INCREMENT') return state + 1;
|
|
176
|
-
return state;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const nameReducer: Reducer = (state = '', action: any) => {
|
|
180
|
-
if (action.type === 'SET_NAME') return action.payload;
|
|
181
|
-
return state;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
registry.register('counter', counterReducer);
|
|
185
|
-
registry.register('name', nameReducer);
|
|
186
|
-
|
|
187
|
-
const combined = registry.getCombinedReducer();
|
|
188
|
-
let state = combined(undefined, { type: '@@INIT' });
|
|
189
|
-
|
|
190
|
-
expect(state).toEqual({ counter: 0, name: '' });
|
|
191
|
-
|
|
192
|
-
state = combined(state, { type: 'INCREMENT' });
|
|
193
|
-
expect(state).toEqual({ counter: 1, name: '' });
|
|
194
|
-
|
|
195
|
-
state = combined(state, { type: 'SET_NAME', payload: 'Test' });
|
|
196
|
-
expect(state).toEqual({ counter: 1, name: 'Test' });
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('should update combined reducer when new reducer is registered', () => {
|
|
200
|
-
const reducer1: Reducer = (state = 1) => state;
|
|
201
|
-
registry.register('first', reducer1);
|
|
202
|
-
|
|
203
|
-
const combined1 = registry.getCombinedReducer();
|
|
204
|
-
const state1 = combined1(undefined, { type: '@@INIT' });
|
|
205
|
-
expect(state1).toEqual({ first: 1 });
|
|
206
|
-
|
|
207
|
-
const reducer2: Reducer = (state = 2) => state;
|
|
208
|
-
registry.register('second', reducer2);
|
|
209
|
-
|
|
210
|
-
const combined2 = registry.getCombinedReducer();
|
|
211
|
-
const state2 = combined2(undefined, { type: '@@INIT' });
|
|
212
|
-
expect(state2).toEqual({ first: 1, second: 2 });
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reducer Registry Implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { combineReducers, type Reducer } from 'redux';
|
|
6
|
-
import type { IReducerRegistry, ReducerMap, ReducerRegistration } from '@hamak/ui-store-api';
|
|
7
|
-
|
|
8
|
-
export class ReducerRegistry implements IReducerRegistry {
|
|
9
|
-
private reducers = new Map<string, ReducerRegistration>();
|
|
10
|
-
private onReducerChange?: (rootReducer: Reducer) => void;
|
|
11
|
-
|
|
12
|
-
constructor(onReducerChange?: (rootReducer: Reducer) => void) {
|
|
13
|
-
this.onReducerChange = onReducerChange;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
register(key: string, reducer: Reducer, replace = false): void {
|
|
17
|
-
if (!replace && this.reducers.has(key)) {
|
|
18
|
-
console.warn(`[ReducerRegistry] Reducer "${key}" already registered. Use replace=true to override.`);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.reducers.set(key, {
|
|
23
|
-
key,
|
|
24
|
-
reducer,
|
|
25
|
-
registeredAt: new Date(),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Notify about reducer change for hot replacement
|
|
29
|
-
if (this.onReducerChange) {
|
|
30
|
-
this.onReducerChange(this.getCombinedReducer());
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
unregister(key: string): void {
|
|
35
|
-
this.reducers.delete(key);
|
|
36
|
-
|
|
37
|
-
if (this.onReducerChange) {
|
|
38
|
-
this.onReducerChange(this.getCombinedReducer());
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getAll(): ReducerMap {
|
|
43
|
-
const map: ReducerMap = {};
|
|
44
|
-
this.reducers.forEach((registration, key) => {
|
|
45
|
-
map[key] = registration.reducer;
|
|
46
|
-
});
|
|
47
|
-
return map;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
getCombinedReducer(): Reducer {
|
|
51
|
-
const reducerMap = this.getAll();
|
|
52
|
-
|
|
53
|
-
if (Object.keys(reducerMap).length === 0) {
|
|
54
|
-
return (state = {}) => state;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return combineReducers(reducerMap);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
has(key: string): boolean {
|
|
61
|
-
return this.reducers.has(key);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getInfo(key: string): ReducerRegistration | undefined {
|
|
65
|
-
return this.reducers.get(key);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
getAllRegistrations(): ReducerRegistration[] {
|
|
69
|
-
return Array.from(this.reducers.values());
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StoreManager Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
6
|
-
import { StoreManager } from './store-manager';
|
|
7
|
-
import type { Middleware, Reducer } from 'redux';
|
|
8
|
-
|
|
9
|
-
describe('StoreManager', () => {
|
|
10
|
-
let manager: StoreManager;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
manager = new StoreManager();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('initialization', () => {
|
|
17
|
-
test('should not be initialized by default', () => {
|
|
18
|
-
expect(manager.isInitialized()).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('should throw error when getting store before initialization', () => {
|
|
22
|
-
expect(() => manager.getStore()).toThrow(
|
|
23
|
-
/Store not initialized/
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('should initialize store', () => {
|
|
28
|
-
const store = manager.initialize();
|
|
29
|
-
|
|
30
|
-
expect(manager.isInitialized()).toBe(true);
|
|
31
|
-
expect(store).toBeDefined();
|
|
32
|
-
expect(store.getState).toBeDefined();
|
|
33
|
-
expect(store.dispatch).toBeDefined();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('should throw error when initializing twice', () => {
|
|
37
|
-
manager.initialize();
|
|
38
|
-
|
|
39
|
-
expect(() => manager.initialize()).toThrow(
|
|
40
|
-
/Store already initialized/
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('should lock middleware registry after initialization', () => {
|
|
45
|
-
const middleware: Middleware = () => (next) => (action) => next(action);
|
|
46
|
-
|
|
47
|
-
manager.initialize();
|
|
48
|
-
|
|
49
|
-
expect(() =>
|
|
50
|
-
manager.getMiddlewareRegistry().register({
|
|
51
|
-
id: 'test',
|
|
52
|
-
middleware,
|
|
53
|
-
})
|
|
54
|
-
).toThrow(/store already created/);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('middleware registration', () => {
|
|
59
|
-
test('should register middleware before initialization', () => {
|
|
60
|
-
const middleware: Middleware = () => (next) => (action) => next(action);
|
|
61
|
-
|
|
62
|
-
manager.getMiddlewareRegistry().register({
|
|
63
|
-
id: 'test',
|
|
64
|
-
middleware,
|
|
65
|
-
priority: 100,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(manager.getMiddlewareRegistry().has('test')).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('should apply middleware in priority order', () => {
|
|
72
|
-
const callOrder: string[] = [];
|
|
73
|
-
|
|
74
|
-
const middleware1: Middleware = () => (next) => (action: any) => {
|
|
75
|
-
callOrder.push('middleware1');
|
|
76
|
-
return next(action);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const middleware2: Middleware = () => (next) => (action: any) => {
|
|
80
|
-
callOrder.push('middleware2');
|
|
81
|
-
return next(action);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const middleware3: Middleware = () => (next) => (action: any) => {
|
|
85
|
-
callOrder.push('middleware3');
|
|
86
|
-
return next(action);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
manager.getMiddlewareRegistry().register({
|
|
90
|
-
id: 'low',
|
|
91
|
-
middleware: middleware1,
|
|
92
|
-
priority: 10,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
manager.getMiddlewareRegistry().register({
|
|
96
|
-
id: 'high',
|
|
97
|
-
middleware: middleware2,
|
|
98
|
-
priority: 100,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
manager.getMiddlewareRegistry().register({
|
|
102
|
-
id: 'medium',
|
|
103
|
-
middleware: middleware3,
|
|
104
|
-
priority: 50,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const store = manager.initialize();
|
|
108
|
-
store.dispatch({ type: 'TEST' });
|
|
109
|
-
|
|
110
|
-
expect(callOrder).toEqual(['middleware2', 'middleware3', 'middleware1']);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('reducer registration', () => {
|
|
115
|
-
test('should register reducers', () => {
|
|
116
|
-
const reducer: Reducer = (state = {}) => state;
|
|
117
|
-
|
|
118
|
-
manager.getReducerRegistry().register('test', reducer);
|
|
119
|
-
|
|
120
|
-
expect(manager.getReducerRegistry().has('test')).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('should combine multiple reducers', () => {
|
|
124
|
-
const counterReducer: Reducer = (state = 0, action: any) => {
|
|
125
|
-
if (action.type === 'INCREMENT') return state + 1;
|
|
126
|
-
return state;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const nameReducer: Reducer = (state = 'initial', action: any) => {
|
|
130
|
-
if (action.type === 'SET_NAME') return action.payload;
|
|
131
|
-
return state;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
manager.getReducerRegistry().register('counter', counterReducer);
|
|
135
|
-
manager.getReducerRegistry().register('name', nameReducer);
|
|
136
|
-
|
|
137
|
-
const store = manager.initialize();
|
|
138
|
-
|
|
139
|
-
expect(store.getState()).toEqual({ counter: 0, name: 'initial' });
|
|
140
|
-
|
|
141
|
-
store.dispatch({ type: 'INCREMENT' });
|
|
142
|
-
expect(store.getState()).toEqual({ counter: 1, name: 'initial' });
|
|
143
|
-
|
|
144
|
-
store.dispatch({ type: 'SET_NAME', payload: 'Updated' });
|
|
145
|
-
expect(store.getState()).toEqual({ counter: 1, name: 'Updated' });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('should support hot reducer replacement', () => {
|
|
149
|
-
const reducer1: Reducer = (state = { version: 1 }) => state;
|
|
150
|
-
manager.getReducerRegistry().register('feature', reducer1);
|
|
151
|
-
|
|
152
|
-
const store = manager.initialize();
|
|
153
|
-
expect(store.getState()).toEqual({ feature: { version: 1 } });
|
|
154
|
-
|
|
155
|
-
// Hot replace reducer
|
|
156
|
-
const reducer2: Reducer = (state = { version: 2 }, action: any) => {
|
|
157
|
-
// If state is provided from previous reducer, keep it
|
|
158
|
-
// Only use new initial state if state is undefined
|
|
159
|
-
if (state === undefined) return { version: 2 };
|
|
160
|
-
return state;
|
|
161
|
-
};
|
|
162
|
-
manager.getReducerRegistry().register('feature', reducer2, true);
|
|
163
|
-
|
|
164
|
-
// The state won't automatically reset - hot replacement keeps existing state
|
|
165
|
-
// unless we dispatch an action that changes it
|
|
166
|
-
expect(store.getState().feature).toEqual({ version: 1 });
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('store operations', () => {
|
|
171
|
-
test('should dispatch actions', () => {
|
|
172
|
-
const reducer: Reducer = (state = 0, action: any) => {
|
|
173
|
-
if (action.type === 'INCREMENT') return state + 1;
|
|
174
|
-
return state;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
manager.getReducerRegistry().register('counter', reducer);
|
|
178
|
-
manager.initialize();
|
|
179
|
-
|
|
180
|
-
const action = { type: 'INCREMENT' };
|
|
181
|
-
const result = manager.dispatch(action);
|
|
182
|
-
|
|
183
|
-
expect(result).toEqual(action);
|
|
184
|
-
expect(manager.getState()).toEqual({ counter: 1 });
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('should get state', () => {
|
|
188
|
-
const reducer: Reducer = (state = { test: 'value' }) => state;
|
|
189
|
-
|
|
190
|
-
manager.getReducerRegistry().register('data', reducer);
|
|
191
|
-
manager.initialize();
|
|
192
|
-
|
|
193
|
-
expect(manager.getState()).toEqual({ data: { test: 'value' } });
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('should subscribe to changes', () => {
|
|
197
|
-
const reducer: Reducer = (state = 0, action: any) => {
|
|
198
|
-
if (action.type === 'INCREMENT') return state + 1;
|
|
199
|
-
return state;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
manager.getReducerRegistry().register('counter', reducer);
|
|
203
|
-
manager.initialize();
|
|
204
|
-
|
|
205
|
-
const listener = vi.fn(() => {});
|
|
206
|
-
const unsubscribe = manager.subscribe(listener);
|
|
207
|
-
|
|
208
|
-
manager.dispatch({ type: 'INCREMENT' });
|
|
209
|
-
expect(listener).toHaveBeenCalled();
|
|
210
|
-
|
|
211
|
-
listener.mockClear();
|
|
212
|
-
unsubscribe();
|
|
213
|
-
manager.dispatch({ type: 'INCREMENT' });
|
|
214
|
-
expect(listener).not.toHaveBeenCalled();
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('should replace reducer', () => {
|
|
218
|
-
const reducer1: Reducer = (state = { value: 1 }) => state;
|
|
219
|
-
manager.getReducerRegistry().register('data', reducer1);
|
|
220
|
-
manager.initialize();
|
|
221
|
-
|
|
222
|
-
expect(manager.getState()).toEqual({ data: { value: 1 } });
|
|
223
|
-
|
|
224
|
-
// Use combineReducers to create a proper reducer function
|
|
225
|
-
const { combineReducers } = require('redux');
|
|
226
|
-
const reducer2: Reducer = (state = { value: 2 }) => state;
|
|
227
|
-
const combined = combineReducers({ data: reducer2 });
|
|
228
|
-
manager.replaceReducer(combined);
|
|
229
|
-
|
|
230
|
-
// State is preserved unless reducer explicitly changes it
|
|
231
|
-
// The existing state is passed to the new reducer
|
|
232
|
-
expect(manager.getState().data.value).toBe(1);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('preloaded state', () => {
|
|
237
|
-
test('should initialize with preloaded state', () => {
|
|
238
|
-
const reducer: Reducer = (state = 0) => state;
|
|
239
|
-
manager.getReducerRegistry().register('counter', reducer);
|
|
240
|
-
|
|
241
|
-
const preloadedState = { counter: 42 };
|
|
242
|
-
manager.initialize({ preloadedState });
|
|
243
|
-
|
|
244
|
-
expect(manager.getState()).toEqual({ counter: 42 });
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe('destroy', () => {
|
|
249
|
-
test('should destroy store', () => {
|
|
250
|
-
manager.initialize();
|
|
251
|
-
manager.destroy();
|
|
252
|
-
|
|
253
|
-
expect(manager.isInitialized()).toBe(false);
|
|
254
|
-
expect(() => manager.getStore()).toThrow(/Store not initialized/);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test('should allow re-initialization after destroy', () => {
|
|
258
|
-
manager.initialize();
|
|
259
|
-
manager.destroy();
|
|
260
|
-
|
|
261
|
-
// Should be able to initialize again
|
|
262
|
-
expect(() => manager.initialize()).not.toThrow();
|
|
263
|
-
expect(manager.isInitialized()).toBe(true);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe('DevTools configuration', () => {
|
|
268
|
-
test('should accept devTools configuration', () => {
|
|
269
|
-
// Should not throw with devTools config
|
|
270
|
-
expect(() => manager.initialize({ devTools: false })).not.toThrow();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test('should accept enhancers', () => {
|
|
274
|
-
const enhancer = (next: any) => (reducer: any, initialState: any) => {
|
|
275
|
-
const store = next(reducer, initialState);
|
|
276
|
-
return {
|
|
277
|
-
...store,
|
|
278
|
-
customMethod: () => 'enhanced',
|
|
279
|
-
};
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const store = manager.initialize({ enhancers: [enhancer] }) as any;
|
|
283
|
-
|
|
284
|
-
expect(store.customMethod).toBeDefined();
|
|
285
|
-
expect(store.customMethod()).toBe('enhanced');
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Store Manager Implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { createStore, applyMiddleware, compose, type Store, type Reducer } from 'redux';
|
|
6
|
-
import type { IStoreManager, StoreConfig, RootState, AppAction } from '@hamak/ui-store-api';
|
|
7
|
-
import { MiddlewareRegistry } from './middleware-registry';
|
|
8
|
-
import { ReducerRegistry } from './reducer-registry';
|
|
9
|
-
|
|
10
|
-
export class StoreManager implements IStoreManager {
|
|
11
|
-
private store: Store<RootState, AppAction> | null = null;
|
|
12
|
-
private middlewareRegistry: MiddlewareRegistry;
|
|
13
|
-
private reducerRegistry: ReducerRegistry;
|
|
14
|
-
private initialized = false;
|
|
15
|
-
private config: StoreConfig | null = null;
|
|
16
|
-
|
|
17
|
-
constructor() {
|
|
18
|
-
this.middlewareRegistry = new MiddlewareRegistry();
|
|
19
|
-
this.reducerRegistry = new ReducerRegistry((rootReducer: any) => {
|
|
20
|
-
// Hot replacement callback
|
|
21
|
-
if (this.store) {
|
|
22
|
-
this.store.replaceReducer(rootReducer);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getMiddlewareRegistry() {
|
|
28
|
-
return this.middlewareRegistry;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getReducerRegistry() {
|
|
32
|
-
return this.reducerRegistry;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
isInitialized(): boolean {
|
|
36
|
-
return this.initialized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
initialize(config: StoreConfig = {}): Store<RootState, AppAction> {
|
|
40
|
-
if (this.initialized) {
|
|
41
|
-
throw new Error('[StoreManager] Store already initialized');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.config = config;
|
|
45
|
-
|
|
46
|
-
// Get all middleware in priority order
|
|
47
|
-
const middleware = this.middlewareRegistry.getAll();
|
|
48
|
-
|
|
49
|
-
// Lock the middleware registry
|
|
50
|
-
this.middlewareRegistry.lock();
|
|
51
|
-
|
|
52
|
-
// Get combined reducer
|
|
53
|
-
const rootReducer = this.reducerRegistry.getCombinedReducer();
|
|
54
|
-
|
|
55
|
-
// Create enhancers
|
|
56
|
-
const enhancers = config.enhancers || [];
|
|
57
|
-
|
|
58
|
-
// Setup Redux DevTools
|
|
59
|
-
let composeEnhancers = compose;
|
|
60
|
-
if (config.devTools !== false && typeof window !== 'undefined') {
|
|
61
|
-
const devToolsExtension = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
|
|
62
|
-
if (devToolsExtension) {
|
|
63
|
-
composeEnhancers = devToolsExtension({
|
|
64
|
-
trace: true,
|
|
65
|
-
traceLimit: 25,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Create store
|
|
71
|
-
const enhancer = composeEnhancers(
|
|
72
|
-
applyMiddleware(...middleware),
|
|
73
|
-
...enhancers
|
|
74
|
-
) as any;
|
|
75
|
-
|
|
76
|
-
this.store = createStore(
|
|
77
|
-
rootReducer as any,
|
|
78
|
-
config.preloadedState,
|
|
79
|
-
enhancer
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
this.initialized = true;
|
|
83
|
-
|
|
84
|
-
console.log('[StoreManager] Store initialized with:', {
|
|
85
|
-
reducers: this.reducerRegistry.getAllRegistrations().map(r => r.key),
|
|
86
|
-
middleware: this.middlewareRegistry.getAllRegistrations().map(m => ({
|
|
87
|
-
id: m.id,
|
|
88
|
-
priority: m.priority,
|
|
89
|
-
plugin: m.plugin,
|
|
90
|
-
})),
|
|
91
|
-
devTools: config.devTools !== false,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return this.store;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
getStore(): Store<RootState, AppAction> {
|
|
98
|
-
if (!this.store) {
|
|
99
|
-
throw new Error('[StoreManager] Store not initialized. Call initialize() first.');
|
|
100
|
-
}
|
|
101
|
-
return this.store;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
dispatch<A extends AppAction>(action: A): A {
|
|
105
|
-
return this.getStore().dispatch(action);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
getState<S = RootState>(): S {
|
|
109
|
-
return this.getStore().getState() as S;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
subscribe(listener: () => void): () => void {
|
|
113
|
-
return this.getStore().subscribe(listener);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
replaceReducer(nextReducer: Reducer<RootState, AppAction>): void {
|
|
117
|
-
this.getStore().replaceReducer(nextReducer);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
destroy(): void {
|
|
121
|
-
this.store = null;
|
|
122
|
-
this.initialized = false;
|
|
123
|
-
console.log('[StoreManager] Store destroyed');
|
|
124
|
-
}
|
|
125
|
-
}
|