@blac/core 2.0.0-rc-14 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/Blac.d.ts +246 -0
- package/dist/Blac.d.ts.map +1 -0
- package/dist/Blac.js +625 -0
- package/dist/Blac.js.map +1 -0
- package/dist/Bloc.d.ts +41 -0
- package/dist/Bloc.d.ts.map +1 -0
- package/dist/Bloc.js +140 -0
- package/dist/Bloc.js.map +1 -0
- package/dist/BlocBase.d.ts +110 -0
- package/dist/BlocBase.d.ts.map +1 -0
- package/dist/BlocBase.js +287 -0
- package/dist/BlocBase.js.map +1 -0
- package/dist/Cubit.d.ts +25 -0
- package/dist/Cubit.d.ts.map +1 -0
- package/dist/Cubit.js +64 -0
- package/dist/Cubit.js.map +1 -0
- package/dist/adapter/BlacAdapter.d.ts +71 -0
- package/dist/adapter/BlacAdapter.d.ts.map +1 -0
- package/dist/adapter/BlacAdapter.js +373 -0
- package/dist/adapter/BlacAdapter.js.map +1 -0
- package/dist/adapter/ProxyFactory.d.ts +50 -0
- package/dist/adapter/ProxyFactory.d.ts.map +1 -0
- package/dist/adapter/ProxyFactory.js +150 -0
- package/dist/adapter/ProxyFactory.js.map +1 -0
- package/{src/adapter/index.ts → dist/adapter/index.d.ts} +1 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +19 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/errors/BlacError.d.ts +38 -0
- package/dist/errors/BlacError.d.ts.map +1 -0
- package/dist/errors/BlacError.js +55 -0
- package/dist/errors/BlacError.js.map +1 -0
- package/dist/errors/ErrorManager.d.ts +39 -0
- package/dist/errors/ErrorManager.d.ts.map +1 -0
- package/dist/errors/ErrorManager.js +91 -0
- package/dist/errors/ErrorManager.js.map +1 -0
- package/dist/errors/handleError.d.ts +24 -0
- package/dist/errors/handleError.d.ts.map +1 -0
- package/dist/errors/handleError.js +62 -0
- package/dist/errors/handleError.js.map +1 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/index.js.map +1 -0
- package/{src/events.ts → dist/events.d.ts} +4 -2
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +17 -0
- package/dist/events.js.map +1 -0
- package/dist/index.cjs +38 -0
- package/dist/index.d.ts +17 -1303
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/BlocLifecycle.d.ts +51 -0
- package/dist/lifecycle/BlocLifecycle.d.ts.map +1 -0
- package/dist/lifecycle/BlocLifecycle.js +123 -0
- package/dist/lifecycle/BlocLifecycle.js.map +1 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +18 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/plugins/BlocPluginRegistry.d.ts +58 -0
- package/dist/plugins/BlocPluginRegistry.d.ts.map +1 -0
- package/dist/plugins/BlocPluginRegistry.js +201 -0
- package/dist/plugins/BlocPluginRegistry.js.map +1 -0
- package/dist/plugins/SystemPluginRegistry.d.ts +79 -0
- package/dist/plugins/SystemPluginRegistry.d.ts.map +1 -0
- package/dist/plugins/SystemPluginRegistry.js +184 -0
- package/dist/plugins/SystemPluginRegistry.js.map +1 -0
- package/{src/plugins/index.ts → dist/plugins/index.d.ts} +1 -5
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +26 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +103 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +3 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/subscription/SubscriptionManager.d.ts +67 -0
- package/dist/subscription/SubscriptionManager.d.ts.map +1 -0
- package/dist/subscription/SubscriptionManager.js +317 -0
- package/dist/subscription/SubscriptionManager.js.map +1 -0
- package/{src/subscription/index.ts → dist/subscription/index.d.ts} +1 -0
- package/dist/subscription/index.d.ts.map +1 -0
- package/dist/subscription/index.js +19 -0
- package/dist/subscription/index.js.map +1 -0
- package/dist/subscription/types.d.ts +65 -0
- package/dist/subscription/types.d.ts.map +1 -0
- package/dist/subscription/types.js +3 -0
- package/dist/subscription/types.js.map +1 -0
- package/dist/testing.d.ts +85 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +186 -0
- package/dist/testing.js.map +1 -0
- package/{src/types.ts → dist/types.d.ts} +11 -28
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/BatchingManager.d.ts +28 -0
- package/dist/utils/BatchingManager.d.ts.map +1 -0
- package/dist/utils/BatchingManager.js +48 -0
- package/dist/utils/BatchingManager.js.map +1 -0
- package/dist/utils/RerenderLogger.d.ts +60 -0
- package/dist/utils/RerenderLogger.d.ts.map +1 -0
- package/dist/utils/RerenderLogger.js +205 -0
- package/dist/utils/RerenderLogger.js.map +1 -0
- package/dist/utils/generateInstanceId.d.ts +11 -0
- package/dist/utils/generateInstanceId.d.ts.map +1 -0
- package/dist/utils/generateInstanceId.js +50 -0
- package/dist/utils/generateInstanceId.js.map +1 -0
- package/dist/utils/shallowEqual.d.ts +9 -0
- package/dist/utils/shallowEqual.d.ts.map +1 -0
- package/dist/utils/shallowEqual.js +35 -0
- package/dist/utils/shallowEqual.js.map +1 -0
- package/dist/utils/uuid.d.ts +10 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +24 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +17 -10
- package/dist/index.cjs.js +0 -11
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -2275
- package/dist/index.es.js.map +0 -1
- package/src/Blac.ts +0 -802
- package/src/Bloc.ts +0 -180
- package/src/BlocBase.ts +0 -398
- package/src/Cubit.ts +0 -68
- package/src/__tests__/Blac.config.test.ts +0 -79
- package/src/__tests__/Bloc.event.test.ts +0 -394
- package/src/__tests__/BlocBase.subscription.test.ts +0 -349
- package/src/__tests__/Cubit.test.ts +0 -462
- package/src/__tests__/memory-leaks.test.ts +0 -383
- package/src/__tests__/plugins.test.ts +0 -464
- package/src/adapter/BlacAdapter.ts +0 -467
- package/src/adapter/ProxyFactory.ts +0 -249
- package/src/adapter/__tests__/BlacAdapter.test.ts +0 -555
- package/src/adapter/__tests__/ProxyFactory.test.ts +0 -655
- package/src/errors/BlacError.ts +0 -69
- package/src/errors/ErrorManager.ts +0 -101
- package/src/errors/handleError.ts +0 -114
- package/src/errors/index.ts +0 -13
- package/src/index.ts +0 -26
- package/src/lifecycle/BlocLifecycle.ts +0 -156
- package/src/lifecycle/index.ts +0 -1
- package/src/plugins/BlocPluginRegistry.ts +0 -240
- package/src/plugins/SystemPluginRegistry.ts +0 -242
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +0 -450
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +0 -404
- package/src/plugins/types.ts +0 -129
- package/src/subscription/README.md +0 -132
- package/src/subscription/SubscriptionManager.ts +0 -395
- package/src/subscription/__tests__/SubscriptionManager.test.ts +0 -355
- package/src/subscription/types.ts +0 -90
- package/src/testing.ts +0 -246
- package/src/utils/BatchingManager.ts +0 -59
- package/src/utils/RerenderLogger.ts +0 -265
- package/src/utils/__tests__/generateInstanceId.test.ts +0 -104
- package/src/utils/__tests__/shallowEqual.test.ts +0 -171
- package/src/utils/__tests__/uuid.test.ts +0 -144
- package/src/utils/generateInstanceId.ts +0 -54
- package/src/utils/shallowEqual.ts +0 -40
- package/src/utils/uuid.ts +0 -22
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { SystemPluginRegistry } from '../SystemPluginRegistry';
|
|
3
|
-
import { BlacPlugin } from '../types';
|
|
4
|
-
import { BlocBase } from '../../BlocBase';
|
|
5
|
-
import { Bloc } from '../../Bloc';
|
|
6
|
-
|
|
7
|
-
// Mock bloc for testing
|
|
8
|
-
class MockBloc extends BlocBase<number> {
|
|
9
|
-
constructor(initialState = 0) {
|
|
10
|
-
super(initialState);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Mock event bloc
|
|
15
|
-
class MockEventBloc extends Bloc<number, { type: string }> {
|
|
16
|
-
constructor() {
|
|
17
|
-
super(0);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Helper to create test plugins
|
|
22
|
-
const createTestPlugin = (
|
|
23
|
-
name: string,
|
|
24
|
-
hooks?: Partial<BlacPlugin>,
|
|
25
|
-
): BlacPlugin => ({
|
|
26
|
-
name,
|
|
27
|
-
version: '1.0.0',
|
|
28
|
-
...hooks,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('SystemPluginRegistry', () => {
|
|
32
|
-
let registry: SystemPluginRegistry;
|
|
33
|
-
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
registry = new SystemPluginRegistry();
|
|
36
|
-
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('Plugin Management', () => {
|
|
40
|
-
it('should add plugins successfully', () => {
|
|
41
|
-
const plugin = createTestPlugin('test-plugin');
|
|
42
|
-
|
|
43
|
-
registry.add(plugin);
|
|
44
|
-
|
|
45
|
-
expect(registry.get('test-plugin')).toBe(plugin);
|
|
46
|
-
expect(registry.getAll()).toContain(plugin);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should throw error when adding duplicate plugin', () => {
|
|
50
|
-
const plugin = createTestPlugin('test-plugin');
|
|
51
|
-
|
|
52
|
-
registry.add(plugin);
|
|
53
|
-
|
|
54
|
-
expect(() => registry.add(plugin)).toThrow(
|
|
55
|
-
"Plugin 'test-plugin' is already registered",
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should maintain execution order', () => {
|
|
60
|
-
const plugin1 = createTestPlugin('plugin-1');
|
|
61
|
-
const plugin2 = createTestPlugin('plugin-2');
|
|
62
|
-
const plugin3 = createTestPlugin('plugin-3');
|
|
63
|
-
|
|
64
|
-
registry.add(plugin1);
|
|
65
|
-
registry.add(plugin2);
|
|
66
|
-
registry.add(plugin3);
|
|
67
|
-
|
|
68
|
-
const all = registry.getAll();
|
|
69
|
-
expect(all[0]).toBe(plugin1);
|
|
70
|
-
expect(all[1]).toBe(plugin2);
|
|
71
|
-
expect(all[2]).toBe(plugin3);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should remove plugins and clean up metrics', () => {
|
|
75
|
-
const plugin = createTestPlugin('test-plugin');
|
|
76
|
-
|
|
77
|
-
registry.add(plugin);
|
|
78
|
-
expect(registry.get('test-plugin')).toBe(plugin);
|
|
79
|
-
expect(registry.getMetrics('test-plugin')).toBeDefined();
|
|
80
|
-
|
|
81
|
-
const removed = registry.remove('test-plugin');
|
|
82
|
-
expect(removed).toBe(true);
|
|
83
|
-
expect(registry.get('test-plugin')).toBeUndefined();
|
|
84
|
-
expect(registry.getAll()).not.toContain(plugin);
|
|
85
|
-
expect(registry.getMetrics('test-plugin')).toBeUndefined();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should return false when removing non-existent plugin', () => {
|
|
89
|
-
const removed = registry.remove('non-existent');
|
|
90
|
-
expect(removed).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should clear all plugins and metrics', () => {
|
|
94
|
-
registry.add(createTestPlugin('plugin-1'));
|
|
95
|
-
registry.add(createTestPlugin('plugin-2'));
|
|
96
|
-
registry.add(createTestPlugin('plugin-3'));
|
|
97
|
-
|
|
98
|
-
expect(registry.getAll().length).toBe(3);
|
|
99
|
-
|
|
100
|
-
registry.clear();
|
|
101
|
-
|
|
102
|
-
expect(registry.getAll().length).toBe(0);
|
|
103
|
-
expect(registry.getMetrics('plugin-1')).toBeUndefined();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Hook Execution', () => {
|
|
108
|
-
it('should execute hooks on all plugins', () => {
|
|
109
|
-
const hook1 = vi.fn();
|
|
110
|
-
const hook2 = vi.fn();
|
|
111
|
-
const hook3 = vi.fn();
|
|
112
|
-
|
|
113
|
-
registry.add(createTestPlugin('plugin-1', { onBlocCreated: hook1 }));
|
|
114
|
-
registry.add(createTestPlugin('plugin-2', { onBlocCreated: hook2 }));
|
|
115
|
-
registry.add(createTestPlugin('plugin-3', { onBlocCreated: hook3 }));
|
|
116
|
-
|
|
117
|
-
const bloc = new MockBloc();
|
|
118
|
-
registry.executeHook('onBlocCreated', [bloc]);
|
|
119
|
-
|
|
120
|
-
expect(hook1).toHaveBeenCalledWith(bloc);
|
|
121
|
-
expect(hook2).toHaveBeenCalledWith(bloc);
|
|
122
|
-
expect(hook3).toHaveBeenCalledWith(bloc);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should skip plugins without the specified hook', () => {
|
|
126
|
-
const hook1 = vi.fn();
|
|
127
|
-
const hook3 = vi.fn();
|
|
128
|
-
|
|
129
|
-
registry.add(createTestPlugin('plugin-1', { onBlocCreated: hook1 }));
|
|
130
|
-
registry.add(createTestPlugin('plugin-2')); // No hook
|
|
131
|
-
registry.add(createTestPlugin('plugin-3', { onBlocCreated: hook3 }));
|
|
132
|
-
|
|
133
|
-
const bloc = new MockBloc();
|
|
134
|
-
registry.executeHook('onBlocCreated', [bloc]);
|
|
135
|
-
|
|
136
|
-
expect(hook1).toHaveBeenCalledWith(bloc);
|
|
137
|
-
expect(hook3).toHaveBeenCalledWith(bloc);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should handle hook errors and continue execution', () => {
|
|
141
|
-
const hook1 = vi.fn(() => {
|
|
142
|
-
throw new Error('Hook 1 error');
|
|
143
|
-
});
|
|
144
|
-
const hook2 = vi.fn();
|
|
145
|
-
const hook3 = vi.fn();
|
|
146
|
-
|
|
147
|
-
registry.add(createTestPlugin('plugin-1', { onBlocCreated: hook1 }));
|
|
148
|
-
registry.add(createTestPlugin('plugin-2', { onBlocCreated: hook2 }));
|
|
149
|
-
registry.add(createTestPlugin('plugin-3', { onBlocCreated: hook3 }));
|
|
150
|
-
|
|
151
|
-
const bloc = new MockBloc();
|
|
152
|
-
registry.executeHook('onBlocCreated', [bloc]);
|
|
153
|
-
|
|
154
|
-
expect(hook1).toHaveBeenCalled();
|
|
155
|
-
expect(hook2).toHaveBeenCalled();
|
|
156
|
-
expect(hook3).toHaveBeenCalled();
|
|
157
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
158
|
-
expect.stringContaining(
|
|
159
|
-
"Plugin 'plugin-1' error in hook 'onBlocCreated'",
|
|
160
|
-
),
|
|
161
|
-
expect.any(Error),
|
|
162
|
-
);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should use custom error handler if provided', () => {
|
|
166
|
-
const errorHandler = vi.fn();
|
|
167
|
-
const failingHook = vi.fn(() => {
|
|
168
|
-
throw new Error('Test error');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const plugin = createTestPlugin('failing-plugin', {
|
|
172
|
-
onBlocCreated: failingHook,
|
|
173
|
-
});
|
|
174
|
-
registry.add(plugin);
|
|
175
|
-
|
|
176
|
-
const bloc = new MockBloc();
|
|
177
|
-
registry.executeHook('onBlocCreated', [bloc], errorHandler);
|
|
178
|
-
|
|
179
|
-
expect(errorHandler).toHaveBeenCalledWith(expect.any(Error), plugin);
|
|
180
|
-
expect(console.error).not.toHaveBeenCalled();
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe('Lifecycle Hooks', () => {
|
|
185
|
-
it('should execute bootstrap hooks', () => {
|
|
186
|
-
const beforeBootstrap1 = vi.fn();
|
|
187
|
-
const beforeBootstrap2 = vi.fn();
|
|
188
|
-
const afterBootstrap1 = vi.fn();
|
|
189
|
-
const afterBootstrap2 = vi.fn();
|
|
190
|
-
|
|
191
|
-
registry.add(
|
|
192
|
-
createTestPlugin('plugin-1', {
|
|
193
|
-
beforeBootstrap: beforeBootstrap1,
|
|
194
|
-
afterBootstrap: afterBootstrap1,
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
registry.add(
|
|
198
|
-
createTestPlugin('plugin-2', {
|
|
199
|
-
beforeBootstrap: beforeBootstrap2,
|
|
200
|
-
afterBootstrap: afterBootstrap2,
|
|
201
|
-
}),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
registry.bootstrap();
|
|
205
|
-
|
|
206
|
-
expect(beforeBootstrap1).toHaveBeenCalled();
|
|
207
|
-
expect(beforeBootstrap2).toHaveBeenCalled();
|
|
208
|
-
expect(afterBootstrap1).toHaveBeenCalled();
|
|
209
|
-
expect(afterBootstrap2).toHaveBeenCalled();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should execute shutdown hooks', () => {
|
|
213
|
-
const beforeShutdown = vi.fn();
|
|
214
|
-
const afterShutdown = vi.fn();
|
|
215
|
-
|
|
216
|
-
registry.add(
|
|
217
|
-
createTestPlugin('plugin-1', {
|
|
218
|
-
beforeShutdown,
|
|
219
|
-
afterShutdown,
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
registry.shutdown();
|
|
224
|
-
|
|
225
|
-
expect(beforeShutdown).toHaveBeenCalled();
|
|
226
|
-
expect(afterShutdown).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
describe('Bloc Lifecycle Notifications', () => {
|
|
231
|
-
it('should notify plugins of bloc creation', () => {
|
|
232
|
-
const onBlocCreated = vi.fn();
|
|
233
|
-
|
|
234
|
-
registry.add(createTestPlugin('plugin-1', { onBlocCreated }));
|
|
235
|
-
|
|
236
|
-
const bloc = new MockBloc();
|
|
237
|
-
registry.notifyBlocCreated(bloc);
|
|
238
|
-
|
|
239
|
-
expect(onBlocCreated).toHaveBeenCalledWith(bloc);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should notify plugins of bloc disposal', () => {
|
|
243
|
-
const onBlocDisposed = vi.fn();
|
|
244
|
-
|
|
245
|
-
registry.add(createTestPlugin('plugin-1', { onBlocDisposed }));
|
|
246
|
-
|
|
247
|
-
const bloc = new MockBloc();
|
|
248
|
-
registry.notifyBlocDisposed(bloc);
|
|
249
|
-
|
|
250
|
-
expect(onBlocDisposed).toHaveBeenCalledWith(bloc);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should notify plugins of state changes', () => {
|
|
254
|
-
const onStateChanged = vi.fn();
|
|
255
|
-
|
|
256
|
-
registry.add(createTestPlugin('plugin-1', { onStateChanged }));
|
|
257
|
-
|
|
258
|
-
const bloc = new MockBloc();
|
|
259
|
-
const prevState = 0;
|
|
260
|
-
const currState = 1;
|
|
261
|
-
|
|
262
|
-
registry.notifyStateChanged(bloc, prevState, currState);
|
|
263
|
-
|
|
264
|
-
expect(onStateChanged).toHaveBeenCalledWith(bloc, prevState, currState);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('should notify plugins of event additions', () => {
|
|
268
|
-
const onEventAdded = vi.fn();
|
|
269
|
-
|
|
270
|
-
registry.add(createTestPlugin('plugin-1', { onEventAdded }));
|
|
271
|
-
|
|
272
|
-
const bloc = new MockEventBloc();
|
|
273
|
-
const event = 'test-event';
|
|
274
|
-
|
|
275
|
-
registry.notifyEventAdded(bloc, event);
|
|
276
|
-
|
|
277
|
-
expect(onEventAdded).toHaveBeenCalledWith(bloc, event);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should notify plugins of errors with double fault protection', () => {
|
|
281
|
-
const onError1 = vi.fn();
|
|
282
|
-
const onError2 = vi.fn(() => {
|
|
283
|
-
throw new Error('Error handler failed');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
registry.add(createTestPlugin('plugin-1', { onError: onError1 }));
|
|
287
|
-
registry.add(createTestPlugin('plugin-2', { onError: onError2 }));
|
|
288
|
-
|
|
289
|
-
const error = new Error('Test error');
|
|
290
|
-
const bloc = new MockBloc();
|
|
291
|
-
const context = {
|
|
292
|
-
phase: 'state-change' as const,
|
|
293
|
-
operation: 'test-operation',
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
registry.notifyError(error, bloc as any, context);
|
|
297
|
-
|
|
298
|
-
expect(onError1).toHaveBeenCalledWith(error, bloc, context);
|
|
299
|
-
expect(onError2).toHaveBeenCalledWith(error, bloc, context);
|
|
300
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
301
|
-
expect.stringContaining("Plugin 'plugin-2' error handler failed:"),
|
|
302
|
-
expect.any(Error),
|
|
303
|
-
);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe('Metrics Tracking', () => {
|
|
308
|
-
it('should initialize metrics for new plugins', () => {
|
|
309
|
-
registry.add(createTestPlugin('test-plugin'));
|
|
310
|
-
|
|
311
|
-
const metrics = registry.getMetrics('test-plugin');
|
|
312
|
-
expect(metrics).toBeDefined();
|
|
313
|
-
expect(metrics).toBeInstanceOf(Map);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('should record successful hook executions', () => {
|
|
317
|
-
const hook = vi.fn();
|
|
318
|
-
registry.add(createTestPlugin('test-plugin', { onBlocCreated: hook }));
|
|
319
|
-
|
|
320
|
-
const bloc = new MockBloc();
|
|
321
|
-
registry.notifyBlocCreated(bloc);
|
|
322
|
-
|
|
323
|
-
const metrics = registry.getMetrics('test-plugin');
|
|
324
|
-
const hookMetrics = metrics?.get('onBlocCreated');
|
|
325
|
-
|
|
326
|
-
expect(hookMetrics).toBeDefined();
|
|
327
|
-
expect(hookMetrics?.executionCount).toBe(1);
|
|
328
|
-
expect(hookMetrics?.errorCount).toBe(0);
|
|
329
|
-
expect(hookMetrics?.executionTime).toBeGreaterThan(0);
|
|
330
|
-
expect(hookMetrics?.lastExecutionTime).toBeGreaterThan(0);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('should record hook errors', () => {
|
|
334
|
-
const error = new Error('Hook error');
|
|
335
|
-
const hook = vi.fn(() => {
|
|
336
|
-
throw error;
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
registry.add(createTestPlugin('test-plugin', { onBlocCreated: hook }));
|
|
340
|
-
|
|
341
|
-
const bloc = new MockBloc();
|
|
342
|
-
registry.notifyBlocCreated(bloc);
|
|
343
|
-
|
|
344
|
-
const metrics = registry.getMetrics('test-plugin');
|
|
345
|
-
const hookMetrics = metrics?.get('onBlocCreated');
|
|
346
|
-
|
|
347
|
-
expect(hookMetrics).toBeDefined();
|
|
348
|
-
expect(hookMetrics?.errorCount).toBe(1);
|
|
349
|
-
expect(hookMetrics?.lastError).toBe(error);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should accumulate metrics over multiple executions', () => {
|
|
353
|
-
const hook = vi.fn();
|
|
354
|
-
registry.add(createTestPlugin('test-plugin', { onBlocCreated: hook }));
|
|
355
|
-
|
|
356
|
-
const bloc = new MockBloc();
|
|
357
|
-
|
|
358
|
-
// Execute multiple times
|
|
359
|
-
for (let i = 0; i < 5; i++) {
|
|
360
|
-
registry.notifyBlocCreated(bloc);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const metrics = registry.getMetrics('test-plugin');
|
|
364
|
-
const hookMetrics = metrics?.get('onBlocCreated');
|
|
365
|
-
|
|
366
|
-
expect(hookMetrics?.executionCount).toBe(5);
|
|
367
|
-
expect(hookMetrics?.executionTime).toBeGreaterThan(0);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it('should return undefined metrics for non-existent plugin', () => {
|
|
371
|
-
const metrics = registry.getMetrics('non-existent');
|
|
372
|
-
expect(metrics).toBeUndefined();
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
describe('Edge Cases', () => {
|
|
377
|
-
it('should handle plugins with context binding correctly', () => {
|
|
378
|
-
let capturedThis: any;
|
|
379
|
-
const plugin = createTestPlugin('test-plugin', {
|
|
380
|
-
onBlocCreated: function (this: any) {
|
|
381
|
-
capturedThis = this;
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
registry.add(plugin);
|
|
386
|
-
registry.notifyBlocCreated(new MockBloc());
|
|
387
|
-
|
|
388
|
-
expect(capturedThis).toBe(plugin);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it('should handle multiple rapid hook executions', () => {
|
|
392
|
-
const hook = vi.fn();
|
|
393
|
-
registry.add(createTestPlugin('test-plugin', { onBlocCreated: hook }));
|
|
394
|
-
|
|
395
|
-
const bloc = new MockBloc();
|
|
396
|
-
|
|
397
|
-
for (let i = 0; i < 100; i++) {
|
|
398
|
-
registry.notifyBlocCreated(bloc);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
expect(hook).toHaveBeenCalledTimes(100);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
});
|
package/src/plugins/types.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { BlocBase } from '../BlocBase';
|
|
2
|
-
import { Bloc } from '../Bloc';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Error context provided to error handlers
|
|
6
|
-
*/
|
|
7
|
-
export interface ErrorContext {
|
|
8
|
-
readonly phase:
|
|
9
|
-
| 'initialization'
|
|
10
|
-
| 'state-change'
|
|
11
|
-
| 'event-processing'
|
|
12
|
-
| 'disposal';
|
|
13
|
-
readonly operation: string;
|
|
14
|
-
readonly metadata?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Plugin capabilities for security model
|
|
19
|
-
*/
|
|
20
|
-
export interface PluginCapabilities {
|
|
21
|
-
readonly readState: boolean;
|
|
22
|
-
readonly transformState: boolean;
|
|
23
|
-
readonly interceptEvents: boolean;
|
|
24
|
-
readonly persistData: boolean;
|
|
25
|
-
readonly accessMetadata: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Base interface for all plugins
|
|
30
|
-
*/
|
|
31
|
-
export interface Plugin {
|
|
32
|
-
readonly name: string;
|
|
33
|
-
readonly version: string;
|
|
34
|
-
readonly capabilities?: PluginCapabilities;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Adapter metadata provided to plugins
|
|
39
|
-
*/
|
|
40
|
-
export interface AdapterMetadata {
|
|
41
|
-
componentName?: string;
|
|
42
|
-
blocInstance: BlocBase<any>;
|
|
43
|
-
renderCount: number;
|
|
44
|
-
trackedPaths?: string[];
|
|
45
|
-
isUsingDependencies?: boolean;
|
|
46
|
-
lastState?: any;
|
|
47
|
-
lastDependencyValues?: any[];
|
|
48
|
-
currentDependencyValues?: any[];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* System-wide plugin that observes all blocs
|
|
53
|
-
*/
|
|
54
|
-
export interface BlacPlugin extends Plugin {
|
|
55
|
-
// Lifecycle hooks - all synchronous
|
|
56
|
-
beforeBootstrap?(): void;
|
|
57
|
-
afterBootstrap?(): void;
|
|
58
|
-
beforeShutdown?(): void;
|
|
59
|
-
afterShutdown?(): void;
|
|
60
|
-
|
|
61
|
-
// System-wide observations
|
|
62
|
-
onBlocCreated?(bloc: BlocBase<any>): void;
|
|
63
|
-
onBlocDisposed?(bloc: BlocBase<any>): void;
|
|
64
|
-
onStateChanged?(
|
|
65
|
-
bloc: BlocBase<any>,
|
|
66
|
-
previousState: any,
|
|
67
|
-
currentState: any,
|
|
68
|
-
): void;
|
|
69
|
-
onEventAdded?(bloc: Bloc<any, any>, event: any): void;
|
|
70
|
-
onError?(error: Error, bloc: BlocBase<unknown>, context: ErrorContext): void;
|
|
71
|
-
|
|
72
|
-
// Adapter lifecycle hooks
|
|
73
|
-
onAdapterCreated?(adapter: any, metadata: AdapterMetadata): void;
|
|
74
|
-
onAdapterMount?(adapter: any, metadata: AdapterMetadata): void;
|
|
75
|
-
onAdapterUnmount?(adapter: any, metadata: AdapterMetadata): void;
|
|
76
|
-
onAdapterRender?(adapter: any, metadata: AdapterMetadata): void;
|
|
77
|
-
onAdapterDisposed?(adapter: any, metadata: AdapterMetadata): void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Bloc-specific plugin that can transform behavior
|
|
82
|
-
*/
|
|
83
|
-
export interface BlocPlugin<TState = any, TEvent = never> extends Plugin {
|
|
84
|
-
// Transform hooks - can modify data
|
|
85
|
-
transformState?(previousState: TState, nextState: TState): TState;
|
|
86
|
-
transformEvent?(event: TEvent): TEvent | null;
|
|
87
|
-
|
|
88
|
-
// Lifecycle hooks
|
|
89
|
-
onAttach?(bloc: BlocBase<TState>): void;
|
|
90
|
-
onDetach?(): void;
|
|
91
|
-
|
|
92
|
-
// Observation hooks
|
|
93
|
-
onStateChange?(previousState: TState, currentState: TState): void;
|
|
94
|
-
onEvent?(event: TEvent): void;
|
|
95
|
-
onError?(error: Error, context: ErrorContext): void;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Plugin execution context for performance monitoring
|
|
100
|
-
*/
|
|
101
|
-
export interface PluginExecutionContext {
|
|
102
|
-
readonly pluginName: string;
|
|
103
|
-
readonly hookName: string;
|
|
104
|
-
readonly startTime: number;
|
|
105
|
-
readonly blocName?: string;
|
|
106
|
-
readonly blocId?: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Plugin metrics for monitoring
|
|
111
|
-
*/
|
|
112
|
-
export interface PluginMetrics {
|
|
113
|
-
readonly executionTime: number;
|
|
114
|
-
readonly executionCount: number;
|
|
115
|
-
readonly errorCount: number;
|
|
116
|
-
readonly lastError?: Error;
|
|
117
|
-
readonly lastExecutionTime?: number;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Plugin registry interface
|
|
122
|
-
*/
|
|
123
|
-
export interface PluginRegistry<T extends Plugin> {
|
|
124
|
-
add(plugin: T): void;
|
|
125
|
-
remove(pluginName: string): boolean;
|
|
126
|
-
get(pluginName: string): T | undefined;
|
|
127
|
-
getAll(): ReadonlyArray<T>;
|
|
128
|
-
clear(): void;
|
|
129
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Unified Subscription Model
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The unified subscription model replaces the dual consumer/observer system in BlaC with a single, consistent subscription mechanism. This reduces complexity, improves performance, and provides a cleaner API for state management.
|
|
6
|
-
|
|
7
|
-
## Architecture
|
|
8
|
-
|
|
9
|
-
### Core Components
|
|
10
|
-
|
|
11
|
-
1. **Subscription Interface** (`types.ts`)
|
|
12
|
-
- Single interface for all subscription types
|
|
13
|
-
- Supports selectors, custom equality functions, and priorities
|
|
14
|
-
- Tracks dependencies and metadata
|
|
15
|
-
|
|
16
|
-
2. **SubscriptionManager** (`SubscriptionManager.ts`)
|
|
17
|
-
- Manages all subscriptions with a single Map
|
|
18
|
-
- Handles notification dispatch with priority ordering
|
|
19
|
-
- Automatic weak reference cleanup
|
|
20
|
-
- Path-based dependency tracking
|
|
21
|
-
|
|
22
|
-
3. **Backward Compatibility Adapters**
|
|
23
|
-
- `ConsumerTrackerAdapter`: Maintains ConsumerTracker API
|
|
24
|
-
- `BlacObservableAdapter`: Maintains BlacObservable API
|
|
25
|
-
- Zero breaking changes for existing code
|
|
26
|
-
|
|
27
|
-
## Benefits
|
|
28
|
-
|
|
29
|
-
### 1. Simplified Mental Model
|
|
30
|
-
|
|
31
|
-
- Single subscription concept instead of two overlapping systems
|
|
32
|
-
- Consistent API across React and non-React usage
|
|
33
|
-
- Easier to understand and debug
|
|
34
|
-
|
|
35
|
-
### 2. Better Performance
|
|
36
|
-
|
|
37
|
-
- Single notification pass instead of dual system
|
|
38
|
-
- Reduced memory overhead
|
|
39
|
-
- O(1) subscription lookups
|
|
40
|
-
- Automatic dead reference cleanup
|
|
41
|
-
|
|
42
|
-
### 3. Enhanced Features
|
|
43
|
-
|
|
44
|
-
- Priority-based notification ordering
|
|
45
|
-
- Unified dependency tracking
|
|
46
|
-
- Consistent selector/equality function support
|
|
47
|
-
- Better TypeScript support
|
|
48
|
-
|
|
49
|
-
### 4. Reduced Complexity
|
|
50
|
-
|
|
51
|
-
- ~50% less code to maintain
|
|
52
|
-
- Single source of truth for subscriptions
|
|
53
|
-
- Eliminated circular dependencies
|
|
54
|
-
- Cleaner separation of concerns
|
|
55
|
-
|
|
56
|
-
## Migration Path
|
|
57
|
-
|
|
58
|
-
### Phase 1: Infrastructure (COMPLETE)
|
|
59
|
-
|
|
60
|
-
✅ Implement core subscription types and manager
|
|
61
|
-
✅ Create backward compatibility adapters
|
|
62
|
-
✅ Comprehensive test coverage
|
|
63
|
-
|
|
64
|
-
### Phase 2: Integration (IN PROGRESS)
|
|
65
|
-
|
|
66
|
-
⏳ Update BlocBase to use SubscriptionManager
|
|
67
|
-
⏳ Maintain existing APIs through adapters
|
|
68
|
-
⏳ Ensure all tests pass
|
|
69
|
-
|
|
70
|
-
### Phase 3: React Adapter
|
|
71
|
-
|
|
72
|
-
- Update useBloc to create unified subscriptions
|
|
73
|
-
- Optimize re-render logic with unified system
|
|
74
|
-
- Maintain backward compatibility
|
|
75
|
-
|
|
76
|
-
### Phase 4: Deprecation
|
|
77
|
-
|
|
78
|
-
- Mark old APIs as deprecated
|
|
79
|
-
- Provide migration guide
|
|
80
|
-
- Remove in next major version
|
|
81
|
-
|
|
82
|
-
## Usage Examples
|
|
83
|
-
|
|
84
|
-
### React-style Subscription
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
// Subscribe to specific state slice
|
|
88
|
-
const unsubscribe = bloc.subscribeWithSelector(
|
|
89
|
-
(state) => state.count,
|
|
90
|
-
(newCount) => console.log('Count changed:', newCount),
|
|
91
|
-
);
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Observer-style Subscription
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
// Subscribe with dependency array
|
|
98
|
-
const unsubscribe = bloc.observe(
|
|
99
|
-
(newState, oldState) => console.log('State changed'),
|
|
100
|
-
(state) => [state.user.id, state.settings.theme], // Only notify when these change
|
|
101
|
-
);
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Direct Subscription Manager Usage
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
const subscription = subscriptionManager.subscribe({
|
|
108
|
-
type: 'consumer',
|
|
109
|
-
selector: (state) => state.items.length,
|
|
110
|
-
equalityFn: (a, b) => a === b,
|
|
111
|
-
notify: (length) => updateItemCount(length),
|
|
112
|
-
priority: 10, // Higher priority = earlier notification
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Performance Considerations
|
|
117
|
-
|
|
118
|
-
1. **Selector Optimization**: Use memoized selectors for expensive computations
|
|
119
|
-
2. **Equality Functions**: Provide custom equality for complex objects
|
|
120
|
-
3. **Priority Usage**: Use priorities sparingly, only when order matters
|
|
121
|
-
4. **Weak References**: Automatic cleanup reduces memory leaks
|
|
122
|
-
|
|
123
|
-
## Future Enhancements
|
|
124
|
-
|
|
125
|
-
1. **Batched Notifications**: Group updates for better performance
|
|
126
|
-
2. **Async Selectors**: Support for async data derivation
|
|
127
|
-
3. **DevTools Integration**: Enhanced debugging capabilities
|
|
128
|
-
4. **Subscription Composition**: Combine multiple subscriptions
|
|
129
|
-
|
|
130
|
-
## Conclusion
|
|
131
|
-
|
|
132
|
-
The unified subscription model simplifies BlaC's architecture while maintaining full backward compatibility. It provides a solid foundation for future enhancements and makes the library easier to use and maintain.
|