mycelia-kernel-plugin 1.4.0 → 1.4.1

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
@@ -320,6 +320,7 @@ Comprehensive documentation is available in the [`docs/`](./docs/) directory:
320
320
  - **[Svelte Bindings](./docs/svelte/README.md)** - Svelte integration utilities (`setMyceliaSystem`, `useFacet`, `useListener`) ⭐
321
321
  - **[Angular Bindings](./docs/angular/README.md)** - Angular integration utilities (`MyceliaService`, `useFacet`, `useListener`) ⭐
322
322
  - **[Qwik Bindings](./docs/qwik/README.md)** - Qwik integration utilities (`MyceliaProvider`, `useFacet`, `useListener`) ⭐
323
+ - **[Solid.js Bindings](./src/solid/README.md)** - Solid.js integration utilities (`MyceliaProvider`, `useFacet`, `useListener`) ⭐
323
324
  - **[Standalone Plugin System](./docs/standalone/STANDALONE-PLUGIN-SYSTEM.md)** - Complete usage guide
324
325
  - **[Documentation Index](./docs/README.md)** - Full documentation index
325
326
 
@@ -347,14 +348,13 @@ See the `examples/` directory for:
347
348
  - Composition API integration with reactive state management
348
349
 
349
350
  - **[Svelte Todo App](./examples/svelte-todo/README.md)** ⭐ – A complete Svelte example demonstrating:
350
- - **[Solid.js Todo App](./examples/solid-todo/README.md)** A complete Solid.js example demonstrating:
351
- - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, and Svelte examples
351
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, and other examples
352
352
  - Event-driven state synchronization (`todos:changed` events)
353
- - Solid.js bindings (`MyceliaProvider`, `useFacet`, `useListener`)
354
- - Signal-based reactivity with automatic updates
353
+ - Svelte bindings (`setMyceliaSystem`, `useFacet`, `useListener`)
354
+ - Svelte stores for reactive state management
355
355
 
356
356
  - **[Angular Todo App](./examples/angular-todo/README.md)** ⭐ – A complete Angular example demonstrating:
357
- - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, and Svelte examples
357
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, Svelte, and other examples
358
358
  - Event-driven state synchronization (`todos:changed` events)
359
359
  - Angular bindings (`MyceliaService`, `useFacet`, `useListener`)
360
360
  - RxJS observables for reactive state management
@@ -365,6 +365,12 @@ See the `examples/` directory for:
365
365
  - Qwik bindings (`MyceliaProvider`, `useFacet`, `useListener`)
366
366
  - Qwik signals for reactive state management
367
367
 
368
+ - **[Solid.js Todo App](./examples/solid-todo/README.md)** ⭐ – A complete Solid.js example demonstrating:
369
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, Svelte, Angular, and Qwik examples
370
+ - Event-driven state synchronization (`todos:changed` events)
371
+ - Solid.js bindings (`MyceliaProvider`, `useFacet`, `useListener`)
372
+ - Signal-based reactivity with automatic updates
373
+
368
374
  All six examples use the **exact same Mycelia plugin code** from `examples/todo-shared/`, proving that plugins are truly framework-independent. Write your domain logic once, use it everywhere!
369
375
 
370
376
  ## CLI Tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mycelia-kernel-plugin",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "A sophisticated, framework-agnostic plugin system with transaction safety, lifecycle management, and official bindings for React, Vue 3, Svelte, Angular, Qwik, and Solid.js",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,13 +57,13 @@
57
57
  "test:coverage": "vitest run --coverage"
58
58
  },
59
59
  "peerDependencies": {
60
- "react": ">=16.8.0",
61
- "vue": ">=3.0.0",
62
- "svelte": ">=3.0.0",
63
60
  "@angular/core": ">=15.0.0",
64
61
  "@builder.io/qwik": ">=1.0.0",
62
+ "react": ">=16.8.0",
63
+ "rxjs": ">=7.0.0",
65
64
  "solid-js": ">=1.0.0",
66
- "rxjs": ">=7.0.0"
65
+ "svelte": ">=3.0.0",
66
+ "vue": ">=3.0.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@eslint/js": "^9.36.0",
@@ -73,5 +73,8 @@
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=18.0.0"
76
+ },
77
+ "dependencies": {
78
+ "mitt": "^3.0.1"
76
79
  }
77
80
  }
@@ -19,6 +19,7 @@ import { createFacetContract } from '../facet-contract.js';
19
19
  * Required methods:
20
20
  * - on: Register a listener for a specific message path
21
21
  * - off: Unregister a listener for a specific message path
22
+ * - emit: Emit an event to listeners for a specific path
22
23
  * - hasListeners: Check if listeners are enabled
23
24
  * - enableListeners: Enable listeners and initialize ListenerManager
24
25
  * - disableListeners: Disable listeners (but keep manager instance)
@@ -37,6 +38,7 @@ export const listenersContract = createFacetContract({
37
38
  requiredMethods: [
38
39
  'on',
39
40
  'off',
41
+ 'emit',
40
42
  'hasListeners',
41
43
  'enableListeners',
42
44
  'disableListeners'
@@ -0,0 +1,172 @@
1
+ /**
2
+ * useSimpleListeners Hook
3
+ *
4
+ * Provides simple listener management functionality to subsystems using mitt.
5
+ * A lightweight alternative to useListeners that wraps the mitt event emitter.
6
+ *
7
+ * @param {Object} ctx - Context object containing config.listeners for listener configuration
8
+ * @param {Object} api - Subsystem API being built
9
+ * @param {BaseSubsystem} subsystem - Subsystem instance
10
+ * @returns {Facet} Facet object with listener methods
11
+ */
12
+ import mitt from 'mitt';
13
+ import { Facet } from '../../core/facet.js';
14
+ import { createHook } from '../../core/create-hook.js';
15
+ import { getDebugFlag } from '../../utils/debug-flag.js';
16
+ import { createLogger } from '../../utils/logger.js';
17
+
18
+ export const useSimpleListeners = createHook({
19
+ kind: 'listeners',
20
+ version: '1.0.0',
21
+ overwrite: false,
22
+ required: [],
23
+ attach: true,
24
+ source: import.meta.url,
25
+ contract: 'listeners',
26
+ // eslint-disable-next-line no-unused-vars
27
+ fn: (ctx, api, _subsystem) => {
28
+ const { name } = api;
29
+ const config = ctx.config?.listeners || {};
30
+ const debug = getDebugFlag(config, ctx);
31
+
32
+ // Listeners are optional - can be enabled/disabled
33
+ let emitter = null;
34
+ let listenersEnabled = false;
35
+
36
+ return new Facet('listeners', { attach: true, source: import.meta.url, contract: 'listeners' })
37
+ .add({
38
+
39
+ /**
40
+ * Check if listeners are enabled
41
+ * @returns {boolean} True if listeners are enabled
42
+ */
43
+ hasListeners() {
44
+ return listenersEnabled && emitter !== null;
45
+ },
46
+
47
+ /**
48
+ * Enable listeners
49
+ * @param {Object} [listenerOptions={}] - Options (currently unused, for API compatibility)
50
+ */
51
+ enableListeners(listenerOptions = {}) {
52
+ if (emitter === null) {
53
+ emitter = mitt();
54
+ }
55
+ listenersEnabled = true;
56
+ },
57
+
58
+ /**
59
+ * Disable listeners
60
+ */
61
+ disableListeners() {
62
+ listenersEnabled = false;
63
+ },
64
+
65
+ /**
66
+ * Register a listener for a specific path
67
+ * @param {string} path - Message path to listen for
68
+ * @param {Function} handler - Handler function
69
+ * @param {Object} [options={}] - Registration options (for API compatibility)
70
+ * @returns {boolean} Success status
71
+ */
72
+ on(path, handler, options = {}) {
73
+ // Check if listeners are enabled
74
+ if (!listenersEnabled || emitter === null) {
75
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
76
+ if (runtimeDebug) {
77
+ const runtimeLogger = createLogger(runtimeDebug, `useSimpleListeners ${name}`);
78
+ runtimeLogger.warn('Cannot register listener - listeners not enabled');
79
+ }
80
+ return false;
81
+ }
82
+
83
+ // Validate handler is a function
84
+ if (typeof handler !== 'function') {
85
+ if (debug) {
86
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
87
+ runtimeLogger.warn('Handler must be a function');
88
+ }
89
+ return false;
90
+ }
91
+
92
+ // Register with mitt
93
+ emitter.on(path, handler);
94
+ return true;
95
+ },
96
+
97
+ /**
98
+ * Unregister a listener for a specific path
99
+ * @param {string} path - Message path
100
+ * @param {Function} handler - Handler function to remove
101
+ * @param {Object} [options={}] - Unregistration options (for API compatibility)
102
+ * @returns {boolean} Success status
103
+ */
104
+ off(path, handler, options = {}) {
105
+ // Check if listeners are enabled
106
+ if (!listenersEnabled || emitter === null) {
107
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
108
+ if (runtimeDebug) {
109
+ const runtimeLogger = createLogger(runtimeDebug, `useSimpleListeners ${name}`);
110
+ runtimeLogger.warn('Cannot unregister listener - listeners not enabled');
111
+ }
112
+ return false;
113
+ }
114
+
115
+ // Validate handler is a function
116
+ if (typeof handler !== 'function') {
117
+ if (debug) {
118
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
119
+ runtimeLogger.warn('Handler must be a function');
120
+ }
121
+ return false;
122
+ }
123
+
124
+ // Unregister from mitt
125
+ emitter.off(path, handler);
126
+ return true;
127
+ },
128
+
129
+ /**
130
+ * Emit an event to listeners for a specific path
131
+ * @param {string} path - Message path to emit to
132
+ * @param {any} message - Message/data to send to listeners
133
+ * @returns {number} Number of listeners notified, or 0 if listeners not enabled
134
+ *
135
+ * @example
136
+ * // Emit event to listeners
137
+ * const notified = subsystem.listeners.emit('layers/create', message);
138
+ */
139
+ emit(path, message) {
140
+ // Check if listeners are enabled
141
+ if (!listenersEnabled || emitter === null) {
142
+ if (debug) {
143
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
144
+ runtimeLogger.warn('Cannot emit event - listeners not enabled');
145
+ }
146
+ return 0;
147
+ }
148
+
149
+ // Get listeners count before emitting (mitt doesn't return count)
150
+ const listeners = emitter.all.get(path);
151
+ const count = listeners ? listeners.length : 0;
152
+
153
+ // Emit to mitt
154
+ emitter.emit(path, message);
155
+
156
+ return count;
157
+ },
158
+
159
+ /**
160
+ * Expose emitter property for direct access
161
+ * Returns null if listeners are not enabled
162
+ */
163
+ get listeners() {
164
+ return emitter;
165
+ },
166
+
167
+ // Expose emitter for internal use (compatible with listeners contract)
168
+ _listenerManager: () => emitter
169
+ });
170
+ }
171
+ });
172
+
package/src/index.js CHANGED
@@ -31,6 +31,7 @@ export * from './contract/contracts/index.js';
31
31
 
32
32
  // Hook exports
33
33
  export { useListeners } from './hooks/listeners/use-listeners.js';
34
+ export { useSimpleListeners } from './hooks/listeners/use-simple-listeners.js';
34
35
  export { useQueue } from './hooks/queue/use-queue.js';
35
36
  export { useSpeak } from './hooks/speak/use-speak.js';
36
37