Skip to content

fix(core): axiosAdapter(axios) typechecks against real axios (#708) - #715

Open
rejifald wants to merge 1 commit into
mainfrom
fix/axios-adapter-response-type
Open

fix(core): axiosAdapter(axios) typechecks against real axios (#708)#715
rejifald wants to merge 1 commit into
mainfrom
fix/axios-adapter-response-type

Conversation

@rejifald

@rejifald rejifald commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Fixes §1 of #708: axiosAdapter(axios) — the snippet in the adapter's own file header at
packages/core/src/axios-adapter.ts:4-6 — did not typecheck against real axios.

Reproduced before touching anything

axios 1.19.0, tsc 5.9.3, compiled under packages/core/tsconfig.json with all three documented
call forms in one file:

import axios from 'axios';
import { axiosAdapter } from './axios-adapter';

export const a = axiosAdapter(axios);
export const b = axiosAdapter(axios.create());
export const c = axiosAdapter(axios, { timeout: 5 });

BEFORE — exit 2, every call form rejected (one of the three, elided identically for the others):

src/__repro708.ts(6,31): error TS2345: Argument of type 'AxiosStatic' is not assignable to parameter of type 'AxiosLike'.
  Types of property 'request' are incompatible.
    Type '<T = any, R = unique symbol, D = any, P = any>(config: AxiosRequestConfig<D, P>) => Promise<AxiosResponseResult<T, R, D, P>>' is not assignable to type '(config: AxiosLikeConfig) => Promise<AxiosLikeResponse>'.
      Types of parameters 'config' and 'config' are incompatible.
        Type 'AxiosLikeConfig' is not assignable to type 'AxiosRequestConfig<unknown, any>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
          Types of property 'responseType' are incompatible.
            Type 'string' is not assignable to type 'ResponseType'.

src/__repro708.ts(7,31) (axios.create()AxiosInstance) and src/__repro708.ts(8,31) (with
defaults) 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

-    responseType?: string;
+    responseType?: 'arraybuffer' | 'json' | 'text';

AxiosLikeConfig.responseType was string. axios types the same field as its own ResponseType
union. Because config is a parameter, the wider type 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 surfaces 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 through defaults, where a
caller-supplied responseType is overwritten anyway.

Why the existing type-level test could not catch it

packages/core/test-d/adapter-capabilities.test-d.ts had:

const client = null as unknown as AxiosLike;
expectAssignable<Adapter>(axiosAdapter(client));

The as unknown as AxiosLike cast asserts the client is an AxiosLike rather than testing
whether 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 axios types and pins all three call forms:

expectAssignable<Adapter>(axiosAdapter(axios));
expectAssignable<Adapter>(axiosAdapter(axios.create()));
expectAssignable<Adapter>(axiosAdapter(axios, { timeout: 5 }));

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 original TS2345 on all three
new lines (47:39, 48:39, 49:39), then green again once restored. The pre-existing cast line
never failed in either direction.

The dependency

axios becomes a devDependency of packages/core — where zod already sits for the same reason, a
type-only test-d dependency, and where tsd resolves it. Resolved offline from the shared pnpm
store. The shipped runtime stays zero-dependency: nothing under src/ imports axios, and
devDependencies 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 via pnpm add --filter also
re-resolved some unrelated @types/node peer keys (25.9.322.19.21); that churn was reverted
and 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:

Gate Result
prettier --check (changed files) pass
pnpm --filter stitchapi check:lint pass
pnpm --filter stitchapi check:types pass
pnpm --filter stitchapi check:types-d pass
pnpm --filter stitchapi test pass — 139 files, 1491 tests
node scripts/check-changelog.mjs pass
node scripts/check-contract.mjs pass
node scripts/check-unknown-keys.mjs pass
pnpm install --frozen-lockfile pass (lockfile in sync for CI)

The twelve existing call sites across test/adapters.spec.ts,
test/adapter-streaming.spec.ts, test/adapter-upload-progress.spec.ts and
test/axios-adapter-normalize.spec.ts are unaffected — they pass structural stubs, and all 1491
tests pass. (Note: #708 states there is no call site under packages/; there are twelve.)

Scope

Refs, not Fixes — §2 (AdapterResponse.url not set), §3, §4 and §5 are untouched and stay open.

Refs #708

🤖 Generated with Claude Code

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant