ReviewOS

stacks/ts-http

Add a fluent PendingRequest builder (Laravel Http parity: withToken/withHeaders/retry/asForm)

#1759
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Summary

HttxClient today is options-object only — every per-request concern goes through a RequestOptions / HttxConfig bag:

await client.post(url, body, {
  json: true,
  headers: { Authorization: `Bearer ${token}`, 'X-Foo': 'bar' },
  retry: { retries: 3, retryDelay: 200 },
  timeout: 5000,
})

That's complete but not discoverable — you have to know the option keys, and there's no autocomplete-driven path through the common cases (auth, headers, content-type, retries). Laravel's Http facade solves this with a chainable PendingRequest builder.

Proposal

Add a fluent builder that accumulates config and then dispatches via the existing request() path (so all the current retry/circuit-breaker/timing machinery is reused — this is sugar, not a parallel implementation):

import { http } from '@stacksjs/httx'

const res = await http()
  .withToken(token)                 // Authorization: Bearer <token>
  .withHeaders({ 'X-Tenant': id })
  .acceptJson()                     // Accept: application/json
  .retry(3, 200)                    // retries, delay ms
  .timeout(5000)
  .post<CreatedOrder>(url, body)
//    ^ still returns Result<HttxResponse<CreatedOrder>, Error>

Builder methods (Laravel-aligned)

MethodEffect
withHeaders(record)merge headers
replaceHeaders(record)overwrite headers
withToken(token, type='Bearer')Authorization header
withBasicAuth(user, pass)base64 Authorization: Basic
withQueryParameters(record)merge into query
withBody(body)set body
asJson() / asForm() / asMultipart()set the json/form/multipart flag + content-type
accept(mime) / acceptJson()Accept header
withUserAgent(ua)User-Agent header
baseUrl(url)per-builder base URL
timeout(ms)request timeout
retry(times, delayMs?, shouldRetry?)maps to RetryOptions
withOptions(partial RequestOptions)escape hatch — merge raw options
terminal get/post/put/patch/delete/head<T>(url, body?)dispatch, return Result<HttxResponse<T>, Error>

Notes

  • http() returns a fresh PendingRequest; http(config) seeds defaults (baseUrl, defaultHeaders, etc.) so it composes with the existing HttxConfig.
  • Keep the existing HttxClient.get/post/... options-object API — the builder lives alongside it and delegates to it. No breaking change.
  • Generic <T> flows through to HttxResponse<T>.data exactly as it does today.

Why

Discoverability + ergonomics on the 90% path (auth + JSON + retries). Matches the mental model anyone coming from Laravel/Guzzle already has, while keeping httx's Result-based error handling.

Discovered while

Auditing the HTTP client surface from the Stacks framework side (stacksjs/stacks#1924) — the typing is already solid (generics work, zero any), but the fluent layer is the missing DX piece.

Sign in to comment on this issue.