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.
- package/README.md +54 -7
- package/bin/cli.js +261 -0
- package/package.json +15 -5
- package/src/angular/builders.js +37 -0
- package/src/angular/helpers.js +102 -0
- package/src/angular/index.js +189 -0
- package/src/angular/services.js +32 -0
- package/src/builder/dependency-graph.js +65 -9
- package/src/builder/hook-processor.js +26 -4
- package/src/builder/utils.js +78 -22
- package/src/contract/contracts/listeners.contract.js +2 -0
- package/src/core/facet.js +16 -3
- package/src/hooks/listeners/use-simple-listeners.js +172 -0
- package/src/index.js +19 -0
- package/src/manager/facet-manager.js +10 -2
- package/src/qwik/builders.js +39 -0
- package/src/qwik/index.js +178 -0
- package/src/qwik/listeners.js +96 -0
- package/src/qwik/queues.js +87 -0
- package/src/qwik/signals.js +32 -0
- package/src/react/README.md +3 -0
- package/src/solid/README.md +69 -0
- package/src/solid/index.js +387 -0
- package/src/utils/instrumentation.js +204 -0
- package/src/utils/use-base.js +205 -30
|
@@ -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
|
+
|
package/src/react/README.md
CHANGED
|
@@ -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
|
+
|