svelte-adapter-uws 0.5.7 → 0.5.8

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/files/handler.js CHANGED
@@ -366,12 +366,24 @@ const app = is_tls
366
366
  : uWS.App();
367
367
 
368
368
  // - Cross-worker pub/sub relay (batched) ------------------------------------
369
- // Batch postMessage calls within a single microtask. A SvelteKit action that
370
- // publishes N events sends one structured-clone across the thread boundary
371
- // instead of N. No-op in single-process mode (parentPort is null).
369
+ // Batch postMessage calls within a single event-loop iteration. A SvelteKit
370
+ // action that publishes N events sends one structured-clone across the thread
371
+ // boundary instead of N. No-op in single-process mode (parentPort is null).
372
+ //
373
+ // Why `setTimeout(0)` and not `queueMicrotask`: uWS dispatches each WS message
374
+ // as its own JS task, and N-API drains microtasks at the C++/JS boundary
375
+ // between tasks. A microtask-deferred flush fires BEFORE the next socket's
376
+ // handler runs, so cross-socket coalescing is impossible at the microtask
377
+ // level - N publishes from N socket handlers in the same iteration produce N
378
+ // postMessage structured-clones instead of one batched. `setTimeout(0)` lands
379
+ // in libuv's timers phase, which fires only after the poll phase has
380
+ // dispatched every ready socket message in the current iteration. Same
381
+ // structural choice the 0.5.6 cursor always-tick rewrite locked in.
372
382
 
373
383
  /** @type {Array<{topic: string, envelope: string}> | null} */
374
384
  let relayBatch = null;
385
+ /** @type {ReturnType<typeof setTimeout> | null} */
386
+ let relayTimer = null;
375
387
 
376
388
  /**
377
389
  * @param {string} topic
@@ -380,12 +392,14 @@ let relayBatch = null;
380
392
  function batchRelay(topic, envelope) {
381
393
  if (!relayBatch) {
382
394
  relayBatch = [];
383
- queueMicrotask(() => {
395
+ relayTimer = setTimeout(() => {
396
+ relayTimer = null;
384
397
  if (relayBatch) {
385
398
  parentPort.postMessage({ type: 'publish-batch', messages: relayBatch });
386
399
  }
387
400
  relayBatch = null;
388
- });
401
+ }, 0);
402
+ if (relayTimer.unref) relayTimer.unref();
389
403
  }
390
404
  relayBatch.push({ topic, envelope });
391
405
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-adapter-uws",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "publishConfig": {
5
5
  "tag": "latest"
6
6
  },
@@ -302,12 +302,28 @@ export function createPresence(options = {}) {
302
302
 
303
303
  /**
304
304
  * Per-topic pending diff buffer: latest op per key wins. Joins and leaves
305
- * happening on the same key in a tick collapse so the wire only sees the
306
- * net change. Flushed once per microtask via `scheduleDiffFlush`.
305
+ * happening on the same key in one event-loop iteration collapse so the
306
+ * wire only sees the net change. Flushed once per iteration via
307
+ * `setTimeout(() => flushDiffs(platform), 0)` armed when the first dirty
308
+ * entry lands.
309
+ *
310
+ * Why `setTimeout(0)` and not `queueMicrotask`: uWS dispatches each WS
311
+ * message as its own JS task, and N-API drains microtasks at the C++/JS
312
+ * boundary between tasks. A microtask-deferred flush fires BEFORE the
313
+ * next socket's handler runs, so cross-socket coalescing is impossible
314
+ * at the microtask level - a mass-join into a populated topic produces
315
+ * O(N) one-entry diffs instead of one batched diff. `setTimeout(0)`
316
+ * lands in libuv's timers phase, which fires only after the poll phase
317
+ * has dispatched every ready socket message in the current iteration -
318
+ * so all joins arriving together end up in one flush regardless of how
319
+ * many task boundaries separate them. Same structural choice the
320
+ * 0.5.6 cursor always-tick rewrite locked in.
321
+ *
307
322
  * @type {Map<string, Map<string, { op: 'join' | 'leave', data: Record<string, any> }>>}
308
323
  */
309
324
  const pendingDiffs = new Map();
310
- let diffFlushScheduled = false;
325
+ /** @type {ReturnType<typeof setTimeout> | null} */
326
+ let diffFlushTimer = null;
311
327
 
312
328
  /**
313
329
  * @param {string} topic
@@ -323,15 +339,18 @@ export function createPresence(options = {}) {
323
339
  pendingDiffs.set(topic, entries);
324
340
  }
325
341
  entries.set(key, { op, data });
326
- if (!diffFlushScheduled) {
327
- diffFlushScheduled = true;
328
- queueMicrotask(() => flushDiffs(platform));
342
+ if (diffFlushTimer === null) {
343
+ diffFlushTimer = setTimeout(() => flushDiffs(platform), 0);
344
+ if (diffFlushTimer.unref) diffFlushTimer.unref();
329
345
  }
330
346
  }
331
347
 
332
348
  /** @param {import('../../index.js').Platform} platform */
333
349
  function flushDiffs(platform) {
334
- diffFlushScheduled = false;
350
+ if (diffFlushTimer !== null) {
351
+ clearTimeout(diffFlushTimer);
352
+ diffFlushTimer = null;
353
+ }
335
354
  for (const [topic, entries] of pendingDiffs) {
336
355
  /** @type {Record<string, Record<string, any>>} */
337
356
  const joins = {};
@@ -554,21 +573,24 @@ export function createPresence(options = {}) {
554
573
  wsTopics.clear();
555
574
  topicPresence.clear();
556
575
  pendingDiffs.clear();
557
- diffFlushScheduled = false;
576
+ if (diffFlushTimer !== null) {
577
+ clearTimeout(diffFlushTimer);
578
+ diffFlushTimer = null;
579
+ }
558
580
  connCounter = 0;
559
581
  },
560
582
 
561
583
  /**
562
584
  * Drain any buffered diff publishes synchronously. Tests use this
563
- * to assert on the wire output without awaiting the microtask
564
- * queue. Production code generally does not need to call it - the
565
- * microtask flush happens automatically. Useful when a caller
585
+ * to assert on the wire output without awaiting the next-tick
586
+ * setTimeout flush. Production code generally does not need to call
587
+ * it - the tick flush happens automatically. Useful when a caller
566
588
  * needs presence state visible to other workers before its own
567
589
  * synchronous block returns (e.g. before responding to an HTTP
568
590
  * request that just triggered a leave).
569
591
  */
570
592
  flushDiffs() {
571
- if (!diffFlushScheduled || !_platform) return;
593
+ if (diffFlushTimer === null || !_platform) return;
572
594
  flushDiffs(_platform);
573
595
  },
574
596