mycelia-kernel-plugin 1.3.0 → 1.4.1
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/README.md +54 -7
- package/bin/cli.js +261 -0
- package/package.json +15 -5
- package/src/angular/builders.js +37 -0
- package/src/angular/helpers.js +102 -0
- package/src/angular/index.js +189 -0
- package/src/angular/services.js +32 -0
- package/src/builder/dependency-graph.js +65 -9
- package/src/builder/hook-processor.js +26 -4
- package/src/builder/utils.js +78 -22
- package/src/contract/contracts/listeners.contract.js +2 -0
- package/src/core/facet.js +16 -3
- package/src/hooks/listeners/use-simple-listeners.js +172 -0
- package/src/index.js +19 -0
- package/src/manager/facet-manager.js +10 -2
- package/src/qwik/builders.js +39 -0
- package/src/qwik/index.js +178 -0
- package/src/qwik/listeners.js +96 -0
- package/src/qwik/queues.js +87 -0
- package/src/qwik/signals.js +32 -0
- package/src/react/README.md +3 -0
- package/src/solid/README.md +69 -0
- package/src/solid/index.js +387 -0
- package/src/utils/instrumentation.js +204 -0
- package/src/utils/use-base.js +205 -30
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mycelia Plugin System - Solid.js Bindings
|
|
3
|
+
*
|
|
4
|
+
* Solid.js utilities that make the Mycelia Plugin System feel natural
|
|
5
|
+
* inside Solid.js applications using signals and context.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```jsx
|
|
9
|
+
* import { MyceliaProvider, useFacet, useListener } from 'mycelia-kernel-plugin/solid';
|
|
10
|
+
*
|
|
11
|
+
* const buildSystem = () => useBase('app')
|
|
12
|
+
* .use(useDatabase)
|
|
13
|
+
* .build();
|
|
14
|
+
*
|
|
15
|
+
* function App() {
|
|
16
|
+
* return (
|
|
17
|
+
* <MyceliaProvider build={buildSystem}>
|
|
18
|
+
* <MyComponent />
|
|
19
|
+
* </MyceliaProvider>
|
|
20
|
+
* );
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* function MyComponent() {
|
|
24
|
+
* const db = useFacet('database');
|
|
25
|
+
* useListener('user:created', (msg) => console.log(msg));
|
|
26
|
+
* // ...
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { createContext, useContext, createSignal, createEffect, onCleanup, onMount } from 'solid-js';
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Core Bindings: Provider + Basic Hooks
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
const MyceliaContext = createContext();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* MyceliaProvider - Provides Mycelia system to Solid.js component tree
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} props
|
|
43
|
+
* @param {Function} props.build - Async function that returns a built system
|
|
44
|
+
* @param {JSX.Element} props.children - Child components
|
|
45
|
+
* @param {JSX.Element} [props.fallback] - Optional loading/fallback component
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```jsx
|
|
49
|
+
* <MyceliaProvider build={buildSystem}>
|
|
50
|
+
* <App />
|
|
51
|
+
* </MyceliaProvider>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function MyceliaProvider(props) {
|
|
55
|
+
const [system, setSystem] = createSignal(null);
|
|
56
|
+
const [error, setError] = createSignal(null);
|
|
57
|
+
const [loading, setLoading] = createSignal(true);
|
|
58
|
+
let currentSystem = null;
|
|
59
|
+
|
|
60
|
+
onMount(async () => {
|
|
61
|
+
try {
|
|
62
|
+
currentSystem = await props.build();
|
|
63
|
+
setSystem(currentSystem);
|
|
64
|
+
setError(null);
|
|
65
|
+
setLoading(false);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
setError(err);
|
|
68
|
+
setSystem(null);
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
onCleanup(() => {
|
|
74
|
+
if (currentSystem && typeof currentSystem.dispose === 'function') {
|
|
75
|
+
currentSystem.dispose().catch(() => {
|
|
76
|
+
// Ignore disposal errors
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (error()) {
|
|
82
|
+
throw error(); // Let error boundaries handle it
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (loading()) {
|
|
86
|
+
return props.fallback || null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<MyceliaContext.Provider value={system}>
|
|
91
|
+
{props.children}
|
|
92
|
+
</MyceliaContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* useMycelia - Get the Mycelia system from context
|
|
98
|
+
*
|
|
99
|
+
* @returns {Function} Signal accessor that returns the Mycelia system instance
|
|
100
|
+
* @throws {Error} If used outside MyceliaProvider
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```jsx
|
|
104
|
+
* function MyComponent() {
|
|
105
|
+
* const system = useMycelia();
|
|
106
|
+
* // Use system().find(), system().listeners, etc.
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function useMycelia() {
|
|
111
|
+
const context = useContext(MyceliaContext);
|
|
112
|
+
if (!context) {
|
|
113
|
+
throw new Error('useMycelia must be used within a MyceliaProvider');
|
|
114
|
+
}
|
|
115
|
+
return context;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* useFacet - Get a facet by kind from the system with reactivity
|
|
120
|
+
*
|
|
121
|
+
* @param {string} kind - Facet kind identifier
|
|
122
|
+
* @returns {Function} Signal accessor that returns the facet instance, or null if not found
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```jsx
|
|
126
|
+
* function UserList() {
|
|
127
|
+
* const db = useFacet('database');
|
|
128
|
+
* // Use db().query(), etc.
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function useFacet(kind) {
|
|
133
|
+
const system = useMycelia();
|
|
134
|
+
const [facet, setFacet] = createSignal(() => {
|
|
135
|
+
const sys = system();
|
|
136
|
+
return sys?.find?.(kind) ?? null;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
createEffect(() => {
|
|
140
|
+
const sys = system();
|
|
141
|
+
setFacet(sys?.find?.(kind) ?? null);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return facet;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Listener Helpers
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* useListener - Register an event listener with automatic cleanup
|
|
153
|
+
*
|
|
154
|
+
* @param {string} eventName - Event name/path to listen for
|
|
155
|
+
* @param {Function} handler - Handler function: (message) => void
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```jsx
|
|
159
|
+
* function AuditLog() {
|
|
160
|
+
* useListener('user:created', (msg) => {
|
|
161
|
+
* console.log('User created:', msg.body);
|
|
162
|
+
* });
|
|
163
|
+
* // ...
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function useListener(eventName, handler) {
|
|
168
|
+
const system = useMycelia();
|
|
169
|
+
let listeners = null;
|
|
170
|
+
let wrappedHandler = null;
|
|
171
|
+
|
|
172
|
+
createEffect(() => {
|
|
173
|
+
const sys = system();
|
|
174
|
+
if (!sys) return;
|
|
175
|
+
|
|
176
|
+
listeners = sys.listeners; // useListeners facet
|
|
177
|
+
if (!listeners || !listeners.hasListeners?.()) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
wrappedHandler = (msg) => {
|
|
182
|
+
handler(msg);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
listeners.on(eventName, wrappedHandler);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
onCleanup(() => {
|
|
189
|
+
if (listeners && wrappedHandler) {
|
|
190
|
+
listeners.off?.(eventName, wrappedHandler);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* useEventStream - Subscribe to events and keep them in reactive state
|
|
197
|
+
*
|
|
198
|
+
* @param {string} eventName - Event name/path to listen for
|
|
199
|
+
* @param {Object} [options={}] - Options
|
|
200
|
+
* @param {boolean} [options.accumulate=false] - If true, accumulate events in array
|
|
201
|
+
* @returns {Function} Signal accessor that returns latest event value, array of events, or null
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```jsx
|
|
205
|
+
* function EventList() {
|
|
206
|
+
* const events = useEventStream('todo:created', { accumulate: true });
|
|
207
|
+
* return <ul>{events()?.map(e => <li key={e.id}>{e.text}</li>)}</ul>;
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
export function useEventStream(eventName, options = {}) {
|
|
212
|
+
const { accumulate = false } = options;
|
|
213
|
+
const [value, setValue] = createSignal(accumulate ? [] : null);
|
|
214
|
+
|
|
215
|
+
useListener(eventName, (msg) => {
|
|
216
|
+
if (accumulate) {
|
|
217
|
+
setValue((prev) => [...prev, msg.body]);
|
|
218
|
+
} else {
|
|
219
|
+
setValue(msg.body);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Queue Helpers
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* useQueueStatus - Get queue status with reactive updates
|
|
232
|
+
*
|
|
233
|
+
* @returns {Function} Signal accessor that returns queue status object
|
|
234
|
+
* @returns {number} size - Current queue size
|
|
235
|
+
* @returns {number} capacity - Maximum queue capacity
|
|
236
|
+
* @returns {number} utilization - Utilization ratio (0-1)
|
|
237
|
+
* @returns {boolean} isFull - Whether queue is full
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```jsx
|
|
241
|
+
* function QueueStatus() {
|
|
242
|
+
* const status = useQueueStatus();
|
|
243
|
+
* return <div>Queue: {status().size}/{status().capacity}</div>;
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export function useQueueStatus() {
|
|
248
|
+
const queue = useFacet('queue');
|
|
249
|
+
const [status, setStatus] = createSignal(() => {
|
|
250
|
+
const q = queue();
|
|
251
|
+
if (q?.getQueueStatus) {
|
|
252
|
+
return q.getQueueStatus();
|
|
253
|
+
}
|
|
254
|
+
return { size: 0, maxSize: 0, utilization: 0, isFull: false };
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Poll for status updates (could be improved with event-based updates)
|
|
258
|
+
createEffect(() => {
|
|
259
|
+
const q = queue();
|
|
260
|
+
if (!q || !q.getQueueStatus) return;
|
|
261
|
+
|
|
262
|
+
const interval = setInterval(() => {
|
|
263
|
+
const newStatus = q.getQueueStatus();
|
|
264
|
+
setStatus(newStatus);
|
|
265
|
+
}, 100); // Poll every 100ms
|
|
266
|
+
|
|
267
|
+
onCleanup(() => clearInterval(interval));
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return () => {
|
|
271
|
+
const s = status();
|
|
272
|
+
return {
|
|
273
|
+
size: s.size || 0,
|
|
274
|
+
capacity: s.maxSize || 0,
|
|
275
|
+
utilization: s.utilization || 0,
|
|
276
|
+
isFull: s.isFull || false
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* useQueueDrain - Automatically drain queue on mount
|
|
283
|
+
*
|
|
284
|
+
* @param {Object} [options={}] - Options
|
|
285
|
+
* @param {number} [options.interval=100] - Polling interval in ms
|
|
286
|
+
* @param {Function} [options.onMessage] - Callback for each message: (msg, options) => void
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```jsx
|
|
290
|
+
* function QueueProcessor() {
|
|
291
|
+
* useQueueDrain({
|
|
292
|
+
* interval: 50,
|
|
293
|
+
* onMessage: (msg) => console.log('Processed:', msg)
|
|
294
|
+
* });
|
|
295
|
+
* return null;
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export function useQueueDrain(options = {}) {
|
|
300
|
+
const { interval = 100, onMessage } = options;
|
|
301
|
+
const queue = useFacet('queue');
|
|
302
|
+
|
|
303
|
+
createEffect(() => {
|
|
304
|
+
const q = queue();
|
|
305
|
+
if (!q || !q.hasMessagesToProcess) return;
|
|
306
|
+
|
|
307
|
+
const processInterval = setInterval(() => {
|
|
308
|
+
if (q.hasMessagesToProcess()) {
|
|
309
|
+
const next = q.selectNextMessage();
|
|
310
|
+
if (next && onMessage) {
|
|
311
|
+
onMessage(next.msg, next.options);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}, interval);
|
|
315
|
+
|
|
316
|
+
onCleanup(() => clearInterval(processInterval));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Builder Helpers
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* createSolidSystemBuilder - Create a reusable system builder function
|
|
326
|
+
*
|
|
327
|
+
* @param {string} name - System name
|
|
328
|
+
* @param {Function} configure - Configuration function: (builder) => builder
|
|
329
|
+
* @returns {Function} Build function: () => Promise<System>
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```js
|
|
333
|
+
* import { useBase } from 'mycelia-kernel-plugin';
|
|
334
|
+
*
|
|
335
|
+
* const buildTodoSystem = createSolidSystemBuilder('todo-app', (b) =>
|
|
336
|
+
* b
|
|
337
|
+
* .config('database', { host: 'localhost' })
|
|
338
|
+
* .use(useDatabase)
|
|
339
|
+
* .use(useListeners)
|
|
340
|
+
* );
|
|
341
|
+
*
|
|
342
|
+
* // Then use in Provider
|
|
343
|
+
* <MyceliaProvider build={buildTodoSystem}>
|
|
344
|
+
* <App />
|
|
345
|
+
* </MyceliaProvider>
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
export function createSolidSystemBuilder(name, configure) {
|
|
349
|
+
return async function build() {
|
|
350
|
+
// Import useBase - users should have it available
|
|
351
|
+
// This avoids bundling issues by letting users import useBase themselves
|
|
352
|
+
const { useBase } = await import('../utils/use-base.js');
|
|
353
|
+
let builder = useBase(name);
|
|
354
|
+
builder = configure(builder);
|
|
355
|
+
return builder.build();
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================================
|
|
360
|
+
// Facet Hook Generator
|
|
361
|
+
// ============================================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* createFacetSignal - Generate a custom signal hook for a specific facet kind
|
|
365
|
+
*
|
|
366
|
+
* @param {string} kind - Facet kind identifier
|
|
367
|
+
* @returns {Function} Custom hook: () => Signal<Facet | null>
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```js
|
|
371
|
+
* // In bindings/todo-hooks.ts
|
|
372
|
+
* export const useTodoStore = createFacetSignal('todoStore');
|
|
373
|
+
* export const useAuth = createFacetSignal('auth');
|
|
374
|
+
*
|
|
375
|
+
* // In component
|
|
376
|
+
* function TodoList() {
|
|
377
|
+
* const todoStore = useTodoStore();
|
|
378
|
+
* // Use todoStore()...
|
|
379
|
+
* }
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
export function createFacetSignal(kind) {
|
|
383
|
+
return function useNamedFacet() {
|
|
384
|
+
return useFacet(kind);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instrumentation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides timing instrumentation for debugging build, initialization, and disposal phases.
|
|
5
|
+
* Helps identify slow hooks and facets when debugging performance issues.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createSubsystemLogger } from './logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default thresholds for timing warnings (in milliseconds)
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_THRESHOLDS = {
|
|
14
|
+
hookExecution: 50, // Warn if hook execution takes > 50ms
|
|
15
|
+
facetInit: 100, // Warn if facet init takes > 100ms
|
|
16
|
+
facetDispose: 50, // Warn if facet dispose takes > 50ms
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if instrumentation is enabled
|
|
21
|
+
*
|
|
22
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
23
|
+
* @returns {boolean} True if instrumentation is enabled
|
|
24
|
+
*/
|
|
25
|
+
export function isInstrumentationEnabled(subsystem) {
|
|
26
|
+
// Enable if debug is on, or if instrumentation is explicitly enabled
|
|
27
|
+
return subsystem?.debug === true || subsystem?.ctx?.instrumentation === true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get timing thresholds from config or use defaults
|
|
32
|
+
*
|
|
33
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
34
|
+
* @returns {Object} Thresholds object
|
|
35
|
+
*/
|
|
36
|
+
function getThresholds(subsystem) {
|
|
37
|
+
const config = subsystem?.ctx?.config?.instrumentation || {};
|
|
38
|
+
return {
|
|
39
|
+
hookExecution: config.hookExecutionThreshold ?? DEFAULT_THRESHOLDS.hookExecution,
|
|
40
|
+
facetInit: config.facetInitThreshold ?? DEFAULT_THRESHOLDS.facetInit,
|
|
41
|
+
facetDispose: config.facetDisposeThreshold ?? DEFAULT_THRESHOLDS.facetDispose,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Time a hook execution and log if it exceeds threshold
|
|
47
|
+
*
|
|
48
|
+
* @param {Function} hook - Hook function to execute
|
|
49
|
+
* @param {Object} resolvedCtx - Resolved context
|
|
50
|
+
* @param {Object} api - Subsystem API
|
|
51
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
52
|
+
* @returns {*} Result of hook execution
|
|
53
|
+
*/
|
|
54
|
+
export function instrumentHookExecution(hook, resolvedCtx, api, subsystem) {
|
|
55
|
+
if (!isInstrumentationEnabled(subsystem)) {
|
|
56
|
+
// No instrumentation - just execute
|
|
57
|
+
return hook(resolvedCtx, api, subsystem);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const logger = createSubsystemLogger(subsystem);
|
|
61
|
+
const hookKind = hook.kind || '<unknown>';
|
|
62
|
+
const hookSource = hook.source || '<unknown>';
|
|
63
|
+
const thresholds = getThresholds(subsystem);
|
|
64
|
+
|
|
65
|
+
const start = performance.now();
|
|
66
|
+
let result;
|
|
67
|
+
try {
|
|
68
|
+
result = hook(resolvedCtx, api, subsystem);
|
|
69
|
+
} finally {
|
|
70
|
+
const duration = performance.now() - start;
|
|
71
|
+
|
|
72
|
+
if (duration > thresholds.hookExecution) {
|
|
73
|
+
logger.warn(
|
|
74
|
+
`⚠️ Slow hook execution: '${hookKind}' took ${duration.toFixed(2)}ms ` +
|
|
75
|
+
`(threshold: ${thresholds.hookExecution}ms) [${hookSource}]`
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
logger.log(`✓ Hook '${hookKind}' executed in ${duration.toFixed(2)}ms [${hookSource}]`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Time a facet initialization callback and log if it exceeds threshold
|
|
87
|
+
*
|
|
88
|
+
* @param {Facet} facet - Facet instance
|
|
89
|
+
* @param {Object} ctx - Context object
|
|
90
|
+
* @param {Object} api - Subsystem API
|
|
91
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
92
|
+
* @param {Function} initCallback - The init callback to execute
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
export async function instrumentFacetInit(facet, ctx, api, subsystem, initCallback) {
|
|
96
|
+
if (!initCallback) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!isInstrumentationEnabled(subsystem)) {
|
|
101
|
+
// No instrumentation - call callback directly
|
|
102
|
+
return initCallback({ ctx, api, subsystem, facet });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const logger = createSubsystemLogger(subsystem);
|
|
106
|
+
const facetKind = facet.getKind?.() || '<unknown>';
|
|
107
|
+
const facetSource = facet.getSource?.() || '<unknown>';
|
|
108
|
+
const thresholds = getThresholds(subsystem);
|
|
109
|
+
|
|
110
|
+
const start = performance.now();
|
|
111
|
+
try {
|
|
112
|
+
await initCallback({ ctx, api, subsystem, facet });
|
|
113
|
+
} finally {
|
|
114
|
+
const duration = performance.now() - start;
|
|
115
|
+
|
|
116
|
+
if (duration > thresholds.facetInit) {
|
|
117
|
+
logger.warn(
|
|
118
|
+
`⚠️ Slow facet initialization: '${facetKind}' took ${duration.toFixed(2)}ms ` +
|
|
119
|
+
`(threshold: ${thresholds.facetInit}ms) [${facetSource}]`
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
logger.log(`✓ Facet '${facetKind}' initialized in ${duration.toFixed(2)}ms [${facetSource}]`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Time a facet disposal callback and warn if it exceeds threshold
|
|
129
|
+
*
|
|
130
|
+
* @param {Facet} facet - Facet instance
|
|
131
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
132
|
+
* @param {Function} disposeCallback - The dispose callback to execute
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
export async function instrumentDisposeCallback(facet, subsystem, disposeCallback) {
|
|
136
|
+
if (!disposeCallback) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!isInstrumentationEnabled(subsystem)) {
|
|
141
|
+
// No instrumentation - call callback directly
|
|
142
|
+
return disposeCallback(facet);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const logger = createSubsystemLogger(subsystem);
|
|
146
|
+
const facetKind = facet.getKind?.() || '<unknown>';
|
|
147
|
+
const facetSource = facet.getSource?.() || '<unknown>';
|
|
148
|
+
const thresholds = getThresholds(subsystem);
|
|
149
|
+
|
|
150
|
+
const start = performance.now();
|
|
151
|
+
let errorOccurred = false;
|
|
152
|
+
try {
|
|
153
|
+
await disposeCallback(facet);
|
|
154
|
+
const duration = performance.now() - start;
|
|
155
|
+
|
|
156
|
+
if (duration > thresholds.facetDispose) {
|
|
157
|
+
logger.warn(
|
|
158
|
+
`⚠️ Slow facet disposal: '${facetKind}' took ${duration.toFixed(2)}ms ` +
|
|
159
|
+
`(threshold: ${thresholds.facetDispose}ms) [${facetSource}]`
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
logger.log(`✓ Facet '${facetKind}' disposed in ${duration.toFixed(2)}ms [${facetSource}]`);
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
errorOccurred = true;
|
|
166
|
+
const duration = performance.now() - start;
|
|
167
|
+
logger.warn(
|
|
168
|
+
`⚠️ Facet disposal error: '${facetKind}' failed after ${duration.toFixed(2)}ms ` +
|
|
169
|
+
`[${facetSource}]: ${error.message}`
|
|
170
|
+
);
|
|
171
|
+
// Re-throw so disposeAll can catch and handle it
|
|
172
|
+
throw error;
|
|
173
|
+
} finally {
|
|
174
|
+
// Ensure timing is logged even if error occurred
|
|
175
|
+
if (!errorOccurred) {
|
|
176
|
+
// Already logged in try block
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Time the entire build phase and log summary
|
|
183
|
+
*
|
|
184
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
185
|
+
* @param {Function} buildFn - Build function to execute
|
|
186
|
+
* @returns {Promise<void>}
|
|
187
|
+
*/
|
|
188
|
+
export async function instrumentBuildPhase(subsystem, buildFn) {
|
|
189
|
+
if (!isInstrumentationEnabled(subsystem)) {
|
|
190
|
+
// No instrumentation - just build
|
|
191
|
+
return buildFn();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const logger = createSubsystemLogger(subsystem);
|
|
195
|
+
const start = performance.now();
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await buildFn();
|
|
199
|
+
} finally {
|
|
200
|
+
const duration = performance.now() - start;
|
|
201
|
+
logger.log(`📦 Build phase completed in ${duration.toFixed(2)}ms`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|