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

87 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `publish` — envelope encode/decode
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.