import { ClientUnaryCall, ServiceError, status } from "@grpc/grpc-js"; type UnaryCallback = ( error: ServiceError | null, response: TRes ) => void; type UnaryLike = ( req: TReq, callback: UnaryCallback ) => ClientUnaryCall; type RequestOf = T extends ( req: infer TReq, callback: UnaryCallback ) => ClientUnaryCall ? TReq : never; type ResponseOf = T extends ( req: any, callback: UnaryCallback ) => ClientUnaryCall ? TRes : never; /** * Lấy ra overload đúng dạng (req, callback) => ClientUnaryCall */ type ExtractUnaryOverload = Extract>; export type PromisifiedClient = { [K in keyof TClient as ExtractUnaryOverload extends never ? never : K]: ( req: RequestOf> ) => Promise>>; }; const grpcCodeToHttpStatus = (code?: number) => { switch (code) { case status.INVALID_ARGUMENT: return 400; case status.UNAUTHENTICATED: return 401; case status.PERMISSION_DENIED: return 403; case status.NOT_FOUND: return 404; default: return 500; } }; const normalizeGrpcError = (error: ServiceError) => { const normalized = new Error(error.details || error.message) as Error & { status?: number; code?: number; body?: { code?: number; message?: string; data?: unknown }; }; normalized.code = error.code; normalized.status = grpcCodeToHttpStatus(error.code); const trailerBody = error.metadata?.get('x-error-body')?.[0]; if (typeof trailerBody === 'string' && trailerBody) { try { normalized.body = JSON.parse(trailerBody) as { code?: number; message?: string; data?: unknown }; if (normalized.body?.message) { normalized.message = normalized.body.message; } if (typeof normalized.body?.code === 'number') { normalized.status = normalized.body.code; } } catch { // ignore malformed structured error payloads } } return normalized; }; export function promisifyClient( client: TClient ): PromisifiedClient { const proto = Object.getPrototypeOf(client); const result: Record = {}; for (const key of Object.getOwnPropertyNames(proto)) { if (key === "constructor") continue; const value = (client as Record)[key]; if (typeof value !== "function") continue; result[key] = (req: unknown) => new Promise((resolve, reject) => { (value as Function).call( client, req, (error: ServiceError | null, response: unknown) => { if (error) { reject(normalizeGrpcError(error)); return; } resolve(response); } ); }); } return result as PromisifiedClient; }