@theholocron/http-client 1.56.0 → 1.57.0
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 +14 -0
- package/dist/index.d.mts +27 -1
- package/dist/index.mjs +32 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,20 @@ const client = createRestClient({ baseUrl: "...", token: "...", logger, errors }
|
|
|
62
62
|
typically expected and already handled by the caller (a 404 from a
|
|
63
63
|
"does this exist" check, etc.).
|
|
64
64
|
|
|
65
|
+
### `onRetry(listener)`
|
|
66
|
+
|
|
67
|
+
Hear about every retry any client in this process is about to make, so a command line can say "retrying GitHub, attempt 2 of 3" instead of sitting silent. Returns a function that stops listening.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const stop = onRetry(({ vendor, attempt, of, code, delayMs }) => {
|
|
71
|
+
console.error(`retrying ${vendor}, attempt ${attempt} of ${of} (${code}, in ${delayMs} ms)`);
|
|
72
|
+
});
|
|
73
|
+
// …requests…
|
|
74
|
+
stop();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It is per process, not per client, so nothing has to be handed to each client. It needs one copy of this package in the process (a workspace pins it); with two, the other copy's retries go unseen. A listener that throws is ignored.
|
|
78
|
+
|
|
65
79
|
### `createResolveToken(config)`
|
|
66
80
|
|
|
67
81
|
Factory for the standard token resolution used by all holocron plugins. Resolution order:
|
package/dist/index.d.mts
CHANGED
|
@@ -117,4 +117,30 @@ interface RestClient {
|
|
|
117
117
|
}
|
|
118
118
|
declare function createRestClient(config: RestClientConfig): RestClient;
|
|
119
119
|
//#endregion
|
|
120
|
-
|
|
120
|
+
//#region src/retry-events.d.ts
|
|
121
|
+
/** A request about to be sent again after a transport failure. */
|
|
122
|
+
interface RetryEvent {
|
|
123
|
+
/** The client's `vendor` label, e.g. `GitHub`. */
|
|
124
|
+
vendor?: string;
|
|
125
|
+
method: string;
|
|
126
|
+
path: string;
|
|
127
|
+
/** What went wrong (`ECONNRESET`, `UND_ERR_CONNECT_TIMEOUT`); a failure with no known cause is never retried. */
|
|
128
|
+
code: string;
|
|
129
|
+
/** The number of the send that is about to happen: 2 for the first retry. */
|
|
130
|
+
attempt: number;
|
|
131
|
+
/** How many sends there can be in all (the retries plus the first). */
|
|
132
|
+
of: number;
|
|
133
|
+
/** How long it waits before sending. */
|
|
134
|
+
delayMs: number;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Hear about every retry any client in this process is about to make, so a command line can say "retrying GitHub,
|
|
138
|
+
* attempt 2 of 3" instead of sitting silent. Returns the way to stop listening. A listener that throws is ignored: a
|
|
139
|
+
* display problem must not fail a request.
|
|
140
|
+
*
|
|
141
|
+
* This is per process, not per client, so a command need not hand a hook to every client it builds. It relies on there
|
|
142
|
+
* being one copy of this package, which the workspace already requires; with two, retries by the other copy go unseen.
|
|
143
|
+
*/
|
|
144
|
+
declare function onRetry(listener: (event: RetryEvent) => void): () => void;
|
|
145
|
+
//#endregion
|
|
146
|
+
export { AuthError, ProviderApiError, type RequestOptions, type ResolveTokenConfig, type ResolveTokenInput, type RestClient, type RestClientConfig, type RetryEvent, createResolveToken, createRestClient, onRetry };
|
package/dist/index.mjs
CHANGED
|
@@ -47,6 +47,26 @@ var ProviderApiError = class extends Error {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
//#endregion
|
|
50
|
+
//#region src/retry-events.ts
|
|
51
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
52
|
+
/**
|
|
53
|
+
* Hear about every retry any client in this process is about to make, so a command line can say "retrying GitHub,
|
|
54
|
+
* attempt 2 of 3" instead of sitting silent. Returns the way to stop listening. A listener that throws is ignored: a
|
|
55
|
+
* display problem must not fail a request.
|
|
56
|
+
*
|
|
57
|
+
* This is per process, not per client, so a command need not hand a hook to every client it builds. It relies on there
|
|
58
|
+
* being one copy of this package, which the workspace already requires; with two, retries by the other copy go unseen.
|
|
59
|
+
*/
|
|
60
|
+
function onRetry(listener) {
|
|
61
|
+
listeners.add(listener);
|
|
62
|
+
return () => void listeners.delete(listener);
|
|
63
|
+
}
|
|
64
|
+
function notifyRetry(event) {
|
|
65
|
+
for (const listener of [...listeners]) try {
|
|
66
|
+
listener(event);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
50
70
|
//#region src/transport.ts
|
|
51
71
|
/**
|
|
52
72
|
* Node's `fetch` rejects with a bare `TypeError: fetch failed`; the reason (a DNS miss, a connect timeout, a reset) is on
|
|
@@ -138,7 +158,17 @@ function createRestClient(config) {
|
|
|
138
158
|
code,
|
|
139
159
|
attempt: attempt + 1
|
|
140
160
|
}, "retrying");
|
|
141
|
-
|
|
161
|
+
const delayMs = retryDelay * 3 ** attempt;
|
|
162
|
+
notifyRetry({
|
|
163
|
+
...vendor ? { vendor } : {},
|
|
164
|
+
method: init.method,
|
|
165
|
+
path,
|
|
166
|
+
code,
|
|
167
|
+
attempt: attempt + 2,
|
|
168
|
+
of: maxRetries + 1,
|
|
169
|
+
delayMs
|
|
170
|
+
});
|
|
171
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
142
172
|
continue;
|
|
143
173
|
}
|
|
144
174
|
const apiErr = new ProviderApiError(`${tag} failed: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}${code ? ` (${code})` : ""}`, 0, void 0);
|
|
@@ -165,4 +195,4 @@ function createRestClient(config) {
|
|
|
165
195
|
};
|
|
166
196
|
}
|
|
167
197
|
//#endregion
|
|
168
|
-
export { AuthError, ProviderApiError, createResolveToken, createRestClient };
|
|
198
|
+
export { AuthError, ProviderApiError, createResolveToken, createRestClient, onRetry };
|