@blac/core 2.0.0-rc-10 → 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.
@@ -1,14 +1,16 @@
1
1
  import { Blac } from '../Blac';
2
- import { BlocBase } from '../BlocBase';
2
+ import { BlocBase, BlocLifecycleState } from '../BlocBase';
3
3
  import { BlocConstructor, BlocState, InferPropsFromGeneric } from '../types';
4
+ import { PropsUpdated } from '../events';
4
5
  import { generateUUID } from '../utils/uuid';
6
+ import { shallowEqual } from '../utils/shallowEqual';
5
7
  import { ConsumerTracker, DependencyArray } from './ConsumerTracker';
6
8
  import { ProxyFactory } from './ProxyFactory';
7
9
 
8
10
  export interface AdapterOptions<B extends BlocBase<any>> {
9
11
  id?: string;
10
12
  dependencies?: (bloc: B) => unknown[];
11
- props?: InferPropsFromGeneric<B>;
13
+ props?: any;
12
14
  onMount?: (bloc: B) => void;
13
15
  onUnmount?: (bloc: B) => void;
14
16
  }
@@ -26,6 +28,10 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
26
28
  // Core components
27
29
  private consumerTracker: ConsumerTracker;
28
30
 
31
+ // Props ownership tracking
32
+ private static propsOwners = new WeakMap<BlocBase<any>, string>();
33
+ private lastProps?: any;
34
+
29
35
  unmountTime: number = 0;
30
36
  mountTime: number = 0;
31
37
 
@@ -59,6 +65,11 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
59
65
  if (this.isUsingDependencies && options?.dependencies) {
60
66
  this.dependencyValues = options.dependencies(this.blocInstance);
61
67
  }
68
+
69
+ // Handle initial props if provided
70
+ if (options?.props !== undefined) {
71
+ this.updateProps(options.props);
72
+ }
62
73
  }
63
74
 
64
75
  trackAccess(
@@ -133,7 +144,7 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
133
144
  newState: BlocState<InstanceType<B>>,
134
145
  oldState: BlocState<InstanceType<B>>,
135
146
  ) => {
136
- // Handle dependency-based change detection
147
+ // Case 1: Manual dependencies provided
137
148
  if (this.isUsingDependencies && this.options?.dependencies) {
138
149
  const newValues = this.options.dependencies(this.blocInstance);
139
150
  const hasChanged = this.hasDependencyValuesChanged(
@@ -146,7 +157,15 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
146
157
  }
147
158
 
148
159
  this.dependencyValues = newValues;
149
- } else {
160
+ }
161
+ // Case 2: Proxy tracking disabled globally (and no manual dependencies)
162
+ else if (!Blac.config.proxyDependencyTracking) {
163
+ // Always trigger re-render when proxy tracking is disabled
164
+ options.onChange();
165
+ return;
166
+ }
167
+ // Case 3: Proxy tracking enabled (default behavior)
168
+ else {
150
169
  // Check if any tracked values have changed (proxy-based tracking)
151
170
  const consumerInfo = this.consumerTracker.getConsumerInfo(
152
171
  this.componentRef.current,
@@ -202,6 +221,11 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
202
221
  this.consumerTracker.unregister(this.componentRef.current);
203
222
  this.blocInstance._removeConsumer(this.id);
204
223
 
224
+ // Clear ownership if we own this bloc
225
+ if (BlacAdapter.propsOwners.get(this.blocInstance) === this.id) {
226
+ BlacAdapter.propsOwners.delete(this.blocInstance);
227
+ }
228
+
205
229
  // Call onUnmount callback
206
230
  if (this.options?.onUnmount) {
207
231
  try {
@@ -215,8 +239,9 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
215
239
  getProxyState = (
216
240
  state: BlocState<InstanceType<B>>,
217
241
  ): BlocState<InstanceType<B>> => {
218
- if (this.isUsingDependencies) {
219
- return state; // Return raw state when using dependencies
242
+ // Return raw state if proxy tracking is disabled globally or using manual dependencies
243
+ if (this.isUsingDependencies || !Blac.config.proxyDependencyTracking) {
244
+ return state;
220
245
  }
221
246
 
222
247
  return ProxyFactory.getProxyState({
@@ -227,8 +252,9 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
227
252
  };
228
253
 
229
254
  getProxyBlocInstance = (): InstanceType<B> => {
230
- if (this.isUsingDependencies) {
231
- return this.blocInstance; // Return raw instance when using dependencies
255
+ // Return raw instance if proxy tracking is disabled globally or using manual dependencies
256
+ if (this.isUsingDependencies || !Blac.config.proxyDependencyTracking) {
257
+ return this.blocInstance;
232
258
  }
233
259
 
234
260
  return ProxyFactory.getProxyBlocInstance({
@@ -243,6 +269,45 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
243
269
  return this.hasMounted;
244
270
  }
245
271
 
272
+ updateProps(props: any): void {
273
+ const bloc = this.blocInstance;
274
+
275
+ // Check ownership
276
+ if (!BlacAdapter.propsOwners.has(bloc)) {
277
+ // First adapter with props becomes the owner
278
+ BlacAdapter.propsOwners.set(bloc, this.id);
279
+ }
280
+
281
+ if (BlacAdapter.propsOwners.get(bloc) !== this.id) {
282
+ Blac.warn(
283
+ `[BlacAdapter] Attempted to set props on ${bloc.constructor.name} from non-owner adapter`
284
+ );
285
+ return;
286
+ }
287
+
288
+ // Disposal safety
289
+ if ((bloc as any)._disposalState === BlocLifecycleState.DISPOSED ||
290
+ (bloc as any)._disposalState === BlocLifecycleState.DISPOSING) {
291
+ return;
292
+ }
293
+
294
+ // Check if props have changed
295
+ if (shallowEqual(this.lastProps, props)) {
296
+ return;
297
+ }
298
+
299
+ // Update props based on bloc type
300
+ if ('add' in bloc && typeof bloc.add === 'function') {
301
+ // Bloc: dispatch PropsUpdated event
302
+ (bloc as any).add(new PropsUpdated(props));
303
+ } else if ('_updateProps' in bloc && typeof (bloc as any)._updateProps === 'function') {
304
+ // Cubit: direct props update
305
+ (bloc as any)._updateProps(props);
306
+ }
307
+
308
+ this.lastProps = props;
309
+ }
310
+
246
311
  private hasDependencyValuesChanged(
247
312
  prev: unknown[] | undefined,
248
313
  next: unknown[],
package/src/events.ts ADDED
@@ -0,0 +1,3 @@
1
+ export class PropsUpdated<P = any> {
2
+ constructor(public readonly props: P) {}
3
+ }
package/src/index.ts CHANGED
@@ -4,12 +4,17 @@ export * from './Bloc';
4
4
  export * from './BlocBase';
5
5
  export * from './Cubit';
6
6
  export * from './types';
7
+ export * from './events';
7
8
 
8
9
  // Utilities
9
10
  export * from './utils/uuid';
11
+ export * from './utils/shallowEqual';
10
12
 
11
13
  // Test utilities
12
14
  export * from './testing';
13
15
 
14
16
  // Adapter
15
17
  export * from './adapter';
18
+
19
+ // Plugins
20
+ export * from './plugins';
@@ -0,0 +1,220 @@
1
+ import { BlocPlugin, PluginRegistry, ErrorContext } from '../core/types';
2
+
3
+ /**
4
+ * Registry for bloc-specific plugins
5
+ */
6
+ export class BlocPluginRegistry<TState, TEvent = never> implements PluginRegistry<BlocPlugin<TState, TEvent>> {
7
+ private plugins = new Map<string, BlocPlugin<TState, TEvent>>();
8
+ private executionOrder: string[] = [];
9
+ private attached = false;
10
+
11
+ /**
12
+ * Add a bloc plugin
13
+ */
14
+ add(plugin: BlocPlugin<TState, TEvent>): void {
15
+ if (this.plugins.has(plugin.name)) {
16
+ throw new Error(`Plugin '${plugin.name}' is already registered`);
17
+ }
18
+
19
+ // Validate capabilities
20
+ if (plugin.capabilities) {
21
+ this.validateCapabilities(plugin);
22
+ }
23
+
24
+ this.plugins.set(plugin.name, plugin);
25
+ this.executionOrder.push(plugin.name);
26
+ }
27
+
28
+ /**
29
+ * Remove a bloc plugin
30
+ */
31
+ remove(pluginName: string): boolean {
32
+ const plugin = this.plugins.get(pluginName);
33
+ if (!plugin) return false;
34
+
35
+ // Call onDetach if attached
36
+ if (this.attached && plugin.onDetach) {
37
+ try {
38
+ plugin.onDetach();
39
+ } catch (error) {
40
+ console.error(`Plugin '${pluginName}' error in onDetach:`, error);
41
+ }
42
+ }
43
+
44
+ this.plugins.delete(pluginName);
45
+ this.executionOrder = this.executionOrder.filter(name => name !== pluginName);
46
+
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Get a plugin by name
52
+ */
53
+ get(pluginName: string): BlocPlugin<TState, TEvent> | undefined {
54
+ return this.plugins.get(pluginName);
55
+ }
56
+
57
+ /**
58
+ * Get all plugins in execution order
59
+ */
60
+ getAll(): ReadonlyArray<BlocPlugin<TState, TEvent>> {
61
+ return this.executionOrder.map(name => this.plugins.get(name)!);
62
+ }
63
+
64
+ /**
65
+ * Clear all plugins
66
+ */
67
+ clear(): void {
68
+ // Detach all plugins first
69
+ if (this.attached) {
70
+ for (const plugin of this.getAll()) {
71
+ if (plugin.onDetach) {
72
+ try {
73
+ plugin.onDetach();
74
+ } catch (error) {
75
+ console.error(`Plugin '${plugin.name}' error in onDetach:`, error);
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ this.plugins.clear();
82
+ this.executionOrder = [];
83
+ this.attached = false;
84
+ }
85
+
86
+ /**
87
+ * Attach all plugins to a bloc
88
+ */
89
+ attach(bloc: any): void {
90
+ if (this.attached) {
91
+ throw new Error('Plugins are already attached');
92
+ }
93
+
94
+ for (const plugin of this.getAll()) {
95
+ if (plugin.onAttach) {
96
+ try {
97
+ plugin.onAttach(bloc);
98
+ } catch (error) {
99
+ console.error(`Plugin '${plugin.name}' error in onAttach:`, error);
100
+ // Remove failing plugin
101
+ this.remove(plugin.name);
102
+ }
103
+ }
104
+ }
105
+
106
+ this.attached = true;
107
+ }
108
+
109
+ /**
110
+ * Transform state through all plugins
111
+ */
112
+ transformState(previousState: TState, nextState: TState): TState {
113
+ let transformedState = nextState;
114
+
115
+ for (const plugin of this.getAll()) {
116
+ if (plugin.transformState && this.canTransformState(plugin)) {
117
+ try {
118
+ transformedState = plugin.transformState(previousState, transformedState);
119
+ } catch (error) {
120
+ console.error(`Plugin '${plugin.name}' error in transformState:`, error);
121
+ // Continue with untransformed state
122
+ }
123
+ }
124
+ }
125
+
126
+ return transformedState;
127
+ }
128
+
129
+ /**
130
+ * Transform event through all plugins
131
+ */
132
+ transformEvent(event: TEvent): TEvent | null {
133
+ let transformedEvent: TEvent | null = event;
134
+
135
+ for (const plugin of this.getAll()) {
136
+ if (transformedEvent === null) break;
137
+
138
+ if (plugin.transformEvent && this.canInterceptEvents(plugin)) {
139
+ try {
140
+ transformedEvent = plugin.transformEvent(transformedEvent);
141
+ } catch (error) {
142
+ console.error(`Plugin '${plugin.name}' error in transformEvent:`, error);
143
+ // Continue with untransformed event
144
+ }
145
+ }
146
+ }
147
+
148
+ return transformedEvent;
149
+ }
150
+
151
+ /**
152
+ * Notify plugins of state change
153
+ */
154
+ notifyStateChange(previousState: TState, currentState: TState): void {
155
+ for (const plugin of this.getAll()) {
156
+ if (plugin.onStateChange && this.canReadState(plugin)) {
157
+ try {
158
+ plugin.onStateChange(previousState, currentState);
159
+ } catch (error) {
160
+ console.error(`Plugin '${plugin.name}' error in onStateChange:`, error);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Notify plugins of event
168
+ */
169
+ notifyEvent(event: TEvent): void {
170
+ for (const plugin of this.getAll()) {
171
+ if (plugin.onEvent && this.canReadState(plugin)) {
172
+ try {
173
+ plugin.onEvent(event);
174
+ } catch (error) {
175
+ console.error(`Plugin '${plugin.name}' error in onEvent:`, error);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Notify plugins of error
183
+ */
184
+ notifyError(error: Error, context: ErrorContext): void {
185
+ for (const plugin of this.getAll()) {
186
+ if (plugin.onError) {
187
+ try {
188
+ plugin.onError(error, context);
189
+ } catch (hookError) {
190
+ console.error(`Plugin '${plugin.name}' error in onError:`, hookError);
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ private validateCapabilities(plugin: BlocPlugin<TState, TEvent>): void {
197
+ const caps = plugin.capabilities!;
198
+
199
+ // Validate logical constraints
200
+ if (caps.transformState && !caps.readState) {
201
+ throw new Error(`Plugin '${plugin.name}': transformState requires readState capability`);
202
+ }
203
+
204
+ if (caps.interceptEvents && !caps.readState) {
205
+ throw new Error(`Plugin '${plugin.name}': interceptEvents requires readState capability`);
206
+ }
207
+ }
208
+
209
+ private canReadState(plugin: BlocPlugin<TState, TEvent>): boolean {
210
+ return !plugin.capabilities || plugin.capabilities.readState !== false;
211
+ }
212
+
213
+ private canTransformState(plugin: BlocPlugin<TState, TEvent>): boolean {
214
+ return !plugin.capabilities || plugin.capabilities.transformState === true;
215
+ }
216
+
217
+ private canInterceptEvents(plugin: BlocPlugin<TState, TEvent>): boolean {
218
+ return !plugin.capabilities || plugin.capabilities.interceptEvents === true;
219
+ }
220
+ }
@@ -0,0 +1,192 @@
1
+ import { BlacPlugin, PluginRegistry, PluginMetrics, ErrorContext } from '../core/types';
2
+ import { BlocBase } from '../BlocBase';
3
+ import { Bloc } from '../Bloc';
4
+
5
+ /**
6
+ * Registry for system-wide plugins
7
+ */
8
+ export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
9
+ private plugins = new Map<string, BlacPlugin>();
10
+ private metrics = new Map<string, Map<string, PluginMetrics>>();
11
+ private executionOrder: string[] = [];
12
+
13
+ /**
14
+ * Add a system plugin
15
+ */
16
+ add(plugin: BlacPlugin): void {
17
+ if (this.plugins.has(plugin.name)) {
18
+ throw new Error(`Plugin '${plugin.name}' is already registered`);
19
+ }
20
+
21
+ this.plugins.set(plugin.name, plugin);
22
+ this.executionOrder.push(plugin.name);
23
+ this.initializeMetrics(plugin.name);
24
+ }
25
+
26
+ /**
27
+ * Remove a system plugin
28
+ */
29
+ remove(pluginName: string): boolean {
30
+ const plugin = this.plugins.get(pluginName);
31
+ if (!plugin) return false;
32
+
33
+ this.plugins.delete(pluginName);
34
+ this.metrics.delete(pluginName);
35
+ this.executionOrder = this.executionOrder.filter(name => name !== pluginName);
36
+
37
+ return true;
38
+ }
39
+
40
+ /**
41
+ * Get a plugin by name
42
+ */
43
+ get(pluginName: string): BlacPlugin | undefined {
44
+ return this.plugins.get(pluginName);
45
+ }
46
+
47
+ /**
48
+ * Get all plugins in execution order
49
+ */
50
+ getAll(): ReadonlyArray<BlacPlugin> {
51
+ return this.executionOrder.map(name => this.plugins.get(name)!);
52
+ }
53
+
54
+ /**
55
+ * Clear all plugins
56
+ */
57
+ clear(): void {
58
+ this.plugins.clear();
59
+ this.metrics.clear();
60
+ this.executionOrder = [];
61
+ }
62
+
63
+ /**
64
+ * Execute a hook on all plugins
65
+ */
66
+ executeHook(
67
+ hookName: keyof BlacPlugin,
68
+ args: unknown[],
69
+ errorHandler?: (error: Error, plugin: BlacPlugin) => void
70
+ ): void {
71
+ for (const pluginName of this.executionOrder) {
72
+ const plugin = this.plugins.get(pluginName)!;
73
+ const hook = plugin[hookName] as Function | undefined;
74
+
75
+ if (typeof hook !== 'function') continue;
76
+
77
+ const startTime = performance.now();
78
+
79
+ try {
80
+ hook.apply(plugin, args);
81
+ this.recordSuccess(pluginName, hookName as string, startTime);
82
+ } catch (error) {
83
+ this.recordError(pluginName, hookName as string, error as Error);
84
+
85
+ if (errorHandler) {
86
+ errorHandler(error as Error, plugin);
87
+ } else {
88
+ // Default: log and continue
89
+ console.error(`Plugin '${pluginName}' error in hook '${hookName as string}':`, error);
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Bootstrap all plugins
97
+ */
98
+ bootstrap(): void {
99
+ this.executeHook('beforeBootstrap', []);
100
+ this.executeHook('afterBootstrap', []);
101
+ }
102
+
103
+ /**
104
+ * Shutdown all plugins
105
+ */
106
+ shutdown(): void {
107
+ this.executeHook('beforeShutdown', []);
108
+ this.executeHook('afterShutdown', []);
109
+ }
110
+
111
+ /**
112
+ * Notify plugins of bloc creation
113
+ */
114
+ notifyBlocCreated(bloc: BlocBase<any>): void {
115
+ this.executeHook('onBlocCreated', [bloc]);
116
+ }
117
+
118
+ /**
119
+ * Notify plugins of bloc disposal
120
+ */
121
+ notifyBlocDisposed(bloc: BlocBase<any>): void {
122
+ this.executeHook('onBlocDisposed', [bloc]);
123
+ }
124
+
125
+ /**
126
+ * Notify plugins of state change
127
+ */
128
+ notifyStateChanged(bloc: BlocBase<any>, previousState: any, currentState: any): void {
129
+ this.executeHook('onStateChanged', [bloc, previousState, currentState]);
130
+ }
131
+
132
+ /**
133
+ * Notify plugins of event addition
134
+ */
135
+ notifyEventAdded(bloc: Bloc<any, any>, event: any): void {
136
+ this.executeHook('onEventAdded', [bloc, event]);
137
+ }
138
+
139
+ /**
140
+ * Notify plugins of errors
141
+ */
142
+ notifyError(error: Error, bloc: BlocBase<unknown>, context: ErrorContext): void {
143
+ this.executeHook('onError', [error, bloc, context], (hookError, plugin) => {
144
+ // Double fault protection - if error handler fails, just log
145
+ console.error(`Plugin '${plugin.name}' error handler failed:`, hookError);
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Get metrics for a plugin
151
+ */
152
+ getMetrics(pluginName: string): Map<string, PluginMetrics> | undefined {
153
+ return this.metrics.get(pluginName);
154
+ }
155
+
156
+ private initializeMetrics(pluginName: string): void {
157
+ this.metrics.set(pluginName, new Map());
158
+ }
159
+
160
+ private recordSuccess(pluginName: string, hookName: string, startTime: number): void {
161
+ const pluginMetrics = this.metrics.get(pluginName)!;
162
+ const hookMetrics = pluginMetrics.get(hookName) || {
163
+ executionTime: 0,
164
+ executionCount: 0,
165
+ errorCount: 0
166
+ };
167
+
168
+ const executionTime = performance.now() - startTime;
169
+
170
+ pluginMetrics.set(hookName, {
171
+ ...hookMetrics,
172
+ executionTime: hookMetrics.executionTime + executionTime,
173
+ executionCount: hookMetrics.executionCount + 1,
174
+ lastExecutionTime: executionTime
175
+ });
176
+ }
177
+
178
+ private recordError(pluginName: string, hookName: string, error: Error): void {
179
+ const pluginMetrics = this.metrics.get(pluginName)!;
180
+ const hookMetrics = pluginMetrics.get(hookName) || {
181
+ executionTime: 0,
182
+ executionCount: 0,
183
+ errorCount: 0
184
+ };
185
+
186
+ pluginMetrics.set(hookName, {
187
+ ...hookMetrics,
188
+ errorCount: hookMetrics.errorCount + 1,
189
+ lastError: error
190
+ });
191
+ }
192
+ }
@@ -0,0 +1,8 @@
1
+ // Core types
2
+ export * from './types';
3
+
4
+ // System plugins
5
+ export { SystemPluginRegistry } from './SystemPluginRegistry';
6
+
7
+ // Bloc plugins
8
+ export { BlocPluginRegistry } from './BlocPluginRegistry';
@@ -0,0 +1,100 @@
1
+ import { BlocBase } from '../BlocBase';
2
+ import { Bloc } from '../Bloc';
3
+
4
+ /**
5
+ * Error context provided to error handlers
6
+ */
7
+ export interface ErrorContext {
8
+ readonly phase: 'initialization' | 'state-change' | 'event-processing' | 'disposal';
9
+ readonly operation: string;
10
+ readonly metadata?: Record<string, unknown>;
11
+ }
12
+
13
+ /**
14
+ * Plugin capabilities for security model
15
+ */
16
+ export interface PluginCapabilities {
17
+ readonly readState: boolean;
18
+ readonly transformState: boolean;
19
+ readonly interceptEvents: boolean;
20
+ readonly persistData: boolean;
21
+ readonly accessMetadata: boolean;
22
+ }
23
+
24
+ /**
25
+ * Base interface for all plugins
26
+ */
27
+ export interface Plugin {
28
+ readonly name: string;
29
+ readonly version: string;
30
+ readonly capabilities?: PluginCapabilities;
31
+ }
32
+
33
+ /**
34
+ * System-wide plugin that observes all blocs
35
+ */
36
+ export interface BlacPlugin extends Plugin {
37
+ // Lifecycle hooks - all synchronous
38
+ beforeBootstrap?(): void;
39
+ afterBootstrap?(): void;
40
+ beforeShutdown?(): void;
41
+ afterShutdown?(): void;
42
+
43
+ // System-wide observations
44
+ onBlocCreated?(bloc: BlocBase<any>): void;
45
+ onBlocDisposed?(bloc: BlocBase<any>): void;
46
+ onStateChanged?(bloc: BlocBase<any>, previousState: any, currentState: any): void;
47
+ onEventAdded?(bloc: Bloc<any, any>, event: any): void;
48
+ onError?(error: Error, bloc: BlocBase<unknown>, context: ErrorContext): void;
49
+ }
50
+
51
+ /**
52
+ * Bloc-specific plugin that can transform behavior
53
+ */
54
+ export interface BlocPlugin<TState = any, TEvent = never> extends Plugin {
55
+ // Transform hooks - can modify data
56
+ transformState?(previousState: TState, nextState: TState): TState;
57
+ transformEvent?(event: TEvent): TEvent | null;
58
+
59
+ // Lifecycle hooks
60
+ onAttach?(bloc: BlocBase<TState, any>): void;
61
+ onDetach?(): void;
62
+
63
+ // Observation hooks
64
+ onStateChange?(previousState: TState, currentState: TState): void;
65
+ onEvent?(event: TEvent): void;
66
+ onError?(error: Error, context: ErrorContext): void;
67
+ }
68
+
69
+ /**
70
+ * Plugin execution context for performance monitoring
71
+ */
72
+ export interface PluginExecutionContext {
73
+ readonly pluginName: string;
74
+ readonly hookName: string;
75
+ readonly startTime: number;
76
+ readonly blocName?: string;
77
+ readonly blocId?: string;
78
+ }
79
+
80
+ /**
81
+ * Plugin metrics for monitoring
82
+ */
83
+ export interface PluginMetrics {
84
+ readonly executionTime: number;
85
+ readonly executionCount: number;
86
+ readonly errorCount: number;
87
+ readonly lastError?: Error;
88
+ readonly lastExecutionTime?: number;
89
+ }
90
+
91
+ /**
92
+ * Plugin registry interface
93
+ */
94
+ export interface PluginRegistry<T extends Plugin> {
95
+ add(plugin: T): void;
96
+ remove(pluginName: string): boolean;
97
+ get(pluginName: string): T | undefined;
98
+ getAll(): ReadonlyArray<T>;
99
+ clear(): void;
100
+ }