svelte-adapter-uws 0.6.0-next.39 → 0.6.0-next.40
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/README.md +7 -0
- package/package.json +1 -1
- package/src/index.d.ts +7 -1
- package/src/runtime/handler/platform.js +6 -1
- package/src/runtime/utils/epoch.js +13 -5
- package/src/testing.d.ts +3 -2
- package/src/vite.js +4 -1
package/README.md
CHANGED
|
@@ -1205,6 +1205,13 @@ Every published frame is also stamped with a monotonic per-topic `seq` field in
|
|
|
1205
1205
|
platform.publish(`cursor:${userId}`, 'move', pos, { seq: false });
|
|
1206
1206
|
```
|
|
1207
1207
|
|
|
1208
|
+
Pass `{ jitterMs }` to de-herd a thundering-herd broadcast: the frame carries a de-herd WINDOW, and each receiving client rolls its own random delay in `[0, jitterMs)` before dispatching, so a broadcast that makes N clients all react ramps across the window instead of spiking at t+0. The outbound fan-out stays a single native publish - the window is carried verbatim, never a server-rolled offset (which would defer every subscriber identically). Omit / `0` = immediate; the no-jitter frame is byte-identical to before. `svelte-realtime`'s `ctx.publish(..., { jitterMs })` validates the window to a 60s ceiling and the client clamps it again:
|
|
1209
|
+
|
|
1210
|
+
```js
|
|
1211
|
+
// One reroute event; 50k clients ramp their re-fetch across 5s instead of at once
|
|
1212
|
+
platform.publish('route:i95-incident', 'reroute', detour, { jitterMs: 5000 });
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1208
1215
|
```js
|
|
1209
1216
|
// src/routes/todos/+page.server.js
|
|
1210
1217
|
export const actions = {
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1407,6 +1407,12 @@ export interface Platform {
|
|
|
1407
1407
|
* unless `websocket.compression` is configured, where text frames
|
|
1408
1408
|
* compress by default; pass `false` for a high-frequency,
|
|
1409
1409
|
* high-fan-out topic (deflate CPU scales per subscriber).
|
|
1410
|
+
* - `jitterMs: <ms>` stamps a de-herd window on the frame. Each receiving
|
|
1411
|
+
* client rolls its own random delay in `[0, jitterMs)` before handing the
|
|
1412
|
+
* frame to its subscribers, so a single broadcast that makes N clients all
|
|
1413
|
+
* act (retry, refetch, re-render) ramps across the window instead of
|
|
1414
|
+
* spiking at t+0. The outbound fan-out stays one native publish; only the
|
|
1415
|
+
* clients' local dispatch is staggered. Omit / `0` = immediate (default).
|
|
1410
1416
|
*
|
|
1411
1417
|
* @example
|
|
1412
1418
|
* ```js
|
|
@@ -1417,7 +1423,7 @@ export interface Platform {
|
|
|
1417
1423
|
* }
|
|
1418
1424
|
* ```
|
|
1419
1425
|
*/
|
|
1420
|
-
publish(topic: string, event: string, data?: unknown, options?: { relay?: boolean; seq?: boolean; compress?: boolean }): boolean;
|
|
1426
|
+
publish(topic: string, event: string, data?: unknown, options?: { relay?: boolean; seq?: boolean; compress?: boolean; jitterMs?: number }): boolean;
|
|
1421
1427
|
|
|
1422
1428
|
/**
|
|
1423
1429
|
* Publish via a plugin-declared binary wire codec. Subscribers that
|
|
@@ -30,7 +30,12 @@ export const platform = {
|
|
|
30
30
|
// is a bare set with no compare. Skipped when stamping is off so a
|
|
31
31
|
// {seq:false}-only topic never enters the convergence comparison.
|
|
32
32
|
if (seq !== null) maxSeenSeq.set(topic, seq);
|
|
33
|
-
|
|
33
|
+
// `{ jitterMs }` de-herd window: stamp it on the frame so each client rolls its
|
|
34
|
+
// own delay before dispatching (spreads N receivers' follow-up actions across
|
|
35
|
+
// the window). The window is carried verbatim - NOT a server-rolled offset,
|
|
36
|
+
// which would defer every subscriber of this one frame identically.
|
|
37
|
+
const jitterMs = (options && typeof options.jitterMs === 'number' && options.jitterMs > 0) ? options.jitterMs : null;
|
|
38
|
+
const envelope = completeEnvelope(envelopePrefix(topic, event), data, seq, jitterMs);
|
|
34
39
|
// A zero-length frame at a send site would broadcast garbage to every
|
|
35
40
|
// subscriber - unrecoverable framing corruption. One length guard, identical
|
|
36
41
|
// in cost to the assert it replaces.
|
|
@@ -101,18 +101,26 @@ export function createHlc() {
|
|
|
101
101
|
* `{topic,event,data}` envelope verbatim. When `seq` is a number the
|
|
102
102
|
* resulting envelope is `{topic,event,data,seq}`.
|
|
103
103
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
104
|
+
* An optional `jitterMs` stamps a `j` field carrying the de-herd WINDOW (not a
|
|
105
|
+
* pre-rolled offset - one frame fans out to every subscriber, so a single rolled
|
|
106
|
+
* value would defer them all identically and spread nothing). Each client rolls
|
|
107
|
+
* its own delay in `[0, j)` before dispatching, so the receivers ramp instead of
|
|
108
|
+
* spiking. Omitted (`null`/`undefined`) leaves the wire shape unchanged.
|
|
109
|
+
*
|
|
110
|
+
* No JSON.stringify on seq/jitter: numbers serialize identically via plain string
|
|
111
|
+
* concatenation, saving a stringify call on the publish hot path. The no-jitter
|
|
112
|
+
* tail is byte-identical to the legacy envelope.
|
|
107
113
|
*
|
|
108
114
|
* @param {string} prefix output of envelopePrefix(topic, event)
|
|
109
115
|
* @param {unknown} data
|
|
110
116
|
* @param {number | null | undefined} seq
|
|
117
|
+
* @param {number | null | undefined} [jitterMs] de-herd window in ms
|
|
111
118
|
* @returns {string}
|
|
112
119
|
*/
|
|
113
|
-
export function completeEnvelope(prefix, data, seq) {
|
|
120
|
+
export function completeEnvelope(prefix, data, seq, jitterMs) {
|
|
114
121
|
const body = prefix + JSON.stringify(data ?? null);
|
|
115
|
-
|
|
122
|
+
const tail = jitterMs == null ? '}' : ',"j":' + jitterMs + '}';
|
|
123
|
+
return seq == null ? body + tail : body + ',"seq":' + seq + tail;
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
/**
|
package/src/testing.d.ts
CHANGED
|
@@ -207,9 +207,10 @@ export function esc(s: string): string;
|
|
|
207
207
|
* brace to a prebuilt envelope prefix. Pairs with the wire shape
|
|
208
208
|
* `{topic, event, data, seq?}`. When `seq` is `null` / `undefined` the
|
|
209
209
|
* field is omitted entirely so the envelope matches the legacy
|
|
210
|
-
* `{topic,event,data}` shape verbatim.
|
|
210
|
+
* `{topic,event,data}` shape verbatim. An optional `jitterMs` stamps a `j`
|
|
211
|
+
* de-herd window the client uses to roll its own dispatch delay. Pure.
|
|
211
212
|
*/
|
|
212
|
-
export function completeEnvelope(prefix: string, data: unknown, seq?: number | null): string;
|
|
213
|
+
export function completeEnvelope(prefix: string, data: unknown, seq?: number | null, jitterMs?: number | null): string;
|
|
213
214
|
|
|
214
215
|
/**
|
|
215
216
|
* Wrap an array of pre-built per-event envelope strings into a single
|
package/src/vite.js
CHANGED
|
@@ -119,7 +119,10 @@ export default function uws(options = {}) {
|
|
|
119
119
|
* @returns {boolean}
|
|
120
120
|
*/
|
|
121
121
|
function publish(topic, event, data, options) {
|
|
122
|
-
|
|
122
|
+
// Mirror the production `{ jitterMs }` de-herd window stamp (platform.publish):
|
|
123
|
+
// carry the window so each client rolls its own dispatch delay.
|
|
124
|
+
const jitterMs = (options && typeof options.jitterMs === 'number' && options.jitterMs > 0) ? options.jitterMs : null;
|
|
125
|
+
const envelope = '{"topic":' + esc(topic) + ',"event":' + esc(event) + ',"data":' + JSON.stringify(data ?? null) + (jitterMs == null ? '}' : ',"j":' + jitterMs + '}');
|
|
123
126
|
const excludeWs = (options && options.excludeWs) || null;
|
|
124
127
|
let sent = false;
|
|
125
128
|
for (const [ws, topics] of subscriptions) {
|