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,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
+