@myko/ui-svelte 4.8.0 → 4.10.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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
255
|
+
});
|
|
256
|
+
}, (e) => {
|
|
257
|
+
if (disposed)
|
|
258
|
+
return;
|
|
259
|
+
error = e;
|
|
179
260
|
});
|
|
180
|
-
return () =>
|
|
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.
|
|
3
|
+
"@myko/core": "^4.10.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.
|
|
67
|
+
"version": "4.10.0",
|
|
68
68
|
"main": "./dist/index.js",
|
|
69
69
|
"types": "./dist/index.d.ts"
|
|
70
70
|
}
|