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,668 @@
1
+ /**
2
+ * ListenerManager Class
3
+ *
4
+ * Manages listener registration and notification for subsystems using pluggable policies.
5
+ * Provides optional listener functionality that subsystems can opt-in to use.
6
+ *
7
+ * @example
8
+ * // Create listener manager with multiple policy
9
+ * const listenerManager = new ListenerManager({
10
+ * registrationPolicy: 'multiple',
11
+ * debug: true
12
+ * });
13
+ *
14
+ * @example
15
+ * // Register listeners
16
+ * listenerManager.on('layers/create', (message) => {
17
+ * console.log('Layer created:', message.getBody());
18
+ * });
19
+ *
20
+ * @example
21
+ * // Notify listeners
22
+ * listenerManager.notifyListeners('layers/create', message);
23
+ */
24
+ import {
25
+ DEFAULT_POLICIES,
26
+ getAvailablePolicies,
27
+ validatePolicyOptions
28
+ } from './listener-manager-policies.js';
29
+ import { ListenerRegistry } from './listener-registry.js';
30
+ import { PatternMatcher } from './pattern-matcher.js';
31
+ import { HandlerGroupManager } from './handler-group-manager.js';
32
+ import { ListenerStatistics } from './listener-statistics.js';
33
+
34
+ export class ListenerManager {
35
+ /**
36
+ * Create a new ListenerManager instance
37
+ *
38
+ * @param {Object} [options={}] - Configuration options
39
+ * @param {string} [options.registrationPolicy='multiple'] - Registration policy
40
+ * @param {boolean} [options.debug=false] - Enable debug logging
41
+ * @param {Object} [options.policyOptions={}] - Policy-specific options
42
+ *
43
+ * @example
44
+ * // Basic listener manager
45
+ * const listenerManager = new ListenerManager();
46
+ *
47
+ * @example
48
+ * // Configured listener manager
49
+ * const listenerManager = new ListenerManager({
50
+ * registrationPolicy: 'single',
51
+ * debug: true,
52
+ * policyOptions: { maxListeners: 5 }
53
+ * });
54
+ */
55
+ constructor(options = {}) {
56
+ this.registrationPolicy = options.registrationPolicy || 'multiple';
57
+ this.debug = options.debug || false;
58
+ this.policyOptions = options.policyOptions || {};
59
+
60
+ // Policy registry - start with default policies
61
+ this.allowedPolicies = new Map(DEFAULT_POLICIES);
62
+
63
+ // Initialize component modules
64
+ this.registry = new ListenerRegistry({ debug: this.debug });
65
+ this.patternMatcher = new PatternMatcher({ debug: this.debug });
66
+ this.handlerGroupManager = new HandlerGroupManager();
67
+ this.statistics = new ListenerStatistics();
68
+
69
+ if (this.debug) {
70
+ console.log(`ListenerManager: Initialized with policy '${this.registrationPolicy}'`);
71
+ console.log(`ListenerManager: Available policies:`, getAvailablePolicies());
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Register a handler group for a specific path
77
+ * Handler groups contain onSuccess, onFailure, and onTimeout callbacks.
78
+ *
79
+ * @param {string} path - Message path to listen for
80
+ * @param {Object} handlers - Handler group object with onSuccess, onFailure, onTimeout
81
+ * @param {Object} [options={}] - Registration options
82
+ * @returns {boolean} Success status
83
+ *
84
+ * @example
85
+ * // Register a handler group
86
+ * listenerManager.registerHandlerGroup('save/msg_123', {
87
+ * onSuccess: (message) => console.log('Success:', message),
88
+ * onFailure: (message) => console.error('Failure:', message),
89
+ * onTimeout: (message) => console.warn('Timeout:', message)
90
+ * });
91
+ */
92
+ registerHandlerGroup(path, handlers, options = {}) {
93
+ const wrappedHandler = this.handlerGroupManager.wrap(handlers);
94
+ return this.on(path, wrappedHandler, options);
95
+ }
96
+
97
+ /**
98
+ * Register a listener for a specific path
99
+ * @param {string} path - Message path to listen for
100
+ * @param {Function} handler - Handler function to call when message is received
101
+ * @param {Object} [options={}] - Registration options (for future use)
102
+ * @returns {boolean} Success status
103
+ *
104
+ * @example
105
+ * // Register a listener for layer creation
106
+ * listenerManager.on('layers/create', (message) => {
107
+ * console.log('Layer created:', message.getBody());
108
+ * });
109
+ */
110
+ on(path, handler, _options = {}) {
111
+ if (typeof handler !== 'function') {
112
+ throw new Error('Handler must be a function');
113
+ }
114
+
115
+ const existingListeners = this.registry.get(path);
116
+ const policyFunction = this.allowedPolicies.get(this.registrationPolicy);
117
+
118
+ if (!policyFunction) {
119
+ throw new Error(`Unknown policy: ${this.registrationPolicy}. Available: ${Array.from(this.allowedPolicies.keys()).join(', ')}`);
120
+ }
121
+
122
+ // Apply policy function
123
+ const result = policyFunction(existingListeners, path, handler, {
124
+ policy: this.registrationPolicy,
125
+ debug: this.debug,
126
+ ...this.policyOptions
127
+ });
128
+
129
+ if (result.success) {
130
+ this.registry.register(path, handler, result.listeners);
131
+ this.statistics.recordRegistration();
132
+ return true;
133
+ } else {
134
+ throw new Error(result.error);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Unregister a handler group for a specific path
140
+ * Finds and removes the handler group that matches the provided handlers.
141
+ *
142
+ * @param {string} path - Message path
143
+ * @param {Object} handlers - Handler group object with onSuccess, onFailure, onTimeout
144
+ * @param {Object} [options={}] - Unregistration options
145
+ * @returns {boolean} Success status
146
+ *
147
+ * @example
148
+ * // Remove a handler group
149
+ * listenerManager.unregisterHandlerGroup('save/msg_123', {
150
+ * onSuccess: handlerGroup.onSuccess,
151
+ * onFailure: handlerGroup.onFailure,
152
+ * onTimeout: handlerGroup.onTimeout
153
+ * });
154
+ */
155
+ unregisterHandlerGroup(path, handlers, _options = {}) {
156
+ if (!this.registry.has(path)) {
157
+ return false;
158
+ }
159
+
160
+ const existingListeners = this.registry.get(path);
161
+ const wrappedHandler = this.handlerGroupManager.find(existingListeners, handlers);
162
+
163
+ if (wrappedHandler) {
164
+ return this.off(path, wrappedHandler);
165
+ }
166
+
167
+ return false;
168
+ }
169
+
170
+ /**
171
+ * Unregister a specific listener for a path
172
+ * @param {string} path - Message path
173
+ * @param {Function} handler - Handler function to remove
174
+ * @returns {boolean} Success status
175
+ *
176
+ * @example
177
+ * // Remove a specific listener
178
+ * listenerManager.off('layers/create', myHandler);
179
+ */
180
+ off(path, handler) {
181
+ const removed = this.registry.unregister(path, handler);
182
+ if (removed) {
183
+ this.statistics.recordUnregistration();
184
+ }
185
+ return removed;
186
+ }
187
+
188
+ /**
189
+ * Unregister all listeners for a specific path
190
+ * @param {string} path - Message path
191
+ * @returns {number} Number of listeners removed
192
+ *
193
+ * @example
194
+ * // Remove all listeners for a path
195
+ * const removed = listenerManager.offAll('layers/create');
196
+ */
197
+ offAll(path) {
198
+ const count = this.registry.unregisterAll(path);
199
+ if (count > 0) {
200
+ // Record multiple unregistrations
201
+ for (let i = 0; i < count; i++) {
202
+ this.statistics.recordUnregistration();
203
+ }
204
+ }
205
+ return count;
206
+ }
207
+
208
+ /**
209
+ * Clear all listeners
210
+ * @returns {number} Total number of listeners removed
211
+ *
212
+ * @example
213
+ * // Clear all listeners
214
+ * const removed = listenerManager.clearListeners();
215
+ */
216
+ clearListeners() {
217
+ const exactRemoved = this.registry.clear();
218
+ const patternRemoved = this.patternMatcher.getTotalCount();
219
+ this.patternMatcher.clear();
220
+
221
+ // Record unregistrations
222
+ for (let i = 0; i < exactRemoved; i++) {
223
+ this.statistics.recordUnregistration();
224
+ }
225
+ for (let i = 0; i < patternRemoved; i++) {
226
+ this.statistics.recordPatternUnregistration();
227
+ }
228
+
229
+ return exactRemoved + patternRemoved;
230
+ }
231
+
232
+ /**
233
+ * Check if there are listeners for a specific path
234
+ * @param {string} path - Message path
235
+ * @returns {boolean} True if listeners exist
236
+ *
237
+ * @example
238
+ * if (listenerManager.hasListeners('layers/create')) {
239
+ * console.log('Has listeners for layer creation');
240
+ * }
241
+ */
242
+ hasListeners(path) {
243
+ return this.registry.has(path);
244
+ }
245
+
246
+ /**
247
+ * Get the number of listeners for a specific path
248
+ * @param {string} path - Message path
249
+ * @returns {number} Number of listeners
250
+ *
251
+ * @example
252
+ * const count = listenerManager.getListenerCount('layers/create');
253
+ */
254
+ getListenerCount(path) {
255
+ return this.registry.getCount(path);
256
+ }
257
+
258
+ /**
259
+ * Get all listeners for a specific path
260
+ * @param {string} path - Message path
261
+ * @returns {Array<Function>} Array of handler functions
262
+ *
263
+ * @example
264
+ * const handlers = listenerManager.getListeners('layers/create');
265
+ */
266
+ getListeners(path) {
267
+ return this.registry.get(path);
268
+ }
269
+
270
+ /**
271
+ * Get all listeners (for debugging)
272
+ * @returns {Object} Object with paths as keys and handler arrays as values
273
+ *
274
+ * @example
275
+ * const allListeners = listenerManager.getAllListeners();
276
+ */
277
+ getAllListeners() {
278
+ return this.registry.getAll();
279
+ }
280
+
281
+ /**
282
+ * Emit an event to listeners for a specific path
283
+ * Checks both exact path matches and pattern matches
284
+ * @param {string} path - Message path
285
+ * @param {Message} message - Message to send to listeners
286
+ * @returns {number} Number of listeners notified
287
+ *
288
+ * @example
289
+ * // Emit event to listeners
290
+ * const notified = listenerManager.emit('layers/create', message);
291
+ */
292
+ emit(path, message) {
293
+ return this.notifyListeners(path, message);
294
+ }
295
+
296
+ /**
297
+ * Notify listeners for a specific path
298
+ * Checks both exact path matches and pattern matches
299
+ * @param {string} path - Message path
300
+ * @param {Message} message - Message to send to listeners
301
+ * @returns {number} Number of listeners notified
302
+ *
303
+ * @example
304
+ * // Notify listeners after processing a message
305
+ * const notified = listenerManager.notifyListeners('layers/create', message);
306
+ */
307
+ notifyListeners(path, message) {
308
+ let totalNotified = 0;
309
+
310
+ // 1. Check exact path matches first (existing behavior)
311
+ if (this.registry.has(path)) {
312
+ const handlers = this.registry.get(path);
313
+ totalNotified += this._notifyHandlers(handlers, path, message);
314
+ }
315
+
316
+ // 2. Check pattern matches
317
+ const patternMatches = this.patternMatcher.findMatches(path);
318
+ for (const { patternEntry, params } of patternMatches) {
319
+ totalNotified += this._notifyHandlers(patternEntry.handlers, patternEntry.pattern, message, params);
320
+ this.statistics.recordPatternMatch();
321
+ }
322
+
323
+ this.statistics.recordNotifications(totalNotified);
324
+
325
+ if (this.debug && totalNotified > 0) {
326
+ console.log(`ListenerManager: Notified ${totalNotified} listeners for '${path}'`);
327
+ }
328
+
329
+ return totalNotified;
330
+ }
331
+
332
+ /**
333
+ * Notify handlers for a path
334
+ * @param {Array} handlers - Array of handler functions or handler entries
335
+ * @param {string} path - Path for logging
336
+ * @param {Message} message - Message to send
337
+ * @param {Object} [params=null] - Extracted parameters from pattern match
338
+ * @returns {number} Number of handlers notified
339
+ * @private
340
+ */
341
+ _notifyHandlers(handlers, path, message, params = null) {
342
+ let notified = 0;
343
+
344
+ for (const handlerEntry of handlers) {
345
+ try {
346
+ // Handle priority policy: extract handler from object
347
+ // Priority policy stores: { handler, priority, path }
348
+ // Other policies store: function directly
349
+ const handler = typeof handlerEntry === 'function'
350
+ ? handlerEntry
351
+ : (handlerEntry && handlerEntry.handler ? handlerEntry.handler : handlerEntry);
352
+
353
+ if (typeof handler !== 'function') {
354
+ if (this.debug) {
355
+ console.error(`ListenerManager: Handler for '${path}' is not a function:`, handler);
356
+ }
357
+ continue;
358
+ }
359
+
360
+ // Call handler with message and params (if pattern match)
361
+ if (params) {
362
+ handler(message, params);
363
+ } else {
364
+ handler(message);
365
+ }
366
+ notified++;
367
+ } catch (error) {
368
+ this.statistics.recordError();
369
+ if (this.debug) {
370
+ console.error(`ListenerManager: Error in listener for '${path}':`, error);
371
+ }
372
+ }
373
+ }
374
+
375
+ return notified;
376
+ }
377
+
378
+ /**
379
+ * Notify all listeners (broadcast)
380
+ * @param {Message} message - Message to send to all listeners
381
+ * @returns {number} Total number of listeners notified
382
+ *
383
+ * @example
384
+ * // Broadcast to all listeners
385
+ * const notified = listenerManager.notifyAllListeners(message);
386
+ */
387
+ notifyAllListeners(message) {
388
+ let totalNotified = 0;
389
+
390
+ for (const path of this.registry.getPaths()) {
391
+ totalNotified += this.notifyListeners(path, message);
392
+ }
393
+
394
+ if (this.debug && totalNotified > 0) {
395
+ console.log(`ListenerManager: Broadcast to ${totalNotified} listeners`);
396
+ }
397
+
398
+ return totalNotified;
399
+ }
400
+
401
+ /**
402
+ * Set the listener registration policy
403
+ * @param {string} policy - Registration policy
404
+ * @param {Object} [options={}] - Policy-specific options
405
+ * @returns {boolean} Success status
406
+ *
407
+ * @example
408
+ * // Set policy to only allow one listener per path
409
+ * listenerManager.setListenerPolicy('single');
410
+ *
411
+ * @example
412
+ * // Set policy with options
413
+ * listenerManager.setListenerPolicy('limited', { maxListeners: 5 });
414
+ */
415
+ setListenerPolicy(policy, options = {}) {
416
+ if (!this.allowedPolicies.has(policy)) {
417
+ throw new Error(`Unknown policy: ${policy}. Available: ${Array.from(this.allowedPolicies.keys()).join(', ')}`);
418
+ }
419
+
420
+ // Validate policy options
421
+ const validation = validatePolicyOptions(policy, options);
422
+ if (!validation.valid) {
423
+ throw new Error(`Invalid options for policy '${policy}': ${validation.errors.join(', ')}`);
424
+ }
425
+
426
+ this.registrationPolicy = policy;
427
+ this.policyOptions = { ...this.policyOptions, ...options };
428
+
429
+ if (this.debug) {
430
+ console.log(`ListenerManager: Policy set to '${policy}' with options:`, options);
431
+ }
432
+
433
+ return true;
434
+ }
435
+
436
+ /**
437
+ * Get the current listener registration policy
438
+ * @returns {string} Current policy
439
+ *
440
+ * @example
441
+ * const policy = listenerManager.getListenerPolicy();
442
+ */
443
+ getListenerPolicy() {
444
+ return this.registrationPolicy;
445
+ }
446
+
447
+ /**
448
+ * Register a new policy
449
+ * @param {string} name - Policy name
450
+ * @param {Function} policyFunction - Policy function
451
+ * @example
452
+ * listenerManager.registerPolicy('my-custom', (existingListeners, path, handler, options) => {
453
+ * // Custom policy logic
454
+ * return { success: true, listeners: [...existingListeners, handler], error: null };
455
+ * });
456
+ */
457
+ registerPolicy(name, policyFunction) {
458
+ if (typeof policyFunction !== 'function') {
459
+ throw new Error('Policy must be a function');
460
+ }
461
+
462
+ this.allowedPolicies.set(name, policyFunction);
463
+
464
+ if (this.debug) {
465
+ console.log(`ListenerManager: Registered policy '${name}'`);
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Unregister a policy
471
+ * @param {string} name - Policy name
472
+ * @example
473
+ * listenerManager.unregisterPolicy('my-custom');
474
+ */
475
+ unregisterPolicy(name) {
476
+ if (DEFAULT_POLICIES.has(name)) {
477
+ throw new Error(`Cannot unregister default policy: ${name}`);
478
+ }
479
+
480
+ const removed = this.allowedPolicies.delete(name);
481
+
482
+ if (this.debug && removed) {
483
+ console.log(`ListenerManager: Unregistered policy '${name}'`);
484
+ }
485
+
486
+ return removed;
487
+ }
488
+
489
+ /**
490
+ * Get available policy names
491
+ * @returns {Array<string>} Array of policy names
492
+ * @example
493
+ * const policies = listenerManager.getAvailablePolicies();
494
+ * console.log('Available policies:', policies);
495
+ */
496
+ getAvailablePolicies() {
497
+ return Array.from(this.allowedPolicies.keys());
498
+ }
499
+
500
+ /**
501
+ * Validate if a listener can be added for a path
502
+ * @param {string} path - Message path
503
+ * @param {Function} handler - Handler function
504
+ * @returns {Object} Validation result
505
+ *
506
+ * @example
507
+ * const validation = listenerManager.validateListener('layers/create', handler);
508
+ * if (!validation.valid) {
509
+ * console.log('Cannot add listener:', validation.reason);
510
+ * }
511
+ */
512
+ validateListener(path, handler) {
513
+ if (typeof handler !== 'function') {
514
+ return { valid: false, reason: 'Handler must be a function' };
515
+ }
516
+
517
+ const existingListeners = this.registry.get(path);
518
+ const policyFunction = this.allowedPolicies.get(this.registrationPolicy);
519
+
520
+ if (!policyFunction) {
521
+ return { valid: false, reason: `Unknown policy: ${this.registrationPolicy}` };
522
+ }
523
+
524
+ // Test the policy function
525
+ const result = policyFunction(existingListeners, path, handler, {
526
+ policy: this.registrationPolicy,
527
+ debug: this.debug,
528
+ ...this.policyOptions
529
+ });
530
+
531
+ return { valid: result.success, reason: result.error };
532
+ }
533
+
534
+ /**
535
+ * Get listener manager statistics
536
+ * @returns {Object} Statistics object
537
+ */
538
+ getStatistics() {
539
+ const exactListeners = this.registry.getTotalCount();
540
+ const patternListeners = this.patternMatcher.getTotalCount();
541
+
542
+ return this.statistics.get({
543
+ registrationPolicy: this.registrationPolicy,
544
+ policyOptions: this.policyOptions,
545
+ availablePolicies: this.getAvailablePolicies(),
546
+ totalPaths: this.registry.getPathCount(),
547
+ totalListeners: exactListeners + patternListeners,
548
+ exactPaths: this.registry.getPathCount(),
549
+ exactListeners: exactListeners,
550
+ patternCount: this.patternMatcher.getPatternCount(),
551
+ patternListeners: patternListeners
552
+ });
553
+ }
554
+
555
+ /**
556
+ * Check if a path contains pattern syntax (e.g., {param})
557
+ * @param {string} path - Path to check
558
+ * @returns {boolean} True if path contains pattern syntax
559
+ *
560
+ * @example
561
+ * listenerManager.isPattern('command/completed/id/{id}'); // true
562
+ * listenerManager.isPattern('command/completed'); // false
563
+ */
564
+ isPattern(path) {
565
+ return this.patternMatcher.isPattern(path);
566
+ }
567
+
568
+ /**
569
+ * Register a listener for a pattern path
570
+ * @param {string} pattern - Pattern path with {param} placeholders (e.g., 'command/completed/id/{id}')
571
+ * @param {Function} handler - Handler function to call when pattern matches
572
+ * @returns {boolean} Success status
573
+ *
574
+ * @example
575
+ * // Register a pattern listener
576
+ * listenerManager.onPattern('command/completed/id/{id}', (message, params) => {
577
+ * console.log('Command completed:', params.id);
578
+ * });
579
+ */
580
+ onPattern(pattern, handler) {
581
+ if (typeof handler !== 'function') {
582
+ throw new Error('Handler must be a function');
583
+ }
584
+
585
+ const existingHandlers = this.patternMatcher.has(pattern)
586
+ ? this.patternMatcher.findMatches(pattern)[0]?.patternEntry?.handlers || []
587
+ : [];
588
+
589
+ const policyFunction = this.allowedPolicies.get(this.registrationPolicy);
590
+
591
+ if (!policyFunction) {
592
+ throw new Error(`Unknown policy: ${this.registrationPolicy}. Available: ${Array.from(this.allowedPolicies.keys()).join(', ')}`);
593
+ }
594
+
595
+ // Apply policy function to handlers array
596
+ const result = policyFunction(existingHandlers, pattern, handler, {
597
+ policy: this.registrationPolicy,
598
+ debug: this.debug,
599
+ ...this.policyOptions
600
+ });
601
+
602
+ if (result.success) {
603
+ this.patternMatcher.register(pattern, handler, result.listeners);
604
+ this.statistics.recordPatternRegistration();
605
+ return true;
606
+ } else {
607
+ throw new Error(result.error);
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Unregister a pattern listener
613
+ * @param {string} pattern - Pattern path
614
+ * @param {Function} handler - Handler function to remove
615
+ * @returns {boolean} Success status
616
+ *
617
+ * @example
618
+ * // Remove a pattern listener
619
+ * listenerManager.offPattern('command/completed/id/{id}', myHandler);
620
+ */
621
+ offPattern(pattern, handler) {
622
+ const removed = this.patternMatcher.unregister(pattern, handler);
623
+ if (removed) {
624
+ this.statistics.recordPatternUnregistration();
625
+ }
626
+ return removed;
627
+ }
628
+
629
+ /**
630
+ * Check if pattern listeners exist for a pattern
631
+ * @param {string} pattern - Pattern path
632
+ * @returns {boolean} True if pattern listeners exist
633
+ */
634
+ hasPatternListeners(pattern) {
635
+ return this.patternMatcher.has(pattern);
636
+ }
637
+
638
+ /**
639
+ * Get pattern listener count for a specific pattern
640
+ * @param {string} pattern - Pattern path
641
+ * @returns {number} Total number of handlers for this pattern
642
+ */
643
+ getPatternListenerCount(pattern) {
644
+ return this.patternMatcher.getCount(pattern);
645
+ }
646
+
647
+ /**
648
+ * Get all registered patterns
649
+ * @returns {Array<string>} Array of pattern strings
650
+ */
651
+ getRegisteredPatterns() {
652
+ return this.patternMatcher.getPatterns();
653
+ }
654
+
655
+ /**
656
+ * Clear statistics and reset state
657
+ */
658
+ clear() {
659
+ this.statistics.clear();
660
+ this.registry.clear();
661
+ this.patternMatcher.clear();
662
+
663
+ if (this.debug) {
664
+ console.log(`ListenerManager: Cleared all data`);
665
+ }
666
+ }
667
+ }
668
+