@37signals/basecamp 0.11.0 → 0.12.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/dist/client.d.ts +14 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +200 -162
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/generated/metadata.d.ts.map +1 -1
- package/dist/generated/metadata.js +172 -1
- package/dist/generated/metadata.js.map +1 -1
- package/dist/generated/metadata.ts +172 -1
- package/dist/generated/openapi-stripped.json +2074 -134
- package/dist/generated/path-mapping.d.ts.map +1 -1
- package/dist/generated/path-mapping.js +13 -0
- package/dist/generated/path-mapping.js.map +1 -1
- package/dist/generated/path-mapping.ts +13 -0
- package/dist/generated/schema.d.ts +1361 -25
- package/dist/generated/services/bookmarks.d.ts +76 -0
- package/dist/generated/services/bookmarks.d.ts.map +1 -0
- package/dist/generated/services/bookmarks.js +114 -0
- package/dist/generated/services/bookmarks.js.map +1 -0
- package/dist/generated/services/bookmarks.ts +158 -0
- package/dist/generated/services/calendars.d.ts +47 -0
- package/dist/generated/services/calendars.d.ts.map +1 -0
- package/dist/generated/services/calendars.js +73 -0
- package/dist/generated/services/calendars.js.map +1 -0
- package/dist/generated/services/calendars.ts +102 -0
- package/dist/generated/services/drafts.d.ts +38 -0
- package/dist/generated/services/drafts.d.ts.map +1 -0
- package/dist/generated/services/drafts.js +40 -0
- package/dist/generated/services/drafts.js.map +1 -0
- package/dist/generated/services/drafts.ts +67 -0
- package/dist/generated/services/everything.d.ts +69 -2
- package/dist/generated/services/everything.d.ts.map +1 -1
- package/dist/generated/services/everything.js +23 -13
- package/dist/generated/services/everything.js.map +1 -1
- package/dist/generated/services/everything.ts +86 -11
- package/dist/generated/services/index.d.ts +5 -1
- package/dist/generated/services/index.d.ts.map +1 -1
- package/dist/generated/services/index.js +5 -1
- package/dist/generated/services/index.js.map +1 -1
- package/dist/generated/services/index.ts +5 -1
- package/dist/generated/services/my-assignments.d.ts +52 -0
- package/dist/generated/services/my-assignments.d.ts.map +1 -1
- package/dist/generated/services/my-assignments.js +71 -0
- package/dist/generated/services/my-assignments.js.map +1 -1
- package/dist/generated/services/my-assignments.ts +104 -0
- package/dist/generated/services/my-notes.d.ts +45 -0
- package/dist/generated/services/my-notes.d.ts.map +1 -0
- package/dist/generated/services/my-notes.js +62 -0
- package/dist/generated/services/my-notes.js.map +1 -0
- package/dist/generated/services/my-notes.ts +92 -0
- package/dist/generated/services/todos.d.ts +33 -0
- package/dist/generated/services/todos.d.ts.map +1 -1
- package/dist/generated/services/todos.js +46 -0
- package/dist/generated/services/todos.js.map +1 -1
- package/dist/generated/services/todos.ts +71 -0
- package/dist/hooks/otel.d.ts +1 -1
- package/dist/hooks/otel.js +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth/discovery.d.ts.map +1 -1
- package/dist/oauth/discovery.js +1 -0
- package/dist/oauth/discovery.js.map +1 -1
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +1 -1
- package/package.json +1 -1
- package/src/generated/metadata.ts +172 -1
- package/src/generated/openapi-stripped.json +2074 -134
- package/src/generated/path-mapping.ts +13 -0
- package/src/generated/schema.d.ts +1361 -25
- package/src/generated/services/bookmarks.ts +158 -0
- package/src/generated/services/calendars.ts +102 -0
- package/src/generated/services/drafts.ts +67 -0
- package/src/generated/services/everything.ts +86 -11
- package/src/generated/services/index.ts +5 -1
- package/src/generated/services/my-assignments.ts +104 -0
- package/src/generated/services/my-notes.ts +92 -0
- package/src/generated/services/todos.ts +71 -0
package/dist/client.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ import { ClientApprovalsService } from "./generated/services/client-approvals.js
|
|
|
33
33
|
import { ClientCorrespondencesService } from "./generated/services/client-correspondences.js";
|
|
34
34
|
import { ClientRepliesService } from "./generated/services/client-replies.js";
|
|
35
35
|
import { WebhooksService } from "./generated/services/webhooks.js";
|
|
36
|
+
import { BookmarksService } from "./generated/services/bookmarks.js";
|
|
37
|
+
import { DraftsService } from "./generated/services/drafts.js";
|
|
38
|
+
import { CalendarsService } from "./generated/services/calendars.js";
|
|
39
|
+
import { MyNotesService } from "./generated/services/my-notes.js";
|
|
36
40
|
import { SubscriptionsService } from "./generated/services/subscriptions.js";
|
|
37
41
|
import { AttachmentsService } from "./generated/services/attachments.js";
|
|
38
42
|
import { VaultsService } from "./generated/services/vaults.js";
|
|
@@ -119,6 +123,14 @@ export interface BasecampClient extends RawClient {
|
|
|
119
123
|
readonly clientReplies: ClientRepliesService;
|
|
120
124
|
/** Webhooks service - create, update, delete webhooks */
|
|
121
125
|
readonly webhooks: WebhooksService;
|
|
126
|
+
/** Bookmarks service - the current user's personal bookmarks */
|
|
127
|
+
readonly bookmarks: BookmarksService;
|
|
128
|
+
/** Drafts service - the current user's unpublished drafts */
|
|
129
|
+
readonly drafts: DraftsService;
|
|
130
|
+
/** Calendars service - per-account calendars (show + update) */
|
|
131
|
+
readonly calendars: CalendarsService;
|
|
132
|
+
/** MyNotes service - the current user's scratchpad note */
|
|
133
|
+
readonly myNotes: MyNotesService;
|
|
122
134
|
/** Subscriptions service - manage notification subscriptions */
|
|
123
135
|
readonly subscriptions: SubscriptionsService;
|
|
124
136
|
/** Attachments service - upload files for embedding in rich text */
|
|
@@ -200,8 +212,8 @@ export interface BasecampClientOptions {
|
|
|
200
212
|
/** Maximum pages to follow during auto-pagination (defaults to 10,000) */
|
|
201
213
|
maxPages?: number;
|
|
202
214
|
}
|
|
203
|
-
export declare const VERSION = "0.
|
|
204
|
-
export declare const API_VERSION = "2026-07-
|
|
215
|
+
export declare const VERSION = "0.12.0";
|
|
216
|
+
export declare const API_VERSION = "2026-07-31";
|
|
205
217
|
/**
|
|
206
218
|
* Creates a type-safe Basecamp API client with built-in middleware for:
|
|
207
219
|
* - Authentication (Bearer token)
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,YAAiC,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -38,6 +38,10 @@ import { ClientApprovalsService } from "./generated/services/client-approvals.js
|
|
|
38
38
|
import { ClientCorrespondencesService } from "./generated/services/client-correspondences.js";
|
|
39
39
|
import { ClientRepliesService } from "./generated/services/client-replies.js";
|
|
40
40
|
import { WebhooksService } from "./generated/services/webhooks.js";
|
|
41
|
+
import { BookmarksService } from "./generated/services/bookmarks.js";
|
|
42
|
+
import { DraftsService } from "./generated/services/drafts.js";
|
|
43
|
+
import { CalendarsService } from "./generated/services/calendars.js";
|
|
44
|
+
import { MyNotesService } from "./generated/services/my-notes.js";
|
|
41
45
|
import { SubscriptionsService } from "./generated/services/subscriptions.js";
|
|
42
46
|
import { AttachmentsService } from "./generated/services/attachments.js";
|
|
43
47
|
import { VaultsService } from "./generated/services/vaults.js";
|
|
@@ -72,8 +76,8 @@ import { AuthorizationService } from "./services/authorization.js";
|
|
|
72
76
|
* a TimeoutOverflowWarning rather than honored.
|
|
73
77
|
*/
|
|
74
78
|
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
75
|
-
export const VERSION = "0.
|
|
76
|
-
export const API_VERSION = "2026-07-
|
|
79
|
+
export const VERSION = "0.12.0";
|
|
80
|
+
export const API_VERSION = "2026-07-31";
|
|
77
81
|
const DEFAULT_USER_AGENT = `basecamp-sdk-ts/${VERSION} (api:${API_VERSION})`;
|
|
78
82
|
/**
|
|
79
83
|
* Creates a type-safe Basecamp API client with built-in middleware for:
|
|
@@ -130,28 +134,31 @@ export function createBasecampClient(options) {
|
|
|
130
134
|
throw new BasecampError("usage", `Invalid base URL: ${baseUrl}`);
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
|
-
|
|
134
|
-
//
|
|
137
|
+
// One lifecycle, shared between the retrying fetch and the lifecycle
|
|
138
|
+
// middleware: the fetch owns every attempt's start and each abandoned
|
|
139
|
+
// attempt's end; the middleware finalizes the terminal outcome.
|
|
140
|
+
const lifecycle = new RequestLifecycle(hooks);
|
|
141
|
+
// The retry loop lives BENEATH the middleware chain, as the client's custom
|
|
142
|
+
// fetch: openapi-fetch calls it exactly once per logical request, after every
|
|
143
|
+
// onRequest middleware and before every onError/onResponse. Installed
|
|
144
|
+
// unconditionally — with retry disabled it degenerates to a single attempt —
|
|
145
|
+
// so hook emission is single-sourced regardless of config.
|
|
146
|
+
const client = createClient({
|
|
147
|
+
baseUrl,
|
|
148
|
+
fetch: createRetryingFetch(lifecycle, authStrategy, enableRetry),
|
|
149
|
+
});
|
|
150
|
+
// Apply middleware in order: auth, lifecycle, cache.
|
|
135
151
|
// onRequest runs in this order; onResponse and onError run in reverse, so the
|
|
136
|
-
//
|
|
152
|
+
// lifecycle middleware finalizes the terminal attempt only after the cache
|
|
153
|
+
// middleware has had its chance to transform the response.
|
|
137
154
|
client.use(createAuthMiddleware(authStrategy, userAgent, requestTimeoutMs, baseUrl));
|
|
138
|
-
// One lifecycle, shared with the retry middleware: the retry loop lives inside a
|
|
139
|
-
// single middleware callback, so per-attempt hooks have to be emitted from there
|
|
140
|
-
// rather than by a middleware that is only visited once per request.
|
|
141
|
-
const lifecycle = new RequestLifecycle(hooks);
|
|
142
155
|
// Registered even when no hooks are configured. It emits nothing in that case
|
|
143
|
-
// (every call is optional-chained), but it
|
|
144
|
-
//
|
|
145
|
-
// listening, so skipping this would strand one map entry per retried request.
|
|
156
|
+
// (every call is optional-chained), but registering it unconditionally keeps
|
|
157
|
+
// the finalize path identical whether or not anyone is listening.
|
|
146
158
|
client.use(createLifecycleMiddleware(lifecycle));
|
|
147
159
|
if (enableCache) {
|
|
148
160
|
client.use(createCacheMiddleware());
|
|
149
161
|
}
|
|
150
|
-
// Registered last so that on the reverse-order response pass it runs first,
|
|
151
|
-
// before the cache and lifecycle middleware see the response.
|
|
152
|
-
if (enableRetry) {
|
|
153
|
-
client.use(createRetryMiddleware(lifecycle, authStrategy));
|
|
154
|
-
}
|
|
155
162
|
// Create enhanced client with additional properties
|
|
156
163
|
const enhancedClient = client;
|
|
157
164
|
Object.defineProperty(enhancedClient, "raw", {
|
|
@@ -237,6 +244,10 @@ export function createBasecampClient(options) {
|
|
|
237
244
|
defineService("clientCorrespondences", () => new ClientCorrespondencesService(client, hooks, fetchPage, maxPages));
|
|
238
245
|
defineService("clientReplies", () => new ClientRepliesService(client, hooks, fetchPage, maxPages));
|
|
239
246
|
defineService("webhooks", () => new WebhooksService(client, hooks, fetchPage, maxPages));
|
|
247
|
+
defineService("bookmarks", () => new BookmarksService(client, hooks, fetchPage, maxPages));
|
|
248
|
+
defineService("drafts", () => new DraftsService(client, hooks, fetchPage, maxPages));
|
|
249
|
+
defineService("calendars", () => new CalendarsService(client, hooks, fetchPage, maxPages));
|
|
250
|
+
defineService("myNotes", () => new MyNotesService(client, hooks, fetchPage, maxPages));
|
|
240
251
|
defineService("subscriptions", () => new SubscriptionsService(client, hooks, fetchPage, maxPages));
|
|
241
252
|
defineService("attachments", () => new AttachmentsService(client, hooks, fetchPage, maxPages));
|
|
242
253
|
defineService("vaults", () => new VaultsService(client, hooks, fetchPage, maxPages));
|
|
@@ -301,45 +312,41 @@ function createAuthMiddleware(authStrategy, userAgent, requestTimeoutMs, baseUrl
|
|
|
301
312
|
};
|
|
302
313
|
}
|
|
303
314
|
/**
|
|
304
|
-
* Owns the onRequestStart / onRequestEnd / onRetry lifecycle for every attempt
|
|
305
|
-
* every in-flight request, keyed by openapi-fetch
|
|
315
|
+
* Owns the onRequestStart / onRequestEnd / onRetry lifecycle for every attempt
|
|
316
|
+
* of every in-flight request, keyed by the final Request object openapi-fetch
|
|
317
|
+
* hands to its custom fetch.
|
|
306
318
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* middleware
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* name was a collision.)
|
|
319
|
+
* openapi-fetch passes that same object to onResponse and onError, so identity
|
|
320
|
+
* holds from the retrying fetch (which begins every attempt) through to the
|
|
321
|
+
* lifecycle middleware (which finalizes the terminal one). Nothing ever goes on
|
|
322
|
+
* the wire — this keying replaced the old `X-SDK-Request-Id` and `X-Request-Id`
|
|
323
|
+
* correlation headers. (`X-Request-Id` in particular is a spec-reserved BC3
|
|
324
|
+
* *response* header; sending an SDK-internal value under that name was a
|
|
325
|
+
* collision.)
|
|
315
326
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
327
|
+
* A WeakMap makes the release bookkeeping structural: once a request settles
|
|
328
|
+
* and its Request is unreachable, the state is collectable — there is no
|
|
329
|
+
* release() to forget.
|
|
319
330
|
*/
|
|
320
331
|
class RequestLifecycle {
|
|
321
332
|
hooks;
|
|
322
|
-
states = new
|
|
333
|
+
states = new WeakMap();
|
|
323
334
|
constructor(hooks) {
|
|
324
335
|
this.hooks = hooks;
|
|
325
336
|
}
|
|
326
337
|
/** Begin an attempt and fire onRequestStart for it. */
|
|
327
|
-
begin(
|
|
328
|
-
this.states.set(
|
|
338
|
+
begin(request, method, url, attempt) {
|
|
339
|
+
this.states.set(request, { startTime: performance.now(), attempt, finalized: false });
|
|
329
340
|
this.emit(() => this.hooks?.onRequestStart?.({ method, url, attempt }));
|
|
330
341
|
}
|
|
331
|
-
/** The attempt currently in flight, or 1 if we have no record. */
|
|
332
|
-
attemptOf(id) {
|
|
333
|
-
return this.states.get(id)?.attempt ?? 1;
|
|
334
|
-
}
|
|
335
342
|
/**
|
|
336
343
|
* Fire onRequestEnd for the in-flight attempt, exactly once.
|
|
337
344
|
*
|
|
338
345
|
* A network failure or timeout carries statusCode 0 and the originating error,
|
|
339
346
|
* matching the multipart transport in services/base.ts and SPEC section 7.
|
|
340
347
|
*/
|
|
341
|
-
finalize(
|
|
342
|
-
const state = this.states.get(
|
|
348
|
+
finalize(request, method, url, outcome) {
|
|
349
|
+
const state = this.states.get(request);
|
|
343
350
|
if (!state || state.finalized)
|
|
344
351
|
return;
|
|
345
352
|
state.finalized = true;
|
|
@@ -359,13 +366,9 @@ class RequestLifecycle {
|
|
|
359
366
|
* just FAILED, while the standalone argument is the UPCOMING attempt. Go,
|
|
360
367
|
* Python, Ruby and Kotlin all pass (failed, failed + 1).
|
|
361
368
|
*/
|
|
362
|
-
retrying(
|
|
369
|
+
retrying(method, url, failedAttempt, error, delayMs) {
|
|
363
370
|
this.emit(() => this.hooks?.onRetry?.({ method, url, attempt: failedAttempt }, failedAttempt + 1, error, delayMs));
|
|
364
371
|
}
|
|
365
|
-
/** Drop all state for a logical request. Safe to call more than once. */
|
|
366
|
-
release(id) {
|
|
367
|
-
this.states.delete(id);
|
|
368
|
-
}
|
|
369
372
|
emit(fn) {
|
|
370
373
|
try {
|
|
371
374
|
fn();
|
|
@@ -377,37 +380,32 @@ class RequestLifecycle {
|
|
|
377
380
|
}
|
|
378
381
|
function createLifecycleMiddleware(lifecycle) {
|
|
379
382
|
return {
|
|
380
|
-
async
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
386
|
-
// single-attempt request, or attempt 2 after a retry — the retry middleware
|
|
387
|
-
// deliberately does not finalize a successful attempt, so that the cache
|
|
388
|
-
// middleware (which runs between the two) can transform the response first.
|
|
389
|
-
// finalize stays idempotent as a backstop.
|
|
383
|
+
async onResponse({ request, response }) {
|
|
384
|
+
// The authoritative end for the terminal attempt: the retrying fetch
|
|
385
|
+
// begins every attempt but deliberately does not finalize the response it
|
|
386
|
+
// returns, so that this pass can record the outcome the cache middleware
|
|
387
|
+
// has since transformed (a 304 rewritten into a cached 200). finalize
|
|
388
|
+
// stays idempotent as a backstop.
|
|
390
389
|
//
|
|
391
390
|
// fromCache means "served out of the ETag cache", and only the header the
|
|
392
391
|
// cache middleware sets proves that. A bare 304 does NOT: it reaches here
|
|
393
392
|
// when the cache is disabled, or is enabled but holds no entry for this key,
|
|
394
393
|
// and in both cases the caller's own conditional request went to the server.
|
|
395
394
|
const fromCache = response.headers.get("X-From-Cache") === "1";
|
|
396
|
-
lifecycle.finalize(
|
|
395
|
+
lifecycle.finalize(request, request.method, request.url, {
|
|
397
396
|
statusCode: response.status,
|
|
398
397
|
fromCache,
|
|
399
398
|
});
|
|
400
|
-
lifecycle.release(id);
|
|
401
399
|
return response;
|
|
402
400
|
},
|
|
403
|
-
async onError({ request, error
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
401
|
+
async onError({ request, error }) {
|
|
402
|
+
// A terminal throw from the retrying fetch — a network error, a timeout,
|
|
403
|
+
// or a retry's auth refresh — skips onResponse entirely, so this is the
|
|
404
|
+
// only place it can be observed.
|
|
405
|
+
lifecycle.finalize(request, request.method, request.url, {
|
|
407
406
|
statusCode: 0,
|
|
408
407
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
409
408
|
});
|
|
410
|
-
lifecycle.release(id);
|
|
411
409
|
// Returning nothing preserves the original error's identity and rethrows it.
|
|
412
410
|
return undefined;
|
|
413
411
|
},
|
|
@@ -679,44 +677,105 @@ function getRetryConfigForRequest(method, url) {
|
|
|
679
677
|
}
|
|
680
678
|
return DEFAULT_RETRY_CONFIG;
|
|
681
679
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
680
|
+
/**
|
|
681
|
+
* The retry loop, installed beneath the middleware chain as the client's custom
|
|
682
|
+
* fetch (createClient's `fetch` option). openapi-fetch calls it exactly once
|
|
683
|
+
* per logical request, after every onRequest middleware and before every
|
|
684
|
+
* onError/onResponse — so the loop runs to the operation's full declared
|
|
685
|
+
* maxAttempts, and no attempt ever leaves the chain, because the chain sits
|
|
686
|
+
* above the loop.
|
|
687
|
+
*
|
|
688
|
+
* Division of labor with the lifecycle middleware: the loop begins EVERY
|
|
689
|
+
* attempt and finalizes the attempts it abandons (before the backoff sleep);
|
|
690
|
+
* the terminal outcome — the response it returns, or the error it throws — is
|
|
691
|
+
* deliberately NOT finalized here. It flows up through the cache middleware,
|
|
692
|
+
* which may rewrite a 304 into a cached 200 + X-From-Cache, to the lifecycle
|
|
693
|
+
* middleware, which records the post-transform result. Finalizing in the loop
|
|
694
|
+
* would freeze the pre-transformation status, and the idempotent finalize
|
|
695
|
+
* would keep the hooks pass from correcting it.
|
|
696
|
+
*
|
|
697
|
+
* With retry disabled the loop degenerates to a single attempt but still owns
|
|
698
|
+
* hook emission, so start/end events are single-sourced regardless of config.
|
|
699
|
+
*/
|
|
700
|
+
function createRetryingFetch(lifecycle, authStrategy, enableRetry) {
|
|
701
|
+
return async (request) => {
|
|
702
|
+
const { method, url } = request;
|
|
703
|
+
const retryConfig = getRetryConfigForRequest(method, url);
|
|
704
|
+
const maxAttempts = enableRetry ? retryConfig.maxAttempts : 1;
|
|
705
|
+
// Serialize the body once, before the first send, because Request.body is
|
|
706
|
+
// a stream that can only be consumed once and a retry needs to replay it.
|
|
707
|
+
// clone() tees the stream, so attempt 1 sends the original untouched and
|
|
708
|
+
// replays read from this buffer. Requests that can never retry — including
|
|
709
|
+
// every non-idempotent POST, via NO_RETRY_CONFIG — skip the buffering.
|
|
710
|
+
const upperMethod = method.toUpperCase();
|
|
711
|
+
let bodyBuffer = null;
|
|
712
|
+
if (maxAttempts > 1 &&
|
|
713
|
+
(upperMethod === "POST" || upperMethod === "PUT" || upperMethod === "PATCH") &&
|
|
714
|
+
request.body) {
|
|
715
|
+
bodyBuffer = await request.clone().arrayBuffer();
|
|
716
|
+
}
|
|
717
|
+
let attempt = 1;
|
|
718
|
+
let attemptRequest = request;
|
|
719
|
+
lifecycle.begin(request, method, url, attempt);
|
|
720
|
+
for (;;) {
|
|
721
|
+
if (attempt > 1) {
|
|
722
|
+
// Rebuild from the original request: the headers carry everything the
|
|
723
|
+
// onRequest middleware attached (auth, User-Agent, If-None-Match), and
|
|
724
|
+
// the signal keeps the caller's cancellation and the per-request
|
|
725
|
+
// timeout — one budget shared by all attempts and backoffs.
|
|
726
|
+
attemptRequest = new Request(url, {
|
|
727
|
+
method,
|
|
728
|
+
headers: new Headers(request.headers),
|
|
729
|
+
body: bodyBuffer,
|
|
730
|
+
signal: request.signal,
|
|
731
|
+
});
|
|
732
|
+
// Refresh auth (the token may have rotated since the last attempt).
|
|
733
|
+
// The attempt is already begun, so a throwing refresh lands on a live
|
|
734
|
+
// attempt and propagates to the lifecycle middleware's onError.
|
|
735
|
+
await authStrategy.authenticate(attemptRequest.headers);
|
|
702
736
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const retryConfig = getRetryConfigForRequest(method, url);
|
|
709
|
-
// Not retrying: let the lifecycle middleware end the attempt, and drop the body.
|
|
710
|
-
if (!retryConfig.retryOn.includes(response.status)) {
|
|
711
|
-
bodyCache.delete(id);
|
|
712
|
-
return response;
|
|
737
|
+
let response;
|
|
738
|
+
try {
|
|
739
|
+
// globalThis.fetch resolved per attempt rather than captured at client
|
|
740
|
+
// creation, so test interceptors that patch it (MSW) are honored.
|
|
741
|
+
response = await globalThis.fetch(attemptRequest);
|
|
713
742
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
743
|
+
catch (error) {
|
|
744
|
+
// An abort is terminal no matter what the budget says: a caller
|
|
745
|
+
// cancellation must not re-send, and the per-request timeout is one
|
|
746
|
+
// budget shared by every attempt and backoff — once it fires, a retry
|
|
747
|
+
// would instantly re-reject. Terminal errors are rethrown as-is so
|
|
748
|
+
// their identity survives to the lifecycle middleware's onError (and
|
|
749
|
+
// to the caller).
|
|
750
|
+
//
|
|
751
|
+
// The signal is the authoritative abort test: a caller can abort with
|
|
752
|
+
// a CUSTOM reason — AbortController.abort(reason) — and fetch then
|
|
753
|
+
// rejects with that reason, not a DOMException named AbortError. The
|
|
754
|
+
// DOMException check remains for abort-shaped rejections that arrive
|
|
755
|
+
// without an aborted request signal.
|
|
756
|
+
const isAbort = request.signal?.aborted === true ||
|
|
757
|
+
(error instanceof DOMException &&
|
|
758
|
+
(error.name === "AbortError" || error.name === "TimeoutError"));
|
|
759
|
+
if (isAbort || attempt >= maxAttempts) {
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
// Network-error retry rides the same per-operation gate as status
|
|
763
|
+
// retry: a non-idempotent POST resolves NO_RETRY_CONFIG (maxAttempts
|
|
764
|
+
// 1), so it is rethrown above after its single attempt.
|
|
765
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
766
|
+
const delay = calculateBackoffDelay(retryConfig, attempt - 1);
|
|
767
|
+
lifecycle.finalize(request, method, url, { statusCode: 0, error: cause });
|
|
768
|
+
lifecycle.retrying(method, url, attempt, cause, delay);
|
|
769
|
+
await sleep(delay, request.signal);
|
|
770
|
+
// Same placement rationale as the status path below.
|
|
771
|
+
attempt += 1;
|
|
772
|
+
lifecycle.begin(request, method, url, attempt);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
// Terminal: a status outside the operation's declared retryOn set, or a
|
|
776
|
+
// spent budget — maxAttempts is a total attempt count, so attempt N is
|
|
777
|
+
// terminal when it equals the cap.
|
|
778
|
+
if (!retryConfig.retryOn.includes(response.status) || attempt >= maxAttempts) {
|
|
720
779
|
return response;
|
|
721
780
|
}
|
|
722
781
|
// For 429, respect Retry-After; otherwise back off.
|
|
@@ -727,71 +786,29 @@ function createRetryMiddleware(lifecycle, authStrategy) {
|
|
|
727
786
|
delay = retryAfterSeconds * 1000;
|
|
728
787
|
}
|
|
729
788
|
else {
|
|
730
|
-
delay = calculateBackoffDelay(retryConfig,
|
|
789
|
+
delay = calculateBackoffDelay(retryConfig, attempt - 1);
|
|
731
790
|
}
|
|
732
791
|
const statusError = new Error(`HTTP ${response.status}: ${response.statusText || "Request failed"}`);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
lifecycle.begin(id, method, url, failedAttempt + 1);
|
|
754
|
-
const body = bodyCache.get(id) ?? null;
|
|
755
|
-
const retryRequest = new Request(url, {
|
|
756
|
-
method,
|
|
757
|
-
headers: new Headers(request.headers),
|
|
758
|
-
body,
|
|
759
|
-
signal: request.signal,
|
|
760
|
-
});
|
|
761
|
-
// Refresh auth header for retry (token may have been refreshed since initial request)
|
|
762
|
-
if (authStrategy) {
|
|
763
|
-
await authStrategy.authenticate(retryRequest.headers);
|
|
764
|
-
}
|
|
765
|
-
// Deliberately NOT finalized here. The returned response still flows
|
|
766
|
-
// through the cache middleware, which may rewrite a 304 into a cached 200;
|
|
767
|
-
// finalizing now would freeze the pre-transformation status and
|
|
768
|
-
// fromCache: false, and an idempotent finalize means the hooks pass could
|
|
769
|
-
// not correct it. The lifecycle middleware ends this attempt instead, reading
|
|
770
|
-
// attempt 2 from the state begun above.
|
|
771
|
-
return await fetch(retryRequest);
|
|
772
|
-
}
|
|
773
|
-
catch (error) {
|
|
774
|
-
// openapi-fetch routes only the INITIAL fetch through onError, so anything
|
|
775
|
-
// thrown in here — the retry fetch, the auth refresh, the sleep — would
|
|
776
|
-
// otherwise escape with the attempt still open and its state stranded.
|
|
777
|
-
lifecycle.finalize(id, method, url, {
|
|
778
|
-
statusCode: 0,
|
|
779
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
780
|
-
});
|
|
781
|
-
lifecycle.release(id);
|
|
782
|
-
// Rethrow the original value so its identity survives.
|
|
783
|
-
throw error;
|
|
784
|
-
}
|
|
785
|
-
finally {
|
|
786
|
-
bodyCache.delete(id);
|
|
787
|
-
}
|
|
788
|
-
},
|
|
789
|
-
async onError({ id }) {
|
|
790
|
-
// The initial fetch rejected, so onResponse never ran and the cached body
|
|
791
|
-
// would leak. The lifecycle middleware owns ending the attempt.
|
|
792
|
-
bodyCache.delete(id);
|
|
793
|
-
return undefined;
|
|
794
|
-
},
|
|
792
|
+
// End the failed attempt before sleeping, so a slow backoff cannot leave
|
|
793
|
+
// an attempt open, then announce the upcoming one.
|
|
794
|
+
lifecycle.finalize(request, method, url, { statusCode: response.status });
|
|
795
|
+
lifecycle.retrying(method, url, attempt, statusError, delay);
|
|
796
|
+
// This response is being discarded, so release its stream before we sleep
|
|
797
|
+
// rather than leaving it open across the backoff — otherwise a throttled
|
|
798
|
+
// client holds a connection per in-flight retry and cannot reuse any of
|
|
799
|
+
// them. The multipart transport in services/base.ts already does this.
|
|
800
|
+
// Errors are ignored: the body may already be consumed or closed.
|
|
801
|
+
void response.body?.cancel().catch(() => { });
|
|
802
|
+
await sleep(delay, request.signal);
|
|
803
|
+
// Begun after the backoff but before any work that can throw. After, so
|
|
804
|
+
// the attempt's duration measures the request rather than the sleep;
|
|
805
|
+
// before, so that if the auth refresh or the fetch throws, the lifecycle
|
|
806
|
+
// middleware's onError still finds a live attempt to finalize. Starting
|
|
807
|
+
// it later would let onRetry announce an attempt and then never account
|
|
808
|
+
// for it.
|
|
809
|
+
attempt += 1;
|
|
810
|
+
lifecycle.begin(request, method, url, attempt);
|
|
811
|
+
}
|
|
795
812
|
};
|
|
796
813
|
}
|
|
797
814
|
function calculateBackoffDelay(config, attempt) {
|
|
@@ -812,8 +829,29 @@ function calculateBackoffDelay(config, attempt) {
|
|
|
812
829
|
const jitter = Math.random() * MAX_JITTER_MS;
|
|
813
830
|
return delay + jitter;
|
|
814
831
|
}
|
|
815
|
-
|
|
816
|
-
|
|
832
|
+
/**
|
|
833
|
+
* Signal-aware sleep for backoff waits: resolves after `ms`, or rejects with
|
|
834
|
+
* the signal's abort reason the moment it fires. Without this, a caller abort
|
|
835
|
+
* or the request-timeout budget expiring during a backoff would leave the
|
|
836
|
+
* request pending for the full delay and then start another attempt — begin,
|
|
837
|
+
* auth refresh, fetch — against an already-aborted signal.
|
|
838
|
+
*/
|
|
839
|
+
function sleep(ms, signal) {
|
|
840
|
+
return new Promise((resolve, reject) => {
|
|
841
|
+
if (signal?.aborted) {
|
|
842
|
+
reject(signal.reason);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const onAbort = () => {
|
|
846
|
+
clearTimeout(timer);
|
|
847
|
+
reject(signal.reason);
|
|
848
|
+
};
|
|
849
|
+
const timer = setTimeout(() => {
|
|
850
|
+
signal?.removeEventListener("abort", onAbort);
|
|
851
|
+
resolve();
|
|
852
|
+
}, ms);
|
|
853
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
854
|
+
});
|
|
817
855
|
}
|
|
818
856
|
// =============================================================================
|
|
819
857
|
// Pagination Helper
|