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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListenerRegistry Class
|
|
3
|
+
*
|
|
4
|
+
* Manages listener storage and basic registration/unregistration operations.
|
|
5
|
+
* Handles exact path matching only (no pattern matching).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const registry = new ListenerRegistry();
|
|
9
|
+
* registry.register('layers/create', handler);
|
|
10
|
+
* const handlers = registry.get('layers/create');
|
|
11
|
+
*/
|
|
12
|
+
export class ListenerRegistry {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new ListenerRegistry instance
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} [options={}] - Configuration options
|
|
17
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
18
|
+
*/
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.debug = options.debug || false;
|
|
21
|
+
|
|
22
|
+
// Listener storage: path -> [handler1, handler2, ...]
|
|
23
|
+
this.listeners = new Map();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a listener for a specific path
|
|
28
|
+
* @param {string} path - Message path to listen for
|
|
29
|
+
* @param {Function} handler - Handler function to call when message is received
|
|
30
|
+
* @param {Array<Function>} existingListeners - Existing listeners for this path (from policy)
|
|
31
|
+
* @returns {void}
|
|
32
|
+
*/
|
|
33
|
+
register(path, handler, existingListeners = []) {
|
|
34
|
+
this.listeners.set(path, existingListeners);
|
|
35
|
+
|
|
36
|
+
if (this.debug) {
|
|
37
|
+
console.log(`ListenerRegistry: Registered listener for '${path}' (${existingListeners.length} total)`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Unregister a specific listener for a path
|
|
43
|
+
* @param {string} path - Message path
|
|
44
|
+
* @param {Function} handler - Handler function to remove
|
|
45
|
+
* @returns {boolean} Success status
|
|
46
|
+
*/
|
|
47
|
+
unregister(path, handler) {
|
|
48
|
+
if (!this.listeners.has(path)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const existingListeners = this.listeners.get(path);
|
|
53
|
+
const index = existingListeners.indexOf(handler);
|
|
54
|
+
|
|
55
|
+
if (index === -1) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
existingListeners.splice(index, 1);
|
|
60
|
+
|
|
61
|
+
// Clean up empty arrays
|
|
62
|
+
if (existingListeners.length === 0) {
|
|
63
|
+
this.listeners.delete(path);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this.debug) {
|
|
67
|
+
console.log(`ListenerRegistry: Unregistered listener for '${path}' (${existingListeners.length} remaining)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Unregister all listeners for a specific path
|
|
75
|
+
* @param {string} path - Message path
|
|
76
|
+
* @returns {number} Number of listeners removed
|
|
77
|
+
*/
|
|
78
|
+
unregisterAll(path) {
|
|
79
|
+
if (!this.listeners.has(path)) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const count = this.listeners.get(path).length;
|
|
84
|
+
this.listeners.delete(path);
|
|
85
|
+
|
|
86
|
+
if (this.debug) {
|
|
87
|
+
console.log(`ListenerRegistry: Removed ${count} listeners for '${path}'`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return count;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clear all listeners
|
|
95
|
+
* @returns {number} Total number of listeners removed
|
|
96
|
+
*/
|
|
97
|
+
clear() {
|
|
98
|
+
let totalRemoved = 0;
|
|
99
|
+
|
|
100
|
+
for (const [_path, handlers] of this.listeners) {
|
|
101
|
+
totalRemoved += handlers.length;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.listeners.clear();
|
|
105
|
+
|
|
106
|
+
if (this.debug) {
|
|
107
|
+
console.log(`ListenerRegistry: Cleared ${totalRemoved} listeners`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return totalRemoved;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if there are listeners for a specific path
|
|
115
|
+
* @param {string} path - Message path
|
|
116
|
+
* @returns {boolean} True if listeners exist
|
|
117
|
+
*/
|
|
118
|
+
has(path) {
|
|
119
|
+
return this.listeners.has(path) && this.listeners.get(path).length > 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get the number of listeners for a specific path
|
|
124
|
+
* @param {string} path - Message path
|
|
125
|
+
* @returns {number} Number of listeners
|
|
126
|
+
*/
|
|
127
|
+
getCount(path) {
|
|
128
|
+
return this.listeners.has(path) ? this.listeners.get(path).length : 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get all listeners for a specific path
|
|
133
|
+
* @param {string} path - Message path
|
|
134
|
+
* @returns {Array<Function>} Array of handler functions
|
|
135
|
+
*/
|
|
136
|
+
get(path) {
|
|
137
|
+
return this.listeners.has(path) ? [...this.listeners.get(path)] : [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all listeners (for debugging)
|
|
142
|
+
* @returns {Object} Object with paths as keys and handler arrays as values
|
|
143
|
+
*/
|
|
144
|
+
getAll() {
|
|
145
|
+
const result = {};
|
|
146
|
+
for (const [path, handlers] of this.listeners) {
|
|
147
|
+
result[path] = [...handlers];
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get all registered paths
|
|
154
|
+
* @returns {Array<string>} Array of path strings
|
|
155
|
+
*/
|
|
156
|
+
getPaths() {
|
|
157
|
+
return Array.from(this.listeners.keys());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get total listener count across all paths
|
|
162
|
+
* @returns {number} Total number of listeners
|
|
163
|
+
*/
|
|
164
|
+
getTotalCount() {
|
|
165
|
+
return Array.from(this.listeners.values()).reduce((sum, handlers) => sum + handlers.length, 0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get number of paths with listeners
|
|
170
|
+
* @returns {number} Number of paths
|
|
171
|
+
*/
|
|
172
|
+
getPathCount() {
|
|
173
|
+
return this.listeners.size;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListenerStatistics Class
|
|
3
|
+
*
|
|
4
|
+
* Tracks statistics for listener operations.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const stats = new ListenerStatistics();
|
|
8
|
+
* stats.recordRegistration();
|
|
9
|
+
* stats.recordNotification(5);
|
|
10
|
+
*/
|
|
11
|
+
export class ListenerStatistics {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new ListenerStatistics instance
|
|
14
|
+
*/
|
|
15
|
+
constructor() {
|
|
16
|
+
this.stats = {
|
|
17
|
+
listenersRegistered: 0,
|
|
18
|
+
listenersUnregistered: 0,
|
|
19
|
+
notificationsSent: 0,
|
|
20
|
+
notificationErrors: 0,
|
|
21
|
+
patternListenersRegistered: 0,
|
|
22
|
+
patternListenersUnregistered: 0,
|
|
23
|
+
patternMatches: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Record a listener registration
|
|
29
|
+
*/
|
|
30
|
+
recordRegistration() {
|
|
31
|
+
this.stats.listenersRegistered++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Record a listener unregistration
|
|
36
|
+
*/
|
|
37
|
+
recordUnregistration() {
|
|
38
|
+
this.stats.listenersUnregistered++;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Record notifications sent
|
|
43
|
+
* @param {number} count - Number of notifications sent
|
|
44
|
+
*/
|
|
45
|
+
recordNotifications(count) {
|
|
46
|
+
this.stats.notificationsSent += count;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Record a notification error
|
|
51
|
+
*/
|
|
52
|
+
recordError() {
|
|
53
|
+
this.stats.notificationErrors++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Record a pattern listener registration
|
|
58
|
+
*/
|
|
59
|
+
recordPatternRegistration() {
|
|
60
|
+
this.stats.patternListenersRegistered++;
|
|
61
|
+
this.stats.listenersRegistered++; // Also count as regular registration
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Record a pattern listener unregistration
|
|
66
|
+
*/
|
|
67
|
+
recordPatternUnregistration() {
|
|
68
|
+
this.stats.patternListenersUnregistered++;
|
|
69
|
+
this.stats.listenersUnregistered++; // Also count as regular unregistration
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Record a pattern match
|
|
74
|
+
*/
|
|
75
|
+
recordPatternMatch() {
|
|
76
|
+
this.stats.patternMatches++;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get current statistics
|
|
81
|
+
* @param {Object} additionalData - Additional data to include in stats
|
|
82
|
+
* @returns {Object} Statistics object
|
|
83
|
+
*/
|
|
84
|
+
get(additionalData = {}) {
|
|
85
|
+
return {
|
|
86
|
+
...this.stats,
|
|
87
|
+
...additionalData
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear statistics and reset state
|
|
93
|
+
*/
|
|
94
|
+
clear() {
|
|
95
|
+
this.stats = {
|
|
96
|
+
listenersRegistered: 0,
|
|
97
|
+
listenersUnregistered: 0,
|
|
98
|
+
notificationsSent: 0,
|
|
99
|
+
notificationErrors: 0,
|
|
100
|
+
patternListenersRegistered: 0,
|
|
101
|
+
patternListenersUnregistered: 0,
|
|
102
|
+
patternMatches: 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatternMatcher Class
|
|
3
|
+
*
|
|
4
|
+
* Handles pattern-based listener registration and matching.
|
|
5
|
+
* Supports {param} syntax in paths (e.g., 'command/completed/id/{id}').
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const matcher = new PatternMatcher({ debug: false });
|
|
9
|
+
* matcher.register('command/completed/id/{id}', handler);
|
|
10
|
+
* const matches = matcher.findMatches('command/completed/id/123');
|
|
11
|
+
*/
|
|
12
|
+
export class PatternMatcher {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new PatternMatcher instance
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} [options={}] - Configuration options
|
|
17
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
18
|
+
*/
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.debug = options.debug || false;
|
|
21
|
+
|
|
22
|
+
// Pattern listener storage: pattern -> [{ handlers, paramNames, regex }, ...]
|
|
23
|
+
// Each entry contains handlers for that pattern and compiled regex for matching
|
|
24
|
+
this.patternListeners = new Map();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a path contains pattern syntax (e.g., {param})
|
|
29
|
+
* @param {string} path - Path to check
|
|
30
|
+
* @returns {boolean} True if path contains pattern syntax
|
|
31
|
+
*/
|
|
32
|
+
isPattern(path) {
|
|
33
|
+
return /\{[a-zA-Z_][a-zA-Z0-9_]*\}/.test(path);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse pattern to extract parameter names
|
|
38
|
+
* @param {string} pattern - Pattern string (e.g., 'command/completed/id/{id}')
|
|
39
|
+
* @returns {Array<string>} Array of parameter names (e.g., ['id'])
|
|
40
|
+
*/
|
|
41
|
+
parsePattern(pattern) {
|
|
42
|
+
const paramNames = [];
|
|
43
|
+
const paramRegex = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
44
|
+
let match;
|
|
45
|
+
|
|
46
|
+
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
47
|
+
const paramName = match[1];
|
|
48
|
+
if (!paramNames.includes(paramName)) {
|
|
49
|
+
paramNames.push(paramName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return paramNames;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert pattern to regex with capture groups
|
|
58
|
+
* @param {string} pattern - Pattern string (e.g., 'command/completed/id/{id}')
|
|
59
|
+
* @returns {RegExp} Regex with capture groups
|
|
60
|
+
*/
|
|
61
|
+
patternToRegex(pattern) {
|
|
62
|
+
// Escape special regex characters except the pattern syntax
|
|
63
|
+
let regexStr = pattern
|
|
64
|
+
.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
|
|
65
|
+
// Don't escape { and } as they're used for pattern syntax
|
|
66
|
+
if (char === '{' || char === '}') {
|
|
67
|
+
return char;
|
|
68
|
+
}
|
|
69
|
+
return '\\' + char;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Replace {param} with capture groups
|
|
73
|
+
// Each {param} becomes (.+) to capture any value
|
|
74
|
+
regexStr = regexStr.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, '(.+)');
|
|
75
|
+
|
|
76
|
+
// Anchor to start and end
|
|
77
|
+
regexStr = '^' + regexStr + '$';
|
|
78
|
+
|
|
79
|
+
return new RegExp(regexStr);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract parameter values from a pattern match
|
|
84
|
+
* @param {string} pattern - Original pattern
|
|
85
|
+
* @param {Array<string>} paramNames - Parameter names in order
|
|
86
|
+
* @param {Array} matchResult - Regex match result array
|
|
87
|
+
* @returns {Object} Parameters object (e.g., { id: 'msg_123' })
|
|
88
|
+
*/
|
|
89
|
+
extractParams(pattern, paramNames, matchResult) {
|
|
90
|
+
if (!matchResult || matchResult.length === 0) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// matchResult[0] is the full match
|
|
95
|
+
// matchResult[1], matchResult[2], ... are capture groups
|
|
96
|
+
const params = {};
|
|
97
|
+
const captureGroups = matchResult.slice(1);
|
|
98
|
+
|
|
99
|
+
if (paramNames.length !== captureGroups.length) {
|
|
100
|
+
if (this.debug) {
|
|
101
|
+
console.warn(`PatternMatcher: Parameter count mismatch for pattern '${pattern}'. Expected ${paramNames.length}, got ${captureGroups.length}`);
|
|
102
|
+
}
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
107
|
+
params[paramNames[i]] = captureGroups[i];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return params;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Register a listener for a pattern path
|
|
115
|
+
* @param {string} pattern - Pattern path with {param} placeholders
|
|
116
|
+
* @param {Function} handler - Handler function to call when pattern matches
|
|
117
|
+
* @param {Array<Function>} existingHandlers - Existing handlers for this pattern (from policy)
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
120
|
+
register(pattern, handler, existingHandlers = []) {
|
|
121
|
+
if (!this.isPattern(pattern)) {
|
|
122
|
+
throw new Error(`Path '${pattern}' does not contain pattern syntax. Use exact path matching or onPattern() with {param} syntax.`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Parse pattern to extract parameter names
|
|
126
|
+
const paramNames = this.parsePattern(pattern);
|
|
127
|
+
|
|
128
|
+
if (paramNames.length === 0) {
|
|
129
|
+
throw new Error(`Pattern '${pattern}' contains no valid parameters. Use {paramName} syntax.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Convert pattern to regex
|
|
133
|
+
const regex = this.patternToRegex(pattern);
|
|
134
|
+
|
|
135
|
+
// Get existing pattern entries or create new array
|
|
136
|
+
const existingEntries = this.patternListeners.get(pattern) || [];
|
|
137
|
+
|
|
138
|
+
// Check if this pattern already has entries
|
|
139
|
+
let patternEntry = existingEntries.find(entry =>
|
|
140
|
+
entry.paramNames.join(',') === paramNames.join(',') &&
|
|
141
|
+
entry.regex.source === regex.source
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (!patternEntry) {
|
|
145
|
+
// Create new pattern entry
|
|
146
|
+
patternEntry = {
|
|
147
|
+
pattern: pattern,
|
|
148
|
+
paramNames: paramNames,
|
|
149
|
+
regex: regex,
|
|
150
|
+
handlers: []
|
|
151
|
+
};
|
|
152
|
+
existingEntries.push(patternEntry);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Update handlers
|
|
156
|
+
patternEntry.handlers = existingHandlers;
|
|
157
|
+
this.patternListeners.set(pattern, existingEntries);
|
|
158
|
+
|
|
159
|
+
if (this.debug) {
|
|
160
|
+
console.log(`PatternMatcher: Registered pattern listener for '${pattern}' (${patternEntry.handlers.length} total handlers for this pattern)`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Unregister a pattern listener
|
|
166
|
+
* @param {string} pattern - Pattern path
|
|
167
|
+
* @param {Function} handler - Handler function to remove
|
|
168
|
+
* @returns {boolean} Success status
|
|
169
|
+
*/
|
|
170
|
+
unregister(pattern, handler) {
|
|
171
|
+
if (!this.patternListeners.has(pattern)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const patternEntries = this.patternListeners.get(pattern);
|
|
176
|
+
let removed = false;
|
|
177
|
+
|
|
178
|
+
for (const patternEntry of patternEntries) {
|
|
179
|
+
const index = patternEntry.handlers.indexOf(handler);
|
|
180
|
+
if (index !== -1) {
|
|
181
|
+
patternEntry.handlers.splice(index, 1);
|
|
182
|
+
removed = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Clean up empty pattern entries
|
|
187
|
+
const filteredEntries = patternEntries.filter(entry => entry.handlers.length > 0);
|
|
188
|
+
if (filteredEntries.length === 0) {
|
|
189
|
+
this.patternListeners.delete(pattern);
|
|
190
|
+
} else {
|
|
191
|
+
this.patternListeners.set(pattern, filteredEntries);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (removed && this.debug) {
|
|
195
|
+
console.log(`PatternMatcher: Unregistered pattern listener for '${pattern}'`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return removed;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Find all pattern matches for a given path
|
|
203
|
+
* @param {string} path - Actual path to match against
|
|
204
|
+
* @returns {Array<Object>} Array of { patternEntry, params } objects
|
|
205
|
+
*/
|
|
206
|
+
findMatches(path) {
|
|
207
|
+
const matches = [];
|
|
208
|
+
|
|
209
|
+
for (const [pattern, patternEntries] of this.patternListeners) {
|
|
210
|
+
for (const patternEntry of patternEntries) {
|
|
211
|
+
const matchResult = patternEntry.regex.exec(path);
|
|
212
|
+
if (matchResult) {
|
|
213
|
+
const params = this.extractParams(pattern, patternEntry.paramNames, matchResult);
|
|
214
|
+
matches.push({ patternEntry, params });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return matches;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Check if pattern listeners exist for a pattern
|
|
224
|
+
* @param {string} pattern - Pattern path
|
|
225
|
+
* @returns {boolean} True if pattern listeners exist
|
|
226
|
+
*/
|
|
227
|
+
has(pattern) {
|
|
228
|
+
return this.patternListeners.has(pattern) &&
|
|
229
|
+
this.patternListeners.get(pattern).some(entry => entry.handlers.length > 0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get pattern listener count for a specific pattern
|
|
234
|
+
* @param {string} pattern - Pattern path
|
|
235
|
+
* @returns {number} Total number of handlers for this pattern
|
|
236
|
+
*/
|
|
237
|
+
getCount(pattern) {
|
|
238
|
+
if (!this.patternListeners.has(pattern)) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this.patternListeners.get(pattern).reduce((sum, entry) => sum + entry.handlers.length, 0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get all registered patterns
|
|
247
|
+
* @returns {Array<string>} Array of pattern strings
|
|
248
|
+
*/
|
|
249
|
+
getPatterns() {
|
|
250
|
+
return Array.from(this.patternListeners.keys());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get total pattern listener count across all patterns
|
|
255
|
+
* @returns {number} Total number of pattern listeners
|
|
256
|
+
*/
|
|
257
|
+
getTotalCount() {
|
|
258
|
+
return Array.from(this.patternListeners.values()).reduce((sum, entries) => {
|
|
259
|
+
return sum + entries.reduce((entrySum, entry) => entrySum + entry.handlers.length, 0);
|
|
260
|
+
}, 0);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get number of registered patterns
|
|
265
|
+
* @returns {number} Number of patterns
|
|
266
|
+
*/
|
|
267
|
+
getPatternCount() {
|
|
268
|
+
return this.patternListeners.size;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Clear all pattern listeners
|
|
273
|
+
* @returns {void}
|
|
274
|
+
*/
|
|
275
|
+
clear() {
|
|
276
|
+
this.patternListeners.clear();
|
|
277
|
+
|
|
278
|
+
if (this.debug) {
|
|
279
|
+
console.log(`PatternMatcher: Cleared all pattern listeners`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|