@blac/core 2.0.0-rc-13 → 2.0.0-rc-14

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.
Files changed (54) hide show
  1. package/dist/index.cjs.js +2 -2
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.d.ts +522 -297
  4. package/dist/index.es.js +1833 -1550
  5. package/dist/index.es.js.map +1 -1
  6. package/package.json +6 -7
  7. package/src/Blac.ts +38 -71
  8. package/src/Bloc.ts +8 -9
  9. package/src/BlocBase.ts +246 -540
  10. package/src/Cubit.ts +1 -16
  11. package/src/__tests__/Blac.config.test.ts +10 -17
  12. package/src/__tests__/Bloc.event.test.ts +27 -52
  13. package/src/__tests__/BlocBase.subscription.test.ts +349 -0
  14. package/src/__tests__/Cubit.test.ts +39 -50
  15. package/src/__tests__/memory-leaks.test.ts +383 -0
  16. package/src/__tests__/plugins.test.ts +107 -97
  17. package/src/adapter/BlacAdapter.ts +347 -207
  18. package/src/adapter/ProxyFactory.ts +196 -255
  19. package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
  20. package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
  21. package/src/adapter/index.ts +0 -1
  22. package/src/errors/BlacError.ts +69 -0
  23. package/src/errors/ErrorManager.ts +101 -0
  24. package/src/errors/handleError.ts +114 -0
  25. package/src/errors/index.ts +13 -0
  26. package/src/events.ts +7 -1
  27. package/src/index.ts +7 -1
  28. package/src/lifecycle/BlocLifecycle.ts +156 -0
  29. package/src/lifecycle/index.ts +1 -0
  30. package/src/plugins/BlocPluginRegistry.ts +71 -51
  31. package/src/plugins/SystemPluginRegistry.ts +63 -13
  32. package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
  33. package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
  34. package/src/plugins/types.ts +32 -3
  35. package/src/subscription/README.md +132 -0
  36. package/src/subscription/SubscriptionManager.ts +395 -0
  37. package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
  38. package/src/subscription/index.ts +2 -0
  39. package/src/subscription/types.ts +90 -0
  40. package/src/testing.ts +31 -37
  41. package/src/types.ts +4 -21
  42. package/src/utils/BatchingManager.ts +59 -0
  43. package/src/utils/RerenderLogger.ts +265 -0
  44. package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
  45. package/src/utils/__tests__/shallowEqual.test.ts +171 -0
  46. package/src/utils/__tests__/uuid.test.ts +144 -0
  47. package/src/utils/generateInstanceId.ts +54 -0
  48. package/src/utils/shallowEqual.ts +1 -1
  49. package/README.md +0 -329
  50. package/src/BlacObserver.ts +0 -189
  51. package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
  52. package/src/__tests__/props.test.ts +0 -256
  53. package/src/adapter/ConsumerTracker.ts +0 -241
  54. package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
@@ -0,0 +1,404 @@
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
+ });
@@ -5,7 +5,11 @@ import { Bloc } from '../Bloc';
5
5
  * Error context provided to error handlers
6
6
  */
7
7
  export interface ErrorContext {
8
- readonly phase: 'initialization' | 'state-change' | 'event-processing' | 'disposal';
8
+ readonly phase:
9
+ | 'initialization'
10
+ | 'state-change'
11
+ | 'event-processing'
12
+ | 'disposal';
9
13
  readonly operation: string;
10
14
  readonly metadata?: Record<string, unknown>;
11
15
  }
@@ -30,6 +34,20 @@ export interface Plugin {
30
34
  readonly capabilities?: PluginCapabilities;
31
35
  }
32
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
+
33
51
  /**
34
52
  * System-wide plugin that observes all blocs
35
53
  */
@@ -43,9 +61,20 @@ export interface BlacPlugin extends Plugin {
43
61
  // System-wide observations
44
62
  onBlocCreated?(bloc: BlocBase<any>): void;
45
63
  onBlocDisposed?(bloc: BlocBase<any>): void;
46
- onStateChanged?(bloc: BlocBase<any>, previousState: any, currentState: any): void;
64
+ onStateChanged?(
65
+ bloc: BlocBase<any>,
66
+ previousState: any,
67
+ currentState: any,
68
+ ): void;
47
69
  onEventAdded?(bloc: Bloc<any, any>, event: any): void;
48
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;
49
78
  }
50
79
 
51
80
  /**
@@ -57,7 +86,7 @@ export interface BlocPlugin<TState = any, TEvent = never> extends Plugin {
57
86
  transformEvent?(event: TEvent): TEvent | null;
58
87
 
59
88
  // Lifecycle hooks
60
- onAttach?(bloc: BlocBase<TState, any>): void;
89
+ onAttach?(bloc: BlocBase<TState>): void;
61
90
  onDetach?(): void;
62
91
 
63
92
  // Observation hooks
@@ -0,0 +1,132 @@
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.