Skip to content

Commit 1be8953

Browse files
committed
changeset: state the SDK narrowing in the disposition its three siblings use
#8140 / #11925 / #12034 are the same class — a published `@objectstack/client` return-type narrowing with no metadata surface behind it — and all three record the no-ADR-0087-entry disposition as prose rather than as a `**BREAKING**` declaration, which in this repo is the token that routes a change at the metadata migration ledger. The consumer impact is stated in full (FROM/TO per method, and the exact reads that stop compiling); what is dropped is a token that would have claimed a ledger relevance this change does not have. Recorded on the PR: the ADR-0087 gate has no honest disposition for a published TYPE-surface narrowing that carries a consumer source-edit prescription and no metadata migration — filed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
1 parent 8959c1b commit 1be8953

1 file changed

Lines changed: 55 additions & 35 deletions

File tree

‎.changeset/client-json-erasure-in-repo-families.md‎

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
fix(client): bind the five in-repo `return res.json()` methods, whose published type was `Promise< any >` (part of #12104)
66

7-
**BREAKING** return-type narrowing on five already-published SDK methods,
8-
shipped as `minor` under the repo's launch-window convention for breaking
9-
changes. No runtime behaviour changes — the values these methods resolve to are
10-
byte-identical before and after; only their DECLARED types move off `any`.
7+
**Return-type narrowing on a published SDK (clause-②).** No runtime change: the
8+
value each method resolves to is byte-identical before and after. Only the
9+
DECLARED type moved, off `any` — which is exactly why a runtime test cannot
10+
observe it and the pins for it are type-level.
1111

1212
> ⓘ Angle brackets are spaced throughout (`Promise< any >`) on purpose —
1313
> GitHub's body sanitizer strips tag-shaped spans, backticks and fenced code
@@ -19,34 +19,54 @@ its published type was `Promise< any >`, inherited from `lib.dom`'s
1919
`Promise` nor `unwrapResponse`, which is why the class was invisible to the
2020
greps two earlier censuses used.
2121

22-
FROM (all five): `Promise< any >`
23-
24-
TO:
25-
26-
| method | now returns | why |
27-
|---|---|---|
28-
| `analytics.query` | `BaseResponse & { data: AnalyticsResult }` | dispatcher-served; `deps.success(v)` wraps, `res.json()` strips nothing |
29-
| `analytics.meta` | `AnalyticsMetadataResponse` | same envelope; `data` is the bare `CubeMeta[]` projection |
30-
| `analytics.explain` | `AnalyticsSqlResponse` | same envelope; `data` is `{ sql, params }` |
31-
| `automation.trigger` | `BaseResponse & { data: AutomationResult }` | same envelope; the payload its sibling `automation.execute` unwraps |
32-
| `analytics.queryDataset` | `AnalyticsResult` | served by `@objectstack/rest`, which answers `res.json(result)` — no envelope |
33-
34-
**What breaks.** Nothing that was already reading these correctly. `any`
35-
accepted every read, so code that treated `analytics.query(...)` as if it were
36-
the payload — `(await client.analytics.query(q)).rows` — compiled and was
37-
`undefined` at runtime; it is now a compile error naming the envelope. The
38-
remedy is the read the wire always required: `.data.rows`. The one method that
39-
moves the other way is `queryDataset`, which is served BARE — a caller reading
40-
`.data` off it was likewise reading `undefined`, and that read is now refused
41-
too.
42-
43-
Every shape above was measured by driving the real producers (a real
44-
`AnalyticsService`, a real `AutomationEngine`, the real `HttpDispatcher` and the
45-
real `RestServer`, with only the socket stood in for), not by reading source or
46-
asserting against a mock.
47-
48-
Scope: the five families whose producers live in this repo. The 38
49-
better-auth-backed `auth.*` / `organizations.*` / `oauth.*` methods of the same
50-
class are untouched and remain ledgered.
51-
52-
<!-- adr-0087: not-required (no-migration-prescription) A TypeScript return-type narrowing on five runtime SDK methods. No authorable key, schema property or metadata shape is removed, renamed or re-shaped anywhere — `packages/spec` is untouched — so there is no tombstone and nothing mechanical for `objectstack migrate meta` to rewrite; a ledger entry would have no artifact to project into. The channel that reaches an affected caller is the compile error at their own call site, which names the envelope and is why the change is worth shipping: the remedy (`.data.rows` instead of `.rows`) is a source edit in consumer code, which no migration entry can perform on an upgrader's behalf. -->
22+
## What each method declares now
23+
24+
| method | declared before | declares now | why |
25+
|---|---|---|---|
26+
| `client.analytics.query` | `any` | `BaseResponse & { data: AnalyticsResult }` | dispatcher-served; `deps.success(v)` wraps and `res.json()` strips nothing |
27+
| `client.analytics.meta` | `any` | `AnalyticsMetadataResponse` | same envelope; `data` is the bare `CubeMeta[]` projection |
28+
| `client.analytics.explain` | `any` | `AnalyticsSqlResponse` | same envelope; `data` is `{ sql, params }` |
29+
| `client.automation.trigger` | `any` | `BaseResponse & { data: AutomationResult }` | same envelope, over the payload its sibling `automation.execute` unwraps |
30+
| `client.analytics.queryDataset` | `any` | `AnalyticsResult` | served by `@objectstack/rest`, which ends `res.json(result)` — no envelope |
31+
32+
`any` is assignable to everything and admits every property read, so a
33+
consumer's code can stop compiling where it previously did not. Concretely:
34+
35+
- **Reading a payload key off one of the four ENVELOPED results.**
36+
`(await client.analytics.query(q)).rows` compiled and was `undefined` at
37+
runtime; the read the wire always required is `.data.rows`. Same for
38+
`.data` on `meta` / `explain`, and `.data.runId` / `.data.screen` on
39+
`automation.trigger`.
40+
- **Reading `.data` off `queryDataset`**, which is served bare — likewise
41+
`undefined` today, likewise refused now.
42+
- Assigning any of the five results to an unrelated annotation, or forwarding
43+
one to a differently-typed parameter.
44+
45+
That break is the point: those call sites are already wrong at runtime and the
46+
`any` is what hid it. The compiler is the channel that reaches every affected
47+
consumer, and it is strictly more precise than a release note.
48+
49+
## How the shapes were established
50+
51+
By DRIVING the real producers — a real `AnalyticsService`, a real
52+
`AutomationEngine`, the real `HttpDispatcher` and the real `RestServer`, with
53+
only the socket stood in for — not by reading source and not by asserting
54+
against a mock. Two spec response types that look like the right binding are
55+
NARROWER than the contract their route relays
56+
(`AnalyticsResultResponseSchema.data.fields` and
57+
`TriggerFlowResponseSchema.data`), so those two annotations bind the producer's
58+
contract instead; the near-miss is pinned so a later sweep cannot retarget them.
59+
60+
## Scope
61+
62+
The five families whose producers live in this repo. The 38 better-auth-backed
63+
`auth.*` / `organizations.*` / `oauth.*` methods of the same class are untouched
64+
and keep their erased `any` — they are exactly as permissive as before, and no
65+
consumer loses anything by that.
66+
67+
No ADR-0087 ledger entry: nothing here is a metadata surface. No Zod schema, no
68+
`packages/spec` declaration and no stored representation changed — the erasure
69+
lived only in a TypeScript return annotation — so `objectstack migrate meta` has
70+
nothing to rewrite and an entry would have no artifact to project into. This is
71+
the disposition #8140, #11925 and #12034 recorded for the same class of SDK
72+
return-type narrowing.

0 commit comments

Comments
 (0)