Throttle / Debounce

Per-topic publish rate limiting. Wraps platform.publish() to coalesce rapid-fire updates (mouse position, typing indicators, live metrics). Sends the latest value at most once per interval. No timers to manage yourself.

Two modes:

  • throttle(ms) - sends immediately on first call (leading edge), then at most once per interval (trailing edge). Latest value wins within each interval.
  • debounce(ms) - waits until no calls for the full interval, then sends the latest value. Each new call resets the timer.

Setup

import { throttle, debounce } from 'svelte-adapter-uws/plugins/throttle';

const mouse  = throttle(50);   // at most once per 50ms per topic
const search = debounce(300);  // wait for 300ms of silence

Usage

// In hooks.ws.js
import { mouse, search } from '$lib/server/rate-limiters';

export function message(ws, { data, platform }) {
  const msg = JSON.parse(Buffer.from(data).toString());

  if (msg.type === 'cursor') {
    // 60 mouse moves/sec from 20 users = 1200 publishes/sec
    // With throttle(50), each topic publishes at most 20/sec
    mouse.publish(platform, 'cursors', 'move', {
      userId: ws.getUserData().id,
      x: msg.x, y: msg.y
    });
  }

  if (msg.type === 'search') {
    // User types fast -- only publish when they pause
    search.publish(platform, 'search-results', 'query', { q: msg.q });
  }
}

Rate limiting is per-topic. If you call mouse.publish() for topics 'room-a' and 'room-b', each topic has its own independent timer.

API

MethodDescription
limiter.publish(platform, topic, event, data)Publish with rate limiting
limiter.flush()Send all pending immediately, clear all timers
limiter.flush(topic)Send pending for one topic
limiter.cancel()Discard all pending, clear all timers
limiter.cancel(topic)Discard pending for one topic
limiter.intervalThe configured interval in ms

How throttle works

t=0    publish({x:0})  --> sends immediately (leading edge)
t=10   publish({x:1})  --> stored (latest)
t=30   publish({x:2})  --> stored (overwrites x:1)
t=50   [timer fires]   --> sends {x:2} (trailing edge)
t=60   publish({x:3})  --> stored
t=100  [timer fires]   --> sends {x:3}
t=150  [timer fires]   --> nothing pending, goes idle
t=200  publish({x:4})  --> sends immediately (new leading edge)

How debounce works

t=0    publish({q:"h"})      --> stored, timer starts
t=80   publish({q:"he"})     --> stored, timer resets
t=160  publish({q:"hel"})    --> stored, timer resets
t=260  [timer fires, 100ms]  --> sends {q:"hel"}

Limitations

  • Server-side only. No client component - the client receives messages at the throttled rate naturally.
  • Latest value only. Intermediate values within an interval are discarded, not queued. If you need every message delivered, don’t throttle.
  • Timer-based. Uses setTimeout internally. Precision depends on Node.js event loop load (typically under 1ms drift).

Was this page helpful?