release: http-client@0.7.0 - #10
Draft
afoures wants to merge 1 commit into
Draft
Conversation
afoures
force-pushed
the
release/http-client
branch
5 times, most recently
from
August 2, 2026 08:10
ee50eb3 to
ef4310f
Compare
afoures
force-pushed
the
release/http-client
branch
2 times, most recently
from
August 3, 2026 22:56
3e83209 to
6aad427
Compare
afoures
force-pushed
the
release/http-client
branch
from
August 6, 2026 06:16
6aad427 to
dd96108
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is managed by auto-release. Do not edit it manually.
Automated release for
http-clientVersion:
0.6.0→0.7.0Changelog
Breaking Changes
HttpClientConfigis now parameterized by the endpoint treeHttpClientConfig<client_context>becameHttpClientConfig<endpoints, default_context?>, so the client-levelcontextshape is derived from the endpoints instead of being restated by hand:The merged context shape is also exported as
ClientContext<endpoints>, to constrain a wrapper's own context type parameter and keep the client-level defaults precise.default_contextdefaults tonever: without it, a config declares no client-level defaults, so itscontextis rejected and every declared context key stays required at the call site. Thread the type parameter to record the defaults a caller actually passed, which is what makes those keys optional per call.http_clientnow throws on an invalidbase_urlinstead of returning an error per callAn unparsable
base_urlis a static misconfiguration: it cannot depend on call input, so it was either always broken or never broken for a given client. It is now validated once inhttp_client, which throws aTypeError, rather than making every call return anUnexpectedErrorwithoperation: "base_url_validation".This is the only failure the client throws instead of returning as a value. Call sites that matched on
operation === "base_url_validation"no longer need that branch.Replace
@remix-run/route-patternwith a built-in pathname parserThe client no longer depends on
@remix-run/route-pattern. Pathname patterns are parsed in-house, which leaves the package with no runtime dependencies. The supported syntax is unchanged: static text,:param, optional groups(...)that nest, and several params in one segment (/v:major.:minor). Param names keep the JavaScript identifier charset,[a-zA-Z_$][a-zA-Z_$0-9]*.Percent-encoding of param values, dropping an optional group whose param is
undefinedornull, and reporting every missing required param rather than the first all behave as before.A leading optional group that is dropped no longer leaves a protocol-relative
//. Given/(:lang)/userswith nolang, the generated pathname is now/usersrather than//users, whichnew URL()resolved as the hostusersand so sent the request to a different origin.A
?or#in apathnameis now rejected, at the type level on the endpoint definition and at runtime when the pattern is compiled. Search params are declared withquery; previously a?was parsed as a search-constraint pattern and a#was emitted as path text thatnew URL()then reinterpreted as a fragment.Undocumented pattern syntax that came from the library is gone: wildcards (
*rest), enums ({a,b}), and protocol, hostname, port or search patterns. Only pathnames are supported.generate_urland theEndpointconstructor now throwPathnameErrorandMissingParamsError, exported from the package root, in place of the library'sCreateHrefError.A client-level
contextdefault for a key that endpoints declare with conflicting types is now a compile errorClientContextchecks, per key, that every endpoint declaring it agrees on its type. A key two endpoints declare asstringandnumberresolves to anErrorMessageinstead ofstring | number, so supplying a client-level default for it fails to compile.Previously this was accepted and unsound: the default made
tenantoptional at every call site, including the one needing anumber, so a string reached a schema factory expecting a number. The check fires on the value, not the tree: the same endpoints are fine as long as no client-level default is set for the conflicting key. Fix by aligning the type in every endpoint that declares the key, or by using separate clients. Mutually assignable declarations stay valid, sobooleanon both sides and a single endpoint'sstring | numberare unaffected.EndpointMapis no longer exported from the package rootThe default retry condition now retries transient failures instead of every non-ok response.
timeoutis now the call deadline, not a per-attempt boundIt used to bound each attempt, so
{ timeout: 5000, retry: { attempts: 4, delay: 1000 } }could run for 23 seconds while the config said 5. It now covers the whole call: every attempt, every retry delay, and response parsing.timeoutaccepts{ total?, attempt? }and merges per key, so a client-level{ attempt: 2000 }survives a per-call{ total: 5000 }.totalis terminal, so an expiry never reaches the retry condition;attemptis retryable, which is the point of it. This also fixes an attempt timeout firing during the retry delay, and an abort during a delay surfacing asUnexpectedError: Failed to check retry policyinstead of anAbortedErrorwithoperation: "retry_delay".timeout: 0now means immediately, not neverA truthy gate made
0disable the timeout, which was an oversight. Onlyundefineddisables it now, so{ total: 0 }gives aTimeoutErrorand zero attempts. That is the reading a call deadline needs: people writetimeout: { total: budget_remaining() }, and an exhausted budget must fail fast.Both keys are also floored and clamped to
0, so1.5and-1no longer throw aRangeErrorout of the call.NaNandInfinitycome back as anUnexpectedErrorwithoperation: "resolve_timeout"naming the key.ErrorContext.request.timeoutis now aTimeoutConfig, not a numberIt carries the normalized
{ total?, attempt? }the call ran under, soresult.context.request?.timeoutreads{ total: 5000 }where it used to read5000. Breaking for anyone reading it as a number.The default
"urlencoded"query encoder now handles array values and entry lists correctlyA value it cannot express (a nested object, or an entry that isn't a
[key, value]pair) now returns aSerializationErrornaming the key instead of writing[object Object].nullandundefinedare still skipped.serializealso stays optional for more schemas: numbers, booleans and array values are urlencoded-compatible now, so the cast that used to be needed to reach the comma-join is gone.Features
Add a
kinddiscriminant to every response envelope and error classEach arm of a call result now carries a
kindliteral named after its own type or class, so the whole union narrows in one flatswitch, with noinstanceofand no value import:The values are exported as
HTTPFetch.ResponseKindandErrorKind. Adding adefaultbranch that calls a(value: never) => neverhelper turns an unhandled arm into a compile error.Unlike
okandstatus,kindneeds no priorinstanceof Errorcheck, because the error classes carry it too. It also survives a spread, a clone or a serialization round-trip, and keeps working when two copies of this package end up installed, all of which defeatinstanceof.For the error classes the value matches
name, which already held the same string at runtime. The difference is thatErrortypesnameasstring, so onlykindcan discriminate a union.Reading a result is unaffected, since
kindis an added field. Code that builds an envelope by hand, such as a test fixture or a mock, has to add the matchingkindfor it to satisfy the type.Bug Fixes
Fix documented examples that did not compile
docs/error-handling.mdshowedresult.kindin five places, a field that did not exist on the error classes until this release.The "handle every case" examples in
docs/response-parsing.mdanddocs/http-client.mddid not typecheck either. The first narrowed with relational status comparisons (result.status >= 400), which do not narrow a union of numeric literals in TypeScript, leavingresult.errorinaccessible. The second readresult.errorfrom theelseof anokcheck, a branch that also contains the redirect arm, which has noerrorfield.Both are rewritten as
kindswitches, with thestatus-based form shown separately using exact comparisons after aninstanceof Errorpeel.