@cyanheads/mcp-ts-core 0.13.3 → 0.13.5
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/AGENTS.md +9 -7
- package/CLAUDE.md +9 -7
- package/README.md +1 -1
- package/changelog/0.13.x/0.13.4.md +65 -0
- package/changelog/0.13.x/0.13.5.md +41 -0
- package/changelog/template.md +7 -7
- package/dist/config/appRoot.d.ts.map +1 -1
- package/dist/config/appRoot.js +48 -16
- package/dist/config/appRoot.js.map +1 -1
- package/dist/core/app.d.ts +20 -0
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +1 -0
- package/dist/core/app.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/linter/rules/error-contract-rules.d.ts +65 -3
- package/dist/linter/rules/error-contract-rules.d.ts.map +1 -1
- package/dist/linter/rules/error-contract-rules.js +138 -3
- package/dist/linter/rules/error-contract-rules.js.map +1 -1
- package/dist/linter/rules/index.d.ts +1 -1
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +1 -1
- package/dist/linter/rules/index.js.map +1 -1
- package/dist/linter/rules/resource-rules.d.ts.map +1 -1
- package/dist/linter/rules/resource-rules.js +4 -2
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +19 -0
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +36 -0
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts +17 -0
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +101 -3
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +11 -2
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js +6 -4
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/inputRequired.d.ts +35 -2
- package/dist/mcp-server/inputRequired.d.ts.map +1 -1
- package/dist/mcp-server/inputRequired.js +116 -2
- package/dist/mcp-server/inputRequired.js.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts +2 -1
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +4 -4
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +5 -2
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -2
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts +2 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +4 -4
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +114 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.js +428 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -0
- package/dist/mcp-server/tools/utils/strictenRecord.d.ts +42 -0
- package/dist/mcp-server/tools/utils/strictenRecord.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/strictenRecord.js +48 -0
- package/dist/mcp-server/tools/utils/strictenRecord.js.map +1 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +27 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js +35 -6
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +33 -5
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +136 -20
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.d.ts +4 -1
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.js +5 -0
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +52 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +79 -3
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts +14 -1
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/core/sqlGate.js +69 -7
- package/dist/services/canvas/core/sqlGate.js.map +1 -1
- package/dist/services/canvas/index.d.ts +2 -2
- package/dist/services/canvas/index.d.ts.map +1 -1
- package/dist/services/canvas/index.js +2 -2
- package/dist/services/canvas/index.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +20 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +75 -25
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/types.d.ts +6 -1
- package/dist/services/canvas/types.d.ts.map +1 -1
- package/dist/types-global/errors.d.ts +30 -0
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +4 -3
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +13 -3
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/mappings.d.ts +14 -1
- package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/mappings.js +19 -1
- package/dist/utils/internal/error-handler/mappings.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +12 -1
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +4 -1
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +24 -4
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +10 -6
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/httpError.d.ts +56 -6
- package/dist/utils/network/httpError.d.ts.map +1 -1
- package/dist/utils/network/httpError.js +56 -7
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/network/pacer.d.ts +117 -0
- package/dist/utils/network/pacer.d.ts.map +1 -0
- package/dist/utils/network/pacer.js +304 -0
- package/dist/utils/network/pacer.js.map +1 -0
- package/dist/utils/network/retry.d.ts +119 -3
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +176 -35
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/rateLimiter.d.ts +19 -1
- package/dist/utils/security/rateLimiter.d.ts.map +1 -1
- package/dist/utils/security/rateLimiter.js +49 -1
- package/dist/utils/security/rateLimiter.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +27 -0
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +38 -0
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/framework-skills/add-tool/SKILL.md +49 -5
- package/framework-skills/api-canvas/SKILL.md +37 -16
- package/framework-skills/api-config/SKILL.md +4 -4
- package/framework-skills/api-context/SKILL.md +4 -2
- package/framework-skills/api-errors/SKILL.md +39 -7
- package/framework-skills/api-linter/SKILL.md +92 -5
- package/framework-skills/api-telemetry/SKILL.md +43 -4
- package/framework-skills/api-utils/SKILL.md +9 -5
- package/framework-skills/api-utils/references/security.md +2 -2
- package/framework-skills/design-mcp-server/SKILL.md +20 -3
- package/framework-skills/field-test/SKILL.md +4 -2
- package/framework-skills/git-wrapup/SKILL.md +87 -69
- package/framework-skills/release-and-publish/SKILL.md +5 -5
- package/framework-skills/release-pr-review/SKILL.md +5 -5
- package/framework-skills/report-issue-framework/SKILL.md +6 -35
- package/framework-skills/report-issue-local/SKILL.md +6 -36
- package/package.json +2 -2
- package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +2 -2
- package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +2 -2
- package/templates/AGENTS.md +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/changelog/template.md +7 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pacer.js","sourceRoot":"","sources":["../../../src/utils/network/pacer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import type { RequestContext } from '../internal/requestContext.js';
|
|
2
|
+
/**
|
|
3
|
+
* The per-attempt handle {@link withRetry} passes to its operation.
|
|
4
|
+
*
|
|
5
|
+
* Both fields describe the *total* budget, not this attempt's: they exist so an
|
|
6
|
+
* attempt can bound its own I/O against what is left, which is what keeps the
|
|
7
|
+
* deadline from overshooting by one in-flight request.
|
|
8
|
+
*/
|
|
9
|
+
export interface RetryAttempt {
|
|
10
|
+
/**
|
|
11
|
+
* Milliseconds left on {@link RetryOptions.deadlineMs} as this attempt starts.
|
|
12
|
+
* Never negative, and `Number.POSITIVE_INFINITY` when no deadline is set — so
|
|
13
|
+
* `Math.min(perAttemptMs, remainingMs)` is correct either way.
|
|
14
|
+
*/
|
|
15
|
+
readonly remainingMs: number;
|
|
16
|
+
/**
|
|
17
|
+
* `AbortSignal.any` over the deadline clock and {@link RetryOptions.signal}.
|
|
18
|
+
* Thread it into the attempt's fetch: a deadline that only fires *between*
|
|
19
|
+
* attempts cannot stop one already in flight.
|
|
20
|
+
*/
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
}
|
|
2
23
|
/** Configuration for {@link withRetry}. */
|
|
3
24
|
export interface RetryOptions {
|
|
4
25
|
/**
|
|
@@ -18,6 +39,24 @@ export interface RetryOptions {
|
|
|
18
39
|
* `state`, protocol method handles) before pino sees them.
|
|
19
40
|
*/
|
|
20
41
|
context?: RequestContext;
|
|
42
|
+
/**
|
|
43
|
+
* Total wall-clock budget in milliseconds covering every attempt, every
|
|
44
|
+
* backoff, and any honored `Retry-After` — the bound `maxRetries` and a
|
|
45
|
+
* per-attempt timeout cannot express between them. Four 30s attempts plus
|
|
46
|
+
* backoff outlast a client's 60s request timeout, so the caller gets a
|
|
47
|
+
* transport timeout instead of this server's classified error.
|
|
48
|
+
*
|
|
49
|
+
* The clock is an `AbortController` + `setTimeout`, composed into
|
|
50
|
+
* {@link RetryAttempt.signal}; pass that signal into the attempt's I/O or the
|
|
51
|
+
* deadline overshoots by one in-flight request. Expiry rejects with a
|
|
52
|
+
* `Timeout` `McpError` carrying
|
|
53
|
+
* `data: { reason: 'retry_deadline_exceeded', deadlineMs, elapsedMs, retryAttempts }`
|
|
54
|
+
* and the last attempt's error as `cause`.
|
|
55
|
+
*
|
|
56
|
+
* Unset (the default) leaves attempt counts, delays, logging, and the
|
|
57
|
+
* exhausted-error shape exactly as they are.
|
|
58
|
+
*/
|
|
59
|
+
deadlineMs?: number;
|
|
21
60
|
/**
|
|
22
61
|
* Custom predicate to determine if an error is transient and should be
|
|
23
62
|
* retried. When provided, this **replaces** the default predicate entirely
|
|
@@ -56,10 +95,63 @@ export interface RetryOptions {
|
|
|
56
95
|
operation?: string;
|
|
57
96
|
/**
|
|
58
97
|
* Optional AbortSignal. When aborted, the retry loop exits immediately
|
|
59
|
-
* without further attempts
|
|
98
|
+
* without further attempts, rethrowing unchanged — this is the caller-abort
|
|
99
|
+
* passthrough and it outranks a {@link deadlineMs} expiry, so a cancelled
|
|
100
|
+
* request is never relabelled as one that ran out of budget. Also composed
|
|
101
|
+
* into {@link RetryAttempt.signal}.
|
|
60
102
|
*/
|
|
61
103
|
signal?: AbortSignal;
|
|
62
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Parses an upstream `Retry-After` hint into milliseconds. The two HTTP helpers
|
|
107
|
+
* (`fetchWithTimeout`, `httpErrorFromResponse`) capture the raw header value into
|
|
108
|
+
* `error.data.retryAfter`; this reads it back so the retry delay can honor the
|
|
109
|
+
* wait the upstream explicitly asked for instead of blind exponential backoff.
|
|
110
|
+
*
|
|
111
|
+
* Handles both RFC 9110 §10.2.3 forms:
|
|
112
|
+
* - **delta-seconds** — a bare non-negative integer (`"30"` → `30_000`).
|
|
113
|
+
* - **HTTP-date** — an absolute instant, converted to a wait from now and
|
|
114
|
+
* clamped at `0` (a past date means "retry now").
|
|
115
|
+
*
|
|
116
|
+
* A numeric `data.retryAfter` is also accepted and interpreted as delta-seconds,
|
|
117
|
+
* matching the header's units. Returns `undefined` when the error carries no
|
|
118
|
+
* parseable hint, so callers fall back to exponential backoff.
|
|
119
|
+
*
|
|
120
|
+
* Module-level, not public: the pacer's cooldown gate reads the same hint off
|
|
121
|
+
* the same errors, and a second copy of the RFC 9110 forms would drift.
|
|
122
|
+
*/
|
|
123
|
+
export declare function parseRetryAfterMs(error: unknown): number | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Default transient check: `McpError` with a transient code, or any non-McpError
|
|
126
|
+
* (network failures, unexpected throws) which are assumed transient.
|
|
127
|
+
*
|
|
128
|
+
* **Per-error opt-out.** When a thrown `McpError` carries `data.retryable === false`,
|
|
129
|
+
* the error is treated as non-transient and fails fast — even if its code is in
|
|
130
|
+
* `TRANSIENT_CODES`. This is the in-band escape hatch for deterministic upstream
|
|
131
|
+
* failures (e.g. a query too expensive to ever succeed surfaced as HTTP 200 +
|
|
132
|
+
* error body) that arrive with a transient code but should never be retried.
|
|
133
|
+
*
|
|
134
|
+
* Absent the flag (or when it is `true`), behavior is unchanged — code-based
|
|
135
|
+
* classification applies. Non-`McpError` throws (raw network errors, unexpected
|
|
136
|
+
* throws) are always assumed transient regardless of the flag.
|
|
137
|
+
*
|
|
138
|
+
* **Composing rather than replacing.** {@link RetryOptions.isTransient} replaces
|
|
139
|
+
* this predicate outright, so a caller that wants "the framework default, except
|
|
140
|
+
* this one error" composes off this export instead of mirroring the transient
|
|
141
|
+
* code set — a copy drifts silently when the framework's classification changes:
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* withRetry(fn, {
|
|
145
|
+
* isTransient: (error) =>
|
|
146
|
+
* !(error instanceof McpError && error.data?.reason === 'budget_exhausted') &&
|
|
147
|
+
* defaultIsTransient(error),
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* The inverse — treating one more shape as transient — composes the same way:
|
|
152
|
+
* `defaultIsTransient(error) || isMyRetryableShape(error)`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function defaultIsTransient(error: unknown): boolean;
|
|
63
155
|
/**
|
|
64
156
|
* Executes `fn` with retry logic and exponential backoff.
|
|
65
157
|
*
|
|
@@ -82,11 +174,24 @@ export interface RetryOptions {
|
|
|
82
174
|
* fast: a window that won't clear within the retry budget is surfaced to the
|
|
83
175
|
* caller immediately rather than burning attempts that cannot succeed.
|
|
84
176
|
*
|
|
177
|
+
* **Total deadline.** `deadlineMs` bounds the whole ladder — every attempt, every
|
|
178
|
+
* backoff, every honored `Retry-After` — where `maxRetries` and a per-attempt
|
|
179
|
+
* timeout together cannot. `fn` receives `{ signal, remainingMs }`; thread
|
|
180
|
+
* `signal` into the attempt's I/O, or an expiry that fires mid-attempt overshoots
|
|
181
|
+
* by one in-flight request. A backoff that would consume the remaining budget
|
|
182
|
+
* fails fast rather than sleeping into a certain timeout, and expiry rejects with
|
|
183
|
+
* a single `Timeout` error carrying `data.reason: 'retry_deadline_exceeded'` —
|
|
184
|
+
* the `RequestCancelled` that an external-signal abort produces inside
|
|
185
|
+
* `fetchWithTimeout` normalizes to it. A caller abort on `options.signal` keeps
|
|
186
|
+
* precedence and is never relabelled. Without `deadlineMs`, behavior is
|
|
187
|
+
* unchanged.
|
|
188
|
+
*
|
|
85
189
|
* When retries exhaust, the final error is enriched with attempt count in both
|
|
86
190
|
* the message and structured data, so callers know retries were already attempted.
|
|
87
191
|
*
|
|
88
192
|
* @typeParam T - Return type of the operation.
|
|
89
|
-
* @param fn - The async operation to execute with retries.
|
|
193
|
+
* @param fn - The async operation to execute with retries. Receives a
|
|
194
|
+
* {@link RetryAttempt}; a zero-argument operation is assignable unchanged.
|
|
90
195
|
* @param options - Retry configuration. All fields optional with sensible defaults.
|
|
91
196
|
* @returns The result of `fn` on success.
|
|
92
197
|
* @throws The enriched final error when all attempts are exhausted, or the original
|
|
@@ -105,6 +210,17 @@ export interface RetryOptions {
|
|
|
105
210
|
* );
|
|
106
211
|
* }
|
|
107
212
|
* ```
|
|
213
|
+
*
|
|
214
|
+
* @example Bounded by one wall-clock budget
|
|
215
|
+
* ```ts
|
|
216
|
+
* const data = await withRetry(
|
|
217
|
+
* async ({ signal, remainingMs }) => {
|
|
218
|
+
* const res = await fetchWithTimeout(url, Math.min(30_000, remainingMs), ctx, { signal });
|
|
219
|
+
* return parse(await res.text());
|
|
220
|
+
* },
|
|
221
|
+
* { operation: 'search', context: ctx, signal: ctx.signal, deadlineMs: 50_000 },
|
|
222
|
+
* );
|
|
223
|
+
* ```
|
|
108
224
|
*/
|
|
109
|
-
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
225
|
+
export declare function withRetry<T>(fn: (attempt: RetryAttempt) => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
110
226
|
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAYzE,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE1C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAYzE;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE1C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAoBpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgB1D;AA2FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAuHZ"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* (HTTP fetch + response parsing/validation), not just the network call.
|
|
5
5
|
* @module src/utils/network/retry
|
|
6
6
|
*/
|
|
7
|
-
import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
|
|
7
|
+
import { JsonRpcErrorCode, McpError, timeout } from '../../types-global/errors.js';
|
|
8
8
|
import { logger } from '../internal/logger.js';
|
|
9
9
|
/**
|
|
10
10
|
* Error codes considered transient — eligible for retry.
|
|
@@ -45,8 +45,11 @@ function computeDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
|
|
|
45
45
|
* A numeric `data.retryAfter` is also accepted and interpreted as delta-seconds,
|
|
46
46
|
* matching the header's units. Returns `undefined` when the error carries no
|
|
47
47
|
* parseable hint, so callers fall back to exponential backoff.
|
|
48
|
+
*
|
|
49
|
+
* Module-level, not public: the pacer's cooldown gate reads the same hint off
|
|
50
|
+
* the same errors, and a second copy of the RFC 9110 forms would drift.
|
|
48
51
|
*/
|
|
49
|
-
function parseRetryAfterMs(error) {
|
|
52
|
+
export function parseRetryAfterMs(error) {
|
|
50
53
|
if (!(error instanceof McpError))
|
|
51
54
|
return;
|
|
52
55
|
const raw = error.data?.retryAfter;
|
|
@@ -81,17 +84,81 @@ function parseRetryAfterMs(error) {
|
|
|
81
84
|
* Absent the flag (or when it is `true`), behavior is unchanged — code-based
|
|
82
85
|
* classification applies. Non-`McpError` throws (raw network errors, unexpected
|
|
83
86
|
* throws) are always assumed transient regardless of the flag.
|
|
87
|
+
*
|
|
88
|
+
* **Composing rather than replacing.** {@link RetryOptions.isTransient} replaces
|
|
89
|
+
* this predicate outright, so a caller that wants "the framework default, except
|
|
90
|
+
* this one error" composes off this export instead of mirroring the transient
|
|
91
|
+
* code set — a copy drifts silently when the framework's classification changes:
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* withRetry(fn, {
|
|
95
|
+
* isTransient: (error) =>
|
|
96
|
+
* !(error instanceof McpError && error.data?.reason === 'budget_exhausted') &&
|
|
97
|
+
* defaultIsTransient(error),
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* The inverse — treating one more shape as transient — composes the same way:
|
|
102
|
+
* `defaultIsTransient(error) || isMyRetryableShape(error)`.
|
|
84
103
|
*/
|
|
85
|
-
function defaultIsTransient(error) {
|
|
104
|
+
export function defaultIsTransient(error) {
|
|
86
105
|
if (error instanceof McpError) {
|
|
87
106
|
// Explicit opt-out wins over code-based classification.
|
|
88
107
|
if (error.data?.retryable === false)
|
|
89
108
|
return false;
|
|
109
|
+
/**
|
|
110
|
+
* A pacer shed is a `RateLimited` the *caller* should honor and this loop
|
|
111
|
+
* should not: sleeping its `retryAfter` would burn the very deadline the
|
|
112
|
+
* shed exists to enforce. It carries no `data.retryable: false`, because to
|
|
113
|
+
* the calling agent a shed is an ordinary rate limit — wait, then call
|
|
114
|
+
* again — and that flag on the wire would say the opposite.
|
|
115
|
+
*/
|
|
116
|
+
if (error.data?.reason === 'pacer_shed')
|
|
117
|
+
return false;
|
|
90
118
|
return TRANSIENT_CODES.has(error.code);
|
|
91
119
|
}
|
|
92
120
|
// Non-McpError (raw network errors, unexpected throws) — assume transient
|
|
93
121
|
return true;
|
|
94
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Arms the total-deadline clock.
|
|
125
|
+
*
|
|
126
|
+
* `AbortController` + `setTimeout`, never `AbortSignal.timeout()` — which can
|
|
127
|
+
* fail in Bun's stdio transport on a realm mismatch, the same reason
|
|
128
|
+
* `fetchWithTimeout` avoids it. The reason instance is held so expiry is matched
|
|
129
|
+
* by identity: a caller signal aborting with its own `TimeoutError` stays a
|
|
130
|
+
* caller abort.
|
|
131
|
+
*
|
|
132
|
+
* `withRetry` cannot tell the three clocks apart from a caught error alone — a
|
|
133
|
+
* deadline abort reaching `fetchWithTimeout` on its external signal arrives as
|
|
134
|
+
* `RequestCancelled`, and one landing mid-backoff arrives as the raw abort
|
|
135
|
+
* reason — so both normalize through {@link DeadlineClock.exceeded} and the
|
|
136
|
+
* caller matches one shape.
|
|
137
|
+
*
|
|
138
|
+
* The expiry carries no `retryable` flag: a narrower call can still succeed, and
|
|
139
|
+
* that flag is {@link defaultIsTransient}'s in-band opt-out rather than a
|
|
140
|
+
* statement to the caller. No `attempt` index either — `data.retryAttempts`
|
|
141
|
+
* already carries it.
|
|
142
|
+
*/
|
|
143
|
+
function createDeadlineClock(deadlineMs, operation) {
|
|
144
|
+
const label = operation ?? 'operation';
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const reason = new DOMException(`${label} exceeded its ${deadlineMs}ms retry deadline.`, 'TimeoutError');
|
|
147
|
+
const startedAt = Date.now();
|
|
148
|
+
const timer = setTimeout(() => controller.abort(reason), deadlineMs);
|
|
149
|
+
return {
|
|
150
|
+
signal: controller.signal,
|
|
151
|
+
expired: () => controller.signal.reason === reason,
|
|
152
|
+
remainingMs: () => Math.max(0, deadlineMs - (Date.now() - startedAt)),
|
|
153
|
+
stop: () => clearTimeout(timer),
|
|
154
|
+
exceeded: (cause, retryAttempts) => timeout(`${label} exceeded its ${deadlineMs}ms retry deadline after ${retryAttempts} attempt${retryAttempts > 1 ? 's' : ''}.`, {
|
|
155
|
+
reason: 'retry_deadline_exceeded',
|
|
156
|
+
deadlineMs,
|
|
157
|
+
elapsedMs: Date.now() - startedAt,
|
|
158
|
+
retryAttempts,
|
|
159
|
+
}, { cause }),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
95
162
|
/**
|
|
96
163
|
* Enriches an error with retry exhaustion context.
|
|
97
164
|
* Appends attempt count to the message and to `data` for programmatic access.
|
|
@@ -137,11 +204,24 @@ function enrichExhaustedError(error, totalAttempts, operation) {
|
|
|
137
204
|
* fast: a window that won't clear within the retry budget is surfaced to the
|
|
138
205
|
* caller immediately rather than burning attempts that cannot succeed.
|
|
139
206
|
*
|
|
207
|
+
* **Total deadline.** `deadlineMs` bounds the whole ladder — every attempt, every
|
|
208
|
+
* backoff, every honored `Retry-After` — where `maxRetries` and a per-attempt
|
|
209
|
+
* timeout together cannot. `fn` receives `{ signal, remainingMs }`; thread
|
|
210
|
+
* `signal` into the attempt's I/O, or an expiry that fires mid-attempt overshoots
|
|
211
|
+
* by one in-flight request. A backoff that would consume the remaining budget
|
|
212
|
+
* fails fast rather than sleeping into a certain timeout, and expiry rejects with
|
|
213
|
+
* a single `Timeout` error carrying `data.reason: 'retry_deadline_exceeded'` —
|
|
214
|
+
* the `RequestCancelled` that an external-signal abort produces inside
|
|
215
|
+
* `fetchWithTimeout` normalizes to it. A caller abort on `options.signal` keeps
|
|
216
|
+
* precedence and is never relabelled. Without `deadlineMs`, behavior is
|
|
217
|
+
* unchanged.
|
|
218
|
+
*
|
|
140
219
|
* When retries exhaust, the final error is enriched with attempt count in both
|
|
141
220
|
* the message and structured data, so callers know retries were already attempted.
|
|
142
221
|
*
|
|
143
222
|
* @typeParam T - Return type of the operation.
|
|
144
|
-
* @param fn - The async operation to execute with retries.
|
|
223
|
+
* @param fn - The async operation to execute with retries. Receives a
|
|
224
|
+
* {@link RetryAttempt}; a zero-argument operation is assignable unchanged.
|
|
145
225
|
* @param options - Retry configuration. All fields optional with sensible defaults.
|
|
146
226
|
* @returns The result of `fn` on success.
|
|
147
227
|
* @throws The enriched final error when all attempts are exhausted, or the original
|
|
@@ -160,44 +240,105 @@ function enrichExhaustedError(error, totalAttempts, operation) {
|
|
|
160
240
|
* );
|
|
161
241
|
* }
|
|
162
242
|
* ```
|
|
243
|
+
*
|
|
244
|
+
* @example Bounded by one wall-clock budget
|
|
245
|
+
* ```ts
|
|
246
|
+
* const data = await withRetry(
|
|
247
|
+
* async ({ signal, remainingMs }) => {
|
|
248
|
+
* const res = await fetchWithTimeout(url, Math.min(30_000, remainingMs), ctx, { signal });
|
|
249
|
+
* return parse(await res.text());
|
|
250
|
+
* },
|
|
251
|
+
* { operation: 'search', context: ctx, signal: ctx.signal, deadlineMs: 50_000 },
|
|
252
|
+
* );
|
|
253
|
+
* ```
|
|
163
254
|
*/
|
|
164
255
|
export async function withRetry(fn, options = {}) {
|
|
165
|
-
const { maxRetries = 3, baseDelayMs = 1000, maxDelayMs = 30_000, jitter = 0.25, operation, context, signal, isTransient = defaultIsTransient, } = options;
|
|
256
|
+
const { maxRetries = 3, baseDelayMs = 1000, maxDelayMs = 30_000, jitter = 0.25, operation, context, signal, deadlineMs, isTransient = defaultIsTransient, } = options;
|
|
166
257
|
const totalAttempts = maxRetries + 1;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (!isTransient(error)) {
|
|
179
|
-
throw error;
|
|
258
|
+
const clock = deadlineMs === undefined ? undefined : createDeadlineClock(deadlineMs, operation);
|
|
259
|
+
const attemptSignal = clock && signal
|
|
260
|
+
? AbortSignal.any([clock.signal, signal])
|
|
261
|
+
: (clock?.signal ?? signal ?? new AbortController().signal);
|
|
262
|
+
try {
|
|
263
|
+
for (let attempt = 0; attempt < totalAttempts; attempt++) {
|
|
264
|
+
try {
|
|
265
|
+
return await fn({
|
|
266
|
+
signal: attemptSignal,
|
|
267
|
+
remainingMs: clock?.remainingMs() ?? Number.POSITIVE_INFINITY,
|
|
268
|
+
});
|
|
180
269
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
270
|
+
catch (error) {
|
|
271
|
+
// Abort signal — exit immediately, no more retries. Outranks the
|
|
272
|
+
// deadline so a cancelled request is never relabelled as an expiry.
|
|
273
|
+
if (signal?.aborted) {
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
// The deadline fired inside the attempt. Whatever shape it took on the
|
|
277
|
+
// way back (RequestCancelled from an external-signal abort, a raw abort
|
|
278
|
+
// reason), the caller sees one error.
|
|
279
|
+
if (clock?.expired()) {
|
|
280
|
+
throw clock.exceeded(error, attempt + 1);
|
|
281
|
+
}
|
|
282
|
+
const isLastAttempt = attempt >= maxRetries;
|
|
283
|
+
// Non-transient errors fail immediately
|
|
284
|
+
if (!isTransient(error)) {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
// Honor an upstream Retry-After hint over blind exponential backoff.
|
|
288
|
+
const retryAfterMs = parseRetryAfterMs(error);
|
|
289
|
+
// A requested wait longer than the cap can't clear within the retry budget —
|
|
290
|
+
// surface the limit immediately instead of burning an attempt on a window
|
|
291
|
+
// that won't open in time.
|
|
292
|
+
if (retryAfterMs !== undefined && retryAfterMs > maxDelayMs) {
|
|
293
|
+
logger.debug(`Retry-After ${Math.round(retryAfterMs)}ms exceeds maxDelayMs ${maxDelayMs}ms for ${operation ?? 'operation'} — failing fast`, context);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
if (isLastAttempt) {
|
|
297
|
+
throw enrichExhaustedError(error, totalAttempts, operation);
|
|
298
|
+
}
|
|
299
|
+
// Log and backoff — the honored Retry-After wins over the exponential value.
|
|
300
|
+
const delay = retryAfterMs ?? computeDelay(attempt, baseDelayMs, maxDelayMs, jitter);
|
|
301
|
+
/**
|
|
302
|
+
* A wait that would outlast the remaining budget leaves the next attempt
|
|
303
|
+
* nothing, so sleeping it out only converts a fast failure into a certain
|
|
304
|
+
* timeout. A wait landing exactly on the deadline still sleeps: the
|
|
305
|
+
* clock's own timer was armed first and fires first, so the expiry
|
|
306
|
+
* surfaces through the mid-backoff path below rather than here. The two
|
|
307
|
+
* waits exit differently: an honored
|
|
308
|
+
* `Retry-After` takes the same exit the `maxDelayMs` cap takes — the
|
|
309
|
+
* attempt's own error, untouched, `data.retryAfter` intact, because
|
|
310
|
+
* "wait the window the upstream named" is still the caller's action.
|
|
311
|
+
* Blind exponential backoff has no such message for the caller, so it
|
|
312
|
+
* surfaces the expiry.
|
|
313
|
+
*/
|
|
314
|
+
if (clock && delay > clock.remainingMs()) {
|
|
315
|
+
if (retryAfterMs !== undefined) {
|
|
316
|
+
logger.debug(`Retry-After ${Math.round(retryAfterMs)}ms outlasts the ${Math.round(clock.remainingMs())}ms left of the ${deadlineMs}ms deadline for ${operation ?? 'operation'} — failing fast`, context);
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
throw clock.exceeded(error, attempt + 1);
|
|
320
|
+
}
|
|
321
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
322
|
+
const delaySource = retryAfterMs === undefined ? '' : ' (Retry-After)';
|
|
323
|
+
logger.debug(`Retry ${attempt + 1}/${maxRetries} for ${operation ?? 'operation'}: ${errorMessage} — waiting ${Math.round(delay)}ms${delaySource}`, context);
|
|
324
|
+
try {
|
|
325
|
+
await sleep(delay, attemptSignal);
|
|
326
|
+
}
|
|
327
|
+
catch (sleepError) {
|
|
328
|
+
// Same precedence as the attempt path: a caller abort rethrows its own
|
|
329
|
+
// reason, an expiry landing mid-backoff normalizes.
|
|
330
|
+
if (signal?.aborted)
|
|
331
|
+
throw sleepError;
|
|
332
|
+
if (clock?.expired())
|
|
333
|
+
throw clock.exceeded(error, attempt + 1);
|
|
334
|
+
throw sleepError;
|
|
335
|
+
}
|
|
189
336
|
}
|
|
190
|
-
if (isLastAttempt) {
|
|
191
|
-
throw enrichExhaustedError(error, totalAttempts, operation);
|
|
192
|
-
}
|
|
193
|
-
// Log and backoff — the honored Retry-After wins over the exponential value.
|
|
194
|
-
const delay = retryAfterMs ?? computeDelay(attempt, baseDelayMs, maxDelayMs, jitter);
|
|
195
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
-
const delaySource = retryAfterMs === undefined ? '' : ' (Retry-After)';
|
|
197
|
-
logger.debug(`Retry ${attempt + 1}/${maxRetries} for ${operation ?? 'operation'}: ${errorMessage} — waiting ${Math.round(delay)}ms${delaySource}`, context);
|
|
198
|
-
await sleep(delay, signal);
|
|
199
337
|
}
|
|
200
338
|
}
|
|
339
|
+
finally {
|
|
340
|
+
clock?.stop();
|
|
341
|
+
}
|
|
201
342
|
// Unreachable — the loop always returns or throws
|
|
202
343
|
throw new McpError(JsonRpcErrorCode.InternalError, 'withRetry: unexpected loop exit');
|
|
203
344
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAGpD;;;GAGG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAmB;IAChD,gBAAgB,CAAC,kBAAkB;IACnC,gBAAgB,CAAC,OAAO;IACxB,gBAAgB,CAAC,WAAW;CAC7B,CAAC,CAAC;AAkHH;;;;;;;;GAQG;AACH,SAAS,YAAY,CACnB,OAAe,EACf,WAAmB,EACnB,UAAkB,EAClB,MAAc;IAEd,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,IAAI,OAAO,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,WAAW,CAAC;IACpC,MAAM,WAAW,GAAG,WAAW,GAAG,MAAM,CAAC;IACzC,OAAO,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,WAAW,GAAG,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,CAAC;QAAE,OAAO;IACzC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;IAEnC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO;IAEpC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO;IAE3B,6EAA6E;IAC7E,wDAAwD;IACxD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAEzD,6DAA6D;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO;IACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,wDAAwD;QACxD,IAAI,KAAK,CAAC,IAAI,EAAE,SAAS,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAClD;;;;;;WAMG;QACH,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY;YAAE,OAAO,KAAK,CAAC;QACtD,OAAO,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,0EAA0E;IAC1E,OAAO,IAAI,CAAC;AACd,CAAC;AAgBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,mBAAmB,CAAC,UAAkB,EAAE,SAAkB;IACjE,MAAM,KAAK,GAAG,SAAS,IAAI,WAAW,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,YAAY,CAC7B,GAAG,KAAK,iBAAiB,UAAU,oBAAoB,EACvD,cAAc,CACf,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC;IAErE,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM;QAClD,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;QACrE,IAAI,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC;QAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,CACjC,OAAO,CACL,GAAG,KAAK,iBAAiB,UAAU,2BAA2B,aAAa,WAAW,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EACrH;YACE,MAAM,EAAE,yBAAyB;YACjC,UAAU;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACjC,aAAa;SACd,EACD,EAAE,KAAK,EAAE,CACV;KACJ,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,KAAc,EAAE,aAAqB,EAAE,SAAkB;IACrF,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,iBAAiB,aAAa,WAAW,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;QACxF,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAC9E,MAAM,YAAY,GAA4B;YAC5C,GAAG,KAAK,CAAC,IAAI;YACb,aAAa,EAAE,aAAa;YAC5B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC;QACF,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,iBAAiB,aAAa,WAAW,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;QACxF,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAyC,EACzC,OAAO,GAAiB,EAAE;IAE1B,MAAM,EACJ,UAAU,GAAG,CAAC,EACd,WAAW,GAAG,IAAI,EAClB,UAAU,GAAG,MAAM,EACnB,MAAM,GAAG,IAAI,EACb,SAAS,EACT,OAAO,EACP,MAAM,EACN,UAAU,EACV,WAAW,GAAG,kBAAkB,GACjC,GAAG,OAAO,CAAC;IAEZ,MAAM,aAAa,GAAG,UAAU,GAAG,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAEhG,MAAM,aAAa,GACjB,KAAK,IAAI,MAAM;QACb,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,IAAI,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC;IAEhE,IAAI,CAAC;QACH,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,aAAa,EAAE,OAAO,EAAE,EAAE,CAAC;YACzD,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,CAAC;oBACd,MAAM,EAAE,aAAa;oBACrB,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,MAAM,CAAC,iBAAiB;iBAC9D,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,iEAAiE;gBACjE,oEAAoE;gBACpE,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;oBACpB,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,uEAAuE;gBACvE,wEAAwE;gBACxE,sCAAsC;gBACtC,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;oBACrB,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;gBAC3C,CAAC;gBAED,MAAM,aAAa,GAAG,OAAO,IAAI,UAAU,CAAC;gBAE5C,wCAAwC;gBACxC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;oBACxB,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,qEAAqE;gBACrE,MAAM,YAAY,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBAE9C,6EAA6E;gBAC7E,0EAA0E;gBAC1E,2BAA2B;gBAC3B,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,GAAG,UAAU,EAAE,CAAC;oBAC5D,MAAM,CAAC,KAAK,CACV,eAAe,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,yBAAyB,UAAU,UAAU,SAAS,IAAI,WAAW,iBAAiB,EAC7H,OAAO,CACR,CAAC;oBACF,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,IAAI,aAAa,EAAE,CAAC;oBAClB,MAAM,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;gBAC9D,CAAC;gBAED,6EAA6E;gBAC7E,MAAM,KAAK,GAAG,YAAY,IAAI,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;gBAErF;;;;;;;;;;;;mBAYG;gBACH,IAAI,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACzC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;wBAC/B,MAAM,CAAC,KAAK,CACV,eAAe,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,mBAAmB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,kBAAkB,UAAU,mBAAmB,SAAS,IAAI,WAAW,iBAAiB,EACjL,OAAO,CACR,CAAC;wBACF,MAAM,KAAK,CAAC;oBACd,CAAC;oBACD,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;gBAC3C,CAAC;gBAED,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC5E,MAAM,WAAW,GAAG,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;gBAEvE,MAAM,CAAC,KAAK,CACV,SAAS,OAAO,GAAG,CAAC,IAAI,UAAU,QAAQ,SAAS,IAAI,WAAW,KAAK,YAAY,cAAc,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,WAAW,EAAE,EACpI,OAAO,CACR,CAAC;gBAEF,IAAI,CAAC;oBACH,MAAM,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;gBACpC,CAAC;gBAAC,OAAO,UAAmB,EAAE,CAAC;oBAC7B,uEAAuE;oBACvE,oDAAoD;oBACpD,IAAI,MAAM,EAAE,OAAO;wBAAE,MAAM,UAAU,CAAC;oBACtC,IAAI,KAAK,EAAE,OAAO,EAAE;wBAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;oBAC/D,MAAM,UAAU,CAAC;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,KAAK,EAAE,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,kDAAkD;IAClD,MAAM,IAAI,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,iCAAiC,CAAC,CAAC;AACxF,CAAC;AAED,yEAAyE;AACzE,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,sFAAsF;QACtF,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QAE3F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,QAAQ,CAAC,gBAAgB,CACvB,OAAO,EACP,GAAG,EAAE;YACH,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,sEAAsE;YACtE,0EAA0E;YAC1E,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACxB,CAAC;QACH,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -62,7 +62,9 @@ export declare class RateLimiter {
|
|
|
62
62
|
* - 5-minute cleanup interval
|
|
63
63
|
* - Up to 10,000 tracked keys (LRU eviction beyond that)
|
|
64
64
|
*
|
|
65
|
-
* Call {@link configure} to override any of these defaults
|
|
65
|
+
* Call {@link configure} to override any of these defaults, at construction or at
|
|
66
|
+
* runtime. A runtime reduction of `maxTrackedKeys` is enforced during that call —
|
|
67
|
+
* see {@link configure} for the trim order.
|
|
66
68
|
*
|
|
67
69
|
* @param config - Application config, used to check `environment` when `skipInDevelopment` is set.
|
|
68
70
|
* @param logger - Logger instance for debug output on cleanup and eviction events.
|
|
@@ -74,6 +76,16 @@ export declare class RateLimiter {
|
|
|
74
76
|
* @private
|
|
75
77
|
*/
|
|
76
78
|
private evictLRUEntry;
|
|
79
|
+
/**
|
|
80
|
+
* Reduces the tracked-key map to `maxTrackedKeys` in one pass, dropping expired
|
|
81
|
+
* windows before live ones and taking live entries in least-recently-used order.
|
|
82
|
+
* Survivors keep their `count`, `resetTime`, and `lastAccess` untouched.
|
|
83
|
+
*
|
|
84
|
+
* Separate from {@link evictLRUEntry}, which rescans the whole map to remove a
|
|
85
|
+
* single entry: looping that to shed thousands of keys would be quadratic.
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
private trimToCapacity;
|
|
77
89
|
/**
|
|
78
90
|
* Starts (or restarts) the periodic cleanup interval using the current `cleanupInterval` config.
|
|
79
91
|
* Clears any existing timer first. The timer is unref'd so it does not prevent Node.js from exiting.
|
|
@@ -91,6 +103,12 @@ export declare class RateLimiter {
|
|
|
91
103
|
* Merges the provided partial config into the current effective configuration.
|
|
92
104
|
* If `cleanupInterval` is included, the background timer is restarted with the new interval.
|
|
93
105
|
*
|
|
106
|
+
* Lowering `maxTrackedKeys` below the current tracked-key count enforces the new
|
|
107
|
+
* ceiling synchronously, before this call returns: the map is trimmed in one pass,
|
|
108
|
+
* expired windows first and then live entries in least-recently-used order.
|
|
109
|
+
* Survivors keep their counts and reset times. Raising the cap, or restating a
|
|
110
|
+
* value at or above the current size, trims nothing.
|
|
111
|
+
*
|
|
94
112
|
* @param config - Partial {@link RateLimitConfig} fields to apply.
|
|
95
113
|
*
|
|
96
114
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rateLimiter.d.ts","sourceRoot":"","sources":["../../../src/utils/security/rateLimiter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;AAchG,0FAA0F;AAC1F,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IACxE,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,WAAW;
|
|
1
|
+
{"version":3,"file":"rateLimiter.d.ts","sourceRoot":"","sources":["../../../src/utils/security/rateLimiter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;AAchG,0FAA0F;AAC1F,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IACxE,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,WAAW;IAmBpB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IAnBhB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAElD;;;;;;;;;;;;OAYG;IACH,YACU,MAAM,EAAE,OAAO,UAAU,EACzB,MAAM,EAAE,OAAO,UAAU,EAalC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IA2BrB;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IA8BtB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;;;;;;;;;;;;;;;OAgBG;IACI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAQvD;IAED;;;;OAIG;IACI,SAAS,IAAI,eAAe,CAElC;IAED;;;OAGG;IACI,KAAK,IAAI,IAAI,CAMnB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAsExD;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACI,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG;QAC7B,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,IAAI,CASP;IAED;;;;OAIG;IACI,OAAO,IAAI,IAAI,CAMrB;IAED;;;OAGG;IACH,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAEvB;CACF"}
|
|
@@ -45,7 +45,9 @@ export class RateLimiter {
|
|
|
45
45
|
* - 5-minute cleanup interval
|
|
46
46
|
* - Up to 10,000 tracked keys (LRU eviction beyond that)
|
|
47
47
|
*
|
|
48
|
-
* Call {@link configure} to override any of these defaults
|
|
48
|
+
* Call {@link configure} to override any of these defaults, at construction or at
|
|
49
|
+
* runtime. A runtime reduction of `maxTrackedKeys` is enforced during that call —
|
|
50
|
+
* see {@link configure} for the trim order.
|
|
49
51
|
*
|
|
50
52
|
* @param config - Application config, used to check `environment` when `skipInDevelopment` is set.
|
|
51
53
|
* @param logger - Logger instance for debug output on cleanup and eviction events.
|
|
@@ -94,6 +96,43 @@ export class RateLimiter {
|
|
|
94
96
|
this.logger.debug('Evicted LRU entry from rate limiter', logContext);
|
|
95
97
|
}
|
|
96
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Reduces the tracked-key map to `maxTrackedKeys` in one pass, dropping expired
|
|
101
|
+
* windows before live ones and taking live entries in least-recently-used order.
|
|
102
|
+
* Survivors keep their `count`, `resetTime`, and `lastAccess` untouched.
|
|
103
|
+
*
|
|
104
|
+
* Separate from {@link evictLRUEntry}, which rescans the whole map to remove a
|
|
105
|
+
* single entry: looping that to shed thousands of keys would be quadratic.
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
trimToCapacity() {
|
|
109
|
+
const maxKeys = this.effectiveConfig.maxTrackedKeys;
|
|
110
|
+
// Zero and negative caps have their own semantics (#405) and are not a trim.
|
|
111
|
+
if (maxKeys === undefined || maxKeys <= 0)
|
|
112
|
+
return;
|
|
113
|
+
const surplus = this.limits.size - maxKeys;
|
|
114
|
+
if (surplus <= 0)
|
|
115
|
+
return;
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const ordered = [...this.limits.entries()].map(([key, entry]) => ({
|
|
118
|
+
expired: now >= entry.resetTime,
|
|
119
|
+
key,
|
|
120
|
+
lastAccess: entry.lastAccess,
|
|
121
|
+
}));
|
|
122
|
+
// Stable sort, so equal recency falls back to insertion order — the same
|
|
123
|
+
// tie-break `evictLRUEntry` gets from the map's own iteration order.
|
|
124
|
+
ordered.sort((a, b) => a.expired === b.expired ? a.lastAccess - b.lastAccess : a.expired ? -1 : 1);
|
|
125
|
+
for (const { key } of ordered.slice(0, surplus))
|
|
126
|
+
this.limits.delete(key);
|
|
127
|
+
const logContext = requestContextService.createRequestContext({
|
|
128
|
+
operation: 'RateLimiter.trimToCapacity',
|
|
129
|
+
additionalContext: {
|
|
130
|
+
removedCount: surplus,
|
|
131
|
+
totalRemainingAfterTrim: this.limits.size,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
this.logger.debug(`Trimmed ${surplus} rate limit entries to the reduced capacity`, logContext);
|
|
135
|
+
}
|
|
97
136
|
/**
|
|
98
137
|
* Starts (or restarts) the periodic cleanup interval using the current `cleanupInterval` config.
|
|
99
138
|
* Clears any existing timer first. The timer is unref'd so it does not prevent Node.js from exiting.
|
|
@@ -143,6 +182,12 @@ export class RateLimiter {
|
|
|
143
182
|
* Merges the provided partial config into the current effective configuration.
|
|
144
183
|
* If `cleanupInterval` is included, the background timer is restarted with the new interval.
|
|
145
184
|
*
|
|
185
|
+
* Lowering `maxTrackedKeys` below the current tracked-key count enforces the new
|
|
186
|
+
* ceiling synchronously, before this call returns: the map is trimmed in one pass,
|
|
187
|
+
* expired windows first and then live entries in least-recently-used order.
|
|
188
|
+
* Survivors keep their counts and reset times. Raising the cap, or restating a
|
|
189
|
+
* value at or above the current size, trims nothing.
|
|
190
|
+
*
|
|
146
191
|
* @param config - Partial {@link RateLimitConfig} fields to apply.
|
|
147
192
|
*
|
|
148
193
|
* @example
|
|
@@ -155,6 +200,9 @@ export class RateLimiter {
|
|
|
155
200
|
if (config.cleanupInterval !== undefined) {
|
|
156
201
|
this.startCleanupTimer();
|
|
157
202
|
}
|
|
203
|
+
if (config.maxTrackedKeys !== undefined) {
|
|
204
|
+
this.trimToCapacity();
|
|
205
|
+
}
|
|
158
206
|
}
|
|
159
207
|
/**
|
|
160
208
|
* Returns a shallow copy of the current effective {@link RateLimitConfig}.
|