Summary
Two additive features for httx — no breaking changes to the existing options-object HttxClient API.
Closes #1759 (fluent builder) and #1760 (test faking).
#1759 — Fluent PendingRequest builder
import { http } from '@stacksjs/httx'
const res = await http()
.withToken(token)
.withHeaders({ 'X-Tenant': id })
.acceptJson()
.retry(3, 200)
.timeout(5000)
.post<CreatedOrder>('https://api.test/orders', body)
// still returns Result<HttxResponse<CreatedOrder>, Error>Builder methods: withHeaders / replaceHeaders / withToken / withBasicAuth / withUserAgent / accept / acceptJson / withQueryParameters / asJson / asForm / asMultipart / timeout / retry / baseUrl / withOptions, then terminal get/post/put/patch/delete/head<T>. Delegates to HttxClient.request() so retry, circuit breaker, and timing are reused.
#1760 — Test faking (Http::fake() equivalent)
httx.fake({ 'api.github.com/*': { status: 200, body: { login: 'octocat' } } })
const res = await httx.get('https://api.github.com/users/octocat')
httx.assertSent(req => req.method === 'GET')
httx.restoreFetch()- URL glob patterns (optional
METHODprefix; protocol-less matching), function responders(req) => spec, andhttx.sequence([...])for ordered responses. - Strict by default — an unmatched request throws
HttxFakeUnmatchedErrorso a test can't silently hit the network; opt into{ passthrough: true }. - Assertions:
assertSent/assertNotSent/assertSentCount/assertNothingSent+recorded(). - The intercept synthesizes a real
Response, so the client's parse path is identical to a live request.
New httx facade
A process-wide singleton bundling the shared-client verbs, the http() builder (httx.request()), and the fake controls.
Test plan
- 28 new hermetic tests (
test/fake.test.ts,test/pending-request.test.ts) — no network tsc --noEmitclean under--isolatedDeclarations- pickier clean
- existing non-network suites (errors, utils) still green
- maintainer: confirm the
httxfacade naming + that strict-by-default faking is the desired default
🤖 Generated with Claude Code