@procedures/client 0.2.3 → 0.4.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/CHANGELOG.md +94 -0
- package/README.md +77 -44
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +35 -3
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +3 -2
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +2 -2
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +54 -22
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/shared/headers.d.ts +1 -1
- package/dist/adapters/http/shared/headers.d.ts.map +1 -1
- package/dist/adapters/http/shared/headers.js +7 -6
- package/dist/adapters/http/shared/headers.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts +3 -0
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +67 -6
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/adapters/local/client/types.d.ts +15 -0
- package/dist/adapters/local/client/types.d.ts.map +1 -1
- package/dist/adapters/local/client/types.js.map +1 -1
- package/dist/adapters/mock/client/transport.d.ts +3 -2
- package/dist/adapters/mock/client/transport.d.ts.map +1 -1
- package/dist/adapters/mock/client/transport.js +8 -5
- package/dist/adapters/mock/client/transport.js.map +1 -1
- package/dist/adapters/mock/client/types.d.ts +7 -6
- package/dist/adapters/mock/client/types.d.ts.map +1 -1
- package/dist/adapters/port/transport.d.ts +62 -0
- package/dist/adapters/port/transport.d.ts.map +1 -0
- package/dist/adapters/port/transport.js +94 -0
- package/dist/adapters/port/transport.js.map +1 -0
- package/dist/adapters/websocket/client/index.d.ts +1 -1
- package/dist/adapters/websocket/client/index.d.ts.map +1 -1
- package/dist/adapters/websocket/client/index.js.map +1 -1
- package/dist/adapters/websocket/client/node.d.ts +30 -0
- package/dist/adapters/websocket/client/node.d.ts.map +1 -0
- package/dist/adapters/websocket/client/node.js +32 -0
- package/dist/adapters/websocket/client/node.js.map +1 -0
- package/dist/adapters/websocket/client/transport.d.ts +47 -7
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +229 -75
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/types.d.ts +71 -21
- package/dist/adapters/websocket/client/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/types.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +65 -24
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +192 -263
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +20 -1
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
- package/dist/adapters/websocket/shared/rpc.js +425 -0
- package/dist/adapters/websocket/shared/rpc.js.map +1 -0
- package/dist/browser.d.ts +29 -7
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +26 -3
- package/dist/browser.js.map +1 -1
- package/dist/client/call-types.d.ts +10 -10
- package/dist/client/call-types.d.ts.map +1 -1
- package/dist/client/call-types.js +1 -6
- package/dist/client/call-types.js.map +1 -1
- package/dist/client/client.d.ts +6 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +23 -9
- package/dist/client/client.js.map +1 -1
- package/dist/client/deadline.d.ts +38 -0
- package/dist/client/deadline.d.ts.map +1 -0
- package/dist/client/deadline.js +60 -0
- package/dist/client/deadline.js.map +1 -0
- package/dist/client/errors/factory.d.ts.map +1 -1
- package/dist/client/errors/factory.js +11 -7
- package/dist/client/errors/factory.js.map +1 -1
- package/dist/client/errors/http-errors.d.ts.map +1 -1
- package/dist/client/errors/http-errors.js +6 -0
- package/dist/client/errors/http-errors.js.map +1 -1
- package/dist/client/errors/index.d.ts +1 -1
- package/dist/client/errors/index.js +1 -1
- package/dist/client/errors/registry.d.ts +2 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +72 -22
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/index.d.ts +9 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +8 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/middleware/cache.d.ts +19 -5
- package/dist/client/middleware/cache.d.ts.map +1 -1
- package/dist/client/middleware/cache.js +106 -18
- package/dist/client/middleware/cache.js.map +1 -1
- package/dist/client/middleware/circuit-breaker.d.ts +28 -10
- package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
- package/dist/client/middleware/circuit-breaker.js +35 -24
- package/dist/client/middleware/circuit-breaker.js.map +1 -1
- package/dist/client/middleware/contexts.d.ts +16 -10
- package/dist/client/middleware/contexts.d.ts.map +1 -1
- package/dist/client/middleware/contexts.js.map +1 -1
- package/dist/client/middleware/items.d.ts +31 -2
- package/dist/client/middleware/items.d.ts.map +1 -1
- package/dist/client/middleware/items.js +86 -6
- package/dist/client/middleware/items.js.map +1 -1
- package/dist/client/middleware/lru.d.ts +2 -0
- package/dist/client/middleware/lru.d.ts.map +1 -1
- package/dist/client/middleware/lru.js +11 -0
- package/dist/client/middleware/lru.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts +7 -6
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +9 -7
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/middleware/resilience.d.ts +61 -0
- package/dist/client/middleware/resilience.d.ts.map +1 -0
- package/dist/client/middleware/resilience.js +59 -0
- package/dist/client/middleware/resilience.js.map +1 -0
- package/dist/client/middleware/retry.d.ts +45 -11
- package/dist/client/middleware/retry.d.ts.map +1 -1
- package/dist/client/middleware/retry.js +102 -22
- package/dist/client/middleware/retry.js.map +1 -1
- package/dist/client/middleware/timeout.d.ts +15 -12
- package/dist/client/middleware/timeout.d.ts.map +1 -1
- package/dist/client/middleware/timeout.js +48 -46
- package/dist/client/middleware/timeout.js.map +1 -1
- package/dist/client/middleware/tracing.d.ts.map +1 -1
- package/dist/client/middleware/tracing.js +11 -3
- package/dist/client/middleware/tracing.js.map +1 -1
- package/dist/client/types.d.ts +9 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/clock.d.ts +19 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +18 -0
- package/dist/clock.js.map +1 -0
- package/dist/duration.d.ts +37 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +44 -0
- package/dist/duration.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +90 -0
- package/dist/machines.d.ts.map +1 -0
- package/dist/machines.js +127 -0
- package/dist/machines.js.map +1 -0
- package/dist/node.d.ts +11 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/procedures/capability.d.ts +3 -6
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +7 -8
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/index.d.ts +3 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +1 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +49 -1
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +33 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +68 -0
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +37 -0
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/core/array.d.ts +1 -1
- package/dist/procedures/core/array.d.ts.map +1 -1
- package/dist/procedures/core/array.js +81 -29
- package/dist/procedures/core/array.js.map +1 -1
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +17 -2
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/logic.js +1 -1
- package/dist/procedures/core/logic.js.map +1 -1
- package/dist/procedures/core/math.d.ts.map +1 -1
- package/dist/procedures/core/math.js +5 -1
- package/dist/procedures/core/math.js.map +1 -1
- package/dist/procedures/core/meta.d.ts.map +1 -1
- package/dist/procedures/core/meta.js +43 -8
- package/dist/procedures/core/meta.js.map +1 -1
- package/dist/procedures/core/object.d.ts.map +1 -1
- package/dist/procedures/core/object.js +42 -13
- package/dist/procedures/core/object.js.map +1 -1
- package/dist/procedures/core/operands.d.ts.map +1 -1
- package/dist/procedures/core/operands.js +6 -1
- package/dist/procedures/core/operands.js.map +1 -1
- package/dist/procedures/core/string.d.ts.map +1 -1
- package/dist/procedures/core/string.js +4 -2
- package/dist/procedures/core/string.js.map +1 -1
- package/dist/procedures/core/type.d.ts.map +1 -1
- package/dist/procedures/core/type.js +4 -2
- package/dist/procedures/core/type.js.map +1 -1
- package/dist/procedures/define-procedure.d.ts +19 -7
- package/dist/procedures/define-procedure.d.ts.map +1 -1
- package/dist/procedures/define-procedure.js +57 -17
- package/dist/procedures/define-procedure.js.map +1 -1
- package/dist/procedures/discovery.d.ts +4 -6
- package/dist/procedures/discovery.d.ts.map +1 -1
- package/dist/procedures/discovery.js +4 -6
- package/dist/procedures/discovery.js.map +1 -1
- package/dist/procedures/effects.d.ts +16 -0
- package/dist/procedures/effects.d.ts.map +1 -1
- package/dist/procedures/effects.js +33 -0
- package/dist/procedures/effects.js.map +1 -1
- package/dist/procedures/idempotency.d.ts +73 -0
- package/dist/procedures/idempotency.d.ts.map +1 -0
- package/dist/procedures/idempotency.js +131 -0
- package/dist/procedures/idempotency.js.map +1 -0
- package/dist/procedures/index.d.ts +7 -3
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +6 -2
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/interceptors.d.ts +17 -4
- package/dist/procedures/interceptors.d.ts.map +1 -1
- package/dist/procedures/interceptors.js +18 -7
- package/dist/procedures/interceptors.js.map +1 -1
- package/dist/procedures/invoke.d.ts +15 -3
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +8 -3
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/manifest.d.ts +83 -0
- package/dist/procedures/manifest.d.ts.map +1 -0
- package/dist/procedures/manifest.js +90 -0
- package/dist/procedures/manifest.js.map +1 -0
- package/dist/procedures/ref.d.ts +4 -2
- package/dist/procedures/ref.d.ts.map +1 -1
- package/dist/procedures/ref.js +2 -1
- package/dist/procedures/ref.js.map +1 -1
- package/dist/procedures/runtime-programs.d.ts +19 -0
- package/dist/procedures/runtime-programs.d.ts.map +1 -0
- package/dist/procedures/runtime-programs.js +24 -0
- package/dist/procedures/runtime-programs.js.map +1 -0
- package/dist/procedures/types.d.ts +16 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/protocol.d.ts +47 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +68 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/host-policy.d.ts +4 -3
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +6 -3
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/host-procedures.d.ts +31 -0
- package/dist/server/host-procedures.d.ts.map +1 -0
- package/dist/server/host-procedures.js +75 -0
- package/dist/server/host-procedures.js.map +1 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +112 -5
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +273 -19
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +19 -0
- package/dist/server/request-auth.d.ts.map +1 -0
- package/dist/server/request-auth.js +32 -0
- package/dist/server/request-auth.js.map +1 -0
- package/dist/server/types.d.ts +32 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/timers.d.ts +60 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +33 -0
- package/dist/timers.js.map +1 -0
- package/dist/tracing/traceparent.d.ts +26 -0
- package/dist/tracing/traceparent.d.ts.map +1 -0
- package/dist/tracing/traceparent.js +53 -0
- package/dist/tracing/traceparent.js.map +1 -0
- package/package.json +11 -4
- package/src/adapters/http/client/transport.ts +31 -3
- package/src/adapters/http/client/types.ts +3 -2
- package/src/adapters/http/server/transport.ts +61 -26
- package/src/adapters/http/shared/headers.ts +7 -6
- package/src/adapters/local/client/transport.ts +74 -7
- package/src/adapters/local/client/types.ts +18 -0
- package/src/adapters/mock/client/transport.ts +9 -6
- package/src/adapters/mock/client/types.ts +7 -6
- package/src/adapters/port/transport.ts +124 -0
- package/src/adapters/websocket/client/index.ts +1 -0
- package/src/adapters/websocket/client/node.ts +34 -0
- package/src/adapters/websocket/client/transport.ts +251 -81
- package/src/adapters/websocket/client/types.ts +81 -21
- package/src/adapters/websocket/server/transport.ts +199 -301
- package/src/adapters/websocket/server/types.ts +20 -1
- package/src/adapters/websocket/shared/rpc.ts +453 -0
- package/src/browser.ts +51 -3
- package/src/client/call-types.ts +10 -14
- package/src/client/client.ts +28 -9
- package/src/client/deadline.ts +64 -0
- package/src/client/errors/README.md +2 -0
- package/src/client/errors/factory.ts +12 -8
- package/src/client/errors/http-errors.ts +5 -0
- package/src/client/errors/index.ts +1 -1
- package/src/client/errors/registry.ts +79 -22
- package/src/client/index.ts +21 -3
- package/src/client/middleware/cache.ts +120 -23
- package/src/client/middleware/circuit-breaker.ts +65 -35
- package/src/client/middleware/contexts.ts +17 -10
- package/src/client/middleware/items.ts +101 -6
- package/src/client/middleware/lru.ts +12 -0
- package/src/client/middleware/rate-limit.ts +9 -9
- package/src/client/middleware/resilience.ts +81 -0
- package/src/client/middleware/retry.ts +146 -34
- package/src/client/middleware/timeout.ts +57 -55
- package/src/client/middleware/tracing.ts +13 -4
- package/src/client/types.ts +9 -1
- package/src/clock.ts +21 -0
- package/src/duration.ts +56 -0
- package/src/index.ts +4 -0
- package/src/machines.ts +186 -0
- package/src/node.ts +11 -0
- package/src/procedures/capability.ts +7 -8
- package/src/procedures/collection/index.ts +9 -1
- package/src/procedures/collection/procedures.ts +57 -0
- package/src/procedures/collection/schemas.ts +110 -0
- package/src/procedures/collection/storage.ts +38 -0
- package/src/procedures/core/array.ts +79 -31
- package/src/procedures/core/index.ts +17 -2
- package/src/procedures/core/logic.ts +1 -1
- package/src/procedures/core/math.ts +5 -1
- package/src/procedures/core/meta.ts +45 -8
- package/src/procedures/core/object.ts +48 -14
- package/src/procedures/core/operands.ts +7 -0
- package/src/procedures/core/string.ts +4 -2
- package/src/procedures/core/type.ts +4 -2
- package/src/procedures/define-procedure.ts +73 -20
- package/src/procedures/discovery.ts +4 -6
- package/src/procedures/effects.ts +35 -0
- package/src/procedures/idempotency.ts +152 -0
- package/src/procedures/index.ts +18 -2
- package/src/procedures/interceptors.ts +28 -11
- package/src/procedures/invoke.ts +19 -5
- package/src/procedures/manifest.ts +158 -0
- package/src/procedures/ref.ts +4 -2
- package/src/procedures/runtime-programs.ts +27 -0
- package/src/procedures/types.ts +16 -0
- package/src/protocol.ts +108 -0
- package/src/server/host-policy.ts +8 -5
- package/src/server/host-procedures.ts +102 -0
- package/src/server/index.ts +5 -2
- package/src/server/procedure-server.ts +328 -12
- package/src/server/request-auth.ts +35 -0
- package/src/server/types.ts +37 -0
- package/src/timers.ts +82 -0
- package/src/tracing/traceparent.ts +61 -0
- package/dist/procedures/cli/announce.d.ts +0 -67
- package/dist/procedures/cli/announce.d.ts.map +0 -1
- package/dist/procedures/cli/announce.js +0 -392
- package/dist/procedures/cli/announce.js.map +0 -1
- package/dist/procedures/cli/discover.d.ts +0 -66
- package/dist/procedures/cli/discover.d.ts.map +0 -1
- package/dist/procedures/cli/discover.js +0 -310
- package/dist/procedures/cli/discover.js.map +0 -1
- package/dist/procedures/cli/index.d.ts +0 -13
- package/dist/procedures/cli/index.d.ts.map +0 -1
- package/dist/procedures/cli/index.js +0 -203
- package/dist/procedures/cli/index.js.map +0 -1
- package/dist/procedures/cli/init.d.ts +0 -17
- package/dist/procedures/cli/init.d.ts.map +0 -1
- package/dist/procedures/cli/init.js +0 -336
- package/dist/procedures/cli/init.js.map +0 -1
- package/src/procedures/cli/announce.ts +0 -470
- package/src/procedures/cli/discover.ts +0 -390
- package/src/procedures/cli/index.ts +0 -231
- package/src/procedures/cli/init.ts +0 -370
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Protocol-agnostic retry with exponential backoff and jitter.
|
|
5
5
|
* Works with any transport!
|
|
6
6
|
*/
|
|
7
|
-
import type { ResponseItem, TypedClientMiddleware } from "../types.js";
|
|
7
|
+
import type { Method, ResponseItem, TypedClientMiddleware } from "../types.js";
|
|
8
8
|
import type { RetryContext } from "./contexts.js";
|
|
9
|
+
import { type Clock } from "../../clock.js";
|
|
10
|
+
import { type Duration } from "../../duration.js";
|
|
9
11
|
/**
|
|
10
12
|
* Retry middleware options.
|
|
11
13
|
*/
|
|
@@ -16,30 +18,62 @@ export interface RetryOptions {
|
|
|
16
18
|
*/
|
|
17
19
|
maxRetries?: number;
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
21
|
+
* The clock of the backoff and of the budget (roadmap R35). The default is the clock of the
|
|
22
|
+
* platform. A test gives a fake clock.
|
|
23
|
+
*/
|
|
24
|
+
clock?: Clock;
|
|
25
|
+
/**
|
|
26
|
+
* Base delay for exponential backoff, for example `ms(500)`.
|
|
20
27
|
* Actual delay will be: baseDelay * 2^attempt + jitter
|
|
21
|
-
* @default
|
|
28
|
+
* @default 1 second
|
|
22
29
|
*/
|
|
23
|
-
retryDelay?:
|
|
30
|
+
retryDelay?: Duration;
|
|
24
31
|
/**
|
|
25
32
|
* The factor of the delay from one attempt to the next.
|
|
26
33
|
* @default 2
|
|
27
34
|
*/
|
|
28
35
|
backoffMultiplier?: number;
|
|
29
36
|
/**
|
|
30
|
-
* The longest delay between two attempts
|
|
37
|
+
* The longest delay between two attempts.
|
|
31
38
|
* @default no limit
|
|
32
39
|
*/
|
|
33
|
-
maxDelay?:
|
|
40
|
+
maxDelay?: Duration;
|
|
34
41
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
42
|
+
* The random part of each delay, from 0 to 1. With 1 (full jitter), the delay is a random time
|
|
43
|
+
* from 0 to the backoff, so the clients that failed together do not retry together. 0 gives the
|
|
44
|
+
* backoff exactly.
|
|
45
|
+
* @default 1 (full jitter; roadmap R12: before, 0.1 gave a delay within 10 percent of the backoff)
|
|
38
46
|
*/
|
|
39
47
|
jitter?: number;
|
|
48
|
+
/**
|
|
49
|
+
* The retry budget of this middleware: each call adds `ratio` of a retry to the budget, and each
|
|
50
|
+
* retry takes one. When the budget is empty, an error goes to the caller without a retry, so a
|
|
51
|
+
* failing service gets at most `ratio` more requests, not `maxRetries` times more. Besides the
|
|
52
|
+
* budget, `minPerSecond` retries a second are always allowed. `false` turns the budget off.
|
|
53
|
+
* @default { ratio: 0.2, minPerSecond: 10 }
|
|
54
|
+
*/
|
|
55
|
+
budget?: {
|
|
56
|
+
ratio?: number;
|
|
57
|
+
minPerSecond?: number;
|
|
58
|
+
} | false;
|
|
59
|
+
/**
|
|
60
|
+
* The calls that the middleware can send again after a try with an indeterminate outcome: the
|
|
61
|
+
* request left, and no answer came (a reset, a closed connection, the timeout of one try). The
|
|
62
|
+
* server can have run such a call, so only an idempotent call goes again (roadmap R1).
|
|
63
|
+
*
|
|
64
|
+
* - a function: true for an idempotent method. `idempotentIn(registry)` reads the effects of
|
|
65
|
+
* the procedures that the client knows.
|
|
66
|
+
* - `true`: every call. Use it only with a server that removes duplicate calls by their
|
|
67
|
+
* idempotency key (`ProcedureServer` does).
|
|
68
|
+
* - absent: only a call with `retry: { idempotent: true }` in its context.
|
|
69
|
+
*
|
|
70
|
+
* A try that did not leave the client (a refused connection) and an error that the server
|
|
71
|
+
* marks retryable go again in each case.
|
|
72
|
+
*/
|
|
73
|
+
idempotent?: boolean | ((method: Method) => boolean | undefined);
|
|
40
74
|
/**
|
|
41
75
|
* Custom function to determine if a request should be retried.
|
|
42
|
-
* Takes precedence over Status.retryable flag.
|
|
76
|
+
* Takes precedence over Status.retryable flag. The rule of `idempotent` still applies.
|
|
43
77
|
*
|
|
44
78
|
* @param item - The response item
|
|
45
79
|
* @param attempt - Current attempt number (0-indexed)
|
|
@@ -88,7 +122,7 @@ export interface RetryOptions {
|
|
|
88
122
|
* @example
|
|
89
123
|
* ```typescript
|
|
90
124
|
* // Create middleware with defaults
|
|
91
|
-
* client.use(createRetryMiddleware({ maxRetries: 3 }));
|
|
125
|
+
* client.use(createRetryMiddleware({ maxRetries: 3, retryDelay: ms(500) }));
|
|
92
126
|
*
|
|
93
127
|
* // Override per-call via context
|
|
94
128
|
* await client.call(method, payload, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/client/middleware/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAA+B,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/client/middleware/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAA+B,MAAM,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvD,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG1E;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,CAAC;IAEtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAE3D;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAC,CAAC;IAEjE;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAExE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,EAC3B,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEtG;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,YAAiB,GAAG,qBAAqB,CAAC,YAAY,EAAE,EAAE,CAAC,CA4MzG"}
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Protocol-agnostic retry with exponential backoff and jitter.
|
|
5
5
|
* Works with any transport!
|
|
6
6
|
*/
|
|
7
|
-
import { abortedItem, sleep, thrownItem } from "./items.js";
|
|
7
|
+
import { abortedItem, deliveryOf, retryAfterOf, sleep, thrownItem } from "./items.js";
|
|
8
|
+
import { realClock } from "../../clock.js";
|
|
9
|
+
import { backoffDelay } from "@mark1russell7/service/backoff";
|
|
10
|
+
import { IDEMPOTENCY_KEY_METADATA, idempotencyKeyOf, newIdempotencyKey } from "../../procedures/idempotency.js";
|
|
11
|
+
import { isDuration, optionalMs } from "../../duration.js";
|
|
12
|
+
import { TIMER_DEFAULTS } from "../../timers.js";
|
|
8
13
|
/**
|
|
9
14
|
* Create retry middleware with exponential backoff and jitter.
|
|
10
15
|
*
|
|
@@ -24,7 +29,7 @@ import { abortedItem, sleep, thrownItem } from "./items.js";
|
|
|
24
29
|
* @example
|
|
25
30
|
* ```typescript
|
|
26
31
|
* // Create middleware with defaults
|
|
27
|
-
* client.use(createRetryMiddleware({ maxRetries: 3 }));
|
|
32
|
+
* client.use(createRetryMiddleware({ maxRetries: 3, retryDelay: ms(500) }));
|
|
28
33
|
*
|
|
29
34
|
* // Override per-call via context
|
|
30
35
|
* await client.call(method, payload, {
|
|
@@ -33,45 +38,112 @@ import { abortedItem, sleep, thrownItem } from "./items.js";
|
|
|
33
38
|
* ```
|
|
34
39
|
*/
|
|
35
40
|
export function createRetryMiddleware(options = {}) {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
+
const defaultRetryDelay = optionalMs(options.retryDelay, "retryDelay") ?? TIMER_DEFAULTS.retry.retryDelayMs;
|
|
42
|
+
const { jitter: defaultJitter = 1, shouldRetry: customShouldRetry, onBeforeRetry, onAfterRetry, idempotent: idempotentOption, } = options;
|
|
43
|
+
const defaultMaxRetries = options.maxRetries ?? TIMER_DEFAULTS.retry.maxRetries;
|
|
44
|
+
const defaultMultiplier = options.backoffMultiplier ?? TIMER_DEFAULTS.retry.backoffMultiplier;
|
|
45
|
+
const defaultMaxDelay = optionalMs(options.maxDelay, "maxDelay") ?? TIMER_DEFAULTS.retry.maxDelayMs;
|
|
46
|
+
/**
|
|
47
|
+
* The overrides of one call (`retry: { ... }` in its context), with the delays in milliseconds.
|
|
48
|
+
* Before, only maxAttempts was read.
|
|
49
|
+
*/
|
|
41
50
|
const overridesOf = (value) => {
|
|
42
51
|
if (typeof value !== "object" || value === null)
|
|
43
52
|
return {};
|
|
44
53
|
const out = {};
|
|
45
|
-
for (const key of ["maxAttempts", "
|
|
54
|
+
for (const key of ["maxAttempts", "backoffMultiplier"]) {
|
|
46
55
|
const field = value[key];
|
|
47
56
|
if (typeof field === "number" && Number.isFinite(field) && field >= 0)
|
|
48
57
|
out[key] = field;
|
|
49
58
|
}
|
|
59
|
+
for (const key of ["delay", "maxDelay"]) {
|
|
60
|
+
const field = value[key];
|
|
61
|
+
if (field !== undefined) {
|
|
62
|
+
if (!isDuration(field))
|
|
63
|
+
throw new TypeError(`retry.${key} must be a duration such as ms(500) (roadmap R11)`);
|
|
64
|
+
out[key] = optionalMs(field, `retry.${key}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
return out;
|
|
51
68
|
};
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
/**
|
|
70
|
+
* The backoff delay of an attempt (from 0): exponential, at most the maximum delay, with the
|
|
71
|
+
* random part of `jitter` (full jitter by default). The backoff is `backoffDelay` of
|
|
72
|
+
* `@mark1russell7/service/backoff` (roadmap R37), whose tries start at 1.
|
|
73
|
+
*/
|
|
74
|
+
const retryDelayOf = (attempt, overrides) => backoffDelay({
|
|
75
|
+
base: overrides.delay ?? defaultRetryDelay,
|
|
76
|
+
factor: overrides.backoffMultiplier ?? defaultMultiplier,
|
|
77
|
+
cap: overrides.maxDelay ?? defaultMaxDelay,
|
|
78
|
+
jitter: { kind: "subtract", fraction: Math.min(1, Math.max(0, defaultJitter)) },
|
|
79
|
+
}, attempt + 1);
|
|
80
|
+
// The retry budget (R12): a balance of retries that each call fills by `ratio`
|
|
81
|
+
const budget = options.budget === false ? undefined : { ratio: options.budget?.ratio ?? 0.2, minPerSecond: options.budget?.minPerSecond ?? 10 };
|
|
82
|
+
const budgetCap = budget ? Math.max(1, budget.ratio * 100) : 0;
|
|
83
|
+
let balance = budgetCap;
|
|
84
|
+
const clock = options.clock ?? realClock;
|
|
85
|
+
let secondStart = clock.now();
|
|
86
|
+
let retriesThisSecond = 0;
|
|
87
|
+
/** This function takes one retry from the budget, or gives false when the budget is empty. */
|
|
88
|
+
const takeRetry = () => {
|
|
89
|
+
if (!budget)
|
|
90
|
+
return true;
|
|
91
|
+
if (balance >= 1) {
|
|
92
|
+
balance -= 1;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const now = clock.now();
|
|
96
|
+
if (now - secondStart >= 1000) {
|
|
97
|
+
secondStart = now;
|
|
98
|
+
retriesThisSecond = 0;
|
|
99
|
+
}
|
|
100
|
+
if (retriesThisSecond < budget.minPerSecond) {
|
|
101
|
+
retriesThisSecond++;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
/** True when the call can go again after an indeterminate try: the call says so, or the option. */
|
|
107
|
+
const isIdempotent = (method, value) => {
|
|
108
|
+
const own = typeof value === "object" && value !== null ? value.idempotent : undefined;
|
|
109
|
+
if (typeof own === "boolean")
|
|
110
|
+
return own;
|
|
111
|
+
if (typeof idempotentOption === "function")
|
|
112
|
+
return idempotentOption(method) === true;
|
|
113
|
+
return idempotentOption === true;
|
|
57
114
|
};
|
|
58
115
|
return (next) => {
|
|
59
116
|
return async function* (context) {
|
|
60
117
|
// Read from metadata first (user-provided context), then fall back to options
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
118
|
+
const given = context.message.metadata.retry;
|
|
119
|
+
const overrides = overridesOf(given);
|
|
120
|
+
// maxAttempts counts the attempts: the first try and the retries (R12: before, the override
|
|
121
|
+
// was used as the number of retries, against its name and its documentation)
|
|
122
|
+
const maxRetries = overrides.maxAttempts !== undefined ? Math.max(0, overrides.maxAttempts - 1) : defaultMaxRetries;
|
|
123
|
+
if (budget)
|
|
124
|
+
balance = Math.min(budgetCap, balance + budget.ratio);
|
|
125
|
+
const idempotent = isIdempotent(context.message.method, context.message.metadata.retry);
|
|
126
|
+
const shouldRetryItem = (item, attempt) => {
|
|
127
|
+
const wanted = customShouldRetry
|
|
128
|
+
? customShouldRetry(item, attempt)
|
|
129
|
+
: item.status.type === "error" && item.status.retryable;
|
|
130
|
+
// A request that can have run goes again only when the call is idempotent (R1). Before,
|
|
131
|
+
// a reset or a timeout of one try sent a write again.
|
|
132
|
+
return wanted && (deliveryOf(item) !== "indeterminate" || idempotent);
|
|
133
|
+
};
|
|
66
134
|
const signal = context.message.signal;
|
|
67
135
|
const baseId = context.message.id;
|
|
136
|
+
// One key for all the tries of the call: a server that removes duplicates runs it one time
|
|
137
|
+
const idempotencyKey = idempotencyKeyOf(context.message.metadata) ?? newIdempotencyKey();
|
|
138
|
+
context.message.metadata[IDEMPOTENCY_KEY_METADATA] = idempotencyKey;
|
|
68
139
|
let attempt = 0;
|
|
69
140
|
for (;;) {
|
|
70
141
|
// Update retry metadata with current state
|
|
71
142
|
context.message.metadata.retry = {
|
|
72
|
-
...
|
|
143
|
+
...(typeof given === "object" && given !== null ? given : {}),
|
|
73
144
|
attempt,
|
|
74
|
-
maxAttempts: maxRetries,
|
|
145
|
+
maxAttempts: maxRetries + 1,
|
|
146
|
+
idempotent,
|
|
75
147
|
};
|
|
76
148
|
// Each attempt gets its own request id. The id correlates the frames of one attempt
|
|
77
149
|
// on the wire: a cancelled attempt can still answer under its id (deep dive TRN-8/9).
|
|
@@ -120,7 +192,15 @@ export function createRetryMiddleware(options = {}) {
|
|
|
120
192
|
}
|
|
121
193
|
return;
|
|
122
194
|
}
|
|
123
|
-
|
|
195
|
+
// The budget is empty: the error goes to the caller (R12)
|
|
196
|
+
if (!takeRetry()) {
|
|
197
|
+
yield retryItem;
|
|
198
|
+
if (onAfterRetry)
|
|
199
|
+
await onAfterRetry(false, attempt);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// A server that asks for a wait (Retry-After) gets at least that wait (R12)
|
|
203
|
+
let delay = Math.max(retryDelayOf(attempt, overrides), retryAfterOf(retryItem) ?? 0);
|
|
124
204
|
// Before retry hook
|
|
125
205
|
if (onBeforeRetry) {
|
|
126
206
|
const hookResult = await onBeforeRetry(retryItem, attempt);
|
|
@@ -138,7 +218,7 @@ export function createRetryMiddleware(options = {}) {
|
|
|
138
218
|
}
|
|
139
219
|
}
|
|
140
220
|
// The backoff stops at once when the caller aborts (deep dive TRN-8)
|
|
141
|
-
if (!(await sleep(delay, signal))) {
|
|
221
|
+
if (!(await sleep(delay, signal, clock))) {
|
|
142
222
|
yield abortedItem(context.message.id);
|
|
143
223
|
return;
|
|
144
224
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/client/middleware/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/client/middleware/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,21 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { TypedClientMiddleware } from "../types.js";
|
|
8
8
|
import type { TimeoutContext } from "./contexts.js";
|
|
9
|
+
import { type Duration } from "../../duration.js";
|
|
9
10
|
/**
|
|
10
11
|
* Timeout middleware options.
|
|
11
12
|
*/
|
|
12
13
|
export interface TimeoutOptions {
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* The time of the entire request (including retries), for example `seconds(10)`.
|
|
15
16
|
* @default undefined (no overall timeout)
|
|
16
17
|
*/
|
|
17
|
-
overall?:
|
|
18
|
+
overall?: Duration | undefined;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* The time of each individual attempt, for example `seconds(2)`.
|
|
20
21
|
* Useful with retry middleware for per-attempt timeouts.
|
|
21
22
|
* @default undefined (no per-attempt timeout)
|
|
22
23
|
*/
|
|
23
|
-
perAttempt?:
|
|
24
|
+
perAttempt?: Duration | undefined;
|
|
24
25
|
/**
|
|
25
26
|
* Custom timeout error message.
|
|
26
27
|
* @default "Request timeout"
|
|
@@ -41,11 +42,11 @@ export interface TimeoutOptions {
|
|
|
41
42
|
* @example
|
|
42
43
|
* ```typescript
|
|
43
44
|
* // Create middleware with default timeout
|
|
44
|
-
* client.use(createOverallTimeoutMiddleware({ overall:
|
|
45
|
+
* client.use(createOverallTimeoutMiddleware({ overall: seconds(5) }));
|
|
45
46
|
*
|
|
46
47
|
* // Override per-call via context
|
|
47
48
|
* await client.call(method, payload, {
|
|
48
|
-
* context: { timeout: { overall:
|
|
49
|
+
* context: { timeout: { overall: seconds(10) } }
|
|
49
50
|
* });
|
|
50
51
|
* ```
|
|
51
52
|
*/
|
|
@@ -64,11 +65,11 @@ export declare function createOverallTimeoutMiddleware(options: Pick<TimeoutOpti
|
|
|
64
65
|
* @example
|
|
65
66
|
* ```typescript
|
|
66
67
|
* // Create middleware with default timeout
|
|
67
|
-
* client.use(createTimeoutMiddleware({ perAttempt:
|
|
68
|
+
* client.use(createTimeoutMiddleware({ perAttempt: seconds(1) }));
|
|
68
69
|
*
|
|
69
70
|
* // Override per-call via context
|
|
70
71
|
* await client.call(method, payload, {
|
|
71
|
-
* context: { timeout: { perAttempt:
|
|
72
|
+
* context: { timeout: { perAttempt: seconds(2) } }
|
|
72
73
|
* });
|
|
73
74
|
* ```
|
|
74
75
|
*/
|
|
@@ -76,15 +77,17 @@ export declare function createTimeoutMiddleware(options: Pick<TimeoutOptions, "p
|
|
|
76
77
|
/**
|
|
77
78
|
* Create combined timeout middleware with both overall and per-attempt timeouts.
|
|
78
79
|
*
|
|
80
|
+
* @deprecated Use `resilience({ overall, perAttempt, retry })`. This function puts the two
|
|
81
|
+
* timeouts in one slot, so a retry middleware that you add with another `use()` cannot sit
|
|
82
|
+
* between them: the retry is outside both, or inside both. `resilience` puts the retry between
|
|
83
|
+
* them (roadmap R10).
|
|
84
|
+
*
|
|
79
85
|
* @param options - Timeout configuration
|
|
80
86
|
* @returns Middleware function
|
|
81
87
|
*
|
|
82
88
|
* @example
|
|
83
89
|
* ```typescript
|
|
84
|
-
* client.use(
|
|
85
|
-
* overall: 5000, // 5 seconds total
|
|
86
|
-
* perAttempt: 1000 // 1 second per attempt
|
|
87
|
-
* }));
|
|
90
|
+
* client.use(resilience({ overall: seconds(5), perAttempt: seconds(1), retry: { maxRetries: 2 } }));
|
|
88
91
|
* ```
|
|
89
92
|
*/
|
|
90
93
|
export declare function createCombinedTimeoutMiddleware(options: TimeoutOptions): TypedClientMiddleware<TimeoutContext, {}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../src/client/middleware/timeout.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../src/client/middleware/timeout.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAA6C,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAElC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgKD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG,qBAAqB,CAAC,cAAc,EAAE,EAAE,CAAC,CAqB9I;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,SAAS,CAAC,GAAG,qBAAqB,CAAC,cAAc,EAAE,EAAE,CAAC,CAyB1I;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,cAAc,GAAG,qBAAqB,CAAC,cAAc,EAAE,EAAE,CAAC,CAGlH"}
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Protocol-agnostic timeout handling with per-request and overall timeouts.
|
|
5
5
|
* Works with any transport!
|
|
6
6
|
*/
|
|
7
|
-
import { ABORTED_FIRST, errorItem, nextOrAbort } from "./items.js";
|
|
7
|
+
import { ABORTED_FIRST, abortedItem, errorItem, nextOrAbort, withDelivery } from "./items.js";
|
|
8
|
+
import { deadlineOf, narrowDeadline, restoreDeadline } from "../deadline.js";
|
|
9
|
+
import { resilience } from "./resilience.js";
|
|
10
|
+
import { optionalMs } from "../../duration.js";
|
|
8
11
|
/**
|
|
9
12
|
* Compose multiple AbortSignals into one.
|
|
10
13
|
*
|
|
@@ -55,15 +58,16 @@ function composeAbortSignals(...signals) {
|
|
|
55
58
|
// Fall through to manual composition if AbortSignal.any() fails
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
|
-
// Fallback: Manual composition for older environments
|
|
61
|
+
// Fallback: Manual composition for older environments. The composed signal keeps the reason of
|
|
62
|
+
// the signal that aborted, as with AbortSignal.any() (before, it got a new AbortError).
|
|
59
63
|
const controller = new AbortController();
|
|
60
64
|
const abortListeners = [];
|
|
61
65
|
for (const signal of validSignals) {
|
|
62
66
|
if (signal.aborted) {
|
|
63
|
-
controller.abort();
|
|
67
|
+
controller.abort(signal.reason);
|
|
64
68
|
break;
|
|
65
69
|
}
|
|
66
|
-
const listener = () => controller.abort();
|
|
70
|
+
const listener = () => controller.abort(signal.reason);
|
|
67
71
|
signal.addEventListener("abort", listener, { once: true });
|
|
68
72
|
abortListeners.push(() => signal.removeEventListener("abort", listener));
|
|
69
73
|
}
|
|
@@ -77,7 +81,8 @@ function composeAbortSignals(...signals) {
|
|
|
77
81
|
/**
|
|
78
82
|
* Run `next` with a timeout. When the time runs out, the request's signal aborts, and the
|
|
79
83
|
* reader gets one TIMEOUT error item in place of the transport's ABORTED item or thrown error.
|
|
80
|
-
* The middleware does not wait for a transport that ignores the signal
|
|
84
|
+
* The middleware does not wait for a transport that ignores the signal: not after the timeout,
|
|
85
|
+
* and not after an abort of the caller, which gives an ABORTED item.
|
|
81
86
|
*
|
|
82
87
|
* Before, the middleware waited for a thrown error, but the transports yield error items: the
|
|
83
88
|
* reader got "ABORTED" (not retryable), so a per-attempt timeout with retry never retried, and
|
|
@@ -86,47 +91,49 @@ function composeAbortSignals(...signals) {
|
|
|
86
91
|
async function* runWithTimeout(next, context, ms, message, retryable) {
|
|
87
92
|
const timeoutController = new AbortController();
|
|
88
93
|
const timeoutId = setTimeout(() => timeoutController.abort(), ms);
|
|
94
|
+
// The overall timeout is the deadline of the call: the transport sends its remaining time to
|
|
95
|
+
// the server (roadmap R10). The timeout of one try is not: the server keeps a keyed write
|
|
96
|
+
// until the next try can get its result, and the request signal stops another call.
|
|
97
|
+
const deadlineBefore = retryable ? deadlineOf(context.message.metadata) : narrowDeadline(context.message.metadata, Date.now() + ms);
|
|
89
98
|
// Compose with existing signal
|
|
90
99
|
const originalSignal = context.message.signal;
|
|
91
100
|
const { controller: composedController, cleanup } = composeAbortSignals(originalSignal, timeoutController.signal);
|
|
92
101
|
context.message.signal = composedController.signal;
|
|
93
102
|
const timedOut = () => timeoutController.signal.aborted && !originalSignal?.aborted;
|
|
94
|
-
|
|
103
|
+
// The request left before the time ran out: the server can still run it (R1)
|
|
104
|
+
const timeoutItem = () => withDelivery(errorItem(context.message.id, "TIMEOUT", message, retryable), "indeterminate");
|
|
95
105
|
const iterator = next(context)[Symbol.asyncIterator]();
|
|
96
106
|
// "running": a next() may be pending; "done": the iterator ended; "abandoned": the timeout won
|
|
97
107
|
let phase = "running";
|
|
98
108
|
try {
|
|
99
109
|
for (;;) {
|
|
100
|
-
|
|
110
|
+
// The timeout and the abort of the caller both end the wait (roadmap R30: before, the wait
|
|
111
|
+
// ignored the abort of the caller, so a transport that ignored the signal kept the caller
|
|
112
|
+
// waiting until the timeout, and the caller got TIMEOUT for its own abort)
|
|
113
|
+
const result = await nextOrAbort(iterator, composedController.signal);
|
|
101
114
|
if (result === ABORTED_FIRST) {
|
|
102
115
|
phase = "abandoned";
|
|
103
|
-
// The transport did not stop in time: end it in the background, and give the timeout
|
|
116
|
+
// The transport did not stop in time: end it in the background, and give the timeout or the abort
|
|
104
117
|
void Promise.resolve(iterator.return?.()).catch(() => undefined);
|
|
105
|
-
yield timeoutItem();
|
|
118
|
+
yield timedOut() ? timeoutItem() : abortedItem(context.message.id);
|
|
106
119
|
return;
|
|
107
120
|
}
|
|
108
121
|
if (result.done) {
|
|
109
122
|
phase = "done";
|
|
110
123
|
return;
|
|
111
124
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
yield timeoutItem();
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
125
|
+
// An item or an error of the transport comes before the abort. `nextOrAbort` adds its abort
|
|
126
|
+
// listener before it calls `next()`, so a later abort gives ABORTED_FIRST, not an item.
|
|
117
127
|
yield result.value;
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
catch (error) {
|
|
121
131
|
phase = "done";
|
|
122
|
-
if (timedOut()) {
|
|
123
|
-
yield timeoutItem();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
132
|
throw error;
|
|
127
133
|
}
|
|
128
134
|
finally {
|
|
129
135
|
clearTimeout(timeoutId);
|
|
136
|
+
restoreDeadline(context.message.metadata, deadlineBefore);
|
|
130
137
|
cleanup();
|
|
131
138
|
// Restore the caller's original signal; we only swapped in the composed signal for this
|
|
132
139
|
// attempt. Leaving the (possibly aborted) composed signal in place poisons subsequent
|
|
@@ -156,21 +163,22 @@ async function* runWithTimeout(next, context, ms, message, retryable) {
|
|
|
156
163
|
* @example
|
|
157
164
|
* ```typescript
|
|
158
165
|
* // Create middleware with default timeout
|
|
159
|
-
* client.use(createOverallTimeoutMiddleware({ overall:
|
|
166
|
+
* client.use(createOverallTimeoutMiddleware({ overall: seconds(5) }));
|
|
160
167
|
*
|
|
161
168
|
* // Override per-call via context
|
|
162
169
|
* await client.call(method, payload, {
|
|
163
|
-
* context: { timeout: { overall:
|
|
170
|
+
* context: { timeout: { overall: seconds(10) } }
|
|
164
171
|
* });
|
|
165
172
|
* ```
|
|
166
173
|
*/
|
|
167
174
|
export function createOverallTimeoutMiddleware(options) {
|
|
168
|
-
const {
|
|
175
|
+
const { message = "Overall request timeout" } = options;
|
|
176
|
+
const defaultOverall = optionalMs(options.overall, "overall");
|
|
169
177
|
return (next) => {
|
|
170
178
|
return async function* (context) {
|
|
171
179
|
// Read from metadata first (user-provided context), then fall back to options
|
|
172
|
-
const overall = context.message.metadata.timeout?.overall
|
|
173
|
-
|
|
180
|
+
const overall = optionalMs(context.message.metadata.timeout?.overall, "timeout.overall") ??
|
|
181
|
+
defaultOverall;
|
|
174
182
|
if (!overall) {
|
|
175
183
|
// No timeout - passthrough
|
|
176
184
|
yield* next(context);
|
|
@@ -195,59 +203,53 @@ export function createOverallTimeoutMiddleware(options) {
|
|
|
195
203
|
* @example
|
|
196
204
|
* ```typescript
|
|
197
205
|
* // Create middleware with default timeout
|
|
198
|
-
* client.use(createTimeoutMiddleware({ perAttempt:
|
|
206
|
+
* client.use(createTimeoutMiddleware({ perAttempt: seconds(1) }));
|
|
199
207
|
*
|
|
200
208
|
* // Override per-call via context
|
|
201
209
|
* await client.call(method, payload, {
|
|
202
|
-
* context: { timeout: { perAttempt:
|
|
210
|
+
* context: { timeout: { perAttempt: seconds(2) } }
|
|
203
211
|
* });
|
|
204
212
|
* ```
|
|
205
213
|
*/
|
|
206
214
|
export function createTimeoutMiddleware(options) {
|
|
207
|
-
const {
|
|
215
|
+
const { message = "Request timeout" } = options;
|
|
216
|
+
const defaultPerAttempt = optionalMs(options.perAttempt, "perAttempt");
|
|
208
217
|
return (next) => {
|
|
209
218
|
return async function* (context) {
|
|
210
219
|
// Read from metadata first (user-provided context), then fall back to options
|
|
211
|
-
const perAttempt = context.message.metadata.timeout?.perAttempt
|
|
212
|
-
|
|
220
|
+
const perAttempt = optionalMs(context.message.metadata.timeout?.perAttempt, "timeout.perAttempt") ??
|
|
221
|
+
defaultPerAttempt;
|
|
213
222
|
if (!perAttempt) {
|
|
214
223
|
// No timeout - passthrough
|
|
215
224
|
yield* next(context);
|
|
216
225
|
return;
|
|
217
226
|
}
|
|
227
|
+
// The limit of one try is at most the remaining time of the call (roadmap R10)
|
|
228
|
+
const deadline = deadlineOf(context.message.metadata);
|
|
229
|
+
const limit = deadline === undefined ? perAttempt : Math.max(0, Math.min(perAttempt, deadline - Date.now()));
|
|
218
230
|
// A retry middleware outside this one can start a new attempt
|
|
219
|
-
yield* runWithTimeout(next, context,
|
|
231
|
+
yield* runWithTimeout(next, context, limit, message, true);
|
|
220
232
|
};
|
|
221
233
|
};
|
|
222
234
|
}
|
|
223
235
|
/**
|
|
224
236
|
* Create combined timeout middleware with both overall and per-attempt timeouts.
|
|
225
237
|
*
|
|
238
|
+
* @deprecated Use `resilience({ overall, perAttempt, retry })`. This function puts the two
|
|
239
|
+
* timeouts in one slot, so a retry middleware that you add with another `use()` cannot sit
|
|
240
|
+
* between them: the retry is outside both, or inside both. `resilience` puts the retry between
|
|
241
|
+
* them (roadmap R10).
|
|
242
|
+
*
|
|
226
243
|
* @param options - Timeout configuration
|
|
227
244
|
* @returns Middleware function
|
|
228
245
|
*
|
|
229
246
|
* @example
|
|
230
247
|
* ```typescript
|
|
231
|
-
* client.use(
|
|
232
|
-
* overall: 5000, // 5 seconds total
|
|
233
|
-
* perAttempt: 1000 // 1 second per attempt
|
|
234
|
-
* }));
|
|
248
|
+
* client.use(resilience({ overall: seconds(5), perAttempt: seconds(1), retry: { maxRetries: 2 } }));
|
|
235
249
|
* ```
|
|
236
250
|
*/
|
|
237
251
|
export function createCombinedTimeoutMiddleware(options) {
|
|
238
252
|
const { overall, perAttempt, message = "Request timeout" } = options;
|
|
239
|
-
return (
|
|
240
|
-
// Compose both timeout middlewares
|
|
241
|
-
let composed = next;
|
|
242
|
-
if (perAttempt) {
|
|
243
|
-
const perAttemptMiddleware = createTimeoutMiddleware({ perAttempt, message: `${message} (per-attempt)` });
|
|
244
|
-
composed = perAttemptMiddleware(composed);
|
|
245
|
-
}
|
|
246
|
-
if (overall) {
|
|
247
|
-
const overallMiddleware = createOverallTimeoutMiddleware({ overall, message: `${message} (overall)` });
|
|
248
|
-
composed = overallMiddleware(composed);
|
|
249
|
-
}
|
|
250
|
-
return composed;
|
|
251
|
-
};
|
|
253
|
+
return resilience({ overall, perAttempt, message });
|
|
252
254
|
}
|
|
253
255
|
//# sourceMappingURL=timeout.js.map
|