Files
core_ng/@strpc/client_ky/src/callClientAPI.ts
T
2023-09-23 17:34:56 +00:00

158 lines
3.8 KiB
TypeScript

import { isArray, isEmpty } from "lodash-es";
import ky from "ky";
import type { HttpMethod, InvalidResponse, RawArgType, ValidResponse, recoveryMethod } from "@strpc/def";
export type globalRecoveryFunction = (method: HttpMethod, path: string, args: RawArgType, recovery: recoveryMethod, result: InvalidResponse) => Promise<{
justRetryAPI?: boolean;
} | void>;
export class APIFailed extends Error {
constructor(public readonly reason: string, public readonly detail: unknown, public readonly recovery?: recoveryMethod) {
super(reason);
}
}
export async function callClientAPI<ResultType extends ValidResponse>(
method: HttpMethod,
apiRoot: string,
path: string | string[],
args: RawArgType,
globalRecovery: globalRecoveryFunction | undefined
): Promise<ResultType>;
export async function callClientAPI<ResultType extends ValidResponse>(
method: HttpMethod,
apiRoot: string,
path: string | string[],
args: RawArgType,
globalRecovery: globalRecoveryFunction | undefined,
returnError: undefined
): Promise<ResultType>;
export async function callClientAPI<ResultType extends ValidResponse>(
method: HttpMethod,
apiRoot: string,
path: string | string[],
args: RawArgType,
globalRecovery: globalRecoveryFunction | undefined,
returnError: false
): Promise<ResultType>;
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
method: HttpMethod,
apiRoot: string,
path: string | string[],
args: RawArgType,
globalRecovery: globalRecoveryFunction | undefined,
returnError: true
): Promise<ResultType | ErrorType>;
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
method: HttpMethod,
apiRoot: string,
path: string | string[],
args: RawArgType,
globalRecovery: globalRecoveryFunction | undefined,
returnError?: boolean
): Promise<ResultType | ErrorType> {
if (isArray(path)) {
path = [apiRoot, ...path].join("/");
}
else if (path.startsWith("/")) {
path = `${apiRoot}${path}`;
}
else {
path = `${apiRoot}/${path}`;
}
if (args && isEmpty(args)) {
args = undefined;
}
try {
let trialRemain = 3;
let lastErr: ErrorType | undefined = undefined;
// eslint-disable-next-line no-constant-condition
while(trialRemain > 0){
const result = (await (() => {
if (method == "get") {
//TODO: args가 복합 object일 경우의 처리
return ky(path, {
searchParams: args === undefined ? undefined : {
...(args as Record<string, string | number>),
},
method,
headers: {
"content-type": "application/json",
},
timeout: 30000,
retry: 0,
});
}
return ky(path, {
method,
json: args,
headers: {
"content-type": "application/json",
},
timeout: 30000,
retry: 0,
});
})().json()) as ErrorType | ResultType;
if (!result.result) {
lastErr = result;
if(result.recovery && globalRecovery){
const recoveryResult = await globalRecovery(method, path, args, result.recovery, result);
if(recoveryResult !== undefined && recoveryResult.justRetryAPI){
trialRemain--;
continue;
}
}
if (returnError) {
return result;
}
throw new APIFailed(
result.reason,
result.detail,
result.recovery
);
}
return result;
}
if(lastErr === undefined){
throw new Error('lastErr is undefined');
}
if (returnError) {
return lastErr;
}
throw lastErr.reason;
}
catch(err) {
if(!returnError){
throw err;
}
let infoText = ''
if(err instanceof APIFailed){
infoText = err.reason;
}
else if(err instanceof Error){
infoText = err.toString();
}
else if(typeof err == 'string'){
infoText = err;
}
else{
infoText = JSON.stringify(err);
}
return {
result: false,
reason: `failed to fetch(${path}): ${infoText}`,
detail: err,
} as ErrorType;
}
}