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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-adapter-uws",
3
- "version": "0.6.0-next.39",
3
+ "version": "0.6.0-next.40",
4
4
  "publishConfig": {
5
5
  "tag": "next"
6
6
  },
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
- const envelope = completeEnvelope(envelopePrefix(topic, event), data, seq);
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
- * No JSON.stringify on the seq itself: numbers serialize identically
105
- * via plain string concatenation, saving a stringify call on the
106
- * publish hot path.
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
- return seq == null ? body + '}' : body + ',"seq":' + seq + '}';
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. Pure.
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
- const envelope = '{"topic":' + esc(topic) + ',"event":' + esc(event) + ',"data":' + JSON.stringify(data ?? null) + '}';
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) {