@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.
- package/LICENSE +21 -0
- package/README.md +604 -0
- package/dist/adapters/zod.d.ts +14 -0
- package/dist/adapters/zod.d.ts.map +1 -0
- package/dist/adapters/zod.js +14 -0
- package/dist/adapters/zod.js.map +1 -0
- package/dist/core/backoff.d.ts +9 -0
- package/dist/core/backoff.d.ts.map +1 -0
- package/dist/core/backoff.js +23 -0
- package/dist/core/backoff.js.map +1 -0
- package/dist/core/client.d.ts +98 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +781 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/errors.d.ts +87 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +140 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/listeners.d.ts +14 -0
- package/dist/core/listeners.d.ts.map +1 -0
- package/dist/core/listeners.js +27 -0
- package/dist/core/listeners.js.map +1 -0
- package/dist/core/metrics.d.ts +33 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +24 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/nanoid.d.ts +2 -0
- package/dist/core/nanoid.d.ts.map +1 -0
- package/dist/core/nanoid.js +11 -0
- package/dist/core/nanoid.js.map +1 -0
- package/dist/core/redact.d.ts +12 -0
- package/dist/core/redact.d.ts.map +1 -0
- package/dist/core/redact.js +42 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/core/types.d.ts +261 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +41 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validate.d.ts +19 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +135 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/middleware/index.d.ts +26 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +55 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/queue/bulkhead.d.ts +63 -0
- package/dist/queue/bulkhead.d.ts.map +1 -0
- package/dist/queue/bulkhead.js +192 -0
- package/dist/queue/bulkhead.js.map +1 -0
- package/dist/queue/circuit-breaker.d.ts +81 -0
- package/dist/queue/circuit-breaker.d.ts.map +1 -0
- package/dist/queue/circuit-breaker.js +283 -0
- package/dist/queue/circuit-breaker.js.map +1 -0
- package/dist/queue/executor.d.ts +67 -0
- package/dist/queue/executor.d.ts.map +1 -0
- package/dist/queue/executor.js +273 -0
- package/dist/queue/executor.js.map +1 -0
- package/dist/queue/policy.d.ts +26 -0
- package/dist/queue/policy.d.ts.map +1 -0
- package/dist/queue/policy.js +37 -0
- package/dist/queue/policy.js.map +1 -0
- package/dist/queue/retry.d.ts +58 -0
- package/dist/queue/retry.d.ts.map +1 -0
- package/dist/queue/retry.js +259 -0
- package/dist/queue/retry.js.map +1 -0
- package/dist/queue/semaphore.d.ts +32 -0
- package/dist/queue/semaphore.d.ts.map +1 -0
- package/dist/queue/semaphore.js +83 -0
- package/dist/queue/semaphore.js.map +1 -0
- package/dist/ticket/ticket.d.ts +77 -0
- package/dist/ticket/ticket.d.ts.map +1 -0
- package/dist/ticket/ticket.js +186 -0
- package/dist/ticket/ticket.js.map +1 -0
- package/package.json +85 -0
- package/src/adapters/zod.ts +16 -0
- package/src/core/backoff.ts +26 -0
- package/src/core/client.ts +1048 -0
- package/src/core/errors.ts +194 -0
- package/src/core/index.ts +56 -0
- package/src/core/listeners.ts +28 -0
- package/src/core/metrics.ts +42 -0
- package/src/core/nanoid.ts +11 -0
- package/src/core/redact.ts +46 -0
- package/src/core/types.ts +306 -0
- package/src/core/validate.ts +163 -0
- package/src/middleware/index.ts +63 -0
- package/src/queue/bulkhead.ts +243 -0
- package/src/queue/circuit-breaker.ts +373 -0
- package/src/queue/executor.ts +355 -0
- package/src/queue/policy.ts +49 -0
- package/src/queue/retry.ts +380 -0
- package/src/queue/semaphore.ts +91 -0
- package/src/ticket/ticket.ts +246 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { AppError } from "./errors.ts";
|
|
2
|
+
import type { MetricsSink } from "./metrics.ts";
|
|
3
|
+
export type Result<T> = {
|
|
4
|
+
success: true;
|
|
5
|
+
data: T;
|
|
6
|
+
raw: Response;
|
|
7
|
+
} | {
|
|
8
|
+
success: false;
|
|
9
|
+
error: AppError;
|
|
10
|
+
};
|
|
11
|
+
export type ParseFn<T> = (data: unknown) => T;
|
|
12
|
+
export type BackoffFn = (attempt: number) => number;
|
|
13
|
+
export interface BackoffOptions {
|
|
14
|
+
/** Base delay in ms. @default {@link DEFAULT_BASE_DELAY_MS} */
|
|
15
|
+
baseDelayMs?: number;
|
|
16
|
+
/** Maximum delay cap in ms. @default {@link DEFAULT_MAX_DELAY_MS} */
|
|
17
|
+
maxDelayMs?: number;
|
|
18
|
+
/** Whether to add jitter. @default {@link DEFAULT_JITTER} */
|
|
19
|
+
jitter?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface TimeoutConfig {
|
|
22
|
+
/** Per-attempt timeout in ms. Omit to inherit the client-level default;
|
|
23
|
+
* `Infinity` explicitly means no per-attempt cap. Also bounds, from
|
|
24
|
+
* attempt start, how long the caller has to read the body of a Response
|
|
25
|
+
* handed back unread (a success without `parse`, or an `HttpError`'s
|
|
26
|
+
* `.response`) before that read is aborted. */
|
|
27
|
+
attemptMs?: number;
|
|
28
|
+
/** Whole-ticket deadline in ms. Starts at request(); cancels the ticket
|
|
29
|
+
* and resolves with DeadlineExceededError on expiry. Omit (or pass
|
|
30
|
+
* `Infinity`) for no total deadline. Also caps how long the caller has
|
|
31
|
+
* to read an unread Response's body handed back as above, if that's
|
|
32
|
+
* sooner than `attemptMs` would otherwise allow. */
|
|
33
|
+
totalMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/** `ClientConfig.timeout` variant where `attemptMs` is mandatory. Every
|
|
36
|
+
* client must explicitly decide its per-attempt timeout — `Infinity` is a
|
|
37
|
+
* legal, deliberate choice to opt out of a cap. This is the one default in
|
|
38
|
+
* the library that cannot safely be implicit: every other default (retries,
|
|
39
|
+
* concurrency, queue sizes) fails safe when omitted; an omitted timeout
|
|
40
|
+
* fails unbounded. Partition- and request-level `timeout` stay optional —
|
|
41
|
+
* they inherit this client-level decision unless they override it. */
|
|
42
|
+
export interface ClientTimeoutConfig extends Omit<TimeoutConfig, "attemptMs"> {
|
|
43
|
+
attemptMs: number;
|
|
44
|
+
}
|
|
45
|
+
/** True when `ms` is a real, finite bound — not omitted and not `Infinity`
|
|
46
|
+
* (the explicit "no cap" value). Use instead of `!== undefined` wherever a
|
|
47
|
+
* timeout/deadline value is checked, since `Infinity` must be treated the
|
|
48
|
+
* same as "not set" everywhere a timer would otherwise be created. */
|
|
49
|
+
export declare function isBoundedMs(ms: number | undefined): ms is number;
|
|
50
|
+
export declare const DEFAULT_RETRY_ON_STATUS: number[];
|
|
51
|
+
/** Default per-partition concurrency limit (decision D1). */
|
|
52
|
+
export declare const DEFAULT_CONCURRENCY = 5;
|
|
53
|
+
/** Default max queued items per partition before rejecting new ones. */
|
|
54
|
+
export declare const DEFAULT_MAX_QUEUE_SIZE = 100;
|
|
55
|
+
export interface PartitionConfig {
|
|
56
|
+
/** Max concurrent in-flight retries for this partition.
|
|
57
|
+
* @default {@link DEFAULT_CONCURRENCY} */
|
|
58
|
+
concurrency?: number;
|
|
59
|
+
/** Max number of pending items in the queue before rejecting new ones.
|
|
60
|
+
* @default {@link DEFAULT_MAX_QUEUE_SIZE} */
|
|
61
|
+
maxQueueSize?: number;
|
|
62
|
+
/** When true, the first attempt also goes through the bulkhead (R6).
|
|
63
|
+
* @default false */
|
|
64
|
+
limitFirstAttempts?: boolean;
|
|
65
|
+
retry?: RetryConfig;
|
|
66
|
+
timeout?: TimeoutConfig;
|
|
67
|
+
circuitBreaker?: CircuitBreakerConfig;
|
|
68
|
+
}
|
|
69
|
+
/** Default consecutive-failure count that trips the circuit open. */
|
|
70
|
+
export declare const DEFAULT_FAILURE_THRESHOLD = 5;
|
|
71
|
+
/** Default ms to stay open before allowing a half-open trial. */
|
|
72
|
+
export declare const DEFAULT_RESET_TIMEOUT_MS = 30000;
|
|
73
|
+
/** Default number of concurrent trial requests allowed while half-open. */
|
|
74
|
+
export declare const DEFAULT_HALF_OPEN_MAX_ATTEMPTS = 1;
|
|
75
|
+
export interface CircuitBreakerConfig {
|
|
76
|
+
/** @default false */
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
/** Consecutive-failure trip mode (default strategy).
|
|
79
|
+
* @default {@link DEFAULT_FAILURE_THRESHOLD} */
|
|
80
|
+
failureThreshold?: number;
|
|
81
|
+
/** Rolling-window trip mode. If set, used INSTEAD of failureThreshold. */
|
|
82
|
+
window?: {
|
|
83
|
+
sizeMs: number;
|
|
84
|
+
failureRatePercent: number;
|
|
85
|
+
minimumRequests: number;
|
|
86
|
+
};
|
|
87
|
+
/** ms to stay open before allowing a half-open trial.
|
|
88
|
+
* @default {@link DEFAULT_RESET_TIMEOUT_MS} */
|
|
89
|
+
resetTimeoutMs?: number;
|
|
90
|
+
/** concurrent trial requests allowed while half-open.
|
|
91
|
+
* @default {@link DEFAULT_HALF_OPEN_MAX_ATTEMPTS} */
|
|
92
|
+
halfOpenMaxAttempts?: number;
|
|
93
|
+
/** Override default failure classification (network/timeout/retryable_status).
|
|
94
|
+
* Not consulted for an error that shows the attempt never reached the host
|
|
95
|
+
* (e.g. a body factory that threw before the request was dispatched) —
|
|
96
|
+
* those are always ignored, since there is no host-health signal to classify. */
|
|
97
|
+
isFailure?: (error: AppError) => boolean;
|
|
98
|
+
}
|
|
99
|
+
/** Default number of retries after the first attempt. */
|
|
100
|
+
export declare const DEFAULT_MAX_RETRIES = 3;
|
|
101
|
+
export interface RetryConfig {
|
|
102
|
+
/** Retries after the first attempt. `MaxRetriesExceededError.attempts` is
|
|
103
|
+
* total executions, i.e. `maxRetries + 1`.
|
|
104
|
+
* @default {@link DEFAULT_MAX_RETRIES} */
|
|
105
|
+
maxRetries?: number;
|
|
106
|
+
/** @default `{ baseDelayMs: 200, maxDelayMs: 30_000, jitter: true }` */
|
|
107
|
+
backoff?: BackoffFn | BackoffOptions;
|
|
108
|
+
/** HTTP status codes that trigger retry (e.g. 408, 429, 500, 502, 503, 504).
|
|
109
|
+
* @default {@link DEFAULT_RETRY_ON_STATUS} */
|
|
110
|
+
retryOnStatus?: number[];
|
|
111
|
+
/** Allows retrying non-idempotent methods (POST/PATCH/CONNECT). An
|
|
112
|
+
* `Idempotency-Key` header also enables retries. Default: false. */
|
|
113
|
+
idempotent?: boolean;
|
|
114
|
+
/** Optional predicate to decide whether a failed attempt should be retried.
|
|
115
|
+
* Called exactly once per failed attempt that could still be retried, with
|
|
116
|
+
* the zero-based attempt number:
|
|
117
|
+
* - 0 = the first attempt (called client-side before the retry loop)
|
|
118
|
+
* - 1, 2, … = retries (called inside the loop, after attempt 0)
|
|
119
|
+
* Returning `false` surfaces the error immediately without retrying. Not
|
|
120
|
+
* called after the final attempt when no retries remain — there is
|
|
121
|
+
* nothing left to decide. */
|
|
122
|
+
retryWhen?: (error: AppError, attempt: number) => boolean;
|
|
123
|
+
}
|
|
124
|
+
export interface Logger {
|
|
125
|
+
debug(msg: string, meta?: Record<string, unknown>): void;
|
|
126
|
+
info(msg: string, meta?: Record<string, unknown>): void;
|
|
127
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
128
|
+
error(msg: string, meta?: Record<string, unknown>): void;
|
|
129
|
+
}
|
|
130
|
+
export type LifecycleEventMap = {
|
|
131
|
+
request: {
|
|
132
|
+
ticketId: string;
|
|
133
|
+
url: string;
|
|
134
|
+
method: string;
|
|
135
|
+
partition: string;
|
|
136
|
+
};
|
|
137
|
+
/** Zero-based retry index (0 = first retry after the initial attempt).
|
|
138
|
+
* Note: retryWhen's attempt parameter uses a different numbering —
|
|
139
|
+
* 0 = first attempt, 1 = first retry, etc. */
|
|
140
|
+
retry: {
|
|
141
|
+
ticketId: string;
|
|
142
|
+
url: string;
|
|
143
|
+
partition: string;
|
|
144
|
+
attempt: number;
|
|
145
|
+
delayMs: number;
|
|
146
|
+
error: AppError;
|
|
147
|
+
};
|
|
148
|
+
success: {
|
|
149
|
+
ticketId: string;
|
|
150
|
+
url: string;
|
|
151
|
+
partition: string;
|
|
152
|
+
attempts: number;
|
|
153
|
+
durationMs: number;
|
|
154
|
+
queuedMs: number;
|
|
155
|
+
statusCode: number;
|
|
156
|
+
};
|
|
157
|
+
failure: {
|
|
158
|
+
ticketId: string;
|
|
159
|
+
url: string;
|
|
160
|
+
/** `undefined` only when the URL couldn't be resolved, so no partition
|
|
161
|
+
* was ever chosen (no `request` event is emitted in that case either). */
|
|
162
|
+
partition: string | undefined;
|
|
163
|
+
attempts: number;
|
|
164
|
+
durationMs: number;
|
|
165
|
+
queuedMs: number;
|
|
166
|
+
error: AppError;
|
|
167
|
+
};
|
|
168
|
+
cancelled: {
|
|
169
|
+
ticketId: string;
|
|
170
|
+
url: string;
|
|
171
|
+
partition: string;
|
|
172
|
+
attempts: number;
|
|
173
|
+
durationMs: number;
|
|
174
|
+
queuedMs: number;
|
|
175
|
+
};
|
|
176
|
+
circuitOpen: {
|
|
177
|
+
partition: string;
|
|
178
|
+
};
|
|
179
|
+
circuitClose: {
|
|
180
|
+
partition: string;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
export interface RequestOptions<T = unknown> {
|
|
184
|
+
/** @default "GET" */
|
|
185
|
+
method?: string;
|
|
186
|
+
headers?: HeadersInit;
|
|
187
|
+
/** Request body, or a factory that returns a fresh body on every attempt
|
|
188
|
+
* (replayable body). A ReadableStream must be supplied via a factory. The
|
|
189
|
+
* factory must return a fresh body each invocation — reusing the same
|
|
190
|
+
* ReadableStream replays an already-consumed (empty) stream. */
|
|
191
|
+
body?: BodyInit | (() => BodyInit);
|
|
192
|
+
/** Named bulkhead partition. Defaults to `host` (hostname + port when non-default). */
|
|
193
|
+
partition?: string;
|
|
194
|
+
/** Schema parse function. Use withZod() or custom. */
|
|
195
|
+
parse?: ParseFn<T>;
|
|
196
|
+
retry?: RetryConfig;
|
|
197
|
+
timeout?: TimeoutConfig;
|
|
198
|
+
/** Signal to cancel the request externally */
|
|
199
|
+
signal?: AbortSignal;
|
|
200
|
+
}
|
|
201
|
+
/** Request options with `parse` set: the ticket resolves with `data: T`. */
|
|
202
|
+
export type ParsedRequestOptions<T> = Omit<RequestOptions<T>, "parse"> & {
|
|
203
|
+
parse: ParseFn<T>;
|
|
204
|
+
};
|
|
205
|
+
/** Request options without `parse`: the body is left unread on `raw` and
|
|
206
|
+
* `data` is `undefined`. `parse: undefined` is accepted so options can be
|
|
207
|
+
* built conditionally. */
|
|
208
|
+
export type UnparsedRequestOptions = Omit<RequestOptions, "parse"> & {
|
|
209
|
+
parse?: undefined;
|
|
210
|
+
};
|
|
211
|
+
/** Default global concurrency cap across all partitions (decision D1). */
|
|
212
|
+
export declare const DEFAULT_GLOBAL_CONCURRENCY = 50;
|
|
213
|
+
/** Default max requests queued globally, across all partitions, waiting for
|
|
214
|
+
* a concurrency permit before new requests are rejected with QueueFullError. */
|
|
215
|
+
export declare const DEFAULT_GLOBAL_QUEUE_SIZE = 100;
|
|
216
|
+
export interface ClientConfig {
|
|
217
|
+
/** Base URL prepended to all requests */
|
|
218
|
+
baseUrl?: string;
|
|
219
|
+
/** Default retry config */
|
|
220
|
+
retry?: RetryConfig;
|
|
221
|
+
/** Default timeout config. `attemptMs` is required — pass `Infinity` to
|
|
222
|
+
* explicitly opt out of a per-attempt cap, so "no timeout" is always a
|
|
223
|
+
* deliberate choice rather than an accidental default. */
|
|
224
|
+
timeout: ClientTimeoutConfig;
|
|
225
|
+
/** Global concurrency across all partitions.
|
|
226
|
+
* @default {@link DEFAULT_GLOBAL_CONCURRENCY} */
|
|
227
|
+
concurrency?: number;
|
|
228
|
+
/** Max requests queued globally (across all partitions) waiting for a
|
|
229
|
+
* concurrency permit before new requests are rejected with QueueFullError.
|
|
230
|
+
* @default {@link DEFAULT_GLOBAL_QUEUE_SIZE} */
|
|
231
|
+
maxQueueSize?: number;
|
|
232
|
+
/** Per-partition overrides. Partitions not listed here use
|
|
233
|
+
* `{ concurrency: 5, maxQueueSize: 100 }`.
|
|
234
|
+
* @default {} */
|
|
235
|
+
partitions?: Record<string, PartitionConfig>;
|
|
236
|
+
/** Default circuit breaker config. Opt-in — inert unless `enabled: true`. */
|
|
237
|
+
circuitBreaker?: CircuitBreakerConfig;
|
|
238
|
+
/** Optional structured logger */
|
|
239
|
+
logger?: Logger;
|
|
240
|
+
/** Optional metrics sink for counters, histograms, and gauges. */
|
|
241
|
+
metrics?: MetricsSink;
|
|
242
|
+
/** Redact query parameter values in logged URLs.
|
|
243
|
+
* @default true */
|
|
244
|
+
redactQuery?: boolean;
|
|
245
|
+
/** Custom fetch function (defaults to globalThis.fetch). */
|
|
246
|
+
fetch?: typeof globalThis.fetch;
|
|
247
|
+
}
|
|
248
|
+
/** Options for `HttpClient.close()`.
|
|
249
|
+
*
|
|
250
|
+
* Modelled as a union so the type mirrors the runtime contract: `timeoutMs` is
|
|
251
|
+
* required only when draining. A single `{ drain?: boolean; timeoutMs: number }`
|
|
252
|
+
* shape made `close({ drain: false })` a type error even though it is the
|
|
253
|
+
* documented, working way to close without draining. */
|
|
254
|
+
export type CloseOptions = {
|
|
255
|
+
drain?: false;
|
|
256
|
+
timeoutMs?: number;
|
|
257
|
+
} | {
|
|
258
|
+
drain: true;
|
|
259
|
+
timeoutMs: number;
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** True when `ms` is a real, finite bound — not omitted and not `Infinity`
|
|
2
|
+
* (the explicit "no cap" value). Use instead of `!== undefined` wherever a
|
|
3
|
+
* timeout/deadline value is checked, since `Infinity` must be treated the
|
|
4
|
+
* same as "not set" everywhere a timer would otherwise be created. */
|
|
5
|
+
export function isBoundedMs(ms) {
|
|
6
|
+
return ms !== undefined && Number.isFinite(ms);
|
|
7
|
+
}
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Default retry-on-status codes (D1)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export const DEFAULT_RETRY_ON_STATUS = [408, 425, 429, 500, 502, 503, 504];
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Partition / bulkhead config
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/** Default per-partition concurrency limit (decision D1). */
|
|
16
|
+
export const DEFAULT_CONCURRENCY = 5;
|
|
17
|
+
/** Default max queued items per partition before rejecting new ones. */
|
|
18
|
+
export const DEFAULT_MAX_QUEUE_SIZE = 100;
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Circuit breaker config
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/** Default consecutive-failure count that trips the circuit open. */
|
|
23
|
+
export const DEFAULT_FAILURE_THRESHOLD = 5;
|
|
24
|
+
/** Default ms to stay open before allowing a half-open trial. */
|
|
25
|
+
export const DEFAULT_RESET_TIMEOUT_MS = 30_000;
|
|
26
|
+
/** Default number of concurrent trial requests allowed while half-open. */
|
|
27
|
+
export const DEFAULT_HALF_OPEN_MAX_ATTEMPTS = 1;
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Retry config
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/** Default number of retries after the first attempt. */
|
|
32
|
+
export const DEFAULT_MAX_RETRIES = 3;
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Client config
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Default global concurrency cap across all partitions (decision D1). */
|
|
37
|
+
export const DEFAULT_GLOBAL_CONCURRENCY = 50;
|
|
38
|
+
/** Default max requests queued globally, across all partitions, waiting for
|
|
39
|
+
* a concurrency permit before new requests are rejected with QueueFullError. */
|
|
40
|
+
export const DEFAULT_GLOBAL_QUEUE_SIZE = 100;
|
|
41
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AA4DA;;;uEAGuE;AACvE,MAAM,UAAU,WAAW,CAAC,EAAsB;IACjD,OAAO,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,qCAAqC;AACrC,8EAA8E;AAE9E,MAAM,CAAC,MAAM,uBAAuB,GAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAErF,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAiB1C,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E,qEAAqE;AACrE,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAC3C,iEAAiE;AACjE,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC/C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AA2BhD,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,yDAAyD;AACzD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAqHrC,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,0EAA0E;AAC1E,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAC7C;iFACiF;AACjF,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClientConfig, RequestOptions } from "./types.ts";
|
|
2
|
+
/** Realm-safe ReadableStream detection — instanceof fails across realms
|
|
3
|
+
* (vm contexts, other copies of node:stream/web). No non-stream BodyInit
|
|
4
|
+
* member has getReader, so duck-typing is safe here. */
|
|
5
|
+
export declare function isReadableStream(body: unknown): body is ReadableStream;
|
|
6
|
+
/** A raw ReadableStream body cannot be replayed across retries, so it must be
|
|
7
|
+
* supplied via a factory. Throws ConfigurationError otherwise. */
|
|
8
|
+
export declare function validateRequestBody(body: BodyInit | (() => BodyInit) | undefined): void;
|
|
9
|
+
export declare function validateConfig(config: ClientConfig): void;
|
|
10
|
+
/** Validates a single request's own `timeout`/`retry` options — the raw
|
|
11
|
+
* values passed to `client.get()`/`.post()`/etc., before they're merged onto
|
|
12
|
+
* partition/client defaults. Reuses the same rules as `validateConfig`, so
|
|
13
|
+
* an invalid request-level value (e.g. `timeout: { attemptMs: -5 }`) is
|
|
14
|
+
* rejected the same way an invalid client config is, just surfaced as a
|
|
15
|
+
* ticket `ConfigurationError` instead of a throw from `create()`. Omitted
|
|
16
|
+
* fields stay valid (inherit the client/partition default), and `Infinity`
|
|
17
|
+
* stays a legal explicit `attemptMs`/`totalMs`. */
|
|
18
|
+
export declare function validateRequestOptions(options: Pick<RequestOptions, "timeout" | "retry">): void;
|
|
19
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,YAAY,EAEZ,cAAc,EAGd,MAAM,YAAY,CAAC;AAUpB;;yDAEyD;AACzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc,CAEtE;AAED;mEACmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,SAAS,GAAG,IAAI,CAIvF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAuBzD;AAED;;;;;;;oDAOoD;AACpD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,CAG/F"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ConfigurationError } from "./errors.js";
|
|
2
|
+
// Every numeric check is written so that NaN fails it: `NaN <= 0` is false,
|
|
3
|
+
// so a bare `x <= 0` guard would let NaN through (and `attemptMs: NaN` would
|
|
4
|
+
// silently mean "no timeout"). `!(x > 0)` rejects it.
|
|
5
|
+
function isPositiveInteger(value) {
|
|
6
|
+
return Number.isInteger(value) && value >= 1;
|
|
7
|
+
}
|
|
8
|
+
/** Realm-safe ReadableStream detection — instanceof fails across realms
|
|
9
|
+
* (vm contexts, other copies of node:stream/web). No non-stream BodyInit
|
|
10
|
+
* member has getReader, so duck-typing is safe here. */
|
|
11
|
+
export function isReadableStream(body) {
|
|
12
|
+
return body != null && typeof body.getReader === "function";
|
|
13
|
+
}
|
|
14
|
+
/** A raw ReadableStream body cannot be replayed across retries, so it must be
|
|
15
|
+
* supplied via a factory. Throws ConfigurationError otherwise. */
|
|
16
|
+
export function validateRequestBody(body) {
|
|
17
|
+
if (isReadableStream(body)) {
|
|
18
|
+
throw new ConfigurationError("body must be supplied as a factory (() => BodyInit) when it is a ReadableStream");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function validateConfig(config) {
|
|
22
|
+
if (config.baseUrl !== undefined && !URL.canParse(config.baseUrl)) {
|
|
23
|
+
throw new ConfigurationError("baseUrl must be an absolute URL");
|
|
24
|
+
}
|
|
25
|
+
if (config.concurrency !== undefined && !isPositiveInteger(config.concurrency)) {
|
|
26
|
+
throw new ConfigurationError("concurrency must be a positive integer");
|
|
27
|
+
}
|
|
28
|
+
// 0 is valid globally: no waiting, overflow rejects with QueueFullError.
|
|
29
|
+
if (config.maxQueueSize !== undefined && !(Number.isInteger(config.maxQueueSize) && config.maxQueueSize >= 0)) {
|
|
30
|
+
throw new ConfigurationError("maxQueueSize must be a non-negative integer");
|
|
31
|
+
}
|
|
32
|
+
if (!config.timeout || config.timeout.attemptMs === undefined) {
|
|
33
|
+
throw new ConfigurationError("timeout.attemptMs is required — pass a positive number, or Infinity to explicitly opt out of a per-attempt timeout");
|
|
34
|
+
}
|
|
35
|
+
validateTimeoutConfig(config.timeout, "timeout");
|
|
36
|
+
validateRetryConfig(config.retry, "retry");
|
|
37
|
+
validateCircuitBreakerConfig(config.circuitBreaker, "circuitBreaker");
|
|
38
|
+
validatePartitions(config.partitions);
|
|
39
|
+
}
|
|
40
|
+
/** Validates a single request's own `timeout`/`retry` options — the raw
|
|
41
|
+
* values passed to `client.get()`/`.post()`/etc., before they're merged onto
|
|
42
|
+
* partition/client defaults. Reuses the same rules as `validateConfig`, so
|
|
43
|
+
* an invalid request-level value (e.g. `timeout: { attemptMs: -5 }`) is
|
|
44
|
+
* rejected the same way an invalid client config is, just surfaced as a
|
|
45
|
+
* ticket `ConfigurationError` instead of a throw from `create()`. Omitted
|
|
46
|
+
* fields stay valid (inherit the client/partition default), and `Infinity`
|
|
47
|
+
* stays a legal explicit `attemptMs`/`totalMs`. */
|
|
48
|
+
export function validateRequestOptions(options) {
|
|
49
|
+
validateTimeoutConfig(options.timeout, "request.timeout");
|
|
50
|
+
validateRetryConfig(options.retry, "request.retry");
|
|
51
|
+
}
|
|
52
|
+
function validateTimeoutConfig(timeout, prefix) {
|
|
53
|
+
if (!timeout)
|
|
54
|
+
return;
|
|
55
|
+
if (timeout.attemptMs !== undefined && !(timeout.attemptMs > 0)) {
|
|
56
|
+
throw new ConfigurationError(`${prefix}.attemptMs must be positive`);
|
|
57
|
+
}
|
|
58
|
+
if (timeout.totalMs !== undefined && !(timeout.totalMs > 0)) {
|
|
59
|
+
throw new ConfigurationError(`${prefix}.totalMs must be positive`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function validateRetryConfig(retry, prefix) {
|
|
63
|
+
if (!retry)
|
|
64
|
+
return;
|
|
65
|
+
if (retry.maxRetries !== undefined && !(Number.isInteger(retry.maxRetries) && retry.maxRetries >= 0)) {
|
|
66
|
+
throw new ConfigurationError(`${prefix}.maxRetries must be a non-negative integer`);
|
|
67
|
+
}
|
|
68
|
+
if (retry.retryOnStatus !== undefined) {
|
|
69
|
+
for (const status of retry.retryOnStatus) {
|
|
70
|
+
if (!Number.isInteger(status) || status < 400 || status > 599) {
|
|
71
|
+
throw new ConfigurationError(`${prefix}.retryOnStatus must contain only integer HTTP error status codes (400-599)`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (retry.backoff && typeof retry.backoff === "object") {
|
|
76
|
+
const { baseDelayMs, maxDelayMs } = retry.backoff;
|
|
77
|
+
if (baseDelayMs !== undefined && !(baseDelayMs >= 0)) {
|
|
78
|
+
throw new ConfigurationError(`${prefix}.backoff.baseDelayMs must be non-negative`);
|
|
79
|
+
}
|
|
80
|
+
if (maxDelayMs !== undefined && !(maxDelayMs >= 0)) {
|
|
81
|
+
throw new ConfigurationError(`${prefix}.backoff.maxDelayMs must be non-negative`);
|
|
82
|
+
}
|
|
83
|
+
if (baseDelayMs !== undefined && maxDelayMs !== undefined && baseDelayMs > maxDelayMs) {
|
|
84
|
+
throw new ConfigurationError(`${prefix}.backoff.baseDelayMs must not exceed maxDelayMs`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function validatePartitions(partitions) {
|
|
89
|
+
if (!partitions)
|
|
90
|
+
return;
|
|
91
|
+
for (const [name, config] of Object.entries(partitions)) {
|
|
92
|
+
if (config.concurrency !== undefined && !isPositiveInteger(config.concurrency)) {
|
|
93
|
+
throw new ConfigurationError(`partitions.${name}.concurrency must be a positive integer`);
|
|
94
|
+
}
|
|
95
|
+
if (config.maxQueueSize !== undefined && !isPositiveInteger(config.maxQueueSize)) {
|
|
96
|
+
throw new ConfigurationError(`partitions.${name}.maxQueueSize must be a positive integer`);
|
|
97
|
+
}
|
|
98
|
+
validateRetryConfig(config.retry, `partitions.${name}.retry`);
|
|
99
|
+
validateTimeoutConfig(config.timeout, `partitions.${name}.timeout`);
|
|
100
|
+
validateCircuitBreakerConfig(config.circuitBreaker, `partitions.${name}.circuitBreaker`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function validateCircuitBreakerConfig(breaker, prefix) {
|
|
104
|
+
if (!breaker)
|
|
105
|
+
return;
|
|
106
|
+
if (breaker.failureThreshold !== undefined && !isPositiveInteger(breaker.failureThreshold)) {
|
|
107
|
+
throw new ConfigurationError(`${prefix}.failureThreshold must be a positive integer`);
|
|
108
|
+
}
|
|
109
|
+
// Finite: the open -> half-open transition is a timer, and an unbounded
|
|
110
|
+
// reset would keep the circuit open forever.
|
|
111
|
+
if (breaker.resetTimeoutMs !== undefined &&
|
|
112
|
+
!(Number.isFinite(breaker.resetTimeoutMs) && breaker.resetTimeoutMs > 0)) {
|
|
113
|
+
throw new ConfigurationError(`${prefix}.resetTimeoutMs must be a positive finite number`);
|
|
114
|
+
}
|
|
115
|
+
// 0 would admit no half-open trial, so the circuit could never close again.
|
|
116
|
+
if (breaker.halfOpenMaxAttempts !== undefined && !isPositiveInteger(breaker.halfOpenMaxAttempts)) {
|
|
117
|
+
throw new ConfigurationError(`${prefix}.halfOpenMaxAttempts must be a positive integer`);
|
|
118
|
+
}
|
|
119
|
+
if (breaker.isFailure !== undefined && typeof breaker.isFailure !== "function") {
|
|
120
|
+
throw new ConfigurationError(`${prefix}.isFailure must be a function`);
|
|
121
|
+
}
|
|
122
|
+
const { window } = breaker;
|
|
123
|
+
if (window === undefined)
|
|
124
|
+
return;
|
|
125
|
+
if (!(Number.isFinite(window.sizeMs) && window.sizeMs > 0)) {
|
|
126
|
+
throw new ConfigurationError(`${prefix}.window.sizeMs must be a positive finite number`);
|
|
127
|
+
}
|
|
128
|
+
if (!(window.failureRatePercent > 0 && window.failureRatePercent <= 100)) {
|
|
129
|
+
throw new ConfigurationError(`${prefix}.window.failureRatePercent must be greater than 0 and at most 100`);
|
|
130
|
+
}
|
|
131
|
+
if (!isPositiveInteger(window.minimumRequests)) {
|
|
132
|
+
throw new ConfigurationError(`${prefix}.window.minimumRequests must be a positive integer`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type { MiddlewareFn, NextFn, RequestContext } from "../queue/executor.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Adds a base set of headers to every request. Only sets a header the
|
|
4
|
+
* request doesn't already have — comparison is case-insensitive, so a
|
|
5
|
+
* request-level `authorization` header wins over a default `Authorization`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* client.use(defaultHeaders({ 'X-Api-Key': 'secret' }));
|
|
9
|
+
*/
|
|
10
|
+
export declare function defaultHeaders(headers: HeadersInit): import("../queue/executor.ts").MiddlewareFn;
|
|
11
|
+
/**
|
|
12
|
+
* Logs request timing to the console (or a provided logger). Logged URLs are
|
|
13
|
+
* redacted like the client's own (`redactUrl`): query values and userinfo
|
|
14
|
+
* credentials are replaced with `[redacted]`. Middleware can't see the
|
|
15
|
+
* client's `redactQuery` setting, so pass `redactQuery: false` here too to
|
|
16
|
+
* log raw URLs.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* client.use(requestLogger());
|
|
20
|
+
*/
|
|
21
|
+
export declare function requestLogger(options?: {
|
|
22
|
+
log?: (msg: string, meta: Record<string, unknown>) => void;
|
|
23
|
+
/** @default true */
|
|
24
|
+
redactQuery?: boolean;
|
|
25
|
+
}): import("../queue/executor.ts").MiddlewareFn;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,sBAAsB,EAAE,YAAY,CAShG;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE;IACvC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC3D,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,sBAAsB,EAAE,YAAY,CAyB9C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { redactUrl } from "../core/redact.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adds a base set of headers to every request. Only sets a header the
|
|
4
|
+
* request doesn't already have — comparison is case-insensitive, so a
|
|
5
|
+
* request-level `authorization` header wins over a default `Authorization`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* client.use(defaultHeaders({ 'X-Api-Key': 'secret' }));
|
|
9
|
+
*/
|
|
10
|
+
export function defaultHeaders(headers) {
|
|
11
|
+
return async (ctx, next) => {
|
|
12
|
+
for (const [key, value] of new Headers(headers)) {
|
|
13
|
+
if (!ctx.headers.has(key)) {
|
|
14
|
+
ctx.headers.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return next(ctx);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Logs request timing to the console (or a provided logger). Logged URLs are
|
|
22
|
+
* redacted like the client's own (`redactUrl`): query values and userinfo
|
|
23
|
+
* credentials are replaced with `[redacted]`. Middleware can't see the
|
|
24
|
+
* client's `redactQuery` setting, so pass `redactQuery: false` here too to
|
|
25
|
+
* log raw URLs.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* client.use(requestLogger());
|
|
29
|
+
*/
|
|
30
|
+
export function requestLogger(options) {
|
|
31
|
+
// biome-ignore lint/suspicious/noConsole: console is the intended default sink for this opt-in logger middleware; callers override it via options.log.
|
|
32
|
+
const log = options?.log ?? ((msg, meta) => console.log(msg, meta));
|
|
33
|
+
const shownUrl = options?.redactQuery === false ? (url) => url : redactUrl;
|
|
34
|
+
return async (ctx, next) => {
|
|
35
|
+
const start = Date.now();
|
|
36
|
+
try {
|
|
37
|
+
const response = await next(ctx);
|
|
38
|
+
log("Request completed", {
|
|
39
|
+
url: shownUrl(ctx.url),
|
|
40
|
+
status: response.status,
|
|
41
|
+
durationMs: Date.now() - start,
|
|
42
|
+
});
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
log("Request failed", {
|
|
47
|
+
url: shownUrl(ctx.url),
|
|
48
|
+
error: err instanceof Error ? err.message : String(err),
|
|
49
|
+
durationMs: Date.now() - start,
|
|
50
|
+
});
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI9C;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoB;IAClD,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,OAI7B;IACA,uJAAuJ;IACvJ,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAEpE,MAAM,QAAQ,GAAG,OAAO,EAAE,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnF,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YACjC,GAAG,CAAC,mBAAmB,EAAE;gBACxB,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gBACtB,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;aAC9B,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,gBAAgB,EAAE;gBACrB,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gBACtB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACvD,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;aAC9B,CAAC,CAAC;YACH,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type PartitionConfig } from "../core/types.ts";
|
|
2
|
+
import type { Semaphore } from "./semaphore.ts";
|
|
3
|
+
/** Default TTL (ms) before an idle partition registry entry (bulkhead or
|
|
4
|
+
* circuit breaker) is swept from its registry. Shared by both registries
|
|
5
|
+
* so the eviction policy stays in one place. */
|
|
6
|
+
export declare const DEFAULT_PARTITION_TTL_MS = 60000;
|
|
7
|
+
export declare class Bulkhead {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
private readonly concurrency;
|
|
10
|
+
private readonly maxQueueSize;
|
|
11
|
+
private readonly _limitFirstAttempts;
|
|
12
|
+
private running;
|
|
13
|
+
private readonly _waitQueue;
|
|
14
|
+
constructor(name: string, config?: PartitionConfig);
|
|
15
|
+
get queueSize(): number;
|
|
16
|
+
get runningCount(): number;
|
|
17
|
+
get concurrencyLimit(): number;
|
|
18
|
+
get maxQueueSizeLimit(): number;
|
|
19
|
+
get limitFirstAttempts(): boolean;
|
|
20
|
+
/** Acquire a concurrency slot, execute `task`, release the slot.
|
|
21
|
+
* Rejects with `QueueFullError` when the queue is at capacity.
|
|
22
|
+
* `this.running` is decremented *before* the returned Promise resolves,
|
|
23
|
+
* giving consumers an accurate count immediately after `await`.
|
|
24
|
+
* When a global `semaphore` is provided, a permit is acquired after the
|
|
25
|
+
* partition slot and released before it (D4).
|
|
26
|
+
* `onDequeue`, if given, fires once — right before `task()` starts —
|
|
27
|
+
* with the ms elapsed since `run()` was called, covering both the
|
|
28
|
+
* partition wait and the semaphore wait.
|
|
29
|
+
* If `signal` aborts while the caller is still waiting (for a partition
|
|
30
|
+
* slot or a global permit), it leaves the queue and the promise rejects
|
|
31
|
+
* with `CancelledError` without running `task` (B9). Once `task` has
|
|
32
|
+
* started, cancelling it is the task's own job. */
|
|
33
|
+
run<T>(task: () => Promise<T>, semaphore?: Semaphore, onDequeue?: (queuedMs: number) => void, signal?: AbortSignal): Promise<T>;
|
|
34
|
+
/** Free a counted slot and hand it to the next waiter, if any. Every path
|
|
35
|
+
* that decrements `running` — task success, task failure, and a rejected
|
|
36
|
+
* semaphore acquire after the slot was already granted — must go through
|
|
37
|
+
* this, or a slot leaks as permanently phantom-running. */
|
|
38
|
+
private _releaseSlot;
|
|
39
|
+
private _drainWaitQueue;
|
|
40
|
+
}
|
|
41
|
+
export interface BulkheadSnapshot {
|
|
42
|
+
name: string;
|
|
43
|
+
running: number;
|
|
44
|
+
queued: number;
|
|
45
|
+
concurrency: number;
|
|
46
|
+
maxQueueSize: number;
|
|
47
|
+
}
|
|
48
|
+
export declare class BulkheadRegistry {
|
|
49
|
+
private readonly bulkheads;
|
|
50
|
+
private readonly ttlMs;
|
|
51
|
+
private readonly globalConfig;
|
|
52
|
+
private readonly partitionConfigs;
|
|
53
|
+
private readonly sweepInterval;
|
|
54
|
+
private readonly semaphore?;
|
|
55
|
+
private callCounter;
|
|
56
|
+
constructor(globalConfig?: PartitionConfig, partitionConfigs?: Record<string, PartitionConfig>, ttlMs?: number, semaphore?: Semaphore);
|
|
57
|
+
get(partitionName: string): Bulkhead;
|
|
58
|
+
prune(): void;
|
|
59
|
+
delete(partitionName: string): void;
|
|
60
|
+
getAll(): BulkheadSnapshot[];
|
|
61
|
+
getSemaphore(): Semaphore | undefined;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=bulkhead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulkhead.d.ts","sourceRoot":"","sources":["../../src/queue/bulkhead.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;iDAEiD;AACjD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,qBAAa,QAAQ;IACpB,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAU;IAC9C,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAExC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,eAAoB;IAOtD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED;;;;;;;;;;;;wDAYoD;IACpD,GAAG,CAAC,CAAC,EACJ,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,SAAS,CAAC,EAAE,SAAS,EACrB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,CAAC,CAAC;IA0Eb;;;gEAG4D;IAC5D,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;CAMvB;AAMD,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACrB;AAID,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IACnE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAY;IACvC,OAAO,CAAC,WAAW,CAAK;gBAGvB,YAAY,GAAE,eAAoB,EAClC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAM,EACtD,KAAK,GAAE,MAAiC,EACxC,SAAS,CAAC,EAAE,SAAS;IAStB,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ;IAuBpC,KAAK,IAAI,IAAI;IASb,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAInC,MAAM,IAAI,gBAAgB,EAAE;IAc5B,YAAY,IAAI,SAAS,GAAG,SAAS;CAGrC"}
|