@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
@@ -74,10 +74,10 @@ describe('BlacAdapter', () => {
74
74
  it('should create adapter with proper initialization', () => {
75
75
  const adapter = new BlacAdapter(
76
76
  { componentRef, blocConstructor: CounterCubit },
77
- { id: 'test-counter' },
77
+ { instanceId: 'test-counter' },
78
78
  );
79
79
 
80
- expect(adapter.id).toMatch(/^consumer-/);
80
+ expect(adapter.id).toMatch(/^adapter-/);
81
81
  expect(adapter.blocConstructor).toBe(CounterCubit);
82
82
  expect(adapter.componentRef).toBe(componentRef);
83
83
  expect(adapter.blocInstance).toBeInstanceOf(CounterCubit);
@@ -87,36 +87,39 @@ describe('BlacAdapter', () => {
87
87
  // Create first adapter
88
88
  const adapter1 = new BlacAdapter(
89
89
  { componentRef, blocConstructor: CounterCubit },
90
- { id: 'shared-counter' },
90
+ { instanceId: 'shared-counter' },
91
91
  );
92
92
 
93
93
  // Create second adapter with same id
94
94
  const componentRef2 = { current: {} };
95
95
  const adapter2 = new BlacAdapter(
96
96
  { componentRef: componentRef2, blocConstructor: CounterCubit },
97
- { id: 'shared-counter' },
97
+ { instanceId: 'shared-counter' },
98
98
  );
99
99
 
100
100
  // Should share the same bloc instance
101
101
  expect(adapter1.blocInstance).toBe(adapter2.blocInstance);
102
102
  });
103
103
 
104
- it('should pass props to bloc constructor', () => {
105
- class PropsCubit extends Cubit<string, { initialValue: string }> {
106
- constructor(
107
- public override props: { initialValue: string } | null = null,
108
- ) {
104
+ it('should pass staticProps to bloc constructor', () => {
105
+ class PropsCubit extends Cubit<string> {
106
+ props?: { initialValue: string };
107
+
108
+ constructor(props?: { initialValue: string }) {
109
109
  super(props?.initialValue || 'default');
110
+ this.props = props;
110
111
  }
111
112
  }
112
113
 
113
114
  const adapter = new BlacAdapter(
114
115
  { componentRef, blocConstructor: PropsCubit as any },
115
- { props: { initialValue: 'custom' } },
116
+ { staticProps: { initialValue: 'custom' } },
116
117
  );
117
118
 
118
119
  expect(adapter.blocInstance.state).toBe('custom');
119
- expect(adapter.blocInstance.props).toEqual({ initialValue: 'custom' });
120
+ expect((adapter.blocInstance as PropsCubit).props).toEqual({
121
+ initialValue: 'custom',
122
+ });
120
123
  });
121
124
  });
122
125
 
@@ -199,16 +202,13 @@ describe('BlacAdapter', () => {
199
202
  });
200
203
 
201
204
  // Access state through proxy
202
- const proxyState = adapter.getProxyState(adapter.blocInstance.state);
205
+ const proxyState = adapter.getStateProxy();
203
206
  const name = proxyState.name;
204
207
  const email = proxyState.profile.email;
205
208
 
206
- // Check tracked dependencies
207
- const dependencies = adapter.getConsumerDependencies(
208
- componentRef.current,
209
- );
210
- expect(dependencies?.statePaths).toContain('name');
211
- expect(dependencies?.statePaths).toContain('profile.email');
209
+ // Verify proxy state works correctly
210
+ expect(name).toBe('John');
211
+ expect(email).toBe('john@example.com');
212
212
  });
213
213
 
214
214
  it('should track class property access through proxy', () => {
@@ -232,16 +232,13 @@ describe('BlacAdapter', () => {
232
232
  });
233
233
 
234
234
  // Access getters through proxy
235
- const proxyBloc = adapter.getProxyBlocInstance();
235
+ const proxyBloc = adapter.getBlocProxy();
236
236
  const doubled = proxyBloc.doubled;
237
237
  const isPositive = proxyBloc.isPositive;
238
238
 
239
- // Check tracked dependencies
240
- const dependencies = adapter.getConsumerDependencies(
241
- componentRef.current,
242
- );
243
- expect(dependencies?.classPaths).toContain('doubled');
244
- expect(dependencies?.classPaths).toContain('isPositive');
239
+ // Verify proxy getters work correctly
240
+ expect(doubled).toBe(0);
241
+ expect(isPositive).toBe(false);
245
242
  });
246
243
 
247
244
  it('should only notify when tracked values change', () => {
@@ -251,21 +248,25 @@ describe('BlacAdapter', () => {
251
248
  blocConstructor: UserCubit,
252
249
  });
253
250
 
254
- // Track specific property access
255
- adapter.resetConsumerTracking();
256
- const proxyState = adapter.getProxyState(adapter.blocInstance.state);
257
- const name = proxyState.name; // Only track name
258
-
259
- // Mark as rendered to enable dependency checking
260
- adapter.updateLastNotified(componentRef.current);
251
+ // Mount the adapter first
252
+ adapter.mount();
261
253
 
254
+ // Create subscription with automatic tracking
262
255
  const unsubscribe = adapter.createSubscription({ onChange });
263
256
 
257
+ // Access specific property through proxy to track it
258
+ const proxyState = adapter.getStateProxy();
259
+ const name = proxyState.name; // Track name property
260
+
261
+ // Clear onChange calls from initial subscription
262
+ onChange.mockClear();
263
+
264
264
  // Change tracked property - should notify
265
265
  adapter.blocInstance.updateName('Jane');
266
266
  expect(onChange).toHaveBeenCalledTimes(1);
267
267
 
268
- // Change untracked property - should NOT notify
268
+ // For automatic tracking, only tracked properties trigger notifications
269
+ // Since we only accessed 'name', changing 'theme' should not notify
269
270
  onChange.mockClear();
270
271
  adapter.blocInstance.updateTheme('dark');
271
272
  expect(onChange).not.toHaveBeenCalled();
@@ -280,14 +281,15 @@ describe('BlacAdapter', () => {
280
281
  });
281
282
 
282
283
  // Access nested property
283
- const proxyState = adapter.getProxyState(adapter.blocInstance.state);
284
+ const proxyState = adapter.getStateProxy();
284
285
  const theme = proxyState.profile.preferences.theme;
285
286
 
286
- // Check tracked path
287
- const dependencies = adapter.getConsumerDependencies(
288
- componentRef.current,
289
- );
290
- expect(dependencies?.statePaths).toContain('profile.preferences.theme');
287
+ // Verify nested property access works
288
+ expect(theme).toBe('light');
289
+
290
+ // Verify nested proxy returns correct values
291
+ expect(proxyState.profile.email).toBe('john@example.com');
292
+ expect(proxyState.profile.preferences).toEqual({ theme: 'light' });
291
293
  });
292
294
  });
293
295
 
@@ -304,17 +306,16 @@ describe('BlacAdapter', () => {
304
306
  // Mount
305
307
  adapter.mount();
306
308
  expect(onMount).toHaveBeenCalledWith(adapter.blocInstance);
307
- expect(adapter.calledOnMount).toBe(true);
308
- expect(adapter.blocInstance._consumers.has(adapter.id)).toBe(true);
309
+ expect(adapter.mountTime).toBeGreaterThan(0);
309
310
 
310
- // Mount again - should not call onMount twice
311
+ // Mount again - onMount is called each time
311
312
  adapter.mount();
312
- expect(onMount).toHaveBeenCalledTimes(1);
313
+ expect(onMount).toHaveBeenCalledTimes(2);
313
314
 
314
315
  // Unmount
315
316
  adapter.unmount();
316
317
  expect(onUnmount).toHaveBeenCalledWith(adapter.blocInstance);
317
- expect(adapter.blocInstance._consumers.has(adapter.id)).toBe(false);
318
+ expect(adapter.unmountTime).toBeGreaterThan(0);
318
319
  });
319
320
 
320
321
  it('should handle onMount errors', () => {
@@ -371,7 +372,7 @@ describe('BlacAdapter', () => {
371
372
  const onChange = vi.fn();
372
373
  const unsubscribe = adapter.createSubscription({ onChange });
373
374
 
374
- expect(adapter.blocInstance._observer.size).toBe(1);
375
+ expect(adapter.blocInstance.subscriptionCount).toBe(1);
375
376
 
376
377
  // Trigger state change
377
378
  adapter.blocInstance.increment();
@@ -379,7 +380,7 @@ describe('BlacAdapter', () => {
379
380
 
380
381
  // Cleanup
381
382
  unsubscribe();
382
- expect(adapter.blocInstance._observer.size).toBe(0);
383
+ expect(adapter.blocInstance.subscriptionCount).toBe(0);
383
384
  });
384
385
 
385
386
  it('should handle first render without dependencies check', () => {
@@ -407,7 +408,7 @@ describe('BlacAdapter', () => {
407
408
  { dependencies: (bloc) => [bloc.state.name] },
408
409
  );
409
410
 
410
- const state = adapter.getProxyState(adapter.blocInstance.state);
411
+ const state = adapter.getStateProxy();
411
412
 
412
413
  // Should be raw state, not proxy
413
414
  expect(state).toBe(adapter.blocInstance.state);
@@ -419,7 +420,7 @@ describe('BlacAdapter', () => {
419
420
  { dependencies: (bloc) => [bloc.state] },
420
421
  );
421
422
 
422
- const blocInstance = adapter.getProxyBlocInstance();
423
+ const blocInstance = adapter.getBlocProxy();
423
424
 
424
425
  // Should be raw instance, not proxy
425
426
  expect(blocInstance).toBe(adapter.blocInstance);
@@ -431,8 +432,8 @@ describe('BlacAdapter', () => {
431
432
  blocConstructor: UserCubit,
432
433
  });
433
434
 
434
- const proxyState = adapter.getProxyState(adapter.blocInstance.state);
435
- const proxyBloc = adapter.getProxyBlocInstance();
435
+ const proxyState = adapter.getStateProxy();
436
+ const proxyBloc = adapter.getBlocProxy();
436
437
 
437
438
  // Should be proxies
438
439
  expect(proxyState).not.toBe(adapter.blocInstance.state);
@@ -441,7 +442,7 @@ describe('BlacAdapter', () => {
441
442
  });
442
443
 
443
444
  describe('Consumer Tracking Integration', () => {
444
- it('should properly track and validate consumers', () => {
445
+ it('should properly handle subscription lifecycle', () => {
445
446
  const adapter = new BlacAdapter({
446
447
  componentRef,
447
448
  blocConstructor: CounterCubit,
@@ -449,42 +450,35 @@ describe('BlacAdapter', () => {
449
450
 
450
451
  adapter.mount();
451
452
 
452
- // Consumer should be tracked
453
- const hasConsumer = adapter.shouldNotifyConsumer(
454
- componentRef.current,
455
- new Set(['state']),
456
- );
457
- expect(hasConsumer).toBe(true); // First render always notifies
453
+ const onChange = vi.fn();
454
+ const unsubscribe = adapter.createSubscription({ onChange });
458
455
 
459
- // Update tracking info
460
- adapter.updateLastNotified(componentRef.current);
456
+ // State change should notify
457
+ adapter.blocInstance.increment();
458
+ expect(onChange).toHaveBeenCalledTimes(1);
461
459
 
462
- // Now should use dependency tracking
463
- const shouldNotify = adapter.shouldNotifyConsumer(
464
- componentRef.current,
465
- new Set(['untracked']),
466
- );
467
- expect(shouldNotify).toBe(false);
460
+ // After unsubscribe, no more notifications
461
+ unsubscribe();
462
+ adapter.blocInstance.increment();
463
+ expect(onChange).toHaveBeenCalledTimes(1);
468
464
  });
469
465
 
470
- it('should reset consumer tracking', () => {
466
+ it('should reset tracking state properly', () => {
471
467
  const adapter = new BlacAdapter({
472
468
  componentRef,
473
469
  blocConstructor: UserCubit,
474
470
  });
475
471
 
476
- // Track some accesses
477
- adapter.trackAccess(componentRef.current, 'state', 'name', 'John');
478
- adapter.trackAccess(componentRef.current, 'state', 'age', 30);
479
-
480
- const depsBefore = adapter.getConsumerDependencies(componentRef.current);
481
- expect(depsBefore?.statePaths.length).toBe(2);
472
+ // Access state to potentially track
473
+ const proxyState = adapter.getStateProxy();
474
+ const name = proxyState.name;
482
475
 
483
- // Reset tracking
484
- adapter.resetConsumerTracking();
476
+ // Reset tracking clears internal state
477
+ adapter.resetTracking();
485
478
 
486
- const depsAfter = adapter.getConsumerDependencies(componentRef.current);
487
- expect(depsAfter?.statePaths.length).toBe(0);
479
+ // Verify adapter is still functional after reset
480
+ const theme = proxyState.profile.preferences.theme;
481
+ expect(theme).toBe('light');
488
482
  });
489
483
  });
490
484
 
@@ -534,7 +528,7 @@ describe('BlacAdapter', () => {
534
528
  adapter.unmount();
535
529
  }
536
530
 
537
- expect(adapter.blocInstance._consumers.size).toBe(0);
531
+ expect(adapter.blocInstance.subscriptionCount).toBe(0);
538
532
  });
539
533
 
540
534
  it('should handle value changes with Object.is semantics', () => {
@@ -191,7 +191,7 @@ describe('ProxyFactory', () => {
191
191
  expect(userName).toBe('Jane');
192
192
 
193
193
  // Array methods should work
194
- const mapped = proxy.items.map((x) => x * 2);
194
+ const mapped = proxy.items.map((x: number) => x * 2);
195
195
  expect(mapped).toEqual([2, 4, 6]);
196
196
  });
197
197
 
@@ -1,3 +1,2 @@
1
1
  export * from './BlacAdapter';
2
- export * from './ConsumerTracker';
3
2
  export * from './ProxyFactory';
@@ -0,0 +1,69 @@
1
+ export enum ErrorCategory {
2
+ LIFECYCLE = 'LIFECYCLE',
3
+ STATE = 'STATE',
4
+ PLUGIN = 'PLUGIN',
5
+ VALIDATION = 'VALIDATION',
6
+ DISPOSAL = 'DISPOSAL',
7
+ CONSUMER = 'CONSUMER',
8
+ }
9
+
10
+ export enum ErrorSeverity {
11
+ FATAL = 'FATAL', // System cannot continue
12
+ ERROR = 'ERROR', // Operation failed but system continues
13
+ WARNING = 'WARNING', // Potential issue but operation succeeded
14
+ INFO = 'INFO', // Informational, not an error
15
+ }
16
+
17
+ export type BlacErrorContext = {
18
+ blocName?: string;
19
+ blocId?: string;
20
+ consumerId?: string;
21
+ pluginName?: string;
22
+ operation?: string;
23
+ metadata?: Record<string, unknown>;
24
+ };
25
+
26
+ export class BlacError extends Error {
27
+ constructor(
28
+ message: string,
29
+ public readonly category: ErrorCategory,
30
+ public readonly severity: ErrorSeverity,
31
+ public readonly context?: BlacErrorContext,
32
+ public readonly cause?: unknown,
33
+ ) {
34
+ super(message);
35
+ this.name = 'BlacError';
36
+
37
+ // Ensure proper prototype chain
38
+ Object.setPrototypeOf(this, BlacError.prototype);
39
+ }
40
+
41
+ toString(): string {
42
+ const contextStr = this.context
43
+ ? ` [${Object.entries(this.context)
44
+ .filter(([_, v]) => v !== undefined)
45
+ .map(([k, v]) => `${k}: ${v}`)
46
+ .join(', ')}]`
47
+ : '';
48
+
49
+ return `${this.name} [${this.severity}/${this.category}]: ${this.message}${contextStr}`;
50
+ }
51
+ }
52
+
53
+ export type ErrorHandler = (error: BlacError) => void;
54
+
55
+ export type ErrorHandlingStrategy = {
56
+ shouldPropagate: (error: BlacError) => boolean;
57
+ shouldLog: (error: BlacError) => boolean;
58
+ handle: (error: BlacError) => void;
59
+ };
60
+
61
+ export const DefaultErrorStrategy: ErrorHandlingStrategy = {
62
+ shouldPropagate: (error) => error.severity === ErrorSeverity.FATAL,
63
+ shouldLog: (error) => error.severity !== ErrorSeverity.INFO,
64
+ handle: (error) => {
65
+ if (DefaultErrorStrategy.shouldLog(error)) {
66
+ console.error(error.toString(), error.cause);
67
+ }
68
+ },
69
+ };
@@ -0,0 +1,101 @@
1
+ import {
2
+ BlacError,
3
+ ErrorHandler,
4
+ ErrorHandlingStrategy,
5
+ DefaultErrorStrategy,
6
+ } from './BlacError';
7
+
8
+ /**
9
+ * Centralized error management system for BlaC framework.
10
+ * Handles error processing, logging, and propagation strategies.
11
+ */
12
+ export class ErrorManager {
13
+ private static instance: ErrorManager;
14
+ private handlers: Set<ErrorHandler> = new Set();
15
+ private strategy: ErrorHandlingStrategy = DefaultErrorStrategy;
16
+
17
+ /**
18
+ * Get the singleton instance of ErrorManager
19
+ */
20
+ static getInstance(): ErrorManager {
21
+ if (!ErrorManager.instance) {
22
+ ErrorManager.instance = new ErrorManager();
23
+ }
24
+ return ErrorManager.instance;
25
+ }
26
+
27
+ /**
28
+ * Set a custom error handling strategy
29
+ */
30
+ setStrategy(strategy: ErrorHandlingStrategy): void {
31
+ this.strategy = strategy;
32
+ }
33
+
34
+ /**
35
+ * Add a custom error handler that will be called for all errors
36
+ */
37
+ addHandler(handler: ErrorHandler): void {
38
+ this.handlers.add(handler);
39
+ }
40
+
41
+ /**
42
+ * Remove a previously added error handler
43
+ */
44
+ removeHandler(handler: ErrorHandler): void {
45
+ this.handlers.delete(handler);
46
+ }
47
+
48
+ /**
49
+ * Process an error through all handlers and apply the configured strategy
50
+ */
51
+ handle(error: BlacError): void {
52
+ // Always notify handlers first
53
+ this.handlers.forEach((handler) => {
54
+ try {
55
+ handler(error);
56
+ } catch (handlerError) {
57
+ // Prevent handler errors from breaking error handling
58
+ console.error('Error in error handler:', handlerError);
59
+ }
60
+ });
61
+
62
+ // Apply strategy
63
+ this.strategy.handle(error);
64
+
65
+ // Propagate if needed
66
+ if (this.strategy.shouldPropagate(error)) {
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Convenience method for wrapping operations with error handling
73
+ */
74
+ wrap<T>(operation: () => T, errorFactory: (error: unknown) => BlacError): T {
75
+ try {
76
+ return operation();
77
+ } catch (error) {
78
+ const blacError = errorFactory(error);
79
+ this.handle(blacError);
80
+ // Will only reach here if strategy says not to propagate
81
+ return undefined as T;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Async version of wrap
87
+ */
88
+ async wrapAsync<T>(
89
+ operation: () => Promise<T>,
90
+ errorFactory: (error: unknown) => BlacError,
91
+ ): Promise<T> {
92
+ try {
93
+ return await operation();
94
+ } catch (error) {
95
+ const blacError = errorFactory(error);
96
+ this.handle(blacError);
97
+ // Will only reach here if strategy says not to propagate
98
+ return undefined as T;
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,114 @@
1
+ import {
2
+ BlacError,
3
+ ErrorCategory,
4
+ ErrorSeverity,
5
+ BlacErrorContext,
6
+ } from './BlacError';
7
+ import { ErrorManager } from './ErrorManager';
8
+
9
+ /**
10
+ * Standard error handling helper for BlaC
11
+ *
12
+ * Usage:
13
+ * - For fatal errors that should propagate: handleError.fatal(...)
14
+ * - For recoverable errors: handleError.error(...)
15
+ * - For warnings: handleError.warn(...)
16
+ */
17
+ export const handleError = {
18
+ fatal: (
19
+ message: string,
20
+ category: ErrorCategory,
21
+ context?: BlacErrorContext,
22
+ cause?: unknown,
23
+ ): never => {
24
+ const error = new BlacError(
25
+ message,
26
+ category,
27
+ ErrorSeverity.FATAL,
28
+ context,
29
+ cause,
30
+ );
31
+ ErrorManager.getInstance().handle(error);
32
+ // This will always throw due to FATAL severity
33
+ throw error;
34
+ },
35
+
36
+ error: (
37
+ message: string,
38
+ category: ErrorCategory,
39
+ context?: BlacErrorContext,
40
+ cause?: unknown,
41
+ ): void => {
42
+ const error = new BlacError(
43
+ message,
44
+ category,
45
+ ErrorSeverity.ERROR,
46
+ context,
47
+ cause,
48
+ );
49
+ ErrorManager.getInstance().handle(error);
50
+ },
51
+
52
+ warn: (
53
+ message: string,
54
+ category: ErrorCategory,
55
+ context?: BlacErrorContext,
56
+ cause?: unknown,
57
+ ): void => {
58
+ const error = new BlacError(
59
+ message,
60
+ category,
61
+ ErrorSeverity.WARNING,
62
+ context,
63
+ cause,
64
+ );
65
+ ErrorManager.getInstance().handle(error);
66
+ },
67
+
68
+ info: (
69
+ message: string,
70
+ category: ErrorCategory,
71
+ context?: BlacErrorContext,
72
+ ): void => {
73
+ const error = new BlacError(message, category, ErrorSeverity.INFO, context);
74
+ ErrorManager.getInstance().handle(error);
75
+ },
76
+
77
+ /**
78
+ * Wraps a function to catch and handle errors
79
+ */
80
+ wrap: <T>(
81
+ fn: () => T,
82
+ category: ErrorCategory,
83
+ context?: BlacErrorContext,
84
+ severity: ErrorSeverity = ErrorSeverity.ERROR,
85
+ ): T | undefined => {
86
+ try {
87
+ return fn();
88
+ } catch (cause) {
89
+ const message = cause instanceof Error ? cause.message : String(cause);
90
+ const error = new BlacError(message, category, severity, context, cause);
91
+ ErrorManager.getInstance().handle(error);
92
+ return undefined;
93
+ }
94
+ },
95
+
96
+ /**
97
+ * Wraps an async function to catch and handle errors
98
+ */
99
+ wrapAsync: async <T>(
100
+ fn: () => Promise<T>,
101
+ category: ErrorCategory,
102
+ context?: BlacErrorContext,
103
+ severity: ErrorSeverity = ErrorSeverity.ERROR,
104
+ ): Promise<T | undefined> => {
105
+ try {
106
+ return await fn();
107
+ } catch (cause) {
108
+ const message = cause instanceof Error ? cause.message : String(cause);
109
+ const error = new BlacError(message, category, severity, context, cause);
110
+ ErrorManager.getInstance().handle(error);
111
+ return undefined;
112
+ }
113
+ },
114
+ };
@@ -0,0 +1,13 @@
1
+ export {
2
+ BlacError,
3
+ ErrorCategory,
4
+ ErrorSeverity,
5
+ DefaultErrorStrategy,
6
+ } from './BlacError';
7
+ export type {
8
+ BlacErrorContext,
9
+ ErrorHandler,
10
+ ErrorHandlingStrategy,
11
+ } from './BlacError';
12
+ export { ErrorManager } from './ErrorManager';
13
+ export { handleError } from './handleError';
package/src/events.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Event fired when props are updated on a Bloc or Cubit instance.
3
+ * This is primarily used internally by the framework to notify
4
+ * about prop changes in React components.
5
+ * @template P - The type of props being updated
6
+ */
1
7
  export class PropsUpdated<P = any> {
2
8
  constructor(public readonly props: P) {}
3
- }
9
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,17 @@
1
1
  export * from './Blac';
2
- export * from './BlacObserver';
3
2
  export * from './Bloc';
4
3
  export * from './BlocBase';
5
4
  export * from './Cubit';
6
5
  export * from './types';
7
6
  export * from './events';
7
+ export * from './subscription';
8
+ export * from './lifecycle';
8
9
 
9
10
  // Utilities
10
11
  export * from './utils/uuid';
11
12
  export * from './utils/shallowEqual';
13
+ export * from './utils/generateInstanceId';
14
+ export * from './utils/RerenderLogger';
12
15
 
13
16
  // Test utilities
14
17
  export * from './testing';
@@ -18,3 +21,6 @@ export * from './adapter';
18
21
 
19
22
  // Plugins
20
23
  export * from './plugins';
24
+
25
+ // Error handling
26
+ export * from './errors';