mycelia-kernel-plugin 1.0.0

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.
Files changed (53) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +248 -0
  3. package/bin/cli.js +433 -0
  4. package/package.json +63 -0
  5. package/src/builder/context-resolver.js +62 -0
  6. package/src/builder/dependency-graph-cache.js +105 -0
  7. package/src/builder/dependency-graph.js +141 -0
  8. package/src/builder/facet-validator.js +43 -0
  9. package/src/builder/hook-processor.js +271 -0
  10. package/src/builder/index.js +13 -0
  11. package/src/builder/subsystem-builder.js +104 -0
  12. package/src/builder/utils.js +165 -0
  13. package/src/contract/contracts/hierarchy.contract.js +60 -0
  14. package/src/contract/contracts/index.js +17 -0
  15. package/src/contract/contracts/listeners.contract.js +66 -0
  16. package/src/contract/contracts/processor.contract.js +47 -0
  17. package/src/contract/contracts/queue.contract.js +58 -0
  18. package/src/contract/contracts/router.contract.js +53 -0
  19. package/src/contract/contracts/scheduler.contract.js +65 -0
  20. package/src/contract/contracts/server.contract.js +88 -0
  21. package/src/contract/contracts/speak.contract.js +50 -0
  22. package/src/contract/contracts/storage.contract.js +107 -0
  23. package/src/contract/contracts/websocket.contract.js +90 -0
  24. package/src/contract/facet-contract-registry.js +155 -0
  25. package/src/contract/facet-contract.js +136 -0
  26. package/src/contract/index.js +63 -0
  27. package/src/core/create-hook.js +63 -0
  28. package/src/core/facet.js +189 -0
  29. package/src/core/index.js +3 -0
  30. package/src/hooks/listeners/handler-group-manager.js +88 -0
  31. package/src/hooks/listeners/listener-manager-policies.js +229 -0
  32. package/src/hooks/listeners/listener-manager.js +668 -0
  33. package/src/hooks/listeners/listener-registry.js +176 -0
  34. package/src/hooks/listeners/listener-statistics.js +106 -0
  35. package/src/hooks/listeners/pattern-matcher.js +283 -0
  36. package/src/hooks/listeners/use-listeners.js +164 -0
  37. package/src/hooks/queue/bounded-queue.js +341 -0
  38. package/src/hooks/queue/circular-buffer.js +231 -0
  39. package/src/hooks/queue/subsystem-queue-manager.js +198 -0
  40. package/src/hooks/queue/use-queue.js +96 -0
  41. package/src/hooks/speak/use-speak.js +79 -0
  42. package/src/index.js +49 -0
  43. package/src/manager/facet-manager-transaction.js +45 -0
  44. package/src/manager/facet-manager.js +570 -0
  45. package/src/manager/index.js +3 -0
  46. package/src/system/base-subsystem.js +416 -0
  47. package/src/system/base-subsystem.utils.js +106 -0
  48. package/src/system/index.js +4 -0
  49. package/src/system/standalone-plugin-system.js +70 -0
  50. package/src/utils/debug-flag.js +34 -0
  51. package/src/utils/find-facet.js +30 -0
  52. package/src/utils/logger.js +84 -0
  53. package/src/utils/semver.js +221 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * useListeners Hook
3
+ *
4
+ * Provides listener management functionality to subsystems.
5
+ * Wraps ListenerManager and exposes on(), off(), hasListeners() methods.
6
+ *
7
+ * @param {Object} ctx - Context object containing config.listeners for listener configuration
8
+ * @param {Object} api - Subsystem API being built
9
+ * @param {BaseSubsystem} subsystem - Subsystem instance
10
+ * @returns {Facet} Facet object with listener methods
11
+ */
12
+ import { ListenerManager } from './listener-manager.js';
13
+ import { Facet } from '../../core/facet.js';
14
+ import { createHook } from '../../core/create-hook.js';
15
+ import { getDebugFlag } from '../../utils/debug-flag.js';
16
+ import { createLogger } from '../../utils/logger.js';
17
+
18
+ export const useListeners = createHook({
19
+ kind: 'listeners',
20
+ version: '1.0.0',
21
+ overwrite: false,
22
+ required: [],
23
+ attach: true,
24
+ source: import.meta.url,
25
+ contract: 'listeners',
26
+ // eslint-disable-next-line no-unused-vars
27
+ fn: (ctx, api, _subsystem) => {
28
+ const { name } = api;
29
+ const config = ctx.config?.listeners || {};
30
+ const debug = getDebugFlag(config, ctx);
31
+
32
+ // Listeners are optional - can be enabled/disabled
33
+ let listeners = null;
34
+ let listenersEnabled = false;
35
+
36
+ return new Facet('listeners', { attach: true, source: import.meta.url, contract: 'listeners' })
37
+ .add({
38
+
39
+ /**
40
+ * Check if listeners are enabled
41
+ * @returns {boolean} True if listeners are enabled
42
+ */
43
+ hasListeners() {
44
+ return listenersEnabled && listeners !== null;
45
+ },
46
+
47
+ /**
48
+ * Enable listeners
49
+ * @param {Object} [listenerOptions={}] - ListenerManager options (overrides config)
50
+ */
51
+ enableListeners(listenerOptions = {}) {
52
+ if (listeners === null) {
53
+ listeners = new ListenerManager({
54
+ registrationPolicy: listenerOptions.registrationPolicy || config.registrationPolicy || 'multiple',
55
+ debug: listenerOptions.debug !== undefined ? listenerOptions.debug : getDebugFlag(config, ctx),
56
+ policyOptions: listenerOptions.policyOptions || config.policyOptions || {}
57
+ });
58
+ }
59
+ listenersEnabled = true;
60
+ },
61
+
62
+ /**
63
+ * Disable listeners
64
+ */
65
+ disableListeners() {
66
+ listenersEnabled = false;
67
+ },
68
+
69
+ /**
70
+ * Register a listener for a specific path
71
+ * @param {string} path - Message path to listen for
72
+ * @param {Function|Object} handlers - Handler function or handler group object
73
+ * @param {Object} [options={}] - Registration options
74
+ * @param {boolean} [options.isHandlerGroup=false] - Whether handlers is a handler group object
75
+ * @returns {boolean} Success status
76
+ */
77
+ on(path, handlers, options = {}) {
78
+ // Check if listeners are enabled
79
+ if (!listenersEnabled || listeners === null) {
80
+ // Use runtime debug flag from options, fallback to hook debug
81
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
82
+ if (runtimeDebug) {
83
+ const runtimeLogger = createLogger(runtimeDebug, `useListeners ${name}`);
84
+ runtimeLogger.warn('Cannot register listener - listeners not enabled');
85
+ }
86
+ return false;
87
+ }
88
+
89
+ // If it's a handler group, delegate to ListenerManager's handler group method
90
+ if (options.isHandlerGroup && typeof handlers === 'object') {
91
+ return listeners.registerHandlerGroup(path, handlers, options);
92
+ }
93
+
94
+ // Delegate to ListenerManager for regular handlers
95
+ return listeners.on(path, handlers);
96
+ },
97
+
98
+ /**
99
+ * Unregister a listener for a specific path
100
+ * @param {string} path - Message path
101
+ * @param {Function|Object} handlers - Handler function or handler group object to remove
102
+ * @param {Object} [options={}] - Unregistration options
103
+ * @param {boolean} [options.isHandlerGroup=false] - Whether handlers is a handler group object
104
+ * @returns {boolean} Success status
105
+ */
106
+ off(path, handlers, options = {}) {
107
+ // Check if listeners are enabled
108
+ if (!listenersEnabled || listeners === null) {
109
+ // Use runtime debug flag from options, fallback to hook debug
110
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
111
+ if (runtimeDebug) {
112
+ const runtimeLogger = createLogger(runtimeDebug, `useListeners ${name}`);
113
+ runtimeLogger.warn('Cannot unregister listener - listeners not enabled');
114
+ }
115
+ return false;
116
+ }
117
+
118
+ // If it's a handler group, delegate to ListenerManager's handler group unregistration
119
+ if (options.isHandlerGroup && typeof handlers === 'object') {
120
+ return listeners.unregisterHandlerGroup(path, handlers, options);
121
+ }
122
+
123
+ // Delegate to ListenerManager for regular handlers
124
+ return listeners.off(path, handlers);
125
+ },
126
+
127
+ /**
128
+ * Emit an event to listeners for a specific path
129
+ * @param {string} path - Message path to emit to
130
+ * @param {Message} message - Message to send to listeners
131
+ * @returns {number} Number of listeners notified, or 0 if listeners not enabled
132
+ *
133
+ * @example
134
+ * // Emit event to listeners
135
+ * const notified = subsystem.listeners.emit('layers/create', message);
136
+ */
137
+ emit(path, message) {
138
+ // Check if listeners are enabled
139
+ if (!listenersEnabled || listeners === null) {
140
+ if (debug) {
141
+ const runtimeLogger = createLogger(debug, `useListeners ${name}`);
142
+ runtimeLogger.warn('Cannot emit event - listeners not enabled');
143
+ }
144
+ return 0;
145
+ }
146
+
147
+ // Delegate to ListenerManager
148
+ return listeners.emit(path, message);
149
+ },
150
+
151
+ /**
152
+ * Expose listeners property for direct access
153
+ * Returns null if listeners are not enabled
154
+ */
155
+ get listeners() {
156
+ return listeners;
157
+ },
158
+
159
+ // Expose listener manager for internal use
160
+ _listenerManager: () => listeners
161
+ });
162
+ }
163
+ });
164
+
@@ -0,0 +1,341 @@
1
+ import { CircularBuffer } from './circular-buffer.js';
2
+
3
+ /**
4
+ * BoundedQueue Class
5
+ *
6
+ * A queue with a maximum capacity and configurable overflow policy for handling backpressure
7
+ * and memory management in message-driven systems. Provides event-driven notifications and
8
+ * comprehensive statistics for monitoring queue behavior.
9
+ *
10
+ * Performance: Uses CircularBuffer internally for O(1) enqueue/dequeue operations (16x faster
11
+ * than array-based implementation for large queues).
12
+ *
13
+ * @example
14
+ * // Create a queue with capacity 100 and drop-oldest policy
15
+ * const queue = new BoundedQueue(100, 'drop-oldest');
16
+ *
17
+ * @example
18
+ * // Create a queue with error policy for critical data
19
+ * const criticalQueue = new BoundedQueue(50, 'error');
20
+ *
21
+ * @example
22
+ * // Monitor queue events
23
+ * queue.on('full', () => console.log('Queue is full!'));
24
+ * queue.on('dropped', (data) => console.log('Item dropped:', data.reason));
25
+ */
26
+ export class BoundedQueue {
27
+ /**
28
+ * Create a new BoundedQueue instance
29
+ *
30
+ * @param {number} capacity - Maximum number of items the queue can hold
31
+ * @param {string} [policy='drop-oldest'] - Overflow policy when queue is full
32
+ * - 'drop-oldest': Remove oldest item and add new one (FIFO with replacement)
33
+ * - 'drop-newest': Reject new item when full (strict capacity)
34
+ * - 'block': Wait for space (simplified implementation)
35
+ * - 'error': Throw error when full (fail-fast)
36
+ *
37
+ * @example
38
+ * // Basic queue with default policy
39
+ * const queue = new BoundedQueue(100);
40
+ *
41
+ * @example
42
+ * // Queue with error policy for critical data
43
+ * const criticalQueue = new BoundedQueue(50, 'error');
44
+ *
45
+ * @example
46
+ * // Queue with drop-newest for real-time data
47
+ * const realtimeQueue = new BoundedQueue(200, 'drop-newest');
48
+ */
49
+ constructor(capacity, policy = 'drop-oldest') {
50
+ this.capacity = capacity;
51
+ this.policy = policy;
52
+ this.queue = new CircularBuffer(capacity);
53
+ this.stats = {
54
+ itemsEnqueued: 0,
55
+ itemsDequeued: 0,
56
+ itemsDropped: 0,
57
+ queueFullEvents: 0,
58
+ errors: 0
59
+ };
60
+
61
+ // Event emitters for queue events
62
+ this.eventHandlers = {
63
+ full: [],
64
+ empty: [],
65
+ dropped: [],
66
+ error: []
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Add an item to the queue
72
+ * @param {any} item - Item to enqueue
73
+ * @returns {boolean} Success status
74
+ */
75
+ enqueue(item) {
76
+ try {
77
+ if (this.isFull()) {
78
+ this.stats.queueFullEvents++;
79
+ this.emit('full');
80
+ return this.handleFullQueue(item);
81
+ }
82
+
83
+ const success = this.queue.enqueue(item);
84
+ if (success) {
85
+ this.stats.itemsEnqueued++;
86
+
87
+ if (this.debug) {
88
+ console.log(`BoundedQueue: Enqueued item, queue size: ${this.size()}`);
89
+ }
90
+ }
91
+
92
+ return success;
93
+ } catch (error) {
94
+ // Only catch errors that aren't from the error policy
95
+ if (this.policy !== 'error') {
96
+ this.stats.errors++;
97
+ this.emit('error', { error, item });
98
+ return false;
99
+ }
100
+ // Re-throw error for error policy
101
+ throw error;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Remove and return the next item from the queue
107
+ * @returns {any|null} Next item or null if empty
108
+ */
109
+ dequeue() {
110
+ try {
111
+ if (this.isEmpty()) {
112
+ return null;
113
+ }
114
+
115
+ const item = this.queue.dequeue();
116
+ this.stats.itemsDequeued++;
117
+
118
+ if (this.isEmpty()) {
119
+ this.emit('empty');
120
+ }
121
+
122
+ if (this.debug) {
123
+ console.log(`BoundedQueue: Dequeued item, queue size: ${this.size()}`);
124
+ }
125
+
126
+ return item;
127
+ } catch (error) {
128
+ this.stats.errors++;
129
+ this.emit('error', { error });
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Look at the next item without removing it
136
+ * @returns {any|null} Next item or null if empty
137
+ */
138
+ peek() {
139
+ return this.queue.peek();
140
+ }
141
+
142
+ /**
143
+ * Get all items in the queue without removing them
144
+ * @returns {Array} Copy of all items in the queue
145
+ */
146
+ peekAll() {
147
+ return this.queue.toArray();
148
+ }
149
+
150
+ /**
151
+ * Remove a specific item from the queue
152
+ * @param {any} item - Item to remove (matched by reference)
153
+ * @returns {boolean} True if item was found and removed
154
+ *
155
+ * Note: This operation is O(n) and requires converting to array temporarily.
156
+ * For high-performance use cases, avoid using this method.
157
+ */
158
+ remove(item) {
159
+ try {
160
+ // Get all items, find and remove the target, then rebuild queue
161
+ const items = this.queue.toArray();
162
+ const index = items.indexOf(item);
163
+
164
+ if (index === -1) {
165
+ return false;
166
+ }
167
+
168
+ // Remove the item
169
+ items.splice(index, 1);
170
+
171
+ // Rebuild queue
172
+ this.queue.clear();
173
+ items.forEach(i => this.queue.enqueue(i));
174
+
175
+ this.stats.itemsDequeued++;
176
+
177
+ if (this.isEmpty()) {
178
+ this.emit('empty');
179
+ }
180
+
181
+ if (this.debug) {
182
+ console.log(`BoundedQueue: Removed item, queue size: ${this.size()}`);
183
+ }
184
+
185
+ return true;
186
+ } catch (error) {
187
+ this.stats.errors++;
188
+ this.emit('error', { error });
189
+ return false;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Check if queue is empty
195
+ * @returns {boolean} True if empty
196
+ */
197
+ isEmpty() {
198
+ return this.queue.isEmpty();
199
+ }
200
+
201
+ /**
202
+ * Check if queue is at capacity
203
+ * @returns {boolean} True if full
204
+ */
205
+ isFull() {
206
+ return this.queue.isFull();
207
+ }
208
+
209
+ /**
210
+ * Get current queue size
211
+ * @returns {number} Current size
212
+ */
213
+ size() {
214
+ return this.queue.size();
215
+ }
216
+
217
+ /**
218
+ * Get queue capacity
219
+ * @returns {number} Maximum capacity
220
+ */
221
+ getCapacity() {
222
+ return this.capacity;
223
+ }
224
+
225
+ /**
226
+ * Clear all items from the queue
227
+ */
228
+ clear() {
229
+ this.queue.clear();
230
+ if (this.debug) {
231
+ console.log('BoundedQueue: Cleared all items');
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Handle queue overflow based on policy
237
+ * @param {any} item - Item that couldn't be enqueued
238
+ * @returns {boolean} Success status
239
+ */
240
+ handleFullQueue(item) {
241
+ switch (this.policy) {
242
+ case 'drop-oldest':
243
+ // Remove oldest item and add new one
244
+ this.queue.dropOldest();
245
+ const success = this.queue.enqueue(item);
246
+ this.stats.itemsDropped++;
247
+ this.emit('dropped', { item, reason: 'drop-oldest' });
248
+ return success;
249
+
250
+ case 'drop-newest':
251
+ // Reject new item
252
+ this.stats.itemsDropped++;
253
+ this.emit('dropped', { item, reason: 'drop-newest' });
254
+ return false;
255
+
256
+ case 'block':
257
+ // Wait for space (simplified - in real implementation would be async)
258
+ this.emit('dropped', { item, reason: 'block-timeout' });
259
+ return false;
260
+
261
+ case 'error': {
262
+ // Throw error
263
+ const error = new Error(`Queue is full (capacity: ${this.capacity})`);
264
+ this.stats.errors++;
265
+ this.emit('error', { error, item });
266
+ throw error;
267
+ }
268
+
269
+ default:
270
+ // Unknown policy, default to drop-newest
271
+ this.stats.itemsDropped++;
272
+ this.emit('dropped', { item, reason: 'unknown-policy' });
273
+ return false;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Add event listener
279
+ * @param {string} event - Event name
280
+ * @param {function} handler - Event handler
281
+ */
282
+ on(event, handler) {
283
+ if (this.eventHandlers[event]) {
284
+ this.eventHandlers[event].push(handler);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Remove event listener
290
+ * @param {string} event - Event name
291
+ * @param {function} handler - Event handler
292
+ */
293
+ off(event, handler) {
294
+ if (this.eventHandlers[event]) {
295
+ const index = this.eventHandlers[event].indexOf(handler);
296
+ if (index > -1) {
297
+ this.eventHandlers[event].splice(index, 1);
298
+ }
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Emit event to all listeners
304
+ * @param {string} event - Event name
305
+ * @param {any} data - Event data
306
+ */
307
+ emit(event, data) {
308
+ if (this.eventHandlers[event]) {
309
+ this.eventHandlers[event].forEach(handler => {
310
+ try {
311
+ handler(data);
312
+ } catch (error) {
313
+ console.error('BoundedQueue: Event handler error:', error);
314
+ }
315
+ });
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Get queue statistics
321
+ * @returns {Object} Statistics object
322
+ */
323
+ getStatistics() {
324
+ return {
325
+ ...this.stats,
326
+ capacity: this.capacity,
327
+ currentSize: this.size(),
328
+ policy: this.policy,
329
+ utilization: this.size() / this.capacity
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Set debug mode
335
+ * @param {boolean} debug - Debug mode
336
+ */
337
+ setDebug(debug) {
338
+ this.debug = debug;
339
+ }
340
+ }
341
+