From 145a8726d5102cfb330278a1ac20af8e2a520917 Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 15:40:12 -0300 Subject: [PATCH 1/6] fix: update node version in container --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8eb7b8a..6709bcf 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "features": { "ghcr.io/devcontainers/features/node:1": { "nodeGypDependencies": true, - "version": "18.18.2", + "version": "20.9.0", "nvmVersion": "latest" } }, From 5fa02e672bda511b90690f2bd60a0f64583a84c3 Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 16:15:31 -0300 Subject: [PATCH 2/6] fix: format files --- src/extended.ts | 2 +- tests/mocks/handlers.ts | 48 ++++++++++++++++++----------------------- tests/mocks/response.ts | 5 ++--- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/extended.ts b/src/extended.ts index 0679b68..9f133bd 100644 --- a/src/extended.ts +++ b/src/extended.ts @@ -127,7 +127,7 @@ export class TheTVDBExtended extends Base { } public async getEntities(): Promise { - return await this.fetcher(this.api + '/v4/entities') + return await this.fetcher(this.api + '/v4/entities'); } public async getGenres(): Promise { diff --git a/tests/mocks/handlers.ts b/tests/mocks/handlers.ts index f477032..9d2e116 100644 --- a/tests/mocks/handlers.ts +++ b/tests/mocks/handlers.ts @@ -55,53 +55,51 @@ import { export const handlers: HttpHandler[] = [ http.get('https://api4.thetvdb.com/v4/awards/categories/:id/extended', () => { - return HttpResponse.json(awardsCategoryIdExtended) + return HttpResponse.json(awardsCategoryIdExtended); }), http.get('https://api4.thetvdb.com/v4/awards/categories/:id', () => { - return HttpResponse.json(awardsCategoryId) + return HttpResponse.json(awardsCategoryId); }), http.get('https://api4.thetvdb.com/v4/awards/:id/extended', () => { - return HttpResponse.json(awardsIdExtended) + return HttpResponse.json(awardsIdExtended); }), http.get('https://api4.thetvdb.com/v4/awards/:id', () => { - return HttpResponse.json(awardsId) - + return HttpResponse.json(awardsId); }), http.get('https://api4.thetvdb.com/v4/awards', () => { - return HttpResponse.json(awards) + return HttpResponse.json(awards); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/companies', ({ request }) => { if (request.url === 'https://api4.thetvdb.com/v4/companies?page=94') { - return HttpResponse.json(companiesPage) + return HttpResponse.json(companiesPage); } else { - return HttpResponse.json(companies) + return HttpResponse.json(companies); } }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/companies/:path', ({ request }) => { switch (request.url) { case 'https://api4.thetvdb.com/v4/companies/types': - return HttpResponse.json(companiesTypes) + return HttpResponse.json(companiesTypes); default: - return HttpResponse.json(companyId) + return HttpResponse.json(companyId); } }), http.get('https://api4.thetvdb.com/v4/content/ratings', () => { - return HttpResponse.json(contentRatings) + return HttpResponse.json(contentRatings); }), http.get('https://api4.thetvdb.com/v4/countries', () => { - return HttpResponse.json(countries) - + return HttpResponse.json(countries); }), http.get('https://api4.thetvdb.com/v4/entities', () => { - return HttpResponse.json(entities) + return HttpResponse.json(entities); }), http.get('https://api4.thetvdb.com/v4/genres', () => { - return HttpResponse.json(genres) + return HttpResponse.json(genres); }), http.get('https://api4.thetvdb.com/v4/languages', () => { - return HttpResponse.json(languages) + return HttpResponse.json(languages); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/updates', ({ request }) => { @@ -112,7 +110,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/artwork/:id/extended', () => { - return HttpResponse.json(artworkExtended) + return HttpResponse.json(artworkExtended); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/artwork/:id', ({ request }) => { @@ -126,7 +124,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/characters/:id', () => { - return HttpResponse.json(character) + return HttpResponse.json(character); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/episodes/:id/extended', ({ request }) => { @@ -137,8 +135,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/episodes/:id', () => { - return HttpResponse.json(episodes) - + return HttpResponse.json(episodes); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/people/:id/extended', ({ request }) => { @@ -149,8 +146,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/people/:id', () => { - return HttpResponse.json(people) - + return HttpResponse.json(people); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/search', ({ request }) => { @@ -188,8 +184,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/movies/:id', () => { - return HttpResponse.json(movie) - + return HttpResponse.json(movie); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/seasons/:id/extended', ({ request }) => { @@ -201,8 +196,7 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/seasons/:id', () => { - return HttpResponse.json(season) - + return HttpResponse.json(season); }), // @ts-expect-error: DefaultBodyType doesn't expect undefined http.get('https://api4.thetvdb.com/v4/series/filter', ({ request }) => { @@ -233,6 +227,6 @@ export const handlers: HttpHandler[] = [ } }), http.get('https://api4.thetvdb.com/v4/series/:id', () => { - return HttpResponse.json(series) + return HttpResponse.json(series); }), ]; diff --git a/tests/mocks/response.ts b/tests/mocks/response.ts index 99e29c7..8c3252c 100644 --- a/tests/mocks/response.ts +++ b/tests/mocks/response.ts @@ -114,7 +114,7 @@ const entities = { status: 'success', data: [ { - name: "series", + name: 'series', }, ], }; @@ -493,6 +493,5 @@ export { seriesET, seriesETS, updates, - updatesFull + updatesFull, }; - From 047df33c3c67c353627033a8646b017d785c1403 Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 17:03:06 -0300 Subject: [PATCH 3/6] feat: `/inspiration/types` `/genders` supp. api --- .changeset/three-hounds-train.md | 5 +++++ docs/api/thetvdb-extended.md | 32 +++++++++++++++++++++++++++++ docs/guide/supported-endpoints.md | 4 ++-- src/extended.ts | 34 ++++++++++++++++++++----------- tests/extended.test.ts | 20 ++++++++++++++++++ tests/mocks/handlers.ts | 8 ++++++++ tests/mocks/response.ts | 32 +++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 .changeset/three-hounds-train.md diff --git a/.changeset/three-hounds-train.md b/.changeset/three-hounds-train.md new file mode 100644 index 0000000..5035bf2 --- /dev/null +++ b/.changeset/three-hounds-train.md @@ -0,0 +1,5 @@ +--- +'@untidy/thetvdb': minor +--- + +feat: support `/inspiration/types` and `/genders` endpoints diff --git a/docs/api/thetvdb-extended.md b/docs/api/thetvdb-extended.md index 408c403..70bb897 100644 --- a/docs/api/thetvdb-extended.md +++ b/docs/api/thetvdb-extended.md @@ -94,6 +94,22 @@ This method returns a list of active entity types records and does not require a await client.getEntities(); ``` +## getGenders + +This method returns a list of genders records and does not require any parameters. + +### Supported endpoint + +| method | endpoint | +| ------------------------------- | ---------- | +| | `/genders` | + +### List of genre records + +```js +await client.getGenders(); +``` + ## getGenres This method returns a list of genre records and does not require any parameters. @@ -110,6 +126,22 @@ This method returns a list of genre records and does not require any parameters. await client.getGenres(); ``` +## getInspirationTypes + +This method returns a list of inspiration types records and does not require any parameters. + +### Supported endpoint + +| method | endpoint | +| ------------------------------- | -------------------- | +| | `/inspiration/types` | + +### List of genre records + +```js +await client.getInspirationTypes(); +``` + ## getLanguages This method returns a list of language records and does not require any parameters. diff --git a/docs/guide/supported-endpoints.md b/docs/guide/supported-endpoints.md index 5ca3849..7d9619a 100644 --- a/docs/guide/supported-endpoints.md +++ b/docs/guide/supported-endpoints.md @@ -33,10 +33,10 @@ List of endpoints from [TheTVDB API V4](https://thetvdb.github.io/v4-api/). | `/episodes/{id}` | :white_check_mark: | | `/episodes/{id}/extended` | :white_check_mark: | | `/episodes/{id}/translations/{language}` | :interrobang: | -| `/genders` | :interrobang: | +| `/genders` | :white_check_mark: | | `/genres` | :white_check_mark: | | `/genres/{id}` | :interrobang: | -| `/inspiration/types` | :interrobang: | +| `/inspiration/types` | :white_check_mark: | | `/languages` | :white_check_mark: | | `/lists` | :interrobang: | | `/lists/{id}` | :interrobang: | diff --git a/src/extended.ts b/src/extended.ts index 9f133bd..c93f92f 100644 --- a/src/extended.ts +++ b/src/extended.ts @@ -96,10 +96,18 @@ interface Shared { name: string; } +interface Inspiration extends Shared { + description: string; + reference_name: string; + url: string; +} + type GetContentRatings = Data; type GetCountries = Data; type GetEntities = Data; +type GetGenders = Data; type GetGenres = Data; +type GetInspirationTypes = Data; type GetLanguages = Data; type GetUpdates = DataLink; type GetArtworkStatuses = Data; @@ -107,37 +115,39 @@ type GetArtworkTypes = Data; export class TheTVDBExtended extends Base { public async getArtworkStatuses(): Promise { - const endpoint = this.api + '/v4/artwork/statuses'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/artwork/statuses'); } public async getArtworkTypes(): Promise { - const endpoint = this.api + '/v4/artwork/types'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/artwork/types'); } public async getContentRatings(): Promise { - const endpoint = this.api + '/v4/content/ratings'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/content/ratings'); } public async getCountries(): Promise { - const endpoint = this.api + '/v4/countries'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/countries'); } public async getEntities(): Promise { return await this.fetcher(this.api + '/v4/entities'); } + public async getGenders(): Promise { + return await this.fetcher(this.api + '/v4/genders'); + } + public async getGenres(): Promise { - const endpoint = this.api + '/v4/genres'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/genres'); + } + + public async getInspirationTypes(): Promise { + return await this.fetcher(this.api + '/v4/inspiration/types'); } public async getLanguages(): Promise { - const endpoint = this.api + '/v4/languages'; - return await this.fetcher(endpoint); + return await this.fetcher(this.api + '/v4/languages'); } public async getUpdates(options: updateO): Promise { diff --git a/tests/extended.test.ts b/tests/extended.test.ts index 6e651d4..1f4b272 100644 --- a/tests/extended.test.ts +++ b/tests/extended.test.ts @@ -61,6 +61,26 @@ describe('getGenres()', () => { }); }); +describe('getGenders()', () => { + test('returns a successful response', async () => { + const { data } = await client.getGenders(); + expect(Array.isArray(data)).toBe(true); + expect(data[0]?.name).toBe('Male'); + expect(data[1]?.id).toBe(2); + }); +}); + +describe('getInspirationTypes()', () => { + test('returns a successful response', async () => { + const { data } = await client.getInspirationTypes(); + expect(Array.isArray(data)).toBe(true); + expect(data).toHaveLength(2); + expect(data[0]?.name).toBe('Historical Event'); + expect(data[1]?.id).toBe(2); + expect(data[1]?.reference_name).toBe('Goodreads'); + }); +}); + describe('getLanguages()', () => { test('returns a successful response', async () => { const { status, data } = await client.getLanguages(); diff --git a/tests/mocks/handlers.ts b/tests/mocks/handlers.ts index 9d2e116..45da0af 100644 --- a/tests/mocks/handlers.ts +++ b/tests/mocks/handlers.ts @@ -26,7 +26,9 @@ import { filterSerie, filterSerieS, filterSerieY, + genders, genres, + inspirationTypes, languages, movie, movieE, @@ -95,9 +97,15 @@ export const handlers: HttpHandler[] = [ http.get('https://api4.thetvdb.com/v4/entities', () => { return HttpResponse.json(entities); }), + http.get('https://api4.thetvdb.com/v4/genders', () => { + return HttpResponse.json(genders); + }), http.get('https://api4.thetvdb.com/v4/genres', () => { return HttpResponse.json(genres); }), + http.get('https://api4.thetvdb.com/v4/inspiration/types', () => { + return HttpResponse.json(inspirationTypes); + }), http.get('https://api4.thetvdb.com/v4/languages', () => { return HttpResponse.json(languages); }), diff --git a/tests/mocks/response.ts b/tests/mocks/response.ts index 8c3252c..0b7bd8a 100644 --- a/tests/mocks/response.ts +++ b/tests/mocks/response.ts @@ -180,6 +180,20 @@ const filterSerie = { data: [{ name: 'Made In Hollywood' }], }; +// https://api4.thetvdb.com/v4/genders +const genders = { + data: [ + { + id: 1, + name: 'Male', + }, + { + id: 2, + name: 'Female', + }, + ], +}; + // https://api4.thetvdb.com/v4/genres const genres = { status: 'success', @@ -193,6 +207,22 @@ const genres = { ], }; +// https://api4.thetvdb.com/v4/inspiration/types +const inspirationTypes = { + data: [ + { + id: 1, + name: 'Historical Event', + reference_name: 'Wikipedia', + }, + { + id: 2, + name: 'Book Series', + reference_name: 'Goodreads', + }, + ], +}; + // https://api4.thetvdb.com/v4/languages const languages = { status: 'success', @@ -469,7 +499,9 @@ export { filterSerie, filterSerieS, filterSerieY, + genders, genres, + inspirationTypes, languages, movie, movieE, From 4e632dde5f664fb3111b8b4168abc2c7734bdac6 Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 17:14:30 -0300 Subject: [PATCH 4/6] feat: temp. remove signal, possibly due to a mleak --- .changeset/hungry-ghosts-dream.md | 5 +++ docs/api/index.md | 57 ++----------------------------- src/core.ts | 43 +++-------------------- 3 files changed, 13 insertions(+), 92 deletions(-) create mode 100644 .changeset/hungry-ghosts-dream.md diff --git a/.changeset/hungry-ghosts-dream.md b/.changeset/hungry-ghosts-dream.md new file mode 100644 index 0000000..b4a9195 --- /dev/null +++ b/.changeset/hungry-ghosts-dream.md @@ -0,0 +1,5 @@ +--- +'@untidy/thetvdb': minor +--- + +feat: temporarily remove signal, possibly due to a memory leak. diff --git a/docs/api/index.md b/docs/api/index.md index 44e9d2a..79be17d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -17,10 +17,9 @@ future updates. ## Constructor -| params | type | Required | Description | -| ------- | -------- | :------: | ------------------------------------------------- | -| token | `string` | Yes | Your TVDB API token. | -| timeout | `number` | optional | Request timeout in milliseconds. `Default: 5000`. | +| params | type | Required | Description | +| ------ | -------- | :------: | -------------------- | +| token | `string` | Yes | Your TVDB API token. | ::: danger WARNING @@ -35,53 +34,3 @@ import { TheTVDBExtended } from '@untidy/thetvdb'; new TheTVDBExtended('your token'); ``` - -### Set token and timeout - -```js -import { TheTVDBExtended } from '@untidy/thetvdb'; - -new TheTVDBExtended('your token', 10000); -``` - -## Advanced - -### getTime - -This method retrieves the custom request timeout value in milliseconds. - -| type | Description | -| -------- | ----------------------------------------------- | -| `number` | The custom request timeout value currently set. | - -::: details Example - -```js -import { TheTVDB } from '@untidy/thetvdb'; - -const client = new TheTVDB('your token'); - -client.getTime(); -``` - -::: - -### setTime - -This method allows setting a custom request timeout value in milliseconds. - -| param | type | Required | Description | -| ------- | -------- | :------: | ---------------------------------------------- | -| timeout | `number` | Yes | The new request timeout value in milliseconds. | - -::: details Example - -```js -import { TheTVDBExtended } from '@untidy/thetvdb'; - -const client = new TheTVDBExtended('your token'); - -client.setTime(10000); -``` - -::: diff --git a/src/core.ts b/src/core.ts index cbff33a..1eb7015 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,44 +1,20 @@ -import { clearTimeout, setTimeout } from 'node:timers'; import { URL } from 'node:url'; export abstract class Base { private readonly _token; protected readonly api = 'https://api4.thetvdb.com'; - private _timeout: number; - constructor(token: string, timeout = 5000) { + constructor(token: string) { this.validateInput(token, 'Token is required'); - this.validateTimeout(timeout); this._token = token; - this._timeout = timeout; - } - - public getTime(): number { - return this._timeout; - } - - public setTime(value: number): void { - this.validateTimeout(value); - this._timeout = value; } protected async fetcher(url: string | URL): Promise { - const controller = new AbortController(); - const { signal } = controller; - const timeout = setTimeout(() => { - controller.abort(); - }, this._timeout); - - try { - const response = await fetch(url, { - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this._token}` }, - signal, - }); + const response = await fetch(url, { + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this._token}` }, + }); - return (await response.json()) as T; - } finally { - clearTimeout(timeout); - } + return (await response.json()) as T; } protected validateInput(input: string, message: string): void { @@ -47,15 +23,6 @@ export abstract class Base { } } - protected validateTimeout(input: number): void { - if (typeof input !== 'number' || isNaN(input)) { - throw new TypeError('timeout must be of type number'); - } - if (input <= 0) { - throw new RangeError('timeout must be a positive number'); - } - } - protected createURL(path: string): URL { return new URL(path, this.api); } From de007fb15eda1c6c5575fea8db9275458c06c0b1 Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 17:15:02 -0300 Subject: [PATCH 5/6] chore: bump require node engine with ada --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 618393e..8c2ecb3 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "vitepress": "1.0.0-rc.24" }, "engines": { - "node": "^18.0.0 || ^20.0.0", + "node": "^18.16.1 || ^20.0.0", "pnpm": ">=8" }, "repository": { From 2493dd21ffac6eba402a874b31696a5576f0bced Mon Sep 17 00:00:00 2001 From: falsepopsky Date: Tue, 24 Oct 2023 17:21:04 -0300 Subject: [PATCH 6/6] fix: remove abort controller tests. --- tests/core.test.ts | 53 ---------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/tests/core.test.ts b/tests/core.test.ts index 335bde3..6c4835b 100644 --- a/tests/core.test.ts +++ b/tests/core.test.ts @@ -16,61 +16,8 @@ describe('constructor token validation tests', () => { expect(() => new Base(1234)).toThrow('Token is required'); }); - it('throws error when non-string timeout is provided', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - expect(() => new Base('fake token', 'lala')).toThrow('timeout must be of type number'); - }); - - it('throws error when the timeout provided is 0', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - expect(() => new Base('fake token', 0)).toThrow('timeout must be a positive number'); - }); - it('creates an instance when a valid token is provided', () => { // @ts-expect-error: Cannot create an instance of an abstract class. expect(() => new Base('fake token')).not.toThrow(); }); - - it('returns the default custom timeout', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - const client = new Base('fake token'); - expect(client.getTime()).toBe(5000); - }); - - it('returns the custom timeout set by the user', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - const client = new Base('fake token', 500); - expect(client.getTime()).toBe(500); - }); - - it('returns the updated custom timeout after it is modified', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - const client = new Base('fake token', 1000); - expect(client.getTime()).toBe(1000); - - client.setTime(400); - expect(client.getTime()).toBe(400); - }); - - it('sets the default timeout and allows updating it later', () => { - // @ts-expect-error: Cannot create an instance of an abstract class. - const client = new Base('fake token'); - expect(client.getTime()).toBe(5000); - - client.setTime(100); - expect(client.getTime()).toBe(100); - }); -}); - -describe('abort controller', () => { - it('aborts the request when the timeout is reached', async () => { - const abortSpy = jest.spyOn(global.AbortController.prototype, 'abort'); - // @ts-expect-error: Cannot create an instance of an abstract class. - const client = new Base('fake token', 1); - - await expect(async () => await client.fetcher('https://delay.com/delay')).rejects.toThrow( - 'This operation was aborted' - ); - expect(abortSpy).toHaveBeenCalled(); - }); });