> ## Documentation Index
> Fetch the complete documentation index at: https://docs.verglas.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Define workers and access tables, queues, graphs, feeds, and vector indexes from TypeScript.

`@verglas/sdk` is a thin, fetch-based client. It does not parse Parquet or commit Iceberg metadata in JavaScript. The Verglas endpoint owns those operations.

## Install from the repository

Reference the current workspace package from your application:

```json package.json theme={null}
{
  "type": "module",
  "dependencies": {
    "@verglas/sdk": "file:../verglas/sdks/typescript"
  }
}
```

```bash theme={null}
npm install
```

## Define a cron worker

The following worker fetches one logical interval of order events and commits the result with a stable idempotency key:

```ts orders-worker.ts theme={null}
import { defineWorker, type Row, type WorkerContext } from "@verglas/sdk";

interface OrdersEnv {
  ORDERS_API_URL: string;
  ORDERS_API_KEY: string;
}

export default defineWorker<OrdersEnv>({
  name: "orders-ingest",
  triggers: [
    {
      type: "cron",
      schedule: "*/5 * * * *",
      startDate: "2026-08-01T00:00:00Z",
      catchup: "sequential",
    },
  ],
  secrets: ["ORDERS_API_KEY"],
  async handler(ctx: WorkerContext<OrdersEnv>) {
    if (ctx.trigger.type !== "cron") {
      throw new Error("orders-ingest requires a cron trigger");
    }

    const url = new URL(ctx.env.ORDERS_API_URL);
    if (ctx.trigger.intervalStart) {
      url.searchParams.set("start", ctx.trigger.intervalStart);
    }
    if (ctx.trigger.intervalEnd) {
      url.searchParams.set("end", ctx.trigger.intervalEnd);
    }

    const response = await fetch(url, {
      headers: { authorization: `Bearer ${ctx.env.ORDERS_API_KEY}` },
      signal: ctx.signal,
    });
    if (!response.ok) {
      throw new Error(`orders API returned HTTP ${response.status}`);
    }

    const rows = (await response.json()) as Row[];
    if (rows.length === 0) return { rowsWritten: 0 };

    const runKey = ctx.trigger.logicalDate ?? `${ctx.trigger.intervalStart}:${ctx.trigger.intervalEnd}`;
    const commit = await ctx.client.table(ctx.output).append(rows, {
      idempotencyKey: `orders-ingest:${runKey}`,
    });
    ctx.log("committed orders", { rows: commit.rowsCommitted });
    return { rowsWritten: commit.rowsCommitted };
  },
});
```

The runtime provides `ctx.client`, trigger data, configured outputs, environment bindings, structured logging, and an abort signal. Do not call `connect` inside a worker.

## Connect from a standalone application

```ts client.ts theme={null}
import { connect } from "@verglas/sdk";

const client = connect({
  endpoint: process.env.VERGLAS_ENDPOINT!,
  token: process.env.VERGLAS_TOKEN!,
});

const orders = client.table<{ id: string; total: number }>("analytics.orders");
const page = await orders.scan({ limit: 1000 });
const commit = await orders.append(
  [{ id: "order-1042", total: 79.5 }],
  { idempotencyKey: "import-2026-08-04:order-1042" },
);

console.log({ rows: page.rows.length, snapshot: commit.snapshotId });
```

## Work with tables

Create a table with an explicit schema when inference cannot express the required types or partitioning:

```ts theme={null}
await client.ensureTable("analytics.orders", {
  schema: [
    { name: "id", type: "string", nullable: false },
    { name: "created_date", type: "date32", nullable: false },
    { name: "total", type: "double", nullable: false },
  ],
  partitions: [{ source: "created_date", transform: "month" }],
});
```

Use a table handle for the current snapshot, paged scans, deltas, commits, and vector indexes:

```ts theme={null}
const table = client.table("analytics.orders");
const snapshot = await table.snapshot();
const page = await table.scan({ limit: 500 });
const delta = await table.delta(snapshot.watermark, { limit: 500 });
```

## Follow commits and rows

Follow notifications when you only need commit metadata:

```ts theme={null}
const subscription = client.follow("analytics.orders", (change) => {
  console.log(change.seq, change.snapshotId, change.committedAt);
});

await subscription.closed;
```

Follow rows when each commit should drive a bounded delta read:

```ts theme={null}
const subscription = client.followRows("analytics.orders", async (rows, watermark) => {
  console.log(`received ${rows.length} rows through ${watermark}`);
});
```

One edge websocket multiplexes all `follow` subscriptions on a client. An idle socket does not keep tenant compute awake.

## Use queues

```ts theme={null}
const queue = client.queue<{ orderId: string }>("orders-to-enrich");
await queue.enqueue([{ orderId: "order-1042" }]);

const batch = await queue.poll("enrichment-workers", { max: 100 });
// Process batch.records idempotently.
const nextPosition = batch.records.at(-1)?.position;
if (nextPosition !== undefined) {
  await queue.ack("enrichment-workers", nextPosition + 1);
}
```

Queues deliver at least once. Consumers must make processing idempotent.

## Use property graphs

```ts theme={null}
const graph = client.graph("fraud");
await graph.create();
await graph.insertNodes([
  { id: "customer-17", labels: ["customer"], properties: { country: "US" } },
  { id: "card-93", labels: ["card"] },
]);
await graph.insertEdges([
  {
    srcId: "customer-17",
    predicate: "uses",
    dstId: "card-93",
    provenance: "checkout-service",
    confidence: 1,
  },
]);
await graph.buildIndex();

const neighbors = await graph.neighbors("customer-17", { direction: "out" });
```

Graphs use ordinary Iceberg node and edge tables plus a snapshot-bound adjacency index.

## Search a vector index

```ts theme={null}
const documents = client.table("search.documents");
await documents.addIndex("embedding", { metric: "cosine", idField: "id" });
const result = await documents.searchIndex("embedding", [0.12, -0.04, 0.88], { k: 10 });
```

The table's current snapshot must carry the index attachment used by the search.
