Skip to content

feat(gateway): WebSocket client with MessagePack and structures#7

Merged
0xSaiNova merged 9 commits intodevfrom
3-sdk-gateway-websocket-client-with-messagepack
Feb 19, 2026
Merged

feat(gateway): WebSocket client with MessagePack and structures#7
0xSaiNova merged 9 commits intodevfrom
3-sdk-gateway-websocket-client-with-messagepack

Conversation

@0xSaiNova
Copy link
Copy Markdown
Member

Summary

  • Gateway connection manager handling the full lifecycle — connect, identify, ready, heartbeat with missed-ACK detection, auto-reconnect with exponential backoff and jitter, sequence tracking for future resume
  • Client class with typed event emitter mapping raw dispatch events to camelCase structure objects
  • Message, Server, Channel, User structure classes wrapping raw API shapes — Message exposes reply/edit/delete through ClientRef
  • Shared raw types deduplicating interfaces between REST and gateway
  • Patched several bugs caught during review: heartbeat timer not stopped on timeout, rate limit bucket spin loop replaced with promise-chain mutex, stale processQueue reset, REST retries bypassing bucket acquire, missing token guard
  • All structure constructors accept ClientRef to avoid breaking changes when REST methods land on them
  • CI workflow now runs vitest alongside typecheck/lint/build

Closes #3

Test plan

  • npm test passes — 17 tests across bucket, REST, and structures
  • tsc --noEmit clean
  • Manual bot connection against staging gateway
  • Force-disconnect to verify reconnect backoff

…#3]

Adds encode/decode wrappers over @msgpack/msgpack, GatewayState enum,
Opcodes constants, and typed payload interfaces (GatewayPayload, IdentifyData,
ReadyData). GatewayClient connection layer is pending architecture decisions.
WebSocket lifecycle with Identify on open, Ready handler, heartbeat loop
with missed-ACK tracking, exponential backoff reconnect, and sequence
number tracking. Gateway is internal — not exported from the package.
discord.js-style Client over an internal Gateway WebSocket manager.
Gateway handles connect/identify/heartbeat/reconnect with exponential
backoff and a clean intentionalClose flag. Client maps raw SCREAMING_SNAKE
dispatch events to camelCase EventEmitter events with typed overloads.

Structure classes (Message, Server, Channel, User) wrap raw API shapes.
Message exposes reply(), edit(), delete() via ClientRef to avoid circular
imports. Shared src/types.ts deduplicates raw interfaces between REST and
gateway. ReadyEvent emits structured User/Server objects, not raw wire data.
…caffold [refs #3]

Heartbeat interval wasn't stopped before closing the socket on
timeout — kept firing close() against an already-dying connection.
Also swapped the ws! non-null assertion in _onOpen to optional
chaining since disconnect() can race with the open event.

Rate limit bucket had a 10ms spin loop for mutual exclusion, which
is just wasteful. Replaced it with a promise-chain mutex. Also
fixed processQueue blindly resetting remaining after sleeping
without checking if update() shifted the window mid-wait.

REST retry loop was skipping bucket.acquire() on retries, so
retried requests flew under the bucket's radar entirely. And
there was no token guard — missing token just gave you unhelpful
401s instead of a clear error up front.

Gave Channel, Server, and User constructors a ClientRef param to
match Message, so adding REST methods to them later won't break
the constructor signature.

Added vitest + 17 tests for the bucket, REST token guard, snowflake
validation, and structure wiring.

Test: vitest run -- 17 passed, 0 failed (RateLimitBucket, REST, structures)
vitest is wired up now, CI should actually run it.

Test: n/a (workflow change)
Was gitignored, which broke actions/setup-node cache lookup.
npm ci needs the lockfile anyway.

Test: n/a (infra change)
tsconfig excludes *.test.ts but eslint was still trying to parse
them against it. Added ignorePatterns so they don't collide.

Test: eslint src --ext .ts — 0 errors, 1 pre-existing warning
…refs #3]

Wrapped structure instantiation in #wire() handlers with try-catch
so a malformed payload from the gateway doesn't crash the whole bot —
errors route to the 'error' event instead.

Capped incoming WebSocket frames at 4MB via maxPayload on the ws
constructor. Without it a rogue payload could eat all memory.

Swapped the Client event overload list for a proper ClientEvents
mapped type — less duplication and kills the no-explicit-any warning.

Added a 100ms backoff in the rate limit bucket's processQueue when
the reset window shifts mid-sleep, prevents tight looping on
inconsistent server headers.
@0xSaiNova 0xSaiNova merged commit 4c67d91 into dev Feb 19, 2026
3 checks passed
@0xSaiNova 0xSaiNova deleted the 3-sdk-gateway-websocket-client-with-messagepack branch February 19, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant