@wavehouse/sdk 0.0.1-dev.20260901200048.h2a16ebc04961 → 0.0.1-dev.20260903180609.hf6de910fd9b8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.global.js +3 -3
- package/dist/index.global.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/url.ts","../src/http.ts","../src/dlq.ts","../src/pipes.ts","../src/schema.ts","../src/settings.ts","../src/sql.ts","../src/stream/controller.ts","../src/stream/sse.ts","../src/sys.ts","../src/stream/live-query.ts","../src/query-builder.ts","../src/table.ts","../src/client.ts"],"sourcesContent":["// ============================================================================\n// @wavehouse/sdk — Public API\n// ============================================================================\n\n// --- Main entry point ---\nexport { createClient, WaveHouseClient } from \"./client.js\";\nexport { DLQNamespace } from \"./dlq.js\";\nexport { PipeRef, PipesNamespace } from \"./pipes.js\";\nexport { QueryBuilder } from \"./query-builder.js\";\nexport { SchemaNamespace } from \"./schema.js\";\nexport { SettingsNamespace } from \"./settings.js\";\nexport { StreamController } from \"./stream/controller.js\";\nexport { LiveQuery } from \"./stream/live-query.js\";\nexport { SysNamespace } from \"./sys.js\";\nexport type { NDJSONSource } from \"./table.js\";\n// --- Core classes ---\nexport { TableRef } from \"./table.js\";\n\n// --- Types ---\nexport type {\n Aggregation,\n // Config\n ClientConfig,\n ClientOptions,\n // Schema\n Column,\n // Database & result\n Database,\n // DLQ\n DLQStats,\n // HTTP\n FetchLike,\n // Query\n FilterOp,\n // Ingest\n InsertRecordResult,\n InsertResult,\n OrderClause,\n ParamDef,\n // Pipes\n Pipe,\n PipeRequestOptions,\n // Policy\n Policy,\n PolicyFilter,\n QueryFilter,\n RequestOptions,\n Result,\n RolePermissions,\n Schemas,\n SettingsFinding,\n SettingsReloadResult,\n StreamEvent,\n StreamOptions,\n // Streaming\n StreamStatus,\n StreamSubscriber,\n StructuredQuery,\n TablePolicy,\n TableSchema,\n TimeRange,\n WaveHouseError,\n} from \"./types.js\";\n","import type { Result, WaveHouseError } from \"./types.js\";\n\n/** Create a WaveHouseError from an HTTP response. */\nexport async function parseErrorResponse(res: Response): Promise<WaveHouseError> {\n let body: Record<string, unknown> | undefined;\n try {\n body = (await res.json()) as Record<string, unknown>;\n } catch {\n body = undefined;\n }\n\n const message =\n typeof body?.error === \"string\"\n ? body.error\n : typeof body?.message === \"string\"\n ? body.message\n : res.statusText;\n\n const retryable = res.status === 503 || res.status >= 500;\n return {\n status: res.status,\n code: `HTTP_${res.status}`,\n message,\n details: body,\n retryable,\n };\n}\n\n/** Create a WaveHouseError from a network/fetch error. */\nexport function networkError(cause: unknown): WaveHouseError {\n const message = cause instanceof Error ? cause.message : String(cause);\n return {\n status: 0,\n code: \"NETWORK_ERROR\",\n message,\n retryable: true,\n };\n}\n\n/** Wrap a successful value in a Result. */\nexport function ok<T>(data: T): Result<T> {\n return { ok: true, data, error: null };\n}\n\n/** Wrap a successful paginated value in a Result. */\nexport function okPage<T>(data: T, hasMore: boolean, next?: () => Promise<Result<T>>): Result<T> {\n return { ok: true, data, error: null, hasMore, next };\n}\n\n/** Wrap an error in a Result. */\nexport function err<T = unknown>(error: WaveHouseError): Result<T> {\n return { ok: false, data: null, error };\n}\n","/**\n * Resolve an SDK request path against the configured `baseURL`.\n *\n * The single URL-construction rule for every transport (REST in `http.ts`, SSE\n * in `stream/sse.ts`, the codegen CLI). Request paths are joined **onto** the\n * base rather than resolved as absolute ones, so a base carrying a path prefix\n * — `https://app.example.com/api/warehouse`, a WaveHouse behind a BFF,\n * app-server route, or path-routed ingress — keeps that prefix. Resolving\n * `/v1/query` against such a base would silently discard it (#428).\n *\n * @internal\n */\nexport function resolveURL(base: string, path: string, params?: Record<string, string>): URL {\n const root = new URL(base);\n // Normalize the base to a directory: a bare last segment (or a query/fragment\n // shadowing one) makes relative resolution *replace* the prefix, not extend it.\n root.search = \"\";\n root.hash = \"\";\n if (!root.pathname.endsWith(\"/\")) root.pathname += \"/\";\n\n const url = new URL(path.replace(/^\\/+/, \"\"), root);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n }\n return url;\n}\n","import { networkError, parseErrorResponse } from \"./errors.js\";\nimport type { HttpContext, WaveHouseError } from \"./types.js\";\nimport { resolveURL } from \"./url.js\";\n\ninterface RequestSpec {\n method: string;\n path: string;\n body?: unknown;\n /**\n * Pre-serialized request body, sent verbatim (no `JSON.stringify`) — e.g.\n * NDJSON text. Takes precedence over `body`. Must be re-sendable so retries\n * work, hence a string rather than a stream.\n */\n rawBody?: string;\n /** Override the request Content-Type (default `application/json`). */\n contentType?: string;\n params?: Record<string, string>;\n signal?: AbortSignal;\n}\n\n/** @internal */\nexport interface HttpResult<T> {\n data: T | null;\n error: WaveHouseError | null;\n headers: Headers;\n}\n\n/**\n * Merge configured headers underneath the SDK's own, matching names\n * case-insensitively so a caller's `authorization` can't sit alongside our\n * `Authorization` and let the server pick. Canonical casing wins, and values\n * replace rather than append — a header joined instead of replaced is a known\n * way to produce `Content-Type: application/json, image/png`.\n *\n * Shared with the SSE transport so both paths resolve `headers` the same way,\n * rather than growing a second precedence story.\n *\n * @internal\n */\nexport function mergeHeaders(\n base: Record<string, string>,\n extra: Record<string, string> | undefined,\n): Record<string, string> {\n if (!extra) return base;\n const sdkNames = new Set(Object.keys(base).map((k) => k.toLowerCase()));\n const merged = { ...base };\n // Spelling each configured name was last stored under, so two entries\n // differing only in case replace each other here rather than surviving as\n // separate keys for `Headers` to comma-join at fetch time.\n const configured = new Map<string, string>();\n for (const [name, value] of Object.entries(extra)) {\n const lower = name.toLowerCase();\n // Skip rather than overwrite: `base` is the SDK's own, which outranks.\n if (sdkNames.has(lower)) continue;\n const prior = configured.get(lower);\n if (prior !== undefined) delete merged[prior];\n merged[name] = value;\n configured.set(lower, name);\n }\n return merged;\n}\n\n/** The documented shape for a cancelled request: a Result, never a throw. */\nfunction abortedResult<T>(): HttpResult<T> {\n return {\n data: null,\n error: { status: 0, code: \"ABORTED\", message: \"Request aborted\", retryable: false },\n headers: new Headers(),\n };\n}\n\n/**\n * Internal fetch wrapper with auth injection, retry, backoff, and Retry-After.\n * @internal\n */\nexport async function request<T>(ctx: HttpContext, opts: RequestSpec): Promise<HttpResult<T>> {\n const url = resolveURL(ctx.baseURL, opts.path, opts.params).toString();\n const headers: Record<string, string> = {\n \"Content-Type\": opts.contentType ?? \"application/json\",\n Accept: \"application/json\",\n };\n\n // Serialize once so every retry attempt re-sends an identical body.\n const requestBody: string | undefined =\n opts.rawBody !== undefined\n ? opts.rawBody\n : opts.body !== undefined\n ? JSON.stringify(opts.body)\n : undefined;\n\n if (ctx.auth) {\n const token = await ctx.auth();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // After auth, so `auth` keeps ownership of Authorization, and after the\n // Content-Type/Accept this request needs.\n const finalHeaders = mergeHeaders(headers, ctx.options.headers);\n\n let lastError: WaveHouseError | null = null;\n const maxAttempts = ctx.options.maxRetries + 1;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n // Configured RequestInit first, so the fields the SDK controls overwrite\n // it — a supplied `method` or `body` would otherwise corrupt the request.\n const init: RequestInit = {\n ...ctx.options.fetchOptions,\n method: opts.method,\n headers: finalHeaders,\n body: requestBody,\n signal: opts.signal,\n };\n // Call the global directly when no override is configured, rather than\n // capturing it — a detached `fetch` reference is not universally safe to\n // invoke, and late binding is what lets tests swap `globalThis.fetch`.\n const doFetch = ctx.options.fetch;\n const res = doFetch ? await doFetch(url, init) : await fetch(url, init);\n\n if (res.ok) {\n const text = await res.text();\n const data = text ? (JSON.parse(text) as T) : (undefined as T);\n return { data, error: null, headers: res.headers };\n }\n\n const error = await parseErrorResponse(res);\n\n // 503 with Retry-After: wait the specified duration\n if (res.status === 503) {\n const retryAfter = res.headers.get(\"Retry-After\");\n if (retryAfter && attempt < maxAttempts - 1) {\n const delay = parseInt(retryAfter, 10) * 1000 || 30_000;\n await sleep(delay, opts.signal);\n lastError = error;\n continue;\n }\n }\n\n // Retryable server errors (5xx)\n if (error.retryable && attempt < maxAttempts - 1) {\n await sleep(backoff(attempt), opts.signal);\n lastError = error;\n continue;\n }\n\n return { data: null, error, headers: res.headers };\n } catch (e) {\n // Classified on the *signal*, never the rejection's type. Keying off the\n // error made the outcome depend on `maxRetries` — an implementation that\n // throws something other than a `DOMException` named `AbortError`\n // (`AbortSignal.timeout()` raises a `TimeoutError`, `node-fetch` its own\n // class) fell through to NETWORK_ERROR here and was reclassified only if\n // a retry remained for the backoff to notice the signal.\n //\n // Matching on the error would also misread middleware: an `options.fetch`\n // enforcing its own per-attempt deadline aborts an internal controller\n // and rejects with a real `AbortError` while the caller's signal is\n // untouched. Nobody asked to cancel, so that is a transient failure to\n // retry, not a terminal `ABORTED`.\n if (opts.signal?.aborted) {\n return abortedResult<T>();\n }\n\n lastError = networkError(e);\n if (attempt < maxAttempts - 1) {\n // This sleep is the one that runs inside the catch, so unlike the two\n // in the try above, an abort raised *by* the backoff would escape\n // `request()` as a raw DOMException instead of the documented ABORTED\n // result — and no caller wraps this, so it would reach the consumer as\n // an unhandled rejection.\n try {\n await sleep(backoff(attempt), opts.signal);\n } catch (abort) {\n // Same rule as above, so the two classifications can't drift apart.\n // `sleep` only rejects when the signal fires, so this always holds.\n if (opts.signal?.aborted) {\n return abortedResult<T>();\n }\n throw abort;\n }\n }\n }\n }\n\n return { data: null, error: lastError!, headers: new Headers() };\n}\n\nfunction backoff(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 30_000);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type { DLQStats, HttpContext, Result, StreamOptions } from \"./types.js\";\n\ntype CreateStreamFn = (table: string, opts?: StreamOptions) => StreamController;\n\n/** Namespace for Dead Letter Queue operations. */\nexport class DLQNamespace {\n private readonly _ctx: HttpContext;\n private readonly _createStream: CreateStreamFn;\n\n constructor(ctx: HttpContext, createStream: CreateStreamFn) {\n this._ctx = ctx;\n this._createStream = createStream;\n }\n\n /** Get DLQ statistics (message counts per table). */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<DLQStats>> {\n const { data, error } = await request<DLQStats>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/dlq/stats\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Get DLQ stats filtered by table name. */\n async table(name: string, opts?: { signal?: AbortSignal }): Promise<Result<DLQStats>> {\n const { data, error } = await request<DLQStats>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/dlq/stats\",\n params: { table: name },\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live DLQ events. */\n stream(opts?: StreamOptions): StreamController {\n return this._createStream(\"dlq\", opts);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type { HttpContext, Pipe, PipeRequestOptions, Result, StreamOptions } from \"./types.js\";\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/** Reference to a named query pipe — PromiseLike for convenient `await`. */\nexport class PipeRef<Row = Record<string, unknown>> implements PromiseLike<Result<Row[]>> {\n private readonly _ctx: HttpContext;\n private readonly _name: string;\n private readonly _params?: Record<string, unknown>;\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(\n ctx: HttpContext,\n name: string,\n params: Record<string, unknown> | undefined,\n createStream: CreateStreamFn<Row>,\n ) {\n this._ctx = ctx;\n this._name = name;\n this._params = params;\n this._createStream = createStream;\n }\n\n /**\n * Execute the pipe and return results.\n *\n * Takes only `signal` — deliberately narrower than the `RequestOptions` the\n * query builder accepts. The pipes endpoint binds the body as the pipe's\n * parameters, so there is no row cap to forward; give the pipe a `{{limit}}`\n * parameter in its SQL and pass it via `wh.pipe(name, { limit })`.\n */\n async fetch(opts?: PipeRequestOptions): Promise<Result<Row[]>> {\n const { data, error } = await request<Row[]>(this._ctx, {\n method: \"POST\",\n path: `/v1/pipes/${encodeURIComponent(this._name)}`,\n body: this._params ?? {},\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live events from the pipe's underlying query. */\n stream(opts?: StreamOptions): StreamController<Row> {\n return this._createStream(this._name, opts);\n }\n\n then<TResult1 = Result<Row[]>, TResult2 = never>(\n onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.fetch().then(onfulfilled, onrejected);\n }\n}\n\n/**\n * Admin namespace for reading named query pipes.\n *\n * Pipes are defined in the server's settings directory (`pipes.json`, with\n * every `allowed_roles` entry declared in `roles.json`); files are the only\n * write path. Edit the files and the server re-adopts them on change, on\n * SIGHUP, or on `wh.settings.reload()` (POST /v1/ops/settings/reload).\n */\nexport class PipesNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /** List all registered pipes. */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<Pipe[]>> {\n const { data, error } = await request<Pipe[]>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/pipes\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Get a single pipe definition by name. */\n async get(name: string, opts?: { signal?: AbortSignal }): Promise<Result<Pipe>> {\n const { data, error } = await request<Pipe>(this._ctx, {\n method: \"GET\",\n path: `/v1/ops/pipes/${encodeURIComponent(name)}`,\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result, Schemas } from \"./types.js\";\n\n/** Namespace for schema introspection. */\nexport class SchemaNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /** List all table schemas discovered from ClickHouse. */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<Schemas>> {\n // The backend returns TableSchema[] — transform to Record<string, TableSchema>.\n const { data, error } = await request<unknown>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/schema\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n\n let schemas: Schemas;\n if (Array.isArray(data)) {\n schemas = {};\n for (const table of data) {\n if (table && typeof table === \"object\" && \"name\" in table) {\n schemas[(table as { name: string }).name] = table as Schemas[string];\n }\n }\n } else {\n schemas = data as Schemas;\n }\n return ok(schemas);\n }\n\n /** Force a schema refresh from ClickHouse system.columns. */\n async refresh(opts?: { signal?: AbortSignal }): Promise<Result<void>> {\n const { error } = await request<Schemas>(this._ctx, {\n method: \"POST\",\n path: \"/v1/ops/schema/refresh\",\n signal: opts?.signal,\n });\n if (error) return err<void>(error);\n return ok(undefined as undefined);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result, SettingsReloadResult } from \"./types.js\";\n\n/**\n * Namespace for the server's hot-reloadable settings directory. Requires the\n * admin role (the configured `admin_role`, `\"admin\"` by default).\n *\n * Policy (`policies.json`), roles (`roles.json`), and pipes (`pipes.json`)\n * are defined in that directory; files are the only write path. The server\n * re-adopts them on file change and on SIGHUP, and `reload()` triggers the\n * same path on demand.\n */\nexport class SettingsNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /**\n * Re-validate and adopt the settings directory (POST /v1/ops/settings/reload).\n * Resolves with `{ adopted: true, findings }` when adopted (warnings\n * included); a rejected directory is a 422 error whose `details` carries the\n * same `{ adopted: false, findings }` body, and the previous settings stay\n * in effect.\n */\n async reload(opts?: { signal?: AbortSignal }): Promise<Result<SettingsReloadResult>> {\n const { data, error } = await request<SettingsReloadResult>(this._ctx, {\n method: \"POST\",\n path: \"/v1/ops/settings/reload\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result } from \"./types.js\";\n\n/**\n * Execute a raw SQL query against ClickHouse.\n *\n * Backed by `POST /v1/ops/query`, which requires the admin role — the same\n * gate as the rest of `/v1/ops/*`. Callers must hold a JWT whose role is the\n * configured `admin_role` (`\"admin\"` by default); there is no separate\n * `service` role. The JWT middleware always runs, so a request with no token\n * or an invalid one is denied, never granted. Non-admin use cases should use\n * the structured query builder (`wh.from(table)...`) instead.\n *\n * The server proxies the SQL string verbatim to ClickHouse's HTTP interface,\n * so any ClickHouse-accepted statement works — including multi-statement\n * input (`SELECT 1; TRUNCATE t`) and arbitrary DDL/DML/SYSTEM verbs.\n *\n * **JSON-row contract.** This helper returns `Result<Row[]>` and assumes the\n * response is the standard `FORMAT JSON` envelope (or an empty body for\n * no-result mutations — coerced to `[]`). Inline non-JSON `FORMAT` overrides\n * (`SELECT 1 FORMAT CSV` / `FORMAT TSV` / `FORMAT Pretty` / etc.) are NOT\n * compatible with `sql()` — the proxy passes the upstream Content-Type\n * through, and the SDK's JSON decoder throws on a non-JSON body, which the\n * retry layer surfaces as a `NETWORK_ERROR` result (not a structured\n * format-mismatch error). For CSV/TSV exports, hit `/v1/ops/query`\n * directly with `fetch()` and read the body as text.\n *\n * **No parameter binding.** Positional `?` substitution is not supported.\n * The SDK has no way to forward ClickHouse-style named params\n * (`WHERE id = {id:UInt32}` with `param_id=42` on the query string) —\n * the proxy doesn't forward arbitrary query-string params to ClickHouse\n * and the SDK doesn't expose a hook to add them. Inline literals into\n * the SQL string, or — for safe binding from user-supplied input — use\n * the structured query builder (`wh.from(...).select(...).where(...)`).\n */\nexport async function sql<Row = Record<string, unknown>>(\n ctx: HttpContext,\n query: string,\n opts?: { signal?: AbortSignal },\n): Promise<Result<Row[]>> {\n const { data, error } = await request<Row[]>(ctx, {\n method: \"POST\",\n path: \"/v1/ops/query\",\n body: { sql: query },\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n}\n","import type { StreamEvent, StreamStatus, StreamSubscriber, WaveHouseError } from \"../types.js\";\n\n/** @internal Transport abstraction for SSE backend. */\nexport interface StreamTransport<T = Record<string, unknown>> {\n connect(): void;\n disconnect(): void;\n onEvent: ((event: StreamEvent<T>) => void) | null;\n onStatus: ((status: StreamStatus) => void) | null;\n onError: ((error: WaveHouseError) => void) | null;\n}\n\n/**\n * Controls a live event stream. NOT thenable.\n * Use `.subscribe()` for callback-based consumption or `for await` for async iteration.\n */\nexport class StreamController<T = Record<string, unknown>> {\n private _transport: StreamTransport<T>;\n private _subscribers = new Set<StreamSubscriber<T>>();\n private _status: StreamStatus = \"connecting\";\n private _buffer: StreamEvent<T>[] = [];\n private _waiters: { resolve: (value: IteratorResult<StreamEvent<T>>) => void }[] = [];\n private _done = false;\n\n constructor(transport: StreamTransport<T>) {\n this._transport = transport;\n\n this._transport.onEvent = (event) => {\n for (const sub of this._subscribers) {\n sub.next(event);\n }\n // Async iterator support\n const waiter = this._waiters.shift();\n if (waiter) {\n waiter.resolve({ value: event, done: false });\n } else {\n this._buffer.push(event);\n }\n };\n\n this._transport.onStatus = (status) => {\n // Deduplicate: skip if status hasn't changed (e.g. transport fires\n // 'connecting' after StreamController already set it as the initial state).\n if (status === this._status) return;\n this._status = status;\n for (const sub of this._subscribers) {\n sub.status?.(status);\n }\n if (status === \"closed\") {\n this._done = true;\n for (const w of this._waiters) {\n w.resolve({ value: undefined as never, done: true });\n }\n this._waiters = [];\n }\n };\n\n this._transport.onError = (error) => {\n for (const sub of this._subscribers) {\n sub.error?.(error);\n }\n };\n\n this._transport.connect();\n }\n\n /** Current connection status. */\n get status(): StreamStatus {\n return this._status;\n }\n\n /**\n * Returns a promise that resolves when the stream status reaches `'live'`,\n * rejects immediately if the stream is already `'closed'`, or rejects after\n * `timeoutMs` milliseconds (default: 5 000) if it never connects.\n *\n * Safe to call before `.subscribe()` — does not trigger auto-close when\n * the internal waiter is removed.\n *\n * `@example`\n * const stream = client.from('events').stream();\n * const unsub = stream.subscribe({ next: (e) => console.log(e) });\n * await stream.connected(); // waits until the transport is live\n * await client.from('events').insert({ ... });\n */\n connected(timeoutMs = 5_000): Promise<void> {\n if (this._status === \"live\") return Promise.resolve();\n if (this._status === \"closed\") return Promise.reject(new Error(\"Stream is closed\"));\n if (this._done) return Promise.reject(new Error(\"Stream closed before connecting\"));\n\n return new Promise((resolve, reject) => {\n let settled = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n this._subscribers.delete(watcher);\n reject(new Error(`Stream did not connect within ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Use a private subscriber directly to avoid the auto-close side-effect\n // that the public subscribe() triggers when subscriber count drops to zero.\n const watcher: StreamSubscriber<T> = {\n next: () => {\n // no-op: we only care about status transitions here\n },\n status: (s) => {\n if (s === \"live\") {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this._subscribers.delete(watcher);\n resolve();\n } else if (s === \"closed\") {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this._subscribers.delete(watcher);\n reject(new Error(\"Stream closed before connecting\"));\n }\n },\n };\n\n this._subscribers.add(watcher);\n });\n }\n\n /** Subscribe to stream events via callbacks. Returns an unsubscribe function. */\n subscribe(subscriber: StreamSubscriber<T>): () => void {\n this._subscribers.add(subscriber);\n subscriber.status?.(this._status);\n\n return () => {\n this._subscribers.delete(subscriber);\n if (this._subscribers.size === 0 && this._waiters.length === 0) {\n this.close();\n }\n };\n }\n\n /** Attach an AbortSignal — when aborted, the stream is closed. */\n attachSignal(signal: AbortSignal): void {\n if (signal.aborted) {\n this.close();\n return;\n }\n signal.addEventListener(\"abort\", () => this.close(), { once: true });\n }\n\n /** Close the stream and release resources. */\n close(): void {\n this._transport.disconnect();\n if (this._status !== \"closed\") {\n this._status = \"closed\";\n for (const sub of this._subscribers) {\n sub.status?.(\"closed\");\n }\n }\n this._done = true;\n for (const w of this._waiters) {\n w.resolve({ value: undefined as never, done: true });\n }\n this._waiters = [];\n }\n\n /** Async iterator protocol — enables `for await (const event of stream)`. */\n [Symbol.asyncIterator](): AsyncIterableIterator<StreamEvent<T>> {\n const self = this;\n return {\n next(): Promise<IteratorResult<StreamEvent<T>>> {\n if (self._buffer.length > 0) {\n return Promise.resolve({ value: self._buffer.shift()!, done: false });\n }\n if (self._done) {\n return Promise.resolve({ value: undefined as never, done: true });\n }\n return new Promise((resolve) => {\n self._waiters.push({ resolve });\n });\n },\n return(): Promise<IteratorResult<StreamEvent<T>>> {\n self.close();\n return Promise.resolve({ value: undefined as never, done: true });\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n }\n}\n","import { createParser } from \"eventsource-parser\";\nimport { parseErrorResponse } from \"../errors.js\";\nimport { mergeHeaders } from \"../http.js\";\nimport type { FetchLike, StreamEvent, StreamStatus, WaveHouseError } from \"../types.js\";\nimport { resolveURL } from \"../url.js\";\nimport type { StreamTransport } from \"./controller.js\";\n\nexport interface SSEOptions {\n baseURL: string;\n table: string;\n since?: string;\n auth?: () => Promise<string> | string;\n fetch?: FetchLike;\n headers?: Record<string, string>;\n fetchOptions?: RequestInit;\n}\n\n/** Module-level active SSE connection counter. */\nlet activeSSEConnections = 0;\nconst SSE_WARN_THRESHOLD = 5;\n\n/**\n * Cap on characters the frame parser will buffer before giving up, guarding\n * against unbounded growth from a malformed or hostile stream. The parser\n * defaults to unbounded, so this has to be set explicitly. 16 MiB is generous\n * headroom over the ~1 MiB NATS payload ceiling a single event can carry.\n *\n * @internal Exported only so the overflow test can hit the cap exactly; not\n * re-exported from `index.ts`, so it stays off the public surface.\n */\nexport const MAX_BUFFER_CHARS = 16 * 1024 * 1024;\n\n/** Ceiling for reconnect backoff. */\nconst MAX_BACKOFF_MS = 30_000;\n\n/**\n * How long a connection must stay live before it counts as healthy enough to\n * reset the backoff. Roughly the fixed delay native `EventSource` used, so a\n * server that accepts and instantly closes can't be hammered faster than the\n * transport this replaces.\n */\nconst STABLE_CONNECTION_MS = 3_000;\n\n/**\n * Delay before reconnect attempt `attempt` (0-based), jittered.\n *\n * Unlike the REST backoff in `http.ts`, this one is randomized: REST retries\n * are spread across independent calls, but every subscriber to a stream that\n * drops reconnects at the same instant, so a fixed schedule synchronizes the\n * whole fleet into a thundering herd against a server that is probably already\n * unwell. Each attempt waits between 50% and 100% of its nominal delay.\n */\nfunction backoff(attempt: number, retryFloorMs: number): number {\n const nominal = Math.max(retryFloorMs, Math.min(1000 * 2 ** attempt, MAX_BACKOFF_MS));\n // Jitter spans [max(floor, nominal/2), nominal), so a server-requested\n // `retry:` is honored as the lower bound the SSE spec makes it — jittering\n // over the whole nominal range could re-dial at half what the server asked.\n // With no `retry:` (the default) the floor is 0 and this is the full spread.\n const low = Math.max(retryFloorMs, nominal / 2);\n return low + Math.random() * (nominal - low);\n}\n\n/**\n * Hand a response body back to the connection pool on a path that won't read\n * it. Skipped when locked — `cancel()` throws on a locked stream, and a locked\n * body was never ours to release anyway.\n */\nfunction releaseBody(res: Response): void {\n // `.catch` is load-bearing, not defensive: cancelling an *errored* stream\n // returns a rejected promise, and an unhandled rejection takes the host\n // process down under Node's default `--unhandled-rejections=throw`. A\n // connection reset between the headers arriving and this call is enough.\n if (res.body && !res.body.locked) res.body.cancel().catch(() => {});\n}\n\n/** Outcome of one connection attempt, driving the reconnect decision. */\ninterface AttemptResult {\n /** Milliseconds the connection stayed readable; 0 if it never opened. */\n liveMs: number;\n /** Stop for good: a bad token or missing table can't be fixed by retrying. */\n terminal: boolean;\n}\n\n/**\n * SSE transport over `fetch`.\n *\n * Replaces `EventSource`, which cannot set request headers — the reason the JWT\n * used to ride in `?token=`, where it crossed every intermediary in the request\n * URI. In exchange for the `Authorization` header this owns what `EventSource`\n * used to provide: framing, reconnect, and `Last-Event-ID` resumption.\n */\nexport class SSETransport<T = Record<string, unknown>> implements StreamTransport<T> {\n private _opts: SSEOptions;\n private _abort: AbortController | null = null;\n /**\n * Set by `disconnect()` before it calls `abort()` — both synchronously — so\n * a rejection caused by our own abort always observes this as `true`. That\n * makes it a complete and precise test for \"did we cause this?\", which is\n * why the catch blocks read it directly instead of matching on the error\n * type: an `AbortError` we *didn't* raise (a custom `fetch` enforcing a\n * per-attempt deadline, an `auth()` call cancelled by its own controller) is\n * a transient failure to re-dial, exactly as on the REST path. Treating one\n * as a teardown killed the stream permanently, and silently.\n */\n private _closed = false;\n private _started = false;\n private _counted = false;\n /**\n * Most recent non-empty event id, replayed as `Last-Event-ID` on reconnect.\n *\n * Deliberately ignores empty ids. The server emits a blank `id:` line for\n * passthrough payloads, and per the SSE spec an empty `id` field *clears* the\n * last-event-id — so tracking it faithfully would silently discard the\n * resumption point mid-stream and re-open from the beginning.\n */\n private _lastEventId: string | null = null;\n /** Reconnect floor most recently requested by the server via `retry:`. */\n private _retryFloorMs = 0;\n /** Cuts a reconnect gap short when set — see `_sleep`. */\n private _wake: (() => void) | null = null;\n\n onEvent: ((event: StreamEvent<T>) => void) | null = null;\n onStatus: ((status: StreamStatus) => void) | null = null;\n onError: ((error: WaveHouseError) => void) | null = null;\n\n constructor(opts: SSEOptions) {\n this._opts = opts;\n }\n\n connect(): void {\n // Idempotent: a second call would leave two reconnect loops racing over one\n // transport, each re-dialing the other's dropped connection.\n if (this._closed || this._started) return;\n this._started = true;\n\n if (!this._opts.fetch && typeof fetch === \"undefined\") {\n throw new Error(\n \"[wavehouse] global fetch is not available in this environment. \" +\n \"Upgrade to Node 22+ or supply one via `options.fetch`.\",\n );\n }\n\n activeSSEConnections++;\n this._counted = true;\n if (activeSSEConnections > SSE_WARN_THRESHOLD) {\n console.warn(\n `[wavehouse] ${activeSSEConnections} SSE connections open. ` +\n `Browsers limit HTTP/1.1 to 6 connections per domain.`,\n );\n }\n\n // `_run` resolves rather than rejects on stream failure; this catch is for\n // a programming error inside the loop itself.\n this._run().catch((err) => {\n this._emitError({\n status: 0,\n code: \"SSE_CONNECT_ERROR\",\n message: err instanceof Error ? err.message : String(err),\n retryable: false,\n });\n this.disconnect();\n });\n }\n\n disconnect(): void {\n if (!this._closed) {\n this._closed = true;\n this._abort?.abort();\n this._abort = null;\n this._wake?.();\n if (this._counted) {\n activeSSEConnections = Math.max(0, activeSSEConnections - 1);\n this._counted = false;\n }\n // Same isolation as `_emitStatus`, which this deliberately bypasses:\n // `_emitStatus` returns early once `_closed` is set, so the terminal\n // status would never get out. Inside the guard, so a redundant\n // `disconnect()` is a no-op rather than a second `closed`.\n try {\n this.onStatus?.(\"closed\");\n } catch (e) {\n console.error(\"[wavehouse] SSE status handler threw:\", e);\n }\n }\n }\n\n /** Connect/read/reconnect until the stream is closed or hits a terminal error. */\n private async _run(): Promise<void> {\n // Yield before the first attempt so a failure that is detectable\n // synchronously — an unresolvable `baseURL` — still reaches a subscriber.\n // `connect()` runs inside the StreamController constructor, so anything\n // emitted before this point fires before `.stream()` has returned and\n // nobody is listening yet.\n await Promise.resolve();\n\n let attempt = 0;\n\n while (!this._closed) {\n const { liveMs, terminal } = await this._attempt();\n\n if (this._closed) return;\n if (terminal) {\n this.disconnect();\n return;\n }\n\n // Only a connection that *held* resets the schedule. Resetting on any\n // connection that merely opened lets a server accepting and immediately\n // closing (slow-consumer eviction, a half-broken upstream) pin the client\n // at sub-second retries forever — more aggressive than the ~3s fixed\n // delay EventSource used, against a server already in trouble.\n if (liveMs >= STABLE_CONNECTION_MS) attempt = 0;\n\n this._emitStatus(\"reconnecting\");\n await this._sleep(backoff(attempt, this._retryFloorMs));\n attempt++;\n }\n }\n\n /** One connection attempt: request, validate, then pump frames until it ends. */\n private async _attempt(): Promise<AttemptResult> {\n const ac = new AbortController();\n this._abort = ac;\n\n // A URL that won't build is deterministic — every retry produces the same\n // failure — so it ends the stream rather than looping.\n let target: string;\n try {\n target = this._url();\n } catch (e) {\n this._emitError({\n status: 0,\n code: \"SSE_CONNECT_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: false,\n });\n return { liveMs: 0, terminal: true };\n }\n\n // A token provider that throws is treated as transient, unlike the URL\n // above: `auth` now runs on every attempt, so a refresh endpoint having a\n // bad minute would otherwise tear down a healthy long-lived stream.\n let init: RequestInit;\n try {\n init = await this._init(ac.signal);\n } catch (e) {\n if (this._closed) return { liveMs: 0, terminal: true };\n this._emitError({\n status: 0,\n code: \"SSE_AUTH_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n return { liveMs: 0, terminal: false };\n }\n\n let res: Response;\n try {\n const doFetch = this._opts.fetch;\n res = doFetch ? await doFetch(target, init) : await fetch(target, init);\n } catch (e) {\n if (this._closed) return { liveMs: 0, terminal: true };\n this._emitError({\n status: 0,\n code: \"SSE_NETWORK_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n return { liveMs: 0, terminal: false };\n }\n\n // Redirects are refused, not followed — see `_init`. Surfaced explicitly\n // because both shapes are otherwise misleading: a browser reports an\n // opaque status-0 response, and Node hands back the raw 3xx.\n if (res.type === \"opaqueredirect\" || (res.status >= 300 && res.status < 400)) {\n this._emitError({\n status: res.status,\n code: \"SSE_REDIRECT\",\n message:\n \"The stream endpoint redirected and the SDK did not follow it. Redirects are \" +\n \"refused when the request carries a credential — an `auth` token or configured \" +\n \"`headers` — because a cross-origin hop strips the Authorization header, and this \" +\n \"endpoint answers an unauthenticated caller with a reduced view instead of an \" +\n \"error, while forwarding any configured headers to wherever the redirect points. \" +\n \"If this request carries neither, `fetch` was left to follow the redirect itself, \" +\n \"so arriving here means a response it will not follow (a 304, or a 3xx with no \" +\n \"Location header) or an `options.fetch` that returned the redirect verbatim. \" +\n \"Either way, set `baseURL` to the final URL; most often this is an http→https \" +\n \"upgrade or a canonical-host redirect at a proxy. To follow a credentialed \" +\n \"redirect anyway, supply an `options.fetch` that overrides `redirect`.\",\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n if (!res.ok) {\n const error = await parseErrorResponse(res);\n this._emitError(error);\n // 4xx is terminal: whatever rejected this usually won't be talked round\n // by repeating the request, and native EventSource likewise stops on\n // non-200. The exception is a 429/408 from a fronting rate limiter, which\n // is transient even though the stream still ends (#469). Note WaveHouse\n // doesn't reject here *for authentication* — the endpoint is ungated and\n // answers a bad token with a reduced view, which is why `auth` is re-read\n // per attempt (#239 tracks enforcing expiry). The one 4xx it raises itself\n // is a 400 on this route for a missing or empty `table`\n // (internal/api/stream.go); a 404/405 means the request missed the route\n // entirely (router.go's chi handlers), and anything else comes from\n // something in front — a gateway, or a proxy.\n return { liveMs: 0, terminal: !error.retryable };\n }\n\n // A 200 that isn't an event stream is an intermediary answering for the\n // server — an auth gateway serving its login page is the common one. Left\n // unchecked the parser would chew HTML, find no frames, and leave the\n // stream \"live\" and permanently silent, indistinguishable from a quiet\n // table. `EventSource` fails the connection here too.\n const contentType = res.headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().split(\";\")[0].trim().startsWith(\"text/event-stream\")) {\n this._emitError({\n status: res.status,\n code: \"SSE_BAD_CONTENT_TYPE\",\n message: `Expected \\`text/event-stream\\`, got \\`${contentType || \"(none)\"}\\`. Something between the client and WaveHouse answered this request — an auth gateway's login page is the usual cause.`,\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n // `options.fetch` only has to stream on this path — every REST call reads\n // the body with `.text()`. A wrapper that buffers, or clones and logs the\n // body, satisfies REST and then hangs here forever, so say so instead.\n // `locked` and `bodyUsed` both matter and neither implies the other: after\n // `.text()` both are set, but after a bare `getReader()` only `locked` is.\n const body = res.body;\n if (!body || typeof body.getReader !== \"function\" || body.locked || res.bodyUsed) {\n this._emitError({\n status: res.status,\n code: \"SSE_NO_STREAM_BODY\",\n message:\n \"Streaming requires a response with a readable body. The configured `options.fetch` \" +\n \"returned one whose body is absent or already read — an implementation that buffers \" +\n \"or consumes the response cannot be used for `.stream()` or `.liveQuery()`.\",\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n this._emitStatus(\"live\");\n const openedAt = Date.now();\n await this._pump(body);\n return { liveMs: Date.now() - openedAt, terminal: false };\n }\n\n /** Resolve the stream URL. Throws on a `baseURL` that isn't a usable http(s) origin. */\n private _url(): string {\n const url = resolveURL(this._opts.baseURL, \"/v1/stream\");\n // Caught here rather than at `fetch`, which reports an unusable scheme as a\n // generic failure the loop would then retry forever. `baseURL` must be\n // absolute, so a bare host throws out of `resolveURL` above; this catches\n // the absolute-but-wrong case, `ws://` being the tempting one for streams.\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(\n `baseURL must use http or https, got \"${url.protocol}\". Streaming is plain HTTP — there is no WebSocket endpoint.`,\n );\n }\n url.searchParams.set(\"table\", this._opts.table);\n if (this._opts.since) {\n url.searchParams.set(\"since\", this._opts.since);\n }\n return url.toString();\n }\n\n /** Build the request init, minting a token for this attempt. */\n private async _init(signal: AbortSignal): Promise<RequestInit> {\n const base: Record<string, string> = { Accept: \"text/event-stream\" };\n\n // Resolved per attempt, not once per stream: a stream outlives its token,\n // and a reconnect replaying an expired JWT authenticates as no one — which\n // this endpoint answers with a silently reduced view, not an error.\n if (this._opts.auth) {\n const token = await this._opts.auth();\n if (token) {\n base.Authorization = `Bearer ${token}`;\n }\n }\n\n // Takes precedence over `since` server-side, so the initial window stays on\n // the URL and resumption rides the header once there's something to resume.\n if (this._lastEventId) {\n base[\"Last-Event-ID\"] = this._lastEventId;\n }\n\n // Anything a redirect target must not be handed: the bearer token, or the\n // configured headers that carry proxy service-token secrets.\n const credentialed =\n base.Authorization !== undefined || Object.keys(this._opts.headers ?? {}).length > 0;\n\n const init: RequestInit = {\n ...this._opts.fetchOptions,\n method: \"GET\",\n headers: mergeHeaders(base, this._opts.headers),\n signal,\n // Owned by the SDK rather than left to `fetchOptions`, unlike the REST\n // path, because on a stream these are correctness rather than preference:\n //\n // cache — as an init field, not a `Cache-Control` header. The header\n // form is not in the server's Access-Control-Allow-Headers,\n // so it would fail every cross-origin preflight.\n // redirect — followed only when this request carries no credential.\n // Platforms strip `Authorization` on a cross-origin redirect\n // (whatwg/fetch#1544) and forward other headers intact, so a\n // credentialed hop either silently downgrades the stream to\n // the default role — this endpoint answers an unauthenticated\n // caller rather than rejecting them — or delivers a configured\n // secret to whatever the redirect names. Neither is worth a\n // convenience. Without either, redirects are followed, so CDN\n // canonicalization, geo/LB indirection, and an http→https\n // upgrade all just work. The test is deliberately that\n // concrete pair and not \"is this authenticated\": cookies do\n // not appear in it. A cookie is re-derived per\n // destination rather than carried, so under the default\n // same-origin credentials mode it survives a redirect only\n // while the hop stays inside both the request's origin and\n // the cookie's Path; outside either the stream is\n // silently unauthed. See #478 and the SDK guide under\n // \"Supplying your own fetch\".\n // `manual` over `error` for the credentialed case so the\n // outcome stays inspectable: `error` rejects with a bare\n // TypeError indistinguishable from a connection failure, which\n // the loop above would retry forever against a redirect that\n // is never going to stop happening.\n cache: \"no-store\",\n redirect: credentialed ? \"manual\" : \"follow\",\n // Neutralized like the REST path does, and for a sharper reason here: a\n // body on a GET makes `fetch` throw, which reads as a network failure and\n // would be retried forever against a request that can never be built.\n body: undefined,\n };\n\n // Only browsers get `credentials`: some runtimes (Cloudflare Workers) throw\n // outright if it is present. Inside a browser an explicit value from\n // `fetchOptions` survives, which is how a cookie-authenticated origin opts\n // into `include`.\n if (!(\"window\" in globalThis)) {\n delete init.credentials;\n }\n\n return init;\n }\n\n /** Decode and parse frames until the stream ends, aborts, or errors. */\n private async _pump(body: ReadableStream<Uint8Array>): Promise<void> {\n // Set when the parser reports a buffer overflow: it is terminated at that\n // point, so the read loop must stop rather than feed it again.\n let overflowed = false;\n const parser = createParser({\n onEvent: (msg) => {\n if (msg.id) this._lastEventId = msg.id;\n if (!msg.data) return;\n this._dispatch(msg.data);\n },\n onRetry: (ms) => {\n this._retryFloorMs = Math.min(Math.max(ms, 0), MAX_BACKOFF_MS);\n },\n onError: (err) => {\n if (err.type === \"max-buffer-size-exceeded\") overflowed = true;\n this._emitError({\n status: 0,\n code: \"SSE_PARSE_ERROR\",\n message: err.message,\n retryable: true,\n });\n },\n maxBufferSize: MAX_BUFFER_CHARS,\n });\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n parser.feed(decoder.decode(value, { stream: true }));\n // End the connection on the overflow we just reported, instead of\n // feeding a terminated parser and re-reporting it as a read failure.\n if (overflowed) return;\n }\n } catch (e) {\n if (this._closed) return;\n this._emitError({\n status: 0,\n code: \"SSE_READ_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n } finally {\n reader.cancel().catch(() => {\n // Already torn down — nothing left to release.\n });\n }\n }\n\n /**\n * Turn one frame's `data` into a StreamEvent.\n *\n * Guarded like `_emitError` and `_emitStatus`: `parser.feed()` dispatches\n * every complete frame in a chunk synchronously, so a subscriber that closes\n * from inside its own `next` — \"read until I see my event, then stop\" —\n * would otherwise keep receiving the rest of that chunk. `EventSource` ended\n * delivery on `close()`, and gap-fill replay routinely lands many frames in\n * one chunk.\n */\n private _dispatch(data: string): void {\n if (this._closed) return;\n\n // Two failures with one culprit each, so they are caught separately: a\n // shared catch blamed the server's frame for an exception thrown by the\n // consumer's own handler, pointing a debugging reader at the wrong side.\n let event: StreamEvent<T>;\n try {\n const msg = JSON.parse(data) as {\n table_name: string;\n received_timestamp: string;\n data: T;\n };\n event = { table: msg.table_name, timestamp: msg.received_timestamp, data: msg.data };\n } catch {\n console.warn(\"[wavehouse] SSE received malformed message:\", data);\n return; // ignore malformed messages\n }\n\n try {\n this.onEvent?.(event);\n } catch (e) {\n console.error(\"[wavehouse] SSE event handler threw:\", e);\n }\n }\n\n /** Suppress callbacks that race a `disconnect()`. */\n private _emitError(error: WaveHouseError): void {\n if (this._closed) return;\n // Guarded like `_dispatch`: a subscriber that throws must not take the\n // transport with it. Unguarded, the throw unwinds `_run` into `connect()`'s\n // terminal handler, which reports a spurious SSE_CONNECT_ERROR and then\n // calls `disconnect()` — whose own `onStatus(\"closed\")` throws again, this\n // time out of the `.catch()`, as a process-fatal unhandled rejection.\n // `EventSource` isolated a throwing handler and kept streaming. Note this\n // protects the transport, not the fan-out: StreamController still stops\n // delivering to later subscribers when an earlier one throws (#473).\n try {\n this.onError?.(error);\n } catch (e) {\n console.error(\"[wavehouse] SSE error handler threw:\", e);\n }\n }\n\n /**\n * Same guard for status. Aborting does not retract an already-resolved\n * `Response`, so a `disconnect()` landing between the fetch settling and the\n * \"live\" below would otherwise flip a closed controller back to live — and it\n * stays there, since the loop then exits without emitting anything further.\n * `disconnect()` calls `onStatus` directly; that terminal \"closed\" is the one\n * status that must always get through.\n */\n private _emitStatus(status: StreamStatus): void {\n if (this._closed) return;\n try {\n this.onStatus?.(status);\n } catch (e) {\n console.error(\"[wavehouse] SSE status handler threw:\", e);\n }\n }\n\n /** Wait out a reconnect gap, cut short by `disconnect()`. */\n private _sleep(ms: number): Promise<void> {\n // `_wake` only cuts a gap short once the timer exists. A `disconnect()`\n // raised synchronously from the \"reconnecting\" status callback runs a line\n // earlier than that, so without this the loop would install a timer nobody\n // can cancel and hold the event loop open for the whole backoff — the one\n // hole in the `_wake` machinery.\n if (this._closed) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this._wake = null;\n resolve();\n }, ms);\n this._wake = () => {\n clearTimeout(timer);\n this._wake = null;\n resolve();\n };\n });\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result } from \"./types.js\";\n\n/** Namespace for the SDK's content-free server-online check. */\nexport class SysNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /**\n * Liveness ping — resolves with no error when the server is reachable and\n * past boot. Hits the public, content-free `/v1/health` route (200/503, no\n * body), kept intentionally distinct from the `/livez` Kubernetes probe so\n * it stays reachable even in deployments that filter probe paths at the\n * reverse proxy. Use it to check a server is online before sending data, or\n * to pick among servers in a distributed setup.\n */\n async health(opts?: { signal?: AbortSignal }): Promise<Result<void>> {\n const { error } = await request<void>(this._ctx, {\n method: \"GET\",\n path: \"/v1/health\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok<void>(undefined);\n }\n}\n","import type { QueryFilter, Result, StreamEvent, StreamSubscriber } from \"../types.js\";\nimport type { StreamController } from \"./controller.js\";\n\n/**\n * Stream-first live query orchestrator.\n *\n * 1. Opens a live stream immediately (buffers events).\n * 2. Fetches historical data.\n * 3. Calls `subscriber.initial(result)` with the historical snapshot.\n * 4. Deduplicates buffered events against the fetch result.\n * 5. Flushes remaining buffered events through `subscriber.next()`.\n * 6. Resumes live: pipes stream events directly to `subscriber.next()`.\n */\nexport class LiveQuery<T = Record<string, unknown>> {\n private _stream: StreamController<T>;\n private _subscriber: StreamSubscriber<T>;\n private _buffer: StreamEvent<T>[] = [];\n private _buffering = true;\n private _unsubStream: (() => void) | null = null;\n private _closed = false;\n\n constructor(\n stream: StreamController<T>,\n fetchFn: () => Promise<Result<T[]>>,\n subscriber: StreamSubscriber<T>,\n _filters: QueryFilter[],\n ) {\n this._stream = stream;\n this._subscriber = subscriber;\n\n // Step 1: Subscribe to live events and buffer them.\n this._unsubStream = stream.subscribe({\n next: (event) => {\n if (this._closed) return;\n if (this._buffering) {\n this._buffer.push(event);\n } else {\n subscriber.next(event);\n }\n },\n status: (s) => subscriber.status?.(s),\n error: (e) => subscriber.error?.(e),\n });\n\n // Step 2-5: Fetch historical and flush.\n this._runBackfill(fetchFn);\n }\n\n private async _runBackfill(fetchFn: () => Promise<Result<T[]>>): Promise<void> {\n try {\n const result = await fetchFn();\n\n if (this._closed) return;\n\n // Step 3: Call initial() with the historical snapshot.\n this._subscriber.initial?.(result);\n\n if (result.error) {\n this._buffering = false;\n return;\n }\n\n // Step 4: Deduplicate buffered events against fetched rows.\n // The boundary is the `received_timestamp` of the *last row returned* —\n // not the newest, unless the query happens to order ascending. The ways\n // that misbehaves (and there are three) are enumerated once, in the SDK\n // docs under Live Queries → How it works; #449 tracks the fix. Stated\n // there rather than restated here, because this claim has already drifted\n // out of sync three times across the docs and this comment.\n const rows = result.data;\n let lastTimestamp: string | undefined;\n if (rows.length > 0) {\n const lastRow = rows[rows.length - 1] as Record<string, unknown>;\n lastTimestamp = lastRow?.received_timestamp as string | undefined;\n }\n\n // Step 5: Flush buffered events newer than that boundary.\n this._buffering = false;\n for (const event of this._buffer) {\n if (this._closed) break;\n if (lastTimestamp && event.timestamp <= lastTimestamp) {\n continue; // already covered by the fetch\n }\n this._subscriber.next(event);\n }\n this._buffer = [];\n } catch {\n // Fetch failed — stop buffering and resume live only.\n this._buffering = false;\n this._buffer = [];\n }\n }\n\n /** Close the live query and the underlying stream. */\n close(): void {\n this._closed = true;\n this._buffering = false;\n this._buffer = [];\n this._unsubStream?.();\n this._stream.close();\n }\n}\n","import { err, okPage } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamTransport } from \"./stream/controller.js\";\nimport { StreamController } from \"./stream/controller.js\";\nimport { LiveQuery } from \"./stream/live-query.js\";\nimport type {\n Aggregation,\n FilterOp,\n HttpContext,\n OrderClause,\n QueryFilter,\n RequestOptions,\n Result,\n StreamOptions,\n StreamSubscriber,\n StructuredQuery,\n TimeRange,\n} from \"./types.js\";\n\n/** SDK operator → backend operator mapping. */\nconst OP_MAP: Record<FilterOp, string> = {\n \"=\": \"eq\",\n \"!=\": \"neq\",\n \">\": \"gt\",\n \">=\": \"gte\",\n \"<\": \"lt\",\n \"<=\": \"lte\",\n in: \"in\",\n like: \"like\",\n not_like: \"not_like\",\n};\n\ninterface QueryState {\n table: string;\n columns: string[];\n selectAll?: boolean;\n aggregations: Aggregation[];\n filters: QueryFilter[];\n groupBy: string[];\n orderBy: OrderClause[];\n limit?: number;\n timeRange?: TimeRange;\n cacheTTL?: number;\n}\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/**\n * Immutable, PromiseLike query builder.\n * Every chain method returns a new instance. `await builder` auto-executes `.fetch()`.\n */\nexport class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike<Result<Row[]>> {\n /** @internal */\n private readonly _state: Readonly<QueryState>;\n /** @internal */\n private readonly _ctx: HttpContext;\n /** @internal */\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(ctx: HttpContext, state: QueryState, createStream: CreateStreamFn<Row>) {\n this._ctx = ctx;\n this._state = Object.freeze({ ...state });\n this._createStream = createStream;\n }\n\n // --- Builder methods (each returns a new QueryBuilder) ---\n\n select(...columns: string[]): QueryBuilder<Row> {\n return this._clone({ columns: [...this._state.columns, ...columns] });\n }\n\n /**\n * Select every column the caller's role is allowed to read (the all-columns\n * wildcard). Use this instead of `.select(...)` when you want a full-row read;\n * a bare `.fetch()` with no `.select()` does this implicitly. Mutually\n * exclusive with `.select(...)`.\n */\n selectAll(): QueryBuilder<Row> {\n return this._clone({ selectAll: true });\n }\n\n where(column: string, op: FilterOp, value: unknown): QueryBuilder<Row> {\n const filter: QueryFilter = { column, op: OP_MAP[op], value };\n return this._clone({ filters: [...this._state.filters, filter] });\n }\n\n count(column = \"*\", alias = \"count\"): QueryBuilder<Row> {\n return this._addAgg(\"count\", column, alias);\n }\n\n sum(column: string, alias = `sum_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"sum\", column, alias);\n }\n\n avg(column: string, alias = `avg_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"avg\", column, alias);\n }\n\n min(column: string, alias = `min_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"min\", column, alias);\n }\n\n max(column: string, alias = `max_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"max\", column, alias);\n }\n\n countDistinct(column: string, alias = `count_distinct_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"countDistinct\", column, alias);\n }\n\n aggregate(fn: string, column: string, alias: string): QueryBuilder<Row> {\n return this._addAgg(fn, column, alias);\n }\n\n groupBy(...columns: string[]): QueryBuilder<Row> {\n return this._clone({ groupBy: [...this._state.groupBy, ...columns] });\n }\n\n orderBy(column: string, dir: \"asc\" | \"desc\" = \"asc\"): QueryBuilder<Row> {\n return this._clone({ orderBy: [...this._state.orderBy, { column, dir }] });\n }\n\n limit(n: number): QueryBuilder<Row> {\n return this._clone({ limit: n });\n }\n\n timeRange(column: string, since: string, until?: string): QueryBuilder<Row> {\n return this._clone({ timeRange: { column, since, until } });\n }\n\n cacheTTL(seconds: number): QueryBuilder<Row> {\n return this._clone({ cacheTTL: seconds });\n }\n\n // --- Execution ---\n\n /** Default row limit when none is specified — deliberately tighter than the backend's DefaultMaxRows (10000) safety cap. */\n static readonly DEFAULT_LIMIT = 1000;\n\n async fetch(opts?: RequestOptions): Promise<Result<Row[]>> {\n const effectiveLimit = opts?.limit ?? this._state.limit ?? QueryBuilder.DEFAULT_LIMIT;\n const ast = this._buildAST(effectiveLimit);\n\n const { data, error } = await request<Row[]>(this._ctx, {\n method: \"POST\",\n path: `/v1/query?table=${encodeURIComponent(this._state.table)}`,\n body: ast,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n\n const rows = data!;\n const hasMore = effectiveLimit != null && rows.length >= effectiveLimit;\n\n // next() needs an order column for its cursor; with no .orderBy() we still\n // report hasMore honestly from the row count but can't offer a next().\n if (hasMore && this._state.orderBy.length > 0) {\n const nextFn = () => this._fetchNext(rows, effectiveLimit!, opts);\n return okPage(rows, true, nextFn);\n }\n\n return okPage(rows, hasMore);\n }\n\n stream(opts?: StreamOptions): StreamController<Row> {\n const raw = this._createStream(this._state.table, opts);\n const filters = this._state.filters;\n const columns = this._state.columns;\n\n // If no filters or column projection, return the raw stream.\n if (filters.length === 0 && columns.length === 0) {\n return raw;\n }\n\n // Wrap with a filtering transport that applies AST predicates client-side.\n return new FilteredStreamController<Row>(raw, filters, columns);\n }\n\n /**\n * Start a live query: fetches historical data, then streams live updates.\n *\n * The subscriber's `initial()` is called once with the fetch result, then\n * `next()` fires for each live event. Events that arrived during the fetch\n * are deduplicated and flushed automatically.\n *\n * Returns a LiveQuery handle with a `.close()` method.\n */\n liveQuery(subscriber: StreamSubscriber<Row>, opts?: StreamOptions): LiveQuery<Row> {\n const stream = this.stream(opts);\n const fetchFn = () => this.fetch();\n return new LiveQuery<Row>(stream, fetchFn, subscriber, this._state.filters);\n }\n\n // --- PromiseLike implementation ---\n\n then<TResult1 = Result<Row[]>, TResult2 = never>(\n onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.fetch().then(onfulfilled, onrejected);\n }\n\n // --- Private helpers ---\n\n private _clone(overrides: Partial<QueryState>): QueryBuilder<Row> {\n return new QueryBuilder(this._ctx, { ...this._state, ...overrides }, this._createStream);\n }\n\n private _addAgg(fn: string, column: string, alias: string): QueryBuilder<Row> {\n const agg: Aggregation = { fn, column, alias };\n return this._clone({ aggregations: [...this._state.aggregations, agg] });\n }\n\n private _buildAST(effectiveLimit?: number): StructuredQuery {\n const ast: StructuredQuery = {};\n const hasColumns = this._state.columns.length > 0;\n const hasAggs = this._state.aggregations.length > 0;\n // Projection: an explicit select_all, then explicit columns, else — for a\n // bare query with no projection intent and no aggregations — default to\n // select_all so `from(t).fetch()` returns rows. (The wire default for omitted\n // columns is \"nothing\"; the SDK opts into select_all on the caller's behalf.)\n if (this._state.selectAll) {\n ast.select_all = true;\n } else if (hasColumns) {\n ast.columns = this._state.columns;\n } else if (!hasAggs) {\n ast.select_all = true;\n }\n if (hasAggs) ast.aggregations = this._state.aggregations;\n if (this._state.filters.length > 0) ast.filters = this._state.filters;\n if (this._state.groupBy.length > 0) ast.group_by = this._state.groupBy;\n if (this._state.orderBy.length > 0) ast.order_by = this._state.orderBy;\n if (effectiveLimit != null) ast.limit = effectiveLimit;\n if (this._state.timeRange) ast.time_range = this._state.timeRange;\n return ast;\n }\n\n private async _fetchNext(\n prevRows: Row[],\n _limit: number,\n opts?: RequestOptions,\n ): Promise<Result<Row[]>> {\n // No explicit order → no keyset cursor to build; nothing to page by.\n const cursor = this._state.orderBy[0];\n if (cursor == null) return okPage([] as unknown as Row[], false);\n const { column: orderCol, dir: orderDir } = cursor;\n\n const lastRow = prevRows[prevRows.length - 1] as Record<string, unknown>;\n const lastValue = lastRow?.[orderCol];\n\n if (lastValue === undefined) return okPage([] as unknown as Row[], false);\n\n const cursorOp = orderDir === \"desc\" ? \"lt\" : \"gt\";\n const cursorFilter: QueryFilter = { column: orderCol, op: cursorOp, value: lastValue };\n const nextBuilder = this._clone({\n filters: [...this._state.filters, cursorFilter],\n orderBy: this._state.orderBy,\n });\n\n return nextBuilder.fetch(opts);\n }\n}\n\n// ============================================================================\n// Client-side stream filtering\n// ============================================================================\n\n/**\n * Wraps a StreamController, applying client-side filters and column projection\n * before delivering events to subscribers.\n *\n * Lifecycle: by the time we reach this constructor, `inner` has already been\n * built (and is opening its own SSE connection). The outer transport's\n * `connect()` doesn't open a new connection — it bridges `inner`'s events\n * through a filter into the outer subscriber set. On `disconnect()`, closing\n * `inner` is what actually tears down the SSE socket; the subscription we\n * register on `inner` is left to be garbage-collected with it, which is why\n * we don't bother saving the unsubscribe handle.\n * @internal\n */\nclass FilteredStreamController<T = Record<string, unknown>> extends StreamController<T> {\n constructor(inner: StreamController<T>, filters: QueryFilter[], columns: string[]) {\n const transport: StreamTransport<T> = {\n onEvent: null,\n onStatus: null,\n onError: null,\n connect() {\n inner.subscribe({\n next: (event) => {\n if (!matchesFilters(event.data as Record<string, unknown>, filters)) {\n return;\n }\n const projected =\n columns.length > 0\n ? {\n ...event,\n data: projectColumns(event.data as Record<string, unknown>, columns) as T,\n }\n : event;\n this.onEvent?.(projected);\n },\n status: (s) => this.onStatus?.(s),\n error: (e) => this.onError?.(e),\n });\n },\n disconnect() {\n inner.close();\n },\n };\n super(transport);\n }\n}\n\n/** @internal Evaluate all filters against a data row. All must pass (AND). */\nfunction matchesFilters(row: Record<string, unknown>, filters: QueryFilter[]): boolean {\n for (const f of filters) {\n const val = row[f.column];\n if (!evaluateFilter(val, f.op, f.value)) return false;\n }\n return true;\n}\n\n/** @internal Evaluate a single filter predicate. */\nfunction evaluateFilter(actual: unknown, op: string, expected: unknown): boolean {\n switch (op) {\n case \"eq\":\n return actual === expected;\n case \"neq\":\n return actual !== expected;\n case \"gt\":\n return compareOrdered(actual, expected, (a, b) => a > b);\n case \"gte\":\n return compareOrdered(actual, expected, (a, b) => a >= b);\n case \"lt\":\n return compareOrdered(actual, expected, (a, b) => a < b);\n case \"lte\":\n return compareOrdered(actual, expected, (a, b) => a <= b);\n case \"in\":\n return Array.isArray(expected) && expected.includes(actual);\n case \"like\": {\n if (typeof actual !== \"string\" || typeof expected !== \"string\") return false;\n // Convert SQL LIKE pattern to regex: % → .*, _ → .\n const escaped = expected.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = escaped.replace(/%/g, \".*\").replace(/_/g, \".\");\n return new RegExp(`^${pattern}$`, \"i\").test(actual);\n }\n case \"not_like\": {\n if (typeof actual !== \"string\" || typeof expected !== \"string\") return false;\n const escaped = expected.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = escaped.replace(/%/g, \".*\").replace(/_/g, \".\");\n return !new RegExp(`^${pattern}$`, \"i\").test(actual);\n }\n default:\n return true; // unknown op — pass through\n }\n}\n\n/**\n * @internal Apply an ordered comparison only when both sides are the same\n * comparable primitive (number-vs-number or string-vs-string — strings are\n * lexicographic, which is correct for ISO-8601 timestamps). Mismatched or\n * unsupported types return false instead of relying on JS coercion.\n */\nfunction compareOrdered(\n actual: unknown,\n expected: unknown,\n cmp: (a: number, b: number) => boolean,\n): boolean {\n if (typeof actual === \"number\" && typeof expected === \"number\") {\n return cmp(actual, expected);\n }\n if (typeof actual === \"string\" && typeof expected === \"string\") {\n // `>`/`<` on strings is lexicographic; reuse the same comparator by\n // casting through `as unknown as number` — the runtime operator works\n // identically on strings.\n return cmp(actual as unknown as number, expected as unknown as number);\n }\n return false;\n}\n\n/** @internal Project a row to only the specified columns. */\nfunction projectColumns(row: Record<string, unknown>, columns: string[]): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const col of columns) {\n if (col in row) result[col] = row[col];\n }\n return result;\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport { QueryBuilder } from \"./query-builder.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type {\n HttpContext,\n InsertRecordResult,\n InsertResult,\n RequestOptions,\n Result,\n StreamOptions,\n TableSchema,\n} from \"./types.js\";\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/** Content-Type for the NDJSON batch ingest path. */\nconst NDJSON_CONTENT_TYPE = \"application/x-ndjson\";\n\n/**\n * Accepted sources for {@link TableRef.insertNDJSON}: a string, raw bytes, a\n * Blob/File (browser file inputs), or a byte stream. Non-string sources are\n * read fully before sending.\n */\nexport type NDJSONSource = string | Uint8Array | Blob | ReadableStream<Uint8Array>;\n\n/** Wire shape of the server's batch ingest response (JSON array or NDJSON). */\ninterface BatchIngestResponse {\n total: number;\n succeeded: number;\n failed: number;\n duplicates: number;\n results?: InsertRecordResult[];\n}\n\n/** Read an {@link NDJSONSource} fully into a UTF-8 string. */\nasync function ndjsonSourceToString(source: NDJSONSource): Promise<string> {\n if (typeof source === \"string\") return source;\n if (source instanceof Uint8Array) return new TextDecoder().decode(source);\n // Blob/File and ReadableStream<Uint8Array> are both valid fetch body inits,\n // so let Response drain them to text.\n return new Response(source).text();\n}\n\n/**\n * Reference to a table. NOT thenable — safe to pass around without triggering requests.\n * Use `.fetch()`, `.select()`, `.insert()`, `.schema()`, or `.stream()` to act on it.\n */\nexport class TableRef<Row = Record<string, unknown>> {\n private readonly _ctx: HttpContext;\n private readonly _table: string;\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(ctx: HttpContext, table: string, createStream: CreateStreamFn<Row>) {\n this._ctx = ctx;\n this._table = table;\n this._createStream = createStream;\n }\n\n /** SELECT * shortcut — fetches rows with optional pagination. */\n async fetch(opts?: RequestOptions): Promise<Result<Row[]>> {\n return this.select()\n .limit(opts?.limit ?? 1000)\n .fetch(opts);\n }\n\n /** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */\n select(...columns: string[]): QueryBuilder<Row> {\n return new QueryBuilder<Row>(\n this._ctx,\n {\n table: this._table,\n columns,\n aggregations: [],\n filters: [],\n groupBy: [],\n orderBy: [],\n },\n this._createStream,\n );\n }\n\n /**\n * Start a query that selects every column the caller's role is allowed to read\n * (the all-columns wildcard). Use instead of `.select(...)` for an explicit\n * full-row read; `.fetch()` does this implicitly.\n */\n selectAll(): QueryBuilder<Row> {\n return this.select().selectAll();\n }\n\n /**\n * Insert one or more rows into this table.\n *\n * A single object is sent as a JSON `POST /v1/ingest`. An **array** is\n * serialized to NDJSON (one record per line) and sent as a single\n * `application/x-ndjson` request: a bad record no longer fails or hides the\n * rest of the batch — per-record outcomes come back in the result\n * (`failed` / `results`), and `ok` is true only when every record succeeded.\n *\n * The array path sends one request regardless of size; bounded-concurrency\n * chunking of very large arrays is tracked separately (#196). For NDJSON you\n * already have (a file or stream), use {@link insertNDJSON}.\n */\n async insert(\n data: Partial<Row> | Partial<Row>[],\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n if (Array.isArray(data)) {\n if (data.length === 0) {\n // Nothing to send — succeed without a round trip.\n return ok({ ok: true, total: 0, succeeded: 0, failed: 0, duplicates: 0, results: [] });\n }\n const ndjson = data.map((row) => JSON.stringify(row)).join(\"\\n\");\n return this._sendNDJSON(ndjson, opts);\n }\n\n const { data: res, error } = await request<{ ok?: boolean; duplicate?: boolean }>(this._ctx, {\n method: \"POST\",\n path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,\n body: data,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n const result: InsertResult = { ok: res?.ok ?? true };\n if (res?.duplicate != null) result.duplicate = res.duplicate;\n return ok(result);\n }\n\n /**\n * Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)\n * from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`\n * file or a stream produced by another system. For in-memory rows, prefer\n * {@link insert}.\n *\n * Non-string sources are read fully into memory before sending (the server\n * streams the parse). Returns the same per-record batch summary as an array\n * `insert`.\n */\n async insertNDJSON(\n source: NDJSONSource,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n const ndjson = await ndjsonSourceToString(source);\n return this._sendNDJSON(ndjson, opts);\n }\n\n /**\n * @internal Send an NDJSON body and map the server's batch response onto an\n * {@link InsertResult}. A transport / whole-request failure surfaces as the\n * Result error arm; a processed batch (even with rejected records) surfaces\n * as the success arm with `ok = failed === 0`.\n */\n private async _sendNDJSON(\n ndjson: string,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n const { data, error } = await request<BatchIngestResponse>(this._ctx, {\n method: \"POST\",\n path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,\n rawBody: ndjson,\n contentType: NDJSON_CONTENT_TYPE,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n // A 200 with no/empty body (e.g. a record-stripping intermediary) must not\n // throw — mirror the single-object path's optimistic `?? true` and fall back\n // to a zeroed summary so the \"never throws\" Result contract holds.\n const r = data ?? { total: 0, succeeded: 0, failed: 0, duplicates: 0 };\n const result: InsertResult = {\n ok: r.failed === 0,\n total: r.total,\n succeeded: r.succeeded,\n failed: r.failed,\n duplicates: r.duplicates,\n };\n if (r.results && r.results.length > 0) result.results = r.results;\n return ok(result);\n }\n\n /** Fetch the schema for this table. */\n async schema(opts?: { signal?: AbortSignal }): Promise<Result<TableSchema>> {\n const { data, error } = await request<TableSchema>(this._ctx, {\n method: \"GET\",\n path: `/v1/ops/schema?table=${encodeURIComponent(this._table)}`,\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live events for this table. */\n stream(opts?: StreamOptions): StreamController<Row> {\n return this._createStream(this._table, opts);\n }\n}\n","import { DLQNamespace } from \"./dlq.js\";\nimport { PipeRef, PipesNamespace } from \"./pipes.js\";\nimport { SchemaNamespace } from \"./schema.js\";\nimport { SettingsNamespace } from \"./settings.js\";\nimport { sql } from \"./sql.js\";\nimport { StreamController } from \"./stream/controller.js\";\nimport { SSETransport } from \"./stream/sse.js\";\nimport { SysNamespace } from \"./sys.js\";\nimport { TableRef } from \"./table.js\";\nimport type { ClientConfig, Database, HttpContext, Result, StreamOptions } from \"./types.js\";\n\ntype TableName<DB> = DB extends Database ? Extract<keyof DB, string> : string;\ntype RowType<DB, T extends string> = DB extends Database\n ? T extends keyof DB\n ? DB[T]\n : Record<string, unknown>\n : Record<string, unknown>;\n\nexport class WaveHouseClient<DB extends Database = Database> {\n /** @internal */\n readonly _ctx: HttpContext;\n\n /** Schema introspection namespace. */\n readonly schema: SchemaNamespace;\n /** Dead Letter Queue namespace. */\n readonly dlq: DLQNamespace;\n /** System health/readiness namespace. */\n readonly sys: SysNamespace;\n /** Named query pipes admin namespace. */\n readonly pipes: PipesNamespace;\n /** Settings directory namespace (admin). */\n readonly settings: SettingsNamespace;\n\n constructor(config: ClientConfig<DB>) {\n this._ctx = {\n baseURL: config.baseURL.replace(/\\/+$/, \"\"),\n auth: config.auth,\n options: {\n maxRetries: config.options?.maxRetries ?? 2,\n fetch: config.options?.fetch,\n headers: config.options?.headers,\n fetchOptions: config.options?.fetchOptions,\n },\n };\n\n this.schema = new SchemaNamespace(this._ctx);\n this.dlq = new DLQNamespace(this._ctx, (table, opts) => this._createStream(table, opts));\n this.sys = new SysNamespace(this._ctx);\n this.pipes = new PipesNamespace(this._ctx);\n this.settings = new SettingsNamespace(this._ctx);\n }\n\n /** Get a table reference for building queries, inserts, and streams. */\n from<T extends TableName<DB>>(table: T): TableRef<RowType<DB, T>> {\n return new TableRef<RowType<DB, T>>(this._ctx, table, (t, opts) =>\n this._createStream<RowType<DB, T>>(t, opts),\n );\n }\n\n /** Get a reference to a named query pipe. PromiseLike — `await` it to execute. */\n pipe<Row = Record<string, unknown>>(\n name: string,\n params?: Record<string, unknown>,\n ): PipeRef<Row> {\n return new PipeRef<Row>(this._ctx, name, params, (t, opts) => this._createStream<Row>(t, opts));\n }\n\n /**\n * Execute a raw SQL query against ClickHouse. Requires the admin role (the\n * configured `admin_role`, `\"admin\"` by default; there is no separate\n * `service` role). The endpoint proxies straight to ClickHouse's HTTP\n * interface so any ClickHouse-accepted SQL works; positional `?` param\n * binding is NOT supported — inline literals or use the structured query\n * builder for safe binding. See sql.ts for details.\n */\n sql<Row = Record<string, unknown>>(\n query: string,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<Row[]>> {\n // Migration guard: the second argument used to be a positional-`?`\n // params array. TS callers get a compile-time error from the type\n // signature, but JS callers (or `any`-typed callsites) would silently\n // pass an array as `opts` and only discover the break via downstream\n // SQL errors. Throw a clear runtime error pointing at the migration.\n if (Array.isArray(opts)) {\n throw new Error(\n \"[WaveHouse SDK] client.sql(sql, params) was removed. The /v1/ops/query endpoint does not accept positional `?` params. Inline literals into the SQL, or use the structured query builder (wh.from(table)…) for safe binding from user input.\",\n );\n }\n return sql<Row>(this._ctx, query, opts);\n }\n\n /** @internal Create a stream for the given table. */\n private _createStream<T = Record<string, unknown>>(\n table: string,\n opts?: StreamOptions,\n ): StreamController<T> {\n const transport = new SSETransport<T>({\n baseURL: this._ctx.baseURL,\n table,\n since: opts?.since,\n auth: this._ctx.auth,\n fetch: this._ctx.options.fetch,\n headers: this._ctx.options.headers,\n fetchOptions: this._ctx.options.fetchOptions,\n });\n const controller = new StreamController<T>(transport);\n if (opts?.signal) controller.attachSignal(opts.signal);\n return controller;\n }\n}\n\n/** Create a new WaveHouse client instance. */\nexport function createClient<DB extends Database = Database>(\n config: ClientConfig<DB>,\n): WaveHouseClient<DB> {\n return new WaveHouseClient(config);\n}\n"],"mappings":"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","names":["backoff","err"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/url.ts","../src/http.ts","../src/dlq.ts","../src/pipes.ts","../src/schema.ts","../src/settings.ts","../src/sql.ts","../src/stream/controller.ts","../src/stream/sse.ts","../src/sys.ts","../src/stream/live-query.ts","../src/query-builder.ts","../src/table.ts","../src/client.ts"],"sourcesContent":["// ============================================================================\n// @wavehouse/sdk — Public API\n// ============================================================================\n\n// --- Main entry point ---\nexport { createClient, WaveHouseClient } from \"./client.js\";\nexport { DLQNamespace } from \"./dlq.js\";\nexport { PipeRef, PipesNamespace } from \"./pipes.js\";\nexport { QueryBuilder } from \"./query-builder.js\";\nexport { SchemaNamespace } from \"./schema.js\";\nexport { SettingsNamespace } from \"./settings.js\";\nexport { StreamController } from \"./stream/controller.js\";\nexport { LiveQuery } from \"./stream/live-query.js\";\nexport { SysNamespace } from \"./sys.js\";\nexport type { NDJSONSource } from \"./table.js\";\n// --- Core classes ---\nexport { TableRef } from \"./table.js\";\n\n// --- Types ---\nexport type {\n Aggregation,\n // Config\n ClientConfig,\n ClientOptions,\n // Schema\n Column,\n // Database & result\n Database,\n // DLQ\n DLQStats,\n // HTTP\n FetchLike,\n // Query\n FilterOp,\n InsertPermissions,\n // Ingest\n InsertRecordResult,\n InsertResult,\n OrderClause,\n ParamDef,\n // Pipes\n Pipe,\n PipeRequestOptions,\n // Policy\n Policy,\n PolicyFilter,\n QueryFilter,\n RequestOptions,\n Result,\n RolePermissions,\n Schemas,\n SelectPermissions,\n SettingsFinding,\n SettingsReloadResult,\n StreamEvent,\n StreamOptions,\n // Streaming\n StreamStatus,\n StreamSubscriber,\n StructuredQuery,\n TablePolicy,\n TableSchema,\n TimeRange,\n WaveHouseError,\n} from \"./types.js\";\n","import type { Result, WaveHouseError } from \"./types.js\";\n\n/** Create a WaveHouseError from an HTTP response. */\nexport async function parseErrorResponse(res: Response): Promise<WaveHouseError> {\n let body: Record<string, unknown> | undefined;\n try {\n body = (await res.json()) as Record<string, unknown>;\n } catch {\n body = undefined;\n }\n\n const message =\n typeof body?.error === \"string\"\n ? body.error\n : typeof body?.message === \"string\"\n ? body.message\n : res.statusText;\n\n const retryable = res.status === 503 || res.status >= 500;\n return {\n status: res.status,\n code: `HTTP_${res.status}`,\n message,\n details: body,\n retryable,\n };\n}\n\n/** Create a WaveHouseError from a network/fetch error. */\nexport function networkError(cause: unknown): WaveHouseError {\n const message = cause instanceof Error ? cause.message : String(cause);\n return {\n status: 0,\n code: \"NETWORK_ERROR\",\n message,\n retryable: true,\n };\n}\n\n/** Wrap a successful value in a Result. */\nexport function ok<T>(data: T): Result<T> {\n return { ok: true, data, error: null };\n}\n\n/** Wrap a successful paginated value in a Result. */\nexport function okPage<T>(data: T, hasMore: boolean, next?: () => Promise<Result<T>>): Result<T> {\n return { ok: true, data, error: null, hasMore, next };\n}\n\n/** Wrap an error in a Result. */\nexport function err<T = unknown>(error: WaveHouseError): Result<T> {\n return { ok: false, data: null, error };\n}\n","/**\n * Resolve an SDK request path against the configured `baseURL`.\n *\n * The single URL-construction rule for every transport (REST in `http.ts`, SSE\n * in `stream/sse.ts`, the codegen CLI). Request paths are joined **onto** the\n * base rather than resolved as absolute ones, so a base carrying a path prefix\n * — `https://app.example.com/api/warehouse`, a WaveHouse behind a BFF,\n * app-server route, or path-routed ingress — keeps that prefix. Resolving\n * `/v1/query` against such a base would silently discard it (#428).\n *\n * @internal\n */\nexport function resolveURL(base: string, path: string, params?: Record<string, string>): URL {\n const root = new URL(base);\n // Normalize the base to a directory: a bare last segment (or a query/fragment\n // shadowing one) makes relative resolution *replace* the prefix, not extend it.\n root.search = \"\";\n root.hash = \"\";\n if (!root.pathname.endsWith(\"/\")) root.pathname += \"/\";\n\n const url = new URL(path.replace(/^\\/+/, \"\"), root);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n }\n return url;\n}\n","import { networkError, parseErrorResponse } from \"./errors.js\";\nimport type { HttpContext, WaveHouseError } from \"./types.js\";\nimport { resolveURL } from \"./url.js\";\n\ninterface RequestSpec {\n method: string;\n path: string;\n body?: unknown;\n /**\n * Pre-serialized request body, sent verbatim (no `JSON.stringify`) — e.g.\n * NDJSON text. Takes precedence over `body`. Must be re-sendable so retries\n * work, hence a string rather than a stream.\n */\n rawBody?: string;\n /** Override the request Content-Type (default `application/json`). */\n contentType?: string;\n params?: Record<string, string>;\n signal?: AbortSignal;\n}\n\n/** @internal */\nexport interface HttpResult<T> {\n data: T | null;\n error: WaveHouseError | null;\n headers: Headers;\n}\n\n/**\n * Merge configured headers underneath the SDK's own, matching names\n * case-insensitively so a caller's `authorization` can't sit alongside our\n * `Authorization` and let the server pick. Canonical casing wins, and values\n * replace rather than append — a header joined instead of replaced is a known\n * way to produce `Content-Type: application/json, image/png`.\n *\n * Shared with the SSE transport so both paths resolve `headers` the same way,\n * rather than growing a second precedence story.\n *\n * @internal\n */\nexport function mergeHeaders(\n base: Record<string, string>,\n extra: Record<string, string> | undefined,\n): Record<string, string> {\n if (!extra) return base;\n const sdkNames = new Set(Object.keys(base).map((k) => k.toLowerCase()));\n const merged = { ...base };\n // Spelling each configured name was last stored under, so two entries\n // differing only in case replace each other here rather than surviving as\n // separate keys for `Headers` to comma-join at fetch time.\n const configured = new Map<string, string>();\n for (const [name, value] of Object.entries(extra)) {\n const lower = name.toLowerCase();\n // Skip rather than overwrite: `base` is the SDK's own, which outranks.\n if (sdkNames.has(lower)) continue;\n const prior = configured.get(lower);\n if (prior !== undefined) delete merged[prior];\n merged[name] = value;\n configured.set(lower, name);\n }\n return merged;\n}\n\n/** The documented shape for a cancelled request: a Result, never a throw. */\nfunction abortedResult<T>(): HttpResult<T> {\n return {\n data: null,\n error: { status: 0, code: \"ABORTED\", message: \"Request aborted\", retryable: false },\n headers: new Headers(),\n };\n}\n\n/**\n * Internal fetch wrapper with auth injection, retry, backoff, and Retry-After.\n * @internal\n */\nexport async function request<T>(ctx: HttpContext, opts: RequestSpec): Promise<HttpResult<T>> {\n const url = resolveURL(ctx.baseURL, opts.path, opts.params).toString();\n const headers: Record<string, string> = {\n \"Content-Type\": opts.contentType ?? \"application/json\",\n Accept: \"application/json\",\n };\n\n // Serialize once so every retry attempt re-sends an identical body.\n const requestBody: string | undefined =\n opts.rawBody !== undefined\n ? opts.rawBody\n : opts.body !== undefined\n ? JSON.stringify(opts.body)\n : undefined;\n\n if (ctx.auth) {\n const token = await ctx.auth();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // After auth, so `auth` keeps ownership of Authorization, and after the\n // Content-Type/Accept this request needs.\n const finalHeaders = mergeHeaders(headers, ctx.options.headers);\n\n let lastError: WaveHouseError | null = null;\n const maxAttempts = ctx.options.maxRetries + 1;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n // Configured RequestInit first, so the fields the SDK controls overwrite\n // it — a supplied `method` or `body` would otherwise corrupt the request.\n const init: RequestInit = {\n ...ctx.options.fetchOptions,\n method: opts.method,\n headers: finalHeaders,\n body: requestBody,\n signal: opts.signal,\n };\n // Call the global directly when no override is configured, rather than\n // capturing it — a detached `fetch` reference is not universally safe to\n // invoke, and late binding is what lets tests swap `globalThis.fetch`.\n const doFetch = ctx.options.fetch;\n const res = doFetch ? await doFetch(url, init) : await fetch(url, init);\n\n if (res.ok) {\n const text = await res.text();\n const data = text ? (JSON.parse(text) as T) : (undefined as T);\n return { data, error: null, headers: res.headers };\n }\n\n const error = await parseErrorResponse(res);\n\n // 503 with Retry-After: wait the specified duration\n if (res.status === 503) {\n const retryAfter = res.headers.get(\"Retry-After\");\n if (retryAfter && attempt < maxAttempts - 1) {\n const delay = parseInt(retryAfter, 10) * 1000 || 30_000;\n await sleep(delay, opts.signal);\n lastError = error;\n continue;\n }\n }\n\n // Retryable server errors (5xx)\n if (error.retryable && attempt < maxAttempts - 1) {\n await sleep(backoff(attempt), opts.signal);\n lastError = error;\n continue;\n }\n\n return { data: null, error, headers: res.headers };\n } catch (e) {\n // Classified on the *signal*, never the rejection's type. Keying off the\n // error made the outcome depend on `maxRetries` — an implementation that\n // throws something other than a `DOMException` named `AbortError`\n // (`AbortSignal.timeout()` raises a `TimeoutError`, `node-fetch` its own\n // class) fell through to NETWORK_ERROR here and was reclassified only if\n // a retry remained for the backoff to notice the signal.\n //\n // Matching on the error would also misread middleware: an `options.fetch`\n // enforcing its own per-attempt deadline aborts an internal controller\n // and rejects with a real `AbortError` while the caller's signal is\n // untouched. Nobody asked to cancel, so that is a transient failure to\n // retry, not a terminal `ABORTED`.\n if (opts.signal?.aborted) {\n return abortedResult<T>();\n }\n\n lastError = networkError(e);\n if (attempt < maxAttempts - 1) {\n // This sleep is the one that runs inside the catch, so unlike the two\n // in the try above, an abort raised *by* the backoff would escape\n // `request()` as a raw DOMException instead of the documented ABORTED\n // result — and no caller wraps this, so it would reach the consumer as\n // an unhandled rejection.\n try {\n await sleep(backoff(attempt), opts.signal);\n } catch (abort) {\n // Same rule as above, so the two classifications can't drift apart.\n // `sleep` only rejects when the signal fires, so this always holds.\n if (opts.signal?.aborted) {\n return abortedResult<T>();\n }\n throw abort;\n }\n }\n }\n }\n\n return { data: null, error: lastError!, headers: new Headers() };\n}\n\nfunction backoff(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 30_000);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type { DLQStats, HttpContext, Result, StreamOptions } from \"./types.js\";\n\ntype CreateStreamFn = (table: string, opts?: StreamOptions) => StreamController;\n\n/** Namespace for Dead Letter Queue operations. */\nexport class DLQNamespace {\n private readonly _ctx: HttpContext;\n private readonly _createStream: CreateStreamFn;\n\n constructor(ctx: HttpContext, createStream: CreateStreamFn) {\n this._ctx = ctx;\n this._createStream = createStream;\n }\n\n /** Get DLQ statistics (message counts per table). */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<DLQStats>> {\n const { data, error } = await request<DLQStats>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/dlq/stats\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Get DLQ stats filtered by table name. */\n async table(name: string, opts?: { signal?: AbortSignal }): Promise<Result<DLQStats>> {\n const { data, error } = await request<DLQStats>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/dlq/stats\",\n params: { table: name },\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live DLQ events. */\n stream(opts?: StreamOptions): StreamController {\n return this._createStream(\"dlq\", opts);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type { HttpContext, Pipe, PipeRequestOptions, Result, StreamOptions } from \"./types.js\";\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/** Reference to a named query pipe — PromiseLike for convenient `await`. */\nexport class PipeRef<Row = Record<string, unknown>> implements PromiseLike<Result<Row[]>> {\n private readonly _ctx: HttpContext;\n private readonly _name: string;\n private readonly _params?: Record<string, unknown>;\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(\n ctx: HttpContext,\n name: string,\n params: Record<string, unknown> | undefined,\n createStream: CreateStreamFn<Row>,\n ) {\n this._ctx = ctx;\n this._name = name;\n this._params = params;\n this._createStream = createStream;\n }\n\n /**\n * Execute the pipe and return results.\n *\n * Takes only `signal` — deliberately narrower than the `RequestOptions` the\n * query builder accepts. The pipes endpoint binds the body as the pipe's\n * parameters, so there is no row cap to forward; give the pipe a `{{limit}}`\n * parameter in its SQL and pass it via `wh.pipe(name, { limit })`.\n */\n async fetch(opts?: PipeRequestOptions): Promise<Result<Row[]>> {\n const { data, error } = await request<Row[]>(this._ctx, {\n method: \"POST\",\n path: `/v1/pipes/${encodeURIComponent(this._name)}`,\n body: this._params ?? {},\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live events from the pipe's underlying query. */\n stream(opts?: StreamOptions): StreamController<Row> {\n return this._createStream(this._name, opts);\n }\n\n then<TResult1 = Result<Row[]>, TResult2 = never>(\n onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.fetch().then(onfulfilled, onrejected);\n }\n}\n\n/**\n * Admin namespace for reading named query pipes.\n *\n * Pipes are defined in the server's settings directory (`pipes.json`, with\n * every `allowed_roles` entry declared in `roles.json`); files are the only\n * write path. Edit the files and the server re-adopts them on change, on\n * SIGHUP, or on `wh.settings.reload()` (POST /v1/ops/settings/reload).\n */\nexport class PipesNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /** List all registered pipes. */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<Pipe[]>> {\n const { data, error } = await request<Pipe[]>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/pipes\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Get a single pipe definition by name. */\n async get(name: string, opts?: { signal?: AbortSignal }): Promise<Result<Pipe>> {\n const { data, error } = await request<Pipe>(this._ctx, {\n method: \"GET\",\n path: `/v1/ops/pipes/${encodeURIComponent(name)}`,\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result, Schemas } from \"./types.js\";\n\n/** Namespace for schema introspection. */\nexport class SchemaNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /** List all table schemas discovered from ClickHouse. */\n async list(opts?: { signal?: AbortSignal }): Promise<Result<Schemas>> {\n // The backend returns TableSchema[] — transform to Record<string, TableSchema>.\n const { data, error } = await request<unknown>(this._ctx, {\n method: \"GET\",\n path: \"/v1/ops/schema\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n\n let schemas: Schemas;\n if (Array.isArray(data)) {\n schemas = {};\n for (const table of data) {\n if (table && typeof table === \"object\" && \"name\" in table) {\n schemas[(table as { name: string }).name] = table as Schemas[string];\n }\n }\n } else {\n schemas = data as Schemas;\n }\n return ok(schemas);\n }\n\n /** Force a schema refresh from ClickHouse system.columns. */\n async refresh(opts?: { signal?: AbortSignal }): Promise<Result<void>> {\n const { error } = await request<Schemas>(this._ctx, {\n method: \"POST\",\n path: \"/v1/ops/schema/refresh\",\n signal: opts?.signal,\n });\n if (error) return err<void>(error);\n return ok(undefined as undefined);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result, SettingsReloadResult } from \"./types.js\";\n\n/**\n * Namespace for the server's hot-reloadable settings directory. Requires the\n * admin role (the configured `admin_role`, `\"admin\"` by default).\n *\n * Policy (`policies.json`), roles (`roles.json`), and pipes (`pipes.json`)\n * are defined in that directory; files are the only write path. The server\n * re-adopts them on file change and on SIGHUP, and `reload()` triggers the\n * same path on demand.\n */\nexport class SettingsNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /**\n * Re-validate and adopt the settings directory (POST /v1/ops/settings/reload).\n * Resolves with `{ adopted: true, findings }` when adopted (warnings\n * included); a rejected directory is a 422 error whose `details` carries the\n * same `{ adopted: false, findings }` body, and the previous settings stay\n * in effect.\n */\n async reload(opts?: { signal?: AbortSignal }): Promise<Result<SettingsReloadResult>> {\n const { data, error } = await request<SettingsReloadResult>(this._ctx, {\n method: \"POST\",\n path: \"/v1/ops/settings/reload\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result } from \"./types.js\";\n\n/**\n * Execute a raw SQL query against ClickHouse.\n *\n * Backed by `POST /v1/ops/query`, which requires the admin role — the same\n * gate as the rest of `/v1/ops/*`. Callers must hold a JWT whose role is the\n * configured `admin_role` (`\"admin\"` by default); there is no separate\n * `service` role. The JWT middleware always runs, so a request with no token\n * or an invalid one is denied, never granted. Non-admin use cases should use\n * the structured query builder (`wh.from(table)...`) instead.\n *\n * The server proxies the SQL string verbatim to ClickHouse's HTTP interface,\n * so any ClickHouse-accepted statement works — including multi-statement\n * input (`SELECT 1; TRUNCATE t`) and arbitrary DDL/DML/SYSTEM verbs.\n *\n * **JSON-row contract.** This helper returns `Result<Row[]>` and assumes the\n * response is the standard `FORMAT JSON` envelope (or an empty body for\n * no-result mutations — coerced to `[]`). Inline non-JSON `FORMAT` overrides\n * (`SELECT 1 FORMAT CSV` / `FORMAT TSV` / `FORMAT Pretty` / etc.) are NOT\n * compatible with `sql()` — the proxy passes the upstream Content-Type\n * through, and the SDK's JSON decoder throws on a non-JSON body, which the\n * retry layer surfaces as a `NETWORK_ERROR` result (not a structured\n * format-mismatch error). For CSV/TSV exports, hit `/v1/ops/query`\n * directly with `fetch()` and read the body as text.\n *\n * **No parameter binding.** Positional `?` substitution is not supported.\n * The SDK has no way to forward ClickHouse-style named params\n * (`WHERE id = {id:UInt32}` with `param_id=42` on the query string) —\n * the proxy doesn't forward arbitrary query-string params to ClickHouse\n * and the SDK doesn't expose a hook to add them. Inline literals into\n * the SQL string, or — for safe binding from user-supplied input — use\n * the structured query builder (`wh.from(...).select(...).where(...)`).\n */\nexport async function sql<Row = Record<string, unknown>>(\n ctx: HttpContext,\n query: string,\n opts?: { signal?: AbortSignal },\n): Promise<Result<Row[]>> {\n const { data, error } = await request<Row[]>(ctx, {\n method: \"POST\",\n path: \"/v1/ops/query\",\n body: { sql: query },\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n}\n","import type { StreamEvent, StreamStatus, StreamSubscriber, WaveHouseError } from \"../types.js\";\n\n/** @internal Transport abstraction for SSE backend. */\nexport interface StreamTransport<T = Record<string, unknown>> {\n connect(): void;\n disconnect(): void;\n onEvent: ((event: StreamEvent<T>) => void) | null;\n onStatus: ((status: StreamStatus) => void) | null;\n onError: ((error: WaveHouseError) => void) | null;\n}\n\n/**\n * Controls a live event stream. NOT thenable.\n * Use `.subscribe()` for callback-based consumption or `for await` for async iteration.\n */\nexport class StreamController<T = Record<string, unknown>> {\n private _transport: StreamTransport<T>;\n private _subscribers = new Set<StreamSubscriber<T>>();\n private _status: StreamStatus = \"connecting\";\n private _buffer: StreamEvent<T>[] = [];\n private _waiters: { resolve: (value: IteratorResult<StreamEvent<T>>) => void }[] = [];\n private _done = false;\n\n constructor(transport: StreamTransport<T>) {\n this._transport = transport;\n\n this._transport.onEvent = (event) => {\n for (const sub of this._subscribers) {\n sub.next(event);\n }\n // Async iterator support\n const waiter = this._waiters.shift();\n if (waiter) {\n waiter.resolve({ value: event, done: false });\n } else {\n this._buffer.push(event);\n }\n };\n\n this._transport.onStatus = (status) => {\n // Deduplicate: skip if status hasn't changed (e.g. transport fires\n // 'connecting' after StreamController already set it as the initial state).\n if (status === this._status) return;\n this._status = status;\n for (const sub of this._subscribers) {\n sub.status?.(status);\n }\n if (status === \"closed\") {\n this._done = true;\n for (const w of this._waiters) {\n w.resolve({ value: undefined as never, done: true });\n }\n this._waiters = [];\n }\n };\n\n this._transport.onError = (error) => {\n for (const sub of this._subscribers) {\n sub.error?.(error);\n }\n };\n\n this._transport.connect();\n }\n\n /** Current connection status. */\n get status(): StreamStatus {\n return this._status;\n }\n\n /**\n * Returns a promise that resolves when the stream status reaches `'live'`,\n * rejects immediately if the stream is already `'closed'`, or rejects after\n * `timeoutMs` milliseconds (default: 5 000) if it never connects.\n *\n * Safe to call before `.subscribe()` — does not trigger auto-close when\n * the internal waiter is removed.\n *\n * `@example`\n * const stream = client.from('events').stream();\n * const unsub = stream.subscribe({ next: (e) => console.log(e) });\n * await stream.connected(); // waits until the transport is live\n * await client.from('events').insert({ ... });\n */\n connected(timeoutMs = 5_000): Promise<void> {\n if (this._status === \"live\") return Promise.resolve();\n if (this._status === \"closed\") return Promise.reject(new Error(\"Stream is closed\"));\n if (this._done) return Promise.reject(new Error(\"Stream closed before connecting\"));\n\n return new Promise((resolve, reject) => {\n let settled = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n this._subscribers.delete(watcher);\n reject(new Error(`Stream did not connect within ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Use a private subscriber directly to avoid the auto-close side-effect\n // that the public subscribe() triggers when subscriber count drops to zero.\n const watcher: StreamSubscriber<T> = {\n next: () => {\n // no-op: we only care about status transitions here\n },\n status: (s) => {\n if (s === \"live\") {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this._subscribers.delete(watcher);\n resolve();\n } else if (s === \"closed\") {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this._subscribers.delete(watcher);\n reject(new Error(\"Stream closed before connecting\"));\n }\n },\n };\n\n this._subscribers.add(watcher);\n });\n }\n\n /** Subscribe to stream events via callbacks. Returns an unsubscribe function. */\n subscribe(subscriber: StreamSubscriber<T>): () => void {\n this._subscribers.add(subscriber);\n subscriber.status?.(this._status);\n\n return () => {\n this._subscribers.delete(subscriber);\n if (this._subscribers.size === 0 && this._waiters.length === 0) {\n this.close();\n }\n };\n }\n\n /** Attach an AbortSignal — when aborted, the stream is closed. */\n attachSignal(signal: AbortSignal): void {\n if (signal.aborted) {\n this.close();\n return;\n }\n signal.addEventListener(\"abort\", () => this.close(), { once: true });\n }\n\n /** Close the stream and release resources. */\n close(): void {\n this._transport.disconnect();\n if (this._status !== \"closed\") {\n this._status = \"closed\";\n for (const sub of this._subscribers) {\n sub.status?.(\"closed\");\n }\n }\n this._done = true;\n for (const w of this._waiters) {\n w.resolve({ value: undefined as never, done: true });\n }\n this._waiters = [];\n }\n\n /** Async iterator protocol — enables `for await (const event of stream)`. */\n [Symbol.asyncIterator](): AsyncIterableIterator<StreamEvent<T>> {\n const self = this;\n return {\n next(): Promise<IteratorResult<StreamEvent<T>>> {\n if (self._buffer.length > 0) {\n return Promise.resolve({ value: self._buffer.shift()!, done: false });\n }\n if (self._done) {\n return Promise.resolve({ value: undefined as never, done: true });\n }\n return new Promise((resolve) => {\n self._waiters.push({ resolve });\n });\n },\n return(): Promise<IteratorResult<StreamEvent<T>>> {\n self.close();\n return Promise.resolve({ value: undefined as never, done: true });\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n }\n}\n","import { createParser } from \"eventsource-parser\";\nimport { parseErrorResponse } from \"../errors.js\";\nimport { mergeHeaders } from \"../http.js\";\nimport type { FetchLike, StreamEvent, StreamStatus, WaveHouseError } from \"../types.js\";\nimport { resolveURL } from \"../url.js\";\nimport type { StreamTransport } from \"./controller.js\";\n\nexport interface SSEOptions {\n baseURL: string;\n table: string;\n since?: string;\n auth?: () => Promise<string> | string;\n fetch?: FetchLike;\n headers?: Record<string, string>;\n fetchOptions?: RequestInit;\n}\n\n/** Module-level active SSE connection counter. */\nlet activeSSEConnections = 0;\nconst SSE_WARN_THRESHOLD = 5;\n\n/**\n * Cap on characters the frame parser will buffer before giving up, guarding\n * against unbounded growth from a malformed or hostile stream. The parser\n * defaults to unbounded, so this has to be set explicitly. 16 MiB is generous\n * headroom over the ~1 MiB NATS payload ceiling a single event can carry.\n *\n * @internal Exported only so the overflow test can hit the cap exactly; not\n * re-exported from `index.ts`, so it stays off the public surface.\n */\nexport const MAX_BUFFER_CHARS = 16 * 1024 * 1024;\n\n/** Ceiling for reconnect backoff. */\nconst MAX_BACKOFF_MS = 30_000;\n\n/**\n * How long a connection must stay live before it counts as healthy enough to\n * reset the backoff. Roughly the fixed delay native `EventSource` used, so a\n * server that accepts and instantly closes can't be hammered faster than the\n * transport this replaces.\n */\nconst STABLE_CONNECTION_MS = 3_000;\n\n/**\n * Delay before reconnect attempt `attempt` (0-based), jittered.\n *\n * Unlike the REST backoff in `http.ts`, this one is randomized: REST retries\n * are spread across independent calls, but every subscriber to a stream that\n * drops reconnects at the same instant, so a fixed schedule synchronizes the\n * whole fleet into a thundering herd against a server that is probably already\n * unwell. Each attempt waits between 50% and 100% of its nominal delay.\n */\nfunction backoff(attempt: number, retryFloorMs: number): number {\n const nominal = Math.max(retryFloorMs, Math.min(1000 * 2 ** attempt, MAX_BACKOFF_MS));\n // Jitter spans [max(floor, nominal/2), nominal), so a server-requested\n // `retry:` is honored as the lower bound the SSE spec makes it — jittering\n // over the whole nominal range could re-dial at half what the server asked.\n // With no `retry:` (the default) the floor is 0 and this is the full spread.\n const low = Math.max(retryFloorMs, nominal / 2);\n return low + Math.random() * (nominal - low);\n}\n\n/**\n * Hand a response body back to the connection pool on a path that won't read\n * it. Skipped when locked — `cancel()` throws on a locked stream, and a locked\n * body was never ours to release anyway.\n */\nfunction releaseBody(res: Response): void {\n // `.catch` is load-bearing, not defensive: cancelling an *errored* stream\n // returns a rejected promise, and an unhandled rejection takes the host\n // process down under Node's default `--unhandled-rejections=throw`. A\n // connection reset between the headers arriving and this call is enough.\n if (res.body && !res.body.locked) res.body.cancel().catch(() => {});\n}\n\n/** Outcome of one connection attempt, driving the reconnect decision. */\ninterface AttemptResult {\n /** Milliseconds the connection stayed readable; 0 if it never opened. */\n liveMs: number;\n /** Stop for good: a bad token or missing table can't be fixed by retrying. */\n terminal: boolean;\n}\n\n/**\n * SSE transport over `fetch`.\n *\n * Replaces `EventSource`, which cannot set request headers — the reason the JWT\n * used to ride in `?token=`, where it crossed every intermediary in the request\n * URI. In exchange for the `Authorization` header this owns what `EventSource`\n * used to provide: framing, reconnect, and `Last-Event-ID` resumption.\n */\nexport class SSETransport<T = Record<string, unknown>> implements StreamTransport<T> {\n private _opts: SSEOptions;\n private _abort: AbortController | null = null;\n /**\n * Set by `disconnect()` before it calls `abort()` — both synchronously — so\n * a rejection caused by our own abort always observes this as `true`. That\n * makes it a complete and precise test for \"did we cause this?\", which is\n * why the catch blocks read it directly instead of matching on the error\n * type: an `AbortError` we *didn't* raise (a custom `fetch` enforcing a\n * per-attempt deadline, an `auth()` call cancelled by its own controller) is\n * a transient failure to re-dial, exactly as on the REST path. Treating one\n * as a teardown killed the stream permanently, and silently.\n */\n private _closed = false;\n private _started = false;\n private _counted = false;\n /**\n * Most recent non-empty event id, replayed as `Last-Event-ID` on reconnect.\n *\n * Deliberately ignores empty ids. The server emits a blank `id:` line for\n * passthrough payloads, and per the SSE spec an empty `id` field *clears* the\n * last-event-id — so tracking it faithfully would silently discard the\n * resumption point mid-stream and re-open from the beginning.\n */\n private _lastEventId: string | null = null;\n /** Reconnect floor most recently requested by the server via `retry:`. */\n private _retryFloorMs = 0;\n /** Cuts a reconnect gap short when set — see `_sleep`. */\n private _wake: (() => void) | null = null;\n\n onEvent: ((event: StreamEvent<T>) => void) | null = null;\n onStatus: ((status: StreamStatus) => void) | null = null;\n onError: ((error: WaveHouseError) => void) | null = null;\n\n constructor(opts: SSEOptions) {\n this._opts = opts;\n }\n\n connect(): void {\n // Idempotent: a second call would leave two reconnect loops racing over one\n // transport, each re-dialing the other's dropped connection.\n if (this._closed || this._started) return;\n this._started = true;\n\n if (!this._opts.fetch && typeof fetch === \"undefined\") {\n throw new Error(\n \"[wavehouse] global fetch is not available in this environment. \" +\n \"Upgrade to Node 22+ or supply one via `options.fetch`.\",\n );\n }\n\n activeSSEConnections++;\n this._counted = true;\n if (activeSSEConnections > SSE_WARN_THRESHOLD) {\n console.warn(\n `[wavehouse] ${activeSSEConnections} SSE connections open. ` +\n `Browsers limit HTTP/1.1 to 6 connections per domain.`,\n );\n }\n\n // `_run` resolves rather than rejects on stream failure; this catch is for\n // a programming error inside the loop itself.\n this._run().catch((err) => {\n this._emitError({\n status: 0,\n code: \"SSE_CONNECT_ERROR\",\n message: err instanceof Error ? err.message : String(err),\n retryable: false,\n });\n this.disconnect();\n });\n }\n\n disconnect(): void {\n if (!this._closed) {\n this._closed = true;\n this._abort?.abort();\n this._abort = null;\n this._wake?.();\n if (this._counted) {\n activeSSEConnections = Math.max(0, activeSSEConnections - 1);\n this._counted = false;\n }\n // Same isolation as `_emitStatus`, which this deliberately bypasses:\n // `_emitStatus` returns early once `_closed` is set, so the terminal\n // status would never get out. Inside the guard, so a redundant\n // `disconnect()` is a no-op rather than a second `closed`.\n try {\n this.onStatus?.(\"closed\");\n } catch (e) {\n console.error(\"[wavehouse] SSE status handler threw:\", e);\n }\n }\n }\n\n /** Connect/read/reconnect until the stream is closed or hits a terminal error. */\n private async _run(): Promise<void> {\n // Yield before the first attempt so a failure that is detectable\n // synchronously — an unresolvable `baseURL` — still reaches a subscriber.\n // `connect()` runs inside the StreamController constructor, so anything\n // emitted before this point fires before `.stream()` has returned and\n // nobody is listening yet.\n await Promise.resolve();\n\n let attempt = 0;\n\n while (!this._closed) {\n const { liveMs, terminal } = await this._attempt();\n\n if (this._closed) return;\n if (terminal) {\n this.disconnect();\n return;\n }\n\n // Only a connection that *held* resets the schedule. Resetting on any\n // connection that merely opened lets a server accepting and immediately\n // closing (slow-consumer eviction, a half-broken upstream) pin the client\n // at sub-second retries forever — more aggressive than the ~3s fixed\n // delay EventSource used, against a server already in trouble.\n if (liveMs >= STABLE_CONNECTION_MS) attempt = 0;\n\n this._emitStatus(\"reconnecting\");\n await this._sleep(backoff(attempt, this._retryFloorMs));\n attempt++;\n }\n }\n\n /** One connection attempt: request, validate, then pump frames until it ends. */\n private async _attempt(): Promise<AttemptResult> {\n const ac = new AbortController();\n this._abort = ac;\n\n // A URL that won't build is deterministic — every retry produces the same\n // failure — so it ends the stream rather than looping.\n let target: string;\n try {\n target = this._url();\n } catch (e) {\n this._emitError({\n status: 0,\n code: \"SSE_CONNECT_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: false,\n });\n return { liveMs: 0, terminal: true };\n }\n\n // A token provider that throws is treated as transient, unlike the URL\n // above: `auth` now runs on every attempt, so a refresh endpoint having a\n // bad minute would otherwise tear down a healthy long-lived stream.\n let init: RequestInit;\n try {\n init = await this._init(ac.signal);\n } catch (e) {\n if (this._closed) return { liveMs: 0, terminal: true };\n this._emitError({\n status: 0,\n code: \"SSE_AUTH_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n return { liveMs: 0, terminal: false };\n }\n\n let res: Response;\n try {\n const doFetch = this._opts.fetch;\n res = doFetch ? await doFetch(target, init) : await fetch(target, init);\n } catch (e) {\n if (this._closed) return { liveMs: 0, terminal: true };\n this._emitError({\n status: 0,\n code: \"SSE_NETWORK_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n return { liveMs: 0, terminal: false };\n }\n\n // Redirects are refused, not followed — see `_init`. Surfaced explicitly\n // because both shapes are otherwise misleading: a browser reports an\n // opaque status-0 response, and Node hands back the raw 3xx.\n if (res.type === \"opaqueredirect\" || (res.status >= 300 && res.status < 400)) {\n this._emitError({\n status: res.status,\n code: \"SSE_REDIRECT\",\n message:\n \"The stream endpoint redirected and the SDK did not follow it. Redirects are \" +\n \"refused when the request carries a credential — an `auth` token or configured \" +\n \"`headers` — because a cross-origin hop strips the Authorization header, and this \" +\n \"endpoint answers an unauthenticated caller with a reduced view instead of an \" +\n \"error, while forwarding any configured headers to wherever the redirect points. \" +\n \"If this request carries neither, `fetch` was left to follow the redirect itself, \" +\n \"so arriving here means a response it will not follow (a 304, or a 3xx with no \" +\n \"Location header) or an `options.fetch` that returned the redirect verbatim. \" +\n \"Either way, set `baseURL` to the final URL; most often this is an http→https \" +\n \"upgrade or a canonical-host redirect at a proxy. To follow a credentialed \" +\n \"redirect anyway, supply an `options.fetch` that overrides `redirect`.\",\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n if (!res.ok) {\n const error = await parseErrorResponse(res);\n this._emitError(error);\n // 4xx is terminal: whatever rejected this usually won't be talked round\n // by repeating the request, and native EventSource likewise stops on\n // non-200. The exception is a 429/408 from a fronting rate limiter, which\n // is transient even though the stream still ends (#469). Note WaveHouse\n // doesn't reject here *for authentication* — the endpoint is ungated and\n // answers a bad token with a reduced view, which is why `auth` is re-read\n // per attempt (#239 tracks enforcing expiry). The one 4xx it raises itself\n // is a 400 on this route for a missing or empty `table`\n // (internal/api/stream.go); a 404/405 means the request missed the route\n // entirely (router.go's chi handlers), and anything else comes from\n // something in front — a gateway, or a proxy.\n return { liveMs: 0, terminal: !error.retryable };\n }\n\n // A 200 that isn't an event stream is an intermediary answering for the\n // server — an auth gateway serving its login page is the common one. Left\n // unchecked the parser would chew HTML, find no frames, and leave the\n // stream \"live\" and permanently silent, indistinguishable from a quiet\n // table. `EventSource` fails the connection here too.\n const contentType = res.headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().split(\";\")[0].trim().startsWith(\"text/event-stream\")) {\n this._emitError({\n status: res.status,\n code: \"SSE_BAD_CONTENT_TYPE\",\n message: `Expected \\`text/event-stream\\`, got \\`${contentType || \"(none)\"}\\`. Something between the client and WaveHouse answered this request — an auth gateway's login page is the usual cause.`,\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n // `options.fetch` only has to stream on this path — every REST call reads\n // the body with `.text()`. A wrapper that buffers, or clones and logs the\n // body, satisfies REST and then hangs here forever, so say so instead.\n // `locked` and `bodyUsed` both matter and neither implies the other: after\n // `.text()` both are set, but after a bare `getReader()` only `locked` is.\n const body = res.body;\n if (!body || typeof body.getReader !== \"function\" || body.locked || res.bodyUsed) {\n this._emitError({\n status: res.status,\n code: \"SSE_NO_STREAM_BODY\",\n message:\n \"Streaming requires a response with a readable body. The configured `options.fetch` \" +\n \"returned one whose body is absent or already read — an implementation that buffers \" +\n \"or consumes the response cannot be used for `.stream()` or `.liveQuery()`.\",\n retryable: false,\n });\n releaseBody(res);\n return { liveMs: 0, terminal: true };\n }\n\n this._emitStatus(\"live\");\n const openedAt = Date.now();\n await this._pump(body);\n return { liveMs: Date.now() - openedAt, terminal: false };\n }\n\n /** Resolve the stream URL. Throws on a `baseURL` that isn't a usable http(s) origin. */\n private _url(): string {\n const url = resolveURL(this._opts.baseURL, \"/v1/stream\");\n // Caught here rather than at `fetch`, which reports an unusable scheme as a\n // generic failure the loop would then retry forever. `baseURL` must be\n // absolute, so a bare host throws out of `resolveURL` above; this catches\n // the absolute-but-wrong case, `ws://` being the tempting one for streams.\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(\n `baseURL must use http or https, got \"${url.protocol}\". Streaming is plain HTTP — there is no WebSocket endpoint.`,\n );\n }\n url.searchParams.set(\"table\", this._opts.table);\n if (this._opts.since) {\n url.searchParams.set(\"since\", this._opts.since);\n }\n return url.toString();\n }\n\n /** Build the request init, minting a token for this attempt. */\n private async _init(signal: AbortSignal): Promise<RequestInit> {\n const base: Record<string, string> = { Accept: \"text/event-stream\" };\n\n // Resolved per attempt, not once per stream: a stream outlives its token,\n // and a reconnect replaying an expired JWT authenticates as no one — which\n // this endpoint answers with a silently reduced view, not an error.\n if (this._opts.auth) {\n const token = await this._opts.auth();\n if (token) {\n base.Authorization = `Bearer ${token}`;\n }\n }\n\n // Takes precedence over `since` server-side, so the initial window stays on\n // the URL and resumption rides the header once there's something to resume.\n if (this._lastEventId) {\n base[\"Last-Event-ID\"] = this._lastEventId;\n }\n\n // Anything a redirect target must not be handed: the bearer token, or the\n // configured headers that carry proxy service-token secrets.\n const credentialed =\n base.Authorization !== undefined || Object.keys(this._opts.headers ?? {}).length > 0;\n\n const init: RequestInit = {\n ...this._opts.fetchOptions,\n method: \"GET\",\n headers: mergeHeaders(base, this._opts.headers),\n signal,\n // Owned by the SDK rather than left to `fetchOptions`, unlike the REST\n // path, because on a stream these are correctness rather than preference:\n //\n // cache — as an init field, not a `Cache-Control` header. The header\n // form is not in the server's Access-Control-Allow-Headers,\n // so it would fail every cross-origin preflight.\n // redirect — followed only when this request carries no credential.\n // Platforms strip `Authorization` on a cross-origin redirect\n // (whatwg/fetch#1544) and forward other headers intact, so a\n // credentialed hop either silently downgrades the stream to\n // the default role — this endpoint answers an unauthenticated\n // caller rather than rejecting them — or delivers a configured\n // secret to whatever the redirect names. Neither is worth a\n // convenience. Without either, redirects are followed, so CDN\n // canonicalization, geo/LB indirection, and an http→https\n // upgrade all just work. The test is deliberately that\n // concrete pair and not \"is this authenticated\": cookies do\n // not appear in it. A cookie is re-derived per\n // destination rather than carried, so under the default\n // same-origin credentials mode it survives a redirect only\n // while the hop stays inside both the request's origin and\n // the cookie's Path; outside either the stream is\n // silently unauthed. See #478 and the SDK guide under\n // \"Supplying your own fetch\".\n // `manual` over `error` for the credentialed case so the\n // outcome stays inspectable: `error` rejects with a bare\n // TypeError indistinguishable from a connection failure, which\n // the loop above would retry forever against a redirect that\n // is never going to stop happening.\n cache: \"no-store\",\n redirect: credentialed ? \"manual\" : \"follow\",\n // Neutralized like the REST path does, and for a sharper reason here: a\n // body on a GET makes `fetch` throw, which reads as a network failure and\n // would be retried forever against a request that can never be built.\n body: undefined,\n };\n\n // Only browsers get `credentials`: some runtimes (Cloudflare Workers) throw\n // outright if it is present. Inside a browser an explicit value from\n // `fetchOptions` survives, which is how a cookie-authenticated origin opts\n // into `include`.\n if (!(\"window\" in globalThis)) {\n delete init.credentials;\n }\n\n return init;\n }\n\n /** Decode and parse frames until the stream ends, aborts, or errors. */\n private async _pump(body: ReadableStream<Uint8Array>): Promise<void> {\n // Set when the parser reports a buffer overflow: it is terminated at that\n // point, so the read loop must stop rather than feed it again.\n let overflowed = false;\n const parser = createParser({\n onEvent: (msg) => {\n if (msg.id) this._lastEventId = msg.id;\n if (!msg.data) return;\n this._dispatch(msg.data);\n },\n onRetry: (ms) => {\n this._retryFloorMs = Math.min(Math.max(ms, 0), MAX_BACKOFF_MS);\n },\n onError: (err) => {\n if (err.type === \"max-buffer-size-exceeded\") overflowed = true;\n this._emitError({\n status: 0,\n code: \"SSE_PARSE_ERROR\",\n message: err.message,\n retryable: true,\n });\n },\n maxBufferSize: MAX_BUFFER_CHARS,\n });\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n parser.feed(decoder.decode(value, { stream: true }));\n // End the connection on the overflow we just reported, instead of\n // feeding a terminated parser and re-reporting it as a read failure.\n if (overflowed) return;\n }\n } catch (e) {\n if (this._closed) return;\n this._emitError({\n status: 0,\n code: \"SSE_READ_ERROR\",\n message: e instanceof Error ? e.message : String(e),\n retryable: true,\n });\n } finally {\n reader.cancel().catch(() => {\n // Already torn down — nothing left to release.\n });\n }\n }\n\n /**\n * Turn one frame's `data` into a StreamEvent.\n *\n * Guarded like `_emitError` and `_emitStatus`: `parser.feed()` dispatches\n * every complete frame in a chunk synchronously, so a subscriber that closes\n * from inside its own `next` — \"read until I see my event, then stop\" —\n * would otherwise keep receiving the rest of that chunk. `EventSource` ended\n * delivery on `close()`, and gap-fill replay routinely lands many frames in\n * one chunk.\n */\n private _dispatch(data: string): void {\n if (this._closed) return;\n\n // Two failures with one culprit each, so they are caught separately: a\n // shared catch blamed the server's frame for an exception thrown by the\n // consumer's own handler, pointing a debugging reader at the wrong side.\n let event: StreamEvent<T>;\n try {\n const msg = JSON.parse(data) as {\n table_name: string;\n received_timestamp: string;\n data: T;\n };\n event = { table: msg.table_name, timestamp: msg.received_timestamp, data: msg.data };\n } catch {\n console.warn(\"[wavehouse] SSE received malformed message:\", data);\n return; // ignore malformed messages\n }\n\n try {\n this.onEvent?.(event);\n } catch (e) {\n console.error(\"[wavehouse] SSE event handler threw:\", e);\n }\n }\n\n /** Suppress callbacks that race a `disconnect()`. */\n private _emitError(error: WaveHouseError): void {\n if (this._closed) return;\n // Guarded like `_dispatch`: a subscriber that throws must not take the\n // transport with it. Unguarded, the throw unwinds `_run` into `connect()`'s\n // terminal handler, which reports a spurious SSE_CONNECT_ERROR and then\n // calls `disconnect()` — whose own `onStatus(\"closed\")` throws again, this\n // time out of the `.catch()`, as a process-fatal unhandled rejection.\n // `EventSource` isolated a throwing handler and kept streaming. Note this\n // protects the transport, not the fan-out: StreamController still stops\n // delivering to later subscribers when an earlier one throws (#473).\n try {\n this.onError?.(error);\n } catch (e) {\n console.error(\"[wavehouse] SSE error handler threw:\", e);\n }\n }\n\n /**\n * Same guard for status. Aborting does not retract an already-resolved\n * `Response`, so a `disconnect()` landing between the fetch settling and the\n * \"live\" below would otherwise flip a closed controller back to live — and it\n * stays there, since the loop then exits without emitting anything further.\n * `disconnect()` calls `onStatus` directly; that terminal \"closed\" is the one\n * status that must always get through.\n */\n private _emitStatus(status: StreamStatus): void {\n if (this._closed) return;\n try {\n this.onStatus?.(status);\n } catch (e) {\n console.error(\"[wavehouse] SSE status handler threw:\", e);\n }\n }\n\n /** Wait out a reconnect gap, cut short by `disconnect()`. */\n private _sleep(ms: number): Promise<void> {\n // `_wake` only cuts a gap short once the timer exists. A `disconnect()`\n // raised synchronously from the \"reconnecting\" status callback runs a line\n // earlier than that, so without this the loop would install a timer nobody\n // can cancel and hold the event loop open for the whole backoff — the one\n // hole in the `_wake` machinery.\n if (this._closed) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this._wake = null;\n resolve();\n }, ms);\n this._wake = () => {\n clearTimeout(timer);\n this._wake = null;\n resolve();\n };\n });\n }\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { HttpContext, Result } from \"./types.js\";\n\n/** Namespace for the SDK's content-free server-online check. */\nexport class SysNamespace {\n private readonly _ctx: HttpContext;\n\n constructor(ctx: HttpContext) {\n this._ctx = ctx;\n }\n\n /**\n * Liveness ping — resolves with no error when the server is reachable and\n * past boot. Hits the public, content-free `/v1/health` route (200/503, no\n * body), kept intentionally distinct from the `/livez` Kubernetes probe so\n * it stays reachable even in deployments that filter probe paths at the\n * reverse proxy. Use it to check a server is online before sending data, or\n * to pick among servers in a distributed setup.\n */\n async health(opts?: { signal?: AbortSignal }): Promise<Result<void>> {\n const { error } = await request<void>(this._ctx, {\n method: \"GET\",\n path: \"/v1/health\",\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok<void>(undefined);\n }\n}\n","import type { QueryFilter, Result, StreamEvent, StreamSubscriber } from \"../types.js\";\nimport type { StreamController } from \"./controller.js\";\n\n/**\n * Stream-first live query orchestrator.\n *\n * 1. Opens a live stream immediately (buffers events).\n * 2. Fetches historical data.\n * 3. Calls `subscriber.initial(result)` with the historical snapshot.\n * 4. Deduplicates buffered events against the fetch result.\n * 5. Flushes remaining buffered events through `subscriber.next()`.\n * 6. Resumes live: pipes stream events directly to `subscriber.next()`.\n */\nexport class LiveQuery<T = Record<string, unknown>> {\n private _stream: StreamController<T>;\n private _subscriber: StreamSubscriber<T>;\n private _buffer: StreamEvent<T>[] = [];\n private _buffering = true;\n private _unsubStream: (() => void) | null = null;\n private _closed = false;\n\n constructor(\n stream: StreamController<T>,\n fetchFn: () => Promise<Result<T[]>>,\n subscriber: StreamSubscriber<T>,\n _filters: QueryFilter[],\n ) {\n this._stream = stream;\n this._subscriber = subscriber;\n\n // Step 1: Subscribe to live events and buffer them.\n this._unsubStream = stream.subscribe({\n next: (event) => {\n if (this._closed) return;\n if (this._buffering) {\n this._buffer.push(event);\n } else {\n subscriber.next(event);\n }\n },\n status: (s) => subscriber.status?.(s),\n error: (e) => subscriber.error?.(e),\n });\n\n // Step 2-5: Fetch historical and flush.\n this._runBackfill(fetchFn);\n }\n\n private async _runBackfill(fetchFn: () => Promise<Result<T[]>>): Promise<void> {\n try {\n const result = await fetchFn();\n\n if (this._closed) return;\n\n // Step 3: Call initial() with the historical snapshot.\n this._subscriber.initial?.(result);\n\n if (result.error) {\n this._buffering = false;\n return;\n }\n\n // Step 4: Deduplicate buffered events against fetched rows.\n // The boundary is the `received_timestamp` of the *last row returned* —\n // not the newest, unless the query happens to order ascending. The ways\n // that misbehaves (and there are three) are enumerated once, in the SDK\n // docs under Live Queries → How it works; #449 tracks the fix. Stated\n // there rather than restated here, because this claim has already drifted\n // out of sync three times across the docs and this comment.\n const rows = result.data;\n let lastTimestamp: string | undefined;\n if (rows.length > 0) {\n const lastRow = rows[rows.length - 1] as Record<string, unknown>;\n lastTimestamp = lastRow?.received_timestamp as string | undefined;\n }\n\n // Step 5: Flush buffered events newer than that boundary.\n this._buffering = false;\n for (const event of this._buffer) {\n if (this._closed) break;\n if (lastTimestamp && event.timestamp <= lastTimestamp) {\n continue; // already covered by the fetch\n }\n this._subscriber.next(event);\n }\n this._buffer = [];\n } catch {\n // Fetch failed — stop buffering and resume live only.\n this._buffering = false;\n this._buffer = [];\n }\n }\n\n /** Close the live query and the underlying stream. */\n close(): void {\n this._closed = true;\n this._buffering = false;\n this._buffer = [];\n this._unsubStream?.();\n this._stream.close();\n }\n}\n","import { err, okPage } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport type { StreamTransport } from \"./stream/controller.js\";\nimport { StreamController } from \"./stream/controller.js\";\nimport { LiveQuery } from \"./stream/live-query.js\";\nimport type {\n Aggregation,\n FilterOp,\n HttpContext,\n OrderClause,\n QueryFilter,\n RequestOptions,\n Result,\n StreamOptions,\n StreamSubscriber,\n StructuredQuery,\n TimeRange,\n} from \"./types.js\";\n\n/** SDK operator → backend operator mapping. */\nconst OP_MAP: Record<FilterOp, string> = {\n \"=\": \"eq\",\n \"!=\": \"neq\",\n \">\": \"gt\",\n \">=\": \"gte\",\n \"<\": \"lt\",\n \"<=\": \"lte\",\n in: \"in\",\n like: \"like\",\n not_like: \"not_like\",\n};\n\ninterface QueryState {\n table: string;\n columns: string[];\n selectAll?: boolean;\n aggregations: Aggregation[];\n filters: QueryFilter[];\n groupBy: string[];\n orderBy: OrderClause[];\n limit?: number;\n timeRange?: TimeRange;\n cacheTTL?: number;\n}\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/**\n * Immutable, PromiseLike query builder.\n * Every chain method returns a new instance. `await builder` auto-executes `.fetch()`.\n */\nexport class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike<Result<Row[]>> {\n /** @internal */\n private readonly _state: Readonly<QueryState>;\n /** @internal */\n private readonly _ctx: HttpContext;\n /** @internal */\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(ctx: HttpContext, state: QueryState, createStream: CreateStreamFn<Row>) {\n this._ctx = ctx;\n this._state = Object.freeze({ ...state });\n this._createStream = createStream;\n }\n\n // --- Builder methods (each returns a new QueryBuilder) ---\n\n select(...columns: string[]): QueryBuilder<Row> {\n return this._clone({ columns: [...this._state.columns, ...columns] });\n }\n\n /**\n * Select every column the caller's role is allowed to read (the all-columns\n * wildcard). Use this instead of `.select(...)` when you want a full-row read;\n * a bare `.fetch()` with no `.select()` does this implicitly. Mutually\n * exclusive with `.select(...)`.\n */\n selectAll(): QueryBuilder<Row> {\n return this._clone({ selectAll: true });\n }\n\n where(column: string, op: FilterOp, value: unknown): QueryBuilder<Row> {\n const filter: QueryFilter = { column, op: OP_MAP[op], value };\n return this._clone({ filters: [...this._state.filters, filter] });\n }\n\n count(column = \"*\", alias = \"count\"): QueryBuilder<Row> {\n return this._addAgg(\"count\", column, alias);\n }\n\n sum(column: string, alias = `sum_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"sum\", column, alias);\n }\n\n avg(column: string, alias = `avg_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"avg\", column, alias);\n }\n\n min(column: string, alias = `min_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"min\", column, alias);\n }\n\n max(column: string, alias = `max_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"max\", column, alias);\n }\n\n countDistinct(column: string, alias = `count_distinct_${column}`): QueryBuilder<Row> {\n return this._addAgg(\"countDistinct\", column, alias);\n }\n\n aggregate(fn: string, column: string, alias: string): QueryBuilder<Row> {\n return this._addAgg(fn, column, alias);\n }\n\n groupBy(...columns: string[]): QueryBuilder<Row> {\n return this._clone({ groupBy: [...this._state.groupBy, ...columns] });\n }\n\n orderBy(column: string, dir: \"asc\" | \"desc\" = \"asc\"): QueryBuilder<Row> {\n return this._clone({ orderBy: [...this._state.orderBy, { column, dir }] });\n }\n\n limit(n: number): QueryBuilder<Row> {\n return this._clone({ limit: n });\n }\n\n timeRange(column: string, since: string, until?: string): QueryBuilder<Row> {\n return this._clone({ timeRange: { column, since, until } });\n }\n\n cacheTTL(seconds: number): QueryBuilder<Row> {\n return this._clone({ cacheTTL: seconds });\n }\n\n // --- Execution ---\n\n /** Default row limit when none is specified — deliberately tighter than the backend's DefaultMaxRows (10000) safety cap. */\n static readonly DEFAULT_LIMIT = 1000;\n\n async fetch(opts?: RequestOptions): Promise<Result<Row[]>> {\n const effectiveLimit = opts?.limit ?? this._state.limit ?? QueryBuilder.DEFAULT_LIMIT;\n const ast = this._buildAST(effectiveLimit);\n\n const { data, error } = await request<Row[]>(this._ctx, {\n method: \"POST\",\n path: `/v1/query?table=${encodeURIComponent(this._state.table)}`,\n body: ast,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n\n const rows = data!;\n const hasMore = effectiveLimit != null && rows.length >= effectiveLimit;\n\n // next() needs an order column for its cursor; with no .orderBy() we still\n // report hasMore honestly from the row count but can't offer a next().\n if (hasMore && this._state.orderBy.length > 0) {\n const nextFn = () => this._fetchNext(rows, effectiveLimit!, opts);\n return okPage(rows, true, nextFn);\n }\n\n return okPage(rows, hasMore);\n }\n\n stream(opts?: StreamOptions): StreamController<Row> {\n const raw = this._createStream(this._state.table, opts);\n const filters = this._state.filters;\n const columns = this._state.columns;\n\n // If no filters or column projection, return the raw stream.\n if (filters.length === 0 && columns.length === 0) {\n return raw;\n }\n\n // Wrap with a filtering transport that applies AST predicates client-side.\n return new FilteredStreamController<Row>(raw, filters, columns);\n }\n\n /**\n * Start a live query: fetches historical data, then streams live updates.\n *\n * The subscriber's `initial()` is called once with the fetch result, then\n * `next()` fires for each live event. Events that arrived during the fetch\n * are deduplicated and flushed automatically.\n *\n * Returns a LiveQuery handle with a `.close()` method.\n */\n liveQuery(subscriber: StreamSubscriber<Row>, opts?: StreamOptions): LiveQuery<Row> {\n const stream = this.stream(opts);\n const fetchFn = () => this.fetch();\n return new LiveQuery<Row>(stream, fetchFn, subscriber, this._state.filters);\n }\n\n // --- PromiseLike implementation ---\n\n then<TResult1 = Result<Row[]>, TResult2 = never>(\n onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.fetch().then(onfulfilled, onrejected);\n }\n\n // --- Private helpers ---\n\n private _clone(overrides: Partial<QueryState>): QueryBuilder<Row> {\n return new QueryBuilder(this._ctx, { ...this._state, ...overrides }, this._createStream);\n }\n\n private _addAgg(fn: string, column: string, alias: string): QueryBuilder<Row> {\n const agg: Aggregation = { fn, column, alias };\n return this._clone({ aggregations: [...this._state.aggregations, agg] });\n }\n\n private _buildAST(effectiveLimit?: number): StructuredQuery {\n const ast: StructuredQuery = {};\n const hasColumns = this._state.columns.length > 0;\n const hasAggs = this._state.aggregations.length > 0;\n // Projection: an explicit select_all, then explicit columns, else — for a\n // bare query with no projection intent and no aggregations — default to\n // select_all so `from(t).fetch()` returns rows. (The wire default for omitted\n // columns is \"nothing\"; the SDK opts into select_all on the caller's behalf.)\n if (this._state.selectAll) {\n ast.select_all = true;\n } else if (hasColumns) {\n ast.columns = this._state.columns;\n } else if (!hasAggs) {\n ast.select_all = true;\n }\n if (hasAggs) ast.aggregations = this._state.aggregations;\n if (this._state.filters.length > 0) ast.filters = this._state.filters;\n if (this._state.groupBy.length > 0) ast.group_by = this._state.groupBy;\n if (this._state.orderBy.length > 0) ast.order_by = this._state.orderBy;\n if (effectiveLimit != null) ast.limit = effectiveLimit;\n if (this._state.timeRange) ast.time_range = this._state.timeRange;\n return ast;\n }\n\n private async _fetchNext(\n prevRows: Row[],\n _limit: number,\n opts?: RequestOptions,\n ): Promise<Result<Row[]>> {\n // No explicit order → no keyset cursor to build; nothing to page by.\n const cursor = this._state.orderBy[0];\n if (cursor == null) return okPage([] as unknown as Row[], false);\n const { column: orderCol, dir: orderDir } = cursor;\n\n const lastRow = prevRows[prevRows.length - 1] as Record<string, unknown>;\n const lastValue = lastRow?.[orderCol];\n\n if (lastValue === undefined) return okPage([] as unknown as Row[], false);\n\n const cursorOp = orderDir === \"desc\" ? \"lt\" : \"gt\";\n const cursorFilter: QueryFilter = { column: orderCol, op: cursorOp, value: lastValue };\n const nextBuilder = this._clone({\n filters: [...this._state.filters, cursorFilter],\n orderBy: this._state.orderBy,\n });\n\n return nextBuilder.fetch(opts);\n }\n}\n\n// ============================================================================\n// Client-side stream filtering\n// ============================================================================\n\n/**\n * Wraps a StreamController, applying client-side filters and column projection\n * before delivering events to subscribers.\n *\n * Lifecycle: by the time we reach this constructor, `inner` has already been\n * built (and is opening its own SSE connection). The outer transport's\n * `connect()` doesn't open a new connection — it bridges `inner`'s events\n * through a filter into the outer subscriber set. On `disconnect()`, closing\n * `inner` is what actually tears down the SSE socket; the subscription we\n * register on `inner` is left to be garbage-collected with it, which is why\n * we don't bother saving the unsubscribe handle.\n * @internal\n */\nclass FilteredStreamController<T = Record<string, unknown>> extends StreamController<T> {\n constructor(inner: StreamController<T>, filters: QueryFilter[], columns: string[]) {\n const transport: StreamTransport<T> = {\n onEvent: null,\n onStatus: null,\n onError: null,\n connect() {\n inner.subscribe({\n next: (event) => {\n if (!matchesFilters(event.data as Record<string, unknown>, filters)) {\n return;\n }\n const projected =\n columns.length > 0\n ? {\n ...event,\n data: projectColumns(event.data as Record<string, unknown>, columns) as T,\n }\n : event;\n this.onEvent?.(projected);\n },\n status: (s) => this.onStatus?.(s),\n error: (e) => this.onError?.(e),\n });\n },\n disconnect() {\n inner.close();\n },\n };\n super(transport);\n }\n}\n\n/** @internal Evaluate all filters against a data row. All must pass (AND). */\nfunction matchesFilters(row: Record<string, unknown>, filters: QueryFilter[]): boolean {\n for (const f of filters) {\n const val = row[f.column];\n if (!evaluateFilter(val, f.op, f.value)) return false;\n }\n return true;\n}\n\n/** @internal Evaluate a single filter predicate. */\nfunction evaluateFilter(actual: unknown, op: string, expected: unknown): boolean {\n switch (op) {\n case \"eq\":\n return actual === expected;\n case \"neq\":\n return actual !== expected;\n case \"gt\":\n return compareOrdered(actual, expected, (a, b) => a > b);\n case \"gte\":\n return compareOrdered(actual, expected, (a, b) => a >= b);\n case \"lt\":\n return compareOrdered(actual, expected, (a, b) => a < b);\n case \"lte\":\n return compareOrdered(actual, expected, (a, b) => a <= b);\n case \"in\":\n return Array.isArray(expected) && expected.includes(actual);\n case \"like\": {\n if (typeof actual !== \"string\" || typeof expected !== \"string\") return false;\n // Convert SQL LIKE pattern to regex: % → .*, _ → .\n const escaped = expected.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = escaped.replace(/%/g, \".*\").replace(/_/g, \".\");\n return new RegExp(`^${pattern}$`, \"i\").test(actual);\n }\n case \"not_like\": {\n if (typeof actual !== \"string\" || typeof expected !== \"string\") return false;\n const escaped = expected.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = escaped.replace(/%/g, \".*\").replace(/_/g, \".\");\n return !new RegExp(`^${pattern}$`, \"i\").test(actual);\n }\n default:\n return true; // unknown op — pass through\n }\n}\n\n/**\n * @internal Apply an ordered comparison only when both sides are the same\n * comparable primitive (number-vs-number or string-vs-string — strings are\n * lexicographic, which is correct for ISO-8601 timestamps). Mismatched or\n * unsupported types return false instead of relying on JS coercion.\n */\nfunction compareOrdered(\n actual: unknown,\n expected: unknown,\n cmp: (a: number, b: number) => boolean,\n): boolean {\n if (typeof actual === \"number\" && typeof expected === \"number\") {\n return cmp(actual, expected);\n }\n if (typeof actual === \"string\" && typeof expected === \"string\") {\n // `>`/`<` on strings is lexicographic; reuse the same comparator by\n // casting through `as unknown as number` — the runtime operator works\n // identically on strings.\n return cmp(actual as unknown as number, expected as unknown as number);\n }\n return false;\n}\n\n/** @internal Project a row to only the specified columns. */\nfunction projectColumns(row: Record<string, unknown>, columns: string[]): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const col of columns) {\n if (col in row) result[col] = row[col];\n }\n return result;\n}\n","import { err, ok } from \"./errors.js\";\nimport { request } from \"./http.js\";\nimport { QueryBuilder } from \"./query-builder.js\";\nimport type { StreamController } from \"./stream/controller.js\";\nimport type {\n HttpContext,\n InsertRecordResult,\n InsertResult,\n RequestOptions,\n Result,\n StreamOptions,\n TableSchema,\n} from \"./types.js\";\n\ntype CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;\n\n/** Content-Type for the NDJSON batch ingest path. */\nconst NDJSON_CONTENT_TYPE = \"application/x-ndjson\";\n\n/**\n * Accepted sources for {@link TableRef.insertNDJSON}: a string, raw bytes, a\n * Blob/File (browser file inputs), or a byte stream. Non-string sources are\n * read fully before sending.\n */\nexport type NDJSONSource = string | Uint8Array | Blob | ReadableStream<Uint8Array>;\n\n/** Wire shape of the server's batch ingest response (JSON array or NDJSON). */\ninterface BatchIngestResponse {\n total: number;\n succeeded: number;\n failed: number;\n duplicates: number;\n results?: InsertRecordResult[];\n}\n\n/** Read an {@link NDJSONSource} fully into a UTF-8 string. */\nasync function ndjsonSourceToString(source: NDJSONSource): Promise<string> {\n if (typeof source === \"string\") return source;\n if (source instanceof Uint8Array) return new TextDecoder().decode(source);\n // Blob/File and ReadableStream<Uint8Array> are both valid fetch body inits,\n // so let Response drain them to text.\n return new Response(source).text();\n}\n\n/**\n * Reference to a table. NOT thenable — safe to pass around without triggering requests.\n * Use `.fetch()`, `.select()`, `.insert()`, `.schema()`, or `.stream()` to act on it.\n */\nexport class TableRef<Row = Record<string, unknown>> {\n private readonly _ctx: HttpContext;\n private readonly _table: string;\n private readonly _createStream: CreateStreamFn<Row>;\n\n constructor(ctx: HttpContext, table: string, createStream: CreateStreamFn<Row>) {\n this._ctx = ctx;\n this._table = table;\n this._createStream = createStream;\n }\n\n /** SELECT * shortcut — fetches rows with optional pagination. */\n async fetch(opts?: RequestOptions): Promise<Result<Row[]>> {\n return this.select()\n .limit(opts?.limit ?? 1000)\n .fetch(opts);\n }\n\n /** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */\n select(...columns: string[]): QueryBuilder<Row> {\n return new QueryBuilder<Row>(\n this._ctx,\n {\n table: this._table,\n columns,\n aggregations: [],\n filters: [],\n groupBy: [],\n orderBy: [],\n },\n this._createStream,\n );\n }\n\n /**\n * Start a query that selects every column the caller's role is allowed to read\n * (the all-columns wildcard). Use instead of `.select(...)` for an explicit\n * full-row read; `.fetch()` does this implicitly.\n */\n selectAll(): QueryBuilder<Row> {\n return this.select().selectAll();\n }\n\n /**\n * Insert one or more rows into this table.\n *\n * A single object is sent as a JSON `POST /v1/ingest`. An **array** is\n * serialized to NDJSON (one record per line) and sent as a single\n * `application/x-ndjson` request: a bad record no longer fails or hides the\n * rest of the batch — per-record outcomes come back in the result\n * (`failed` / `results`), and `ok` is true only when every record succeeded.\n *\n * The array path sends one request regardless of size; bounded-concurrency\n * chunking of very large arrays is tracked separately (#196). For NDJSON you\n * already have (a file or stream), use {@link insertNDJSON}.\n */\n async insert(\n data: Partial<Row> | Partial<Row>[],\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n if (Array.isArray(data)) {\n if (data.length === 0) {\n // Nothing to send — succeed without a round trip.\n return ok({ ok: true, total: 0, succeeded: 0, failed: 0, duplicates: 0, results: [] });\n }\n const ndjson = data.map((row) => JSON.stringify(row)).join(\"\\n\");\n return this._sendNDJSON(ndjson, opts);\n }\n\n const { data: res, error } = await request<{ ok?: boolean; duplicate?: boolean }>(this._ctx, {\n method: \"POST\",\n path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,\n body: data,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n const result: InsertResult = { ok: res?.ok ?? true };\n if (res?.duplicate != null) result.duplicate = res.duplicate;\n return ok(result);\n }\n\n /**\n * Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)\n * from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`\n * file or a stream produced by another system. For in-memory rows, prefer\n * {@link insert}.\n *\n * Non-string sources are read fully into memory before sending (the server\n * streams the parse). Returns the same per-record batch summary as an array\n * `insert`.\n */\n async insertNDJSON(\n source: NDJSONSource,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n const ndjson = await ndjsonSourceToString(source);\n return this._sendNDJSON(ndjson, opts);\n }\n\n /**\n * @internal Send an NDJSON body and map the server's batch response onto an\n * {@link InsertResult}. A transport / whole-request failure surfaces as the\n * Result error arm; a processed batch (even with rejected records) surfaces\n * as the success arm with `ok = failed === 0`.\n */\n private async _sendNDJSON(\n ndjson: string,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<InsertResult>> {\n const { data, error } = await request<BatchIngestResponse>(this._ctx, {\n method: \"POST\",\n path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,\n rawBody: ndjson,\n contentType: NDJSON_CONTENT_TYPE,\n signal: opts?.signal,\n });\n\n if (error) return err(error);\n // A 200 with no/empty body (e.g. a record-stripping intermediary) must not\n // throw — mirror the single-object path's optimistic `?? true` and fall back\n // to a zeroed summary so the \"never throws\" Result contract holds.\n const r = data ?? { total: 0, succeeded: 0, failed: 0, duplicates: 0 };\n const result: InsertResult = {\n ok: r.failed === 0,\n total: r.total,\n succeeded: r.succeeded,\n failed: r.failed,\n duplicates: r.duplicates,\n };\n if (r.results && r.results.length > 0) result.results = r.results;\n return ok(result);\n }\n\n /** Fetch the schema for this table. */\n async schema(opts?: { signal?: AbortSignal }): Promise<Result<TableSchema>> {\n const { data, error } = await request<TableSchema>(this._ctx, {\n method: \"GET\",\n path: `/v1/ops/schema?table=${encodeURIComponent(this._table)}`,\n signal: opts?.signal,\n });\n if (error) return err(error);\n return ok(data!);\n }\n\n /** Subscribe to live events for this table. */\n stream(opts?: StreamOptions): StreamController<Row> {\n return this._createStream(this._table, opts);\n }\n}\n","import { DLQNamespace } from \"./dlq.js\";\nimport { PipeRef, PipesNamespace } from \"./pipes.js\";\nimport { SchemaNamespace } from \"./schema.js\";\nimport { SettingsNamespace } from \"./settings.js\";\nimport { sql } from \"./sql.js\";\nimport { StreamController } from \"./stream/controller.js\";\nimport { SSETransport } from \"./stream/sse.js\";\nimport { SysNamespace } from \"./sys.js\";\nimport { TableRef } from \"./table.js\";\nimport type { ClientConfig, Database, HttpContext, Result, StreamOptions } from \"./types.js\";\n\ntype TableName<DB> = DB extends Database ? Extract<keyof DB, string> : string;\ntype RowType<DB, T extends string> = DB extends Database\n ? T extends keyof DB\n ? DB[T]\n : Record<string, unknown>\n : Record<string, unknown>;\n\nexport class WaveHouseClient<DB extends Database = Database> {\n /** @internal */\n readonly _ctx: HttpContext;\n\n /** Schema introspection namespace. */\n readonly schema: SchemaNamespace;\n /** Dead Letter Queue namespace. */\n readonly dlq: DLQNamespace;\n /** System health/readiness namespace. */\n readonly sys: SysNamespace;\n /** Named query pipes admin namespace. */\n readonly pipes: PipesNamespace;\n /** Settings directory namespace (admin). */\n readonly settings: SettingsNamespace;\n\n constructor(config: ClientConfig<DB>) {\n this._ctx = {\n baseURL: config.baseURL.replace(/\\/+$/, \"\"),\n auth: config.auth,\n options: {\n maxRetries: config.options?.maxRetries ?? 2,\n fetch: config.options?.fetch,\n headers: config.options?.headers,\n fetchOptions: config.options?.fetchOptions,\n },\n };\n\n this.schema = new SchemaNamespace(this._ctx);\n this.dlq = new DLQNamespace(this._ctx, (table, opts) => this._createStream(table, opts));\n this.sys = new SysNamespace(this._ctx);\n this.pipes = new PipesNamespace(this._ctx);\n this.settings = new SettingsNamespace(this._ctx);\n }\n\n /** Get a table reference for building queries, inserts, and streams. */\n from<T extends TableName<DB>>(table: T): TableRef<RowType<DB, T>> {\n return new TableRef<RowType<DB, T>>(this._ctx, table, (t, opts) =>\n this._createStream<RowType<DB, T>>(t, opts),\n );\n }\n\n /** Get a reference to a named query pipe. PromiseLike — `await` it to execute. */\n pipe<Row = Record<string, unknown>>(\n name: string,\n params?: Record<string, unknown>,\n ): PipeRef<Row> {\n return new PipeRef<Row>(this._ctx, name, params, (t, opts) => this._createStream<Row>(t, opts));\n }\n\n /**\n * Execute a raw SQL query against ClickHouse. Requires the admin role (the\n * configured `admin_role`, `\"admin\"` by default; there is no separate\n * `service` role). The endpoint proxies straight to ClickHouse's HTTP\n * interface so any ClickHouse-accepted SQL works; positional `?` param\n * binding is NOT supported — inline literals or use the structured query\n * builder for safe binding. See sql.ts for details.\n */\n sql<Row = Record<string, unknown>>(\n query: string,\n opts?: { signal?: AbortSignal },\n ): Promise<Result<Row[]>> {\n // Migration guard: the second argument used to be a positional-`?`\n // params array. TS callers get a compile-time error from the type\n // signature, but JS callers (or `any`-typed callsites) would silently\n // pass an array as `opts` and only discover the break via downstream\n // SQL errors. Throw a clear runtime error pointing at the migration.\n if (Array.isArray(opts)) {\n throw new Error(\n \"[WaveHouse SDK] client.sql(sql, params) was removed. The /v1/ops/query endpoint does not accept positional `?` params. Inline literals into the SQL, or use the structured query builder (wh.from(table)…) for safe binding from user input.\",\n );\n }\n return sql<Row>(this._ctx, query, opts);\n }\n\n /** @internal Create a stream for the given table. */\n private _createStream<T = Record<string, unknown>>(\n table: string,\n opts?: StreamOptions,\n ): StreamController<T> {\n const transport = new SSETransport<T>({\n baseURL: this._ctx.baseURL,\n table,\n since: opts?.since,\n auth: this._ctx.auth,\n fetch: this._ctx.options.fetch,\n headers: this._ctx.options.headers,\n fetchOptions: this._ctx.options.fetchOptions,\n });\n const controller = new StreamController<T>(transport);\n if (opts?.signal) controller.attachSignal(opts.signal);\n return controller;\n }\n}\n\n/** Create a new WaveHouse client instance. */\nexport function createClient<DB extends Database = Database>(\n config: ClientConfig<DB>,\n): WaveHouseClient<DB> {\n return new WaveHouseClient(config);\n}\n"],"mappings":"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","names":["backoff","err"]}
|