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

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/package.json +10 -15
  2. package/src/Blac.ts +38 -71
  3. package/src/Bloc.ts +8 -9
  4. package/src/BlocBase.ts +246 -540
  5. package/src/Cubit.ts +1 -16
  6. package/src/__tests__/Blac.config.test.ts +10 -17
  7. package/src/__tests__/Bloc.event.test.ts +27 -52
  8. package/src/__tests__/BlocBase.subscription.test.ts +349 -0
  9. package/src/__tests__/Cubit.test.ts +39 -50
  10. package/src/__tests__/memory-leaks.test.ts +383 -0
  11. package/src/__tests__/plugins.test.ts +107 -97
  12. package/src/adapter/BlacAdapter.ts +347 -207
  13. package/src/adapter/ProxyFactory.ts +196 -255
  14. package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
  15. package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
  16. package/src/adapter/index.ts +0 -1
  17. package/src/errors/BlacError.ts +69 -0
  18. package/src/errors/ErrorManager.ts +101 -0
  19. package/src/errors/handleError.ts +114 -0
  20. package/src/errors/index.ts +13 -0
  21. package/src/events.ts +7 -1
  22. package/src/index.ts +7 -1
  23. package/src/lifecycle/BlocLifecycle.ts +156 -0
  24. package/src/lifecycle/index.ts +1 -0
  25. package/src/plugins/BlocPluginRegistry.ts +71 -51
  26. package/src/plugins/SystemPluginRegistry.ts +63 -13
  27. package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
  28. package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
  29. package/src/plugins/types.ts +32 -3
  30. package/src/subscription/README.md +132 -0
  31. package/src/subscription/SubscriptionManager.ts +395 -0
  32. package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
  33. package/src/subscription/index.ts +2 -0
  34. package/src/subscription/types.ts +90 -0
  35. package/src/testing.ts +31 -37
  36. package/src/types.ts +4 -21
  37. package/src/utils/BatchingManager.ts +59 -0
  38. package/src/utils/RerenderLogger.ts +265 -0
  39. package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
  40. package/src/utils/__tests__/shallowEqual.test.ts +171 -0
  41. package/src/utils/__tests__/uuid.test.ts +144 -0
  42. package/src/utils/generateInstanceId.ts +54 -0
  43. package/src/utils/shallowEqual.ts +1 -1
  44. package/README.md +0 -329
  45. package/dist/index.cjs.js +0 -11
  46. package/dist/index.cjs.js.map +0 -1
  47. package/dist/index.d.ts +0 -1078
  48. package/dist/index.es.js +0 -1992
  49. package/dist/index.es.js.map +0 -1
  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
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@blac/core",
3
- "version": "2.0.0-rc-13",
3
+ "version": "2.0.0-rc-16",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
- "main": "dist/index.cjs.js",
7
- "module": "dist/index.es.js",
8
- "typings": "dist/index.d.ts",
9
- "types": "dist/index.d.ts",
6
+ "main": "src/index.ts",
7
+ "module": "src/index.ts",
8
+ "typings": "src/index.ts",
9
+ "types": "src/index.ts",
10
10
  "files": [
11
- "dist",
12
11
  "src",
13
12
  "README.md",
14
13
  "LICENSE"
@@ -27,21 +26,17 @@
27
26
  "@testing-library/jest-dom": "^6.6.4",
28
27
  "@testing-library/user-event": "^14.6.1",
29
28
  "@vitest/browser": "^3.2.4",
30
- "jsdom": "^24.1.1",
29
+ "jsdom": "^26.1.0",
31
30
  "prettier": "^3.6.2",
32
- "typescript": "^5.8.3",
33
- "vite": "^5.3.1",
34
- "vite-plugin-dts": "^4.5.4",
35
- "vitest": "^1.6.0"
31
+ "typescript": "^5.9.2",
32
+ "vitest": "^3.2.4"
36
33
  },
37
34
  "scripts": {
38
- "prettier": "prettier --write ./src",
39
- "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx}\"",
35
+ "format": "prettier --write \".\"",
40
36
  "test": "vitest run",
41
37
  "test:watch": "vitest --watch",
42
38
  "coverage": "vitest run --coverage",
43
39
  "typecheck": "tsc --noEmit",
44
- "build": "vite build",
45
- "deploy": "pnpm run build && pnpm publish --access public"
40
+ "deploy": "pnpm publish --access public"
46
41
  }
47
42
  }
package/src/Blac.ts CHANGED
@@ -1,19 +1,19 @@
1
- import { BlocBase, BlocInstanceId, BlocLifecycleState } from './BlocBase';
1
+ import { BlocBase, BlocInstanceId } from './BlocBase';
2
+ import { BlocLifecycleState } from './lifecycle/BlocLifecycle';
2
3
  import {
3
4
  BlocBaseAbstract,
4
5
  BlocConstructor,
5
6
  BlocHookDependencyArrayFn,
6
7
  BlocState,
7
- InferPropsFromGeneric,
8
8
  } from './types';
9
9
  import { SystemPluginRegistry } from './plugins/SystemPluginRegistry';
10
+ import { BlacError, ErrorCategory, ErrorSeverity } from './errors/BlacError';
11
+ import { ErrorManager } from './errors/ErrorManager';
10
12
 
11
13
  /**
12
14
  * Configuration options for the Blac instance
13
15
  */
14
16
  export interface BlacConfig {
15
- /** Whether to expose the Blac instance globally */
16
- exposeBlacInstance?: boolean;
17
17
  /**
18
18
  * Whether to enable proxy dependency tracking for automatic re-render optimization.
19
19
  * When false, state changes always cause re-renders unless dependencies are manually specified.
@@ -25,7 +25,7 @@ export interface BlacConfig {
25
25
  export interface GetBlocOptions<B extends BlocBase<unknown>> {
26
26
  id?: string;
27
27
  selector?: BlocHookDependencyArrayFn<BlocState<B>>;
28
- props?: InferPropsFromGeneric<B>;
28
+ constructorParams?: ConstructorParameters<BlocConstructor<B>>[];
29
29
  onMount?: (bloc: B) => void;
30
30
  instanceRef?: string;
31
31
  throwIfNotFound?: boolean;
@@ -105,13 +105,13 @@ export class Blac {
105
105
  }
106
106
  /** Timestamp when the instance was created */
107
107
  createdAt = Date.now();
108
+ private errorManager = ErrorManager.getInstance();
108
109
  static get getAllBlocs() {
109
110
  return Blac.instance.getAllBlocs;
110
111
  }
111
112
 
112
113
  /** Private static configuration */
113
114
  private static _config: BlacConfig = {
114
- exposeBlacInstance: false,
115
115
  proxyDependencyTracking: true,
116
116
  };
117
117
 
@@ -127,14 +127,16 @@ export class Blac {
127
127
  */
128
128
  static setConfig(config: Partial<BlacConfig>): void {
129
129
  // Validate config
130
- if (config.proxyDependencyTracking !== undefined &&
131
- typeof config.proxyDependencyTracking !== 'boolean') {
132
- throw new Error('BlacConfig.proxyDependencyTracking must be a boolean');
133
- }
134
-
135
- if (config.exposeBlacInstance !== undefined &&
136
- typeof config.exposeBlacInstance !== 'boolean') {
137
- throw new Error('BlacConfig.exposeBlacInstance must be a boolean');
130
+ if (
131
+ config.proxyDependencyTracking !== undefined &&
132
+ typeof config.proxyDependencyTracking !== 'boolean'
133
+ ) {
134
+ const error = new BlacError(
135
+ 'BlacConfig.proxyDependencyTracking must be a boolean',
136
+ ErrorCategory.VALIDATION,
137
+ ErrorSeverity.FATAL,
138
+ );
139
+ this.instance.errorManager.handle(error);
138
140
  }
139
141
 
140
142
  // Merge with existing config
@@ -219,9 +221,7 @@ export class Blac {
219
221
  * @param args - Additional arguments
220
222
  */
221
223
  warn = (message: string, ...args: unknown[]) => {
222
- if (Blac.enableLog) {
223
- // Logging disabled - console.warn removed
224
- }
224
+ console.warn(message, ...args);
225
225
  };
226
226
  static get warn() {
227
227
  return Blac.instance.warn;
@@ -233,7 +233,7 @@ export class Blac {
233
233
  */
234
234
  error = (message: string, ...args: unknown[]) => {
235
235
  if (Blac.enableLog) {
236
- // Logging disabled - console.error removed
236
+ console.error(message, ...args);
237
237
  }
238
238
  };
239
239
  static get error() {
@@ -317,7 +317,7 @@ export class Blac {
317
317
  );
318
318
 
319
319
  // First dispose the bloc to prevent further operations
320
- bloc._dispose();
320
+ bloc.dispose();
321
321
 
322
322
  // Notify plugins of bloc disposal
323
323
  this.plugins.notifyBlocDisposed(bloc);
@@ -393,6 +393,12 @@ export class Blac {
393
393
 
394
394
  const key = this.createBlocInstanceMapKey(blocClass.name, id);
395
395
  const found = this.blocInstanceMap.get(key) as InstanceType<B> | undefined;
396
+
397
+ // Don't return disposed blocs
398
+ if (found && (found as any).isDisposed) {
399
+ return undefined;
400
+ }
401
+
396
402
  return found;
397
403
  }
398
404
 
@@ -477,7 +483,7 @@ export class Blac {
477
483
  return undefined;
478
484
  }
479
485
  // Find the specific bloc by ID within the isolated array
480
- const found = blocs.find((b) => b._id === id) as
486
+ const found = blocs.find((b) => b._id === id && !(b as any).isDisposed) as
481
487
  | InstanceType<B>
482
488
  | undefined;
483
489
  return found;
@@ -505,14 +511,14 @@ export class Blac {
505
511
  id: BlocInstanceId,
506
512
  options: GetBlocOptions<InstanceType<B>> = {},
507
513
  ): InstanceType<B> {
508
- const { props, instanceRef } = options;
509
- const newBloc = new blocClass(props as never) as InstanceType<B>;
514
+ const { constructorParams, instanceRef } = options;
515
+ const newBloc = new blocClass(constructorParams) as InstanceType<B>;
516
+ newBloc.blacInstance = this;
510
517
  newBloc._instanceRef = instanceRef;
511
- newBloc.props = props || null;
512
- newBloc._updateId(id);
518
+ newBloc._id = id;
513
519
 
514
520
  // Set up disposal handler to break circular dependency
515
- newBloc._setDisposalHandler((bloc) => this.disposeBloc(bloc));
521
+ newBloc.setDisposalHandler((bloc) => this.disposeBloc(bloc));
516
522
 
517
523
  if (newBloc.isIsolated) {
518
524
  this.registerIsolatedBlocInstance(newBloc);
@@ -520,8 +526,7 @@ export class Blac {
520
526
  this.registerBlocInstance(newBloc);
521
527
  }
522
528
 
523
- // Activate bloc plugins
524
- newBloc._activatePlugins();
529
+ // Plugins are activated in constructor
525
530
 
526
531
  // Notify system plugins of bloc creation
527
532
  this.plugins.notifyBlocCreated(newBloc);
@@ -632,38 +637,6 @@ export class Blac {
632
637
  return Blac.instance.getBloc;
633
638
  }
634
639
 
635
- /**
636
- * Gets a bloc instance or throws an error if it doesn't exist
637
- * @param blocClass - The bloc class to get
638
- * @param options - Options including:
639
- * - id: The instance ID (defaults to class name if not provided)
640
- * - props: Properties to pass to the bloc constructor
641
- * - instanceRef: Optional reference string for the instance
642
- */
643
- getBlocOrThrow = <B extends BlocConstructor<any>>(
644
- blocClass: B,
645
- options: {
646
- id?: BlocInstanceId;
647
- props?: InferPropsFromGeneric<B>;
648
- instanceRef?: string;
649
- } = {},
650
- ): InstanceType<B> => {
651
- const isIsolated = (blocClass as unknown as BlocBaseAbstract).isolated;
652
- const id = options.id || blocClass.name;
653
-
654
- const registered = isIsolated
655
- ? this.findIsolatedBlocInstance(blocClass, id)
656
- : this.findRegisteredBlocInstance(blocClass, id);
657
-
658
- if (registered) {
659
- return registered;
660
- }
661
- throw new Error(`Bloc ${blocClass.name} not found`);
662
- };
663
- static get getBlocOrThrow() {
664
- return Blac.instance.getBlocOrThrow;
665
- }
666
-
667
640
  /**
668
641
  * Gets all instances of a specific bloc class
669
642
  * @param blocClass - The bloc class to search for
@@ -678,7 +651,6 @@ export class Blac {
678
651
  } = {},
679
652
  ): InstanceType<B>[] => {
680
653
  const results: InstanceType<B>[] = [];
681
- // const blocClassName = (blocClass as any).name; // Temporarily removed for debugging
682
654
 
683
655
  // Search non-isolated blocs
684
656
  this.blocInstanceMap.forEach((blocInstance) => {
@@ -714,7 +686,7 @@ export class Blac {
714
686
  }
715
687
  }
716
688
 
717
- toDispose.forEach((bloc) => bloc._dispose());
689
+ toDispose.forEach((bloc) => bloc.dispose());
718
690
  };
719
691
  static get disposeKeepAliveBlocs() {
720
692
  return Blac.instance.disposeKeepAliveBlocs;
@@ -743,7 +715,7 @@ export class Blac {
743
715
  }
744
716
  }
745
717
 
746
- toDispose.forEach((bloc) => bloc._dispose());
718
+ toDispose.forEach((bloc) => bloc.dispose());
747
719
  };
748
720
  static get disposeBlocs() {
749
721
  return Blac.instance.disposeBlocs;
@@ -769,26 +741,23 @@ export class Blac {
769
741
  }
770
742
 
771
743
  /**
772
- * Validates consumer references and cleans up orphaned consumers
744
+ * Validates subscription references and cleans up orphaned blocs
773
745
  */
774
746
  validateConsumers = (): void => {
775
747
  for (const bloc of this.uidRegistry.values()) {
776
- // Validate consumers using the bloc's own validation method
777
- bloc._validateConsumers();
778
-
779
748
  // Check if bloc should be disposed after validation
780
749
  // TODO: Type assertion for private property access (see explanation above)
781
750
  if (
782
- bloc._consumers.size === 0 &&
751
+ bloc.subscriptionCount === 0 &&
783
752
  !bloc._keepAlive &&
784
753
  (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
785
754
  ) {
786
- // Schedule disposal for blocs with no consumers
755
+ // Schedule disposal for blocs with no subscriptions
787
756
  setTimeout(() => {
788
757
  // Double-check conditions before disposal
789
758
  // TODO: Type assertion for private property access (see explanation above)
790
759
  if (
791
- bloc._consumers.size === 0 &&
760
+ bloc.subscriptionCount === 0 &&
792
761
  !bloc._keepAlive &&
793
762
  (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
794
763
  ) {
@@ -830,6 +799,4 @@ export class Blac {
830
799
  }
831
800
  }
832
801
  }
833
-
834
-
835
802
  }
package/src/Bloc.ts CHANGED
@@ -5,8 +5,7 @@ import { BlocEventConstraint } from './types';
5
5
  export abstract class Bloc<
6
6
  S, // State type
7
7
  A extends BlocEventConstraint = BlocEventConstraint, // Base Action/Event type with proper constraints
8
- P = unknown, // Props type
9
- > extends BlocBase<S, P> {
8
+ > extends BlocBase<S> {
10
9
  readonly eventHandlers: Map<
11
10
  new (...args: any[]) => A,
12
11
  (event: A, emit: (newState: S) => void) => void | Promise<void>
@@ -63,22 +62,22 @@ export abstract class Bloc<
63
62
  console.error('Error transforming event:', error);
64
63
  // Continue with original event if transformation fails
65
64
  }
66
-
65
+
67
66
  // If event was cancelled by plugin, don't process it
68
67
  if (transformedAction === null) {
69
68
  return;
70
69
  }
71
-
70
+
72
71
  // Notify bloc plugins of event
73
72
  try {
74
73
  (this._plugins as any).notifyEvent(transformedAction);
75
74
  } catch (error) {
76
75
  console.error('Error notifying plugins of event:', error);
77
76
  }
78
-
77
+
79
78
  // Notify system plugins of event
80
79
  Blac.instance.plugins.notifyEventAdded(this as any, transformedAction);
81
-
80
+
82
81
  this._eventQueue.push(transformedAction);
83
82
 
84
83
  if (!this._isProcessingEvent) {
@@ -149,13 +148,13 @@ export abstract class Bloc<
149
148
  this._plugins.notifyError(error, {
150
149
  phase: 'event-processing',
151
150
  operation: 'handler',
152
- metadata: { event: action }
151
+ metadata: { event: action },
153
152
  });
154
-
153
+
155
154
  Blac.instance.plugins.notifyError(error, this as any, {
156
155
  phase: 'event-processing',
157
156
  operation: 'handler',
158
- metadata: { event: action }
157
+ metadata: { event: action },
159
158
  });
160
159
  } catch (pluginError) {
161
160
  console.error('Error notifying plugins:', pluginError);