RequestBuilder
Fluent builder for a single request. Constructed by BaseService.request — or directly when fine-grained control is required. Every chain method returns the builder so calls can be chained; every runner returns a Promise.
The builder seeds itself with { cache: 'no-cache', method: 'GET' } and falls back to HttpClient.instance when no client is passed.
Importing
import { RequestBuilder } from '@basmilius/http-client';Constructor
new RequestBuilder(path: string, client?: HttpClient)| Argument | Type | Description |
|---|---|---|
path | string | Path appended to the client's base URL. |
client | HttpClient | Optional. Defaults to HttpClient.instance when omitted. |
Properties
client—HttpClient. Read-only. The client used when the request runs.options—RequestInit. Read-only. The mutableRequestInitpopulated by the fluent setters.path—string. Read/write.query—QueryString | null. Read/write.
Configuration methods
Each configuration method returns the builder, so they can be chained. Method order does not matter; the runner is always last.
method
Sets the HTTP method. Accepts a lowercase HttpMethod ('connect' | 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace') and uppercases it internally.
method(method: HttpMethod): RequestBuilder;.method() is the only way to set the verb — there is no .get() / .post() shortcut.
this
.request('/users')
.method('post')
.body(payload)
.bearerToken()
.run();body
Sets the body. The behaviour depends on the type:
FormDatais passed through verbatim andContent-Typeis not set, so the browser computes the multipart boundary.Arrayand plainobjectvalues are JSON-stringified and tagged asapplication/json.- Anything else is used as-is with the supplied
contentType(defaultapplication/octet-stream).
body(
body: BodyInit | FormData | object | null,
contentType?: string | null
): RequestBuilder;this.request('/files').method('post').body(formData);
this.request('/users').method('post').body({email: 'a@example.com'});header
Sets a single header.
header(name: string, value: string): RequestBuilder;bearerToken
Sets an Authorization: Bearer ... header. When token is omitted the client's authToken is used; if neither is available, an existing Authorization header is removed.
bearerToken(token?: string): RequestBuilder;this.request('/me').method('get').bearerToken().run();
this.request('/me').method('get').bearerToken(customToken).run();queryString
Attaches a QueryString to be appended at execution time.
queryString(queryString: QueryString): RequestBuilder;this
.request('/users')
.method('get')
.queryString(QueryString.builder()
.append('offset', 0)
.append('limit', 25))
.run();signal
Attaches an AbortSignal to the request. Pass null to detach.
signal(signal: AbortSignal | null): RequestBuilder;autoCancel
Registers the request under a shared identifier. The next request issued with the same identifier aborts the previous one (which surfaces as a RequestAbortedError).
autoCancel(identifier: symbol): RequestBuilder;const SEARCH_TOKEN = Symbol('search');
this
.request('/search')
.method('get')
.queryString(QueryString.builder().set('q', term))
.autoCancel(SEARCH_TOKEN)
.runArrayAdapter(SearchAdapter.parseResult);Runners
The runners execute the configured request and resolve with shaped output. The "safe" runners (run, runAdapter, runArrayAdapter, runPaginatedAdapter, runEmpty, runData, runDataKey, runStatusCode) feed the response through a single normaliser that maps JSON error envelopes onto RequestError / ValidationError.
fetch
Convenience runner that returns the parsed JSON body. No error normalisation, no BaseResponse wrapper — for trusted endpoints only.
fetch<TResult>(): Promise<TResult>;fetchBlob
Resolves with a BlobResponse containing the body and a filename derived from the Content-Disposition header. Throws a RequestError when the status is not 200.
fetchBlob(): Promise<BlobResponse>;run
Executes the request and resolves with BaseResponse<TResult>. Maps JSON envelopes to RequestError / ValidationError. Returns BaseResponse(null, response) for 204 responses and unauthenticated responses without a JSON body.
run<TResult extends {}>(): Promise<BaseResponse<TResult>>;runAdapter
Same as run but feeds the parsed data through adapterMethod before wrapping it in a BaseResponse. Use when the response shape needs to be mapped onto a DTO.
runAdapter<TResult extends {}>(adapterMethod: (item: object) => TResult): Promise<BaseResponse<TResult>>;this
.request(`/users/${id}`)
.method('get')
.bearerToken()
.runAdapter(UserAdapter.parseUser);runArrayAdapter
Convenience over runAdapter for endpoints that return arrays. Maps every item through adapterMethod.
runArrayAdapter<TResult extends {}>(adapterMethod: (item: object) => TResult): Promise<BaseResponse<TResult[]>>;runPaginatedAdapter
Wraps HttpAdapter.parsePaginatedAdapter, so the resolved data is a Paginated<TResult> DTO.
runPaginatedAdapter<TResult extends {}>(adapterMethod: (item: object) => TResult): Promise<BaseResponse<Paginated<TResult>>>;runEmpty
Runs the request and discards the body, resolving with BaseResponse<never>. Suitable for 204 responses or fire-and-forget endpoints.
runEmpty(): Promise<BaseResponse<never>>;runData
Returns BaseResponse<TResult> with data typed as the caller specifies. Useful when the JSON shape is already correct and no adaptation is needed.
runData<TResult>(): Promise<BaseResponse<TResult>>;runDataKey
Returns BaseResponse<TResult[TKey]> — extracts a single key from the parsed body.
runDataKey<TResult extends object, TKey extends keyof TResult = keyof TResult>(key: TKey): Promise<BaseResponse<TResult[TKey]>>;runStatusCode
Resolves with the HTTP status code. The body is read but discarded by the runner.
runStatusCode(): Promise<HttpStatusCode>;Example
import {
BaseResponse,
BaseService,
QueryString
} from '@basmilius/http-client';
import { UserAdapter } from '../adapter/UserAdapter';
import type { UserDto } from '../dto/UserDto';
const SEARCH_TOKEN = Symbol('user-search');
class UserService extends BaseService {
async search(term: string): Promise<BaseResponse<UserDto[]>> {
return await this
.request('/users/search')
.method('get')
.queryString(QueryString.builder()
.set('q', term))
.autoCancel(SEARCH_TOKEN)
.bearerToken()
.runArrayAdapter(UserAdapter.parseUser);
}
}Notes
- The
datafield on the resolvedBaseResponsecan benullfor204responses or401/403responses without a JSON body. runAdapterand friends throw a syntheticRequestError(-1, 'not_a_json_response', ..., status)when the response is not JSON and the status is not2xx.- When the registered
HttpClientwas constructed withdataField: true, the runners unwrapdata.datafrom the JSON envelope before invoking the adapter.