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"
|
"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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 _ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Reference in a new issue