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