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,416 @@
1
+ import { SubsystemBuilder } from '../builder/subsystem-builder.js';
2
+ import { FacetManager } from '../manager/facet-manager.js';
3
+ import { disposeChildren } from './base-subsystem.utils.js';
4
+ import { createSubsystemLogger } from '../utils/logger.js';
5
+ import { DependencyGraphCache } from '../builder/dependency-graph-cache.js';
6
+
7
+ // Constants for facet kinds (used if facets are available)
8
+ const HIERARCHY_KIND = 'hierarchy';
9
+
10
+ export class BaseSubsystem {
11
+ _isBuilt = false;
12
+ _buildPromise = null;
13
+ _disposePromise = null;
14
+ _builder = null;
15
+ _initCallbacks = [];
16
+ _disposeCallbacks = [];
17
+ _parent = null; // ← parent subsystem
18
+
19
+ /**
20
+ * @param {string} name - Unique name for the subsystem
21
+ * @param {Object} options - Configuration options
22
+ * @param {Object} [options.ms] - Optional message system instance (for compatibility, not required for standalone)
23
+ * @param {Object} [options.config={}] - Optional configuration object keyed by facet kind.
24
+ * Each key corresponds to a facet kind (e.g., 'router', 'queue', 'scheduler').
25
+ * Each value is the configuration object for that specific hook/facet.
26
+ * @param {boolean} [options.debug=false] - Enable debug logging
27
+ */
28
+ constructor(name, options = {}) {
29
+ if (!name || typeof name !== 'string')
30
+ throw new Error('BaseSubsystem: name must be a non-empty string');
31
+
32
+ // ms is optional for standalone plugin system
33
+ // if (!options.ms)
34
+ // throw new Error('BaseSubsystem: options.ms is required');
35
+
36
+ this.name = name;
37
+ this.options = options;
38
+ this.messageSystem = options.ms || null;
39
+
40
+ // create the context object
41
+ this.ctx = {};
42
+ this.ctx.ms = options.ms || null; // Optional message system
43
+ this.ctx.config = options.config || {}; // Optional configuration object keyed by facet kind
44
+ this.ctx.debug = !!options.debug;
45
+
46
+ // Legacy property for backward compatibility (use ctx.debug instead)
47
+ this.debug = this.ctx.debug;
48
+
49
+ this.defaultHooks = options.defaultHooks;
50
+ this.hooks = [];
51
+ this._builder = new SubsystemBuilder(this);
52
+ this.api = { name,
53
+ __facets: new FacetManager(this) };
54
+ this.coreProcessor = null;
55
+ }
56
+
57
+ // ==== Hierarchy Management ====
58
+
59
+ /** Assign a parent subsystem (called during child registration). */
60
+ setParent(parent) {
61
+ const hierarchy = this.find(HIERARCHY_KIND);
62
+ if (hierarchy) {
63
+ return hierarchy.setParent(parent);
64
+ }
65
+ // Fallback if hierarchy facet not present
66
+ if (parent && typeof parent !== 'object')
67
+ throw new Error(`${this.name}: parent must be an object or null`);
68
+ this._parent = parent;
69
+ return this;
70
+ }
71
+
72
+ /** Retrieve the parent subsystem. */
73
+ getParent() {
74
+ const hierarchy = this.find(HIERARCHY_KIND);
75
+ if (hierarchy) {
76
+ return hierarchy.getParent();
77
+ }
78
+ // Fallback if hierarchy facet not present
79
+ return this._parent;
80
+ }
81
+
82
+ /** True if this subsystem has no parent (i.e., top-level). */
83
+ isRoot() {
84
+ const hierarchy = this.find(HIERARCHY_KIND);
85
+ if (hierarchy) {
86
+ return hierarchy.isRoot();
87
+ }
88
+ // Fallback if hierarchy facet not present
89
+ return this._parent === null;
90
+ }
91
+
92
+ /** Returns the root subsystem by traversing up the parent chain. */
93
+ getRoot() {
94
+ const hierarchy = this.find(HIERARCHY_KIND);
95
+ if (hierarchy) {
96
+ return hierarchy.getRoot();
97
+ }
98
+ // Fallback if hierarchy facet not present
99
+ let current = this;
100
+ while (current._parent !== null) {
101
+ current = current._parent;
102
+ }
103
+ return current;
104
+ }
105
+
106
+ /**
107
+ * Returns a fully-qualified subsystem name string.
108
+ * Example:
109
+ * Root subsystem "kernel" → "kernel://"
110
+ * Child subsystem "cache" under "kernel" → "kernel://cache"
111
+ * Grandchild "manager" → "kernel://cache/manager"
112
+ */
113
+ getNameString() {
114
+ if (this._parent === null) {
115
+ return `${this.name}://`;
116
+ }
117
+ const parentName = this._parent.getNameString();
118
+ // ensure no accidental trailing "//"
119
+ return `${parentName.replace(/\/$/, '')}/${this.name}`;
120
+ }
121
+
122
+ // ==== State getters ====
123
+
124
+ get isBuilt() { return this._isBuilt; }
125
+
126
+ /** Returns an array of all facet kinds (capabilities) available on this subsystem. */
127
+ get capabilities() { return this.api.__facets.getAllKinds(); }
128
+
129
+ // ==== Hook registration ====
130
+
131
+ use(hook) {
132
+ if (this._isBuilt)
133
+ throw new Error(`${this.name}: cannot add hooks after build()`);
134
+ if (typeof hook !== 'function')
135
+ throw new Error(`${this.name}: hook must be a function`);
136
+ this.hooks.push(hook);
137
+ return this;
138
+ }
139
+
140
+ onInit(cb) {
141
+ if (typeof cb !== 'function')
142
+ throw new Error(`${this.name}: onInit callback must be a function`);
143
+ this._initCallbacks.push(cb);
144
+ return this;
145
+ }
146
+
147
+ onDispose(cb) {
148
+ if (typeof cb !== 'function')
149
+ throw new Error(`${this.name}: onDispose callback must be a function`);
150
+ this._disposeCallbacks.push(cb);
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Find a facet by kind and optional orderIndex
156
+ * @param {string} kind - Facet kind to find
157
+ * @param {number} [orderIndex] - Optional order index. If provided, returns facet at that index. If not, returns the last facet (highest orderIndex).
158
+ * @returns {Object|undefined} Facet instance or undefined if not found
159
+ */
160
+ find(kind, orderIndex = undefined) { return this.api.__facets.find(kind, orderIndex); }
161
+
162
+ /**
163
+ * Get a facet by its index in the array of facets of that kind
164
+ * @param {string} kind - Facet kind to find
165
+ * @param {number} index - Zero-based index in the array of facets of this kind
166
+ * @returns {Object|undefined} Facet instance or undefined if not found
167
+ */
168
+ getByIndex(kind, index) { return this.api.__facets.getByIndex(kind, index); }
169
+
170
+ // ==== Lifecycle ====
171
+
172
+ async build(ctx = {}) {
173
+ if (this._isBuilt) return this;
174
+ if (this._buildPromise) return this._buildPromise;
175
+
176
+ this._buildPromise = (async () => {
177
+ try {
178
+ // Determine graphCache: use provided, inherited from parent, or create new
179
+ let graphCache = ctx.graphCache || this.ctx?.graphCache || this.ctx?.parent?.graphCache;
180
+
181
+ if (!graphCache) {
182
+ // Create new cache with default capacity (configurable via ctx.config.graphCache.capacity)
183
+ const cacheCapacity = ctx.config?.graphCache?.capacity || 100;
184
+ graphCache = new DependencyGraphCache(cacheCapacity);
185
+ }
186
+
187
+ // Set graphCache on ctx so it's available after build
188
+ this.ctx.graphCache = graphCache;
189
+
190
+ this._builder.withCtx(ctx); // any additional context to be passed to the builder
191
+ await this._builder.build(graphCache); // Pass graphCache explicitly
192
+ for (const cb of this._initCallbacks)
193
+ await cb(this.api, this.ctx);
194
+ this._isBuilt = true;
195
+
196
+ // Note: coreProcessor is not set for standalone plugin system
197
+ // (no message processing needed)
198
+ this.coreProcessor = null;
199
+
200
+ const logger = createSubsystemLogger(this);
201
+ logger.log('Built successfully');
202
+ return this;
203
+ } finally {
204
+ this._buildPromise = null;
205
+ }
206
+ })();
207
+
208
+ return this._buildPromise;
209
+ }
210
+
211
+ async dispose() {
212
+ if (!this._isBuilt && !this._buildPromise) return;
213
+ if (this._disposePromise) return this._disposePromise;
214
+
215
+ const waitBuild = this._buildPromise ? this._buildPromise.catch(() => {}) : Promise.resolve();
216
+
217
+ this._disposePromise = (async () => {
218
+ try {
219
+ await waitBuild;
220
+ if (!this._isBuilt) return;
221
+
222
+ await disposeChildren(this);
223
+ if (this.api && this.api.__facets) {
224
+ await this.api.__facets.disposeAll(this);
225
+ }
226
+
227
+ const logger = createSubsystemLogger(this);
228
+ for (const cb of this._disposeCallbacks) {
229
+ try { await cb(); }
230
+ catch (err) { logger.error('Dispose callback error:', err); }
231
+ }
232
+
233
+ this._isBuilt = false;
234
+ this.coreProcessor = null;
235
+ this._builder.invalidate();
236
+
237
+ logger.log('Disposed');
238
+ } finally {
239
+ this._disposePromise = null;
240
+ }
241
+ })();
242
+
243
+ return this._disposePromise;
244
+ }
245
+
246
+ /**
247
+ * Reload the subsystem: dispose all facets and reset built state,
248
+ * while preserving hooks and configuration. Allows adding more hooks
249
+ * and rebuilding.
250
+ *
251
+ * This method:
252
+ * - Disposes all facets and child subsystems
253
+ * - Resets built state (allows use() to work again)
254
+ * - Preserves hooks, defaultHooks, context, and lifecycle callbacks
255
+ *
256
+ * @returns {Promise<BaseSubsystem>} This subsystem for chaining
257
+ *
258
+ * @example
259
+ * // Initial build
260
+ * await system.use(useDatabase).build();
261
+ *
262
+ * // Reload and extend
263
+ * await system.reload().use(useCache).build();
264
+ */
265
+ async reload() {
266
+ // Wait for any in-progress operations
267
+ if (this._buildPromise) {
268
+ await this._buildPromise.catch(() => {}); // Wait even if failed
269
+ }
270
+ if (this._disposePromise) {
271
+ await this._disposePromise.catch(() => {}); // Wait even if failed
272
+ }
273
+
274
+ // Only proceed if actually built
275
+ if (!this._isBuilt) {
276
+ return this; // Already not built, nothing to reload
277
+ }
278
+
279
+ // Dispose all facets (clean slate)
280
+ if (this.api?.__facets) {
281
+ // Remove attached properties before disposing (to allow re-attachment on rebuild)
282
+ const facetKinds = Array.from(this.api.__facets[Symbol.iterator]?.() || []);
283
+ for (const [kind] of facetKinds) {
284
+ if (kind in this && this[kind] !== undefined) {
285
+ try {
286
+ delete this[kind];
287
+ } catch {
288
+ // Best-effort cleanup (property might be non-configurable)
289
+ }
290
+ }
291
+ }
292
+ await this.api.__facets.disposeAll(this);
293
+ }
294
+
295
+ // Dispose child subsystems
296
+ await disposeChildren(this);
297
+
298
+ // Reset built state (allows use() to work again)
299
+ this._isBuilt = false;
300
+
301
+ // Invalidate builder cache (force recalculation on next build)
302
+ this._builder.invalidate();
303
+
304
+ // Clear promises
305
+ this._buildPromise = null;
306
+ this._disposePromise = null;
307
+
308
+ // NOTE: Preserved:
309
+ // - this.hooks (user-added hooks)
310
+ // - this.defaultHooks (default hooks)
311
+ // - this.ctx (context/config)
312
+ // - this._initCallbacks
313
+ // - this._disposeCallbacks
314
+
315
+ const logger = createSubsystemLogger(this);
316
+ logger.log('Reloaded - ready for extension and rebuild');
317
+
318
+ return this; // For chaining: system.reload().use(hook).build()
319
+ }
320
+
321
+ // ==== Message flow (No-ops for standalone plugin system) ====
322
+
323
+ /**
324
+ * No-op: Message acceptance is not needed for standalone plugin system.
325
+ * @param {*} _message - Ignored
326
+ * @param {*} _options - Ignored
327
+ * @returns {Promise<boolean>} Always returns true
328
+ */
329
+ async accept(_message, _options = {}) {
330
+ // No-op for standalone plugin system
331
+ return true;
332
+ }
333
+
334
+ /**
335
+ * No-op: Message processing is not needed for standalone plugin system.
336
+ * @param {*} _timeSlice - Ignored
337
+ * @returns {Promise<null>} Always returns null
338
+ */
339
+ async process(_timeSlice) {
340
+ // No-op for standalone plugin system
341
+ return null;
342
+ }
343
+
344
+ /**
345
+ * No-op: Immediate message processing is not needed for standalone plugin system.
346
+ * @param {*} _message - Ignored
347
+ * @param {*} _options - Ignored
348
+ * @returns {Promise<null>} Always returns null
349
+ */
350
+ async processImmediately(_message, _options = {}) {
351
+ // No-op for standalone plugin system
352
+ return null;
353
+ }
354
+
355
+ /**
356
+ * No-op: Pause functionality is not needed for standalone plugin system.
357
+ * @returns {BaseSubsystem} Returns this for chaining
358
+ */
359
+ pause() {
360
+ // No-op for standalone plugin system
361
+ return this;
362
+ }
363
+
364
+ /**
365
+ * No-op: Resume functionality is not needed for standalone plugin system.
366
+ * @returns {BaseSubsystem} Returns this for chaining
367
+ */
368
+ resume() {
369
+ // No-op for standalone plugin system
370
+ return this;
371
+ }
372
+
373
+ /**
374
+ * Get queue status (returns default if queue facet not available).
375
+ * @returns {Object} Queue status object
376
+ */
377
+ getQueueStatus() {
378
+ const queue = this.find('queue');
379
+ if (!queue?.getStatus) {
380
+ // Return default status if queue facet is not available
381
+ return { size: 0, maxSize: 0, isFull: false };
382
+ }
383
+ return queue.getStatus();
384
+ }
385
+
386
+ // ==== Routing (Optional - returns null if router not available) ====
387
+
388
+ /**
389
+ * Register a route (optional - returns null if router facet not available).
390
+ * @param {string} pattern - Route pattern
391
+ * @param {Function} handler - Route handler
392
+ * @param {Object} routeOptions - Route options
393
+ * @returns {boolean|null} True if registered, null if router not available
394
+ */
395
+ registerRoute(pattern, handler, routeOptions = {}) {
396
+ const router = this.find('router');
397
+ if (!router?.registerRoute) {
398
+ return null;
399
+ }
400
+ return router.registerRoute(pattern, handler, routeOptions);
401
+ }
402
+
403
+ /**
404
+ * Unregister a route (optional - returns null if router facet not available).
405
+ * @param {string} pattern - Route pattern
406
+ * @returns {boolean|null} True if unregistered, null if router not available
407
+ */
408
+ unregisterRoute(pattern) {
409
+ const router = this.find('router');
410
+ if (!router?.unregisterRoute) {
411
+ return null;
412
+ }
413
+ return router.unregisterRoute(pattern);
414
+ }
415
+ }
416
+
@@ -0,0 +1,106 @@
1
+ /**
2
+ * base-subsystem.utils.js
3
+ *
4
+ * Hierarchy lifecycle utilities for collecting, building, and disposing child subsystems.
5
+ * These are read-only helpers that work with or without the useHierarchy facet.
6
+ */
7
+
8
+ // Constant for hierarchy facet kind (used if hierarchy facet is available)
9
+ const HIERARCHY_KIND = 'hierarchy';
10
+
11
+ /**
12
+ * Collects all children from a parent subsystem.
13
+ *
14
+ * Prefers useHierarchy facet → registry, with fallbacks to Map or array.
15
+ *
16
+ * @param {object} parent - The parent subsystem
17
+ * @returns {object[]} Array of child subsystem instances
18
+ *
19
+ * @example
20
+ * const children = collectChildren(parent);
21
+ * console.log(`Parent has ${children.length} children`);
22
+ */
23
+ export function collectChildren(parent) {
24
+ if (!parent || typeof parent !== 'object') {
25
+ return [];
26
+ }
27
+
28
+ // Prefer useHierarchy facet → registry
29
+ const hierarchy = parent.find?.(HIERARCHY_KIND);
30
+ const reg = hierarchy?.children || parent.children;
31
+
32
+ if (!reg) {
33
+ return [];
34
+ }
35
+
36
+ // Try registry's list() method (ChildSubsystemRegistry)
37
+ if (typeof reg.list === 'function') {
38
+ return reg.list();
39
+ }
40
+
41
+ // Fallback to Map
42
+ if (reg instanceof Map) {
43
+ return Array.from(reg.values());
44
+ }
45
+
46
+ // Fallback to array
47
+ if (Array.isArray(reg)) {
48
+ return reg;
49
+ }
50
+
51
+ return [];
52
+ }
53
+
54
+ /**
55
+ * Builds all child subsystems of a parent.
56
+ *
57
+ * Iterates through children and calls build() on each if not already built.
58
+ * Parent's ctx should already be fully resolved.
59
+ *
60
+ * @param {object} parent - The parent subsystem
61
+ * @returns {Promise<void>}
62
+ *
63
+ * @example
64
+ * await buildChildren(parent);
65
+ * console.log('All children built');
66
+ */
67
+ export async function buildChildren(parent) {
68
+ const children = collectChildren(parent);
69
+
70
+ for (const child of children) {
71
+ if (child && typeof child.build === 'function' && !child._isBuilt) {
72
+ // Merge parent context into child context
73
+ if (!child.ctx) {
74
+ child.ctx = {};
75
+ }
76
+ child.ctx.parent = parent.ctx;
77
+ child.ctx.graphCache = parent.ctx.graphCache;
78
+ await child.build(); // parent.ctx is now accessible via child.ctx.parent
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Disposes all child subsystems of a parent.
85
+ *
86
+ * Disposes children in reverse order (bottom-up) for proper cleanup.
87
+ * Deep trees recurse via child.dispose().
88
+ *
89
+ * @param {object} parent - The parent subsystem
90
+ * @returns {Promise<void>}
91
+ *
92
+ * @example
93
+ * await disposeChildren(parent);
94
+ * console.log('All children disposed');
95
+ */
96
+ export async function disposeChildren(parent) {
97
+ const children = collectChildren(parent);
98
+
99
+ // Bottom-up: reverse shallow order; deep trees recurse via child.dispose()
100
+ for (const child of [...children].reverse()) {
101
+ if (child && typeof child.dispose === 'function') {
102
+ await child.dispose();
103
+ }
104
+ }
105
+ }
106
+
@@ -0,0 +1,4 @@
1
+ export { BaseSubsystem } from './base-subsystem.js';
2
+ export { StandalonePluginSystem } from './standalone-plugin-system.js';
3
+ export { collectChildren, buildChildren, disposeChildren } from './base-subsystem.utils.js';
4
+
@@ -0,0 +1,70 @@
1
+ import { BaseSubsystem } from './base-subsystem.js';
2
+
3
+ /**
4
+ * StandalonePluginSystem
5
+ *
6
+ * A specialized BaseSubsystem designed for standalone plugin systems without message processing.
7
+ * Automatically overrides message-specific methods as no-ops.
8
+ *
9
+ * This class is ideal for:
10
+ * - Plugin architectures
11
+ * - Modular applications
12
+ * - Component systems
13
+ * - Service containers
14
+ *
15
+ * @example
16
+ * ```javascript
17
+ * import { StandalonePluginSystem } from './standalone-plugin-system.js';
18
+ * import { useDatabase } from './plugins/use-database.js';
19
+ *
20
+ * const system = new StandalonePluginSystem('my-app', {
21
+ * config: {
22
+ * database: { host: 'localhost' }
23
+ * }
24
+ * });
25
+ *
26
+ * system
27
+ * .use(useDatabase)
28
+ * .build();
29
+ *
30
+ * const db = system.find('database');
31
+ * ```
32
+ */
33
+ export class StandalonePluginSystem extends BaseSubsystem {
34
+ /**
35
+ * @param {string} name - Unique name for the plugin system
36
+ * @param {Object} options - Configuration options
37
+ * @param {Object} [options.config={}] - Optional configuration object keyed by facet kind.
38
+ * Each key corresponds to a facet kind (e.g., 'database', 'cache').
39
+ * Each value is the configuration object for that specific hook/facet.
40
+ * @param {boolean} [options.debug=false] - Enable debug logging
41
+ * @param {Array} [options.defaultHooks=[]] - Optional default hooks to install
42
+ */
43
+ constructor(name, options = {}) {
44
+ // Pass null for message system - not needed for standalone plugin system
45
+ super(name, { ...options, ms: null });
46
+
47
+ // No default hooks by default (can be set via options.defaultHooks)
48
+ // Users can add hooks via .use() method
49
+ this.defaultHooks = options.defaultHooks || [];
50
+ }
51
+
52
+ // ==== Message Flow Methods (No-Ops) ====
53
+ // Inherited from BaseSubsystem - already no-ops
54
+
55
+ // ==== Routing Methods (No-Ops) ====
56
+ // Inherited from BaseSubsystem - already returns null if router not available
57
+
58
+ // ==== Lifecycle Methods (Kept from BaseSubsystem) ====
59
+ // build(), dispose(), onInit(), onDispose() are inherited and work as expected
60
+
61
+ // ==== Plugin Management Methods (Kept from BaseSubsystem) ====
62
+ // use(), find() are inherited and work as expected
63
+
64
+ // ==== Hierarchy Methods (Kept from BaseSubsystem) ====
65
+ // setParent(), getParent(), isRoot(), getRoot(), getNameString() are inherited
66
+
67
+ // ==== State Getters (Kept from BaseSubsystem) ====
68
+ // isBuilt getter is inherited
69
+ }
70
+
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Debug Flag Utilities
3
+ *
4
+ * Provides standardized debug flag extraction from configuration and context.
5
+ */
6
+
7
+ /**
8
+ * Extract debug flag from config and context with proper fallback chain.
9
+ *
10
+ * Checks in order:
11
+ * 1. config.debug (if explicitly set, including false)
12
+ * 2. ctx.debug (if available)
13
+ * 3. false (default)
14
+ *
15
+ * @param {Object} config - Facet-specific configuration object (may be undefined)
16
+ * @param {Object} ctx - Context object with debug flag (may be undefined)
17
+ * @returns {boolean} Debug flag value
18
+ *
19
+ * @example
20
+ * ```javascript
21
+ * const config = ctx.config?.router || {};
22
+ * const debug = getDebugFlag(config, ctx);
23
+ * ```
24
+ */
25
+ export function getDebugFlag(config, ctx) {
26
+ if (config?.debug !== undefined) {
27
+ return !!config.debug;
28
+ }
29
+ if (ctx?.debug !== undefined) {
30
+ return !!ctx.debug;
31
+ }
32
+ return false;
33
+ }
34
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * findFacet Utility
3
+ *
4
+ * Safely finds a facet by kind from a FacetManager.
5
+ *
6
+ * @param {FacetManager} facetManager - The FacetManager instance (e.g., api.__facets)
7
+ * @param {string} kind - The facet kind to find
8
+ * @returns {false|{result: true, facet: Object}} - Returns false if not found, or an object with result and facet if found
9
+ *
10
+ * @example
11
+ * const found = findFacet(api.__facets, 'router');
12
+ * if (found) {
13
+ * const routerFacet = found.facet;
14
+ * // Use routerFacet...
15
+ * }
16
+ */
17
+ export function findFacet(facetManager, kind) {
18
+ if (!facetManager || typeof facetManager.find !== 'function') {
19
+ return false;
20
+ }
21
+
22
+ const facet = facetManager.find(kind);
23
+
24
+ if (!facet) {
25
+ return false;
26
+ }
27
+
28
+ return { result: true, facet };
29
+ }
30
+