@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 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 p(e, t = "https://openapi.tidal.com/v2/") {
338
- let n = { async onRequest({ request: t }) {
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
- } }, i = r({
460
+ } }, a = r({
342
461
  baseUrl: t,
462
+ fetch: _(n),
343
463
  querySerializer: { allowReserved: !0 }
344
464
  });
345
- return i.use(n), i;
465
+ return a.use(i), a;
346
466
  }
347
467
  //#endregion
348
- export { p as createAPIClient };
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}`>;
@@ -8,3 +8,4 @@
8
8
  */
9
9
  export type { components } from './allAPI.generated.js';
10
10
  export { createAPIClient } from './api.js';
11
+ export type { RetryOptions } from './retry.js';
@@ -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 {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tidal-music/api",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"