@sdxc/outbound 2026.10.24 → 2026.10.25

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/README.md CHANGED
@@ -102,6 +102,9 @@ Answers the final response whatever its status. Beside every `checkUrl` option:
102
102
  - `maxRedirects`: default `5`.
103
103
  - `resolve`: when `true`, resolves every hop's name with `resolveHost` and refuses it unless every
104
104
  address is public. Default `false`; skipped with `hosts: "any"`.
105
+ - `sign`: `(request) => Promise<Result<Request, Error>>`, run on each hop's request before it is
106
+ sent, such as `webBotAuthSigner` from `@sdxc/http-signatures/web-bot-auth`. A failure stops the
107
+ walk as `signing-failed`.
105
108
 
106
109
  ### `resolveHost(url: URL, options?: { signal?: AbortSignal }): Promise<Result<string[], OutboundError>>`
107
110
 
@@ -145,6 +148,7 @@ for `invalid-url`, or `""` for a body with no URL), and `retryable`.
145
148
  | `refused-host` | No | A reserved name, a single label, or no address |
146
149
  | `refused-address` | No | A non-public literal, or a non-public DNS answer |
147
150
  | `too-many-redirects` | No | The chain ran past `maxRedirects` |
151
+ | `signing-failed` | No | The `sign` option answered a failure |
148
152
  | `too-large` | No | The body passed `maxBytes` |
149
153
  | `timeout` | Yes | The deadline passed |
150
154
  | `network` | Yes | `fetch` rejected, a body broke off, or DNS failed |
package/dist/error.d.ts CHANGED
@@ -7,11 +7,11 @@
7
7
  * @copyright Sergio Xalambrí 2026
8
8
  */
9
9
  /**
10
- * Why an outbound request was refused or failed. Each `refused-*` code is decided
11
- * before a request leaves; `timeout` and `network` are the only outcomes a retry can
12
- * change.
10
+ * Why an outbound request was refused or failed. Each `refused-*` code, and
11
+ * `signing-failed`, is decided before a request leaves; `timeout` and `network` are the
12
+ * only outcomes a retry can change.
13
13
  */
14
- export type OutboundErrorCode = "invalid-url" | "refused-scheme" | "refused-credentials" | "refused-port" | "refused-host" | "refused-address" | "too-many-redirects" | "too-large" | "timeout" | "network";
14
+ export type OutboundErrorCode = "invalid-url" | "refused-scheme" | "refused-credentials" | "refused-port" | "refused-host" | "refused-address" | "too-many-redirects" | "signing-failed" | "too-large" | "timeout" | "network";
15
15
  /** Returned inside a `Failure` by every function in this package, never thrown. */
16
16
  export declare class OutboundError extends Error {
17
17
  name: string;
package/dist/follow.d.ts CHANGED
@@ -30,6 +30,12 @@ export interface FollowOptions extends CheckOptions {
30
30
  resolve?: boolean;
31
31
  /** The caller's own abort, combined with `timeout`. */
32
32
  signal?: AbortSignal;
33
+ /**
34
+ * Signs each hop's request before it is sent, such as with a Web Bot Auth signature, so
35
+ * every origin in the chain receives a signature over its own authority. A failure
36
+ * stops the walk as `signing-failed`.
37
+ */
38
+ sign?: (request: Request) => Promise<Result<Request, Error>>;
33
39
  }
34
40
  /** The last response of a chain, where it came from, and how many hops it took. */
35
41
  export interface Followed {
package/dist/follow.js CHANGED
@@ -40,15 +40,25 @@ export async function follow(input, options = {}) {
40
40
  if (isFailure(resolved))
41
41
  return resolved;
42
42
  }
43
+ let request = new Request(url, {
44
+ method: options.method ?? "GET",
45
+ headers,
46
+ signal,
47
+ redirect: "manual",
48
+ credentials: "omit",
49
+ });
50
+ if (options.sign) {
51
+ let signed = await options.sign(request);
52
+ if (isFailure(signed)) {
53
+ return failure(new OutboundError("signing-failed", url.href, `Could not sign ${url.href}`, {
54
+ cause: signed.error,
55
+ }));
56
+ }
57
+ request = signed.data;
58
+ }
43
59
  let response;
44
60
  try {
45
- response = await fetch(url, {
46
- method: options.method ?? "GET",
47
- headers,
48
- signal,
49
- redirect: "manual",
50
- credentials: "omit",
51
- });
61
+ response = await fetch(request);
52
62
  }
53
63
  catch (error) {
54
64
  return failure(failedWith(url.href, error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdxc/outbound",
3
- "version": "2026.10.24",
3
+ "version": "2026.10.25",
4
4
  "description": "Check, follow and read URLs a stranger chose: public hosts on every redirect, one deadline, bounded bodies",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,12 +8,12 @@
8
8
  ".": "./dist/index.js"
9
9
  },
10
10
  "dependencies": {
11
- "@sdxc/doh": "2026.10.24",
11
+ "@sdxc/doh": "2026.10.25",
12
12
  "@sdxc/duration": "2026.10.13",
13
13
  "@sdxc/ip": "2026.10.13",
14
14
  "@sdxc/result": "2026.10.13"
15
15
  },
16
- "gitHead": "f9db0f8f1f51a256e74b94b0f4b1db4be9880370",
16
+ "gitHead": "1440b969c0a3e3808dfcee3f17d2ef43915f32a8",
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },