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
npm install @oxia-db/client
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();
}
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
});
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
// 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' });
const r = await client.put('ticket', 'payload', {
partitionKey: 'queue',
sequenceKeysDeltas: [1], // server assigns a zero-padded suffix
});
console.log(r.key); // -> "ticket-00000000000000000001"
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
}
const updates = client.getSequenceUpdates('ticket', { partitionKey: 'queue' });
for await (const latestKey of updates) {
console.log('sequence advanced to', latestKey);
}
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' };
}
}
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) |
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.
Apache License 2.0. See LICENSE and NOTICE.