tmdb-kit 1.0.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/README.md +455 -196
- package/dist/cli.js +3823 -0
- package/dist/client.d.cts +71 -0
- package/dist/client.d.ts +71 -0
- package/dist/errors.d.cts +86 -0
- package/dist/errors.d.ts +86 -0
- package/dist/helpers/cache.d.cts +28 -0
- package/dist/helpers/cache.d.ts +28 -0
- package/dist/helpers/pagination.d.cts +35 -0
- package/dist/helpers/pagination.d.ts +35 -0
- package/dist/helpers/retry.d.cts +47 -0
- package/dist/helpers/retry.d.ts +47 -0
- package/dist/helpers/timeout.d.cts +20 -0
- package/dist/helpers/timeout.d.ts +20 -0
- package/dist/http.d.cts +80 -0
- package/dist/http.d.ts +80 -0
- package/dist/index.cjs +1115 -768
- package/dist/index.cjs.map +27 -1
- package/dist/index.d.cts +13 -783
- package/dist/index.d.ts +13 -783
- package/dist/index.js +1074 -744
- package/dist/index.js.map +27 -1
- package/dist/query.d.cts +91 -0
- package/dist/query.d.ts +91 -0
- package/dist/resources/configuration.d.cts +17 -0
- package/dist/resources/configuration.d.ts +17 -0
- package/dist/resources/genres.d.cts +13 -0
- package/dist/resources/genres.d.ts +13 -0
- package/dist/resources/images.d.cts +17 -0
- package/dist/resources/images.d.ts +17 -0
- package/dist/resources/movies.d.cts +21 -0
- package/dist/resources/movies.d.ts +21 -0
- package/dist/resources/people.d.cts +14 -0
- package/dist/resources/people.d.ts +14 -0
- package/dist/resources/search.d.cts +15 -0
- package/dist/resources/search.d.ts +15 -0
- package/dist/resources/trending.d.cts +23 -0
- package/dist/resources/trending.d.ts +23 -0
- package/dist/resources/tv.d.cts +30 -0
- package/dist/resources/tv.d.ts +30 -0
- package/dist/search-guards.d.cts +32 -0
- package/dist/search-guards.d.ts +32 -0
- package/dist/types.d.cts +550 -0
- package/dist/types.d.ts +550 -0
- package/package.json +26 -19
package/dist/index.cjs
CHANGED
|
@@ -1,823 +1,1170 @@
|
|
|
1
|
-
Object.defineProperty
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
statusCode;
|
|
24
|
-
statusMessage;
|
|
25
|
-
statusText;
|
|
26
|
-
url;
|
|
27
|
-
constructor(message, options) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.name = "TMDBResponseError";
|
|
30
|
-
this.body = options.body;
|
|
31
|
-
this.headers = options.headers;
|
|
32
|
-
this.requestId = options.headers.get("x-request-id");
|
|
33
|
-
this.status = options.status;
|
|
34
|
-
this.statusText = options.statusText;
|
|
35
|
-
this.url = options.url;
|
|
36
|
-
const body = isErrorResponseBody(options.body) ? options.body : void 0;
|
|
37
|
-
this.statusCode = body?.status_code;
|
|
38
|
-
this.statusMessage = body?.status_message;
|
|
39
|
-
}
|
|
40
|
-
/** True when TMDB has asked the caller to back off (HTTP 429). */
|
|
41
|
-
get isRateLimit() {
|
|
42
|
-
return this.status === 429;
|
|
43
|
-
}
|
|
44
|
-
/** True when the resource was not found (HTTP 404). */
|
|
45
|
-
get isNotFound() {
|
|
46
|
-
return this.status === 404;
|
|
47
|
-
}
|
|
48
|
-
/** True when the request was rejected as unauthenticated (HTTP 401). */
|
|
49
|
-
get isUnauthorized() {
|
|
50
|
-
return this.status === 401;
|
|
51
|
-
}
|
|
52
|
-
/** True when the request was rejected as forbidden (HTTP 403). */
|
|
53
|
-
get isForbidden() {
|
|
54
|
-
return this.status === 403;
|
|
55
|
-
}
|
|
56
|
-
/** True when the upstream returned a 5xx server error. */
|
|
57
|
-
get isServerError() {
|
|
58
|
-
return this.status >= 500 && this.status < 600;
|
|
59
|
-
}
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
60
23
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
75
37
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
38
|
+
|
|
39
|
+
// src/index.ts
|
|
40
|
+
var exports_src = {};
|
|
41
|
+
__export(exports_src, {
|
|
42
|
+
KNOWN_BACKDROP_SIZES: () => KNOWN_BACKDROP_SIZES,
|
|
43
|
+
KNOWN_IMAGE_SIZES: () => KNOWN_IMAGE_SIZES,
|
|
44
|
+
KNOWN_LOGO_SIZES: () => KNOWN_LOGO_SIZES,
|
|
45
|
+
KNOWN_POSTER_SIZES: () => KNOWN_POSTER_SIZES,
|
|
46
|
+
KNOWN_PROFILE_SIZES: () => KNOWN_PROFILE_SIZES,
|
|
47
|
+
KNOWN_STILL_SIZES: () => KNOWN_STILL_SIZES,
|
|
48
|
+
TMDBClient: () => TMDBClient,
|
|
49
|
+
TMDBError: () => TMDBError,
|
|
50
|
+
TMDBHttpClient: () => TMDBHttpClient,
|
|
51
|
+
TMDBRateLimitError: () => TMDBRateLimitError,
|
|
52
|
+
TMDBRequestError: () => TMDBRequestError,
|
|
53
|
+
TMDBResponseError: () => TMDBResponseError,
|
|
54
|
+
TMDBTimeoutError: () => TMDBTimeoutError,
|
|
55
|
+
buildImageUrl: () => buildImageUrl,
|
|
56
|
+
createTMDB: () => createTMDB,
|
|
57
|
+
isKnownBackdropSize: () => isKnownBackdropSize,
|
|
58
|
+
isKnownImageSize: () => isKnownImageSize,
|
|
59
|
+
isKnownLogoSize: () => isKnownLogoSize,
|
|
60
|
+
isKnownPosterSize: () => isKnownPosterSize,
|
|
61
|
+
isKnownProfileSize: () => isKnownProfileSize,
|
|
62
|
+
isKnownStillSize: () => isKnownStillSize,
|
|
63
|
+
isMovie: () => isMovie,
|
|
64
|
+
isMovieSearchResult: () => isMovieSearchResult,
|
|
65
|
+
isPerson: () => isPerson,
|
|
66
|
+
isPersonSearchResult: () => isPersonSearchResult,
|
|
67
|
+
isTV: () => isTV,
|
|
68
|
+
isTVSearchResult: () => isTVSearchResult,
|
|
69
|
+
paginate: () => paginate,
|
|
70
|
+
paginateItems: () => paginateItems,
|
|
71
|
+
retry: () => retry,
|
|
72
|
+
withMemoryCache: () => withMemoryCache,
|
|
73
|
+
withRetry: () => withRetry,
|
|
74
|
+
withTimeout: () => withTimeout
|
|
75
|
+
});
|
|
76
|
+
module.exports = __toCommonJS(exports_src);
|
|
77
|
+
|
|
78
|
+
// src/errors.ts
|
|
79
|
+
class TMDBError extends Error {
|
|
80
|
+
constructor(message, options) {
|
|
81
|
+
super(message, options);
|
|
82
|
+
this.name = "TMDBError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class TMDBRequestError extends TMDBError {
|
|
87
|
+
constructor(message, options) {
|
|
88
|
+
super(message, options);
|
|
89
|
+
this.name = "TMDBRequestError";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
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
|
+
|
|
102
|
+
class TMDBResponseError extends TMDBError {
|
|
103
|
+
body;
|
|
104
|
+
headers;
|
|
105
|
+
requestId;
|
|
106
|
+
status;
|
|
107
|
+
statusCode;
|
|
108
|
+
statusMessage;
|
|
109
|
+
statusText;
|
|
110
|
+
url;
|
|
111
|
+
constructor(message, options) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "TMDBResponseError";
|
|
114
|
+
this.body = options.body;
|
|
115
|
+
this.headers = options.headers;
|
|
116
|
+
this.requestId = options.headers.get("x-request-id");
|
|
117
|
+
this.status = options.status;
|
|
118
|
+
this.statusText = options.statusText;
|
|
119
|
+
this.url = options.url;
|
|
120
|
+
const body = isErrorResponseBody(options.body) ? options.body : undefined;
|
|
121
|
+
this.statusCode = body?.status_code;
|
|
122
|
+
this.statusMessage = body?.status_message;
|
|
123
|
+
}
|
|
124
|
+
get isRateLimit() {
|
|
125
|
+
return this.status === 429;
|
|
126
|
+
}
|
|
127
|
+
get isNotFound() {
|
|
128
|
+
return this.status === 404;
|
|
129
|
+
}
|
|
130
|
+
get isUnauthorized() {
|
|
131
|
+
return this.status === 401;
|
|
132
|
+
}
|
|
133
|
+
get isForbidden() {
|
|
134
|
+
return this.status === 403;
|
|
135
|
+
}
|
|
136
|
+
get isServerError() {
|
|
137
|
+
return this.status >= 500 && this.status < 600;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
class TMDBRateLimitError extends TMDBResponseError {
|
|
142
|
+
retryAfter;
|
|
143
|
+
constructor(message, options) {
|
|
144
|
+
super(message, options);
|
|
145
|
+
this.name = "TMDBRateLimitError";
|
|
146
|
+
this.retryAfter = options.retryAfter;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
83
149
|
function createResponseError(options) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* Falls back to 1 second when the header is missing or unparseable.
|
|
95
|
-
*/
|
|
150
|
+
const body = isErrorResponseBody(options.body) ? options.body : undefined;
|
|
151
|
+
const message = body?.status_message ?? `${options.status} ${options.statusText}`.trim();
|
|
152
|
+
if (options.status === 429) {
|
|
153
|
+
return new TMDBRateLimitError(message, {
|
|
154
|
+
...options,
|
|
155
|
+
retryAfter: parseRetryAfter(options.headers.get("retry-after"))
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return new TMDBResponseError(message, options);
|
|
159
|
+
}
|
|
96
160
|
function parseRetryAfter(value) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
161
|
+
if (value === null || value === undefined) {
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
const trimmed = value.trim();
|
|
165
|
+
if (trimmed === "") {
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
const seconds = Number(trimmed);
|
|
169
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
170
|
+
return seconds;
|
|
171
|
+
}
|
|
172
|
+
const date = Date.parse(trimmed);
|
|
173
|
+
if (Number.isFinite(date)) {
|
|
174
|
+
const deltaMs = date - Date.now();
|
|
175
|
+
return Math.max(1, Math.ceil(deltaMs / 1000));
|
|
176
|
+
}
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
110
179
|
function isErrorResponseBody(value) {
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* - Arrays are joined with commas (TMDB convention).
|
|
118
|
-
* - All values are converted to strings.
|
|
119
|
-
*/
|
|
180
|
+
return typeof value === "object" && value !== null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/query.ts
|
|
184
|
+
var DEFAULT_API_BASE_URL = "https://api.themoviedb.org/3";
|
|
185
|
+
var DEFAULT_IMAGE_BASE_URL = "https://image.tmdb.org/t/p";
|
|
120
186
|
function appendQuery(url, query) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*
|
|
133
|
-
* If `query.language` is set it takes precedence; otherwise the client's
|
|
134
|
-
* default language is used.
|
|
135
|
-
*/
|
|
187
|
+
for (const [key, value] of Object.entries(query)) {
|
|
188
|
+
if (value === undefined || value === null) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
url.searchParams.set(key, value.map(String).join(","));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
url.searchParams.set(key, String(value));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
136
198
|
function withLanguage(defaults, query) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Merge language and region defaults for list endpoints.
|
|
145
|
-
*
|
|
146
|
-
* Combines {@link withLanguage} with region fallback from client defaults.
|
|
147
|
-
*/
|
|
199
|
+
const language = typeof query.language === "string" ? query.language : defaults.language;
|
|
200
|
+
return {
|
|
201
|
+
...query,
|
|
202
|
+
language
|
|
203
|
+
};
|
|
204
|
+
}
|
|
148
205
|
function withListDefaults(defaults, query) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Convert a string or string array to a comma-separated list.
|
|
157
|
-
*
|
|
158
|
-
* Returns `undefined` if the input is `undefined` (skips the parameter).
|
|
159
|
-
* TMDB uses comma-separated values for multi-value query parameters.
|
|
160
|
-
*/
|
|
206
|
+
const region = typeof query.region === "string" ? query.region : defaults.region;
|
|
207
|
+
return {
|
|
208
|
+
...withLanguage(defaults, query),
|
|
209
|
+
region
|
|
210
|
+
};
|
|
211
|
+
}
|
|
161
212
|
function toCommaList(value) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*
|
|
168
|
-
* @throws {TMDBRequestError} If `value` is not a positive integer.
|
|
169
|
-
*/
|
|
213
|
+
if (value === undefined) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
return typeof value === "string" ? value : value.join(",");
|
|
217
|
+
}
|
|
170
218
|
function toId(value, name) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
219
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
220
|
+
throw new TMDBRequestError(`${name} must be a positive integer`);
|
|
221
|
+
}
|
|
222
|
+
return String(value);
|
|
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
|
+
}
|
|
179
237
|
function toSearchQuery(value) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
*
|
|
187
|
-
* @throws {TMDBRequestError} If `value` is not `"day"` or `"week"`.
|
|
188
|
-
*/
|
|
238
|
+
const query = value.trim();
|
|
239
|
+
if (query.length === 0) {
|
|
240
|
+
throw new TMDBRequestError("search query must not be empty");
|
|
241
|
+
}
|
|
242
|
+
return query;
|
|
243
|
+
}
|
|
189
244
|
function toTimeWindow(value) {
|
|
190
|
-
|
|
191
|
-
|
|
245
|
+
if (value !== "day" && value !== "week") {
|
|
246
|
+
throw new TMDBRequestError('timeWindow must be either "day" or "week"');
|
|
247
|
+
}
|
|
248
|
+
return value;
|
|
192
249
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Build a full TMDB image URL from the path returned by API payloads.
|
|
195
|
-
* Returns null for nullish image paths so UI code can branch naturally.
|
|
196
|
-
*/
|
|
197
250
|
function buildImageUrl(path, size = "original", options = {}) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
251
|
+
if (path === undefined || path === null || path.trim() === "") {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_IMAGE_BASE_URL);
|
|
255
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
256
|
+
const url = `${baseUrl}/${size}${normalizedPath}`;
|
|
257
|
+
return options.transform ? options.transform(url) : url;
|
|
201
258
|
}
|
|
202
|
-
/**
|
|
203
|
-
* Strip trailing slashes from a base URL to ensure consistent path joining.
|
|
204
|
-
*/
|
|
205
259
|
function normalizeBaseUrl(baseUrl) {
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
* text for certain error conditions).
|
|
295
|
-
*/
|
|
260
|
+
return baseUrl.replace(/\/+$/, "");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/http.ts
|
|
264
|
+
class TMDBHttpClient {
|
|
265
|
+
defaults;
|
|
266
|
+
imageTransform;
|
|
267
|
+
#apiKey;
|
|
268
|
+
#accessToken;
|
|
269
|
+
#baseUrl;
|
|
270
|
+
#fetch;
|
|
271
|
+
#headers;
|
|
272
|
+
constructor(options) {
|
|
273
|
+
const accessToken = normalizeSecret("accessToken" in options ? options.accessToken : undefined);
|
|
274
|
+
const apiKey = normalizeSecret("apiKey" in options ? options.apiKey : undefined);
|
|
275
|
+
if (Boolean(accessToken) === Boolean(apiKey)) {
|
|
276
|
+
throw new TMDBRequestError("Provide exactly one of accessToken or apiKey");
|
|
277
|
+
}
|
|
278
|
+
this.#accessToken = accessToken;
|
|
279
|
+
this.#apiKey = apiKey;
|
|
280
|
+
this.#baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_API_BASE_URL);
|
|
281
|
+
this.#fetch = options.fetch;
|
|
282
|
+
this.#headers = options.headers;
|
|
283
|
+
this.defaults = {
|
|
284
|
+
imageBaseUrl: normalizeBaseUrl(options.imageBaseUrl ?? DEFAULT_IMAGE_BASE_URL),
|
|
285
|
+
language: options.defaultLanguage,
|
|
286
|
+
region: options.defaultRegion
|
|
287
|
+
};
|
|
288
|
+
this.imageTransform = options.images?.transform;
|
|
289
|
+
}
|
|
290
|
+
async get(path, options = {}) {
|
|
291
|
+
const url = this.#createUrl(path, options.query ?? {});
|
|
292
|
+
const headers = this.#createHeaders(options.headers);
|
|
293
|
+
const fetchImpl = this.#resolveFetch();
|
|
294
|
+
let response;
|
|
295
|
+
try {
|
|
296
|
+
response = await fetchImpl(url, {
|
|
297
|
+
headers,
|
|
298
|
+
method: "GET",
|
|
299
|
+
signal: options.signal
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
throw new TMDBRequestError(`TMDB request failed: ${String(error)}`, { cause: error });
|
|
303
|
+
}
|
|
304
|
+
const body = await readJson(response);
|
|
305
|
+
if (!response.ok) {
|
|
306
|
+
throw createResponseError({
|
|
307
|
+
body,
|
|
308
|
+
headers: response.headers,
|
|
309
|
+
status: response.status,
|
|
310
|
+
statusText: response.statusText,
|
|
311
|
+
url
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return body;
|
|
315
|
+
}
|
|
316
|
+
#resolveFetch() {
|
|
317
|
+
if (this.#fetch !== undefined) {
|
|
318
|
+
return this.#fetch;
|
|
319
|
+
}
|
|
320
|
+
const globalFetch = globalThis.fetch;
|
|
321
|
+
if (typeof globalFetch !== "function") {
|
|
322
|
+
throw new TMDBRequestError("No fetch implementation found. Pass options.fetch in this runtime.");
|
|
323
|
+
}
|
|
324
|
+
return globalFetch.bind(globalThis);
|
|
325
|
+
}
|
|
326
|
+
#createHeaders(headersInit) {
|
|
327
|
+
const headers = new Headers(this.#headers);
|
|
328
|
+
const requestHeaders = new Headers(headersInit);
|
|
329
|
+
headers.set("Accept", "application/json");
|
|
330
|
+
for (const [key, value] of requestHeaders) {
|
|
331
|
+
headers.set(key, value);
|
|
332
|
+
}
|
|
333
|
+
if (this.#accessToken !== undefined) {
|
|
334
|
+
headers.set("Authorization", `Bearer ${this.#accessToken}`);
|
|
335
|
+
}
|
|
336
|
+
return headers;
|
|
337
|
+
}
|
|
338
|
+
#createUrl(path, query) {
|
|
339
|
+
const relativePath = path.replace(/^\/+/, "");
|
|
340
|
+
const url = new URL(relativePath, `${this.#baseUrl}/`);
|
|
341
|
+
appendQuery(url, {
|
|
342
|
+
...query,
|
|
343
|
+
api_key: this.#apiKey
|
|
344
|
+
});
|
|
345
|
+
return url.toString();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
296
348
|
async function readJson(response) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
* strings as undefined (not provided).
|
|
308
|
-
*/
|
|
349
|
+
const text = await response.text();
|
|
350
|
+
if (text.length === 0) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
return JSON.parse(text);
|
|
355
|
+
} catch {
|
|
356
|
+
return text;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
309
359
|
function normalizeSecret(value) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
360
|
+
const secret = value?.trim();
|
|
361
|
+
return secret === "" ? undefined : secret;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/resources/configuration.ts
|
|
365
|
+
class ConfigurationResource {
|
|
366
|
+
#transport;
|
|
367
|
+
constructor(transport) {
|
|
368
|
+
this.#transport = transport;
|
|
369
|
+
}
|
|
370
|
+
details(options = {}) {
|
|
371
|
+
return this.#transport.get("/configuration", { signal: options.signal });
|
|
372
|
+
}
|
|
373
|
+
countries(options = {}) {
|
|
374
|
+
return this.#transport.get("/configuration/countries", { signal: options.signal });
|
|
375
|
+
}
|
|
376
|
+
jobs(options = {}) {
|
|
377
|
+
return this.#transport.get("/configuration/jobs", { signal: options.signal });
|
|
378
|
+
}
|
|
379
|
+
languages(options = {}) {
|
|
380
|
+
return this.#transport.get("/configuration/languages", { signal: options.signal });
|
|
381
|
+
}
|
|
382
|
+
primaryTranslations(options = {}) {
|
|
383
|
+
return this.#transport.get("/configuration/primary_translations", {
|
|
384
|
+
signal: options.signal
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
timezones(options = {}) {
|
|
388
|
+
return this.#transport.get("/configuration/timezones", { signal: options.signal });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/resources/genres.ts
|
|
393
|
+
class GenresResource {
|
|
394
|
+
#transport;
|
|
395
|
+
constructor(transport) {
|
|
396
|
+
this.#transport = transport;
|
|
397
|
+
}
|
|
398
|
+
movies(options = {}) {
|
|
399
|
+
return this.#transport.get("/genre/movie/list", {
|
|
400
|
+
query: withLanguage(this.#transport.defaults, {
|
|
401
|
+
language: options.language
|
|
402
|
+
}),
|
|
403
|
+
signal: options.signal
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
tv(options = {}) {
|
|
407
|
+
return this.#transport.get("/genre/tv/list", {
|
|
408
|
+
query: withLanguage(this.#transport.defaults, {
|
|
409
|
+
language: options.language
|
|
410
|
+
}),
|
|
411
|
+
signal: options.signal
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/resources/images.ts
|
|
417
|
+
class ImagesHelper {
|
|
418
|
+
#transport;
|
|
419
|
+
#transform;
|
|
420
|
+
constructor(transport, transform) {
|
|
421
|
+
this.#transport = transport;
|
|
422
|
+
this.#transform = transform;
|
|
423
|
+
}
|
|
424
|
+
url(path, size = "original", options = {}) {
|
|
425
|
+
return buildImageUrl(path, size, {
|
|
426
|
+
baseUrl: this.#transport.defaults.imageBaseUrl,
|
|
427
|
+
transform: options.transform ?? this.#transform
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/resources/movies.ts
|
|
433
|
+
class MoviesResource {
|
|
434
|
+
#transport;
|
|
435
|
+
constructor(transport) {
|
|
436
|
+
this.#transport = transport;
|
|
437
|
+
}
|
|
438
|
+
popular(options = {}) {
|
|
439
|
+
return this.#transport.get("/movie/popular", {
|
|
440
|
+
query: movieListQuery(this.#transport, options),
|
|
441
|
+
signal: options.signal
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
topRated(options = {}) {
|
|
445
|
+
return this.#transport.get("/movie/top_rated", {
|
|
446
|
+
query: movieListQuery(this.#transport, options),
|
|
447
|
+
signal: options.signal
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
nowPlaying(options = {}) {
|
|
451
|
+
return this.#transport.get("/movie/now_playing", {
|
|
452
|
+
query: movieListQuery(this.#transport, options),
|
|
453
|
+
signal: options.signal
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
upcoming(options = {}) {
|
|
457
|
+
return this.#transport.get("/movie/upcoming", {
|
|
458
|
+
query: movieListQuery(this.#transport, options),
|
|
459
|
+
signal: options.signal
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
details(movieId, options = {}) {
|
|
463
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}`, {
|
|
464
|
+
query: withLanguage(this.#transport.defaults, {
|
|
465
|
+
append_to_response: toCommaList(options.appendToResponse),
|
|
466
|
+
language: options.language
|
|
467
|
+
}),
|
|
468
|
+
signal: options.signal
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
credits(movieId, options = {}) {
|
|
472
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}/credits`, {
|
|
473
|
+
query: withLanguage(this.#transport.defaults, {
|
|
474
|
+
language: options.language
|
|
475
|
+
}),
|
|
476
|
+
signal: options.signal
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
images(movieId, options = {}) {
|
|
480
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}/images`, {
|
|
481
|
+
query: withLanguage(this.#transport.defaults, {
|
|
482
|
+
include_image_language: toCommaList(options.includeImageLanguage),
|
|
483
|
+
language: options.language
|
|
484
|
+
}),
|
|
485
|
+
signal: options.signal
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
recommendations(movieId, options = {}) {
|
|
489
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}/recommendations`, {
|
|
490
|
+
query: movieListQuery(this.#transport, options),
|
|
491
|
+
signal: options.signal
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
similar(movieId, options = {}) {
|
|
495
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}/similar`, {
|
|
496
|
+
query: movieListQuery(this.#transport, options),
|
|
497
|
+
signal: options.signal
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
videos(movieId, options = {}) {
|
|
501
|
+
return this.#transport.get(`/movie/${toId(movieId, "movieId")}/videos`, {
|
|
502
|
+
query: withLanguage(this.#transport.defaults, {
|
|
503
|
+
language: options.language
|
|
504
|
+
}),
|
|
505
|
+
signal: options.signal
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
435
509
|
function movieListQuery(transport, options) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
510
|
+
return withListDefaults(transport.defaults, {
|
|
511
|
+
language: options.language,
|
|
512
|
+
page: options.page,
|
|
513
|
+
region: options.region
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/resources/people.ts
|
|
518
|
+
class PeopleResource {
|
|
519
|
+
#transport;
|
|
520
|
+
constructor(transport) {
|
|
521
|
+
this.#transport = transport;
|
|
522
|
+
}
|
|
523
|
+
popular(options = {}) {
|
|
524
|
+
return this.#transport.get("/person/popular", {
|
|
525
|
+
query: peopleListQuery(this.#transport, options),
|
|
526
|
+
signal: options.signal
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
details(personId, options = {}) {
|
|
530
|
+
return this.#transport.get(`/person/${toId(personId, "personId")}`, {
|
|
531
|
+
query: withLanguage(this.#transport.defaults, {
|
|
532
|
+
language: options.language
|
|
533
|
+
}),
|
|
534
|
+
signal: options.signal
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
combinedCredits(personId, options = {}) {
|
|
538
|
+
return this.#transport.get(`/person/${toId(personId, "personId")}/combined_credits`, {
|
|
539
|
+
query: withLanguage(this.#transport.defaults, {
|
|
540
|
+
language: options.language
|
|
541
|
+
}),
|
|
542
|
+
signal: options.signal
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
462
546
|
function peopleListQuery(transport, options) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
547
|
+
return withLanguage(transport.defaults, {
|
|
548
|
+
language: options.language,
|
|
549
|
+
page: options.page
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/resources/search.ts
|
|
554
|
+
class SearchResource {
|
|
555
|
+
#transport;
|
|
556
|
+
constructor(transport) {
|
|
557
|
+
this.#transport = transport;
|
|
558
|
+
}
|
|
559
|
+
movies(query, options = {}) {
|
|
560
|
+
return this.#transport.get("/search/movie", {
|
|
561
|
+
query: movieSearchQuery(this.#transport, query, options),
|
|
562
|
+
signal: options.signal
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
tv(query, options = {}) {
|
|
566
|
+
return this.#transport.get("/search/tv", {
|
|
567
|
+
query: tvSearchQuery(this.#transport, query, options),
|
|
568
|
+
signal: options.signal
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
people(query, options = {}) {
|
|
572
|
+
return this.#transport.get("/search/person", {
|
|
573
|
+
query: commonSearchQuery(this.#transport, query, options),
|
|
574
|
+
signal: options.signal
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
async multi(query, options = {}) {
|
|
578
|
+
const response = await this.#transport.get("/search/multi", {
|
|
579
|
+
query: commonSearchQuery(this.#transport, query, options),
|
|
580
|
+
signal: options.signal
|
|
581
|
+
});
|
|
582
|
+
const excluded = options.exclude;
|
|
583
|
+
if (excluded === undefined || excluded.length === 0) {
|
|
584
|
+
return response;
|
|
585
|
+
}
|
|
586
|
+
const excludedSet = new Set(excluded);
|
|
587
|
+
return {
|
|
588
|
+
...response,
|
|
589
|
+
results: response.results.filter((result) => !excludedSet.has(result.media_type))
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
501
593
|
function movieSearchQuery(transport, query, options) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
/** Build query parameters for TV search. */
|
|
594
|
+
return withListDefaults(transport.defaults, {
|
|
595
|
+
include_adult: options.includeAdult,
|
|
596
|
+
language: options.language,
|
|
597
|
+
page: options.page,
|
|
598
|
+
primary_release_year: options.primaryReleaseYear,
|
|
599
|
+
query: toSearchQuery(query),
|
|
600
|
+
region: options.region,
|
|
601
|
+
year: options.year
|
|
602
|
+
});
|
|
603
|
+
}
|
|
513
604
|
function tvSearchQuery(transport, query, options) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
/** Build query parameters for person and multi-search. */
|
|
605
|
+
return withLanguage(transport.defaults, {
|
|
606
|
+
first_air_date_year: options.firstAirDateYear,
|
|
607
|
+
include_adult: options.includeAdult,
|
|
608
|
+
language: options.language,
|
|
609
|
+
page: options.page,
|
|
610
|
+
query: toSearchQuery(query),
|
|
611
|
+
year: options.year
|
|
612
|
+
});
|
|
613
|
+
}
|
|
524
614
|
function commonSearchQuery(transport, query, options) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
615
|
+
return withLanguage(transport.defaults, {
|
|
616
|
+
include_adult: options.includeAdult,
|
|
617
|
+
language: options.language,
|
|
618
|
+
page: options.page,
|
|
619
|
+
query: toSearchQuery(query)
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/resources/trending.ts
|
|
624
|
+
class TrendingResource {
|
|
625
|
+
#transport;
|
|
626
|
+
constructor(transport) {
|
|
627
|
+
this.#transport = transport;
|
|
628
|
+
}
|
|
629
|
+
all(timeWindow = "day", options = {}) {
|
|
630
|
+
return this.#transport.get(`/trending/all/${toTimeWindow(timeWindow)}`, {
|
|
631
|
+
query: withLanguage(this.#transport.defaults, {
|
|
632
|
+
language: options.language,
|
|
633
|
+
page: options.page
|
|
634
|
+
}),
|
|
635
|
+
signal: options.signal
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
movies(timeWindow = "day", options = {}) {
|
|
639
|
+
return this.#transport.get(`/trending/movie/${toTimeWindow(timeWindow)}`, {
|
|
640
|
+
query: withLanguage(this.#transport.defaults, {
|
|
641
|
+
language: options.language,
|
|
642
|
+
page: options.page
|
|
643
|
+
}),
|
|
644
|
+
signal: options.signal
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
tv(timeWindow = "day", options = {}) {
|
|
648
|
+
return this.#transport.get(`/trending/tv/${toTimeWindow(timeWindow)}`, {
|
|
649
|
+
query: withLanguage(this.#transport.defaults, {
|
|
650
|
+
language: options.language,
|
|
651
|
+
page: options.page
|
|
652
|
+
}),
|
|
653
|
+
signal: options.signal
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
people(timeWindow = "day", options = {}) {
|
|
657
|
+
return this.#transport.get(`/trending/person/${toTimeWindow(timeWindow)}`, {
|
|
658
|
+
query: withLanguage(this.#transport.defaults, {
|
|
659
|
+
language: options.language,
|
|
660
|
+
page: options.page
|
|
661
|
+
}),
|
|
662
|
+
signal: options.signal
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/resources/tv.ts
|
|
668
|
+
class TVResource {
|
|
669
|
+
#transport;
|
|
670
|
+
constructor(transport) {
|
|
671
|
+
this.#transport = transport;
|
|
672
|
+
}
|
|
673
|
+
popular(options = {}) {
|
|
674
|
+
return this.#transport.get("/tv/popular", {
|
|
675
|
+
query: tvListQuery(this.#transport, options),
|
|
676
|
+
signal: options.signal
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
topRated(options = {}) {
|
|
680
|
+
return this.#transport.get("/tv/top_rated", {
|
|
681
|
+
query: tvListQuery(this.#transport, options),
|
|
682
|
+
signal: options.signal
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
airingToday(options = {}) {
|
|
686
|
+
return this.#transport.get("/tv/airing_today", {
|
|
687
|
+
query: tvListQuery(this.#transport, options),
|
|
688
|
+
signal: options.signal
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
onTheAir(options = {}) {
|
|
692
|
+
return this.#transport.get("/tv/on_the_air", {
|
|
693
|
+
query: tvListQuery(this.#transport, options),
|
|
694
|
+
signal: options.signal
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
details(seriesId, options = {}) {
|
|
698
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}`, {
|
|
699
|
+
query: withLanguage(this.#transport.defaults, {
|
|
700
|
+
append_to_response: toCommaList(options.appendToResponse),
|
|
701
|
+
language: options.language
|
|
702
|
+
}),
|
|
703
|
+
signal: options.signal
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
credits(seriesId, options = {}) {
|
|
707
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/credits`, {
|
|
708
|
+
query: withLanguage(this.#transport.defaults, {
|
|
709
|
+
language: options.language
|
|
710
|
+
}),
|
|
711
|
+
signal: options.signal
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
images(seriesId, options = {}) {
|
|
715
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/images`, {
|
|
716
|
+
query: withLanguage(this.#transport.defaults, {
|
|
717
|
+
include_image_language: toCommaList(options.includeImageLanguage),
|
|
718
|
+
language: options.language
|
|
719
|
+
}),
|
|
720
|
+
signal: options.signal
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
recommendations(seriesId, options = {}) {
|
|
724
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/recommendations`, {
|
|
725
|
+
query: tvListQuery(this.#transport, options),
|
|
726
|
+
signal: options.signal
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
similar(seriesId, options = {}) {
|
|
730
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/similar`, {
|
|
731
|
+
query: tvListQuery(this.#transport, options),
|
|
732
|
+
signal: options.signal
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
videos(seriesId, options = {}) {
|
|
736
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/videos`, {
|
|
737
|
+
query: withLanguage(this.#transport.defaults, {
|
|
738
|
+
language: options.language
|
|
739
|
+
}),
|
|
740
|
+
signal: options.signal
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
seasonDetails(seriesId, seasonNumber, options = {}) {
|
|
744
|
+
return this.#transport.get(`/tv/${toId(seriesId, "seriesId")}/season/${toSeasonNumber(seasonNumber)}`, {
|
|
745
|
+
query: withLanguage(this.#transport.defaults, {
|
|
746
|
+
append_to_response: toCommaList(options.appendToResponse),
|
|
747
|
+
language: options.language
|
|
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
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
613
819
|
function tvListQuery(transport, options) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
this.tv = new TVResource(this.#transport);
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Make a raw GET request to any TMDB endpoint.
|
|
657
|
-
*
|
|
658
|
-
* This is an escape hatch for endpoints not covered by the typed resource
|
|
659
|
-
* helpers. The `path` should be relative to the API base URL (e.g.
|
|
660
|
-
* `/movie/{id}/credits`). Response is parsed as JSON and cast to `T`.
|
|
661
|
-
*
|
|
662
|
-
* @param path - API path relative to the base URL (e.g. `/movie/123`)
|
|
663
|
-
* @param options - Optional request-level overrides (headers, query, signal)
|
|
664
|
-
* @returns Parsed JSON response typed as `T`
|
|
665
|
-
*/
|
|
666
|
-
request(path, options) {
|
|
667
|
-
return this.#transport.get(path, options);
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
/**
|
|
671
|
-
* Create a new {@link TMDBClient} instance.
|
|
672
|
-
*
|
|
673
|
-
* @example
|
|
674
|
-
* ```ts
|
|
675
|
-
* const tmdb = createTMDB({ accessToken: 'YOUR_TOKEN' })
|
|
676
|
-
* const { results } = await tmdb.search.movies('Inception')
|
|
677
|
-
* ```
|
|
678
|
-
*
|
|
679
|
-
* @param options - Authentication and configuration options
|
|
680
|
-
* @returns A configured TMDBClient ready to make API calls
|
|
681
|
-
*/
|
|
820
|
+
return withLanguage(transport.defaults, {
|
|
821
|
+
language: options.language,
|
|
822
|
+
page: options.page
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/client.ts
|
|
827
|
+
class TMDBClient {
|
|
828
|
+
configuration;
|
|
829
|
+
genres;
|
|
830
|
+
images;
|
|
831
|
+
movies;
|
|
832
|
+
people;
|
|
833
|
+
search;
|
|
834
|
+
trending;
|
|
835
|
+
tv;
|
|
836
|
+
#transport;
|
|
837
|
+
constructor(options) {
|
|
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
|
+
}
|
|
846
|
+
this.configuration = new ConfigurationResource(this.#transport);
|
|
847
|
+
this.genres = new GenresResource(this.#transport);
|
|
848
|
+
this.images = new ImagesHelper(this.#transport, imageTransform);
|
|
849
|
+
this.movies = new MoviesResource(this.#transport);
|
|
850
|
+
this.people = new PeopleResource(this.#transport);
|
|
851
|
+
this.search = new SearchResource(this.#transport);
|
|
852
|
+
this.trending = new TrendingResource(this.#transport);
|
|
853
|
+
this.tv = new TVResource(this.#transport);
|
|
854
|
+
}
|
|
855
|
+
request(path, options) {
|
|
856
|
+
return this.#transport.get(path, options);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
682
859
|
function createTMDB(options) {
|
|
683
|
-
|
|
860
|
+
return new TMDBClient(options);
|
|
684
861
|
}
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
}
|
|
1094
|
+
// src/search-guards.ts
|
|
687
1095
|
function isMovieSearchResult(result) {
|
|
688
|
-
|
|
1096
|
+
return isObjectWithMediaType(result, "movie");
|
|
689
1097
|
}
|
|
690
1098
|
function isTVSearchResult(result) {
|
|
691
|
-
|
|
1099
|
+
return isObjectWithMediaType(result, "tv");
|
|
692
1100
|
}
|
|
693
1101
|
function isPersonSearchResult(result) {
|
|
694
|
-
|
|
1102
|
+
return isObjectWithMediaType(result, "person");
|
|
695
1103
|
}
|
|
696
1104
|
function isMovie(result) {
|
|
697
|
-
|
|
1105
|
+
return isObjectWithMediaType(result, "movie");
|
|
698
1106
|
}
|
|
699
1107
|
function isTV(result) {
|
|
700
|
-
|
|
1108
|
+
return isObjectWithMediaType(result, "tv");
|
|
701
1109
|
}
|
|
702
1110
|
function isPerson(result) {
|
|
703
|
-
|
|
1111
|
+
return isObjectWithMediaType(result, "person");
|
|
704
1112
|
}
|
|
705
|
-
/** Internal helper: check if a value is an object with a matching `media_type` field. */
|
|
706
1113
|
function isObjectWithMediaType(value, mediaType) {
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
* canonical list lets callers:
|
|
716
|
-
*
|
|
717
|
-
* - iterate over the set to build a size picker
|
|
718
|
-
* - validate user-supplied sizes with `isKnownBackdropSize(...)` etc.
|
|
719
|
-
* - type a variable as a "known" size without giving up the
|
|
720
|
-
* `(string & {})` escape hatch for forward-compatibility
|
|
721
|
-
*
|
|
722
|
-
* If TMDB publishes a new size, the SDK will still accept it at runtime
|
|
723
|
-
* (every helper is `(string & {})` permissive), but you will need to
|
|
724
|
-
* add the literal to these constants to get autocomplete on it.
|
|
725
|
-
*/
|
|
726
|
-
const KNOWN_BACKDROP_SIZES = Object.freeze([
|
|
727
|
-
"w300",
|
|
728
|
-
"w780",
|
|
729
|
-
"w1280",
|
|
730
|
-
"original"
|
|
731
|
-
]);
|
|
732
|
-
const KNOWN_LOGO_SIZES = Object.freeze([
|
|
733
|
-
"w45",
|
|
734
|
-
"w92",
|
|
735
|
-
"w154",
|
|
736
|
-
"w185",
|
|
737
|
-
"w300",
|
|
738
|
-
"w500",
|
|
739
|
-
"original"
|
|
740
|
-
]);
|
|
741
|
-
const KNOWN_POSTER_SIZES = Object.freeze([
|
|
742
|
-
"w92",
|
|
743
|
-
"w154",
|
|
744
|
-
"w185",
|
|
745
|
-
"w342",
|
|
746
|
-
"w500",
|
|
747
|
-
"w780",
|
|
748
|
-
"original"
|
|
1114
|
+
return typeof value === "object" && value !== null && "media_type" in value && value.media_type === mediaType;
|
|
1115
|
+
}
|
|
1116
|
+
// src/types.ts
|
|
1117
|
+
var KNOWN_BACKDROP_SIZES = Object.freeze([
|
|
1118
|
+
"w300",
|
|
1119
|
+
"w780",
|
|
1120
|
+
"w1280",
|
|
1121
|
+
"original"
|
|
749
1122
|
]);
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1123
|
+
var KNOWN_LOGO_SIZES = Object.freeze(["w45", "w92", "w154", "w185", "w300", "w500", "original"]);
|
|
1124
|
+
var KNOWN_POSTER_SIZES = Object.freeze(["w92", "w154", "w185", "w342", "w500", "w780", "original"]);
|
|
1125
|
+
var KNOWN_PROFILE_SIZES = Object.freeze([
|
|
1126
|
+
"w45",
|
|
1127
|
+
"w185",
|
|
1128
|
+
"h632",
|
|
1129
|
+
"original"
|
|
755
1130
|
]);
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1131
|
+
var KNOWN_STILL_SIZES = Object.freeze([
|
|
1132
|
+
"w92",
|
|
1133
|
+
"w185",
|
|
1134
|
+
"w300",
|
|
1135
|
+
"original"
|
|
761
1136
|
]);
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
...KNOWN_STILL_SIZES
|
|
1137
|
+
var KNOWN_IMAGE_SIZES = Object.freeze([
|
|
1138
|
+
...KNOWN_BACKDROP_SIZES,
|
|
1139
|
+
...KNOWN_LOGO_SIZES,
|
|
1140
|
+
...KNOWN_POSTER_SIZES,
|
|
1141
|
+
...KNOWN_PROFILE_SIZES,
|
|
1142
|
+
...KNOWN_STILL_SIZES
|
|
769
1143
|
]);
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const KNOWN_IMAGE_SET = new Set(KNOWN_IMAGE_SIZES);
|
|
1144
|
+
var KNOWN_BACKDROP_SET = new Set(KNOWN_BACKDROP_SIZES);
|
|
1145
|
+
var KNOWN_LOGO_SET = new Set(KNOWN_LOGO_SIZES);
|
|
1146
|
+
var KNOWN_POSTER_SET = new Set(KNOWN_POSTER_SIZES);
|
|
1147
|
+
var KNOWN_PROFILE_SET = new Set(KNOWN_PROFILE_SIZES);
|
|
1148
|
+
var KNOWN_STILL_SET = new Set(KNOWN_STILL_SIZES);
|
|
1149
|
+
var KNOWN_IMAGE_SET = new Set(KNOWN_IMAGE_SIZES);
|
|
777
1150
|
function isKnownBackdropSize(value) {
|
|
778
|
-
|
|
1151
|
+
return KNOWN_BACKDROP_SET.has(value);
|
|
779
1152
|
}
|
|
780
1153
|
function isKnownLogoSize(value) {
|
|
781
|
-
|
|
1154
|
+
return KNOWN_LOGO_SET.has(value);
|
|
782
1155
|
}
|
|
783
1156
|
function isKnownPosterSize(value) {
|
|
784
|
-
|
|
1157
|
+
return KNOWN_POSTER_SET.has(value);
|
|
785
1158
|
}
|
|
786
1159
|
function isKnownProfileSize(value) {
|
|
787
|
-
|
|
1160
|
+
return KNOWN_PROFILE_SET.has(value);
|
|
788
1161
|
}
|
|
789
1162
|
function isKnownStillSize(value) {
|
|
790
|
-
|
|
1163
|
+
return KNOWN_STILL_SET.has(value);
|
|
791
1164
|
}
|
|
792
|
-
/** True if `value` is in any documented size set. */
|
|
793
1165
|
function isKnownImageSize(value) {
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
//#endregion
|
|
797
|
-
exports.KNOWN_BACKDROP_SIZES = KNOWN_BACKDROP_SIZES;
|
|
798
|
-
exports.KNOWN_IMAGE_SIZES = KNOWN_IMAGE_SIZES;
|
|
799
|
-
exports.KNOWN_LOGO_SIZES = KNOWN_LOGO_SIZES;
|
|
800
|
-
exports.KNOWN_POSTER_SIZES = KNOWN_POSTER_SIZES;
|
|
801
|
-
exports.KNOWN_PROFILE_SIZES = KNOWN_PROFILE_SIZES;
|
|
802
|
-
exports.KNOWN_STILL_SIZES = KNOWN_STILL_SIZES;
|
|
803
|
-
exports.TMDBClient = TMDBClient;
|
|
804
|
-
exports.TMDBError = TMDBError;
|
|
805
|
-
exports.TMDBRateLimitError = TMDBRateLimitError;
|
|
806
|
-
exports.TMDBRequestError = TMDBRequestError;
|
|
807
|
-
exports.TMDBResponseError = TMDBResponseError;
|
|
808
|
-
exports.buildImageUrl = buildImageUrl;
|
|
809
|
-
exports.createTMDB = createTMDB;
|
|
810
|
-
exports.isKnownBackdropSize = isKnownBackdropSize;
|
|
811
|
-
exports.isKnownImageSize = isKnownImageSize;
|
|
812
|
-
exports.isKnownLogoSize = isKnownLogoSize;
|
|
813
|
-
exports.isKnownPosterSize = isKnownPosterSize;
|
|
814
|
-
exports.isKnownProfileSize = isKnownProfileSize;
|
|
815
|
-
exports.isKnownStillSize = isKnownStillSize;
|
|
816
|
-
exports.isMovie = isMovie;
|
|
817
|
-
exports.isMovieSearchResult = isMovieSearchResult;
|
|
818
|
-
exports.isPerson = isPerson;
|
|
819
|
-
exports.isPersonSearchResult = isPersonSearchResult;
|
|
820
|
-
exports.isTV = isTV;
|
|
821
|
-
exports.isTVSearchResult = isTVSearchResult;
|
|
1166
|
+
return KNOWN_IMAGE_SET.has(value);
|
|
1167
|
+
}
|
|
822
1168
|
|
|
823
|
-
//#
|
|
1169
|
+
//# debugId=D1FA66D9B94DF6A164756E2164756E21
|
|
1170
|
+
//# sourceMappingURL=index.cjs.map
|