@myko/ui-vue 4.4.2 → 4.4.6

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,40 @@
1
+ import { ComputedRef, Ref } from 'vue';
2
+ import { ConnectionStatus } from '@myko/core';
3
+
4
+ export interface UseConnectionReturn {
5
+ /** Current connection status (reactive) */
6
+ status: Ref<ConnectionStatus>;
7
+ /** Whether currently connected (computed) */
8
+ isConnected: ComputedRef<boolean>;
9
+ /** Whether currently connecting (computed) */
10
+ isConnecting: ComputedRef<boolean>;
11
+ /** Whether currently disconnected (computed) */
12
+ isDisconnected: ComputedRef<boolean>;
13
+ /** Connect to a server address */
14
+ connect: (address: string) => void;
15
+ /** Disconnect from the server */
16
+ disconnect: () => void;
17
+ }
18
+ /**
19
+ * Vue composable for managing Myko connection status.
20
+ *
21
+ * @example
22
+ * ```vue
23
+ * <script setup>
24
+ * import { useConnection } from '@myko/ui-vue'
25
+ *
26
+ * const { status, isConnected, connect, disconnect } = useConnection()
27
+ *
28
+ * function handleConnect() {
29
+ * connect('ws://localhost:5155')
30
+ * }
31
+ * </script>
32
+ *
33
+ * <template>
34
+ * <div>Status: {{ status }}</div>
35
+ * <button v-if="!isConnected" @click="handleConnect">Connect</button>
36
+ * <button v-else @click="disconnect">Disconnect</button>
37
+ * </template>
38
+ * ```
39
+ */
40
+ export declare function useConnection(): UseConnectionReturn;
@@ -0,0 +1,40 @@
1
+ import { ComputedRef, Ref } from 'vue';
2
+ import { Query, QueryItem } from '@myko/core';
3
+
4
+ export interface UseQueryReturn<Q extends Query<unknown>> {
5
+ /** Reactive map of items by ID */
6
+ items: Map<string, QueryItem<Q> & {
7
+ id: string;
8
+ }>;
9
+ /** Array of all items (computed for convenience) */
10
+ itemsArray: ComputedRef<(QueryItem<Q> & {
11
+ id: string;
12
+ })[]>;
13
+ /** Whether the query has received its first response */
14
+ resolved: Ref<boolean>;
15
+ /** Manually release the subscription (also called on unmount) */
16
+ release: () => void;
17
+ }
18
+ /**
19
+ * Vue composable for watching a Myko query.
20
+ *
21
+ * Automatically releases the subscription when the component is unmounted.
22
+ *
23
+ * @example
24
+ * ```vue
25
+ * <script setup>
26
+ * import { useQuery } from '@myko/ui-vue'
27
+ * import { queries } from '@rship/entities'
28
+ *
29
+ * const { items, itemsArray, resolved } = useQuery(queries.GetAllTargets({}))
30
+ * </script>
31
+ *
32
+ * <template>
33
+ * <div v-if="!resolved">Loading...</div>
34
+ * <div v-for="target in itemsArray" :key="target.id">
35
+ * {{ target.name }}
36
+ * </div>
37
+ * </template>
38
+ * ```
39
+ */
40
+ export declare function useQuery<Q extends Query<unknown>>(queryFactory: Q): UseQueryReturn<Q>;
@@ -0,0 +1,29 @@
1
+ import { ShallowRef } from 'vue';
2
+ import { Report, ReportResult } from '@myko/core';
3
+
4
+ export interface UseReportReturn<R extends Report<unknown>> {
5
+ /** Current value (reactive via ref) */
6
+ value: ShallowRef<ReportResult<R> | undefined>;
7
+ /** Manually release the subscription (also called on unmount) */
8
+ release: () => void;
9
+ }
10
+ /**
11
+ * Vue composable for watching a Myko report.
12
+ *
13
+ * Automatically releases the subscription when the component is unmounted.
14
+ *
15
+ * @example
16
+ * ```vue
17
+ * <script setup>
18
+ * import { useReport } from '@myko/ui-vue'
19
+ * import { reports } from '@rship/entities'
20
+ *
21
+ * const { value } = useReport(reports.CountAllTargets({}))
22
+ * </script>
23
+ *
24
+ * <template>
25
+ * <div>Count: {{ value?.count ?? 'loading...' }}</div>
26
+ * </template>
27
+ * ```
28
+ */
29
+ export declare function useReport<R extends Report<unknown>>(reportFactory: R): UseReportReturn<R>;
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var S=Object.defineProperty;var p=(s,t,e)=>t in s?S(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var i=(s,t,e)=>p(s,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("@myko/core"),c=require("vue"),f=require("rxjs");class b{constructor(){i(this,"client");i(this,"sharedQueries",new Map);i(this,"sharedReports",new Map);i(this,"commandSuccessSubject",new f.Subject);i(this,"commandErrorSubject",new f.Subject);i(this,"commandSuccess$",this.commandSuccessSubject.asObservable());i(this,"commandError$",this.commandErrorSubject.asObservable());i(this,"_connectionStatus",c.ref(u.ConnectionStatus.Disconnected));this.client=new u.MykoClient,this.client.connectionStatus$.subscribe(t=>{this._connectionStatus.value=t})}getCacheKey(t,e){const n=t==="query"?e.queryId:e.reportId,o=t==="query"?e.query:e.report;return`${t}:${n}:${JSON.stringify(o)}`}get connectionStatus(){return this._connectionStatus}get isConnected(){return this._connectionStatus.value===u.ConnectionStatus.Connected}connect(t){this.client.setAddress(t)}disconnect(){for(const t of this.sharedQueries.values())t.subscription.unsubscribe();this.sharedQueries.clear();for(const t of this.sharedReports.values())t.subscription.unsubscribe();this.sharedReports.clear(),this.client.disconnect()}query(t){const e=this.getCacheKey("query",t);let n=this.sharedQueries.get(e);if(!n){const r=c.shallowReactive(new Map),a=c.ref(!1),h=this.client.watchQueryDiff(t).subscribe({next:m=>{m.sequence===0n&&r.clear();for(const l of m.deletes)r.delete(l);for(const l of m.upserts)r.set(l.id,l);a.value=!0}});n={items:r,resolved:a,subscription:h,refCount:0},this.sharedQueries.set(e,n)}n.refCount++;let o=!1;return{items:n.items,resolved:n.resolved,release:()=>{if(o)return;o=!0;const r=this.sharedQueries.get(e);r&&(r.refCount--,r.refCount<=0&&(r.subscription.unsubscribe(),this.sharedQueries.delete(e)))}}}report(t){const e=this.getCacheKey("report",t);let n=this.sharedReports.get(e);if(!n){const r=c.shallowRef(void 0),a=this.client.watchReport(t).subscribe({next:h=>{r.value=h}});n={value:r,subscription:a,refCount:0},this.sharedReports.set(e,n)}n.refCount++;let o=!1;return{value:n.value,release:()=>{if(o)return;o=!0;const r=this.sharedReports.get(e);r&&(r.refCount--,r.refCount<=0&&(r.subscription.unsubscribe(),this.sharedReports.delete(e)))}}}async sendCommand(t){const e=t.commandId;try{const n=await this.client.sendCommand(t);return this.commandSuccessSubject.next({commandId:e,response:n}),n}catch(n){const o=n instanceof Error?n:new Error(String(n));throw this.commandErrorSubject.next({commandId:e,error:o}),n}}get raw(){return this.client}}const C=new b;function d(){return C}function v(){return new b}function y(s){const e=d().query(s);c.onUnmounted(()=>{e.release()});const n=c.computed(()=>Array.from(e.items.values()));return{items:e.items,itemsArray:n,resolved:e.resolved,release:e.release}}function g(s){const e=d().report(s);return c.onUnmounted(()=>{e.release()}),{value:e.value,release:e.release}}function w(){const s=d(),t=c.computed(()=>s.connectionStatus.value===u.ConnectionStatus.Connected),e=c.computed(()=>s.connectionStatus.value===u.ConnectionStatus.Connecting),n=c.computed(()=>s.connectionStatus.value===u.ConnectionStatus.Disconnected);return{status:s.connectionStatus,isConnected:t,isConnecting:e,isDisconnected:n,connect:o=>s.connect(o),disconnect:()=>s.disconnect()}}Object.defineProperty(exports,"ConnectionStatus",{enumerable:!0,get:()=>u.ConnectionStatus});exports.VueMykoClient=b;exports.createMykoClient=v;exports.getMykoClient=d;exports.myko=C;exports.useConnection=w;exports.useQuery=y;exports.useReport=g;
@@ -0,0 +1,3 @@
1
+ export { createMykoClient, getMykoClient, myko, VueMykoClient, type CommandError, type CommandSuccess, type ReactiveQuery, type ReactiveReport } from './services/vue-client';
2
+ export { useQuery, useReport, useConnection, type UseQueryReturn, type UseReportReturn, type UseConnectionReturn } from './composables';
3
+ export { ConnectionStatus } from '@myko/core';
package/dist/index.mjs ADDED
@@ -0,0 +1,227 @@
1
+ var S = Object.defineProperty;
2
+ var v = (n, t, e) => t in n ? S(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
3
+ var c = (n, t, e) => v(n, typeof t != "symbol" ? t + "" : t, e);
4
+ import { ConnectionStatus as i, MykoClient as g } from "@myko/core";
5
+ import { ConnectionStatus as I } from "@myko/core";
6
+ import { ref as f, shallowReactive as w, shallowRef as y, onUnmounted as p, computed as l } from "vue";
7
+ import { Subject as b } from "rxjs";
8
+ class C {
9
+ constructor() {
10
+ c(this, "client");
11
+ // Shared queries by cache key
12
+ c(this, "sharedQueries", /* @__PURE__ */ new Map());
13
+ // Shared reports by cache key
14
+ c(this, "sharedReports", /* @__PURE__ */ new Map());
15
+ // Command outcome subjects
16
+ c(this, "commandSuccessSubject", new b());
17
+ c(this, "commandErrorSubject", new b());
18
+ /** Observable of all command successes */
19
+ c(this, "commandSuccess$", this.commandSuccessSubject.asObservable());
20
+ /** Observable of all command errors */
21
+ c(this, "commandError$", this.commandErrorSubject.asObservable());
22
+ // Reactive connection status
23
+ c(this, "_connectionStatus", f(i.Disconnected));
24
+ this.client = new g(), this.client.connectionStatus$.subscribe((t) => {
25
+ this._connectionStatus.value = t;
26
+ });
27
+ }
28
+ /** Create a stable cache key from a query/report factory */
29
+ getCacheKey(t, e) {
30
+ const s = t === "query" ? e.queryId : e.reportId, o = t === "query" ? e.query : e.report;
31
+ return `${t}:${s}:${JSON.stringify(o)}`;
32
+ }
33
+ /** Current connection status (reactive) */
34
+ get connectionStatus() {
35
+ return this._connectionStatus;
36
+ }
37
+ /** Whether currently connected (reactive) - use .value to access */
38
+ get isConnected() {
39
+ return this._connectionStatus.value === i.Connected;
40
+ }
41
+ /** Set the server address and connect */
42
+ connect(t) {
43
+ this.client.setAddress(t);
44
+ }
45
+ /** Disconnect from the server */
46
+ disconnect() {
47
+ for (const t of this.sharedQueries.values())
48
+ t.subscription.unsubscribe();
49
+ this.sharedQueries.clear();
50
+ for (const t of this.sharedReports.values())
51
+ t.subscription.unsubscribe();
52
+ this.sharedReports.clear(), this.client.disconnect();
53
+ }
54
+ /**
55
+ * Watch a query with reactive Map updates.
56
+ *
57
+ * Multiple calls with the same query args share the same Map,
58
+ * and the subscription is only cancelled when all consumers release.
59
+ *
60
+ * @example
61
+ * ```vue
62
+ * <script setup>
63
+ * import { onUnmounted } from 'vue'
64
+ * const { items, resolved, release } = client.query(queries.GetAllTargets({}))
65
+ * onUnmounted(release)
66
+ * <\/script>
67
+ *
68
+ * <template>
69
+ * <div v-for="[id, target] in items" :key="id">{{ target.name }}</div>
70
+ * </template>
71
+ * ```
72
+ */
73
+ query(t) {
74
+ const e = this.getCacheKey("query", t);
75
+ let s = this.sharedQueries.get(e);
76
+ if (!s) {
77
+ const r = w(/* @__PURE__ */ new Map()), u = f(!1), d = this.client.watchQueryDiff(t).subscribe({
78
+ next: (h) => {
79
+ h.sequence === 0n && r.clear();
80
+ for (const a of h.deletes)
81
+ r.delete(a);
82
+ for (const a of h.upserts)
83
+ r.set(a.id, a);
84
+ u.value = !0;
85
+ }
86
+ });
87
+ s = { items: r, resolved: u, subscription: d, refCount: 0 }, this.sharedQueries.set(e, s);
88
+ }
89
+ s.refCount++;
90
+ let o = !1;
91
+ return {
92
+ items: s.items,
93
+ resolved: s.resolved,
94
+ release: () => {
95
+ if (o) return;
96
+ o = !0;
97
+ const r = this.sharedQueries.get(e);
98
+ r && (r.refCount--, r.refCount <= 0 && (r.subscription.unsubscribe(), this.sharedQueries.delete(e)));
99
+ }
100
+ };
101
+ }
102
+ /**
103
+ * Watch a report with reactive value updates.
104
+ *
105
+ * Multiple calls with the same report args share the same subscription,
106
+ * and the subscription is only cancelled when all consumers release.
107
+ *
108
+ * @example
109
+ * ```vue
110
+ * <script setup>
111
+ * import { onUnmounted } from 'vue'
112
+ * const { value, release } = client.report(reports.CountAllTargets({}))
113
+ * onUnmounted(release)
114
+ * <\/script>
115
+ *
116
+ * <template>
117
+ * <div>Count: {{ value?.count ?? 'loading...' }}</div>
118
+ * </template>
119
+ * ```
120
+ */
121
+ report(t) {
122
+ const e = this.getCacheKey("report", t);
123
+ let s = this.sharedReports.get(e);
124
+ if (!s) {
125
+ const r = y(void 0), u = this.client.watchReport(t).subscribe({
126
+ next: (d) => {
127
+ r.value = d;
128
+ }
129
+ });
130
+ s = {
131
+ value: r,
132
+ subscription: u,
133
+ refCount: 0
134
+ }, this.sharedReports.set(e, s);
135
+ }
136
+ s.refCount++;
137
+ let o = !1;
138
+ return {
139
+ value: s.value,
140
+ release: () => {
141
+ if (o) return;
142
+ o = !0;
143
+ const r = this.sharedReports.get(e);
144
+ r && (r.refCount--, r.refCount <= 0 && (r.subscription.unsubscribe(), this.sharedReports.delete(e)));
145
+ }
146
+ };
147
+ }
148
+ /**
149
+ * Send a command and wait for the response.
150
+ *
151
+ * Emits to commandSuccess$ or commandError$ observables for generic handling.
152
+ *
153
+ * @example
154
+ * ```vue
155
+ * <script setup>
156
+ * async function deleteMachine(id: string) {
157
+ * const result = await myko.sendCommand(commands.DeleteMachine({ machineId: id }))
158
+ * console.log('Deleted:', result)
159
+ * }
160
+ * <\/script>
161
+ * ```
162
+ */
163
+ async sendCommand(t) {
164
+ const e = t.commandId;
165
+ try {
166
+ const s = await this.client.sendCommand(t);
167
+ return this.commandSuccessSubject.next({ commandId: e, response: s }), s;
168
+ } catch (s) {
169
+ const o = s instanceof Error ? s : new Error(String(s));
170
+ throw this.commandErrorSubject.next({ commandId: e, error: o }), s;
171
+ }
172
+ }
173
+ /** Access the underlying MykoClient for advanced use cases */
174
+ get raw() {
175
+ return this.client;
176
+ }
177
+ }
178
+ const R = new C();
179
+ function m() {
180
+ return R;
181
+ }
182
+ function x() {
183
+ return new C();
184
+ }
185
+ function E(n) {
186
+ const e = m().query(n);
187
+ p(() => {
188
+ e.release();
189
+ });
190
+ const s = l(() => Array.from(e.items.values()));
191
+ return {
192
+ items: e.items,
193
+ itemsArray: s,
194
+ resolved: e.resolved,
195
+ release: e.release
196
+ };
197
+ }
198
+ function $(n) {
199
+ const e = m().report(n);
200
+ return p(() => {
201
+ e.release();
202
+ }), {
203
+ value: e.value,
204
+ release: e.release
205
+ };
206
+ }
207
+ function k() {
208
+ const n = m(), t = l(() => n.connectionStatus.value === i.Connected), e = l(() => n.connectionStatus.value === i.Connecting), s = l(() => n.connectionStatus.value === i.Disconnected);
209
+ return {
210
+ status: n.connectionStatus,
211
+ isConnected: t,
212
+ isConnecting: e,
213
+ isDisconnected: s,
214
+ connect: (o) => n.connect(o),
215
+ disconnect: () => n.disconnect()
216
+ };
217
+ }
218
+ export {
219
+ I as ConnectionStatus,
220
+ C as VueMykoClient,
221
+ x as createMykoClient,
222
+ m as getMykoClient,
223
+ R as myko,
224
+ k as useConnection,
225
+ E as useQuery,
226
+ $ as useReport
227
+ };
@@ -0,0 +1,124 @@
1
+ import { ConnectionStatus, MykoClient, Command, CommandResult, QueryItem, Query, ReportResult, Report } from '@myko/core';
2
+ import { Ref, ShallowRef } from 'vue';
3
+ import { Observable } from 'rxjs';
4
+
5
+ /** Command success event */
6
+ export type CommandSuccess = {
7
+ commandId: string;
8
+ response: unknown;
9
+ };
10
+ /** Command error event */
11
+ export type CommandError = {
12
+ commandId: string;
13
+ error: Error;
14
+ };
15
+ /** Reactive query result using reactive Map - generic over the Query factory type */
16
+ export type ReactiveQuery<Q extends Query<unknown>> = {
17
+ /** Reactive map of items by ID */
18
+ readonly items: Map<string, QueryItem<Q> & {
19
+ id: string;
20
+ }>;
21
+ /** Whether the query has received its first response */
22
+ readonly resolved: Ref<boolean>;
23
+ /** Release this consumer's reference (unsubscribes when last consumer releases) */
24
+ release: () => void;
25
+ };
26
+ /** Reactive report result - generic over the Report factory type */
27
+ export type ReactiveReport<R extends Report<unknown>> = {
28
+ /** Current value (reactive via ref) */
29
+ readonly value: ShallowRef<ReportResult<R> | undefined>;
30
+ /** Release this consumer's reference (unsubscribes when last consumer releases) */
31
+ release: () => void;
32
+ };
33
+ /**
34
+ * Vue-friendly Myko client
35
+ *
36
+ * Wraps MykoClient to provide reactive Vue state with automatic deduplication.
37
+ */
38
+ export declare class VueMykoClient {
39
+ private client;
40
+ private sharedQueries;
41
+ private sharedReports;
42
+ private commandSuccessSubject;
43
+ private commandErrorSubject;
44
+ /** Observable of all command successes */
45
+ readonly commandSuccess$: Observable<CommandSuccess>;
46
+ /** Observable of all command errors */
47
+ readonly commandError$: Observable<CommandError>;
48
+ private _connectionStatus;
49
+ constructor();
50
+ /** Create a stable cache key from a query/report factory */
51
+ private getCacheKey;
52
+ /** Current connection status (reactive) */
53
+ get connectionStatus(): Ref<ConnectionStatus>;
54
+ /** Whether currently connected (reactive) - use .value to access */
55
+ get isConnected(): boolean;
56
+ /** Set the server address and connect */
57
+ connect(address: string): void;
58
+ /** Disconnect from the server */
59
+ disconnect(): void;
60
+ /**
61
+ * Watch a query with reactive Map updates.
62
+ *
63
+ * Multiple calls with the same query args share the same Map,
64
+ * and the subscription is only cancelled when all consumers release.
65
+ *
66
+ * @example
67
+ * ```vue
68
+ * <script setup>
69
+ * import { onUnmounted } from 'vue'
70
+ * const { items, resolved, release } = client.query(queries.GetAllTargets({}))
71
+ * onUnmounted(release)
72
+ * </script>
73
+ *
74
+ * <template>
75
+ * <div v-for="[id, target] in items" :key="id">{{ target.name }}</div>
76
+ * </template>
77
+ * ```
78
+ */
79
+ query<Q extends Query<unknown>>(queryFactory: Q): ReactiveQuery<Q>;
80
+ /**
81
+ * Watch a report with reactive value updates.
82
+ *
83
+ * Multiple calls with the same report args share the same subscription,
84
+ * and the subscription is only cancelled when all consumers release.
85
+ *
86
+ * @example
87
+ * ```vue
88
+ * <script setup>
89
+ * import { onUnmounted } from 'vue'
90
+ * const { value, release } = client.report(reports.CountAllTargets({}))
91
+ * onUnmounted(release)
92
+ * </script>
93
+ *
94
+ * <template>
95
+ * <div>Count: {{ value?.count ?? 'loading...' }}</div>
96
+ * </template>
97
+ * ```
98
+ */
99
+ report<R extends Report<unknown>>(reportFactory: R): ReactiveReport<R>;
100
+ /**
101
+ * Send a command and wait for the response.
102
+ *
103
+ * Emits to commandSuccess$ or commandError$ observables for generic handling.
104
+ *
105
+ * @example
106
+ * ```vue
107
+ * <script setup>
108
+ * async function deleteMachine(id: string) {
109
+ * const result = await myko.sendCommand(commands.DeleteMachine({ machineId: id }))
110
+ * console.log('Deleted:', result)
111
+ * }
112
+ * </script>
113
+ * ```
114
+ */
115
+ sendCommand<C extends Command<unknown>>(commandFactory: C): Promise<CommandResult<C>>;
116
+ /** Access the underlying MykoClient for advanced use cases */
117
+ get raw(): MykoClient;
118
+ }
119
+ /** Global singleton client instance (auto-initialized) */
120
+ export declare const myko: VueMykoClient;
121
+ /** Get the global MykoClient instance */
122
+ export declare function getMykoClient(): VueMykoClient;
123
+ /** Create a new VueMykoClient instance (non-singleton, for advanced use) */
124
+ export declare function createMykoClient(): VueMykoClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
- "@myko/core": "^4.4.2"
3
+ "@myko/core": "^4.4.6"
4
4
  },
5
5
  "devDependencies": {
6
6
  "@types/luxon": "^3.4.2",
@@ -16,9 +16,13 @@
16
16
  },
17
17
  "exports": {
18
18
  ".": {
19
- "default": "./src/lib/index.ts"
19
+ "default": "./src/lib/index.ts",
20
+ "types": "./src/lib/index.ts"
20
21
  }
21
22
  },
23
+ "files": [
24
+ "dist"
25
+ ],
22
26
  "flux": {
23
27
  "tasks": [
24
28
  "build",
@@ -31,13 +35,24 @@
31
35
  "peerDependencies": {
32
36
  "vue": "^3.4.0"
33
37
  },
38
+ "publishConfig": {
39
+ "exports": {
40
+ ".": {
41
+ "import": "./dist/index.mjs",
42
+ "require": "./dist/index.cjs",
43
+ "types": "./dist/index.d.ts"
44
+ }
45
+ },
46
+ "main": "./dist/index.cjs",
47
+ "types": "./dist/index.d.ts"
48
+ },
34
49
  "repository": {
35
50
  "directory": "libs/myko/ui-vue",
36
51
  "type": "git",
37
52
  "url": "https://github.com/ignition-is-go/myko"
38
53
  },
39
54
  "scripts": {
40
- "build": "vue-tsc --noEmit",
55
+ "build": "vue-tsc --noEmit && vite build",
41
56
  "check": "vue-tsc --noEmit",
42
57
  "dev": "vite build --watch",
43
58
  "format": "prettier --write .",
@@ -45,5 +60,5 @@
45
60
  },
46
61
  "type": "module",
47
62
  "types": "./src/lib/index.ts",
48
- "version": "4.4.2"
63
+ "version": "4.4.6"
49
64
  }
package/.prettierrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "useTabs": true,
3
- "singleQuote": true,
4
- "trailingComma": "none",
5
- "printWidth": 100
6
- }
@@ -1,57 +0,0 @@
1
- import { computed, type ComputedRef, type Ref } from 'vue';
2
- import { ConnectionStatus } from '@myko/core';
3
- import { getMykoClient } from '../services/vue-client';
4
-
5
- export interface UseConnectionReturn {
6
- /** Current connection status (reactive) */
7
- status: Ref<ConnectionStatus>;
8
- /** Whether currently connected (computed) */
9
- isConnected: ComputedRef<boolean>;
10
- /** Whether currently connecting (computed) */
11
- isConnecting: ComputedRef<boolean>;
12
- /** Whether currently disconnected (computed) */
13
- isDisconnected: ComputedRef<boolean>;
14
- /** Connect to a server address */
15
- connect: (address: string) => void;
16
- /** Disconnect from the server */
17
- disconnect: () => void;
18
- }
19
-
20
- /**
21
- * Vue composable for managing Myko connection status.
22
- *
23
- * @example
24
- * ```vue
25
- * <script setup>
26
- * import { useConnection } from '@myko/ui-vue'
27
- *
28
- * const { status, isConnected, connect, disconnect } = useConnection()
29
- *
30
- * function handleConnect() {
31
- * connect('ws://localhost:5155')
32
- * }
33
- * </script>
34
- *
35
- * <template>
36
- * <div>Status: {{ status }}</div>
37
- * <button v-if="!isConnected" @click="handleConnect">Connect</button>
38
- * <button v-else @click="disconnect">Disconnect</button>
39
- * </template>
40
- * ```
41
- */
42
- export function useConnection(): UseConnectionReturn {
43
- const client = getMykoClient();
44
-
45
- const isConnected = computed(() => client.connectionStatus.value === ConnectionStatus.Connected);
46
- const isConnecting = computed(() => client.connectionStatus.value === ConnectionStatus.Connecting);
47
- const isDisconnected = computed(() => client.connectionStatus.value === ConnectionStatus.Disconnected);
48
-
49
- return {
50
- status: client.connectionStatus,
51
- isConnected,
52
- isConnecting,
53
- isDisconnected,
54
- connect: (address: string) => client.connect(address),
55
- disconnect: () => client.disconnect()
56
- };
57
- }
@@ -1,58 +0,0 @@
1
- import { onUnmounted, computed, type ComputedRef, type Ref } from 'vue';
2
- import type { Query, QueryItem } from '@myko/core';
3
- import { getMykoClient, type ReactiveQuery } from '../services/vue-client';
4
-
5
- export interface UseQueryReturn<Q extends Query<unknown>> {
6
- /** Reactive map of items by ID */
7
- items: Map<string, QueryItem<Q> & { id: string }>;
8
- /** Array of all items (computed for convenience) */
9
- itemsArray: ComputedRef<(QueryItem<Q> & { id: string })[]>;
10
- /** Whether the query has received its first response */
11
- resolved: Ref<boolean>;
12
- /** Manually release the subscription (also called on unmount) */
13
- release: () => void;
14
- }
15
-
16
- /**
17
- * Vue composable for watching a Myko query.
18
- *
19
- * Automatically releases the subscription when the component is unmounted.
20
- *
21
- * @example
22
- * ```vue
23
- * <script setup>
24
- * import { useQuery } from '@myko/ui-vue'
25
- * import { queries } from '@rship/entities'
26
- *
27
- * const { items, itemsArray, resolved } = useQuery(queries.GetAllTargets({}))
28
- * </script>
29
- *
30
- * <template>
31
- * <div v-if="!resolved">Loading...</div>
32
- * <div v-for="target in itemsArray" :key="target.id">
33
- * {{ target.name }}
34
- * </div>
35
- * </template>
36
- * ```
37
- */
38
- export function useQuery<Q extends Query<unknown>>(
39
- queryFactory: Q
40
- ): UseQueryReturn<Q> {
41
- const client = getMykoClient();
42
- const result = client.query(queryFactory);
43
-
44
- // Auto-release on unmount
45
- onUnmounted(() => {
46
- result.release();
47
- });
48
-
49
- // Convenience computed for array iteration
50
- const itemsArray = computed(() => Array.from(result.items.values()));
51
-
52
- return {
53
- items: result.items,
54
- itemsArray,
55
- resolved: result.resolved,
56
- release: result.release
57
- };
58
- }
@@ -1,46 +0,0 @@
1
- import { onUnmounted, type ShallowRef } from 'vue';
2
- import type { Report, ReportResult } from '@myko/core';
3
- import { getMykoClient, type ReactiveReport } from '../services/vue-client';
4
-
5
- export interface UseReportReturn<R extends Report<unknown>> {
6
- /** Current value (reactive via ref) */
7
- value: ShallowRef<ReportResult<R> | undefined>;
8
- /** Manually release the subscription (also called on unmount) */
9
- release: () => void;
10
- }
11
-
12
- /**
13
- * Vue composable for watching a Myko report.
14
- *
15
- * Automatically releases the subscription when the component is unmounted.
16
- *
17
- * @example
18
- * ```vue
19
- * <script setup>
20
- * import { useReport } from '@myko/ui-vue'
21
- * import { reports } from '@rship/entities'
22
- *
23
- * const { value } = useReport(reports.CountAllTargets({}))
24
- * </script>
25
- *
26
- * <template>
27
- * <div>Count: {{ value?.count ?? 'loading...' }}</div>
28
- * </template>
29
- * ```
30
- */
31
- export function useReport<R extends Report<unknown>>(
32
- reportFactory: R
33
- ): UseReportReturn<R> {
34
- const client = getMykoClient();
35
- const result = client.report(reportFactory);
36
-
37
- // Auto-release on unmount
38
- onUnmounted(() => {
39
- result.release();
40
- });
41
-
42
- return {
43
- value: result.value,
44
- release: result.release
45
- };
46
- }
package/src/lib/index.ts DELETED
@@ -1,24 +0,0 @@
1
- // Vue-friendly Myko client exports
2
- export {
3
- createMykoClient,
4
- getMykoClient,
5
- myko,
6
- VueMykoClient,
7
- type CommandError,
8
- type CommandSuccess,
9
- type ReactiveQuery,
10
- type ReactiveReport
11
- } from './services/vue-client';
12
-
13
- // Vue composables
14
- export {
15
- useQuery,
16
- useReport,
17
- useConnection,
18
- type UseQueryReturn,
19
- type UseReportReturn,
20
- type UseConnectionReturn
21
- } from './composables';
22
-
23
- // Re-export useful types from @myko/ts
24
- export { ConnectionStatus } from '@myko/core';
@@ -1,345 +0,0 @@
1
- /**
2
- * Vue-friendly Myko client wrapper
3
- *
4
- * Provides reactive state using Vue 3 reactivity and reactive Map for efficient updates.
5
- * Queries and reports are deduplicated - multiple calls with the same args share
6
- * the same subscription and are only cancelled when all consumers unsubscribe.
7
- */
8
-
9
- import {
10
- ConnectionStatus,
11
- MykoClient,
12
- type Command,
13
- type CommandResult,
14
- type QueryDiff,
15
- type QueryItem,
16
- type Query,
17
- type ReportResult,
18
- type Report
19
- } from '@myko/core';
20
- import { ref, shallowRef, shallowReactive, type Ref, type ShallowRef } from 'vue';
21
- import { Subject, type Observable, type Subscription } from 'rxjs';
22
-
23
- /** Command success event */
24
- export type CommandSuccess = {
25
- commandId: string;
26
- response: unknown;
27
- };
28
-
29
- /** Command error event */
30
- export type CommandError = {
31
- commandId: string;
32
- error: Error;
33
- };
34
-
35
- /** Reactive query result using reactive Map - generic over the Query factory type */
36
- export type ReactiveQuery<Q extends Query<unknown>> = {
37
- /** Reactive map of items by ID */
38
- readonly items: Map<string, QueryItem<Q> & { id: string }>;
39
- /** Whether the query has received its first response */
40
- readonly resolved: Ref<boolean>;
41
- /** Release this consumer's reference (unsubscribes when last consumer releases) */
42
- release: () => void;
43
- };
44
-
45
- /** Reactive report result - generic over the Report factory type */
46
- export type ReactiveReport<R extends Report<unknown>> = {
47
- /** Current value (reactive via ref) */
48
- readonly value: ShallowRef<ReportResult<R> | undefined>;
49
- /** Release this consumer's reference (unsubscribes when last consumer releases) */
50
- release: () => void;
51
- };
52
-
53
- /** Internal state for a shared query */
54
- type SharedQuery<T extends { id: string }> = {
55
- items: Map<string, T>;
56
- resolved: Ref<boolean>;
57
- subscription: Subscription;
58
- refCount: number;
59
- };
60
-
61
- /** Internal state for a shared report */
62
- type SharedReport<T> = {
63
- value: ShallowRef<T | undefined>;
64
- subscription: Subscription;
65
- refCount: number;
66
- };
67
-
68
- /**
69
- * Vue-friendly Myko client
70
- *
71
- * Wraps MykoClient to provide reactive Vue state with automatic deduplication.
72
- */
73
- export class VueMykoClient {
74
- private client: MykoClient;
75
-
76
- // Shared queries by cache key
77
- private sharedQueries = new Map<string, SharedQuery<{ id: string }>>();
78
-
79
- // Shared reports by cache key
80
- private sharedReports = new Map<string, SharedReport<unknown>>();
81
-
82
- // Command outcome subjects
83
- private commandSuccessSubject = new Subject<CommandSuccess>();
84
- private commandErrorSubject = new Subject<CommandError>();
85
-
86
- /** Observable of all command successes */
87
- readonly commandSuccess$: Observable<CommandSuccess> = this.commandSuccessSubject.asObservable();
88
-
89
- /** Observable of all command errors */
90
- readonly commandError$: Observable<CommandError> = this.commandErrorSubject.asObservable();
91
-
92
- // Reactive connection status
93
- private _connectionStatus = ref<ConnectionStatus>(ConnectionStatus.Disconnected);
94
-
95
- constructor() {
96
- this.client = new MykoClient();
97
-
98
- // Sync connection status to reactive state
99
- this.client.connectionStatus$.subscribe((status: ConnectionStatus) => {
100
- this._connectionStatus.value = status;
101
- });
102
- }
103
-
104
- /** Create a stable cache key from a query/report factory */
105
- private getCacheKey(
106
- type: 'query' | 'report',
107
- factory: {
108
- query?: Record<string, unknown>;
109
- report?: Record<string, unknown>;
110
- queryId?: string;
111
- reportId?: string;
112
- }
113
- ): string {
114
- const id = type === 'query' ? factory.queryId : factory.reportId;
115
- const args = type === 'query' ? factory.query : factory.report;
116
- return `${type}:${id}:${JSON.stringify(args)}`;
117
- }
118
-
119
- /** Current connection status (reactive) */
120
- get connectionStatus(): Ref<ConnectionStatus> {
121
- return this._connectionStatus;
122
- }
123
-
124
- /** Whether currently connected (reactive) - use .value to access */
125
- get isConnected(): boolean {
126
- return this._connectionStatus.value === ConnectionStatus.Connected;
127
- }
128
-
129
- /** Set the server address and connect */
130
- connect(address: string): void {
131
- this.client.setAddress(address);
132
- }
133
-
134
- /** Disconnect from the server */
135
- disconnect(): void {
136
- // Unsubscribe all shared queries
137
- for (const shared of this.sharedQueries.values()) {
138
- shared.subscription.unsubscribe();
139
- }
140
- this.sharedQueries.clear();
141
-
142
- // Unsubscribe all shared reports
143
- for (const shared of this.sharedReports.values()) {
144
- shared.subscription.unsubscribe();
145
- }
146
- this.sharedReports.clear();
147
-
148
- this.client.disconnect();
149
- }
150
-
151
- /**
152
- * Watch a query with reactive Map updates.
153
- *
154
- * Multiple calls with the same query args share the same Map,
155
- * and the subscription is only cancelled when all consumers release.
156
- *
157
- * @example
158
- * ```vue
159
- * <script setup>
160
- * import { onUnmounted } from 'vue'
161
- * const { items, resolved, release } = client.query(queries.GetAllTargets({}))
162
- * onUnmounted(release)
163
- * </script>
164
- *
165
- * <template>
166
- * <div v-for="[id, target] in items" :key="id">{{ target.name }}</div>
167
- * </template>
168
- * ```
169
- */
170
- query<Q extends Query<unknown>>(queryFactory: Q): ReactiveQuery<Q> {
171
- type Item = QueryItem<Q> & { id: string };
172
- const cacheKey = this.getCacheKey('query', queryFactory);
173
-
174
- // Return existing shared query if available
175
- let shared = this.sharedQueries.get(cacheKey) as SharedQuery<Item> | undefined;
176
-
177
- if (!shared) {
178
- // Create new shared query with reactive Map
179
- const items = shallowReactive(new Map<string, Item>());
180
- const resolved = ref(false);
181
-
182
- const subscription = this.client.watchQueryDiff(queryFactory).subscribe({
183
- next: (diff) => {
184
- if (diff.sequence === 0n) {
185
- items.clear();
186
- }
187
- for (const id of diff.deletes) {
188
- items.delete(id);
189
- }
190
- for (const item of diff.upserts as Item[]) {
191
- items.set(item.id, item);
192
- }
193
- resolved.value = true;
194
- }
195
- });
196
-
197
- shared = { items, resolved, subscription, refCount: 0 };
198
- this.sharedQueries.set(cacheKey, shared as SharedQuery<{ id: string }>);
199
- }
200
-
201
- // Increment reference count
202
- shared.refCount++;
203
-
204
- let released = false;
205
-
206
- return {
207
- items: shared.items,
208
- resolved: shared.resolved,
209
- release: () => {
210
- if (released) return;
211
- released = true;
212
-
213
- const s = this.sharedQueries.get(cacheKey);
214
- if (s) {
215
- s.refCount--;
216
- if (s.refCount <= 0) {
217
- s.subscription.unsubscribe();
218
- this.sharedQueries.delete(cacheKey);
219
- }
220
- }
221
- }
222
- };
223
- }
224
-
225
- /**
226
- * Watch a report with reactive value updates.
227
- *
228
- * Multiple calls with the same report args share the same subscription,
229
- * and the subscription is only cancelled when all consumers release.
230
- *
231
- * @example
232
- * ```vue
233
- * <script setup>
234
- * import { onUnmounted } from 'vue'
235
- * const { value, release } = client.report(reports.CountAllTargets({}))
236
- * onUnmounted(release)
237
- * </script>
238
- *
239
- * <template>
240
- * <div>Count: {{ value?.count ?? 'loading...' }}</div>
241
- * </template>
242
- * ```
243
- */
244
- report<R extends Report<unknown>>(reportFactory: R): ReactiveReport<R> {
245
- type Result = ReportResult<R>;
246
- const cacheKey = this.getCacheKey('report', reportFactory);
247
-
248
- // Return existing shared report if available
249
- let shared = this.sharedReports.get(cacheKey) as SharedReport<Result> | undefined;
250
-
251
- if (!shared) {
252
- // Create new shared report with reactive state
253
- const value = shallowRef<Result | undefined>(undefined);
254
- const subscription = this.client.watchReport(reportFactory).subscribe({
255
- next: (result) => {
256
- value.value = result;
257
- }
258
- });
259
-
260
- shared = {
261
- value: value as ShallowRef<Result | undefined>,
262
- subscription,
263
- refCount: 0
264
- };
265
- this.sharedReports.set(cacheKey, shared as SharedReport<unknown>);
266
- }
267
-
268
- // Increment reference count (shared is guaranteed to be defined here)
269
- shared!.refCount++;
270
-
271
- let released = false;
272
-
273
- return {
274
- value: shared!.value as ShallowRef<Result | undefined>,
275
- release: () => {
276
- if (released) return;
277
- released = true;
278
-
279
- const s = this.sharedReports.get(cacheKey);
280
- if (s) {
281
- s.refCount--;
282
- if (s.refCount <= 0) {
283
- s.subscription.unsubscribe();
284
- this.sharedReports.delete(cacheKey);
285
- }
286
- }
287
- }
288
- };
289
- }
290
-
291
- /**
292
- * Send a command and wait for the response.
293
- *
294
- * Emits to commandSuccess$ or commandError$ observables for generic handling.
295
- *
296
- * @example
297
- * ```vue
298
- * <script setup>
299
- * async function deleteMachine(id: string) {
300
- * const result = await myko.sendCommand(commands.DeleteMachine({ machineId: id }))
301
- * console.log('Deleted:', result)
302
- * }
303
- * </script>
304
- * ```
305
- */
306
- async sendCommand<C extends Command<unknown>>(
307
- commandFactory: C
308
- ): Promise<CommandResult<C>> {
309
- const commandId = commandFactory.commandId;
310
- try {
311
- const response = await this.client.sendCommand(commandFactory);
312
- this.commandSuccessSubject.next({ commandId, response });
313
- return response;
314
- } catch (e) {
315
- const error = e instanceof Error ? e : new Error(String(e));
316
- this.commandErrorSubject.next({ commandId, error });
317
- throw e;
318
- }
319
- }
320
-
321
- /** Access the underlying MykoClient for advanced use cases */
322
- get raw(): MykoClient {
323
- return this.client;
324
- }
325
- }
326
-
327
- /** Global singleton client instance (auto-initialized) */
328
- export const myko = new VueMykoClient();
329
-
330
- // HMR cleanup - disconnect old client when module is hot-reloaded
331
- if (import.meta.hot) {
332
- import.meta.hot.dispose(() => {
333
- myko.disconnect();
334
- });
335
- }
336
-
337
- /** Get the global MykoClient instance */
338
- export function getMykoClient(): VueMykoClient {
339
- return myko;
340
- }
341
-
342
- /** Create a new VueMykoClient instance (non-singleton, for advanced use) */
343
- export function createMykoClient(): VueMykoClient {
344
- return new VueMykoClient();
345
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "jsx": "preserve",
8
- "resolveJsonModule": true,
9
- "isolatedModules": true,
10
- "esModuleInterop": true,
11
- "lib": ["ESNext", "DOM"],
12
- "skipLibCheck": true,
13
- "noEmit": true,
14
- "types": ["vite/client"]
15
- },
16
- "include": ["src/**/*.ts", "src/**/*.vue"],
17
- "exclude": ["node_modules", "dist"]
18
- }
package/vite.config.ts DELETED
@@ -1,6 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import vue from '@vitejs/plugin-vue';
3
-
4
- export default defineConfig({
5
- plugins: [vue()]
6
- });