Skip to content

Instantly share code, notes, and snippets.

@jimzer
Created June 3, 2025 19:52
Show Gist options
  • Select an option

  • Save jimzer/5adfb462172a100e7619f9566bc9a46c to your computer and use it in GitHub Desktop.

Select an option

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.
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