orama/sdk/README.md
2026-03-26 18:40:20 +02:00

16 KiB

@debros/network-ts-sdk - TypeScript SDK for DeBros Network

A modern, isomorphic TypeScript SDK for the DeBros Network gateway. Works seamlessly in both Node.js and browser environments with support for database operations, pub/sub messaging, and network management.

Features

  • Isomorphic: Works in Node.js and browsers (uses fetch and isomorphic-ws)
  • Database ORM-like API: QueryBuilder, Repository pattern, transactions
  • Pub/Sub Messaging: WebSocket subscriptions with automatic reconnection
  • Authentication: API key and JWT support with automatic token management
  • TypeScript First: Full type safety and IntelliSense
  • Error Handling: Unified SDKError with HTTP status and code

Installation

npm install @debros/network-ts-sdk

Quick Start

Initialize the Client

import { createClient } from "@debros/network-ts-sdk";

const client = createClient({
  baseURL: "http://localhost:6001",
  apiKey: "ak_your_api_key:namespace",
});

// Or with JWT
const client = createClient({
  baseURL: "http://localhost:6001",
  jwt: "your_jwt_token",
});

Database Operations

Create a Table

await client.db.createTable(
  "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)"
);

Insert Data

const result = await client.db.exec(
  "INSERT INTO users (name, email) VALUES (?, ?)",
  ["Alice", "alice@example.com"]
);
console.log(result.last_insert_id);

Query Data

const users = await client.db.query("SELECT * FROM users WHERE email = ?", [
  "alice@example.com",
]);

Using QueryBuilder

const activeUsers = await client.db
  .createQueryBuilder("users")
  .where("active = ?", [1])
  .orderBy("name DESC")
  .limit(10)
  .getMany();

const firstUser = await client.db
  .createQueryBuilder("users")
  .where("id = ?", [1])
  .getOne();

Using Repository Pattern

interface User {
  id?: number;
  name: string;
  email: string;
}

const repo = client.db.repository<User>("users");

// Find
const users = await repo.find({ active: 1 });
const user = await repo.findOne({ email: "alice@example.com" });

// Save (INSERT or UPDATE)
const newUser: User = { name: "Bob", email: "bob@example.com" };
await repo.save(newUser);

// Remove
await repo.remove(newUser);

Transactions

const results = await client.db.transaction([
  {
    kind: "exec",
    sql: "INSERT INTO users (name, email) VALUES (?, ?)",
    args: ["Charlie", "charlie@example.com"],
  },
  {
    kind: "query",
    sql: "SELECT COUNT(*) as count FROM users",
    args: [],
  },
]);

Pub/Sub Messaging

The SDK provides a robust pub/sub client with:

  • Multi-subscriber support: Multiple connections can subscribe to the same topic
  • Namespace isolation: Topics are scoped to your authenticated namespace
  • Server timestamps: Messages preserve server-side timestamps
  • Binary-safe: Supports both string and binary (Uint8Array) payloads
  • Strict envelope validation: Type-safe message parsing with error handling

Publish a Message

// Publish a string message
await client.pubsub.publish("notifications", "Hello, Network!");

// Publish binary data
const binaryData = new Uint8Array([1, 2, 3, 4]);
await client.pubsub.publish("binary-topic", binaryData);

Subscribe to Topics

const subscription = await client.pubsub.subscribe("notifications", {
  onMessage: (msg) => {
    console.log("Topic:", msg.topic);
    console.log("Data:", msg.data);
    console.log("Server timestamp:", new Date(msg.timestamp));
  },
  onError: (err) => {
    console.error("Subscription error:", err);
  },
  onClose: () => {
    console.log("Subscription closed");
  },
});

// Later, close the subscription
subscription.close();

Message Interface:

interface Message {
  data: string; // Decoded message payload (string)
  topic: string; // Topic name
  timestamp: number; // Server timestamp in milliseconds
}

Debug Raw Envelopes

For debugging, you can inspect raw message envelopes before decoding:

const subscription = await client.pubsub.subscribe("notifications", {
  onMessage: (msg) => {
    console.log("Decoded message:", msg.data);
  },
  onRaw: (envelope) => {
    console.log("Raw envelope:", envelope);
    // { data: "base64...", timestamp: 1234567890, topic: "notifications" }
  },
});

Multi-Subscriber Support

Multiple subscriptions to the same topic are supported. Each receives its own copy of messages:

// First subscriber
const sub1 = await client.pubsub.subscribe("events", {
  onMessage: (msg) => console.log("Sub1:", msg.data),
});

// Second subscriber (both receive messages)
const sub2 = await client.pubsub.subscribe("events", {
  onMessage: (msg) => console.log("Sub2:", msg.data),
});

// Unsubscribe independently
sub1.close(); // sub2 still active
sub2.close(); // fully unsubscribed

List Topics

const topics = await client.pubsub.topics();
console.log("Active topics:", topics);

Presence Support

The SDK supports real-time presence tracking, allowing you to see who is currently subscribed to a topic.

Subscribe with Presence

Enable presence by providing presence options in subscribe:

const subscription = await client.pubsub.subscribe("room.123", {
  onMessage: (msg) => console.log("Message:", msg.data),
  presence: {
    enabled: true,
    memberId: "user-alice",
    meta: { displayName: "Alice", avatar: "URL" },
    onJoin: (member) => {
      console.log(`${member.memberId} joined at ${new Date(member.joinedAt)}`);
      console.log("Meta:", member.meta);
    },
    onLeave: (member) => {
      console.log(`${member.memberId} left`);
    },
  },
});

Get Presence for a Topic

Query current members without subscribing:

const presence = await client.pubsub.getPresence("room.123");
console.log(`Total members: ${presence.count}`);
presence.members.forEach((member) => {
  console.log(`- ${member.memberId} (joined: ${new Date(member.joinedAt)})`);
});

Subscription Helpers

Get presence information from an active subscription:

if (subscription.hasPresence()) {
  const members = await subscription.getPresence();
  console.log("Current members:", members);
}

Authentication

Switch API Key

client.auth.setApiKey("ak_new_key:namespace");

Switch JWT

client.auth.setJwt("new_jwt_token");

Get Current Token

const token = client.auth.getToken(); // Returns API key or JWT

Get Authentication Info

const info = await client.auth.whoami();
console.log(info.authenticated, info.namespace);

Logout

await client.auth.logout();

Network Operations

Check Health

const healthy = await client.network.health();

Get Network Status

const status = await client.network.status();
console.log(status.healthy, status.peers);

List Peers

const peers = await client.network.peers();
peers.forEach((peer) => {
  console.log(peer.id, peer.addresses);
});

Proxy Requests Through Anyone Network

Make anonymous HTTP requests through the Anyone network:

// Simple GET request
const response = await client.network.proxyAnon({
  url: "https://api.example.com/data",
  method: "GET",
  headers: {
    Accept: "application/json",
  },
});

console.log(response.status_code); // 200
console.log(response.body); // Response data as string
console.log(response.headers); // Response headers

// POST request with body
const postResponse = await client.network.proxyAnon({
  url: "https://api.example.com/submit",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ key: "value" }),
});

// Parse JSON response
const data = JSON.parse(postResponse.body);

Note: The proxy endpoint requires authentication (API key or JWT) and only works when the Anyone relay is running on the gateway server.

Configuration

ClientConfig

interface ClientConfig {
  baseURL: string; // Gateway URL
  apiKey?: string; // API key (optional, if using JWT instead)
  jwt?: string; // JWT token (optional, if using API key instead)
  timeout?: number; // Request timeout in ms (default: 30000)
  maxRetries?: number; // Max retry attempts (default: 3)
  retryDelayMs?: number; // Delay between retries (default: 1000)
  debug?: boolean; // Enable debug logging with full SQL queries (default: false)
  storage?: StorageAdapter; // For persisting JWT/API key (default: MemoryStorage)
  wsConfig?: Partial<WSClientConfig>; // WebSocket configuration
  fetch?: typeof fetch; // Custom fetch implementation
}

Storage Adapters

By default, credentials are stored in memory. For browser apps, use localStorage:

import { createClient, LocalStorageAdapter } from "@debros/network-ts-sdk";

const client = createClient({
  baseURL: "http://localhost:6001",
  storage: new LocalStorageAdapter(),
  apiKey: "ak_your_key:namespace",
});

Cache Operations

The SDK provides a distributed cache client backed by Olric. Data is organized into distributed maps (dmaps).

Put a Value

// Put with optional TTL
await client.cache.put("sessions", "user:alice", { role: "admin" }, "1h");

Get a Value

// Returns null on cache miss (not an error)
const result = await client.cache.get("sessions", "user:alice");
if (result) {
  console.log(result.value); // { role: "admin" }
}

Delete a Value

await client.cache.delete("sessions", "user:alice");

Multi-Get

const results = await client.cache.multiGet("sessions", [
  "user:alice",
  "user:bob",
]);
// Returns Map<string, any | null> — null for misses
results.forEach((value, key) => {
  console.log(key, value);
});

Scan Keys

// Scan all keys in a dmap, optionally matching a regex
const scan = await client.cache.scan("sessions", "user:.*");
console.log(scan.keys); // ["user:alice", "user:bob"]
console.log(scan.count); // 2

Health Check

const health = await client.cache.health();
console.log(health.status); // "ok"

Storage (IPFS)

Upload, pin, and retrieve files from decentralized IPFS storage.

Upload a File

// Browser
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const result = await client.storage.upload(file, file.name);
console.log(result.cid); // "Qm..."

// Node.js
import { readFileSync } from "fs";
const buffer = readFileSync("image.jpg");
const result = await client.storage.upload(buffer, "image.jpg", { pin: true });

Retrieve Content

// Get as ReadableStream
const stream = await client.storage.get(cid);
const reader = stream.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // Process chunk
}

// Get full Response (for headers like content-length)
const response = await client.storage.getBinary(cid);
const contentLength = response.headers.get("content-length");

Pin / Unpin / Status

// Pin an existing CID
await client.storage.pin("QmExampleCid", "my-file");

// Check pin status
const status = await client.storage.status("QmExampleCid");
console.log(status.status); // "pinned", "pinning", "queued", "unpinned", "error"

// Unpin
await client.storage.unpin("QmExampleCid");

Serverless Functions (WASM)

Invoke WebAssembly serverless functions deployed on the network.

// Configure functions namespace
const client = createClient({
  baseURL: "http://localhost:6001",
  apiKey: "ak_your_key:namespace",
  functionsConfig: {
    namespace: "my-namespace",
  },
});

// Invoke a function with typed input/output
interface PushInput {
  token: string;
  message: string;
}
interface PushOutput {
  success: boolean;
  messageId: string;
}

const result = await client.functions.invoke<PushInput, PushOutput>(
  "send-push",
  { token: "device-token", message: "Hello!" }
);
console.log(result.messageId);

Vault (Distributed Secrets)

The vault client provides Shamir-split secret storage across guardian nodes. Secrets are split into shares, distributed to guardians, and reconstructed only when enough shares are collected (quorum).

const client = createClient({
  baseURL: "http://localhost:6001",
  apiKey: "ak_your_key:namespace",
  vaultConfig: {
    guardians: [
      { address: "10.0.0.1", port: 8443 },
      { address: "10.0.0.2", port: 8443 },
      { address: "10.0.0.3", port: 8443 },
    ],
    identityHex: "your-identity-hex",
  },
});

// Store a secret (Shamir-split across guardians)
const data = new TextEncoder().encode("my-secret-data");
const storeResult = await client.vault.store("api-key", data, 1);
console.log(storeResult.quorumMet); // true if enough guardians ACKed

// Retrieve and reconstruct a secret
const retrieved = await client.vault.retrieve("api-key");
console.log(new TextDecoder().decode(retrieved.data)); // "my-secret-data"

// List all secrets for this identity
const secrets = await client.vault.list();
console.log(secrets.secrets);

// Delete a secret from all guardians
await client.vault.delete("api-key");

Wallet-Based Authentication

For wallet-based auth (challenge-response flow):

// 1. Request a challenge
const challenge = await client.auth.challenge();

// 2. Sign the challenge with your wallet (external)
const signature = await wallet.signMessage(challenge.message);

// 3. Verify signature and get JWT
const session = await client.auth.verify(challenge.id, signature);
console.log(session.token);

// 4. Get an API key for long-lived access
const apiKey = await client.auth.getApiKey();

Error Handling

The SDK throws SDKError for all errors:

import { SDKError } from "@debros/network-ts-sdk";

try {
  await client.db.query("SELECT * FROM nonexistent");
} catch (error) {
  if (error instanceof SDKError) {
    console.log(error.httpStatus); // e.g., 400
    console.log(error.code); // e.g., "HTTP_400"
    console.log(error.message); // Error message
    console.log(error.details); // Full error response
  }
}

Browser Usage

The SDK works in browsers with minimal setup:

// Browser example
import { createClient } from "@debros/network-ts-sdk";

const client = createClient({
  baseURL: "https://gateway.example.com",
  apiKey: "ak_browser_key:my-app",
});

// Use like any other API client
const data = await client.db.query("SELECT * FROM items");

Note: For WebSocket connections in browsers with authentication, ensure your gateway supports either header-based auth or query parameter auth.

Testing

Run E2E tests against a running gateway:

# Set environment variables
export GATEWAY_BASE_URL=http://localhost:6001
export GATEWAY_API_KEY=ak_test_key:default

# Run tests
npm run test:e2e

Examples

See the tests/e2e/ directory for complete examples of:

  • Authentication (auth.test.ts)
  • Database operations (db.test.ts)
  • Transactions (tx.test.ts)
  • Pub/Sub messaging (pubsub.test.ts)
  • Network operations (network.test.ts)

Building

npm run build

Output goes to dist/ with ESM and type declarations.

Development

npm run dev      # Watch mode
npm run typecheck # Type checking
npm run lint     # Linting (if configured)

License

MIT

Support

For issues, questions, or contributions, please open an issue on GitHub or visit DeBros Network Documentation.