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.
- package/LICENSE +22 -0
- package/README.md +248 -0
- package/bin/cli.js +433 -0
- package/package.json +63 -0
- package/src/builder/context-resolver.js +62 -0
- package/src/builder/dependency-graph-cache.js +105 -0
- package/src/builder/dependency-graph.js +141 -0
- package/src/builder/facet-validator.js +43 -0
- package/src/builder/hook-processor.js +271 -0
- package/src/builder/index.js +13 -0
- package/src/builder/subsystem-builder.js +104 -0
- package/src/builder/utils.js +165 -0
- package/src/contract/contracts/hierarchy.contract.js +60 -0
- package/src/contract/contracts/index.js +17 -0
- package/src/contract/contracts/listeners.contract.js +66 -0
- package/src/contract/contracts/processor.contract.js +47 -0
- package/src/contract/contracts/queue.contract.js +58 -0
- package/src/contract/contracts/router.contract.js +53 -0
- package/src/contract/contracts/scheduler.contract.js +65 -0
- package/src/contract/contracts/server.contract.js +88 -0
- package/src/contract/contracts/speak.contract.js +50 -0
- package/src/contract/contracts/storage.contract.js +107 -0
- package/src/contract/contracts/websocket.contract.js +90 -0
- package/src/contract/facet-contract-registry.js +155 -0
- package/src/contract/facet-contract.js +136 -0
- package/src/contract/index.js +63 -0
- package/src/core/create-hook.js +63 -0
- package/src/core/facet.js +189 -0
- package/src/core/index.js +3 -0
- package/src/hooks/listeners/handler-group-manager.js +88 -0
- package/src/hooks/listeners/listener-manager-policies.js +229 -0
- package/src/hooks/listeners/listener-manager.js +668 -0
- package/src/hooks/listeners/listener-registry.js +176 -0
- package/src/hooks/listeners/listener-statistics.js +106 -0
- package/src/hooks/listeners/pattern-matcher.js +283 -0
- package/src/hooks/listeners/use-listeners.js +164 -0
- package/src/hooks/queue/bounded-queue.js +341 -0
- package/src/hooks/queue/circular-buffer.js +231 -0
- package/src/hooks/queue/subsystem-queue-manager.js +198 -0
- package/src/hooks/queue/use-queue.js +96 -0
- package/src/hooks/speak/use-speak.js +79 -0
- package/src/index.js +49 -0
- package/src/manager/facet-manager-transaction.js +45 -0
- package/src/manager/facet-manager.js +570 -0
- package/src/manager/index.js +3 -0
- package/src/system/base-subsystem.js +416 -0
- package/src/system/base-subsystem.utils.js +106 -0
- package/src/system/index.js +4 -0
- package/src/system/standalone-plugin-system.js +70 -0
- package/src/utils/debug-flag.js +34 -0
- package/src/utils/find-facet.js +30 -0
- package/src/utils/logger.js +84 -0
- 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
|
+
|