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.
- package/README.md +89 -6
- package/bin/cli.js +769 -3
- 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/core/facet.js +16 -3
- package/src/index.js +18 -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/svelte/builders.js +43 -0
- package/src/svelte/index.js +183 -0
- package/src/svelte/listeners.js +96 -0
- package/src/svelte/queues.js +114 -0
- package/src/svelte/stores.js +36 -0
- package/src/utils/instrumentation.js +204 -0
- package/src/utils/use-base.js +205 -30
- package/src/vue/builders.js +40 -0
- package/src/vue/composables.js +37 -0
- package/src/vue/index.js +252 -0
- package/src/vue/listeners.js +78 -0
- package/src/vue/queues.js +113 -0
package/src/utils/use-base.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { StandalonePluginSystem } from '../system/standalone-plugin-system.js';
|
|
2
|
+
import { BaseSubsystem } from '../system/base-subsystem.js';
|
|
2
3
|
import { deepMerge } from '../builder/context-resolver.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* useBase - Fluent API Builder for StandalonePluginSystem
|
|
6
|
+
* useBase - Fluent API Builder for StandalonePluginSystem / BaseSubsystem
|
|
6
7
|
*
|
|
7
8
|
* Provides a convenient, chainable API for creating and configuring
|
|
8
|
-
* StandalonePluginSystem instances.
|
|
9
|
+
* StandalonePluginSystem or BaseSubsystem instances via a fluent API.
|
|
9
10
|
*
|
|
10
11
|
* @param {string} name - Unique name for the plugin system
|
|
11
12
|
* @param {Object} [options={}] - Initial configuration options
|
|
@@ -14,6 +15,9 @@ import { deepMerge } from '../builder/context-resolver.js';
|
|
|
14
15
|
* @param {Array} [options.defaultHooks=[]] - Optional default hooks to install
|
|
15
16
|
* @returns {UseBaseBuilder} Builder instance with fluent API
|
|
16
17
|
*
|
|
18
|
+
* By default it uses StandalonePluginSystem; call .setBase(BaseSubsystem)
|
|
19
|
+
* to build a subsystem instead.
|
|
20
|
+
*
|
|
17
21
|
* @example
|
|
18
22
|
* ```javascript
|
|
19
23
|
* import { useBase } from 'mycelia-kernel-plugin';
|
|
@@ -35,24 +39,73 @@ export function useBase(name, options = {}) {
|
|
|
35
39
|
throw new Error('useBase: name must be a non-empty string');
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
// Create
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Create builder with fluent API
|
|
42
|
-
return new UseBaseBuilder(system);
|
|
42
|
+
// Create builder with fluent API (system will be created lazily)
|
|
43
|
+
return new UseBaseBuilder(name, options);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
* UseBaseBuilder - Fluent API builder for StandalonePluginSystem
|
|
47
|
+
* UseBaseBuilder - Fluent API builder for StandalonePluginSystem or BaseSubsystem
|
|
47
48
|
*
|
|
48
49
|
* Provides chainable methods for configuring and building the system.
|
|
49
50
|
*/
|
|
50
51
|
class UseBaseBuilder {
|
|
51
|
-
#
|
|
52
|
+
#name;
|
|
53
|
+
#options;
|
|
54
|
+
#BaseClass = StandalonePluginSystem; // Default to StandalonePluginSystem
|
|
55
|
+
#system = null; // Lazy initialization
|
|
52
56
|
#pendingConfig = {};
|
|
53
57
|
|
|
54
|
-
constructor(
|
|
55
|
-
this.#
|
|
58
|
+
constructor(name, options) {
|
|
59
|
+
this.#name = name;
|
|
60
|
+
this.#options = options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get or create the system instance (lazy initialization)
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
#getSystem() {
|
|
68
|
+
if (!this.#system) {
|
|
69
|
+
this.#system = new this.#BaseClass(this.#name, this.#options);
|
|
70
|
+
}
|
|
71
|
+
return this.#system;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set the base class for the system
|
|
76
|
+
*
|
|
77
|
+
* @param {Function} BaseClass - The base class to use. Must be BaseSubsystem or any class that extends BaseSubsystem
|
|
78
|
+
* @returns {UseBaseBuilder} This builder for chaining
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```javascript
|
|
82
|
+
* import { BaseSubsystem } from 'mycelia-kernel-plugin';
|
|
83
|
+
*
|
|
84
|
+
* builder.setBase(BaseSubsystem);
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```javascript
|
|
89
|
+
* // Must be called before any methods that use the system
|
|
90
|
+
* const system = await useBase('my-app')
|
|
91
|
+
* .setBase(BaseSubsystem)
|
|
92
|
+
* .use(useDatabase)
|
|
93
|
+
* .build();
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
setBase(BaseClass) {
|
|
97
|
+
if (this.#system) {
|
|
98
|
+
throw new Error('useBase.setBase: cannot change base class after system is created');
|
|
99
|
+
}
|
|
100
|
+
if (typeof BaseClass !== 'function') {
|
|
101
|
+
throw new Error('useBase.setBase: BaseClass must be a constructor function');
|
|
102
|
+
}
|
|
103
|
+
// Validate it's a subclass of BaseSubsystem
|
|
104
|
+
if (BaseClass !== BaseSubsystem && !(BaseClass.prototype instanceof BaseSubsystem)) {
|
|
105
|
+
throw new Error('useBase.setBase: BaseClass must be BaseSubsystem or a subclass of BaseSubsystem');
|
|
106
|
+
}
|
|
107
|
+
this.#BaseClass = BaseClass;
|
|
108
|
+
return this;
|
|
56
109
|
}
|
|
57
110
|
|
|
58
111
|
/**
|
|
@@ -70,7 +123,7 @@ class UseBaseBuilder {
|
|
|
70
123
|
if (typeof hook !== 'function') {
|
|
71
124
|
throw new Error('useBase.use: hook must be a function');
|
|
72
125
|
}
|
|
73
|
-
this.#
|
|
126
|
+
this.#getSystem().use(hook);
|
|
74
127
|
return this;
|
|
75
128
|
}
|
|
76
129
|
|
|
@@ -93,6 +146,75 @@ class UseBaseBuilder {
|
|
|
93
146
|
return this;
|
|
94
147
|
}
|
|
95
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Register multiple hooks at once
|
|
151
|
+
*
|
|
152
|
+
* @param {Array<Function>} hooks - Array of hook functions to register
|
|
153
|
+
* @returns {UseBaseBuilder} This builder for chaining
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```javascript
|
|
157
|
+
* builder.useMultiple([useDatabase, useCache, useAuth]);
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```javascript
|
|
162
|
+
* // Can be combined with other methods
|
|
163
|
+
* builder
|
|
164
|
+
* .use(useLogger)
|
|
165
|
+
* .useMultiple([useDatabase, useCache])
|
|
166
|
+
* .use(useAuth);
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
useMultiple(hooks) {
|
|
170
|
+
if (!Array.isArray(hooks)) {
|
|
171
|
+
throw new Error('useBase.useMultiple: hooks must be an array');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const system = this.#getSystem();
|
|
175
|
+
for (const hook of hooks) {
|
|
176
|
+
if (typeof hook !== 'function') {
|
|
177
|
+
throw new Error('useBase.useMultiple: all hooks must be functions');
|
|
178
|
+
}
|
|
179
|
+
system.use(hook);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Conditionally register multiple hooks
|
|
187
|
+
*
|
|
188
|
+
* @param {boolean} condition - Whether to register the hooks
|
|
189
|
+
* @param {Array<Function>} hooks - Array of hook functions to register if condition is true
|
|
190
|
+
* @returns {UseBaseBuilder} This builder for chaining
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```javascript
|
|
194
|
+
* builder.useIfMultiple(process.env.NODE_ENV === 'development', [
|
|
195
|
+
* useDebugTools,
|
|
196
|
+
* useDevLogger
|
|
197
|
+
* ]);
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```javascript
|
|
202
|
+
* const optionalHooks = [];
|
|
203
|
+
* if (enableCache) optionalHooks.push(useCache);
|
|
204
|
+
* if (enableAuth) optionalHooks.push(useAuth);
|
|
205
|
+
*
|
|
206
|
+
* builder
|
|
207
|
+
* .use(useDatabase)
|
|
208
|
+
* .useIfMultiple(optionalHooks.length > 0, optionalHooks);
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
useIfMultiple(condition, hooks) {
|
|
212
|
+
if (condition) {
|
|
213
|
+
return this.useMultiple(hooks);
|
|
214
|
+
}
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
96
218
|
/**
|
|
97
219
|
* Add or update configuration for a specific facet kind
|
|
98
220
|
*
|
|
@@ -122,13 +244,18 @@ class UseBaseBuilder {
|
|
|
122
244
|
throw new Error('useBase.config: kind must be a non-empty string');
|
|
123
245
|
}
|
|
124
246
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
247
|
+
// Get existing config for this kind (from pending or system if created)
|
|
248
|
+
let existingConfig;
|
|
249
|
+
if (this.#system) {
|
|
250
|
+
if (!this.#system.ctx.config || typeof this.#system.ctx.config !== 'object') {
|
|
251
|
+
this.#system.ctx.config = {};
|
|
252
|
+
}
|
|
253
|
+
existingConfig = this.#system.ctx.config[kind];
|
|
254
|
+
} else {
|
|
255
|
+
// System not created yet, check pending config
|
|
256
|
+
existingConfig = this.#pendingConfig[kind];
|
|
128
257
|
}
|
|
129
258
|
|
|
130
|
-
// Get existing config for this kind
|
|
131
|
-
const existingConfig = this.#system.ctx.config[kind];
|
|
132
259
|
const pendingConfig = this.#pendingConfig[kind];
|
|
133
260
|
|
|
134
261
|
// Determine the base config (existing or pending)
|
|
@@ -153,6 +280,49 @@ class UseBaseBuilder {
|
|
|
153
280
|
return this;
|
|
154
281
|
}
|
|
155
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Add or update configurations for multiple facet kinds at once
|
|
285
|
+
*
|
|
286
|
+
* Configurations are merged when possible (deep merge for objects).
|
|
287
|
+
*
|
|
288
|
+
* @param {Object} configs - Object where keys are facet kinds and values are configurations
|
|
289
|
+
* @returns {UseBaseBuilder} This builder for chaining
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```javascript
|
|
293
|
+
* builder.configMultiple({
|
|
294
|
+
* database: { host: 'localhost', port: 5432 },
|
|
295
|
+
* cache: { ttl: 3600 },
|
|
296
|
+
* auth: { secret: 'abc123' }
|
|
297
|
+
* });
|
|
298
|
+
* ```
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```javascript
|
|
302
|
+
* // Merge configurations
|
|
303
|
+
* builder
|
|
304
|
+
* .configMultiple({
|
|
305
|
+
* database: { host: 'localhost' },
|
|
306
|
+
* cache: { ttl: 3600 }
|
|
307
|
+
* })
|
|
308
|
+
* .configMultiple({
|
|
309
|
+
* database: { port: 5432 }, // Merges with existing
|
|
310
|
+
* auth: { secret: 'abc123' }
|
|
311
|
+
* });
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
configMultiple(configs) {
|
|
315
|
+
if (!configs || typeof configs !== 'object' || Array.isArray(configs)) {
|
|
316
|
+
throw new Error('useBase.configMultiple: configs must be an object');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
for (const [kind, config] of Object.entries(configs)) {
|
|
320
|
+
this.config(kind, config);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return this;
|
|
324
|
+
}
|
|
325
|
+
|
|
156
326
|
/**
|
|
157
327
|
* Add an initialization callback
|
|
158
328
|
*
|
|
@@ -170,7 +340,7 @@ class UseBaseBuilder {
|
|
|
170
340
|
if (typeof callback !== 'function') {
|
|
171
341
|
throw new Error('useBase.onInit: callback must be a function');
|
|
172
342
|
}
|
|
173
|
-
this.#
|
|
343
|
+
this.#getSystem().onInit(callback);
|
|
174
344
|
return this;
|
|
175
345
|
}
|
|
176
346
|
|
|
@@ -191,7 +361,7 @@ class UseBaseBuilder {
|
|
|
191
361
|
if (typeof callback !== 'function') {
|
|
192
362
|
throw new Error('useBase.onDispose: callback must be a function');
|
|
193
363
|
}
|
|
194
|
-
this.#
|
|
364
|
+
this.#getSystem().onDispose(callback);
|
|
195
365
|
return this;
|
|
196
366
|
}
|
|
197
367
|
|
|
@@ -212,16 +382,18 @@ class UseBaseBuilder {
|
|
|
212
382
|
* ```
|
|
213
383
|
*/
|
|
214
384
|
async build(ctx = {}) {
|
|
385
|
+
const system = this.#getSystem();
|
|
386
|
+
|
|
215
387
|
// Apply pending configurations
|
|
216
388
|
if (Object.keys(this.#pendingConfig).length > 0) {
|
|
217
389
|
// Merge pending config into system config
|
|
218
|
-
if (!
|
|
219
|
-
|
|
390
|
+
if (!system.ctx.config || typeof system.ctx.config !== 'object') {
|
|
391
|
+
system.ctx.config = {};
|
|
220
392
|
}
|
|
221
393
|
|
|
222
394
|
// Deep merge pending configs
|
|
223
395
|
for (const [kind, config] of Object.entries(this.#pendingConfig)) {
|
|
224
|
-
const existing =
|
|
396
|
+
const existing = system.ctx.config[kind];
|
|
225
397
|
if (
|
|
226
398
|
existing &&
|
|
227
399
|
typeof existing === 'object' &&
|
|
@@ -230,9 +402,9 @@ class UseBaseBuilder {
|
|
|
230
402
|
typeof config === 'object' &&
|
|
231
403
|
!Array.isArray(config)
|
|
232
404
|
) {
|
|
233
|
-
|
|
405
|
+
system.ctx.config[kind] = deepMerge(existing, config);
|
|
234
406
|
} else {
|
|
235
|
-
|
|
407
|
+
system.ctx.config[kind] = config;
|
|
236
408
|
}
|
|
237
409
|
}
|
|
238
410
|
|
|
@@ -243,20 +415,23 @@ class UseBaseBuilder {
|
|
|
243
415
|
// Merge any additional context
|
|
244
416
|
if (ctx && typeof ctx === 'object' && !Array.isArray(ctx)) {
|
|
245
417
|
if (ctx.config && typeof ctx.config === 'object' && !Array.isArray(ctx.config)) {
|
|
246
|
-
if (!
|
|
247
|
-
|
|
418
|
+
if (!system.ctx.config) {
|
|
419
|
+
system.ctx.config = {};
|
|
248
420
|
}
|
|
249
|
-
|
|
421
|
+
system.ctx.config = deepMerge(system.ctx.config, ctx.config);
|
|
250
422
|
}
|
|
251
423
|
// Merge other ctx properties (shallow)
|
|
252
|
-
Object.assign(
|
|
424
|
+
Object.assign(system.ctx, ctx);
|
|
253
425
|
}
|
|
254
426
|
|
|
255
427
|
// Build the system
|
|
256
|
-
await
|
|
428
|
+
await system.build(ctx);
|
|
257
429
|
|
|
258
430
|
// Return the system instance
|
|
259
|
-
return
|
|
431
|
+
return system;
|
|
260
432
|
}
|
|
261
433
|
}
|
|
262
434
|
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mycelia Plugin System - Vue Builder Helpers
|
|
3
|
+
*
|
|
4
|
+
* Vue utilities for creating system builders.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* createVueSystemBuilder - 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 { createVueSystemBuilder } from 'mycelia-kernel-plugin/vue';
|
|
18
|
+
*
|
|
19
|
+
* const buildTodoSystem = createVueSystemBuilder('todo-app', (b) =>
|
|
20
|
+
* b
|
|
21
|
+
* .config('database', { host: 'localhost' })
|
|
22
|
+
* .use(useDatabase)
|
|
23
|
+
* .use(useListeners)
|
|
24
|
+
* );
|
|
25
|
+
*
|
|
26
|
+
* // Then use in plugin
|
|
27
|
+
* app.use(MyceliaPlugin, { build: buildTodoSystem });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createVueSystemBuilder(name, configure) {
|
|
31
|
+
return async function build() {
|
|
32
|
+
// Import useBase - users should have it available
|
|
33
|
+
// This avoids bundling issues by letting users import useBase themselves
|
|
34
|
+
const { useBase } = await import('../utils/use-base.js');
|
|
35
|
+
let builder = useBase(name);
|
|
36
|
+
builder = configure(builder);
|
|
37
|
+
return builder.build();
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mycelia Plugin System - Vue Composable Generator
|
|
3
|
+
*
|
|
4
|
+
* Utility for generating custom composables for specific facets.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useFacet } from './index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* createFacetComposable - Generate a custom composable for a specific facet kind
|
|
11
|
+
*
|
|
12
|
+
* @param {string} kind - Facet kind identifier
|
|
13
|
+
* @returns {Function} Custom composable: () => import('vue').Ref<Object|null>
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```js
|
|
17
|
+
* // In composables/todo.js
|
|
18
|
+
* import { createFacetComposable } from 'mycelia-kernel-plugin/vue';
|
|
19
|
+
*
|
|
20
|
+
* export const useTodos = createFacetComposable('todos');
|
|
21
|
+
* export const useAuth = createFacetComposable('auth');
|
|
22
|
+
*
|
|
23
|
+
* // In component
|
|
24
|
+
* export default {
|
|
25
|
+
* setup() {
|
|
26
|
+
* const todos = useTodos();
|
|
27
|
+
* // Use todos.value...
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createFacetComposable(kind) {
|
|
33
|
+
return function useNamedFacet() {
|
|
34
|
+
return useFacet(kind);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
package/src/vue/index.js
ADDED
|
@@ -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
|
+
|