From bb49db0816460f53a0ca390a5d1f5426f9cd1da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 18 Jan 2025 23:24:48 +0100 Subject: [PATCH 01/24] Cleanup --- packages/example/src/routes/index.tsx | 7 +------ packages/example/src/routes/time.tsx | 18 ++---------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index c4caacd..6de5c78 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -1,10 +1,9 @@ -import { R } from "@/reffuse" import { TodosContext } from "@/todos/reffuse" import { TodosState } from "@/todos/services" import { VTodos } from "@/todos/views/VTodos" import { Container } from "@radix-ui/themes" import { createFileRoute } from "@tanstack/react-router" -import { Console, Effect, Layer } from "effect" +import { Layer } from "effect" import { useMemo } from "react" @@ -18,10 +17,6 @@ function Index() { Layer.provideMerge(TodosState.make("todos")) ), []) - R.useEffect(Effect.addFinalizer(() => Console.log("Effect cleanup")).pipe( - Effect.flatMap(() => Console.log("Effect recalculated")) - )) - return ( diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx index e2ba3d8..702a5ad 100644 --- a/packages/example/src/routes/time.tsx +++ b/packages/example/src/routes/time.tsx @@ -1,6 +1,6 @@ import { R } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" -import { Console, DateTime, Effect, Ref, Schedule, Stream } from "effect" +import { DateTime, Ref, Schedule, Stream } from "effect" const timeEverySecond = Stream.repeatEffectWithSchedule( @@ -16,21 +16,7 @@ export const Route = createFileRoute("/time")({ function Time() { const timeRef = R.useRefFromEffect(DateTime.now) - - R.useFork(Effect.addFinalizer(() => Console.log("Cleanup")).pipe( - Effect.flatMap(() => - Stream.runForEach(timeEverySecond, v => Ref.set(timeRef, v)) - ) - ), [timeRef]) - // Reffuse.useFork(Effect.addFinalizer(() => Console.log("Cleanup")).pipe( - // Effect.flatMap(() => DateTime.now), - // Effect.flatMap(v => Ref.set(timeRef, v)), - // Effect.repeat(Schedule.intersect( - // Schedule.forever, - // Schedule.spaced("1 second"), - // )), - // ), [timeRef]) - + R.useFork(Stream.runForEach(timeEverySecond, v => Ref.set(timeRef, v)), [timeRef]) const [time] = R.useRefState(timeRef) -- 2.49.1 From 707f83ce8744861b7f8c8017dd944114496afa24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 18 Jan 2025 23:27:21 +0100 Subject: [PATCH 02/24] Fix --- packages/example/src/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index f3e3987..8fe1e6d 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,10 +1,10 @@ import { FetchHttpClient } from "@effect/platform" import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser" import { createRouter, RouterProvider } from "@tanstack/react-router" -import { ReffuseRuntime } from "@thilawyn/reffuse" import { Layer } from "effect" import { StrictMode } from "react" import { createRoot } from "react-dom/client" +import { ReffuseRuntime } from "reffuse" import { GlobalContext } from "./reffuse" import { routeTree } from "./routeTree.gen" -- 2.49.1 From bca9b6d0d4f03eaa324a98d15aed23d15fbfa74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 00:26:30 +0100 Subject: [PATCH 03/24] useMemo scoping attempt --- packages/example/src/routeTree.gen.ts | 52 ++++++++++++++++++++++++-- packages/example/src/routes/__root.tsx | 2 + packages/example/src/routes/blank.tsx | 9 +++++ packages/example/src/routes/tests.tsx | 16 ++++++++ packages/reffuse/src/Reffuse.ts | 41 +++++++++++++++++++- 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 packages/example/src/routes/blank.tsx create mode 100644 packages/example/src/routes/tests.tsx diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts index 2cec328..92e0a99 100644 --- a/packages/example/src/routeTree.gen.ts +++ b/packages/example/src/routeTree.gen.ts @@ -12,7 +12,9 @@ import { Route as rootRoute } from './routes/__root' import { Route as TimeImport } from './routes/time' +import { Route as TestsImport } from './routes/tests' import { Route as CountImport } from './routes/count' +import { Route as BlankImport } from './routes/blank' import { Route as IndexImport } from './routes/index' // Create/Update Routes @@ -23,12 +25,24 @@ const TimeRoute = TimeImport.update({ getParentRoute: () => rootRoute, } as any) +const TestsRoute = TestsImport.update({ + id: '/tests', + path: '/tests', + getParentRoute: () => rootRoute, +} as any) + const CountRoute = CountImport.update({ id: '/count', path: '/count', getParentRoute: () => rootRoute, } as any) +const BlankRoute = BlankImport.update({ + id: '/blank', + path: '/blank', + getParentRoute: () => rootRoute, +} as any) + const IndexRoute = IndexImport.update({ id: '/', path: '/', @@ -46,6 +60,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/blank': { + id: '/blank' + path: '/blank' + fullPath: '/blank' + preLoaderRoute: typeof BlankImport + parentRoute: typeof rootRoute + } '/count': { id: '/count' path: '/count' @@ -53,6 +74,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CountImport parentRoute: typeof rootRoute } + '/tests': { + id: '/tests' + path: '/tests' + fullPath: '/tests' + preLoaderRoute: typeof TestsImport + parentRoute: typeof rootRoute + } '/time': { id: '/time' path: '/time' @@ -67,41 +95,51 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute + '/blank': typeof BlankRoute '/count': typeof CountRoute + '/tests': typeof TestsRoute '/time': typeof TimeRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/count' | '/time' + fullPaths: '/' | '/blank' | '/count' | '/tests' | '/time' fileRoutesByTo: FileRoutesByTo - to: '/' | '/count' | '/time' - id: '__root__' | '/' | '/count' | '/time' + to: '/' | '/blank' | '/count' | '/tests' | '/time' + id: '__root__' | '/' | '/blank' | '/count' | '/tests' | '/time' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + BlankRoute: typeof BlankRoute CountRoute: typeof CountRoute + TestsRoute: typeof TestsRoute TimeRoute: typeof TimeRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + BlankRoute: BlankRoute, CountRoute: CountRoute, + TestsRoute: TestsRoute, TimeRoute: TimeRoute, } @@ -116,16 +154,24 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", + "/blank", "/count", + "/tests", "/time" ] }, "/": { "filePath": "index.tsx" }, + "/blank": { + "filePath": "blank.tsx" + }, "/count": { "filePath": "count.tsx" }, + "/tests": { + "filePath": "tests.tsx" + }, "/time": { "filePath": "time.tsx" } diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index beef033..25107eb 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -17,6 +17,8 @@ function Root() { Index Time Count + Tests + Blank diff --git a/packages/example/src/routes/blank.tsx b/packages/example/src/routes/blank.tsx new file mode 100644 index 0000000..3d1cd68 --- /dev/null +++ b/packages/example/src/routes/blank.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/blank')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/blank"!
+} diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx new file mode 100644 index 0000000..729f1e0 --- /dev/null +++ b/packages/example/src/routes/tests.tsx @@ -0,0 +1,16 @@ +import { R } from "@/reffuse" +import { createFileRoute } from "@tanstack/react-router" +import { Console, Effect } from "effect" + + +export const Route = createFileRoute("/tests")({ + component: RouteComponent +}) + +function RouteComponent() { + R.useMemo(Effect.addFinalizer(() => Console.log("Cleanup!")).pipe( + Effect.map(() => "test") + )) + + return
Hello "/tests"!
+} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index fd42420..dc1c6c6 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -66,6 +66,43 @@ export class Reffuse { * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ useMemo( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: RenderOptions & ScopeOptions, + ): A { + const runSync = this.useRunSync() + + const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => + Effect.provideService(effect, Scope.Scope, scope).pipe( + Effect.map(value => [value, scope] as const) + ) + ), + + runSync, + ), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...(deps ?? []), + ]) + + React.useEffect(() => { + console.log("effect", value, scope) + // return () => { console.log("cleanup", value, scope); runSync(Scope.close(scope, Exit.void)) } + }, [scope]) + + return value + } + + /** + * Reffuse equivalent to `React.useMemo`. + * + * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ + * Trying to run an asynchronous effect will throw. + * + * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. + * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. + */ + useMemoUnscoped( effect: Effect.Effect, deps?: React.DependencyList, options?: RenderOptions, @@ -238,14 +275,14 @@ export class Reffuse { useRef(value: A): SubscriptionRef.SubscriptionRef { - return this.useMemo( + return this.useMemoUnscoped( SubscriptionRef.make(value), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes ) } - useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { + useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { return this.useMemo( effect.pipe(Effect.flatMap(SubscriptionRef.make)), [], -- 2.49.1 From 1ec3e616bb2179a27f8324985d504c623d857d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 00:33:42 +0100 Subject: [PATCH 04/24] Tests --- packages/reffuse/src/Reffuse.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index dc1c6c6..bc9fe8b 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -57,6 +57,8 @@ export class Reffuse { /** + * ⚠️ Scope closing on cleanup is currently broken when using React strict mode! ⚠️ + * * Reffuse equivalent to `React.useMemo`. * * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ @@ -87,7 +89,7 @@ export class Reffuse { React.useEffect(() => { console.log("effect", value, scope) - // return () => { console.log("cleanup", value, scope); runSync(Scope.close(scope, Exit.void)) } + return () => { console.log("cleanup", value, scope); runSync(Scope.close(scope, Exit.void)) } }, [scope]) return value -- 2.49.1 From d070ebb9035dff496ea2ccdd1fceed38a48f40be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 00:48:46 +0100 Subject: [PATCH 05/24] Comment logs --- packages/reffuse/src/Reffuse.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index bc9fe8b..aaee1ae 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -88,8 +88,11 @@ export class Reffuse { ]) React.useEffect(() => { - console.log("effect", value, scope) - return () => { console.log("cleanup", value, scope); runSync(Scope.close(scope, Exit.void)) } + // console.log("effect", value, scope) + return () => { + // console.log("cleanup", value, scope) + runSync(Scope.close(scope, Exit.void)) + } }, [scope]) return value -- 2.49.1 From 7ada793943c7dcd7e82f3d8829ea499ce492c428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 01:32:26 +0100 Subject: [PATCH 06/24] useMemoSync --- packages/reffuse/src/Reffuse.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index aaee1ae..e87c891 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -98,16 +98,7 @@ export class Reffuse { return value } - /** - * Reffuse equivalent to `React.useMemo`. - * - * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ - * Trying to run an asynchronous effect will throw. - * - * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. - * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. - */ - useMemoUnscoped( + useMemoSync( effect: Effect.Effect, deps?: React.DependencyList, options?: RenderOptions, @@ -280,15 +271,15 @@ export class Reffuse { useRef(value: A): SubscriptionRef.SubscriptionRef { - return this.useMemoUnscoped( + return this.useMemoSync( SubscriptionRef.make(value), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes ) } - useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { - return this.useMemo( + useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { + return this.useMemoSync( effect.pipe(Effect.flatMap(SubscriptionRef.make)), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes -- 2.49.1 From b0fac2b027e1e89af0cc70890bafae68eb989a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 01:35:15 +0100 Subject: [PATCH 07/24] Revert --- packages/reffuse/src/Reffuse.ts | 75 ++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index e87c891..f6b7fac 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -67,38 +67,47 @@ export class Reffuse { * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ + // useMemo( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: RenderOptions & ScopeOptions, + // ): A { + // const runSync = this.useRunSync() + + // const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + // Effect.flatMap(scope => + // Effect.provideService(effect, Scope.Scope, scope).pipe( + // Effect.map(value => [value, scope] as const) + // ) + // ), + + // runSync, + // ), [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + // ...(deps ?? []), + // ]) + + // React.useEffect(() => { + // // console.log("effect", value, scope) + // return () => { + // // console.log("cleanup", value, scope) + // runSync(Scope.close(scope, Exit.void)) + // } + // }, [scope]) + + // return value + // } + + /** + * Reffuse equivalent to `React.useMemo`. + * + * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ + * Trying to run an asynchronous effect will throw. + * + * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. + * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. + */ useMemo( - effect: Effect.Effect, - deps?: React.DependencyList, - options?: RenderOptions & ScopeOptions, - ): A { - const runSync = this.useRunSync() - - const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( - Effect.flatMap(scope => - Effect.provideService(effect, Scope.Scope, scope).pipe( - Effect.map(value => [value, scope] as const) - ) - ), - - runSync, - ), [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - ...(deps ?? []), - ]) - - React.useEffect(() => { - // console.log("effect", value, scope) - return () => { - // console.log("cleanup", value, scope) - runSync(Scope.close(scope, Exit.void)) - } - }, [scope]) - - return value - } - - useMemoSync( effect: Effect.Effect, deps?: React.DependencyList, options?: RenderOptions, @@ -271,7 +280,7 @@ export class Reffuse { useRef(value: A): SubscriptionRef.SubscriptionRef { - return this.useMemoSync( + return this.useMemo( SubscriptionRef.make(value), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes @@ -279,7 +288,7 @@ export class Reffuse { } useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { - return this.useMemoSync( + return this.useMemo( effect.pipe(Effect.flatMap(SubscriptionRef.make)), [], { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes -- 2.49.1 From ea00defc1d899f8a16fed2114bf4e21a15f234bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 01:45:34 +0100 Subject: [PATCH 08/24] Cleanup --- packages/example/src/routes/__root.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index 25107eb..b9e5fa0 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -1,7 +1,8 @@ import { Container, Flex, Theme } from "@radix-ui/themes" -import "@radix-ui/themes/styles.css" import { createRootRoute, Link, Outlet } from "@tanstack/react-router" import { TanStackRouterDevtools } from "@tanstack/router-devtools" + +import "@radix-ui/themes/styles.css" import "../index.css" -- 2.49.1 From a78d52c60ad3afc1effe770628960803c3f644d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 02:46:09 +0100 Subject: [PATCH 09/24] useSuspense attempt --- packages/example/src/routes/tests.tsx | 11 ++++-- packages/reffuse/src/Reffuse.ts | 53 +++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 729f1e0..1dee1f5 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -1,6 +1,6 @@ import { R } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" -import { Console, Effect } from "effect" +import { Effect } from "effect" export const Route = createFileRoute("/tests")({ @@ -8,9 +8,12 @@ export const Route = createFileRoute("/tests")({ }) function RouteComponent() { - R.useMemo(Effect.addFinalizer(() => Console.log("Cleanup!")).pipe( - Effect.map(() => "test") - )) + // R.useMemo(Effect.addFinalizer(() => Console.log("Cleanup!")).pipe( + // Effect.map(() => "test") + // )) + + const value = R.useSuspense(Effect.succeed("test")) + console.log(value) return
Hello "/tests"!
} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index f6b7fac..e30a56d 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -55,6 +55,19 @@ export class Reffuse { ), [runtime, context]) } + useRunCallback() { + const runtime = ReffuseRuntime.useRuntime() + const context = this.useContext() + + return React.useCallback(( + effect: Effect.Effect, + options?: Runtime.RunCallbackOptions, + ): Runtime.Cancel => effect.pipe( + Effect.provide(context), + effect => Runtime.runCallback(runtime)(effect, options), + ), [runtime, context]) + } + /** * ⚠️ Scope closing on cleanup is currently broken when using React strict mode! ⚠️ @@ -213,18 +226,44 @@ export class Reffuse { ]) } + // useSuspense( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: { readonly signal?: AbortSignal } & RenderOptions, + // ): A { + // const runPromise = this.useRunPromise() + + // const promise = React.useMemo(() => runPromise(effect, options), [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], + // ...(deps ?? []), + // ]) + // return React.use(promise) + // } + useSuspense( effect: Effect.Effect, deps?: React.DependencyList, - options?: { readonly signal?: AbortSignal } & RenderOptions, + options?: RenderOptions, ): A { - const runPromise = this.useRunPromise() + const runSync = this.useRunPromise() + const runCallback = this.useRunCallback() - const promise = React.useMemo(() => runPromise(effect, options), [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], - ...(deps ?? []), - ]) - return React.use(promise) + const promiseRef = React.useRef(Promise.withResolvers
().promise) + + React.useEffect(() => { + const { promise, resolve, reject } = Promise.withResolvers() + promiseRef.current = promise + + runCallback(effect, { + onExit: Exit.mapBoth({ + onSuccess: resolve, + onFailure: reject, + }) + }) + }, deps) + + console.log(promiseRef.current) + return React.use(promiseRef.current) } /** -- 2.49.1 From eee29e3d1d031f3c5227618e426bfdc95c2282fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 03:08:34 +0100 Subject: [PATCH 10/24] Revert --- packages/reffuse/src/Reffuse.ts | 40 ++++++--------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index e30a56d..0f9acdc 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -226,44 +226,18 @@ export class Reffuse { ]) } - // useSuspense( - // effect: Effect.Effect, - // deps?: React.DependencyList, - // options?: { readonly signal?: AbortSignal } & RenderOptions, - // ): A { - // const runPromise = this.useRunPromise() - - // const promise = React.useMemo(() => runPromise(effect, options), [ - // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], - // ...(deps ?? []), - // ]) - // return React.use(promise) - // } - useSuspense( effect: Effect.Effect, deps?: React.DependencyList, - options?: RenderOptions, + options?: { readonly signal?: AbortSignal } & RenderOptions, ): A { - const runSync = this.useRunPromise() - const runCallback = this.useRunCallback() + const runPromise = this.useRunPromise() - const promiseRef = React.useRef(Promise.withResolvers().promise) - - React.useEffect(() => { - const { promise, resolve, reject } = Promise.withResolvers() - promiseRef.current = promise - - runCallback(effect, { - onExit: Exit.mapBoth({ - onSuccess: resolve, - onFailure: reject, - }) - }) - }, deps) - - console.log(promiseRef.current) - return React.use(promiseRef.current) + const promise = React.useMemo(() => runPromise(effect, options), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], + ...(deps ?? []), + ]) + return React.use(promise) } /** -- 2.49.1 From 567140ac1d6c3baf2e78c6ab5a6898830a3daf68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 03:09:27 +0100 Subject: [PATCH 11/24] useMemo --- packages/reffuse/src/Reffuse.ts | 74 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 0f9acdc..5fe882d 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -80,36 +80,36 @@ export class Reffuse { * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ - // useMemo( - // effect: Effect.Effect, - // deps?: React.DependencyList, - // options?: RenderOptions & ScopeOptions, - // ): A { - // const runSync = this.useRunSync() + useMemo( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: RenderOptions & ScopeOptions, + ): A { + const runSync = this.useRunSync() - // const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( - // Effect.flatMap(scope => - // Effect.provideService(effect, Scope.Scope, scope).pipe( - // Effect.map(value => [value, scope] as const) - // ) - // ), + const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => + Effect.provideService(effect, Scope.Scope, scope).pipe( + Effect.map(value => [value, scope] as const) + ) + ), - // runSync, - // ), [ - // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - // ...(deps ?? []), - // ]) + runSync, + ), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...(deps ?? []), + ]) - // React.useEffect(() => { - // // console.log("effect", value, scope) - // return () => { - // // console.log("cleanup", value, scope) - // runSync(Scope.close(scope, Exit.void)) - // } - // }, [scope]) + React.useEffect(() => { + // console.log("effect", value, scope) + return () => { + // console.log("cleanup", value, scope) + runSync(Scope.close(scope, Exit.void)) + } + }, [scope]) - // return value - // } + return value + } /** * Reffuse equivalent to `React.useMemo`. @@ -120,18 +120,18 @@ export class Reffuse { * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ - useMemo( - effect: Effect.Effect, - deps?: React.DependencyList, - options?: RenderOptions, - ): A { - const runSync = this.useRunSync() + // useMemo( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: RenderOptions, + // ): A { + // const runSync = this.useRunSync() - return React.useMemo(() => runSync(effect), [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - ...(deps ?? []), - ]) - } + // return React.useMemo(() => runSync(effect), [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + // ...(deps ?? []), + // ]) + // } /** * Reffuse equivalent to `React.useEffect`. -- 2.49.1 From d68cce4e1caf2952913774afac7acdcc6e596192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jan 2025 03:31:23 +0100 Subject: [PATCH 12/24] Added TODO --- packages/reffuse/src/ReffuseContext.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/reffuse/src/ReffuseContext.tsx b/packages/reffuse/src/ReffuseContext.tsx index 7d4a17c..dbca682 100644 --- a/packages/reffuse/src/ReffuseContext.tsx +++ b/packages/reffuse/src/ReffuseContext.tsx @@ -9,6 +9,7 @@ export class ReffuseContext { readonly Provider: ReffuseContextReactProvider constructor() { + // TODO: scope the layer creation this.Provider = (props) => { const runtime = ReffuseRuntime.useRuntime() -- 2.49.1 From b5fe545837d17820d64be6fcb0c846454ad5007d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 20 Jan 2025 22:10:48 +0100 Subject: [PATCH 13/24] Switch --- packages/reffuse/src/Reffuse.ts | 74 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 5fe882d..0f9acdc 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -80,36 +80,36 @@ export class Reffuse { * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ - useMemo( - effect: Effect.Effect, - deps?: React.DependencyList, - options?: RenderOptions & ScopeOptions, - ): A { - const runSync = this.useRunSync() + // useMemo( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: RenderOptions & ScopeOptions, + // ): A { + // const runSync = this.useRunSync() - const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( - Effect.flatMap(scope => - Effect.provideService(effect, Scope.Scope, scope).pipe( - Effect.map(value => [value, scope] as const) - ) - ), + // const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + // Effect.flatMap(scope => + // Effect.provideService(effect, Scope.Scope, scope).pipe( + // Effect.map(value => [value, scope] as const) + // ) + // ), - runSync, - ), [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - ...(deps ?? []), - ]) + // runSync, + // ), [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + // ...(deps ?? []), + // ]) - React.useEffect(() => { - // console.log("effect", value, scope) - return () => { - // console.log("cleanup", value, scope) - runSync(Scope.close(scope, Exit.void)) - } - }, [scope]) + // React.useEffect(() => { + // // console.log("effect", value, scope) + // return () => { + // // console.log("cleanup", value, scope) + // runSync(Scope.close(scope, Exit.void)) + // } + // }, [scope]) - return value - } + // return value + // } /** * Reffuse equivalent to `React.useMemo`. @@ -120,18 +120,18 @@ export class Reffuse { * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. */ - // useMemo( - // effect: Effect.Effect, - // deps?: React.DependencyList, - // options?: RenderOptions, - // ): A { - // const runSync = this.useRunSync() + useMemo( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: RenderOptions, + ): A { + const runSync = this.useRunSync() - // return React.useMemo(() => runSync(effect), [ - // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - // ...(deps ?? []), - // ]) - // } + return React.useMemo(() => runSync(effect), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...(deps ?? []), + ]) + } /** * Reffuse equivalent to `React.useEffect`. -- 2.49.1 From 20b7c7e582401c6b892b858495281ebd548c62a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 02:36:49 +0100 Subject: [PATCH 14/24] useSuspenseScoped --- packages/example/src/routes/tests.tsx | 11 ++++-- packages/reffuse/src/Reffuse.ts | 52 +++++++++++++++++++-------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 1dee1f5..b36e30a 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -1,6 +1,6 @@ import { R } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" -import { Effect } from "effect" +import { Console, Effect } from "effect" export const Route = createFileRoute("/tests")({ @@ -12,8 +12,13 @@ function RouteComponent() { // Effect.map(() => "test") // )) - const value = R.useSuspense(Effect.succeed("test")) - console.log(value) + R.useSuspenseScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( + Effect.andThen(Effect.promise(() => new Promise(resolve => + resolve("test") + ))), + + Effect.tap(Console.log), + ), []) return
Hello "/tests"!
} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 0f9acdc..83c5df4 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -226,20 +226,6 @@ export class Reffuse { ]) } - useSuspense( - effect: Effect.Effect, - deps?: React.DependencyList, - options?: { readonly signal?: AbortSignal } & RenderOptions, - ): A { - const runPromise = this.useRunPromise() - - const promise = React.useMemo(() => runPromise(effect, options), [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], - ...(deps ?? []), - ]) - return React.use(promise) - } - /** * An asynchronous and non-blocking alternative to `React.useEffect`. * @@ -291,6 +277,44 @@ export class Reffuse { ]) } + // useSuspense( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: { readonly signal?: AbortSignal } & RenderOptions, + // ): A { + // const runPromise = this.useRunPromise() + + // const promise = React.useMemo(() => runPromise(effect, options), [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runPromise], + // ...(deps ?? []), + // ]) + // return React.use(promise) + // } + + useSuspenseScoped( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: { readonly signal?: AbortSignal } & RenderOptions & ScopeOptions, + ): A { + const runSync = this.useRunSync() + const runPromise = this.useRunPromise() + + const initialPromise = React.useMemo(() => runPromise(Effect.scoped(effect)), []) + const [promise, setPromise] = React.useState(initialPromise) + + React.useEffect(() => { + const scope = runSync(Scope.make()) + setPromise(runPromise(Effect.provideService(effect, Scope.Scope, scope), options)) + + return () => { runPromise(Scope.close(scope, Exit.void)) } + }, [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runPromise], + ...(deps ?? []), + ]) + + return React.use(promise) + } + useRef
(value: A): SubscriptionRef.SubscriptionRef { return this.useMemo( -- 2.49.1 From 630c7d7b6cac9f93787636a9e7dbe5d374916ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 02:39:57 +0100 Subject: [PATCH 15/24] Fix --- packages/reffuse/src/Reffuse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 83c5df4..9e3b4cc 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -267,7 +267,7 @@ export class Reffuse { return () => { Fiber.interrupt(fiber).pipe( - Effect.flatMap(() => Scope.close(scope, Exit.void)), + Effect.andThen(Scope.close(scope, Exit.void)), runFork, ) } -- 2.49.1 From e2bd448654705f0e890be7efe36a6fa36424a87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 02:46:12 +0100 Subject: [PATCH 16/24] Tests --- packages/example/src/routes/tests.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index b36e30a..5528315 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -13,9 +13,9 @@ function RouteComponent() { // )) R.useSuspenseScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( - Effect.andThen(Effect.promise(() => new Promise(resolve => - resolve("test") - ))), + Effect.andThen(Effect.promise(() => new Promise(resolve => { + setTimeout(() => { resolve("test") }, 0) + }))), Effect.tap(Console.log), ), []) -- 2.49.1 From a1703aae37afdaa8512be5b0b8edfcef8af1a545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 03:46:41 +0100 Subject: [PATCH 17/24] useMemoScoped --- packages/reffuse/src/Reffuse.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 9e3b4cc..597c8b5 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -133,6 +133,29 @@ export class Reffuse { ]) } + useMemoScoped( + effect: Effect.Effect, + deps?: React.DependencyList, + options?: RenderOptions & ScopeOptions, + ): A { + const runSync = this.useRunSync() + + const initialValue = React.useMemo(() => runSync(Effect.scoped(effect)), []) + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + const scope = runSync(Scope.make(options?.finalizerExecutionStrategy)) + setValue(runSync(Effect.provideService(effect, Scope.Scope, scope))) + + return () => { runSync(Scope.close(scope, Exit.void)) } + }, [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...(deps ?? []), + ]) + + return value + } + /** * Reffuse equivalent to `React.useEffect`. * -- 2.49.1 From 50c2f1cc83bdf8aa15ec95c04173ebf249799555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 03:54:04 +0100 Subject: [PATCH 18/24] Fix --- packages/reffuse/src/Reffuse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 597c8b5..c1cd16f 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -365,7 +365,7 @@ export class Reffuse { useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { const runSync = this.useRunSync() - const initialState = React.useMemo(() => runSync(ref), [ref]) + const initialState = React.useMemo(() => runSync(ref), []) const [reactStateValue, setReactStateValue] = React.useState(initialState) this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => -- 2.49.1 From 3004235690814963c93aa3c27fec9ca9879d8fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 21 Jan 2025 03:55:07 +0100 Subject: [PATCH 19/24] Fix --- packages/reffuse/src/Reffuse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index c1cd16f..5ed64cf 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -373,8 +373,8 @@ export class Reffuse { )), [ref]) const setValue = React.useCallback((setStateAction: React.SetStateAction) => - runSync(Ref.update(ref, previousState => - SetStateAction.value(setStateAction, previousState) + runSync(Ref.update(ref, prevState => + SetStateAction.value(setStateAction, prevState) )), [ref]) -- 2.49.1 From 175cbaa704d5138c1a467021f74d7b218f2ad24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jan 2025 00:59:40 +0100 Subject: [PATCH 20/24] Cleanup --- packages/reffuse/src/Reffuse.ts | 55 +-------------------------------- 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 5ed64cf..011c9aa 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,4 +1,4 @@ -import { Context, Effect, ExecutionStrategy, Exit, Fiber, Option, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" +import { Context, Effect, ExecutionStrategy, Exit, Fiber, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" import * as ReffuseContext from "./ReffuseContext.js" import * as ReffuseRuntime from "./ReffuseRuntime.js" @@ -69,48 +69,6 @@ export class Reffuse { } - /** - * ⚠️ Scope closing on cleanup is currently broken when using React strict mode! ⚠️ - * - * Reffuse equivalent to `React.useMemo`. - * - * `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \ - * Trying to run an asynchronous effect will throw. - * - * Changes to the Reffuse runtime or context will recompute the value in addition to the deps. - * You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`. - */ - // useMemo( - // effect: Effect.Effect, - // deps?: React.DependencyList, - // options?: RenderOptions & ScopeOptions, - // ): A { - // const runSync = this.useRunSync() - - // const [value, scope] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( - // Effect.flatMap(scope => - // Effect.provideService(effect, Scope.Scope, scope).pipe( - // Effect.map(value => [value, scope] as const) - // ) - // ), - - // runSync, - // ), [ - // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - // ...(deps ?? []), - // ]) - - // React.useEffect(() => { - // // console.log("effect", value, scope) - // return () => { - // // console.log("cleanup", value, scope) - // runSync(Scope.close(scope, Exit.void)) - // } - // }, [scope]) - - // return value - // } - /** * Reffuse equivalent to `React.useMemo`. * @@ -381,17 +339,6 @@ export class Reffuse { return [reactStateValue, setValue] } - - useStreamState(stream: Stream.Stream): Option.Option { - const [reactStateValue, setReactStateValue] = React.useState(Option.none()) - - this.useFork(Stream.runForEach(stream, v => Effect.sync(() => - setReactStateValue(Option.some(v)) - )), [stream]) - - return reactStateValue - } - } -- 2.49.1 From 37cfcf6714d383f6b9895d0a507a80f75310ca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jan 2025 01:25:39 +0100 Subject: [PATCH 21/24] useLazyRefState --- bun.lockb | Bin 152424 -> 152816 bytes packages/reffuse/package.json | 1 + packages/reffuse/src/Reffuse.ts | 29 ++++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index ea854204c0c64f0ae4a18816aaeeca67a4f65389..2cfc90528c15b61d8655bc431e6e72d3aa365735 100755 GIT binary patch delta 29441 zcmeHwcU)D+*7lwwM>#5D2LuG{4Hc;egd=KHJT`1cG!_Izlwubw0h4Gn2HR*xtTDwB zHEN6ndyQg8V~-k>m}rbf%}o?b{GPS<9*QCNYTo<3?>{*|p1o$xteIJBW=+|Jy>Biz z-CJdv)wurT4R@~{aZt|QR_Z@*oOLwpMo8MJl`+AQt2>sim+1d+$bjw!g?@u)+dK^I zVrQqeNMFwsrL>}CJOnj?HU}*W8Z&a_sI*~miZ^&i@a2&W$Vf^Vm`X)3J5BCU2`%4E zP)c8}^SSm~d@Im8h_4Ci32F!G0Xn~=qPQqZdPZBLqEtqNxs;-~f!+Wm0e@&gd}UBC z(5Gb-#ToP`(2AfrpcMbmK~ZXe`k|akpy^t{8NY&e0>2J&)j+@0%UuQP4n7*%R#wuL zj7k->j9H+N%@{Q@F<~GqbVe=Zz)wRwSvCr^3TOaop_U3lMO1;ko^QBrz(Q1974Z*{ ziRd_FqH+d;k^${?>JM5a9T6S~kl-C0sG{~Ll`J%aqU?IF)cmDcGMy`V!n-JsJHo%R8x zF02Jg6;uNypE?C!pmO)Li88YA1>IhmJG-0H7d}7K#Wm1SH zXNPziiV5+_DI?+(B`-kZ2PGuOdXFBaY;6v{0|eGm0CbplVoFT(C}@$Gkd!b=SsSXw z-$F*RAS_HPcu->Ws8MmT%Fwv9F=WwCh^PFQc&^dOe8Zwi#f=;`A`aCaLT+lu0WCB$ zqhnG=P(y4-JgNUqxE8;HdzEymxCpUS*5;O)(Pdik_9g42cWkZMJUDu!H>!_s37*8N zyr%h5EGP|%=pm{`${q0J{1-u~lSYq7@E$T!QHG69PK!y2je8f>P^XOnrSx8)1+4c`d`)B|+~C4U**S6lBY_tP5K07@17 zj(93KvcFbP+{l>d#OT<#*5Ij}rl6!)El`rX19>teEjlqVVdN-fWL(mixDkpHfp`yE z4=+b+v+#r0H9xL^gbK)TcA%D#fv1KX9-~=)50osq27h>aeRG>S&TP$i?k8c;GyjMjQ^L|k-iY{H0P(aC8^(W7Dp&qqPja~Yt{peD4V z3+S(yA;>2q#%lROLCJNRfYK1F21@<#Mylo^@qAgC6#JwPYS)|U+u*g0<;&J9Yih?7 z#TPP__}&JAY&=hMwyQ1gip*oB~awz+pzoD8a+42bw+wX91`1 zNnTC(dY5(v2Rr`QCCm_J$Ahbdv6VcnnuUGMv#VK5?_)z5l3&p!Zg#bps+bgI2;vxL zE`g@C;4oM;&QPW_f9x8@;sx4DOzdSH09l#xtub9IZ!1?@9X#u>+hYfXikb|cg!Ki(0GWCyj{J_vQCkYTMO z>18s(RpIs-20m$EkR2GP=MiG4Q-P233^QKBKn&xd)e%vVKlThW^}@)(f}+{R7V$JM zi|KpBbybUC?tvytC9Rzq4|fY>Y1~}H!XEOp8Wy8JrUlhx<8*}DQ?cwUH`lZnr=oX* z`Occ@#!j+qV?TIAJJpuu2(?wq-|HU8p7HEj7WO*N!*L-u*R~ihI4MeZ)w)LTDy@b$ z^FwzLYOj_P1Ye^jhN(3IjWdW-jG<}iJp;#kL_3imi_Yh-y zH8inqh;gnOLgO2+=BL)HP8|X{mdvvoSlAk#hvQ9dZfG&I^5EkdhOs$3yP<`h;CYC0 z_vFEi!b~%#ZdFNAEA*I;eHxA}yzDLl`|V*IKB#sLql8Dgy3P*IwzeK-IiKbVa1 zTqBSz<$294#-rZqXlNE$B57$J-mgs$hu)o-GX6U2>12qOWZ?Fhkt z5HdEEIU*3!N|}uiMix@9sBxhI7S>7SNBYQ=-3V!A8Jo#`9TC!MpPe6f5upw|G@!X1 zCNeFM0LH1xqs6{})U=88pLc>hcF^^I%17~i5rbFOp zkg0VVjLms)SeVI-bc!s=_cjkSjRi+)8EKL??bp-nRE~w_OCrqzj)s6HITjq{E1}NM zTXjiPgxUIcaMV2ZYANkd8L4DPvzO^@a9Y0bFfui-g~jw6;z%QS8YFzNY*5o^>qr_n zlE=Uc4P@(hUbuz*!p$u$riN$ zsqa53{}7#0oO(4$n@$ldvdoZM*fvht+ zx1y1=vq6ZdH$v1tXc1Vk1{@U!DYWv{0uEJ`Z=GKiX7@he@Q1=l#ia;RDOwMl2S*jF z3L9(Sd;+7?EyR?J5Vff`qkhV>+gePepw#P#L)W_m8e_n9;S+;HOdAoRacsvY1qZSp zd3HOC!J{pI+%C)*hczHv6zO8+jAsECQW=e_B+8dq>V@`nUggjc4G{71_T?y}nf&;(>sm9Dlh`bsd zjy(5uPHO-kt6ZSTH70-~jWnm*3=RV^qa1YvyTj8uT1>UEJfKot=eNM2IBJqcfu`en z8XCem&{Vafw$^CP@)pnTWHIILyX3qs@q@T!hM z_V6K6U-btznx}QK7}p`LjcVL)2x%(Q!V$&Ikrv~UNW}4-?Ltf!5YpyYc()TJiZZkm%AIqM(9&D|`f)rf(I=ypE^M_Vxr zp)P@@knWngn3Y-v8s7zndC@I|o#JWTEv6E10_r1_>=bBd(}O?m9%dYeNUd2;Aq1~N z19|qOjMNx|5F*u3Ae8w49Qm&!4{sI7F7ULT7E_>2QFCzhhuMU?qxCVfp{+-+AGB1+=p-L6~+efJVYPvg9hTyY#~NFc=`wuV6%93 zl*Kr$ucE}NDb4y(e<8&<3nAEM3!(M8x5XIWpZX6gzv%!%+E7Isu%|q&kHugaz_;}Y zLveY1ET;1VG@GC?+^#~joU@2)3yvBe^Ct$vcyI&w&ZrRMLxg&(6-N$46RQ=^=GlEM zrmNtog;bX|I>*o`Cbiz>Y5gpQGO>JHzc5qZSj|_}HNdn&=M-9ejV0q$cfy*|1EFRp z3Q|3T?7*~88{rT)53rb?lNb^)X<;C}HVBKF%F#SE8(bLa$xd=}w8d04Ud~V0VH(?k z!_Ey2y9A+RD$`VUur~Ct{l^T}30xvdQXiNzEis!{x43&oR;Mg!`<7u%L)10B24`L?p3N(ES4$}!a5EN~IS_1jR zI9l{W<3fz|EDZ^-l+Nq(P`5|M9jO&;mnx}?# z4r*?fA60vp6idnvZ9)ixnl#A|g%6i;@8pM$Bh*@zt1v=_dgO<))DTscALTWYe2AQR z63-rLG3A2CFrrBeTZOu#q_Z*%*zw>n@nXH%O(C`QY0a%XT26J;_x-@>YZ18j^Eo;* zI{^+O1I-;C8E9-YhR$ASxV(W-B=wl_BtllT?|Sp>WQ%FxSUF{2mNH%j*P2iC3Nd)3 z@^L9)#>`Y2l@n7!OgRXV;_6gj@JQpq!@^j1o;Ivc1v^DO;m1*+0c-pa!5bKtG}-fUW?=cL$6>lulu@nvP+rreheU(^U%v?V(1% zE|sQ%mITrPD)3Ff1WecY8KCqdN)^r0=^RjdU;#iCF9pf~p8{2ZJwPeo5J1gwRINb` z;Gk2g@B}~trvdsACH@>hEpZ;8R{9154-CT|2GsK_A(WdMV`8%#ZhV@v!4D+lsx>EGW?h2ss3UWAm<8$MKwXALCI<2 zbqk47FaZbZ<{^6gzoR5KRL_^B%Y&-*D}^E;rl>q+NY(|5qm-Va7dTv(BTDfjbUIR} zbYYBsL@9kVC`FAmX!RqYC8Sa;e#KGBn5L%_C7+m}^F%3qqRtbgV7eZkp~n*?xk)-d z$qxZ4=xse=vYt?wQidsdJW&I`Gs($^V&BzsP1jS2(lj+w=L=KD9mYCwx3Ts>dDZMW zx)f0g&c%UBou|hWrCE20&J(5dr8-ZPg3ENCD5Za>^MxtJe}s6lb-Cw14{mT0u+DBDLUwsJU#shDDh8q{tr-^h0`6-5maDB>W~*Gc~DivQ<96G{_iMN zP)(OBOsSmeh)11@Cn$AgT~PeBQ5xV#Kcb{a6HpTL(c=qKit^Lr3)2$ZeXMK`G^Uw=IvG+o^;jnyOm z_o*++Pq9rSrJY-DL;ODp3)8`*z0vM;}pP1iS?|KB&Vs7L$#;rIvbaP{BDrtcgp5pK6~L9;Xc zbE~{p)%eM>7H&ts{y8Am@l8veoTnegJD=WoI5@J4&2HzF3lSw64D#!1%$jo9bYOYl zVnbTXL#OKR_^{nv9y;Agd~IOi2$W$CJYu?yKbr2q*G{)GN1g|6<$Dgi?|W8Ofv}dM&jvSmrUQ4MX=N@vVWy2c&vM|$ z!MSpmSvGzc+{9T{=FW4#rOtNX-m|UDgQv~5@p^L{_!V$oy#5>;zW^?Cj+NEq7r{;C z4m^}wS#3Uz+j!7i2Yv@!U2dLh<2S)Ao@-_G`7Lns=Q;3>^Q^2P&zfiB?cR6b&%k-} zi1%S1xV7(FSreWIZl!>I!peO3DgpcE!@l`e=F6kz!@exo2hN|HvS1&$q%1279Wv}sROJLtp*axmHH!p>K;1(~n;uh{LaPvQaeIHnvm1lhb`@_4qVbU zD;{{u1~+&+yl%UdW$}dV@VXuFI&cfQ%MN%QxQRQgc={y=TxE0*I2VMtm@g6H%#czR|zZYJ& z*UDD&ti7;rAM69SmPhP^ec;yav(jT9;8yO3efzC!17Ect_8owI2dr!(k2(PR4#GZg zo4Dy9>;so{(8{*(Y;c3WgneIHSvF7j680T}ec-lpmqV}*+{8mxwv*?8OFazx4qMr7 zo^}}a<-k60dwKmF*at2%$BJ8@7r{+E0{f0w*+D+-2<$rw`@kLI=A*C=+~T8Fmcws> zn|}=U9ka5dJnI^o^?XL!^} z*mnx{fji4hr(hqrq*GRQo@av_oD2JMt?VLC$c25UVIR25+~qXv12^%sm0jUE;8M@P zzB5*Kji;S~eP6*oaMyYLuV5dz%&)At4Sx~b)U&Yftd-s1)6T-abFdHGkKB9?_JLb` z&dP4_Tj1uOhkfU*>^9Fj5Bn~_K5%z<#0A&~ZtVpt9@xnPxAG$FyJ%(i`KpVs?-K00 zWM#kds7tW#GVBBQ8#i5sec+NVTk()%Hn_oG!@jSrERQFA4g0RZK5$RC%N5uMZsHXy z9%0M@mwFZUUA403Jnbs%y9WEfF<$=~>;sp1&B{vfi{Pey1N*+QG9#b%4eYxP`@ogr z=IgKz+~Vt2X5zQN&HonmeQRZ9c-FVD?>pEB&VfgK2m8RS{Z3ugz^(iq_I;pIPrn;(u zOT7jAZmFx;vb;>;DA%z-9iVu4>?>-iCd*)m7~_?E4w^fvd~SKf^w7i+@&E zHE{Fqz`i@`s&)tV-GzPNym`c3*avRyU3FChxAGU*_lvr!{Q~>$!M=Oys&)_d-G_bP z{JH5q>;so{UtQI}4SoRo9;mC@0~-tGdvOfmF2CAXb3Po$P@aQh829>@jn*?9Tku>Q z!+HJRY^)`pgkvjy5y#ft=XV>eWH`3r*Kusi%@1v?9iNS31iyu2dmjGCMr#<39r%45 zJMxG;8=fXuhGS=*hhrBW`PfFQ7mi&ydt#%t>j|uQ3M-zdYu8g)0WRsOl~ylsgP*~Q zXI5Ijo}riiKreyo!(IMBFM*r*hn3baaH-GHOV6#eiaoc@s|R1X0xo)9eP)|?fjP{} zWL6e4?;^8_sf;;@PzF9uOk;=$G9cm(MZ|A08`!TU=Qvg7O`YsSQxmf{R`kT(QE}ac zIZ>QxL`q^J{*y*Iv?bcat})hCgqLBJjB^|6Qb)IZV_+?qDl7jlXx5hD4(y@9SSB1d zHK?d9YVw>`+J9gy+Tv3IMH$`N5hy*?NZZGaBHW3M zts?)8Ato`QG5xy>oeW2c<4%kh%B36RApfPHL!7S2c&elHkdZ0L{>WE0UR-rym#UT+ zsy+EnWsMq%#|!YY4izV=vEUaB{n;R{d9njraG1*InAFk~t8H#G;zW-aW})lUe>~TI zRL*NiC4tWRc3ARMS35nfjV?nh-H@bTTRo0W@LK9|^sGFUMc0{I>v0i!9Nl|)4RPvq z^7eWpT|brA{H=N%UFF`YXYPPF3eue?x(7h=oe)P)M$j$zUAo*LJulrg-;FpbBVLbN zABO;bU52Vu%#3CJ;#e&6qkkEse;c4@rs-dO=zkFuPrwVP0n`L)0c6hmsGTmIb6_qo z51>oxGk^|&73c`ioecUnkCp&6X%`?8=nAv~S_7{EZAvI&d>k|RM}TP$(5(>*KqlOV z2|ojO0WzK1+yb-!!hx0|JB|%a?}uQ2fSv&f0j2*$Fau9vDBbga3_Jnoc>wyi*m{tw z53~mGa4J1hNso8Zqi*zQ96eH{v;e$;AfOe1C${JzI{Fs~r7>Uz0s%Zzn@&$c(gT=E z6QC*30Pq1Y_LP=@AJ7(P2ebj|0_4{VU?<&oC=J*HWq~q)9YFuP!GPy5glMW7tu2vi2D0#yJfz!h)<$^w-D7vNj;at81g z@HS8fXaR%+Eg5>O6#_IOX#gezv1lZEUd0KZ2W-v&bbqisPyuiR$^otb-41L9&~3uk z0XjiHg-YpG)Kbt7fF%Ik@ty`u1?ZkS-A5k-jHX+@^mxEfAQ9LFOhJYFfdjx#z-{0@ z@GEczy+YYf4RuJ40soyCkI9VbkF$+a11yO972A2+J~+`(QW3D={TGP z=$>*(B$NWqAx!sfLLGzFa#I? z36O}rb+>Qte-@pfbp zfMj)|EhDB4VXpL_;z};iANKSEngLA!nmgzzB?gepdjREo4k90is(>&RI~~ANYf3ku zD?kq<(QRsaa*3X1qDhd-hy=O-o`4h3nI?ix2($!z0Gf?D09K#|&>n~YX!f9qfF=Yl zfF1^G1JLZ!3J3>U05rGLY(x_Z%~G|2S^z!YL-&X)1C;=M4SQ}RHOUHk_KLKuEW#66 zCGpizR^El;Ny`FZP1aeJdCe0^H36!VbS1rLC7>0-4Imw5eY8^0ia|OQDNZdaf{GxK zB1whfNHb4>T6nAXtklmfL0!op+=xZg5o9rVwhGREe+F! zETpioCj?&9niQ3-TAZbnR$T7{B0d0+Ehft#S$`T9eu52Sl~g~H`N;@hK-Ed)4rP_n z$<2ZQN}@2WnzT|9sAyR%Wuc#xlVMNDNdF^QYGYcRi!;R&@g!3yt#N*Vlt=~1UYE|R z)+#G5(3!-g=`9haa*K1c!g4e|bO%w#NnzihyrkD(`9*7_c><_2J>OvISfs0LW7#m& zn0kgk$x|ayWi%-LfFd@6_XMPIq;ye7hN^{HFuwt76yG>f$5*MZxWOYE=s%3ybafUg z+{#6j*LvySwRB2NI1XuyA0#*W_0-vFN<#Pl!0HiI2zW_edu?2JoupP(- zwgEeVy})i@53mo|573T{Ec_BU2pk4Z0LOt-04Xk8i&}|V<`U|sW>6mt{|c0>KLeZv zE&%6&ivVT30#HVh`39iwqP)+59{_4K>dvdc6X1K`F+g(Et<=qw{{|q_(!T>E1t>8O zpys;;{0LCNG6NMttwcP9Zvxcp{{kKW_knxBPr%Q>ZQu@Y7x)FBavuV}0>1&sm#+Md zz#~9bLP}7jWJzJ=DE%5h78Q;s5xpm=3_u%a1JD!kM$poL381Is?Es3S{c}l% z_9sRP5p4ueVWdbyP-mb4AZ6+!TpMr&ssnC7J)kb&0yqGarz${?;X4B5fGR*`pb}64 zphxplsQ*cb&S9v)Y5?u-Ndc;m;z$uX8IlT6<+5UWu0I9ol!u<}mohaF_5i#9PoSnw zDNf3e;^|V5BB@|$y$OZVGr~=E^wra;X{iZibJ9Nu^aQ#C{s1XP%2Shf1IPd>+Yj&s z>bBk#EvxP$JUGcY0QN^6lKOSU)Q)ngW_UFK>^Qe zb0-};xW&Nw`8M|PX>3+*2@CQB{Ay6wk&GW(A9MAuys!xhMXEnam9LC=_`rF`(5jfS$N%6CuKV?U`7>m)9t4%ScnHjZ_4rSm}-=-+eH z-L$CZRrW*Nzj4#XO>w3r`i^HE*>bUZJo9Di#5EAtJrFMs@lRuurmvV=vw96OHxSw@ zr-b(e=Hhx&7bv%Mz?APdWpso9QX2k4D*%#>$@R#onJHN`~yPbZkAL|0BUA}Y6vHOgA?LJ#j z3n|S|D^d=L<5aeMNmswrHYXo^{}cYVR#gjS|0>uUFtl_@;r0e|cJnH!Zi6z~mP%?c zFxVqk7et2yl@whd;V<6aTUn z@ZV6njBkx(t@+gM+aPiwz>bRBAa3%VWZw4XsO>EoZumo67=+oql<-W467p?k53(-p zJN9YJ{er6Go8sE`xvoq;I%RG_N)M5g4khH9*!I}5FTWW*=t6-&rdS36wn}U#xi3p; zFH$+~5M0)8PIdT#?8zJA7YGE%m({K9IBMEG|3g;_1mx@Ns%^?CIsS0uvx1aX_985U zIkV;>d=jeaB!*|OhBXuH)$4=#ug4qj9P&8fo$iCmGntYrc4shOLrr@zY>Ld@(3FnJ zurXXpEvtVT;yLOUf>X%OhJxK^-!stx=SpDhrpoy+l59Dbe&TnXf<#O{vnd zbU&l`W*(&0i7PYUZ!*)bqQXq*nl2g=$q~;NgZw0-r-3{b8{PuZ%#pSmTG_w526q9Dd z!^Vk)L|%2k7qP2gGA>+NL9C`*(K=qD3Gavyj!o8bk%DE$O}>b9RcH6yMRQJXR~;aL zc0BE!#4e5&kyN%!(I$E2>R*_xqze^YNp#b+5$7gfhWg~pnb$(cuivJ%HU^mTKb$#q zQ~rmg=&Bc;)AXtPf9B^!8}}tZva@aeG^=&atIo17F&D^J=DL>e_UP@;Kbl%FhX2AUeV0>YQIM;^-`--pLS1qJ z(pp4rotQr3yy{f*7ZvM!xK~>m|E`x8*`oZHoc)D;=l@1y{jKf93l3iV4)|{m`F}SA z*(-Lg-0=Lpk@)vbC^9?$TX+9&jP`$jYEx7feVP70)=3|##m%z+xr>WV{Q1+EK3rZf zP&((U&cZLT8!0jcF`;x0OwzVh5-_7IC!V{5-hY+B8H z-Q-)(_qkRyf4r)<4ZEnOIEV1ZwMI=)xUFGtRvzk!3&}WppWUQ)W}Qu;=E|(2r}%sg z3ua@)oi*5$y(zrbvW12+USii;*38Y>3s*WJzrAwRihXBhy=lk%8vCMPT-NjwWk16e zxXzI9fJEeiyLVfBko!l0M6$4ahO18RLZCVXN{%`mn|R_^LxI3bJ?qBWc72=PoLs3O z<&elk)_@-%Py<<~&wRFMOU(*D6$scPx+YQ{^~-w0Z_$zG1t|?{h_6Yn2uRd|MBkj@ zueEMJ@pOSil5kuHy)t!yIg{L?-Wc;+y#j#^NFj~8U){azoqtWLR*-U1^hMTyn-Fk^ zfcv=4?8bz%83h7HC{+z9Z(5fQy}hb*hk_K3nqtj5Y-U4hiZ9o(X8t=HsFznWMow$@ z#}}W}nxJlbv4_VcH>5bAjD}}F8j(Bn^ViiB_3G6*;k_Q`U-Ff|hf8%4ZEhy*vtvyg zH}j*bbx%b!1Pm1$i3#g*F|JM{?ds?f*QVRf+=}U4AQ;j}tcL_HAWb^WDwk9<$BGTP zGMp-&QBCr#!uH&h8 zYXKuW$; z*}g(p!>w%Yv4WIru^1Kjdt!h&qpwPJ8b5Ye{kts0C5k62grB62m4sNSDSQg4J2>@nrxD9h5y&3npKvq`p%-}^`jKb z7hzw(pWYSGJ8>Ie*cZ4ButOoL7J|Igu^Bl?|KPY>2`z5;n}7ulh?0?09{^(uD8U@O_;6n#C{NiU7&chi8V7g z28u?T;rH?j0LgjgM;(WBbycejpaIe%Pz->CAtF$8*adxi3BDQSED?u@tQ1c+t4>{I z3lx_hC`irzEzlSoLABGVI&{)LQJ`dO%t@cqGJZ{Mr0Ca|H;9Q_;CFv5uv%<@fPn>z zu)Ub2n~rR4_Dj?$qIDzNRYuwQji~&SbNAwpoO1LzQJ(iJsfAMzn0Lttp+F?EBE= z$o|TZhfk1-9b1c!A>k^&Mlf)AtNkWp9NATVpx|~{PnR-p)@}v?YEKAgHLzqcerKU_4~d+e z7~6xzZ#!|jN50noKvdI*rQh2;Q!O6OkDF?p#DM(;e(3tw^N2e7U0mfC17>g8zdxad z56%_kL=z>J?LiBrisQTTXBAgDtGqZ1mB$rN@7=fv_efO0?86FYb}^LiESer*4fLtW zs1_o8b~ATjw}<%!$d4uXSU#>j;jIeQ)W%TnUdhiYco@Fz7kT&YFr;8MqT6t~p+)Lz zXp-t_UZ^LEZF^Y7T4AAnjf0R=e%&K#_g&+nt9R<5NE&@`34QGN%Wo`9kDa?=$EBQW zx&%hK{2IgVMhh>6^l)yE6dIqHH}b^vz3`*XVmFAJ{Mtj00Y5)~JS+NlwFJ7ohwna! za{Dko<#!+KdNu2QyyT|`Awf3`A@Q*Y--qEhU(DQxbwK;h13ubOs{WFWAAL^KE?ti^ zu zywL}kZ>`z5nT&y({{@II@~U5&m9bm`mIKpV(!Mbz=E8t5^h2i>QF zgg=ZMBc2~XJyV6tLDsPJyk6Qg5)>sm9fWVUjuKstvC2l=GF2kQq=S%*7Yjk$3$AM&0;-T?Uli`oa5cdP3<#IB%D`H71kx{NE)u}2o&PS&-N_3CA~ z$!}dawrX?PG4u}2I)U1}1c`DdS=AE$u;BJFOl9u9#0Oup)Y6Av*S21|fL&7sM(jDk zs=7B-)GtV&!Pky2o(N|k? zZ{J+7pj(su9gvfJ0Mmf{N`*3L*MU2;hSQD8X4+I6(N~l`j0I}2sC}6E2FUMf+&eIF z*rM~t&Op!~w!jbNzqX{zKk4|!)D8kE*f-E5W9TPlpojqZO^@5F<33q(dg*3J=q)C{ z+TlAVX58MzA51hbr~X<4H0>|GMqby}I={vF;Mb>4{qeo3AkDStQO6wS>;2c;*cTNm zk4ZNr2UCyMPVBN+jplOu>;6Qie3Jtm+&z&nt^Oe3Q*(shKK_>?F7S2bpqbRP_-Xi@d zx_h`-e2h7{`zse?v|HW-B4WzanAYPG3Zq>Ly7!)7B;u#Yua#W?V;rSNV~Wp$|9vg*|Bb zPJvdcOH?TtfE7e*0%zCL3EI5WH#Xw@3MH=rg!Si3eiq>;(b=XU;`NiPfuZUUG4&)% zW?D3z!k@zYJVm?);x9k+Qr)*r^v!YeYN(~CgIIn!X;x1X)Kw%^tUU$O z<)>DDe1Ft~WtQK!7YL|lAucRiJRv#xsg|ZK7xdp!(h+CMIImL+QqQ(32dm*?PkPjp zV$W98l$&B=E-H|p>T-Vk*r)4}fVhGR)N?-nTo>K(VKpsz6ci z76E5iHMUJeo?(+*<>$SYOxfOQ<+F0j4eSk?IEF!a4olHm#Zp0Iew^~L%p*Fs;f7h}S@h0}&r6lh_vdP3#lExHk;_l%9AOnMbzXh*vFfYpV_KnOMfr2I?;I-{ zoWnj@E7CPSRhz;#wys`x<>_>q!tf9kO<`|{+2>e8|C5!p??wIjIS848&;QU;nF$P75lfhM-Y-%vN6eKnA&X@AWb7xyVxx_Y%DP6vsL93vf7!YZw;d;AM zts5!xKkL8$_M=McZd}h8tfwFDW)SY@Sv9dU((qR4M|!M%cgj^q4C`trU#3fUttfoT MQWO28t6~5D0~z%ri~s-t delta 29043 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 E2TP=)>Hq)$ diff --git a/packages/reffuse/package.json b/packages/reffuse/package.json index e07c985..e0eb37d 100644 --- a/packages/reffuse/package.json +++ b/packages/reffuse/package.json @@ -29,6 +29,7 @@ "clean:node": "rm -rf node_modules" }, "devDependencies": { + "@typed/lazy-ref": "^0.3.3", "@types/react": "^19.0.4", "effect": "^3.12.1", "react": "^19.0.0" diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 011c9aa..389601f 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,3 +1,4 @@ +import * as LazyRef from "@typed/lazy-ref" import { Context, Effect, ExecutionStrategy, Exit, Fiber, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" import * as ReffuseContext from "./ReffuseContext.js" @@ -316,7 +317,7 @@ export class Reffuse { /** * Binds the state of a `SubscriptionRef` to the state of the React component. * - * Returns a [value, setter] tuple just like `React.useState` and triggers a re-render everytime the value of the ref changes. + * Returns a [value, setter] tuple just like `React.useState` and triggers a re-render everytime the value held by the ref changes. * * Note that the rules of React's immutable state still apply: updating a ref with the same value will not trigger a re-render. */ @@ -339,6 +340,32 @@ export class Reffuse { return [reactStateValue, setValue] } + /** + * Binds the state of a `LazyRef` from the `@typed/lazy-ref` package to the state of the React component. + * + * Returns a [value, setter] tuple just like `React.useState` and triggers a re-render everytime the value held by the ref changes. + * + * Note that the rules of React's immutable state still apply: updating a ref with the same value will not trigger a re-render. + */ + useLazyRefState(ref: LazyRef.LazyRef): [A, React.Dispatch>] { + const runSync = this.useRunSync() + + const initialState = React.useMemo(() => runSync(ref), []) + const [reactStateValue, setReactStateValue] = React.useState(initialState) + + this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => + setReactStateValue(v) + )), [ref]) + + const setValue = React.useCallback((setStateAction: React.SetStateAction) => + runSync(LazyRef.update(ref, prevState => + SetStateAction.value(setStateAction, prevState) + )), + [ref]) + + return [reactStateValue, setValue] + } + } -- 2.49.1 From 22e5bbfcc233abd6f11322c984224947c1a4ed36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jan 2025 03:18:20 +0100 Subject: [PATCH 22/24] useMemoScoped --- bun.lockb | Bin 152816 -> 152848 bytes packages/example/package.json | 1 + packages/example/src/routes/tests.tsx | 11 ++++----- packages/reffuse/src/Reffuse.ts | 32 +++++++++++++++++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/bun.lockb b/bun.lockb index 2cfc90528c15b61d8655bc431e6e72d3aa365735..5e9fd1ea742d9020c52f41ed6fd820bd82ee9316 100755 GIT binary patch delta 6772 zcmeI1d5le09LMi{L(Ma`F&KuL(x^c(47HohD5XP zo!)i3yz8>v(tU&K&+;{L9OoI(BD>JioMf?V?190cMUH|TQ2b@63M0&;vGTc~{CKDf zg;p;%mpG2kd4ftQXoJPzh1FJHi?zt=Xua8wqhTv($K?ddTSbyhmS zSf4eB!#abemS};iQ*V#eVJEBi#=4Y)u?}n$)*|b`rdqrh>wp*F8h9nHgSX%~ya#K4 zzKiqA36H5XW3R=yB>{G$pJ)2TS~EGsKpVs%+} z^>wSu$}h0G?D6j@jE_}xkwxVw|AoT%{sf(6)RbLrSy{yu7GG&`S?yMtS6f`J;_p=y zuV1^f-l7k(n)t0_I(8N&pE1`4?*!ZuR;rarv^f`@`DF`l0+~`RiDz zn_7h&ysW{1I=E#G?qJpLTKyi@+p!iGTOG#fqTgrz{fQ^8RD$InWcA-TDztwJ)PjM~ zAF@R1?9*w6b;FXdnzYf%tOH4=u67+Q9&Uv?TRhwfb+vd`9|d*L-2$@eJ*{4umG4bn zXOL>`)2+R%cm~$rk6{*q!+OY8TYU}ICG}%HBU`YR%B=0SSzcDZ+hui79_7E4 zKP57xj0`CwLrQ)Qp`oRIER_u zZeoW46!nFa2HL|F_Iee5107i^PJ?vFfXu*w3GPha7>b&_?TLGUrmXIApqi|^4R=5j zc{iXH?OHOgI1 z0-7vH0Zk%|0)1$nW~cfPEyr)cc31}**l3Wm6g00^1cfl$!`+%jVLBXwj1gv0nCjG;UkX2TSi1$i(Brovd51`}W|6v1>TfEmy+ zm(@flgqbi7@?jE;hZF2X6KbWJE;KjmbfkVCzrTuq8hcO z4Kq!p)5}{Vm8G=KYVN+8+%BbEinIUV>+a?e-oq84PSOISE8KLw3|Ru@6>h0F|9mKQ zZGWrt?kDrS$vNzlB`t7uh$khc%&M1iJb|$lZbBd{$FtbmaA>GeQs8usr;9h=s-=P0 Ip`IWA1}hLKe*gdg delta 6737 zcmeI0d2Ccw6o=m%5IR7iw3Jd2C{Zb(6j>CZw$QQ&3KS{9$X;m6Qnmsuv{s zF;EIs-S4>WtO}=8N|MSx3z~`VArgKD4fG;3h8j?ks-v5xwWtPEhsN^PLpAUSb!Mup zmx~4%<+BRWsGgve6+D49z)nKdVK=i=P)%h9s)3C`m82TjH1p3#HQ*Ao5xNSELbsqX z==-Sd=c^*qgv=o{9zBn0YJWl1@G7bls(P|(_@y_jJXF87Hwtr9qPMR!9()% z{zFHkCrYr9-c2<_J+1tKs8;?1>F7W9E>X zS-Tll|32z*T>lpZiA_~A->OSpe`H}?7o5&KS!e;O3iEX_m4AWRLG`B;w&|jm__}$e zs#RjPR9T<-OJ(>g3gdigwamQB>VB!qAI*?j!>2V9sHW1 zcda~B<$BNjQvGavis}b$FRFnYMEP?L>0+vW4`WM@_r9Yxt;1^7PMU_HTz5a$@deQ1#wt7;H@lPwi zhN`GWmpfGLZ+M((!3sC6!7Xc0i|SW0k~vj}(VE*kR12m#eig-A`Tf-OuPuu6sa0!g zYeWgCUP2$s=Ic^R=waCbBC&GcdH+&a`iC3RPB43jrxL)WOLk2)gXm(%}=Uz z(8uaapC(vzc!V9-0P!Mjr5uHDg6Qh;*Sn zkW~Msmzpir4EUCrLn_B|R1M2et&KHiuSGSb8&Iv0EvVAnRP8FPT&jMznO2%#ZRyWn zKYwg^PZ{1*hWC{EAcA{KeINF^BC24q1sXeCF zMR-pcTog{SZ9Jq}DB(TjzuDw!#{LI;%GxHiuJcJ*S=*mFWZ3)e&qtu|Ku<8xd;`oU zGc|BB+l@&{S2<~*6=|>A&wPW-r=wth^Sxv~?S)4M+UC&3XI3wjVaaCeZGC9Snw-VF>8ZGZfN64?7HAf#J|M zkdy1S>7w&eZ_phsao6AB575X`(caJp(m)f?FR&-qo#j*fHW{-QDA;t1fhu127u*EJ z@~%UB>UDsAkOHajG&~2rp)2%(PVhYRgNKm;^`hENy0;KJWLh)%VgAPID^(Rutjyh>hY#u8YlGhr#2 z#jpsp?2AFKPGOC$@D@~nAKrjCWWyx*5I%t2uoHH{9?)7#2ppZ_PV%)P+Zx(H6f}ee&;laiQBb@r8X7|rXa>z8 z7UG~KJO+&*2IAoYZ@3he!ZK(FeIO0ep)c@d_#Ax-Ga;J^Qk*#s6c|4Z3ME8AQ)mK> zp(QAU&=nL?7y~+Xp5UfBmnzt_9kziYU)tGdm!oK#B5Z|F0E+wNDG)WC%syDb4Znng za1k!S6}SrDLNuMnKr@Jicu*+dByAPhS`7*p&Vi%w6?_ffz+u`eO5dFNr?BV3cW}nX zr5*+K;Vk)cPz`OVsMEb7UzFMU3V&;*+ dpiEDtx5mtfz?Do-5AU!Mw<}KwbQ$3}{|~=N7mxq| diff --git a/packages/example/package.json b/packages/example/package.json index d73afec..440093d 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -35,6 +35,7 @@ "@effect/platform-browser": "^0.52.1", "@radix-ui/themes": "^3.1.6", "@typed/id": "^0.17.1", + "@typed/lazy-ref": "^0.3.3", "lucide-react": "^0.471.1", "mobx": "^6.13.5" } diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 5528315..a019c4b 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -1,5 +1,6 @@ import { R } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" +import { GetRandomValues, makeUuid4 } from "@typed/id" import { Console, Effect } from "effect" @@ -12,13 +13,11 @@ function RouteComponent() { // Effect.map(() => "test") // )) - R.useSuspenseScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( - Effect.andThen(Effect.promise(() => new Promise(resolve => { - setTimeout(() => { resolve("test") }, 0) - }))), - - Effect.tap(Console.log), + const value = R.useMemoScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( + Effect.andThen(makeUuid4), + Effect.provide(GetRandomValues.CryptoRandom), ), []) + console.log(value) return
Hello "/tests"!
} diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 389601f..aea43a7 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -99,13 +99,39 @@ export class Reffuse { ): A { const runSync = this.useRunSync() - const initialValue = React.useMemo(() => runSync(Effect.scoped(effect)), []) + // Calculate an initial version of the value so that it can be accessed during the first render + const [initialScope, initialValue] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => effect.pipe( + Effect.provideService(Scope.Scope, scope), + Effect.map(value => [scope, value] as const), + )), + + runSync, + ), []) + + // Keep track of the state of the initial scope + const initialScopeClosed = React.useRef(false) + const [value, setValue] = React.useState(initialValue) React.useEffect(() => { - const scope = runSync(Scope.make(options?.finalizerExecutionStrategy)) - setValue(runSync(Effect.provideService(effect, Scope.Scope, scope))) + const closeInitialScope = Scope.close(initialScope, Exit.void).pipe( + Effect.andThen(Effect.sync(() => { initialScopeClosed.current = true })), + Effect.when(() => !initialScopeClosed.current), + ) + const [scope, value] = closeInitialScope.pipe( + Effect.andThen(Scope.make(options?.finalizerExecutionStrategy).pipe( + Effect.flatMap(scope => effect.pipe( + Effect.provideService(Scope.Scope, scope), + Effect.map(value => [scope, value] as const), + )) + )), + + runSync, + ) + + setValue(value) return () => { runSync(Scope.close(scope, Exit.void)) } }, [ ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], -- 2.49.1 From 5a19b4a2cea573b9c5cbacb907a08769a0d71a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jan 2025 03:21:31 +0100 Subject: [PATCH 23/24] Cleanup --- packages/reffuse/src/Reffuse.ts | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index aea43a7..9b1f114 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -299,29 +299,29 @@ export class Reffuse { // return React.use(promise) // } - useSuspenseScoped( - effect: Effect.Effect, - deps?: React.DependencyList, - options?: { readonly signal?: AbortSignal } & RenderOptions & ScopeOptions, - ): A { - const runSync = this.useRunSync() - const runPromise = this.useRunPromise() + // useSuspenseScoped( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: { readonly signal?: AbortSignal } & RenderOptions & ScopeOptions, + // ): A { + // const runSync = this.useRunSync() + // const runPromise = this.useRunPromise() - const initialPromise = React.useMemo(() => runPromise(Effect.scoped(effect)), []) - const [promise, setPromise] = React.useState(initialPromise) + // const initialPromise = React.useMemo(() => runPromise(Effect.scoped(effect)), []) + // const [promise, setPromise] = React.useState(initialPromise) - React.useEffect(() => { - const scope = runSync(Scope.make()) - setPromise(runPromise(Effect.provideService(effect, Scope.Scope, scope), options)) + // React.useEffect(() => { + // const scope = runSync(Scope.make()) + // setPromise(runPromise(Effect.provideService(effect, Scope.Scope, scope), options)) - return () => { runPromise(Scope.close(scope, Exit.void)) } - }, [ - ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runPromise], - ...(deps ?? []), - ]) + // return () => { runPromise(Scope.close(scope, Exit.void)) } + // }, [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runPromise], + // ...(deps ?? []), + // ]) - return React.use(promise) - } + // return React.use(promise) + // } useRef
(value: A): SubscriptionRef.SubscriptionRef { -- 2.49.1 From f4a9b00a4fbfe9a020a935dbba7eedbd5940e8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jan 2025 03:40:32 +0100 Subject: [PATCH 24/24] Version bump --- bun.lockb | Bin 152848 -> 153528 bytes package.json | 2 +- packages/example/package.json | 30 +++++++++++++++--------------- packages/reffuse/package.json | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bun.lockb b/bun.lockb index 5e9fd1ea742d9020c52f41ed6fd820bd82ee9316..fa0518b996a55f809b137efb772d51e4a8a05f45 100755 GIT binary patch delta 22539 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:MY>k)^M+MeJtkvz#n!X)jT3nGq=Eqsr3sa? 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 CpC|7C delta 22108 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