mycelia-kernel-plugin 1.2.0 → 1.4.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.
@@ -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
+
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Mycelia Plugin System - Solid.js Bindings
3
+ *
4
+ * Solid.js utilities that make the Mycelia Plugin System feel natural
5
+ * inside Solid.js applications using signals and context.
6
+ *
7
+ * @example
8
+ * ```jsx
9
+ * import { MyceliaProvider, useFacet, useListener } from 'mycelia-kernel-plugin/solid';
10
+ *
11
+ * const buildSystem = () => useBase('app')
12
+ * .use(useDatabase)
13
+ * .build();
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <MyceliaProvider build={buildSystem}>
18
+ * <MyComponent />
19
+ * </MyceliaProvider>
20
+ * );
21
+ * }
22
+ *
23
+ * function MyComponent() {
24
+ * const db = useFacet('database');
25
+ * useListener('user:created', (msg) => console.log(msg));
26
+ * // ...
27
+ * }
28
+ * ```
29
+ */
30
+
31
+ import { createContext, useContext, createSignal, createEffect, onCleanup, onMount } from 'solid-js';
32
+
33
+ // ============================================================================
34
+ // Core Bindings: Provider + Basic Hooks
35
+ // ============================================================================
36
+
37
+ const MyceliaContext = createContext();
38
+
39
+ /**
40
+ * MyceliaProvider - Provides Mycelia system to Solid.js component tree
41
+ *
42
+ * @param {Object} props
43
+ * @param {Function} props.build - Async function that returns a built system
44
+ * @param {JSX.Element} props.children - Child components
45
+ * @param {JSX.Element} [props.fallback] - Optional loading/fallback component
46
+ *
47
+ * @example
48
+ * ```jsx
49
+ * <MyceliaProvider build={buildSystem}>
50
+ * <App />
51
+ * </MyceliaProvider>
52
+ * ```
53
+ */
54
+ export function MyceliaProvider(props) {
55
+ const [system, setSystem] = createSignal(null);
56
+ const [error, setError] = createSignal(null);
57
+ const [loading, setLoading] = createSignal(true);
58
+ let currentSystem = null;
59
+
60
+ onMount(async () => {
61
+ try {
62
+ currentSystem = await props.build();
63
+ setSystem(currentSystem);
64
+ setError(null);
65
+ setLoading(false);
66
+ } catch (err) {
67
+ setError(err);
68
+ setSystem(null);
69
+ setLoading(false);
70
+ }
71
+ });
72
+
73
+ onCleanup(() => {
74
+ if (currentSystem && typeof currentSystem.dispose === 'function') {
75
+ currentSystem.dispose().catch(() => {
76
+ // Ignore disposal errors
77
+ });
78
+ }
79
+ });
80
+
81
+ if (error()) {
82
+ throw error(); // Let error boundaries handle it
83
+ }
84
+
85
+ if (loading()) {
86
+ return props.fallback || null;
87
+ }
88
+
89
+ return (
90
+ <MyceliaContext.Provider value={system}>
91
+ {props.children}
92
+ </MyceliaContext.Provider>
93
+ );
94
+ }
95
+
96
+ /**
97
+ * useMycelia - Get the Mycelia system from context
98
+ *
99
+ * @returns {Function} Signal accessor that returns the Mycelia system instance
100
+ * @throws {Error} If used outside MyceliaProvider
101
+ *
102
+ * @example
103
+ * ```jsx
104
+ * function MyComponent() {
105
+ * const system = useMycelia();
106
+ * // Use system().find(), system().listeners, etc.
107
+ * }
108
+ * ```
109
+ */
110
+ export function useMycelia() {
111
+ const context = useContext(MyceliaContext);
112
+ if (!context) {
113
+ throw new Error('useMycelia must be used within a MyceliaProvider');
114
+ }
115
+ return context;
116
+ }
117
+
118
+ /**
119
+ * useFacet - Get a facet by kind from the system with reactivity
120
+ *
121
+ * @param {string} kind - Facet kind identifier
122
+ * @returns {Function} Signal accessor that returns the facet instance, or null if not found
123
+ *
124
+ * @example
125
+ * ```jsx
126
+ * function UserList() {
127
+ * const db = useFacet('database');
128
+ * // Use db().query(), etc.
129
+ * }
130
+ * ```
131
+ */
132
+ export function useFacet(kind) {
133
+ const system = useMycelia();
134
+ const [facet, setFacet] = createSignal(() => {
135
+ const sys = system();
136
+ return sys?.find?.(kind) ?? null;
137
+ });
138
+
139
+ createEffect(() => {
140
+ const sys = system();
141
+ setFacet(sys?.find?.(kind) ?? null);
142
+ });
143
+
144
+ return facet;
145
+ }
146
+
147
+ // ============================================================================
148
+ // Listener Helpers
149
+ // ============================================================================
150
+
151
+ /**
152
+ * useListener - Register an event listener with automatic cleanup
153
+ *
154
+ * @param {string} eventName - Event name/path to listen for
155
+ * @param {Function} handler - Handler function: (message) => void
156
+ *
157
+ * @example
158
+ * ```jsx
159
+ * function AuditLog() {
160
+ * useListener('user:created', (msg) => {
161
+ * console.log('User created:', msg.body);
162
+ * });
163
+ * // ...
164
+ * }
165
+ * ```
166
+ */
167
+ export function useListener(eventName, handler) {
168
+ const system = useMycelia();
169
+ let listeners = null;
170
+ let wrappedHandler = null;
171
+
172
+ createEffect(() => {
173
+ const sys = system();
174
+ if (!sys) return;
175
+
176
+ listeners = sys.listeners; // useListeners facet
177
+ if (!listeners || !listeners.hasListeners?.()) {
178
+ return;
179
+ }
180
+
181
+ wrappedHandler = (msg) => {
182
+ handler(msg);
183
+ };
184
+
185
+ listeners.on(eventName, wrappedHandler);
186
+ });
187
+
188
+ onCleanup(() => {
189
+ if (listeners && wrappedHandler) {
190
+ listeners.off?.(eventName, wrappedHandler);
191
+ }
192
+ });
193
+ }
194
+
195
+ /**
196
+ * useEventStream - Subscribe to events and keep them in reactive state
197
+ *
198
+ * @param {string} eventName - Event name/path to listen for
199
+ * @param {Object} [options={}] - Options
200
+ * @param {boolean} [options.accumulate=false] - If true, accumulate events in array
201
+ * @returns {Function} Signal accessor that returns latest event value, array of events, or null
202
+ *
203
+ * @example
204
+ * ```jsx
205
+ * function EventList() {
206
+ * const events = useEventStream('todo:created', { accumulate: true });
207
+ * return <ul>{events()?.map(e => <li key={e.id}>{e.text}</li>)}</ul>;
208
+ * }
209
+ * ```
210
+ */
211
+ export function useEventStream(eventName, options = {}) {
212
+ const { accumulate = false } = options;
213
+ const [value, setValue] = createSignal(accumulate ? [] : null);
214
+
215
+ useListener(eventName, (msg) => {
216
+ if (accumulate) {
217
+ setValue((prev) => [...prev, msg.body]);
218
+ } else {
219
+ setValue(msg.body);
220
+ }
221
+ });
222
+
223
+ return value;
224
+ }
225
+
226
+ // ============================================================================
227
+ // Queue Helpers
228
+ // ============================================================================
229
+
230
+ /**
231
+ * useQueueStatus - Get queue status with reactive updates
232
+ *
233
+ * @returns {Function} Signal accessor that returns queue status object
234
+ * @returns {number} size - Current queue size
235
+ * @returns {number} capacity - Maximum queue capacity
236
+ * @returns {number} utilization - Utilization ratio (0-1)
237
+ * @returns {boolean} isFull - Whether queue is full
238
+ *
239
+ * @example
240
+ * ```jsx
241
+ * function QueueStatus() {
242
+ * const status = useQueueStatus();
243
+ * return <div>Queue: {status().size}/{status().capacity}</div>;
244
+ * }
245
+ * ```
246
+ */
247
+ export function useQueueStatus() {
248
+ const queue = useFacet('queue');
249
+ const [status, setStatus] = createSignal(() => {
250
+ const q = queue();
251
+ if (q?.getQueueStatus) {
252
+ return q.getQueueStatus();
253
+ }
254
+ return { size: 0, maxSize: 0, utilization: 0, isFull: false };
255
+ });
256
+
257
+ // Poll for status updates (could be improved with event-based updates)
258
+ createEffect(() => {
259
+ const q = queue();
260
+ if (!q || !q.getQueueStatus) return;
261
+
262
+ const interval = setInterval(() => {
263
+ const newStatus = q.getQueueStatus();
264
+ setStatus(newStatus);
265
+ }, 100); // Poll every 100ms
266
+
267
+ onCleanup(() => clearInterval(interval));
268
+ });
269
+
270
+ return () => {
271
+ const s = status();
272
+ return {
273
+ size: s.size || 0,
274
+ capacity: s.maxSize || 0,
275
+ utilization: s.utilization || 0,
276
+ isFull: s.isFull || false
277
+ };
278
+ };
279
+ }
280
+
281
+ /**
282
+ * useQueueDrain - Automatically drain queue on mount
283
+ *
284
+ * @param {Object} [options={}] - Options
285
+ * @param {number} [options.interval=100] - Polling interval in ms
286
+ * @param {Function} [options.onMessage] - Callback for each message: (msg, options) => void
287
+ *
288
+ * @example
289
+ * ```jsx
290
+ * function QueueProcessor() {
291
+ * useQueueDrain({
292
+ * interval: 50,
293
+ * onMessage: (msg) => console.log('Processed:', msg)
294
+ * });
295
+ * return null;
296
+ * }
297
+ * ```
298
+ */
299
+ export function useQueueDrain(options = {}) {
300
+ const { interval = 100, onMessage } = options;
301
+ const queue = useFacet('queue');
302
+
303
+ createEffect(() => {
304
+ const q = queue();
305
+ if (!q || !q.hasMessagesToProcess) return;
306
+
307
+ const processInterval = setInterval(() => {
308
+ if (q.hasMessagesToProcess()) {
309
+ const next = q.selectNextMessage();
310
+ if (next && onMessage) {
311
+ onMessage(next.msg, next.options);
312
+ }
313
+ }
314
+ }, interval);
315
+
316
+ onCleanup(() => clearInterval(processInterval));
317
+ });
318
+ }
319
+
320
+ // ============================================================================
321
+ // Builder Helpers
322
+ // ============================================================================
323
+
324
+ /**
325
+ * createSolidSystemBuilder - Create a reusable system builder function
326
+ *
327
+ * @param {string} name - System name
328
+ * @param {Function} configure - Configuration function: (builder) => builder
329
+ * @returns {Function} Build function: () => Promise<System>
330
+ *
331
+ * @example
332
+ * ```js
333
+ * import { useBase } from 'mycelia-kernel-plugin';
334
+ *
335
+ * const buildTodoSystem = createSolidSystemBuilder('todo-app', (b) =>
336
+ * b
337
+ * .config('database', { host: 'localhost' })
338
+ * .use(useDatabase)
339
+ * .use(useListeners)
340
+ * );
341
+ *
342
+ * // Then use in Provider
343
+ * <MyceliaProvider build={buildTodoSystem}>
344
+ * <App />
345
+ * </MyceliaProvider>
346
+ * ```
347
+ */
348
+ export function createSolidSystemBuilder(name, configure) {
349
+ return async function build() {
350
+ // Import useBase - users should have it available
351
+ // This avoids bundling issues by letting users import useBase themselves
352
+ const { useBase } = await import('../utils/use-base.js');
353
+ let builder = useBase(name);
354
+ builder = configure(builder);
355
+ return builder.build();
356
+ };
357
+ }
358
+
359
+ // ============================================================================
360
+ // Facet Hook Generator
361
+ // ============================================================================
362
+
363
+ /**
364
+ * createFacetSignal - Generate a custom signal hook for a specific facet kind
365
+ *
366
+ * @param {string} kind - Facet kind identifier
367
+ * @returns {Function} Custom hook: () => Signal<Facet | null>
368
+ *
369
+ * @example
370
+ * ```js
371
+ * // In bindings/todo-hooks.ts
372
+ * export const useTodoStore = createFacetSignal('todoStore');
373
+ * export const useAuth = createFacetSignal('auth');
374
+ *
375
+ * // In component
376
+ * function TodoList() {
377
+ * const todoStore = useTodoStore();
378
+ * // Use todoStore()...
379
+ * }
380
+ * ```
381
+ */
382
+ export function createFacetSignal(kind) {
383
+ return function useNamedFacet() {
384
+ return useFacet(kind);
385
+ };
386
+ }
387
+
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Mycelia Plugin System - Svelte Builder Helpers
3
+ *
4
+ * Svelte utilities for creating system builders.
5
+ */
6
+
7
+ /**
8
+ * createSvelteSystemBuilder - Create a reusable system builder function
9
+ *
10
+ * @param {string} name - System name
11
+ * @param {Function} configure - Configuration function: (builder) => builder
12
+ * @returns {Function} Build function: () => Promise<System>
13
+ *
14
+ * @example
15
+ * ```js
16
+ * import { useBase } from 'mycelia-kernel-plugin';
17
+ * import { createSvelteSystemBuilder } from 'mycelia-kernel-plugin/svelte';
18
+ *
19
+ * const buildTodoSystem = createSvelteSystemBuilder('todo-app', (b) =>
20
+ * b
21
+ * .config('database', { host: 'localhost' })
22
+ * .use(useDatabase)
23
+ * .use(useListeners)
24
+ * );
25
+ *
26
+ * // Then use in component
27
+ * onMount(async () => {
28
+ * const system = await buildTodoSystem();
29
+ * setMyceliaSystem(system);
30
+ * });
31
+ * ```
32
+ */
33
+ export function createSvelteSystemBuilder(name, configure) {
34
+ return async function build() {
35
+ // Import useBase - users should have it available
36
+ // This avoids bundling issues by letting users import useBase themselves
37
+ const { useBase } = await import('../utils/use-base.js');
38
+ let builder = useBase(name);
39
+ builder = configure(builder);
40
+ return builder.build();
41
+ };
42
+ }
43
+