@myko/ui-vue 4.4.1 → 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.
- package/dist/composables/useConnection.d.ts +40 -0
- package/dist/composables/useQuery.d.ts +40 -0
- package/dist/composables/useReport.d.ts +29 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +227 -0
- package/dist/services/vue-client.d.ts +124 -0
- package/package.json +19 -4
- package/.prettierrc +0 -6
- package/src/lib/composables/useConnection.ts +0 -57
- package/src/lib/composables/useQuery.ts +0 -58
- package/src/lib/composables/useReport.ts +0 -46
- package/src/lib/index.ts +0 -24
- package/src/lib/services/vue-client.ts +0 -345
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -6
- /package/{src/lib/composables/index.ts → dist/composables/index.d.ts} +0 -0
|
@@ -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;
|
package/dist/index.d.ts
ADDED
|
@@ -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.
|
|
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.
|
|
63
|
+
"version": "4.4.6"
|
|
49
64
|
}
|
package/.prettierrc
DELETED
|
@@ -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
|
File without changes
|