tmdb-kit 1.1.0 → 1.2.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.cjs CHANGED
@@ -47,9 +47,11 @@ __export(exports_src, {
47
47
  KNOWN_STILL_SIZES: () => KNOWN_STILL_SIZES,
48
48
  TMDBClient: () => TMDBClient,
49
49
  TMDBError: () => TMDBError,
50
+ TMDBHttpClient: () => TMDBHttpClient,
50
51
  TMDBRateLimitError: () => TMDBRateLimitError,
51
52
  TMDBRequestError: () => TMDBRequestError,
52
53
  TMDBResponseError: () => TMDBResponseError,
54
+ TMDBTimeoutError: () => TMDBTimeoutError,
53
55
  buildImageUrl: () => buildImageUrl,
54
56
  createTMDB: () => createTMDB,
55
57
  isKnownBackdropSize: () => isKnownBackdropSize,
@@ -63,7 +65,13 @@ __export(exports_src, {
63
65
  isPerson: () => isPerson,
64
66
  isPersonSearchResult: () => isPersonSearchResult,
65
67
  isTV: () => isTV,
66
- isTVSearchResult: () => isTVSearchResult
68
+ isTVSearchResult: () => isTVSearchResult,
69
+ paginate: () => paginate,
70
+ paginateItems: () => paginateItems,
71
+ retry: () => retry,
72
+ withMemoryCache: () => withMemoryCache,
73
+ withRetry: () => withRetry,
74
+ withTimeout: () => withTimeout
67
75
  });
68
76
  module.exports = __toCommonJS(exports_src);
69
77
 
@@ -82,6 +90,15 @@ class TMDBRequestError extends TMDBError {
82
90
  }
83
91
  }
84
92
 
93
+ class TMDBTimeoutError extends TMDBError {
94
+ timeoutMs;
95
+ constructor(timeoutMs, message = `TMDB request timed out after ${timeoutMs}ms`) {
96
+ super(message);
97
+ this.name = "TMDBTimeoutError";
98
+ this.timeoutMs = timeoutMs;
99
+ }
100
+ }
101
+
85
102
  class TMDBResponseError extends TMDBError {
86
103
  body;
87
104
  headers;
@@ -204,6 +221,19 @@ function toId(value, name) {
204
221
  }
205
222
  return String(value);
206
223
  }
224
+ function toSeasonNumber(value) {
225
+ if (!Number.isInteger(value) || value < 0) {
226
+ throw new TMDBRequestError("seasonNumber must be a non-negative integer");
227
+ }
228
+ return String(value);
229
+ }
230
+ function toGroupId(value) {
231
+ const groupId = value.trim();
232
+ if (!/^[A-Za-z0-9_-]+$/.test(groupId)) {
233
+ throw new TMDBRequestError("groupId must be a non-empty identifier");
234
+ }
235
+ return groupId;
236
+ }
207
237
  function toSearchQuery(value) {
208
238
  const query = value.trim();
209
239
  if (query.length === 0) {
@@ -337,23 +367,25 @@ class ConfigurationResource {
337
367
  constructor(transport) {
338
368
  this.#transport = transport;
339
369
  }
340
- details() {
341
- return this.#transport.get("/configuration");
370
+ details(options = {}) {
371
+ return this.#transport.get("/configuration", { signal: options.signal });
342
372
  }
343
- countries() {
344
- return this.#transport.get("/configuration/countries");
373
+ countries(options = {}) {
374
+ return this.#transport.get("/configuration/countries", { signal: options.signal });
345
375
  }
346
- jobs() {
347
- return this.#transport.get("/configuration/jobs");
376
+ jobs(options = {}) {
377
+ return this.#transport.get("/configuration/jobs", { signal: options.signal });
348
378
  }
349
- languages() {
350
- return this.#transport.get("/configuration/languages");
379
+ languages(options = {}) {
380
+ return this.#transport.get("/configuration/languages", { signal: options.signal });
351
381
  }
352
- primaryTranslations() {
353
- return this.#transport.get("/configuration/primary_translations");
382
+ primaryTranslations(options = {}) {
383
+ return this.#transport.get("/configuration/primary_translations", {
384
+ signal: options.signal
385
+ });
354
386
  }
355
- timezones() {
356
- return this.#transport.get("/configuration/timezones");
387
+ timezones(options = {}) {
388
+ return this.#transport.get("/configuration/timezones", { signal: options.signal });
357
389
  }
358
390
  }
359
391
 
@@ -367,14 +399,16 @@ class GenresResource {
367
399
  return this.#transport.get("/genre/movie/list", {
368
400
  query: withLanguage(this.#transport.defaults, {
369
401
  language: options.language
370
- })
402
+ }),
403
+ signal: options.signal
371
404
  });
372
405
  }
373
406
  tv(options = {}) {
374
407
  return this.#transport.get("/genre/tv/list", {
375
408
  query: withLanguage(this.#transport.defaults, {
376
409
  language: options.language
377
- })
410
+ }),
411
+ signal: options.signal
378
412
  });
379
413
  }
380
414
  }
@@ -403,22 +437,26 @@ class MoviesResource {
403
437
  }
404
438
  popular(options = {}) {
405
439
  return this.#transport.get("/movie/popular", {
406
- query: movieListQuery(this.#transport, options)
440
+ query: movieListQuery(this.#transport, options),
441
+ signal: options.signal
407
442
  });
408
443
  }
409
444
  topRated(options = {}) {
410
445
  return this.#transport.get("/movie/top_rated", {
411
- query: movieListQuery(this.#transport, options)
446
+ query: movieListQuery(this.#transport, options),
447
+ signal: options.signal
412
448
  });
413
449
  }
414
450
  nowPlaying(options = {}) {
415
451
  return this.#transport.get("/movie/now_playing", {
416
- query: movieListQuery(this.#transport, options)
452
+ query: movieListQuery(this.#transport, options),
453
+ signal: options.signal
417
454
  });
418
455
  }
419
456
  upcoming(options = {}) {
420
457
  return this.#transport.get("/movie/upcoming", {
421
- query: movieListQuery(this.#transport, options)
458
+ query: movieListQuery(this.#transport, options),
459
+ signal: options.signal
422
460
  });
423
461
  }
424
462
  details(movieId, options = {}) {
@@ -426,14 +464,16 @@ class MoviesResource {
426
464
  query: withLanguage(this.#transport.defaults, {
427
465
  append_to_response: toCommaList(options.appendToResponse),
428
466
  language: options.language
429
- })
467
+ }),
468
+ signal: options.signal
430
469
  });
431
470
  }
432
471
  credits(movieId, options = {}) {
433
472
  return this.#transport.get(`/movie/${toId(movieId, "movieId")}/credits`, {
434
473
  query: withLanguage(this.#transport.defaults, {
435
474
  language: options.language
436
- })
475
+ }),
476
+ signal: options.signal
437
477
  });
438
478
  }
439
479
  images(movieId, options = {}) {
@@ -441,24 +481,28 @@ class MoviesResource {
441
481
  query: withLanguage(this.#transport.defaults, {
442
482
  include_image_language: toCommaList(options.includeImageLanguage),
443
483
  language: options.language
444
- })
484
+ }),
485
+ signal: options.signal
445
486
  });
446
487
  }
447
488
  recommendations(movieId, options = {}) {
448
489
  return this.#transport.get(`/movie/${toId(movieId, "movieId")}/recommendations`, {
449
- query: movieListQuery(this.#transport, options)
490
+ query: movieListQuery(this.#transport, options),
491
+ signal: options.signal
450
492
  });
451
493
  }
452
494
  similar(movieId, options = {}) {
453
495
  return this.#transport.get(`/movie/${toId(movieId, "movieId")}/similar`, {
454
- query: movieListQuery(this.#transport, options)
496
+ query: movieListQuery(this.#transport, options),
497
+ signal: options.signal
455
498
  });
456
499
  }
457
500
  videos(movieId, options = {}) {
458
501
  return this.#transport.get(`/movie/${toId(movieId, "movieId")}/videos`, {
459
502
  query: withLanguage(this.#transport.defaults, {
460
503
  language: options.language
461
- })
504
+ }),
505
+ signal: options.signal
462
506
  });
463
507
  }
464
508
  }
@@ -478,21 +522,24 @@ class PeopleResource {
478
522
  }
479
523
  popular(options = {}) {
480
524
  return this.#transport.get("/person/popular", {
481
- query: peopleListQuery(this.#transport, options)
525
+ query: peopleListQuery(this.#transport, options),
526
+ signal: options.signal
482
527
  });
483
528
  }
484
529
  details(personId, options = {}) {
485
530
  return this.#transport.get(`/person/${toId(personId, "personId")}`, {
486
531
  query: withLanguage(this.#transport.defaults, {
487
532
  language: options.language
488
- })
533
+ }),
534
+ signal: options.signal
489
535
  });
490
536
  }
491
537
  combinedCredits(personId, options = {}) {
492
538
  return this.#transport.get(`/person/${toId(personId, "personId")}/combined_credits`, {
493
539
  query: withLanguage(this.#transport.defaults, {
494
540
  language: options.language
495
- })
541
+ }),
542
+ signal: options.signal
496
543
  });
497
544
  }
498
545
  }
@@ -511,22 +558,26 @@ class SearchResource {
511
558
  }
512
559
  movies(query, options = {}) {
513
560
  return this.#transport.get("/search/movie", {
514
- query: movieSearchQuery(this.#transport, query, options)
561
+ query: movieSearchQuery(this.#transport, query, options),
562
+ signal: options.signal
515
563
  });
516
564
  }
517
565
  tv(query, options = {}) {
518
566
  return this.#transport.get("/search/tv", {
519
- query: tvSearchQuery(this.#transport, query, options)
567
+ query: tvSearchQuery(this.#transport, query, options),
568
+ signal: options.signal
520
569
  });
521
570
  }
522
571
  people(query, options = {}) {
523
572
  return this.#transport.get("/search/person", {
524
- query: commonSearchQuery(this.#transport, query, options)
573
+ query: commonSearchQuery(this.#transport, query, options),
574
+ signal: options.signal
525
575
  });
526
576
  }
527
577
  async multi(query, options = {}) {
528
578
  const response = await this.#transport.get("/search/multi", {
529
- query: commonSearchQuery(this.#transport, query, options)
579
+ query: commonSearchQuery(this.#transport, query, options),
580
+ signal: options.signal
530
581
  });
531
582
  const excluded = options.exclude;
532
583
  if (excluded === undefined || excluded.length === 0) {
@@ -580,7 +631,8 @@ class TrendingResource {
580
631
  query: withLanguage(this.#transport.defaults, {
581
632
  language: options.language,
582
633
  page: options.page
583
- })
634
+ }),
635
+ signal: options.signal
584
636
  });
585
637
  }
586
638
  movies(timeWindow = "day", options = {}) {
@@ -588,7 +640,8 @@ class TrendingResource {
588
640
  query: withLanguage(this.#transport.defaults, {
589
641
  language: options.language,
590
642
  page: options.page
591
- })
643
+ }),
644
+ signal: options.signal
592
645
  });
593
646
  }
594
647
  tv(timeWindow = "day", options = {}) {
@@ -596,7 +649,8 @@ class TrendingResource {
596
649
  query: withLanguage(this.#transport.defaults, {
597
650
  language: options.language,
598
651
  page: options.page
599
- })
652
+ }),
653
+ signal: options.signal
600
654
  });
601
655
  }
602
656
  people(timeWindow = "day", options = {}) {
@@ -604,7 +658,8 @@ class TrendingResource {
604
658
  query: withLanguage(this.#transport.defaults, {
605
659
  language: options.language,
606
660
  page: options.page
607
- })
661
+ }),
662
+ signal: options.signal
608
663
  });
609
664
  }
610
665
  }
@@ -617,22 +672,26 @@ class TVResource {
617
672
  }
618
673
  popular(options = {}) {
619
674
  return this.#transport.get("/tv/popular", {
620
- query: tvListQuery(this.#transport, options)
675
+ query: tvListQuery(this.#transport, options),
676
+ signal: options.signal
621
677
  });
622
678
  }
623
679
  topRated(options = {}) {
624
680
  return this.#transport.get("/tv/top_rated", {
625
- query: tvListQuery(this.#transport, options)
681
+ query: tvListQuery(this.#transport, options),
682
+ signal: options.signal
626
683
  });
627
684
  }
628
685
  airingToday(options = {}) {
629
686
  return this.#transport.get("/tv/airing_today", {
630
- query: tvListQuery(this.#transport, options)
687
+ query: tvListQuery(this.#transport, options),
688
+ signal: options.signal
631
689
  });
632
690
  }
633
691
  onTheAir(options = {}) {
634
692
  return this.#transport.get("/tv/on_the_air", {
635
- query: tvListQuery(this.#transport, options)
693
+ query: tvListQuery(this.#transport, options),
694
+ signal: options.signal
636
695
  });
637
696
  }
638
697
  details(seriesId, options = {}) {
@@ -640,14 +699,16 @@ class TVResource {
640
699
  query: withLanguage(this.#transport.defaults, {
641
700
  append_to_response: toCommaList(options.appendToResponse),
642
701
  language: options.language
643
- })
702
+ }),
703
+ signal: options.signal
644
704
  });
645
705
  }
646
706
  credits(seriesId, options = {}) {
647
707
  return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/credits`, {
648
708
  query: withLanguage(this.#transport.defaults, {
649
709
  language: options.language
650
- })
710
+ }),
711
+ signal: options.signal
651
712
  });
652
713
  }
653
714
  images(seriesId, options = {}) {
@@ -655,32 +716,103 @@ class TVResource {
655
716
  query: withLanguage(this.#transport.defaults, {
656
717
  include_image_language: toCommaList(options.includeImageLanguage),
657
718
  language: options.language
658
- })
719
+ }),
720
+ signal: options.signal
659
721
  });
660
722
  }
661
723
  recommendations(seriesId, options = {}) {
662
724
  return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/recommendations`, {
663
- query: tvListQuery(this.#transport, options)
725
+ query: tvListQuery(this.#transport, options),
726
+ signal: options.signal
664
727
  });
665
728
  }
666
729
  similar(seriesId, options = {}) {
667
730
  return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/similar`, {
668
- query: tvListQuery(this.#transport, options)
731
+ query: tvListQuery(this.#transport, options),
732
+ signal: options.signal
669
733
  });
670
734
  }
671
735
  videos(seriesId, options = {}) {
672
736
  return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/videos`, {
673
737
  query: withLanguage(this.#transport.defaults, {
674
738
  language: options.language
675
- })
739
+ }),
740
+ signal: options.signal
676
741
  });
677
742
  }
678
743
  seasonDetails(seriesId, seasonNumber, options = {}) {
679
- return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toId(seasonNumber, "seasonNumber")}`, {
744
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}`, {
680
745
  query: withLanguage(this.#transport.defaults, {
681
746
  append_to_response: toCommaList(options.appendToResponse),
682
747
  language: options.language
683
- })
748
+ }),
749
+ signal: options.signal
750
+ });
751
+ }
752
+ seasonCredits(seriesId, seasonNumber, options = {}) {
753
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}/credits`, {
754
+ query: withLanguage(this.#transport.defaults, {
755
+ language: options.language
756
+ }),
757
+ signal: options.signal
758
+ });
759
+ }
760
+ seasonAggregateCredits(seriesId, seasonNumber, options = {}) {
761
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}/aggregate_credits`, {
762
+ query: withLanguage(this.#transport.defaults, {
763
+ language: options.language
764
+ }),
765
+ signal: options.signal
766
+ });
767
+ }
768
+ seasonImages(seriesId, seasonNumber, options = {}) {
769
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}/images`, {
770
+ query: withLanguage(this.#transport.defaults, {
771
+ include_image_language: toCommaList(options.includeImageLanguage),
772
+ language: options.language
773
+ }),
774
+ signal: options.signal
775
+ });
776
+ }
777
+ seasonVideos(seriesId, seasonNumber, options = {}) {
778
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}/videos`, {
779
+ query: withLanguage(this.#transport.defaults, {
780
+ language: options.language
781
+ }),
782
+ signal: options.signal
783
+ });
784
+ }
785
+ episodeDetails(seriesId, seasonNumber, episodeNumber, options = {}) {
786
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}/episode/${toId(episodeNumber, "episodeNumber")}`, {
787
+ query: withLanguage(this.#transport.defaults, {
788
+ append_to_response: toCommaList(options.appendToResponse),
789
+ language: options.language
790
+ }),
791
+ signal: options.signal
792
+ });
793
+ }
794
+ aggregateCredits(seriesId, options = {}) {
795
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/aggregate_credits`, {
796
+ query: withLanguage(this.#transport.defaults, {
797
+ language: options.language
798
+ }),
799
+ signal: options.signal
800
+ });
801
+ }
802
+ episodeGroups(seriesId, options = {}) {
803
+ return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/episode_groups`, {
804
+ query: withLanguage(this.#transport.defaults, {
805
+ language: options.language
806
+ }),
807
+ signal: options.signal
808
+ });
809
+ }
810
+ episodeGroup(groupId, options = {}) {
811
+ return this.#transport.get(`/tv/episode_group/${toGroupId(groupId)}`, {
812
+ query: withLanguage(this.#transport.defaults, {
813
+ language: options.language
814
+ }),
815
+ signal: options.signal
684
816
  });
685
817
  }
686
818
  }
@@ -703,13 +835,17 @@ class TMDBClient {
703
835
  tv;
704
836
  #transport;
705
837
  constructor(options) {
706
- const { transport, images, ...httpOptions } = options;
707
- const fallbackTransform = images?.transform;
708
- const builtInClient = transport === undefined ? new TMDBHttpClient({ ...httpOptions, images }) : undefined;
709
- this.#transport = builtInClient ?? transport;
838
+ let imageTransform = options.images?.transform;
839
+ if (options.transport === undefined) {
840
+ const builtInClient = new TMDBHttpClient(options);
841
+ this.#transport = builtInClient;
842
+ imageTransform = builtInClient.imageTransform;
843
+ } else {
844
+ this.#transport = options.transport;
845
+ }
710
846
  this.configuration = new ConfigurationResource(this.#transport);
711
847
  this.genres = new GenresResource(this.#transport);
712
- this.images = new ImagesHelper(this.#transport, builtInClient ? builtInClient.imageTransform : fallbackTransform);
848
+ this.images = new ImagesHelper(this.#transport, imageTransform);
713
849
  this.movies = new MoviesResource(this.#transport);
714
850
  this.people = new PeopleResource(this.#transport);
715
851
  this.search = new SearchResource(this.#transport);
@@ -723,6 +859,238 @@ class TMDBClient {
723
859
  function createTMDB(options) {
724
860
  return new TMDBClient(options);
725
861
  }
862
+ // src/helpers/cache.ts
863
+ function withMemoryCache(transport, options = {}) {
864
+ const ttlMs = options.ttlMs ?? 5 * 60000;
865
+ const maxEntries = options.maxEntries ?? 100;
866
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
867
+ throw new RangeError("ttlMs must be a positive number");
868
+ }
869
+ if (!Number.isInteger(maxEntries) || maxEntries < 1) {
870
+ throw new RangeError("maxEntries must be a positive integer");
871
+ }
872
+ const entries = new Map;
873
+ return {
874
+ ...transport,
875
+ async get(path, requestOptions = {}) {
876
+ const key = cacheKey(path, requestOptions);
877
+ const cached = entries.get(key);
878
+ if (cached !== undefined) {
879
+ if (cached.expiresAt > Date.now()) {
880
+ entries.delete(key);
881
+ entries.set(key, cached);
882
+ return cached.value;
883
+ }
884
+ entries.delete(key);
885
+ }
886
+ const value = await transport.get(path, requestOptions);
887
+ entries.set(key, { expiresAt: Date.now() + ttlMs, value });
888
+ evictOverflow(entries, maxEntries);
889
+ return value;
890
+ },
891
+ clear() {
892
+ entries.clear();
893
+ }
894
+ };
895
+ }
896
+ function cacheKey(path, options) {
897
+ return `${path}?${serializeQuery(options.query)}|${serializeHeaders(options.headers)}`;
898
+ }
899
+ function serializeQuery(query) {
900
+ if (query === undefined) {
901
+ return "";
902
+ }
903
+ return Object.entries(query).filter(([, value]) => value !== undefined && value !== null).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${Array.isArray(value) ? value.map(String).join(",") : String(value)}`).join("&");
904
+ }
905
+ function serializeHeaders(headers) {
906
+ if (headers === undefined) {
907
+ return "";
908
+ }
909
+ return [...new Headers(headers)].sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}:${value}`).join("|");
910
+ }
911
+ function evictOverflow(entries, maxEntries) {
912
+ while (entries.size > maxEntries) {
913
+ entries.delete(entries.keys().next().value);
914
+ }
915
+ }
916
+ // src/helpers/pagination.ts
917
+ async function* paginate(fetchPage, options = {}) {
918
+ const startPage = options.startPage ?? 1;
919
+ const endPage = options.endPage ?? 500;
920
+ assertPositiveInteger(startPage, "startPage");
921
+ assertPositiveInteger(endPage, "endPage");
922
+ for (let page = startPage;page <= endPage; page += 1) {
923
+ options.signal?.throwIfAborted();
924
+ const response = await fetchPage(page);
925
+ yield response;
926
+ if (response.results.length === 0 || page >= response.total_pages) {
927
+ return;
928
+ }
929
+ }
930
+ }
931
+ async function* paginateItems(fetchPage, options = {}) {
932
+ for await (const page of paginate(fetchPage, options)) {
933
+ yield* page.results;
934
+ }
935
+ }
936
+ function assertPositiveInteger(value, name) {
937
+ if (!Number.isInteger(value) || value < 1) {
938
+ throw new RangeError(`${name} must be a positive integer`);
939
+ }
940
+ }
941
+ // src/helpers/retry.ts
942
+ var MAX_RETRY_AFTER_MS = 60000;
943
+ async function retry(task, options = {}) {
944
+ const retries = options.retries ?? 2;
945
+ if (!Number.isInteger(retries) || retries < 0) {
946
+ throw new RangeError("retries must be a non-negative integer");
947
+ }
948
+ const retryOn = options.retryOn ?? shouldRetry;
949
+ const signal = options.signal;
950
+ if (signal?.aborted) {
951
+ throw signal.reason;
952
+ }
953
+ for (let attempt = 0;; attempt += 1) {
954
+ try {
955
+ return await task();
956
+ } catch (error) {
957
+ if (signal?.aborted) {
958
+ throw signal.reason;
959
+ }
960
+ if (attempt >= retries || !retryOn(error, attempt)) {
961
+ throw error;
962
+ }
963
+ const delayMs = retryDelay(error, attempt, options);
964
+ options.onRetry?.({ attempt, delayMs, error });
965
+ await sleep(delayMs, signal);
966
+ }
967
+ }
968
+ }
969
+ function withRetry(transport, options = {}) {
970
+ return {
971
+ ...transport,
972
+ get(path, requestOptions) {
973
+ const { cleanup, signal } = combineSignals(options.signal, requestOptions?.signal);
974
+ const effectiveOptions = signal === undefined ? requestOptions : { ...requestOptions, signal };
975
+ return retry(() => transport.get(path, effectiveOptions), {
976
+ ...options,
977
+ signal
978
+ }).finally(cleanup);
979
+ }
980
+ };
981
+ }
982
+ function shouldRetry(error) {
983
+ if (error instanceof TMDBRateLimitError) {
984
+ return true;
985
+ }
986
+ if (error instanceof TMDBResponseError) {
987
+ return error.isServerError || error.status === 408;
988
+ }
989
+ if (error instanceof TMDBTimeoutError) {
990
+ return false;
991
+ }
992
+ if (error instanceof TMDBRequestError) {
993
+ if (error.cause === undefined || error.cause instanceof TMDBTimeoutError) {
994
+ return false;
995
+ }
996
+ return !isAbortError(error.cause);
997
+ }
998
+ return false;
999
+ }
1000
+ function retryDelay(error, attempt, options) {
1001
+ const retryAfter = retryAfterMs(error);
1002
+ if (retryAfter !== undefined) {
1003
+ return Math.min(Math.max(retryAfter, 0), MAX_RETRY_AFTER_MS);
1004
+ }
1005
+ if (error instanceof TMDBRateLimitError) {
1006
+ return Math.min(Math.max(error.retryAfter * 1000, 0), MAX_RETRY_AFTER_MS);
1007
+ }
1008
+ const baseDelayMs = options.baseDelayMs ?? 250;
1009
+ const maxDelayMs = options.maxDelayMs ?? 1e4;
1010
+ const factor = options.factor ?? 2;
1011
+ const delay = Math.min(maxDelayMs, baseDelayMs * factor ** attempt);
1012
+ return options.jitter === false ? delay : Math.random() * delay;
1013
+ }
1014
+ function retryAfterMs(error) {
1015
+ if (!(error instanceof TMDBResponseError)) {
1016
+ return;
1017
+ }
1018
+ const header = error.headers.get("retry-after");
1019
+ if (header === null || header.trim() === "") {
1020
+ return;
1021
+ }
1022
+ return parseRetryAfter(header) * 1000;
1023
+ }
1024
+ function combineSignals(first, second) {
1025
+ if (first === undefined || second === undefined || first === second) {
1026
+ return { cleanup: noop, signal: first ?? second };
1027
+ }
1028
+ const controller = new AbortController;
1029
+ const onAbort = () => controller.abort((first.aborted ? first : second).reason);
1030
+ first.addEventListener("abort", onAbort, { once: true });
1031
+ second.addEventListener("abort", onAbort, { once: true });
1032
+ if (first.aborted || second.aborted) {
1033
+ onAbort();
1034
+ }
1035
+ return {
1036
+ cleanup() {
1037
+ first.removeEventListener("abort", onAbort);
1038
+ second.removeEventListener("abort", onAbort);
1039
+ },
1040
+ signal: controller.signal
1041
+ };
1042
+ }
1043
+ function noop() {}
1044
+ function isAbortError(error) {
1045
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1046
+ }
1047
+ function sleep(ms, signal) {
1048
+ return new Promise((resolve, reject) => {
1049
+ const onAbort = () => {
1050
+ clearTimeout(timer);
1051
+ signal?.removeEventListener("abort", onAbort);
1052
+ reject(signal.reason);
1053
+ };
1054
+ const timer = setTimeout(() => {
1055
+ signal?.removeEventListener("abort", onAbort);
1056
+ resolve();
1057
+ }, ms);
1058
+ signal?.addEventListener("abort", onAbort, { once: true });
1059
+ if (signal?.aborted) {
1060
+ onAbort();
1061
+ }
1062
+ });
1063
+ }
1064
+ // src/helpers/timeout.ts
1065
+ async function withTimeout(timeoutMs, task, options = {}) {
1066
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1067
+ throw new RangeError("timeoutMs must be a positive number");
1068
+ }
1069
+ const parent = options.signal;
1070
+ const controller = new AbortController;
1071
+ const timeoutError = new TMDBTimeoutError(timeoutMs, options.message);
1072
+ const onAbort = () => controller.abort(parent.reason);
1073
+ parent?.addEventListener("abort", onAbort, { once: true });
1074
+ if (parent?.aborted) {
1075
+ parent.removeEventListener("abort", onAbort);
1076
+ throw parent.reason;
1077
+ }
1078
+ const timer = setTimeout(() => controller.abort(timeoutError), timeoutMs);
1079
+ try {
1080
+ return await task(controller.signal);
1081
+ } catch (error) {
1082
+ if (controller.signal.reason === timeoutError) {
1083
+ throw timeoutError;
1084
+ }
1085
+ if (parent?.aborted) {
1086
+ throw parent.reason;
1087
+ }
1088
+ throw error;
1089
+ } finally {
1090
+ clearTimeout(timer);
1091
+ parent?.removeEventListener("abort", onAbort);
1092
+ }
1093
+ }
726
1094
  // src/search-guards.ts
727
1095
  function isMovieSearchResult(result) {
728
1096
  return isObjectWithMediaType(result, "movie");
@@ -798,5 +1166,5 @@ function isKnownImageSize(value) {
798
1166
  return KNOWN_IMAGE_SET.has(value);
799
1167
  }
800
1168
 
801
- //# debugId=CD39AE1F6030D61F64756E2164756E21
1169
+ //# debugId=D1FA66D9B94DF6A164756E2164756E21
802
1170
  //# sourceMappingURL=index.cjs.map