@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
|
@@ -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,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
|
+
}
|