?
code:ts
// ============================================================
// ここから「定義」部分
// ============================================================
// 疑似 schema
type Query = {
__typename?: "Query";
getStudent: { __typename?: "Student"; name: string; grade: number };
getPurposes: { __typename?: "Purpose"; purposeId: number }[];
};
// 疑似 TypedDocumentNode (本物は @graphql-typed-document-node/core)
// 実際は GraphQL の AST が入るが、ここでは operationName だけ持たせる
type TypedDocumentNode<RawResponse, Variables> = {
__rawResponseType?: RawResponse;
__variablesType?: Variables;
operationName: string;
};
type FakeApiResponse<RawResponse> = {
data: RawResponse;
errors?: unknown[];
};
type GqlClient = {
query: <RawResponse, Variables>(args: {
query: TypedDocumentNode<RawResponse, Variables>;
variables: Variables;
}) => Promise<FakeApiResponse<RawResponse>>;
};
type Ctx = {
logLabel: string;
gqlClient: GqlClient;
};
const createQuery =
(ctx: Ctx) =>
<
Variables,
RawResponse extends Record<string, unknown>,
OperationName extends Exclude<keyof Query & keyof RawResponse, "__typename">,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Acl extends ((raw: RawResponseOperationName) => any) | undefined, (
operationName: OperationName,
query: TypedDocumentNode<RawResponse, Variables>,
acl: Acl,
) =>
async (variables: Variables): Promise<Result> => {
console.log([${ctx.logLabel}] ${operationName} を実行);
const { data } = await ctx.gqlClient.query({ query, variables });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (acl ? acl(res) : res) as any;
};
// ============================================================
// ここまでが「定義」部分、ここから先が「使用例」
// ============================================================
// 疑似の実サーバーの代わり、本物なら HTTP でリクエストするところ
const fakeGqlClient: GqlClient = {
query: async (args) => {
if (args.query.operationName === "getStudent") {
return { data: { getStudent: { name: "太郎", grade: 3 } } as never };
}
if (args.query.operationName === "getPurposes") {
return { data: { getPurposes: purposeId: 1 }, { purposeId: 2 } } as never };
}
throw new Error("unknown operation");
},
};
// 疑似 document
const GetStudentDocument: TypedDocumentNode<{ getStudent: { name: string; grade: number } }, { studentId: number }> =
{ operationName: "getStudent" };
const GetPurposesDocument: TypedDocumentNode<{ getPurposes: { purposeId: number }[] }, { studentId: number }> = {
operationName: "getPurposes",
};
// acl 関数
const toStudentEntity = (raw: { name: string; grade: number }) => ({
displayName: ${raw.name}さん,
isElementarySchool: raw.grade <= 6,
});
async function example() {
// 段階1: ctx を渡して、この後何度でも使える query 関数を作る
const ctx: Ctx = { logLabel: "demo", gqlClient: fakeGqlClient };
const query = createQuery(ctx);
// 段階2: 「getStudent」を「GetStudentDocument」で取得し「toStudentEntity」で変換する、と定義
const getStudent = query("getStudent", GetStudentDocument, toStudentEntity);
// 段階2: 「getPurposes」は変換なし(acl に undefined)で、生のまま取得する、と定義
const getPurposes = query("getPurposes", GetPurposesDocument, undefined);
// 段階3: 実際に呼び出す、それぞれ違う variables を渡す
const student = await getStudent({ studentId: 123 });
const purposes = await getPurposes({ studentId: 123 });
console.log("student:", student);
console.log("purposes:", purposes);
// 型チェック用
const displayNameIsString: string = student.displayName;
const isElementarySchoolIsBoolean: boolean = student.isElementarySchool;
const purposeIdIsNumber: number = purposes0.purposeId; console.log(displayNameIsString, isElementarySchoolIsBoolean, purposeIdIsNumber);
}
example();