Found while implementing #11925 and out of its declared scope — that card's population is defined as
"no return annotation, typed from unwrapResponse< …any… >", and these methods never call unwrapResponse
at all. Recorded because it is larger than the fifth spelling and invisible to every grep used so far.
Measured at c804f0ca5, in packages/client/src/index.ts.
The lineage
#8140's census enumerated four spellings, all putting the any inside a visible Promise< … > annotation.
#11925 found a fifth: no annotation at all, the type inferred from unwrapResponse< …any… >. Both are
text-visible — one greps Promise, the other greps unwrapResponse.
This sixth writes neither:
query: async(payload: any)=>{constroute=this.getRoute('analytics');constres=awaitthis.fetch(`${this.baseUrl}${route}/query`,{method: 'POST',body: JSON.stringify(payload)});returnres.json();},Response.json() is Promise<any> in the DOM lib, so the method's published type is any — with no
Promise, no unwrapResponse, and no any token anywhere in the method body.
The count, and how the three populations relate
A text census cannot see this class, so the ground truth was taken from the TypeScript checker instead —
walking the public members of ObjectStackClient / ScopedProjectClient and resolving each awaited return
type:
reachable methods walked : 273
…whose awaited return contains `any`: 89
of those, ANNOTATED : 4
of those, UNANNOTATED : 85
Against the spelling-based census of #11925's population:
spelling-census unannotated methods (unwrapResponse<..any..>): 39
tsc-truth unannotated methods with `any` in published return : 85
spelling ⊆ tsc-truth? missing from tsc: []
tsc-truth NOT in spelling population : 46
The 39 are a strict subset. The residual 46 are this spelling:
$ grep -cE '^\s*return res\.json\(\);\s*$' packages/client/src/index.ts
48
# reverse-check — a term independently present, never a substring of the term under test
$ grep -cE 'this\.getRoute\(' packages/client/src/index.ts
186
(48 textual sites, 46 of which sit in unannotated methods the checker reports as any.)
Which families
analytics.* (query, meta, explain, queryDataset), organizations.* including invitations.* and
teams.* (~24), oauth.applications.* and oauth.consent (6), auth.* including sessions.*,
twoFactor.* and accounts.* (~14), and automation.trigger.
Most of these are better-auth relays (this.getRoute('auth')), which is likely why they bypassed
unwrapResponse — better-auth does not send the { success, data } envelope, so there is nothing to strip.
That makes it a coherent family rather than 46 scattered oversights, and it means the binding question for
them is "what does better-auth declare", not "which @objectstack/spec type".
Why it is worth its own card
Related: #8140, #11925, #11924.
Generated by Claude Code
Found while implementing #11925 and out of its declared scope — that card's population is defined as
"no return annotation, typed from
unwrapResponse< …any… >", and these methods never callunwrapResponseat all. Recorded because it is larger than the fifth spelling and invisible to every grep used so far.
Measured at
c804f0ca5, inpackages/client/src/index.ts.The lineage
#8140's census enumerated four spellings, all putting the
anyinside a visiblePromise< … >annotation.#11925 found a fifth: no annotation at all, the type inferred from
unwrapResponse< …any… >. Both aretext-visible — one greps
Promise, the other grepsunwrapResponse.This sixth writes neither:
Response.json()isPromise<any>in the DOM lib, so the method's published type isany— with noPromise, nounwrapResponse, and noanytoken anywhere in the method body.The count, and how the three populations relate
A text census cannot see this class, so the ground truth was taken from the TypeScript checker instead —
walking the public members of
ObjectStackClient/ScopedProjectClientand resolving each awaited returntype:
Against the spelling-based census of #11925's population:
The 39 are a strict subset. The residual 46 are this spelling:
(48 textual sites, 46 of which sit in unannotated methods the checker reports as
any.)Which families
analytics.*(query,meta,explain,queryDataset),organizations.*includinginvitations.*andteams.*(~24),oauth.applications.*andoauth.consent(6),auth.*includingsessions.*,twoFactor.*andaccounts.*(~14), andautomation.trigger.Most of these are better-auth relays (
this.getRoute('auth')), which is likely why they bypassedunwrapResponse— better-auth does not send the{ success, data }envelope, so there is nothing to strip.That makes it a coherent family rather than 46 scattered oversights, and it means the binding question for
them is "what does better-auth declare", not "which
@objectstack/spectype".Why it is worth its own card
unwrapResponse< …any… >#11925's 39 and client SDK drops the precise spec types at its boundary: 32 methods returnPromise< any >on a package that already depends on@objectstack/spec#8140's 51.external SDK consumers.
Promise< any >on a package that already depends on@objectstack/spec#8140's closed with "an audit anchored on the wordPromisecannot see a method that never writes it"; A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed from
unwrapResponse< …any… >#11925 repeated it one level out. An audit anchored onunwrapResponsecannot see a method that never calls it. A text census cannot close this class at all —whoever scopes the implementation should work from the checker, not from grep.
Related: #8140, #11925, #11924.
Generated by Claude Code