mycelia-kernel-plugin 1.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Mycelia Plugin System
2
2
 
3
- A sophisticated, **framework-agnostic** plugin system with transaction safety, lifecycle management, and official bindings for React and Vue 3.
3
+ A sophisticated, **framework-agnostic** plugin system with transaction safety, lifecycle management, and official bindings for React, Vue 3, Svelte, Angular, Qwik, and Solid.js.
4
4
 
5
5
  ## Overview
6
6
 
@@ -15,7 +15,7 @@ Mycelia Plugin System is a **framework-agnostic**, standalone plugin architectur
15
15
  - **Facet contracts** - Runtime validation of plugin interfaces
16
16
  - **Standalone mode** - Works without message system or other dependencies
17
17
  - **Built-in hooks** - Ships with `useListeners` for event-driven architectures (see [Simple Event System Example](#simple-event-system-example)), plus `useQueue` and `useSpeak`
18
- - **Framework bindings** - Official bindings for [React](#react-bindings), [Vue 3](#vue-bindings), and [Svelte](#svelte-bindings) with more coming soon
18
+ - **Framework bindings** - Official bindings for [React](#react-bindings), [Vue 3](#vue-bindings), [Svelte](#svelte-bindings), [Angular](#angular-bindings), [Qwik](#qwik-bindings), and [Solid.js](#solidjs-bindings)
19
19
 
20
20
  **Facets** are the concrete runtime capabilities produced by hooks and attached to the system.
21
21
 
@@ -26,9 +26,12 @@ The system is designed to be framework-agnostic. Your domain logic lives in Myce
26
26
  - **React** - Use `MyceliaProvider` and React hooks (`useFacet`, `useListener`)
27
27
  - **Vue 3** - Use `MyceliaPlugin` and Vue composables (`useFacet`, `useListener`)
28
28
  - **Svelte** - Use `setMyceliaSystem` and Svelte stores (`useFacet`, `useListener`)
29
+ - **Angular** - Use `MyceliaService` and RxJS observables (`useFacet`, `useListener`)
30
+ - **Qwik** - Use `MyceliaProvider` and Qwik signals (`useFacet`, `useListener`)
31
+ - **Solid.js** - Use `MyceliaProvider` and Solid.js signals (`useFacet`, `useListener`)
29
32
  - **Vanilla JS/Node.js** - Use the system directly without any framework bindings
30
33
 
31
- See the [React Todo App](./examples/react-todo/README.md), [Vue Todo App](./examples/vue-todo/README.md), and [Svelte Todo App](./examples/svelte-todo/README.md) examples - they all use the **exact same plugin code**, demonstrating true framework independence.
34
+ See the [React Todo App](./examples/react-todo/README.md), [Vue Todo App](./examples/vue-todo/README.md), [Svelte Todo App](./examples/svelte-todo/README.md), and [Solid.js Todo App](./examples/solid-todo/README.md) examples - they all use the **exact same plugin code**, demonstrating true framework independence.
32
35
 
33
36
  ## Quick Start
34
37
 
@@ -75,6 +78,15 @@ const system = await useBase('my-app')
75
78
  .use(useDatabase)
76
79
  .build();
77
80
 
81
+ // Or configure multiple facets and hooks at once
82
+ const system = await useBase('my-app')
83
+ .configMultiple({
84
+ database: { host: 'localhost', port: 5432 },
85
+ cache: { ttl: 3600 }
86
+ })
87
+ .useMultiple([useDatabase, useCache])
88
+ .build();
89
+
78
90
  // Use the plugin
79
91
  const db = system.find('database');
80
92
  await db.query('SELECT * FROM users');
@@ -284,7 +296,7 @@ StandalonePluginSystem
284
296
 
285
297
  - **`createHook()`** - Create a plugin hook
286
298
  - **`createFacetContract()`** - Create a facet contract
287
- - **`useBase()`** - Fluent API builder for StandalonePluginSystem
299
+ - **`useBase()`** - Fluent API builder for StandalonePluginSystem (see [useBase Documentation](./docs/utils/USE-BASE.md))
288
300
 
289
301
  ### Utilities
290
302
 
@@ -295,12 +307,19 @@ StandalonePluginSystem
295
307
 
296
308
  Comprehensive documentation is available in the [`docs/`](./docs/) directory:
297
309
 
310
+ ### Quick Links
311
+ - **[useBase Guide](./docs/utils/USE-BASE.md)** - Complete guide to the fluent API builder with all methods
312
+ - **[Instrumentation](./docs/instrumentation.md)** - Debugging and performance instrumentation
313
+
314
+ ### Full Documentation
298
315
  - **[Getting Started Guide](./docs/getting-started/README.md)** - Quick start with examples
299
316
  - **[Hooks and Facets Overview](./docs/core-concepts/HOOKS-AND-FACETS-OVERVIEW.md)** - Core concepts
300
317
  - **[Built-in Hooks](./docs/hooks/README.md)** - Documentation for `useListeners`, `useQueue`, and `useSpeak`
301
318
  - **[React Bindings](./docs/react/README.md)** - React integration utilities (`MyceliaProvider`, `useFacet`, `useListener`)
302
319
  - **[Vue Bindings](./docs/vue/README.md)** - Vue 3 integration utilities (`MyceliaPlugin`, `useFacet`, `useListener`) ⭐
303
320
  - **[Svelte Bindings](./docs/svelte/README.md)** - Svelte integration utilities (`setMyceliaSystem`, `useFacet`, `useListener`) ⭐
321
+ - **[Angular Bindings](./docs/angular/README.md)** - Angular integration utilities (`MyceliaService`, `useFacet`, `useListener`) ⭐
322
+ - **[Qwik Bindings](./docs/qwik/README.md)** - Qwik integration utilities (`MyceliaProvider`, `useFacet`, `useListener`) ⭐
304
323
  - **[Standalone Plugin System](./docs/standalone/STANDALONE-PLUGIN-SYSTEM.md)** - Complete usage guide
305
324
  - **[Documentation Index](./docs/README.md)** - Full documentation index
306
325
 
@@ -328,12 +347,25 @@ See the `examples/` directory for:
328
347
  - Composition API integration with reactive state management
329
348
 
330
349
  - **[Svelte Todo App](./examples/svelte-todo/README.md)** ⭐ – A complete Svelte example demonstrating:
331
- - **Framework-agnostic plugins** - Uses the same shared plugin code as React and Vue examples
350
+ - **[Solid.js Todo App](./examples/solid-todo/README.md)** A complete Solid.js example demonstrating:
351
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, and Svelte examples
332
352
  - Event-driven state synchronization (`todos:changed` events)
333
- - Svelte bindings (`setMyceliaSystem`, `useFacet`, `useListener`)
334
- - Store-based reactivity with automatic subscription
353
+ - Solid.js bindings (`MyceliaProvider`, `useFacet`, `useListener`)
354
+ - Signal-based reactivity with automatic updates
335
355
 
336
- All three examples use the **exact same Mycelia plugin code** from `examples/todo-shared/`, proving that plugins are truly framework-independent. Write your domain logic once, use it everywhere!
356
+ - **[Angular Todo App](./examples/angular-todo/README.md)** A complete Angular example demonstrating:
357
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as React, Vue, and Svelte examples
358
+ - Event-driven state synchronization (`todos:changed` events)
359
+ - Angular bindings (`MyceliaService`, `useFacet`, `useListener`)
360
+ - RxJS observables for reactive state management
361
+
362
+ - **[Qwik Todo App](./examples/qwik-todo/README.md)** ⭐ – A complete Qwik example demonstrating:
363
+ - **Framework-agnostic plugins** - Uses the same shared plugin code as all other framework examples
364
+ - Event-driven state synchronization (`todos:changed` events)
365
+ - Qwik bindings (`MyceliaProvider`, `useFacet`, `useListener`)
366
+ - Qwik signals for reactive state management
367
+
368
+ All six examples use the **exact same Mycelia plugin code** from `examples/todo-shared/`, proving that plugins are truly framework-independent. Write your domain logic once, use it everywhere!
337
369
 
338
370
  ## CLI Tool
339
371
 
@@ -357,6 +389,15 @@ npx mycelia-kernel-plugin init vue my-vue-app
357
389
 
358
390
  # Initialize a Svelte project with Mycelia bindings
359
391
  npx mycelia-kernel-plugin init svelte my-svelte-app
392
+
393
+ # Initialize an Angular project with Mycelia bindings
394
+ npx mycelia-kernel-plugin init angular my-angular-app
395
+
396
+ # Initialize a Qwik project with Mycelia bindings
397
+ npx mycelia-kernel-plugin init qwik my-qwik-app
398
+
399
+ # Initialize a Solid.js project with Mycelia bindings
400
+ npx mycelia-kernel-plugin init solid my-solid-app
360
401
  ```
361
402
 
362
403
  Or install globally:
package/bin/cli.js CHANGED
@@ -74,6 +74,12 @@ function handleInit(args) {
74
74
  } else if (args[0] === 'svelte') {
75
75
  const projectName = args[1] || 'my-svelte-app';
76
76
  initSvelteProject(projectName);
77
+ } else if (args[0] === 'angular') {
78
+ const projectName = args[1] || 'my-angular-app';
79
+ initAngularProject(projectName);
80
+ } else if (args[0] === 'qwik') {
81
+ const projectName = args[1] || 'my-qwik-app';
82
+ initQwikProject(projectName);
77
83
  } else {
78
84
  const projectName = args[0];
79
85
  initProject(projectName);
@@ -867,6 +873,257 @@ dist/
867
873
  console.log(` npm run dev`);
868
874
  }
869
875
 
876
+ /**
877
+ * Initialize an Angular project with Mycelia bindings
878
+ */
879
+ function initAngularProject(projectName) {
880
+ const projectDir = projectName;
881
+
882
+ if (existsSync(projectDir)) {
883
+ console.error(`Error: Directory already exists: ${projectDir}`);
884
+ process.exit(1);
885
+ }
886
+
887
+ console.log(`Creating Angular project: ${projectName}...`);
888
+ console.log(`\nNote: This creates a basic Angular project structure.`);
889
+ console.log(`You may need to run 'ng new' separately for a full Angular CLI setup.`);
890
+
891
+ // Create directory structure
892
+ mkdirSync(projectDir, { recursive: true });
893
+ mkdirSync(join(projectDir, 'src'), { recursive: true });
894
+ mkdirSync(join(projectDir, 'src/app'), { recursive: true });
895
+ mkdirSync(join(projectDir, 'src/app/services'), { recursive: true });
896
+ mkdirSync(join(projectDir, 'src/app/components'), { recursive: true });
897
+ mkdirSync(join(projectDir, 'src/mycelia'), { recursive: true });
898
+
899
+ // Create package.json
900
+ const packageJson = {
901
+ name: projectName,
902
+ version: '1.0.0',
903
+ type: 'module',
904
+ scripts: {
905
+ start: 'ng serve',
906
+ build: 'ng build',
907
+ test: 'ng test'
908
+ },
909
+ dependencies: {
910
+ 'mycelia-kernel-plugin': '^1.3.0',
911
+ '@angular/core': '^17.0.0',
912
+ '@angular/common': '^17.0.0',
913
+ 'rxjs': '^7.8.0'
914
+ },
915
+ devDependencies: {
916
+ '@angular/cli': '^17.0.0',
917
+ '@angular/compiler-cli': '^17.0.0',
918
+ 'typescript': '^5.0.0'
919
+ }
920
+ };
921
+
922
+ writeFileSync(
923
+ join(projectDir, 'package.json'),
924
+ JSON.stringify(packageJson, null, 2),
925
+ 'utf8'
926
+ );
927
+
928
+ // Create Mycelia service
929
+ const myceliaService = `import { Injectable } from '@angular/core';
930
+ import { BehaviorSubject, Observable } from 'rxjs';
931
+ import { createMyceliaService } from 'mycelia-kernel-plugin/angular';
932
+ import { buildSystem } from '../mycelia/system.builder.js';
933
+
934
+ @Injectable({ providedIn: 'root' })
935
+ export class MyceliaService {
936
+ private service = createMyceliaService(buildSystem);
937
+
938
+ get system$(): Observable<any> {
939
+ return this.service.system$;
940
+ }
941
+
942
+ getSystem() {
943
+ return this.service.getSystem();
944
+ }
945
+
946
+ useFacet(kind: string) {
947
+ return this.service.useFacet(kind);
948
+ }
949
+
950
+ useListener(eventName: string, handler: Function) {
951
+ return this.service.useListener(eventName, handler);
952
+ }
953
+
954
+ async dispose() {
955
+ await this.service.dispose();
956
+ }
957
+ }
958
+ `;
959
+
960
+ writeFileSync(join(projectDir, 'src/app/services/mycelia.service.ts'), myceliaService, 'utf8');
961
+
962
+ // Create system builder
963
+ const systemBuilder = `import { useBase } from 'mycelia-kernel-plugin';
964
+ import { useListeners } from 'mycelia-kernel-plugin';
965
+
966
+ export async function buildSystem() {
967
+ return useBase('${projectName}')
968
+ .use(useListeners)
969
+ .build();
970
+ }
971
+ `;
972
+
973
+ writeFileSync(join(projectDir, 'src/mycelia/system.builder.ts'), systemBuilder, 'utf8');
974
+
975
+ // Create README
976
+ const readme = `# ${projectName}
977
+
978
+ An Angular application built with Mycelia Plugin System.
979
+
980
+ ## Getting Started
981
+
982
+ \`\`\`bash
983
+ npm install
984
+ ng serve
985
+ \`\`\`
986
+
987
+ ## Structure
988
+
989
+ - \`src/app/services/\` - Angular services (including MyceliaService)
990
+ - \`src/app/components/\` - Angular components
991
+ - \`src/mycelia/\` - Mycelia plugin code (framework-agnostic)
992
+ `;
993
+
994
+ writeFileSync(join(projectDir, 'README.md'), readme, 'utf8');
995
+
996
+ console.log(`✅ Angular project created: ${projectDir}`);
997
+ console.log(`\nNext steps:`);
998
+ console.log(` cd ${projectDir}`);
999
+ console.log(` npm install`);
1000
+ console.log(` ng serve`);
1001
+ }
1002
+
1003
+ /**
1004
+ * Initialize a Qwik project with Mycelia bindings
1005
+ */
1006
+ function initQwikProject(projectName) {
1007
+ const projectDir = projectName;
1008
+
1009
+ if (existsSync(projectDir)) {
1010
+ console.error(`Error: Directory already exists: ${projectDir}`);
1011
+ process.exit(1);
1012
+ }
1013
+
1014
+ console.log(`Creating Qwik project: ${projectName}...`);
1015
+
1016
+ // Create directory structure
1017
+ mkdirSync(projectDir, { recursive: true });
1018
+ mkdirSync(join(projectDir, 'src'), { recursive: true });
1019
+ mkdirSync(join(projectDir, 'src/components'), { recursive: true });
1020
+ mkdirSync(join(projectDir, 'src/mycelia'), { recursive: true });
1021
+
1022
+ // Create package.json
1023
+ const packageJson = {
1024
+ name: projectName,
1025
+ version: '1.0.0',
1026
+ type: 'module',
1027
+ scripts: {
1028
+ dev: 'vite',
1029
+ build: 'vite build',
1030
+ preview: 'vite preview'
1031
+ },
1032
+ dependencies: {
1033
+ 'mycelia-kernel-plugin': '^1.3.0',
1034
+ '@builder.io/qwik': '^1.0.0',
1035
+ '@builder.io/qwik-city': '^1.0.0'
1036
+ },
1037
+ devDependencies: {
1038
+ 'vite': '^5.0.0',
1039
+ '@vitejs/plugin-qwik': '^1.0.0'
1040
+ }
1041
+ };
1042
+
1043
+ writeFileSync(
1044
+ join(projectDir, 'package.json'),
1045
+ JSON.stringify(packageJson, null, 2),
1046
+ 'utf8'
1047
+ );
1048
+
1049
+ // Create App component
1050
+ const appComponent = `import { component$ } from '@builder.io/qwik';
1051
+ import { MyceliaProvider } from 'mycelia-kernel-plugin/qwik';
1052
+ import { buildSystem } from './mycelia/system.builder.js';
1053
+ import { TodoApp } from './components/TodoApp';
1054
+
1055
+ export default component$(() => {
1056
+ return (
1057
+ <MyceliaProvider build={buildSystem}>
1058
+ <TodoApp />
1059
+ </MyceliaProvider>
1060
+ );
1061
+ });
1062
+ `;
1063
+
1064
+ writeFileSync(join(projectDir, 'src/App.tsx'), appComponent, 'utf8');
1065
+
1066
+ // Create system builder
1067
+ const systemBuilder = `import { useBase } from 'mycelia-kernel-plugin';
1068
+ import { useListeners } from 'mycelia-kernel-plugin';
1069
+
1070
+ export async function buildSystem() {
1071
+ return useBase('${projectName}')
1072
+ .use(useListeners)
1073
+ .build();
1074
+ }
1075
+ `;
1076
+
1077
+ writeFileSync(join(projectDir, 'src/mycelia/system.builder.ts'), systemBuilder, 'utf8');
1078
+
1079
+ // Create example component
1080
+ const todoComponent = `import { component$ } from '@builder.io/qwik';
1081
+ import { useFacet, useListener } from 'mycelia-kernel-plugin/qwik';
1082
+
1083
+ export const TodoApp = component$(() => {
1084
+ const todos = useFacet('todos');
1085
+ useListener('todos:changed', (msg) => {
1086
+ console.log('Todos changed:', msg.body);
1087
+ });
1088
+
1089
+ return (
1090
+ <div>
1091
+ <h1>Todo App</h1>
1092
+ {todos.value && <p>Todos facet loaded</p>}
1093
+ </div>
1094
+ );
1095
+ });
1096
+ `;
1097
+
1098
+ writeFileSync(join(projectDir, 'src/components/TodoApp.tsx'), todoComponent, 'utf8');
1099
+
1100
+ // Create README
1101
+ const readme = `# ${projectName}
1102
+
1103
+ A Qwik application built with Mycelia Plugin System.
1104
+
1105
+ ## Getting Started
1106
+
1107
+ \`\`\`bash
1108
+ npm install
1109
+ npm run dev
1110
+ \`\`\`
1111
+
1112
+ ## Structure
1113
+
1114
+ - \`src/components/\` - Qwik components
1115
+ - \`src/mycelia/\` - Mycelia plugin code (framework-agnostic)
1116
+ `;
1117
+
1118
+ writeFileSync(join(projectDir, 'README.md'), readme, 'utf8');
1119
+
1120
+ console.log(`✅ Qwik project created: ${projectDir}`);
1121
+ console.log(`\nNext steps:`);
1122
+ console.log(` cd ${projectDir}`);
1123
+ console.log(` npm install`);
1124
+ console.log(` npm run dev`);
1125
+ }
1126
+
870
1127
  /**
871
1128
  * Show help message
872
1129
  */
@@ -884,6 +1141,8 @@ Commands:
884
1141
  init react [name] Initialize a React project with Mycelia bindings
885
1142
  init vue [name] Initialize a Vue 3 project with Mycelia bindings
886
1143
  init svelte [name] Initialize a Svelte project with Mycelia bindings
1144
+ init angular [name] Initialize an Angular project with Mycelia bindings
1145
+ init qwik [name] Initialize a Qwik project with Mycelia bindings
887
1146
  help Show this help message
888
1147
 
889
1148
  Examples:
@@ -893,6 +1152,8 @@ Examples:
893
1152
  mycelia-kernel-plugin init react my-react-app
894
1153
  mycelia-kernel-plugin init vue my-vue-app
895
1154
  mycelia-kernel-plugin init svelte my-svelte-app
1155
+ mycelia-kernel-plugin init angular my-angular-app
1156
+ mycelia-kernel-plugin init qwik my-qwik-app
896
1157
 
897
1158
  For more information, visit:
898
1159
  https://github.com/lesfleursdelanuitdev/mycelia-kernel-plugin-system
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mycelia-kernel-plugin",
3
- "version": "1.3.0",
4
- "description": "A sophisticated, dependency-aware plugin system with transaction safety and lifecycle management",
3
+ "version": "1.4.0",
4
+ "description": "A sophisticated, framework-agnostic plugin system with transaction safety, lifecycle management, and official bindings for React, Vue 3, Svelte, Angular, Qwik, and Solid.js",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "author": {
@@ -37,7 +37,10 @@
37
37
  "./contract/contracts": "./src/contract/contracts/index.js",
38
38
  "./react": "./src/react/index.js",
39
39
  "./vue": "./src/vue/index.js",
40
- "./svelte": "./src/svelte/index.js"
40
+ "./svelte": "./src/svelte/index.js",
41
+ "./angular": "./src/angular/index.js",
42
+ "./qwik": "./src/qwik/index.js",
43
+ "./solid": "./src/solid/index.js"
41
44
  },
42
45
  "files": [
43
46
  "src/",
@@ -56,7 +59,11 @@
56
59
  "peerDependencies": {
57
60
  "react": ">=16.8.0",
58
61
  "vue": ">=3.0.0",
59
- "svelte": ">=3.0.0"
62
+ "svelte": ">=3.0.0",
63
+ "@angular/core": ">=15.0.0",
64
+ "@builder.io/qwik": ">=1.0.0",
65
+ "solid-js": ">=1.0.0",
66
+ "rxjs": ">=7.0.0"
60
67
  },
61
68
  "devDependencies": {
62
69
  "@eslint/js": "^9.36.0",
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Angular Builder Helpers
3
+ */
4
+
5
+ /**
6
+ * createAngularSystemBuilder - Create a reusable system builder function for Angular
7
+ *
8
+ * @param {string} name - System name
9
+ * @param {Function} configure - Configuration function: (builder) => builder
10
+ * @returns {Function} Build function: () => Promise<System>
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { useBase } from 'mycelia-kernel-plugin';
15
+ *
16
+ * const buildTodoSystem = createAngularSystemBuilder('todo-app', (b) =>
17
+ * b
18
+ * .config('database', { host: 'localhost' })
19
+ * .use(useDatabase)
20
+ * .use(useListeners)
21
+ * );
22
+ *
23
+ * // Then use in MyceliaModule
24
+ * MyceliaModule.forRoot({ build: buildTodoSystem })
25
+ * ```
26
+ */
27
+ export function createAngularSystemBuilder(name, configure) {
28
+ return async function build() {
29
+ // Import useBase - users should have it available
30
+ const { useBase } = await import('../utils/use-base.js');
31
+ let builder = useBase(name);
32
+ builder = configure(builder);
33
+ return builder.build();
34
+ };
35
+ }
36
+
37
+
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Angular Helper Functions
3
+ *
4
+ * Helper functions for working with Mycelia in Angular components
5
+ */
6
+
7
+ /**
8
+ * useFacet - Get a facet by kind (for use in Angular components)
9
+ *
10
+ * @param {Object} myceliaService - MyceliaService instance
11
+ * @param {string} kind - Facet kind identifier
12
+ * @returns {import('rxjs').Observable} Observable that emits the facet
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * @Component({...})
17
+ * export class MyComponent {
18
+ * db$ = useFacet(this.mycelia, 'database');
19
+ * }
20
+ * ```
21
+ */
22
+ export function useFacet(myceliaService, kind) {
23
+ const { map } = require('rxjs/operators');
24
+ return myceliaService.system$.pipe(
25
+ map(system => system?.find?.(kind) ?? null)
26
+ );
27
+ }
28
+
29
+ /**
30
+ * useListener - Register an event listener in Angular component
31
+ *
32
+ * @param {Object} myceliaService - MyceliaService instance
33
+ * @param {string} eventName - Event name/path
34
+ * @param {Function} handler - Handler function
35
+ * @returns {Function} Unsubscribe function
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * @Component({...})
40
+ * export class MyComponent implements OnInit, OnDestroy {
41
+ * private unsubscribe: Function;
42
+ *
43
+ * ngOnInit() {
44
+ * this.unsubscribe = useListener(this.mycelia, 'user:created', (msg) => {
45
+ * console.log('User created:', msg.body);
46
+ * });
47
+ * }
48
+ *
49
+ * ngOnDestroy() {
50
+ * this.unsubscribe();
51
+ * }
52
+ * }
53
+ * ```
54
+ */
55
+ export function useListener(myceliaService, eventName, handler) {
56
+ return myceliaService.useListener(eventName, handler);
57
+ }
58
+
59
+ /**
60
+ * useEventStream - Subscribe to events as an RxJS observable
61
+ *
62
+ * @param {Object} myceliaService - MyceliaService instance
63
+ * @param {string} eventName - Event name/path
64
+ * @param {Object} [options={}] - Options
65
+ * @param {boolean} [options.accumulate=false] - If true, accumulate events
66
+ * @returns {import('rxjs').Observable} Observable that emits event messages
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * @Component({...})
71
+ * export class EventListComponent {
72
+ * events$ = useEventStream(this.mycelia, 'todo:created', { accumulate: true });
73
+ * }
74
+ * ```
75
+ */
76
+ export function useEventStream(myceliaService, eventName, options = {}) {
77
+ const { Subject } = require('rxjs');
78
+ const { scan, startWith } = require('rxjs/operators');
79
+ const { accumulate = false } = options;
80
+
81
+ const subject = new Subject();
82
+
83
+ const unsubscribe = myceliaService.useListener(eventName, (msg) => {
84
+ subject.next(msg.body);
85
+ });
86
+
87
+ let stream$ = subject.asObservable();
88
+
89
+ if (accumulate) {
90
+ stream$ = stream$.pipe(
91
+ scan((acc, value) => [...acc, value], []),
92
+ startWith([])
93
+ );
94
+ }
95
+
96
+ // Note: In Angular, you'd typically handle unsubscription in ngOnDestroy
97
+ // This is a simplified version - real implementation would need proper cleanup
98
+
99
+ return stream$;
100
+ }
101
+
102
+