import { Endpoints } from "./api.types"; const MK_API_ERROR = Symbol(); export type APIError = { id: string; code: string; message: string; kind: "client" | "server"; info: Record; }; export function isAPIError(reason: any): reason is APIError { return reason[MK_API_ERROR] === true; } export type FetchLike = ( input: string, init?: { method?: string; body?: string; credentials?: RequestCredentials; cache?: RequestCache; }, ) => Promise<{ status: number; json(): Promise; }>; type IsNeverType = [T] extends [never] ? true : false; type StrictExtract = Cond extends Union ? Union : never; type IsCaseMatched< E extends keyof Endpoints, P extends Endpoints[E]["req"], C extends number, > = IsNeverType< StrictExtract > extends false ? true : false; type GetCaseResult< E extends keyof Endpoints, P extends Endpoints[E]["req"], C extends number, > = StrictExtract[1]; export class APIClient { public origin: string; public credential: string | null | undefined; public fetch: FetchLike; constructor(opts: { origin: APIClient["origin"]; credential?: APIClient["credential"]; fetch?: APIClient["fetch"] | null | undefined; }) { this.origin = opts.origin; this.credential = opts.credential; // ネイティブ関数をそのまま変数に代入して使おうとするとChromiumではIllegal invocationエラーが発生するため、 // 環境で実装されているfetchを使う場合は無名関数でラップして使用する this.fetch = opts.fetch || ((...args) => fetch(...args)); } public request( endpoint: E, params: P = {} as P, credential?: string | null | undefined, ): Promise< Endpoints[E]["res"] extends { $switch: { $cases: [any, any][]; $default: any }; } ? IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : Endpoints[E]["res"]["$switch"]["$default"] : Endpoints[E]["res"] > { const promise = new Promise((resolve, reject) => { this.fetch(`${this.origin}/api/${endpoint}`, { method: "POST", body: JSON.stringify({ ...params, i: credential !== undefined ? credential : this.credential, }), credentials: "omit", cache: "no-cache", }) .then(async (res) => { const body = res.status === 204 ? null : await res.json(); if (res.status === 200) { resolve(body); } else if (res.status === 204) { resolve(null); } else { reject({ [MK_API_ERROR]: true, ...body.error, }); } }) .catch(reject); }); return promise as any; } }