network-ts-sdk/README.md
anonpenguin23 23a742e5d4 Add proxy request functionality to NetworkClient and update README with usage examples
- Introduced `proxyAnon` method in `NetworkClient` to facilitate anonymous HTTP requests through the Anyone network.
- Added `ProxyRequest` and `ProxyResponse` interfaces to define the structure of proxy requests and responses.
- Updated README.md with detailed examples on how to use the new proxy functionality, including GET and POST requests.
- Enhanced error handling for proxy requests to ensure graceful failure in case of issues.
2025-10-30 06:21:29 +02:00

9.5 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);

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)
  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",
});

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.