Skip to content

oxia-db/oxia-client-node

Oxia Node.js Client SDK

CI npm License

Node.js / TypeScript client for Oxia, a scalable metadata store and coordination system for large-scale distributed systems.

Getting Started | Documentation | TSDoc reference | npm | Discussion

Requirements

  • Node.js 20+

Install

npm install @oxia-db/client

Quick start

import { OxiaClient, EXPECTED_RECORD_DOES_NOT_EXIST } from '@oxia-db/client';

const client = await OxiaClient.connect('localhost:6648');
try {
  const { key, version } = await client.put('/users/alice', 'hello', {
    expectedVersionId: EXPECTED_RECORD_DOES_NOT_EXIST, // fail if already exists
  });

  const got = await client.get('/users/alice');
  console.log(got.key, new TextDecoder().decode(got.value), got.version.versionId);

  await client.delete('/users/alice');
} finally {
  await client.close();
}

Client options

await OxiaClient.connect('localhost:6648', {
  namespace: 'default',          // default: 'default'
  sessionTimeoutMs: 30_000,      // default: 30s; min 2000ms
  heartbeatIntervalMs: 3_000,    // default: ~sessionTimeoutMs/10, floored at 2s
  clientIdentifier: 'my-worker', // default: random UUID hex
  requestTimeoutMs: 30_000,      // default: 30s; applied to unary RPCs only
  initTimeoutMs: 30_000,         // time to wait for the first shard map
  authentication: undefined,     // see "Authentication" below
});

Operations

put / delete / get

await client.put('/k', 'v');
await client.put('/k', 'v2', { expectedVersionId: v1.version.versionId });
await client.put('/k', 'v',  { ephemeral: true });                  // removed when client closes
await client.put('/k', 'v',  { secondaryIndexes: { 'by-user': 'alice' } });

await client.delete('/k');
await client.delete('/k', { expectedVersionId: v.version.versionId });

const r = await client.get('/k');                                   // EQUAL
const r2 = await client.get('/k', { comparisonType: ComparisonType.FLOOR });
const r3 = await client.get('alice', { useIndex: 'by-user' });      // by secondary index

Range operations

// List all keys in a range (hierarchical ordering; see note below).
const keys = await client.list('/users/', '/users/~');

// Stream records in a range.
for await (const rec of client.rangeScan('/users/', '/users/~')) {
  console.log(rec.key, new TextDecoder().decode(rec.value));
}

// Delete a range.
await client.deleteRange('/tmp/', '/tmp/~');

With a partitionKey, operations are limited to a single shard:

await client.put('entry-a', 'v', { partitionKey: 'session-42' });
await client.list('entry-', 'entry-~', { partitionKey: 'session-42' });

Sequential keys

const r = await client.put('ticket', 'payload', {
  partitionKey: 'queue',
  sequenceKeysDeltas: [1],  // server assigns a zero-padded suffix
});
console.log(r.key); // -> "ticket-00000000000000000001"

Notifications

const notifications = client.getNotifications();
for await (const n of notifications) {
  console.log(n.type, n.key, n.versionId);
  if (shouldStop()) notifications.close(); // ends the loop
}

Sequence updates

const updates = client.getSequenceUpdates('ticket', { partitionKey: 'queue' });
for await (const latestKey of updates) {
  console.log('sequence advanced to', latestKey);
}

Authentication

import { OxiaClient, TokenAuthentication } from '@oxia-db/client';

// Static bearer token:
const client = await OxiaClient.connect('oxia.example.com:6648', {
  authentication: new TokenAuthentication('my-token'),
});

// Or a refresh-on-demand supplier:
const client2 = await OxiaClient.connect('oxia.example.com:6648', {
  authentication: new TokenAuthentication(async () => fetchTokenFromIdp()),
});

You can also implement the Authentication interface directly to add arbitrary gRPC metadata headers to every outgoing RPC:

import type { Authentication } from '@oxia-db/client';

class MyAuth implements Authentication {
  generateCredentials() {
    return { 'x-my-header': 'value' };
  }
}

Errors

All client errors extend OxiaError:

Error Meaning
KeyNotFoundError get / delete against a missing key
UnexpectedVersionIdError conditional put / delete saw a different version
SessionNotFoundError ephemeral put referenced a session the server has closed
InvalidOptionsError incompatible option combination (e.g. sequenceKeysDeltas without partitionKey)

Verifying the published package

Every release is published from a GitHub Actions run with SLSA provenance signed by Sigstore. You can verify that a given @oxia-db/client version came from this repository's release.yml workflow — with no long-lived npm token anywhere in the chain — before installing it:

npm audit signatures @oxia-db/client

npm also shows the provenance attestation on the package page under Provenance.

License

Apache License 2.0. See LICENSE and NOTICE.