feat: base api handler
This commit is contained in:
parent
ba43c954fd
commit
26638b00d5
8 changed files with 164 additions and 69 deletions
22
.eslintrc
22
.eslintrc
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
24
src/api/apiHelper.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -40,8 +40,7 @@
|
|||
"noEmitOnError": false,
|
||||
"preserveConstEnums": true,
|
||||
"traceResolution": false,
|
||||
"moduleResolution": "Node",
|
||||
"baseUrl": "."
|
||||
"moduleResolution": "Node"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
|
Reference in a new issue