@gcmdev/faxios 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/adapters/fetch-helpers.d.ts +2 -1
- package/dist/lib/adapters/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/adapters/fetch.d.ts +1 -0
- package/dist/lib/adapters/fetch.d.ts.map +1 -0
- package/dist/lib/adapters/fetch.js +86 -26
- package/dist/lib/adapters/fetch.js.map +1 -1
- package/dist/lib/cancel/CanceledError.d.ts +2 -1
- package/dist/lib/cancel/CanceledError.d.ts.map +1 -0
- package/dist/lib/cancel/isCancel.d.ts +1 -0
- package/dist/lib/cancel/isCancel.d.ts.map +1 -0
- package/dist/lib/core/Faxios.d.ts +4 -3
- package/dist/lib/core/Faxios.d.ts.map +1 -0
- package/dist/lib/core/Faxios.js +8 -4
- package/dist/lib/core/Faxios.js.map +1 -1
- package/dist/lib/core/FaxiosError.d.ts +3 -2
- package/dist/lib/core/FaxiosError.d.ts.map +1 -0
- package/dist/lib/core/FaxiosHeaders.d.ts +9 -8
- package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -0
- package/dist/lib/core/buildFullPath.d.ts +1 -0
- package/dist/lib/core/buildFullPath.d.ts.map +1 -0
- package/dist/lib/core/define.d.ts +20 -3
- package/dist/lib/core/define.d.ts.map +1 -0
- package/dist/lib/core/define.js.map +1 -1
- package/dist/lib/core/dispatchRequest.d.ts +2 -1
- package/dist/lib/core/dispatchRequest.d.ts.map +1 -0
- package/dist/lib/core/mergeConfig.d.ts +3 -2
- package/dist/lib/core/mergeConfig.d.ts.map +1 -0
- package/dist/lib/core/mergeConfig.js +40 -2
- package/dist/lib/core/mergeConfig.js.map +1 -1
- package/dist/lib/core/prepareRequest.d.ts +1 -0
- package/dist/lib/core/prepareRequest.d.ts.map +1 -0
- package/dist/lib/core/route.d.ts +10 -1
- package/dist/lib/core/route.d.ts.map +1 -0
- package/dist/lib/core/route.js +2 -5
- package/dist/lib/core/route.js.map +1 -1
- package/dist/lib/core/settle.d.ts +1 -0
- package/dist/lib/core/settle.d.ts.map +1 -0
- package/dist/lib/core/transformData.d.ts +3 -2
- package/dist/lib/core/transformData.d.ts.map +1 -0
- package/dist/lib/core/validateSchema.d.ts +3 -2
- package/dist/lib/core/validateSchema.d.ts.map +1 -0
- package/dist/lib/defaults/index.d.ts +1 -0
- package/dist/lib/defaults/index.d.ts.map +1 -0
- package/dist/lib/defaults/transitional.d.ts +1 -0
- package/dist/lib/defaults/transitional.d.ts.map +1 -0
- package/dist/lib/env/data.d.ts +2 -1
- package/dist/lib/env/data.d.ts.map +1 -0
- package/dist/lib/env/data.js +1 -1
- package/dist/lib/faxios.d.ts +59 -38
- package/dist/lib/faxios.d.ts.map +1 -0
- package/dist/lib/faxios.js.map +1 -1
- package/dist/lib/helpers/FaxiosURLSearchParams.d.ts +1 -0
- package/dist/lib/helpers/FaxiosURLSearchParams.d.ts.map +1 -0
- package/dist/lib/helpers/HttpStatusCode.d.ts +1 -0
- package/dist/lib/helpers/HttpStatusCode.d.ts.map +1 -0
- package/dist/lib/helpers/buildURL.d.ts +1 -0
- package/dist/lib/helpers/buildURL.d.ts.map +1 -0
- package/dist/lib/helpers/cloneConfig.d.ts +1 -1
- package/dist/lib/helpers/cloneConfig.d.ts.map +1 -0
- package/dist/lib/helpers/cloneConfig.js +2 -2
- package/dist/lib/helpers/cloneConfig.js.map +1 -1
- package/dist/lib/helpers/combineURLs.d.ts +1 -0
- package/dist/lib/helpers/combineURLs.d.ts.map +1 -0
- package/dist/lib/helpers/composeSignals.d.ts +1 -0
- package/dist/lib/helpers/composeSignals.d.ts.map +1 -0
- package/dist/lib/helpers/cookies.d.ts +2 -1
- package/dist/lib/helpers/cookies.d.ts.map +1 -0
- package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts +1 -0
- package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts.map +1 -0
- package/dist/lib/helpers/formDataToJSON.d.ts +2 -1
- package/dist/lib/helpers/formDataToJSON.d.ts.map +1 -0
- package/dist/lib/helpers/formDataToJSON.js +1 -1
- package/dist/lib/helpers/formDataToJSON.js.map +1 -1
- package/dist/lib/helpers/isAbsoluteURL.d.ts +1 -0
- package/dist/lib/helpers/isAbsoluteURL.d.ts.map +1 -0
- package/dist/lib/helpers/isFaxiosError.d.ts +1 -0
- package/dist/lib/helpers/isFaxiosError.d.ts.map +1 -0
- package/dist/lib/helpers/isURLSameOrigin.d.ts +1 -0
- package/dist/lib/helpers/isURLSameOrigin.d.ts.map +1 -0
- package/dist/lib/helpers/parseHeaders.d.ts +2 -1
- package/dist/lib/helpers/parseHeaders.d.ts.map +1 -0
- package/dist/lib/helpers/progressEventReducer.d.ts +1 -0
- package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -0
- package/dist/lib/helpers/sanitizeHeaderValue.d.ts +1 -0
- package/dist/lib/helpers/sanitizeHeaderValue.d.ts.map +1 -0
- package/dist/lib/helpers/speedometer.d.ts +1 -0
- package/dist/lib/helpers/speedometer.d.ts.map +1 -0
- package/dist/lib/helpers/substitutePathParams.d.ts +1 -0
- package/dist/lib/helpers/substitutePathParams.d.ts.map +1 -0
- package/dist/lib/helpers/substitutePathParams.js +11 -3
- package/dist/lib/helpers/substitutePathParams.js.map +1 -1
- package/dist/lib/helpers/throttle.d.ts +1 -0
- package/dist/lib/helpers/throttle.d.ts.map +1 -0
- package/dist/lib/helpers/toFormData.d.ts +1 -0
- package/dist/lib/helpers/toFormData.d.ts.map +1 -0
- package/dist/lib/helpers/toFormData.js +4 -2
- package/dist/lib/helpers/toFormData.js.map +1 -1
- package/dist/lib/helpers/toURLEncodedForm.d.ts +1 -0
- package/dist/lib/helpers/toURLEncodedForm.d.ts.map +1 -0
- package/dist/lib/helpers/trackStream.d.ts +1 -0
- package/dist/lib/helpers/trackStream.d.ts.map +1 -0
- package/dist/lib/helpers/validator.d.ts +2 -1
- package/dist/lib/helpers/validator.d.ts.map +1 -0
- package/dist/lib/platform.d.ts +1 -0
- package/dist/lib/platform.d.ts.map +1 -0
- package/dist/lib/plugins/attempt.d.ts +1 -0
- package/dist/lib/plugins/attempt.d.ts.map +1 -0
- package/dist/lib/plugins/authBearer.d.ts +10 -5
- package/dist/lib/plugins/authBearer.d.ts.map +1 -0
- package/dist/lib/plugins/authBearer.js +6 -0
- package/dist/lib/plugins/authBearer.js.map +1 -1
- package/dist/lib/plugins/definePlugin.d.ts +4 -3
- package/dist/lib/plugins/definePlugin.d.ts.map +1 -0
- package/dist/lib/plugins/retry.d.ts +24 -23
- package/dist/lib/plugins/retry.d.ts.map +1 -0
- package/dist/lib/plugins/timing.d.ts +9 -8
- package/dist/lib/plugins/timing.d.ts.map +1 -0
- package/dist/lib/types/standard-schema.d.ts +7 -6
- package/dist/lib/types/standard-schema.d.ts.map +1 -0
- package/dist/lib/types.d.ts +104 -92
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +5 -3
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +9 -2
- package/dist/lib/utils.js.map +1 -1
- package/package.json +8 -4
- package/skills/configure-requests/SKILL.md +53 -8
- package/skills/configure-requests/references/plugins.md +54 -6
- package/src/index.ts +57 -0
- package/src/lib/adapters/fetch-helpers.ts +167 -0
- package/src/lib/adapters/fetch.ts +871 -0
- package/src/lib/cancel/CanceledError.ts +23 -0
- package/src/lib/cancel/isCancel.ts +10 -0
- package/src/lib/core/Faxios.ts +517 -0
- package/src/lib/core/FaxiosError.ts +283 -0
- package/src/lib/core/FaxiosHeaders.ts +529 -0
- package/src/lib/core/buildFullPath.ts +51 -0
- package/src/lib/core/define.ts +127 -0
- package/src/lib/core/dispatchRequest.ts +171 -0
- package/src/lib/core/mergeConfig.ts +213 -0
- package/src/lib/core/prepareRequest.ts +155 -0
- package/src/lib/core/route.ts +65 -0
- package/src/lib/core/settle.ts +20 -0
- package/src/lib/core/transformData.ts +23 -0
- package/src/lib/core/validateSchema.ts +53 -0
- package/src/lib/defaults/index.ts +221 -0
- package/src/lib/defaults/transitional.ts +9 -0
- package/src/lib/env/data.ts +1 -0
- package/src/lib/faxios.ts +306 -0
- package/src/lib/helpers/FaxiosURLSearchParams.ts +52 -0
- package/src/lib/helpers/HttpStatusCode.ts +77 -0
- package/src/lib/helpers/buildURL.ts +77 -0
- package/src/lib/helpers/cloneConfig.ts +22 -0
- package/src/lib/helpers/combineURLs.ts +11 -0
- package/src/lib/helpers/composeSignals.ts +74 -0
- package/src/lib/helpers/cookies.ts +68 -0
- package/src/lib/helpers/deprecatedMethod.ts +32 -0
- package/src/lib/helpers/estimateDataURLDecodedBytes.ts +95 -0
- package/src/lib/helpers/formDataToJSON.ts +122 -0
- package/src/lib/helpers/isAbsoluteURL.ts +13 -0
- package/src/lib/helpers/isFaxiosError.ts +19 -0
- package/src/lib/helpers/isURLSameOrigin.ts +16 -0
- package/src/lib/helpers/parseHeaders.ts +76 -0
- package/src/lib/helpers/progressEventReducer.ts +57 -0
- package/src/lib/helpers/sanitizeHeaderValue.ts +61 -0
- package/src/lib/helpers/speedometer.ts +55 -0
- package/src/lib/helpers/substitutePathParams.ts +23 -0
- package/src/lib/helpers/throttle.ts +45 -0
- package/src/lib/helpers/toFormData.ts +298 -0
- package/src/lib/helpers/toURLEncodedForm.ts +14 -0
- package/src/lib/helpers/trackStream.ts +123 -0
- package/src/lib/helpers/validator.ts +114 -0
- package/src/lib/platform.ts +44 -0
- package/src/lib/plugins/attempt.ts +3 -0
- package/src/lib/plugins/authBearer.ts +129 -0
- package/src/lib/plugins/definePlugin.ts +34 -0
- package/src/lib/plugins/retry.ts +245 -0
- package/src/lib/plugins/timing.ts +76 -0
- package/src/lib/types/standard-schema.ts +58 -0
- package/src/lib/types.ts +496 -0
- package/src/lib/utils.ts +941 -0
|
@@ -13,7 +13,7 @@ metadata:
|
|
|
13
13
|
generating Axios APIs that faxios removed or never verified.
|
|
14
14
|
type: core
|
|
15
15
|
library: '@gcmdev/faxios'
|
|
16
|
-
library_version: '0.2.
|
|
16
|
+
library_version: '0.2.1'
|
|
17
17
|
sources:
|
|
18
18
|
- src/index.ts
|
|
19
19
|
- src/lib/faxios.ts
|
|
@@ -22,6 +22,8 @@ sources:
|
|
|
22
22
|
- src/lib/core/buildFullPath.ts
|
|
23
23
|
- src/lib/core/dispatchRequest.ts
|
|
24
24
|
- src/lib/core/FaxiosError.ts
|
|
25
|
+
- src/lib/core/define.ts
|
|
26
|
+
- src/lib/core/route.ts
|
|
25
27
|
- src/lib/types.ts
|
|
26
28
|
- src/lib/adapters/fetch.ts
|
|
27
29
|
- src/lib/helpers/composeSignals.ts
|
|
@@ -70,7 +72,7 @@ const { data, status } = await api.get<{ id: number; name: string }>("/users/1")
|
|
|
70
72
|
- `get`, `delete`, `head`, `options` take `(url, config?)`.
|
|
71
73
|
- `post`, `put`, `patch`, `query` take `(url, data?, config?)`. `query` sends the HTTP QUERY method.
|
|
72
74
|
- `postForm`, `putForm`, `patchForm` send `multipart/form-data`.
|
|
73
|
-
- `api.getUri(config)`
|
|
75
|
+
- `api.getUri(config)` builds the URL from `baseURL`, `url` and `params` without sending anything. It doesn't substitute `pathParams`, so `{key}` placeholders stay as written.
|
|
74
76
|
|
|
75
77
|
Path and query parameters:
|
|
76
78
|
|
|
@@ -84,8 +86,23 @@ const res = await faxios.get("https://api.example.com/users/{id}/posts", {
|
|
|
84
86
|
// GET https://api.example.com/users/42/posts?page=2&tag=news
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
`pathParams` values are `encodeURIComponent`-encoded. A missing or `null` value
|
|
88
|
-
`ERR_BAD_OPTION_VALUE`.
|
|
89
|
+
`pathParams` values are `encodeURIComponent`-encoded. A missing or `null` value, or `.`/`..`
|
|
90
|
+
(also percent-encoded, such as `%2e%2e`), rejects with `ERR_BAD_OPTION_VALUE`.
|
|
91
|
+
|
|
92
|
+
Binary downloads need `responseType`, and a type argument because `data` is `unknown`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import faxios from "@gcmdev/faxios";
|
|
96
|
+
|
|
97
|
+
const { data } = await faxios.get<ArrayBuffer>("https://example.com/report.zip", {
|
|
98
|
+
responseType: "arraybuffer",
|
|
99
|
+
maxContentLength: 50 * 1024 * 1024, // the body is buffered; cap it for untrusted servers
|
|
100
|
+
});
|
|
101
|
+
console.log(data.byteLength);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Unset `responseType` reads the body as text, then JSON-parses it if it parses. `"text"` skips the
|
|
105
|
+
JSON parse; `"blob"`, `"formdata"` and `"stream"` (a `ReadableStream`) are also supported.
|
|
89
106
|
|
|
90
107
|
### Merge defaults the way faxios does
|
|
91
108
|
|
|
@@ -134,7 +151,29 @@ catch (error) {
|
|
|
134
151
|
`ERR_BAD_RESPONSE_SCHEMA`, `error.response.data` is the body the schema rejected. Schemas run
|
|
135
152
|
inside dispatch, so middleware sees the unvalidated config before `next(ctx)`.
|
|
136
153
|
- For reusable typed endpoints, use `api.define(method, url, config)` or `api.route(url, config)`
|
|
137
|
-
rather than wrapping calls by hand.
|
|
154
|
+
rather than wrapping calls by hand. They return plain functions with no `.use()`
|
|
155
|
+
(`api.define(...).use(...)` is a TypeError), so put middleware on the instance and call
|
|
156
|
+
`define()`/`route()` on the value `.use()` returned. Middleware added later still runs (the
|
|
157
|
+
endpoint calls `api.request()` each time), but only that value types plugin options.
|
|
158
|
+
`pathParams` is accepted only when `pathParamsSchema` is set.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import faxios from "@gcmdev/faxios";
|
|
162
|
+
import { z } from "zod";
|
|
163
|
+
|
|
164
|
+
const api = faxios
|
|
165
|
+
.create({ baseURL: "https://api.example.com" })
|
|
166
|
+
.use(async (ctx, next) => next(ctx));
|
|
167
|
+
const getUser = api.define("get", "/users/{id}", {
|
|
168
|
+
pathParamsSchema: z.object({ id: z.string() }),
|
|
169
|
+
responseSchema: z.object({ name: z.string() }),
|
|
170
|
+
});
|
|
171
|
+
const { data } = await getUser({ pathParams: { id: "123" } });
|
|
172
|
+
console.log(data.name);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- With a plugin such as `retry()`, see "Define endpoints on a plugin instance" in
|
|
176
|
+
[references/plugins.md](references/plugins.md).
|
|
138
177
|
|
|
139
178
|
### Cancel requests and apply timeouts
|
|
140
179
|
|
|
@@ -234,6 +273,7 @@ plugin's request options (such as `retry: false`). Before writing plugin code, r
|
|
|
234
273
|
`baseURL` origin when `baseURL` is absolute; otherwise only to relative request URLs. Use
|
|
235
274
|
`origins` to allow more. It leaves an explicit `Authorization` header alone unless
|
|
236
275
|
`overwrite: true`. Install `retry` before it if each attempt should call `getToken` again.
|
|
276
|
+
A `getToken` result that isn't a non-empty string sends the request without the header.
|
|
237
277
|
- `retry` defaults: 3 attempts, statuses 408/429/500/502/503/504 plus `ERR_NETWORK` and
|
|
238
278
|
`ETIMEDOUT`, exponential backoff from 100 ms with full jitter. A 429/503 `Retry-After` sets the
|
|
239
279
|
wait unless `respectRetryAfter: false`. Turn that off when something else, such as a queue
|
|
@@ -265,9 +305,11 @@ const { data } = await testApi.get("https://api.example.com/feed"); // { ok: tru
|
|
|
265
305
|
await faxios.get("https://api.example.com/feed", { fetchOptions: { cache: "no-store" } });
|
|
266
306
|
```
|
|
267
307
|
|
|
268
|
-
- `fetchOptions` is
|
|
269
|
-
`
|
|
270
|
-
`
|
|
308
|
+
- Every own enumerable key of `fetchOptions` is copied into a null-prototype init passed to
|
|
309
|
+
`fetch`, including runtime extensions (Next.js `next`, Workers `cf`, undici `dispatcher`).
|
|
310
|
+
Inherited keys are ignored. faxios sets `signal`, `method`, `headers`, `body`, `duplex` and
|
|
311
|
+
`credentials` itself and ignores those keys in `fetchOptions`.
|
|
312
|
+
- `env.fetch`, `env.Request` and `env.Response` count only as own properties of `env`.
|
|
271
313
|
- Plain-object values inside `fetchOptions` are copied when configs merge; class instances are
|
|
272
314
|
passed by reference.
|
|
273
315
|
- Proxies are runtime configuration, not a faxios option. In Node, the project docs route a
|
|
@@ -319,6 +361,9 @@ export only. Instances from `create()` do not have them; import them by name ins
|
|
|
319
361
|
progress through fetch; do not promise it there.
|
|
320
362
|
9. **MEDIUM: Retrying POST by accident or by assumption.** The retry plugin skips POST and PATCH
|
|
321
363
|
unless they are listed in `methods`; a custom `retryOn` cannot override that.
|
|
364
|
+
10. **MEDIUM: Downloading a binary body without `responseType`.** The bytes are decoded as UTF-8
|
|
365
|
+
text and come back corrupted. Set `responseType: "arraybuffer"` and type the call with
|
|
366
|
+
`get<ArrayBuffer>`. Source: `src/lib/adapters/fetch.ts`.
|
|
322
367
|
|
|
323
368
|
## Completion
|
|
324
369
|
|
|
@@ -27,6 +27,23 @@ Check: `faxios.get("/health", { retry: false })` on the default export (or any i
|
|
|
27
27
|
`retry()` installed) is a type error. Use the value `.use()` returned. The option defaults and
|
|
28
28
|
ordering rules are in the SKILL.md "Use the built-in plugins from their subpaths" section.
|
|
29
29
|
|
|
30
|
+
## Define endpoints on a plugin instance
|
|
31
|
+
|
|
32
|
+
`define()` and `route()` return plain functions with no `.use()`. Install plugins first and call
|
|
33
|
+
`define()` on the value `.use()` returned, so the endpoint's per-call config types `retry`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import faxios from "@gcmdev/faxios";
|
|
37
|
+
import { retry } from "@gcmdev/faxios/plugins/retry";
|
|
38
|
+
import { z } from "zod";
|
|
39
|
+
|
|
40
|
+
const api = faxios.create({ baseURL: "https://tags.tiqcdn.com" }).use(retry());
|
|
41
|
+
const getDistro = api.define("get", "/utag/{account}/{profile}/prod/distro.zip", {
|
|
42
|
+
pathParamsSchema: z.object({ account: z.string(), profile: z.string() }),
|
|
43
|
+
});
|
|
44
|
+
await getDistro({ pathParams: { account: "acme", profile: "main" }, retry: { attempts: 2 } });
|
|
45
|
+
```
|
|
46
|
+
|
|
30
47
|
## Build a plugin with `definePlugin`
|
|
31
48
|
|
|
32
49
|
`definePlugin` comes from `@gcmdev/faxios/plugins`. It returns the same object and only changes the
|
|
@@ -68,21 +85,49 @@ Check: `faxios.get("/orders", { trace: "x" })` on an instance without the plugin
|
|
|
68
85
|
installing it before `authBearer`:
|
|
69
86
|
|
|
70
87
|
```ts
|
|
71
|
-
import faxios, {
|
|
88
|
+
import faxios, { type FaxiosContext } from "@gcmdev/faxios";
|
|
72
89
|
import { definePlugin } from "@gcmdev/faxios/plugins";
|
|
73
90
|
import { authBearer, type AuthBearerCapability } from "@gcmdev/faxios/plugins/auth-bearer";
|
|
74
91
|
|
|
92
|
+
function requireSignIn() {
|
|
93
|
+
return definePlugin({
|
|
94
|
+
name: "requireSignIn",
|
|
95
|
+
middleware: async (ctx: FaxiosContext<unknown, AuthBearerCapability>, next) => {
|
|
96
|
+
if (!(await ctx.capabilities.auth.getToken())) throw new Error("Sign in first");
|
|
97
|
+
return next(ctx);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const api = faxios
|
|
103
|
+
.create({ baseURL: "https://api.example.com" })
|
|
104
|
+
.use(authBearer(() => "token"))
|
|
105
|
+
.use(requireSignIn());
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Refresh a token after a 401
|
|
109
|
+
|
|
110
|
+
Install the refresh plugin before `authBearer` and never set `Authorization` in it. The retry
|
|
111
|
+
runs `authBearer` again, which calls `getToken` and applies its origin rule, so a third-party 401
|
|
112
|
+
is retried without the token. Share one in-flight refresh so rotating refresh tokens aren't spent twice:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import faxios, { FaxiosError } from "@gcmdev/faxios";
|
|
116
|
+
import { definePlugin } from "@gcmdev/faxios/plugins";
|
|
117
|
+
import { authBearer } from "@gcmdev/faxios/plugins/auth-bearer";
|
|
118
|
+
|
|
75
119
|
function refreshOn401(refresh: () => Promise<void>) {
|
|
120
|
+
let refreshing: Promise<void> | undefined;
|
|
76
121
|
return definePlugin({
|
|
77
122
|
name: "refreshOn401",
|
|
78
|
-
middleware: async (ctx
|
|
123
|
+
middleware: async (ctx, next) => {
|
|
79
124
|
try {
|
|
80
125
|
return await next(ctx);
|
|
81
126
|
}
|
|
82
127
|
catch (error) {
|
|
83
128
|
if (!(error instanceof FaxiosError) || error.response?.status !== 401) throw error;
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
refreshing ??= refresh().finally(() => { refreshing = undefined; });
|
|
130
|
+
await refreshing;
|
|
86
131
|
return next(ctx);
|
|
87
132
|
}
|
|
88
133
|
},
|
|
@@ -92,10 +137,13 @@ function refreshOn401(refresh: () => Promise<void>) {
|
|
|
92
137
|
let token = "old";
|
|
93
138
|
const api = faxios
|
|
94
139
|
.create({ baseURL: "https://api.example.com" })
|
|
95
|
-
.use(
|
|
96
|
-
.use(
|
|
140
|
+
.use(refreshOn401(async () => { token = "new"; }))
|
|
141
|
+
.use(authBearer(() => token));
|
|
97
142
|
```
|
|
98
143
|
|
|
144
|
+
Calling `next` again re-sends the body. A stream body is used up by the first try; sending it
|
|
145
|
+
again rejects with `ERR_BAD_REQUEST`.
|
|
146
|
+
|
|
99
147
|
## Rules `.use()` enforces at runtime
|
|
100
148
|
|
|
101
149
|
- `name` must be a non-empty own string, otherwise `ERR_BAD_OPTION_VALUE`.
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @ts-self-types="./index.d.ts"
|
|
2
|
+
/* eslint-disable no-barrel-files/no-barrel-files */
|
|
3
|
+
import faxios from "./lib/faxios.ts";
|
|
4
|
+
import type { FaxiosInstance, FaxiosStatic } from "./lib/faxios.ts";
|
|
5
|
+
import type { CreateFaxiosDefaults } from "./lib/types.ts";
|
|
6
|
+
|
|
7
|
+
// Static re-exports so the emitted .d.ts references source modules.
|
|
8
|
+
// zshy rewrites .ts -> .js for static import/export, but NOT for inline
|
|
9
|
+
// import() type expressions (ImportTypeNode). Destructuring these off the
|
|
10
|
+
// runtime instance forced TS to emit the broken inline `import("./x.ts")` form.
|
|
11
|
+
export { default as Faxios } from "./lib/core/Faxios.ts";
|
|
12
|
+
export { default as FaxiosError, isSchemaValidationError } from "./lib/core/FaxiosError.ts";
|
|
13
|
+
export type { SchemaValidationError } from "./lib/core/FaxiosError.ts";
|
|
14
|
+
export { default as Cancel, default as CanceledError } from "./lib/cancel/CanceledError.ts";
|
|
15
|
+
export { default as isCancel } from "./lib/cancel/isCancel.ts";
|
|
16
|
+
export { default as isFaxiosError } from "./lib/helpers/isFaxiosError.ts";
|
|
17
|
+
export { default as toFormData } from "./lib/helpers/toFormData.ts";
|
|
18
|
+
export { default as FaxiosHeaders } from "./lib/core/FaxiosHeaders.ts";
|
|
19
|
+
export { default as HttpStatusCode } from "./lib/helpers/HttpStatusCode.ts";
|
|
20
|
+
export { default as mergeConfig } from "./lib/core/mergeConfig.ts";
|
|
21
|
+
export { VERSION } from "./lib/env/data.ts";
|
|
22
|
+
|
|
23
|
+
export type { FaxiosInstance, FaxiosStatic } from "./lib/faxios.ts";
|
|
24
|
+
export type { StandardSchemaV1 } from "./lib/types/standard-schema.ts";
|
|
25
|
+
export type { BasePerCallConfig, DefineConfig, DefinedEndpoint, FaxiosLike, PerCallConfig } from "./lib/core/define.ts";
|
|
26
|
+
export type { RouteBuilder, RouteConfig, RouteMethodConfig } from "./lib/core/route.ts";
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
CreateFaxiosDefaults,
|
|
30
|
+
FaxiosConfigHeaders,
|
|
31
|
+
FaxiosContext,
|
|
32
|
+
FaxiosHeaderValue,
|
|
33
|
+
FaxiosMiddleware,
|
|
34
|
+
FaxiosNext,
|
|
35
|
+
FaxiosPlugin,
|
|
36
|
+
FaxiosPromise,
|
|
37
|
+
FaxiosRequestConfig,
|
|
38
|
+
FaxiosRequestHeaders,
|
|
39
|
+
FaxiosResponse,
|
|
40
|
+
FaxiosResponseHeadersLike,
|
|
41
|
+
GenericHTMLFormElement,
|
|
42
|
+
HeadersDefaults,
|
|
43
|
+
InternalFaxiosRequestConfig,
|
|
44
|
+
Method,
|
|
45
|
+
RawFaxiosRequestConfig,
|
|
46
|
+
RawFaxiosRequestHeaders,
|
|
47
|
+
ResponseType
|
|
48
|
+
} from "./lib/types.ts";
|
|
49
|
+
|
|
50
|
+
// Instance-synthesized members (no 1:1 source module). Types are self-contained
|
|
51
|
+
// (all/formToJSON) or annotated with statically imported types (create), so no
|
|
52
|
+
// inline import() is emitted. Runtime values are the same references as on the instance.
|
|
53
|
+
export const all: FaxiosStatic["all"] = faxios.all;
|
|
54
|
+
export const formToJSON: FaxiosStatic["formToJSON"] = faxios.formToJSON;
|
|
55
|
+
export const create: (instanceConfig?: CreateFaxiosDefaults) => FaxiosInstance = faxios.create;
|
|
56
|
+
|
|
57
|
+
export { faxios as default };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import FaxiosError from "../core/FaxiosError.js";
|
|
4
|
+
import type FaxiosHeaders from "../core/FaxiosHeaders.js";
|
|
5
|
+
import type {
|
|
6
|
+
FaxiosRequestHeaders,
|
|
7
|
+
FaxiosResponse,
|
|
8
|
+
InternalFaxiosRequestConfig
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
import utils from "../utils.js";
|
|
11
|
+
|
|
12
|
+
export type ComposedSignal = AbortSignal & { unsubscribe?: () => void; };
|
|
13
|
+
export type PendingBodyErrorRef = { value: (FaxiosError & { request?: unknown; }) | null; };
|
|
14
|
+
|
|
15
|
+
const _textEncoder: TextEncoder | undefined =
|
|
16
|
+
typeof globalThis.TextEncoder === "function" ? new globalThis.TextEncoder() : undefined;
|
|
17
|
+
|
|
18
|
+
// ponytail: exported for unit testing only — not part of public API
|
|
19
|
+
export const checkDeclaredContentLength = (
|
|
20
|
+
responseHeaders: FaxiosHeaders,
|
|
21
|
+
hasMaxContentLength: boolean,
|
|
22
|
+
maxContentLength: number,
|
|
23
|
+
config: InternalFaxiosRequestConfig,
|
|
24
|
+
request: unknown
|
|
25
|
+
): void => {
|
|
26
|
+
if (!hasMaxContentLength) return;
|
|
27
|
+
const declaredLength = utils.toFiniteNumber(
|
|
28
|
+
(responseHeaders.getContentLength as () => unknown)()
|
|
29
|
+
);
|
|
30
|
+
if (declaredLength != null && declaredLength > maxContentLength) {
|
|
31
|
+
throw new FaxiosError(
|
|
32
|
+
"maxContentLength size of " + maxContentLength + " exceeded",
|
|
33
|
+
FaxiosError.ERR_BAD_RESPONSE,
|
|
34
|
+
config,
|
|
35
|
+
request
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ponytail: exported for unit testing only — not part of public API
|
|
41
|
+
export const checkMaterializedSize = (
|
|
42
|
+
responseData: unknown,
|
|
43
|
+
hasMaxContentLength: boolean,
|
|
44
|
+
maxContentLength: number,
|
|
45
|
+
isStreamResponse: boolean,
|
|
46
|
+
supportsResponseStream: boolean,
|
|
47
|
+
config: InternalFaxiosRequestConfig,
|
|
48
|
+
request: unknown
|
|
49
|
+
): void => {
|
|
50
|
+
if (!hasMaxContentLength || supportsResponseStream || isStreamResponse) return;
|
|
51
|
+
if (responseData == null) return;
|
|
52
|
+
let materializedSize: number | undefined;
|
|
53
|
+
const rd = responseData as Record<string, unknown>;
|
|
54
|
+
if (typeof rd["byteLength"] === "number") {
|
|
55
|
+
materializedSize = rd["byteLength"];
|
|
56
|
+
}
|
|
57
|
+
else if (typeof rd["size"] === "number") {
|
|
58
|
+
materializedSize = rd["size"];
|
|
59
|
+
}
|
|
60
|
+
else if (typeof responseData === "string") {
|
|
61
|
+
materializedSize = _textEncoder
|
|
62
|
+
? _textEncoder.encode(responseData).byteLength
|
|
63
|
+
: responseData.length;
|
|
64
|
+
}
|
|
65
|
+
if (typeof materializedSize === "number" && materializedSize > maxContentLength) {
|
|
66
|
+
throw new FaxiosError(
|
|
67
|
+
"maxContentLength size of " + maxContentLength + " exceeded",
|
|
68
|
+
FaxiosError.ERR_BAD_RESPONSE,
|
|
69
|
+
config,
|
|
70
|
+
request
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ponytail: exported for unit testing only — not part of public API
|
|
76
|
+
export const handleFetchCaughtError = (
|
|
77
|
+
err: unknown,
|
|
78
|
+
composedSignal: ComposedSignal | undefined,
|
|
79
|
+
pendingBodyErrorRef: PendingBodyErrorRef,
|
|
80
|
+
config: InternalFaxiosRequestConfig,
|
|
81
|
+
request: unknown
|
|
82
|
+
): never => {
|
|
83
|
+
// Safari can surface fetch aborts as a DOMException-like object whose
|
|
84
|
+
// branded getters throw. Prefer our composed signal reason before reading
|
|
85
|
+
// the caught error, preserving timeout vs cancellation semantics.
|
|
86
|
+
if (
|
|
87
|
+
composedSignal &&
|
|
88
|
+
composedSignal.aborted &&
|
|
89
|
+
composedSignal.reason instanceof FaxiosError
|
|
90
|
+
) {
|
|
91
|
+
const canceledError = composedSignal.reason;
|
|
92
|
+
canceledError.config = config;
|
|
93
|
+
request && (canceledError.request = request);
|
|
94
|
+
err !== canceledError && (canceledError.cause = err as Error);
|
|
95
|
+
throw canceledError;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Surface a maxBodyLength violation we raised while the request body was
|
|
99
|
+
// being streamed. Matching by identity keeps the error deterministic across
|
|
100
|
+
// runtimes and avoids prototype-pollution reads.
|
|
101
|
+
if (pendingBodyErrorRef.value) {
|
|
102
|
+
const _pbe = pendingBodyErrorRef.value;
|
|
103
|
+
request && !_pbe.request && (_pbe.request = request);
|
|
104
|
+
throw _pbe;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Re-throw FaxiosErrors we raised synchronously without re-wrapping them.
|
|
108
|
+
if (err instanceof FaxiosError) {
|
|
109
|
+
request && !err.request && (err.request = request);
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _err = err as Record<string, unknown> & Error;
|
|
114
|
+
if (
|
|
115
|
+
_err.name === "TypeError" &&
|
|
116
|
+
/Load failed|fetch/i.test(_err.message)
|
|
117
|
+
) {
|
|
118
|
+
throw Object.assign(
|
|
119
|
+
new FaxiosError(
|
|
120
|
+
"Network Error",
|
|
121
|
+
FaxiosError.ERR_NETWORK,
|
|
122
|
+
config,
|
|
123
|
+
request,
|
|
124
|
+
_err["response"] as FaxiosResponse | undefined
|
|
125
|
+
),
|
|
126
|
+
{
|
|
127
|
+
cause: _err["cause"] || _err,
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw FaxiosError.from(
|
|
133
|
+
_err,
|
|
134
|
+
_err["code"] as string | undefined,
|
|
135
|
+
config,
|
|
136
|
+
request,
|
|
137
|
+
_err["response"] as FaxiosResponse | undefined
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ponytail: exported for unit testing only — not part of public API
|
|
142
|
+
export const cleanFormDataContentType = (
|
|
143
|
+
data: unknown,
|
|
144
|
+
headers: FaxiosRequestHeaders
|
|
145
|
+
): void => {
|
|
146
|
+
if (utils.isFormData(data)) {
|
|
147
|
+
const contentType = headers.getContentType() as null | string | undefined;
|
|
148
|
+
if (
|
|
149
|
+
contentType &&
|
|
150
|
+
/^multipart\/form-data/i.test(contentType) &&
|
|
151
|
+
!/boundary=/i.test(contentType)
|
|
152
|
+
) {
|
|
153
|
+
headers.delete("content-type");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ponytail: exported for unit testing only — not part of public API
|
|
159
|
+
export const encodeBodyIfNeeded = (
|
|
160
|
+
data: unknown,
|
|
161
|
+
headers: Record<string, unknown>,
|
|
162
|
+
encode: (str: string) => Promise<Uint8Array> | Uint8Array
|
|
163
|
+
): Promise<unknown> | unknown => {
|
|
164
|
+
if (!utils.isString(data) || utils.findKey(headers, "content-type")) return data;
|
|
165
|
+
// ponytail: skip await when TextEncoder is sync (avoids microtask hop on every string body)
|
|
166
|
+
return encode(data as string);
|
|
167
|
+
};
|