mirror of
https://github.com/versia-pub/api.git
synced 2026-03-13 04:09:15 +01:00
feat(client): ✨ Add client package
This commit is contained in:
parent
69be6967bd
commit
605d6a4c7d
46 changed files with 1920 additions and 2 deletions
191
client/lysand/base.ts
Normal file
191
client/lysand/base.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import { DEFAULT_UA } from "./constants";
|
||||
|
||||
type HttpVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
type ConvertibleObject = Record<
|
||||
string,
|
||||
string | number | boolean | File | undefined | null
|
||||
>;
|
||||
|
||||
export interface Output<ReturnType> {
|
||||
data: ReturnType;
|
||||
headers: Headers;
|
||||
}
|
||||
|
||||
const objectToFormData = (obj: ConvertibleObject): FormData => {
|
||||
return Object.keys(obj).reduce((formData, key) => {
|
||||
if (obj[key] === undefined || obj[key] === null) return formData;
|
||||
formData.append(key, String(obj[key]));
|
||||
return formData;
|
||||
}, new FormData());
|
||||
};
|
||||
|
||||
export class ResponseError extends Error {}
|
||||
|
||||
export class BaseClient {
|
||||
constructor(
|
||||
protected baseUrl: URL,
|
||||
private accessToken?: string,
|
||||
) {}
|
||||
|
||||
get url(): URL {
|
||||
return this.baseUrl;
|
||||
}
|
||||
|
||||
get token(): string | undefined {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
private async request<ReturnType>(
|
||||
request: Request,
|
||||
): Promise<Output<ReturnType>> {
|
||||
const result = await fetch(request);
|
||||
|
||||
if (!result.ok) {
|
||||
const error = await result.json();
|
||||
throw new ResponseError(
|
||||
`Request failed (${result.status}): ${
|
||||
error.error || error.message || result.statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
data: await result.json(),
|
||||
headers: result.headers,
|
||||
};
|
||||
}
|
||||
|
||||
private async constructRequest(
|
||||
path: string,
|
||||
method: HttpVerb,
|
||||
body?: object | FormData,
|
||||
extra?: RequestInit,
|
||||
): Promise<Request> {
|
||||
return new Request(new URL(path, this.baseUrl).toString(), {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: this.accessToken
|
||||
? `Bearer ${this.accessToken}`
|
||||
: "",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": DEFAULT_UA,
|
||||
...extra?.headers,
|
||||
},
|
||||
body: body
|
||||
? body instanceof FormData
|
||||
? body
|
||||
: JSON.stringify(body)
|
||||
: undefined,
|
||||
...extra,
|
||||
});
|
||||
}
|
||||
|
||||
protected async get<ReturnType>(
|
||||
path: string,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(path, "GET", undefined, extra),
|
||||
);
|
||||
}
|
||||
|
||||
protected async post<ReturnType>(
|
||||
path: string,
|
||||
body?: object,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(path, "POST", body, extra),
|
||||
);
|
||||
}
|
||||
|
||||
protected async postForm<ReturnType>(
|
||||
path: string,
|
||||
body: FormData | ConvertibleObject,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(
|
||||
path,
|
||||
"POST",
|
||||
body instanceof FormData ? body : objectToFormData(body),
|
||||
extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected async put<ReturnType>(
|
||||
path: string,
|
||||
body?: object,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(path, "PUT", body, extra),
|
||||
);
|
||||
}
|
||||
|
||||
protected async putForm<ReturnType>(
|
||||
path: string,
|
||||
body: FormData | ConvertibleObject,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(
|
||||
path,
|
||||
"PUT",
|
||||
body instanceof FormData ? body : objectToFormData(body),
|
||||
extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected async patch<ReturnType>(
|
||||
path: string,
|
||||
body?: object,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(path, "PATCH", body, extra),
|
||||
);
|
||||
}
|
||||
|
||||
protected async patchForm<ReturnType>(
|
||||
path: string,
|
||||
body: FormData | ConvertibleObject,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(
|
||||
path,
|
||||
"PATCH",
|
||||
body instanceof FormData ? body : objectToFormData(body),
|
||||
extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected async delete<ReturnType>(
|
||||
path: string,
|
||||
body?: object,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(path, "DELETE", body, extra),
|
||||
);
|
||||
}
|
||||
|
||||
protected async deleteForm<ReturnType>(
|
||||
path: string,
|
||||
body: FormData | ConvertibleObject,
|
||||
extra?: RequestInit,
|
||||
): Promise<Output<ReturnType>> {
|
||||
return await this.request(
|
||||
await this.constructRequest(
|
||||
path,
|
||||
"DELETE",
|
||||
body instanceof FormData ? body : objectToFormData(body),
|
||||
extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
5
client/lysand/constants.ts
Normal file
5
client/lysand/constants.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import pkg from "../package.json";
|
||||
|
||||
export const NO_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
|
||||
export const DEFAULT_SCOPE = ["read", "write", "follow"];
|
||||
export const DEFAULT_UA = `LysandClient/${pkg.version} (+${pkg.homepage})`;
|
||||
1133
client/lysand/lysand.ts
Normal file
1133
client/lysand/lysand.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue