fix(core): call the default fetch as a free function (#100)
## Summary
Issue 106. `Transport.fromFetch` captured `options.fetch ??
globalThis.fetch` and `internal/http.ts` invoked it as
`input.fetch(...)`, so in a browser the receiver was the options object
and Chrome threw `TypeError: Failed to execute 'fetch' on 'Window':
Illegal invocation`. No request left the page for any 0.9.0 browser
consumer relying on the default fetch; `Testing.transport` supplies its
own fetch, which is why nothing caught it. `Resolver` and
`internal/doh.ts` had the same shape.
## Fix
- Default fetch is `(input, init) => globalThis.fetch(input, init)`,
resolved at call time, in `Transport.fromFetch` and `Resolver.make`, so
a host that polyfills `fetch` after construction is picked up.
- `internal/http.ts` and `internal/doh.ts` invoke the captured fetch as
a free function (`const { fetch: send } = input; send(...)`), never as a
method of the options object.
- Audit of other captured callables invoked as methods:
`options.headers` in the transport is called through `Http.headersFrom`
as a free function; the custody and storage async adapters call host
methods on the host's own object (`service.prepareRevocation(...)`),
which is the receiver they expect. Nothing else invokes a captured
function as a method.
## Tests that catch it
- `packages/domainkit/tests/client/transport.test.ts`: the global fetch
is replaced by a function that throws Chrome's error for any receiver
other than `undefined` or `globalThis`, then
`Transport.fromFetch("http://domainkit.test")` with no `fetch` option
runs `connection.inspect` through a real `Server.toWebHandler`.
- `packages/react/tests/browser/transport-fetch.spec.ts` (Chromium via
the existing Playwright fixture): a real `domainkit/server` handler
answers `page.route("**/api/domainkit/**")` from Node; the page renders
`?view=fetch`, which builds `Transport.fromFetch("/api/domainkit")` over
Chrome's own fetch and prints the inspect result. Placed under
`packages/react/tests/browser` because nothing else in the repo runs a
browser fetch.
Both were run against the previous binding and fail with
`@domainkit/DomainKitError: Failed to execute 'fetch' on 'Window':
Illegal invocation`; both pass with the fix.
## Release
`.tegami/domainkit-transport-fetch-binding.md` records `domainkit:
patch`. `tegami version` was not run.
## Validation
- Core gate (oxfmt, lint, typecheck, 135 tests), `bun run --filter
domainkit build`
- `bun run --filter @domainkit/react test:browser` (11 specs in Chrome)
- Root lint, rules, format, and root `release:check`
- Live provider tests are user-owned and were not run
https://claude.ai/code/session_017P54ng7iisz1dsk18u1w1x S
Saatvik Arya committed
5cf90991597e171df4c6af9c702f745477c060dc
Parent: 168ef7a
Committed by GitHub <noreply@github.com>
on 9/4/2026, 6:03:51 AM