Skip to content

scylladb/alternator-client-javascript

Repository files navigation

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

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

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.

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 Only with DecompressionStream

Unsupported edge combinations throw at construction time with clear errors.

Options

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.

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.

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.

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.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors