Created
June 3, 2025 19:52
-
-
Save jimzer/5adfb462172a100e7619f9566bc9a46c to your computer and use it in GitHub Desktop.
Type-safe Effect-TS + React Query integration. Auto-generates useQuery/useMutation hooks with full TypeScript inference from Effect API clients.RetryClaude can make mistakes. Please double-check responses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Api } from "@api/domains/api"; | |
| import { | |
| FetchHttpClient, | |
| HttpApiClient, | |
| type HttpClient, | |
| type HttpClientResponse, | |
| } from "@effect/platform"; | |
| import { Fetch } from "@effect/platform/FetchHttpClient"; | |
| import { | |
| type UseMutationOptions, | |
| type UseQueryOptions, | |
| useMutation, | |
| useQuery, | |
| } from "@tanstack/react-query"; | |
| import { Effect, Layer } from "effect"; | |
| const getClient = HttpApiClient.make(Api, { | |
| baseUrl: "http://localhost:3000", | |
| }); | |
| type Client = Effect.Effect.Success<typeof getClient>; | |
| const CustomFetchLive = FetchHttpClient.layer.pipe( | |
| Layer.provide( | |
| Layer.succeed(FetchHttpClient.RequestInit, { | |
| credentials: "include", | |
| }), | |
| ), | |
| ); | |
| class ApiClient extends Effect.Service<ApiClient>()("ApiClient", { | |
| dependencies: [CustomFetchLive], | |
| effect: Effect.gen(function* () { | |
| return { | |
| client: yield* HttpApiClient.make(Api, { | |
| baseUrl: "http://localhost:3000", | |
| }), | |
| }; | |
| }), | |
| }) {} | |
| type GetRequestParams< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| > = Client[X][Y] extends (...args: any[]) => any | |
| ? Parameters<Client[X][Y]>[0] | |
| : never; | |
| type GetReturnType< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| > = Client[X][Y] extends (...args: any[]) => any | |
| ? ReturnType<Client[X][Y]> | |
| : never; | |
| function apiEffect<X extends keyof Client, Y extends keyof Client[X]>( | |
| section: X, | |
| method: Y, | |
| params: GetRequestParams<X, Y>, | |
| ): GetReturnType<X, Y> { | |
| const res = Effect.gen(function* () { | |
| const { client } = yield* ApiClient; | |
| const sectionObj = client[section]; | |
| const methodFn = sectionObj[method]; | |
| if (typeof methodFn !== "function") { | |
| throw new Error( | |
| `Method ${String(section)}.${String(method)} is not a function`, | |
| ); | |
| } | |
| return yield* (methodFn as any)(params); | |
| }) as GetReturnType<X, Y>; | |
| return res; | |
| } | |
| type ExcludeHttpResponseTuple<T> = Exclude< | |
| T, | |
| readonly [any, HttpClientResponse.HttpClientResponse] | |
| >; | |
| type GetCleanSuccessType< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| > = ExcludeHttpResponseTuple<Effect.Effect.Success<GetReturnType<X, Y>>>; | |
| type PromiseSuccess< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| > = Promise<GetCleanSuccessType<X, Y>>; | |
| export function apiEffectRunner< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| >(section: X, method: Y, params: GetRequestParams<X, Y>): PromiseSuccess<X, Y> { | |
| const program = apiEffect(section, method, params); | |
| return Effect.runPromise(program.pipe(Effect.provide(ApiClient.Default))); | |
| } | |
| export function useEffectQuery< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| >( | |
| section: X, | |
| method: Y, | |
| params: GetRequestParams<X, Y>, | |
| useQueryParams?: Omit< | |
| UseQueryOptions<GetCleanSuccessType<X, Y>, Error>, | |
| "queryKey" | "queryFn" | |
| >, | |
| ) { | |
| return useQuery({ | |
| queryKey: [section, method], | |
| queryFn: () => apiEffectRunner(section, method, params), | |
| ...useQueryParams, | |
| }); | |
| } | |
| export function useEffectMutation< | |
| X extends keyof Client, | |
| Y extends keyof Client[X], | |
| >( | |
| section: X, | |
| method: Y, | |
| useMutationParams?: Omit< | |
| UseMutationOptions< | |
| GetCleanSuccessType<X, Y>, | |
| Error, | |
| GetRequestParams<X, Y> | |
| >, | |
| "mutationFn" | |
| >, | |
| ) { | |
| return useMutation({ | |
| mutationFn: (params: GetRequestParams<X, Y>) => | |
| apiEffectRunner(section, method, params), | |
| ...useMutationParams, | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment