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"
}
]
],
// 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
}

View file

@ -1,27 +1,78 @@
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 {
public handler: ApiHandler
public options: DeepRequired<IApiClientOptions>;
public client: Client;
public handler: ApiHandler;
private _token: string;
public constructor(token: string, options: DeepRequired<IApiClientOptions>) {
this.options = options;
public constructor(client: Client, token: string) {
this.client = client;
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>({
method: 'GET',
body: '',
headers: {},
path: '/',
requireAuth: false,
body: options.body,
headers: options.headers,
path: options.path,
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 { queue, QueueObject } from 'async';
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 {
public options: DeepRequired<IApiClientOptions>;
public client: Client;
public queue: QueueObject<unknown>;
private _token: string;
public constructor(token: string, options: DeepRequired<IApiClientOptions>) {
this.options = options;
public constructor(client: Client, token: string) {
this.client = client;
this._token = token;
this.queue = queue(async (requestOptions: IMakeRequestOptions, callback) => {
@ -21,10 +22,10 @@ export class ApiHandler {
}
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();
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 timeout = setTimeout(() => controller.abort(), this.options.apiRequestTimeout);
const timeout = setTimeout(() => controller.abort(), this.client.options.api.apiRequestTimeout);
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @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 non-2xx responses
return res.json() as unknown as T;
// TODO: Handle non-2xx responses (Will slowly add them as i get them)
if (res.ok) {
return res.json() as unknown as T;
} else {
return null;
}
} finally {
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 { ApiClient } from '../api/apiClient';
import { ApiHelper } from '../api/apiHelper';
import { defaults } from '../utils/defaults';
import type { DeepRequired, IClientOptions } from '../utils/types';
export class Client {
public api: ApiClient;
public api: ApiHelper;
public options: DeepRequired<IClientOptions>;
private _token: string;
@ -15,6 +15,6 @@ export class Client {
this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>);
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 = {
clientOptions: {
api: {
apiRequestTimeout: 5000,
apiUrl: 'https://discord.com/api',
apiVersion: 9,
apiRequestTimeout: 5000,
},
},
};

View file

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

View file

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