@myko/ui-svelte 4.4.0 → 4.4.3
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/{src/lib → dist}/components/ConnectionStats.svelte +8 -22
- package/dist/components/ConnectionStats.svelte.d.ts +10 -0
- package/dist/components/EntityDiffBadge.svelte +10 -0
- package/dist/components/EntityDiffBadge.svelte.d.ts +7 -0
- package/{src/lib → dist}/components/EntityDiffFields.svelte +27 -49
- package/dist/components/EntityDiffFields.svelte.d.ts +11 -0
- package/{src/lib → dist}/components/EntityDiffNode.svelte +25 -43
- package/dist/components/EntityDiffNode.svelte.d.ts +14 -0
- package/{src/lib → dist}/components/EntityDiffTree.svelte +150 -200
- package/dist/components/EntityDiffTree.svelte.d.ts +21 -0
- package/dist/components/Logs.svelte +30 -0
- package/dist/components/Logs.svelte.d.ts +7 -0
- package/dist/components/Query.svelte +21 -0
- package/dist/components/Query.svelte.d.ts +35 -0
- package/dist/components/Report.svelte +13 -0
- package/dist/components/Report.svelte.d.ts +32 -0
- package/dist/components/Search.svelte +46 -0
- package/dist/components/Search.svelte.d.ts +56 -0
- package/dist/components/ServerView.svelte +76 -0
- package/dist/components/ServerView.svelte.d.ts +8 -0
- package/dist/components/state/resolutions.d.ts +11 -0
- package/dist/components/state/resolutions.js +129 -0
- package/dist/components/state/viewstate.svelte.d.ts +60 -0
- package/dist/components/state/viewstate.svelte.js +259 -0
- package/dist/components/state/windback.svelte.d.ts +16 -0
- package/dist/components/state/windback.svelte.js +65 -0
- package/dist/components/transactions/EntityHistory.svelte +135 -0
- package/dist/components/transactions/EntityHistory.svelte.d.ts +10 -0
- package/{src/lib → dist}/components/transactions/TimeStrip.svelte +18 -25
- package/dist/components/transactions/TimeStrip.svelte.d.ts +3 -0
- package/dist/components/transactions/TransactionDetails.svelte +24 -0
- package/dist/components/transactions/TransactionDetails.svelte.d.ts +7 -0
- package/{src/lib → dist}/components/transactions/TransactionEvent.svelte +20 -32
- package/dist/components/transactions/TransactionEvent.svelte.d.ts +7 -0
- package/{src/lib → dist}/components/transactions/TransactionEventGroup.svelte +8 -13
- package/dist/components/transactions/TransactionEventGroup.svelte.d.ts +8 -0
- package/dist/components/transactions/Transactions.svelte +94 -0
- package/dist/components/transactions/Transactions.svelte.d.ts +8 -0
- package/{src/lib → dist}/components/transactions/TransactonView.svelte +3 -7
- package/dist/components/transactions/TransactonView.svelte.d.ts +7 -0
- package/{src/lib → dist}/components/windback/WindbackFrame.svelte +3 -6
- package/dist/components/windback/WindbackFrame.svelte.d.ts +7 -0
- package/dist/components/windback/index.js +1 -0
- package/dist/index.d.ts +18 -0
- package/{src/lib/index.ts → dist/index.js} +2 -15
- package/dist/services/svelte-client.svelte.d.ts +278 -0
- package/dist/services/svelte-client.svelte.js +678 -0
- package/dist/utils/entity-apply.d.ts +17 -0
- package/dist/utils/entity-apply.js +35 -0
- package/dist/utils/entity-diff.d.ts +40 -0
- package/dist/utils/entity-diff.js +57 -0
- package/dist/utils/entity-tree.d.ts +24 -0
- package/dist/utils/entity-tree.js +111 -0
- package/package.json +13 -9
- package/.prettierignore +0 -4
- package/.prettierrc +0 -15
- package/src/app.d.ts +0 -13
- package/src/app.html +0 -12
- package/src/lib/components/EntityDiffBadge.svelte +0 -18
- package/src/lib/components/Logs.svelte +0 -37
- package/src/lib/components/Query.svelte +0 -34
- package/src/lib/components/Report.svelte +0 -25
- package/src/lib/components/Search.svelte +0 -85
- package/src/lib/components/ServerView.svelte +0 -95
- package/src/lib/components/state/resolutions.ts +0 -137
- package/src/lib/components/state/viewstate.svelte.ts +0 -375
- package/src/lib/components/state/windback.svelte.ts +0 -88
- package/src/lib/components/transactions/EntityHistory.svelte +0 -173
- package/src/lib/components/transactions/TransactionDetails.svelte +0 -26
- package/src/lib/components/transactions/Transactions.svelte +0 -111
- package/src/lib/services/svelte-client.svelte.ts +0 -863
- package/src/lib/utils/entity-apply.ts +0 -47
- package/src/lib/utils/entity-diff.ts +0 -105
- package/src/lib/utils/entity-tree.ts +0 -130
- package/src/routes/+page.svelte +0 -3
- package/static/favicon.png +0 -0
- package/svelte.config.js +0 -18
- package/tsconfig.json +0 -13
- package/vite.config.ts +0 -6
- /package/{src/lib/components/windback/index.ts → dist/components/windback/index.d.ts} +0 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte-friendly Myko client wrapper
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive state using Svelte 5 runes and SvelteMap 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
|
+
import { ConnectionStatus, MykoClient } from '@myko/core';
|
|
9
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
10
|
+
import { Subject } from 'rxjs';
|
|
11
|
+
import { untrack } from 'svelte';
|
|
12
|
+
function stableStringify(value) {
|
|
13
|
+
const seen = new WeakSet();
|
|
14
|
+
try {
|
|
15
|
+
return JSON.stringify(value, (_key, raw) => {
|
|
16
|
+
if (typeof raw === 'bigint') {
|
|
17
|
+
return `__bigint:${raw.toString()}`;
|
|
18
|
+
}
|
|
19
|
+
if (!raw || typeof raw !== 'object') {
|
|
20
|
+
return raw;
|
|
21
|
+
}
|
|
22
|
+
if (seen.has(raw)) {
|
|
23
|
+
return '__circular__';
|
|
24
|
+
}
|
|
25
|
+
seen.add(raw);
|
|
26
|
+
if (Array.isArray(raw)) {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
const sorted = {};
|
|
30
|
+
for (const key of Object.keys(raw).sort()) {
|
|
31
|
+
sorted[key] = raw[key];
|
|
32
|
+
}
|
|
33
|
+
return sorted;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function requestKey(kind, request) {
|
|
41
|
+
const serialized = stableStringify(request);
|
|
42
|
+
return serialized ? `${kind}:${serialized}` : null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Svelte-friendly Myko client
|
|
46
|
+
*
|
|
47
|
+
* Wraps MykoClient to provide reactive Svelte state with automatic lifecycle management.
|
|
48
|
+
*/
|
|
49
|
+
export class SvelteMykoClient {
|
|
50
|
+
client;
|
|
51
|
+
// Command lifecycle subjects
|
|
52
|
+
commandSentSubject = new Subject();
|
|
53
|
+
commandSuccessSubject = new Subject();
|
|
54
|
+
commandErrorSubject = new Subject();
|
|
55
|
+
/** Observable of all commands when sent (before response) */
|
|
56
|
+
commandSent$ = this.commandSentSubject.asObservable();
|
|
57
|
+
/** Observable of all command successes */
|
|
58
|
+
commandSuccess$ = this.commandSuccessSubject.asObservable();
|
|
59
|
+
/** Observable of all command errors */
|
|
60
|
+
commandError$ = this.commandErrorSubject.asObservable();
|
|
61
|
+
// Reactive connection status
|
|
62
|
+
#connectionStatus = $state(ConnectionStatus.Disconnected);
|
|
63
|
+
// Reactive connection stats
|
|
64
|
+
#stats = $state(null);
|
|
65
|
+
statsSubscription = null;
|
|
66
|
+
constructor() {
|
|
67
|
+
this.client = new MykoClient();
|
|
68
|
+
// Sync connection status to reactive state
|
|
69
|
+
this.client.connectionStatus$.subscribe((status) => {
|
|
70
|
+
this.#connectionStatus = status;
|
|
71
|
+
// Start/stop stats subscription based on connection
|
|
72
|
+
if (status === ConnectionStatus.Connected && !this.statsSubscription) {
|
|
73
|
+
this.statsSubscription = this.client.stats().subscribe((stats) => {
|
|
74
|
+
this.#stats = stats;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (status === ConnectionStatus.Disconnected && this.statsSubscription) {
|
|
78
|
+
this.statsSubscription.unsubscribe();
|
|
79
|
+
this.statsSubscription = null;
|
|
80
|
+
this.#stats = null;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Current connection status (reactive) */
|
|
85
|
+
get connectionStatus() {
|
|
86
|
+
return this.#connectionStatus;
|
|
87
|
+
}
|
|
88
|
+
/** Whether currently connected (reactive) */
|
|
89
|
+
get isConnected() {
|
|
90
|
+
return this.#connectionStatus === ConnectionStatus.Connected;
|
|
91
|
+
}
|
|
92
|
+
/** Current connection stats (reactive, null when disconnected) */
|
|
93
|
+
get stats() {
|
|
94
|
+
return this.#stats;
|
|
95
|
+
}
|
|
96
|
+
/** Set the server address and connect */
|
|
97
|
+
connect(address) {
|
|
98
|
+
this.client.setAddress(address);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Enable automatic peer discovery via GetPeerServers query.
|
|
102
|
+
* When enabled, the client will automatically add discovered peer servers
|
|
103
|
+
* to the connection pool for redundancy and load balancing.
|
|
104
|
+
*/
|
|
105
|
+
enablePeerDiscovery(enabled, secure = false) {
|
|
106
|
+
this.client.enablePeerDiscovery(enabled, secure);
|
|
107
|
+
}
|
|
108
|
+
/** Set authentication token for commands */
|
|
109
|
+
setToken(token) {
|
|
110
|
+
this.client.setToken(token);
|
|
111
|
+
}
|
|
112
|
+
/** Disconnect from the server */
|
|
113
|
+
disconnect() {
|
|
114
|
+
// Unsubscribe stats
|
|
115
|
+
if (this.statsSubscription) {
|
|
116
|
+
this.statsSubscription.unsubscribe();
|
|
117
|
+
this.statsSubscription = null;
|
|
118
|
+
this.#stats = null;
|
|
119
|
+
}
|
|
120
|
+
this.client.disconnect();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a live report subscription with automatic lifecycle management.
|
|
124
|
+
*
|
|
125
|
+
* The subscription automatically:
|
|
126
|
+
* - Re-subscribes when dependencies in the factory function change
|
|
127
|
+
* - Cleans up when the component unmounts
|
|
128
|
+
* - No manual release() call needed
|
|
129
|
+
*
|
|
130
|
+
* If the factory returns null/undefined, no subscription is created and value remains undefined.
|
|
131
|
+
*
|
|
132
|
+
* IMPORTANT: Must be called during component initialization (in the <script> block),
|
|
133
|
+
* not inside event handlers or other callbacks.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```svelte
|
|
137
|
+
* <script>
|
|
138
|
+
* let { nodeId, sessionId } = $props()
|
|
139
|
+
* const output = client.liveReport(() =>
|
|
140
|
+
* sessionId ? new BindingNodeOutputValue({ nodeId, sessionId }) : null
|
|
141
|
+
* )
|
|
142
|
+
* </script>
|
|
143
|
+
*
|
|
144
|
+
* <div>{output.value?.datagram.data}</div>
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
liveReport(factory) {
|
|
148
|
+
let value = $state(undefined);
|
|
149
|
+
let error = $state(undefined);
|
|
150
|
+
const reportKey = $derived.by(() => {
|
|
151
|
+
const report = factory();
|
|
152
|
+
if (!report)
|
|
153
|
+
return null;
|
|
154
|
+
return requestKey('report', report);
|
|
155
|
+
});
|
|
156
|
+
$effect(() => {
|
|
157
|
+
const key = reportKey;
|
|
158
|
+
const report = untrack(factory);
|
|
159
|
+
// If factory returns null/undefined, don't subscribe
|
|
160
|
+
if (!report) {
|
|
161
|
+
value = undefined;
|
|
162
|
+
error = undefined;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!key) {
|
|
166
|
+
value = undefined;
|
|
167
|
+
error = new Error('Failed to compute stable report key');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
error = undefined;
|
|
171
|
+
const subscription = this.client.watchReport(report).subscribe({
|
|
172
|
+
next: (result) => {
|
|
173
|
+
value = result;
|
|
174
|
+
error = undefined;
|
|
175
|
+
},
|
|
176
|
+
error: (e) => {
|
|
177
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return () => subscription.unsubscribe();
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
get value() {
|
|
184
|
+
return value;
|
|
185
|
+
},
|
|
186
|
+
get error() {
|
|
187
|
+
return error;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Create a live query subscription with automatic lifecycle management.
|
|
193
|
+
*
|
|
194
|
+
* The subscription automatically:
|
|
195
|
+
* - Re-subscribes when dependencies in the factory function change
|
|
196
|
+
* - Cleans up when the component unmounts
|
|
197
|
+
* - No manual release() call needed
|
|
198
|
+
*
|
|
199
|
+
* If the factory returns null/undefined, no subscription is created and items map is cleared.
|
|
200
|
+
*
|
|
201
|
+
* IMPORTANT: Must be called during component initialization (in the <script> block),
|
|
202
|
+
* not inside event handlers or other callbacks.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```svelte
|
|
206
|
+
* <script>
|
|
207
|
+
* let { sceneId } = $props()
|
|
208
|
+
* const nodes = client.liveQuery(() =>
|
|
209
|
+
* sceneId ? new GetBindingNodesByQuery({ sceneId }) : null
|
|
210
|
+
* )
|
|
211
|
+
* </script>
|
|
212
|
+
*
|
|
213
|
+
* {#each nodes.items as [id, node] (id)}
|
|
214
|
+
* <div>{node.name}</div>
|
|
215
|
+
* {/each}
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
liveQuery(factory) {
|
|
219
|
+
const items = new SvelteMap();
|
|
220
|
+
let resolved = $state(false);
|
|
221
|
+
let error = $state(undefined);
|
|
222
|
+
const queryKey = $derived.by(() => {
|
|
223
|
+
const query = factory();
|
|
224
|
+
if (!query)
|
|
225
|
+
return null;
|
|
226
|
+
return requestKey('query', query);
|
|
227
|
+
});
|
|
228
|
+
$effect(() => {
|
|
229
|
+
const key = queryKey;
|
|
230
|
+
const query = untrack(factory);
|
|
231
|
+
// If factory returns null/undefined, clear and don't subscribe
|
|
232
|
+
if (!query) {
|
|
233
|
+
items.clear();
|
|
234
|
+
resolved = false;
|
|
235
|
+
error = undefined;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!key) {
|
|
239
|
+
items.clear();
|
|
240
|
+
resolved = false;
|
|
241
|
+
error = new Error('Failed to compute stable query key');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
error = undefined;
|
|
245
|
+
const subscription = this.client.watchQueryDiff(query).subscribe({
|
|
246
|
+
next: (diff) => {
|
|
247
|
+
if (diff.sequence === 0n) {
|
|
248
|
+
items.clear();
|
|
249
|
+
}
|
|
250
|
+
for (const id of diff.deletes) {
|
|
251
|
+
items.delete(id);
|
|
252
|
+
}
|
|
253
|
+
for (const item of diff.upserts) {
|
|
254
|
+
const typedItem = item;
|
|
255
|
+
items.set(typedItem.id, typedItem);
|
|
256
|
+
}
|
|
257
|
+
resolved = true;
|
|
258
|
+
error = undefined;
|
|
259
|
+
},
|
|
260
|
+
error: (e) => {
|
|
261
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return () => {
|
|
265
|
+
subscription.unsubscribe();
|
|
266
|
+
items.clear();
|
|
267
|
+
resolved = false;
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
items,
|
|
272
|
+
get resolved() {
|
|
273
|
+
return resolved;
|
|
274
|
+
},
|
|
275
|
+
get error() {
|
|
276
|
+
return error;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** Create a live view subscription with automatic lifecycle management. */
|
|
281
|
+
liveView(factory) {
|
|
282
|
+
const items = new SvelteMap();
|
|
283
|
+
let resolved = $state(false);
|
|
284
|
+
let error = $state(undefined);
|
|
285
|
+
const viewKey = $derived.by(() => {
|
|
286
|
+
const view = factory();
|
|
287
|
+
if (!view)
|
|
288
|
+
return null;
|
|
289
|
+
return requestKey('view', view);
|
|
290
|
+
});
|
|
291
|
+
$effect(() => {
|
|
292
|
+
const key = viewKey;
|
|
293
|
+
const view = untrack(factory);
|
|
294
|
+
if (!view) {
|
|
295
|
+
items.clear();
|
|
296
|
+
resolved = false;
|
|
297
|
+
error = undefined;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!key) {
|
|
301
|
+
items.clear();
|
|
302
|
+
resolved = false;
|
|
303
|
+
error = new Error('Failed to compute stable view key');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
error = undefined;
|
|
307
|
+
const subscription = this.client.watchViewDiff(view).subscribe({
|
|
308
|
+
next: (diff) => {
|
|
309
|
+
if (diff.sequence === 0n) {
|
|
310
|
+
items.clear();
|
|
311
|
+
}
|
|
312
|
+
for (const id of diff.deletes) {
|
|
313
|
+
items.delete(id);
|
|
314
|
+
}
|
|
315
|
+
for (const item of diff.upserts) {
|
|
316
|
+
const typedItem = item;
|
|
317
|
+
items.set(typedItem.id, typedItem);
|
|
318
|
+
}
|
|
319
|
+
resolved = true;
|
|
320
|
+
error = undefined;
|
|
321
|
+
},
|
|
322
|
+
error: (e) => {
|
|
323
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
return () => {
|
|
327
|
+
subscription.unsubscribe();
|
|
328
|
+
items.clear();
|
|
329
|
+
resolved = false;
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
items,
|
|
334
|
+
get resolved() {
|
|
335
|
+
return resolved;
|
|
336
|
+
},
|
|
337
|
+
get error() {
|
|
338
|
+
return error;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Create a live windowed query with automatic lifecycle management.
|
|
344
|
+
*
|
|
345
|
+
* Uses server-side query windows while exposing Svelte-native reactive state.
|
|
346
|
+
*/
|
|
347
|
+
liveQueryWindowed(factory, optionsFactory) {
|
|
348
|
+
let items = $state([]);
|
|
349
|
+
let totalCount = $state(null);
|
|
350
|
+
let window = $state(null);
|
|
351
|
+
let resolved = $state(false);
|
|
352
|
+
let error = $state(undefined);
|
|
353
|
+
let setWindowFn = () => { };
|
|
354
|
+
const queryWindowKey = $derived.by(() => {
|
|
355
|
+
const query = factory();
|
|
356
|
+
if (!query)
|
|
357
|
+
return null;
|
|
358
|
+
const q = requestKey('query', query);
|
|
359
|
+
const o = stableStringify(optionsFactory?.());
|
|
360
|
+
if (!q)
|
|
361
|
+
return null;
|
|
362
|
+
return `${q}|options:${o ?? '__none__'}`;
|
|
363
|
+
});
|
|
364
|
+
$effect(() => {
|
|
365
|
+
const key = queryWindowKey;
|
|
366
|
+
const query = untrack(factory);
|
|
367
|
+
const options = untrack(() => optionsFactory?.());
|
|
368
|
+
if (!query) {
|
|
369
|
+
items = [];
|
|
370
|
+
totalCount = null;
|
|
371
|
+
window = null;
|
|
372
|
+
resolved = false;
|
|
373
|
+
error = undefined;
|
|
374
|
+
setWindowFn = () => { };
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (!key) {
|
|
378
|
+
items = [];
|
|
379
|
+
totalCount = null;
|
|
380
|
+
window = null;
|
|
381
|
+
resolved = false;
|
|
382
|
+
error = new Error('Failed to compute stable windowed query key');
|
|
383
|
+
setWindowFn = () => { };
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
error = undefined;
|
|
387
|
+
resolved = false;
|
|
388
|
+
const handle = this.client.watchQueryWindowed(query, options);
|
|
389
|
+
setWindowFn = handle.setWindow;
|
|
390
|
+
const itemsSub = handle.results$.subscribe({
|
|
391
|
+
next: (next) => {
|
|
392
|
+
items = next;
|
|
393
|
+
resolved = true;
|
|
394
|
+
error = undefined;
|
|
395
|
+
},
|
|
396
|
+
error: (e) => {
|
|
397
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
const infoSub = handle.windowInfo$.subscribe({
|
|
401
|
+
next: (info) => {
|
|
402
|
+
totalCount = info.totalCount;
|
|
403
|
+
window = info.window;
|
|
404
|
+
resolved = true;
|
|
405
|
+
error = undefined;
|
|
406
|
+
},
|
|
407
|
+
error: (e) => {
|
|
408
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return () => {
|
|
412
|
+
itemsSub.unsubscribe();
|
|
413
|
+
infoSub.unsubscribe();
|
|
414
|
+
items = [];
|
|
415
|
+
totalCount = null;
|
|
416
|
+
window = null;
|
|
417
|
+
resolved = false;
|
|
418
|
+
setWindowFn = () => { };
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
get items() {
|
|
423
|
+
return items;
|
|
424
|
+
},
|
|
425
|
+
get totalCount() {
|
|
426
|
+
return totalCount;
|
|
427
|
+
},
|
|
428
|
+
get window() {
|
|
429
|
+
return window;
|
|
430
|
+
},
|
|
431
|
+
get resolved() {
|
|
432
|
+
return resolved;
|
|
433
|
+
},
|
|
434
|
+
get error() {
|
|
435
|
+
return error;
|
|
436
|
+
},
|
|
437
|
+
setWindow(nextWindow) {
|
|
438
|
+
setWindowFn(nextWindow);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/** Create a live windowed view with automatic lifecycle management. */
|
|
443
|
+
liveViewWindowed(factory, optionsFactory) {
|
|
444
|
+
let items = $state([]);
|
|
445
|
+
let totalCount = $state(null);
|
|
446
|
+
let window = $state(null);
|
|
447
|
+
let resolved = $state(false);
|
|
448
|
+
let error = $state(undefined);
|
|
449
|
+
let setWindowFn = () => { };
|
|
450
|
+
const viewWindowKey = $derived.by(() => {
|
|
451
|
+
const view = factory();
|
|
452
|
+
if (!view)
|
|
453
|
+
return null;
|
|
454
|
+
const v = requestKey('view', view);
|
|
455
|
+
const o = stableStringify(optionsFactory?.());
|
|
456
|
+
if (!v)
|
|
457
|
+
return null;
|
|
458
|
+
return `${v}|options:${o ?? '__none__'}`;
|
|
459
|
+
});
|
|
460
|
+
$effect(() => {
|
|
461
|
+
const key = viewWindowKey;
|
|
462
|
+
const view = untrack(factory);
|
|
463
|
+
const options = untrack(() => optionsFactory?.());
|
|
464
|
+
if (!view) {
|
|
465
|
+
items = [];
|
|
466
|
+
totalCount = null;
|
|
467
|
+
window = null;
|
|
468
|
+
resolved = false;
|
|
469
|
+
error = undefined;
|
|
470
|
+
setWindowFn = () => { };
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (!key) {
|
|
474
|
+
items = [];
|
|
475
|
+
totalCount = null;
|
|
476
|
+
window = null;
|
|
477
|
+
resolved = false;
|
|
478
|
+
error = new Error('Failed to compute stable windowed view key');
|
|
479
|
+
setWindowFn = () => { };
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
error = undefined;
|
|
483
|
+
resolved = false;
|
|
484
|
+
const handle = this.client.watchViewWindowed(view, options);
|
|
485
|
+
setWindowFn = handle.setWindow;
|
|
486
|
+
const itemsSub = handle.results$.subscribe({
|
|
487
|
+
next: (next) => {
|
|
488
|
+
items = next;
|
|
489
|
+
resolved = true;
|
|
490
|
+
error = undefined;
|
|
491
|
+
},
|
|
492
|
+
error: (e) => {
|
|
493
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
const infoSub = handle.windowInfo$.subscribe({
|
|
497
|
+
next: (info) => {
|
|
498
|
+
totalCount = info.totalCount;
|
|
499
|
+
window = info.window;
|
|
500
|
+
resolved = true;
|
|
501
|
+
error = undefined;
|
|
502
|
+
},
|
|
503
|
+
error: (e) => {
|
|
504
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
return () => {
|
|
508
|
+
itemsSub.unsubscribe();
|
|
509
|
+
infoSub.unsubscribe();
|
|
510
|
+
items = [];
|
|
511
|
+
totalCount = null;
|
|
512
|
+
window = null;
|
|
513
|
+
resolved = false;
|
|
514
|
+
setWindowFn = () => { };
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
return {
|
|
518
|
+
get items() {
|
|
519
|
+
return items;
|
|
520
|
+
},
|
|
521
|
+
get totalCount() {
|
|
522
|
+
return totalCount;
|
|
523
|
+
},
|
|
524
|
+
get window() {
|
|
525
|
+
return window;
|
|
526
|
+
},
|
|
527
|
+
get resolved() {
|
|
528
|
+
return resolved;
|
|
529
|
+
},
|
|
530
|
+
get error() {
|
|
531
|
+
return error;
|
|
532
|
+
},
|
|
533
|
+
setWindow(nextWindow) {
|
|
534
|
+
setWindowFn(nextWindow);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Watch a query with Observable-based updates.
|
|
540
|
+
*
|
|
541
|
+
* Returns an Observable that emits the full array of items whenever
|
|
542
|
+
* the result set changes.
|
|
543
|
+
*
|
|
544
|
+
* @deprecated Use `liveQuery()` for component usage. This method requires manual
|
|
545
|
+
* subscription management and doesn't integrate with Svelte's lifecycle.
|
|
546
|
+
* Only use for non-component contexts (e.g., context classes).
|
|
547
|
+
*/
|
|
548
|
+
watchQuery(queryFactory) {
|
|
549
|
+
return this.client.watchQuery(queryFactory);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Watch a view with Observable-based updates.
|
|
553
|
+
*
|
|
554
|
+
* @deprecated Use `liveView()` for component usage. Kept for backward compatibility
|
|
555
|
+
* with existing service code paths that still consume observables.
|
|
556
|
+
*/
|
|
557
|
+
watchView(viewFactory) {
|
|
558
|
+
return this.client.watchView(viewFactory);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Watch a query with optional server-side windowing.
|
|
562
|
+
*
|
|
563
|
+
* @deprecated Use `liveQuery()` (or `liveQueryWindowed()` for runes-native windowing)
|
|
564
|
+
* in component/service rune contexts.
|
|
565
|
+
*/
|
|
566
|
+
watchQueryWithOptions(queryFactory, options) {
|
|
567
|
+
return this.client.watchQuery(queryFactory, options);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Watch a view with optional server-side windowing.
|
|
571
|
+
*
|
|
572
|
+
* @deprecated Use `liveView()` in component/service rune contexts.
|
|
573
|
+
*/
|
|
574
|
+
watchViewWithOptions(viewFactory, options) {
|
|
575
|
+
return this.client.watchView(viewFactory, options);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Watch a query and mutate its server-side window without re-subscribing.
|
|
579
|
+
*
|
|
580
|
+
* @deprecated Use `liveQueryWindowed()` in component/service rune contexts.
|
|
581
|
+
*/
|
|
582
|
+
watchQueryWindowed(queryFactory, options) {
|
|
583
|
+
return this.client.watchQueryWindowed(queryFactory, options);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Watch a view and mutate its server-side window without re-subscribing.
|
|
587
|
+
*
|
|
588
|
+
* @deprecated Use `liveView()` in component/service rune contexts.
|
|
589
|
+
*/
|
|
590
|
+
watchViewWindowed(viewFactory, options) {
|
|
591
|
+
return this.client.watchViewWindowed(viewFactory, options);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Watch a report with Observable-based updates.
|
|
595
|
+
*
|
|
596
|
+
* Returns an Observable that emits whenever the report result changes.
|
|
597
|
+
*
|
|
598
|
+
* @deprecated Use `liveReport()` for component usage. This method requires manual
|
|
599
|
+
* subscription management and doesn't integrate with Svelte's lifecycle.
|
|
600
|
+
* Only use for non-component contexts (e.g., context classes).
|
|
601
|
+
*/
|
|
602
|
+
watchReport(reportFactory) {
|
|
603
|
+
return this.client.watchReport(reportFactory);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Send a command and wait for the response.
|
|
607
|
+
*
|
|
608
|
+
* Emits to commandSuccess$ or commandError$ observables for generic handling.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```svelte
|
|
612
|
+
* <script>
|
|
613
|
+
* async function deleteMachine(id: string) {
|
|
614
|
+
* const result = await myko.sendCommand(new DeleteMachine({ id }))
|
|
615
|
+
* console.log('Deleted:', result)
|
|
616
|
+
* }
|
|
617
|
+
* </script>
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
async sendCommand(commandFactory) {
|
|
621
|
+
const commandId = commandFactory.commandId;
|
|
622
|
+
this.commandSentSubject.next({ commandId });
|
|
623
|
+
try {
|
|
624
|
+
const response = await this.client.sendCommand(commandFactory);
|
|
625
|
+
this.commandSuccessSubject.next({ commandId, response });
|
|
626
|
+
return response;
|
|
627
|
+
}
|
|
628
|
+
catch (e) {
|
|
629
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
630
|
+
this.commandErrorSubject.next({ commandId, error });
|
|
631
|
+
throw e;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/** Access the underlying MykoClient for advanced use cases */
|
|
635
|
+
get raw() {
|
|
636
|
+
return this.client;
|
|
637
|
+
}
|
|
638
|
+
/** Measure round-trip latency */
|
|
639
|
+
ping() {
|
|
640
|
+
return this.client.ping();
|
|
641
|
+
}
|
|
642
|
+
/** Observable of all errors from the server */
|
|
643
|
+
get errors() {
|
|
644
|
+
return this.client.errors$;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Watch command completion status.
|
|
648
|
+
* Note: This is a compatibility shim - the underlying MykoClient doesn't track
|
|
649
|
+
* command completions the same way as the legacy WSMClient.
|
|
650
|
+
*/
|
|
651
|
+
watchCommandStatus() {
|
|
652
|
+
// Return empty observable - command tracking is handled via sendCommand promises
|
|
653
|
+
return new Subject().asObservable();
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Clear a command completion from tracking.
|
|
657
|
+
* Note: This is a compatibility shim - no-op in the new client.
|
|
658
|
+
*/
|
|
659
|
+
clearCommandCompletion(_tx) {
|
|
660
|
+
// No-op - command tracking is handled via sendCommand promises
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/** Global singleton client instance (auto-initialized) */
|
|
664
|
+
export const myko = new SvelteMykoClient();
|
|
665
|
+
// HMR cleanup - disconnect old client when module is hot-reloaded
|
|
666
|
+
if (import.meta.hot) {
|
|
667
|
+
import.meta.hot.dispose(() => {
|
|
668
|
+
myko.disconnect();
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/** Get the global MykoClient instance */
|
|
672
|
+
export function getMykoClient() {
|
|
673
|
+
return myko;
|
|
674
|
+
}
|
|
675
|
+
/** Create a new SvelteMykoClient instance (non-singleton, for advanced use) */
|
|
676
|
+
export function createMykoClient() {
|
|
677
|
+
return new SvelteMykoClient();
|
|
678
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExportedEntity, DiffStatus, FieldDiff } from './entity-diff.js';
|
|
2
|
+
export interface ApplyPlan {
|
|
3
|
+
toSet: ExportedEntity[];
|
|
4
|
+
toDel: ExportedEntity[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Compute the minimal SET/DEL operations to apply an import.
|
|
8
|
+
*
|
|
9
|
+
* @param diffs - Flat diff map from diffEntityLists()
|
|
10
|
+
* @param allFkFields - All FK field names used in BelongsTo relationships (for cascade detection)
|
|
11
|
+
* @param excluded - Optional set of "Type:id" keys to skip (user chose to keep current version)
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeApplyPlan(diffs: Map<string, {
|
|
14
|
+
status: DiffStatus;
|
|
15
|
+
fields?: FieldDiff[];
|
|
16
|
+
entity: ExportedEntity;
|
|
17
|
+
}>, allFkFields: string[], excluded?: Set<string>): ApplyPlan;
|