@effectify/react-query 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @effectify/react-query
2
+
3
+ Integration of [Effect](https://effect.website/) with [TanStack Query](https://tanstack.com/query/latest) for [React](https://react.dev/).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # npm
9
+ npm install @effectify/react-query
10
+
11
+ # yarn
12
+ yarn add @effectify/react-query
13
+
14
+ # pnpm
15
+ pnpm add @effectify/react-query
16
+
17
+ # bun
18
+ bun add @effectify/react-query
19
+ ```
20
+
21
+ ## Basic Usage
22
+
23
+ ```tsx
24
+ import * as Layer from "effect/Layer"
25
+ import * as Effect from "effect/Effect"
26
+ import { QueryClient } from "@tanstack/react-query"
27
+ import { tanstackQueryEffect } from "@effectify/react-query"
28
+
29
+ // Create an Effect layer
30
+ const AppLayer = Layer.succeed("AppConfig", { apiUrl: "https://api.example.com" })
31
+
32
+ // Create a QueryClient instance
33
+ const queryClient = new QueryClient()
34
+
35
+ // Initialize the TanStack Query integration
36
+ const {
37
+ RuntimeProvider,
38
+ useRuntime,
39
+ useEffectQuery,
40
+ useEffectMutation,
41
+ useRxSubscribe,
42
+ useRxSubscriptionRef,
43
+ } = tanstackQueryEffect({ layer: AppLayer, queryClient })
44
+
45
+ // Wrap your application with the provider
46
+ function App() {
47
+ return (
48
+ <RuntimeProvider>
49
+ <YourApp />
50
+ </RuntimeProvider>
51
+ )
52
+ }
53
+
54
+ // Use in components
55
+ function YourComponent() {
56
+ const query = useEffectQuery({
57
+ queryKey: ["data"],
58
+ queryFn: () => Effect.succeed(["item1", "item2"]),
59
+ })
60
+
61
+ return (
62
+ <div>
63
+ {query.isPending ? <p>Loading...</p> : query.isError ? <p>Error: {query.error.message}</p> : (
64
+ <ul>
65
+ {query.data.map((item) => <li>{item}</li>)}
66
+ </ul>
67
+ )}
68
+ </div>
69
+ )
70
+ }
71
+ ```
72
+
73
+ ## API
74
+
75
+ ### `tanstackQueryEffect({ layer, queryClient })`
76
+
77
+ Creates an instance of the TanStack Query integration.
78
+
79
+ #### Parameters
80
+
81
+ - `layer`: An Effect layer that provides the necessary dependencies.
82
+ - `queryClient`: A TanStack Query QueryClient instance.
83
+
84
+ #### Returns
85
+
86
+ An object with the following properties:
87
+
88
+ - `RuntimeProvider`: Provider component that should wrap your application.
89
+ - `useRuntime`: Hook to access the Effect runtime.
90
+ - `useEffectQuery`: Hook to perform queries with Effect.
91
+ - `useEffectMutation`: Hook to perform mutations with Effect.
92
+ - `useRxSubscribe`: Hook to subscribe to Effect streams.
93
+ - `useRxSubscriptionRef`: Hook to maintain a reference to a subscription.
94
+
95
+ ### Helpers
96
+
97
+ - `createQueryDataHelpers`: Utility to create query data manipulation helpers.
98
+ - `createQueryKey`: Utility to create typed query keys.
99
+
100
+ ## Complete Example
101
+
102
+ Check out the example application in [apps/tanstack-react-app](../../apps/tanstack-react-app) to see a complete use case.
103
+
104
+ ## Credits & Inspiration
105
+
106
+ This package was inspired by the excellent educational content from [Lucas Barake](https://www.youtube.com/@lucas-barake), particularly his [video on Effect and TanStack Query](https://www.youtube.com/watch?v=zl4w3BQAoJM&t=1011s) which provides great insights into these technologies.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@effectify/react-query",
3
+ "version": "0.0.3",
4
+ "description": "Integration of Effect with TanStack Query for React",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "module": "./dist/src/index.js",
8
+ "types": "./dist/src/index.d.ts",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "@effectify/source": "./src/index.ts",
15
+ "types": "./dist/src/index.d.ts",
16
+ "import": "./dist/src/index.js",
17
+ "default": "./dist/src/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "!**/*.tsbuildinfo"
23
+ ],
24
+ "dependencies": {
25
+ "tslib": "catalog:",
26
+ "@tanstack/react-query": "5.90.10",
27
+ "@tanstack/query-core": "5.90.10",
28
+ "effect": "catalog:",
29
+ "react": "catalog:"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "catalog:",
33
+ "@types/react": "catalog:"
34
+ },
35
+ "optionalDependencies": {},
36
+ "peerDependencies": {
37
+ "effect": "^3.19.16 || ^4.0.0-beta"
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./lib/internal/query-data-helpers.js";
2
+ export * from "./lib/tanstack-query-effect.jsx";
3
+ export * from "./lib/types.js";
@@ -0,0 +1,5 @@
1
+ // Effect v4 Beta - React Query Integration
2
+ // @beta
3
+ export * from "./lib/internal/query-data-helpers.js";
4
+ export * from "./lib/tanstack-query-effect.jsx";
5
+ export * from "./lib/types.js";
@@ -0,0 +1,3 @@
1
+ import { type UseMutationResult } from "@tanstack/react-query";
2
+ import type { EffectfulError, EffectfulMutationOptions, Runner } from "../types.js";
3
+ export declare const makeUseEffectMutation: <R>(createRunner: Runner<R>) => <TData, TError extends EffectfulError, TVariables>(options: EffectfulMutationOptions<TData, TError, TVariables, R>) => UseMutationResult<TData, Error, TVariables>;
@@ -0,0 +1,13 @@
1
+ import { useMutation } from "@tanstack/react-query";
2
+ export const makeUseEffectMutation = (createRunner) => (options) => {
3
+ const effectRunner = createRunner();
4
+ const [spanName] = options.mutationKey;
5
+ const mutationFn = (variables) => {
6
+ const effect = options.mutationFn(variables);
7
+ return effectRunner(spanName)(effect);
8
+ };
9
+ return useMutation({
10
+ ...options,
11
+ mutationFn,
12
+ });
13
+ };
@@ -0,0 +1,3 @@
1
+ import { type UseQueryResult } from "@tanstack/react-query";
2
+ import type { EffectfulError, EffectfulQueryOptions, QueryKey, Runner } from "../types.js";
3
+ export declare const makeUseEffectQuery: <R>(createRunner: Runner<R>) => <TData, TError extends EffectfulError, TQueryKey extends QueryKey = QueryKey>({ gcTime, staleTime, ...options }: EffectfulQueryOptions<TData, TError, R, TQueryKey>) => UseQueryResult<TData, Error>;
@@ -0,0 +1,22 @@
1
+ import { skipToken, useQuery } from "@tanstack/react-query";
2
+ import * as Duration from "effect/Duration";
3
+ import { useMemo } from "react";
4
+ export const makeUseEffectQuery = (createRunner) => ({ gcTime, staleTime, ...options }) => {
5
+ const effectRunner = createRunner();
6
+ const [spanName] = options.queryKey;
7
+ const queryFn = useMemo(() => (context) => {
8
+ const effect = options.queryFn(context);
9
+ return effectRunner(spanName)(effect);
10
+ }, [effectRunner, spanName, options.queryFn]);
11
+ return useQuery({
12
+ ...options,
13
+ queryKey: options.queryKey,
14
+ queryFn: options.queryFn === skipToken ? skipToken : queryFn,
15
+ ...(staleTime !== undefined && {
16
+ staleTime: Duration.toMillis(Duration.millis(staleTime)),
17
+ }),
18
+ ...(gcTime !== undefined && {
19
+ gcTime: Duration.toMillis(Duration.millis(gcTime)),
20
+ }),
21
+ });
22
+ };
@@ -0,0 +1,20 @@
1
+ import { type Context } from "react";
2
+ import type * as ManagedRuntime from "effect/ManagedRuntime";
3
+ import type { Subscribable, SubscriptionOptions } from "../types.js";
4
+ import type * as Effect from "effect/Effect";
5
+ /**
6
+ * ⚠️ TEMPORARILY DISABLED - Effect v4 Migration
7
+ *
8
+ * This hook is temporarily disabled due to significant API changes in Effect v4:
9
+ * - SubscriptionRef.SubscriptionRefTypeId was removed
10
+ * - Stream APIs reorganized under effect/unstable/*
11
+ * - Migration documentation is incomplete (see Effect-TS/effect-smol#1378)
12
+ *
13
+ * The core functionality (useEffectQuery, useEffectMutation) works with v4.
14
+ * This advanced subscription feature will be revisited when v4 documentation
15
+ * is complete or when the beta stabilizes.
16
+ *
17
+ * TODO: Re-enable after Effect v4 stable release and documentation update
18
+ * @deprecated Temporarily disabled during Effect v4 beta migration
19
+ */
20
+ export declare const makeUseRxSubscriptionRef: <R, E>(RuntimeContext: Context<ManagedRuntime.ManagedRuntime<R, E> | null>) => <A, E2>(_subscribable: Subscribable<A, E2> | Effect.Effect<Subscribable<A, E2>, never, R> | Effect.Effect<unknown, never, R>, _onNext: (value: A) => void, _opts?: SubscriptionOptions) => A;
@@ -0,0 +1,25 @@
1
+ import { useContext } from "react";
2
+ /**
3
+ * ⚠️ TEMPORARILY DISABLED - Effect v4 Migration
4
+ *
5
+ * This hook is temporarily disabled due to significant API changes in Effect v4:
6
+ * - SubscriptionRef.SubscriptionRefTypeId was removed
7
+ * - Stream APIs reorganized under effect/unstable/*
8
+ * - Migration documentation is incomplete (see Effect-TS/effect-smol#1378)
9
+ *
10
+ * The core functionality (useEffectQuery, useEffectMutation) works with v4.
11
+ * This advanced subscription feature will be revisited when v4 documentation
12
+ * is complete or when the beta stabilizes.
13
+ *
14
+ * TODO: Re-enable after Effect v4 stable release and documentation update
15
+ * @deprecated Temporarily disabled during Effect v4 beta migration
16
+ */
17
+ export const makeUseRxSubscriptionRef = (RuntimeContext) => (_subscribable, _onNext, _opts) => {
18
+ const runtime = useContext(RuntimeContext);
19
+ if (!runtime) {
20
+ throw new Error("Runtime context not found. Make sure to wrap your app with RuntimeProvider");
21
+ }
22
+ throw new Error("useRxSubscriptionRef is temporarily disabled during Effect v4 beta migration. " +
23
+ "Please use useEffectQuery or useEffectMutation instead, or wait for v4 stable release. " +
24
+ "See: https://github.com/Effect-TS/effect-smol/issues/1378");
25
+ };
@@ -0,0 +1,5 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type * as ManagedRuntime from "effect/ManagedRuntime";
3
+ import * as Stream from "effect/Stream";
4
+ import { type Context } from "react";
5
+ export declare const makeUseRxSubscribe: <R, E>(RuntimeContext: Context<ManagedRuntime.ManagedRuntime<R, E> | null>) => <E2, A>(stream: Stream.Stream<A, E2, R> | Effect.Effect<Stream.Stream<A, E2, R>, E2, R>, initialValue: A, onNext: (value: A) => void, onError?: (error: E2) => void) => A | undefined;
@@ -0,0 +1,40 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Exit from "effect/Exit";
3
+ import * as Fiber from "effect/Fiber";
4
+ import * as Stream from "effect/Stream";
5
+ import { useContext, useEffect, useRef, useState } from "react";
6
+ export const makeUseRxSubscribe = (RuntimeContext) => {
7
+ return (stream, initialValue, onNext, onError) => {
8
+ const runtime = useContext(RuntimeContext);
9
+ if (!runtime) {
10
+ throw new Error("Runtime context not found. Make sure to wrap your app with RuntimeProvider");
11
+ }
12
+ const [value, setValue] = useState(initialValue);
13
+ const fiberRef = useRef(null);
14
+ const finalStream = Effect.isEffect(stream)
15
+ ? Stream.unwrap(stream)
16
+ : stream;
17
+ useEffect(() => {
18
+ const subscription = finalStream.pipe(Stream.tap((a) => Effect.sync(() => {
19
+ setValue(a);
20
+ onNext(a);
21
+ })), Stream.catch((e) => Stream.fromEffect(Effect.sync(() => {
22
+ onError?.(e);
23
+ return;
24
+ }))), Stream.runDrain, Effect.forever, Effect.forkDetach);
25
+ runtime.runCallback(subscription, {
26
+ onExit: (exit) => {
27
+ if (Exit.isSuccess(exit)) {
28
+ fiberRef.current = exit.value;
29
+ }
30
+ },
31
+ });
32
+ return () => {
33
+ if (fiberRef.current !== null) {
34
+ runtime.runCallback(Fiber.interrupt(fiberRef.current));
35
+ }
36
+ };
37
+ }, [finalStream, runtime, onNext, onError]);
38
+ return value;
39
+ };
40
+ };
@@ -0,0 +1,75 @@
1
+ import type { QueryClient } from "@tanstack/react-query";
2
+ type DeepMutable<T> = {
3
+ -readonly [P in keyof T]: T[P] extends object ? DeepMutable<T[P]> : T[P];
4
+ };
5
+ export type QueryDataUpdater<TData> = (draft: DeepMutable<TData>) => void;
6
+ type QueryKey<TKey extends string, TVariables = void> = TVariables extends void ? readonly [TKey] : readonly [TKey, TVariables];
7
+ /**
8
+ * Creates a type-safe query key factory that can be used with or without variables
9
+ * @template TKey The string literal type for the query key
10
+ * @template TVariables Optional variables type. If not provided, the factory will not accept variables
11
+ * @param key The query key string
12
+ * @returns A function that creates a query key tuple
13
+ *
14
+ * @example Without variables:
15
+ * ```typescript
16
+ * const userKey = createQueryKey("user");
17
+ * const key = userKey(); // returns ["user"]
18
+ * ```
19
+ *
20
+ * @example With variables:
21
+ * ```typescript
22
+ * type UserVars = { id: string };
23
+ * const userKey = createQueryKey<"user", UserVars>("user");
24
+ * const key = userKey({ id: "123" }); // returns ["user", { id: "123" }]
25
+ * ```
26
+ */
27
+ export declare function createQueryKey<TKey extends string, TVariables = void>(key: TKey): TVariables extends void ? () => QueryKey<TKey> : (variables: TVariables) => QueryKey<TKey, TVariables>;
28
+ type QueryDataHelpers<TData, TVariables> = {
29
+ removeQuery: (variables: TVariables) => void;
30
+ removeAllQueries: () => void;
31
+ setData: (variables: TVariables, updater: QueryDataUpdater<TData>) => TData | undefined;
32
+ invalidateQuery: (variables: TVariables) => Promise<void>;
33
+ invalidateAllQueries: () => Promise<void>;
34
+ refetchQuery: (variables: TVariables) => Promise<void>;
35
+ refetchAllQueries: () => Promise<void>;
36
+ };
37
+ /**
38
+ * Creates a set of helpers to manage query data in the cache
39
+ * @template TData The type of data stored in the query
40
+ * @template TVariables Automatically inferred from the queryKey function parameter
41
+ * @param queryKey A function that creates a query key tuple from variables
42
+ * @returns An object with methods to remove, update, invalidate, and invalidate all query data
43
+ *
44
+ * @example Without variables:
45
+ * ```typescript
46
+ * const userKey = createQueryKey("user");
47
+ * type User = { name: string };
48
+ *
49
+ * // Types are inferred from userKey
50
+ * const helpers = createQueryDataHelpers<User>(userKey);
51
+ * helpers.setData(undefined, (draft) => {
52
+ * draft.name = "New Name";
53
+ * });
54
+ * ```
55
+ *
56
+ * @example With variables and explicit types:
57
+ * ```typescript
58
+ * type UserVars = { id: string };
59
+ * type User = { id: string; name: string };
60
+ *
61
+ * const userKey = createQueryKey<"user", UserVars>("user");
62
+ * const helpers = createQueryDataHelpers<User, UserVars>(userKey);
63
+ *
64
+ * helpers.setData({ id: "123" }, (draft) => {
65
+ * draft.name = "New Name";
66
+ * });
67
+ *
68
+ * // Other helper methods
69
+ * await helpers.invalidateQuery({ id: "123" });
70
+ * await helpers.refetchQuery({ id: "123" });
71
+ * helpers.removeQuery({ id: "123" });
72
+ * ```
73
+ */
74
+ export declare const makeCreateQueryDataHelpers: (queryClient: QueryClient) => <TData, TVariables = void>(queryKey: (variables: TVariables) => readonly [string, ...unknown[]]) => QueryDataHelpers<TData, TVariables>;
75
+ export {};
@@ -0,0 +1,107 @@
1
+ // Simple deep clone function to replace mutative
2
+ function deepClone(obj) {
3
+ if (obj === null || typeof obj !== "object") {
4
+ return obj;
5
+ }
6
+ if (obj instanceof Date) {
7
+ return new Date(obj.getTime());
8
+ }
9
+ if (Array.isArray(obj)) {
10
+ return obj.map((item) => deepClone(item));
11
+ }
12
+ if (typeof obj === "object") {
13
+ const cloned = {};
14
+ for (const key in obj) {
15
+ if (Object.hasOwn(obj, key)) {
16
+ cloned[key] = deepClone(obj[key]);
17
+ }
18
+ }
19
+ return cloned;
20
+ }
21
+ return obj;
22
+ }
23
+ /**
24
+ * Creates a type-safe query key factory that can be used with or without variables
25
+ * @template TKey The string literal type for the query key
26
+ * @template TVariables Optional variables type. If not provided, the factory will not accept variables
27
+ * @param key The query key string
28
+ * @returns A function that creates a query key tuple
29
+ *
30
+ * @example Without variables:
31
+ * ```typescript
32
+ * const userKey = createQueryKey("user");
33
+ * const key = userKey(); // returns ["user"]
34
+ * ```
35
+ *
36
+ * @example With variables:
37
+ * ```typescript
38
+ * type UserVars = { id: string };
39
+ * const userKey = createQueryKey<"user", UserVars>("user");
40
+ * const key = userKey({ id: "123" }); // returns ["user", { id: "123" }]
41
+ * ```
42
+ */
43
+ export function createQueryKey(key) {
44
+ return ((variables) => variables === undefined ? [key] : [key, variables]);
45
+ }
46
+ /**
47
+ * Creates a set of helpers to manage query data in the cache
48
+ * @template TData The type of data stored in the query
49
+ * @template TVariables Automatically inferred from the queryKey function parameter
50
+ * @param queryKey A function that creates a query key tuple from variables
51
+ * @returns An object with methods to remove, update, invalidate, and invalidate all query data
52
+ *
53
+ * @example Without variables:
54
+ * ```typescript
55
+ * const userKey = createQueryKey("user");
56
+ * type User = { name: string };
57
+ *
58
+ * // Types are inferred from userKey
59
+ * const helpers = createQueryDataHelpers<User>(userKey);
60
+ * helpers.setData(undefined, (draft) => {
61
+ * draft.name = "New Name";
62
+ * });
63
+ * ```
64
+ *
65
+ * @example With variables and explicit types:
66
+ * ```typescript
67
+ * type UserVars = { id: string };
68
+ * type User = { id: string; name: string };
69
+ *
70
+ * const userKey = createQueryKey<"user", UserVars>("user");
71
+ * const helpers = createQueryDataHelpers<User, UserVars>(userKey);
72
+ *
73
+ * helpers.setData({ id: "123" }, (draft) => {
74
+ * draft.name = "New Name";
75
+ * });
76
+ *
77
+ * // Other helper methods
78
+ * await helpers.invalidateQuery({ id: "123" });
79
+ * await helpers.refetchQuery({ id: "123" });
80
+ * helpers.removeQuery({ id: "123" });
81
+ * ```
82
+ */
83
+ export const makeCreateQueryDataHelpers = (queryClient) => (queryKey) => {
84
+ const [namespaceKey] = queryKey(undefined);
85
+ return {
86
+ removeQuery: (variables) => {
87
+ queryClient.removeQueries({ queryKey: queryKey(variables) });
88
+ },
89
+ removeAllQueries: () => {
90
+ queryClient.removeQueries({ queryKey: [namespaceKey], exact: false });
91
+ },
92
+ setData: (variables, updater) => {
93
+ return queryClient.setQueryData(queryKey(variables), (oldData) => {
94
+ if (oldData === undefined) {
95
+ return oldData;
96
+ }
97
+ const clonedData = deepClone(oldData);
98
+ updater(clonedData);
99
+ return clonedData;
100
+ });
101
+ },
102
+ invalidateQuery: (variables) => queryClient.invalidateQueries({ queryKey: queryKey(variables) }),
103
+ invalidateAllQueries: () => queryClient.invalidateQueries({ queryKey: [namespaceKey], exact: false }),
104
+ refetchQuery: (variables) => queryClient.refetchQueries({ queryKey: queryKey(variables) }),
105
+ refetchAllQueries: () => queryClient.refetchQueries({ queryKey: [namespaceKey], exact: false }),
106
+ };
107
+ };
@@ -0,0 +1,28 @@
1
+ import { type QueryClient } from "@tanstack/react-query";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as ManagedRuntime from "effect/ManagedRuntime";
5
+ import { type ReactNode } from "react";
6
+ export declare const tanstackQueryEffect: <R, E>({ layer, queryClient, }: {
7
+ layer: Layer.Layer<R, E, never>;
8
+ queryClient: QueryClient;
9
+ }) => {
10
+ useRunner: () => <A, E2>(span: string) => (effect: Effect.Effect<A, E2, R>) => Promise<A>;
11
+ RuntimeProvider: ({ children }: {
12
+ children: ReactNode;
13
+ }) => import("react/jsx-runtime").JSX.Element;
14
+ useRuntime: () => ManagedRuntime.ManagedRuntime<R, E>;
15
+ useEffectQuery: <TData, TError extends import("./types.js").EffectfulError, TQueryKey extends import("./types.js").QueryKey = import("./types.js").QueryKey>({ gcTime, staleTime, ...options }: import("./types.js").EffectfulQueryOptions<TData, TError, R, TQueryKey>) => import("@tanstack/react-query").UseQueryResult<TData, Error>;
16
+ useEffectMutation: <TData, TError_1 extends import("./types.js").EffectfulError, TVariables>(options: import("./types.js").EffectfulMutationOptions<TData, TError_1, TVariables, R>) => import("@tanstack/react-query").UseMutationResult<TData, Error, TVariables>;
17
+ useRxSubscribe: <E2_1, A>(stream: import("effect/Stream").Stream<A, E2_1, R> | Effect.Effect<import("effect/Stream").Stream<A, E2_1, R>, E2_1, R>, initialValue: A, onNext: (value: A) => void, onError?: ((error: E2_1) => void) | undefined) => A | undefined;
18
+ useRxSubscriptionRef: <A_1, E2_1>(_subscribable: import("./types.js").Subscribable<A_1, E2_1> | Effect.Effect<import("./types.js").Subscribable<A_1, E2_1>, never, R> | Effect.Effect<unknown, never, R>, _onNext: (value: A_1) => void, _opts?: import("./types.js").SubscriptionOptions) => A_1;
19
+ createQueryDataHelpers: <TData, TVariables_1 = void>(queryKey: (variables: TVariables_1) => readonly [string, ...unknown[]]) => {
20
+ removeQuery: (variables: TVariables_1) => void;
21
+ removeAllQueries: () => void;
22
+ setData: (variables: TVariables_1, updater: import("./internal/query-data-helpers.js").QueryDataUpdater<TData>) => TData | undefined;
23
+ invalidateQuery: (variables: TVariables_1) => Promise<void>;
24
+ invalidateAllQueries: () => Promise<void>;
25
+ refetchQuery: (variables: TVariables_1) => Promise<void>;
26
+ refetchAllQueries: () => Promise<void>;
27
+ };
28
+ };
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { QueryClientProvider } from "@tanstack/react-query";
3
+ import * as Effect from "effect/Effect";
4
+ import * as ManagedRuntime from "effect/ManagedRuntime";
5
+ import { createContext, useContext, useEffect, useMemo, useRef } from "react";
6
+ import { makeUseEffectMutation } from "./internal/make-use-effect-mutation.js";
7
+ import { makeUseEffectQuery } from "./internal/make-use-effect-query.js";
8
+ import { makeUseRxSubscriptionRef } from "./internal/make-use-rx-subsciption-ref.js";
9
+ import { makeUseRxSubscribe } from "./internal/make-use-rx-subscribe.js";
10
+ import { makeCreateQueryDataHelpers } from "./internal/query-data-helpers.js";
11
+ export const tanstackQueryEffect = ({ layer, queryClient, }) => {
12
+ const RuntimeContext = createContext(null);
13
+ const useRunner = () => {
14
+ const runtime = useContext(RuntimeContext);
15
+ if (!runtime) {
16
+ throw new Error("Runtime context not found. Make sure to wrap your app with RuntimeProvider");
17
+ }
18
+ return useMemo(() => (span) => (effect) => runtime.runPromise(effect.pipe(Effect.withSpan(span))), [runtime]);
19
+ };
20
+ const RuntimeProvider = ({ children }) => {
21
+ const runtimeRef = useRef(null);
22
+ if (!runtimeRef.current) {
23
+ runtimeRef.current = ManagedRuntime.make(layer);
24
+ }
25
+ useEffect(() => {
26
+ return () => {
27
+ if (runtimeRef.current) {
28
+ runtimeRef.current.dispose();
29
+ }
30
+ };
31
+ }, []);
32
+ return (_jsx(RuntimeContext.Provider, { value: runtimeRef.current, children: _jsx(QueryClientProvider, { client: queryClient, children: children }) }));
33
+ };
34
+ const useRuntime = () => {
35
+ const runtime = useContext(RuntimeContext);
36
+ if (!runtime) {
37
+ throw new Error("Runtime context not found. Make sure to wrap your app with RuntimeProvider");
38
+ }
39
+ return runtime;
40
+ };
41
+ return {
42
+ useRunner,
43
+ RuntimeProvider,
44
+ useRuntime,
45
+ useEffectQuery: makeUseEffectQuery(useRunner),
46
+ useEffectMutation: makeUseEffectMutation(useRunner),
47
+ useRxSubscribe: makeUseRxSubscribe(RuntimeContext),
48
+ useRxSubscriptionRef: makeUseRxSubscriptionRef(RuntimeContext),
49
+ createQueryDataHelpers: makeCreateQueryDataHelpers(queryClient),
50
+ };
51
+ };
@@ -0,0 +1,25 @@
1
+ import type { QueryFunctionContext, skipToken, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
2
+ import type * as Effect from "effect/Effect";
3
+ export type QueryKey = readonly [string, Record<string, unknown>?];
4
+ export type EffectfulError = {
5
+ _tag: string;
6
+ };
7
+ export type Runner<R> = () => <A, E>(span: string) => (effect: Effect.Effect<A, E, R>) => Promise<A>;
8
+ export type EffectfulMutationOptions<TData, TError extends EffectfulError, TVariables, R> = Omit<UseMutationOptions<TData, Error, TVariables>, "mutationFn" | "onSuccess" | "onError" | "onSettled" | "onMutate" | "retry" | "retryDelay"> & {
9
+ mutationKey: QueryKey;
10
+ mutationFn: (variables: TVariables) => Effect.Effect<TData, TError, R>;
11
+ };
12
+ export type EffectfulQueryFunction<TData, TError, TQueryKey extends QueryKey = QueryKey, R = never, TPageParam = never> = (context: QueryFunctionContext<TQueryKey, TPageParam>) => Effect.Effect<TData, TError, R>;
13
+ export type EffectfulQueryOptions<TData, TError, R, TQueryKey extends QueryKey = QueryKey, TPageParam = never> = Omit<UseQueryOptions<TData, Error, TData, TQueryKey>, "queryKey" | "queryFn" | "retry" | "retryDelay" | "staleTime" | "gcTime"> & {
14
+ queryKey: TQueryKey;
15
+ queryFn: EffectfulQueryFunction<TData, TError, TQueryKey, R, TPageParam> | typeof skipToken;
16
+ staleTime?: number;
17
+ gcTime?: number;
18
+ };
19
+ export interface Subscribable<A, E = never> {
20
+ readonly changes: unknown;
21
+ readonly get: () => A;
22
+ }
23
+ export interface SubscriptionOptions {
24
+ readonly skipInitial?: boolean;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@effectify/react-query",
3
+ "version": "0.0.3",
4
+ "description": "Integration of Effect with TanStack Query for React",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "module": "./dist/src/index.js",
8
+ "types": "./dist/src/index.d.ts",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "@effectify/source": "./src/index.ts",
15
+ "types": "./dist/src/index.d.ts",
16
+ "import": "./dist/src/index.js",
17
+ "default": "./dist/src/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "!**/*.tsbuildinfo"
23
+ ],
24
+ "dependencies": {
25
+ "tslib": "2.8.1",
26
+ "@tanstack/react-query": "5.90.10",
27
+ "@tanstack/query-core": "5.90.10",
28
+ "effect": "4.0.0-beta.31",
29
+ "react": "19.2.0"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "5.9.3",
33
+ "@types/react": "19.2.7"
34
+ },
35
+ "optionalDependencies": {},
36
+ "peerDependencies": {
37
+ "effect": "^3.19.16 || ^4.0.0-beta"
38
+ }
39
+ }