mycelia-kernel-plugin 1.1.0 → 1.3.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,252 @@
1
+ /**
2
+ * Mycelia Plugin System - Vue Bindings
3
+ *
4
+ * Vue utilities that make the Mycelia Plugin System feel natural
5
+ * inside Vue 3 applications using the Composition API.
6
+ *
7
+ * @example
8
+ * ```js
9
+ * import { MyceliaPlugin, useFacet, useListener } from 'mycelia-kernel-plugin/vue';
10
+ * import { createApp } from 'vue';
11
+ *
12
+ * const buildSystem = () => useBase('app')
13
+ * .use(useDatabase)
14
+ * .build();
15
+ *
16
+ * const app = createApp(App);
17
+ * app.use(MyceliaPlugin, { build: buildSystem });
18
+ *
19
+ * // In component
20
+ * export default {
21
+ * setup() {
22
+ * const db = useFacet('database');
23
+ * useListener('user:created', (msg) => console.log(msg));
24
+ * }
25
+ * }
26
+ * ```
27
+ */
28
+
29
+ import { provide, inject, ref, onUnmounted, watch, getCurrentInstance, onBeforeUnmount } from 'vue';
30
+
31
+ // ============================================================================
32
+ // Core Bindings: Plugin + Basic Composables
33
+ // ============================================================================
34
+
35
+ const MyceliaKey = Symbol('mycelia');
36
+
37
+ /**
38
+ * MyceliaPlugin - Vue plugin that provides Mycelia system to the app
39
+ *
40
+ * @param {Object} app - Vue app instance
41
+ * @param {Object} options - Plugin options
42
+ * @param {Function} options.build - Async function that returns a built system
43
+ *
44
+ * @example
45
+ * ```js
46
+ * import { createApp } from 'vue';
47
+ * import { MyceliaPlugin } from 'mycelia-kernel-plugin/vue';
48
+ *
49
+ * const buildSystem = () => useBase('app').use(useDatabase).build();
50
+ *
51
+ * const app = createApp(App);
52
+ * app.use(MyceliaPlugin, { build: buildSystem });
53
+ * ```
54
+ */
55
+ export const MyceliaPlugin = {
56
+ async install(app, options) {
57
+ const { build } = options;
58
+ const system = ref(null);
59
+ const error = ref(null);
60
+ const loading = ref(true);
61
+ let currentSystem = null;
62
+
63
+ try {
64
+ currentSystem = await build();
65
+ system.value = currentSystem;
66
+ loading.value = false;
67
+ error.value = null;
68
+ } catch (err) {
69
+ error.value = err;
70
+ loading.value = false;
71
+ system.value = null;
72
+ // Re-throw to let Vue handle it
73
+ throw err;
74
+ }
75
+
76
+ // Provide system to all components
77
+ const context = {
78
+ system,
79
+ loading,
80
+ error
81
+ };
82
+
83
+ app.provide(MyceliaKey, context);
84
+
85
+ // Store cleanup function on app config for manual cleanup
86
+ // Vue 3 doesn't provide a plugin unmount hook, so cleanup should be
87
+ // handled manually when unmounting the app:
88
+ // const dispose = app.config.globalProperties.$myceliaDispose;
89
+ // if (dispose) await dispose();
90
+ // app.unmount();
91
+ app.config.globalProperties.$myceliaDispose = async () => {
92
+ if (currentSystem && typeof currentSystem.dispose === 'function') {
93
+ await currentSystem.dispose().catch(() => {
94
+ // Ignore disposal errors
95
+ });
96
+ }
97
+ };
98
+ }
99
+ };
100
+
101
+ /**
102
+ * useMycelia - Get the Mycelia system from inject
103
+ *
104
+ * @returns {Object} The Mycelia system instance
105
+ * @throws {Error} If used outside MyceliaPlugin
106
+ *
107
+ * @example
108
+ * ```js
109
+ * import { useMycelia } from 'mycelia-kernel-plugin/vue';
110
+ *
111
+ * export default {
112
+ * setup() {
113
+ * const system = useMycelia();
114
+ * // Use system.find(), system.listeners, etc.
115
+ * }
116
+ * }
117
+ * ```
118
+ */
119
+ export function useMycelia() {
120
+ const context = inject(MyceliaKey);
121
+ if (!context) {
122
+ throw new Error('useMycelia must be used within MyceliaPlugin');
123
+ }
124
+ return context.system.value;
125
+ }
126
+
127
+ /**
128
+ * useMyceliaContext - Get the full Mycelia context (system, loading, error)
129
+ *
130
+ * @returns {Object} Context object with system, loading, and error refs
131
+ * @throws {Error} If used outside MyceliaPlugin
132
+ *
133
+ * @example
134
+ * ```js
135
+ * import { useMyceliaContext } from 'mycelia-kernel-plugin/vue';
136
+ *
137
+ * export default {
138
+ * setup() {
139
+ * const { system, loading, error } = useMyceliaContext();
140
+ * if (loading.value) return { loading: true };
141
+ * if (error.value) return { error: error.value };
142
+ * return { system: system.value };
143
+ * }
144
+ * }
145
+ * ```
146
+ */
147
+ export function useMyceliaContext() {
148
+ const context = inject(MyceliaKey);
149
+ if (!context) {
150
+ throw new Error('useMyceliaContext must be used within MyceliaPlugin');
151
+ }
152
+ return context;
153
+ }
154
+
155
+ /**
156
+ * useFacet - Get a facet by kind from the system with reactivity
157
+ *
158
+ * @param {string} kind - Facet kind identifier
159
+ * @returns {import('vue').Ref<Object|null>} Reactive ref to the facet instance, or null if not found
160
+ *
161
+ * @example
162
+ * ```js
163
+ * import { useFacet } from 'mycelia-kernel-plugin/vue';
164
+ *
165
+ * export default {
166
+ * setup() {
167
+ * const db = useFacet('database');
168
+ * // Use db.value.query(), etc.
169
+ * }
170
+ * }
171
+ * ```
172
+ */
173
+ export function useFacet(kind) {
174
+ const system = useMycelia();
175
+ const facet = ref(system?.find?.(kind) ?? null);
176
+
177
+ // Watch for system changes (e.g., after reload)
178
+ watch(() => system, (newSystem) => {
179
+ facet.value = newSystem?.find?.(kind) ?? null;
180
+ }, { immediate: true });
181
+
182
+ return facet;
183
+ }
184
+
185
+ /**
186
+ * useMyceliaCleanup - Automatically handle system cleanup on component unmount
187
+ *
188
+ * This composable automatically disposes the Mycelia system when the component
189
+ * unmounts. It's useful for root components or components that manage app lifecycle.
190
+ *
191
+ * @param {Object} [options={}] - Options
192
+ * @param {boolean} [options.auto=true] - If true, automatically cleanup on unmount. If false, only return cleanup function.
193
+ * @returns {Function} Cleanup function that can be called manually
194
+ *
195
+ * @example
196
+ * ```vue
197
+ * <script setup>
198
+ * import { useMyceliaCleanup } from 'mycelia-kernel-plugin/vue';
199
+ *
200
+ * // Automatic cleanup on component unmount
201
+ * useMyceliaCleanup();
202
+ * </script>
203
+ * ```
204
+ *
205
+ * @example
206
+ * ```vue
207
+ * <script setup>
208
+ * import { useMyceliaCleanup } from 'mycelia-kernel-plugin/vue';
209
+ *
210
+ * // Get cleanup function for manual use
211
+ * const dispose = useMyceliaCleanup({ auto: false });
212
+ *
213
+ * const handleLogout = async () => {
214
+ * await dispose();
215
+ * // Continue with logout logic
216
+ * };
217
+ * </script>
218
+ * ```
219
+ */
220
+ export function useMyceliaCleanup(options = {}) {
221
+ const { auto = true } = options;
222
+ const instance = getCurrentInstance();
223
+ const app = instance?.appContext.app;
224
+
225
+ const cleanup = async () => {
226
+ const dispose = app?.config.globalProperties.$myceliaDispose;
227
+ if (dispose) {
228
+ await dispose();
229
+ }
230
+ };
231
+
232
+ if (auto) {
233
+ onBeforeUnmount(async () => {
234
+ await cleanup();
235
+ });
236
+ }
237
+
238
+ return cleanup;
239
+ }
240
+
241
+ // Re-export listener helpers
242
+ export { useListener, useEventStream } from './listeners.js';
243
+
244
+ // Re-export queue helpers
245
+ export { useQueueStatus, useQueueDrain } from './queues.js';
246
+
247
+ // Re-export builder helpers
248
+ export { createVueSystemBuilder } from './builders.js';
249
+
250
+ // Re-export composable generator
251
+ export { createFacetComposable } from './composables.js';
252
+
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Mycelia Plugin System - Vue Listener Helpers
3
+ *
4
+ * Vue composables for event listener management.
5
+ */
6
+
7
+ import { ref, onUnmounted } from 'vue';
8
+ import { useMycelia } from './index.js';
9
+
10
+ /**
11
+ * useListener - Register an event listener with automatic cleanup
12
+ *
13
+ * @param {string} eventName - Event name/path to listen for
14
+ * @param {Function} handler - Handler function: (message) => void
15
+ *
16
+ * @example
17
+ * ```js
18
+ * import { useListener } from 'mycelia-kernel-plugin/vue';
19
+ *
20
+ * export default {
21
+ * setup() {
22
+ * useListener('user:created', (msg) => {
23
+ * console.log('User created:', msg.body);
24
+ * });
25
+ * }
26
+ * }
27
+ * ```
28
+ */
29
+ export function useListener(eventName, handler) {
30
+ const system = useMycelia();
31
+ const listeners = system?.listeners; // useListeners facet
32
+
33
+ if (!listeners || !listeners.hasListeners?.()) {
34
+ return;
35
+ }
36
+
37
+ listeners.on(eventName, handler);
38
+
39
+ onUnmounted(() => {
40
+ listeners.off?.(eventName, handler);
41
+ });
42
+ }
43
+
44
+ /**
45
+ * useEventStream - Subscribe to events and keep them in reactive state
46
+ *
47
+ * @param {string} eventName - Event name/path to listen for
48
+ * @param {Object} [options={}] - Options
49
+ * @param {boolean} [options.accumulate=false] - If true, accumulate events in array
50
+ * @returns {import('vue').Ref<any|any[]|null>} Reactive ref to latest event value, array of events, or null
51
+ *
52
+ * @example
53
+ * ```js
54
+ * import { useEventStream } from 'mycelia-kernel-plugin/vue';
55
+ *
56
+ * export default {
57
+ * setup() {
58
+ * const events = useEventStream('todo:created', { accumulate: true });
59
+ * return { events };
60
+ * }
61
+ * }
62
+ * ```
63
+ */
64
+ export function useEventStream(eventName, options = {}) {
65
+ const { accumulate = false } = options;
66
+ const value = ref(accumulate ? [] : null);
67
+
68
+ useListener(eventName, (msg) => {
69
+ if (accumulate) {
70
+ value.value = [...value.value, msg.body];
71
+ } else {
72
+ value.value = msg.body;
73
+ }
74
+ });
75
+
76
+ return value;
77
+ }
78
+
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Mycelia Plugin System - Vue Queue Helpers
3
+ *
4
+ * Vue composables for queue management.
5
+ */
6
+
7
+ import { ref, onMounted, onUnmounted } from 'vue';
8
+ import { useFacet } from './index.js';
9
+
10
+ /**
11
+ * useQueueStatus - Get queue status with reactive updates
12
+ *
13
+ * @returns {import('vue').Ref<Object>} Reactive ref to queue status object
14
+ * @returns {number} size - Current queue size
15
+ * @returns {number} capacity - Maximum queue capacity
16
+ * @returns {number} utilization - Utilization ratio (0-1)
17
+ * @returns {boolean} isFull - Whether queue is full
18
+ *
19
+ * @example
20
+ * ```js
21
+ * import { useQueueStatus } from 'mycelia-kernel-plugin/vue';
22
+ *
23
+ * export default {
24
+ * setup() {
25
+ * const status = useQueueStatus();
26
+ * return { status };
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ export function useQueueStatus() {
32
+ const queue = useFacet('queue');
33
+ const status = ref({
34
+ size: 0,
35
+ capacity: 0,
36
+ utilization: 0,
37
+ isFull: false
38
+ });
39
+
40
+ let intervalId = null;
41
+
42
+ const updateStatus = () => {
43
+ if (queue.value?.getQueueStatus) {
44
+ const newStatus = queue.value.getQueueStatus();
45
+ status.value = {
46
+ size: newStatus.size || 0,
47
+ capacity: newStatus.maxSize || 0,
48
+ utilization: newStatus.utilization || 0,
49
+ isFull: newStatus.isFull || false
50
+ };
51
+ }
52
+ };
53
+
54
+ onMounted(() => {
55
+ updateStatus();
56
+ intervalId = setInterval(updateStatus, 100); // Poll every 100ms
57
+ });
58
+
59
+ onUnmounted(() => {
60
+ if (intervalId) {
61
+ clearInterval(intervalId);
62
+ }
63
+ });
64
+
65
+ return status;
66
+ }
67
+
68
+ /**
69
+ * useQueueDrain - Automatically drain queue on mount
70
+ *
71
+ * @param {Object} [options={}] - Options
72
+ * @param {number} [options.interval=100] - Polling interval in ms
73
+ * @param {Function} [options.onMessage] - Callback for each message: (msg, options) => void
74
+ *
75
+ * @example
76
+ * ```js
77
+ * import { useQueueDrain } from 'mycelia-kernel-plugin/vue';
78
+ *
79
+ * export default {
80
+ * setup() {
81
+ * useQueueDrain({
82
+ * interval: 50,
83
+ * onMessage: (msg) => console.log('Processed:', msg)
84
+ * });
85
+ * }
86
+ * }
87
+ * ```
88
+ */
89
+ export function useQueueDrain(options = {}) {
90
+ const { interval = 100, onMessage } = options;
91
+ const queue = useFacet('queue');
92
+ let processInterval = null;
93
+
94
+ onMounted(() => {
95
+ if (!queue.value || !queue.value.hasMessagesToProcess) return;
96
+
97
+ processInterval = setInterval(() => {
98
+ if (queue.value?.hasMessagesToProcess()) {
99
+ const next = queue.value.selectNextMessage();
100
+ if (next && onMessage) {
101
+ onMessage(next.msg, next.options);
102
+ }
103
+ }
104
+ }, interval);
105
+ });
106
+
107
+ onUnmounted(() => {
108
+ if (processInterval) {
109
+ clearInterval(processInterval);
110
+ }
111
+ });
112
+ }
113
+