@myko/ui-svelte 4.8.1 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -15,4 +15,4 @@ export * from './utils/entity-diff';
15
15
  export * from './utils/entity-tree';
16
16
  export * from './utils/entity-apply';
17
17
  export { createMykoClient, getMykoClient, myko, myko as client, SvelteMykoClient, type CommandError, type CommandSuccess, type LiveQuery, type LiveReport } from './services/svelte-client.svelte';
18
- export { ConnectionStatus, type ClientStats } from '@myko/core';
18
+ export { ConnectionStatus, MykoProtocol, type ClientStats } from '@myko/core';
package/dist/index.js CHANGED
@@ -19,4 +19,4 @@ export * from './utils/entity-apply';
19
19
  // Svelte-friendly Myko client
20
20
  export { createMykoClient, getMykoClient, myko, myko as client, SvelteMykoClient } from './services/svelte-client.svelte';
21
21
  // Re-export useful types from @myko/core
22
- export { ConnectionStatus } from '@myko/core';
22
+ export { ConnectionStatus, MykoProtocol } from '@myko/core';
@@ -5,7 +5,7 @@
5
5
  * Queries and reports are deduplicated - multiple calls with the same args share
6
6
  * the same subscription and are only cancelled when all consumers unsubscribe.
7
7
  */
8
- import { ConnectionStatus, MykoClient, type ClientStats, type Command, type CommandResult, type MykoError, type Query, type QueryWindow, type QueryWatchOptions, type QueryItem, type QueryWindowInfo, type QueryResult, type View, type ViewItem, type ViewResult, type Report, type ReportResult } from '@myko/core';
8
+ import { ConnectionStatus, MykoClient, MykoProtocol, type ClientStats, type Command, type CommandResult, type MykoError, type Query, type QueryWindow, type QueryWatchOptions, type QueryItem, type QueryWindowInfo, type QueryResult, type View, type ViewItem, type ViewResult, type Report, type ReportResult } from '@myko/core';
9
9
  import { SvelteMap } from 'svelte/reactivity';
10
10
  import { type Observable } from 'rxjs';
11
11
  /** Command sent event (before response) */
@@ -107,6 +107,8 @@ export declare class SvelteMykoClient {
107
107
  enablePeerDiscovery(enabled: boolean, secure?: boolean): void;
108
108
  /** Set authentication token for commands */
109
109
  setToken(token: string | null): void;
110
+ /** Set the wire protocol (JSON or CBOR). Default is JSON. */
111
+ setProtocol(protocol: MykoProtocol): void;
110
112
  /** Disconnect from the server */
111
113
  disconnect(): void;
112
114
  /**
@@ -5,7 +5,7 @@
5
5
  * Queries and reports are deduplicated - multiple calls with the same args share
6
6
  * the same subscription and are only cancelled when all consumers unsubscribe.
7
7
  */
8
- import { ConnectionStatus, MykoClient } from '@myko/core';
8
+ import { ConnectionStatus, MykoClient, MykoProtocol } from '@myko/core';
9
9
  import { SvelteMap } from 'svelte/reactivity';
10
10
  import { Subject } from 'rxjs';
11
11
  import { untrack } from 'svelte';
@@ -41,6 +41,62 @@ function requestKey(kind, request) {
41
41
  const serialized = stableStringify(request);
42
42
  return serialized ? `${kind}:${serialized}` : null;
43
43
  }
44
+ /**
45
+ * Frame-paced callback dispatcher for incoming report values.
46
+ *
47
+ * Default behaviour: every queued callback runs on the next animation frame.
48
+ * This already coalesces multiple updates per anchor into a single Svelte
49
+ * effect cascade per frame and combines bursts spanning several WS frames.
50
+ *
51
+ * Burst handling: when the queue grows past `BURST_THRESHOLD` (i.e. an
52
+ * initial scene load lands hundreds of pending updates at once), the flush
53
+ * processes only `BURST_BATCH` callbacks per frame and reschedules the rest.
54
+ * That spreads the cost across multiple frames so the main thread can paint
55
+ * between chunks. Steady-state high-frequency updates stay below the
56
+ * threshold and run every frame with no added lag.
57
+ *
58
+ * This logic belongs in the Svelte client (browser-specific) — the core
59
+ * MykoClient stays runtime-agnostic.
60
+ */
61
+ const BURST_THRESHOLD = 96;
62
+ const BURST_BATCH = 32;
63
+ let pendingValueCallbacks = [];
64
+ let valueFlushScheduled = false;
65
+ function scheduleFlush() {
66
+ if (valueFlushScheduled)
67
+ return;
68
+ valueFlushScheduled = true;
69
+ if (typeof requestAnimationFrame === 'function') {
70
+ requestAnimationFrame(flushPendingValues);
71
+ }
72
+ else {
73
+ setTimeout(flushPendingValues, 0);
74
+ }
75
+ }
76
+ function scheduleValueDispatch(cb) {
77
+ pendingValueCallbacks.push(cb);
78
+ scheduleFlush();
79
+ }
80
+ function flushPendingValues() {
81
+ valueFlushScheduled = false;
82
+ if (pendingValueCallbacks.length <= BURST_THRESHOLD) {
83
+ // Normal path: process everything queued this frame. Per-anchor
84
+ // coalescing in liveReport keeps the queue size proportional to the
85
+ // number of distinct anchors with new data, so this scales naturally.
86
+ const drained = pendingValueCallbacks;
87
+ pendingValueCallbacks = [];
88
+ for (const cb of drained)
89
+ cb();
90
+ return;
91
+ }
92
+ // Burst: spread across multiple frames so the user sees mounted nodes
93
+ // before the entire value cascade lands.
94
+ const batch = pendingValueCallbacks.splice(0, BURST_BATCH);
95
+ for (const cb of batch)
96
+ cb();
97
+ if (pendingValueCallbacks.length > 0)
98
+ scheduleFlush();
99
+ }
44
100
  /**
45
101
  * Svelte-friendly Myko client
46
102
  *
@@ -109,6 +165,10 @@ export class SvelteMykoClient {
109
165
  setToken(token) {
110
166
  this.client.setToken(token);
111
167
  }
168
+ /** Set the wire protocol (JSON or CBOR). Default is JSON. */
169
+ setProtocol(protocol) {
170
+ this.client.setProtocol(protocol);
171
+ }
112
172
  /** Disconnect from the server */
113
173
  disconnect() {
114
174
  // Unsubscribe stats
@@ -153,10 +213,18 @@ export class SvelteMykoClient {
153
213
  return null;
154
214
  return requestKey('report', report);
155
215
  });
216
+ // Use the direct-callback subscribeReport instead of `watchReport().subscribe`.
217
+ // The Observable form wraps the per-tx Subject in `pipe(map, finalize,
218
+ // shareReplay, map)` — each subscriber walks that operator chain on
219
+ // subscribe, and a workspace with hundreds of anchor views was paying
220
+ // measurable cost in RxJS plumbing during scene mount.
221
+ //
222
+ // Cell writes (`value = result`) are routed through the frame-paced
223
+ // dispatcher so a burst of incoming responses doesn't all cascade
224
+ // through Svelte effects in a single task.
156
225
  $effect(() => {
157
226
  const key = reportKey;
158
227
  const report = untrack(factory);
159
- // If factory returns null/undefined, don't subscribe
160
228
  if (!report) {
161
229
  value = undefined;
162
230
  error = undefined;
@@ -168,16 +236,32 @@ export class SvelteMykoClient {
168
236
  return;
169
237
  }
170
238
  error = undefined;
171
- const subscription = this.client.watchReport(report).subscribe({
172
- next: (result) => {
173
- value = result;
239
+ let disposed = false;
240
+ let latestResult = undefined;
241
+ let hasPending = false;
242
+ const dispose = this.client.subscribeReport(report, (result) => {
243
+ if (disposed)
244
+ return;
245
+ latestResult = result;
246
+ if (hasPending)
247
+ return;
248
+ hasPending = true;
249
+ scheduleValueDispatch(() => {
250
+ hasPending = false;
251
+ if (disposed)
252
+ return;
253
+ value = latestResult;
174
254
  error = undefined;
175
- },
176
- error: (e) => {
177
- error = e instanceof Error ? e : new Error(String(e));
178
- }
255
+ });
256
+ }, (e) => {
257
+ if (disposed)
258
+ return;
259
+ error = e;
179
260
  });
180
- return () => subscription.unsubscribe();
261
+ return () => {
262
+ disposed = true;
263
+ dispose();
264
+ };
181
265
  });
182
266
  return {
183
267
  get value() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
- "@myko/core": "^4.8.1",
3
+ "@myko/core": "^4.11.0",
4
4
  "luxon": "^3.5.0",
5
5
  "ramda": "^0.30.1",
6
6
  "rxjs": "^7.8.1",
@@ -64,7 +64,7 @@
64
64
  "**/*.css"
65
65
  ],
66
66
  "type": "module",
67
- "version": "4.8.1",
67
+ "version": "4.11.0",
68
68
  "main": "./dist/index.js",
69
69
  "types": "./dist/index.d.ts"
70
70
  }