# `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 ```ts 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 ```ts 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(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.