npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scylladb/alternator-client

v1.0.0

Published

AWS SDK v3-compatible DynamoDB client with ScyllaDB Alternator cluster discovery and routing.

Readme

Alternator DynamoDB JavaScript Client

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

Install

npm install @scylladb/alternator-client @aws-sdk/client-dynamodb

For document commands, also install @aws-sdk/lib-dynamodb.

Node.js 22 or newer is required for the Node runtime. Continuous integration tests the package on Node.js 22 and 24.

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";

The integration suite uses ScyllaDB 2025.1 as its compatibility baseline. This records the version tested for this release; it is not a blanket compatibility guarantee for every ScyllaDB release.

Low-Level Client

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.

Document Client

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" },
  }),
);

docClient.destroy();

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);

// The wrapper does not own base, so destroy it separately when finished.
docClient.destroy();
base.destroy();

.from(normalClient) is wrap-only. It does not add Alternator discovery or load balancing unless the passed client is already an AlternatorDynamoDBClient. The caller retains ownership of the supplied client, and destroying the document wrapper does not destroy it. In contrast, .fromConfig() creates and owns an AlternatorDynamoDBClient; destroying that document client also stops its background discovery and destroys its HTTP resources.

Alternator APIs

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.

Runtime Matrix

| 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 | Yes; custom raw HTTP handlers require DecompressionStream |

Unsupported edge options that can be detected statically throw at construction time. A custom raw handler reports a missing DecompressionStream when it receives an encoded response.

Options

new AlternatorDynamoDBClient({
  seeds: ["scylla-0.internal"],
  scheme: "https",
  port: 8043,
  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"],
    additionalAllowedHeaders: ["X-Request-Id"],
  },

  userAgent: { append: "my-app/1.2.3" },

  keyRouteAffinity: {
    mode: "any-write",
    partitionKeys: {
      users: "id",
    },
    autoDiscoverPartitionKeys: true,
  },

  tls: {
    ca: { file: "/etc/ssl/scylla-ca.pem" },
    cert: { file: "/etc/ssl/client-cert.pem" },
    key: { file: "/etc/ssl/client-key.pem" },
    rejectUnauthorized: true,
    sessionCache: true,
  },

  connection: {
    keepAlive: true,
    maxSockets: 50,
    throwOnRequestTimeout: true,
    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.

Behavior Details

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.

Use allowedHeaders to replace the default whitelist. Use additionalAllowedHeaders to extend the default (or explicitly supplied) whitelist without repeating it:

headerOptimization: {
  additionalAllowedHeaders: ["X-Request-Id"],
}

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" },
});

Or transform the generated value. Returning null, undefined, or a blank string removes the header:

new AlternatorDynamoDBClient({
  seeds: ["scylla-0.internal"],
  userAgent: {
    transform: (generated) => `${generated} my-app/4.5.6`,
  },
});

value, append, and transform are mutually exclusive.

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.

TLS and HTTP transport

Node TLS configuration accepts CA certificates, client certificates, and client keys. Each ca, cert, or key value uses exactly one material form:

tls: {
  ca: { file: "/etc/ssl/scylla-ca.pem" },
  cert: { text: process.env.SCYLLA_CLIENT_CERT_PEM! },
  key: { bytes: clientKeyBytes },
  rejectUnauthorized: true,
  sessionCache: true,
}

file is a filesystem path, text is PEM text, and bytes is a Uint8Array. TLS material and session-cache tuning are Node-only. Prefer rejectUnauthorized: true; disabling certificate verification is unsafe.

The built-in Node handler supports connection.keepAlive, connection.maxSockets, connection.timeouts.connectMs, connection.timeouts.requestMs, connection.timeouts.socketMs, and connection.throwOnRequestTimeout. Additional Smithy NodeHttpHandlerOptions can be passed through connection.node, except httpAgent and httpsAgent; configure pooling and TLS through connection and tls instead.

The Edge handler supports connection.keepAlive, connection.timeouts.requestMs, and additional FetchHttpHandlerOptions through connection.fetch. The top-level AWS SDK-compatible requestHandler option accepts a handler instance, a Smithy options-provider function, or a handler options object for the selected runtime. An options object is merged with the Alternator connection and tls settings. A supplied handler instance or options-provider function bypasses those generated settings and therefore owns its transport configuration.

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. Voted coordinators are tried by vote count descending, ties use canonical node-address order, and zero-vote live coordinators follow in canonical order. The client falls back to the normal query plan only when no candidate can vote. If one table lacks partition-key metadata and autoDiscoverPartitionKeys is enabled, the client starts a background DescribeTable lookup for that table while other usable tables can still vote.

Per request, the client creates a lazy node query plan. When the AWS retry strategy retries a transport failure or retryable service response, the next attempt consumes the next node from that plan. Active nodes are tried without repeating until the plan is exhausted; additional attempts start another plan.

The client does not maintain a persistent dead-node quarantine, subscribe to topology events, or health-rank nodes across requests. A failed node can be selected by a later request until a discovery refresh returns a node list that does not include it. Topology changes are therefore learned through scheduled, request-triggered, or manual /localnodes refreshes.

Development

npm run typecheck
npm run lint
npm test
npm run test:integration
npm run build
npm run verify
make test-all

npm 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:integration

For 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.

See CONTRIBUTING.md for the development workflow and RELEASING.md for the maintainer release procedure.