Skip to content

[absorbed into #630 Phase 4] add consuming query APIs, ClickHouse errors, and stateless KILL QUERY #634

Description

@BorisTyshkevich

Part of #630.

Depends on: #633

Goal

Add explicit higher-level APIs to @altinity/clickhouse-http for callers that deliberately choose to consume/interpret a native Response, while preserving the low-level request() contract unchanged.

This unit also moves generic non-2xx ClickHouse error interpretation and provides a stateless server-side KILL QUERY operation. SQL Browser authentication policy remains outside the package until #636.

API layering

The distinction between low-level and consuming APIs is part of the public contract.

Level 1 — request()

Unchanged from #632:

const response = await client.request(request);
  • returns the exact native Response;
  • resolves non-2xx;
  • never consumes body;
  • never parses errors;
  • never retries.

Level 2 — non-consuming success classification

Expose one classifier for callers such as raw export that need ClickHouse HTTP error handling without consuming a successful response body:

export function ensureClickHouseSuccess(response: Response): Promise<Response>;

Semantics:

  • when response.ok is true, resolve with the same Response object by identity;
  • the successful response body remains untouched and bodyUsed stays false;
  • when response.ok is false, consume the response text exactly once, parse its ClickHouse exception text, and throw ClickHouseError;
  • do not clone or replace a successful response;
  • this function does not see network errors because it operates on an already-resolved Response.

This is the common status/error primitive for all package response consumers and for SQL Browser's raw export path in #637.

Level 3 — response consumers

Expose consumers that can be applied to a Response obtained through any policy layer, including SQL Browser's authenticated adapter in #636:

export function consumeJsonResponse<T>(response: Response): Promise<T>;
export function consumeTextResponse(response: Response): Promise<string>;
export function consumeProgressResponse(
  response: Response,
  callbacks?: ProgressStreamCallbacks,
): Promise<Response>;

Semantics:

  • each consumer first calls ensureClickHouseSuccess(response);
  • on non-2xx, the shared classifier consumes the response text once and throws ClickHouseError;
  • on success, JSON consumes response.json(), text consumes response.text(), progress consumes response.body with [absorbed into #630 Phase 3] extract progress-stream and late-exception protocol primitives #633's readProgressStream;
  • consumeProgressResponse returns the same Response object after successful stream completion so status/headers remain available, with bodyUsed === true as an explicit consequence of choosing a consuming API;
  • if the successful body/reader rejects, propagate that rejection unchanged;
  • no consumer turns AbortError or a body/network TypeError into ClickHouseError.

Level 4 — convenience query methods

Compose request() plus the consumers:

client.queryJson<T>(request): Promise<T>
client.queryText(request): Promise<string>
client.queryProgress(request, callbacks?): Promise<Response>

queryJson may default defaultFormat to JSON only when the caller omits it. queryText and queryProgress require an explicit output format; the package must not know SQL Browser logical modes such as Table, KPI, or TSV aliases.

All request fields continue to be explicit caller inputs: settings, params, authorization, and signal.

ClickHouseError

Add one minimal package error type for a non-success HTTP response consumed/classified by a higher-level API:

export class ClickHouseError extends Error {
  readonly status: number;
  readonly responseText: string;
}

Required behavior:

  • message is parseExceptionText(responseText) from [absorbed into #630 Phase 3] extract progress-stream and late-exception protocol primitives #633;
  • status is the HTTP status;
  • responseText preserves the exact consumed text for diagnostics;
  • name is stable (ClickHouseError);
  • do not invent a second parser for exception code/type unless the existing repository already has a proven generic parser that can be moved without semantic expansion;
  • no wrapping of abort/network/body-consumption errors.

If a successful 2xx response contains a streamed { exception: ... } event, queryProgress must preserve the current protocol behavior by delivering that line through callbacks; SQL Browser decides how that event affects its result accumulator. Do not silently convert current in-band stream semantics into an early throw in this unit.

Stateless KILL QUERY

Provide a package operation equivalent to:

client.killQuery({
  queryId,
  authorization,
  signal?,
  settings?,
  params?,
}): Promise<void>

Contract:

  • constructs KILL QUERY WHERE query_id = <quoted id> ASYNC safely;
  • performs one request through this client's normal low-level machinery;
  • treats non-2xx as ClickHouseError using ensureClickHouseSuccess;
  • returns only after the HTTP response has been classified/consumed as needed for a no-result command;
  • does not swallow failure;
  • does not retry;
  • does not look up credentials;
  • does not track active query IDs;
  • does not decide when remote cancellation is appropriate.

A narrow private quoting helper inside the package is acceptable for this operation in this unit; #635 makes the generic SQL-quoting API public and reconciles it with SQL Browser's existing sqlString() implementation. Do not import SQL Browser formatting code into the package.

SQL Browser compatibility integration

This unit may add compatibility delegates in src/net/ch-client.ts or the existing transport layer so tests can exercise the new consumers, but it must not yet rewrite authedFetch() or move auth/epoch logic. #636 owns that cut.

Useful low-risk adoption in this unit is allowed where no policy changes:

  • current generic non-2xx parsing helpers may delegate to package consumers/parsers;
  • current stream adapter may delegate to consumeProgressResponse only where the response has already been classified exactly as today.

Do not force every product operation to use the convenience methods before the authenticated adapter exists.

Tests

Error classification

  • successful ensureClickHouseSuccess returns the exact same Response and leaves bodyUsed === false;
  • non-2xx ensureClickHouseSuccess throws ClickHouseError after one error-body read;
  • 2xx JSON returns parsed JSON;
  • 2xx text returns exact text;
  • non-2xx JSON/text/progress path throws ClickHouseError through the same classifier;
  • message uses package parseExceptionText;
  • status and exact responseText are retained;
  • malformed/unrecognized error body falls back to raw text;
  • AbortError from successful body consumption propagates by identity/name, not as ClickHouseError;
  • arbitrary reader error propagates unchanged.

Response-consumer semantics

Convenience methods

  • each performs exactly one Fetch request;
  • explicit SQL/settings/params/auth/signal pass through unchanged;
  • queryJson default format behavior is tested;
  • queryText/queryProgress do not invent SQL Browser mode mappings;
  • post-header cancellation still reaches the body through the original signal.

killQuery

  • quotes query IDs containing ' and \ safely;
  • emits ASYNC form;
  • one request only;
  • arbitrary Authorization is unchanged;
  • non-2xx throws ClickHouseError;
  • network/abort rejection stays native;
  • no hidden retry or registry state.

Run the full repository gate and the targeted real-browser cancellation suite because the new progress convenience method consumes a real response body:

npm run check:types
npm run check:arch
npm run check:schemas
npm run check:examples
npm test
npm run build
npm run test:client-spike:browser

Use the repository's current equivalent if #631 renamed the targeted browser script, but Chromium and WebKit post-header cancellation coverage must run.

Acceptance criteria

Non-goals

Agent execution notes

Before planning, read #630#633, current queryJson, runQuery, exportQuery, killQuery, and killQueryWithLease in src/net/ch-client.ts, plus existing error/stream/cancellation tests. Preserve the policy boundary: this issue creates generic response classification/consuming primitives; it does not decide when SQL Browser refreshes credentials, signs out, retries, or marks itself offline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorRestructuring without user-facing behavior changetech-debt

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions