@shortsqueeze/thunderstrike
v0.1.3
Published
Node.js client for the thunderstrike key-value database (TLS 1.3, pipelined, prebuilt native addon)
Maintainers
Readme
@shortsqueeze/thunderstrike
Node.js client for the thunderstrike key-value database: a pool of pipelined TLS 1.3 connections, with a Promise for every operation. Nothing blocks the event loop.
The client is a prebuilt native addon. npm installs the one for your
platform as an optional dependency
(@shortsqueeze/thunderstrike-<platform>-<arch>); there is no build step
and no compiler needed. TypeScript types are included.
Install
npm install @shortsqueeze/thunderstrikeNode.js 16 or newer, on Linux x64 / arm64 (glibc 2.17 or newer) or
Windows x64. macOS comes in a later release. Do not install with
--omit=optional / --no-optional: that skips the addon.
Quick start
The server speaks TLS 1.3 only. The client needs the CA certificate that
issued the server's certificate (docker/tls/ca.crt in the server's kit)
and a DNS name that certificate carries (localhost unless the server
was set up with other THUNDERSTRIKE_TLS_NAMES).
// ES module (top-level await). With require():
// const { ThunderstrikeClient } = require("@shortsqueeze/thunderstrike");
import { ThunderstrikeClient } from "@shortsqueeze/thunderstrike";
import { readFileSync } from "node:fs";
const db = await ThunderstrikeClient.connect({
endpoints: ["127.0.0.1:7380"],
tlsServerName: "localhost",
tlsCaPem: readFileSync("ca.crt"),
});
await db.set(Buffer.from("user:1"), Buffer.from("ada"));
const v = await db.get(Buffer.from("user:1")); // Buffer, or null on a miss
console.log(v?.toString()); // "ada"
db.close();Give it one address. The client asks that server for the cluster layout
and connects to every core (core c listens on port 7380 + c), so
those ports must be reachable too. connect resolves only once every
connection, TLS included, is up, so a wrong address or a certificate that
does not verify rejects there rather than on first use.
tlsServerName is sent as SNI and checked against the certificate, so
it is the name the certificate was issued for, not necessarily the host
you dial. There is no way to turn verification off.
Operations
Keys and values are Buffers; the *String variants take and return
UTF-8 text. A key is at most 65,535 bytes; an empty value is refused.
Keys do not expire: a value stays until it is deleted or overwritten.
const k = Buffer.from("k");
await db.set(k, Buffer.from("v"));
await db.get(k); // Buffer | null
await db.setString("greeting", "hello");
await db.getString("greeting"); // "hello" | null
await db.exists(k); // true / false
await db.append(k, Buffer.from("-more")); // appends (creates if absent); resolves to the new length
await db.delete(k); // true if it was there
await db.ping();Batches
For more than one key, use the batch calls rather than Promise.all
over single calls: every request goes on the wire before any answer is
awaited, in one trip across the JS/native boundary.
const keys = ["a", "b", "missing"].map((s) => Buffer.from(s));
await db.setMany(keys.slice(0, 2), [Buffer.from("1"), Buffer.from("2")]);
await db.getMany(keys); // [<Buffer 31>, <Buffer 32>, null]
await db.deleteMany(keys); // [true, true, false]One client per process is the intended shape: it is a connection pool, and every method may be called concurrently.
Errors and retries
Every rejection is an Error carrying:
thunderstrikeCode: the numeric status (positive from the server, negative from the client). Compare withcodes, e.g.codes.BUSY.retryClass: whether sending the request again is sound.busy:truewhen the server answered Busy.
| retryClass | meaning |
|---|---|
| RETRY_ALWAYS | Busy: the server did not apply it. Retry anything after a short backoff. |
| RETRY_IF_IDEMPOTENT | A lost connection or a timeout: the outcome is unknown. Repeat get, set, exists and ping only. |
| RETRY_NEVER | Everything else. |
The client has already retried Busy answers (every operation but delete) and dropped connections (idempotent operations) before it rejects.
import { codes, RETRY_ALWAYS, describe } from "@shortsqueeze/thunderstrike";
try {
await db.set(k, v);
} catch (e) {
if (e.retryClass === RETRY_ALWAYS) { /* not applied; try again later */ }
console.error(e.thunderstrikeCode, describe(e.thunderstrikeCode), e.message);
}Configuration
Options to ThunderstrikeClient.connect, all optional except the
endpoint and (against the real server) the TLS pair:
| option | meaning |
|---|---|
| endpoints | Bootstrap addresses. One is enough. |
| tlsServerName, tlsCaPem | TLS 1.3: the certificate's name and the CA (PEM, string or Buffer). Give both. |
| tlsClientCertPem, tlsClientKeyPem | Client certificate (mTLS), for a server that requires one. |
| requestTimeoutMs | Per-request deadline, covering retries. Default 60000; 0 disables it. |
| connectTimeoutMs | Connect and TLS handshake deadline. |
| connectionsPerEndpoint | TCP connections per server core. |
| maxInflight | Pipeline depth per connection. |
| maxRetries | Retries for an idempotent request whose connection dropped. |
| busyRetries, busyBackoffMinMs, busyBackoffMaxMs, busyRetryWindowMs | Busy retry policy. Defaults 4, 2, 200, 5000. |
| maxResponseBytes | Largest response the client will accept. |
db.stats() returns pool counters (connections, requests, bytes,
retries). db.endpoints() is the number of server cores connected,
db.shards() the number of shards. version() is the client's version.
Links
- Source and issues: https://github.com/TheManhattanProject/thunderstrike-client
- Server image: https://hub.docker.com/r/sbansal793/thunderstrike
- Clients for other languages: see the repository README.
License
Proprietary, free to use: see the LICENSE file in the package. You may
build this library into your own applications and ship it as part of
them.
Copyright (c) 2026 SHORTSQUEEZE PRIVATE LIMITED.
