@hamak/ui-store-impl 0.2.1 → 0.2.3
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +115 -0
- package/dist/es2015/core/index.js +3 -0
- package/dist/es2015/core/middleware-registry.js +47 -0
- package/dist/es2015/core/reducer-registry.js +54 -0
- package/dist/es2015/core/store-manager.js +91 -0
- package/dist/es2015/extensions/store-extensions.js +61 -0
- package/dist/es2015/index.js +7 -0
- package/dist/es2015/middleware/event-bridge-middleware.js +19 -0
- package/dist/es2015/middleware/index.js +5 -0
- package/dist/es2015/middleware/logger-middleware.js +18 -0
- package/dist/es2015/plugin/index.js +4 -0
- package/dist/es2015/plugin/store-plugin-factory.js +112 -0
- package/dist/extensions/store-extensions.d.ts +23 -0
- package/dist/extensions/store-extensions.d.ts.map +1 -0
- package/dist/extensions/store-extensions.js +61 -0
- package/dist/plugin/store-plugin-factory.d.ts +2 -10
- package/dist/plugin/store-plugin-factory.d.ts.map +1 -1
- package/dist/plugin/store-plugin-factory.js +52 -37
- package/package.json +13 -7
- package/src/core/middleware-registry.test.ts +1 -1
- package/src/core/reducer-registry.test.ts +3 -3
- package/src/core/store-manager.test.ts +2 -2
- package/src/extensions/store-extensions.ts +90 -0
- package/src/middleware/event-bridge-middleware.test.ts +8 -8
- package/src/middleware/logger-middleware.test.ts +8 -8
- package/src/plugin/store-plugin-factory.ts +70 -52
- package/tsconfig.es2015.json +24 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
$ tsc -p tsconfig.json
|
|
1
|
+
$ tsc -p tsconfig.json && tsc -p tsconfig.es2015.json
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
$ vitest run
|
|
2
|
+
|
|
3
|
+
RUN v2.1.9 /Users/amah/Devs/projects/app-framework/packages/ui-store/ui-store-impl
|
|
4
|
+
|
|
5
|
+
stderr | src/core/middleware-registry.test.ts > MiddlewareRegistry > register > should replace existing middleware with same id
|
|
6
|
+
[MiddlewareRegistry] Middleware "test-middleware" already registered, overwriting.
|
|
7
|
+
|
|
8
|
+
✓ src/core/middleware-registry.test.ts (20 tests) 16ms
|
|
9
|
+
stdout | src/core/store-manager.test.ts > StoreManager > initialization > should initialize store
|
|
10
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
11
|
+
|
|
12
|
+
stdout | src/core/store-manager.test.ts > StoreManager > initialization > should throw error when initializing twice
|
|
13
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
14
|
+
|
|
15
|
+
stdout | src/core/store-manager.test.ts > StoreManager > initialization > should lock middleware registry after initialization
|
|
16
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
17
|
+
|
|
18
|
+
stdout | src/core/store-manager.test.ts > StoreManager > middleware registration > should apply middleware in priority order
|
|
19
|
+
[StoreManager] Store initialized with: {
|
|
20
|
+
reducers: [],
|
|
21
|
+
middleware: [
|
|
22
|
+
{ id: 'high', priority: 100, plugin: undefined },
|
|
23
|
+
{ id: 'medium', priority: 50, plugin: undefined },
|
|
24
|
+
{ id: 'low', priority: 10, plugin: undefined }
|
|
25
|
+
],
|
|
26
|
+
devTools: true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stdout | src/core/store-manager.test.ts > StoreManager > reducer registration > should combine multiple reducers
|
|
30
|
+
[StoreManager] Store initialized with: { reducers: [ 'counter', 'name' ], middleware: [], devTools: true }
|
|
31
|
+
|
|
32
|
+
stdout | src/core/store-manager.test.ts > StoreManager > reducer registration > should support hot reducer replacement
|
|
33
|
+
[StoreManager] Store initialized with: { reducers: [ 'feature' ], middleware: [], devTools: true }
|
|
34
|
+
|
|
35
|
+
stdout | src/core/store-manager.test.ts > StoreManager > store operations > should dispatch actions
|
|
36
|
+
[StoreManager] Store initialized with: { reducers: [ 'counter' ], middleware: [], devTools: true }
|
|
37
|
+
|
|
38
|
+
stdout | src/core/store-manager.test.ts > StoreManager > store operations > should get state
|
|
39
|
+
[StoreManager] Store initialized with: { reducers: [ 'data' ], middleware: [], devTools: true }
|
|
40
|
+
|
|
41
|
+
stdout | src/core/store-manager.test.ts > StoreManager > store operations > should subscribe to changes
|
|
42
|
+
[StoreManager] Store initialized with: { reducers: [ 'counter' ], middleware: [], devTools: true }
|
|
43
|
+
|
|
44
|
+
stdout | src/core/store-manager.test.ts > StoreManager > store operations > should replace reducer
|
|
45
|
+
[StoreManager] Store initialized with: { reducers: [ 'data' ], middleware: [], devTools: true }
|
|
46
|
+
|
|
47
|
+
stdout | src/core/store-manager.test.ts > StoreManager > preloaded state > should initialize with preloaded state
|
|
48
|
+
[StoreManager] Store initialized with: { reducers: [ 'counter' ], middleware: [], devTools: true }
|
|
49
|
+
|
|
50
|
+
stdout | src/core/store-manager.test.ts > StoreManager > destroy > should destroy store
|
|
51
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
52
|
+
[StoreManager] Store destroyed
|
|
53
|
+
|
|
54
|
+
stdout | src/core/store-manager.test.ts > StoreManager > destroy > should allow re-initialization after destroy
|
|
55
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
56
|
+
[StoreManager] Store destroyed
|
|
57
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
58
|
+
|
|
59
|
+
stdout | src/core/store-manager.test.ts > StoreManager > DevTools configuration > should accept devTools configuration
|
|
60
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: false }
|
|
61
|
+
|
|
62
|
+
stdout | src/core/store-manager.test.ts > StoreManager > DevTools configuration > should accept enhancers
|
|
63
|
+
[StoreManager] Store initialized with: { reducers: [], middleware: [], devTools: true }
|
|
64
|
+
|
|
65
|
+
✓ src/core/store-manager.test.ts (19 tests) 229ms
|
|
66
|
+
stderr | src/core/reducer-registry.test.ts > ReducerRegistry > register > should warn when replacing without replace flag
|
|
67
|
+
[ReducerRegistry] Reducer "test" already registered. Use replace=true to override.
|
|
68
|
+
|
|
69
|
+
✓ src/core/reducer-registry.test.ts (19 tests) 130ms
|
|
70
|
+
stdout | src/middleware/logger-middleware.test.ts > createLoggerMiddleware > should log actions with type
|
|
71
|
+
[Redux] INCREMENT
|
|
72
|
+
Prev State: { count: 0 }
|
|
73
|
+
Action: { type: 'INCREMENT' }
|
|
74
|
+
Next State: { count: 1 }
|
|
75
|
+
|
|
76
|
+
stdout | src/middleware/logger-middleware.test.ts > createLoggerMiddleware > should pass action through middleware chain
|
|
77
|
+
[Redux] TEST
|
|
78
|
+
Prev State: {}
|
|
79
|
+
Action: { type: 'TEST' }
|
|
80
|
+
Next State: {}
|
|
81
|
+
|
|
82
|
+
stdout | src/middleware/logger-middleware.test.ts > createLoggerMiddleware > should return action result
|
|
83
|
+
[Redux] TEST
|
|
84
|
+
Prev State: {}
|
|
85
|
+
Action: { type: 'TEST', payload: 123 }
|
|
86
|
+
Next State: {}
|
|
87
|
+
|
|
88
|
+
stdout | src/middleware/logger-middleware.test.ts > createLoggerMiddleware > should log state before and after action
|
|
89
|
+
[Redux] SET_VALUE
|
|
90
|
+
Prev State: { value: 0 }
|
|
91
|
+
Action: { type: 'SET_VALUE', payload: 42 }
|
|
92
|
+
Next State: { value: 42 }
|
|
93
|
+
|
|
94
|
+
stdout | src/middleware/logger-middleware.test.ts > createLoggerMiddleware > should work with multiple actions
|
|
95
|
+
[Redux] INCREMENT
|
|
96
|
+
Prev State: { count: 0 }
|
|
97
|
+
Action: { type: 'INCREMENT' }
|
|
98
|
+
Next State: { count: 1 }
|
|
99
|
+
[Redux] INCREMENT
|
|
100
|
+
Prev State: { count: 1 }
|
|
101
|
+
Action: { type: 'INCREMENT' }
|
|
102
|
+
Next State: { count: 2 }
|
|
103
|
+
[Redux] DECREMENT
|
|
104
|
+
Prev State: { count: 2 }
|
|
105
|
+
Action: { type: 'DECREMENT' }
|
|
106
|
+
Next State: { count: 1 }
|
|
107
|
+
|
|
108
|
+
✓ src/middleware/logger-middleware.test.ts (8 tests) 160ms
|
|
109
|
+
✓ src/middleware/event-bridge-middleware.test.ts (8 tests) 73ms
|
|
110
|
+
|
|
111
|
+
Test Files 5 passed (5)
|
|
112
|
+
Tests 74 passed (74)
|
|
113
|
+
Start at 21:18:15
|
|
114
|
+
Duration 3.29s (transform 279ms, setup 0ms, collect 376ms, tests 608ms, environment 1ms, prepare 2.75s)
|
|
115
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Registry Implementation
|
|
3
|
+
*/
|
|
4
|
+
export class MiddlewareRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.registrations = new Map();
|
|
7
|
+
this.locked = false;
|
|
8
|
+
}
|
|
9
|
+
register(registration) {
|
|
10
|
+
if (this.locked) {
|
|
11
|
+
throw new Error(`Cannot register middleware "${registration.id}" - store already created. ` +
|
|
12
|
+
`Register middleware during plugin initialization phase.`);
|
|
13
|
+
}
|
|
14
|
+
if (this.registrations.has(registration.id)) {
|
|
15
|
+
console.warn(`[MiddlewareRegistry] Middleware "${registration.id}" already registered, overwriting.`);
|
|
16
|
+
}
|
|
17
|
+
this.registrations.set(registration.id, Object.assign({ priority: 0 }, registration));
|
|
18
|
+
}
|
|
19
|
+
unregister(id) {
|
|
20
|
+
if (this.locked) {
|
|
21
|
+
throw new Error(`Cannot unregister middleware after store creation`);
|
|
22
|
+
}
|
|
23
|
+
this.registrations.delete(id);
|
|
24
|
+
}
|
|
25
|
+
getAll() {
|
|
26
|
+
const sorted = Array.from(this.registrations.values())
|
|
27
|
+
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
28
|
+
return sorted.map(reg => reg.middleware);
|
|
29
|
+
}
|
|
30
|
+
has(id) {
|
|
31
|
+
return this.registrations.has(id);
|
|
32
|
+
}
|
|
33
|
+
getInfo(id) {
|
|
34
|
+
return this.registrations.get(id);
|
|
35
|
+
}
|
|
36
|
+
getAllRegistrations() {
|
|
37
|
+
return Array.from(this.registrations.values())
|
|
38
|
+
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Lock the registry (called when store is created)
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
lock() {
|
|
45
|
+
this.locked = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reducer Registry Implementation
|
|
3
|
+
*/
|
|
4
|
+
import { combineReducers } from 'redux';
|
|
5
|
+
export class ReducerRegistry {
|
|
6
|
+
constructor(onReducerChange) {
|
|
7
|
+
this.reducers = new Map();
|
|
8
|
+
this.onReducerChange = onReducerChange;
|
|
9
|
+
}
|
|
10
|
+
register(key, reducer, replace = false) {
|
|
11
|
+
if (!replace && this.reducers.has(key)) {
|
|
12
|
+
console.warn(`[ReducerRegistry] Reducer "${key}" already registered. Use replace=true to override.`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
this.reducers.set(key, {
|
|
16
|
+
key,
|
|
17
|
+
reducer,
|
|
18
|
+
registeredAt: new Date(),
|
|
19
|
+
});
|
|
20
|
+
// Notify about reducer change for hot replacement
|
|
21
|
+
if (this.onReducerChange) {
|
|
22
|
+
this.onReducerChange(this.getCombinedReducer());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
unregister(key) {
|
|
26
|
+
this.reducers.delete(key);
|
|
27
|
+
if (this.onReducerChange) {
|
|
28
|
+
this.onReducerChange(this.getCombinedReducer());
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
getAll() {
|
|
32
|
+
const map = {};
|
|
33
|
+
this.reducers.forEach((registration, key) => {
|
|
34
|
+
map[key] = registration.reducer;
|
|
35
|
+
});
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
getCombinedReducer() {
|
|
39
|
+
const reducerMap = this.getAll();
|
|
40
|
+
if (Object.keys(reducerMap).length === 0) {
|
|
41
|
+
return (state = {}) => state;
|
|
42
|
+
}
|
|
43
|
+
return combineReducers(reducerMap);
|
|
44
|
+
}
|
|
45
|
+
has(key) {
|
|
46
|
+
return this.reducers.has(key);
|
|
47
|
+
}
|
|
48
|
+
getInfo(key) {
|
|
49
|
+
return this.reducers.get(key);
|
|
50
|
+
}
|
|
51
|
+
getAllRegistrations() {
|
|
52
|
+
return Array.from(this.reducers.values());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Manager Implementation
|
|
3
|
+
*/
|
|
4
|
+
import { createStore, applyMiddleware, compose } from 'redux';
|
|
5
|
+
import { MiddlewareRegistry } from './middleware-registry';
|
|
6
|
+
import { ReducerRegistry } from './reducer-registry';
|
|
7
|
+
export class StoreManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.store = null;
|
|
10
|
+
this.initialized = false;
|
|
11
|
+
this.config = null;
|
|
12
|
+
this.middlewareRegistry = new MiddlewareRegistry();
|
|
13
|
+
this.reducerRegistry = new ReducerRegistry((rootReducer) => {
|
|
14
|
+
// Hot replacement callback
|
|
15
|
+
if (this.store) {
|
|
16
|
+
this.store.replaceReducer(rootReducer);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
getMiddlewareRegistry() {
|
|
21
|
+
return this.middlewareRegistry;
|
|
22
|
+
}
|
|
23
|
+
getReducerRegistry() {
|
|
24
|
+
return this.reducerRegistry;
|
|
25
|
+
}
|
|
26
|
+
isInitialized() {
|
|
27
|
+
return this.initialized;
|
|
28
|
+
}
|
|
29
|
+
initialize(config = {}) {
|
|
30
|
+
if (this.initialized) {
|
|
31
|
+
throw new Error('[StoreManager] Store already initialized');
|
|
32
|
+
}
|
|
33
|
+
this.config = config;
|
|
34
|
+
// Get all middleware in priority order
|
|
35
|
+
const middleware = this.middlewareRegistry.getAll();
|
|
36
|
+
// Lock the middleware registry
|
|
37
|
+
this.middlewareRegistry.lock();
|
|
38
|
+
// Get combined reducer
|
|
39
|
+
const rootReducer = this.reducerRegistry.getCombinedReducer();
|
|
40
|
+
// Create enhancers
|
|
41
|
+
const enhancers = config.enhancers || [];
|
|
42
|
+
// Setup Redux DevTools
|
|
43
|
+
let composeEnhancers = compose;
|
|
44
|
+
if (config.devTools !== false && typeof window !== 'undefined') {
|
|
45
|
+
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
|
|
46
|
+
if (devToolsExtension) {
|
|
47
|
+
composeEnhancers = devToolsExtension({
|
|
48
|
+
trace: true,
|
|
49
|
+
traceLimit: 25,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Create store
|
|
54
|
+
const enhancer = composeEnhancers(applyMiddleware(...middleware), ...enhancers);
|
|
55
|
+
this.store = createStore(rootReducer, config.preloadedState, enhancer);
|
|
56
|
+
this.initialized = true;
|
|
57
|
+
console.log('[StoreManager] Store initialized with:', {
|
|
58
|
+
reducers: this.reducerRegistry.getAllRegistrations().map(r => r.key),
|
|
59
|
+
middleware: this.middlewareRegistry.getAllRegistrations().map(m => ({
|
|
60
|
+
id: m.id,
|
|
61
|
+
priority: m.priority,
|
|
62
|
+
plugin: m.plugin,
|
|
63
|
+
})),
|
|
64
|
+
devTools: config.devTools !== false,
|
|
65
|
+
});
|
|
66
|
+
return this.store;
|
|
67
|
+
}
|
|
68
|
+
getStore() {
|
|
69
|
+
if (!this.store) {
|
|
70
|
+
throw new Error('[StoreManager] Store not initialized. Call initialize() first.');
|
|
71
|
+
}
|
|
72
|
+
return this.store;
|
|
73
|
+
}
|
|
74
|
+
dispatch(action) {
|
|
75
|
+
return this.getStore().dispatch(action);
|
|
76
|
+
}
|
|
77
|
+
getState() {
|
|
78
|
+
return this.getStore().getState();
|
|
79
|
+
}
|
|
80
|
+
subscribe(listener) {
|
|
81
|
+
return this.getStore().subscribe(listener);
|
|
82
|
+
}
|
|
83
|
+
replaceReducer(nextReducer) {
|
|
84
|
+
this.getStore().replaceReducer(nextReducer);
|
|
85
|
+
}
|
|
86
|
+
destroy() {
|
|
87
|
+
this.store = null;
|
|
88
|
+
this.initialized = false;
|
|
89
|
+
console.log('[StoreManager] Store destroyed');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store extensions collector
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Collects middleware and reducer contributions until the store boots
|
|
6
|
+
*/
|
|
7
|
+
export class StoreExtensionsCollector {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.entries = [];
|
|
10
|
+
}
|
|
11
|
+
register(source, extensions) {
|
|
12
|
+
var _a;
|
|
13
|
+
const hasMiddleware = Boolean((_a = extensions.middleware) === null || _a === void 0 ? void 0 : _a.length);
|
|
14
|
+
const hasReducers = Boolean(extensions.reducers && Object.keys(extensions.reducers).length);
|
|
15
|
+
if (!hasMiddleware && !hasReducers) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.entries.push({
|
|
19
|
+
source,
|
|
20
|
+
extensions: {
|
|
21
|
+
middleware: extensions.middleware,
|
|
22
|
+
reducers: extensions.reducers,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Drain collected extensions (used internally by the plugin)
|
|
28
|
+
*/
|
|
29
|
+
drain() {
|
|
30
|
+
const snapshot = this.entries;
|
|
31
|
+
this.entries = [];
|
|
32
|
+
return snapshot;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function withSourceMetadata(middleware, source) {
|
|
36
|
+
var _a;
|
|
37
|
+
if (!middleware) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return Object.assign({ plugin: (_a = middleware.plugin) !== null && _a !== void 0 ? _a : source }, middleware);
|
|
41
|
+
}
|
|
42
|
+
export function applyStoreExtensions(collector, middlewareRegistry, reducerRegistry) {
|
|
43
|
+
const entries = collector.drain();
|
|
44
|
+
entries.forEach(({ source, extensions }) => {
|
|
45
|
+
var _a;
|
|
46
|
+
(_a = extensions.middleware) === null || _a === void 0 ? void 0 : _a.forEach((mw) => {
|
|
47
|
+
const middleware = withSourceMetadata(mw, source);
|
|
48
|
+
if (middleware) {
|
|
49
|
+
middlewareRegistry.register(middleware);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (extensions.reducers) {
|
|
53
|
+
Object.entries(extensions.reducers).forEach(([key, reducer]) => {
|
|
54
|
+
reducerRegistry.register(key, reducer);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function createStoreExtensionsCollector() {
|
|
60
|
+
return new StoreExtensionsCollector();
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Bridge Middleware
|
|
3
|
+
* Bridges Redux actions to microkernel event bus
|
|
4
|
+
*/
|
|
5
|
+
export function createEventBridgeMiddleware(hooks) {
|
|
6
|
+
return (store) => (next) => (action) => {
|
|
7
|
+
// Emit before action
|
|
8
|
+
hooks === null || hooks === void 0 ? void 0 : hooks.emit('redux:action:before', { action, state: store.getState() });
|
|
9
|
+
// Execute action
|
|
10
|
+
const result = next(action);
|
|
11
|
+
// Emit after action
|
|
12
|
+
hooks === null || hooks === void 0 ? void 0 : hooks.emit('redux:action:after', { action, state: store.getState() });
|
|
13
|
+
// Emit specific action type event
|
|
14
|
+
if (action === null || action === void 0 ? void 0 : action.type) {
|
|
15
|
+
hooks === null || hooks === void 0 ? void 0 : hooks.emit(`redux:action:${action.type}`, action);
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Middleware
|
|
3
|
+
* Development logger for Redux actions
|
|
4
|
+
*/
|
|
5
|
+
export function createLoggerMiddleware() {
|
|
6
|
+
return (store) => (next) => (action) => {
|
|
7
|
+
if (typeof (action === null || action === void 0 ? void 0 : action.type) === 'string') {
|
|
8
|
+
console.group(`[Redux] ${action.type}`);
|
|
9
|
+
console.log('Prev State:', store.getState());
|
|
10
|
+
console.log('Action:', action);
|
|
11
|
+
const result = next(action);
|
|
12
|
+
console.log('Next State:', store.getState());
|
|
13
|
+
console.groupEnd();
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
return next(action);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Plugin Factory
|
|
3
|
+
* Creates a microkernel plugin for Redux store management
|
|
4
|
+
*/
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN, } from '@hamak/ui-store-api';
|
|
15
|
+
import { StoreManager } from '../core/store-manager';
|
|
16
|
+
import { createEventBridgeMiddleware, createLoggerMiddleware, } from '../middleware';
|
|
17
|
+
import { applyStoreExtensions, createStoreExtensionsCollector, } from '../extensions/store-extensions';
|
|
18
|
+
export function createStorePlugin(config = {}) {
|
|
19
|
+
const storeManager = new StoreManager();
|
|
20
|
+
const middlewareRegistry = storeManager.getMiddlewareRegistry();
|
|
21
|
+
const reducerRegistry = storeManager.getReducerRegistry();
|
|
22
|
+
const extensionsCollector = createStoreExtensionsCollector();
|
|
23
|
+
const registerDefaultMiddleware = (hooks) => {
|
|
24
|
+
extensionsCollector.register('ui-store:event-bridge', {
|
|
25
|
+
middleware: [
|
|
26
|
+
{
|
|
27
|
+
id: 'event-bridge',
|
|
28
|
+
middleware: createEventBridgeMiddleware(hooks),
|
|
29
|
+
priority: 1000,
|
|
30
|
+
plugin: 'ui-store',
|
|
31
|
+
description: 'Bridges Redux actions to microkernel events',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
if (config.logger !== false &&
|
|
36
|
+
process.env.NODE_ENV === 'development') {
|
|
37
|
+
extensionsCollector.register('ui-store:logger', {
|
|
38
|
+
middleware: [
|
|
39
|
+
{
|
|
40
|
+
id: 'logger',
|
|
41
|
+
middleware: createLoggerMiddleware(),
|
|
42
|
+
priority: -1000,
|
|
43
|
+
plugin: 'ui-store',
|
|
44
|
+
description: 'Development logger',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const registerConfigExtensions = () => {
|
|
51
|
+
var _a;
|
|
52
|
+
if ((_a = config.middleware) === null || _a === void 0 ? void 0 : _a.length) {
|
|
53
|
+
const middleware = config.middleware.map((mw) => {
|
|
54
|
+
var _a;
|
|
55
|
+
return (Object.assign(Object.assign({}, mw), { plugin: (_a = mw.plugin) !== null && _a !== void 0 ? _a : 'ui-store-config' }));
|
|
56
|
+
});
|
|
57
|
+
extensionsCollector.register('ui-store-config:middleware', {
|
|
58
|
+
middleware,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (config.reducers && Object.keys(config.reducers).length) {
|
|
62
|
+
extensionsCollector.register('ui-store-config:reducers', {
|
|
63
|
+
reducers: config.reducers,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
initialize(ctx) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
// Register services via DI
|
|
71
|
+
ctx.provide({ provide: STORE_MANAGER_TOKEN, useValue: storeManager });
|
|
72
|
+
ctx.provide({
|
|
73
|
+
provide: MIDDLEWARE_REGISTRY_TOKEN,
|
|
74
|
+
useValue: middlewareRegistry,
|
|
75
|
+
});
|
|
76
|
+
ctx.provide({
|
|
77
|
+
provide: REDUCER_REGISTRY_TOKEN,
|
|
78
|
+
useValue: reducerRegistry,
|
|
79
|
+
});
|
|
80
|
+
ctx.provide({
|
|
81
|
+
provide: STORE_EXTENSIONS_TOKEN,
|
|
82
|
+
useValue: extensionsCollector,
|
|
83
|
+
});
|
|
84
|
+
registerDefaultMiddleware(ctx.hooks);
|
|
85
|
+
registerConfigExtensions();
|
|
86
|
+
console.log('[ui-store] Plugin initialized');
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
activate(ctx) {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
applyStoreExtensions(extensionsCollector, middlewareRegistry, reducerRegistry);
|
|
92
|
+
// Initialize the store after all plugins have registered middleware/reducers
|
|
93
|
+
const store = storeManager.initialize({
|
|
94
|
+
devTools: config.devTools,
|
|
95
|
+
});
|
|
96
|
+
// Bridge Redux state changes to microkernel events
|
|
97
|
+
store.subscribe(() => {
|
|
98
|
+
ctx.hooks.emit('redux:state-changed', store.getState());
|
|
99
|
+
});
|
|
100
|
+
// Emit ready event
|
|
101
|
+
ctx.hooks.emit('ui-store:ready', { store });
|
|
102
|
+
console.log('[ui-store] Plugin activated');
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
deactivate() {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
storeManager.destroy();
|
|
108
|
+
console.log('[ui-store] Plugin deactivated');
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store extensions collector
|
|
3
|
+
*/
|
|
4
|
+
import type { IMiddlewareRegistry, IReducerRegistry, StoreExtensionsRegistry, StorePluginExtensions } from '@hamak/ui-store-api';
|
|
5
|
+
interface StoreExtensionsEntry {
|
|
6
|
+
source: string;
|
|
7
|
+
extensions: StorePluginExtensions;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Collects middleware and reducer contributions until the store boots
|
|
11
|
+
*/
|
|
12
|
+
export declare class StoreExtensionsCollector implements StoreExtensionsRegistry {
|
|
13
|
+
private entries;
|
|
14
|
+
register(source: string, extensions: StorePluginExtensions): void;
|
|
15
|
+
/**
|
|
16
|
+
* Drain collected extensions (used internally by the plugin)
|
|
17
|
+
*/
|
|
18
|
+
drain(): StoreExtensionsEntry[];
|
|
19
|
+
}
|
|
20
|
+
export declare function applyStoreExtensions(collector: StoreExtensionsCollector, middlewareRegistry: IMiddlewareRegistry, reducerRegistry: IReducerRegistry): void;
|
|
21
|
+
export declare function createStoreExtensionsCollector(): StoreExtensionsCollector;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=store-extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-extensions.d.ts","sourceRoot":"","sources":["../../src/extensions/store-extensions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EAEtB,MAAM,qBAAqB,CAAC;AAE7B,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,qBAAqB,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,wBAAyB,YAAW,uBAAuB;IACtE,OAAO,CAAC,OAAO,CAA8B;IAE7C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,qBAAqB,GAAG,IAAI;IAiBjE;;OAEG;IACH,KAAK,IAAI,oBAAoB,EAAE;CAKhC;AAgBD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,wBAAwB,EACnC,kBAAkB,EAAE,mBAAmB,EACvC,eAAe,EAAE,gBAAgB,GAChC,IAAI,CAiBN;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAEzE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store extensions collector
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Collects middleware and reducer contributions until the store boots
|
|
6
|
+
*/
|
|
7
|
+
export class StoreExtensionsCollector {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.entries = [];
|
|
10
|
+
}
|
|
11
|
+
register(source, extensions) {
|
|
12
|
+
const hasMiddleware = Boolean(extensions.middleware?.length);
|
|
13
|
+
const hasReducers = Boolean(extensions.reducers && Object.keys(extensions.reducers).length);
|
|
14
|
+
if (!hasMiddleware && !hasReducers) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.entries.push({
|
|
18
|
+
source,
|
|
19
|
+
extensions: {
|
|
20
|
+
middleware: extensions.middleware,
|
|
21
|
+
reducers: extensions.reducers,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Drain collected extensions (used internally by the plugin)
|
|
27
|
+
*/
|
|
28
|
+
drain() {
|
|
29
|
+
const snapshot = this.entries;
|
|
30
|
+
this.entries = [];
|
|
31
|
+
return snapshot;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function withSourceMetadata(middleware, source) {
|
|
35
|
+
if (!middleware) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
plugin: middleware.plugin ?? source,
|
|
40
|
+
...middleware,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function applyStoreExtensions(collector, middlewareRegistry, reducerRegistry) {
|
|
44
|
+
const entries = collector.drain();
|
|
45
|
+
entries.forEach(({ source, extensions }) => {
|
|
46
|
+
extensions.middleware?.forEach((mw) => {
|
|
47
|
+
const middleware = withSourceMetadata(mw, source);
|
|
48
|
+
if (middleware) {
|
|
49
|
+
middlewareRegistry.register(middleware);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (extensions.reducers) {
|
|
53
|
+
Object.entries(extensions.reducers).forEach(([key, reducer]) => {
|
|
54
|
+
reducerRegistry.register(key, reducer);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function createStoreExtensionsCollector() {
|
|
60
|
+
return new StoreExtensionsCollector();
|
|
61
|
+
}
|
|
@@ -3,20 +3,12 @@
|
|
|
3
3
|
* Creates a microkernel plugin for Redux store management
|
|
4
4
|
*/
|
|
5
5
|
import type { PluginModule } from '@hamak/microkernel-spi';
|
|
6
|
-
import type
|
|
7
|
-
export interface StorePluginConfig {
|
|
6
|
+
import { type StorePluginExtensions } from '@hamak/ui-store-api';
|
|
7
|
+
export interface StorePluginConfig extends StorePluginExtensions {
|
|
8
8
|
/** Enable Redux DevTools integration */
|
|
9
9
|
devTools?: boolean;
|
|
10
10
|
/** Enable logger middleware in development */
|
|
11
11
|
logger?: boolean;
|
|
12
|
-
/** Initial middleware to register */
|
|
13
|
-
middleware?: Array<{
|
|
14
|
-
id: string;
|
|
15
|
-
middleware: any;
|
|
16
|
-
priority?: number;
|
|
17
|
-
}>;
|
|
18
|
-
/** Initial reducers to register */
|
|
19
|
-
reducers?: Record<string, Reducer>;
|
|
20
12
|
}
|
|
21
13
|
export declare function createStorePlugin(config?: StorePluginConfig): PluginModule;
|
|
22
14
|
//# sourceMappingURL=store-plugin-factory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-plugin-factory.d.ts","sourceRoot":"","sources":["../../src/plugin/store-plugin-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store-plugin-factory.d.ts","sourceRoot":"","sources":["../../src/plugin/store-plugin-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAW7B,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC9D,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,iBAAsB,GAC7B,YAAY,CA2Gd"}
|
|
@@ -2,13 +2,58 @@
|
|
|
2
2
|
* Store Plugin Factory
|
|
3
3
|
* Creates a microkernel plugin for Redux store management
|
|
4
4
|
*/
|
|
5
|
-
import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, } from '@hamak/ui-store-api';
|
|
5
|
+
import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN, } from '@hamak/ui-store-api';
|
|
6
6
|
import { StoreManager } from '../core/store-manager';
|
|
7
7
|
import { createEventBridgeMiddleware, createLoggerMiddleware, } from '../middleware';
|
|
8
|
+
import { applyStoreExtensions, createStoreExtensionsCollector, } from '../extensions/store-extensions';
|
|
8
9
|
export function createStorePlugin(config = {}) {
|
|
9
10
|
const storeManager = new StoreManager();
|
|
10
11
|
const middlewareRegistry = storeManager.getMiddlewareRegistry();
|
|
11
12
|
const reducerRegistry = storeManager.getReducerRegistry();
|
|
13
|
+
const extensionsCollector = createStoreExtensionsCollector();
|
|
14
|
+
const registerDefaultMiddleware = (hooks) => {
|
|
15
|
+
extensionsCollector.register('ui-store:event-bridge', {
|
|
16
|
+
middleware: [
|
|
17
|
+
{
|
|
18
|
+
id: 'event-bridge',
|
|
19
|
+
middleware: createEventBridgeMiddleware(hooks),
|
|
20
|
+
priority: 1000,
|
|
21
|
+
plugin: 'ui-store',
|
|
22
|
+
description: 'Bridges Redux actions to microkernel events',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
if (config.logger !== false &&
|
|
27
|
+
process.env.NODE_ENV === 'development') {
|
|
28
|
+
extensionsCollector.register('ui-store:logger', {
|
|
29
|
+
middleware: [
|
|
30
|
+
{
|
|
31
|
+
id: 'logger',
|
|
32
|
+
middleware: createLoggerMiddleware(),
|
|
33
|
+
priority: -1000,
|
|
34
|
+
plugin: 'ui-store',
|
|
35
|
+
description: 'Development logger',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const registerConfigExtensions = () => {
|
|
42
|
+
if (config.middleware?.length) {
|
|
43
|
+
const middleware = config.middleware.map((mw) => ({
|
|
44
|
+
...mw,
|
|
45
|
+
plugin: mw.plugin ?? 'ui-store-config',
|
|
46
|
+
}));
|
|
47
|
+
extensionsCollector.register('ui-store-config:middleware', {
|
|
48
|
+
middleware,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (config.reducers && Object.keys(config.reducers).length) {
|
|
52
|
+
extensionsCollector.register('ui-store-config:reducers', {
|
|
53
|
+
reducers: config.reducers,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
12
57
|
return {
|
|
13
58
|
async initialize(ctx) {
|
|
14
59
|
// Register services via DI
|
|
@@ -21,46 +66,16 @@ export function createStorePlugin(config = {}) {
|
|
|
21
66
|
provide: REDUCER_REGISTRY_TOKEN,
|
|
22
67
|
useValue: reducerRegistry,
|
|
23
68
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
middleware: createEventBridgeMiddleware(ctx.hooks),
|
|
28
|
-
priority: 1000,
|
|
29
|
-
plugin: 'ui-store',
|
|
30
|
-
description: 'Bridges Redux actions to microkernel events',
|
|
31
|
-
});
|
|
32
|
-
// Register logger middleware in development (low priority - runs last)
|
|
33
|
-
if (config.logger !== false &&
|
|
34
|
-
process.env.NODE_ENV === 'development') {
|
|
35
|
-
middlewareRegistry.register({
|
|
36
|
-
id: 'logger',
|
|
37
|
-
middleware: createLoggerMiddleware(),
|
|
38
|
-
priority: -1000,
|
|
39
|
-
plugin: 'ui-store',
|
|
40
|
-
description: 'Development logger',
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
// Register user-provided middleware
|
|
44
|
-
config.middleware?.forEach((mw) => {
|
|
45
|
-
middlewareRegistry.register({
|
|
46
|
-
...mw,
|
|
47
|
-
plugin: 'ui-store-config',
|
|
48
|
-
});
|
|
69
|
+
ctx.provide({
|
|
70
|
+
provide: STORE_EXTENSIONS_TOKEN,
|
|
71
|
+
useValue: extensionsCollector,
|
|
49
72
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.log('[ui-store] Registering reducers:', Object.keys(config.reducers));
|
|
53
|
-
Object.entries(config.reducers).forEach(([key, reducer]) => {
|
|
54
|
-
reducerRegistry.register(key, reducer);
|
|
55
|
-
console.log('[ui-store] Registered reducer:', key);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
console.log('[ui-store] No reducers provided in config');
|
|
60
|
-
}
|
|
73
|
+
registerDefaultMiddleware(ctx.hooks);
|
|
74
|
+
registerConfigExtensions();
|
|
61
75
|
console.log('[ui-store] Plugin initialized');
|
|
62
76
|
},
|
|
63
77
|
async activate(ctx) {
|
|
78
|
+
applyStoreExtensions(extensionsCollector, middlewareRegistry, reducerRegistry);
|
|
64
79
|
// Initialize the store after all plugins have registered middleware/reducers
|
|
65
80
|
const store = storeManager.initialize({
|
|
66
81
|
devTools: config.devTools,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamak/ui-store-impl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "UI Store Implementation - Redux store implementation with middleware",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.es2015.json",
|
|
20
20
|
"clean": "rm -rf dist",
|
|
21
21
|
"test": "vitest run",
|
|
22
22
|
"test:watch": "vitest"
|
|
@@ -24,14 +24,20 @@
|
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/index.js"
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js",
|
|
29
|
+
"legacy": "./dist/es2015/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./es2015": {
|
|
32
|
+
"import": "./dist/es2015/index.js",
|
|
33
|
+
"default": "./dist/es2015/index.js"
|
|
28
34
|
}
|
|
29
35
|
},
|
|
30
36
|
"dependencies": {
|
|
31
|
-
"@hamak/ui-store-api": "0.2.
|
|
32
|
-
"@hamak/ui-store-spi": "0.2.
|
|
33
|
-
"@hamak/microkernel-api": "0.2.
|
|
34
|
-
"@hamak/microkernel-spi": "0.2.
|
|
37
|
+
"@hamak/ui-store-api": "0.2.2",
|
|
38
|
+
"@hamak/ui-store-spi": "0.2.2",
|
|
39
|
+
"@hamak/microkernel-api": "0.2.2",
|
|
40
|
+
"@hamak/microkernel-spi": "0.2.2",
|
|
35
41
|
"redux": "^5.0.1",
|
|
36
42
|
"redux-thunk": "^3.1.0"
|
|
37
43
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MiddlewareRegistry Tests
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, test, expect, beforeEach } from '
|
|
5
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
6
6
|
import { MiddlewareRegistry } from './middleware-registry';
|
|
7
7
|
import type { Middleware } from 'redux';
|
|
8
8
|
import type { MiddlewareRegistration } from '@hamak/ui-store-api';
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* ReducerRegistry Tests
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, test, expect, beforeEach,
|
|
5
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
6
6
|
import { ReducerRegistry } from './reducer-registry';
|
|
7
7
|
import type { Reducer } from 'redux';
|
|
8
8
|
|
|
9
9
|
describe('ReducerRegistry', () => {
|
|
10
10
|
let registry: ReducerRegistry;
|
|
11
|
-
let onReducerChange: ReturnType<typeof
|
|
11
|
+
let onReducerChange: ReturnType<typeof vi.fn>;
|
|
12
12
|
|
|
13
13
|
beforeEach(() => {
|
|
14
|
-
onReducerChange =
|
|
14
|
+
onReducerChange = vi.fn(() => {});
|
|
15
15
|
registry = new ReducerRegistry(onReducerChange);
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* StoreManager Tests
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, test, expect, beforeEach,
|
|
5
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
6
6
|
import { StoreManager } from './store-manager';
|
|
7
7
|
import type { Middleware, Reducer } from 'redux';
|
|
8
8
|
|
|
@@ -202,7 +202,7 @@ describe('StoreManager', () => {
|
|
|
202
202
|
manager.getReducerRegistry().register('counter', reducer);
|
|
203
203
|
manager.initialize();
|
|
204
204
|
|
|
205
|
-
const listener =
|
|
205
|
+
const listener = vi.fn(() => {});
|
|
206
206
|
const unsubscribe = manager.subscribe(listener);
|
|
207
207
|
|
|
208
208
|
manager.dispatch({ type: 'INCREMENT' });
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store extensions collector
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
IMiddlewareRegistry,
|
|
7
|
+
IReducerRegistry,
|
|
8
|
+
StoreExtensionsRegistry,
|
|
9
|
+
StorePluginExtensions,
|
|
10
|
+
StoreMiddlewareExtension,
|
|
11
|
+
} from '@hamak/ui-store-api';
|
|
12
|
+
|
|
13
|
+
interface StoreExtensionsEntry {
|
|
14
|
+
source: string;
|
|
15
|
+
extensions: StorePluginExtensions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Collects middleware and reducer contributions until the store boots
|
|
20
|
+
*/
|
|
21
|
+
export class StoreExtensionsCollector implements StoreExtensionsRegistry {
|
|
22
|
+
private entries: StoreExtensionsEntry[] = [];
|
|
23
|
+
|
|
24
|
+
register(source: string, extensions: StorePluginExtensions): void {
|
|
25
|
+
const hasMiddleware = Boolean(extensions.middleware?.length);
|
|
26
|
+
const hasReducers = Boolean(extensions.reducers && Object.keys(extensions.reducers).length);
|
|
27
|
+
|
|
28
|
+
if (!hasMiddleware && !hasReducers) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.entries.push({
|
|
33
|
+
source,
|
|
34
|
+
extensions: {
|
|
35
|
+
middleware: extensions.middleware,
|
|
36
|
+
reducers: extensions.reducers,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Drain collected extensions (used internally by the plugin)
|
|
43
|
+
*/
|
|
44
|
+
drain(): StoreExtensionsEntry[] {
|
|
45
|
+
const snapshot = this.entries;
|
|
46
|
+
this.entries = [];
|
|
47
|
+
return snapshot;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function withSourceMetadata(
|
|
52
|
+
middleware: StoreMiddlewareExtension | undefined,
|
|
53
|
+
source: string
|
|
54
|
+
): StoreMiddlewareExtension | undefined {
|
|
55
|
+
if (!middleware) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
plugin: middleware.plugin ?? source,
|
|
61
|
+
...middleware,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function applyStoreExtensions(
|
|
66
|
+
collector: StoreExtensionsCollector,
|
|
67
|
+
middlewareRegistry: IMiddlewareRegistry,
|
|
68
|
+
reducerRegistry: IReducerRegistry
|
|
69
|
+
): void {
|
|
70
|
+
const entries = collector.drain();
|
|
71
|
+
|
|
72
|
+
entries.forEach(({ source, extensions }) => {
|
|
73
|
+
extensions.middleware?.forEach((mw) => {
|
|
74
|
+
const middleware = withSourceMetadata(mw, source);
|
|
75
|
+
if (middleware) {
|
|
76
|
+
middlewareRegistry.register(middleware);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (extensions.reducers) {
|
|
81
|
+
Object.entries(extensions.reducers).forEach(([key, reducer]) => {
|
|
82
|
+
reducerRegistry.register(key, reducer);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createStoreExtensionsCollector(): StoreExtensionsCollector {
|
|
89
|
+
return new StoreExtensionsCollector();
|
|
90
|
+
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Event Bridge Middleware Tests
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, test, expect,
|
|
5
|
+
import { describe, test, expect, vi } from 'vitest';
|
|
6
6
|
import { createEventBridgeMiddleware } from './event-bridge-middleware';
|
|
7
7
|
import { createStore, applyMiddleware } from 'redux';
|
|
8
8
|
|
|
9
9
|
describe('createEventBridgeMiddleware', () => {
|
|
10
10
|
test('should create middleware', () => {
|
|
11
|
-
const hooks = { emit:
|
|
11
|
+
const hooks = { emit: vi.fn(() => {}) };
|
|
12
12
|
const middleware = createEventBridgeMiddleware(hooks);
|
|
13
13
|
|
|
14
14
|
expect(middleware).toBeDefined();
|
|
@@ -16,7 +16,7 @@ describe('createEventBridgeMiddleware', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('should emit before and after action events', () => {
|
|
19
|
-
const hooks = { emit:
|
|
19
|
+
const hooks = { emit: vi.fn(() => {}) };
|
|
20
20
|
const middleware = createEventBridgeMiddleware(hooks);
|
|
21
21
|
|
|
22
22
|
const reducer = (state = {}) => state;
|
|
@@ -37,7 +37,7 @@ describe('createEventBridgeMiddleware', () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test('should emit specific action type event', () => {
|
|
40
|
-
const hooks = { emit:
|
|
40
|
+
const hooks = { emit: vi.fn(() => {}) };
|
|
41
41
|
const middleware = createEventBridgeMiddleware(hooks);
|
|
42
42
|
|
|
43
43
|
const reducer = (state = {}) => state;
|
|
@@ -64,10 +64,10 @@ describe('createEventBridgeMiddleware', () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test('should pass action through middleware chain', () => {
|
|
67
|
-
const hooks = { emit:
|
|
67
|
+
const hooks = { emit: vi.fn(() => {}) };
|
|
68
68
|
const middleware = createEventBridgeMiddleware(hooks);
|
|
69
69
|
|
|
70
|
-
const reducer =
|
|
70
|
+
const reducer = vi.fn((state = { count: 0 }, action: any) => {
|
|
71
71
|
if (action.type === 'INCREMENT') {
|
|
72
72
|
return { count: state.count + 1 };
|
|
73
73
|
}
|
|
@@ -85,7 +85,7 @@ describe('createEventBridgeMiddleware', () => {
|
|
|
85
85
|
test('should emit events in correct order', () => {
|
|
86
86
|
const eventOrder: string[] = [];
|
|
87
87
|
const hooks = {
|
|
88
|
-
emit:
|
|
88
|
+
emit: vi.fn((event: string) => {
|
|
89
89
|
eventOrder.push(event);
|
|
90
90
|
}),
|
|
91
91
|
};
|
|
@@ -104,7 +104,7 @@ describe('createEventBridgeMiddleware', () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test('should include state in before/after events', () => {
|
|
107
|
-
const hooks = { emit:
|
|
107
|
+
const hooks = { emit: vi.fn(() => {}) };
|
|
108
108
|
const middleware = createEventBridgeMiddleware(hooks);
|
|
109
109
|
|
|
110
110
|
const reducer = (state = { value: 0 }, action: any) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Logger Middleware Tests
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, test, expect,
|
|
5
|
+
import { describe, test, expect, vi } from 'vitest';
|
|
6
6
|
import { createLoggerMiddleware } from './logger-middleware';
|
|
7
7
|
import { createStore, applyMiddleware } from 'redux';
|
|
8
8
|
|
|
@@ -15,9 +15,9 @@ describe('createLoggerMiddleware', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
test('should log actions with type', () => {
|
|
18
|
-
const consoleGroupSpy = spyOn(console, 'group');
|
|
19
|
-
const consoleLogSpy = spyOn(console, 'log');
|
|
20
|
-
const consoleGroupEndSpy = spyOn(console, 'groupEnd');
|
|
18
|
+
const consoleGroupSpy = vi.spyOn(console, 'group');
|
|
19
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
20
|
+
const consoleGroupEndSpy = vi.spyOn(console, 'groupEnd');
|
|
21
21
|
|
|
22
22
|
const middleware = createLoggerMiddleware();
|
|
23
23
|
const reducer = (state = { count: 0 }, action: any) => {
|
|
@@ -42,7 +42,7 @@ describe('createLoggerMiddleware', () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test('should not log actions without string type', () => {
|
|
45
|
-
const consoleGroupSpy = spyOn(console, 'group');
|
|
45
|
+
const consoleGroupSpy = vi.spyOn(console, 'group');
|
|
46
46
|
const middleware = createLoggerMiddleware();
|
|
47
47
|
|
|
48
48
|
const reducer = (state = {}) => state;
|
|
@@ -56,7 +56,7 @@ describe('createLoggerMiddleware', () => {
|
|
|
56
56
|
|
|
57
57
|
test('should pass action through middleware chain', () => {
|
|
58
58
|
const middleware = createLoggerMiddleware();
|
|
59
|
-
const reducer =
|
|
59
|
+
const reducer = vi.fn((state = {}) => state);
|
|
60
60
|
|
|
61
61
|
const store = createStore(reducer, applyMiddleware(middleware));
|
|
62
62
|
store.dispatch({ type: 'TEST' });
|
|
@@ -81,7 +81,7 @@ describe('createLoggerMiddleware', () => {
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
test('should log state before and after action', () => {
|
|
84
|
-
const consoleLogSpy = spyOn(console, 'log');
|
|
84
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
85
85
|
const middleware = createLoggerMiddleware();
|
|
86
86
|
|
|
87
87
|
const reducer = (state = { value: 0 }, action: any) => {
|
|
@@ -101,7 +101,7 @@ describe('createLoggerMiddleware', () => {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test('should work with multiple actions', () => {
|
|
104
|
-
const consoleGroupSpy = spyOn(console, 'group');
|
|
104
|
+
const consoleGroupSpy = vi.spyOn(console, 'group');
|
|
105
105
|
const middleware = createLoggerMiddleware();
|
|
106
106
|
|
|
107
107
|
const reducer = (state = { count: 0 }, action: any) => {
|
|
@@ -4,34 +4,30 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { PluginModule } from '@hamak/microkernel-spi';
|
|
7
|
-
import type { Reducer } from 'redux';
|
|
8
7
|
import {
|
|
9
8
|
STORE_MANAGER_TOKEN,
|
|
10
9
|
MIDDLEWARE_REGISTRY_TOKEN,
|
|
11
10
|
REDUCER_REGISTRY_TOKEN,
|
|
11
|
+
STORE_EXTENSIONS_TOKEN,
|
|
12
|
+
type StoreMiddlewareExtension,
|
|
13
|
+
type StorePluginExtensions,
|
|
12
14
|
} from '@hamak/ui-store-api';
|
|
13
15
|
import { StoreManager } from '../core/store-manager';
|
|
14
16
|
import {
|
|
15
17
|
createEventBridgeMiddleware,
|
|
16
18
|
createLoggerMiddleware,
|
|
17
19
|
} from '../middleware';
|
|
20
|
+
import {
|
|
21
|
+
applyStoreExtensions,
|
|
22
|
+
createStoreExtensionsCollector,
|
|
23
|
+
} from '../extensions/store-extensions';
|
|
18
24
|
|
|
19
|
-
export interface StorePluginConfig {
|
|
25
|
+
export interface StorePluginConfig extends StorePluginExtensions {
|
|
20
26
|
/** Enable Redux DevTools integration */
|
|
21
27
|
devTools?: boolean;
|
|
22
28
|
|
|
23
29
|
/** Enable logger middleware in development */
|
|
24
30
|
logger?: boolean;
|
|
25
|
-
|
|
26
|
-
/** Initial middleware to register */
|
|
27
|
-
middleware?: Array<{
|
|
28
|
-
id: string;
|
|
29
|
-
middleware: any;
|
|
30
|
-
priority?: number;
|
|
31
|
-
}>;
|
|
32
|
-
|
|
33
|
-
/** Initial reducers to register */
|
|
34
|
-
reducers?: Record<string, Reducer>;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export function createStorePlugin(
|
|
@@ -40,6 +36,57 @@ export function createStorePlugin(
|
|
|
40
36
|
const storeManager = new StoreManager();
|
|
41
37
|
const middlewareRegistry = storeManager.getMiddlewareRegistry();
|
|
42
38
|
const reducerRegistry = storeManager.getReducerRegistry();
|
|
39
|
+
const extensionsCollector = createStoreExtensionsCollector();
|
|
40
|
+
|
|
41
|
+
const registerDefaultMiddleware = (hooks: any) => {
|
|
42
|
+
extensionsCollector.register('ui-store:event-bridge', {
|
|
43
|
+
middleware: [
|
|
44
|
+
{
|
|
45
|
+
id: 'event-bridge',
|
|
46
|
+
middleware: createEventBridgeMiddleware(hooks),
|
|
47
|
+
priority: 1000,
|
|
48
|
+
plugin: 'ui-store',
|
|
49
|
+
description: 'Bridges Redux actions to microkernel events',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
config.logger !== false &&
|
|
56
|
+
process.env.NODE_ENV === 'development'
|
|
57
|
+
) {
|
|
58
|
+
extensionsCollector.register('ui-store:logger', {
|
|
59
|
+
middleware: [
|
|
60
|
+
{
|
|
61
|
+
id: 'logger',
|
|
62
|
+
middleware: createLoggerMiddleware(),
|
|
63
|
+
priority: -1000,
|
|
64
|
+
plugin: 'ui-store',
|
|
65
|
+
description: 'Development logger',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const registerConfigExtensions = () => {
|
|
73
|
+
if (config.middleware?.length) {
|
|
74
|
+
const middleware: StoreMiddlewareExtension[] = config.middleware.map((mw) => ({
|
|
75
|
+
...mw,
|
|
76
|
+
plugin: mw.plugin ?? 'ui-store-config',
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
extensionsCollector.register('ui-store-config:middleware', {
|
|
80
|
+
middleware,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (config.reducers && Object.keys(config.reducers).length) {
|
|
85
|
+
extensionsCollector.register('ui-store-config:reducers', {
|
|
86
|
+
reducers: config.reducers,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
43
90
|
|
|
44
91
|
return {
|
|
45
92
|
async initialize(ctx) {
|
|
@@ -53,53 +100,24 @@ export function createStorePlugin(
|
|
|
53
100
|
provide: REDUCER_REGISTRY_TOKEN,
|
|
54
101
|
useValue: reducerRegistry,
|
|
55
102
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
id: 'event-bridge',
|
|
60
|
-
middleware: createEventBridgeMiddleware(ctx.hooks),
|
|
61
|
-
priority: 1000,
|
|
62
|
-
plugin: 'ui-store',
|
|
63
|
-
description: 'Bridges Redux actions to microkernel events',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Register logger middleware in development (low priority - runs last)
|
|
67
|
-
if (
|
|
68
|
-
config.logger !== false &&
|
|
69
|
-
process.env.NODE_ENV === 'development'
|
|
70
|
-
) {
|
|
71
|
-
middlewareRegistry.register({
|
|
72
|
-
id: 'logger',
|
|
73
|
-
middleware: createLoggerMiddleware(),
|
|
74
|
-
priority: -1000,
|
|
75
|
-
plugin: 'ui-store',
|
|
76
|
-
description: 'Development logger',
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Register user-provided middleware
|
|
81
|
-
config.middleware?.forEach((mw) => {
|
|
82
|
-
middlewareRegistry.register({
|
|
83
|
-
...mw,
|
|
84
|
-
plugin: 'ui-store-config',
|
|
85
|
-
});
|
|
103
|
+
ctx.provide({
|
|
104
|
+
provide: STORE_EXTENSIONS_TOKEN,
|
|
105
|
+
useValue: extensionsCollector,
|
|
86
106
|
});
|
|
87
107
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.log('[ui-store] Registering reducers:', Object.keys(config.reducers));
|
|
91
|
-
Object.entries(config.reducers).forEach(([key, reducer]) => {
|
|
92
|
-
reducerRegistry.register(key, reducer);
|
|
93
|
-
console.log('[ui-store] Registered reducer:', key);
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
console.log('[ui-store] No reducers provided in config');
|
|
97
|
-
}
|
|
108
|
+
registerDefaultMiddleware(ctx.hooks);
|
|
109
|
+
registerConfigExtensions();
|
|
98
110
|
|
|
99
111
|
console.log('[ui-store] Plugin initialized');
|
|
100
112
|
},
|
|
101
113
|
|
|
102
114
|
async activate(ctx) {
|
|
115
|
+
applyStoreExtensions(
|
|
116
|
+
extensionsCollector,
|
|
117
|
+
middlewareRegistry,
|
|
118
|
+
reducerRegistry
|
|
119
|
+
);
|
|
120
|
+
|
|
103
121
|
// Initialize the store after all plugins have registered middleware/reducers
|
|
104
122
|
const store = storeManager.initialize({
|
|
105
123
|
devTools: config.devTools,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2015",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2015",
|
|
7
|
+
"DOM"
|
|
8
|
+
],
|
|
9
|
+
"outDir": "./dist/es2015",
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"declarationMap": false,
|
|
12
|
+
"sourceMap": false,
|
|
13
|
+
"downlevelIteration": true,
|
|
14
|
+
"composite": false
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/**/*"
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules",
|
|
21
|
+
"dist",
|
|
22
|
+
"**/*.test.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|