AWS SDK v3-compatible DynamoDB client for ScyllaDB Alternator. It keeps the native
client.send(new Command()) API, middleware stack, retries, waiters, and
destroy(), but configures a ScyllaDB Alternator cluster with seed nodes instead
of an AWS endpoint.
ScyllaDB Alternator exposes unauthenticated GET /localnodes discovery; the
response is a JSON array of live node IP addresses or hostnames without protocol
or port. See the ScyllaDB Alternator-specific API documentation:
https://docs.scylladb.com/manual/stable/alternator/new-apis.html
npm install @scylladb/alternator-client @aws-sdk/client-dynamodbFor document commands, also install @aws-sdk/lib-dynamodb.
Node.js 20 or newer is required for the Node runtime.
The package uses conditional exports. Node resolves the Node build; browser, worker, and default ESM conditions resolve the Edge build, which contains no Node built-in imports. To force the Edge entrypoint in a non-edge test harness:
import { AlternatorDynamoDBClient } from "@scylladb/alternator-client/edge";For document commands in an Edge bundle, use:
import { AlternatorDynamoDBDocumentClient } from "@scylladb/alternator-client/document/edge";import { ListTablesCommand, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { AlternatorDynamoDBClient, routing } from "@scylladb/alternator-client";
const client = new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal", "scylla-1.internal"],
scheme: "http",
port: 8080,
routing: routing.datacenter({
datacenter: "dc1",
fallback: routing.cluster(),
}),
region: "us-east-1",
credentials: {
accessKeyId: "myuser",
secretAccessKey: "mypassword",
},
});
await client.send(new ListTablesCommand({}));
await client.send(
new PutItemCommand({
TableName: "users",
Item: { id: { S: "u1" }, name: { S: "Ada" } },
}),
);
client.destroy();The public config does not include endpoint. Use seeds, optional scheme, and a shared port.
Defaults are scheme: "http" and port: 8080. HTTPS users usually set
scheme: "https" and port: 8043.
Credentials are optional. If omitted, the client uses no-auth mode and does not
resolve the AWS default credential provider chain. If provided, normal SigV4
signing is preserved. region defaults to us-east-1 for signing.
import { PutCommand } from "@aws-sdk/lib-dynamodb";
import { AlternatorDynamoDBDocumentClient } from "@scylladb/alternator-client/document";
const docClient = AlternatorDynamoDBDocumentClient.fromConfig(
{ seeds: ["localhost"] },
{ marshallOptions: { removeUndefinedValues: true } },
);
await docClient.send(
new PutCommand({
TableName: "users",
Item: { id: "u1", name: "Ada" },
}),
);AWS-style wrapping is the primary API when you already have a low-level client:
const base = new AlternatorDynamoDBClient({ seeds: ["localhost"] });
const docClient = AlternatorDynamoDBDocumentClient.from(base);.from(normalClient) is wrap-only. It does not add Alternator discovery or load
balancing unless the passed client is already an AlternatorDynamoDBClient.
client.alternator.nodes();
await client.alternator.refreshNodes();
await client.alternator.supportsScopedDiscovery();
await client.alternator.validateRouting();
client.alternator.partitionKey("users");Routing helpers:
routing.cluster();
routing.datacenter({ datacenter: "dc1", fallback: routing.cluster() });
routing.rack({
datacenter: "dc1",
rack: "rack1",
fallback: routing.datacenter({
datacenter: "dc1",
fallback: routing.cluster(),
}),
});routing.cluster() queries /localnodes on every configured seed and unions
the returned local-node lists. In multi-datacenter deployments, configure at
least one seed per datacenter.
| Feature | Node | Edge |
|---|---|---|
AWS SDK v3 send() commands |
Yes | Yes |
/localnodes seed discovery |
Background or manual | Request-triggered or manual |
| Rack/datacenter routing fallback | Yes | Yes |
| Document client | Yes | Yes |
| Header filtering | Yes | Yes |
| Key-route affinity | Yes | Yes |
| Custom CA/TLS files | Yes | No |
| Node keep-alive agents | Yes | No |
| Socket pool tuning | Yes | No |
| TLS session cache tuning | Yes | No |
| Gzip request compression | Yes | Only with CompressionStream |
| Gzip/deflate response compression | Yes | Only with DecompressionStream |
Unsupported edge combinations throw at construction time with clear errors.
new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal"],
scheme: "http",
port: 8080,
routing: routing.cluster(),
logger: console,
discovery: {
background: true,
refreshIntervalMs: 60_000,
requestRefreshIntervalMs: 60_000,
timeoutMs: 2_000,
},
compression: {
request: {
thresholdBytes: 1_024,
gzipLevel: -1,
},
response: {
algorithms: ["gzip"],
},
},
headerOptimization: {
allowedHeaders: ["Host", "X-Amz-Target", "Content-Length", "Accept-Encoding", "Content-Encoding"],
},
userAgent: { append: "my-app/1.2.3" },
keyRouteAffinity: {
mode: "any-write",
partitionKeys: {
users: "id",
},
autoDiscoverPartitionKeys: true,
},
tls: {
ca: { file: "/etc/ssl/scylla-ca.pem" },
rejectUnauthorized: true,
sessionCache: true,
},
connection: {
keepAlive: true,
maxSockets: 50,
timeouts: {
connectMs: 1_000,
requestMs: 0,
socketMs: 0,
},
},
});Seed values are hostnames or IP addresses only, not URLs and not host:port
strings. Use the port option for the shared Alternator port.
Header optimization is disabled by default. When enabled, headers are
whitelisted, not removed by a strip list. The default
whitelist is Host, X-Amz-Target, Content-Length, Accept-Encoding, and
Content-Encoding; when credentials are configured, Authorization and
X-Amz-Date are also kept. Alternator does not use AWS session tokens, so
sessionToken is not sent even when provided in credentials. The Alternator
User-Agent is applied after this filter, so it is kept unless
userAgent: false is configured.
By default, the client replaces the AWS SDK User-Agent with the ScyllaDB
Alternator client identity:
scylladb-alternator-client-javascript/<version>
You can replace it completely:
new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal"],
userAgent: { value: "my-client/1.2.3" },
});You can append to the generated value:
new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal"],
userAgent: { append: "my-app/4.5.6" },
});Use userAgent: false to remove the header entirely.
Request and response compression are disabled by default and configured separately:
compression: {
request: {
thresholdBytes: 1_024,
gzipLevel: -1,
},
response: {
algorithms: ["gzip"],
},
}compression.request: {} 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. Use
compression.request: false to disable request compression explicitly.
compression.response: {} enables the default response algorithm, gzip.
Pass an options object with an explicit algorithm list to control the
Accept-Encoding value:
new AlternatorDynamoDBClient({
seeds: ["scylla-0.internal"],
compression: {
response: {
algorithms: ["gzip", "deflate"],
},
},
});When enabled, the client sends Accept-Encoding and transparently decodes
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:
keyRouteAffinity: {
mode: "read-before-write", // or "any-write"
partitionKeys: { users: "id" },
}The client hashes DynamoDB S, N, and B partition-key AttributeValues with
the same Murmur3 format as Alternator affinity routing. The hash seeds a
deterministic query plan over lexicographically sorted node URLs, so the same
partition key selects the same first node. In any-write mode,
BatchWriteItem uses voting: each usable write candidate votes for its seeded
first node, a unique majority becomes the preferred node, and ties fall back to
the normal query plan. If partition-key metadata is missing and
autoDiscoverPartitionKeys is enabled, the client starts a background
DescribeTable lookup and falls back to the normal query plan for the current
request.
Per request, the client creates a lazy node query plan. Retries can consume the next node from that plan, so active nodes are tried without repeating until the plan is exhausted.
npm run typecheck
npm run lint
npm test
npm run test:integration
npm run build
npm run verify
make test-allnpm test runs the fast unit suite. Integration tests live under
test/integration-test and are skipped unless INTEGRATION_TESTS is truthy:
INTEGRATION_TESTS=true \
ALTERNATOR_HOST=172.39.0.2 \
ALTERNATOR_PORT=9998 \
ALTERNATOR_HTTPS_PORT=9999 \
ALTERNATOR_DATACENTER=datacenter1 \
ALTERNATOR_RACK=rack1 \
npm run test:integrationFor custom CA HTTPS coverage, also set ALTERNATOR_CA_CERT_PATH to a PEM CA
certificate path.
make test-all starts the same three-node ScyllaDB Docker cluster shape used by
the Java client tests, waits for Alternator, runs npm run test:integration
with the required environment variables, and stops the cluster.