@vereda/http 1.0.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +604 -0
  3. package/dist/adapters/zod.d.ts +14 -0
  4. package/dist/adapters/zod.d.ts.map +1 -0
  5. package/dist/adapters/zod.js +14 -0
  6. package/dist/adapters/zod.js.map +1 -0
  7. package/dist/core/backoff.d.ts +9 -0
  8. package/dist/core/backoff.d.ts.map +1 -0
  9. package/dist/core/backoff.js +23 -0
  10. package/dist/core/backoff.js.map +1 -0
  11. package/dist/core/client.d.ts +98 -0
  12. package/dist/core/client.d.ts.map +1 -0
  13. package/dist/core/client.js +781 -0
  14. package/dist/core/client.js.map +1 -0
  15. package/dist/core/errors.d.ts +87 -0
  16. package/dist/core/errors.d.ts.map +1 -0
  17. package/dist/core/errors.js +140 -0
  18. package/dist/core/errors.js.map +1 -0
  19. package/dist/core/index.d.ts +15 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +10 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/listeners.d.ts +14 -0
  24. package/dist/core/listeners.d.ts.map +1 -0
  25. package/dist/core/listeners.js +27 -0
  26. package/dist/core/listeners.js.map +1 -0
  27. package/dist/core/metrics.d.ts +33 -0
  28. package/dist/core/metrics.d.ts.map +1 -0
  29. package/dist/core/metrics.js +24 -0
  30. package/dist/core/metrics.js.map +1 -0
  31. package/dist/core/nanoid.d.ts +2 -0
  32. package/dist/core/nanoid.d.ts.map +1 -0
  33. package/dist/core/nanoid.js +11 -0
  34. package/dist/core/nanoid.js.map +1 -0
  35. package/dist/core/redact.d.ts +12 -0
  36. package/dist/core/redact.d.ts.map +1 -0
  37. package/dist/core/redact.js +42 -0
  38. package/dist/core/redact.js.map +1 -0
  39. package/dist/core/types.d.ts +261 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/types.js +41 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/core/validate.d.ts +19 -0
  44. package/dist/core/validate.d.ts.map +1 -0
  45. package/dist/core/validate.js +135 -0
  46. package/dist/core/validate.js.map +1 -0
  47. package/dist/middleware/index.d.ts +26 -0
  48. package/dist/middleware/index.d.ts.map +1 -0
  49. package/dist/middleware/index.js +55 -0
  50. package/dist/middleware/index.js.map +1 -0
  51. package/dist/queue/bulkhead.d.ts +63 -0
  52. package/dist/queue/bulkhead.d.ts.map +1 -0
  53. package/dist/queue/bulkhead.js +192 -0
  54. package/dist/queue/bulkhead.js.map +1 -0
  55. package/dist/queue/circuit-breaker.d.ts +81 -0
  56. package/dist/queue/circuit-breaker.d.ts.map +1 -0
  57. package/dist/queue/circuit-breaker.js +283 -0
  58. package/dist/queue/circuit-breaker.js.map +1 -0
  59. package/dist/queue/executor.d.ts +67 -0
  60. package/dist/queue/executor.d.ts.map +1 -0
  61. package/dist/queue/executor.js +273 -0
  62. package/dist/queue/executor.js.map +1 -0
  63. package/dist/queue/policy.d.ts +26 -0
  64. package/dist/queue/policy.d.ts.map +1 -0
  65. package/dist/queue/policy.js +37 -0
  66. package/dist/queue/policy.js.map +1 -0
  67. package/dist/queue/retry.d.ts +58 -0
  68. package/dist/queue/retry.d.ts.map +1 -0
  69. package/dist/queue/retry.js +259 -0
  70. package/dist/queue/retry.js.map +1 -0
  71. package/dist/queue/semaphore.d.ts +32 -0
  72. package/dist/queue/semaphore.d.ts.map +1 -0
  73. package/dist/queue/semaphore.js +83 -0
  74. package/dist/queue/semaphore.js.map +1 -0
  75. package/dist/ticket/ticket.d.ts +77 -0
  76. package/dist/ticket/ticket.d.ts.map +1 -0
  77. package/dist/ticket/ticket.js +186 -0
  78. package/dist/ticket/ticket.js.map +1 -0
  79. package/package.json +85 -0
  80. package/src/adapters/zod.ts +16 -0
  81. package/src/core/backoff.ts +26 -0
  82. package/src/core/client.ts +1048 -0
  83. package/src/core/errors.ts +194 -0
  84. package/src/core/index.ts +56 -0
  85. package/src/core/listeners.ts +28 -0
  86. package/src/core/metrics.ts +42 -0
  87. package/src/core/nanoid.ts +11 -0
  88. package/src/core/redact.ts +46 -0
  89. package/src/core/types.ts +306 -0
  90. package/src/core/validate.ts +163 -0
  91. package/src/middleware/index.ts +63 -0
  92. package/src/queue/bulkhead.ts +243 -0
  93. package/src/queue/circuit-breaker.ts +373 -0
  94. package/src/queue/executor.ts +355 -0
  95. package/src/queue/policy.ts +49 -0
  96. package/src/queue/retry.ts +380 -0
  97. package/src/queue/semaphore.ts +91 -0
  98. package/src/ticket/ticket.ts +246 -0
@@ -0,0 +1,273 @@
1
+ import { ConfigurationError, DeadlineExceededError, HttpError, NetworkError, NO_TIMEOUT_CONFIGURED, RetryableStatusError, TimeoutError, ValidationError, } from "../core/errors.js";
2
+ import { DEFAULT_RETRY_ON_STATUS, isBoundedMs } from "../core/types.js";
3
+ import { isReadableStream } from "../core/validate.js";
4
+ /**
5
+ * Executes a single HTTP request attempt.
6
+ * Returns a discriminated union describing what happened,
7
+ * so the caller (retry loop) can decide whether to retry or resolve.
8
+ */
9
+ export async function executeRequest(req, middleware) {
10
+ const { url, options, timeoutConfig, retryConfig, signal } = req;
11
+ const attemptStart = Date.now();
12
+ if (signal.aborted || options.signal?.aborted) {
13
+ return { kind: "cancelled" };
14
+ }
15
+ // Set once this attempt hands the caller a live, unread Response body (the
16
+ // success-without-parse and HttpError returns below). An unconsumed body
17
+ // handed off this way gets bounded in `finally` instead of having its
18
+ // timer cleared, so `raw.json()`/`error.response.text()` read later can't
19
+ // hang forever (the parse path already reads the body inside the attempt,
20
+ // and the RetryableStatusError path already cancels it).
21
+ let handedOff = false;
22
+ // Resolve a replayable body factory fresh for this attempt so every attempt
23
+ // gets its own materialized body. Do not mutate the caller's options.
24
+ let resolvedBody;
25
+ if (typeof options.body === "function") {
26
+ try {
27
+ resolvedBody = options.body();
28
+ }
29
+ catch (err) {
30
+ return {
31
+ kind: "error",
32
+ error: new ConfigurationError(`body factory threw: ${err instanceof Error ? err.message : String(err)}`),
33
+ };
34
+ }
35
+ }
36
+ else {
37
+ resolvedBody = options.body;
38
+ }
39
+ const timeoutMs = timeoutConfig.attemptMs;
40
+ const retryOnStatus = retryConfig.retryOnStatus ?? DEFAULT_RETRY_ON_STATUS;
41
+ // Build the fetch call wrapped in middleware
42
+ const fetchCall = buildFetchCall(req.fetch);
43
+ const composed = composeMiddleware(middleware, fetchCall);
44
+ // Merge the per-attempt signals with AbortSignal.any. It wires its sources
45
+ // internally (no "abort" listeners attached to them), so neither the ticket
46
+ // signal nor the caller's signal accumulates one listener per attempt (#7).
47
+ // Wrapping even a lone ticket signal shields it from fetch's own abort
48
+ // listener, which undici only removes asynchronously after completion.
49
+ const sources = [signal];
50
+ if (options.signal)
51
+ sources.push(options.signal);
52
+ // `Infinity` is a legal, explicit "no cap" value (see isBoundedMs) — it must
53
+ // be treated the same as "not set" here. Node clamps any setTimeout delay
54
+ // over ~24.8 days to 1ms, so passing Infinity straight to setTimeout would
55
+ // fire the timer almost immediately instead of never.
56
+ const hasAttemptTimeout = isBoundedMs(timeoutMs);
57
+ // Always created (not just when attemptMs is bounded): a handed-off
58
+ // Response's body read may still need bounding by `deadlineAt` alone in
59
+ // `finally` below, even when this attempt itself has no attemptMs cap.
60
+ const timeoutController = new AbortController();
61
+ sources.push(timeoutController.signal);
62
+ const attemptSignal = AbortSignal.any(sources);
63
+ const timeoutId = hasAttemptTimeout ? setTimeout(() => timeoutController.abort(), timeoutMs) : undefined;
64
+ // A fresh Headers instance per attempt: middleware (e.g. defaultHeaders)
65
+ // mutates ctx.headers in place, and that must never leak into the next
66
+ // retry's "starting" headers.
67
+ const ctx = {
68
+ url,
69
+ method: options.method ?? "GET",
70
+ headers: new Headers(options.headers),
71
+ body: resolvedBody,
72
+ signal: attemptSignal,
73
+ attempt: req.attempt,
74
+ ticketId: req.ticketId,
75
+ partition: req.partition,
76
+ };
77
+ let response;
78
+ try {
79
+ response = await composed(ctx);
80
+ // The timeout may fire after fetch resolves but before this check runs;
81
+ // the timed-out attempt is not trustworthy, so it still surfaces as a
82
+ // timeout (cancellation is checked first in the catch path below).
83
+ if (timeoutController.signal.aborted) {
84
+ return { kind: "timeout" };
85
+ }
86
+ // Check if status code is retryable (was "queued_status", now typed error)
87
+ if (retryOnStatus.includes(response.status)) {
88
+ // Cancel the response body since caller won't read it.
89
+ // Fire-and-forget: cancel() may hang on stuck connections.
90
+ // The body will be GC'd when the response is collected.
91
+ response.body?.cancel().catch(() => { });
92
+ return {
93
+ kind: "error",
94
+ error: new RetryableStatusError(`HTTP ${response.status} ${response.statusText}`, response.status, response, parseRetryAfter(response.headers.get("retry-after"))),
95
+ };
96
+ }
97
+ // Non-2xx responses are non-retryable errors. The body is handed back
98
+ // unread on `error.response` — bound its read window in `finally`.
99
+ if (!response.ok) {
100
+ handedOff = true;
101
+ return {
102
+ kind: "error",
103
+ error: new HttpError(`HTTP ${response.status} ${response.statusText}`, response.status, response),
104
+ };
105
+ }
106
+ // Parse body — the timeout is still active so a slow body read is
107
+ // covered by the per-attempt deadline (#9).
108
+ if (options.parse) {
109
+ let raw;
110
+ try {
111
+ raw = await response.json();
112
+ }
113
+ catch (err) {
114
+ // Check timeout first — if our timer fired during response.json(),
115
+ // that is the cause regardless of whether the external signal also
116
+ // aborted (cancellation vs timeout precedence).
117
+ if (timeoutController.signal.aborted) {
118
+ return { kind: "timeout" };
119
+ }
120
+ if (signal.aborted || options.signal?.aborted) {
121
+ return { kind: "cancelled" };
122
+ }
123
+ // The body arrived but isn't JSON: the server answered, and asking
124
+ // again will get the same answer — a parse failure, never retried
125
+ // (B6). Anything else here is the body stream dying mid-read
126
+ // (undici's "terminated" TypeError), which is a real network error.
127
+ if (err instanceof SyntaxError) {
128
+ return {
129
+ kind: "error",
130
+ error: new ValidationError("Response body is not valid JSON", [err], err),
131
+ };
132
+ }
133
+ return {
134
+ kind: "error",
135
+ error: new NetworkError("Failed to parse response body as JSON", {
136
+ cause: err,
137
+ }),
138
+ };
139
+ }
140
+ try {
141
+ const data = options.parse(raw);
142
+ return {
143
+ kind: "success",
144
+ result: { success: true, data, raw: response },
145
+ };
146
+ }
147
+ catch (err) {
148
+ const issues = extractIssues(err);
149
+ return {
150
+ kind: "error",
151
+ error: new ValidationError("Response validation failed", issues, err),
152
+ };
153
+ }
154
+ }
155
+ // No parse fn — return raw response, unread. Bound its read window in
156
+ // `finally` instead of clearing the attempt timer.
157
+ handedOff = true;
158
+ return {
159
+ kind: "success",
160
+ result: { success: true, data: undefined, raw: response },
161
+ };
162
+ }
163
+ catch (err) {
164
+ // Precedence: cancellation wins over timeout. If the ticket or the
165
+ // external signal is aborted, report cancelled even if the timeout
166
+ // also fired — the ticket is already marked cancelled, and
167
+ // cancellation is the caller's terminal intent. A timeout that fires
168
+ // first still surfaces as "timeout": the external signal is not yet
169
+ // aborted when this check runs.
170
+ if (signal.aborted || options.signal?.aborted) {
171
+ return { kind: "cancelled" };
172
+ }
173
+ if (isAbortError(err)) {
174
+ // Could be our timeout abort
175
+ return { kind: "timeout" };
176
+ }
177
+ return {
178
+ kind: "error",
179
+ error: new NetworkError(err instanceof Error ? err.message : "Network error", { cause: err }),
180
+ };
181
+ }
182
+ finally {
183
+ // Clear the in-attempt timer either way — it did its job (bounding the
184
+ // attempt) or the attempt failed on its own. Body has been read (or the
185
+ // attempt failed), so the abort controller can be released.
186
+ if (timeoutId !== undefined)
187
+ clearTimeout(timeoutId);
188
+ // A handed-off Response (unread body on a success-without-parse or
189
+ // HttpError result) gets the same bound its body would have had under
190
+ // `parse`: arm a fresh timer capped at whichever is sooner, this
191
+ // attempt's remaining attemptMs or the ticket's totalMs deadline. It
192
+ // fires only if the caller actually reads the body later — aborting
193
+ // `attemptSignal` (already wired into fetch) makes that read reject,
194
+ // same as any other abort observed after fetch() has resolved. No
195
+ // ticket state changes here: the ticket already resolved.
196
+ if (handedOff) {
197
+ const attemptTimeoutMs = isBoundedMs(timeoutMs) ? timeoutMs : undefined;
198
+ const attemptDeadline = attemptTimeoutMs !== undefined ? attemptStart + attemptTimeoutMs : Number.POSITIVE_INFINITY;
199
+ const totalDeadline = req.deadlineAt ?? Number.POSITIVE_INFINITY;
200
+ const boundAt = Math.min(attemptDeadline, totalDeadline);
201
+ if (Number.isFinite(boundAt)) {
202
+ const delay = Math.max(0, boundAt - Date.now());
203
+ // Name the abort reason after whichever bound fired — ties go to the
204
+ // attempt bound, since it's also the only bound when totalMs isn't
205
+ // configured — using Vereda's own error classes instead of a bare
206
+ // DOMException. That way a caller's `raw.json()`/`error.response.text()`
207
+ // rejects with something `instanceof TimeoutError`/`DeadlineExceededError`,
208
+ // `.kind`-narrowable, and redacted the same as every other error the
209
+ // client builds. The final branch is defensive only: `boundAt` being
210
+ // finite guarantees one of the two bounds above is actually configured.
211
+ const reason = attemptTimeoutMs !== undefined && attemptDeadline <= totalDeadline
212
+ ? new TimeoutError(req.displayUrl, attemptTimeoutMs)
213
+ : isBoundedMs(req.timeoutConfig.totalMs)
214
+ ? new DeadlineExceededError(req.displayUrl, req.timeoutConfig.totalMs)
215
+ : new TimeoutError(req.displayUrl, attemptTimeoutMs ?? NO_TIMEOUT_CONFIGURED);
216
+ const bodyReadTimer = setTimeout(() => {
217
+ timeoutController.abort(reason);
218
+ }, delay);
219
+ bodyReadTimer.unref();
220
+ }
221
+ }
222
+ }
223
+ }
224
+ function buildFetchCall(customFetch) {
225
+ const fetchFn = customFetch ?? globalThis.fetch.bind(globalThis);
226
+ return async (ctx) => {
227
+ const init = {
228
+ method: ctx.method,
229
+ headers: ctx.headers,
230
+ body: ctx.body,
231
+ signal: ctx.signal,
232
+ };
233
+ if (isReadableStream(ctx.body)) {
234
+ // Node's fetch requires duplex: "half" for stream bodies.
235
+ init.duplex = "half";
236
+ }
237
+ return fetchFn(ctx.url, init);
238
+ };
239
+ }
240
+ export function composeMiddleware(middlewares, core) {
241
+ return middlewares.reduceRight((next, middleware) => (ctx) => middleware(ctx, next), core);
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // Helpers
245
+ // ---------------------------------------------------------------------------
246
+ /** Parse a `Retry-After` header value into a delay in ms, or undefined when
247
+ * absent/unparseable. Integer seconds → ms; HTTP-date → ms from now, clamped
248
+ * to ≥ 0; anything else (garbage, negative) → undefined. */
249
+ export function parseRetryAfter(header) {
250
+ if (!header)
251
+ return undefined;
252
+ const trimmed = header.trim();
253
+ if (/^\d+$/.test(trimmed)) {
254
+ return Number(trimmed) * 1000;
255
+ }
256
+ const parsed = Date.parse(trimmed);
257
+ if (Number.isNaN(parsed))
258
+ return undefined;
259
+ return Math.max(0, parsed - Date.now());
260
+ }
261
+ function isAbortError(err) {
262
+ return err instanceof Error && err.name === "AbortError";
263
+ }
264
+ function extractIssues(err) {
265
+ if (err && typeof err === "object" && "errors" in err) {
266
+ return err.errors;
267
+ }
268
+ if (err && typeof err === "object" && "issues" in err) {
269
+ return err.issues;
270
+ }
271
+ return [err];
272
+ }
273
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/queue/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { AppError } from "../core/errors.ts";
2
+ export interface RetryPolicyContext {
3
+ method: string;
4
+ headers?: HeadersInit;
5
+ /** value of merged retry.idempotent for this request */
6
+ idempotent?: boolean;
7
+ }
8
+ export type RetryPolicy = (error: AppError, attempt: number, ctx: RetryPolicyContext) => boolean;
9
+ export declare const RETRIABLE_KINDS: ReadonlySet<AppError["kind"]>;
10
+ /**
11
+ * The default retry policy (decision D3). An attempt is retried iff all hold:
12
+ * 1. attempts so far < maxRetries + 1 (enforced by the retry loop bounds)
13
+ * 2. error `kind` ∈ {network, timeout, retryable_status}
14
+ * 3. method is idempotent (GET HEAD OPTIONS PUT DELETE TRACE) OR
15
+ * `ctx.idempotent === true` OR the request has an `Idempotency-Key` header
16
+ * 4. user `retryWhen` (consulted separately via `shouldRetry`) returns true
17
+ * 5. the request is not cancelled / past its deadline (handled elsewhere)
18
+ */
19
+ export declare function defaultRetryPolicy(error: AppError, _attempt: number, ctx: RetryPolicyContext): boolean;
20
+ /**
21
+ * Combined retry gate used by both call sites so rule ordering lives in one
22
+ * place: the default policy runs first, then the user's `retryWhen` is
23
+ * consulted. `retryWhen` can only veto a retry, never force one.
24
+ */
25
+ export declare function shouldRetry(error: AppError, attempt: number, ctx: RetryPolicyContext, retryWhen?: (error: AppError, attempt: number) => boolean): boolean;
26
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/queue/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC;AAEjG,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAuD,CAAC;AAIlH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAQtG;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,kBAAkB,EACvB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GACvD,OAAO,CAIT"}
@@ -0,0 +1,37 @@
1
+ export const RETRIABLE_KINDS = new Set(["network", "timeout", "retryable_status"]);
2
+ const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"]);
3
+ /**
4
+ * The default retry policy (decision D3). An attempt is retried iff all hold:
5
+ * 1. attempts so far < maxRetries + 1 (enforced by the retry loop bounds)
6
+ * 2. error `kind` ∈ {network, timeout, retryable_status}
7
+ * 3. method is idempotent (GET HEAD OPTIONS PUT DELETE TRACE) OR
8
+ * `ctx.idempotent === true` OR the request has an `Idempotency-Key` header
9
+ * 4. user `retryWhen` (consulted separately via `shouldRetry`) returns true
10
+ * 5. the request is not cancelled / past its deadline (handled elsewhere)
11
+ */
12
+ export function defaultRetryPolicy(error, _attempt, ctx) {
13
+ // Rule 2 — the error kind must be transient.
14
+ if (!RETRIABLE_KINDS.has(error.kind))
15
+ return false;
16
+ // Rule 3 — the request must be safe to repeat (idempotent by method or opt-in).
17
+ if (IDEMPOTENT_METHODS.has(ctx.method.toUpperCase()))
18
+ return true;
19
+ if (ctx.idempotent)
20
+ return true;
21
+ if (ctx.headers && new Headers(ctx.headers).has("idempotency-key"))
22
+ return true;
23
+ return false;
24
+ }
25
+ /**
26
+ * Combined retry gate used by both call sites so rule ordering lives in one
27
+ * place: the default policy runs first, then the user's `retryWhen` is
28
+ * consulted. `retryWhen` can only veto a retry, never force one.
29
+ */
30
+ export function shouldRetry(error, attempt, ctx, retryWhen) {
31
+ if (!defaultRetryPolicy(error, attempt, ctx))
32
+ return false;
33
+ if (retryWhen && !retryWhen(error, attempt))
34
+ return false;
35
+ return true;
36
+ }
37
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/queue/policy.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,eAAe,GAAkC,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAElH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAEzF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAe,EAAE,QAAgB,EAAE,GAAuB;IAC5F,6CAA6C;IAC7C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,gFAAgF;IAChF,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,IAAI,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAAE,OAAO,IAAI,CAAC;IAChF,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAC1B,KAAe,EACf,OAAe,EACf,GAAuB,EACvB,SAAyD;IAEzD,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { AppError } from "../core/errors.ts";
2
+ import { type RequestOptions, type RetryConfig, type TimeoutConfig } from "../core/types.ts";
3
+ import type { Ticket, TicketController } from "../ticket/ticket.ts";
4
+ import type { Bulkhead } from "./bulkhead.ts";
5
+ import type { CircuitBreaker } from "./circuit-breaker.ts";
6
+ import { type MiddlewareFn } from "./executor.ts";
7
+ import type { Semaphore } from "./semaphore.ts";
8
+ export interface RetryJobOptions {
9
+ /** Fully resolved request URL (baseUrl applied) — what each retry fetches. */
10
+ url: string;
11
+ /** `url` as it may appear in errors/logs (query-redacted unless disabled). */
12
+ displayUrl: string;
13
+ requestOptions: RequestOptions<unknown>;
14
+ timeoutConfig: TimeoutConfig;
15
+ retryConfig: RetryConfig;
16
+ /** Absolute `Date.now()` timestamp of the ticket's whole-ticket deadline
17
+ * (`startTime + timeoutConfig.totalMs`), or undefined when `totalMs`
18
+ * isn't bounded. Forwarded to `executeRequest` so a handed-off Response
19
+ * body (unread success or `HttpError`) is bounded by it — see
20
+ * `ExecuteRequest.deadlineAt`. */
21
+ deadlineAt?: number;
22
+ ticket: Ticket<unknown>;
23
+ controller: TicketController<unknown>;
24
+ middleware: MiddlewareFn[];
25
+ /** Per-attempt bulkhead for retry scheduling. */
26
+ bulkhead: Bulkhead;
27
+ /** Global concurrency semaphore acquired after the partition slot (D4). */
28
+ semaphore?: Semaphore;
29
+ /** Per-partition circuit breaker. Inert unless configured/enabled. */
30
+ circuitBreaker: CircuitBreaker;
31
+ /** Partition name, used to construct CircuitOpenError when the breaker is open. */
32
+ partition: string;
33
+ /** The error from the first attempt (fired client-side before queuing). */
34
+ firstError: AppError;
35
+ /** Ms the first attempt spent waiting for a bulkhead/semaphore permit,
36
+ * carried over so the final queuedMs reported to the client covers the
37
+ * whole ticket lifetime, not just the retries run inside this loop. */
38
+ initialQueuedMs: number;
39
+ onRetry?: (attempt: number, delayMs: number, error: AppError) => void;
40
+ /** Called on success so the client can emit the success event with the
41
+ * response status code, the total attempt count, and the cumulative ms
42
+ * spent waiting for a bulkhead/semaphore permit across all attempts. */
43
+ onSuccess?: (statusCode: number, attempts: number, queuedMs: number) => void;
44
+ /** Called on failure (exhausted retries or policy veto inside the loop)
45
+ * so the client can emit the failure event. `queuedMs` is cumulative
46
+ * across all attempts made so far. */
47
+ onFailure?: (error: AppError, attempts: number, queuedMs: number) => void;
48
+ /** Called on cancellation (user cancel, deadline, or external signal)
49
+ * so the client can emit the cancelled event. `queuedMs` is cumulative
50
+ * across all attempts made so far. */
51
+ onCancelled?: (attempts: number, queuedMs: number) => void;
52
+ /** Called before markDone to clean up external resources (e.g. signal listeners). */
53
+ onCleanup?: () => void;
54
+ /** Custom fetch function. Falls back to globalThis.fetch. */
55
+ fetch?: typeof globalThis.fetch;
56
+ }
57
+ export declare function runRetryLoop(job: RetryJobOptions): Promise<void>;
58
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/queue/retry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAWlD,OAAO,EAIN,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC/B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB;;;;uCAImC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,EAAE,QAAQ,CAAC;IACnB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,sEAAsE;IACtE,cAAc,EAAE,cAAc,CAAC;IAC/B,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,UAAU,EAAE,QAAQ,CAAC;IACrB;;4EAEwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtE;;6EAEyE;IACzE,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7E;;2CAEuC;IACvC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E;;2CAEuC;IACvC,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6QtE"}