tmdb-kit 0.1.0-beta.3 → 1.0.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/LICENSE +21 -0
- package/README.md +216 -12
- package/dist/index.cjs +375 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -11
- package/dist/index.d.ts +236 -11
- package/dist/index.js +357 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 isaced
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
# tmdb-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/tmdb-kit)
|
|
4
|
+
[](https://github.com/isaced/tmdb-kit/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/isaced/tmdb-kit/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://github.com/isaced/tmdb-kit/actions/workflows/ci.yml)
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
A minimal, fully typed TMDB SDK with zero runtime dependencies. Built for Node.js, Bun, Deno, and edge runtimes.
|
|
10
|
+
|
|
11
|
+
`tmdb-kit` covers the most common TMDB workflows — movies, TV shows, search, trending, people, genres, and more — with a small, hand-crafted API designed for day-to-day use.
|
|
6
12
|
|
|
7
13
|
## Features
|
|
8
14
|
|
|
9
|
-
- Zero
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- Typed resources for movies, TV, search, trending, people, genres, configuration,
|
|
14
|
-
-
|
|
15
|
+
- **Zero dependencies** — nothing to audit, nothing to conflict.
|
|
16
|
+
- **Universal runtime** — uses only `fetch`, `Headers`, `URL`, and `AbortSignal`. Works on Node.js 18+, Bun, Deno, Cloudflare Workers, Vercel Edge, and any fetch-compatible environment.
|
|
17
|
+
- **Strict TypeScript** — full type safety with autocomplete for request options, response shapes, image sizes, and error handling.
|
|
18
|
+
- **ESM + CommonJS** — dual-format output via tsdown, ready for any module system.
|
|
19
|
+
- **Typed resources** — first-class methods for movies, TV, search, trending, people, genres, configuration, and images.
|
|
20
|
+
- **Injectable transport** — swap the HTTP layer for tests, caching, or instrumentation without mocking `fetch`.
|
|
21
|
+
- **Image helpers** — build CDN / proxy / signed URLs with per-request transform overrides.
|
|
22
|
+
- **Search type guards** — narrow `search.multi()` results to movies, TV, or people with runtime-checked helpers.
|
|
23
|
+
- **100% test coverage** — comprehensive unit tests with mocked fetch; no real TMDB requests.
|
|
15
24
|
|
|
16
25
|
## Install
|
|
17
26
|
|
|
@@ -55,13 +64,15 @@ const tmdb = createTMDB({ accessToken: Deno.env.get('TMDB_ACCESS_TOKEN')! })
|
|
|
55
64
|
|
|
56
65
|
## Authentication
|
|
57
66
|
|
|
58
|
-
The
|
|
67
|
+
The SDK targets the **TMDB v3 API** (`api.themoviedb.org/3`). Two authentication methods are supported:
|
|
68
|
+
|
|
69
|
+
**API Read Access Token** (recommended) — works with both v3 and v4:
|
|
59
70
|
|
|
60
71
|
```ts
|
|
61
72
|
createTMDB({ accessToken: 'tmdb-read-access-token' })
|
|
62
73
|
```
|
|
63
74
|
|
|
64
|
-
|
|
75
|
+
**Legacy v3 API key** — sent as an `api_key` query parameter:
|
|
65
76
|
|
|
66
77
|
```ts
|
|
67
78
|
createTMDB({ apiKey: 'tmdb-v3-api-key' })
|
|
@@ -69,6 +80,61 @@ createTMDB({ apiKey: 'tmdb-v3-api-key' })
|
|
|
69
80
|
|
|
70
81
|
Exactly one of `accessToken` or `apiKey` is required.
|
|
71
82
|
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
The factory accepts a few options beyond authentication:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createTMDB } from 'tmdb-kit'
|
|
89
|
+
|
|
90
|
+
const tmdb = createTMDB({
|
|
91
|
+
accessToken: 'tmdb-read-access-token',
|
|
92
|
+
|
|
93
|
+
// Defaults to https://api.themoviedb.org/3. Useful for tests or proxies.
|
|
94
|
+
baseUrl: 'https://api.themoviedb.org/3',
|
|
95
|
+
|
|
96
|
+
// Inject a fetch implementation. The SDK reads `globalThis.fetch` lazily
|
|
97
|
+
// on every request, so test-time mocks (vi.spyOn, etc.) are picked up
|
|
98
|
+
// automatically after the client has been constructed.
|
|
99
|
+
fetch: customFetch,
|
|
100
|
+
|
|
101
|
+
// Defaults applied to every request that supports them.
|
|
102
|
+
defaultLanguage: 'en-US',
|
|
103
|
+
defaultRegion: 'US',
|
|
104
|
+
|
|
105
|
+
// Custom image host. Defaults to https://image.tmdb.org/t/p.
|
|
106
|
+
imageBaseUrl: 'https://image.tmdb.org/t/p',
|
|
107
|
+
|
|
108
|
+
// Image URL transform applied by the ImagesHelper. See "Image URLs".
|
|
109
|
+
images: {
|
|
110
|
+
transform: (url) => url.replace('image.tmdb.org/t/p', 'img-proxy.example/tmdb'),
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// Extra headers merged into every request before authentication.
|
|
114
|
+
headers: { 'X-Client': 'my-app' },
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Injecting a custom transport
|
|
119
|
+
|
|
120
|
+
For tests, caching, or instrumentation, you can swap the entire transport
|
|
121
|
+
layer instead of mocking `fetch`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createTMDB, type TMDBTransport } from 'tmdb-kit'
|
|
125
|
+
|
|
126
|
+
const stub: TMDBTransport = {
|
|
127
|
+
defaults: { imageBaseUrl: 'https://image.tmdb.org/t/p' },
|
|
128
|
+
get: async (path, _options) => ({ results: [] }),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const tmdb = createTMDB({ accessToken: 'x', transport: stub })
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`transport.get()` is called for every resource and for the `request()`
|
|
135
|
+
escape hatch, so a single stub covers the whole surface area.
|
|
136
|
+
`TMDBTransport` is exported as part of the public API.
|
|
137
|
+
|
|
72
138
|
## Supported Resources
|
|
73
139
|
|
|
74
140
|
```ts
|
|
@@ -93,6 +159,7 @@ tmdb.tv.images(1399)
|
|
|
93
159
|
tmdb.tv.recommendations(1399)
|
|
94
160
|
tmdb.tv.similar(1399)
|
|
95
161
|
tmdb.tv.videos(1399)
|
|
162
|
+
tmdb.tv.seasonDetails(1399, 1)
|
|
96
163
|
|
|
97
164
|
tmdb.search.movies('Dune')
|
|
98
165
|
tmdb.search.tv('Dark')
|
|
@@ -129,20 +196,157 @@ const response = await tmdb.request<{ results: unknown[] }>('/discover/movie', {
|
|
|
129
196
|
})
|
|
130
197
|
```
|
|
131
198
|
|
|
199
|
+
## Image URLs
|
|
200
|
+
|
|
201
|
+
`tmdb.images.url(path, size)` builds a fully-qualified image URL. The
|
|
202
|
+
helper returns `null` for nullish paths so UI code can branch naturally:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const posterUrl = tmdb.images.url(movie.poster_path, 'w500')
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Routing images through a CDN or proxy
|
|
209
|
+
|
|
210
|
+
Pass a `transform` callback to rewrite the resolved URL. The most common
|
|
211
|
+
use case is pointing TMDB image requests at a self-hosted proxy:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const tmdb = createTMDB({
|
|
215
|
+
accessToken: 'x',
|
|
216
|
+
images: {
|
|
217
|
+
transform: (url) => url.replace('https://image.tmdb.org/t/p', 'https://img-proxy.example/tmdb'),
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
tmdb.images.url('/abc.jpg', 'w500')
|
|
222
|
+
// → 'https://img-proxy.example/tmdb/w500/abc.jpg'
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
You can also pass a one-off transform per call — useful for adding
|
|
226
|
+
signed query parameters for selected sizes:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
tmdb.images.url('/abc.jpg', 'original', {
|
|
230
|
+
transform: (url) => `${url}?signature=...`,
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The transform is never invoked for nullish paths, so `null` results stay
|
|
235
|
+
`null`.
|
|
236
|
+
|
|
237
|
+
`buildImageUrl(path, size, options)` is exported directly for callers
|
|
238
|
+
that want to build URLs without a `TMDBClient` instance.
|
|
239
|
+
|
|
240
|
+
## Search
|
|
241
|
+
|
|
242
|
+
`search.multi()` returns a union of movie / TV / person results. Filter
|
|
243
|
+
out media types you do not want and narrow the result type with the
|
|
244
|
+
provided type guards:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import {
|
|
248
|
+
createTMDB,
|
|
249
|
+
isMovieSearchResult,
|
|
250
|
+
isTVSearchResult,
|
|
251
|
+
isPersonSearchResult,
|
|
252
|
+
} from 'tmdb-kit'
|
|
253
|
+
|
|
254
|
+
const { results } = await tmdb.search.multi('Inception', {
|
|
255
|
+
exclude: ['person'],
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const movies = results.filter(isMovieSearchResult) // MovieMultiResult[]
|
|
259
|
+
const tvShows = results.filter(isTVSearchResult) // TVMultiResult[]
|
|
260
|
+
const people = results.filter(isPersonSearchResult) // PersonMultiResult[]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- `exclude` performs client-side filtering — the TMDB multi-search
|
|
264
|
+
endpoint has no server-side media_type filter. `total_results` reflects
|
|
265
|
+
the unfiltered payload; the filtered count is just `results.length`.
|
|
266
|
+
- Short-name aliases `isMovie` / `isTV` / `isPerson` are also exported
|
|
267
|
+
for callers that prefer them.
|
|
268
|
+
- The single-resource search methods (`search.movies`, `search.tv`,
|
|
269
|
+
`search.people`) already return the narrowed result type, so the
|
|
270
|
+
guards are only useful for `search.multi()` payloads.
|
|
271
|
+
|
|
272
|
+
## Image Sizes
|
|
273
|
+
|
|
274
|
+
The `ImageSize` type is intentionally permissive — TMDB has added new
|
|
275
|
+
sizes in the past (e.g. `h632` for profile) without notice, and the SDK
|
|
276
|
+
keeps the `(string & {})` escape hatch so any future size keeps
|
|
277
|
+
working at runtime. If you need the canonical set of documented sizes,
|
|
278
|
+
the SDK ships them as readonly constants:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import {
|
|
282
|
+
KNOWN_BACKDROP_SIZES,
|
|
283
|
+
KNOWN_LOGO_SIZES,
|
|
284
|
+
KNOWN_POSTER_SIZES,
|
|
285
|
+
KNOWN_PROFILE_SIZES,
|
|
286
|
+
KNOWN_STILL_SIZES,
|
|
287
|
+
KNOWN_IMAGE_SIZES,
|
|
288
|
+
isKnownBackdropSize,
|
|
289
|
+
isKnownImageSize,
|
|
290
|
+
// …plus one helper per type
|
|
291
|
+
} from 'tmdb-kit'
|
|
292
|
+
|
|
293
|
+
// Iterate to build a size picker
|
|
294
|
+
for (const size of KNOWN_POSTER_SIZES) {
|
|
295
|
+
console.log(size) // 'w92' | 'w154' | 'w185' | 'w342' | 'w500' | 'w780' | 'original'
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Validate user-supplied sizes at runtime
|
|
299
|
+
if (!isKnownPosterSize(userInput)) {
|
|
300
|
+
throw new Error(`Unknown TMDB poster size: ${userInput}`)
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`KnownBackdropSize` / `KnownPosterSize` / `KnownLogoSize` /
|
|
305
|
+
`KnownProfileSize` / `KnownStillSize` / `KnownImageSize` are the
|
|
306
|
+
matching TS literal-union types for callers that want strict typing
|
|
307
|
+
without giving up forward-compatibility.
|
|
308
|
+
|
|
132
309
|
## Errors
|
|
133
310
|
|
|
134
311
|
```ts
|
|
135
|
-
import {
|
|
312
|
+
import {
|
|
313
|
+
TMDBRateLimitError,
|
|
314
|
+
TMDBRequestError,
|
|
315
|
+
TMDBResponseError,
|
|
316
|
+
} from 'tmdb-kit'
|
|
136
317
|
|
|
137
318
|
try {
|
|
138
319
|
await tmdb.movies.popular()
|
|
139
320
|
} catch (error) {
|
|
140
|
-
if (error instanceof
|
|
321
|
+
if (error instanceof TMDBRateLimitError) {
|
|
322
|
+
// 429 — TMDB asked the caller to back off. The SDK does NOT retry
|
|
323
|
+
// automatically; back off for `retryAfter` seconds (or use your own
|
|
324
|
+
// policy) and try again.
|
|
325
|
+
console.log('rate limited; retry in', error.retryAfter, 's')
|
|
326
|
+
} else if (error instanceof TMDBResponseError) {
|
|
141
327
|
console.log(error.status, error.statusCode, error.statusMessage)
|
|
328
|
+
} else if (error instanceof TMDBRequestError) {
|
|
329
|
+
// Thrown when a request fails before receiving a response (e.g. network
|
|
330
|
+
// error, no fetch implementation found, or invalid client configuration).
|
|
331
|
+
console.log('request error:', error.message)
|
|
142
332
|
}
|
|
143
333
|
}
|
|
144
334
|
```
|
|
145
335
|
|
|
336
|
+
`TMDBResponseError` exposes convenience getters for the most common
|
|
337
|
+
status codes:
|
|
338
|
+
|
|
339
|
+
| Getter | Status |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| `isUnauthorized` | 401 |
|
|
342
|
+
| `isForbidden` | 403 |
|
|
343
|
+
| `isNotFound` | 404 |
|
|
344
|
+
| `isRateLimit` | 429 |
|
|
345
|
+
| `isServerError` | 5xx |
|
|
346
|
+
|
|
347
|
+
All errors are also instances of the base class `TMDBError`; use that
|
|
348
|
+
when you want a single catch-all.
|
|
349
|
+
|
|
146
350
|
## Development
|
|
147
351
|
|
|
148
352
|
```bash
|