fix(core): axiosAdapter(axios) typechecks against real axios (#708) - #715
Open
rejifald wants to merge 1 commit into
Open
fix(core): axiosAdapter(axios) typechecks against real axios (#708)#715rejifald wants to merge 1 commit into
rejifald wants to merge 1 commit into
Conversation
The snippet in the adapter's own file header — `axiosAdapter(axios)` — did not
compile. Against axios 1.19.0 under `packages/core/tsconfig.json`, tsc 5.9.3
rejected the call:
error TS2345: Argument of type 'AxiosStatic' is not assignable to parameter
of type 'AxiosLike'.
Types of property 'request' are incompatible.
...
Types of property 'responseType' are incompatible.
Type 'string' is not assignable to type 'ResponseType'.
One word caused it. `AxiosLikeConfig.responseType` was `string`; axios types the
same field as its own `ResponseType` union. A wider type in a parameter position
makes `AxiosLikeConfig` unassignable to `AxiosRequestConfig`, which makes the
whole client unassignable to `AxiosLike` — so `axios`, the one client the seam
exists to accept, was the one client it rejected. The repo's strict
`exactOptionalPropertyTypes` is what surfaces it rather than causing it.
Narrowing to `'arraybuffer' | 'json' | 'text'` costs nothing: the adapter only
ever sends `'arraybuffer'`, and the field is otherwise reachable only through
`defaults`, where a caller-supplied `responseType` is overwritten anyway. All
three call forms — `axiosAdapter(axios)`, `axiosAdapter(axios.create())`,
`axiosAdapter(axios, { timeout: 5 })` — now compile, and `src/**` stays clean.
The type-level test could not have caught this, by construction:
const client = null as unknown as AxiosLike; // test-d/adapter-capabilities
expectAssignable<Adapter>(axiosAdapter(client));
The cast ASSERTS the client is an `AxiosLike` instead of testing whether axios
is one — it asserted away the exact assignability that was broken. It now
asserts against real `axios` types and pins all three call forms; the structural
hand-rolled client stays alongside it, since the seam is deliberately open.
Reverting the one-word fix turns that file red with the original TS2345 on all
three lines, so the guard is load-bearing rather than decorative.
axios becomes a devDependency of `packages/core` (where `zod` already sits for
the same reason — a type-only test-d dependency). It is resolved offline from
the shared pnpm store; the shipped runtime stays zero-dependency, and nothing
under `src/` imports it.
`Refs`, not `Closes`: #708 §2-§5 are untouched and stay open.
Refs #708
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes §1 of #708:
axiosAdapter(axios)— the snippet in the adapter's own file header atpackages/core/src/axios-adapter.ts:4-6— did not typecheck against real axios.Reproduced before touching anything
axios
1.19.0, tsc5.9.3, compiled underpackages/core/tsconfig.jsonwith all three documentedcall forms in one file:
BEFORE — exit 2, every call form rejected (one of the three, elided identically for the others):
src/__repro708.ts(7,31)(axios.create()→AxiosInstance) andsrc/__repro708.ts(8,31)(withdefaults) failed with the same chain.AFTER — same command, same file, exit 0 and no output. The temp repro file was then deleted; it
is not part of this PR.
Root cause — one word
AxiosLikeConfig.responseTypewasstring. axios types the same field as its ownResponseTypeunion. Because
configis a parameter, the wider type makesAxiosLikeConfigunassignable toAxiosRequestConfig, which makes the whole client unassignable toAxiosLike— soaxios, the oneclient the seam exists to accept, was the one client it rejected. The repo's strict
exactOptionalPropertyTypessurfaces it rather than causing it.The narrowing costs nothing: the adapter only ever sends
'arraybuffer'(
axios-adapter.ts:109), and the field is otherwise reachable only throughdefaults, where acaller-supplied
responseTypeis overwritten anyway.Why the existing type-level test could not catch it
packages/core/test-d/adapter-capabilities.test-d.tshad:The
as unknown as AxiosLikecast asserts the client is anAxiosLikerather than testingwhether axios actually is one — it asserted away the exact assignability that was broken. The file
stayed green while
axiosAdapter(axios)failed to compile for every user.It now asserts against real
axiostypes and pins all three call forms:The structural hand-rolled client stays alongside it, since the seam is deliberately open.
Confirmed load-bearing, not decorative: with the test in place I reverted the one-word fix and
re-ran
pnpm --filter stitchapi check:types-d— it went red with the originalTS2345on all threenew lines (
47:39,48:39,49:39), then green again once restored. The pre-existing cast linenever failed in either direction.
The dependency
axiosbecomes a devDependency ofpackages/core— wherezodalready sits for the same reason, atype-only
test-ddependency, and wheretsdresolves it. Resolved offline from the shared pnpmstore. The shipped runtime stays zero-dependency: nothing under
src/imports axios, anddevDependencies are not installed by consumers.
The lockfile diff is 54 pure insertions, zero deletions (axios +
follow-redirects,proxy-from-env,https-proxy-agent,agent-base). Generating it viapnpm add --filteralsore-resolved some unrelated
@types/nodepeer keys (25.9.3→22.19.21); that churn was revertedand the lockfile regenerated with a plain root install so this PR carries axios and nothing else.
Gates
All run from the repo root on the final tree, all green:
prettier --check(changed files)pnpm --filter stitchapi check:lintpnpm --filter stitchapi check:typespnpm --filter stitchapi check:types-dpnpm --filter stitchapi testnode scripts/check-changelog.mjsnode scripts/check-contract.mjsnode scripts/check-unknown-keys.mjspnpm install --frozen-lockfileThe twelve existing call sites across
test/adapters.spec.ts,test/adapter-streaming.spec.ts,test/adapter-upload-progress.spec.tsandtest/axios-adapter-normalize.spec.tsare unaffected — they pass structural stubs, and all 1491tests pass. (Note: #708 states there is no call site under
packages/; there are twelve.)Scope
Refs, notFixes— §2 (AdapterResponse.urlnot set), §3, §4 and §5 are untouched and stay open.Refs #708
🤖 Generated with Claude Code