# SDK Pipes

> **Section:** [TypeScript SDK](https://wavehouse.dev/sdk.md)
> **Related:** [SDK Admin & System](https://wavehouse.dev/sdk/admin.md) · [SDK Queries](https://wavehouse.dev/sdk/queries.md) · [SDK Reference & CLI](https://wavehouse.dev/sdk/reference.md) · [SDK Streaming & Live Queries](https://wavehouse.dev/sdk/streaming.md)
> **Also:** [HTML version](https://wavehouse.dev/sdk/pipes) · [Docs index](https://wavehouse.dev/llms.txt)

---
Named pipes are server-defined, parameterized queries (see [Named Pipes guide](/pipes)). The SDK executes pipes for allowed roles and lists their definitions under the admin role. Examples import from `@wavehouse/sdk` or `https://esm.sh/@wavehouse/sdk` (see [Imports & Runtimes](/sdk#imports--runtimes)).

## Named Pipes — `wh.pipe(name, params?)`

Execute a pre-defined named query pipe. Returns a `PipeRef` which is **PromiseLike**.

```ts
// These are equivalent (PipeRef is PromiseLike):
const { data } = await wh.pipe('top_pages', { start_date: '2026-01-01', limit: 50 }).fetch();
const { data } = await wh.pipe('top_pages', { start_date: '2026-01-01', limit: 50 });
```

### `.fetch(opts?)`

Execute and return results. A [pipe that writes](/pipes#pipes-that-write) returns `[]`. `.fetch()` takes `PipeRequestOptions` — `{ signal }` only, narrower than the `.fetch(opts?)` on a [query builder](/sdk/queries), which also accepts `limit`. Passing a `limit` is a compile error rather than a silent no-op.

`limit` is typed `never` rather than left out, so the rejection also catches a value passed in a variable — leaving it out would only reject an inline object. That cuts both ways: a value *declared* as `RequestOptions` is rejected whether or not it actually carries a limit, since the type permits one. If you share one options object across calls, type it as `PipeRequestOptions` — the table and query-builder `.fetch()` accept that too — or inline `{ signal }` at the pipe call.

There is no per-call row cap here: the endpoint binds your `params` as the pipe's parameters, so a limit has to be declared in the pipe's SQL as `{{limit}}` (see [Named Pipes](/pipes)) and passed as `wh.pipe(name, { limit })`, as in the example above.

A ClickHouse failure on a [pipe that writes](/pipes#pipes-that-write) comes back `retryable: false`, and the SDK does not retry it. The SDK does still retry when WaveHouse's own answer never reaches it — a dropped connection, or a `502`/`503`/`504` from a proxy in front of WaveHouse that gave up waiting — so a write can run twice that way. If that matters, give the client that runs write pipes [`options.maxRetries`](/sdk#clientconfigdb) `0`.

### `.stream(opts?)`

Open a live stream (see [Streaming](/sdk/streaming)).

---

## Pipes Admin — `wh.pipes`

Inspect the adopted named query pipes. Requires the admin gate — the admin role (`policy.admin_role`) or the [operator key](/api#authentication). Pipes are defined in the server's settings directory `pipes.json` — the files are the only way to define or change a pipe, so there is no `set` or `delete`: edit the file and let the server pick it up, or call [`wh.settings.reload()`](/sdk/admin#settings--whsettings).

```ts
// List all pipes
const { data: pipes } = await wh.pipes.list();

// Get a single pipe definition
const { data: pipe } = await wh.pipes.get('top_pages');
// pipe: { name, sql, parameters, description, allowed_roles }
```

Both take a `tenant` option, sent as `?tenant=`, for a server with [a nested settings directory](/deployment#the-nested-settings-directory) — the admin routes ignore the `X-Tenant-ID` header, so [`options.headers`](/sdk#custom-headers) cannot select one. Without it they read the default tenant. Over a nested directory these routes take the [operator key](/api#authentication) alone; an admin-role token gets `403`.

```ts
const { data: acmePipes } = await wh.pipes.list({ tenant: 'acme' });
const { data: acmePipe } = await wh.pipes.get('top_pages', { tenant: 'acme' });
```