mycelia-kernel-plugin 1.3.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.
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Mycelia Plugin System - Qwik Bindings
3
+ *
4
+ * Qwik utilities that make the Mycelia Plugin System feel natural
5
+ * inside Qwik applications using signals and context.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { MyceliaProvider, useFacet, useListener } from 'mycelia-kernel-plugin/qwik';
10
+ *
11
+ * const buildSystem = () => useBase('app')
12
+ * .use(useDatabase)
13
+ * .build();
14
+ *
15
+ * export default component$(() => {
16
+ * return (
17
+ * <MyceliaProvider build={buildSystem}>
18
+ * <MyComponent />
19
+ * </MyceliaProvider>
20
+ * );
21
+ * });
22
+ *
23
+ * export const MyComponent = component$(() => {
24
+ * const db = useFacet('database');
25
+ * useListener('user:created', (msg) => console.log(msg));
26
+ * // ...
27
+ * });
28
+ * ```
29
+ */
30
+
31
+ import { createContextId, useContextProvider, useContext, useSignal, useStore, useTask$, component$, Slot } from '@builder.io/qwik';
32
+
33
+ // ============================================================================
34
+ // Core Bindings: Provider + Basic Hooks
35
+ // ============================================================================
36
+
37
+ const MyceliaContextId = createContextId('mycelia');
38
+
39
+ /**
40
+ * MyceliaProvider - Provides Mycelia system to Qwik component tree
41
+ *
42
+ * @param {Object} props
43
+ * @param {Function} props.build - Async function that returns a built system
44
+ * @param {import('@builder.io/qwik').QRL} props.children - Child components
45
+ * @param {import('@builder.io/qwik').QRL} [props.fallback] - Optional loading/fallback component
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * export default component$(() => {
50
+ * return (
51
+ * <MyceliaProvider build={buildSystem}>
52
+ * <App />
53
+ * </MyceliaProvider>
54
+ * );
55
+ * });
56
+ * ```
57
+ */
58
+ export const MyceliaProvider = component$(({ build, fallback = null }) => {
59
+ const system = useSignal(null);
60
+ const error = useSignal(null);
61
+ const loading = useSignal(true);
62
+
63
+ useTask$(async () => {
64
+ try {
65
+ const builtSystem = await build();
66
+ system.value = builtSystem;
67
+ error.value = null;
68
+ } catch (err) {
69
+ error.value = err;
70
+ system.value = null;
71
+ } finally {
72
+ loading.value = false;
73
+ }
74
+ });
75
+
76
+ const context = {
77
+ system,
78
+ error,
79
+ loading
80
+ };
81
+
82
+ useContextProvider(MyceliaContextId, context);
83
+
84
+ if (error.value) {
85
+ throw error.value; // Let error boundaries handle it
86
+ }
87
+
88
+ if (loading.value) {
89
+ return fallback || <div>Loading...</div>;
90
+ }
91
+
92
+ return <Slot />;
93
+ });
94
+
95
+ /**
96
+ * useMycelia - Get the Mycelia system from context
97
+ *
98
+ * @returns {Object} The Mycelia system instance
99
+ * @throws {Error} If used outside MyceliaProvider
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * export const MyComponent = component$(() => {
104
+ * const system = useMycelia();
105
+ * // Use system.find(), system.listeners, etc.
106
+ * });
107
+ * ```
108
+ */
109
+ export function useMycelia() {
110
+ const context = useContext(MyceliaContextId);
111
+ if (!context) {
112
+ throw new Error('useMycelia must be used within a MyceliaProvider');
113
+ }
114
+ return context.system.value;
115
+ }
116
+
117
+ /**
118
+ * useMyceliaContext - Get the full Mycelia context (system, loading, error)
119
+ *
120
+ * @returns {Object} Context object with system, loading, and error signals
121
+ * @throws {Error} If used outside MyceliaProvider
122
+ *
123
+ * @example
124
+ * ```tsx
125
+ * export const MyComponent = component$(() => {
126
+ * const { system, loading, error } = useMyceliaContext();
127
+ * if (loading.value) return <div>Loading...</div>;
128
+ * if (error.value) return <div>Error: {error.value.message}</div>;
129
+ * return <div>System: {system.value.name}</div>;
130
+ * });
131
+ * ```
132
+ */
133
+ export function useMyceliaContext() {
134
+ const context = useContext(MyceliaContextId);
135
+ if (!context) {
136
+ throw new Error('useMyceliaContext must be used within a MyceliaProvider');
137
+ }
138
+ return context;
139
+ }
140
+
141
+ /**
142
+ * useFacet - Get a facet by kind from the system (reactive signal)
143
+ *
144
+ * @param {string} kind - Facet kind identifier
145
+ * @returns {import('@builder.io/qwik').Signal} Signal containing the facet instance, or null if not found
146
+ *
147
+ * @example
148
+ * ```tsx
149
+ * export const UserList = component$(() => {
150
+ * const db = useFacet('database');
151
+ * // Use db.value.query(), etc.
152
+ * });
153
+ * ```
154
+ */
155
+ export function useFacet(kind) {
156
+ const context = useMyceliaContext();
157
+ const facet = useSignal(null);
158
+
159
+ useTask$(({ track }) => {
160
+ const system = track(() => context.system.value);
161
+ facet.value = system?.find?.(kind) ?? null;
162
+ });
163
+
164
+ return facet;
165
+ }
166
+
167
+ // Re-export listener helpers
168
+ export { useListener, useEventStream } from './listeners.js';
169
+
170
+ // Re-export queue helpers
171
+ export { useQueueStatus, useQueueDrain } from './queues.js';
172
+
173
+ // Re-export builder helpers
174
+ export { createQwikSystemBuilder } from './builders.js';
175
+
176
+ // Re-export facet signal generator
177
+ export { createFacetSignal } from './signals.js';
178
+
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Qwik Listener Helpers
3
+ */
4
+
5
+ import { useTask$, useSignal } from '@builder.io/qwik';
6
+ import { useMycelia } from './index.js';
7
+
8
+ // Note: Qwik's useTask$ requires proper serialization
9
+ // Event handlers should be QRL-wrapped for proper serialization
10
+
11
+ /**
12
+ * useListener - Register an event listener with automatic cleanup
13
+ *
14
+ * @param {string} eventName - Event name/path to listen for
15
+ * @param {Function} handler - Handler function: (message) => void
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * export const AuditLog = component$(() => {
20
+ * useListener('user:created', (msg) => {
21
+ * console.log('User created:', msg.body);
22
+ * });
23
+ * // ...
24
+ * });
25
+ * ```
26
+ */
27
+ export function useListener(eventName, handler) {
28
+ const system = useMycelia();
29
+ const listeners = system.listeners; // useListeners facet
30
+
31
+ useTask$(({ cleanup }) => {
32
+ if (!listeners || !listeners.hasListeners?.()) {
33
+ return;
34
+ }
35
+
36
+ const wrappedHandler = (msg) => {
37
+ handler(msg);
38
+ };
39
+
40
+ listeners.on(eventName, wrappedHandler);
41
+
42
+ cleanup(() => {
43
+ listeners.off?.(eventName, wrappedHandler);
44
+ });
45
+ });
46
+ }
47
+
48
+ /**
49
+ * useEventStream - Subscribe to events and keep them in Qwik signal
50
+ *
51
+ * @param {string} eventName - Event name/path to listen for
52
+ * @param {Object} [options={}] - Options
53
+ * @param {boolean} [options.accumulate=false] - If true, accumulate events in array
54
+ * @returns {import('@builder.io/qwik').Signal} Signal containing latest event value, array of events, or null
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * export const EventList = component$(() => {
59
+ * const events = useEventStream('todo:created', { accumulate: true });
60
+ * return (
61
+ * <ul>
62
+ * {events.value?.map(e => <li key={e.id}>{e.text}</li>)}
63
+ * </ul>
64
+ * );
65
+ * });
66
+ * ```
67
+ */
68
+ export function useEventStream(eventName, options = {}) {
69
+ const { accumulate = false } = options;
70
+ const value = useSignal(accumulate ? [] : null);
71
+ const system = useMycelia();
72
+ const listeners = system.listeners;
73
+
74
+ useTask$(({ cleanup }) => {
75
+ if (!listeners || !listeners.hasListeners?.()) {
76
+ return;
77
+ }
78
+
79
+ const handler = (msg) => {
80
+ if (accumulate) {
81
+ value.value = [...value.value, msg.body];
82
+ } else {
83
+ value.value = msg.body;
84
+ }
85
+ };
86
+
87
+ listeners.on(eventName, handler);
88
+
89
+ cleanup(() => {
90
+ listeners.off?.(eventName, handler);
91
+ });
92
+ });
93
+
94
+ return value;
95
+ }
96
+
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Qwik Queue Helpers
3
+ */
4
+
5
+ import { useTask$, useSignal } from '@builder.io/qwik';
6
+ import { useFacet } from './index.js';
7
+
8
+ /**
9
+ * useQueueStatus - Get queue status with reactive updates
10
+ *
11
+ * @returns {import('@builder.io/qwik').Signal} Signal containing queue status object
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * export const QueueStatus = component$(() => {
16
+ * const status = useQueueStatus();
17
+ * return <div>Queue: {status.value.size}/{status.value.capacity}</div>;
18
+ * });
19
+ * ```
20
+ */
21
+ export function useQueueStatus() {
22
+ const queue = useFacet('queue');
23
+ const status = useSignal({ size: 0, capacity: 0, utilization: 0, isFull: false });
24
+
25
+ useTask$(({ track }) => {
26
+ const queueInstance = track(() => queue.value);
27
+ if (!queueInstance || !queueInstance.getQueueStatus) return;
28
+
29
+ const updateStatus = () => {
30
+ const newStatus = queueInstance.getQueueStatus();
31
+ status.value = {
32
+ size: newStatus.size || 0,
33
+ capacity: newStatus.maxSize || 0,
34
+ utilization: newStatus.utilization || 0,
35
+ isFull: newStatus.isFull || false
36
+ };
37
+ };
38
+
39
+ updateStatus();
40
+ const interval = setInterval(updateStatus, 100); // Poll every 100ms
41
+
42
+ return () => clearInterval(interval);
43
+ });
44
+
45
+ return status;
46
+ }
47
+
48
+ /**
49
+ * useQueueDrain - Automatically drain queue on mount
50
+ *
51
+ * @param {Object} [options={}] - Options
52
+ * @param {number} [options.interval=100] - Polling interval in ms
53
+ * @param {Function} [options.onMessage] - Callback for each message: (msg, options) => void
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * export const QueueProcessor = component$(() => {
58
+ * useQueueDrain({
59
+ * interval: 50,
60
+ * onMessage: (msg) => console.log('Processed:', msg)
61
+ * });
62
+ * return null;
63
+ * });
64
+ * ```
65
+ */
66
+ export function useQueueDrain(options = {}) {
67
+ const { interval = 100, onMessage } = options;
68
+ const queue = useFacet('queue');
69
+
70
+ useTask$(({ track, cleanup }) => {
71
+ const queueInstance = track(() => queue.value);
72
+ if (!queueInstance || !queueInstance.hasMessagesToProcess) return;
73
+
74
+ const processInterval = setInterval(() => {
75
+ if (queueInstance.hasMessagesToProcess()) {
76
+ const next = queueInstance.selectNextMessage();
77
+ if (next && onMessage) {
78
+ onMessage(next.msg, next.options);
79
+ }
80
+ }
81
+ }, interval);
82
+
83
+ cleanup(() => clearInterval(processInterval));
84
+ });
85
+ }
86
+
87
+
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Qwik Signal Generators
3
+ */
4
+
5
+ import { useFacet } from './index.js';
6
+
7
+ /**
8
+ * createFacetSignal - Generate a signal for a specific facet kind
9
+ *
10
+ * @param {string} kind - Facet kind identifier
11
+ * @returns {Function} Hook function: () => Signal<Facet | null>
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // In bindings/todo-signals.ts
16
+ * export const useTodoStore = createFacetSignal('todoStore');
17
+ * export const useAuth = createFacetSignal('auth');
18
+ *
19
+ * // In component
20
+ * export const TodoList = component$(() => {
21
+ * const todoStore = useTodoStore();
22
+ * // Use todoStore.value...
23
+ * });
24
+ * ```
25
+ */
26
+ export function createFacetSignal(kind) {
27
+ return function useNamedFacet() {
28
+ return useFacet(kind);
29
+ };
30
+ }
31
+
32
+
@@ -172,3 +172,6 @@ See the main [README.md](../../README.md) and [examples](../../examples/) direct
172
172
  - React >= 16.8.0 (for hooks support)
173
173
  - Mycelia Plugin System (included)
174
174
 
175
+
176
+
177
+
@@ -0,0 +1,69 @@
1
+ # Solid.js Bindings
2
+
3
+ Solid.js utilities that make the Mycelia Plugin System feel natural inside Solid.js applications using signals and context.
4
+
5
+ ## Overview
6
+
7
+ The Solid.js bindings provide a set of components and hooks that integrate the Mycelia Plugin System seamlessly with Solid.js. They handle lifecycle management, context provisioning, and automatic cleanup, making the plugin system feel like a native Solid.js data layer.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install mycelia-kernel-plugin solid-js
13
+ ```
14
+
15
+ **Requirements:**
16
+ - Solid.js >= 1.0.0
17
+ - Mycelia Plugin System (included in package)
18
+
19
+ ## Quick Start
20
+
21
+ ```jsx
22
+ import { MyceliaProvider, useFacet, useListener } from 'mycelia-kernel-plugin/solid';
23
+ import { useBase, useListeners, useDatabase } from 'mycelia-kernel-plugin';
24
+
25
+ // Create system builder
26
+ const buildSystem = () =>
27
+ useBase('my-app')
28
+ .config('database', { host: 'localhost' })
29
+ .use(useDatabase)
30
+ .use(useListeners)
31
+ .build();
32
+
33
+ // Bootstrap your app
34
+ export default function App() {
35
+ return (
36
+ <MyceliaProvider build={buildSystem}>
37
+ <MyComponent />
38
+ </MyceliaProvider>
39
+ );
40
+ }
41
+
42
+ // Use in components
43
+ function MyComponent() {
44
+ const db = useFacet('database');
45
+ const system = useMycelia();
46
+
47
+ useListener('user:created', (msg) => {
48
+ console.log('User created:', msg.body);
49
+ });
50
+
51
+ // Use db(), system(), etc.
52
+ }
53
+ ```
54
+
55
+ ## Documentation
56
+
57
+ - **[Core Bindings](./CORE-BINDINGS.md)** - Provider and basic hooks
58
+ - **[Listener Helpers](./LISTENER-HELPERS.md)** - Event listener utilities
59
+ - **[Queue Helpers](./QUEUE-HELPERS.md)** - Queue management utilities
60
+ - **[Builder Helpers](./BUILDER-HELPERS.md)** - System builder utilities
61
+ - **[Signal Generator](./SIGNAL-GENERATOR.md)** - Custom signal generation
62
+
63
+ ## Features
64
+
65
+ - **Automatic Lifecycle Management** - System is built on mount and disposed on unmount
66
+ - **Context-Based Access** - System available throughout component tree
67
+ - **Automatic Cleanup** - Listeners and effects cleaned up automatically
68
+ - **Solid Signals** - Reactive state with Solid.js signals
69
+