@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.
- package/package.json +10 -15
- package/src/Blac.ts +38 -71
- package/src/Bloc.ts +8 -9
- package/src/BlocBase.ts +246 -540
- package/src/Cubit.ts +1 -16
- package/src/__tests__/Blac.config.test.ts +10 -17
- package/src/__tests__/Bloc.event.test.ts +27 -52
- package/src/__tests__/BlocBase.subscription.test.ts +349 -0
- package/src/__tests__/Cubit.test.ts +39 -50
- package/src/__tests__/memory-leaks.test.ts +383 -0
- package/src/__tests__/plugins.test.ts +107 -97
- package/src/adapter/BlacAdapter.ts +347 -207
- package/src/adapter/ProxyFactory.ts +196 -255
- package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
- package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
- package/src/adapter/index.ts +0 -1
- package/src/errors/BlacError.ts +69 -0
- package/src/errors/ErrorManager.ts +101 -0
- package/src/errors/handleError.ts +114 -0
- package/src/errors/index.ts +13 -0
- package/src/events.ts +7 -1
- package/src/index.ts +7 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +71 -51
- package/src/plugins/SystemPluginRegistry.ts +63 -13
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/types.ts +32 -3
- package/src/subscription/README.md +132 -0
- package/src/subscription/SubscriptionManager.ts +395 -0
- package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
- package/src/subscription/index.ts +2 -0
- package/src/subscription/types.ts +90 -0
- package/src/testing.ts +31 -37
- package/src/types.ts +4 -21
- package/src/utils/BatchingManager.ts +59 -0
- package/src/utils/RerenderLogger.ts +265 -0
- package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
- package/src/utils/__tests__/shallowEqual.test.ts +171 -0
- package/src/utils/__tests__/uuid.test.ts +144 -0
- package/src/utils/generateInstanceId.ts +54 -0
- package/src/utils/shallowEqual.ts +1 -1
- package/README.md +0 -329
- package/dist/index.cjs.js +0 -11
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -1078
- package/dist/index.es.js +0 -1992
- package/dist/index.es.js.map +0 -1
- package/src/BlacObserver.ts +0 -189
- package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
- package/src/__tests__/props.test.ts +0 -256
- package/src/adapter/ConsumerTracker.ts +0 -241
- package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enum representing the lifecycle states of a Bloc instance
|
|
3
|
+
*/
|
|
4
|
+
export enum BlocLifecycleState {
|
|
5
|
+
ACTIVE = 'active',
|
|
6
|
+
DISPOSAL_REQUESTED = 'disposal_requested',
|
|
7
|
+
DISPOSING = 'disposing',
|
|
8
|
+
DISPOSED = 'disposed',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StateTransitionResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
currentState: BlocLifecycleState;
|
|
14
|
+
previousState: BlocLifecycleState;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Manages the lifecycle state transitions for Bloc instances.
|
|
19
|
+
* Ensures atomic state transitions to prevent race conditions during disposal.
|
|
20
|
+
*/
|
|
21
|
+
export class BlocLifecycleManager {
|
|
22
|
+
private disposalState = BlocLifecycleState.ACTIVE;
|
|
23
|
+
private disposalLock = false;
|
|
24
|
+
private disposalTimer?: NodeJS.Timeout | number;
|
|
25
|
+
private disposalHandler?: (bloc: unknown) => void;
|
|
26
|
+
|
|
27
|
+
get currentState(): BlocLifecycleState {
|
|
28
|
+
return this.disposalState;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get isDisposed(): boolean {
|
|
32
|
+
return this.disposalState === BlocLifecycleState.DISPOSED;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Atomic state transition for disposal
|
|
37
|
+
*/
|
|
38
|
+
atomicStateTransition(
|
|
39
|
+
expectedState: BlocLifecycleState,
|
|
40
|
+
newState: BlocLifecycleState,
|
|
41
|
+
): StateTransitionResult {
|
|
42
|
+
if (this.disposalLock) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
currentState: this.disposalState,
|
|
46
|
+
previousState: this.disposalState,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.disposalLock = true;
|
|
51
|
+
try {
|
|
52
|
+
if (this.disposalState !== expectedState) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
currentState: this.disposalState,
|
|
56
|
+
previousState: this.disposalState,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const previousState = this.disposalState;
|
|
61
|
+
this.disposalState = newState;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
currentState: newState,
|
|
66
|
+
previousState,
|
|
67
|
+
};
|
|
68
|
+
} finally {
|
|
69
|
+
this.disposalLock = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Schedule disposal after a delay
|
|
75
|
+
*/
|
|
76
|
+
scheduleDisposal(
|
|
77
|
+
delay: number,
|
|
78
|
+
canDispose: () => boolean,
|
|
79
|
+
onDispose: () => void,
|
|
80
|
+
): void {
|
|
81
|
+
// Cancel any existing disposal timer
|
|
82
|
+
if (this.disposalTimer) {
|
|
83
|
+
clearTimeout(this.disposalTimer as NodeJS.Timeout);
|
|
84
|
+
this.disposalTimer = undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const transitionResult = this.atomicStateTransition(
|
|
88
|
+
BlocLifecycleState.ACTIVE,
|
|
89
|
+
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!transitionResult.success) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.disposalTimer = setTimeout(() => {
|
|
97
|
+
if (
|
|
98
|
+
canDispose() &&
|
|
99
|
+
this.disposalState === BlocLifecycleState.DISPOSAL_REQUESTED
|
|
100
|
+
) {
|
|
101
|
+
onDispose();
|
|
102
|
+
} else {
|
|
103
|
+
this.atomicStateTransition(
|
|
104
|
+
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
105
|
+
BlocLifecycleState.ACTIVE,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}, delay);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Cancel disposal if in disposal requested state
|
|
113
|
+
*/
|
|
114
|
+
cancelDisposal(): boolean {
|
|
115
|
+
if (this.disposalState === BlocLifecycleState.DISPOSAL_REQUESTED) {
|
|
116
|
+
// Cancel disposal timer
|
|
117
|
+
if (this.disposalTimer) {
|
|
118
|
+
clearTimeout(this.disposalTimer as NodeJS.Timeout);
|
|
119
|
+
this.disposalTimer = undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Transition back to active state
|
|
123
|
+
const result = this.atomicStateTransition(
|
|
124
|
+
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
125
|
+
BlocLifecycleState.ACTIVE,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return result.success;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Set disposal handler
|
|
135
|
+
*/
|
|
136
|
+
setDisposalHandler(handler: (bloc: unknown) => void): void {
|
|
137
|
+
this.disposalHandler = handler;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get disposal handler
|
|
142
|
+
*/
|
|
143
|
+
getDisposalHandler(): ((bloc: unknown) => void) | undefined {
|
|
144
|
+
return this.disposalHandler;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear disposal timer
|
|
149
|
+
*/
|
|
150
|
+
clearDisposalTimer(): void {
|
|
151
|
+
if (this.disposalTimer) {
|
|
152
|
+
clearTimeout(this.disposalTimer as NodeJS.Timeout);
|
|
153
|
+
this.disposalTimer = undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BlocLifecycle';
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { BlocPlugin, PluginRegistry, ErrorContext } from '
|
|
1
|
+
import { BlocPlugin, PluginRegistry, ErrorContext } from './types';
|
|
2
|
+
import { handleError } from '../errors/handleError';
|
|
3
|
+
import { ErrorCategory } from '../errors/BlacError';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Registry for bloc-specific plugins
|
|
5
7
|
*/
|
|
6
|
-
export class BlocPluginRegistry<TState, TEvent = never>
|
|
8
|
+
export class BlocPluginRegistry<TState, TEvent = never>
|
|
9
|
+
implements PluginRegistry<BlocPlugin<TState, TEvent>>
|
|
10
|
+
{
|
|
7
11
|
private plugins = new Map<string, BlocPlugin<TState, TEvent>>();
|
|
8
12
|
private executionOrder: string[] = [];
|
|
9
13
|
private attached = false;
|
|
10
|
-
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* Add a bloc plugin
|
|
13
17
|
*/
|
|
@@ -15,52 +19,53 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
15
19
|
if (this.plugins.has(plugin.name)) {
|
|
16
20
|
throw new Error(`Plugin '${plugin.name}' is already registered`);
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
|
|
19
23
|
// Validate capabilities
|
|
20
24
|
if (plugin.capabilities) {
|
|
21
25
|
this.validateCapabilities(plugin);
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
|
|
24
28
|
this.plugins.set(plugin.name, plugin);
|
|
25
29
|
this.executionOrder.push(plugin.name);
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
/**
|
|
29
33
|
* Remove a bloc plugin
|
|
30
34
|
*/
|
|
31
35
|
remove(pluginName: string): boolean {
|
|
32
36
|
const plugin = this.plugins.get(pluginName);
|
|
33
37
|
if (!plugin) return false;
|
|
34
|
-
|
|
38
|
+
|
|
35
39
|
// Call onDetach if attached
|
|
36
40
|
if (this.attached && plugin.onDetach) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
41
|
+
handleError.wrap(() => plugin.onDetach!(), ErrorCategory.PLUGIN, {
|
|
42
|
+
pluginName,
|
|
43
|
+
operation: 'onDetach',
|
|
44
|
+
});
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
this.plugins.delete(pluginName);
|
|
45
|
-
this.executionOrder = this.executionOrder.filter(
|
|
46
|
-
|
|
48
|
+
this.executionOrder = this.executionOrder.filter(
|
|
49
|
+
(name) => name !== pluginName,
|
|
50
|
+
);
|
|
51
|
+
|
|
47
52
|
return true;
|
|
48
53
|
}
|
|
49
|
-
|
|
54
|
+
|
|
50
55
|
/**
|
|
51
56
|
* Get a plugin by name
|
|
52
57
|
*/
|
|
53
58
|
get(pluginName: string): BlocPlugin<TState, TEvent> | undefined {
|
|
54
59
|
return this.plugins.get(pluginName);
|
|
55
60
|
}
|
|
56
|
-
|
|
61
|
+
|
|
57
62
|
/**
|
|
58
63
|
* Get all plugins in execution order
|
|
59
64
|
*/
|
|
60
65
|
getAll(): ReadonlyArray<BlocPlugin<TState, TEvent>> {
|
|
61
|
-
return this.executionOrder.map(name => this.plugins.get(name)!);
|
|
66
|
+
return this.executionOrder.map((name) => this.plugins.get(name)!);
|
|
62
67
|
}
|
|
63
|
-
|
|
68
|
+
|
|
64
69
|
/**
|
|
65
70
|
* Clear all plugins
|
|
66
71
|
*/
|
|
@@ -69,20 +74,19 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
69
74
|
if (this.attached) {
|
|
70
75
|
for (const plugin of this.getAll()) {
|
|
71
76
|
if (plugin.onDetach) {
|
|
72
|
-
|
|
73
|
-
plugin.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
+
handleError.wrap(() => plugin.onDetach!(), ErrorCategory.PLUGIN, {
|
|
78
|
+
pluginName: plugin.name,
|
|
79
|
+
operation: 'onDetach',
|
|
80
|
+
});
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
|
|
81
85
|
this.plugins.clear();
|
|
82
86
|
this.executionOrder = [];
|
|
83
87
|
this.attached = false;
|
|
84
88
|
}
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
/**
|
|
87
91
|
* Attach all plugins to a bloc
|
|
88
92
|
*/
|
|
@@ -90,7 +94,7 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
90
94
|
if (this.attached) {
|
|
91
95
|
throw new Error('Plugins are already attached');
|
|
92
96
|
}
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
for (const plugin of this.getAll()) {
|
|
95
99
|
if (plugin.onAttach) {
|
|
96
100
|
try {
|
|
@@ -102,52 +106,61 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
this.attached = true;
|
|
107
111
|
}
|
|
108
|
-
|
|
112
|
+
|
|
109
113
|
/**
|
|
110
114
|
* Transform state through all plugins
|
|
111
115
|
*/
|
|
112
116
|
transformState(previousState: TState, nextState: TState): TState {
|
|
113
117
|
let transformedState = nextState;
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
for (const plugin of this.getAll()) {
|
|
116
120
|
if (plugin.transformState && this.canTransformState(plugin)) {
|
|
117
121
|
try {
|
|
118
|
-
transformedState = plugin.transformState(
|
|
122
|
+
transformedState = plugin.transformState(
|
|
123
|
+
previousState,
|
|
124
|
+
transformedState,
|
|
125
|
+
);
|
|
119
126
|
} catch (error) {
|
|
120
|
-
console.error(
|
|
127
|
+
console.error(
|
|
128
|
+
`Plugin '${plugin.name}' error in transformState:`,
|
|
129
|
+
error,
|
|
130
|
+
);
|
|
121
131
|
// Continue with untransformed state
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
|
-
|
|
135
|
+
|
|
126
136
|
return transformedState;
|
|
127
137
|
}
|
|
128
|
-
|
|
138
|
+
|
|
129
139
|
/**
|
|
130
140
|
* Transform event through all plugins
|
|
131
141
|
*/
|
|
132
142
|
transformEvent(event: TEvent): TEvent | null {
|
|
133
143
|
let transformedEvent: TEvent | null = event;
|
|
134
|
-
|
|
144
|
+
|
|
135
145
|
for (const plugin of this.getAll()) {
|
|
136
146
|
if (transformedEvent === null) break;
|
|
137
|
-
|
|
147
|
+
|
|
138
148
|
if (plugin.transformEvent && this.canInterceptEvents(plugin)) {
|
|
139
149
|
try {
|
|
140
150
|
transformedEvent = plugin.transformEvent(transformedEvent);
|
|
141
151
|
} catch (error) {
|
|
142
|
-
console.error(
|
|
152
|
+
console.error(
|
|
153
|
+
`Plugin '${plugin.name}' error in transformEvent:`,
|
|
154
|
+
error,
|
|
155
|
+
);
|
|
143
156
|
// Continue with untransformed event
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
|
-
|
|
160
|
+
|
|
148
161
|
return transformedEvent;
|
|
149
162
|
}
|
|
150
|
-
|
|
163
|
+
|
|
151
164
|
/**
|
|
152
165
|
* Notify plugins of state change
|
|
153
166
|
*/
|
|
@@ -157,12 +170,15 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
157
170
|
try {
|
|
158
171
|
plugin.onStateChange(previousState, currentState);
|
|
159
172
|
} catch (error) {
|
|
160
|
-
console.error(
|
|
173
|
+
console.error(
|
|
174
|
+
`Plugin '${plugin.name}' error in onStateChange:`,
|
|
175
|
+
error,
|
|
176
|
+
);
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
|
-
|
|
181
|
+
|
|
166
182
|
/**
|
|
167
183
|
* Notify plugins of event
|
|
168
184
|
*/
|
|
@@ -177,7 +193,7 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
177
193
|
}
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
|
-
|
|
196
|
+
|
|
181
197
|
/**
|
|
182
198
|
* Notify plugins of error
|
|
183
199
|
*/
|
|
@@ -192,29 +208,33 @@ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistr
|
|
|
192
208
|
}
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
|
-
|
|
211
|
+
|
|
196
212
|
private validateCapabilities(plugin: BlocPlugin<TState, TEvent>): void {
|
|
197
213
|
const caps = plugin.capabilities!;
|
|
198
|
-
|
|
214
|
+
|
|
199
215
|
// Validate logical constraints
|
|
200
216
|
if (caps.transformState && !caps.readState) {
|
|
201
|
-
throw new Error(
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Plugin '${plugin.name}': transformState requires readState capability`,
|
|
219
|
+
);
|
|
202
220
|
}
|
|
203
|
-
|
|
221
|
+
|
|
204
222
|
if (caps.interceptEvents && !caps.readState) {
|
|
205
|
-
throw new Error(
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Plugin '${plugin.name}': interceptEvents requires readState capability`,
|
|
225
|
+
);
|
|
206
226
|
}
|
|
207
227
|
}
|
|
208
|
-
|
|
228
|
+
|
|
209
229
|
private canReadState(plugin: BlocPlugin<TState, TEvent>): boolean {
|
|
210
230
|
return !plugin.capabilities || plugin.capabilities.readState !== false;
|
|
211
231
|
}
|
|
212
|
-
|
|
232
|
+
|
|
213
233
|
private canTransformState(plugin: BlocPlugin<TState, TEvent>): boolean {
|
|
214
234
|
return !plugin.capabilities || plugin.capabilities.transformState === true;
|
|
215
235
|
}
|
|
216
|
-
|
|
236
|
+
|
|
217
237
|
private canInterceptEvents(plugin: BlocPlugin<TState, TEvent>): boolean {
|
|
218
238
|
return !plugin.capabilities || plugin.capabilities.interceptEvents === true;
|
|
219
239
|
}
|
|
220
|
-
}
|
|
240
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BlacPlugin,
|
|
3
|
+
PluginRegistry,
|
|
4
|
+
PluginMetrics,
|
|
5
|
+
ErrorContext,
|
|
6
|
+
AdapterMetadata,
|
|
7
|
+
} from './types';
|
|
2
8
|
import { BlocBase } from '../BlocBase';
|
|
3
9
|
import { Bloc } from '../Bloc';
|
|
4
10
|
|
|
@@ -32,7 +38,9 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
32
38
|
|
|
33
39
|
this.plugins.delete(pluginName);
|
|
34
40
|
this.metrics.delete(pluginName);
|
|
35
|
-
this.executionOrder = this.executionOrder.filter(
|
|
41
|
+
this.executionOrder = this.executionOrder.filter(
|
|
42
|
+
(name) => name !== pluginName,
|
|
43
|
+
);
|
|
36
44
|
|
|
37
45
|
return true;
|
|
38
46
|
}
|
|
@@ -48,7 +56,7 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
48
56
|
* Get all plugins in execution order
|
|
49
57
|
*/
|
|
50
58
|
getAll(): ReadonlyArray<BlacPlugin> {
|
|
51
|
-
return this.executionOrder.map(name => this.plugins.get(name)!);
|
|
59
|
+
return this.executionOrder.map((name) => this.plugins.get(name)!);
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
/**
|
|
@@ -66,7 +74,7 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
66
74
|
executeHook(
|
|
67
75
|
hookName: keyof BlacPlugin,
|
|
68
76
|
args: unknown[],
|
|
69
|
-
errorHandler?: (error: Error, plugin: BlacPlugin) => void
|
|
77
|
+
errorHandler?: (error: Error, plugin: BlacPlugin) => void,
|
|
70
78
|
): void {
|
|
71
79
|
for (const pluginName of this.executionOrder) {
|
|
72
80
|
const plugin = this.plugins.get(pluginName)!;
|
|
@@ -86,7 +94,10 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
86
94
|
errorHandler(error as Error, plugin);
|
|
87
95
|
} else {
|
|
88
96
|
// Default: log and continue
|
|
89
|
-
console.error(
|
|
97
|
+
console.error(
|
|
98
|
+
`Plugin '${pluginName}' error in hook '${hookName as string}':`,
|
|
99
|
+
error,
|
|
100
|
+
);
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
}
|
|
@@ -125,7 +136,11 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
125
136
|
/**
|
|
126
137
|
* Notify plugins of state change
|
|
127
138
|
*/
|
|
128
|
-
notifyStateChanged(
|
|
139
|
+
notifyStateChanged(
|
|
140
|
+
bloc: BlocBase<any>,
|
|
141
|
+
previousState: any,
|
|
142
|
+
currentState: any,
|
|
143
|
+
): void {
|
|
129
144
|
this.executeHook('onStateChanged', [bloc, previousState, currentState]);
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -139,13 +154,40 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
139
154
|
/**
|
|
140
155
|
* Notify plugins of errors
|
|
141
156
|
*/
|
|
142
|
-
notifyError(
|
|
157
|
+
notifyError(
|
|
158
|
+
error: Error,
|
|
159
|
+
bloc: BlocBase<unknown>,
|
|
160
|
+
context: ErrorContext,
|
|
161
|
+
): void {
|
|
143
162
|
this.executeHook('onError', [error, bloc, context], (hookError, plugin) => {
|
|
144
163
|
// Double fault protection - if error handler fails, just log
|
|
145
164
|
console.error(`Plugin '${plugin.name}' error handler failed:`, hookError);
|
|
146
165
|
});
|
|
147
166
|
}
|
|
148
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Notify plugins of adapter lifecycle events
|
|
170
|
+
*/
|
|
171
|
+
notifyAdapterCreated(adapter: any, metadata: AdapterMetadata): void {
|
|
172
|
+
this.executeHook('onAdapterCreated', [adapter, metadata]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
notifyAdapterMount(adapter: any, metadata: AdapterMetadata): void {
|
|
176
|
+
this.executeHook('onAdapterMount', [adapter, metadata]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
notifyAdapterUnmount(adapter: any, metadata: AdapterMetadata): void {
|
|
180
|
+
this.executeHook('onAdapterUnmount', [adapter, metadata]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
notifyAdapterRender(adapter: any, metadata: AdapterMetadata): void {
|
|
184
|
+
this.executeHook('onAdapterRender', [adapter, metadata]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
notifyAdapterDisposed(adapter: any, metadata: AdapterMetadata): void {
|
|
188
|
+
this.executeHook('onAdapterDisposed', [adapter, metadata]);
|
|
189
|
+
}
|
|
190
|
+
|
|
149
191
|
/**
|
|
150
192
|
* Get metrics for a plugin
|
|
151
193
|
*/
|
|
@@ -157,12 +199,16 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
157
199
|
this.metrics.set(pluginName, new Map());
|
|
158
200
|
}
|
|
159
201
|
|
|
160
|
-
private recordSuccess(
|
|
202
|
+
private recordSuccess(
|
|
203
|
+
pluginName: string,
|
|
204
|
+
hookName: string,
|
|
205
|
+
startTime: number,
|
|
206
|
+
): void {
|
|
161
207
|
const pluginMetrics = this.metrics.get(pluginName)!;
|
|
162
208
|
const hookMetrics = pluginMetrics.get(hookName) || {
|
|
163
209
|
executionTime: 0,
|
|
164
210
|
executionCount: 0,
|
|
165
|
-
errorCount: 0
|
|
211
|
+
errorCount: 0,
|
|
166
212
|
};
|
|
167
213
|
|
|
168
214
|
const executionTime = performance.now() - startTime;
|
|
@@ -171,22 +217,26 @@ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
|
|
|
171
217
|
...hookMetrics,
|
|
172
218
|
executionTime: hookMetrics.executionTime + executionTime,
|
|
173
219
|
executionCount: hookMetrics.executionCount + 1,
|
|
174
|
-
lastExecutionTime: executionTime
|
|
220
|
+
lastExecutionTime: executionTime,
|
|
175
221
|
});
|
|
176
222
|
}
|
|
177
223
|
|
|
178
|
-
private recordError(
|
|
224
|
+
private recordError(
|
|
225
|
+
pluginName: string,
|
|
226
|
+
hookName: string,
|
|
227
|
+
error: Error,
|
|
228
|
+
): void {
|
|
179
229
|
const pluginMetrics = this.metrics.get(pluginName)!;
|
|
180
230
|
const hookMetrics = pluginMetrics.get(hookName) || {
|
|
181
231
|
executionTime: 0,
|
|
182
232
|
executionCount: 0,
|
|
183
|
-
errorCount: 0
|
|
233
|
+
errorCount: 0,
|
|
184
234
|
};
|
|
185
235
|
|
|
186
236
|
pluginMetrics.set(hookName, {
|
|
187
237
|
...hookMetrics,
|
|
188
238
|
errorCount: hookMetrics.errorCount + 1,
|
|
189
|
-
lastError: error
|
|
239
|
+
lastError: error,
|
|
190
240
|
});
|
|
191
241
|
}
|
|
192
242
|
}
|