Skip to content

SDK drift: Router.sql() throws a plain untyped Error in TypeScript instead of a PmxtError subclass #1795

Description

@realfishsam

Drift

Router.sql() was added in commit d8ca8fb (feat(sdk): add Router.sql() and cross-venue Router.fetchOrderBook(), #1129/#1670). On a failed request, TypeScript's implementation throws a plain, untyped Error for most failure shapes, while Python's implementation always raises a typed PmxtError subclass, matching the rest of the SDK's error contract.

TypeScript SDK

sdks/typescript/pmxt/router.ts:671-703 (async sql(query: string): Promise<SqlResult>):

if (!response.ok) {
    const body = await response.json().catch(() => ({}));
    if (body.error && typeof body.error === 'object') {
        const { fromServerError } = await import('./errors.js');
        throw fromServerError(body.error);
    }
    throw new Error(body.message || body.error || response.statusText);
}
...
} catch (error) {
    if (error instanceof Error) throw error;
    throw new Error(`Failed to sql: ${error}`);
}

fromServerError (which returns a PmxtError subclass) is only reached when body.error is present and is an object. Any other failure — a string error field, a message-only body, a non-JSON body (falls back to {} via .catch(() => ({}))), or a non-2xx response with an unparseable payload — throws a bare Error via body.message || body.error || response.statusText, or via the catch-all Failed to sql: ${error}. router.ts does not import PmxtError at all (verified: only Exchange, ExchangeOptions, logger, and model types are imported at the top of the file), so there is no static-analysis signal at this call site that anything other than Error can escape.

This differs from the rest of Exchange's hosted/generated methods, which route failures through PmxtError-returning helpers consistently.

Python SDK

sdks/python/pmxt/router.py:694-730 (def sql(self, query: str) -> SqlResult):

try:
    response = self._fetch_with_retry(
        lambda: self._api_client.call_api(
            method="POST", url=url, body={"query": query}, header_params=headers,
        )
    )
    response.read()
    raw = json.loads(response.data)
except ApiException as e:
    raise self._parse_api_exception(e) from None

_parse_api_exception (sdks/python/pmxt/client.py:474) always returns a PmxtError subclass, so every failure path for sql() in Python raises a typed, catchable PmxtError.

Expected

Every public SDK method should raise the SDK's own typed error hierarchy (PmxtError and subclasses) on failure in both languages, per the documented contract in /api-reference/errors ("the same except/catch clause works in both hosted and self-hosted modes"). TypeScript's sql() should route all non-ok responses through fromServerError (or wrap the fallback in PmxtError), not a bare Error.

Impact

A caller who writes catch (e) { if (e instanceof PmxtError) ... } around router.sql(...) in TypeScript will miss failures where the hosted /v0/sql endpoint returns an error body without a structured error object (e.g. a plain-text 500, a {"message": "..."} shape, or a non-JSON body) — the same query in Python would raise a catchable PmxtError for the equivalent failure.


Found by automated SDK cross-language drift audit

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions