@littlebigbrain/client 0.5.1 → 0.6.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/README.md +10 -0
- package/dist/client.d.ts +35 -7
- package/dist/client.js +109 -10
- package/dist/namespaces.d.ts +21 -1
- package/dist/namespaces.js +55 -0
- package/dist/schema.d.ts +1419 -320
- package/dist/transport.d.ts +32 -2
- package/dist/transport.js +67 -3
- package/dist/types.d.ts +26 -1
- package/package.json +1 -1
package/dist/transport.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export interface CallOptions {
|
|
|
3
3
|
idempotencyKey?: string;
|
|
4
4
|
/** Override the client's per-attempt timeout. Set 0 to disable it. */
|
|
5
5
|
timeoutMs?: number;
|
|
6
|
-
/** Override the client's retry count for this request. */
|
|
6
|
+
/** Override the client's retry count (secondary cap) for this request. */
|
|
7
7
|
maxRetries?: number;
|
|
8
|
+
/** Override the client's deadline-based retry budget (ms) for this request. */
|
|
9
|
+
retryBudgetMs?: number;
|
|
8
10
|
/** Override retry safety classification. Read-only POST namespaces set this automatically. */
|
|
9
11
|
retry?: boolean;
|
|
10
12
|
/** Abort the request and suppress any further retries. */
|
|
@@ -41,6 +43,34 @@ export declare function retryableStatus(status: number): boolean;
|
|
|
41
43
|
export declare function retryAllowed(method: string, idempotencyKey?: string): boolean;
|
|
42
44
|
/** Parse a Retry-After delta-seconds or HTTP-date value, capped at one minute. */
|
|
43
45
|
export declare function parseRetryAfterMs(value: string | null | undefined, nowMs?: number): number | undefined;
|
|
44
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Full-jitter exponential backoff: `uniform(0, base * 2**attempt)`, capped at
|
|
48
|
+
* one minute. Replaces linear backoff so many clients recovering from one
|
|
49
|
+
* outage do not retry in lockstep (a thundering herd that re-triggers it).
|
|
50
|
+
*/
|
|
51
|
+
export declare function fullJitterBackoffMs(baseDelayMs: number, attempt: number, rng?: () => number): number;
|
|
52
|
+
/**
|
|
53
|
+
* The server's own body hint `error.retry_after_seconds` in ms (capped), or
|
|
54
|
+
* `undefined` when the body is naked (a bare LB 5xx) or carries no hint. Used
|
|
55
|
+
* as the backoff when the `Retry-After` *header* is absent.
|
|
56
|
+
*/
|
|
57
|
+
export declare function retryAfterFromBodyMs(body: string): number | undefined;
|
|
58
|
+
/** The parsed `error.code` from an error body, or `undefined` when absent/naked. */
|
|
59
|
+
export declare function errorCodeFromBody(body: string): string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* True iff the server explicitly marked this error non-retryable in the body
|
|
62
|
+
* (`error.retryable === false`) — a durable rejection (e.g. an exhausted quota)
|
|
63
|
+
* the client must surface immediately instead of spending its retry budget.
|
|
64
|
+
*/
|
|
65
|
+
export declare function bodyMarksTerminal(body: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The backoff (ms) before the next attempt: the `Retry-After` header, else the
|
|
68
|
+
* server's body `retry_after_seconds` hint, else full-jitter exponential
|
|
69
|
+
* backoff.
|
|
70
|
+
*/
|
|
71
|
+
export declare function retryDelayMs(baseDelayMs: number, attempt: number, opts?: {
|
|
72
|
+
retryAfterHeader?: string | null;
|
|
73
|
+
body?: string;
|
|
74
|
+
}, rng?: () => number): number;
|
|
45
75
|
export declare function parseResponseJson<T>(text: string, status: number, requestId?: string): T;
|
|
46
76
|
export declare function parseLbbError(status: number, body: string, fallbackRequestId?: string): LbbError;
|
package/dist/transport.js
CHANGED
|
@@ -62,9 +62,73 @@ export function parseRetryAfterMs(value, nowMs = Date.now()) {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
return Math.min(Math.max(0, dateMs - nowMs), MAX_RETRY_AFTER_MS);
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Full-jitter exponential backoff: `uniform(0, base * 2**attempt)`, capped at
|
|
67
|
+
* one minute. Replaces linear backoff so many clients recovering from one
|
|
68
|
+
* outage do not retry in lockstep (a thundering herd that re-triggers it).
|
|
69
|
+
*/
|
|
70
|
+
export function fullJitterBackoffMs(baseDelayMs, attempt, rng = Math.random) {
|
|
71
|
+
const ceiling = Math.min(Math.max(0, baseDelayMs) * 2 ** attempt, MAX_RETRY_AFTER_MS);
|
|
72
|
+
return rng() * ceiling;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The server's own body hint `error.retry_after_seconds` in ms (capped), or
|
|
76
|
+
* `undefined` when the body is naked (a bare LB 5xx) or carries no hint. Used
|
|
77
|
+
* as the backoff when the `Retry-After` *header* is absent.
|
|
78
|
+
*/
|
|
79
|
+
export function retryAfterFromBodyMs(body) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(body);
|
|
82
|
+
const seconds = parsed.error?.retry_after_seconds;
|
|
83
|
+
if (typeof seconds === "number" &&
|
|
84
|
+
Number.isFinite(seconds) &&
|
|
85
|
+
seconds >= 0) {
|
|
86
|
+
return Math.min(seconds * 1_000, MAX_RETRY_AFTER_MS);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Naked LB body (no error envelope) — no hint.
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
/** The parsed `error.code` from an error body, or `undefined` when absent/naked. */
|
|
95
|
+
export function errorCodeFromBody(body) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(body);
|
|
98
|
+
const code = parsed.error?.code;
|
|
99
|
+
return typeof code === "string" ? code : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* True iff the server explicitly marked this error non-retryable in the body
|
|
107
|
+
* (`error.retryable === false`) — a durable rejection (e.g. an exhausted quota)
|
|
108
|
+
* the client must surface immediately instead of spending its retry budget.
|
|
109
|
+
*/
|
|
110
|
+
export function bodyMarksTerminal(body) {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(body);
|
|
113
|
+
return parsed.error?.retryable === false;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The backoff (ms) before the next attempt: the `Retry-After` header, else the
|
|
121
|
+
* server's body `retry_after_seconds` hint, else full-jitter exponential
|
|
122
|
+
* backoff.
|
|
123
|
+
*/
|
|
124
|
+
export function retryDelayMs(baseDelayMs, attempt, opts = {}, rng = Math.random) {
|
|
125
|
+
const header = parseRetryAfterMs(opts.retryAfterHeader);
|
|
126
|
+
if (header !== undefined)
|
|
127
|
+
return header;
|
|
128
|
+
const bodyHint = opts.body !== undefined ? retryAfterFromBodyMs(opts.body) : undefined;
|
|
129
|
+
if (bodyHint !== undefined)
|
|
130
|
+
return bodyHint;
|
|
131
|
+
return fullJitterBackoffMs(baseDelayMs, attempt, rng);
|
|
68
132
|
}
|
|
69
133
|
export function parseResponseJson(text, status, requestId) {
|
|
70
134
|
try {
|
package/dist/types.d.ts
CHANGED
|
@@ -171,16 +171,27 @@ export interface LbbClientOptions {
|
|
|
171
171
|
fetch?: FetchLike;
|
|
172
172
|
/** API version header sent on every request. Defaults to the beta reset contract. */
|
|
173
173
|
apiVersion?: string;
|
|
174
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Secondary safety cap on retries for 429/5xx responses and network failures.
|
|
176
|
+
* The binding limit is `retryBudgetMs`. Defaults to 6.
|
|
177
|
+
*/
|
|
175
178
|
maxRetries?: number;
|
|
176
179
|
/** Base delay between retries. Defaults to 100ms. Tests can set 0. */
|
|
177
180
|
retryDelayMs?: number;
|
|
181
|
+
/**
|
|
182
|
+
* Deadline-based retry budget (ms): keep retrying a retryable request until
|
|
183
|
+
* this much wall-clock has elapsed, so a server's advertised `Retry-After`
|
|
184
|
+
* window is honored rather than truncated by `maxRetries`. Defaults to 60000.
|
|
185
|
+
*/
|
|
186
|
+
retryBudgetMs?: number;
|
|
178
187
|
/** Per-attempt timeout, including response-body reads. Defaults to 120 seconds; 0 disables it. */
|
|
179
188
|
timeoutMs?: number;
|
|
180
189
|
/** Called immediately before each network attempt. Bodies and credentials are never included. */
|
|
181
190
|
onRequest?: (event: LbbRequestEvent) => void;
|
|
182
191
|
/** Called once after the final HTTP response. Bodies and credentials are never included. */
|
|
183
192
|
onResponse?: (event: LbbResponseEvent) => void;
|
|
193
|
+
/** Called before each backoff sleep, so absorbed retries are observable. Bodies and credentials are never included. */
|
|
194
|
+
onRetry?: (event: LbbRetryEvent) => void;
|
|
184
195
|
}
|
|
185
196
|
export interface LbbRequestEvent {
|
|
186
197
|
method: string;
|
|
@@ -198,6 +209,20 @@ export interface LbbResponseEvent {
|
|
|
198
209
|
retryCount: number;
|
|
199
210
|
elapsedMs: number;
|
|
200
211
|
}
|
|
212
|
+
export interface LbbRetryEvent {
|
|
213
|
+
method: string;
|
|
214
|
+
url: string;
|
|
215
|
+
/** 1-based number of the attempt that just failed and triggered this retry. */
|
|
216
|
+
attempt: number;
|
|
217
|
+
/** HTTP status of the failed attempt, or `undefined` for a network error. */
|
|
218
|
+
status?: number;
|
|
219
|
+
/** Parsed `error.code` of the failed attempt, when the body carried one. */
|
|
220
|
+
errorCode?: string;
|
|
221
|
+
/** The backoff (ms) about to be slept — header, body hint, or jittered backoff. */
|
|
222
|
+
delayMs: number;
|
|
223
|
+
/** Inclusive wall-clock elapsed (ms) across attempts and waits so far. */
|
|
224
|
+
elapsedMs: number;
|
|
225
|
+
}
|
|
201
226
|
export type LbbStackActivityWindow = "1h" | "4h" | "12h" | "24h";
|
|
202
227
|
export interface LbbStackActivityResponse {
|
|
203
228
|
ok: true;
|