@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/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +254 -0
- package/dist/index.es.js +700 -296
- package/dist/index.es.js.map +1 -1
- package/package.json +1 -1
- package/src/Blac.ts +48 -2
- package/src/BlacObserver.ts +3 -0
- package/src/Bloc.ts +44 -1
- package/src/BlocBase.ts +107 -4
- package/src/__tests__/plugins.test.ts +454 -0
- package/src/index.ts +3 -0
- package/src/plugins/BlocPluginRegistry.ts +220 -0
- package/src/plugins/SystemPluginRegistry.ts +192 -0
- package/src/plugins/index.ts +8 -0
- package/src/plugins/types.ts +100 -0
package/package.json
CHANGED
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
|
-
|
|
519
|
+
} else {
|
|
520
|
+
this.registerBlocInstance(newBloc);
|
|
511
521
|
}
|
|
512
522
|
|
|
513
|
-
|
|
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
|
}
|
package/src/BlacObserver.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
}
|