@lorenzopant/tmdb 1.16.0 → 1.17.1

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 CHANGED
@@ -3,10 +3,44 @@ export declare class ApiClient {
3
3
  private accessToken;
4
4
  private baseUrl;
5
5
  private logger?;
6
+ /**
7
+ * Tracks in-flight requests keyed by a deterministic string derived from the endpoint
8
+ * and its parameters. When two identical requests are fired concurrently, the second
9
+ * caller receives the same Promise as the first — only one fetch is made.
10
+ * Entries are removed via `.finally()` so the map never holds settled promises.
11
+ */
12
+ private inflightRequests;
13
+ private deduplication;
6
14
  constructor(accessToken: string, options?: {
7
15
  logger?: boolean | TMDBLoggerFn;
16
+ deduplication?: boolean;
8
17
  });
18
+ /**
19
+ * Builds a stable, order-independent cache key for a request.
20
+ *
21
+ * `undefined` values are excluded (they are never serialised into the URL),
22
+ * and the remaining entries are sorted alphabetically before joining so that
23
+ * `{ language, page }` and `{ page, language }` produce the same key.
24
+ */
25
+ private buildRequestKey;
26
+ /**
27
+ * Makes an authenticated GET request to the TMDB API, returning the parsed and
28
+ * null-sanitised response.
29
+ *
30
+ * **Deduplication:** when enabled (the default), concurrent calls with the same
31
+ * `endpoint` + `params` share a single in-flight fetch. The second (and any
32
+ * subsequent) caller receives the same `Promise` — no extra network request is made.
33
+ * Once the promise settles (success or error) it is evicted from the map so the next
34
+ * call triggers a fresh fetch. Deduplication can be disabled globally via
35
+ * `TMDBOptions.deduplication = false`.
36
+ */
9
37
  request<T>(endpoint: string, params?: Record<string, unknown | undefined>): Promise<T>;
38
+ /**
39
+ * The actual fetch + response-parsing pipeline. Called by `request()` only when no
40
+ * matching in-flight promise exists. Handles URL construction, auth headers, logging,
41
+ * error mapping, and null sanitisation.
42
+ */
43
+ private doRequest;
10
44
  /**
11
45
  * Recursively converts null values to undefined in API responses.
12
46
  * This allows optional properties to model fields that TMDB returns as nullable.
package/dist/client.js CHANGED
@@ -1,20 +1,77 @@
1
1
  import { TMDBError } from "./errors/tmdb";
2
2
  import { TMDBLogger } from "./utils/logger";
3
+ import { isJwt } from "./utils";
3
4
  export class ApiClient {
4
5
  accessToken;
5
6
  baseUrl = "https://api.themoviedb.org/3";
6
7
  logger;
8
+ /**
9
+ * Tracks in-flight requests keyed by a deterministic string derived from the endpoint
10
+ * and its parameters. When two identical requests are fired concurrently, the second
11
+ * caller receives the same Promise as the first — only one fetch is made.
12
+ * Entries are removed via `.finally()` so the map never holds settled promises.
13
+ */
14
+ inflightRequests = new Map();
15
+ deduplication;
7
16
  constructor(accessToken, options = {}) {
8
17
  this.accessToken = accessToken;
9
18
  this.logger = TMDBLogger.from(options.logger);
19
+ this.deduplication = options.deduplication !== false;
20
+ }
21
+ /**
22
+ * Builds a stable, order-independent cache key for a request.
23
+ *
24
+ * `undefined` values are excluded (they are never serialised into the URL),
25
+ * and the remaining entries are sorted alphabetically before joining so that
26
+ * `{ language, page }` and `{ page, language }` produce the same key.
27
+ */
28
+ buildRequestKey(endpoint, params) {
29
+ const definedEntries = Object.entries(params)
30
+ .filter(([, v]) => v !== undefined)
31
+ .sort(([a], [b]) => a.localeCompare(b))
32
+ .map(([k, v]) => `${k}=${String(v)}`);
33
+ return definedEntries.length > 0 ? `${endpoint}?${definedEntries.join("&")}` : endpoint;
10
34
  }
35
+ /**
36
+ * Makes an authenticated GET request to the TMDB API, returning the parsed and
37
+ * null-sanitised response.
38
+ *
39
+ * **Deduplication:** when enabled (the default), concurrent calls with the same
40
+ * `endpoint` + `params` share a single in-flight fetch. The second (and any
41
+ * subsequent) caller receives the same `Promise` — no extra network request is made.
42
+ * Once the promise settles (success or error) it is evicted from the map so the next
43
+ * call triggers a fresh fetch. Deduplication can be disabled globally via
44
+ * `TMDBOptions.deduplication = false`.
45
+ */
11
46
  async request(endpoint, params = {}) {
47
+ if (!this.deduplication)
48
+ return this.doRequest(endpoint, params);
49
+ const key = this.buildRequestKey(endpoint, params);
50
+ const existing = this.inflightRequests.get(key);
51
+ if (existing)
52
+ return existing;
53
+ const promise = this.doRequest(endpoint, params).finally(() => {
54
+ this.inflightRequests.delete(key);
55
+ });
56
+ this.inflightRequests.set(key, promise);
57
+ return promise;
58
+ }
59
+ /**
60
+ * The actual fetch + response-parsing pipeline. Called by `request()` only when no
61
+ * matching in-flight promise exists. Handles URL construction, auth headers, logging,
62
+ * error mapping, and null sanitisation.
63
+ */
64
+ async doRequest(endpoint, params) {
12
65
  const url = new URL(`${this.baseUrl}${endpoint}`);
66
+ const jwt = isJwt(this.accessToken);
13
67
  for (const [key, value] of Object.entries(params)) {
14
68
  if (value === undefined)
15
69
  continue;
16
70
  url.searchParams.append(key, String(value));
17
71
  }
72
+ if (!jwt) {
73
+ url.searchParams.append("api_key", this.accessToken);
74
+ }
18
75
  const startedAt = Date.now();
19
76
  this.logger?.log({
20
77
  type: "request",
@@ -24,10 +81,14 @@ export class ApiClient {
24
81
  let res;
25
82
  try {
26
83
  res = await fetch(url.toString(), {
27
- headers: {
28
- Authorization: `Bearer ${this.accessToken}`,
29
- "Content-Type": "application/json;charset=utf-8",
30
- },
84
+ headers: jwt
85
+ ? {
86
+ Authorization: `Bearer ${this.accessToken}`,
87
+ "Content-Type": "application/json;charset=utf-8",
88
+ }
89
+ : {
90
+ "Content-Type": "application/json;charset=utf-8",
91
+ },
31
92
  });
32
93
  }
33
94
  catch (error) {
@@ -0,0 +1,91 @@
1
+ import { TMDBAPIBase } from "./base";
2
+ import type { PersonAppendToResponseNamespace, PersonChanges, PersonChangesParams, PersonCombinedCredits, PersonCreditsParams, PersonDetails, PersonDetailsParams, PersonDetailsWithAppends, PersonExternalIDs, PersonExternalIDsParams, PersonImages, PersonImagesParams, PersonMovieCredits, PersonTaggedImages, PersonTaggedImagesParams, PersonTranslations, PersonTranslationsParams, PersonTVCredits } from "../types/people";
3
+ export declare class PeopleAPI extends TMDBAPIBase {
4
+ private personPath;
5
+ private personSubPath;
6
+ /**
7
+ * Details
8
+ * GET - https://api.themoviedb.org/3/person/{person_id}
9
+ *
10
+ * Get the primary person details by TMDB person id.
11
+ * @param person_id The TMDB person id.
12
+ * @param append_to_response Additional person subresources to append.
13
+ * @param language Language for localized results.
14
+ * @reference https://developer.themoviedb.org/reference/person-details
15
+ */
16
+ details<T extends readonly PersonAppendToResponseNamespace[] = []>(params: PersonDetailsParams & {
17
+ append_to_response?: T[number] | T;
18
+ }): Promise<T extends [] ? PersonDetails : PersonDetailsWithAppends<T>>;
19
+ /**
20
+ * Changes
21
+ * GET - https://api.themoviedb.org/3/person/{person_id}/changes
22
+ *
23
+ * Get the change history for a person.
24
+ * @reference https://developer.themoviedb.org/reference/person-changes
25
+ */
26
+ changes(params: PersonChangesParams): Promise<PersonChanges>;
27
+ /**
28
+ * Combined Credits
29
+ * GET - https://api.themoviedb.org/3/person/{person_id}/combined_credits
30
+ *
31
+ * Get movie and TV credits in a single response.
32
+ * @reference https://developer.themoviedb.org/reference/person-combined-credits
33
+ */
34
+ combined_credits(params: PersonCreditsParams): Promise<PersonCombinedCredits>;
35
+ /**
36
+ * External IDs
37
+ * GET - https://api.themoviedb.org/3/person/{person_id}/external_ids
38
+ *
39
+ * Get external platform identifiers for a person.
40
+ * @reference https://developer.themoviedb.org/reference/person-external-ids
41
+ */
42
+ external_ids(params: PersonExternalIDsParams): Promise<PersonExternalIDs>;
43
+ /**
44
+ * Images
45
+ * GET - https://api.themoviedb.org/3/person/{person_id}/images
46
+ *
47
+ * Get profile images for a person.
48
+ * @reference https://developer.themoviedb.org/reference/person-images
49
+ */
50
+ images(params: PersonImagesParams): Promise<PersonImages>;
51
+ /**
52
+ * Latest
53
+ * GET - https://api.themoviedb.org/3/person/latest
54
+ *
55
+ * Get the newest person id added to TMDB.
56
+ * @reference https://developer.themoviedb.org/reference/person-latest-id
57
+ */
58
+ latest(): Promise<PersonDetails>;
59
+ /**
60
+ * Movie Credits
61
+ * GET - https://api.themoviedb.org/3/person/{person_id}/movie_credits
62
+ *
63
+ * Get a person's movie cast and crew credits.
64
+ * @reference https://developer.themoviedb.org/reference/person-movie-credits
65
+ */
66
+ movie_credits(params: PersonCreditsParams): Promise<PersonMovieCredits>;
67
+ /**
68
+ * Tagged Images
69
+ * GET - https://api.themoviedb.org/3/person/{person_id}/tagged_images
70
+ *
71
+ * Get images where the person has been tagged.
72
+ * @reference https://developer.themoviedb.org/reference/person-tagged-images
73
+ */
74
+ tagged_images(params: PersonTaggedImagesParams): Promise<PersonTaggedImages>;
75
+ /**
76
+ * Translations
77
+ * GET - https://api.themoviedb.org/3/person/{person_id}/translations
78
+ *
79
+ * Get the translations available for a person biography and name.
80
+ * @reference https://developer.themoviedb.org/reference/person-translations
81
+ */
82
+ translations(params: PersonTranslationsParams): Promise<PersonTranslations>;
83
+ /**
84
+ * TV Credits
85
+ * GET - https://api.themoviedb.org/3/person/{person_id}/tv_credits
86
+ *
87
+ * Get a person's TV cast and crew credits.
88
+ * @reference https://developer.themoviedb.org/reference/person-tv-credits
89
+ */
90
+ tv_credits(params: PersonCreditsParams): Promise<PersonTVCredits>;
91
+ }
@@ -0,0 +1,128 @@
1
+ import { ENDPOINTS } from "../routes";
2
+ import { TMDBAPIBase } from "./base";
3
+ export class PeopleAPI extends TMDBAPIBase {
4
+ personPath(person_id) {
5
+ return `${ENDPOINTS.PEOPLE.DETAILS}/${person_id}`;
6
+ }
7
+ personSubPath(person_id, route) {
8
+ return `${this.personPath(person_id)}${route}`;
9
+ }
10
+ /**
11
+ * Details
12
+ * GET - https://api.themoviedb.org/3/person/{person_id}
13
+ *
14
+ * Get the primary person details by TMDB person id.
15
+ * @param person_id The TMDB person id.
16
+ * @param append_to_response Additional person subresources to append.
17
+ * @param language Language for localized results.
18
+ * @reference https://developer.themoviedb.org/reference/person-details
19
+ */
20
+ async details(params) {
21
+ const { language = this.defaultOptions.language, person_id, ...rest } = params;
22
+ return this.client.request(this.personPath(person_id), { language, ...rest });
23
+ }
24
+ /**
25
+ * Changes
26
+ * GET - https://api.themoviedb.org/3/person/{person_id}/changes
27
+ *
28
+ * Get the change history for a person.
29
+ * @reference https://developer.themoviedb.org/reference/person-changes
30
+ */
31
+ async changes(params) {
32
+ const { person_id, ...rest } = params;
33
+ return this.client.request(this.personSubPath(person_id, ENDPOINTS.PEOPLE.CHANGES), rest);
34
+ }
35
+ /**
36
+ * Combined Credits
37
+ * GET - https://api.themoviedb.org/3/person/{person_id}/combined_credits
38
+ *
39
+ * Get movie and TV credits in a single response.
40
+ * @reference https://developer.themoviedb.org/reference/person-combined-credits
41
+ */
42
+ async combined_credits(params) {
43
+ const { language = this.defaultOptions.language, person_id, ...rest } = params;
44
+ return this.client.request(this.personSubPath(person_id, ENDPOINTS.PEOPLE.COMBINED_CREDITS), {
45
+ language,
46
+ ...rest,
47
+ });
48
+ }
49
+ /**
50
+ * External IDs
51
+ * GET - https://api.themoviedb.org/3/person/{person_id}/external_ids
52
+ *
53
+ * Get external platform identifiers for a person.
54
+ * @reference https://developer.themoviedb.org/reference/person-external-ids
55
+ */
56
+ async external_ids(params) {
57
+ return this.client.request(this.personSubPath(params.person_id, ENDPOINTS.PEOPLE.EXTERNAL_IDS));
58
+ }
59
+ /**
60
+ * Images
61
+ * GET - https://api.themoviedb.org/3/person/{person_id}/images
62
+ *
63
+ * Get profile images for a person.
64
+ * @reference https://developer.themoviedb.org/reference/person-images
65
+ */
66
+ async images(params) {
67
+ return this.client.request(this.personSubPath(params.person_id, ENDPOINTS.PEOPLE.IMAGES));
68
+ }
69
+ /**
70
+ * Latest
71
+ * GET - https://api.themoviedb.org/3/person/latest
72
+ *
73
+ * Get the newest person id added to TMDB.
74
+ * @reference https://developer.themoviedb.org/reference/person-latest-id
75
+ */
76
+ async latest() {
77
+ return this.client.request(`${ENDPOINTS.PEOPLE.DETAILS}${ENDPOINTS.PEOPLE.LATEST}`);
78
+ }
79
+ /**
80
+ * Movie Credits
81
+ * GET - https://api.themoviedb.org/3/person/{person_id}/movie_credits
82
+ *
83
+ * Get a person's movie cast and crew credits.
84
+ * @reference https://developer.themoviedb.org/reference/person-movie-credits
85
+ */
86
+ async movie_credits(params) {
87
+ const { language = this.defaultOptions.language, person_id, ...rest } = params;
88
+ return this.client.request(this.personSubPath(person_id, ENDPOINTS.PEOPLE.MOVIE_CREDITS), {
89
+ language,
90
+ ...rest,
91
+ });
92
+ }
93
+ /**
94
+ * Tagged Images
95
+ * GET - https://api.themoviedb.org/3/person/{person_id}/tagged_images
96
+ *
97
+ * Get images where the person has been tagged.
98
+ * @reference https://developer.themoviedb.org/reference/person-tagged-images
99
+ */
100
+ async tagged_images(params) {
101
+ const { person_id, ...rest } = params;
102
+ return this.client.request(this.personSubPath(person_id, ENDPOINTS.PEOPLE.TAGGED_IMAGES), rest);
103
+ }
104
+ /**
105
+ * Translations
106
+ * GET - https://api.themoviedb.org/3/person/{person_id}/translations
107
+ *
108
+ * Get the translations available for a person biography and name.
109
+ * @reference https://developer.themoviedb.org/reference/person-translations
110
+ */
111
+ async translations(params) {
112
+ return this.client.request(this.personSubPath(params.person_id, ENDPOINTS.PEOPLE.TRANSLATIONS));
113
+ }
114
+ /**
115
+ * TV Credits
116
+ * GET - https://api.themoviedb.org/3/person/{person_id}/tv_credits
117
+ *
118
+ * Get a person's TV cast and crew credits.
119
+ * @reference https://developer.themoviedb.org/reference/person-tv-credits
120
+ */
121
+ async tv_credits(params) {
122
+ const { language = this.defaultOptions.language, person_id, ...rest } = params;
123
+ return this.client.request(this.personSubPath(person_id, ENDPOINTS.PEOPLE.TV_CREDITS), {
124
+ language,
125
+ ...rest,
126
+ });
127
+ }
128
+ }
package/dist/routes.d.ts CHANGED
@@ -68,6 +68,18 @@ export declare const ENDPOINTS: {
68
68
  ALTERNATIVE_NAMES: string;
69
69
  IMAGES: string;
70
70
  };
71
+ PEOPLE: {
72
+ DETAILS: string;
73
+ CHANGES: string;
74
+ COMBINED_CREDITS: string;
75
+ EXTERNAL_IDS: string;
76
+ IMAGES: string;
77
+ LATEST: string;
78
+ MOVIE_CREDITS: string;
79
+ TAGGED_IMAGES: string;
80
+ TRANSLATIONS: string;
81
+ TV_CREDITS: string;
82
+ };
71
83
  SEARCH: {
72
84
  MOVIE: string;
73
85
  COLLECTION: string;
package/dist/routes.js CHANGED
@@ -68,6 +68,18 @@ export const ENDPOINTS = {
68
68
  ALTERNATIVE_NAMES: "/alternative_names",
69
69
  IMAGES: "/images",
70
70
  },
71
+ PEOPLE: {
72
+ DETAILS: "/person",
73
+ CHANGES: "/changes",
74
+ COMBINED_CREDITS: "/combined_credits",
75
+ EXTERNAL_IDS: "/external_ids",
76
+ IMAGES: "/images",
77
+ LATEST: "/latest",
78
+ MOVIE_CREDITS: "/movie_credits",
79
+ TAGGED_IMAGES: "/tagged_images",
80
+ TRANSLATIONS: "/translations",
81
+ TV_CREDITS: "/tv_credits",
82
+ },
71
83
  SEARCH: {
72
84
  MOVIE: "/search/movie",
73
85
  COLLECTION: "/search/collection",
package/dist/tmdb.d.ts CHANGED
@@ -23,6 +23,7 @@ import { TVSeasonsAPI } from "./endpoints/tv_seasons";
23
23
  import { TrendingAPI } from "./endpoints/trending";
24
24
  import { ReviewsAPI } from "./endpoints/reviews";
25
25
  import { PeopleListsAPI } from "./endpoints/people_lists";
26
+ import { PeopleAPI } from "./endpoints/people";
26
27
  export declare class TMDB {
27
28
  private client;
28
29
  private options;
@@ -50,6 +51,7 @@ export declare class TMDB {
50
51
  trending: TrendingAPI;
51
52
  reviews: ReviewsAPI;
52
53
  people_lists: PeopleListsAPI;
54
+ people: PeopleAPI;
53
55
  /**
54
56
  * Creates a new TMDB instance.
55
57
  * @param accessToken The TMDB API access token.
package/dist/tmdb.js CHANGED
@@ -25,6 +25,7 @@ import { TVSeasonsAPI } from "./endpoints/tv_seasons";
25
25
  import { TrendingAPI } from "./endpoints/trending";
26
26
  import { ReviewsAPI } from "./endpoints/reviews";
27
27
  import { PeopleListsAPI } from "./endpoints/people_lists";
28
+ import { PeopleAPI } from "./endpoints/people";
28
29
  export class TMDB {
29
30
  client;
30
31
  options; // ** Default options for all requests
@@ -52,6 +53,7 @@ export class TMDB {
52
53
  trending;
53
54
  reviews;
54
55
  people_lists;
56
+ people;
55
57
  // etc...
56
58
  /**
57
59
  * Creates a new TMDB instance.
@@ -62,7 +64,7 @@ export class TMDB {
62
64
  if (!accessToken)
63
65
  throw new Error(Errors.NO_ACCESS_TOKEN);
64
66
  this.options = options;
65
- this.client = new ApiClient(accessToken, { logger: options.logger });
67
+ this.client = new ApiClient(accessToken, { logger: options.logger, deduplication: options.deduplication });
66
68
  this.movies = new MoviesAPI(this.client, this.options);
67
69
  this.movie_lists = new MovieListsAPI(this.client, this.options);
68
70
  this.search = new SearchAPI(this.client, this.options);
@@ -87,5 +89,6 @@ export class TMDB {
87
89
  this.trending = new TrendingAPI(this.client, this.options);
88
90
  this.reviews = new ReviewsAPI(this.client, this.options);
89
91
  this.people_lists = new PeopleListsAPI(this.client, this.options);
92
+ this.people = new PeopleAPI(this.client, this.options);
90
93
  }
91
94
  }
@@ -33,4 +33,14 @@ export type TMDBOptions = {
33
33
  * - Pass a function to customize logging output.
34
34
  */
35
35
  logger?: boolean | TMDBLoggerFn;
36
+ /**
37
+ * Controls whether concurrent identical requests are deduplicated.
38
+ * When `true` (default), multiple in-flight calls with the same endpoint and
39
+ * parameters share a single fetch and resolve from the same Promise.
40
+ * Set to `false` to disable deduplication — every call always triggers its
41
+ * own fetch. Useful for polling loops, force-refreshes after mutations, or
42
+ * any scenario where stale in-flight data must not be reused.
43
+ * @default true
44
+ */
45
+ deduplication?: boolean;
36
46
  };
@@ -18,3 +18,4 @@ export * from "./keywords";
18
18
  export * from "./credits";
19
19
  export * from "./reviews";
20
20
  export * from "./people-lists";
21
+ export * from "./people";
@@ -18,3 +18,4 @@ export * from "./keywords";
18
18
  export * from "./credits";
19
19
  export * from "./reviews";
20
20
  export * from "./people-lists";
21
+ export * from "./people";
@@ -0,0 +1,214 @@
1
+ import { Changes, DateRange, ImageItem, ImagesResult, MediaType, PaginatedResponse, TranslationResults, WithLanguage, WithPage } from "./common";
2
+ import { MovieResultItem, TVSeriesResultItem } from "./search";
3
+ import { Prettify } from "./utility";
4
+ /**
5
+ * Top-level person details returned by TMDB.
6
+ */
7
+ export type PersonDetails = {
8
+ /** Whether the person is marked as adult content */
9
+ adult: boolean;
10
+ /** Alternative names or aliases */
11
+ also_known_as: string[];
12
+ /** Localized or default biography text */
13
+ biography?: string;
14
+ /** Date of birth in ISO 8601 format */
15
+ birthday?: string;
16
+ /** Date of death in ISO 8601 format, if applicable */
17
+ deathday?: string;
18
+ /** Gender code reported by TMDB */
19
+ gender: number;
20
+ /** Official homepage URL, if available */
21
+ homepage?: string;
22
+ /** TMDB person identifier */
23
+ id: number;
24
+ /** IMDb identifier, if linked */
25
+ imdb_id?: string;
26
+ /** Primary department the person is known for */
27
+ known_for_department?: string;
28
+ /** Display name */
29
+ name: string;
30
+ /** Place of birth, if known */
31
+ place_of_birth?: string;
32
+ /** Popularity score */
33
+ popularity: number;
34
+ /** Relative path to the profile image */
35
+ profile_path?: string;
36
+ };
37
+ /**
38
+ * Endpoints that can be appended to a person details request.
39
+ */
40
+ export type PersonAppendToResponseNamespace = "changes" | "combined_credits" | "external_ids" | "images" | "movie_credits" | "tagged_images" | "translations" | "tv_credits";
41
+ /**
42
+ * Person details with appended subresources.
43
+ */
44
+ export type PersonDetailsWithAppends<T extends readonly PersonAppendToResponseNamespace[]> = PersonDetails & {
45
+ [K in T[number]]: PersonAppendableMap[K];
46
+ };
47
+ /**
48
+ * Changes made to a person resource.
49
+ */
50
+ export type PersonChanges = Changes;
51
+ /**
52
+ * External identifiers linked to a person.
53
+ */
54
+ export type PersonExternalIDs = {
55
+ id: number;
56
+ freebase_mid?: string;
57
+ freebase_id?: string;
58
+ imdb_id?: string;
59
+ tvrage_id?: number;
60
+ wikidata_id?: string;
61
+ facebook_id?: string;
62
+ instagram_id?: string;
63
+ tiktok_id?: string;
64
+ twitter_id?: string;
65
+ youtube_id?: string;
66
+ };
67
+ /**
68
+ * Profile images belonging to a person.
69
+ */
70
+ export type PersonImages = ImagesResult<ImageItem, "profiles">;
71
+ /**
72
+ * Cast credit for a movie on a person profile.
73
+ */
74
+ export type PersonMovieCastCredit = MovieResultItem & {
75
+ character: string;
76
+ credit_id: string;
77
+ order: number;
78
+ };
79
+ /**
80
+ * Crew credit for a movie on a person profile.
81
+ */
82
+ export type PersonMovieCrewCredit = MovieResultItem & {
83
+ credit_id: string;
84
+ department: string;
85
+ job: string;
86
+ };
87
+ /**
88
+ * Movie credits for a person.
89
+ */
90
+ export type PersonMovieCredits = {
91
+ id: number;
92
+ cast: PersonMovieCastCredit[];
93
+ crew: PersonMovieCrewCredit[];
94
+ };
95
+ /**
96
+ * Cast credit for a TV show on a person profile.
97
+ */
98
+ export type PersonTVCastCredit = TVSeriesResultItem & {
99
+ character: string;
100
+ credit_id: string;
101
+ episode_count: number;
102
+ };
103
+ /**
104
+ * Crew credit for a TV show on a person profile.
105
+ */
106
+ export type PersonTVCrewCredit = TVSeriesResultItem & {
107
+ credit_id: string;
108
+ department: string;
109
+ episode_count: number;
110
+ job: string;
111
+ };
112
+ /**
113
+ * TV credits for a person.
114
+ */
115
+ export type PersonTVCredits = {
116
+ id: number;
117
+ cast: PersonTVCastCredit[];
118
+ crew: PersonTVCrewCredit[];
119
+ };
120
+ /**
121
+ * Combined movie/TV cast credit for a person.
122
+ */
123
+ export type PersonCombinedCastCredit = (PersonMovieCastCredit & {
124
+ media_type: "movie";
125
+ }) | (PersonTVCastCredit & {
126
+ media_type: "tv";
127
+ });
128
+ /**
129
+ * Combined movie/TV crew credit for a person.
130
+ */
131
+ export type PersonCombinedCrewCredit = (PersonMovieCrewCredit & {
132
+ media_type: "movie";
133
+ }) | (PersonTVCrewCredit & {
134
+ media_type: "tv";
135
+ });
136
+ /**
137
+ * Combined credits for a person.
138
+ */
139
+ export type PersonCombinedCredits = {
140
+ id: number;
141
+ cast: PersonCombinedCastCredit[];
142
+ crew: PersonCombinedCrewCredit[];
143
+ };
144
+ /**
145
+ * Tagged image media payload associated with a person image tag.
146
+ */
147
+ export type PersonTaggedImageMedia = (MovieResultItem & {
148
+ media_type: "movie";
149
+ }) | (TVSeriesResultItem & {
150
+ media_type: "tv";
151
+ });
152
+ /**
153
+ * A single tagged image entry for a person.
154
+ */
155
+ export type PersonTaggedImage = ImageItem & {
156
+ id: string;
157
+ image_type: string;
158
+ media: PersonTaggedImageMedia;
159
+ media_type: MediaType;
160
+ };
161
+ /**
162
+ * Paginated tagged images for a person.
163
+ */
164
+ export type PersonTaggedImages = PaginatedResponse<PersonTaggedImage> & {
165
+ id: number;
166
+ };
167
+ /**
168
+ * Translation payload for person records.
169
+ */
170
+ export type PersonTranslationData = {
171
+ biography?: string;
172
+ name?: string;
173
+ };
174
+ /**
175
+ * Available translations for a person.
176
+ */
177
+ export type PersonTranslations = TranslationResults<PersonTranslationData>;
178
+ /**
179
+ * Mapping of person append keys to their response types.
180
+ */
181
+ export type PersonAppendableMap = {
182
+ changes: PersonChanges;
183
+ combined_credits: PersonCombinedCredits;
184
+ external_ids: PersonExternalIDs;
185
+ images: PersonImages;
186
+ movie_credits: PersonMovieCredits;
187
+ tagged_images: PersonTaggedImages;
188
+ translations: PersonTranslations;
189
+ tv_credits: PersonTVCredits;
190
+ };
191
+ /**
192
+ * Base param for person-specific endpoints.
193
+ */
194
+ export type PersonBaseParam = {
195
+ person_id: number;
196
+ };
197
+ /**
198
+ * Parameters for fetching person details.
199
+ */
200
+ export type PersonDetailsParams = Prettify<PersonBaseParam & {
201
+ append_to_response?: PersonAppendToResponseNamespace | PersonAppendToResponseNamespace[];
202
+ } & WithLanguage>;
203
+ /**
204
+ * Parameters for fetching person changes.
205
+ */
206
+ export type PersonChangesParams = Prettify<PersonBaseParam & WithPage & DateRange>;
207
+ /**
208
+ * Parameters for language-aware person credit endpoints.
209
+ */
210
+ export type PersonCreditsParams = Prettify<PersonBaseParam & WithLanguage>;
211
+ export type PersonExternalIDsParams = PersonBaseParam;
212
+ export type PersonImagesParams = PersonBaseParam;
213
+ export type PersonTranslationsParams = PersonBaseParam;
214
+ export type PersonTaggedImagesParams = Prettify<PersonBaseParam & WithPage>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,2 @@
1
1
  export * from "./logger";
2
+ export * from "./jwt";
@@ -1 +1,2 @@
1
1
  export * from "./logger";
2
+ export * from "./jwt";
@@ -0,0 +1 @@
1
+ export declare function isJwt(token: string): boolean;
@@ -0,0 +1,61 @@
1
+ function isBase64Url(str) {
2
+ return /^[A-Za-z0-9\-_]+$/.test(str);
3
+ }
4
+ function decodeBase64Url(str) {
5
+ try {
6
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
7
+ const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
8
+ return atob(padded);
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ function isObject(value) {
15
+ return typeof value === "object" && value !== null;
16
+ }
17
+ function isJwtHeader(value) {
18
+ if (!isObject(value))
19
+ return false;
20
+ return typeof value.alg === "string";
21
+ }
22
+ function isJwtPayload(value) {
23
+ if (!isObject(value))
24
+ return false;
25
+ if ("exp" in value && typeof value.exp !== "number")
26
+ return false;
27
+ if ("nbf" in value && typeof value.nbf !== "number")
28
+ return false;
29
+ if ("iat" in value && typeof value.iat !== "number")
30
+ return false;
31
+ return true;
32
+ }
33
+ export function isJwt(token) {
34
+ if (typeof token !== "string")
35
+ return false;
36
+ const parts = token.split(".");
37
+ if (parts.length !== 3)
38
+ return false;
39
+ const [headerB64, payloadB64, signatureB64] = parts;
40
+ if (!isBase64Url(headerB64) || !isBase64Url(payloadB64) || !isBase64Url(signatureB64)) {
41
+ return false;
42
+ }
43
+ const headerStr = decodeBase64Url(headerB64);
44
+ const payloadStr = decodeBase64Url(payloadB64);
45
+ if (!headerStr || !payloadStr)
46
+ return false;
47
+ let parsedHeader;
48
+ let parsedPayload;
49
+ try {
50
+ parsedHeader = JSON.parse(headerStr);
51
+ parsedPayload = JSON.parse(payloadStr);
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ if (!isJwtHeader(parsedHeader))
57
+ return false;
58
+ if (!isJwtPayload(parsedPayload))
59
+ return false;
60
+ return true;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorenzopant/tmdb",
3
- "version": "1.16.0",
3
+ "version": "1.17.1",
4
4
  "description": "A completely type-safe The Movie Database (TMDB) API wrapper for typescript applications.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -45,7 +45,7 @@
45
45
  "@eslint/compat": "^2.0.0",
46
46
  "@eslint/eslintrc": "^3.3.1",
47
47
  "@eslint/js": "^9.30.0",
48
- "@types/node": "^22.15.2",
48
+ "@types/node": "^22.19.11",
49
49
  "@typescript-eslint/eslint-plugin": "^8.35.0",
50
50
  "@typescript-eslint/parser": "^8.35.0",
51
51
  "@vitest/coverage-v8": "^4.0.9",