@strpc ready

This commit is contained in:
2023-09-22 20:32:41 +09:00
parent 3013554cef
commit 0f9d5c2dc9
19 changed files with 1485 additions and 186 deletions
+158
View File
@@ -0,0 +1,158 @@
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}): ${err}`,
detail: err,
} as ErrorType;
}
}
+15
View File
@@ -0,0 +1,15 @@
import { clientAPIPathGen, type APITail, type DefAPINamespace, type RawArgType } from "@strpc/def";
import { callClientAPI, type globalRecoveryFunction } from "./callClientAPI.js";
export { callClientAPI, type globalRecoveryFunction } from "./callClientAPI.js";
export function buildClientAPI<T extends DefAPINamespace>(structure: T, apiRoot = '/api', globalRecovery?: globalRecoveryFunction){
return clientAPIPathGen(structure, (path: string[], tail: APITail) => {
const method = tail.reqType;
return (args?: RawArgType, returnError?: boolean) => {
if (returnError) {
return callClientAPI(method, apiRoot, path.join('/'), args, globalRecovery, returnError);
}
return callClientAPI(method, apiRoot, path.join('/'), args, globalRecovery);
};
});
}