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