@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.
Files changed (186) hide show
  1. package/LICENSE +8 -0
  2. package/dist/index.d.ts +4 -3
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/adapters/fetch-helpers.d.ts +2 -1
  7. package/dist/lib/adapters/fetch-helpers.d.ts.map +1 -0
  8. package/dist/lib/adapters/fetch.d.ts +1 -0
  9. package/dist/lib/adapters/fetch.d.ts.map +1 -0
  10. package/dist/lib/adapters/fetch.js +86 -26
  11. package/dist/lib/adapters/fetch.js.map +1 -1
  12. package/dist/lib/cancel/CanceledError.d.ts +2 -1
  13. package/dist/lib/cancel/CanceledError.d.ts.map +1 -0
  14. package/dist/lib/cancel/isCancel.d.ts +1 -0
  15. package/dist/lib/cancel/isCancel.d.ts.map +1 -0
  16. package/dist/lib/core/Faxios.d.ts +4 -3
  17. package/dist/lib/core/Faxios.d.ts.map +1 -0
  18. package/dist/lib/core/Faxios.js +8 -4
  19. package/dist/lib/core/Faxios.js.map +1 -1
  20. package/dist/lib/core/FaxiosError.d.ts +3 -2
  21. package/dist/lib/core/FaxiosError.d.ts.map +1 -0
  22. package/dist/lib/core/FaxiosHeaders.d.ts +9 -8
  23. package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -0
  24. package/dist/lib/core/buildFullPath.d.ts +1 -0
  25. package/dist/lib/core/buildFullPath.d.ts.map +1 -0
  26. package/dist/lib/core/define.d.ts +20 -3
  27. package/dist/lib/core/define.d.ts.map +1 -0
  28. package/dist/lib/core/define.js.map +1 -1
  29. package/dist/lib/core/dispatchRequest.d.ts +2 -1
  30. package/dist/lib/core/dispatchRequest.d.ts.map +1 -0
  31. package/dist/lib/core/mergeConfig.d.ts +3 -2
  32. package/dist/lib/core/mergeConfig.d.ts.map +1 -0
  33. package/dist/lib/core/mergeConfig.js +40 -2
  34. package/dist/lib/core/mergeConfig.js.map +1 -1
  35. package/dist/lib/core/prepareRequest.d.ts +1 -0
  36. package/dist/lib/core/prepareRequest.d.ts.map +1 -0
  37. package/dist/lib/core/route.d.ts +10 -1
  38. package/dist/lib/core/route.d.ts.map +1 -0
  39. package/dist/lib/core/route.js +2 -5
  40. package/dist/lib/core/route.js.map +1 -1
  41. package/dist/lib/core/settle.d.ts +1 -0
  42. package/dist/lib/core/settle.d.ts.map +1 -0
  43. package/dist/lib/core/transformData.d.ts +3 -2
  44. package/dist/lib/core/transformData.d.ts.map +1 -0
  45. package/dist/lib/core/validateSchema.d.ts +3 -2
  46. package/dist/lib/core/validateSchema.d.ts.map +1 -0
  47. package/dist/lib/defaults/index.d.ts +1 -0
  48. package/dist/lib/defaults/index.d.ts.map +1 -0
  49. package/dist/lib/defaults/transitional.d.ts +1 -0
  50. package/dist/lib/defaults/transitional.d.ts.map +1 -0
  51. package/dist/lib/env/data.d.ts +2 -1
  52. package/dist/lib/env/data.d.ts.map +1 -0
  53. package/dist/lib/env/data.js +1 -1
  54. package/dist/lib/faxios.d.ts +59 -38
  55. package/dist/lib/faxios.d.ts.map +1 -0
  56. package/dist/lib/faxios.js.map +1 -1
  57. package/dist/lib/helpers/FaxiosURLSearchParams.d.ts +1 -0
  58. package/dist/lib/helpers/FaxiosURLSearchParams.d.ts.map +1 -0
  59. package/dist/lib/helpers/HttpStatusCode.d.ts +1 -0
  60. package/dist/lib/helpers/HttpStatusCode.d.ts.map +1 -0
  61. package/dist/lib/helpers/buildURL.d.ts +1 -0
  62. package/dist/lib/helpers/buildURL.d.ts.map +1 -0
  63. package/dist/lib/helpers/cloneConfig.d.ts +1 -1
  64. package/dist/lib/helpers/cloneConfig.d.ts.map +1 -0
  65. package/dist/lib/helpers/cloneConfig.js +2 -2
  66. package/dist/lib/helpers/cloneConfig.js.map +1 -1
  67. package/dist/lib/helpers/combineURLs.d.ts +1 -0
  68. package/dist/lib/helpers/combineURLs.d.ts.map +1 -0
  69. package/dist/lib/helpers/composeSignals.d.ts +1 -0
  70. package/dist/lib/helpers/composeSignals.d.ts.map +1 -0
  71. package/dist/lib/helpers/cookies.d.ts +2 -1
  72. package/dist/lib/helpers/cookies.d.ts.map +1 -0
  73. package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts +1 -0
  74. package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts.map +1 -0
  75. package/dist/lib/helpers/formDataToJSON.d.ts +2 -1
  76. package/dist/lib/helpers/formDataToJSON.d.ts.map +1 -0
  77. package/dist/lib/helpers/formDataToJSON.js +1 -1
  78. package/dist/lib/helpers/formDataToJSON.js.map +1 -1
  79. package/dist/lib/helpers/isAbsoluteURL.d.ts +1 -0
  80. package/dist/lib/helpers/isAbsoluteURL.d.ts.map +1 -0
  81. package/dist/lib/helpers/isFaxiosError.d.ts +1 -0
  82. package/dist/lib/helpers/isFaxiosError.d.ts.map +1 -0
  83. package/dist/lib/helpers/isURLSameOrigin.d.ts +1 -0
  84. package/dist/lib/helpers/isURLSameOrigin.d.ts.map +1 -0
  85. package/dist/lib/helpers/parseHeaders.d.ts +2 -1
  86. package/dist/lib/helpers/parseHeaders.d.ts.map +1 -0
  87. package/dist/lib/helpers/progressEventReducer.d.ts +1 -0
  88. package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -0
  89. package/dist/lib/helpers/sanitizeHeaderValue.d.ts +1 -0
  90. package/dist/lib/helpers/sanitizeHeaderValue.d.ts.map +1 -0
  91. package/dist/lib/helpers/speedometer.d.ts +1 -0
  92. package/dist/lib/helpers/speedometer.d.ts.map +1 -0
  93. package/dist/lib/helpers/substitutePathParams.d.ts +1 -0
  94. package/dist/lib/helpers/substitutePathParams.d.ts.map +1 -0
  95. package/dist/lib/helpers/substitutePathParams.js +11 -3
  96. package/dist/lib/helpers/substitutePathParams.js.map +1 -1
  97. package/dist/lib/helpers/throttle.d.ts +1 -0
  98. package/dist/lib/helpers/throttle.d.ts.map +1 -0
  99. package/dist/lib/helpers/toFormData.d.ts +1 -0
  100. package/dist/lib/helpers/toFormData.d.ts.map +1 -0
  101. package/dist/lib/helpers/toFormData.js +4 -2
  102. package/dist/lib/helpers/toFormData.js.map +1 -1
  103. package/dist/lib/helpers/toURLEncodedForm.d.ts +1 -0
  104. package/dist/lib/helpers/toURLEncodedForm.d.ts.map +1 -0
  105. package/dist/lib/helpers/trackStream.d.ts +1 -0
  106. package/dist/lib/helpers/trackStream.d.ts.map +1 -0
  107. package/dist/lib/helpers/validator.d.ts +2 -1
  108. package/dist/lib/helpers/validator.d.ts.map +1 -0
  109. package/dist/lib/platform.d.ts +1 -0
  110. package/dist/lib/platform.d.ts.map +1 -0
  111. package/dist/lib/plugins/attempt.d.ts +1 -0
  112. package/dist/lib/plugins/attempt.d.ts.map +1 -0
  113. package/dist/lib/plugins/authBearer.d.ts +10 -5
  114. package/dist/lib/plugins/authBearer.d.ts.map +1 -0
  115. package/dist/lib/plugins/authBearer.js +6 -0
  116. package/dist/lib/plugins/authBearer.js.map +1 -1
  117. package/dist/lib/plugins/definePlugin.d.ts +4 -3
  118. package/dist/lib/plugins/definePlugin.d.ts.map +1 -0
  119. package/dist/lib/plugins/retry.d.ts +24 -23
  120. package/dist/lib/plugins/retry.d.ts.map +1 -0
  121. package/dist/lib/plugins/timing.d.ts +9 -8
  122. package/dist/lib/plugins/timing.d.ts.map +1 -0
  123. package/dist/lib/types/standard-schema.d.ts +7 -6
  124. package/dist/lib/types/standard-schema.d.ts.map +1 -0
  125. package/dist/lib/types.d.ts +104 -92
  126. package/dist/lib/types.d.ts.map +1 -0
  127. package/dist/lib/utils.d.ts +5 -3
  128. package/dist/lib/utils.d.ts.map +1 -0
  129. package/dist/lib/utils.js +9 -2
  130. package/dist/lib/utils.js.map +1 -1
  131. package/package.json +8 -4
  132. package/skills/configure-requests/SKILL.md +53 -8
  133. package/skills/configure-requests/references/plugins.md +54 -6
  134. package/src/index.ts +57 -0
  135. package/src/lib/adapters/fetch-helpers.ts +167 -0
  136. package/src/lib/adapters/fetch.ts +871 -0
  137. package/src/lib/cancel/CanceledError.ts +23 -0
  138. package/src/lib/cancel/isCancel.ts +10 -0
  139. package/src/lib/core/Faxios.ts +517 -0
  140. package/src/lib/core/FaxiosError.ts +283 -0
  141. package/src/lib/core/FaxiosHeaders.ts +529 -0
  142. package/src/lib/core/buildFullPath.ts +51 -0
  143. package/src/lib/core/define.ts +127 -0
  144. package/src/lib/core/dispatchRequest.ts +171 -0
  145. package/src/lib/core/mergeConfig.ts +213 -0
  146. package/src/lib/core/prepareRequest.ts +155 -0
  147. package/src/lib/core/route.ts +65 -0
  148. package/src/lib/core/settle.ts +20 -0
  149. package/src/lib/core/transformData.ts +23 -0
  150. package/src/lib/core/validateSchema.ts +53 -0
  151. package/src/lib/defaults/index.ts +221 -0
  152. package/src/lib/defaults/transitional.ts +9 -0
  153. package/src/lib/env/data.ts +1 -0
  154. package/src/lib/faxios.ts +306 -0
  155. package/src/lib/helpers/FaxiosURLSearchParams.ts +52 -0
  156. package/src/lib/helpers/HttpStatusCode.ts +77 -0
  157. package/src/lib/helpers/buildURL.ts +77 -0
  158. package/src/lib/helpers/cloneConfig.ts +22 -0
  159. package/src/lib/helpers/combineURLs.ts +11 -0
  160. package/src/lib/helpers/composeSignals.ts +74 -0
  161. package/src/lib/helpers/cookies.ts +68 -0
  162. package/src/lib/helpers/deprecatedMethod.ts +32 -0
  163. package/src/lib/helpers/estimateDataURLDecodedBytes.ts +95 -0
  164. package/src/lib/helpers/formDataToJSON.ts +122 -0
  165. package/src/lib/helpers/isAbsoluteURL.ts +13 -0
  166. package/src/lib/helpers/isFaxiosError.ts +19 -0
  167. package/src/lib/helpers/isURLSameOrigin.ts +16 -0
  168. package/src/lib/helpers/parseHeaders.ts +76 -0
  169. package/src/lib/helpers/progressEventReducer.ts +57 -0
  170. package/src/lib/helpers/sanitizeHeaderValue.ts +61 -0
  171. package/src/lib/helpers/speedometer.ts +55 -0
  172. package/src/lib/helpers/substitutePathParams.ts +23 -0
  173. package/src/lib/helpers/throttle.ts +45 -0
  174. package/src/lib/helpers/toFormData.ts +298 -0
  175. package/src/lib/helpers/toURLEncodedForm.ts +14 -0
  176. package/src/lib/helpers/trackStream.ts +123 -0
  177. package/src/lib/helpers/validator.ts +114 -0
  178. package/src/lib/platform.ts +44 -0
  179. package/src/lib/plugins/attempt.ts +3 -0
  180. package/src/lib/plugins/authBearer.ts +129 -0
  181. package/src/lib/plugins/definePlugin.ts +34 -0
  182. package/src/lib/plugins/retry.ts +245 -0
  183. package/src/lib/plugins/timing.ts +76 -0
  184. package/src/lib/types/standard-schema.ts +58 -0
  185. package/src/lib/types.ts +496 -0
  186. 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.0'
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)` returns the URL faxios would request, without sending anything.
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 rejects with
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 spread into the init object passed to `fetch`. faxios sets `signal`, `method`,
269
- `headers`, `body`, `duplex` and `credentials` itself, and those override the same keys in
270
- `fetchOptions`.
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, { FaxiosError, type FaxiosContext } from "@gcmdev/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: FaxiosContext<unknown, AuthBearerCapability>, next) => {
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
- await refresh();
85
- ctx.config.headers.set("Authorization", `Bearer ${await ctx.capabilities.auth.getToken()}`);
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(authBearer(() => token))
96
- .use(refreshOn401(async () => { token = "new"; }));
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
+ };