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

@limidus/kdb-client

v0.9.2

Published

TypeScript client for KDB - wire codec, transports and the document/SQL/lease operation set.

Readme

@limidus/kdb-client

The KDB network driver for TypeScript. Implements Component 63 Phase 1: the wire codec, the transports, and the operation set that go/kdb/client exposes.

Zero runtime dependencies. The main entry point touches no Node built-ins, so it loads unchanged in browsers, Bun, Deno and Workers-style runtimes; the TCP transport lives behind the @limidus/kdb-client/tcp subpath and is reached lazily.

import { connect } from "@limidus/kdb-client";

const client = await connect("tcp://localhost:9090", { token: "alice:secret" });

const docId = "6f9619ff-8b86-d011-b42d-00cf4fc964ff";
await client.putJSON("app/users", docId, { name: "ada", score: 42 });

const { body, commit } = await client.get("app/users", docId);

// Read-modify-write without losing a concurrent update.
await client.compareAndSwap("app/users", docId, (current) => ({
  ...(current as Record<string, unknown>),
  score: 43,
}));

const rows = await client.query("app/users", "SELECT id, name FROM users WHERE score > ?", [40]);

await client.close();

Status

| | | |---|---| | Wire codec, transports, operations | Implemented, 81 tests passing | | Cross-language conformance | Proven against golden fixtures generated by the Go encoder | | Live interop vs. the Go server | Passing, over a real WebSocket | | Live interop vs. the JVM server | Not run — see below | | Published to npm | Per release, at the server's version (see below) |

What is and isn't verified

test/interop.test.ts runs this client against a real Go server over a real WebSocket: handshake, document round trips (including non-ASCII), upsert, conditional writes, concurrent compare-and-swap, multi-document commit, and SQL. It starts the server itself via go/cmd/kdb-tsinterop and skips cleanly when the Go toolchain is absent.

The content-hash agreement is checked end to end there, which is worth calling out: that hash never crosses the wire, so the only way to test it is to hand one back as an EXPECT_CONTENT_HASH precondition and require the server to accept it — and then to show a stale one is refused.

Still unverified: the JVM kdb-server. It implements only part of the message set — codes 0x14–0x1C (documentGet, upsert, and the four lock verbs) are Go-server-only. Against it, get, upsert, appendEvent and the leases should raise KdbUnsupportedError while query, exec, putJSON, commit and the conditional writes work; that split is implemented and unit-tested, but has not been run against an actual JVM server.

Connecting

connect(addr, options) accepts ws://, wss://, tcp://, tcps://, or a bare host:port (which means TCP, and is therefore Node-only). tcps:// and wss:// require options.tls and refuse to fall back to plaintext.

const client = await connect("tcps://kdb.internal:9090", {
  token: "alice:secret",
  tls: { ca: await readFile("ca.pem", "utf8") },
  requestTimeoutMs: 10_000,
});

One Client is one connection is one KDB session — hold it for the process lifetime rather than opening one per request. Concurrent calls on a single client are fine; requests are matched to replies by correlation id.

Attachments

A document can carry named binary attachments. Against a server that agrees the attachments wire feature (binary frames; not jsonOnly):

const { attachment } = await client.putAttachment(ns, docId, file.stream(), { name: "scan.pdf", mime: "application/pdf" });
const items = await client.listAttachments(ns, docId);          // [{ id, name, hash, size, mime }]
for await (const page of await client.getAttachment(ns, docId, "scan.pdf")) { /* ... */ }
const { data } = await client.readAttachment(ns, docId, { id: attachment.id });
await client.renameAttachment(ns, docId, "scan.pdf", "contract.pdf");
await client.deleteAttachment(ns, docId, "contract.pdf");

Sources may be a Uint8Array, Blob, ReadableStream or (async) iterable; uploads go in 1 MiB chunks and a KdbUploadError carries what resumes one. Reads are checked against the attachment's content hash as they stream (the last page is withheld until it verifies; KdbIntegrityError otherwise). uploadAttachment stores bytes only; prepareAttachment returns an operation for Transaction.attachments, so attachments can commit together with document writes. In SQL, a Uint8Array argument binds as bytes, sqlAttachment(blob) binds an uploaded blob (STREAM ?), and queryValues returns bytes cells (ATTACHMENT('name')) as Uint8Array.

Part of an attachment can be read verified, without the rest, from a server that also agrees attachment-proofs: every 1 MiB chunk the range touches is checked against the content hash with its Merkle inclusion proof before any of its bytes is handed out.

const reader = await client.openAttachmentRange(ns, docId, "video.mp4", { offset: 5_000_000, length: 65_536 });
for await (const part of reader) { /* ... */ }
const bytes = await client.readAttachmentAt(ns, docId, "video.mp4", 5_000_000, 65_536); // shorter at the end

An offset past the end throws KdbAttachmentRangeError. (ReadAttachmentOptions.offset is a cheaper, unverified partial read.)

Error handling

Everything thrown extends KdbError, which carries the server's code and retryAfterMs when it sent them.

import { KdbConflictError, KdbNotFoundError, KdbPreconditionError } from "@limidus/kdb-client";

try {
  await client.replaceIf(ns, docId, body, expectedHash);
} catch (error) {
  if (error instanceof KdbPreconditionError) {
    // The document changed under us. Re-read and decide - or just use compareAndSwap.
  } else if (error instanceof KdbConflictError) {
    // Someone else committed against the same baseVersion first. Retryable.
  } else if (error instanceof KdbNotFoundError) {
    // ...
  }
}

KdbPreconditionError and KdbConflictError are deliberately distinct even though both arrive on the same wire message: a failed assertion is generally not worth retrying, a lost race is.

Things worth knowing

Document ids are UUIDs. Anything else is rejected client-side with an argument error rather than becoming an opaque server failure.

SQL values come back as strings. sqlResult.rows is string[][] on the wire and this layer does not guess at types — a driver that silently parsed "0123" as 123 would corrupt data. Typed coercion arrives with @limidus/kdb-codegen (Phase 2), which derives types from the KDB schema.

Content hashes are computed locally. getWithHash and replaceIf compare a hash defined as a pure function of (documentId, json); it never crosses the wire. That is why those methods, and compareAndSwap, are async even when nothing is being sent.

Every request has a deadline (default 30s, overridable per call). This is a correctness backstop: some server paths answer an unrecognized message with nothing at all rather than an error frame, and a driver that can hang forever on a server bug is not shippable.

Reconnection is yours. Connection loss surfaces honestly as a rejected promise; automatic reconnect with request replay is deferred, because replaying a txCommit whose reply was lost is not obviously safe and the client cannot make that call for you.

Development

npm install
npm run typecheck
npm test
npm run build

Regenerate the golden fixtures after any change to the wire format on the Go side:

cd ../../go && go run ./cmd/kdb-tsfixtures

That command writes test/golden/*.json from the real Go encoder — the same code paths a live server uses. It is what makes "wire-compatible" a checked claim rather than an asserted one, and it is the first thing to re-run when a frame stops decoding.

Versioning and compatibility

@limidus/kdb-client is published once per KDB release with the server's version number (0.9.2 of the package speaks to 0.7.x servers). The handshake enforces it: the server reports its release in handshakeAck.serverVersion, and connect fails with a KdbProtocolError when that is not a release this client was published for (same major; while the major is 0, same minor). A server that reports no version (older than 0.7.0, or the JVM engine) is refused too, and a from-source 0.0.0-dev server is accepted. Pass skipVersionCheck: true to override deliberately.

Pin the dependency to the server release you deploy ("@limidus/kdb-client": "0.9.2"); the release workflow refuses to publish a package whose version differs from the repo's VERSION, and attaches the tarball to the GitHub release as well as publishing it to npm.