@lovrozagar/oat 0.7.4 → 0.7.6

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 (52) hide show
  1. package/README.md +175 -146
  2. package/dist/cli.js +26 -1
  3. package/dist/config/define-config.d.ts +17 -0
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +1 -0
  8. package/dist/report/console.d.ts.map +1 -1
  9. package/dist/report/console.js +1 -0
  10. package/dist/report/console.js.map +1 -1
  11. package/dist/report/render.d.ts +11 -0
  12. package/dist/report/render.d.ts.map +1 -1
  13. package/dist/report/render.js +9 -0
  14. package/dist/report/render.js.map +1 -1
  15. package/dist/runtime/checks.d.ts.map +1 -1
  16. package/dist/runtime/checks.js +120 -58
  17. package/dist/runtime/checks.js.map +1 -1
  18. package/dist/runtime/client.d.ts +22 -2
  19. package/dist/runtime/client.d.ts.map +1 -1
  20. package/dist/runtime/client.js +72 -8
  21. package/dist/runtime/client.js.map +1 -1
  22. package/dist/runtime/effects.d.ts +43 -0
  23. package/dist/runtime/effects.d.ts.map +1 -0
  24. package/dist/runtime/effects.js +141 -0
  25. package/dist/runtime/effects.js.map +1 -0
  26. package/dist/runtime/exchanges.d.ts +37 -0
  27. package/dist/runtime/exchanges.d.ts.map +1 -0
  28. package/dist/runtime/exchanges.js +298 -0
  29. package/dist/runtime/exchanges.js.map +1 -0
  30. package/dist/runtime/network.d.ts +74 -0
  31. package/dist/runtime/network.d.ts.map +1 -0
  32. package/dist/runtime/network.js +270 -0
  33. package/dist/runtime/network.js.map +1 -0
  34. package/dist/runtime/progress.d.ts +2 -0
  35. package/dist/runtime/progress.d.ts.map +1 -1
  36. package/dist/runtime/progress.js +2 -2
  37. package/dist/runtime/progress.js.map +1 -1
  38. package/dist/runtime/run.d.ts +23 -0
  39. package/dist/runtime/run.d.ts.map +1 -1
  40. package/dist/runtime/run.js +107 -8
  41. package/dist/runtime/run.js.map +1 -1
  42. package/dist/spec/extensions.d.ts +4 -1
  43. package/dist/spec/extensions.d.ts.map +1 -1
  44. package/dist/spec/extensions.js +8 -2
  45. package/dist/spec/extensions.js.map +1 -1
  46. package/dist/spec/graph.js +1 -1
  47. package/dist/spec/graph.js.map +1 -1
  48. package/dist/spec/load.d.ts +2 -1
  49. package/dist/spec/load.d.ts.map +1 -1
  50. package/dist/spec/load.js +8 -7
  51. package/dist/spec/load.js.map +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Persist every HTTP exchange under the run dir so a green run is still inspectable.
3
+ *
4
+ * Layout (next to `progress.tsv`):
5
+ * exchanges.jsonl one line per Exchange.seq, join key = requestId = TSV req_id
6
+ * exchanges/<requestId>.json full exchange (headers + described / spilled bodies)
7
+ * blobs/<sha256> content-addressed bytes (multipart, binary, oversized text)
8
+ *
9
+ * Redaction is on by default. This is oat's journal, not a HAR file.
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ import { existsSync } from "node:fs";
13
+ import { appendFile, mkdir, writeFile } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { describeRequestBody } from "./client.js";
16
+ import { sseEvents } from "./sse.js";
17
+ export const INLINE_BODY_LIMIT = 256 * 1024;
18
+ const REDACTED = "<redacted>";
19
+ const REDACT_HEADER_NAMES = new Set(["authorization", "cookie", "set-cookie", "proxy-authorization", "x-ia-tester-key"]);
20
+ const REDACT_JSON_KEYS = new Set([
21
+ "access_token",
22
+ "refresh_token",
23
+ "id_token",
24
+ "password",
25
+ "token",
26
+ "secret",
27
+ "api_key",
28
+ ]);
29
+ export function resolveSaveExchanges(options) {
30
+ if (options.flag !== undefined)
31
+ return options.flag;
32
+ if (options.config !== undefined)
33
+ return options.config;
34
+ return options.profile !== "cheap";
35
+ }
36
+ export function isSecretHeaderName(name) {
37
+ const lower = name.toLowerCase();
38
+ if (REDACT_HEADER_NAMES.has(lower))
39
+ return true;
40
+ return /^x-.+-(key|secret)$/i.test(lower);
41
+ }
42
+ export function redactHeaders(headers) {
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(headers)) {
45
+ out[key] = isSecretHeaderName(key) ? REDACTED : value;
46
+ }
47
+ return out;
48
+ }
49
+ export function redactJson(value) {
50
+ if (Array.isArray(value))
51
+ return value.map((item) => redactJson(item));
52
+ if (value === null || typeof value !== "object")
53
+ return value;
54
+ const out = {};
55
+ for (const [key, child] of Object.entries(value)) {
56
+ out[key] = REDACT_JSON_KEYS.has(key.toLowerCase()) ? REDACTED : redactJson(child);
57
+ }
58
+ return out;
59
+ }
60
+ export function sanitizeExchangeId(requestId) {
61
+ const cleaned = requestId
62
+ .trim()
63
+ .replace(/[^A-Za-z0-9._-]+/g, "_")
64
+ .replace(/^_+|_+$/g, "");
65
+ return cleaned === "" ? "id" : cleaned.slice(0, 180);
66
+ }
67
+ export function exchangeFileStem(requestId, seq, taken) {
68
+ const trimmed = requestId.trim();
69
+ const base = trimmed === "" ? `seq-${seq}` : sanitizeExchangeId(trimmed);
70
+ if (!taken.has(base))
71
+ return base;
72
+ const dup = `${base}-${seq}`;
73
+ if (!taken.has(dup))
74
+ return dup;
75
+ let n = 2;
76
+ while (taken.has(`${dup}-${n}`))
77
+ n += 1;
78
+ return `${dup}-${n}`;
79
+ }
80
+ export function primaryMediaType(mediaType, fallback) {
81
+ const named = mediaType
82
+ .split(";")
83
+ .map((part) => part.trim())
84
+ .find((part) => part.includes("/"));
85
+ return named === undefined || named === "" ? fallback : named;
86
+ }
87
+ export function isBinaryMediaType(mediaType) {
88
+ const type = primaryMediaType(mediaType, "").toLowerCase();
89
+ if (type.startsWith("image/") || type.startsWith("audio/") || type.startsWith("video/"))
90
+ return true;
91
+ if (type === "application/octet-stream" || type === "application/pdf" || type === "application/zip")
92
+ return true;
93
+ if (type === "application/vnd.ms-excel")
94
+ return true;
95
+ if (type.includes("spreadsheet") || type.includes("officedocument") || type.includes("opendocument"))
96
+ return true;
97
+ return false;
98
+ }
99
+ function sha256Hex(bytes) {
100
+ return createHash("sha256").update(bytes).digest("hex");
101
+ }
102
+ function utf8Bytes(text) {
103
+ return new TextEncoder().encode(text);
104
+ }
105
+ function headerOf(headers, name) {
106
+ const want = name.toLowerCase();
107
+ for (const [key, value] of Object.entries(headers)) {
108
+ if (key.toLowerCase() === want && value.trim() !== "")
109
+ return value;
110
+ }
111
+ return undefined;
112
+ }
113
+ async function bytesOf(value) {
114
+ if (value instanceof Uint8Array)
115
+ return value;
116
+ if (typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer)
117
+ return new Uint8Array(value);
118
+ if (ArrayBuffer.isView(value))
119
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
120
+ if (typeof Blob !== "undefined" && value instanceof Blob)
121
+ return new Uint8Array(await value.arrayBuffer());
122
+ return undefined;
123
+ }
124
+ function isFormData(body) {
125
+ return typeof FormData !== "undefined" && body instanceof FormData;
126
+ }
127
+ function isURLSearchParams(body) {
128
+ return typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
129
+ }
130
+ export function createExchangeJournal(dir) {
131
+ const taken = new Set();
132
+ const writtenBlobs = new Set();
133
+ let count = 0;
134
+ let ready;
135
+ const ensure = async () => {
136
+ if (ready === undefined) {
137
+ ready = Promise.all([
138
+ mkdir(join(dir, "exchanges"), { recursive: true }),
139
+ mkdir(join(dir, "blobs"), { recursive: true }),
140
+ ]).then(() => undefined);
141
+ }
142
+ await ready;
143
+ };
144
+ const putBlob = async (bytes) => {
145
+ const sha256 = sha256Hex(bytes);
146
+ if (!writtenBlobs.has(sha256)) {
147
+ const path = join(dir, "blobs", sha256);
148
+ if (!existsSync(path))
149
+ await writeFile(path, bytes);
150
+ writtenBlobs.add(sha256);
151
+ }
152
+ return { bytes: bytes.byteLength, sha256 };
153
+ };
154
+ const spillText = async (text, mediaType) => {
155
+ const bytes = utf8Bytes(text);
156
+ if (bytes.byteLength <= INLINE_BODY_LIMIT)
157
+ return text;
158
+ const blob = await putBlob(bytes);
159
+ return { ...blob, mediaType };
160
+ };
161
+ const persistJson = async (value, mediaType) => {
162
+ const redacted = redactJson(value);
163
+ const text = JSON.stringify(redacted);
164
+ if (utf8Bytes(text).byteLength <= INLINE_BODY_LIMIT)
165
+ return redacted;
166
+ const blob = await putBlob(utf8Bytes(text));
167
+ return { ...blob, mediaType };
168
+ };
169
+ const persistRequestBody = async (body) => {
170
+ if (body === undefined)
171
+ return undefined;
172
+ if (isFormData(body)) {
173
+ const parts = [];
174
+ for (const [field, value] of body.entries()) {
175
+ if (typeof value === "string") {
176
+ parts.push({
177
+ field,
178
+ value: REDACT_JSON_KEYS.has(field.toLowerCase()) ? REDACTED : value,
179
+ });
180
+ continue;
181
+ }
182
+ const bytes = new Uint8Array(await value.arrayBuffer());
183
+ const blob = await putBlob(bytes);
184
+ parts.push({
185
+ bytes: blob.bytes,
186
+ field,
187
+ filename: value.name,
188
+ mediaType: value.type || "application/octet-stream",
189
+ sha256: blob.sha256,
190
+ });
191
+ }
192
+ return { parts };
193
+ }
194
+ if (isURLSearchParams(body))
195
+ return redactJson(Object.fromEntries(body.entries()));
196
+ const raw = await bytesOf(body);
197
+ if (raw !== undefined) {
198
+ const blob = await putBlob(raw);
199
+ return { ...blob, mediaType: "application/octet-stream" };
200
+ }
201
+ if (typeof body === "string")
202
+ return spillText(body, "text/plain");
203
+ if (body !== null && typeof body === "object")
204
+ return persistJson(body, "application/json");
205
+ return describeRequestBody(body);
206
+ };
207
+ const persistResponseBody = async (exchange) => {
208
+ const mediaType = headerOf(exchange.responseHeaders, "content-type") ?? "";
209
+ const body = exchange.responseBody;
210
+ if (mediaType.toLowerCase().includes("text/event-stream") && typeof body === "string") {
211
+ const frames = sseEvents(body);
212
+ if (frames !== null) {
213
+ return frames.map((frame) => ({ data: redactJson(frame.data), event: frame.event }));
214
+ }
215
+ return spillText(body, primaryMediaType(mediaType, "text/event-stream"));
216
+ }
217
+ if (isBinaryMediaType(mediaType)) {
218
+ const bytes = typeof body === "string" ? utf8Bytes(body) : ((await bytesOf(body)) ?? utf8Bytes(JSON.stringify(body)));
219
+ const blob = await putBlob(bytes);
220
+ return { ...blob, mediaType: primaryMediaType(mediaType, "application/octet-stream") };
221
+ }
222
+ if (body === undefined)
223
+ return undefined;
224
+ if (typeof body === "string") {
225
+ const asFrames = sseEvents(body);
226
+ if (asFrames !== null) {
227
+ return asFrames.map((frame) => ({ data: redactJson(frame.data), event: frame.event }));
228
+ }
229
+ return spillText(body, primaryMediaType(mediaType, "text/plain"));
230
+ }
231
+ if (body !== null && typeof body === "object") {
232
+ return persistJson(body, primaryMediaType(mediaType, "application/json"));
233
+ }
234
+ return body;
235
+ };
236
+ return {
237
+ get count() {
238
+ return count;
239
+ },
240
+ get dir() {
241
+ return dir;
242
+ },
243
+ async record(exchange, meta = {}) {
244
+ await ensure();
245
+ const requestBody = await persistRequestBody(exchange.requestBody);
246
+ const responseBody = await persistResponseBody(exchange);
247
+ let stem = exchangeFileStem(exchange.requestId, exchange.seq, taken);
248
+ const exchangesDir = join(dir, "exchanges");
249
+ while (existsSync(join(exchangesDir, `${stem}.json`))) {
250
+ taken.add(stem);
251
+ stem = exchangeFileStem(exchange.requestId, exchange.seq, taken);
252
+ }
253
+ taken.add(stem);
254
+ const rel = `exchanges/${stem}.json`;
255
+ const file = {
256
+ at: exchange.at,
257
+ durationMs: exchange.durationMs,
258
+ ...(exchange.fixture === undefined ? {} : { fixture: exchange.fixture }),
259
+ method: exchange.method,
260
+ ...(exchange.operationId === undefined ? {} : { operationId: exchange.operationId }),
261
+ requestBody,
262
+ requestHeaders: redactHeaders(exchange.requestHeaders),
263
+ requestId: exchange.requestId,
264
+ responseBody,
265
+ responseHeaders: redactHeaders(exchange.responseHeaders),
266
+ seq: exchange.seq,
267
+ status: exchange.status,
268
+ url: exchange.url,
269
+ };
270
+ await writeFile(join(dir, rel), `${JSON.stringify(file, null, 2)}\n`);
271
+ const line = {
272
+ at: exchange.at,
273
+ durationMs: exchange.durationMs,
274
+ file: rel,
275
+ method: exchange.method,
276
+ requestBytes: exchange.requestBytes,
277
+ requestId: exchange.requestId,
278
+ responseBytes: exchange.responseBytes,
279
+ seq: exchange.seq,
280
+ status: exchange.status,
281
+ url: exchange.url,
282
+ };
283
+ if (exchange.operationId !== undefined)
284
+ line.operationId = exchange.operationId;
285
+ if (exchange.fixture !== undefined)
286
+ line.fixture = exchange.fixture;
287
+ if (meta.check !== undefined)
288
+ line.check = meta.check;
289
+ if (meta.entity !== undefined)
290
+ line.entity = meta.entity;
291
+ if (meta.phase !== undefined)
292
+ line.phase = meta.phase;
293
+ await appendFile(join(dir, "exchanges.jsonl"), `${JSON.stringify(line)}\n`);
294
+ count += 1;
295
+ },
296
+ };
297
+ }
298
+ //# sourceMappingURL=exchanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchanges.js","sourceRoot":"","sources":["../../src/runtime/exchanges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Network failures are not backend defects.
3
+ *
4
+ * `fetch` throwing is "the request never happened" — DNS, offline, reset, timeout — and must
5
+ * not look like a 4xx/5xx or an uncaught `TypeError: fetch failed`. Classify, retry the blip,
6
+ * then wait once for the link. If it does not come back, one finding names the kind and the
7
+ * rest of the run stands down.
8
+ */
9
+ import { type NetworkInterfaceInfo } from "node:os";
10
+ export type NetworkKind = "offline" | "dns" | "refused" | "reset" | "timeout" | "unreachable" | "unknown";
11
+ export declare const DEFAULT_NETWORK_RETRIES = 4;
12
+ export declare const DEFAULT_NETWORK_WAIT_MS = 60000;
13
+ export declare const DEFAULT_NETWORK_PROBE_MS = 2000;
14
+ export declare class NetworkError extends Error {
15
+ readonly code: "NETWORK";
16
+ readonly kind: NetworkKind;
17
+ readonly attempts: number;
18
+ readonly method: string;
19
+ readonly url: string;
20
+ constructor(options: {
21
+ kind: NetworkKind;
22
+ message: string;
23
+ attempts: number;
24
+ method: string;
25
+ url: string;
26
+ cause?: unknown;
27
+ });
28
+ }
29
+ export declare function isNetworkError(error: unknown): error is NetworkError;
30
+ export declare function classifyNetworkError(error: unknown): NetworkKind | null;
31
+ export declare function refineNetworkKind(kind: NetworkKind, read?: () => NodeJS.Dict<NetworkInterfaceInfo[]>): NetworkKind;
32
+ export declare function hasUsableInterface(read?: () => NodeJS.Dict<NetworkInterfaceInfo[]>): boolean;
33
+ export declare function describeNetworkKind(kind: NetworkKind): string;
34
+ export declare function networkRetryWaitMs(attempt: number): number;
35
+ export declare function describeNetworkFailure(error: NetworkError, waitedMs?: number): string;
36
+ export interface NetworkWaitInfo {
37
+ kind: NetworkKind;
38
+ elapsedMs: number;
39
+ remainingMs: number;
40
+ }
41
+ export interface NetworkGate {
42
+ readonly exhausted: boolean;
43
+ readonly waitedMs: number;
44
+ readonly lastKind: NetworkKind | undefined;
45
+ awaitRecovery(error: NetworkError): Promise<boolean>;
46
+ }
47
+ export declare function createNetworkGate(options: {
48
+ waitBudgetMs?: number;
49
+ probeIntervalMs?: number;
50
+ probe: () => Promise<boolean>;
51
+ sleep?: (ms: number) => Promise<void>;
52
+ now?: () => number;
53
+ onWait?: (info: NetworkWaitInfo) => void;
54
+ }): NetworkGate;
55
+ export declare function probeOrigin(origin: string, options?: {
56
+ timeoutMs?: number;
57
+ fetchImpl?: typeof fetch;
58
+ online?: () => boolean;
59
+ }): Promise<boolean>;
60
+ export interface NetworkFetchOptions {
61
+ retries?: number;
62
+ waitMs?: number;
63
+ requestTimeoutMs?: number;
64
+ probe?: () => Promise<boolean>;
65
+ sleep?: (ms: number) => Promise<void>;
66
+ onWait?: (info: NetworkWaitInfo) => void;
67
+ fetchImpl?: typeof fetch;
68
+ now?: () => number;
69
+ }
70
+ /**
71
+ * `fetch` with the same classify / retry / wait policy Client uses, for spec load (no Exchange).
72
+ */
73
+ export declare function fetchWithNetworkRetry(url: string, init?: RequestInit, options?: NetworkFetchOptions): Promise<Response>;
74
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/runtime/network.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Network failures are not backend defects.
3
+ *
4
+ * `fetch` throwing is "the request never happened" — DNS, offline, reset, timeout — and must
5
+ * not look like a 4xx/5xx or an uncaught `TypeError: fetch failed`. Classify, retry the blip,
6
+ * then wait once for the link. If it does not come back, one finding names the kind and the
7
+ * rest of the run stands down.
8
+ */
9
+ import { networkInterfaces } from "node:os";
10
+ import { sleep } from "./poll.js";
11
+ export const DEFAULT_NETWORK_RETRIES = 4;
12
+ export const DEFAULT_NETWORK_WAIT_MS = 60_000;
13
+ export const DEFAULT_NETWORK_PROBE_MS = 2_000;
14
+ export class NetworkError extends Error {
15
+ code = "NETWORK";
16
+ kind;
17
+ attempts;
18
+ method;
19
+ url;
20
+ constructor(options) {
21
+ super(options.message, options.cause === undefined ? undefined : { cause: options.cause });
22
+ this.name = "NetworkError";
23
+ this.attempts = options.attempts;
24
+ this.kind = options.kind;
25
+ this.method = options.method;
26
+ this.url = options.url;
27
+ }
28
+ }
29
+ export function isNetworkError(error) {
30
+ return error instanceof NetworkError;
31
+ }
32
+ const DNS = new Set(["ENOTFOUND", "EAI_AGAIN", "EAI_FAIL", "EAI_NODATA", "EAI_NONAME"]);
33
+ const REFUSED = new Set(["ECONNREFUSED"]);
34
+ const RESET = new Set(["ECONNRESET", "EPIPE", "UND_ERR_SOCKET", "UND_ERR_DESTROYED"]);
35
+ const TIMEOUT = new Set([
36
+ "ETIMEDOUT",
37
+ "UND_ERR_CONNECT_TIMEOUT",
38
+ "UND_ERR_HEADERS_TIMEOUT",
39
+ "UND_ERR_BODY_TIMEOUT",
40
+ "TimeoutError",
41
+ "AbortError",
42
+ ]);
43
+ const UNREACHABLE = new Set(["ENETUNREACH", "ENETDOWN", "EHOSTUNREACH", "EHOSTDOWN"]);
44
+ const OFFLINE = new Set(["ENONET", "ERR_NETWORK_CHANGED", "ERR_INTERNET_DISCONNECTED"]);
45
+ const NOT_NETWORK = /CERT|UNABLE_TO_VERIFY|ERR_TLS|ERR_SSL|ERR_INVALID_URL|ERR_INVALID_ARG/;
46
+ export function classifyNetworkError(error) {
47
+ const tokens = errorTokens(error);
48
+ if (tokens.some((token) => NOT_NETWORK.test(token)))
49
+ return null;
50
+ const hit = (set) => tokens.some((token) => set.has(token));
51
+ if (hit(OFFLINE))
52
+ return "offline";
53
+ if (hit(UNREACHABLE))
54
+ return "unreachable";
55
+ if (hit(DNS))
56
+ return "dns";
57
+ if (hit(REFUSED))
58
+ return "refused";
59
+ if (hit(RESET))
60
+ return "reset";
61
+ if (hit(TIMEOUT))
62
+ return "timeout";
63
+ const text = tokens.join(" ");
64
+ if (/fetch failed|networkerror|failed to fetch|socket hang up|getaddrinfo/i.test(text))
65
+ return "unknown";
66
+ return null;
67
+ }
68
+ export function refineNetworkKind(kind, read = networkInterfaces) {
69
+ if (kind === "offline")
70
+ return kind;
71
+ return hasUsableInterface(read) ? kind : "offline";
72
+ }
73
+ export function hasUsableInterface(read = networkInterfaces) {
74
+ for (const list of Object.values(read())) {
75
+ for (const info of list ?? []) {
76
+ if (!info.internal && info.address !== "")
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ export function describeNetworkKind(kind) {
83
+ switch (kind) {
84
+ case "offline":
85
+ return "this machine has no usable network interface";
86
+ case "dns":
87
+ return "DNS could not resolve the host";
88
+ case "refused":
89
+ return "the host refused the connection";
90
+ case "reset":
91
+ return "the connection was reset";
92
+ case "timeout":
93
+ return "the connection timed out";
94
+ case "unreachable":
95
+ return "no route to the host";
96
+ case "unknown":
97
+ return "the request never reached the server";
98
+ }
99
+ }
100
+ export function networkRetryWaitMs(attempt) {
101
+ return Math.min(4_000, 250 * 2 ** Math.max(0, attempt));
102
+ }
103
+ export function describeNetworkFailure(error, waitedMs = 0) {
104
+ const wait = waitedMs > 0 ? ` and ${Math.round(waitedMs / 1000)}s waiting for the link` : "";
105
+ return (`${error.method} ${error.url} failed (${error.kind}: ${describeNetworkKind(error.kind)}) ` +
106
+ `after ${error.attempts} attempt(s)${wait}. This is not a backend status.`);
107
+ }
108
+ function errorTokens(error) {
109
+ const tokens = [];
110
+ const seen = new Set();
111
+ let current = error;
112
+ while (current !== null && typeof current === "object" && !seen.has(current)) {
113
+ seen.add(current);
114
+ const rec = current;
115
+ if (typeof rec.code === "string" && rec.code !== "")
116
+ tokens.push(rec.code);
117
+ if (typeof rec.name === "string" && rec.name !== "")
118
+ tokens.push(rec.name);
119
+ if (typeof rec.message === "string" && rec.message !== "")
120
+ tokens.push(rec.message);
121
+ current = rec.cause;
122
+ }
123
+ if (typeof error === "string")
124
+ tokens.push(error);
125
+ return tokens;
126
+ }
127
+ export function createNetworkGate(options) {
128
+ const waitBudgetMs = Math.max(0, options.waitBudgetMs ?? DEFAULT_NETWORK_WAIT_MS);
129
+ const probeIntervalMs = Math.max(50, options.probeIntervalMs ?? DEFAULT_NETWORK_PROBE_MS);
130
+ const pause = options.sleep ?? sleep;
131
+ const now = options.now ?? Date.now;
132
+ let exhausted = false;
133
+ let waitedMs = 0;
134
+ let lastKind;
135
+ let inflight;
136
+ const waitOnce = async (error) => {
137
+ lastKind = error.kind;
138
+ if (waitBudgetMs === 0) {
139
+ exhausted = true;
140
+ return false;
141
+ }
142
+ const began = now();
143
+ const deadline = began + waitBudgetMs;
144
+ options.onWait?.({ elapsedMs: 0, kind: error.kind, remainingMs: waitBudgetMs });
145
+ while (now() < deadline) {
146
+ const remaining = deadline - now();
147
+ await pause(Math.min(probeIntervalMs, Math.max(0, remaining)));
148
+ waitedMs = now() - began;
149
+ options.onWait?.({
150
+ elapsedMs: waitedMs,
151
+ kind: error.kind,
152
+ remainingMs: Math.max(0, deadline - now()),
153
+ });
154
+ if (await options.probe())
155
+ return true;
156
+ }
157
+ waitedMs = Math.max(waitedMs, now() - began);
158
+ exhausted = true;
159
+ return false;
160
+ };
161
+ return {
162
+ get exhausted() {
163
+ return exhausted;
164
+ },
165
+ get lastKind() {
166
+ return lastKind;
167
+ },
168
+ get waitedMs() {
169
+ return waitedMs;
170
+ },
171
+ awaitRecovery(error) {
172
+ if (exhausted)
173
+ return Promise.resolve(false);
174
+ if (inflight !== undefined)
175
+ return inflight;
176
+ inflight = waitOnce(error).finally(() => {
177
+ inflight = undefined;
178
+ });
179
+ return inflight;
180
+ },
181
+ };
182
+ }
183
+ export async function probeOrigin(origin, options = {}) {
184
+ const online = options.online ?? hasUsableInterface;
185
+ if (!online())
186
+ return false;
187
+ const timeoutMs = options.timeoutMs ?? 2_000;
188
+ const fetchImpl = options.fetchImpl ?? fetch;
189
+ let url;
190
+ try {
191
+ url = new URL(origin);
192
+ }
193
+ catch {
194
+ return false;
195
+ }
196
+ try {
197
+ const response = await fetchImpl(url.origin, {
198
+ method: "HEAD",
199
+ redirect: "manual",
200
+ signal: AbortSignal.timeout(timeoutMs),
201
+ });
202
+ return response.status > 0;
203
+ }
204
+ catch (error) {
205
+ return classifyNetworkError(error) === null;
206
+ }
207
+ }
208
+ /**
209
+ * `fetch` with the same classify / retry / wait policy Client uses, for spec load (no Exchange).
210
+ */
211
+ export async function fetchWithNetworkRetry(url, init = {}, options = {}) {
212
+ const retries = Math.max(0, options.retries ?? DEFAULT_NETWORK_RETRIES);
213
+ const pause = options.sleep ?? sleep;
214
+ const fetchImpl = options.fetchImpl ?? fetch;
215
+ const method = (init.method ?? "GET").toUpperCase();
216
+ let last;
217
+ const once = async (attempt) => {
218
+ try {
219
+ const initWithTimeout = options.requestTimeoutMs === undefined
220
+ ? init
221
+ : { ...init, signal: AbortSignal.timeout(options.requestTimeoutMs) };
222
+ return await fetchImpl(url, initWithTimeout);
223
+ }
224
+ catch (error) {
225
+ const raw = classifyNetworkError(error);
226
+ if (raw === null)
227
+ throw error;
228
+ const kind = refineNetworkKind(raw);
229
+ throw new NetworkError({
230
+ attempts: attempt + 1,
231
+ cause: error,
232
+ kind,
233
+ message: `${method} ${url} failed (${kind}: ${describeNetworkKind(kind)})`,
234
+ method,
235
+ url,
236
+ });
237
+ }
238
+ };
239
+ for (let attempt = 0; attempt <= retries; attempt++) {
240
+ try {
241
+ return await once(attempt);
242
+ }
243
+ catch (error) {
244
+ if (!isNetworkError(error))
245
+ throw error;
246
+ last = error;
247
+ if (attempt < retries)
248
+ await pause(networkRetryWaitMs(attempt));
249
+ }
250
+ }
251
+ const gate = createNetworkGate({
252
+ probe: options.probe ?? (() => probeOrigin(url, { fetchImpl })),
253
+ sleep: pause,
254
+ waitBudgetMs: options.waitMs ?? DEFAULT_NETWORK_WAIT_MS,
255
+ ...(options.now === undefined ? {} : { now: options.now }),
256
+ ...(options.onWait === undefined ? {} : { onWait: options.onWait }),
257
+ });
258
+ const failed = last;
259
+ if (await gate.awaitRecovery(failed))
260
+ return once(retries + 1);
261
+ throw new NetworkError({
262
+ attempts: failed.attempts,
263
+ cause: failed,
264
+ kind: failed.kind,
265
+ message: describeNetworkFailure(failed, gate.waitedMs),
266
+ method: failed.method,
267
+ url: failed.url,
268
+ });
269
+ }
270
+ //# sourceMappingURL=network.js.map