@tidal-music/api 0.27.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/allAPI.generated.d.ts +2 -6
- 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 };
|
|
@@ -19611,8 +19611,6 @@ export interface components {
|
|
|
19611
19611
|
ClientsUpdateOperation_Payload_Data_Attributes: {
|
|
19612
19612
|
description?: string;
|
|
19613
19613
|
name?: string;
|
|
19614
|
-
/** @enum {string} */
|
|
19615
|
-
platformPreset?: "NONE" | "WEB" | "ANDROID" | "IOS";
|
|
19616
19614
|
redirectUris?: string[];
|
|
19617
19615
|
scopes?: string[];
|
|
19618
19616
|
};
|
|
@@ -19624,8 +19622,6 @@ export interface components {
|
|
|
19624
19622
|
createdAt?: string;
|
|
19625
19623
|
description?: string;
|
|
19626
19624
|
name: string;
|
|
19627
|
-
/** @enum {string} */
|
|
19628
|
-
platformPreset?: "NONE" | "WEB" | "ANDROID" | "IOS";
|
|
19629
19625
|
redirectUris?: string[];
|
|
19630
19626
|
scopes?: string[];
|
|
19631
19627
|
};
|
|
@@ -20244,7 +20240,7 @@ export interface components {
|
|
|
20244
20240
|
* @example SHORTCUTS
|
|
20245
20241
|
* @enum {string}
|
|
20246
20242
|
*/
|
|
20247
|
-
sourceType: "ALBUM_RECOMMENDATIONS" | "BECAUSE_YOU_LISTENED_TO_ALBUM" | "BECAUSE_YOU_ADDED_ALBUM" | "BECAUSE_YOU_ADDED_ARTIST" | "CONTINUE_LISTEN_TO" | "DAILY_MIXES" | "FORGOTTEN_FAVORITES" | "GENRE_MIXES" | "HISTORY_MIXES" | "LOCAL_PLAYLISTS" | "MY_PLAYLISTS" | "NEW_ALBUM_SUGGESTIONS" | "NEW_TRACK_SUGGESTIONS" | "NEW_ALBUMS" | "NEW_TRACKS" | "POPULAR_PLAYLISTS" | "RECENTLY_UPDATED_FAVORITED_PLAYLIST" | "RECOMMENDED_USERS_PLAYLISTS" | "SUGGESTED_ESSENTIAL_PLAYLISTS" | "SUGGESTED_RADIOS_MIXES" | "WELCOME_MIX" | "YOUR_FAVORITE_ARTISTS" | "UPLOADS_FOR_YOU" | "LATEST_SPOTLIGHTED_TRACKS" | "SHORTCUTS" | "ARTIST_TOP_TRACKS" | "ARTIST_SPOTLIGHTED_TRACKS" | "ARTIST_ALBUMS" | "ARTIST_TOP_SINGLES" | "ARTIST_COMPILATIONS" | "ARTIST_LIVE_ALBUMS" | "ARTIST_APPEARS_ON" | "ARTIST_PLAYLIST" | "ARTIST_PUBLIC_PLAYLIST" | "ARTIST_SIMILAR_ARTISTS" | "ARTIST_TRACK_UPLOADS" | "ARTIST_LINKS" | "ARTIST_VIDEOS" | "ARTIST_CREDITS" | "ALBUM_ITEMS" | "ALBUM_MORE_BY_ARTIST" | "ALBUM_OTHER_VERSIONS" | "ALBUM_RELATED_ALBUMS" | "ALBUM_RELATED_ARTISTS" | "ALBUM_ANNIVERSARY" | "ARTIST_BIRTHDAY" | "ARTIST_MEMORIAM" | "DJ_TOOLS" | "DJ_ARTIST_CURATED" | "THE_HITS" | "FROM_OUR_EDITORS" | "TOP_PLAYLISTS" | "FEATURED_TOP_TRACKS" | "FEATURED_TOP_ALBUMS" | "TOP_ARTISTS_ESSENTIALS" | "FEATURED_RECOMMENDED_PLAYLISTS" | "HOME_3_FEATURED_PLAYLISTS" | "HOME_3_FEATURED_UPLOAD_TRACKS" | "HOME_3_FEATURED_ALBUMS" | "POPULAR_ALBUMS" | "POPULAR_ARTISTS" | "POPULAR_MIXES" | "FEATURED_RECOMMENDED_TRACKS" | "FEATURED_RECOMMENDED_ALBUMS" | "FEATURED_RECOMMENDED_CLASSIC_ALBUMS" | "BACK_TO_SCHOOL_MUSIC_101" | "BACK_TO_SCHOOL_GENRES_FOR_BEGINNERS" | "HEADLINERS_2026" | "HOME_3_0_GENERIC_PLAYLISTS_1" | "HOME_3_0_GENERIC_PLAYLISTS_2" | "HOME_3_0_GENERIC_ALBUMS_1" | "HOME_3_0_GENERIC_TRACKS_1" | "HOME_3_0_GENERIC_ARTISTS_1" | "HOME_3_0_GENERIC_VIDEOS_1" | "STAFF_PICKS_PAGE_ALBUMS_WE_LOVE" | "STAFF_PICKS_PAGE_EXPLORE" | "STAFF_PICKS_PAGE_FAVORITE_SONGS" | "STAFF_PICKS_PAGE_RECENTLY_UPDATED_PLAYLISTS" | "STAFF_PICKS_PAGE_TIDAL_NEWS_MAGAZINE" | "STAFF_PICKS_PAGE_WHAT_LISTENING_TO" | "BASED_ON_YOUR_INTERESTS_1" | "BASED_ON_YOUR_INTERESTS_2" | "UPLOAD_PAGE_SPOTLIGHTED_PLAYLISTS" | "UPLOAD_PAGE_PAYGATED_ALBUMS" | "UPLOAD_PAGE_ALBUMS" | "TOP_UPLOADERS" | "UPLOAD_PAGE_ARTISTS" | "UPLOAD_PAGE_FEATURED_MAGAZINE" | "EXPLORE_DECADES" | "EXPLORE_GENRES" | "EXPLORE_MOODS" | "UNKNOWN";
|
|
20243
|
+
sourceType: "ALBUM_RECOMMENDATIONS" | "BECAUSE_YOU_LISTENED_TO_ALBUM" | "BECAUSE_YOU_ADDED_ALBUM" | "BECAUSE_YOU_ADDED_ARTIST" | "CONTINUE_LISTEN_TO" | "DAILY_MIXES" | "FORGOTTEN_FAVORITES" | "GENRE_MIXES" | "HISTORY_MIXES" | "LOCAL_PLAYLISTS" | "MY_PLAYLISTS" | "NEW_ALBUM_SUGGESTIONS" | "NEW_TRACK_SUGGESTIONS" | "NEW_ALBUMS" | "NEW_TRACKS" | "POPULAR_PLAYLISTS" | "RECENTLY_UPDATED_FAVORITED_PLAYLIST" | "RECOMMENDED_USERS_PLAYLISTS" | "SUGGESTED_ESSENTIAL_PLAYLISTS" | "SUGGESTED_RADIOS_MIXES" | "WELCOME_MIX" | "YOUR_FAVORITE_ARTISTS" | "UPLOADS_FOR_YOU" | "LATEST_SPOTLIGHTED_TRACKS" | "SHORTCUTS" | "ARTIST_TOP_TRACKS" | "ARTIST_SPOTLIGHTED_TRACKS" | "ARTIST_ALBUMS" | "ARTIST_TOP_SINGLES" | "ARTIST_COMPILATIONS" | "ARTIST_LIVE_ALBUMS" | "ARTIST_APPEARS_ON" | "ARTIST_PLAYLIST" | "ARTIST_PUBLIC_PLAYLIST" | "ARTIST_SIMILAR_ARTISTS" | "ARTIST_TRACK_UPLOADS" | "ARTIST_LINKS" | "ARTIST_VIDEOS" | "ARTIST_CREDITS" | "ALBUM_ITEMS" | "ALBUM_MORE_BY_ARTIST" | "ALBUM_OTHER_VERSIONS" | "ALBUM_RELATED_ALBUMS" | "ALBUM_RELATED_ARTISTS" | "COLLECTION_ITEMS" | "ALBUM_ANNIVERSARY" | "ARTIST_BIRTHDAY" | "ARTIST_MEMORIAM" | "DJ_TOOLS" | "DJ_ARTIST_CURATED" | "THE_HITS" | "FROM_OUR_EDITORS" | "TOP_PLAYLISTS" | "FEATURED_TOP_TRACKS" | "FEATURED_TOP_ALBUMS" | "TOP_ARTISTS_ESSENTIALS" | "FEATURED_RECOMMENDED_PLAYLISTS" | "HOME_3_FEATURED_PLAYLISTS" | "HOME_3_FEATURED_UPLOAD_TRACKS" | "HOME_3_FEATURED_ALBUMS" | "POPULAR_ALBUMS" | "POPULAR_ARTISTS" | "POPULAR_MIXES" | "FEATURED_RECOMMENDED_TRACKS" | "FEATURED_RECOMMENDED_ALBUMS" | "FEATURED_RECOMMENDED_CLASSIC_ALBUMS" | "BACK_TO_SCHOOL_MUSIC_101" | "BACK_TO_SCHOOL_GENRES_FOR_BEGINNERS" | "HEADLINERS_2026" | "HOME_3_0_GENERIC_PLAYLISTS_1" | "HOME_3_0_GENERIC_PLAYLISTS_2" | "HOME_3_0_GENERIC_ALBUMS_1" | "HOME_3_0_GENERIC_TRACKS_1" | "HOME_3_0_GENERIC_ARTISTS_1" | "HOME_3_0_GENERIC_VIDEOS_1" | "STAFF_PICKS_PAGE_ALBUMS_WE_LOVE" | "STAFF_PICKS_PAGE_EXPLORE" | "STAFF_PICKS_PAGE_FAVORITE_SONGS" | "STAFF_PICKS_PAGE_RECENTLY_UPDATED_PLAYLISTS" | "STAFF_PICKS_PAGE_TIDAL_NEWS_MAGAZINE" | "STAFF_PICKS_PAGE_WHAT_LISTENING_TO" | "BASED_ON_YOUR_INTERESTS_1" | "BASED_ON_YOUR_INTERESTS_2" | "UPLOAD_PAGE_SPOTLIGHTED_PLAYLISTS" | "UPLOAD_PAGE_PAYGATED_ALBUMS" | "UPLOAD_PAGE_ALBUMS" | "TOP_UPLOADERS" | "UPLOAD_PAGE_ARTISTS" | "UPLOAD_PAGE_FEATURED_MAGAZINE" | "EXPLORE_DECADES" | "EXPLORE_GENRES" | "EXPLORE_MOODS" | "UNKNOWN";
|
|
20248
20244
|
/**
|
|
20249
20245
|
* @description Subtitle of the module
|
|
20250
20246
|
* @example Short description of this module
|
|
@@ -20294,7 +20290,7 @@ export interface components {
|
|
|
20294
20290
|
* @example HOME_STATIC
|
|
20295
20291
|
* @enum {string}
|
|
20296
20292
|
*/
|
|
20297
|
-
pageType: "HOME_STATIC" | "HOME_UPLOADS" | "HOME_EDITORIAL" | "HOME_FREE" | "ARTIST" | "ALBUM";
|
|
20293
|
+
pageType: "HOME_STATIC" | "HOME_UPLOADS" | "HOME_EDITORIAL" | "HOME_FREE" | "ARTIST" | "ALBUM" | "PLAYLIST";
|
|
20298
20294
|
/**
|
|
20299
20295
|
* Format: uuid
|
|
20300
20296
|
* @description Id used for reporting user events
|
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 {};
|