# HydraDB TypeScript SDK

The official TypeScript/JavaScript SDK for [HydraDB](https://hydradb.com) — a managed retrieval
engine that combines vector search, full‑text search, and a knowledge graph behind a single API.

- **Package:** `@hydradb/sdk`
- **Client class:** `HydraDBClient`
- **Version:** `2.1.1` (API version `2`)
- **Docs:** https://docs.hydradb.com
- **Runtime:** Node.js 18+ (works with any `fetch`-capable runtime)

---

## Table of contents

- [Installation](#installation)
- [Quick start](#quick-start)
- [Client configuration](#client-configuration)
- [Core concepts](#core-concepts)
- [Responses & raw access](#responses--raw-access)
- [Endpoints](#endpoints)
  - [`query` — unified retrieval](#query--unified-retrieval)
  - [Context (`client.context`)](#context-clientcontext)
    - [`ingest`](#contextingest)
    - [`list`](#contextlist)
    - [`inspect`](#contextinspect)
    - [`status`](#contextstatus)
    - [`relations`](#contextrelations)
    - [`updateSourceMetadata`](#contextupdatesourcemetadata)
    - [`delete`](#contextdelete)
  - [Databases (`client.databases`)](#databases-clientdatabases)
    - [`create`](#databasescreate)
    - [`list`](#databaseslist)
    - [`collections`](#databasescollections)
    - [`stats`](#databasesstats)
    - [`status`](#databasesstatus)
    - [`delete`](#databasesdelete)
  - [Webhooks (`client.webhooks`)](#webhooks-clientwebhooks)
    - [`register`](#webhooksregister)
    - [`get`](#webhooksget)
    - [`test`](#webhookstest)
    - [`delete`](#webhooksdelete)
    - [`listDeliveries`](#webhookslistdeliveries)
    - [`getDelivery`](#webhooksgetdelivery)
    - [`retryDelivery`](#webhooksretrydelivery)
- [Error handling](#error-handling)
- [Advanced](#advanced)
  - [Per-request options (timeouts, retries, abort)](#per-request-options-timeouts-retries-abort)
  - [Passthrough `fetch`](#passthrough-fetch)
  - [Custom fetch & logging](#custom-fetch--logging)

---

## Installation

```bash
npm install @hydradb/sdk
# or: pnpm add @hydradb/sdk / yarn add @hydradb/sdk
```

## Quick start

```typescript
import { HydraDBClient } from "@hydradb/sdk";

const client = new HydraDBClient({
  token: "YOUR_API_KEY", // bearer token
});

// Run a hybrid search over a database ("tenant")
const result = await client.query({
  query: "What is our refund policy?",
  database: "acme-corp",
  type: "knowledge",
  maxResults: 5,
});

console.log(result.data);
```

Every method returns an awaitable `HttpResponsePromise`. `await`-ing it resolves to the parsed
response body (a `HandlerEnvelope…` object whose payload is on `.data`). See
[Responses & raw access](#responses--raw-access).

---

## Client configuration

```typescript
import { HydraDBClient, HydraDBEnvironment } from "@hydradb/sdk";

const client = new HydraDBClient({
  token: "YOUR_API_KEY",
  apiVersion: "2",                          // optional, defaults to "2"
  environment: HydraDBEnvironment.Default,  // https://api.hydradb.com
  // baseUrl: "https://api.hydradb.com",    // override for self-hosted / staging
  timeoutInSeconds: 60,                     // default 60
  maxRetries: 2,                            // default 2
  headers: { "X-Custom-Header": "value" },  // sent on every request
});
```

| Option             | Type                                  | Default                  | Notes |
| ------------------ | ------------------------------------- | ------------------------ | ----- |
| `token`            | `string \| () => string \| Promise`   | –                        | Bearer token. Accepts a supplier for dynamic/refreshing tokens. |
| `apiVersion`       | `string`                              | `"2"`                    | Sets the `API-Version` header. |
| `environment`      | `HydraDBEnvironment \| string`        | `Default`                | `Default` → `https://api.hydradb.com`. |
| `baseUrl`          | `string`                              | –                        | Explicit URL; overrides `environment`. |
| `timeoutInSeconds` | `number`                              | `60`                     | Per‑request timeout. |
| `maxRetries`       | `number`                              | `2`                      | Automatic retries on transient failures. |
| `headers`          | `Record<string, string>`              | –                        | Extra headers on every request. |
| `fetch`            | `typeof fetch`                        | runtime default          | Custom fetch implementation. |
| `logging`          | `LogConfig \| Logger`                 | silent                   | SDK logging. |

> **Note:** all request fields use **camelCase** in TypeScript (e.g. `subTenantId`,
> `maxResults`); the SDK maps them to the API's snake_case wire format for you.

---

## Core concepts

**Database vs. Collection (tenant vs. sub‑tenant).** HydraDB v2 renamed the isolation scopes:

| v2 name (canonical) | v1 alias (deprecated, still accepted) | Meaning |
| ------------------- | ------------------------------------- | ------- |
| `database`          | `tenantId`                            | Top‑level isolation boundary. |
| `collection`        | `subTenantId`                         | A namespace within a database. |

The server’s `TenantAliases` middleware reconciles the two, so you can pass either — but new
code should use `database` / `collection`. The legacy aliases will be removed in a future release.

**Corpora (`type`).** Data is split into two corpora you can target independently:
`"knowledge"` (documents), `"memory"` (agent memories), or `"all"`.

---

## Responses & raw access

`await`-ing any call gives you the parsed body:

```typescript
const res = await client.databases.list();
console.log(res.data); // the payload
console.log(res.meta); // request metadata
```

To also get the HTTP status and headers, call `.withRawResponse()`:

```typescript
const { data, rawResponse } = await client.query({ query: "hi", database: "acme-corp" })
  .withRawResponse();

console.log(rawResponse.status);
console.log(rawResponse.headers.get("x-request-id"));
console.log(data);
```

---

## Endpoints

### `query` — unified retrieval

`POST /query` → `HandlerEnvelopeSearchV2RetrievalResult`

The single retrieval endpoint. Dispatches across corpus (`type`) and retrieval method
(`queryBy`), optionally enriching results with knowledge‑graph context.

```typescript
const result = await client.query({
  query: "How do I rotate API keys?",
  database: "acme-corp",          // v2 name for the tenant scope
  type: "knowledge",              // "knowledge" | "memory" | "all"
  queryBy: "hybrid",              // "hybrid" | "text"
  mode: "auto",                   // "fast" | "thinking" | "auto"
  operator: "or",                 // "or" | "and" | "phrase"
  maxResults: 10,
  numRelatedChunks: 3,
  graphContext: true,             // include KG context (default true)
  recencyBias: 0.2,
  metadataFilters: {              // exact-match on tenant/document metadata
    department: "security",
    additional_metadata: { author: "ada" },
  },
});

console.log(result.data);
```

**Scoping to specific collections** (preferred over the deprecated `subTenantIds`):

```typescript
// Equal weighting across collections
await client.query({ query: "pricing", database: "acme-corp", collections: ["eu", "us"] });

// Weighted ranking (one decimal place max)
await client.query({ query: "pricing", database: "acme-corp", collections: { eu: 1.0, us: 0.5 } });
```

**Scoping to specific source IDs** — `ids` applies a hard `source_id in [...]` pre‑filter; if
nothing matches it returns empty rather than widening to the whole corpus:

```typescript
await client.query({ query: "onboarding", database: "acme-corp", ids: ["doc_123", "doc_456"] });
```

Key fields (`SearchQueryRequest`):

| Field                     | Type                                | Notes |
| ------------------------- | ----------------------------------- | ----- |
| `query`                   | `string`                            | The search text. |
| `database`                | `string`                            | Tenant scope (v2). Alias: `tenantId`. |
| `collection` / `collections` | `string` / `string[] \| Record<string, number>` | Sub‑tenant scope. Prefer over `subTenantId(s)`. |
| `type`                    | `"knowledge" \| "memory" \| "all"`  | Corpus to query. |
| `queryBy`                 | `"hybrid" \| "text"`                | Retrieval method. |
| `mode`                    | `"fast" \| "thinking" \| "auto"`    | Recall mode. |
| `operator`                | `"or" \| "and" \| "phrase"`         | Text‑match operator. |
| `maxResults`              | `number`                            | Result cap. |
| `numRelatedChunks`        | `number`                            | Neighboring chunks to attach. |
| `graphContext`            | `boolean`                           | Include KG context. Default `true`. |
| `queryApps`               | `boolean`                           | App‑aware knowledge retrieval. |
| `queryForcefulRelations`  | `boolean`                           | Force relation expansion. Default `true`. |
| `metadataFilters`         | `Record<string, unknown>`           | Exact‑match (nest doc metadata under `additional_metadata`). |
| `recencyBias`             | `number`                            | Boost newer sources. |
| `ids`                     | `string[]`                          | Restrict to specific source IDs. |

---

### Context (`client.context`)

Everything about the data *inside* a database: ingesting, listing, inspecting, updating
metadata, checking processing status, reading graph relations, and deleting.

#### `context.ingest`

`POST /context/ingest` (multipart) → `HandlerEnvelopeIngestionV2SourceUploadResponse`

Ingest knowledge documents or memories. `documents` is a list of file uploads (one request may
carry several); the other structured fields are JSON strings.

```typescript
import { createReadStream } from "fs";

// Ingest a document file
const res = await client.context.ingest({
  database: "acme-corp",                 // required
  documents: [createReadStream("handbook.pdf")],  // one entry per file
  collection: "hr",
  type: "knowledge",
  // documentMetadata is a JSON *array* — one object per uploaded file. Your own
  // per-document fields go under `additional_metadata`; the item's other top-level keys
  // are the API's (metadata, evidence_kind, evidence_subject, id, relations, ...) —
  // an unknown top-level key such as `title` is rejected with 400.
  documentMetadata: JSON.stringify([{ additional_metadata: { title: "Employee Handbook", author: "HR" } }]),
  upsert: "true",                        // form field is a string
});

console.log(res.data);

// Ingest memories (no file). Each item needs "text" (or "user_assistant_pairs").
await client.context.ingest({
  database: "acme-corp",
  memories: JSON.stringify([{ text: "User prefers dark mode" }]),
  type: "memory",
});
```

| Field              | Type                     | Notes |
| ------------------ | ------------------------ | ----- |
| `database`         | `string` (required)      | Database (tenant scope). Alias: `tenantId`. |
| `documents`        | `Uploadable[]`           | File uploads (stream, `Blob`, `Buffer`, etc.), one entry per file. |
| `memories`         | `string`                 | JSON **array** string; each item needs `text` (or `user_assistant_pairs`). |
| `documentMetadata` | `string`                 | JSON **array** string — one object per uploaded file (count must match). Put your own fields under `additional_metadata`; the API's item keys (`metadata`, `evidence_kind`, `evidence_subject`, `id`, `relations`, ...) may sit alongside it. Unknown top-level keys return 400. |
| `appKnowledge`     | `string`                 | App‑knowledge items as a JSON **array** string (not raw text). |
| `graphPayload`     | `string`                 | Pre‑computed graph payload. |
| `collection`       | `string`                 | Collection (sub‑tenant scope). Alias: `subTenantId`. |
| `type`             | `string`                 | `"knowledge"` or `"memory"`. |
| `upsert`           | `string`                 | `"true"` to upsert on existing IDs. |

#### `context.list`

`POST /context/list` → `HandlerEnvelopeListV2SourceListResponse`

List sources or memories (IDs + metadata) for a database, with filtering and pagination.

```typescript
const res = await client.context.list({
  database: "acme-corp",
  collection: "hr",
  type: "knowledge",
  page: 1,
  pageSize: 50,
  includeFields: ["title", "type", "timestamp"],
  filters: {
    metadata: { department: "finance" },        // tenant/source metadata
    additionalMetadata: { author: "ada" },       // document metadata
    sourceFields: { type: "pdf" },               // well-known source fields
  },
});

for (const source of res.data?.inner?.sources ?? []) {
  console.log(source);
}
```

#### `context.inspect`

`GET /context/inspect` → `HandlerEnvelopeFetchV2SourceFetchResponse`

Fetch a single ingested source: its content, inferred content, and a presigned download URL.

```typescript
const res = await client.context.inspect({
  id: "doc_1234",              // required — source ID
  database: "acme-corp",       // required
  collection: "hr",
  expirySeconds: 3600,         // presigned URL lifetime
  mode: "both",                // fetch mode: "content", "url", or "both"
});
console.log(res.data);
```

#### `context.status`

`GET /context/status` → `HandlerEnvelopeIngestionV2BatchProcessingStatus`

Check processing status for one or more source IDs.

```typescript
// Single source
await client.context.status({ database: "acme-corp", id: "doc_1234", collection: "hr" });

// Batch
const res = await client.context.status({
  database: "acme-corp",
  ids: ["doc_1", "doc_2", "doc_3"],
});
console.log(res.data);
```

#### `context.relations`

`GET /context/relations` → `HandlerEnvelopeGraphGraphRelationsResponse`

Return knowledge‑graph relations for a whole database or a single source.

```typescript
const res = await client.context.relations({
  database: "acme-corp",       // required
  collection: "hr",
  id: "doc_1234",              // omit for database-wide relations
  type: "knowledge",           // "knowledge" | "memory"
  limit: 100,
  cursor: 0,                   // pagination cursor
});
console.log(res.data);
```

#### `context.updateSourceMetadata`

`PATCH /context/{id}/metadata` → `HandlerEnvelope…MetadataEditResult`

Merge/upsert `tenantMetadata` and `additionalMetadata` for one source. `collection`
(alias `subTenantId`) is required by the server.

```typescript
const res = await client.context.updateSourceMetadata({
  id: "doc_1234",              // required — source ID (path param)
  database: "acme-corp",
  collection: "hr",            // required by the server
  // tenantMetadata keys must be declared in the database's tenant_metadata_schema
  // (and match the declared type). Use additionalMetadata for free-form fields.
  tenantMetadata: { department: "finance" },
  additionalMetadata: { author: "ada", tags: ["policy", "2026"], reviewed: true },
});
console.log(res.data);
```

> **Note:** although the SDK exposes a `documentMetadata` parameter here, this endpoint
> **rejects** it (HTTP 400 "document_metadata is not accepted; use additional_metadata").
> Put per-document fields in `additionalMetadata` instead.

#### `context.delete`

`DELETE /context` → `HandlerEnvelopeSourcesMemoryDeleteResponse`

Delete one or more sources or memories by ID.

```typescript
const res = await client.context.delete({
  database: "acme-corp",
  collection: "hr",
  ids: ["doc_1234", "doc_5678"],
  type: "knowledge",
});
console.log(res.data);
```

---

### Databases (`client.databases`)

Manage databases (tenants) and inspect their collections, stats, and provisioning status.

#### `databases.create`

`POST /databases` → `HandlerEnvelopeTenantsTenantCreateAcceptedResponse`

Create a new database, optionally with a custom metadata schema for its collections.

```typescript
const res = await client.databases.create({
  database: "acme-corp",
  embeddingsDimension: 1536,
  databaseMetadataSchema: [
    {
      name: "department",
      dataType: "VARCHAR",   // BOOL | INT8..INT64 | FLOAT | DOUBLE | VARCHAR | JSON | ARRAY
      maxLength: 128,
      enableMatch: true,
    },
    { name: "priority", dataType: "INT32" },
  ],
});
console.log(res.data);
```

> Creation is asynchronous — poll [`databases.status`](#databasesstatus) until infrastructure
> is provisioned before ingesting.

#### `databases.list`

`GET /databases` → `HandlerEnvelopeTenantsTenantIdsResponse`

List all databases for the authenticated user. Takes no request body.

```typescript
const res = await client.databases.list();
console.log(res.data);
```

#### `databases.collections`

`GET /databases/collections` → `HandlerEnvelopeTenantsSubTenantIdsResponse`

List all collections within a database.

```typescript
const res = await client.databases.collections({ database: "acme-corp" });
console.log(res.data);
```

#### `databases.stats`

`GET /databases/stats` → `HandlerEnvelopeTenantsTenantStatsResponse`

Get collection statistics for a database.

```typescript
const res = await client.databases.stats({ database: "acme-corp" });
console.log(res.data);
```

#### `databases.status`

`GET /databases/status` → `HandlerEnvelopeTenantsInfraStatusResponseV2`

Check infrastructure provisioning status for a database.

```typescript
const res = await client.databases.status({ database: "acme-corp" });
console.log(res.data);
```

#### `databases.delete`

`DELETE /databases` → `HandlerEnvelopeTenantsTenantDeleteResponse`

Delete a database and **all** associated data.

```typescript
const res = await client.databases.delete({ database: "acme-corp" });
console.log(res.data);
```

---

### Webhooks (`client.webhooks`)

Register a single indexing webhook per org and inspect/replay its deliveries.

#### `webhooks.register`

`POST /webhooks/indexing` → `HandlerEnvelopeWebhooksWebhookRegisterResponse`

Register (or update) the indexing webhook for this API key’s org.

```typescript
const res = await client.webhooks.register({
  url: "https://example.com/hooks/hydradb",
  eventTypes: ["indexing.status_changed"],       // the only supported event type
  signingSecret: "whsec_at_least_16_chars",      // must be >= 16 characters
});
console.log(res.data);
```

#### `webhooks.get`

`GET /webhooks/indexing` → `HandlerEnvelopeWebhooksWebhookGetResponse`

Fetch the currently registered webhook. Takes no request body.

```typescript
const res = await client.webhooks.get();
console.log(res.data);
```

#### `webhooks.test`

`POST /webhooks/indexing/test` → `HandlerEnvelopeWebhooksWebhookTestResponse`

Send a test delivery to the registered endpoint.

```typescript
const res = await client.webhooks.test();
console.log(res.data);
```

#### `webhooks.delete`

`DELETE /webhooks/indexing` → `HandlerEnvelopeWebhooksWebhookDeleteResponse`

Remove the registered webhook.

```typescript
const res = await client.webhooks.delete();
console.log(res.data);
```

#### `webhooks.listDeliveries`

`GET /webhooks/indexing/deliveries` → `HandlerEnvelopeWebhooksDeliveryListResponse`

List recent webhook deliveries, with filtering and cursor pagination.

```typescript
const res = await client.webhooks.listDeliveries({
  limit: 50,
  cursor: undefined,           // pass the previous page's cursor to continue
  status: "failed",           // filter by delivery status
});
console.log(res.data);
```

#### `webhooks.getDelivery`

`GET` → `HandlerEnvelopeWebhooksDeliveryItem`

Fetch a single delivery by ID.

```typescript
const res = await client.webhooks.getDelivery({ deliveryId: "dlv_1234" });
console.log(res.data);
```

#### `webhooks.retryDelivery`

`POST` → `HandlerEnvelopeWebhooksRetryResponse`

Re‑attempt a failed delivery.

```typescript
const res = await client.webhooks.retryDelivery({ deliveryId: "dlv_1234" });
console.log(res.data);
```

---

## Error handling

Non‑2xx responses throw typed errors. Each carries `statusCode`, the parsed `body`, and the
`rawResponse`. All extend `HydraDBError`.

```typescript
import {
  HydraDBClient,
  HydraDB, // namespace with the typed error classes
  HydraDBError,
} from "@hydradb/sdk";

const client = new HydraDBClient({ token: "YOUR_API_KEY" });

try {
  await client.databases.status({ database: "does-not-exist" });
} catch (err) {
  if (err instanceof HydraDB.NotFoundError) {
    console.error("not found:", err.body);
  } else if (err instanceof HydraDBError) {
    console.error(`API error ${err.statusCode}:`, err.body);
  } else {
    throw err;
  }
}
```

Typed error classes (under the `HydraDB` namespace): `BadRequestError` (400),
`ForbiddenError` (403), `NotFoundError` (404), `ConflictError` (409),
`UnprocessableEntityError` (422), `InternalServerError` (500). Network/timeout failures throw
`HydraDBTimeoutError` / `HydraDBError`.

---

## Advanced

### Per-request options (timeouts, retries, abort)

Every method accepts a second `requestOptions` argument that overrides client defaults for
that call.

```typescript
const controller = new AbortController();

await client.query(
  { query: "hello", database: "acme-corp" },
  {
    timeoutInSeconds: 30,
    maxRetries: 3,
    apiVersion: "2",
    headers: { "X-Trace-Id": "abc123" },
    abortSignal: controller.signal,
  },
);
```

### Passthrough `fetch`

For endpoints not yet wrapped by the SDK, `client.fetch` issues a request using the SDK's
configured auth, retries, and logging. Relative paths resolve against the configured base URL.

```typescript
const response = await client.fetch("/some/new/endpoint", {
  method: "POST",
  body: JSON.stringify({ hello: "world" }),
});
console.log(await response.json());
```

### Custom fetch & logging

```typescript
import { HydraDBClient } from "@hydradb/sdk";
import nodeFetch from "node-fetch";

const client = new HydraDBClient({
  token: "YOUR_API_KEY",
  fetch: nodeFetch as unknown as typeof fetch,
  logging: { level: "debug" },
});
```

---

## Endpoint reference

| Group     | Method                  | HTTP                                       | Description |
| --------- | ----------------------- | ------------------------------------------ | ----------- |
| —         | `query`                 | `POST /query`                              | Unified hybrid/text retrieval with optional graph context. |
| context   | `ingest`                | `POST /context/ingest`                     | Ingest documents or memories (multipart). |
| context   | `list`                  | `POST /context/list`                       | List sources/memories with filters + pagination. |
| context   | `inspect`               | `GET /context/inspect`                     | Fetch a source’s content + presigned URL. |
| context   | `status`                | `GET /context/status`                      | Processing status for one or many source IDs. |
| context   | `relations`             | `GET /context/relations`                   | KG relations for a database or source. |
| context   | `updateSourceMetadata`  | `PATCH /context/{id}/metadata`             | Merge/upsert metadata for a source. |
| context   | `delete`                | `DELETE /context`                          | Delete sources/memories by ID. |
| databases | `create`                | `POST /databases`                          | Create a database with optional schema. |
| databases | `list`                  | `GET /databases`                           | List all databases. |
| databases | `collections`           | `GET /databases/collections`               | List collections in a database. |
| databases | `stats`                 | `GET /databases/stats`                     | Collection statistics. |
| databases | `status`                | `GET /databases/status`                    | Infra provisioning status. |
| databases | `delete`                | `DELETE /databases`                        | Delete a database and its data. |
| webhooks  | `register`              | `POST /webhooks/indexing`                  | Register/update the org indexing webhook. |
| webhooks  | `get`                   | `GET /webhooks/indexing`                   | Get the registered webhook. |
| webhooks  | `test`                  | `POST /webhooks/indexing/test`             | Send a test delivery. |
| webhooks  | `delete`                | `DELETE /webhooks/indexing`                | Remove the webhook. |
| webhooks  | `listDeliveries`        | `GET /webhooks/indexing/deliveries`        | List recent deliveries. |
| webhooks  | `getDelivery`           | `GET`                                      | Fetch one delivery by ID. |
| webhooks  | `retryDelivery`         | `POST`                                     | Retry a failed delivery. |

---

_This SDK is generated from the HydraDB API definition. For the full type reference see the
`api/` directory or https://docs.hydradb.com._
