Files
2026-05-27 23:29:53 +01:00

3.4 KiB
Raw Permalink Blame History

publish — envelope encode/decode

(no submodules)

Purpose

This module handles wire-encoding for the Redis broker. Every published frame gets a one-byte encoding tag (ENCODING_JSON for uncompressed JSON, ENCODING_GZIP_JSON for gzipped JSON), the JSON bytes for the envelope, and nothing else. The same module owns the inverse path — decodeEnvelope — and all the input-validation that protects subscribers from non-broker traffic landing on a misconfigured channel.

Compression is applied conditionally based on the encoded JSON byte length versus the broker's configured compressionThresholdBytes. The 4 MiB hard cap (MAX_PAYLOAD_BYTES) is enforced both before and after compression so a runaway gzip-stream-bomb cannot sneak past the threshold check.

Features

  • One-byte encoding magic at the head of every frame. Unknown bytes fail fast with WS_BROKER_REDIS.DESERIALISE_FAILED.
  • Zod-validated envelope schema applied during decode so a misshapen frame cannot reach a subscriber handler.
  • Gzip applied via node:zlib's gzipSync/gunzipSync — synchronous, deterministic, no streaming state to leak across frames.
  • Hard 4 MiB cap enforced both pre- and post-compression.
  • Pure encode and decode functions — no I/O, trivially testable, and the broker's publish path calls these once per envelope.

Usage: Basic

import { decodeEnvelope, encodeEnvelope } from "@suaveplan/api-ws-broker-redis/publish";

const frame = encodeEnvelope(
    { topic: "orders", tenant: "t1" as any, payload: { id: 1 } },
    { compressionThresholdBytes: 1024, disableCompression: false }
);
const back = decodeEnvelope<{ id: number }>(frame.bytes);

Usage: Advanced

import {
    encodeEnvelope,
    ENCODING_GZIP_JSON,
} from "@suaveplan/api-ws-broker-redis/publish";

const frame = encodeEnvelope(envelope, {
    compressionThresholdBytes: 0, // compress everything
    disableCompression: false,
});
if (frame.bytes[0] !== ENCODING_GZIP_JSON) {
    throw new Error("expected gzip encoding for any-size payload");
}
// Inspect the actual ratio for tuning the threshold:
const ratio = frame.bytes.length / frame.rawByteLength;
metrics.compressionRatio.record(ratio);

API Reference

  • encodeEnvelope(envelope, opts) — encode a BrokerEnvelope into the wire frame. Returns { bytes, compressed, rawByteLength }. Throws WS_BROKER_REDIS.PAYLOAD_TOO_LARGE for frames above the hard cap.
  • decodeEnvelope<Payload>(bytes) — inverse of encodeEnvelope. Throws WS_BROKER_REDIS.DESERIALISE_FAILED for any framing inconsistency.
  • EncodedFrame — return shape of encodeEnvelope.
  • ENCODING_JSON, ENCODING_GZIP_JSON — magic-byte constants.

Implementation Notes

The schema rebuilds a brand-stripped wire envelope (tenant: string) on decode because the TenantId brand is a TypeScript type, not a runtime value. The shape is otherwise identical to the runtime envelope, and the broker re-applies the brand at the call site via the typed entry points.

Gzip is chosen over deflate or brotli because Redis pub/sub frames are short-lived (the broker is the storage substrate, not a long-term archive) and gzip's framing overhead is the lowest of the three for typical payloads in the 1–16 KiB range. The compression threshold defaults to 1 KiB so tiny events do not pay the framing cost just to land a few bytes shorter.