/** * Composable compression layer for serialized data. * * Wraps/unwraps serialized payloads with a compression codec, using the * format prefix system to mark compressed data (e.g. 'zstd' or 'gzip' * wrapping the inner format: 'zstd' + zstd('devl' + payload)). * * Codec selection (write side): zstd is preferred because it is markedly * faster than gzip at a comparable-or-better ratio (see scripts/README.md), * and compression runs at every step boundary so the write CPU is a * per-step tax. zstd requires `node:zlib` >= 22.15 (Web * `CompressionStream` has no zstd), so on a runtime without it we fall back * to gzip via the portable * `CompressionStream`. `WORKFLOW_COMPRESSION_CODEC=gzip` forces the * portable codec. * * Read side: dispatch on the format prefix, so both 'zstd' and 'gzip' * payloads are always decodable regardless of which codec wrote them. * (The browser o11y read path decodes zstd via a registered WASM decoder, * see `serialization-format.ts`; this module's `decompress` is the Node * runtime/replay path and uses `node:zlib`.) * * Layering order with encryption: compression is applied BEFORE * encryption (encr(zstd(devl))): encrypted bytes are high-entropy and * do not compress, so the reverse order would be a no-op. * * Compression is conditional: * - Payloads smaller than {@link COMPRESSION_MIN_BYTES} are passed * through unchanged (codec overhead isn't worth it). * - If the compressed result isn't meaningfully smaller than the * original (see {@link COMPRESSION_MIN_SAVINGS_RATIO}), the original * is kept. This protects already-compressed binary payloads (images, * archives, etc.) from wasted CPU and size inflation. */ /** * Payloads below this size are never compressed. The 4-byte format * prefix + codec header/trailer overhead means small payloads gain * nothing, and tiny ones would grow. */ export declare const COMPRESSION_MIN_BYTES = 1024; /** * Compression must shave off at least this fraction of the payload * size to be kept; otherwise the uncompressed original is stored. * Guards against incompressible (already-compressed / high-entropy) * data paying a permanent decompression tax for a negligible win. */ export declare const COMPRESSION_MIN_SAVINGS_RATIO = 0.05; /** Which codec compressed a payload (or `none` when stored uncompressed). */ export type CompressionCodec = 'zstd' | 'gzip' | 'none'; /** * Which compressed formats a write may produce for its target run: * - `false`: none, the payload is stored uncompressed. * - `'gzip'`: gzip only. The target can read compressed payloads but its * runtime cannot decode zstd (e.g. a run pinned to a Node.js < 22.15 * deployment; see `getRunCapabilities` in capabilities.ts). * - `true`: any codec, zstd preferred. */ export type CompressionMode = boolean | 'gzip'; /** * Thrown by {@link decompress} when a payload inflates past the caller's * `maxOutputBytes`, so a small compressed input cannot exhaust memory. */ export declare class DecompressedSizeLimitError extends Error { readonly maxOutputBytes: number; constructor(maxOutputBytes: number); } /** * Telemetry sink describing what the compression layer did to a payload. * Populated by {@link compress} (write) and {@link decompress} (read) when * a `stats` object is passed. Sizes are measured at the compression * boundary (i.e. before encryption is layered on the write side and after * decryption on the read side), so they reflect compression's effect, not * the at-rest size (which also includes the `encr` envelope and, on some * backends, base64 expansion). * * Field meanings are identical for both directions: * - `uncompressedBytes`: the logical (devalue-prefixed) payload size. * - `storedBytes`: the size handed to / read from storage (compressed when * a codec applied, otherwise equal to `uncompressedBytes`). * - `codec`: which codec applied (`none` when stored uncompressed). */ export interface CompressionStats { /** True once the compression layer ran (i.e. saw binary data). */ recorded?: boolean; /** Whether a codec was applied (write) or present (read). */ compressed?: boolean; /** Which codec applied / was present. */ codec?: CompressionCodec; /** Logical, uncompressed payload size in bytes. */ uncompressedBytes?: number; /** Stored (post-compression) payload size in bytes. */ storedBytes?: number; } /** * Compress a format-prefixed payload if compression is enabled for the * target run and the payload is worth compressing. * * @param data - The format-prefixed serialized data (e.g. 'devl' + bytes) * @param mode - Which compressed formats the target run can decode (run * specVersion >= SPEC_VERSION_SUPPORTS_COMPRESSION, and for * cross-deployment writes, the target deployment's capabilities; see * `getRunCapabilities` and `getCompressionMode` in capabilities.ts). * @param stats - Optional telemetry sink; populated when `data` is binary. * @returns The compressed data with a codec prefix, or the original data * when compression is disabled, unavailable, or not worthwhile. */ export declare function compress(data: Uint8Array | unknown, mode: CompressionMode, stats?: CompressionStats, opts?: { /** * Compress off the event loop where the codec supports it (zstd via * the libuv threadpool; gzip is stream-based and already async). * For multi-MB payloads compressed while a response is flushing — * the sync zstd path would block the loop for the whole compression. * Output bytes are identical either way. */ preferAsync?: boolean; }): Promise; /** * Decompress a format-prefixed payload if it's compressed. * Dispatches on the prefix ('zstd' or 'gzip') and inflates the inner * payload (which carries its own format prefix, e.g. 'devl'). * * Non-compressed data (including non-binary legacy data) is returned * unchanged, so this is safe to apply unconditionally on read paths. */ export declare function decompress(data: Uint8Array | unknown, stats?: CompressionStats, opts?: { /** * Refuse to inflate past this many bytes (throws * {@link DecompressedSizeLimitError}). For payloads from storage that * may be hostile or corrupt, where a small input could otherwise * inflate without bound. */ maxOutputBytes?: number; }): Promise; /** * Check if data is compressed (has a 'zstd' or 'gzip' format prefix). */ export declare function isCompressed(data: Uint8Array | unknown): boolean; //# sourceMappingURL=compression.d.ts.map