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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blac/core",
3
- "version": "2.0.0-rc-11",
3
+ "version": "2.0.0-rc-13",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
6
  "main": "dist/index.cjs.js",
package/src/Blac.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  BlocState,
7
7
  InferPropsFromGeneric,
8
8
  } from './types';
9
+ import { SystemPluginRegistry } from './plugins/SystemPluginRegistry';
9
10
 
10
11
  /**
11
12
  * Configuration options for the Blac instance
@@ -162,6 +163,8 @@ export class Blac {
162
163
  keepAliveBlocs: Set<BlocBase<unknown>> = new Set();
163
164
  /** Flag to control whether changes should be posted to document */
164
165
  postChangesToDocument = false;
166
+ /** System plugin registry */
167
+ readonly plugins = new SystemPluginRegistry();
165
168
 
166
169
  /**
167
170
  * Creates a new Blac instance.
@@ -173,6 +176,9 @@ export class Blac {
173
176
  return Blac.instance;
174
177
  }
175
178
  instanceManager.setInstance(this);
179
+
180
+ // Bootstrap plugins on creation
181
+ this.plugins.bootstrap();
176
182
  }
177
183
 
178
184
  /** Flag to enable/disable logging */
@@ -313,6 +319,9 @@ export class Blac {
313
319
  // First dispose the bloc to prevent further operations
314
320
  bloc._dispose();
315
321
 
322
+ // Notify plugins of bloc disposal
323
+ this.plugins.notifyBlocDisposed(bloc);
324
+
316
325
  // Then clean up from registries
317
326
  if (base.isolated) {
318
327
  this.unregisterIsolatedBlocInstance(bloc);
@@ -507,10 +516,16 @@ export class Blac {
507
516
 
508
517
  if (newBloc.isIsolated) {
509
518
  this.registerIsolatedBlocInstance(newBloc);
510
- return newBloc;
519
+ } else {
520
+ this.registerBlocInstance(newBloc);
511
521
  }
512
522
 
513
- this.registerBlocInstance(newBloc);
523
+ // Activate bloc plugins
524
+ newBloc._activatePlugins();
525
+
526
+ // Notify system plugins of bloc creation
527
+ this.plugins.notifyBlocCreated(newBloc);
528
+
514
529
  return newBloc;
515
530
  }
516
531
 
@@ -786,4 +801,35 @@ export class Blac {
786
801
  static get validateConsumers() {
787
802
  return Blac.instance.validateConsumers;
788
803
  }
804
+
805
+ /**
806
+ * Bootstrap the Blac instance and all plugins
807
+ */
808
+ bootstrap(): void {
809
+ this.plugins.bootstrap();
810
+ }
811
+
812
+ /**
813
+ * Shutdown the Blac instance and all plugins
814
+ */
815
+ shutdown(): void {
816
+ this.plugins.shutdown();
817
+
818
+ // Dispose all non-keepAlive blocs
819
+ for (const bloc of this.blocInstanceMap.values()) {
820
+ if (!bloc._keepAlive) {
821
+ this.disposeBloc(bloc);
822
+ }
823
+ }
824
+
825
+ for (const blocs of this.isolatedBlocMap.values()) {
826
+ for (const bloc of blocs) {
827
+ if (!bloc._keepAlive) {
828
+ this.disposeBloc(bloc);
829
+ }
830
+ }
831
+ }
832
+ }
833
+
834
+
789
835
  }
@@ -174,6 +174,9 @@ export class BlacObservable<S = unknown> {
174
174
  void observer.fn(newState, oldState, action);
175
175
  }
176
176
  });
177
+
178
+ // Notify system plugins of state change
179
+ Blac.instance.plugins.notifyStateChanged(this.bloc, oldState, newState);
177
180
  }
178
181
 
179
182
  /**
package/src/Bloc.ts CHANGED
@@ -55,7 +55,31 @@ export abstract class Bloc<
55
55
  * @param action The action/event instance to be processed.
56
56
  */
57
57
  public add = async (action: A): Promise<void> => {
58
- this._eventQueue.push(action);
58
+ // Transform event through plugins
59
+ let transformedAction: A | null = action;
60
+ try {
61
+ transformedAction = (this._plugins as any).transformEvent(action);
62
+ } catch (error) {
63
+ console.error('Error transforming event:', error);
64
+ // Continue with original event if transformation fails
65
+ }
66
+
67
+ // If event was cancelled by plugin, don't process it
68
+ if (transformedAction === null) {
69
+ return;
70
+ }
71
+
72
+ // Notify bloc plugins of event
73
+ try {
74
+ (this._plugins as any).notifyEvent(transformedAction);
75
+ } catch (error) {
76
+ console.error('Error notifying plugins of event:', error);
77
+ }
78
+
79
+ // Notify system plugins of event
80
+ Blac.instance.plugins.notifyEventAdded(this as any, transformedAction);
81
+
82
+ this._eventQueue.push(transformedAction);
59
83
 
60
84
  if (!this._isProcessingEvent) {
61
85
  await this._processEventQueue();
@@ -119,6 +143,25 @@ export abstract class Bloc<
119
143
  errorContext,
120
144
  );
121
145
 
146
+ // Notify plugins of the error
147
+ if (error instanceof Error) {
148
+ try {
149
+ this._plugins.notifyError(error, {
150
+ phase: 'event-processing',
151
+ operation: 'handler',
152
+ metadata: { event: action }
153
+ });
154
+
155
+ Blac.instance.plugins.notifyError(error, this as any, {
156
+ phase: 'event-processing',
157
+ operation: 'handler',
158
+ metadata: { event: action }
159
+ });
160
+ } catch (pluginError) {
161
+ console.error('Error notifying plugins:', pluginError);
162
+ }
163
+ }
164
+
122
165
  if (error instanceof Error && error.name === 'CriticalError') {
123
166
  throw error;
124
167
  }
package/src/BlocBase.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { BlacObservable } from './BlacObserver';
2
2
  import { generateUUID } from './utils/uuid';
3
+ import { BlocPlugin, ErrorContext } from './plugins/types';
4
+ import { BlocPluginRegistry } from './plugins/BlocPluginRegistry';
3
5
 
4
6
  export type BlocInstanceId = string | number | undefined;
5
7
  type DependencySelector<S> = (
@@ -32,6 +34,7 @@ export interface StateTransitionResult {
32
34
  interface BlocStaticProperties {
33
35
  isolated: boolean;
34
36
  keepAlive: boolean;
37
+ plugins?: BlocPlugin<any, any>[];
35
38
  }
36
39
 
37
40
  /**
@@ -158,6 +161,11 @@ export abstract class BlocBase<S, P = unknown> {
158
161
  */
159
162
  private _consumerRefs = new Map<string, WeakRef<object>>();
160
163
 
164
+ /**
165
+ * Plugin registry for this bloc instance
166
+ */
167
+ protected _plugins: BlocPluginRegistry<S, any>;
168
+
161
169
  /**
162
170
  * @internal
163
171
  * Validates that all consumer references are still alive
@@ -210,6 +218,16 @@ export abstract class BlocBase<S, P = unknown> {
210
218
  : false;
211
219
  this._isolated =
212
220
  typeof Constructor.isolated === 'boolean' ? Constructor.isolated : false;
221
+
222
+ // Initialize plugin registry
223
+ this._plugins = new BlocPluginRegistry<S, any>();
224
+
225
+ // Register static plugins
226
+ if (Constructor.plugins && Array.isArray(Constructor.plugins)) {
227
+ for (const plugin of Constructor.plugins) {
228
+ this.addPlugin(plugin);
229
+ }
230
+ }
213
231
  }
214
232
 
215
233
  /**
@@ -529,25 +547,55 @@ export abstract class BlocBase<S, P = unknown> {
529
547
  return;
530
548
  }
531
549
 
550
+ // Transform state through plugins
551
+ let transformedState: S = newState;
552
+ try {
553
+ const result = this._plugins.transformState(oldState, newState);
554
+ transformedState = result;
555
+ } catch (error) {
556
+ this._plugins.notifyError(error as Error, {
557
+ phase: 'state-change',
558
+ operation: 'transformState'
559
+ });
560
+ // Continue with original state if transformation fails
561
+ }
562
+
532
563
  if (this._batchingEnabled) {
533
564
  // When batching, just accumulate the updates
534
- this._pendingUpdates.push({ newState, oldState, action });
565
+ this._pendingUpdates.push({ newState: transformedState, oldState, action });
535
566
 
536
567
  // Update internal state for consistency
537
568
  this._oldState = oldState;
538
- this._state = newState;
569
+ this._state = transformedState;
539
570
  return;
540
571
  }
541
572
 
542
573
  // Normal state update flow
543
574
  this._oldState = oldState;
544
- this._state = newState;
575
+ this._state = transformedState;
576
+
577
+ // Notify bloc plugins first
578
+ try {
579
+ this._plugins.notifyStateChange(oldState, transformedState);
580
+ } catch (error) {
581
+ console.error('Error notifying bloc plugins of state change:', error);
582
+ }
545
583
 
546
584
  // Notify observers of the state change
547
- this._observer.notify(newState, oldState, action);
585
+ this._observer.notify(transformedState, oldState, action);
548
586
  this.lastUpdate = Date.now();
549
587
  };
550
588
 
589
+ /**
590
+ * Notify observers of a state change
591
+ * @internal Used by plugins for state hydration
592
+ * @param newState The new state
593
+ * @param oldState The old state
594
+ */
595
+ _notifyObservers(newState: S, oldState: S): void {
596
+ this._observer.notify(newState, oldState);
597
+ }
598
+
551
599
  /**
552
600
  * Enables batching for multiple state updates
553
601
  * @param batchFn Function to execute with batching enabled
@@ -586,4 +634,59 @@ export abstract class BlocBase<S, P = unknown> {
586
634
  this._pendingUpdates = [];
587
635
  }
588
636
  };
637
+
638
+ /**
639
+ * Add a plugin to this bloc instance
640
+ */
641
+ addPlugin(plugin: BlocPlugin<S, any>): void {
642
+ this._plugins.add(plugin);
643
+
644
+ // Attach if already active
645
+ if (this._disposalState === BlocLifecycleState.ACTIVE) {
646
+ try {
647
+ if (plugin.onAttach) {
648
+ plugin.onAttach(this);
649
+ }
650
+ } catch (error) {
651
+ console.error(`Failed to attach plugin '${plugin.name}':`, error);
652
+ this._plugins.remove(plugin.name);
653
+ throw error;
654
+ }
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Remove a plugin from this bloc instance
660
+ */
661
+ removePlugin(pluginName: string): boolean {
662
+ return this._plugins.remove(pluginName);
663
+ }
664
+
665
+ /**
666
+ * Get a plugin by name
667
+ */
668
+ getPlugin(pluginName: string): BlocPlugin<S, any> | undefined {
669
+ return this._plugins.get(pluginName);
670
+ }
671
+
672
+ /**
673
+ * Get all plugins
674
+ */
675
+ getPlugins(): ReadonlyArray<BlocPlugin<S, any>> {
676
+ return this._plugins.getAll();
677
+ }
678
+
679
+ /**
680
+ * @internal
681
+ * Activate plugins when bloc becomes active
682
+ */
683
+ _activatePlugins(): void {
684
+ if (this._disposalState === BlocLifecycleState.ACTIVE) {
685
+ try {
686
+ this._plugins.attach(this);
687
+ } catch (error) {
688
+ console.error(`Failed to activate plugins for ${this._name}:`, error);
689
+ }
690
+ }
691
+ }
589
692
  }