@hamak/ui-store-impl 0.2.2 → 0.2.4
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 -2
- package/.turbo/turbo-test.log +115 -0
- package/dist/es2015/extensions/store-extensions.js +61 -0
- package/dist/es2015/plugin/store-plugin-factory.js +53 -35
- 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 +5 -5
- 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/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
[0m[2m[35m$[0m [2m[1mtsc -p tsconfig.json && tsc -p tsconfig.es2015.json[0m
|
|
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,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
|
+
}
|
|
@@ -11,17 +11,62 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
12
|
});
|
|
13
13
|
};
|
|
14
|
-
import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, } from '@hamak/ui-store-api';
|
|
14
|
+
import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN, } from '@hamak/ui-store-api';
|
|
15
15
|
import { StoreManager } from '../core/store-manager';
|
|
16
16
|
import { createEventBridgeMiddleware, createLoggerMiddleware, } from '../middleware';
|
|
17
|
+
import { applyStoreExtensions, createStoreExtensionsCollector, } from '../extensions/store-extensions';
|
|
17
18
|
export function createStorePlugin(config = {}) {
|
|
18
19
|
const storeManager = new StoreManager();
|
|
19
20
|
const middlewareRegistry = storeManager.getMiddlewareRegistry();
|
|
20
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
|
+
};
|
|
21
67
|
return {
|
|
22
68
|
initialize(ctx) {
|
|
23
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
var _a;
|
|
25
70
|
// Register services via DI
|
|
26
71
|
ctx.provide({ provide: STORE_MANAGER_TOKEN, useValue: storeManager });
|
|
27
72
|
ctx.provide({
|
|
@@ -32,45 +77,18 @@ export function createStorePlugin(config = {}) {
|
|
|
32
77
|
provide: REDUCER_REGISTRY_TOKEN,
|
|
33
78
|
useValue: reducerRegistry,
|
|
34
79
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
middleware: createEventBridgeMiddleware(ctx.hooks),
|
|
39
|
-
priority: 1000,
|
|
40
|
-
plugin: 'ui-store',
|
|
41
|
-
description: 'Bridges Redux actions to microkernel events',
|
|
42
|
-
});
|
|
43
|
-
// Register logger middleware in development (low priority - runs last)
|
|
44
|
-
if (config.logger !== false &&
|
|
45
|
-
process.env.NODE_ENV === 'development') {
|
|
46
|
-
middlewareRegistry.register({
|
|
47
|
-
id: 'logger',
|
|
48
|
-
middleware: createLoggerMiddleware(),
|
|
49
|
-
priority: -1000,
|
|
50
|
-
plugin: 'ui-store',
|
|
51
|
-
description: 'Development logger',
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
// Register user-provided middleware
|
|
55
|
-
(_a = config.middleware) === null || _a === void 0 ? void 0 : _a.forEach((mw) => {
|
|
56
|
-
middlewareRegistry.register(Object.assign(Object.assign({}, mw), { plugin: 'ui-store-config' }));
|
|
80
|
+
ctx.provide({
|
|
81
|
+
provide: STORE_EXTENSIONS_TOKEN,
|
|
82
|
+
useValue: extensionsCollector,
|
|
57
83
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log('[ui-store] Registering reducers:', Object.keys(config.reducers));
|
|
61
|
-
Object.entries(config.reducers).forEach(([key, reducer]) => {
|
|
62
|
-
reducerRegistry.register(key, reducer);
|
|
63
|
-
console.log('[ui-store] Registered reducer:', key);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
console.log('[ui-store] No reducers provided in config');
|
|
68
|
-
}
|
|
84
|
+
registerDefaultMiddleware(ctx.hooks);
|
|
85
|
+
registerConfigExtensions();
|
|
69
86
|
console.log('[ui-store] Plugin initialized');
|
|
70
87
|
});
|
|
71
88
|
},
|
|
72
89
|
activate(ctx) {
|
|
73
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
applyStoreExtensions(extensionsCollector, middlewareRegistry, reducerRegistry);
|
|
74
92
|
// Initialize the store after all plugins have registered middleware/reducers
|
|
75
93
|
const store = storeManager.initialize({
|
|
76
94
|
devTools: config.devTools,
|
|
@@ -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.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "UI Store Implementation - Redux store implementation with middleware",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@hamak/ui-store-api": "0.2.
|
|
38
|
-
"@hamak/ui-store-spi": "0.2.
|
|
39
|
-
"@hamak/microkernel-api": "0.2.
|
|
40
|
-
"@hamak/microkernel-spi": "0.2.
|
|
37
|
+
"@hamak/ui-store-api": "0.2.4",
|
|
38
|
+
"@hamak/ui-store-spi": "0.2.4",
|
|
39
|
+
"@hamak/microkernel-api": "0.2.4",
|
|
40
|
+
"@hamak/microkernel-spi": "0.2.4",
|
|
41
41
|
"redux": "^5.0.1",
|
|
42
42
|
"redux-thunk": "^3.1.0"
|
|
43
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,
|