mycelia-kernel-plugin 1.0.0 → 1.1.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/README.md CHANGED
@@ -13,11 +13,16 @@ Mycelia Plugin System is a standalone plugin architecture extracted from [Myceli
13
13
  - **Hot reloading** - Reload and extend plugins without full teardown
14
14
  - **Facet contracts** - Runtime validation of plugin interfaces
15
15
  - **Standalone mode** - Works without message system or other dependencies
16
+ - **Built-in hooks** - Ships with `useListeners` for event-driven architectures (see [Simple Event System Example](#simple-event-system-example)), plus `useQueue` and `useSpeak`
17
+
18
+ **Facets** are the concrete runtime capabilities produced by hooks and attached to the system.
16
19
 
17
20
  ## Quick Start
18
21
 
22
+ ### Using useBase (Recommended)
23
+
19
24
  ```javascript
20
- import { StandalonePluginSystem, createHook, Facet } from 'mycelia-kernel-plugin';
25
+ import { useBase, createHook, Facet } from 'mycelia-kernel-plugin';
21
26
 
22
27
  // Create a hook
23
28
  const useDatabase = createHook({
@@ -51,6 +56,25 @@ const useDatabase = createHook({
51
56
  }
52
57
  });
53
58
 
59
+ // Create and use the system with fluent API
60
+ const system = await useBase('my-app')
61
+ .config('database', { host: 'localhost' })
62
+ .use(useDatabase)
63
+ .build();
64
+
65
+ // Use the plugin
66
+ const db = system.find('database');
67
+ await db.query('SELECT * FROM users');
68
+ ```
69
+
70
+ ### Using StandalonePluginSystem Directly
71
+
72
+ ```javascript
73
+ import { StandalonePluginSystem, createHook, Facet } from 'mycelia-kernel-plugin';
74
+
75
+ // Create a hook (same as above)
76
+ const useDatabase = createHook({ /* ... */ });
77
+
54
78
  // Create and use the system
55
79
  const system = new StandalonePluginSystem('my-app', {
56
80
  config: {
@@ -67,12 +91,71 @@ const db = system.find('database');
67
91
  await db.query('SELECT * FROM users');
68
92
  ```
69
93
 
94
+ ### Simple Event System Example
95
+
96
+ Create an event-driven system with `useBase` and `useListeners`:
97
+
98
+ ```javascript
99
+ import { useBase, useListeners } from 'mycelia-kernel-plugin';
100
+
101
+ // Create an event system
102
+ const eventSystem = await useBase('event-system')
103
+ .config('listeners', { registrationPolicy: 'multiple' })
104
+ .use(useListeners)
105
+ .build();
106
+
107
+ // Enable listeners
108
+ eventSystem.listeners.enableListeners();
109
+
110
+ // Register event handlers
111
+ eventSystem.listeners.on('user:created', (message) => {
112
+ console.log('User created:', message.body);
113
+ });
114
+
115
+ eventSystem.listeners.on('user:updated', (message) => {
116
+ console.log('User updated:', message.body);
117
+ });
118
+
119
+ // Emit events
120
+ eventSystem.listeners.emit('user:created', {
121
+ type: 'user:created',
122
+ body: { id: 1, name: 'John Doe' }
123
+ });
124
+
125
+ eventSystem.listeners.emit('user:updated', {
126
+ type: 'user:updated',
127
+ body: { id: 1, name: 'Jane Doe' }
128
+ });
129
+
130
+ // Multiple handlers for the same event
131
+ eventSystem.listeners.on('order:placed', (message) => {
132
+ console.log('Order notification:', message.body);
133
+ });
134
+
135
+ eventSystem.listeners.on('order:placed', (message) => {
136
+ console.log('Order logging:', message.body);
137
+ });
138
+
139
+ // Both handlers will be called
140
+ eventSystem.listeners.emit('order:placed', {
141
+ type: 'order:placed',
142
+ body: { orderId: 123, total: 99.99 }
143
+ });
144
+
145
+ // Cleanup
146
+ await eventSystem.dispose();
147
+ ```
148
+
70
149
  ## Installation
71
150
 
72
151
  ```bash
73
152
  npm install mycelia-kernel-plugin
74
153
  ```
75
154
 
155
+ ## What This System Is Not
156
+
157
+ This system intentionally does not provide dependency injection containers, service locators, or global mutable state. It focuses on explicit lifecycle management and composable plugin architecture rather than implicit dependency resolution or shared global state.
158
+
76
159
  ## Features
77
160
 
78
161
  ### Hook System
@@ -141,6 +224,8 @@ The `reload()` method:
141
224
  - Allows adding more hooks and rebuilding
142
225
  - Perfect for development and hot-reload scenarios
143
226
 
227
+ **Note:** Persistent external state (e.g., database contents, file system state) is not automatically reverted. The `reload()` method only manages the plugin system's internal state.
228
+
144
229
  ### Facet Contracts
145
230
  Validate plugin interfaces at build time:
146
231
 
@@ -156,6 +241,11 @@ const databaseContract = createFacetContract({
156
241
  // Contract is automatically enforced during build
157
242
  ```
158
243
 
244
+ If a facet doesn't satisfy its contract, build fails with a clear error:
245
+ ```
246
+ Error: FacetContract 'database': facet is missing required methods: close
247
+ ```
248
+
159
249
  ## Architecture
160
250
 
161
251
  ```
@@ -181,6 +271,7 @@ StandalonePluginSystem
181
271
 
182
272
  - **`createHook()`** - Create a plugin hook
183
273
  - **`createFacetContract()`** - Create a facet contract
274
+ - **`useBase()`** - Fluent API builder for StandalonePluginSystem
184
275
 
185
276
  ### Utilities
186
277
 
@@ -193,6 +284,7 @@ Comprehensive documentation is available in the [`docs/`](./docs/) directory:
193
284
 
194
285
  - **[Getting Started Guide](./docs/getting-started/README.md)** - Quick start with examples
195
286
  - **[Hooks and Facets Overview](./docs/core-concepts/HOOKS-AND-FACETS-OVERVIEW.md)** - Core concepts
287
+ - **[Built-in Hooks](./docs/hooks/README.md)** - Documentation for `useListeners`, `useQueue`, and `useSpeak`
196
288
  - **[Standalone Plugin System](./docs/standalone/STANDALONE-PLUGIN-SYSTEM.md)** - Complete usage guide
197
289
  - **[Documentation Index](./docs/README.md)** - Full documentation index
198
290
 
@@ -204,6 +296,7 @@ See the `examples/` directory for:
204
296
  - Lifecycle management
205
297
  - Contract validation
206
298
  - Hot reloading
299
+ - useBase fluent API
207
300
 
208
301
  ## CLI Tool
209
302
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mycelia-kernel-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A sophisticated, dependency-aware plugin system with transaction safety and lifecycle management",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -38,6 +38,7 @@ export { useSpeak } from './hooks/speak/use-speak.js';
38
38
  export { createLogger, createSubsystemLogger } from './utils/logger.js';
39
39
  export { getDebugFlag } from './utils/debug-flag.js';
40
40
  export { findFacet } from './utils/find-facet.js';
41
+ export { useBase } from './utils/use-base.js';
41
42
  export {
42
43
  parseVersion,
43
44
  isValidSemver,
@@ -0,0 +1,262 @@
1
+ import { StandalonePluginSystem } from '../system/standalone-plugin-system.js';
2
+ import { deepMerge } from '../builder/context-resolver.js';
3
+
4
+ /**
5
+ * useBase - Fluent API Builder for StandalonePluginSystem
6
+ *
7
+ * Provides a convenient, chainable API for creating and configuring
8
+ * StandalonePluginSystem instances.
9
+ *
10
+ * @param {string} name - Unique name for the plugin system
11
+ * @param {Object} [options={}] - Initial configuration options
12
+ * @param {Object} [options.config={}] - Initial configuration object keyed by facet kind
13
+ * @param {boolean} [options.debug=false] - Enable debug logging
14
+ * @param {Array} [options.defaultHooks=[]] - Optional default hooks to install
15
+ * @returns {UseBaseBuilder} Builder instance with fluent API
16
+ *
17
+ * @example
18
+ * ```javascript
19
+ * import { useBase } from 'mycelia-kernel-plugin';
20
+ * import { useDatabase, useCache } from './hooks';
21
+ *
22
+ * const system = await useBase('my-app')
23
+ * .config('database', { host: 'localhost' })
24
+ * .config('cache', { ttl: 3600 })
25
+ * .use(useDatabase)
26
+ * .use(useCache)
27
+ * .onInit(async (api, ctx) => {
28
+ * console.log('System initialized');
29
+ * })
30
+ * .build();
31
+ * ```
32
+ */
33
+ export function useBase(name, options = {}) {
34
+ if (!name || typeof name !== 'string') {
35
+ throw new Error('useBase: name must be a non-empty string');
36
+ }
37
+
38
+ // Create the system instance
39
+ const system = new StandalonePluginSystem(name, options);
40
+
41
+ // Create builder with fluent API
42
+ return new UseBaseBuilder(system);
43
+ }
44
+
45
+ /**
46
+ * UseBaseBuilder - Fluent API builder for StandalonePluginSystem
47
+ *
48
+ * Provides chainable methods for configuring and building the system.
49
+ */
50
+ class UseBaseBuilder {
51
+ #system;
52
+ #pendingConfig = {};
53
+
54
+ constructor(system) {
55
+ this.#system = system;
56
+ }
57
+
58
+ /**
59
+ * Register a hook
60
+ *
61
+ * @param {Function} hook - Hook function to register
62
+ * @returns {UseBaseBuilder} This builder for chaining
63
+ *
64
+ * @example
65
+ * ```javascript
66
+ * builder.use(useDatabase).use(useCache);
67
+ * ```
68
+ */
69
+ use(hook) {
70
+ if (typeof hook !== 'function') {
71
+ throw new Error('useBase.use: hook must be a function');
72
+ }
73
+ this.#system.use(hook);
74
+ return this;
75
+ }
76
+
77
+ /**
78
+ * Conditionally register a hook
79
+ *
80
+ * @param {boolean} condition - Whether to register the hook
81
+ * @param {Function} hook - Hook function to register if condition is true
82
+ * @returns {UseBaseBuilder} This builder for chaining
83
+ *
84
+ * @example
85
+ * ```javascript
86
+ * builder.useIf(process.env.ENABLE_CACHE === 'true', useCache);
87
+ * ```
88
+ */
89
+ useIf(condition, hook) {
90
+ if (condition) {
91
+ return this.use(hook);
92
+ }
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * Add or update configuration for a specific facet kind
98
+ *
99
+ * Configurations are merged when possible (deep merge for objects).
100
+ *
101
+ * @param {string} kind - Facet kind identifier (e.g., 'database', 'cache')
102
+ * @param {*} config - Configuration value for this facet kind
103
+ * @returns {UseBaseBuilder} This builder for chaining
104
+ *
105
+ * @example
106
+ * ```javascript
107
+ * builder
108
+ * .config('database', { host: 'localhost', port: 5432 })
109
+ * .config('cache', { ttl: 3600 });
110
+ * ```
111
+ *
112
+ * @example
113
+ * ```javascript
114
+ * // Merge configurations
115
+ * builder
116
+ * .config('database', { host: 'localhost' })
117
+ * .config('database', { port: 5432 }); // Merges with existing
118
+ * ```
119
+ */
120
+ config(kind, config) {
121
+ if (!kind || typeof kind !== 'string') {
122
+ throw new Error('useBase.config: kind must be a non-empty string');
123
+ }
124
+
125
+ // Initialize config object if needed
126
+ if (!this.#system.ctx.config || typeof this.#system.ctx.config !== 'object') {
127
+ this.#system.ctx.config = {};
128
+ }
129
+
130
+ // Get existing config for this kind
131
+ const existingConfig = this.#system.ctx.config[kind];
132
+ const pendingConfig = this.#pendingConfig[kind];
133
+
134
+ // Determine the base config (existing or pending)
135
+ const baseConfig = pendingConfig !== undefined ? pendingConfig : existingConfig;
136
+
137
+ // Merge if both are objects
138
+ if (
139
+ baseConfig &&
140
+ typeof baseConfig === 'object' &&
141
+ !Array.isArray(baseConfig) &&
142
+ config &&
143
+ typeof config === 'object' &&
144
+ !Array.isArray(config)
145
+ ) {
146
+ // Deep merge
147
+ this.#pendingConfig[kind] = deepMerge(baseConfig, config);
148
+ } else {
149
+ // Override
150
+ this.#pendingConfig[kind] = config;
151
+ }
152
+
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * Add an initialization callback
158
+ *
159
+ * @param {Function} callback - Callback function: (api, ctx) => Promise<void> | void
160
+ * @returns {UseBaseBuilder} This builder for chaining
161
+ *
162
+ * @example
163
+ * ```javascript
164
+ * builder.onInit(async (api, ctx) => {
165
+ * console.log('System initialized:', api.name);
166
+ * });
167
+ * ```
168
+ */
169
+ onInit(callback) {
170
+ if (typeof callback !== 'function') {
171
+ throw new Error('useBase.onInit: callback must be a function');
172
+ }
173
+ this.#system.onInit(callback);
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Add a disposal callback
179
+ *
180
+ * @param {Function} callback - Callback function: () => Promise<void> | void
181
+ * @returns {UseBaseBuilder} This builder for chaining
182
+ *
183
+ * @example
184
+ * ```javascript
185
+ * builder.onDispose(async () => {
186
+ * console.log('System disposed');
187
+ * });
188
+ * ```
189
+ */
190
+ onDispose(callback) {
191
+ if (typeof callback !== 'function') {
192
+ throw new Error('useBase.onDispose: callback must be a function');
193
+ }
194
+ this.#system.onDispose(callback);
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * Build the system
200
+ *
201
+ * This method is required and must be called to build the system.
202
+ * It applies any pending configurations and builds the system.
203
+ *
204
+ * @param {Object} [ctx={}] - Additional context to pass to build
205
+ * @returns {Promise<StandalonePluginSystem>} The built system instance
206
+ *
207
+ * @example
208
+ * ```javascript
209
+ * const system = await useBase('my-app')
210
+ * .use(useDatabase)
211
+ * .build();
212
+ * ```
213
+ */
214
+ async build(ctx = {}) {
215
+ // Apply pending configurations
216
+ if (Object.keys(this.#pendingConfig).length > 0) {
217
+ // Merge pending config into system config
218
+ if (!this.#system.ctx.config || typeof this.#system.ctx.config !== 'object') {
219
+ this.#system.ctx.config = {};
220
+ }
221
+
222
+ // Deep merge pending configs
223
+ for (const [kind, config] of Object.entries(this.#pendingConfig)) {
224
+ const existing = this.#system.ctx.config[kind];
225
+ if (
226
+ existing &&
227
+ typeof existing === 'object' &&
228
+ !Array.isArray(existing) &&
229
+ config &&
230
+ typeof config === 'object' &&
231
+ !Array.isArray(config)
232
+ ) {
233
+ this.#system.ctx.config[kind] = deepMerge(existing, config);
234
+ } else {
235
+ this.#system.ctx.config[kind] = config;
236
+ }
237
+ }
238
+
239
+ // Clear pending configs
240
+ this.#pendingConfig = {};
241
+ }
242
+
243
+ // Merge any additional context
244
+ if (ctx && typeof ctx === 'object' && !Array.isArray(ctx)) {
245
+ if (ctx.config && typeof ctx.config === 'object' && !Array.isArray(ctx.config)) {
246
+ if (!this.#system.ctx.config) {
247
+ this.#system.ctx.config = {};
248
+ }
249
+ this.#system.ctx.config = deepMerge(this.#system.ctx.config, ctx.config);
250
+ }
251
+ // Merge other ctx properties (shallow)
252
+ Object.assign(this.#system.ctx, ctx);
253
+ }
254
+
255
+ // Build the system
256
+ await this.#system.build(ctx);
257
+
258
+ // Return the system instance
259
+ return this.#system;
260
+ }
261
+ }
262
+