@distilled.cloud/core 0.16.1 → 0.16.3

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.
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Server-provided retry hint parsing.
3
+ *
4
+ * Standardizes parsing of:
5
+ * - `Retry-After` (RFC 7231 §7.1.3): `delay-seconds` (integer) or `HTTP-date`.
6
+ * - `RateLimit` (IETF draft-ietf-httpapi-ratelimit-headers): structured
7
+ * dictionary `r=<remaining>, t=<seconds-until-reset>`.
8
+ *
9
+ * Each SDK's `matchError` calls these helpers and attaches the resulting
10
+ * `Duration` to retryable errors via the optional `retryAfter` field, where
11
+ * the retry policy will honor it with precedence over the default backoff.
12
+ *
13
+ * Services that use bespoke headers or body fields are expected to do their
14
+ * own parsing in the SDK's `matchError`, optionally falling back to these
15
+ * helpers for the standard cases.
16
+ */
17
+ import * as Duration from "effect/Duration";
18
+ import { RETRYABLE_HTTP_STATUSES } from "./errors.js";
19
+ /**
20
+ * Parse the standard `Retry-After` HTTP header per RFC 7231 §7.1.3.
21
+ *
22
+ * Accepts either a non-negative integer number of seconds (the common case)
23
+ * or an HTTP-date. HTTP-dates in the past produce a zero duration.
24
+ *
25
+ * Returns `undefined` when the header is missing or unparseable.
26
+ */
27
+ export const parseRetryAfter = (headers) => {
28
+ const raw = headers?.["retry-after"];
29
+ if (!raw)
30
+ return undefined;
31
+ const trimmed = raw.trim();
32
+ if (trimmed.length === 0)
33
+ return undefined;
34
+ // delay-seconds: 1*DIGIT (non-negative decimal integer per RFC).
35
+ if (/^\d+$/.test(trimmed)) {
36
+ const seconds = Number(trimmed);
37
+ if (Number.isFinite(seconds) && seconds >= 0) {
38
+ return Duration.seconds(seconds);
39
+ }
40
+ }
41
+ // Reject anything that *looks* numeric but isn't a valid delay-seconds.
42
+ // Without this, `Date.parse("-5")` happens to return 0 on some engines and
43
+ // would silently yield a zero-duration instead of the expected undefined.
44
+ if (/^[-+]?\d+(?:\.\d+)?$/.test(trimmed))
45
+ return undefined;
46
+ // HTTP-date: try Date.parse, which handles RFC 1123 / RFC 850 / asctime.
47
+ const ts = Date.parse(trimmed);
48
+ if (!Number.isNaN(ts)) {
49
+ const ms = Math.max(0, ts - Date.now());
50
+ return Duration.millis(ms);
51
+ }
52
+ return undefined;
53
+ };
54
+ /**
55
+ * Parse the IETF `RateLimit` structured header.
56
+ *
57
+ * Examples:
58
+ * `RateLimit: limit=100, remaining=0, reset=30` (older draft)
59
+ * `RateLimit: "default";r=0;t=30` (newer draft)
60
+ * `RateLimit: r=0, t=30` (compact form)
61
+ *
62
+ * Returns the `t` (seconds until reset) value as a Duration, but only when
63
+ * `r` (remaining quota) is present and equals `0` — i.e., we're actually
64
+ * rate-limited right now. Returns `undefined` otherwise.
65
+ */
66
+ export const parseRatelimit = (headers) => {
67
+ const raw = headers?.["ratelimit"];
68
+ if (!raw)
69
+ return undefined;
70
+ let remaining;
71
+ let reset;
72
+ // Tokenize on commas and semicolons; tolerate `key=value` and `key =value`.
73
+ for (const piece of raw.split(/[,;]/)) {
74
+ const eq = piece.indexOf("=");
75
+ if (eq < 0)
76
+ continue;
77
+ const key = piece.slice(0, eq).trim().toLowerCase();
78
+ const value = piece.slice(eq + 1).trim();
79
+ const num = Number(value);
80
+ if (!Number.isFinite(num))
81
+ continue;
82
+ if (key === "r" || key === "remaining")
83
+ remaining = num;
84
+ else if (key === "t" || key === "reset")
85
+ reset = num;
86
+ }
87
+ if (remaining !== undefined && remaining > 0)
88
+ return undefined;
89
+ if (reset === undefined || reset < 0)
90
+ return undefined;
91
+ return Duration.seconds(reset);
92
+ };
93
+ /**
94
+ * Convenience: try `Retry-After` first, then `RateLimit`. Returns the first
95
+ * successful parse, or `undefined` if neither header yields a usable value.
96
+ */
97
+ export const parseServerRetryHint = (headers) => parseRetryAfter(headers) ?? parseRatelimit(headers);
98
+ /**
99
+ * Status-gated variant of {@link parseServerRetryHint}. Returns `undefined`
100
+ * unless `status` is one whose error class actually declares a `retryAfter`
101
+ * field (see `RETRYABLE_HTTP_STATUSES`). Use this in `matchError` when you
102
+ * dispatch off a generic `HTTP_STATUS_MAP` lookup that includes both
103
+ * retryable (429/5xx/423) and non-retryable (4xx) classes — it prevents
104
+ * stale `retryAfter` properties from being attached to errors like
105
+ * `BadRequest` or `NotFound` (which would otherwise leak into JSON output).
106
+ */
107
+ export const parseRetryAfterForStatus = (status, headers) => {
108
+ if (!RETRYABLE_HTTP_STATUSES.has(status))
109
+ return undefined;
110
+ return parseServerRetryHint(headers);
111
+ };
112
+ //# sourceMappingURL=retry-after.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-after.js","sourceRoot":"","sources":["../src/retry-after.ts"],"names":[],"mappings":"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"}
package/lib/retry.d.ts CHANGED
@@ -2,22 +2,13 @@
2
2
  * Retry Policy System
3
3
  *
4
4
  * Provides configurable retry policies for API operations.
5
- * Each SDK creates its own Retry service tag but uses these shared utilities
6
- * for building retry schedules and policies.
7
- *
8
- * @example
9
- * ```ts
10
- * import * as Retry from "@distilled.cloud/core/retry";
11
- *
12
- * // Use the default retry policy
13
- * myEffect.pipe(Retry.policy(myRetryService, Retry.makeDefault()))
14
- *
15
- * // Disable retries
16
- * myEffect.pipe(Retry.none(myRetryService))
17
- * ```
5
+ * Each SDK creates its own `Retry` Context.Service tag (via
6
+ * `makeRetryService`) and threads it into `makeAPI({ retry: <SDK>.Retry })`
7
+ * so that callers can install a blanket retry policy at the layer level
8
+ * for that SDK without wrapping every call with `Effect.retry`.
18
9
  */
19
10
  import * as Duration from "effect/Duration";
20
- import * as Effect from "effect/Effect";
11
+ import * as Layer from "effect/Layer";
21
12
  import * as Ref from "effect/Ref";
22
13
  import * as Schedule from "effect/Schedule";
23
14
  import * as Context from "effect/Context";
@@ -45,23 +36,17 @@ export type Factory = (lastError: Ref.Ref<unknown>) => Options;
45
36
  export type Policy = Options | Factory;
46
37
  /**
47
38
  * Create a typed Retry service class for an SDK.
48
- * Each SDK should create its own Retry service using this factory.
39
+ * Each SDK creates its own Retry service using this factory; the SDK's
40
+ * client wraps `makeAPI` with `retry: <SDK>.Retry` so the policy applies
41
+ * to every API call below it.
49
42
  *
50
43
  * @example
51
44
  * ```ts
52
- * // In planetscale-sdk/src/retry.ts
45
+ * // packages/<sdk>/src/retry.ts
53
46
  * export class Retry extends makeRetryService("PlanetScaleRetry") {}
54
47
  * ```
55
48
  */
56
49
  export declare const makeRetryService: (name: string) => Context.ServiceClass<any, string, Policy>;
57
- /**
58
- * Provides a custom retry policy for API calls.
59
- */
60
- export declare const policy: (Service: any, optionsOrFactory: Policy) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, unknown>;
61
- /**
62
- * Disables all automatic retries.
63
- */
64
- export declare const none: (Service: any) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, unknown>;
65
50
  /**
66
51
  * Custom jittered schedule helper.
67
52
  * Adds random jitter between 0-50ms to avoid thundering herd.
@@ -71,21 +56,70 @@ export declare const jittered: <Input, Error, Env>(self: Schedule.Schedule<unkno
71
56
  * Cap delay at a maximum duration.
72
57
  */
73
58
  export declare const capped: (max: Duration.Duration) => <Input, Error, Env>(self: Schedule.Schedule<Duration.Duration, Input, Error, Env>) => Schedule.Schedule<Duration.Duration, Input, Error, Env>;
59
+ /**
60
+ * Default cap (milliseconds) on how long we'll honor a server-provided
61
+ * `Retry-After` / `RateLimit` hint. A misbehaving server can otherwise park
62
+ * us forever.
63
+ */
64
+ export declare const DEFAULT_SERVER_RETRY_HINT_CAP_MS = 60000;
65
+ /**
66
+ * Optional override for the server-hint cap, in milliseconds. When provided
67
+ * via `Layer.succeed(ServerRetryHintCapMs, n)`, that value wins over the
68
+ * environment.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * Effect.retry(op, policy).pipe(
73
+ * Effect.provide(serverRetryHintCapLayer(120_000)),
74
+ * )
75
+ * ```
76
+ */
77
+ export declare const ServerRetryHintCapMs: Context.Service<number, number>;
78
+ /**
79
+ * Layer that pins the server `retryAfter` cap to a fixed value (milliseconds).
80
+ */
81
+ export declare const serverRetryHintCapLayer: (capMs: number) => Layer.Layer<number, never, never>;
82
+ /**
83
+ * Effective cap when neither {@link ServerRetryHintCapMs} nor
84
+ * `DISTILLED_SERVER_RETRY_HINT_CAP_MS` is set: {@link DEFAULT_SERVER_RETRY_HINT_CAP_MS}.
85
+ *
86
+ * Reads `process.env.DISTILLED_SERVER_RETRY_HINT_CAP_MS` when present (must be
87
+ * a non-negative finite integer; invalid values are ignored). Undefined
88
+ * `process` (some edge runtimes) falls back to the default.
89
+ */
90
+ export declare const readServerRetryHintCapMsFromEnv: () => number;
74
91
  /**
75
92
  * Creates the default retry policy.
76
93
  *
77
94
  * This policy:
78
95
  * - Retries transient errors (throttling, server, network, locked errors)
79
- * - Uses exponential backoff starting at 100ms with a factor of 2
96
+ * - Honors `error.retryAfter` (server-provided hint) with precedence, capped
97
+ * by {@link DEFAULT_SERVER_RETRY_HINT_CAP_MS} by default; override with
98
+ * `DISTILLED_SERVER_RETRY_HINT_CAP_MS` or {@link ServerRetryHintCapMs}
99
+ * - Otherwise uses exponential backoff starting at 100ms with a factor of 2
80
100
  * - Ensures at least 500ms delay for throttling errors
81
101
  * - Limits to 5 retry attempts
82
102
  * - Applies jitter to avoid thundering herd
83
103
  */
84
104
  export declare const makeDefault: Factory;
105
+ /**
106
+ * Factory for the throttling-only retry policy. Honors server hints when
107
+ * present so callers using `<SDK>.Retry.throttling` get correct backoff.
108
+ */
109
+ export declare const throttlingFactory: Factory;
85
110
  /**
86
111
  * Retry options that retries all throttling errors indefinitely.
112
+ *
113
+ * Note: prefer {@link throttlingFactory} when you want server-hint honoring.
114
+ * This static `Options` form does not have access to `lastError` and so
115
+ * cannot read `error.retryAfter`.
87
116
  */
88
117
  export declare const throttlingOptions: Options;
118
+ /**
119
+ * Factory for the transient retry policy. Honors server hints when present
120
+ * so callers using `<SDK>.Retry.transient` get correct backoff.
121
+ */
122
+ export declare const transientFactory: Factory;
89
123
  /**
90
124
  * Retry options that retries all transient errors indefinitely.
91
125
  *
@@ -94,6 +128,8 @@ export declare const throttlingOptions: Options;
94
128
  * 2. Server errors
95
129
  * 3. Network errors
96
130
  * 4. Locked errors (423)
131
+ *
132
+ * Note: prefer {@link transientFactory} when you want server-hint honoring.
97
133
  */
98
134
  export declare const transientOptions: Options;
99
135
  //# sourceMappingURL=retry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,GAAG,MAAM,YAAY,CAAC;AAClC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAO1C;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAMvC;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,SAAU,MAAM,8CACP,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,MAAM,YACP,GAAG,oBAAoB,MAAM,MACtC,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,uCACiC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,IAAI,YACL,GAAG,MACZ,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,uCAIrC,CAAC;AAMN;;;GAGG;AACH,eAAO,MAAM,QAAQ,2HAEpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,QAAS,QAAQ,CAAC,QAAQ,kJAK1C,CAAC;AAMJ;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,OAkBxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAO/B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAO9B,CAAC"}
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAG5C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,GAAG,MAAM,YAAY,CAAC;AAClC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAO1C;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAMvC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,SAAU,MAAM,8CACP,CAAC;AAMvC;;;GAGG;AACH,eAAO,MAAM,QAAQ,2HAEpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,QAAS,QAAQ,CAAC,QAAQ,kJAK1C,CAAC;AAMJ;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAIvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,iCAEhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,UAAW,MAAM,sCACT,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,QAAO,MASlD,CAAC;AAwDF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,EAAE,OAaxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAQ9B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAO/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAQ7B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAO9B,CAAC"}
package/lib/retry.js CHANGED
@@ -2,24 +2,16 @@
2
2
  * Retry Policy System
3
3
  *
4
4
  * Provides configurable retry policies for API operations.
5
- * Each SDK creates its own Retry service tag but uses these shared utilities
6
- * for building retry schedules and policies.
7
- *
8
- * @example
9
- * ```ts
10
- * import * as Retry from "@distilled.cloud/core/retry";
11
- *
12
- * // Use the default retry policy
13
- * myEffect.pipe(Retry.policy(myRetryService, Retry.makeDefault()))
14
- *
15
- * // Disable retries
16
- * myEffect.pipe(Retry.none(myRetryService))
17
- * ```
5
+ * Each SDK creates its own `Retry` Context.Service tag (via
6
+ * `makeRetryService`) and threads it into `makeAPI({ retry: <SDK>.Retry })`
7
+ * so that callers can install a blanket retry policy at the layer level
8
+ * for that SDK without wrapping every call with `Effect.retry`.
18
9
  */
19
10
  import * as Duration from "effect/Duration";
20
11
  import * as Effect from "effect/Effect";
21
12
  import { pipe } from "effect/Function";
22
13
  import * as Layer from "effect/Layer";
14
+ import * as Option from "effect/Option";
23
15
  import * as Ref from "effect/Ref";
24
16
  import * as Schedule from "effect/Schedule";
25
17
  import * as Context from "effect/Context";
@@ -29,23 +21,17 @@ import { isThrottling, isTransientError } from "./category.js";
29
21
  // ============================================================================
30
22
  /**
31
23
  * Create a typed Retry service class for an SDK.
32
- * Each SDK should create its own Retry service using this factory.
24
+ * Each SDK creates its own Retry service using this factory; the SDK's
25
+ * client wraps `makeAPI` with `retry: <SDK>.Retry` so the policy applies
26
+ * to every API call below it.
33
27
  *
34
28
  * @example
35
29
  * ```ts
36
- * // In planetscale-sdk/src/retry.ts
30
+ * // packages/<sdk>/src/retry.ts
37
31
  * export class Retry extends makeRetryService("PlanetScaleRetry") {}
38
32
  * ```
39
33
  */
40
34
  export const makeRetryService = (name) => Context.Service()(name);
41
- /**
42
- * Provides a custom retry policy for API calls.
43
- */
44
- export const policy = (Service, optionsOrFactory) => (effect) => Effect.provide(effect, Layer.succeed(Service, optionsOrFactory));
45
- /**
46
- * Disables all automatic retries.
47
- */
48
- export const none = (Service) => (effect) => Effect.provide(effect, Layer.succeed(Service, { while: () => false }));
49
35
  // ============================================================================
50
36
  // Retry Schedule Utilities
51
37
  // ============================================================================
@@ -59,6 +45,91 @@ export const jittered = Schedule.addDelay(() => Effect.succeed(Duration.millis(M
59
45
  */
60
46
  export const capped = (max) => Schedule.modifyDelay((duration) => Effect.succeed(Duration.isGreaterThan(duration, max) ? Duration.millis(5000) : duration));
61
47
  // ============================================================================
48
+ // Server Hint Helpers
49
+ // ============================================================================
50
+ /**
51
+ * Default cap (milliseconds) on how long we'll honor a server-provided
52
+ * `Retry-After` / `RateLimit` hint. A misbehaving server can otherwise park
53
+ * us forever.
54
+ */
55
+ export const DEFAULT_SERVER_RETRY_HINT_CAP_MS = 60_000;
56
+ const ENV_SERVER_RETRY_HINT_CAP_MS = "DISTILLED_SERVER_RETRY_HINT_CAP_MS";
57
+ /**
58
+ * Optional override for the server-hint cap, in milliseconds. When provided
59
+ * via `Layer.succeed(ServerRetryHintCapMs, n)`, that value wins over the
60
+ * environment.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * Effect.retry(op, policy).pipe(
65
+ * Effect.provide(serverRetryHintCapLayer(120_000)),
66
+ * )
67
+ * ```
68
+ */
69
+ export const ServerRetryHintCapMs = Context.Service("@distilled.cloud/core/ServerRetryHintCapMs");
70
+ /**
71
+ * Layer that pins the server `retryAfter` cap to a fixed value (milliseconds).
72
+ */
73
+ export const serverRetryHintCapLayer = (capMs) => Layer.succeed(ServerRetryHintCapMs, capMs);
74
+ /**
75
+ * Effective cap when neither {@link ServerRetryHintCapMs} nor
76
+ * `DISTILLED_SERVER_RETRY_HINT_CAP_MS` is set: {@link DEFAULT_SERVER_RETRY_HINT_CAP_MS}.
77
+ *
78
+ * Reads `process.env.DISTILLED_SERVER_RETRY_HINT_CAP_MS` when present (must be
79
+ * a non-negative finite integer; invalid values are ignored). Undefined
80
+ * `process` (some edge runtimes) falls back to the default.
81
+ */
82
+ export const readServerRetryHintCapMsFromEnv = () => {
83
+ if (typeof process === "undefined" || process.env === undefined) {
84
+ return DEFAULT_SERVER_RETRY_HINT_CAP_MS;
85
+ }
86
+ const raw = process.env[ENV_SERVER_RETRY_HINT_CAP_MS];
87
+ if (raw === undefined || raw === "")
88
+ return DEFAULT_SERVER_RETRY_HINT_CAP_MS;
89
+ const n = Number(raw);
90
+ if (!Number.isFinite(n) || n < 0)
91
+ return DEFAULT_SERVER_RETRY_HINT_CAP_MS;
92
+ return Math.trunc(n);
93
+ };
94
+ const resolveServerRetryHintCapMs = () => Effect.gen(function* () {
95
+ const fromLayer = yield* Effect.serviceOption(ServerRetryHintCapMs);
96
+ return Option.match(fromLayer, {
97
+ onNone: () => readServerRetryHintCapMsFromEnv(),
98
+ onSome: (n) => Number.isFinite(n) && n >= 0
99
+ ? Math.trunc(n)
100
+ : readServerRetryHintCapMsFromEnv(),
101
+ });
102
+ });
103
+ /**
104
+ * Extract a server-provided retry hint (in milliseconds) from an error's
105
+ * optional `retryAfter` field, capped by the effective server-hint cap
106
+ * (see {@link ServerRetryHintCapMs} and {@link readServerRetryHintCapMsFromEnv}).
107
+ *
108
+ * Returns `undefined` when the error doesn't carry a hint.
109
+ */
110
+ const serverHintMillis = (error, capMs) => {
111
+ const hint = error
112
+ ?.retryAfter;
113
+ if (!Duration.isDuration(hint))
114
+ return undefined;
115
+ return Math.min(Duration.toMillis(hint), capMs);
116
+ };
117
+ /**
118
+ * Schedule modifier that honors `error.retryAfter` (set by the SDK's
119
+ * `matchError` from `Retry-After` / `RateLimit` headers) with precedence
120
+ * over the input delay. Falls back to the original delay when no hint is
121
+ * present.
122
+ */
123
+ const honorServerHint = (lastError, baseline) => Schedule.modifyDelay(Effect.fnUntraced(function* (duration) {
124
+ const capMs = yield* resolveServerRetryHintCapMs();
125
+ const error = yield* Ref.get(lastError);
126
+ const hint = serverHintMillis(error, capMs);
127
+ if (hint !== undefined)
128
+ return hint;
129
+ const adjusted = baseline ? baseline(duration, error) : duration;
130
+ return Duration.toMillis(adjusted);
131
+ }));
132
+ // ============================================================================
62
133
  // Default Retry Policies
63
134
  // ============================================================================
64
135
  /**
@@ -66,30 +137,50 @@ export const capped = (max) => Schedule.modifyDelay((duration) => Effect.succeed
66
137
  *
67
138
  * This policy:
68
139
  * - Retries transient errors (throttling, server, network, locked errors)
69
- * - Uses exponential backoff starting at 100ms with a factor of 2
140
+ * - Honors `error.retryAfter` (server-provided hint) with precedence, capped
141
+ * by {@link DEFAULT_SERVER_RETRY_HINT_CAP_MS} by default; override with
142
+ * `DISTILLED_SERVER_RETRY_HINT_CAP_MS` or {@link ServerRetryHintCapMs}
143
+ * - Otherwise uses exponential backoff starting at 100ms with a factor of 2
70
144
  * - Ensures at least 500ms delay for throttling errors
71
145
  * - Limits to 5 retry attempts
72
146
  * - Applies jitter to avoid thundering herd
73
147
  */
74
148
  export const makeDefault = (lastError) => ({
75
149
  while: (error) => isTransientError(error),
76
- schedule: pipe(Schedule.exponential(100, 2), Schedule.modifyDelay(Effect.fnUntraced(function* (duration) {
77
- const error = yield* Ref.get(lastError);
78
- if (isThrottling(error)) {
79
- if (Duration.toMillis(duration) < 500) {
80
- return Duration.toMillis(Duration.millis(500));
81
- }
150
+ schedule: pipe(Schedule.exponential(100, 2), honorServerHint(lastError, (duration, error) => {
151
+ if (isThrottling(error) && Duration.toMillis(duration) < 500) {
152
+ return Duration.millis(500);
82
153
  }
83
- return Duration.toMillis(duration);
84
- })), Schedule.both(Schedule.recurs(5)), jittered),
154
+ return duration;
155
+ }), Schedule.both(Schedule.recurs(5)), jittered),
156
+ });
157
+ /**
158
+ * Factory for the throttling-only retry policy. Honors server hints when
159
+ * present so callers using `<SDK>.Retry.throttling` get correct backoff.
160
+ */
161
+ export const throttlingFactory = (lastError) => ({
162
+ while: (error) => isThrottling(error),
163
+ schedule: pipe(Schedule.exponential(1000, 2), honorServerHint(lastError), capped(Duration.seconds(5)), jittered),
85
164
  });
86
165
  /**
87
166
  * Retry options that retries all throttling errors indefinitely.
167
+ *
168
+ * Note: prefer {@link throttlingFactory} when you want server-hint honoring.
169
+ * This static `Options` form does not have access to `lastError` and so
170
+ * cannot read `error.retryAfter`.
88
171
  */
89
172
  export const throttlingOptions = {
90
173
  while: (error) => isThrottling(error),
91
174
  schedule: pipe(Schedule.exponential(1000, 2), capped(Duration.seconds(5)), jittered),
92
175
  };
176
+ /**
177
+ * Factory for the transient retry policy. Honors server hints when present
178
+ * so callers using `<SDK>.Retry.transient` get correct backoff.
179
+ */
180
+ export const transientFactory = (lastError) => ({
181
+ while: isTransientError,
182
+ schedule: pipe(Schedule.exponential(1000, 2), honorServerHint(lastError), capped(Duration.seconds(5)), jittered),
183
+ });
93
184
  /**
94
185
  * Retry options that retries all transient errors indefinitely.
95
186
  *
@@ -98,6 +189,8 @@ export const throttlingOptions = {
98
189
  * 2. Server errors
99
190
  * 3. Network errors
100
191
  * 4. Locked errors (423)
192
+ *
193
+ * Note: prefer {@link transientFactory} when you want server-hint honoring.
101
194
  */
102
195
  export const transientOptions = {
103
196
  while: isTransientError,
package/lib/retry.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distilled.cloud/core",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/alchemy-run/distilled",
@@ -43,6 +43,11 @@
43
43
  "bun": "./src/retry.ts",
44
44
  "default": "./lib/retry.js"
45
45
  },
46
+ "./retry-after": {
47
+ "types": "./lib/retry-after.d.ts",
48
+ "bun": "./src/retry-after.ts",
49
+ "default": "./lib/retry-after.js"
50
+ },
46
51
  "./sensitive": {
47
52
  "types": "./lib/sensitive.d.ts",
48
53
  "bun": "./src/sensitive.ts",