diff --git a/packages/example/src/query/services/Uuid4Query.ts b/packages/example/src/query/services/Uuid4Query.ts index 08905a2..ffc2b36 100644 --- a/packages/example/src/query/services/Uuid4Query.ts +++ b/packages/example/src/query/services/Uuid4Query.ts @@ -1,6 +1,6 @@ -import { HttpClient, HttpClientError } from "@effect/platform" +import { HttpClientError } from "@effect/platform" import { QueryService } from "@reffuse/extension-query" -import { Console, Effect, ParseResult, Schema } from "effect" +import { ParseResult, Schema } from "effect" export const Result = Schema.Array(Schema.String) @@ -9,14 +9,3 @@ export class Uuid4Query extends QueryService.Tag("Uuid4Query")() {} - -export const Uuid4QueryLive = QueryService.layer(Uuid4Query, { - query: Console.log("Querying...").pipe( - Effect.andThen(Effect.sleep("500 millis")), - Effect.andThen(HttpClient.get("https://www.uuidtools.com/api/generate/v4")), - HttpClient.withTracerPropagation(false), - Effect.flatMap(res => res.json), - Effect.flatMap(Schema.decodeUnknown(Result)), - Effect.scoped, - ) -}) diff --git a/packages/example/src/routes/query/service.tsx b/packages/example/src/routes/query/service.tsx index 38a6b38..e151872 100644 --- a/packages/example/src/routes/query/service.tsx +++ b/packages/example/src/routes/query/service.tsx @@ -21,7 +21,7 @@ function RouteComponent() { // ), [context]) const query = R.useQuery({ - key: ["uuid4", 10], + key: R.useStreamFromValues(["uuid4", 10]), query: () => Console.log(`Querying 10 IDs...`).pipe( Effect.andThen(Effect.sleep("500 millis")), Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/10`)), diff --git a/packages/example/src/routes/query/usequery.tsx b/packages/example/src/routes/query/usequery.tsx index f2c58c8..7fe1a35 100644 --- a/packages/example/src/routes/query/usequery.tsx +++ b/packages/example/src/routes/query/usequery.tsx @@ -20,7 +20,7 @@ function RouteComponent() { const [count, setCount] = useState(1) const query = R.useQuery({ - key: ["uuid4", count], + key: R.useStreamFromValues(["uuid4", count]), query: () => Console.log(`Querying ${ count } IDs...`).pipe( Effect.andThen(Effect.sleep("500 millis")), Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)), diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 93e791e..884b5f7 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -1,7 +1,9 @@ import { R } from "@/reffuse" -import { Button } from "@radix-ui/themes" +import { Button, Flex } from "@radix-ui/themes" import { createFileRoute } from "@tanstack/react-router" -import { Console, Effect } from "effect" +import { GetRandomValues, makeUuid4 } from "@typed/id" +import { Console, Effect, Stream } from "effect" +import { useState } from "react" export const Route = createFileRoute("/tests")({ @@ -20,12 +22,25 @@ function RouteComponent() { Effect.delay("1 second"), ), []) + const [reactValue, setReactValue] = useState("initial") + const reactValueStream = R.useStreamFromValues([reactValue]) + R.useFork(() => Stream.runForEach(reactValueStream, Console.log), [reactValueStream]) + + const logValue = R.useCallbackSync(Effect.fn(function*(value: string) { yield* Effect.log(value) }), []) + const generateUuid = R.useCallbackSync(() => makeUuid4.pipe( + Effect.provide(GetRandomValues.CryptoRandom), + Effect.map(setReactValue), + ), []) + return ( - + + + + ) } diff --git a/packages/extension-query/src/QueryExtension.ts b/packages/extension-query/src/QueryExtension.ts index fe7dd1e..cdc0d7e 100644 --- a/packages/extension-query/src/QueryExtension.ts +++ b/packages/extension-query/src/QueryExtension.ts @@ -1,13 +1,13 @@ import * as AsyncData from "@typed/async-data" -import { Array, Context, Effect, ExecutionStrategy, Fiber, Layer, Ref, Schema, Stream, SubscriptionRef } from "effect" +import { Context, Effect, Fiber, Layer, Ref, Stream, SubscriptionRef } from "effect" import * as React from "react" import { ReffuseExtension, type ReffuseHelpers } from "reffuse" import * as QueryRunner from "./QueryRunner.js" import * as QueryService from "./QueryService.js" -export interface UseQueryProps { - readonly key: Stream.Stream | readonly unknown[] +export interface UseQueryProps { + readonly key: Stream.Stream readonly query: () => Effect.Effect readonly refreshOnWindowFocus?: boolean } @@ -22,29 +22,22 @@ export interface UseQueryResult { export const QueryExtension = ReffuseExtension.make(() => ({ - useQuery( + useQuery( this: ReffuseHelpers.ReffuseHelpers, - props: UseQueryProps, + props: UseQueryProps, ): UseQueryResult { const runner = this.useMemo(() => QueryRunner.make({ - query: props.query() - }), []) + key: props.key, + query: props.query(), + }), [props.key]) - const key = React.useMemo(() => - (Array.isArray as (self: unknown) => self is readonly unknown[])(props.key) - ? props.key - : props.key, - [props.key]) - - this.useEffect( - () => Effect.addFinalizer(() => runner.forkInterrupt).pipe( - Effect.andThen(Ref.set(runner.queryRef, props.query())), - Effect.andThen(runner.forkFetch), - ), - - [runner, ...(Array.isArray(props.key) ? props.key : [])], - { finalizerExecutionStrategy: ExecutionStrategy.parallel }, - ) + this.useFork(() => Effect.addFinalizer(() => runner.forkInterrupt).pipe( + Effect.andThen(Stream.runForEach(runner.key, () => + Ref.set(runner.queryRef, props.query()).pipe( + Effect.andThen(runner.forkFetch) + ) + )) + ), [runner]) this.useFork(() => (props.refreshOnWindowFocus ?? true) ? runner.refreshOnWindowFocus diff --git a/packages/extension-query/src/QueryRunner.ts b/packages/extension-query/src/QueryRunner.ts index f17d226..97e3d35 100644 --- a/packages/extension-query/src/QueryRunner.ts +++ b/packages/extension-query/src/QueryRunner.ts @@ -3,7 +3,8 @@ import * as AsyncData from "@typed/async-data" import { Effect, Fiber, identity, Option, Ref, Stream, SubscriptionRef } from "effect" -export interface QueryRunner { +export interface QueryRunner { + readonly key: Stream.Stream readonly queryRef: SubscriptionRef.SubscriptionRef> readonly stateRef: SubscriptionRef.SubscriptionRef> readonly fiberRef: SubscriptionRef.SubscriptionRef>> @@ -16,13 +17,14 @@ export interface QueryRunner { } -export interface MakeProps { +export interface MakeProps { + readonly key: Stream.Stream readonly query: Effect.Effect } -export const make = ( - props: MakeProps -): Effect.Effect, never, R> => Effect.gen(function*() { +export const make = ( + props: MakeProps +): Effect.Effect, never, R> => Effect.gen(function*() { const context = yield* Effect.context() const queryRef = yield* SubscriptionRef.make(props.query) @@ -112,6 +114,7 @@ export const make = ( ) return { + key: props.key, queryRef, stateRef, fiberRef, diff --git a/packages/extension-query/src/QueryService.ts b/packages/extension-query/src/QueryService.ts index 6dc92d7..a9a1e24 100644 --- a/packages/extension-query/src/QueryService.ts +++ b/packages/extension-query/src/QueryService.ts @@ -1,6 +1,5 @@ import * as AsyncData from "@typed/async-data" -import { Context, Effect, Fiber, Layer, SubscriptionRef } from "effect" -import * as QueryRunner from "./QueryRunner.js" +import { Effect, Fiber, SubscriptionRef } from "effect" export interface QueryService { @@ -13,20 +12,20 @@ export const Tag = (id: Id) => < >() => Effect.Tag(id)>() -export interface LayerProps { - readonly query: Effect.Effect -} +// export interface LayerProps { +// readonly query: Effect.Effect +// } -export const layer = ( - tag: Context.TagClass>, - props: LayerProps, -): Layer.Layer => Layer.effect(tag, Effect.gen(function*() { - const runner = yield* QueryRunner.make({ - query: props.query - }) +// export const layer = ( +// tag: Context.TagClass>, +// props: LayerProps, +// ): Layer.Layer => Layer.effect(tag, Effect.gen(function*() { +// const runner = yield* QueryRunner.make({ +// query: props.query +// }) - return { - state: runner.stateRef, - refresh: runner.forkRefresh, - } -})) +// return { +// state: runner.stateRef, +// refresh: runner.forkRefresh, +// } +// })) diff --git a/packages/reffuse/src/ReffuseHelpers.ts b/packages/reffuse/src/ReffuseHelpers.ts index bdb8858..4bb0216 100644 --- a/packages/reffuse/src/ReffuseHelpers.ts +++ b/packages/reffuse/src/ReffuseHelpers.ts @@ -1,4 +1,4 @@ -import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, Layer, Pipeable, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" +import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, Layer, Pipeable, Queue, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import * as React from "react" import * as ReffuseContext from "./ReffuseContext.js" import * as ReffuseRuntime from "./ReffuseRuntime.js" @@ -407,6 +407,19 @@ export abstract class ReffuseHelpers { return [reactStateValue, setValue] } + + useStreamFromValues( + this: ReffuseHelpers, + values: A, + ): Stream.Stream { + const [queue, stream] = this.useMemo(() => Queue.unbounded().pipe( + Effect.map(queue => [queue, Stream.fromQueue(queue)] as const) + ), []) + + this.useEffect(() => Queue.offer(queue, values), values) + + return stream + } }