Archived
0
0
Fork 0

feat: base api handler

This commit is contained in:
Daryl Ronningen 2021-07-06 15:53:54 -05:00
parent ba43c954fd
commit 26638b00d5
Signed by: Daryl Ronningen
GPG key ID: FD23F0C934A5EC6B
8 changed files with 164 additions and 69 deletions

View file

@ -116,7 +116,27 @@
{ {
"case": "camelCase" "case": "camelCase"
} }
] ],
// Import Rules
"import/order": [
"error",
{
"newlines-between": "always-and-inside-groups",
"groups": [
[
"builtin",
"external",
"internal",
"sibling",
"parent",
"index",
"object",
"type"
]
]
}
],
"import/newline-after-import": "error"
}, },
"reportUnusedDisableDirectives": true "reportUnusedDisableDirectives": true
} }

View file

@ -1,27 +1,78 @@
import { ApiHandler } from './apiHandler'; import { ApiHandler } from './apiHandler';
import type { DeepRequired, IApiClientOptions } from '../utils/types'; import type { Client } from '../client/client';
import type { IRequestOptions } from '../utils/types';
export class ApiClient { export class ApiClient {
public handler: ApiHandler public client: Client;
public options: DeepRequired<IApiClientOptions>; public handler: ApiHandler;
private _token: string; private _token: string;
public constructor(token: string, options: DeepRequired<IApiClientOptions>) { public constructor(client: Client, token: string) {
this.options = options; this.client = client;
this._token = token; this._token = token;
this.handler = new ApiHandler(this._token, this.options); this.handler = new ApiHandler(this.client, this._token);
} }
public async get<T>(): Promise<T> { public async delete<T>(options: IRequestOptions): Promise<T | null> {
return this.handler.makeRequest<T>({
method: 'DELETE',
body: options.body,
headers: options.headers,
path: options.path,
requireAuth: options.requireAuth,
files: options.files,
reason: options.reason,
});
}
public async get<T>(options: IRequestOptions): Promise<T | null> {
return this.handler.makeRequest<T>({ return this.handler.makeRequest<T>({
method: 'GET', method: 'GET',
body: '', body: options.body,
headers: {}, headers: options.headers,
path: '/', path: options.path,
requireAuth: false, requireAuth: options.requireAuth,
files: options.files,
reason: options.reason,
});
}
public async patch<T>(options: IRequestOptions): Promise<T | null> {
return this.handler.makeRequest<T>({
method: 'PATCH',
body: options.body,
headers: options.headers,
path: options.path,
requireAuth: options.requireAuth,
files: options.files,
reason: options.reason,
});
}
public async post<T>(options: IRequestOptions): Promise<T | null> {
return this.handler.makeRequest<T>({
method: 'POST',
body: options.body,
headers: options.headers,
path: options.path,
requireAuth: options.requireAuth,
files: options.files,
reason: options.reason,
});
}
public async put<T>(options: IRequestOptions): Promise<T | null> {
return this.handler.makeRequest<T>({
method: 'PUT',
body: options.body,
headers: options.headers,
path: options.path,
requireAuth: options.requireAuth,
files: options.files,
reason: options.reason,
}); });
} }
} }

View file

@ -1,18 +1,19 @@
import { AbortController } from 'abort-controller'; import { AbortController } from 'abort-controller';
import { queue, QueueObject } from 'async'; import { queue, QueueObject } from 'async';
import FormData from 'form-data'; import FormData from 'form-data';
import fetch, { Headers } from 'node-fetch'; import fetch from 'node-fetch';
import type { DeepRequired, IApiClientOptions, IMakeRequestOptions } from '../utils/types'; import type { Client } from '../client/client';
import type { IMakeRequestOptions } from '../utils/types';
export class ApiHandler { export class ApiHandler {
public options: DeepRequired<IApiClientOptions>; public client: Client;
public queue: QueueObject<unknown>; public queue: QueueObject<unknown>;
private _token: string; private _token: string;
public constructor(token: string, options: DeepRequired<IApiClientOptions>) { public constructor(client: Client, token: string) {
this.options = options; this.client = client;
this._token = token; this._token = token;
this.queue = queue(async (requestOptions: IMakeRequestOptions, callback) => { this.queue = queue(async (requestOptions: IMakeRequestOptions, callback) => {
@ -21,10 +22,10 @@ export class ApiHandler {
} }
public get baseApiUrl(): string { public get baseApiUrl(): string {
return `${this.options.apiUrl}/v${this.options.apiVersion}`; return `${this.client.options.api.apiUrl}/v${this.client.options.api.apiVersion}`;
} }
public async makeRequest<T>(options: IMakeRequestOptions): Promise<T> { public async makeRequest<T>(options: IMakeRequestOptions): Promise<T | null> {
const headers: Map<string, string> = new Map(); const headers: Map<string, string> = new Map();
if (options.headers) for (const prop in options.headers) headers.set(prop, options.headers[prop]!); if (options.headers) for (const prop in options.headers) headers.set(prop, options.headers[prop]!);
@ -43,18 +44,21 @@ export class ApiHandler {
} }
const controller = new AbortController(); const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.options.apiRequestTimeout); const timeout = setTimeout(() => controller.abort(), this.client.options.api.apiRequestTimeout);
try { try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
const res = await fetch(`${this.baseApiUrl}${options.path}`, { method: options.method, headers: new Headers(headers), signal: controller.signal, body }); const res = await fetch(`${this.baseApiUrl}${options.path}`, { method: options.method, headers, signal: controller.signal, body });
// TODO: handle Ratelimits // TODO: handle Ratelimits
// TODO: Handle non-2xx responses // TODO: Handle non-2xx responses (Will slowly add them as i get them)
if (res.ok) {
return res.json() as unknown as T; return res.json() as unknown as T;
} else {
return null;
}
} finally { } finally {
clearTimeout(timeout); clearTimeout(timeout);
} }

24
src/api/apiHelper.ts Normal file
View file

@ -0,0 +1,24 @@
import { ApiClient } from './apiClient';
import type { Client } from '../client/client';
export class ApiHelper {
public apiClient: ApiClient;
public client: Client;
private _token: string;
public constructor(client: Client, token: string) {
this.client = client;
this._token = token;
this.apiClient = new ApiClient(this.client, this._token);
}
public createMessage(channelId: string): void {
this.apiClient.get({
path: `/channels/${channelId}/messages`,
requireAuth: true,
});
}
}

View file

@ -1,12 +1,12 @@
import _ from 'lodash'; import _ from 'lodash';
import { ApiClient } from '../api/apiClient'; import { ApiHelper } from '../api/apiHelper';
import { defaults } from '../utils/defaults'; import { defaults } from '../utils/defaults';
import type { DeepRequired, IClientOptions } from '../utils/types'; import type { DeepRequired, IClientOptions } from '../utils/types';
export class Client { export class Client {
public api: ApiClient; public api: ApiHelper;
public options: DeepRequired<IClientOptions>; public options: DeepRequired<IClientOptions>;
private _token: string; private _token: string;
@ -15,6 +15,6 @@ export class Client {
this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>); this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>);
this._token = token; this._token = token;
this.api = new ApiClient(this._token, this.options.api); this.api = new ApiHelper(this, this._token);
} }
} }

View file

@ -3,9 +3,9 @@ import type { IDefaultOptions } from './types';
export const defaults: IDefaultOptions = { export const defaults: IDefaultOptions = {
clientOptions: { clientOptions: {
api: { api: {
apiRequestTimeout: 5000,
apiUrl: 'https://discord.com/api', apiUrl: 'https://discord.com/api',
apiVersion: 9, apiVersion: 9,
apiRequestTimeout: 5000,
}, },
}, },
}; };

View file

@ -1,9 +1,8 @@
// Interfaces // Interfaces
export interface IApiClientOptions { export interface IApiClientOptions {
apiUrl?: string; apiUrl?: string;
apiRequestTimeout?: number;
apiVersion?: number; apiVersion?: number;
apiRequestTimeout?: number;
} }
export interface IClientOptions { export interface IClientOptions {
@ -14,49 +13,47 @@ export interface IDefaultOptions {
clientOptions: IClientOptions; clientOptions: IClientOptions;
} }
export interface IMakeRequestOptions { export interface IMakeRequestOptions extends IRequestOptions {
path: string; method: ApiMethods;
reason?: string; }
requireAuth?: boolean; export interface IRequestOptions {
path: string;
body?: unknown; reason?: string;
requireAuth?: boolean;
headers?: Record<string, string>; body?: unknown;
headers?: Record<string, string>;
method: ApiMethods; files?: {
file: Buffer;
files?: { name: string;
name: string; }[];
file: Buffer;
}[];
} }
// Enums // Enums
// Type Aliases // Type Aliases
export type ApiMethods = 'GET' | 'POST' | 'DELETE'; export type ApiMethods = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT' ;
export type Primitives = string | number | boolean | bigint | symbol | undefined | null; export type Primitives = string | number | boolean | bigint | symbol | undefined | null;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
export type Builtin = Primitives | Function | Date | Error | RegExp; export type Builtin = Primitives | Function | Date | Error | RegExp;
export type DeepRequired<T> = T extends Builtin export type DeepRequired<T> = T extends Builtin
? NonNullable<T> ? NonNullable<T>
: T extends Map<infer K, infer V> : T extends Map<infer K, infer V>
? Map<DeepRequired<K>, DeepRequired<V>> ? Map<DeepRequired<K>, DeepRequired<V>>
: T extends ReadonlyMap<infer K, infer V> : T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<DeepRequired<K>, DeepRequired<V>> ? ReadonlyMap<DeepRequired<K>, DeepRequired<V>>
: T extends WeakMap<infer K, infer V> : T extends WeakMap<infer K, infer V>
? WeakMap<DeepRequired<K>, DeepRequired<V>> ? WeakMap<DeepRequired<K>, DeepRequired<V>>
: T extends Set<infer U> : T extends Set<infer U>
? Set<DeepRequired<U>> ? Set<DeepRequired<U>>
: T extends ReadonlySet<infer U> : T extends ReadonlySet<infer U>
? ReadonlySet<DeepRequired<U>> ? ReadonlySet<DeepRequired<U>>
: T extends WeakSet<infer U> : T extends WeakSet<infer U>
? WeakSet<DeepRequired<U>> ? WeakSet<DeepRequired<U>>
: T extends Promise<infer U> : T extends Promise<infer U>
? Promise<DeepRequired<U>> ? Promise<DeepRequired<U>>
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
: T extends {} : T extends {}
? { [K in keyof T]-?: DeepRequired<T[K]> } ? { [K in keyof T]-?: DeepRequired<T[K]> }
: NonNullable<T>; : NonNullable<T>;

View file

@ -40,8 +40,7 @@
"noEmitOnError": false, "noEmitOnError": false,
"preserveConstEnums": true, "preserveConstEnums": true,
"traceResolution": false, "traceResolution": false,
"moduleResolution": "Node", "moduleResolution": "Node"
"baseUrl": "."
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"