wyndo-client
v0.1.5
Published
TypeScript client for Wyndo database
Readme
Wyndo Node.js Client
TypeScript/JavaScript client library for the Wyndo database.
Installation
npm install wyndo-clientConnecting
import { WyndoClient, DataType } from "wyndo-client";
// Without authentication
const client = await WyndoClient.connect("localhost:9876");
// With authentication
const client = await WyndoClient.connect("localhost:9876", {
username: "admin",
password: "password",
});
// Close when done
await client.close();The client automatically reconnects on connection failure.
Key-Value Operations
// Set a key
await client.set("user:1:name", DataType.String, "Alice");
// Get a key
const resp = await client.get("user:1:name");
console.log(resp.string()); // "Alice"
// Update an existing key
await client.update("user:1:name", "Bob");
// Upsert (create or update)
await client.upsert("user:1:name", DataType.String, "Charlie");
// Unset (clear value, keep key)
await client.unset("user:1:name");
// Remove (delete key entirely)
await client.remove("user:1:name");
// List all keys
const resp = await client.listKeys();
console.log(resp.keys()); // ["user:1:name", ...]Supported Types
| DataType | JS Type | Set Example | Accessor |
|----------|---------|-------------|----------|
| DataType.Integer | bigint | 42n | resp.integer() |
| DataType.Decimal | number | 3.14 | resp.decimal() |
| DataType.String | string | "hello" | resp.string() |
| DataType.Boolean | boolean | true | resp.boolean() |
| DataType.Bytes | Buffer | Buffer.from([0x01]) | resp.bytes() |
Sequences
Auto-incrementing integer sequences:
const r1 = await client.setNextSeq("order:1:id", "order_ids");
console.log(r1.integer()); // 1n
const r2 = await client.setNextSeq("order:2:id", "order_ids");
console.log(r2.integer()); // 2n
// Also available: upsertNextSeq, updateNextSeqLists
// Set a list with initial values
await client.setList("scores", DataType.ListInteger, [10n, 20n, 30n]);
// Add to a list (creates if doesn't exist)
await client.listAdd("tags", DataType.String, "typescript");
// Get list values
const resp = await client.get("scores");
console.log(resp.listValues()); // [10n, 20n, 30n]
// Remove first occurrence
const removed = await client.listRemove("tags", DataType.String, "typescript");
console.log(removed.boolean()); // true
// Get sublist (offset, count)
const sub = await client.listSub("scores", 0, 2);
console.log(sub.listValues()); // [10n, 20n]
// Add with sequence
await client.listAddNextSeq("ids", "my_seq");Projections
Projections map named fields to other keys, creating structured views:
// Create scalar keys first
await client.set("user:1:name", DataType.String, "Alice");
await client.set("user:1:age", DataType.Integer, 30n);
// Create a projection
await client.proj("user:1", [
{ fieldName: "name", refKey: "user:1:name" },
{ fieldName: "age", refKey: "user:1:age" },
]);
// Get resolves all fields
const resp = await client.get("user:1");
for (const f of resp.projectionFields()) {
console.log(`${f.fieldName} = ${f.value}`);
}
// Dot notation for single fields
const name = await client.get("user:1.name");
console.log(name.string()); // "Alice"
// Get projection metadata
const meta = await client.getMeta("user:1");
for (const m of meta.projectionMeta()) {
console.log(`${m.fieldName} -> ${m.refKey}`);
}Projection with Values
Create a projection and upsert all referenced keys in one operation:
await client.projWithValues("user:2", [
{ fieldName: "name", refKey: "user:2:name", dataType: DataType.String, value: "Bob" },
{ fieldName: "age", refKey: "user:2:age", dataType: DataType.Integer, value: 25n },
]);Search Indexes
Query across projections using B+ tree indexes:
import { Operator } from "wyndo-client";
// Create an index on projections matching "user:*"
await client.createIndex("users_idx", "user:*", [
{ fieldName: "age", dataType: DataType.Integer },
{ fieldName: "name", dataType: DataType.String },
]);
// Search with predicates
const results = await client.search("users_idx", {
predicates: [
{ fieldName: "age", operator: Operator.GTE, valueType: DataType.Integer, value: 18n },
],
orderBy: [{ fieldName: "age", desc: true }],
limit: 10,
});
for (const entry of results.searchResults()) {
console.log(`key=${entry.key}`);
for (const f of entry.fields) {
console.log(` ${f.fieldName} = ${f.value}`);
}
}
// Count only
const count = await client.searchCount("users_idx", [
{ fieldName: "age", operator: Operator.GT, valueType: DataType.Integer, value: 21n },
]);
console.log(count.integer()); // count as bigint
// Drop an index
await client.dropIndex("users_idx");Operators
Operator.EQ, Operator.NEQ, Operator.GT, Operator.GTE, Operator.LT, Operator.LTE
Pipelines
Batch multiple commands into a single round-trip:
const pipe = client.pipeline();
pipe.set("a", DataType.Integer, 10n);
pipe.set("b", DataType.String, "hello");
pipe.get("a");
pipe.get("b");
const results = await pipe.execute();
// results[0] = Set response
// results[1] = Set response
// results[2] = Get response (integer: 10n)
// results[3] = Get response (string: "hello")Response Handling
const resp = await client.get("my-key");
if (resp.isError()) {
// Server error (key not found, etc.)
console.log(`Error ${resp.error!.code}: ${resp.error!.message}`);
return;
}
if (resp.isUnset()) {
console.log("Key exists but has no value");
return;
}
console.log(resp.string());Error Codes
import { ErrorCode, errorCodeName } from "wyndo-client";
// ErrorCode.KeyNotFound (5)
// ErrorCode.KeyInUse (3)
// ErrorCode.InvalidCmdFormat (1)
// ErrorCode.IndexExists (13)
// ErrorCode.IndexNotFound (14)Running Tests
Tests are self-contained — they build and spawn a wyndo server automatically:
npm testRequires Go to be installed (to build the server binary).
