@lorenzopant/tmdb 1.20.0 → 1.20.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/README.md CHANGED
@@ -20,122 +20,260 @@ npm install @lorenzopant/tmdb
20
20
  // or
21
21
  pnpm add @lorenzopant/tmdb
22
22
  // or
23
- yard add @lorenzopant/tmdb
23
+ yarn add @lorenzopant/tmdb
24
24
  ```
25
25
 
26
26
  ---
27
27
 
28
- ## Usage
28
+ ## Quick Start
29
29
 
30
30
  ```typescript
31
- import { TMDB } from "@lorenzopant/tmdb";
32
-
33
- const tmdb = new TMDB("your_access_token");
34
-
35
- async function searchMovies() {
36
- try {
37
- const movies = await tmdb.search.movies({ query: "Fight Club" });
38
- console.log(movies);
39
- } catch (error) {
40
- if (error instanceof TMDBError) {
41
- console.error("TMDB Error:", error.message);
42
- console.error("HTTP Status:", error.http_status_code);
43
- console.error("TMDB Status Code:", error.tmdb_status_code);
44
- } else {
45
- console.error("Unknown error:", error);
46
- }
47
- }
48
- }
31
+ import { TMDB, TMDBError } from "@lorenzopant/tmdb";
32
+
33
+ const tmdb = new TMDB(process.env.TMDB_ACCESS_TOKEN!);
49
34
 
50
- searchMovies();
35
+ const movie = await tmdb.movies.details({ movie_id: 550 });
36
+ console.log(movie.title); // "Fight Club"
51
37
  ```
52
38
 
53
39
  ---
54
40
 
55
- ## API
41
+ ## Namespaces
42
+
43
+ Every namespace maps directly to a TMDB API section. All methods are fully typed.
44
+
45
+ | Namespace | Description |
46
+ | ------------------------ | ----------------------------------------------------------------- |
47
+ | `tmdb.movies` | Movie details, credits, images, videos, recommendations, and more |
48
+ | `tmdb.movie_lists` | Now Playing, Popular, Top Rated, Upcoming |
49
+ | `tmdb.tv_series` | TV series details, credits, seasons, episode groups |
50
+ | `tmdb.tv_lists` | Airing Today, On The Air, Popular, Top Rated TV |
51
+ | `tmdb.tv_seasons` | Season details, credits, images |
52
+ | `tmdb.tv_episodes` | Episode details, credits, images |
53
+ | `tmdb.tv_episode_groups` | Episode group details |
54
+ | `tmdb.search` | Search movies, TV shows, people, collections, keywords |
55
+ | `tmdb.discover` | Discover movies and TV shows by filter |
56
+ | `tmdb.trending` | Trending movies, TV, and people |
57
+ | `tmdb.people` | Person details, credits, images, translations |
58
+ | `tmdb.people_lists` | Popular people |
59
+ | `tmdb.collections` | Collection details and images |
60
+ | `tmdb.companies` | Company details, alternative names, images |
61
+ | `tmdb.credits` | Credit details |
62
+ | `tmdb.genres` | Movie and TV genre lists |
63
+ | `tmdb.keywords` | Keyword details and associated movies |
64
+ | `tmdb.certifications` | Movie and TV certifications per region |
65
+ | `tmdb.changes` | Recent content changes |
66
+ | `tmdb.configuration` | TMDB configuration, countries, languages, timezones |
67
+ | `tmdb.find` | Find resources by external ID (IMDb, TVDB, etc.) |
68
+ | `tmdb.networks` | TV network details |
69
+ | `tmdb.watch_providers` | Watch providers by region |
70
+ | `tmdb.lists` | User-created list management |
71
+ | `tmdb.account` | Account details and user lists |
72
+ | `tmdb.authentication` | Token-based authentication flows |
73
+ | `tmdb.guest_sessions` | Guest session rated movies/TV/episodes |
74
+ | `tmdb.reviews` | Review details |
75
+ | `tmdb.images` | Image URL builder (see below) |
76
+ | `tmdb.v4` | TMDB API v4 (auth, account, lists — requires JWT) |
56
77
 
57
- ### `TMDB`
78
+ ---
58
79
 
59
- The main client class. **Each API method supports all the parameters supported by TMDB API**, for example the search method supports: query, language, region, year, primary_release_year and so on...
80
+ ## Constructor Options
60
81
 
61
- #### Constructor
82
+ ```typescript
83
+ const tmdb = new TMDB(accessToken, {
84
+ language: "en-US", // ISO 639-1: default language for all requests
85
+ region: "US", // ISO 3166-1: default region for all requests
86
+ timezone: "America/New_York", // default timezone for TV airing queries
87
+ logger: true, // enable built-in request/response logging
88
+ deduplication: true, // deduplicate concurrent identical requests (default: true)
89
+ rate_limit: true, // auto-queue requests to stay within TMDB rate limits
90
+ cache: true, // enable in-memory TTL-based response caching
91
+ images: {
92
+ secure_images_url: true,
93
+ default_image_sizes: { posters: "w500", backdrops: "w780" },
94
+ },
95
+ });
96
+ ```
97
+
98
+ ### `logger`
62
99
 
63
100
  ```typescript
64
- const tmdb = new TMDB(accessToken: string, options?: TMDBOptions);
101
+ // Built-in console logger
102
+ const tmdb = new TMDB(token, { logger: true });
103
+
104
+ // Custom logger function
105
+ const tmdb = new TMDB(token, {
106
+ logger: (entry) => {
107
+ if (entry.type === "response") {
108
+ console.log(`[TMDB] ${entry.endpoint} → ${entry.status} (${entry.durationMs}ms)`);
109
+ }
110
+ },
111
+ });
65
112
  ```
66
113
 
67
- - `accessToken`: **required**. Your TMDB API v4 access token.
68
- - `options.logger`: Enable debug logging for all network requests.
114
+ ### `rate_limit`
69
115
 
70
- Example:
116
+ Automatically queues requests to stay within TMDB's API limits (~40 req/s). Useful for bulk scripts.
71
117
 
72
118
  ```typescript
73
- const tmdb = new TMDB("your_access_token", { logger: true });
119
+ // Default limits
120
+ const tmdb = new TMDB(token, { rate_limit: true });
121
+
122
+ // Custom budget
123
+ const tmdb = new TMDB(token, { rate_limit: { max_requests: 30, per_ms: 1_000 } });
74
124
  ```
75
125
 
76
- Custom logger:
126
+ ### `cache`
127
+
128
+ In-memory TTL-based caching for GET requests. Entries expire lazily on access.
77
129
 
78
130
  ```typescript
79
- const tmdb = new TMDB("your_access_token", {
80
- logger: (entry) => {
81
- if (entry.type === "response") {
82
- console.log("TMDB:", entry.endpoint, entry.status, entry.durationMs);
83
- }
131
+ // 5-minute TTL, no size limit (defaults)
132
+ const tmdb = new TMDB(token, { cache: true });
133
+
134
+ // Custom TTL and bounded size
135
+ const tmdb = new TMDB(token, {
136
+ cache: {
137
+ ttl: 60_000, // 60 seconds
138
+ max_size: 500, // evict oldest entry when limit is reached
139
+ excluded_endpoints: ["/trending", /\/discover\//],
140
+ },
141
+ });
142
+
143
+ // Runtime cache controls
144
+ tmdb.cache?.invalidate("/movie/now_playing"); // remove one entry
145
+ tmdb.cache?.clear(); // remove all entries
146
+ console.log(tmdb.cache?.size); // number of cached entries
147
+ ```
148
+
149
+ ### `interceptors`
150
+
151
+ Hook into every request or response globally.
152
+
153
+ ```typescript
154
+ const tmdb = new TMDB(token, {
155
+ interceptors: {
156
+ request: (ctx) => {
157
+ // Inject a param into every request
158
+ return { ...ctx, params: { ...ctx.params, include_adult: false } };
159
+ },
160
+ response: {
161
+ onSuccess: (data) => {
162
+ myAnalytics.track("tmdb_response", data);
163
+ },
164
+ onError: (error) => {
165
+ Sentry.captureException(error);
166
+ },
167
+ },
84
168
  },
85
169
  });
86
170
  ```
87
171
 
88
172
  ---
89
173
 
90
- ### `Search`
174
+ ## Examples
91
175
 
92
- Search for movies:
176
+ ### Movie details
93
177
 
94
178
  ```typescript
95
- tmdb.search.movies({ query: "Fight Club" });
179
+ const movie = await tmdb.movies.details({ movie_id: 550 });
180
+ console.log(movie.title); // "Fight Club"
181
+ console.log(movie.release_date); // "1999-10-15"
96
182
  ```
97
183
 
98
- Returns a **typed response** containing movies.
184
+ ### `append_to_response` typed overloads
99
185
 
100
- ---
186
+ Fetch related data in a single request. The return type is automatically extended with the appended fields.
187
+
188
+ ```typescript
189
+ const movie = await tmdb.movies.details({
190
+ movie_id: 550,
191
+ append_to_response: ["credits", "videos"],
192
+ });
101
193
 
102
- ### `Movie Lists`
194
+ // TypeScript knows these are present:
195
+ console.log(movie.credits.cast[0].name);
196
+ console.log(movie.videos.results[0].key);
197
+ ```
103
198
 
104
- Now Playing, Popular, Top Rated and Upcoming movies:
199
+ ### Search
105
200
 
106
201
  ```typescript
107
- tmdb.movie_lists.now_playing();
108
- tmdb.movie_lists.top_rated();
109
- tmdb.movie_lists.popular();
110
- tmdb.movie_lists.upcoming();
202
+ const results = await tmdb.search.movies({ query: "Inception", language: "en-US" });
203
+ console.log(results.results[0].title); // "Inception"
111
204
  ```
112
205
 
113
- Returns a **typed response** containing movies.
206
+ ### TV series
114
207
 
115
- ---
208
+ ```typescript
209
+ const show = await tmdb.tv_series.details({ series_id: 1396 });
210
+ console.log(show.name); // "Breaking Bad"
116
211
 
117
- ### `Movie`
212
+ const season = await tmdb.tv_seasons.details({ series_id: 1396, season_number: 1 });
213
+ console.log(season.episodes.length);
214
+ ```
118
215
 
119
- Details, alternative titles, changes, credits, external IDs and more:
216
+ ### Discover
120
217
 
121
218
  ```typescript
122
- tmdb.movie.details({ movie_id: 550 });
123
- tmdb.movie.alternative_titles({ movie_id: 550 });
124
- tmdb.movie.changes({ movie_id: 550 });
125
- tmdb.movie.credits({ movie_id: 550 });
126
- tmdb.movie.external_ids({ movie_id: 550 });
219
+ const action = await tmdb.discover.movies({
220
+ with_genres: "28",
221
+ sort_by: "vote_average.desc",
222
+ "vote_count.gte": 1000,
223
+ });
224
+ ```
127
225
 
128
- ...and more
226
+ ### Trending
227
+
228
+ ```typescript
229
+ const trending = await tmdb.trending.movies({ time_window: "week" });
129
230
  ```
130
231
 
131
- Returns a **typed response** containing movies.
232
+ ### Image URLs
233
+
234
+ ```typescript
235
+ const movie = await tmdb.movies.details({ movie_id: 550 });
236
+
237
+ // Build a full URL from a path
238
+ const posterUrl = tmdb.images.poster(movie.poster_path!, "w500");
239
+ const backdropUrl = tmdb.images.backdrop(movie.backdrop_path!, "w1280");
240
+
241
+ // Or enable auto-enrichment so all image paths in every response are resolved automatically
242
+ const tmdb = new TMDB(token, {
243
+ images: { autocomplete_images: true, secure_images_url: true },
244
+ });
245
+ ```
246
+
247
+ ### Error handling
248
+
249
+ ```typescript
250
+ import { TMDB, TMDBError } from "@lorenzopant/tmdb";
251
+
252
+ try {
253
+ const movie = await tmdb.movies.details({ movie_id: 0 });
254
+ } catch (error) {
255
+ if (error instanceof TMDBError) {
256
+ console.error(error.message); // human-readable message
257
+ console.error(error.http_status_code); // e.g. 404
258
+ console.error(error.tmdb_status_code); // TMDB-specific status code
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### TMDB API v4 (requires JWT access token)
264
+
265
+ ```typescript
266
+ const tmdb = new TMDB(jwtAccessToken);
267
+
268
+ const lists = await tmdb.v4.lists.list({ account_id: "me" });
269
+ ```
132
270
 
133
271
  ---
134
272
 
135
273
  ## Requirements
136
274
 
137
- - Node.js 18+ recommended
138
- - Works on frontend (React, Vue) but **don't expose sensitive access tokens** to users!
275
+ - Node.js 18+
276
+ - Works in frontend frameworks (React, Vue, Next.js) **never expose your access token to the browser in production**
139
277
 
140
278
  ---
141
279
 
package/dist/index.d.mts CHANGED
@@ -522,6 +522,43 @@ declare class TMDBError extends Error {
522
522
  constructor(message: string, http_status: number, tmdb_status_code?: number);
523
523
  }
524
524
  //#endregion
525
+ //#region src/utils/cache.d.ts
526
+ type CacheOptions = {
527
+ /**
528
+ * How long (in milliseconds) each cached entry remains valid.
529
+ * @default 300_000 (5 minutes)
530
+ */
531
+ ttl?: number;
532
+ /**
533
+ * Maximum number of entries to hold in memory.
534
+ * When the store is full the oldest entry is evicted before a new one is inserted.
535
+ * Defaults to unlimited.
536
+ */
537
+ max_size?: number;
538
+ /**
539
+ * Endpoints that should never be cached.
540
+ *
541
+ * Each pattern is matched against the full cache key (`endpoint?param=value&…`).
542
+ * - A `string` is matched with `key.startsWith(pattern)`.
543
+ * - A `RegExp` is tested with `pattern.test(key)`.
544
+ *
545
+ * `RegExp` patterns with the global (`g`) or sticky (`y`) flag are automatically
546
+ * normalized by stripping those flags. Stateful `lastIndex` mutations would otherwise
547
+ * cause flaky cache behaviour across repeated calls.
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * // Never cache trending or any discover endpoint
552
+ * const tmdb = new TMDB(token, {
553
+ * cache: {
554
+ * excluded_endpoints: ["/trending", /\/discover\//],
555
+ * },
556
+ * });
557
+ * ```
558
+ */
559
+ excluded_endpoints?: (string | RegExp)[];
560
+ };
561
+ //#endregion
525
562
  //#region src/utils/logger.d.ts
526
563
  type TMDBLoggerEntry = {
527
564
  type: "request" | "response" | "error";
@@ -1474,6 +1511,28 @@ type TMDBOptions = {
1474
1511
  * ```
1475
1512
  */
1476
1513
  rate_limit?: boolean | RateLimitOptions;
1514
+ /**
1515
+ * Enables in-memory TTL-based caching for GET requests.
1516
+ *
1517
+ * - `true` — uses the default TTL of 5 minutes with no size limit.
1518
+ * - Pass a {@link CacheOptions} object to customize `ttl` and/or `max_size`.
1519
+ *
1520
+ * Cached responses are served immediately without hitting the network.
1521
+ * Entries expire lazily on access once the TTL has elapsed.
1522
+ * Only `GET` requests are cached; mutations are never cached.
1523
+ *
1524
+ * @default false (disabled)
1525
+ *
1526
+ * @example
1527
+ * ```ts
1528
+ * // Enable with defaults (5-minute TTL)
1529
+ * const tmdb = new TMDB(token, { cache: true });
1530
+ *
1531
+ * // Custom TTL and bounded size
1532
+ * const tmdb = new TMDB(token, { cache: { ttl: 60_000, max_size: 500 } });
1533
+ * ```
1534
+ */
1535
+ cache?: boolean | CacheOptions;
1477
1536
  };
1478
1537
  //#endregion
1479
1538
  //#region src/types/common/certifications.d.ts
@@ -3684,12 +3743,14 @@ declare class ApiClient {
3684
3743
  private onSuccessInterceptor?;
3685
3744
  private onErrorInterceptor?;
3686
3745
  private imageApi?;
3746
+ private responseCache?;
3687
3747
  constructor(accessToken: string, options?: {
3688
3748
  /** @internal API version to target. Used by TMDBv4 — not exposed in TMDBOptions. */version?: 3 | 4;
3689
3749
  logger?: boolean | TMDBLoggerFn;
3690
3750
  deduplication?: boolean;
3691
3751
  images?: ImagesConfig;
3692
3752
  rate_limit?: boolean | RateLimitOptions;
3753
+ cache?: boolean | CacheOptions;
3693
3754
  interceptors?: {
3694
3755
  request?: RequestInterceptor | RequestInterceptor[];
3695
3756
  response?: {
@@ -3723,6 +3784,20 @@ declare class ApiClient {
3723
3784
  * `TMDBOptions.deduplication = false`.
3724
3785
  */
3725
3786
  request<T>(endpoint: string, params?: Record<string, unknown | undefined>): Promise<T>;
3787
+ /**
3788
+ * Removes a single entry from the response cache.
3789
+ *
3790
+ * The key is built from `endpoint` + `params` using the same deterministic algorithm
3791
+ * as the cache itself, so the arguments must match exactly what was used in the original
3792
+ * request (including parameter values and casing).
3793
+ *
3794
+ * @returns `true` if an entry was found and removed, `false` if it was not cached.
3795
+ */
3796
+ invalidateCache(endpoint: string, params?: Record<string, unknown>): boolean;
3797
+ /** Removes all entries from the response cache. */
3798
+ clearCache(): void;
3799
+ /** Returns the number of entries currently held in the response cache. */
3800
+ get cacheSize(): number;
3726
3801
  /**
3727
3802
  * Runs all registered request interceptors in order, threading the context through each one.
3728
3803
  * If an interceptor returns a new context, it replaces the current context for the next interceptor.
@@ -3752,6 +3827,10 @@ declare class ApiClient {
3752
3827
  /**
3753
3828
  * Shared fetch + response-parsing pipeline used by both `request` and `mutate`.
3754
3829
  * Handles URL construction, auth, logging, error mapping, and null sanitisation.
3830
+ *
3831
+ * When called from `request()`, interceptors have already been applied and
3832
+ * `endpoint`/`params` are the effective (post-interceptor) values — interceptors
3833
+ * are skipped. When called from `mutate()`, interceptors run here as normal.
3755
3834
  */
3756
3835
  private execute;
3757
3836
  }
@@ -5696,11 +5775,8 @@ declare class V4ListsAPI extends TMDBAPIBase {
5696
5775
  */
5697
5776
  declare class TMDBv4 {
5698
5777
  private client;
5699
- /** v4 authentication — request token → access token → logout. */
5700
5778
  auth: V4AuthAPI;
5701
- /** v4 account — details, lists, favorites, watchlist, rated. */
5702
5779
  account: V4AccountAPI;
5703
- /** v4 lists — full CRUD for user-created lists. */
5704
5780
  lists: V4ListsAPI;
5705
5781
  constructor(accessToken: string, options?: TMDBOptions);
5706
5782
  }
@@ -5745,6 +5821,27 @@ declare class TMDB {
5745
5821
  */
5746
5822
  get v4(): TMDBv4;
5747
5823
  private _v4;
5824
+ /**
5825
+ * Response cache controls. Only available when `cache` was set in the constructor options.
5826
+ *
5827
+ * - `clear()` — remove all cached entries (e.g. after a user signs out or state resets).
5828
+ * - `invalidate(endpoint, params?)` — remove a single entry by endpoint + params.
5829
+ * - `size` — number of entries currently held in memory.
5830
+ *
5831
+ * @example
5832
+ * ```ts
5833
+ * // Invalidate now_playing after a mutation
5834
+ * tmdb.cache?.invalidate("/movie/now_playing");
5835
+ *
5836
+ * // Clear everything
5837
+ * tmdb.cache?.clear();
5838
+ * ```
5839
+ */
5840
+ get cache(): {
5841
+ clear(): void;
5842
+ invalidate(endpoint: string, params?: Record<string, unknown>): boolean;
5843
+ readonly size: number;
5844
+ } | undefined;
5748
5845
  /**
5749
5846
  * Creates a new TMDB instance.
5750
5847
  * @param accessToken The TMDB API access token.