@tidal-music/api 0.28.0 → 0.29.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/index.js +125 -5
- package/dist/src/api.d.ts +4 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/retry.d.ts +37 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -333,16 +333,136 @@ function f(e) {
|
|
|
333
333
|
return e.endsWith("/") ? e.substring(0, e.length - 1) : e;
|
|
334
334
|
}
|
|
335
335
|
//#endregion
|
|
336
|
+
//#region src/retry.ts
|
|
337
|
+
var p = {
|
|
338
|
+
network: {
|
|
339
|
+
baseDelayMs: 1e3,
|
|
340
|
+
maxDelayMs: 16e3,
|
|
341
|
+
maxRetries: 10
|
|
342
|
+
},
|
|
343
|
+
status: {
|
|
344
|
+
baseDelayMs: 500,
|
|
345
|
+
maxDelayMs: 16e3,
|
|
346
|
+
maxRetries: 3
|
|
347
|
+
},
|
|
348
|
+
timeout: {
|
|
349
|
+
baseDelayMs: 8e3,
|
|
350
|
+
maxDelayMs: 32e3,
|
|
351
|
+
maxRetries: 3
|
|
352
|
+
}
|
|
353
|
+
}, m = 1e4, h = new Set([
|
|
354
|
+
"GET",
|
|
355
|
+
"HEAD",
|
|
356
|
+
"OPTIONS"
|
|
357
|
+
]), g = (e) => globalThis.fetch(e);
|
|
358
|
+
function _(e = {}, t = g) {
|
|
359
|
+
if (e.enabled === !1) return t;
|
|
360
|
+
let n = y(e);
|
|
361
|
+
return async (e) => {
|
|
362
|
+
if (!h.has(e.method.toUpperCase())) return t(e);
|
|
363
|
+
let r = {
|
|
364
|
+
network: 0,
|
|
365
|
+
status: 0,
|
|
366
|
+
timeout: 0
|
|
367
|
+
};
|
|
368
|
+
for (;;) {
|
|
369
|
+
if (e.signal.aborted) throw S(e.signal.reason);
|
|
370
|
+
let i = await w(t, e);
|
|
371
|
+
if (i.kind === "response") {
|
|
372
|
+
if (!b(i.response.status) || r.status >= n.status.maxRetries) return i.response;
|
|
373
|
+
await C(x(n.status, r.status), e.signal), r.status += 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
let a = i.kind;
|
|
377
|
+
if (r[a] >= n[a].maxRetries) throw i.error;
|
|
378
|
+
await C(x(n[a], r[a]), e.signal), r[a] += 1;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function v(e, t) {
|
|
383
|
+
return {
|
|
384
|
+
baseDelayMs: t?.baseDelayMs ?? e.baseDelayMs,
|
|
385
|
+
maxDelayMs: t?.maxDelayMs ?? e.maxDelayMs,
|
|
386
|
+
maxRetries: t?.maxRetries ?? e.maxRetries
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function y(e) {
|
|
390
|
+
return {
|
|
391
|
+
network: v(p.network, e.network),
|
|
392
|
+
status: v(p.status, e.status),
|
|
393
|
+
timeout: v(p.timeout, e.timeout)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function b(e) {
|
|
397
|
+
return e === 429 || e >= 500 && e < 600;
|
|
398
|
+
}
|
|
399
|
+
function x(e, t) {
|
|
400
|
+
let n = .8 + Math.random() * .2;
|
|
401
|
+
return Math.min(e.baseDelayMs * 2 ** t * n, e.maxDelayMs);
|
|
402
|
+
}
|
|
403
|
+
function S(e) {
|
|
404
|
+
return e instanceof Error ? e : Error(String(e));
|
|
405
|
+
}
|
|
406
|
+
function C(e, t) {
|
|
407
|
+
return new Promise((n, r) => {
|
|
408
|
+
if (t.aborted) {
|
|
409
|
+
r(S(t.reason));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let i, a = () => {
|
|
413
|
+
clearTimeout(i), r(S(t.reason));
|
|
414
|
+
};
|
|
415
|
+
i = setTimeout(() => {
|
|
416
|
+
t.removeEventListener("abort", a), n();
|
|
417
|
+
}, e), t.addEventListener("abort", a, { once: !0 });
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
async function w(e, t) {
|
|
421
|
+
let n = new AbortController(), r = !1, i = setTimeout(() => {
|
|
422
|
+
r = !0, n.abort();
|
|
423
|
+
}, m), a = t.signal, o = () => {
|
|
424
|
+
n.abort();
|
|
425
|
+
};
|
|
426
|
+
a.aborted ? n.abort() : a.addEventListener("abort", o, { once: !0 });
|
|
427
|
+
try {
|
|
428
|
+
return {
|
|
429
|
+
kind: "response",
|
|
430
|
+
response: await T(await e(new Request(t, { signal: n.signal })))
|
|
431
|
+
};
|
|
432
|
+
} catch (e) {
|
|
433
|
+
if (a.aborted) throw e;
|
|
434
|
+
return r ? {
|
|
435
|
+
error: e,
|
|
436
|
+
kind: "timeout"
|
|
437
|
+
} : {
|
|
438
|
+
error: e,
|
|
439
|
+
kind: "network"
|
|
440
|
+
};
|
|
441
|
+
} finally {
|
|
442
|
+
clearTimeout(i), a.removeEventListener("abort", o);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function T(e) {
|
|
446
|
+
if (!e.body) return e;
|
|
447
|
+
let t = await e.arrayBuffer();
|
|
448
|
+
return new Response(t, {
|
|
449
|
+
headers: e.headers,
|
|
450
|
+
status: e.status,
|
|
451
|
+
statusText: e.statusText
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
//#endregion
|
|
336
455
|
//#region src/api.ts
|
|
337
|
-
function
|
|
338
|
-
let
|
|
456
|
+
function E(e, t = "https://openapi.tidal.com/v2/", n) {
|
|
457
|
+
let i = { async onRequest({ request: t }) {
|
|
339
458
|
let n = await e.getCredentials();
|
|
340
459
|
return t.headers.set("Authorization", `Bearer ${n.token}`), (t.method === "POST" || t.method === "PATCH" || t.method === "DELETE") && t.headers.set("Content-Type", "application/vnd.api+json"), t;
|
|
341
|
-
} },
|
|
460
|
+
} }, a = r({
|
|
342
461
|
baseUrl: t,
|
|
462
|
+
fetch: _(n),
|
|
343
463
|
querySerializer: { allowReserved: !0 }
|
|
344
464
|
});
|
|
345
|
-
return
|
|
465
|
+
return a.use(i), a;
|
|
346
466
|
}
|
|
347
467
|
//#endregion
|
|
348
|
-
export {
|
|
468
|
+
export { E as createAPIClient };
|
package/dist/src/api.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { CredentialsProvider } from '@tidal-music/common';
|
|
2
2
|
import { paths } from './allAPI.generated.js';
|
|
3
|
+
import { RetryOptions } from './retry.js';
|
|
3
4
|
/**
|
|
4
5
|
* Create a Tidal API client with the provided credentials.
|
|
5
6
|
*
|
|
6
7
|
* @param credentialsProvider The credentials provider, from Auth module.
|
|
7
8
|
* @param baseUrl Override the base URL to use for the API client.
|
|
9
|
+
* @param retryOptions Customize the retry mechanism applied to read-only
|
|
10
|
+
* (GET/HEAD/OPTIONS) requests. Pass `{ enabled: false }` to disable it.
|
|
8
11
|
* @returns A Tidal API client.
|
|
9
12
|
*/
|
|
10
|
-
export declare function createAPIClient(credentialsProvider: CredentialsProvider, baseUrl?: string): import('openapi-fetch').Client<paths, `${string}/${string}`>;
|
|
13
|
+
export declare function createAPIClient(credentialsProvider: CredentialsProvider, baseUrl?: string, retryOptions?: RetryOptions): import('openapi-fetch').Client<paths, `${string}/${string}`>;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP retry mechanism for the TIDAL API client
|
|
3
|
+
*
|
|
4
|
+
* Read-only requests (GET/HEAD/OPTIONS) are retried with exponential backoff
|
|
5
|
+
* and jitter. The delay before a retry is
|
|
6
|
+
*
|
|
7
|
+
* D = min(B * 2 ** n * j, M)
|
|
8
|
+
*
|
|
9
|
+
* where B is the base delay, n is the zero-based retry number, j is a random
|
|
10
|
+
* jitter factor in [0.8, 1) and M is the maximum delay.
|
|
11
|
+
*
|
|
12
|
+
* Write requests (POST/PATCH/PUT/DELETE) are passed through untouched.
|
|
13
|
+
*/
|
|
14
|
+
/** A fetch implementation compatible with openapi-fetch's `fetch` option. */
|
|
15
|
+
type FetchLike = (input: Request) => Promise<Response>;
|
|
16
|
+
type RetryPolicy = {
|
|
17
|
+
/** Base delay B in milliseconds. */
|
|
18
|
+
baseDelayMs: number;
|
|
19
|
+
/** Maximum delay M in milliseconds; the backoff is clamped to this. */
|
|
20
|
+
maxDelayMs: number;
|
|
21
|
+
/** Maximum number of retries N, in addition to the initial attempt. */
|
|
22
|
+
maxRetries: number;
|
|
23
|
+
};
|
|
24
|
+
type RetryCategory = 'network' | 'status' | 'timeout';
|
|
25
|
+
export type RetryOptions = Partial<Record<RetryCategory, Partial<RetryPolicy>>> & {
|
|
26
|
+
/** Set to `false` to disable retries and the read timeout entirely. */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Per-attempt read timeout in milliseconds. */
|
|
30
|
+
export declare const READ_TIMEOUT_MS = 10000;
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a fetch implementation with a retry mechanism. The returned function
|
|
33
|
+
* matches openapi-fetch's `fetch` option signature, so it can be passed
|
|
34
|
+
* straight to `createClient({ fetch })`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fetchWithRetry(options?: RetryOptions, baseFetch?: FetchLike): FetchLike;
|
|
37
|
+
export {};
|