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,104 @@
|
|
|
1
|
+
import { verifySubsystemBuild, buildSubsystem as executeBuild, deepMerge } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export class SubsystemBuilder {
|
|
4
|
+
#subsystem;
|
|
5
|
+
#ctx = {};
|
|
6
|
+
#plan = null;
|
|
7
|
+
#lastCtxHash = null;
|
|
8
|
+
#lastGraphCache = null;
|
|
9
|
+
|
|
10
|
+
constructor(subsystem) {
|
|
11
|
+
if (!subsystem) throw new Error('SubsystemBuilder: subsystem is required');
|
|
12
|
+
this.#subsystem = subsystem;
|
|
13
|
+
// Note: ctx is resolved in verifySubsystemBuild, not stored here
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Simple hash function for context comparison
|
|
17
|
+
#hashCtx(ctx) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(ctx);
|
|
20
|
+
} catch {
|
|
21
|
+
// If JSON.stringify fails, use a fallback
|
|
22
|
+
return String(ctx);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
withCtx(ctx = {}) {
|
|
27
|
+
// Shallow merge for most properties
|
|
28
|
+
const merged = { ...this.#ctx, ...ctx };
|
|
29
|
+
|
|
30
|
+
// Deep merge config objects if both exist
|
|
31
|
+
if (this.#ctx.config && typeof this.#ctx.config === 'object' &&
|
|
32
|
+
ctx.config && typeof ctx.config === 'object' &&
|
|
33
|
+
!Array.isArray(this.#ctx.config) && !Array.isArray(ctx.config)) {
|
|
34
|
+
merged.config = deepMerge(this.#ctx.config, ctx.config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.#ctx = merged;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
clearCtx() {
|
|
42
|
+
this.#ctx = {};
|
|
43
|
+
this.#lastCtxHash = null;
|
|
44
|
+
this.#plan = null; // Clear plan when context is cleared
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
plan(graphCache = null) {
|
|
49
|
+
// Determine which graphCache to use (priority: subsystem.ctx > parameter > cached)
|
|
50
|
+
const subsystemCache = this.#subsystem.ctx?.graphCache;
|
|
51
|
+
|
|
52
|
+
// Check if we have a cached plan and if the context hasn't changed
|
|
53
|
+
const currentCtxHash = this.#hashCtx(this.#ctx);
|
|
54
|
+
if (this.#plan && this.#lastCtxHash === currentCtxHash) {
|
|
55
|
+
// Update lastGraphCache if a new one is provided
|
|
56
|
+
const cacheToUse = subsystemCache || (graphCache !== null ? graphCache : this.#lastGraphCache);
|
|
57
|
+
if (cacheToUse !== this.#lastGraphCache) {
|
|
58
|
+
this.#lastGraphCache = cacheToUse;
|
|
59
|
+
}
|
|
60
|
+
// Return this for method chaining
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Use the determined cache (subsystem.ctx takes priority, then parameter, then null)
|
|
65
|
+
const cacheToUse = subsystemCache || graphCache;
|
|
66
|
+
const result = verifySubsystemBuild(this.#subsystem, this.#ctx, cacheToUse);
|
|
67
|
+
// Extract plan and updated graphCache from result
|
|
68
|
+
const { graphCache: updatedGraphCache, ...plan } = result;
|
|
69
|
+
this.#plan = plan;
|
|
70
|
+
this.#lastCtxHash = currentCtxHash;
|
|
71
|
+
this.#lastGraphCache = updatedGraphCache || cacheToUse;
|
|
72
|
+
// Return this for method chaining
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
dryRun(graphCache = null) { return this.plan(graphCache); }
|
|
77
|
+
|
|
78
|
+
getPlan() { return this.#plan; }
|
|
79
|
+
|
|
80
|
+
getGraphCache() { return this.#lastGraphCache; }
|
|
81
|
+
|
|
82
|
+
invalidate() {
|
|
83
|
+
this.#plan = null;
|
|
84
|
+
this.#lastCtxHash = null;
|
|
85
|
+
this.#lastGraphCache = null;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Executes verify → transactional facet add/init/attach → build children. No internal guards here. */
|
|
90
|
+
async build(graphCache = null) {
|
|
91
|
+
// Create or use cached plan
|
|
92
|
+
this.plan(graphCache);
|
|
93
|
+
const plan = this.#plan;
|
|
94
|
+
if (!plan) {
|
|
95
|
+
throw new Error('SubsystemBuilder.build: failed to create plan');
|
|
96
|
+
}
|
|
97
|
+
// graphCache is already included in plan.resolvedCtx by verifySubsystemBuild
|
|
98
|
+
// and will be set on subsystem.ctx by buildSubsystem
|
|
99
|
+
|
|
100
|
+
await executeBuild(this.#subsystem, plan);
|
|
101
|
+
return this.#subsystem;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { buildChildren } from '../system/base-subsystem.utils.js';
|
|
2
|
+
import { defaultContractRegistry } from '../contract/index.js';
|
|
3
|
+
import { resolveCtx } from './context-resolver.js';
|
|
4
|
+
import { createCacheKey, buildDepGraph, topoSort } from './dependency-graph.js';
|
|
5
|
+
import { validateFacets } from './facet-validator.js';
|
|
6
|
+
import {
|
|
7
|
+
extractHookMetadata,
|
|
8
|
+
orderHooksByDependencies,
|
|
9
|
+
executeHooksAndCreateFacets,
|
|
10
|
+
validateHookDependencies,
|
|
11
|
+
} from './hook-processor.js';
|
|
12
|
+
|
|
13
|
+
// Re-export deepMerge for backward compatibility
|
|
14
|
+
export { deepMerge } from './context-resolver.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* VERIFY (pure):
|
|
18
|
+
* - resolve ctx (pure)
|
|
19
|
+
* - collect hooks (defaults + user)
|
|
20
|
+
* - instantiate facets
|
|
21
|
+
* - validate + topo-sort (with caching)
|
|
22
|
+
*/
|
|
23
|
+
export function verifySubsystemBuild(subsystem, ctx = {}, graphCache = null) {
|
|
24
|
+
const resolvedCtx = resolveCtx(subsystem, ctx);
|
|
25
|
+
|
|
26
|
+
// Check whether the defaults are defined as a DefaultHooks instance or an array of hooks.
|
|
27
|
+
// If it's a DefaultHooks instance, use the list() method to get the array of hooks.
|
|
28
|
+
const defaults = Array.isArray(subsystem.defaultHooks)
|
|
29
|
+
? subsystem.defaultHooks
|
|
30
|
+
: (subsystem.defaultHooks?.list?.() || []);
|
|
31
|
+
|
|
32
|
+
const user = Array.isArray(subsystem.hooks) ? subsystem.hooks : [];
|
|
33
|
+
const hooks = [...defaults, ...user];
|
|
34
|
+
|
|
35
|
+
// Extract hook metadata
|
|
36
|
+
const hooksByKind = extractHookMetadata(hooks);
|
|
37
|
+
|
|
38
|
+
// Order hooks based on dependencies
|
|
39
|
+
const orderedHooks = orderHooksByDependencies(hooks);
|
|
40
|
+
|
|
41
|
+
// Execute hooks and create facets
|
|
42
|
+
const { facetsByKind } = executeHooksAndCreateFacets(orderedHooks, resolvedCtx, subsystem, hooksByKind);
|
|
43
|
+
|
|
44
|
+
// Validate facets against their contracts (before dependency graph building)
|
|
45
|
+
validateFacets(facetsByKind, resolvedCtx, subsystem, defaultContractRegistry);
|
|
46
|
+
|
|
47
|
+
// Validate hook.required dependencies exist
|
|
48
|
+
validateHookDependencies(hooksByKind, facetsByKind, subsystem);
|
|
49
|
+
|
|
50
|
+
// Create cache key from sorted facet kinds
|
|
51
|
+
const kinds = Object.keys(facetsByKind);
|
|
52
|
+
const cacheKey = graphCache ? createCacheKey(kinds) : null;
|
|
53
|
+
|
|
54
|
+
// Include graphCache in resolvedCtx so it persists through buildSubsystem
|
|
55
|
+
if (graphCache) {
|
|
56
|
+
resolvedCtx.graphCache = graphCache;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check cache before building graph
|
|
60
|
+
if (graphCache && cacheKey) {
|
|
61
|
+
const cached = graphCache.get(cacheKey);
|
|
62
|
+
if (cached) {
|
|
63
|
+
if (cached.valid) {
|
|
64
|
+
// Return cached result (skip graph building and sorting)
|
|
65
|
+
return { resolvedCtx, orderedKinds: cached.orderedKinds, facetsByKind, graphCache };
|
|
66
|
+
} else {
|
|
67
|
+
// Throw cached error
|
|
68
|
+
throw new Error(cached.error || 'Cached dependency graph error');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Build graph and sort (will cache result in topoSort)
|
|
74
|
+
const graph = buildDepGraph(hooksByKind, facetsByKind, subsystem);
|
|
75
|
+
const orderedKinds = topoSort(graph, graphCache, cacheKey);
|
|
76
|
+
|
|
77
|
+
return { resolvedCtx, orderedKinds, facetsByKind, graphCache };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* EXECUTE (transactional):
|
|
82
|
+
* - assign resolved ctx
|
|
83
|
+
* - add/init/attach facets via FacetManager.addMany
|
|
84
|
+
* - build children
|
|
85
|
+
*/
|
|
86
|
+
export async function buildSubsystem(subsystem, plan) {
|
|
87
|
+
if (!plan) throw new Error('buildSubsystem: invalid plan');
|
|
88
|
+
const { resolvedCtx, orderedKinds, facetsByKind } = plan;
|
|
89
|
+
if (!Array.isArray(orderedKinds)) throw new Error('buildSubsystem: invalid plan');
|
|
90
|
+
if (!facetsByKind || typeof facetsByKind !== 'object' || Array.isArray(facetsByKind)) throw new Error('buildSubsystem: invalid plan');
|
|
91
|
+
|
|
92
|
+
// Validate consistency: if one is non-empty, the other must match
|
|
93
|
+
const hasOrderedKinds = orderedKinds.length > 0;
|
|
94
|
+
const hasFacetsByKind = Object.keys(facetsByKind).length > 0;
|
|
95
|
+
|
|
96
|
+
// If orderedKinds is empty but facetsByKind has items, that's invalid
|
|
97
|
+
if (!hasOrderedKinds && hasFacetsByKind) throw new Error('buildSubsystem: invalid plan');
|
|
98
|
+
// If facetsByKind is empty but orderedKinds has items, that's invalid
|
|
99
|
+
if (hasOrderedKinds && !hasFacetsByKind) throw new Error('buildSubsystem: invalid plan');
|
|
100
|
+
// Both empty is valid (no facets to add)
|
|
101
|
+
|
|
102
|
+
subsystem.ctx = resolvedCtx;
|
|
103
|
+
|
|
104
|
+
// Separate facets into new and overwrite
|
|
105
|
+
const facetsToAdd = {};
|
|
106
|
+
const kindsToAdd = [];
|
|
107
|
+
const facetsToOverwrite = {};
|
|
108
|
+
const kindsToOverwrite = [];
|
|
109
|
+
|
|
110
|
+
for (const kind of orderedKinds) {
|
|
111
|
+
const facet = facetsByKind[kind];
|
|
112
|
+
const existingFacet = subsystem.api.__facets.find(kind);
|
|
113
|
+
|
|
114
|
+
if (!existingFacet) {
|
|
115
|
+
// New facet - add normally
|
|
116
|
+
facetsToAdd[kind] = facet;
|
|
117
|
+
kindsToAdd.push(kind);
|
|
118
|
+
} else if (existingFacet === facet) {
|
|
119
|
+
// Same facet instance - this was added during verify phase for dependency lookups
|
|
120
|
+
// It needs to be properly initialized/attached, so add it
|
|
121
|
+
facetsToAdd[kind] = facet;
|
|
122
|
+
kindsToAdd.push(kind);
|
|
123
|
+
} else {
|
|
124
|
+
// Different facet instance - check if we can overwrite
|
|
125
|
+
const canOverwrite = facet.shouldOverwrite?.() === true;
|
|
126
|
+
if (canOverwrite) {
|
|
127
|
+
// Remove old facet first, then add new one
|
|
128
|
+
facetsToOverwrite[kind] = facet;
|
|
129
|
+
kindsToOverwrite.push(kind);
|
|
130
|
+
} else {
|
|
131
|
+
// Cannot overwrite - skip (keep existing)
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// First, remove overwritten facets
|
|
138
|
+
for (const kind of kindsToOverwrite) {
|
|
139
|
+
subsystem.api.__facets.remove(kind);
|
|
140
|
+
// Also remove from subsystem property if it exists
|
|
141
|
+
if (kind in subsystem) {
|
|
142
|
+
try {
|
|
143
|
+
delete subsystem[kind];
|
|
144
|
+
} catch {
|
|
145
|
+
// Best-effort cleanup
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Then add all facets (new + overwritten)
|
|
151
|
+
const allFacets = { ...facetsToAdd, ...facetsToOverwrite };
|
|
152
|
+
const allKinds = [...kindsToAdd, ...kindsToOverwrite];
|
|
153
|
+
|
|
154
|
+
if (allKinds.length > 0) {
|
|
155
|
+
await subsystem.api.__facets.addMany(allKinds, allFacets, {
|
|
156
|
+
init: true,
|
|
157
|
+
attach: true,
|
|
158
|
+
ctx: resolvedCtx,
|
|
159
|
+
api: subsystem.api
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await buildChildren(subsystem);
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchy Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that hierarchy facets must satisfy.
|
|
5
|
+
* Ensures all required hierarchy management methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks and BaseSubsystem.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { hierarchyContract } from './hierarchy.contract.mycelia.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a hierarchy facet
|
|
12
|
+
* hierarchyContract.enforce(ctx, api, subsystem, hierarchyFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hierarchy Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - addChild: Register a child subsystem under the current subsystem
|
|
21
|
+
* - removeChild: Remove a registered child subsystem by reference or by name
|
|
22
|
+
* - getChild: Retrieve a specific child subsystem by name
|
|
23
|
+
* - listChildren: Return an array of all currently registered child subsystems
|
|
24
|
+
* - setParent: Set the parent subsystem
|
|
25
|
+
* - getParent: Get the parent subsystem
|
|
26
|
+
* - isRoot: Check if this subsystem is a root (has no parent)
|
|
27
|
+
* - getRoot: Get the root subsystem by traversing up the parent chain
|
|
28
|
+
* - getLineage: Return the full ancestor chain (from root to node)
|
|
29
|
+
*
|
|
30
|
+
* Required properties:
|
|
31
|
+
* - children: Getter for direct access to registry instance (returns an object)
|
|
32
|
+
*
|
|
33
|
+
* Custom validation:
|
|
34
|
+
* - Validates children getter returns an object (not null or primitive)
|
|
35
|
+
*/
|
|
36
|
+
export const hierarchyContract = createFacetContract({
|
|
37
|
+
name: 'hierarchy',
|
|
38
|
+
requiredMethods: [
|
|
39
|
+
'addChild',
|
|
40
|
+
'removeChild',
|
|
41
|
+
'getChild',
|
|
42
|
+
'listChildren',
|
|
43
|
+
'setParent',
|
|
44
|
+
'getParent',
|
|
45
|
+
'isRoot',
|
|
46
|
+
'getRoot',
|
|
47
|
+
'getLineage'
|
|
48
|
+
],
|
|
49
|
+
requiredProperties: [
|
|
50
|
+
'children'
|
|
51
|
+
],
|
|
52
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
53
|
+
// Validate children getter exists and returns an object
|
|
54
|
+
const registry = facet.children;
|
|
55
|
+
if (typeof registry !== 'object' || registry === null) {
|
|
56
|
+
throw new Error('Hierarchy facet children getter must return an object');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract Exports
|
|
3
|
+
*
|
|
4
|
+
* Exports all contract implementations for easy importing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { hierarchyContract } from './hierarchy.contract.js';
|
|
8
|
+
export { listenersContract } from './listeners.contract.js';
|
|
9
|
+
export { processorContract } from './processor.contract.js';
|
|
10
|
+
export { queueContract } from './queue.contract.js';
|
|
11
|
+
export { routerContract } from './router.contract.js';
|
|
12
|
+
export { schedulerContract } from './scheduler.contract.js';
|
|
13
|
+
export { serverContract } from './server.contract.js';
|
|
14
|
+
export { speakContract } from './speak.contract.js';
|
|
15
|
+
export { storageContract } from './storage.contract.js';
|
|
16
|
+
export { websocketContract } from './websocket.contract.js';
|
|
17
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Listeners Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that listeners facets must satisfy.
|
|
5
|
+
* Ensures all required listener management methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { listenersContract } from './listeners.contract.mycelia.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a listeners facet
|
|
12
|
+
* listenersContract.enforce(ctx, api, subsystem, listenersFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Listeners Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - on: Register a listener for a specific message path
|
|
21
|
+
* - off: Unregister a listener for a specific message path
|
|
22
|
+
* - hasListeners: Check if listeners are enabled
|
|
23
|
+
* - enableListeners: Enable listeners and initialize ListenerManager
|
|
24
|
+
* - disableListeners: Disable listeners (but keep manager instance)
|
|
25
|
+
*
|
|
26
|
+
* Required properties:
|
|
27
|
+
* - listeners: Getter for direct access to ListenerManager instance (can be null)
|
|
28
|
+
* - _listenerManager: Function accessor that returns ListenerManager or null
|
|
29
|
+
*
|
|
30
|
+
* Custom validation:
|
|
31
|
+
* - Validates _listenerManager is a function
|
|
32
|
+
* - Validates listeners property exists
|
|
33
|
+
* - Validates _listenerManager() returns object or null
|
|
34
|
+
*/
|
|
35
|
+
export const listenersContract = createFacetContract({
|
|
36
|
+
name: 'listeners',
|
|
37
|
+
requiredMethods: [
|
|
38
|
+
'on',
|
|
39
|
+
'off',
|
|
40
|
+
'hasListeners',
|
|
41
|
+
'enableListeners',
|
|
42
|
+
'disableListeners'
|
|
43
|
+
],
|
|
44
|
+
requiredProperties: [
|
|
45
|
+
'listeners',
|
|
46
|
+
'_listenerManager'
|
|
47
|
+
],
|
|
48
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
49
|
+
// Validate _listenerManager is a function
|
|
50
|
+
if (typeof facet._listenerManager !== 'function') {
|
|
51
|
+
throw new Error('Listeners facet _listenerManager must be a function');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Validate listeners property exists (getter)
|
|
55
|
+
if (!('listeners' in facet)) {
|
|
56
|
+
throw new Error('Listeners facet must have listeners property');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate _listenerManager() returns object or null
|
|
60
|
+
const manager = facet._listenerManager();
|
|
61
|
+
if (manager !== null && (typeof manager !== 'object' || manager === null)) {
|
|
62
|
+
throw new Error('Listeners facet _listenerManager() must return an object or null');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processor Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that processor facets must satisfy.
|
|
5
|
+
* Ensures all required message processing methods are implemented.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { processorContract } from './processor.contract.js';
|
|
9
|
+
*
|
|
10
|
+
* // Enforce contract on a processor facet
|
|
11
|
+
* processorContract.enforce(ctx, api, subsystem, processorFacet);
|
|
12
|
+
*/
|
|
13
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Processor Facet Contract
|
|
17
|
+
*
|
|
18
|
+
* Required methods:
|
|
19
|
+
* - accept: Accept a message and place it on the queue (or process immediately for queries)
|
|
20
|
+
* - processMessage: Process a message through the complete processing pipeline
|
|
21
|
+
* - processTick: Process a single message from the queue (process one tick)
|
|
22
|
+
* - processImmediately: Process a message immediately without queuing
|
|
23
|
+
*
|
|
24
|
+
* Required properties:
|
|
25
|
+
* - None (processor doesn't expose internal properties)
|
|
26
|
+
*
|
|
27
|
+
* Custom validation:
|
|
28
|
+
* - None (methods are validated by requiredMethods check)
|
|
29
|
+
*/
|
|
30
|
+
export const processorContract = createFacetContract({
|
|
31
|
+
name: 'processor',
|
|
32
|
+
requiredMethods: [
|
|
33
|
+
'accept',
|
|
34
|
+
'processMessage',
|
|
35
|
+
'processTick',
|
|
36
|
+
'processImmediately'
|
|
37
|
+
],
|
|
38
|
+
requiredProperties: [],
|
|
39
|
+
validate: null
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that queue facets must satisfy.
|
|
5
|
+
* Ensures all required queue methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { queueContract } from './queue.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a queue facet
|
|
12
|
+
* queueContract.enforce(ctx, api, subsystem, queueFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Queue Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - selectNextMessage: Dequeue and return the next message-options pair
|
|
21
|
+
* - hasMessagesToProcess: Check if queue has messages to process
|
|
22
|
+
* - getQueueStatus: Get queue status information (size, capacity, utilization, etc.)
|
|
23
|
+
*
|
|
24
|
+
* Required properties:
|
|
25
|
+
* - _queueManager: Internal queue manager instance (used by useMessageProcessor for enqueueing)
|
|
26
|
+
* - queue: Direct access to underlying BoundedQueue instance
|
|
27
|
+
*
|
|
28
|
+
* Custom validation:
|
|
29
|
+
* - Validates _queueManager is an object with enqueue method
|
|
30
|
+
* - Validates queue property is an object
|
|
31
|
+
*/
|
|
32
|
+
export const queueContract = createFacetContract({
|
|
33
|
+
name: 'queue',
|
|
34
|
+
requiredMethods: [
|
|
35
|
+
'selectNextMessage',
|
|
36
|
+
'hasMessagesToProcess',
|
|
37
|
+
'getQueueStatus'
|
|
38
|
+
],
|
|
39
|
+
requiredProperties: [
|
|
40
|
+
'_queueManager',
|
|
41
|
+
'queue'
|
|
42
|
+
],
|
|
43
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
44
|
+
// Validate that _queueManager is an object with enqueue method
|
|
45
|
+
if (typeof facet._queueManager !== 'object' || facet._queueManager === null) {
|
|
46
|
+
throw new Error('Queue facet _queueManager must be an object');
|
|
47
|
+
}
|
|
48
|
+
if (typeof facet._queueManager.enqueue !== 'function') {
|
|
49
|
+
throw new Error('Queue facet _queueManager must have enqueue method');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate queue property is an object
|
|
53
|
+
if (typeof facet.queue !== 'object' || facet.queue === null) {
|
|
54
|
+
throw new Error('Queue facet queue property must be an object');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that router facets must satisfy.
|
|
5
|
+
* Ensures all required routing methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { routerContract } from './router.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a router facet
|
|
12
|
+
* routerContract.enforce(ctx, api, subsystem, routerFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Router Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - registerRoute: Register a route pattern with a handler
|
|
21
|
+
* - match: Match a path against registered routes
|
|
22
|
+
* - route: Route a message by matching its path and executing the handler
|
|
23
|
+
* - unregisterRoute: Unregister a route pattern
|
|
24
|
+
* - hasRoute: Check if a route pattern is registered
|
|
25
|
+
* - getRoutes: Get all registered routes
|
|
26
|
+
*
|
|
27
|
+
* Required properties:
|
|
28
|
+
* - _routeRegistry: Internal router instance (used by useMessageProcessor)
|
|
29
|
+
*
|
|
30
|
+
* Custom validation:
|
|
31
|
+
* - Validates _routeRegistry is an object (not null or primitive)
|
|
32
|
+
*/
|
|
33
|
+
export const routerContract = createFacetContract({
|
|
34
|
+
name: 'router',
|
|
35
|
+
requiredMethods: [
|
|
36
|
+
'registerRoute',
|
|
37
|
+
'match',
|
|
38
|
+
'route',
|
|
39
|
+
'unregisterRoute',
|
|
40
|
+
'hasRoute',
|
|
41
|
+
'getRoutes'
|
|
42
|
+
],
|
|
43
|
+
requiredProperties: [
|
|
44
|
+
'_routeRegistry'
|
|
45
|
+
],
|
|
46
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
47
|
+
// Validate that _routeRegistry is an object (not null or primitive)
|
|
48
|
+
if (typeof facet._routeRegistry !== 'object' || facet._routeRegistry === null) {
|
|
49
|
+
throw new Error('Router facet _routeRegistry must be an object');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that scheduler facets must satisfy.
|
|
5
|
+
* Ensures all required scheduling methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks and BaseSubsystem.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { schedulerContract } from './scheduler.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a scheduler facet
|
|
12
|
+
* schedulerContract.enforce(ctx, api, subsystem, schedulerFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scheduler Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - process: Process messages during a time slice
|
|
21
|
+
* - pauseProcessing: Pause message processing
|
|
22
|
+
* - resumeProcessing: Resume message processing
|
|
23
|
+
* - isPaused: Check if processing is paused
|
|
24
|
+
* - isProcessing: Check if currently processing
|
|
25
|
+
* - getPriority: Get subsystem priority
|
|
26
|
+
* - setPriority: Set subsystem priority
|
|
27
|
+
* - configureScheduler: Configure scheduler options
|
|
28
|
+
* - getScheduler: Get scheduler instance
|
|
29
|
+
*
|
|
30
|
+
* Required properties:
|
|
31
|
+
* - _scheduler: Internal scheduler instance (used internally by other hooks)
|
|
32
|
+
*
|
|
33
|
+
* Custom validation:
|
|
34
|
+
* - Validates _scheduler is an object (not null or primitive)
|
|
35
|
+
*/
|
|
36
|
+
export const schedulerContract = createFacetContract({
|
|
37
|
+
name: 'scheduler',
|
|
38
|
+
requiredMethods: [
|
|
39
|
+
'process',
|
|
40
|
+
'pauseProcessing',
|
|
41
|
+
'resumeProcessing',
|
|
42
|
+
'isPaused',
|
|
43
|
+
'isProcessing',
|
|
44
|
+
'getPriority',
|
|
45
|
+
'setPriority',
|
|
46
|
+
'configureScheduler',
|
|
47
|
+
'getScheduler'
|
|
48
|
+
],
|
|
49
|
+
requiredProperties: [
|
|
50
|
+
'_scheduler'
|
|
51
|
+
],
|
|
52
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
53
|
+
// Validate _scheduler is an object
|
|
54
|
+
if (typeof facet._scheduler !== 'object' || facet._scheduler === null) {
|
|
55
|
+
throw new Error('Scheduler facet _scheduler must be an object');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|