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,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that server facets must satisfy.
|
|
5
|
+
* Ensures all required HTTP server methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with different server implementations (Fastify, Express).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { serverContract } from './server.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a server facet
|
|
12
|
+
* serverContract.enforce(ctx, api, subsystem, serverFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Server Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - Lifecycle: start, stop, isRunning
|
|
21
|
+
* - Route Registration (Single): get, post, put, patch, delete, all
|
|
22
|
+
* - Route Registration (Batch): registerRoutes, registerMyceliaRoutes
|
|
23
|
+
* - Middleware: use, useRoute
|
|
24
|
+
* - Error Handling: setErrorHandler
|
|
25
|
+
* - Server Info: getAddress, getPort
|
|
26
|
+
* - Mycelia Integration: registerMyceliaRoute, registerMyceliaCommand, registerMyceliaQuery
|
|
27
|
+
*
|
|
28
|
+
* Required properties:
|
|
29
|
+
* - _server: Internal server instance (Fastify/Express)
|
|
30
|
+
* - _isRunning: Running state flag
|
|
31
|
+
*
|
|
32
|
+
* Custom validation:
|
|
33
|
+
* - Validates _server is an object (not null or primitive)
|
|
34
|
+
* - Validates _isRunning is a boolean
|
|
35
|
+
*/
|
|
36
|
+
export const serverContract = createFacetContract({
|
|
37
|
+
name: 'server',
|
|
38
|
+
requiredMethods: [
|
|
39
|
+
// Lifecycle
|
|
40
|
+
'start',
|
|
41
|
+
'stop',
|
|
42
|
+
'isRunning',
|
|
43
|
+
|
|
44
|
+
// Route Registration (Single)
|
|
45
|
+
'get',
|
|
46
|
+
'post',
|
|
47
|
+
'put',
|
|
48
|
+
'patch',
|
|
49
|
+
'delete',
|
|
50
|
+
'all', // All HTTP methods
|
|
51
|
+
|
|
52
|
+
// Route Registration (Batch)
|
|
53
|
+
'registerRoutes', // Register multiple routes at once
|
|
54
|
+
'registerMyceliaRoutes', // Register multiple Mycelia routes at once
|
|
55
|
+
|
|
56
|
+
// Middleware
|
|
57
|
+
'use', // Global middleware
|
|
58
|
+
'useRoute', // Route-specific middleware
|
|
59
|
+
|
|
60
|
+
// Error Handling
|
|
61
|
+
'setErrorHandler',
|
|
62
|
+
|
|
63
|
+
// Server Info
|
|
64
|
+
'getAddress',
|
|
65
|
+
'getPort',
|
|
66
|
+
|
|
67
|
+
// Integration
|
|
68
|
+
'registerMyceliaRoute', // Register Mycelia route as HTTP endpoint
|
|
69
|
+
'registerMyceliaCommand', // Register Mycelia command as HTTP endpoint
|
|
70
|
+
'registerMyceliaQuery' // Register Mycelia query as HTTP endpoint
|
|
71
|
+
],
|
|
72
|
+
requiredProperties: [
|
|
73
|
+
'_server', // Internal server instance (Fastify/Express)
|
|
74
|
+
'_isRunning' // Running state flag
|
|
75
|
+
],
|
|
76
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
77
|
+
// Validate _server is an object
|
|
78
|
+
if (typeof facet._server !== 'object' || facet._server === null) {
|
|
79
|
+
throw new Error('Server facet _server must be an object');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate _isRunning is a boolean
|
|
83
|
+
if (typeof facet._isRunning !== 'boolean') {
|
|
84
|
+
throw new Error('Server facet _isRunning must be a boolean');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speak Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for facets that provide speaking/printing functionality.
|
|
5
|
+
* This is a simple example contract for demonstration purposes.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Create a speak contract
|
|
9
|
+
* const speakContract = createFacetContract({
|
|
10
|
+
* name: 'speak',
|
|
11
|
+
* requiredMethods: ['say', 'sayLine'],
|
|
12
|
+
* requiredProperties: []
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
16
|
+
|
|
17
|
+
export const speakContract = createFacetContract({
|
|
18
|
+
name: 'speak',
|
|
19
|
+
requiredMethods: ['say', 'sayLine'],
|
|
20
|
+
requiredProperties: [],
|
|
21
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
22
|
+
const errors = [];
|
|
23
|
+
|
|
24
|
+
// Validate say method
|
|
25
|
+
if (typeof facet.say !== 'function') {
|
|
26
|
+
errors.push('speak facet must have a say() method');
|
|
27
|
+
} else {
|
|
28
|
+
// Check method signature (should accept at least one argument)
|
|
29
|
+
if (facet.say.length < 1) {
|
|
30
|
+
errors.push('speak.say() method must accept at least one argument (message)');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate sayLine method
|
|
35
|
+
if (typeof facet.sayLine !== 'function') {
|
|
36
|
+
errors.push('speak facet must have a sayLine() method');
|
|
37
|
+
} else {
|
|
38
|
+
// Check method signature (should accept at least one argument)
|
|
39
|
+
if (facet.sayLine.length < 1) {
|
|
40
|
+
errors.push('speak.sayLine() method must accept at least one argument (message)');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
valid: errors.length === 0,
|
|
46
|
+
errors
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that storage facets must satisfy.
|
|
5
|
+
* Ensures all required storage methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with other hooks.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { storageContract } from './storage.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a storage facet
|
|
12
|
+
* storageContract.enforce(ctx, api, subsystem, storageFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Storage Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - get: Retrieve a value by key
|
|
21
|
+
* - set: Store a value by key
|
|
22
|
+
* - delete: Delete a value by key
|
|
23
|
+
* - has: Check if a key exists
|
|
24
|
+
* - getMany: Retrieve multiple values by keys
|
|
25
|
+
* - setMany: Store multiple key-value pairs
|
|
26
|
+
* - deleteMany: Delete multiple keys
|
|
27
|
+
* - list: List all keys (or keys matching pattern)
|
|
28
|
+
* - query: Query values by filter criteria
|
|
29
|
+
* - count: Count keys/entries
|
|
30
|
+
* - createNamespace: Create a new namespace/collection
|
|
31
|
+
* - deleteNamespace: Delete a namespace/collection
|
|
32
|
+
* - listNamespaces: List all namespaces
|
|
33
|
+
* - beginTransaction: Begin a transaction (optional)
|
|
34
|
+
* - commit: Commit a transaction (optional)
|
|
35
|
+
* - rollback: Rollback a transaction (optional)
|
|
36
|
+
* - getMetadata: Get metadata for a key
|
|
37
|
+
* - setMetadata: Set metadata for a key
|
|
38
|
+
* - clear: Clear all data (or data in namespace)
|
|
39
|
+
* - getStatus: Get storage status/health
|
|
40
|
+
*
|
|
41
|
+
* Required properties:
|
|
42
|
+
* - _storageBackend: Internal storage backend instance
|
|
43
|
+
* - _config: Storage configuration
|
|
44
|
+
*
|
|
45
|
+
* Optional properties:
|
|
46
|
+
* - supportsTransactions: Whether backend supports transactions
|
|
47
|
+
* - supportsQuery: Whether backend supports query operations
|
|
48
|
+
* - supportsMetadata: Whether backend supports metadata
|
|
49
|
+
*
|
|
50
|
+
* Custom validation:
|
|
51
|
+
* - Validates _storageBackend is an object (not null or primitive)
|
|
52
|
+
* - Validates _config is an object
|
|
53
|
+
*/
|
|
54
|
+
export const storageContract = createFacetContract({
|
|
55
|
+
name: 'storage',
|
|
56
|
+
requiredMethods: [
|
|
57
|
+
'get',
|
|
58
|
+
'set',
|
|
59
|
+
'delete',
|
|
60
|
+
'has',
|
|
61
|
+
'getMany',
|
|
62
|
+
'setMany',
|
|
63
|
+
'deleteMany',
|
|
64
|
+
'list',
|
|
65
|
+
'query',
|
|
66
|
+
'count',
|
|
67
|
+
'createNamespace',
|
|
68
|
+
'deleteNamespace',
|
|
69
|
+
'listNamespaces',
|
|
70
|
+
'getMetadata',
|
|
71
|
+
'setMetadata',
|
|
72
|
+
'clear',
|
|
73
|
+
'getStatus'
|
|
74
|
+
],
|
|
75
|
+
requiredProperties: [
|
|
76
|
+
'_storageBackend',
|
|
77
|
+
'_config'
|
|
78
|
+
],
|
|
79
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
80
|
+
// Validate that _storageBackend is an object (not null or primitive)
|
|
81
|
+
if (typeof facet._storageBackend !== 'object' || facet._storageBackend === null) {
|
|
82
|
+
throw new Error('Storage facet _storageBackend must be an object');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate _config is an object
|
|
86
|
+
if (typeof facet._config !== 'object' || facet._config === null) {
|
|
87
|
+
throw new Error('Storage facet _config must be an object');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate optional properties have correct types if present
|
|
91
|
+
if ('supportsTransactions' in facet && typeof facet.supportsTransactions !== 'boolean') {
|
|
92
|
+
throw new Error('Storage facet supportsTransactions must be a boolean');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if ('supportsQuery' in facet && typeof facet.supportsQuery !== 'boolean') {
|
|
96
|
+
throw new Error('Storage facet supportsQuery must be a boolean');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ('supportsMetadata' in facet && typeof facet.supportsMetadata !== 'boolean') {
|
|
100
|
+
throw new Error('Storage facet supportsMetadata must be a boolean');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Facet Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that WebSocket facets must satisfy.
|
|
5
|
+
* Ensures all required WebSocket server methods are implemented and validates
|
|
6
|
+
* internal structure for compatibility with different WebSocket implementations (ws, uWebSockets.js).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { websocketContract } from './websocket.contract.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce contract on a WebSocket facet
|
|
12
|
+
* websocketContract.enforce(ctx, api, subsystem, websocketFacet);
|
|
13
|
+
*/
|
|
14
|
+
import { createFacetContract } from '../facet-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* WebSocket Facet Contract
|
|
18
|
+
*
|
|
19
|
+
* Required methods:
|
|
20
|
+
* - Lifecycle: start, stop, isRunning
|
|
21
|
+
* - Server Info: getAddress, getPort
|
|
22
|
+
* - Connection Management: getConnection, getAllConnections, getConnectionCount, closeConnection
|
|
23
|
+
* - Message Sending: send, broadcast
|
|
24
|
+
* - Connection Lifecycle Handlers: onConnection, onDisconnection, onError
|
|
25
|
+
* - Message Routing: registerMessageHandler, routeMessage
|
|
26
|
+
*
|
|
27
|
+
* Required properties:
|
|
28
|
+
* - _server: Internal WebSocket server instance
|
|
29
|
+
* - _isRunning: Running state flag
|
|
30
|
+
* - _connections: Connection manager/registry
|
|
31
|
+
*
|
|
32
|
+
* Custom validation:
|
|
33
|
+
* - Validates _server is an object (not null or primitive)
|
|
34
|
+
* - Validates _isRunning is a boolean
|
|
35
|
+
* - Validates _connections exists
|
|
36
|
+
*/
|
|
37
|
+
export const websocketContract = createFacetContract({
|
|
38
|
+
name: 'websocket',
|
|
39
|
+
requiredMethods: [
|
|
40
|
+
// Lifecycle
|
|
41
|
+
'start',
|
|
42
|
+
'stop',
|
|
43
|
+
'isRunning',
|
|
44
|
+
|
|
45
|
+
// Server Info
|
|
46
|
+
'getAddress',
|
|
47
|
+
'getPort',
|
|
48
|
+
|
|
49
|
+
// Connection Management
|
|
50
|
+
'getConnection',
|
|
51
|
+
'getAllConnections',
|
|
52
|
+
'getConnectionCount',
|
|
53
|
+
'closeConnection',
|
|
54
|
+
|
|
55
|
+
// Message Sending
|
|
56
|
+
'send',
|
|
57
|
+
'broadcast',
|
|
58
|
+
|
|
59
|
+
// Connection Lifecycle Handlers
|
|
60
|
+
'onConnection',
|
|
61
|
+
'onDisconnection',
|
|
62
|
+
'onError',
|
|
63
|
+
|
|
64
|
+
// Message Routing
|
|
65
|
+
'registerMessageHandler',
|
|
66
|
+
'routeMessage'
|
|
67
|
+
],
|
|
68
|
+
requiredProperties: [
|
|
69
|
+
'_server', // Internal WebSocket server instance
|
|
70
|
+
'_isRunning', // Running state flag
|
|
71
|
+
'_connections' // Connection manager/registry
|
|
72
|
+
],
|
|
73
|
+
validate: (ctx, api, subsystem, facet) => {
|
|
74
|
+
// Validate _server is an object
|
|
75
|
+
if (typeof facet._server !== 'object' || facet._server === null) {
|
|
76
|
+
throw new Error('WebSocket facet _server must be an object');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate _isRunning is a boolean
|
|
80
|
+
if (typeof facet._isRunning !== 'boolean') {
|
|
81
|
+
throw new Error('WebSocket facet _isRunning must be a boolean');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate _connections exists (could be Map, object, or manager instance)
|
|
85
|
+
if (!facet._connections) {
|
|
86
|
+
throw new Error('WebSocket facet _connections must be defined');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FacetContractRegistry Class
|
|
3
|
+
*
|
|
4
|
+
* Manages a collection of facet contracts, allowing registration and enforcement
|
|
5
|
+
* of contracts on facets by name.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { FacetContractRegistry } from './facet-contract-registry.js';
|
|
9
|
+
* import { createFacetContract } from './facet-contract.js';
|
|
10
|
+
*
|
|
11
|
+
* const registry = new FacetContractRegistry();
|
|
12
|
+
*
|
|
13
|
+
* const routerContract = createFacetContract({
|
|
14
|
+
* name: 'router',
|
|
15
|
+
* requiredMethods: ['registerRoute', 'match']
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* registry.register(routerContract);
|
|
19
|
+
*
|
|
20
|
+
* // Later, enforce the contract
|
|
21
|
+
* registry.enforce('router', ctx, api, subsystem, routerFacet);
|
|
22
|
+
*/
|
|
23
|
+
import { FacetContract } from './facet-contract.js';
|
|
24
|
+
|
|
25
|
+
export class FacetContractRegistry {
|
|
26
|
+
/**
|
|
27
|
+
* Map of contract name → FacetContract instance
|
|
28
|
+
* @private
|
|
29
|
+
* @type {Map<string, FacetContract>}
|
|
30
|
+
*/
|
|
31
|
+
#contracts = new Map();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new FacetContractRegistry
|
|
35
|
+
*/
|
|
36
|
+
constructor() {
|
|
37
|
+
// No initialization needed
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Registers a facet contract in the registry
|
|
42
|
+
*
|
|
43
|
+
* @param {FacetContract} contract - FacetContract instance to register
|
|
44
|
+
* @returns {FacetContract} The registered contract
|
|
45
|
+
* @throws {Error} If contract is invalid or a contract with the same name already exists
|
|
46
|
+
*/
|
|
47
|
+
register(contract) {
|
|
48
|
+
if (!contract || typeof contract !== 'object') {
|
|
49
|
+
throw new Error('FacetContractRegistry.register: contract must be an object');
|
|
50
|
+
}
|
|
51
|
+
if (!(contract instanceof FacetContract)) {
|
|
52
|
+
throw new Error('FacetContractRegistry.register: contract must be a FacetContract instance');
|
|
53
|
+
}
|
|
54
|
+
if (!contract.name || typeof contract.name !== 'string') {
|
|
55
|
+
throw new Error('FacetContractRegistry.register: contract must have a string name property');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (this.#contracts.has(contract.name)) {
|
|
59
|
+
throw new Error(`FacetContractRegistry.register: contract with name '${contract.name}' already exists`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#contracts.set(contract.name, contract);
|
|
63
|
+
return contract;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if a contract exists for the given name
|
|
68
|
+
*
|
|
69
|
+
* @param {string} name - Contract name to check
|
|
70
|
+
* @returns {boolean} True if contract exists
|
|
71
|
+
*/
|
|
72
|
+
has(name) {
|
|
73
|
+
if (typeof name !== 'string') {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return this.#contracts.has(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets a contract by name
|
|
81
|
+
*
|
|
82
|
+
* @param {string} name - Contract name
|
|
83
|
+
* @returns {FacetContract|undefined} Contract instance or undefined if not found
|
|
84
|
+
*/
|
|
85
|
+
get(name) {
|
|
86
|
+
if (typeof name !== 'string') {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return this.#contracts.get(name);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Enforces a contract on a facet
|
|
94
|
+
*
|
|
95
|
+
* Looks up the contract by name and delegates to its enforce method.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} name - Name of the contract to enforce
|
|
98
|
+
* @param {Object} ctx - Context object
|
|
99
|
+
* @param {Object} api - Subsystem API object
|
|
100
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
101
|
+
* @param {Facet} facet - Facet to validate
|
|
102
|
+
* @throws {Error} If contract not found or validation fails
|
|
103
|
+
*/
|
|
104
|
+
enforce(name, ctx, api, subsystem, facet) {
|
|
105
|
+
if (typeof name !== 'string' || !name) {
|
|
106
|
+
throw new Error('FacetContractRegistry.enforce: name must be a non-empty string');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const contract = this.#contracts.get(name);
|
|
110
|
+
if (!contract) {
|
|
111
|
+
throw new Error(`FacetContractRegistry.enforce: no contract found for name '${name}'`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
contract.enforce(ctx, api, subsystem, facet);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Removes a contract from the registry
|
|
119
|
+
*
|
|
120
|
+
* @param {string} name - Contract name to remove
|
|
121
|
+
* @returns {boolean} True if contract was removed, false if not found
|
|
122
|
+
*/
|
|
123
|
+
remove(name) {
|
|
124
|
+
if (typeof name !== 'string') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return this.#contracts.delete(name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Lists all registered contract names
|
|
132
|
+
*
|
|
133
|
+
* @returns {Array<string>} Array of contract names
|
|
134
|
+
*/
|
|
135
|
+
list() {
|
|
136
|
+
return Array.from(this.#contracts.keys());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Gets the number of registered contracts
|
|
141
|
+
*
|
|
142
|
+
* @returns {number} Number of contracts
|
|
143
|
+
*/
|
|
144
|
+
size() {
|
|
145
|
+
return this.#contracts.size;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Clears all contracts from the registry
|
|
150
|
+
*/
|
|
151
|
+
clear() {
|
|
152
|
+
this.#contracts.clear();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FacetContract Class
|
|
3
|
+
*
|
|
4
|
+
* Defines a contract that facets must satisfy, including required methods,
|
|
5
|
+
* required properties, and optional custom validation logic.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const routerContract = new FacetContract('router', {
|
|
9
|
+
* requiredMethods: ['registerRoute', 'match'],
|
|
10
|
+
* requiredProperties: ['_routeRegistry']
|
|
11
|
+
* }, (ctx, api, subsystem, facet) => {
|
|
12
|
+
* // Additional custom validation
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* routerContract.enforce(ctx, api, subsystem, routerFacet);
|
|
16
|
+
*/
|
|
17
|
+
export class FacetContract {
|
|
18
|
+
#validate = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a new FacetContract
|
|
22
|
+
*
|
|
23
|
+
* @param {string} name - Name of the contract (usually same as facet kind)
|
|
24
|
+
* @param {Object} requirements - Requirements object
|
|
25
|
+
* @param {Array<string>} [requirements.requiredMethods=[]] - Array of required method names that must be implemented
|
|
26
|
+
* @param {Array<string>} [requirements.requiredProperties=[]] - Array of required property names that must exist
|
|
27
|
+
* @param {Function} [validate] - Optional validation function with signature (ctx, api, subsystem, facet)
|
|
28
|
+
*/
|
|
29
|
+
constructor(name, requirements = {}, validate = null) {
|
|
30
|
+
if (!name || typeof name !== 'string') {
|
|
31
|
+
throw new Error('FacetContract: name must be a non-empty string');
|
|
32
|
+
}
|
|
33
|
+
if (!requirements || typeof requirements !== 'object' || Array.isArray(requirements)) {
|
|
34
|
+
throw new Error('FacetContract: requirements must be an object');
|
|
35
|
+
}
|
|
36
|
+
if (validate !== null && typeof validate !== 'function') {
|
|
37
|
+
throw new Error('FacetContract: validate must be a function or null');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.name = name;
|
|
41
|
+
this.requiredMethods = Array.isArray(requirements.requiredMethods)
|
|
42
|
+
? [...requirements.requiredMethods]
|
|
43
|
+
: [];
|
|
44
|
+
this.requiredProperties = Array.isArray(requirements.requiredProperties)
|
|
45
|
+
? [...requirements.requiredProperties]
|
|
46
|
+
: [];
|
|
47
|
+
this.#validate = validate;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enforce the contract on a facet
|
|
52
|
+
*
|
|
53
|
+
* Checks that all required methods and properties exist on the facet,
|
|
54
|
+
* then runs the custom validation function if provided.
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} ctx - Context object
|
|
57
|
+
* @param {Object} api - Subsystem API object
|
|
58
|
+
* @param {BaseSubsystem} subsystem - Subsystem instance
|
|
59
|
+
* @param {Facet} facet - Facet to validate
|
|
60
|
+
* @throws {Error} If required methods or properties are missing or validation fails
|
|
61
|
+
*/
|
|
62
|
+
enforce(ctx, api, subsystem, facet) {
|
|
63
|
+
if (!facet || typeof facet !== 'object') {
|
|
64
|
+
throw new Error(`FacetContract '${this.name}': facet must be an object`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check that all required methods are implemented
|
|
68
|
+
const missingMethods = [];
|
|
69
|
+
for (const methodName of this.requiredMethods) {
|
|
70
|
+
if (typeof facet[methodName] !== 'function') {
|
|
71
|
+
missingMethods.push(methodName);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (missingMethods.length > 0) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`FacetContract '${this.name}': facet is missing required methods: ${missingMethods.join(', ')}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check that all required properties exist
|
|
82
|
+
const missingProperties = [];
|
|
83
|
+
for (const propertyName of this.requiredProperties) {
|
|
84
|
+
if (!(propertyName in facet) || facet[propertyName] === undefined) {
|
|
85
|
+
missingProperties.push(propertyName);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (missingProperties.length > 0) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`FacetContract '${this.name}': facet is missing required properties: ${missingProperties.join(', ')}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Run custom validation if provided
|
|
96
|
+
if (this.#validate !== null) {
|
|
97
|
+
try {
|
|
98
|
+
this.#validate(ctx, api, subsystem, facet);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`FacetContract '${this.name}': validation failed: ${error.message}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* FacetContract Factory Function
|
|
110
|
+
*
|
|
111
|
+
* Creates a FacetContract instance with validation and error handling.
|
|
112
|
+
*
|
|
113
|
+
* @param {Object} options - Contract configuration
|
|
114
|
+
* @param {string} options.name - Name of the contract (usually same as facet kind)
|
|
115
|
+
* @param {Array<string>} [options.requiredMethods=[]] - Array of required method names that must be implemented
|
|
116
|
+
* @param {Array<string>} [options.requiredProperties=[]] - Array of required property names that must exist
|
|
117
|
+
* @param {Function} [options.validate] - Optional validation function with signature (ctx, api, subsystem, facet)
|
|
118
|
+
* @returns {FacetContract} New FacetContract instance
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* const routerContract = createFacetContract({
|
|
122
|
+
* name: 'router',
|
|
123
|
+
* requiredMethods: ['registerRoute', 'match', 'route'],
|
|
124
|
+
* requiredProperties: ['_routeRegistry'],
|
|
125
|
+
* validate: (ctx, api, subsystem, facet) => {
|
|
126
|
+
* // Additional custom validation
|
|
127
|
+
* if (typeof facet._routeRegistry !== 'object') {
|
|
128
|
+
* throw new Error('Router facet _routeRegistry must be an object');
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
* });
|
|
132
|
+
*/
|
|
133
|
+
export function createFacetContract({ name, requiredMethods = [], requiredProperties = [], validate = null }) {
|
|
134
|
+
return new FacetContract(name, { requiredMethods, requiredProperties }, validate);
|
|
135
|
+
}
|
|
136
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facet Contract Registry Index
|
|
3
|
+
*
|
|
4
|
+
* Creates a default FacetContractRegistry instance and registers all available
|
|
5
|
+
* facet contracts. This provides a centralized registry for enforcing contracts
|
|
6
|
+
* on facets throughout the system.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { defaultContractRegistry } from './index.js';
|
|
10
|
+
*
|
|
11
|
+
* // Enforce a contract on a facet
|
|
12
|
+
* defaultContractRegistry.enforce('router', ctx, api, subsystem, routerFacet);
|
|
13
|
+
*
|
|
14
|
+
* // Check if a contract exists
|
|
15
|
+
* if (defaultContractRegistry.has('queue')) {
|
|
16
|
+
* // Contract is available
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
import { FacetContractRegistry } from './facet-contract-registry.js';
|
|
20
|
+
import { routerContract } from './contracts/router.contract.js';
|
|
21
|
+
import { queueContract } from './contracts/queue.contract.js';
|
|
22
|
+
import { processorContract } from './contracts/processor.contract.js';
|
|
23
|
+
import { listenersContract } from './contracts/listeners.contract.js';
|
|
24
|
+
import { hierarchyContract } from './contracts/hierarchy.contract.js';
|
|
25
|
+
import { schedulerContract } from './contracts/scheduler.contract.js';
|
|
26
|
+
import { serverContract } from './contracts/server.contract.js';
|
|
27
|
+
import { speakContract } from './contracts/speak.contract.js';
|
|
28
|
+
import { websocketContract } from './contracts/websocket.contract.js';
|
|
29
|
+
import { storageContract } from './contracts/storage.contract.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Default Facet Contract Registry
|
|
33
|
+
*
|
|
34
|
+
* Pre-configured registry with all standard facet contracts registered.
|
|
35
|
+
*/
|
|
36
|
+
export const defaultContractRegistry = new FacetContractRegistry();
|
|
37
|
+
|
|
38
|
+
// Register all available contracts
|
|
39
|
+
defaultContractRegistry.register(routerContract);
|
|
40
|
+
defaultContractRegistry.register(queueContract);
|
|
41
|
+
defaultContractRegistry.register(processorContract);
|
|
42
|
+
defaultContractRegistry.register(listenersContract);
|
|
43
|
+
defaultContractRegistry.register(hierarchyContract);
|
|
44
|
+
defaultContractRegistry.register(schedulerContract);
|
|
45
|
+
defaultContractRegistry.register(serverContract);
|
|
46
|
+
defaultContractRegistry.register(speakContract);
|
|
47
|
+
defaultContractRegistry.register(websocketContract);
|
|
48
|
+
defaultContractRegistry.register(storageContract);
|
|
49
|
+
|
|
50
|
+
// Also export the registry class and individual contracts for flexibility
|
|
51
|
+
export { FacetContractRegistry } from './facet-contract-registry.js';
|
|
52
|
+
export { FacetContract, createFacetContract } from './facet-contract.js';
|
|
53
|
+
export { routerContract } from './contracts/router.contract.js';
|
|
54
|
+
export { queueContract } from './contracts/queue.contract.js';
|
|
55
|
+
export { processorContract } from './contracts/processor.contract.js';
|
|
56
|
+
export { listenersContract } from './contracts/listeners.contract.js';
|
|
57
|
+
export { hierarchyContract } from './contracts/hierarchy.contract.js';
|
|
58
|
+
export { schedulerContract } from './contracts/scheduler.contract.js';
|
|
59
|
+
export { serverContract } from './contracts/server.contract.js';
|
|
60
|
+
export { speakContract } from './contracts/speak.contract.js';
|
|
61
|
+
export { websocketContract } from './contracts/websocket.contract.js';
|
|
62
|
+
export { storageContract } from './contracts/storage.contract.js';
|
|
63
|
+
|