Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: various #47

Merged
merged 6 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hungry-ghosts-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@untidy/thetvdb': minor
---

feat: temporarily remove signal, possibly due to a memory leak.
5 changes: 5 additions & 0 deletions .changeset/three-hounds-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@untidy/thetvdb': minor
---

feat: support `/inspiration/types` and `/genders` endpoints
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "18.18.2",
"version": "20.9.0",
"nvmVersion": "latest"
}
},
Expand Down
57 changes: 3 additions & 54 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -35,53 +34,3 @@ import { TheTVDBExtended } from '@untidy/thetvdb';

new TheTVDBExtended('your token');
```

### Set token and timeout <Badge type="tip" text="example" />

```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);
```

:::
32 changes: 32 additions & 0 deletions docs/api/thetvdb-extended.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Badge type="warning" text="endpoint" />

| method | endpoint |
| ------------------------------- | ---------- |
| <Badge type="tip" text="GET" /> | `/genders` |

### List of genre records <Badge type="tip" text="example" />

```js
await client.getGenders();
```

## getGenres

This method returns a list of genre records and does not require any parameters.
Expand All @@ -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 <Badge type="warning" text="endpoint" />

| method | endpoint |
| ------------------------------- | -------------------- |
| <Badge type="tip" text="GET" /> | `/inspiration/types` |

### List of genre records <Badge type="tip" text="example" />

```js
await client.getInspirationTypes();
```

## getLanguages

This method returns a list of language records and does not require any parameters.
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/supported-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
43 changes: 5 additions & 38 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -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<T>(url: string | URL): Promise<T> {
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 {
Expand All @@ -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);
}
Expand Down
36 changes: 23 additions & 13 deletions src/extended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,48 +96,58 @@ interface Shared {
name: string;
}

interface Inspiration extends Shared {
description: string;
reference_name: string;
url: string;
}

type GetContentRatings = Data<ContentRating[]>;
type GetCountries = Data<Country[]>;
type GetEntities = Data<Entity[]>;
type GetGenders = Data<Shared[]>;
type GetGenres = Data<Genre[]>;
type GetInspirationTypes = Data<Inspiration[]>;
type GetLanguages = Data<Language[]>;
type GetUpdates = DataLink<Update[] | null[]>;
type GetArtworkStatuses = Data<Shared[]>;
type GetArtworkTypes = Data<ArtworkType[]>;

export class TheTVDBExtended extends Base {
public async getArtworkStatuses(): Promise<GetArtworkStatuses> {
const endpoint = this.api + '/v4/artwork/statuses';
return await this.fetcher<GetArtworkStatuses>(endpoint);
return await this.fetcher<GetArtworkStatuses>(this.api + '/v4/artwork/statuses');
}

public async getArtworkTypes(): Promise<GetArtworkTypes> {
const endpoint = this.api + '/v4/artwork/types';
return await this.fetcher<GetArtworkTypes>(endpoint);
return await this.fetcher<GetArtworkTypes>(this.api + '/v4/artwork/types');
}

public async getContentRatings(): Promise<GetContentRatings> {
const endpoint = this.api + '/v4/content/ratings';
return await this.fetcher<GetContentRatings>(endpoint);
return await this.fetcher<GetContentRatings>(this.api + '/v4/content/ratings');
}

public async getCountries(): Promise<GetCountries> {
const endpoint = this.api + '/v4/countries';
return await this.fetcher<GetCountries>(endpoint);
return await this.fetcher<GetCountries>(this.api + '/v4/countries');
}

public async getEntities(): Promise<GetEntities> {
return await this.fetcher<GetEntities>(this.api + '/v4/entities')
return await this.fetcher<GetEntities>(this.api + '/v4/entities');
}

public async getGenders(): Promise<GetGenders> {
return await this.fetcher<GetGenders>(this.api + '/v4/genders');
}

public async getGenres(): Promise<GetGenres> {
const endpoint = this.api + '/v4/genres';
return await this.fetcher<GetGenres>(endpoint);
return await this.fetcher<GetGenres>(this.api + '/v4/genres');
}

public async getInspirationTypes(): Promise<GetInspirationTypes> {
return await this.fetcher<GetInspirationTypes>(this.api + '/v4/inspiration/types');
}

public async getLanguages(): Promise<GetLanguages> {
const endpoint = this.api + '/v4/languages';
return await this.fetcher<GetLanguages>(endpoint);
return await this.fetcher<GetLanguages>(this.api + '/v4/languages');
}

public async getUpdates(options: updateO): Promise<GetUpdates> {
Expand Down
53 changes: 0 additions & 53 deletions tests/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
20 changes: 20 additions & 0 deletions tests/extended.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading