monorepo 버전 준비
## @strpc 기존 RPC를 package화 ### @strpc/express express의 middleware + router 결함 ## @sammo 게임 전체 - server, client - gateway_server, gateway_client
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user