diff --git a/packages/example/src/routes/query.tsx b/packages/example/src/routes/query.tsx
index c8f1374..efa1d87 100644
--- a/packages/example/src/routes/query.tsx
+++ b/packages/example/src/routes/query.tsx
@@ -16,12 +16,13 @@ const Result = Schema.Tuple(Schema.String)
function RouteComponent() {
const runSync = R.useRunSync()
- const { state, triggerRefresh } = R.useQuery({
+ const { state, refresh } = R.useQuery({
effect: () => HttpClient.get("https://www.uuidtools.com/api/generate/v4").pipe(
HttpClient.withTracerPropagation(false),
Effect.flatMap(res => res.json),
Effect.flatMap(Schema.decodeUnknown(Result)),
Effect.delay("500 millis"),
+ Effect.scoped,
),
deps: [],
})
@@ -43,7 +44,7 @@ function RouteComponent() {
})}
-
+
)
diff --git a/packages/extension-query/src/Query.ts b/packages/extension-query/src/Query.ts
deleted file mode 100644
index 0cf4bdf..0000000
--- a/packages/extension-query/src/Query.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Effect, Fiber, Option, SubscriptionRef, type Ref } from "effect"
-import * as AsyncData from "@typed/async-data"
-
-
-export interface QueryRunner {
- readonly stateRef: SubscriptionRef.SubscriptionRef>
- readonly fiberRef: SubscriptionRef.SubscriptionRef>>
-
- readonly interrupt: Effect.Effect
- fetch(effect: Effect.Effect): Effect.Effect
- // refetch(effect: Effect.Effect): Effect.Effect
-}
-
-export const make = Effect.fnUntraced(function*(): Effect.Effect> {
- const stateRef = yield* SubscriptionRef.make(AsyncData.noData())
- const fiberRef = yield* SubscriptionRef.make(Option.none>())
-
- const interrupt = fiberRef.pipe(
- Effect.flatMap(Option.match({
- onSome: Fiber.interrupt,
- onNone: () => Effect.void,
- }))
- )
-
- const fetch = Effect.fnUntraced(function*(effect: Effect.Effect) {
-
- })
-
- return {
- stateRef,
- fiberRef,
- interrupt,
- fetch,
- }
-})
diff --git a/packages/extension-query/src/QueryRunner.ts b/packages/extension-query/src/QueryRunner.ts
new file mode 100644
index 0000000..ddf931d
--- /dev/null
+++ b/packages/extension-query/src/QueryRunner.ts
@@ -0,0 +1,75 @@
+import * as AsyncData from "@typed/async-data"
+import { Effect, Fiber, flow, identity, Option, Ref, SubscriptionRef } from "effect"
+
+
+export interface QueryRunner {
+ readonly queryRef: SubscriptionRef.SubscriptionRef>
+ readonly stateRef: SubscriptionRef.SubscriptionRef>
+ readonly fiberRef: SubscriptionRef.SubscriptionRef>>
+
+ readonly interrupt: Effect.Effect
+ readonly forkFetch: Effect.Effect
+ readonly forkRefetch: Effect.Effect
+}
+
+
+export const make = (
+ query: Effect.Effect
+): Effect.Effect, never, R> => Effect.gen(function*() {
+ const context = yield* Effect.context()
+
+ const queryRef = yield* SubscriptionRef.make(query)
+ const stateRef = yield* SubscriptionRef.make(AsyncData.noData())
+ const fiberRef = yield* SubscriptionRef.make(Option.none>())
+
+ const interrupt = fiberRef.pipe(
+ Effect.flatMap(Option.match({
+ onSome: flow(
+ Fiber.interrupt,
+ Effect.andThen(Ref.set(fiberRef, Option.none())),
+ ),
+ onNone: () => Effect.void,
+ }))
+ )
+
+ const forkFetch = interrupt.pipe(
+ Effect.andThen(Ref.set(stateRef, AsyncData.loading())),
+ Effect.andThen(queryRef.pipe(Effect.flatMap(identity))),
+ Effect.matchCauseEffect({
+ onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
+ onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
+ }),
+ Effect.provide(context),
+ Effect.forkDaemon,
+
+ Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))),
+ )
+
+ const forkRefetch = interrupt.pipe(
+ Effect.andThen(Ref.update(stateRef, previous => {
+ if (AsyncData.isSuccess(previous) || AsyncData.isFailure(previous))
+ return AsyncData.refreshing(previous)
+ if (AsyncData.isRefreshing(previous))
+ return AsyncData.refreshing(previous.previous)
+ return AsyncData.loading()
+ })),
+ Effect.andThen(queryRef.pipe(Effect.flatMap(identity))),
+ Effect.matchCauseEffect({
+ onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
+ onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
+ }),
+ Effect.provide(context),
+ Effect.forkDaemon,
+
+ Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))),
+ )
+
+ return {
+ queryRef,
+ stateRef,
+ fiberRef,
+ interrupt,
+ forkFetch,
+ forkRefetch,
+ }
+})
diff --git a/packages/extension-query/src/index.ts b/packages/extension-query/src/index.ts
index c2731f8..05797e8 100644
--- a/packages/extension-query/src/index.ts
+++ b/packages/extension-query/src/index.ts
@@ -1,71 +1,39 @@
import * as AsyncData from "@typed/async-data"
-import { Effect, Fiber, Option, Ref, Scope, SubscriptionRef } from "effect"
+import { Effect, Ref, SubscriptionRef } from "effect"
import * as React from "react"
import { ReffuseExtension, type ReffuseHelpers } from "reffuse"
+import * as QueryRunner from "./QueryRunner.js"
export interface UseQueryProps {
- effect: () => Effect.Effect
+ effect: () => Effect.Effect
readonly deps: React.DependencyList
}
export interface UseQueryResult {
readonly state: SubscriptionRef.SubscriptionRef>
- readonly triggerRefresh: Effect.Effect
+ readonly refresh: Effect.Effect
}
-const interruptRunningQuery = (fiberRef: Ref.Ref>>) => fiberRef.pipe(
- Effect.flatMap(Option.match({
- onSome: Fiber.interrupt,
- onNone: () => Effect.void,
- }))
-)
-
-const runQuery = (
- effect: Effect.Effect,
- stateRef: Ref.Ref>,
-) => effect.pipe(
- Effect.matchCauseEffect({
- onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
- onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
- })
-)
-
-
export const QueryExtension = ReffuseExtension.make(() => ({
useQuery(
this: ReffuseHelpers.ReffuseHelpers,
props: UseQueryProps,
): UseQueryResult {
- const context = this.useContext()
+ const runner = this.useMemo(() => QueryRunner.make(props.effect()), [])
- const fiberRef = this.useRef(Option.none>())
- const stateRef = this.useRef(AsyncData.noData())
+ this.useFork(() => Effect.addFinalizer(() => runner.interrupt).pipe(
+ Effect.andThen(Ref.set(runner.queryRef, props.effect())),
+ Effect.andThen(runner.forkFetch),
+ ), [runner, ...props.deps])
- const triggerRefresh = React.useMemo(() => interruptRunningQuery(fiberRef).pipe(
- Effect.andThen(Ref.update(stateRef, prev =>
- AsyncData.isSuccess(prev) || AsyncData.isFailure(prev)
- ? AsyncData.refreshing(prev)
- : AsyncData.loading()
- )),
- Effect.andThen(runQuery(props.effect(), stateRef)),
- Effect.provide(context),
- Effect.scoped,
- Effect.forkDaemon,
-
- Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))),
- ), [stateRef, context, fiberRef])
-
- this.useEffect(() => interruptRunningQuery(fiberRef).pipe(
- Effect.andThen(Ref.set(stateRef, AsyncData.loading())),
- Effect.andThen(runQuery(props.effect(), stateRef)),
- Effect.scoped,
- Effect.forkDaemon,
-
- Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))),
- ), [...props.deps, stateRef, fiberRef])
-
- return React.useMemo(() => ({ state: stateRef, triggerRefresh }), [stateRef, triggerRefresh])
+ return React.useMemo(() => ({
+ state: runner.stateRef,
+ refresh: runner.forkRefetch,
+ }), [runner])
}
}))
+
+
+export * as QueryRunner from "./QueryRunner.js"