@lorenzopant/tmdb 1.17.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
@@ -5,11 +5,63 @@ export class ApiClient {
5
5
  accessToken;
6
6
  baseUrl = "https://api.themoviedb.org/3";
7
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;
8
16
  constructor(accessToken, options = {}) {
9
17
  this.accessToken = accessToken;
10
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;
11
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
+ */
12
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) {
13
65
  const url = new URL(`${this.baseUrl}${endpoint}`);
14
66
  const jwt = isJwt(this.accessToken);
15
67
  for (const [key, value] of Object.entries(params)) {
package/dist/tmdb.js CHANGED
@@ -64,7 +64,7 @@ export class TMDB {
64
64
  if (!accessToken)
65
65
  throw new Error(Errors.NO_ACCESS_TOKEN);
66
66
  this.options = options;
67
- this.client = new ApiClient(accessToken, { logger: options.logger });
67
+ this.client = new ApiClient(accessToken, { logger: options.logger, deduplication: options.deduplication });
68
68
  this.movies = new MoviesAPI(this.client, this.options);
69
69
  this.movie_lists = new MovieListsAPI(this.client, this.options);
70
70
  this.search = new SearchAPI(this.client, this.options);
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorenzopant/tmdb",
3
- "version": "1.17.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",