Skip to content

client.analytics.query / meta / explain and automation.trigger hand callers the RAW dispatcher envelope while every sibling unwraps it — one SDK, two calling conventions #13079

Description

@os-litant

Found while binding the five in-repo return res.json() methods of #12104. Out of that card's
declared scope and deliberately not changed there
: #12104 is a type-erasure card and moved only
declarations, never a runtime value. This is the wire-shape question its measurement made
visible, and answering it changes what callers receive.

ⓘ Angle brackets are spaced throughout (Promise< any >) on purpose — GitHub's body sanitizer
strips tag-shaped spans, backticks and fenced code included.

The inconsistency

ObjectStackClient has two response readers:

Four methods take the second path against a dispatcher-served route, so their callers must
write .data where every neighbouring method needs no such step:

methodroutewhat the caller receives
analytics.queryPOST /analytics/query{ success, data: AnalyticsResult }
analytics.metaGET /analytics/meta{ success, data: CubeMeta[] }
analytics.explainPOST /analytics/sql{ success, data: { sql, params } }
automation.triggerPOST /automation/trigger/:name{ success, data: AutomationResult }

The sharpest illustration is inside one namespace: automation.trigger and
automation.execute call the same door with the same body. execute unwraps and resolves
to AutomationResult; trigger does not and resolves to the envelope around it. Two methods,
one route, two calling conventions.

A fifth method, analytics.queryDataset, also uses res.json() but is correct as it stands:
its route is mounted by @objectstack/rest and ends res.json(result) with no envelope, so
there is nothing to strip. It is listed here only so a sweep does not "fix" it into the others'
shape.

Why it was invisible until now

All five were erased to Promise< any > — no return annotation, and lib.dom declares
Response.json(): Promise< any >. With any, (await client.analytics.query(q)).rows
compiled and was undefined at runtime; so did .data.rows. Nothing in the type system
distinguished the two conventions, so neither a caller nor a reviewer met the difference.
#12104 annotated them truthfully, which is what turned an invisible inconsistency into a
visible one.

The decision this card carries

Should these four be converted to unwrapResponse so the SDK has ONE convention?

  • For. One reading rule across the SDK; trigger and execute stop disagreeing about one
    route; the annotations become payload types like every other bound method.
  • Against / cost. It is a runtime breaking change to four published methods, not a
    declaration move — every existing caller that reads .data (or that reads the envelope's
    success flag) breaks. The erasure means both spellings compiled, so the population of
    affected callers is not knowable from types alone and needs measuring across this repo,
    objectui and cloud before anyone commits to it.

Recorded rather than acted on: with the declarations now truthful, the current state is honest
and non-breaking, and the convergence is a deliberate API decision with a migration, not a
cleanup. Whoever takes it should measure the caller population first.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions