Initial Commit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# `compression`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `compression` module owns the engine's optional gzip path. SSE
|
||||
streams compress well — UTF-8 text framed with repeating field labels —
|
||||
so honouring `Accept-Encoding: gzip` shrinks bandwidth meaningfully
|
||||
without paying any encoding cost when the client did not opt in. The
|
||||
module exposes the negotiation decision separately from the encoder so
|
||||
the engine can short-circuit before allocating a `node:zlib` stream when
|
||||
`compressionEnabled: false` or the client did not ask for gzip.
|
||||
|
||||
## Features
|
||||
|
||||
- `negotiateCompression(acceptEncoding, compressionEnabled)` returns a
|
||||
declarative plan; no side effects.
|
||||
- `Z_SYNC_FLUSH` after every `write(chunk)` so events hit the wire
|
||||
immediately — gzip's default block boundary would delay them.
|
||||
- Counters expose `uncompressed`/`compressed` byte totals so the OTel
|
||||
histogram `sse.compression_ratio` can record per-stream values.
|
||||
- Encoder errors throw `SseBunError(COMPRESSION_FAILED)` with the
|
||||
underlying `cause` attached for log forensics.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
createCompressionStream,
|
||||
negotiateCompression,
|
||||
} from "@suaveplan/api-sse-bun";
|
||||
|
||||
const plan = negotiateCompression(request.headers.get("accept-encoding"), true);
|
||||
if (plan.useGzip) {
|
||||
const stream = createCompressionStream();
|
||||
const chunk = await stream.write(new TextEncoder().encode("data: hi\n\n"));
|
||||
// ...write chunk to socket...
|
||||
const tail = await stream.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
createCompressionStream,
|
||||
SSE_BUN_CODES,
|
||||
SseBunError,
|
||||
} from "@suaveplan/api-sse-bun";
|
||||
|
||||
const stream = createCompressionStream();
|
||||
try {
|
||||
await stream.write(new TextEncoder().encode("data: hello\n\n"));
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof SseBunError &&
|
||||
err.code === SSE_BUN_CODES.COMPRESSION_FAILED
|
||||
) {
|
||||
// fall back to plain delivery; emit log + telemetry
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const ratio = stream.uncompressed / stream.compressed;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface CompressionPlan` — `{ useGzip: boolean }`.
|
||||
- `function negotiateCompression(acceptEncoding, compressionEnabled): CompressionPlan`.
|
||||
- `interface CompressionStream` — `write(bytes)`, `close()`,
|
||||
`uncompressed`, `compressed`.
|
||||
- `function createCompressionStream(): CompressionStream`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The encoder buffers no more than one in-flight chunk at a time —
|
||||
`write(chunk)` resolves only after `Z_SYNC_FLUSH` drains the encoder,
|
||||
so back-pressure surfaces to the engine immediately. Closing the
|
||||
stream emits any final bytes (the gzip footer). Error context wraps
|
||||
the underlying `node:zlib` error via `cause` so consumers retain the
|
||||
stack across the throw boundary.
|
||||
Reference in New Issue
Block a user