Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 40 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,15 @@ new AlternatorDynamoDBClient({
},

compression: {
enabled: true,
gzipLevel: -1,
},

responseCompression: {
enabled: true,
encodings: [ResponseCompressionGzip],
request: {
enabled: true,
thresholdBytes: 1_024,
gzipLevel: -1,
},
response: {
enabled: true,
algorithms: [ResponseCompressionGzip],
},
},

headerOptimization: {
Expand Down Expand Up @@ -231,13 +233,30 @@ new AlternatorDynamoDBClient({

Use `userAgent: false` to remove the header entirely.

Request compression is disabled by default. When enabled, it compresses every
request body with gzip. Use `gzipLevel` to select the zlib level, or provide
`compressor` for a custom compressor.
Request and response compression are disabled by default and configured
separately:

```ts
compression: {
request: {
enabled: true,
thresholdBytes: 1_024,
gzipLevel: -1,
},
response: {
enabled: true,
algorithms: [ResponseCompressionGzip],
},
}
```

`compression.request: true` compresses every measurable request body with gzip.
Use `thresholdBytes` to skip smaller request bodies, `gzipLevel` to select the
zlib level, or `compressor` for a custom request compressor.

Response compression is disabled by default. Enable it with
`responseCompression: true` to accept both supported encodings, or pass an
options object with an explicit list:
`compression.response: true` enables the default response algorithm, `gzip`.
Pass an options object with an explicit algorithm list to control the
`Accept-Encoding` value:

```ts
import {
Expand All @@ -247,15 +266,19 @@ import {

new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal"],
responseCompression: {
enabled: true,
encodings: [ResponseCompressionGzip, ResponseCompressionDeflate],
compression: {
response: {
enabled: true,
algorithms: [ResponseCompressionGzip, ResponseCompressionDeflate],
},
},
});
```

When enabled, the client sends `Accept-Encoding` and transparently decodes
`gzip` and `deflate` response bodies before the AWS SDK deserializes them.
`gzip` and `deflate` response bodies before the AWS SDK deserializes them. If
header optimization uses a custom whitelist, keep `Accept-Encoding` and
`Content-Encoding` in that list.

Key-route affinity supports these modes:

Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class AlternatorDynamoDBClient extends DynamoDBClient {
override: true,
},
);

}

getLiveNodes(): AlternatorNode[] {
Expand Down Expand Up @@ -118,7 +119,6 @@ function buildDynamoConfig(
routing: _routing,
runtime: _runtime,
compression: _compression,
responseCompression: _responseCompression,
headerOptimization: _headerOptimization,
userAgent: _userAgent,
keyRouteAffinity: _keyRouteAffinity,
Expand Down
51 changes: 29 additions & 22 deletions src/compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { HttpResponse } from "@smithy/protocol-http";
import { bodyToBytes } from "./body.js";
import { ResponseCompressionDeflate, ResponseCompressionGzip } from "./types.js";
import type {
AlternatorResponseCompression,
AlternatorResponseCompressionAlgorithm,
AlternatorRuntime,
NormalizedCompressionOptions,
NormalizedRequestCompressionOptions,
} from "./types.js";

export async function compressBody(
body: unknown,
runtime: AlternatorRuntime,
config: NormalizedCompressionOptions,
config: NormalizedRequestCompressionOptions,
): Promise<{ body: Uint8Array; contentEncoding: string; contentLength: number } | undefined> {
const bytes = bodyToBytes(body);
if (!bytes) {
Expand Down Expand Up @@ -51,11 +51,11 @@ export async function compressBody(
};
}

export function applyResponseCompressionRequestHeaders(
export function applyResponseEncodingHeaders(
headers: Record<string, string | undefined>,
encodings: readonly AlternatorResponseCompression[],
algorithms: readonly AlternatorResponseCompressionAlgorithm[],
): Record<string, string> {
const acceptEncoding = responseCompressionAcceptEncoding(encodings);
const acceptEncoding = responseAcceptEncoding(algorithms);
if (acceptEncoding === "") {
return copyDefinedHeaders(headers);
}
Expand All @@ -71,18 +71,18 @@ export function applyResponseCompressionRequestHeaders(
};
}

export function responseCompressionAcceptEncoding(
encodings: readonly AlternatorResponseCompression[],
export function responseAcceptEncoding(
algorithms: readonly AlternatorResponseCompressionAlgorithm[],
): string {
const seen = new Set<AlternatorResponseCompression>();
const parts: AlternatorResponseCompression[] = [];
const seen = new Set<AlternatorResponseCompressionAlgorithm>();
const parts: AlternatorResponseCompressionAlgorithm[] = [];

for (const encoding of encodings) {
if (seen.has(encoding)) {
for (const algorithm of algorithms) {
if (seen.has(algorithm)) {
continue;
}
seen.add(encoding);
parts.push(encoding);
seen.add(algorithm);
parts.push(algorithm);
}

return parts.join(", ");
Expand All @@ -92,7 +92,7 @@ export async function decompressResponse(
response: HttpResponse,
runtime: AlternatorRuntime,
): Promise<HttpResponse> {
const encoding = responseCompressionContentEncoding(getHeader(response.headers, "content-encoding"));
const encoding = responseContentEncoding(getHeader(response.headers, "content-encoding"));
const body: unknown = response.body;
if (!encoding || body === undefined || body === null) {
return response;
Expand All @@ -110,7 +110,7 @@ export async function decompressResponse(
async function decompressResponseBody(
body: unknown,
runtime: AlternatorRuntime,
encoding: AlternatorResponseCompression,
encoding: AlternatorResponseCompressionAlgorithm,
): Promise<unknown> {
if (runtime === "edge") {
return decompressWebResponseBody(body, encoding);
Expand All @@ -120,7 +120,7 @@ async function decompressResponseBody(

async function decompressNodeResponseBody(
body: unknown,
encoding: AlternatorResponseCompression,
encoding: AlternatorResponseCompressionAlgorithm,
): Promise<unknown> {
const zlib = await import("node:zlib");

Expand All @@ -139,7 +139,7 @@ async function decompressNodeResponseBody(

async function decompressWebResponseBody(
body: unknown,
encoding: AlternatorResponseCompression,
encoding: AlternatorResponseCompressionAlgorithm,
): Promise<unknown> {
if (typeof DecompressionStream === "undefined") {
throw new Error("response compression requires DecompressionStream support in edge runtime");
Expand Down Expand Up @@ -186,9 +186,7 @@ async function bodyToAsyncBytes(body: unknown): Promise<Uint8Array> {
throw new Error("compressed response body is not readable");
}

function responseCompressionContentEncoding(
value: string | undefined,
): AlternatorResponseCompression | undefined {
function responseContentEncoding(value: string | undefined): AlternatorResponseCompressionAlgorithm | undefined {
switch (value?.trim().toLowerCase()) {
case ResponseCompressionGzip:
return ResponseCompressionGzip;
Expand Down Expand Up @@ -241,7 +239,16 @@ function chunkToBytes(chunk: unknown): Uint8Array {
if (bytes) {
return bytes;
}
return new TextEncoder().encode(String(chunk));
switch (typeof chunk) {
case "string":
return new TextEncoder().encode(chunk);
case "bigint":
case "number":
case "boolean":
return new TextEncoder().encode(String(chunk));
default:
throw new Error("compressed response body chunk is not readable");
}
}

function concatBytes(chunks: readonly Uint8Array[]): Uint8Array {
Expand Down
Loading