Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/effect-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@spiko-tech/effect-react-query": major
---

Migrate to Effect v4. The `runtime` option now accepts a `Context.Context<R>` or a `ManagedRuntime` (v3 `Runtime.Runtime<R>` no longer exists). Cancellation now uses Effect's native `signal` run option.
346 changes: 189 additions & 157 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,17 +349,17 @@ const mutation = useEffectMutation({

# Dependency Injection

When Effects have service requirements, provide a `ManagedRuntime` or `Runtime`:
When Effects have service requirements, provide a `ManagedRuntime` or a `Context`:
Comment thread
antonin-morel-spiko marked this conversation as resolved.

```ts
import { Context, Effect, Layer, ManagedRuntime } from "effect";
import { useEffectQuery } from "@effect-react-query";

// Define a service
class UserService extends Context.Tag("UserService")<
class UserService extends Context.Service<
UserService,
{ readonly getUser: (id: string) => Effect.Effect<User, NetworkError> }
>() {}
>()("UserService") {}

// Create the layer
const UserServiceLive = Layer.succeed(
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"peerDependencies": {
"@tanstack/react-query": "^5.99.2",
"effect": "^3.21.1"
"effect": "^4.0.0-rc.117"
},
"engines": {
"node": ">=20"
Expand Down
26 changes: 5 additions & 21 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 12 additions & 23 deletions src/internal/createEffectQueryFn.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Cause, Effect, Exit, ManagedRuntime, Runtime } from "effect";
import { hasProperty } from "effect/Predicate";
import { Cause, Context, Effect, Exit } from "effect";
import type { ManagedRuntime } from "effect";

/**
* Creates a query function that wraps an Effect-returning function.
Expand All @@ -9,39 +9,28 @@ import { hasProperty } from "effect/Predicate";
*/
export function createEffectQueryFn<TQueryFnData, TError, TContext, R>(
effectFn: (context: TContext) => Effect.Effect<TQueryFnData, TError, R>,
runtime: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown> | undefined,
runtime: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown> | undefined,
getSignal: (context: TContext) => AbortSignal,
): (context: TContext) => Promise<TQueryFnData> {
return async (context: TContext) => {
const effect = effectFn(context);
const signal = getSignal(context);

// Create an effect that listens to the AbortSignal for cancellation
const withAbort = Effect.raceFirst(
effect,
Effect.async<never, never, never>((resume) => {
if (signal.aborted) {
resume(Effect.interrupt);
return;
}
const onAbort = () => resume(Effect.interrupt);
signal.addEventListener("abort", onAbort);
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
}),
);
const options = { signal: getSignal(context) };

// Determine how to run the effect based on runtime type
// Use unknown for error type since ManagedRuntime can add layer errors
let exit: Exit.Exit<TQueryFnData, unknown>;

if (runtime) {
if (hasProperty(runtime, ManagedRuntime.TypeId)) {
exit = await runtime.runPromiseExit(withAbort);
if (Context.isContext(runtime)) {
exit = await Effect.runPromiseExitWith(runtime)(effect, options);
} else {
exit = await Runtime.runPromiseExit(runtime)(withAbort);
exit = await runtime.runPromiseExit(effect, options);
}
} else {
exit = await Effect.runPromiseExit(withAbort as Effect.Effect<TQueryFnData, TError, never>);
exit = await Effect.runPromiseExit(
effect as Effect.Effect<TQueryFnData, TError, never>,
options,
);
}

if (Exit.isSuccess(exit)) return exit.value;
Expand All @@ -50,7 +39,7 @@ export function createEffectQueryFn<TQueryFnData, TError, TContext, R>(

// Check for interruption - don't call onError, just hang
// React Query will handle cleanup
if (Cause.isInterruptedOnly(cause)) {
if (Cause.hasInterruptsOnly(cause)) {
return new Promise<TQueryFnData>(() => {
// Never resolves - query is cancelled
});
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
UseSuspenseQueryOptions,
UseSuspenseQueryResult,
} from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";

/**
* Options for useEffectMutation hook.
Expand All @@ -47,11 +47,11 @@ export type UseEffectMutationOptions<

/**
* Runtime option - required when R is not never, forbidden when R is never.
* Accepts either a Runtime or a ManagedRuntime.
* Accepts either a Context or a ManagedRuntime.
*/
export type RuntimeOption<R> = [R] extends [never]
? { runtime?: undefined }
: { runtime: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown> };
: { runtime: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown> };

/**
* The result of useEffectMutation hook.
Expand Down
13 changes: 6 additions & 7 deletions src/useEffectMutation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useMutation } from "@tanstack/react-query";
import { Cause, Effect, Exit, ManagedRuntime, Runtime } from "effect";
import { hasProperty } from "effect/Predicate";
import { Cause, Context, Effect, Exit } from "effect";
import type { UseEffectMutationOptions, UseEffectMutationResult } from "./types";

/**
Expand All @@ -27,7 +26,7 @@ import type { UseEffectMutationOptions, UseEffectMutationResult } from "./types"
* // Effect with requirements - runtime is required
* const mutation = useEffectMutation({
* mutationFn: createUserWithService, // Effect<User, NetworkError, UserService>
* runtime: myRuntime, // Runtime<UserService>
* runtime: myRuntime, // Context<UserService> or ManagedRuntime<UserService>
* onError: Match.valueTags({
* NetworkError: (e) => toast.error(e.message),
* }),
Expand All @@ -49,10 +48,10 @@ export function useEffectMutation<TData, TError, TVariables = void, TContext = u
let exit: Exit.Exit<TData, unknown>;

if (runtime) {
if (hasProperty(runtime, ManagedRuntime.TypeId)) {
exit = await runtime.runPromiseExit(effect);
if (Context.isContext(runtime)) {
exit = await Effect.runPromiseExitWith(runtime)(effect);
} else {
exit = await Runtime.runPromiseExit(runtime)(effect);
exit = await runtime.runPromiseExit(effect);
}
} else {
exit = await Effect.runPromiseExit(effect as Effect.Effect<TData, TError, never>);
Expand All @@ -64,7 +63,7 @@ export function useEffectMutation<TData, TError, TVariables = void, TContext = u

// Check for interruption - don't call onError, just hang
// React Query will handle cleanup
if (Cause.isInterruptedOnly(cause)) {
if (Cause.hasInterruptsOnly(cause)) {
return new Promise<TData>(() => {
// Never resolves - mutation is cancelled
});
Expand Down
4 changes: 2 additions & 2 deletions src/useEffectQueries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { QueryKey } from "@tanstack/react-query";
import { useQueries } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type { EffectQueriesResults } from "./types";

Expand All @@ -12,7 +12,7 @@ import type { EffectQueriesResults } from "./types";
type EffectQueryOptionsBase = {
queryKey: QueryKey;
queryFn: (...args: any[]) => Effect.Effect<any, any, any>;
runtime?: Runtime.Runtime<any> | ManagedRuntime.ManagedRuntime<any, any> | undefined;
runtime?: Context.Context<any> | ManagedRuntime.ManagedRuntime<any, any> | undefined;
};

/**
Expand Down
6 changes: 3 additions & 3 deletions src/useEffectQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { QueryFunctionContext, QueryKey } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type {
DefinedInitialDataEffectQueryOptions,
Expand Down Expand Up @@ -32,7 +32,7 @@ import type {
* const query = useEffectQuery({
* queryKey: ["user", userId],
* queryFn: () => fetchUserWithService(userId), // Effect<User, NetworkError, UserService>
* runtime: myRuntime, // Runtime<UserService>
* runtime: myRuntime, // Context<UserService> or ManagedRuntime<UserService>
* });
*
* // Handle errors with Match
Expand Down Expand Up @@ -86,7 +86,7 @@ export function useEffectQuery<
): UseEffectQueryResult<TData, TError> {
const { queryFn, runtime, ...restOptions } = options as {
queryFn: (context: QueryFunctionContext<TQueryKey>) => Effect.Effect<TQueryFnData, TError, R>;
runtime?: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
runtime?: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
} & Omit<UseEffectQueryOptions<TQueryFnData, TError, TData, TQueryKey, R>, "queryFn" | "runtime">;

return useQuery<TQueryFnData, TError, TData, TQueryKey>({
Expand Down
4 changes: 2 additions & 2 deletions src/useEffectSuspenseQueries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { QueryKey } from "@tanstack/react-query";
import { useSuspenseQueries } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type { EffectSuspenseQueriesResults } from "./types";

Expand All @@ -12,7 +12,7 @@ import type { EffectSuspenseQueriesResults } from "./types";
type EffectSuspenseQueryOptionsBase = {
queryKey: QueryKey;
queryFn: (...args: any[]) => Effect.Effect<any, any, any>;
runtime?: Runtime.Runtime<any> | ManagedRuntime.ManagedRuntime<any, any>;
runtime?: Context.Context<any> | ManagedRuntime.ManagedRuntime<any, any>;
};

/**
Expand Down
6 changes: 3 additions & 3 deletions src/useEffectSuspenseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { QueryFunctionContext, QueryKey } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type { UseEffectSuspenseQueryOptions, UseEffectSuspenseQueryResult } from "./types";

Expand All @@ -25,7 +25,7 @@ import type { UseEffectSuspenseQueryOptions, UseEffectSuspenseQueryResult } from
* const query = useEffectSuspenseQuery({
* queryKey: ["user", userId],
* queryFn: () => fetchUserWithService(userId), // Effect<User, NetworkError, UserService>
* runtime: myRuntime, // Runtime<UserService>
* runtime: myRuntime, // Context<UserService> or ManagedRuntime<UserService>
* });
* ```
*/
Expand All @@ -40,7 +40,7 @@ export function useEffectSuspenseQuery<
): UseEffectSuspenseQueryResult<TData, TError> {
const { queryFn, runtime, ...restOptions } = options as {
queryFn: (context: QueryFunctionContext<TQueryKey>) => Effect.Effect<TQueryFnData, TError, R>;
runtime?: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
runtime?: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
} & Omit<
UseEffectSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey, R>,
"queryFn" | "runtime"
Expand Down
4 changes: 2 additions & 2 deletions src/useInfiniteEffectQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InfiniteData, QueryFunctionContext, QueryKey } from "@tanstack/react-query";
import { useInfiniteQuery } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type {
DefinedInitialDataInfiniteEffectQueryOptions,
Expand Down Expand Up @@ -108,7 +108,7 @@ export function useInfiniteEffectQuery<
queryFn: (
context: QueryFunctionContext<TQueryKey, TPageParam>,
) => Effect.Effect<TQueryFnData, TError, R>;
runtime?: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
runtime?: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
} & Omit<
UseInfiniteEffectQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam, R>,
"queryFn" | "runtime"
Expand Down
4 changes: 2 additions & 2 deletions src/useInfiniteEffectSuspenseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InfiniteData, QueryFunctionContext, QueryKey } from "@tanstack/react-query";
import { useSuspenseInfiniteQuery } from "@tanstack/react-query";
import type { Effect, ManagedRuntime, Runtime } from "effect";
import type { Context, Effect, ManagedRuntime } from "effect";
import { createEffectQueryFn } from "./internal/createEffectQueryFn";
import type {
UseInfiniteEffectSuspenseQueryOptions,
Expand Down Expand Up @@ -61,7 +61,7 @@ export function useInfiniteEffectSuspenseQuery<
queryFn: (
context: QueryFunctionContext<TQueryKey, TPageParam>,
) => Effect.Effect<TQueryFnData, TError, R>;
runtime?: Runtime.Runtime<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
runtime?: Context.Context<R> | ManagedRuntime.ManagedRuntime<R, unknown>;
} & Omit<
UseInfiniteEffectSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam, R>,
"queryFn" | "runtime"
Expand Down
4 changes: 2 additions & 2 deletions test/effectQueryOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ class NetworkError extends Schema.TaggedError<NetworkError>()("NetworkError", {
}) {}

// Define a service for testing runtime requirements
class UserService extends Context.Tag("UserService")<
class UserService extends Context.Service<
UserService,
{ readonly getUser: (id: string) => Effect.Effect<{ id: string; name: string }, NetworkError> }
>() {}
>()("UserService") {}

describe("effectQueryOptions", () => {
it("should export effectQueryOptions", () => {
Expand Down
4 changes: 2 additions & 2 deletions test/infiniteEffectQueryOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ interface PostsPage {
}

// Define a service for testing runtime requirements
class PostService extends Context.Tag("PostService")<
class PostService extends Context.Service<
PostService,
{ readonly getPosts: (cursor: number) => Effect.Effect<PostsPage, NetworkError> }
>() {}
>()("PostService") {}

describe("infiniteEffectQueryOptions", () => {
it("should export infiniteEffectQueryOptions", () => {
Expand Down
Loading
Loading