From 107d1ba359db53a65cfca98beb2d9787f59a1e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Thu, 9 Jan 2025 11:51:14 +0100 Subject: [PATCH 01/87] Reffuse work --- bun.lockb | Bin 0 -> 4048 bytes packages/reffuse/package.json | 4 +++ packages/reffuse/src/Reffuse.ts | 50 ++++++++++++++++++++++++++++++++ packages/reffuse/src/index.ts | 1 + 4 files changed, 55 insertions(+) create mode 100755 bun.lockb create mode 100644 packages/reffuse/src/Reffuse.ts diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..407e203207f760871d19b874c21db119fac7397f GIT binary patch literal 4048 zcmdT{dr(wW7{AC?uppp36eU`Q!BJx``{1!EAP$ib6(%?cfNDZ zch3GcJS@u0&{0`BLlkAm6=j(XLiogbqbggi(^F!#kJrhaBT`ZaBYI{gW3>+u!x}) zLP=9f6$^Drt)@_=o$DvvZ0wdWXnTX7iecD169p~1t!)0yy>Hy&kS!6G$X8cUS=RQZ z=3wio;=y?UjtBgU;O*+%h#2D+ad^CbLBF#)2QhvR;A6S?(fqFNKVm}u z=tFa&^Fzij(HLGAI$LC@ME&^2puT6r)9c%dh@PbZpBrK4eWuhrUVIKHY{BgnfivWS zkiJ!u{BJd{dsr2d`e4PhcSHZGZn)5-s|heRj-2;dX6pgX^a&4Z()$WBC&*%&M08_! zS!>b6rJd3FJYN1e3et`RZdRQWjV>wITy454XnK25;pWncv~7y{q0-}L>4x7= z-`jP4x{rCy+>HI}^CyqiZZB)}KXbh>eD993`n{8HmYn(dnyF=z)^fR1{$a=8JYJkP zwtXnva%e()i--7B{-Fg2D>9BAxpCk-zvD@5S?m1k;)*M+k(GD7wj7k_)cPK*%ifXV zy=TUy@h9b<*4C_izrI@U-oWEU`G-xAle*!=z2L1uV=DF*ZS$I)k*nI6&@&}s>Zati zc|FTg&vo89e;+%QcbxjW8|riwtl|w{=QMZK@aXXre!I3yf~i`x8Q1*-v=j> zv%WICo*W!9xU|~CN{y&XXmP)*NewJ2Pw7mritn9N8f0~EpIF;{=c+b*+psYwTT6Z! zo8Fz;-0<*C9xwl12;>W_*7M8u4LVva`7^R9XU|YyvUJx7W27P^pleRMuz2IFQE}(n zVp6|t`>DL=o5aYz@T!pIDP5Pz!`1JVRk}|FURMpDD=uBdJr8~ZSf)MP0Rx_gRsIiR z*v-kEBU~wrXJ{3vGcc@DuP3qABInT*l_-))M2v1JWmHFsqD4x*PGc~tK@oxV_Sp^9 z(S7LP=sfJhgfQMLmn8z~33zn8=v_r`Aj)TSSJC@|-U0>3kv;$}ghxDx5Ak9y6k8GI z^MVGLFn)n7Hq)$t^~E~klRDRj^&i%6BHIIn6jT_Xk4tnFY# zPD*8@l)&l{t3Qm95LjPgeF!5lWE=@nsU1ZL^I(NI2(ih4kieP~YepD}C8eZd}UgkTQbU&8@q`v3~xMGgMA4MUzvOuoP`j>gTew&Y(#YNu67xaBdCSK7wUbw({8h zfSq))H2?{A)aTp0Bfjh9MYSx$P&9C6UJ(_{QO~>^2i|DcRw`mHx-IeP9gajCSkGX4 z3AqAiA${CY*c?nsRkl(?F;O%qa5F;Tz~hC!zz1 z6|p@_5J;_LSg}e=sj~Z-(dh&f&IZsxddLus$xKsXT4`9=4~xSGNE~&sEs>rQev)mw z>WXPi#iSKC< z=YH4$drY3sr)WlJG{DpJBs$yN1Nr&s;O4vOn77|>2^_D2`-h7EsXqD?bN3S8M`yIW RmkMv3?LVu<*?K-|{{X2`-(vs( literal 0 HcmV?d00001 diff --git a/packages/reffuse/package.json b/packages/reffuse/package.json index 291ffca..3284391 100644 --- a/packages/reffuse/package.json +++ b/packages/reffuse/package.json @@ -24,5 +24,9 @@ "clean:node": "rm -rf node_modules" }, "devDependencies": { + "@types/react": "^19.0.4", + "effect": "^3.12.1", + "react": "^19.0.0", + "typescript": "^5.7.3" } } diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts new file mode 100644 index 0000000..d54fe14 --- /dev/null +++ b/packages/reffuse/src/Reffuse.ts @@ -0,0 +1,50 @@ +import { Effect, Exit, ManagedRuntime } from "effect" +import { createContext, useContext, type Context, type ReactNode } from "react" + + +export class Reffuse { + + constructor( + private readonly runtime: ManagedRuntime.ManagedRuntime + ) { + this.Context = createContext(runtime) + } + + readonly Context: Context> + + Provider(props: { readonly children: ReactNode }) { + return this.Context.Provider({ + ...props, + value: this.runtime, + }) + } + + + useRuntime(): ManagedRuntime.ManagedRuntime { + return useContext(this.Context) + } + + + runSync(effect: Effect.Effect): A { + return this.useRuntime().runSync(effect) + } + + runSyncExit(effect: Effect.Effect): Exit.Exit { + return this.useRuntime().runSyncExit(effect) + } + + runPromise( + effect: Effect.Effect, + options?: { readonly signal?: AbortSignal }, + ): Promise { + return this.useRuntime().runPromise(effect, options) + } + + runPromiseExit( + effect: Effect.Effect, + options?: { readonly signal?: AbortSignal }, + ): Promise> { + return this.useRuntime().runPromiseExit(effect, options) + } + +} diff --git a/packages/reffuse/src/index.ts b/packages/reffuse/src/index.ts index e69de29..b5dfcef 100644 --- a/packages/reffuse/src/index.ts +++ b/packages/reffuse/src/index.ts @@ -0,0 +1 @@ +export * as Reffuse from "./Reffuse.js" -- 2.49.1 From 53232729c360952c16a3edcaa6c7deed30b3574b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Thu, 9 Jan 2025 13:06:45 +0100 Subject: [PATCH 02/87] Reffuse work --- packages/reffuse/src/Reffuse.ts | 43 ++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index d54fe14..396eeea 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,5 +1,6 @@ -import { Effect, Exit, ManagedRuntime } from "effect" -import { createContext, useContext, type Context, type ReactNode } from "react" +import { Effect, Exit, Fiber, ManagedRuntime, Stream, SubscriptionRef } from "effect" +import type { RunForkOptions } from "effect/Runtime" +import React, { useMemo } from "react" export class Reffuse { @@ -7,12 +8,12 @@ export class Reffuse { constructor( private readonly runtime: ManagedRuntime.ManagedRuntime ) { - this.Context = createContext(runtime) + this.Context = React.createContext(runtime) } - readonly Context: Context> + readonly Context: React.Context> - Provider(props: { readonly children: ReactNode }) { + Provider(props: { readonly children: React.ReactNode }) { return this.Context.Provider({ ...props, value: this.runtime, @@ -21,7 +22,7 @@ export class Reffuse { useRuntime(): ManagedRuntime.ManagedRuntime { - return useContext(this.Context) + return React.useContext(this.Context) } @@ -47,4 +48,34 @@ export class Reffuse { return this.useRuntime().runPromiseExit(effect, options) } + runFork( + self: Effect.Effect, + options?: RunForkOptions, + ): Fiber.RuntimeFiber { + return this.useRuntime().runFork(self, options) + } + + + useFork( + self: Effect.Effect, + deps?: React.DependencyList, + options?: RunForkOptions, + ): void { + return React.useEffect(() => { + const fiber = this.runFork(self, options) + return () => { this.runFork(Fiber.interrupt(fiber)) } + }, deps) + } + + useRefState(ref: SubscriptionRef.SubscriptionRef) { + const initial = useMemo(() => this.runSync(ref), [ref]) + const [value, setValueInternal] = React.useState(initial) + + this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => + setValueInternal(v) + ))) + + return [] + } + } -- 2.49.1 From c91b538c973249f44a9b62e99816d1a0d0e2151d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Thu, 9 Jan 2025 14:27:41 +0100 Subject: [PATCH 03/87] Reffuse work --- packages/reffuse/src/Reffuse.ts | 40 +++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 396eeea..f61c33d 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,6 +1,6 @@ -import { Effect, Exit, Fiber, ManagedRuntime, Stream, SubscriptionRef } from "effect" +import { Effect, Exit, Fiber, ManagedRuntime, Ref, Stream, SubscriptionRef } from "effect" import type { RunForkOptions } from "effect/Runtime" -import React, { useMemo } from "react" +import React from "react" export class Reffuse { @@ -9,17 +9,16 @@ export class Reffuse { private readonly runtime: ManagedRuntime.ManagedRuntime ) { this.Context = React.createContext(runtime) - } - readonly Context: React.Context> - - Provider(props: { readonly children: React.ReactNode }) { - return this.Context.Provider({ + this.Provider = (props: { readonly children?: React.ReactNode }) => this.Context.Provider({ ...props, value: this.runtime, }) } + readonly Context: React.Context> + readonly Provider: React.FC<{ readonly children?: React.ReactNode }> + useRuntime(): ManagedRuntime.ManagedRuntime { return React.useContext(this.Context) @@ -62,20 +61,33 @@ export class Reffuse { options?: RunForkOptions, ): void { return React.useEffect(() => { - const fiber = this.runFork(self, options) + const fiber = this.runFork(self.pipe(Effect.scoped), options) return () => { this.runFork(Fiber.interrupt(fiber)) } }, deps) } - useRefState(ref: SubscriptionRef.SubscriptionRef) { - const initial = useMemo(() => this.runSync(ref), [ref]) - const [value, setValueInternal] = React.useState(initial) + + useRef(value: A): SubscriptionRef.SubscriptionRef { + return React.useMemo(() => this.runSync(SubscriptionRef.make(value)), []) + } + + useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { + const initialState = React.useMemo(() => this.runSync(ref), [ref]) + const [reactStateValue, setReactStateValue] = React.useState(initialState) this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => - setValueInternal(v) - ))) + setReactStateValue(v) + )), [ref]) - return [] + const setValue = React.useCallback((setStateAction: React.SetStateAction) => + this.runSync(Ref.update(ref, previousState => + typeof setStateAction === "function" + ? (setStateAction as (prevState: A) => A)(previousState) + : setStateAction + )), + [ref]) + + return [reactStateValue, setValue] } } -- 2.49.1 From fcd37a591000a63435fbf51bbb2ac26c4e84ee92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Thu, 9 Jan 2025 16:02:39 +0100 Subject: [PATCH 04/87] Fix --- packages/reffuse/src/Reffuse.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index f61c33d..971d359 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,5 +1,4 @@ -import { Effect, Exit, Fiber, ManagedRuntime, Ref, Stream, SubscriptionRef } from "effect" -import type { RunForkOptions } from "effect/Runtime" +import { Effect, Exit, Fiber, ManagedRuntime, Ref, Runtime, Stream, SubscriptionRef } from "effect" import React from "react" @@ -49,7 +48,7 @@ export class Reffuse { runFork( self: Effect.Effect, - options?: RunForkOptions, + options?: Runtime.RunForkOptions, ): Fiber.RuntimeFiber { return this.useRuntime().runFork(self, options) } @@ -58,7 +57,7 @@ export class Reffuse { useFork( self: Effect.Effect, deps?: React.DependencyList, - options?: RunForkOptions, + options?: Runtime.RunForkOptions, ): void { return React.useEffect(() => { const fiber = this.runFork(self.pipe(Effect.scoped), options) -- 2.49.1 From 8624a507b3a7ada1cd81665c327c98e2ef95373c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 21:06:32 +0100 Subject: [PATCH 05/87] Example setup --- bun.lockb | Bin 4048 -> 90664 bytes package.json | 3 ++ packages/example/.gitignore | 24 +++++++++ packages/example/README.md | 50 +++++++++++++++++++ packages/example/eslint.config.js | 28 +++++++++++ packages/example/index.html | 13 +++++ packages/example/package.json | 27 ++++++++++ packages/example/public/vite.svg | 1 + packages/example/src/App.css | 42 ++++++++++++++++ packages/example/src/App.tsx | 35 +++++++++++++ packages/example/src/assets/react.svg | 1 + packages/example/src/index.css | 68 ++++++++++++++++++++++++++ packages/example/src/main.tsx | 10 ++++ packages/example/src/vite-env.d.ts | 1 + packages/example/tsconfig.app.json | 26 ++++++++++ packages/example/tsconfig.json | 7 +++ packages/example/tsconfig.node.json | 24 +++++++++ packages/example/vite.config.ts | 7 +++ 18 files changed, 367 insertions(+) create mode 100644 packages/example/.gitignore create mode 100644 packages/example/README.md create mode 100644 packages/example/eslint.config.js create mode 100644 packages/example/index.html create mode 100644 packages/example/package.json create mode 100644 packages/example/public/vite.svg create mode 100644 packages/example/src/App.css create mode 100644 packages/example/src/App.tsx create mode 100644 packages/example/src/assets/react.svg create mode 100644 packages/example/src/index.css create mode 100644 packages/example/src/main.tsx create mode 100644 packages/example/src/vite-env.d.ts create mode 100644 packages/example/tsconfig.app.json create mode 100644 packages/example/tsconfig.json create mode 100644 packages/example/tsconfig.node.json create mode 100644 packages/example/vite.config.ts diff --git a/bun.lockb b/bun.lockb index 407e203207f760871d19b874c21db119fac7397f..1f746d8a0b5c7e352219469e1d85dd87b073a803 100755 GIT binary patch literal 90664 zcmeFZcT^Qi+dX(BD;dc_vJxbNibzh9ktj(dgOY>fC`kcPK@da*QBa~F83hGYKv7YN zl941)Ku}ScDmZr_VPvK%P3?Tmk;2wav0J#9h z0AvGbgUu^r>p1{&f^rN%4uHd;o2&pEuy`9F)QJTM?Kxq~wXk_nfJ~sA8z3n_cQ-F@ z8!s=+F!5%-3*atL?rP=g=46Y(*m+s|I62#5-jQK2RG|DZz})}~08#@?$CgK9^X>p? zLAeD$*zPDm8h{4?LOusT$Rhy=+kFL2!2B>kI)E<#!tyGBkbfIne;FX;hhX!L0AV{r zfUun$Kxk(_ww@MSkAuxGkZkgqz<|SXqXY>1Y3JqaVXSk9ss!k zI(Z2=IJ;S6M!_J#cJ`nggg(s4!PU(Z%p@~x-rmX8Rsc*SMlh(bo*awraLQYG11FrF zT%5cy!T2_Sa@apB8#hlF7#2_t`!mI{S-unG;W*mbS^GGEdKWM7 z2c%zMP@!M+U{XVSE&#!hgt=RJI|=}&d>x%^96>uLFFQYY1KBuQIiCht41xsX=4j<5 zVDD_@a2w>Ij_=-0{xg8Ee^#e9x`ol%w|QSl0fc_|csdE30zSC=xcb|;+1kwjKj8f7 z1_0k%iBJ(i|e1`r14(fLUyoAl`No?Bf2MEX6-pb3{1H{bJ&d1Bi-XG&- z=W@o*6NBjm{=t6S00`H`!=OLV?{gpz{Xy~yQsR4E$#?B9bR3P6bwYjdu0)%miJ+irOx&VayN>C152+P7^GC(+9HvpdppbJ1a z@2X@m7;p=O*~)E>iylCzuL2P2X31~r*m>DlIa}Gm^867x)E^S<%} zao7v$?}IXE|29C_AFy|L`@7r0vd#U_-rf!ln4-q!d^!&h_7_xdB&7ke`KuPzsZWg; zA3xM?J;j6PM4`I#yIA!oku9NbrUNObLuibacuvXXl6 z+i`hrqA!X{Og@|XguE!<*%&6p~{B&p`P=|~nru_joo`vva)NTUrx3n zYu6JNjdvVNpPQ4*Z2wihKC`%sv6Z`)nwH8UF)c#o1kS_v^F)K@RO9bJF-37 zPnVi|ojdxg><(9ur4B0>M?+^E1*f#4FX2+<0nL{cS(nQKh$vJ{gcx7Ir^Rm<~H@rdn+8_jCZqF`y_nbOHuakB7~-IsCeUdX9ABJQ^z&f2Sp zr1U;-BG!@xlO^?cDsk_}#&Rb8^0?{0F3?dOlOv#vG>3oNF%`<3mC z`h8Do^~ZkIUk>6QR?8P7;?`2rG&_GNMIf2Ik*eiEOXy_SllnRQjDcC6mV_WOI-Cwa<74l&EEZdaUB5|w)jFle zWW;#1o%QpB7T-wwaX}p)iCx!+dm{-)#e~R-tXVHT`1DEdSxdKF&Z!&GwTgFtlI4(% zJCQM#AM;WT+MQ*6VmR06*b8kdin8ZjoJRil4- z^|hnJb6(3Z-(h?5_RPWeMHQ@hza}}BSG{S{TzL0b=OtU~Ff?D<*&lU3-;ZyIDd=&N zYp2+kNzw;AcBiG2V?EyWJ+4oH9Z<d_7 zAX&ch^eyqYbzqXf6Qtft^R(ef#fT^d0%I;Fo-@g`_$DmcEN5HnLZ*B@O@?!I3fp~W zyBH)c^u&>W%PzpdA)o6oY8ZGnP?1k=qjx8B=BstTqFzDKQdO_Q)O5Jzx} z3f5H#Dv0vDMRW()8K`zili_Ieg>y<1ep4Hc{oq}q-Ad&3&@jIUhx{`V|K^vkJ~8T^ z5VY@+Ej1K4G1x4DDP zQZi1vG+jGBaqNZR$(i-<7p+zvQ+E^8G)}NeFFh`4rI2vMJ1%O_Mv(WEPw426uV&PX z%Q;jTIG61&BJ;6+|FvEzLwfZ#60P^9<@3C8w#g+Ads_x$2zhU4WtZkku^v4Ua9LgB z%!v9sVeJG9axP^9*(>|aY1GDyNH24Zy6#!3@95@|yj0oP=)@rsOQ;>_>&`o;&}1UK zx+}gS5xFk|>7~9q#TeLMoai24xY(4eH=1)g*}B*WiQo6j%Nb@AyAb zBw~XEY_)POQ9Oyj1O-K_$!KIR$Nprwr0!~yndD2mdt8$z_UMyw-2hxI!?a57r#brj zm`>qIKO^GNCwg8^a#`7cGO}BhTv6o%x%BLzy==4xS%VDw%f9XDy%=_*N<1jx>S9po zz;wRdof~g&Ry2N{SrF9DWIV_C z)n)dc2FH2z^7Wws^C82@yXVO}I|WH&4-;>JflZ-kCGmMDa4trXkkfzV~4SuK}CiNty|HT7Vb=gyDXJcz~-qQQF z?`NdprVQuQe<``*O+xISbSZLR3U^)7m)VkSFgg6?(@Jj!(XsC;WVPH;&+pW;y{_AV zY@C0I;4yC_X@kF-U>^o&2@KQ(XOk_Zqu5sL0O7vBB@(I;V*em8Dh&7p*g8T~n}7R= z@b3XWI0A;jJb3ioxc-fY)FJ!@zz64~`!|1O9gUPYYg6Ic&rK zV8FNA27d(bEw{m!2M+<;;b#K=cH;LF@V9gS8G(oY?f72~_}j5h1|C9acH8Y%)Sw~!G29N;Tp?ZYy}kKaikd|~jg2_D%u z{D<5>||9QaI0QV0OSpUFK{_*}-0bX*${SzGHG4R<7 z?t@!N$o{ns_!58*j#2PE@0Jt7Zvu-pv=50;0}p=KLPGcg;AJpz@#NFQMR?-IgiX2M`pvGyS!94EJw5Pl%wBjX2&P-80z z;a38_64pNCA?3f*K-#SVz8v7gxWl=Nly8+U#SFenL)rgR{|f;hjz2Q)e|rDV0zQl% z^c&j#Q~SK&%RcZ`QW)$n>~Au zeMZ**`}iaEze9w79Pr`#5BI-6NeDj=@ZtPL;{T`fcO3AM{{PMVV`kgjf1rIh28bWO z>kHz)G2p}bgWR`&>i-qMhwC>oeu&Oi?SBA#*nh;IKjl-hZ}Oqve=>&=d&dADjvsRW z|ITlu9^oedK0LpG*DxFF2U7n#MEEZNA3ndpaYM>}$A$GsTLzBJ{TuNgDgT`Y((Vl4 zivjy^{)1NuTTTf79^lJi`AFGTM3A zY4-^5m9XQ7l>KiUbUD&?$KK8OLHmD_5WXbf!}$aGe=>#$KN9d203XT!>G-_?e7Jrh zW53mYAofW1VK72~PXPQU0T;xrB!uq-_~uyux9U68NBBHpuvpOx?bynlgx!iOE%LPGj)1Nf5I{=+lG zRujU{0Q~K|KWPViIba|59nsv14@g^K{>}3X61zVg|9HTM=f6Lhd(c0`egohm_YW-p zQ~TcmUk=-U*oUnq#J)b*{N=FzZJdJ@ zz~=W)e{%MN8VFwp@L~L6*-mi%ssAy6uZHFS&HXzD`09WU{eias^!^hP+_VqpAKZWb z)PFy~*8}z;|4-&H62CFP*9LqzevrS_If(GZz~*TL_(XvFH}RtaFE5X7gP#KUC$_== z3HS!v;2#s&-2eWz|EYj42kay91Yz7#LfUr$KD__n+=uB;=N~0__<;L2qW7os&lvEL z`ybAoKb=3dfRBEE0pqxpg!l^|9-)1hhqnLJ|HFU}-@hS#BltTX5c__B5B-PdA87ke z?Kc3v65u1__NRPu;5_UygAJP9EBKBqgAKrgR z?EZBAiGsrmm_i$Ts^1w!)g$)p03V)zpnaI&9N0=i_;&#r&L24c;Iq@8`rilmMu7jf z>!&FA@rv#?_z{2)ug_xWV8b1$<~5&i$><0mS|~;41pvVju$OKrA^ai0SH^!}SAMf44*i>ImNo@ZtFd#t#{T-x^2pQvn~IU*LU@X#5HP1>m0q{J*__ zRFyXOpTC{|S%5Exvj3;|-!R~VfB*Q8#PxSEK-vi^Z;l_{?+E`c{qM>4cSUU?3;nbLl5xb+}WyasE_aq0Uy5qLFNup{yQI#cB@!EY!5a5bpJI1 zHy@DL@E>aa>HTvD@WCVaKmJ2MwvrJ4y8vGf@c(xH(t*Ph+<%CHQTYCAs|m5M1NgB2 zFc0Im)r9b`0KO)czf~@5hwz61A3p!W{=@xes|n$=f|rlr2(dAKXs)%Lx0N6mm~+?J zKTm#9lS9|R&j{Zh@ZtEuJiN9V1B8DS@Zs|p;{R6f0favX_^w#~-|XM!;O0>Pd}QB3 zbYUO=Eg|i006yG55dK!zA%x!z_~`v(tMLQF{!aqW9AW=tw)g?DPo}Z?{Ry=DH}8M7 z03XgD$fpArqWe2HkakIc593Dw2wPn{5N&7^i5EmtaKYy<_$SpY=Qq;-gm8W%=cE5WAiUQNH@mxmoZzz`xZqp}!IndWb0rL1aDIe?3#NZV*e(KG zkQWIqnEnl6`>2gp|1-k+XmCNjSZq6pu>2er&towTAWRTpeLT1z?*h1Bf(XkKVFslC z31R)kjoM9w^CAhGhY0JFv3ZD4KLuQ{JQZ9pL4^6MFar`qsB;Zmusj1?Frg8aU*9O* zM5uccT(CS7Trg#V3+A)1cncs*5b?m312Z6@5w_3WDE)VY_U>WZp%IqngA3{uVCy07 z1XnpW4-wW^VDk_muL_%o2AzMV1fwSPhxQji=P0(ghtqI8eEV+i^X|>FhPX% z3oru`M40~!Ga#W6@|VB`@3(JQTn7jf8ezK~P~?9>ScL=rK*R-qpx#cH!6NK08MdAr zAk>5ZKO&gW2#ct&_0#}io(7wz14s_?djUc{epvY*2_)K$c`hY0;R3J|ta$CjfJw%5XzqY;+tV#^^yz8*FY5#|lB`Tq%F zy%DH~O^mU49NQiu^xqs?Zh^%U*m_X4(GTbYOc4LOZ~lL4UxoATzxyVfz5n<96Hf8} z?wcE7+1N+nI`Q9qb2A=r|Ah17zx(E9Jka~+fA>xB9S)cr|GsbjBL9WjLpP(i&A&iz z2WC?jEsb}r<8HRKzlG7Yc4LY=anp)NJqEdAJFTw8di$@YWlYw5`Juiv`9+HbUJ@352Xv&fd9u5N*6glKy5rFsi5LtpPmo;wXfs5k6>8e zy))uE9YK}OZ(W{{(C&y&S)j~x?af@Lbzrw!b_$b9zcbGn-zA&IOY$jCPOT`xvn7lJ zj6b}Pgg3#68ytF5sIqs5WN(CB;++zg2G;iZ@*LsTJZ9UPj{@@#S?Kpv;eTa0o_77~ zsF`MB+V=si16_7)nHgd!bc2eZ^GJN*o{1#9bUnWa!?F*mudAwR$rNRi2Lsc>85kV$ z2m;0u#k?b4Jrj6w%F~KmS|4Izx=U4ek3*BokuF>x;@nzTaU2(vy}I|LjBgp19{WAUsN3> z@s5WXMKP0)*8K43eU**Ug=bDA;pue~xRnuvyEjWspSpX)(T+0azUZsZyiU8%vgf5p z3z~YxrMHNFZ=Jf7RYGxD$Nrk#n^SCbsb+T)$y1;4C!cM=>JkA#cp(Yz{Sy|(JT|#X z<{WzCap#D=xzQ4lZJxG^r}jx}+>h?N>RuJ*xv$iF{PQ&jCXO~@D_L_V<2@gJ0}ghK z-VOiEQH<3M1A_1w2uXP4dD$f%8Fkd7^=d@JUyhV(gp*jl_n)XzGhF*(+(vrFbUJcy zr;A>S%6Eqc<+-(kDLFQtHdlPoPK7oL?V~$<3#$v~!Y)JL>Fs-3pJ zo4Pn~+=$V-3|7voWvh+nv%Q#U#w4E|di|p5vsS@clWxtDe zWQ#aRdY(|S4?3_^Ql~TOh0-NML;>Oj;hEE2J+PX;&r+S3QQ1SrxgemAI>~vQ;RNdIa7Za$+h`N4DV?ri81VZwkGd{#peUM}Th z{c*R!ZjECHb>)0$SbmYO$yLn@^`xBV-;*)Z!~IevH_X&3Ge(#_Xv&M)W8uPAo=fd= zk%Ba9_i#E5j)iXM;%vkLjvbQl2%F4XW#x=3>?ll&2`{=jyIMHiiMCnqa z{f)lkSv_2WBi)CW_n0#}in%-_Mqz&JIIaC_1>IBmUDB-fJ#wUmJ|~xMC1$d_7s=fW zZn7wFAgMm*Lv-6SL~A!z7p^%pXkC`bF?MV(Nm${oa_ca|y@yA$#KSdoY{HKglr;-x41-?-F+>Lg?3-s-AfW|$85&h z$B9@q_dUzKjKdV$@NFXJpdsONN+n)To-+Qq<6;RfY+Y}tr@y3qAn9&xvfRI+yLo^!TZChnahyG*}K{_)5zr3#!o zcb)EWF&#|B`@Ul;<40<|Ps!%`u&ImwP6_W2w@${{YY)zzS<%!_6dlxic;WJjQvN5) zMe(O^k004x8>u}OgFC&J=quZlb}R47*9ooQ_to}xUv^yzvekZAn}PC|32_!6-Z7te zKB`ww^sddj5$U}rx1{UVpDys-JF8b(zc?JZz(rD5rJYN>M8eG3PNXOFfWoV``F>2k?gWN>Pdi{s89GW?-`>>j8G!4W zTA5I)n=0F`n7G(itNepUGu~r#*s~FDNTR``X8)oK*Ux zW}3sezfy0C925*9SNovnRU&C?RA-E1e2$u~+U4qn#M08SAN0F+b5*716YQeYdWX{8 zgVtT^%DheUkkkF_nAgDDknms`%ch5)i*%Rr_bK=1k1)+U#F;1(FBJ>>irkeqd^hq~ z;!?G$(NhtJ_eB!Uz9ec7H8*VUl+?<6PD|s*xOQfeaW}k!^vl&hpGP$N+MF0)khhlTZb&)$KK^8>X1>chyIe6> zQ|;u!CyQCjrVU-Zjs1chtt-f*q2V)@$h61i`hX#l1DYIY-Kc}kX0D^28cKB; zq|8?iIp9YzYOb_V8N61b@zOVT`RpW?{apRwRM%B#1yFq207;yp1x`#o}d zk@Fk=Vu;v)CMR0=JKNjo!&+il`}T1cw?7~duubvzNciF1OSjm?;;|4QQeY%>_g3bh zFk4c<1LY<)l0gC*cTHPAySBV$^A%3U&2tGJ2I_L5b*%_SMsJ6E4&34k6pP~TPx*BA z8pS!>(YX(8nEH-bqCSeNFAe8B+gVaa6^}5l#ixb;4&J(%-vxNaSkxke)+ZY$i{2g~i4=OBoCovT_4K``9aUU*GJjJhs-?W!46YrE!fd#GI?K9^(NJgP&<`;6$F}b; zUZ=YLDb+Xsb{BcF{0ZOw;Svnz4oq9=>aOoJ!Eem2X>96l?8m%l-QkZ>rasbp=mI`} zDW5#Kta%?vaOZ$VU9QivgFDn36SHbjy8F?(M1ykn z^;}fGDz_pl#{>1VF29p2X}ZuMKOWMb`pjG_i6`Sm!_MWx!r0R~n9?evYPEW1Rp8s~*s(RD3H}{U zr!qnw$@QHkSUB<_oQ;4*iMW}BuE*%hR6*=2GkNma4c*OilptDn#{V&Sq0|sfRe#*P zo|Ww2WtPLa-J8mn#J>bC|ZlBLEkd&<7Z)r&~Qi9Ks;`DngUT zoO_;QqCC!wO2U4O`Do@l`EXN?lR=yDC4%QDVYIIHm)gfVKbBW$zs3|(T2b9N2w!Z=}7D>M_n6o973-jb}m; zw65+T;Rz|Vw>mEqa~`c-t66hM>sC^nQFZAcKWfkBf9xWW_qPt7!vu2<`UZ}wTrrX9 zC(8DArKhocif;W<+)M}m=gQz85nLg_Z$th`cqSk7uaOWsej`=AwAvCg*+6UVS{0pl zUbHr-dEF!Wwk4)T)w$@wvgNI!tC7!eJ1(?xnAUlu>Ek}DXIcohdCs<>i?{KPKo3%mK>O?db|@e- z&-iIca8tA7$k#)}dROarCNu_I(=q1QHQC`<>wZGur%3N{m0G_#1<|uR&KxF`JQP}w z_v%+dlCr1hs}0@F&Heydx5Alwgvxg3lImk!-wfJ^uMI^9`Al*dOM=D8+_4R_X0$56@= zco8d>T=K_xaj^B#y^?Xx8_J`J(m$B5oVCB`M0VSy;lt#yk6F*W6qwCNNWAa!ikGGB z!ZUA-+|b=TPaZ=1yFdHALaLI8{tZ5}n7Eefa}S9tr&>vluCLP?P#ueSAIKLP}Jko6WQHbH+%lY{0bm=JV#Jzu>ni z|0KMCH9Lt`wX+ip0xcc<#Efyzo1O(^(|^t2uiB^Aqg5+aN4svusSljn8ktdpP8F^HFvxv;RmuJ$O&(>oeXYMViK z#3AafZO2{fD0aUuq=Y0{(iIxs+U^gp{(879ZJ*-MS!!y3eFdcpe#`w&!Xx3Hn~WL0 z*Rp^1fVz#x=iH#0ysO8@I84&Z>K0d%`$5>$U4+yH5$ubWTb=ILLB$+L(=0nroS{3Ul4dZdQShlN?~EB2&03w)+^+d|cLPznvS{5p zRa}`elWYS9f$DEcC5buYomV3dkLS^v(h=~ke;E5@cUdLhQti>}R;Qi$e(CyodQa~) zf4CYp+o8(W&9c919HlFV*2U|uAWTgcZjjb}VN>AXe>OGZPtxr<1zhmKRaC|$56MR;g>qp%%M|>4ubfdFK26?hl^TEnz^)4t~ z1+=cDjpFCYN~3oQD|etgyr5{A2l<_@vpS+NIfdCRC_b~py?j`^B&=5 z)27j}C|zZ=?oYn?;Sa(sxbh*fD!HgMzDXtZyj zZn8_rl%n+LwYVvK-`COG?89ZM9Q6Bj6}0X^%Pgex#w+I zS0TFV>6Zt1p%$WLkBY7+D3MGyI?jAgnZNHDPvp3R;t1vU*Y5{p`1GrHx|l*(pD=^jPv;&)EIm)bpQ zwZ5-A@N#_h5*muG9Lga`CVn`>Q((mR9qq3gTKDOc-f8ydgE=R6R~SVfuf5~)HL|lU zwK2Q?@|AQ2#Ssb%@<6N42S2W?Fy4P4ljd{5p3*iv6*!$+jCXo?N)P^q0(tIMN9(GU zUc>Z|PHT0<#Wvahk`@?K6Ack%B4%pK;3-fSrQ$EV-#@_}V3%yd*WJ!DJj9PCMHl7fxbPlUGdNvVrO0Kc6H*Dy5nlZ0ZZ9gXu+Ne2J4#mz zt!v3lw4j(}NJN`^_pH$Dhqv{ILt_GVggj8w_Gek}Yk3n{?b+pZ$=qtR=)3>3koQ%Z zN9WcpPFF^plNMuh`BprH($z-m&cA+bA?==A+8BPM@j)$Tez(_ajV|(;iM5BKw^gfx zy+w98*x&nHK(Q>6&URTYp*P+zxT9RJbjhV4+1l*hvl}Q~9klK#9o-knQVG#3W`uiQ zTym--J)cP^Ya6R^^uc8UTl-er8K#{yM*XaIg?vTt#J;;0atjj%Cm*5p4&C9APmOcBQM(Qd`Tex_rFudnd`a`}E}k5V_BE-z_M?3`ZZD;7MLZcAl3rR3 zFj%UHNbJjZFbLAWX`_nI)}fEmJ%-j5$hIk`NybmCd%4G9_maCu9!aY~V$#PtTQ7O= z!rQq)^QPeS)&M!ifv#UYq4MV|HDe+_1*ztwOm>%)rk7Ns?+ZP&uGZP~jJ5?PT$Pff zGad%rM|LfY-VnL&_btyZs-uKw$152RoypXaD={@J0bi%y>#Lp_?(56ZbT@W=s%`9* ze@+?YuRdD0&}4sIOWV%3p|>bArs>HWIfDY|BAzwJiM5ne>CFDJxpFu4zRYNOI>(fz zH-`#dD^X>M(Ahm0L(_;4QhRJndQiFsXk82M_mBBUN{Sqv8da3m^@2Ve$9qE(_fh9D zrM;_>65WHq2Rpju1?%m;NvSc549VE^v9EL)kt(-JkSLNGt|(kb=_0>Zf%15vmCV$O zm1-WUxh(#=;iXD9pK=olRONWLnI-gpf07+O!SBoXqC?3{DWG`;pIHDCao6?YkJbx? z6_ppqarvTOpmdGU{^IN>;CfB({w_VMHYcKIXf0c{>Qh7?*~>V}6QsE>`$Nsy6=j|( zPikDT3}3%+o`OEmh4ops<|^kC^NjZLn_oAd12*?JW3+BIjp{}3P>R$QqK*kOY7Mh{ znKKy?J5>qZNGUN|uXuLwykF8kAFQLt8&bSpRh-0YgsBe+w!QE&FkQSTJ}-9jy~d{Q zakTDmxj+8$ne^N@nrU^$*L0-2e%W8kij8|$h4DYz!d&Q;c1L_&k%UJ1r00`h$5(ZC zs7CvpgWMg{?3Z10I8U>xq2geI)(s7~xDqM4A{`b(0DAaX}z3o5Wa%I2ph)T;<(#ED%s;2nc$+;a;2kFbw zBcJvKuapEh67Ex_9QTm#d!0{FiqbVl>k2=YQyEiIJd~g{qPTE~B+t=Z)k0pGybo{v zo?s`7n&e<#1KH=#Ct}YkxdeZ=duJD{vxeKX8m@V0nPpPp#R48m*8;8EPJr|IQ5B2* zkyqjlq@v$d*au1P)7~un-M{E8d&vksx=??d$6@{w z)Ss14a;!{?M@;-4ap+enKY|)%Kg@~E_i~%_%o43zRP6jU$tqLMgO6oG2@A*o|%xMzuixX>Sit ze%5OoFOsWH$a|vCzgB47BmPFmKG&oy^$qgB{zyiCkWHh*jrF1gv#0fe_g4hhhtJzG z68>~`{WOgy;lqF2z_~T58pF*=?TDe-R^QI=m(TA0LG7hwG!&B@kEAn?lBEuLr!Uz7J8l4rtwrL8m_)aI)^QFX%r-c6i!y#ix+Pmg({5?>l+< zk5&2m7oT{!*Imm>>!gvD=2&5laMzEt+#EsD3v1RNJ{(lFCqd~tqIKuzuhTM_KM(#k zFl3tlxKZ`2YZF}|p>VAoZdRH&QHBT0R9U`7cjOD74ZOfjc2WhrtI=C?sHL7E01hSSI6)?b@3;=#G6N)QM#wmx=KNsf;a6l zvYxRp41L*QrNqf}l8`1RvLnqrPWNTC3X|O8H zHgAYq^iXD=^5WdDH?H50mT3p>m)?~uK7G9EDoWP{t($i->=8q7>4yt@R?nA)Jip#> ztnQb0=PgF}f;Y+Yzl2E4S7-S?;4IdM4b}~c{}7w#O}{|UU_r*mW^Nn&b$Vg*cNLrK zq$^r?H}6iJWeGm_bE_a8I!tqMJ&tx zspgJ%B!Z-e!|NSr4pM#7lt%gMhSqJnA6&p8r(^oz0=J@@<0+nFZ6_a|tk5An#%$6T z?N06yaG=l3A{pm~_pSHUB45KMqu!0tUa$CoFSkGTgl#g_=J#)#{<@=el__y#n%B#6 zOm$}#H~Kw{2U^#Arml}TrN8jLM1?BHD8rqDZ1Jb#^w+|L)~L{G6&tfe;vk!wVx z-?MEHzOux$z>3oKM(aMUXkblMu+r9FsKkqA%9`;ETJV`O3n<_`BmQz+nRi8rrPV0b zM|L0c$7whDqG#8B*>JEBTd(4huf38~TCLptjsE7i`Ji>@N|Rp*tSXvkthVaD7v3w_ zN7tpJJS)m-YFKX7d&JbjI?mw6o_#lBxzR*arnUaAo;Tp+x4?eBkE|_4>>(#&FFHxFC z)+yVRf63CG;5AY%|Nf8`rR$53Scb={Z-jH80;QKH|kGbsJ80 zbUv?((hWlEaxvRdGH2er@AkvKZH(!M-ibIPwYO^*ou=q^ULp!Mrmb zZgpppsBlqn2u15^&>f1--QTG+_m%^9K$U^irDacH`h3Z&<#im{96a35POP3|pOu?> z>wio!|2%oQHEs5%#)tPG#}eWNQ&Z*g2T{6VXx)eh*#_@|Wc_URh7PbPJ?$c4={nhe zbBAK~zCF5^yRCY7-F8)GRMicC&)39FcwXL!sktNMQubJ=orSGlUQ`zSJCJa+?$lD> zw>=y&yAme{dpUmI?8GHp)W1@0OL!}@s=4UyQ3=DW7dbf38}cOSZrr^p$`JoVXq>`x zYSuU_rTYgne9+p<$i%+m8`O^`xW1db|1tT-zDv1Gqig1_9v!;W)p(3^$IZMCD@_EXjdh<|H@}D6 zTqh&Zx{t=`qX_EA4R-KL&PU0Ac6;9Y%ydC7$k|Vr3Fn67jF#~J?z=q?F7x?ylkMjG z78{md=uz+_OF_rl!bFnp5QP}Z-zc>1o%)y7b~9x=MN23>7Oy67nyFB~C!!{uzo_*r zKU@&kSozbAsrBh0`+OYwlza6;r2XVo*(L6E`H9>uJ+ri;=zS;}t;;|fX~L*${Ce^$ z$r0;(OS_~&37Rx(l@V_p+wmH^Ulf&f?a%Q)=XA%6e7bYxTPrT3`g`7sBD*~&E7O=u z7_89m`(n_#vX5N>8vbBEA;^zP{j>bT2l)E%*RA61S&woeF=$!FE9 z`Q|*%zbLL~Ub*M+Q=B*bx94`(?sr1PAr`HhbyuZslt4j$;TM1NN%{`;@~~j*CjlDO z6-iA!ONoL)DJ-kaCbe0;Z?a>!*fn>*480^BFG4t^pQ9B^|D^POIZF2&T9q&^@lz^X;m&BT09|j9^s_X&FX}lH}Cm zrIfaTkxG$U*ZdhMYew*P?9m$ZJl&ZxK57wq1m$lWS~pu|_?hRAl|AxKbNYjp`)I?x zn1rtGzFHsV#B+p*D^0>e>Gnu{KoFO2u?%Og*e>qO+@|w;W3Ck7+{@9t$53a1(v3&! z675wWxpuvZRa;Nq*2bDGC&@%q;0`_!Zg0WD33-mwCo;lk@0~v&>*V)nl0!3Z=;clS zmNWVt@5$5)F3~+%Y->a5CZKg6cvU{hGAMW&B>Sb8Zaq!=y73ox6EmYK!hxO};`rS; zw2rzfxDBP$O#CIw*;I}b{gf$~xZ)JwB72tx-NI4yx_$wz+mI?F;Cri;r=aPZ(k0FJ zpI$6#7*Dg!zC26+CAhCv#*O~$A;Hru34KQO-4N@8_JpI*m)6`ntE!2;}YCYi1!T+<8Lt5m&>CMo@qNKgFDVg z8_>Jn#P`{fX)GXNu7=%}cQyO`wNKw;rP7bAy2)3#m?smXbT6TGFX+5;z>D8&Zg(Ic z>xDdzuYMcV!tFa3;?2jJep-*Y8U>uISnU-^35*MGjYzCF_C6$C;(vdBgm$L?TX_ek z@Mo0nWwdUnWVZg}82(qj#w*2E7lo?L3j31>9}gTadv-T&?5E8;%!+FI@%Dy65sFo* zMay5$6X(<}_MPuM@lKK@E8z1J^mAbnT9=DPRk&)6P_Mk+Lb4+zUKFQun0&Nw?i76? zUgI4$OX*{Ito2PlIy&xDFX_j>Q~n-1u~=+0@Qoy8;X!h!r965*B%^hk_(L^^n7=B< zXc)e}!P7YS(GTM7kPa@?EpGRF@v&E~H9aC98MTgiO-$54#&@R6uY z0nrDP7H>|8G*-W@k6f1(*{R4xfB$F{QQiUc`^hx4F1OaKpLg2@s{;n?QLo02PzE)S zn&aJNN&mb?R8MfZ%unw4r9FoIjvbgEZ@3SCOI5P)j&tD`6vDB&$Yykpni~DP*L1XQ z3qe=d{42F9irsM>f~J#eC4*C>K@{n&M>SY7U$*)!rh4`dJ(T4UXuRkh|Ewnc8?9_4 z={%d%HxDmeub@^B^!uG_XkF_k_X2%l_7^i)3kn=RbYP#rfD7|VVSq3>!!U8 zkJn!u$mLAk%i{WGNv;;2U(YTPo=y3c7094SHR zUPtSW>fQ=DdN{CY@d}?s`QZ}+`h1Vrl#In^%L1E}WWIKNa&UM|TCXKO)TA-$z3Ygi z`j;og2W}+T*&7fTJ+p}SKz~nm1FgGQ)Io*2(<~yW{CS705Zk+V=86OsZab9n=;}?< zcsLJDtb0ErC)VB>XUs%#Q0j%wt5h)}wxRmb71@lk_RHFbQU2aU>taUO)G#-4IF3L5 z`B=rHWbKNB_G}D(jF+>`*RhcW@Fw&n%e-x6seivSakqm@W5()oz8I&Z(lY-==dVse zCg}Su6Rn$hGDM%@q)9WyWfGnR2RtUioGcNSK0UH8Ilayd=}h>|nN(?CZ5Q7reOXEw zU?h8y!V@aN{?^#;@dFcwR{_lGD1Woix^;zB1ci&&E;T8*B=GR9wzw}Oyqed3vO9`m z;_~&g{JlOkx7xpo%oVw{NgfvGaeaQ$M=Bs)-Cp`-YmT5W-S=LU?k%*g%26Ge>ko=D zAIp)i#CJQq>LSN6`#$Vz#c`u|{(>KFx)A4m*Ccv@nWt;jeVE#yq4x)QrM389`xtiY zOV3+5^cAI>jn*AMLDP~PG+7ave8fa+Pxjq<47JwfH{lw>KWlbW%?Rk?Hu+i+u8K2g z5UvUQJhpJFzi(+_O1J7;u47VsrpO!geS90Odtf|4t>|X*?Qh(-uPTZw^Omu&Hc=*AdtlTbdMt78w4Ze2$Cdk3v6m(?Zh z!cN|NNLOjOf1mc5sU`KejO77Lm9Mkq(>4z&!PhfasUteXqrRs0#b9=`$0|sZKH8}z z%3G_s<6DBRB1$(0t*hVjeCb(b@aut#>^`YF?`8FPO8H|GxrXN`+${77t?|u+PR}k4 zbVW?A=#Dh{U65yTs{J)1>R7HnwK7YLxA+>Rn~T=XGQKTqysIoRLR0FwNw|Ug3upeH z;teP9Lix@Ib;+r?33lNcM)5FO-aq0SCCFh&G&X-IR+B9HRgZL}eVdNKBue)#T6bFE zCr@0$I{n`JXT5HDWeAIhFWx*5GhmfT_m-ezPAYax)gV~NT<@}-2_GiWaHW{ca3p#U zMyb0in&l*;XTvN?_a0hT<;KUA;VatHU0YNz9*jQay6cMebYR zd4Yk-?09bH1e4t2GdIm0<9S0h1Bck|w3yRcKRx8{GZ4}u8fxgVMxT=2=yJ*&rJIk| zrFwfmbMAH%$Is()9_O#dXr<}BWT-t}_WeWIC*rj$%Ke0VryfcK-Ej3f-gJ0J+(UuS zA3F=xTg^GIIGR3sZo-B>_Z6UZqYqMbI$GB9RGGLDnRdUPt!XT9$*4Nun#9*#xVI?F zBjGFy)%?Ldvf8~q=XSO{;h)IVJ|?EqR=V$2Y(oFHFZn2c3(>kmcK-K0RNCj4F*0;( zT%}VA76TG?ot)0%zD`d4e32NnE;Dxx?{@@&d-58J3Uc=!$c&^JjvE-6GT?Dyn|%|E z(k(*k8b7AFVbjdUlXBZ(eA0SpCu`?N!xx^Eler1BBR)^kdXzjSeLme!SePf4aH@9Q zV|*;sCuC3SZSs`%LnM5TyEp&)x6S94Vzh3Pp=+&$P0WnBRH-(`T1(R3VrjXL!n1ye zr?84<&#^kId@7B^PQieacs1W1g_Hlv!S4<(6TG{V!+O4GHhjN0%HR8F-NQ5B$VM>|PDOJlQ01CXX&{kwRgkP>G)LPwZ6?z<+MTgq`O_Jn zGuiZ9$32hIEkWy+nw=>6Nzfgzk31PsJLvGdxEUDw9$#O@CC z+Ig*O`#x)*Gjk5(FoWFR|NH*G_hs&VJhAp#d+oJr?S1y%j9dG?%f*7_)hj`rj&G?-{tD<8RpNdJ6UbGSt32SNxfYSyj?yIOwlF?4EblEc8yz6l9dWez=36_Wk& zZvWh?abbHe&YIqPziXGBGg3uzQ^j%}SIwSl*0}bfl+oSK=iT}=eASby1DcN=-uUc} z`O}3@o>_$Lsr{f0ZiIp=ON__6M5o5zm8f7t1YtXKSZr zC)Ck@_u`7AU@@hxV*uIW#*`rYNo)P7G=Y4B6-8}c#)v6sc9eS49 zeEWXV^_e#FCFk097wNl0Ecc%Eh5Y+I)<}(?xT~#GuN$@8Bg~$pehvHSRwLB%M(JCv zC;d!cHfq#J+oXx|7D;o47xmeFrh3KWE>R<7B?qUdUzZih-6@v)XP0*gJGN{aGqLh< z_g3`}D$JfdNp*Z)e~XRvj2jJ~ryf(S)i{@NYUnto+%%UWUz}QZO)nL_-_m^9Gue_8 zYv!B@7RlWumOJ`gXNCQ!zglm!96MN6HZ;9x`I}!R5C69EK-uE1p4~>;OmyG6+pCq` z{qDJ3w)YDzUjDJy$y>+ASv`MS{mmS&b%}38a(9d6wkQ%eVa&{?%VNIAdbdt$`YtJ@ zZ1J?2MfYsbRJRDcdo{?$CV!0mlYx^PoGuqzZ2ra%z7uj;jX2x+dBVP`7CTNU!bNiT zh~<8%mD281aGz4$_Dy;@~;@tYfAS7LYZ{X2VC`&obQ z%buU=f4{ONzv|=cIjxVcpVhQqf=KROvD|T;$Ho0Q;?(tXw>JIEf66-FpNAj4sye!R zJv+0SBOeSd`M6|it04pbdNH!zs|)LYR!XtG^v?S1^pNuDFQWVU1kEk7O(b`pSZ>8Z zn-)%~vLwC5#)B5yJH!_`K6OdY^0Qwm&sA&qxuE~f{9~kJ#>N(L_kU|v@1&+htL^LK zJwkTWYL{h;mG2AfB& zx?9ug(`--KPumky4knGc`Kr~Q?dta3aU;dTsYdis@x1*ZvD^mVR+&eJ?QQ?H*U?6| z%A~}fxKY1s#q~FGHM{q0YFpp%A0sv$AAO-(+LC7L!hE9NZ2p>i%*!j=$}jGtm}&FX z&)K@ENZ-R^x!s$8T-~cjql?=|q&$0gdQa&(B}z4z|F(9kcWw{I&ay1vbz3JC8Q%e4<;=MThrB*4Z~+B=?9|?y;gXs_k!?>Qw86hgdF+)bie{ zfPx);ZQbsse3m5_uXt!ymFChPw_5Z_nLDOf`HGbr4SD>1!RPiDK3;wy()YMnZmns) zev&$s3;dYiu*&*uutAVAI`-?q1{qnj`iBEG1MHMI?u;W_YUKNri z-AGte`Q+)#E7n?etMQLW?g_En@TA-gmrbg$Ft~0^sd;AG2Iafe(4o@3K6ToDKe5Qd zd$YW;dC(q@gS%}fpK{nR+PhV*cH3iXFFmukambAsUz;ZVEuLRb6U)t2{_<|8Kgzyd zebc5%j}y+V7M&>CW6J!!hmN_|UXuIkrK&4ie43Z1gnhEv{7z&xqwVnccixnFWmsmKb|^QOQH|iw~RKcgSvw z@(uGnonYHzbi(WqKc6No9rI1@9;N6~rqx4-h%WU8%>DMFr^l{o9v>_F`HSS970dOz z&^e*2!+a&MToi)STo==^n{M4+@n0jHe@@=brYflDt3 z@E!*go4$Q}Gq2y7@ihx{xBGZ-&4W_?>^uvamsU=d#a>G(qd7M`sCD1n(!GPME#}_J zy(Q&b;<9G}GLhU1V!6Y=U-w&5q*LsJ26+o@+CHr5;u*esS8wm7NgrX|^V^}n)de=T z`8daBc*MsuPnP>R)W5d0+TgQxhlj7R8~WglS4#cTBDojEawCcqNvu@aC&Fjz)~Q~B zmg5%KbbjBxXlV!Q>n$7~<~`DA-GPq2?{aNzyY0jHlkQFYpIox*lQTCy9->&1%_Fn9fd`98f<+Z2XvTh=|m&J0M9WUVL(I;(woiydypG)q~{Jwij z$f%;8{jaQB`npY8=^)?A{=IjS$gj)pvhTGjEHX6Kt;Tc{4O%V#>+5$|sMVT;=z1PO%l+(%Q8E zdD0iwtgt$I+mW(u=l81HCp|e-B=@RV?tABT+h$MNJ-%Vo_w@(=2)%t{%%@jVmL%q? z>$oh>i^i*md3d!S`8M`R*<;-uW*%%<b+3_=e+KTb>z8kKrwM!7Vl>DLYyWZzE& zAAYoXz?5ntxi`gfw{CBAj?n!%AD0`>r@2TSva9+Rz^j2X#8qzFo-=X#?U^?qvh=UM^EEc92Co7m*$A zishb6Ti-;#l!l*G@M!-|_5_f9=NgZ*5BL z@u=FS7i$u3u6tlpWuj-X3%6{=a_@=ds(X6BbV&+z>u_{Wrz6Mm_6^^)@y!agrvH>) z!z%iEOs}O{SuvqeqEq8H!$xd-;e0vTr}vh!4L>}HX+OwiUHp%b{33nR#d2@GvKf1@ zaloF)HjPV{Ei`L$-pO*?yEHdu()4L%Ub{nxi5eg6^(vkcAM zd-Q@?E4nWB>{8zOz%kXOmq)%Vf7Ze(W=f}C>t_#bQX_Q9yembIJ{w(mZOXj!Yn!~8 zaWwr=^xgTPi5H%`j(Z~Z&mM~9j$XL6euKMtrH6-Z?YU(|uYJ2;Y`Sc7!1rOmA4T`p znZ9AN{f18k7H)Bk*lE3GZ+J@Ef@@lyt#`N1flSv=EO29*N~f_|G}`rSzf_ zHCL=nUR}C^T}qw}-3(5=kQ6>;EX6DoUx0iIzxcuY+|a z;q>`(p8q>6K=~Ufi;PkPNhHdG?C+W-(mT02AV#4KlB~zO%*w`pM+$Sa{7+hd+7&Id za=G%NR77Gy@Y@pXjO;`$E$5nJf&W`AK;>Q*7^R|p5fPQxxt@vtjcKKHysyH}4^8}m zt4&TH|KFI6nd(gW8>W`V%OaGLgxXvmpK0J6LXHLg3oSrpTrH0nAjc-ExgP&BJ_z|e zc>%iS{C?j8G-S2#X6M@`{yum1IjC$j^<{sHC^1l}a0tVOIC{6ZAv+US_b&S}{5#J7 zFUsz})9^pbP3a9&%hiFn&TIDnuiwP!5*6tVU3#_Pe4zhqYX5giqq_J%`}?1d;eS^D z9O*e0$gx0<1#&EqV}TqCajVTnS37Ho@9wT5IhO(2vfDAi4$69iShb??CTpe??D#esneiy~_pk z0_b-Spm(_e`g=6=O9$vS51>6y=RWQ!PTI4fJyYc`+*7!G0G)?I;VJF(E-$}9f6-Gs z;FrHajH`<`(wb?iIX;Sm&TpYJ_UJr?o&cqbbS51s4$^_*Bzs{?wZsa*UV2Gagr&1a z={zes+cp{02vSf(3<)nKn?^0AwVdg0K$N9 zKnX+uv~MvAz}9|Ae*jx&B-k1u!8Tq=3@`xr33LQH0PTUI0QLt;3Ijy|I?ul{P!X^L z=)9yVKskWU1}qOy9+d$q0k%LXfX?TkGg%4&bhdm!fX)xx2y6oAY{}KYN?;kV6j%bx z2NnRc0XlDc7BCZ-4*UU32BrYxfKk9`U?eaMhzEuMaljy8AaD#g0vrYQ0|$Up0F|LS zfG1EFs0Y*s8US7Zwy{Wj0AHXX&S?1H*w4z-6E%cv}I@0Xi3n&XJG;bcQ>f zgR&iA76TK3F~B__9e4oz1vnziSX^HK&w+=)Bj6?Q26zm-2A%*niOQIeqm>39#=3{bvPd8Bf=0ay>L1J(jmHY|Yh zfGv;KfIt!hj864OjtWhk`%>AU}`~$P45Fas%an zvOpPt$}p8JDq~dEsGLxFp>jp##1Wt}Bn9jNDqB>BY64X5ssmK!sO(XBtPE(&s2%Pr z0IPw?z$Ad|MfGI@Fdm@%9Rnl+WLv6#0|BawaR8-17El37AQ%V&0s$G&2j~s-1bP77 zfzCi%pbgL(Xa%$Yngh*%#y}&0>MLb}53UUXUx4bZ7eMux-qSN(n*dD#KcFQ*^4bIK zfDS-MpcCK^bOpKq-GE*|KcFu_y3&0BK<^21APi6dAwVb)4nzSFKqL?i^ao-9N}mQ$ z0|S7;z#t$2AicG=A{&uik^!>8NMJZXX&(lR07d~xz-WN@#sS1fVI~5USH!ypm=2K5 zC@;qYtAMG%NZl*hzB4bal2i0DLH0g&xx0)GG`R~x=A?#V{i-8n?_?8080c{+l1I0<{5bH*CDwC8R@%umVydI!567~Xj zfjz)hU<+UYqyoEvUBFIY8$j=N0NeR{Z5X=V0Z10%d81A3!|EfrG#SU_WpSI0_sA4grUOvH*oS0g${?0O>^WQ+)J}^ti@9Q#vSaZMB)zu6NR*P zlH`SX^Fi96QuV(mMt2StD1|{G8!W7vXkR-?9#F$9!QH{b!G-hJ$kqqdi?(0&X?+nq zo(|5QAZbsHs$r?HX_oIx94H=$#$7lw$fmMa`dB= z)_I44;)(RQ)zzIX)n0kCx%IM!Q$PWOE3^()%Lm9KHR|n?hnGxxm3+t2EWrg9bw!&7 ztqVZ6DULlRRNLxRx0qQ7-jv6iFoi||6G+C)yVBLNOTqoc%)Gsv9bB9poRo4zkKddI z7oMaQvUr0dWa$yV$>62jPS_r!KJIzhr5NnSSR`s0$J6m~qp^jjgvJRxbjood@RXck zAAft@_h$k{t%+C0ASRy$GFL0_BxsmM8YfZ_s+BIl}_ zbGmu=G2eaZb>6$?W=Ot+2f}7zQSi8chsxWoVkL_7o-}*7r5OsZ6UEa86toSATYtWI zo8LCAsz6bIQUR0>b6&mjU3C73KuP9#uDI;_IpJ+rGl8-c6w0UEnhQb7Gw;j=%0XV* z=Bk#xot`8W6)5RE#q-Uec2+i{&kGc4m@7frxJlpVrB*KdOrTT(r7|d=`piyno0s-e zp!k788a!%PHSdSUO%4l`Kv1fH()&bzza}k*To5RuL9qj+PW5%3E?>_z6(}oso~gsi zc25}ap@u*?$;Z?A&c4N?-w(4DC{ID5TJMvmL$E6OM36u+w`9IsV!46s%pMNDoG4H# zfMN@t;jI>iKVOx(WYyg2x072G5-8g9zWTPSP^Hqr zE8|e-P_G>7p!G(zqV_;NrumO8%c=|p1x+QDgLF`+#Mi!@qBk7p_3KS1eC_Rs7Xx=XN`M67R{M4Cew(a{k2o(K(+s14h z!WKqY{a(|?Y#u03E71)ZO0A1}xBr^;=GK36y&3iB9YG-t(vxp$HnvU6%P7uNfx_9T)xm`* z`d*E>8Sm6c=7NWELESpszhC3wNlY4gRE%dqL6J-BF}mWwx?4XU;WY3-pRp4ZN>4rK zR-J8!-CYX`btbUrHBdb5p!!<`r4fxG44 z>@Jao4M6`Sk$h-3-OBe?%Z8wMIKVn?lE6@zG8~k%4~99lF45~6DDZVCr9U8zynyxz z%H6j%ZYJGG8sZ6(2gJ}=YvKrrTe@x9QN{!Nzzah0Agw1Z-sJy%$XS0uS~*b2)^lf9 z+1qB?iua(v*`au9f4DTy5! z{K%nQj5{cNF7yXaRm9W9w)y!Ae{Vhk3SUBIfI=3H{9N}_o3M^$7!UI;mw-b4Xa)28 zeLB2)6%7id6uPYeg=|pVX5+4blOks^X^079E>NgvvK!EHgQ{4|{h&BG)Ftcu>hozm zE~;?Ev-!l(sf|L`R%OyypA{sRM@toI$@R*otB!2`!k0;7w$2Y8(rv%h<5kTz2T=`% zX5`%#<)wMfkWR07s0gK$d~)cfln;oc((9VHYMU0VdyeHPs0-!5Lpc|><3pWX^;%G# zQf{M+X0F?>@g(r^WX@9v&KudS`@UDX=G}Q&gK6zdJ$fK0)G~GLaH&o2w%Tt;SF1k3N4{NLVvJ)-E2L_o&ezkIgJW27RM}dbgy`U%}6dK8g-tEVK zSEtni1=Wg5h&5bJipOz<_x?vo0YgBcGKzQ>fkIjztJya(Ha*xH6h5ACc|2=gemtr4 z{vS>8*^J^&*EWs=?5pR28ly`WC-C ztKPo6ls{DBL5To`dYSxByO#4=H2w?YVHAa09)|`=veLfmjZQ_LT@`qiqG_SrZtHk( zN$Iph#XvzHL(={Ph03+xp}@e?)!(T=pRc~#J-ONs@1DtI9X5K$Y$7O>gCl(|m1%kIcr#uDXdN9B86OxGB(LcmFs6=G zuHKBo(qpivWw|}GCYPQI1s(+qnkGHK;$aStLC*nwlarCQDm%S%<~^<9zL5%#{tO1R zraA)MGVhrR*XH_?JKNS+2X5{pxa;gLjUCLZug!diI>H*HrAbi74e?W~0crv&3IVtCpx4Jc~vR9tZEtm%I z9WwQ5pj+lCHPE_XPKxUGuikB%12+d?Fc)AS%&ItY={elp>7V@Lw@qR;pyo|76%?|; znc~eGE`1;NglWxEnt6|I#9N1FhmKRvS0-@_5KlQ!rlcM^q^RVaEO_g?K%v}aimG&H4&tIFLzetDDMM?qS4(v!K?+S3$^w|Lm4(9|jqkf(JpH;C9X z_X6I7N+sUeXKB^JBMRGshh`SgZyPA1+?1-3vXdRVWj;1DP#35rhsK%5^Q$y(FPeaJ zlNj*0_?9Abk829-U<2!n3!1)h_l*;nnPaUB-Y(`T`|Ri5sPA8@B`D-|qMjP?Y0k{s9l=oPWO;Kfg?tp;SRPR_h}{C23yo_HRtHaSsKBO`zl32q@GCTXu8l zd^-1P8aI;vhL$2XN$G9K0*WKwp(6D-4D!+;ks;C{=CPHiVp59rL5 zw=a-Z6w*!?Y;&o{*4H)7&B#GPlPKwkf2iFVou}r4HYuA{Fdk>}-+r|O%xiGwH7oPhGV?a^KU?cF zFYyLvFjyO(c{}=_)h!<7l*&%W2g}2!2mILu`2)`%1xup;g~J9Vg!{trYs=^Dg<6Wb z{VOFMrK1VtN7Und%PpVF-3{0rgBq|J9MORD6RI9r-l=+- zFiTDYvQ9E6G#~${{XmO0{mL{`${+pQM%kg+A}6Vn!FtN#2~%_Dg4R<7^zkaS2G&8F$XaCseWM50T879Y)$x&m zl76=B>dfm>;Iz3}DIYG!GSk{AlRTx8SQWg~!Zy3F^s99sbq8lrqPT)WtyrUW{_WaK zn6_82bt9f)`zhk(s#cZ83X~3@Q2+BhY|yBD^*j~|(qy0%2BqcJpZD(cy5}!YRG?6~ ze*R?6oKB91T7yCjDQeb8P>O>h3Eq47<>dai7=`txvq7lgCI|2X77!iJo`$)J5=64~QP4DJWE`qGp}7PDpM&6BJY{ zYOFefLRud!TlB~Lr`9yyW_8EnueS-*QtR656oXQ#M~?d zZ>X*%;#!P&>dRy&T-wrjj%M@fxJst;AyP{nxpnT`kH0`+UO-*l*~0B#FJ95(_+YLt zVOrzdTbWW8B>$#7^u0~bxW3?_u?#Gl`PUcyl-igTXvA419_us}YK`RW%nes;2Unz? zmU?vb=9!m++Gzghn^B+ZZyee#vK<2+Z9UDj24_}(umZ(k-KIRvTmyr3j(Hn~Mu#*M zcgiFls`Un23f3NEUQ05o3z&;W88uWFTqITfIGaCNIoA5hmE1IMNq$vkImalOw~5*l zke_#K>HYk#j?aahGi*`gDP0ZSs3o6UJoTAv((Z6bqp4{4ItM_Z(Nz53Td&>p*`2^s zT-+r!n{qt;S_BrTG{*l1(}1;nzMzx=&*#GO@E>lcX~qn*A?URv4V!VbKK;k(^)=iI z&0-Wb63>jsDN_yHGRadaJBIU=D2DTgD8Fxm%rr3G!e*zJSt{XL7wwu7!FR#B2u*yn zTvGR=reJ~iX|w`@ufh5}+I1Ul5_xd2JWzvGXLfg|4?A@v6%^DS$_9NONz3CT(I3lz zf;NnN|HiF24_{N-<>8m6R=b!6ScBm#84C*eVpWrlE!$^zmUD%XFfMvdha_{#vj zFWL?37~Ja9YPd<%Z;bSGKV=qY_2b|2`|>s@)K6;*TEpaQXbBTbN3<+3Toxi%JF1Y6 zF>1N5CRCx6#l}Z!U+4nx>zX2?Bcy?$^1yI>>j}byIrq1@9J#aXLp7Rcbv;M$hM*%* z#XBH$nA#yq72@~{pQH5GcT#>ml!GQ@pfBexJBF!!sZ5fFSX3mwmj*>e_>$uqrgn^0 z#)K#$r7R4`C>6hwhDJq&RIxC;Ocmkbrbm)R2C4AJ+zisd$s(mO!Uy;Ohb@#gF(2oDP%~XfuwO!D5Ihxu?8zJ)WeOU zG8l(L3Udh*wLXZ3}A`(?W{YXTle-VLRJD?wl9ywbP=`}|}6ncbg z3Fq<@t-n-g4uu#DQU7H$sF+-%YbX{SF8E2w61N4j7S^ zAsl38c!oaonZtxWx;RNC-2-^(Kk$+l%B}Zx2sEk-7YI_g8!5T6o>~d~h{H%yv||#o zCi~05QcOb8_ciE%Q3ZSDC{x5qqse&^-U#lhp?3yCk{hIzC(e5Pm*Q|4s*En6_N_Ri zu2s;5(!CMI%e^CKQ!g0nI(`Wlp@>#{xXG1zfiqfylgW2*dLdcAuJ=x?b);M)IPO~G z@Gr&TqN3%IzXsI46^B&EL`JEl;y0qEL8pn(nd!X(qmi=mYhV;RP8JwN1t;q=C@*wE z*eF0Kep#2IZD~bm{Y`Cjc7rmy``?ry$`42pJ!MmlHdR_lT*Lo+_7MsbR1zM3QzLOX z735?`AFj5bv_OpR{x`MZ_*mW2N#Q)`-?L4InyZtfbyc$?i?s|oN$e?GY11}5I#Jpe z*^Wj-(=Uo z9p0Z4HqExitT1~5@q-^_wynl>LNvHEju}fAJ zFAbDIJbgZuBJ1%=5fP)p|5y$k%Ehjc1t2gN8J0OoV-=dvs2B}~C{dAkRz?LOr~_M8 z7NLk#M94IOp^-98j7p{?vy;@QXpJHY@1j&jN`nvx87VxVM%2YOL>efmoy>X(S^3pomWY73Snk59(%BqT^a0$s z6kS;YzwRExOB&vSlPyVI_Ve~k(ctIYv1SPx>XOBRKK1kn>BHr=}E`5dHnMlXcrC9C&F494$#1vh`k`exHy zY$q+_Vs~hn=nFrRPSdtBEbCAkD>gzPCPK@>YP_jJ!|h>We6-P5%5CnYsM+2;I8Jd! z)5{!IvGc}6{qh!>NnQ%X8j+@p5i)f5Lc7Inwq$NRTh_|%Sb-)No-vpbt`p4dSu~-K z*i99Z#k^KjBtAWIwMrfsV=9CAFiZ-650g;AG;R_@-ZK;Ot5eNtr73xAQ6dDeD>$;? z+NcpcLd`lXQw3S%_Jc|EBxSqSDXr>Id4LQ90UE_%IBF!r^g=?Q-Wyi8Sc0_#HoC}y zNES{ckUg_z!j%13M}*c1%-UPlFtFB;Mo&_1x{Zxmytcse&ICYr`~YUB$(?idl3F(71Se1VKMUC*_Abb^S3dFrNy zA2<|(k`gumjtSN_vQKlI^UB82Qf*t#IwOvtH^eoP$jq4#XXZpCpL}`a4U-`yA=S`p zI4`Eho7^5`NZ@W*(`3rU(C#M&m-ZIA)2Pe1?!V4g=xI)^6;2#8A%#tv=;le-ObFz# zYnGC%HM;&@fG z)gQ?1|-9ipF?XA#e8cB)R2H+JxLU+Ci=Vtb#T;YecqLc&VFk4iF5y9eM5G;Po zQqPhhP6){GEYv$}YEfdE5;>UpdSqP~h|%4%oH1op8gAp*L-6r;Qtr%vY?bvaR8(CZ z5vGkvXVEOOsK#q2jiUm>uqvOQ<~3plgW(}Q13&9&vRIc{P2pz6xk=<`NaOFQ(3)OH z!mA__#92Vfrq>}&{3vulhK3!q^Ae4Tk@WMjvM@4YhZUI0W8v$A_S**^0oO1J%JU7S z@Hr&}GtZckhdZQ}%AyrqVhwyUVRO8heP|IU!vHTgB!WMW75^&k+$LdgCY33pLuE#d zWk4iqAU??;UEGcF??cQw283j=8m%!ml3`h4hdRHA6lum>DyqA`7me7sh(Y`)68?KK znVMR4vnEAcK8RCPTv+37s`=8*dxKebj}8?3gH4ofy(w<+>%GZR=c2Pd_;vTJTVcai zU-E>MiU4e7aKK-F(1@R7asFC_0^?OSlWxS)LLin%EeGpk*g`T|o@PpjE(~Po?vX^( zt`Rc}br9SQ630(PW1debRbgChtb_}J$U-^}4B7apXv|P@BVkjLG6saSj4#+^DRaUx z`yCmBjV8uMl)5mGqq}Dl1|~}!I~@XS+%Xa8r%jJDM046;6g@#{epqZI|Md$EM4&iP z@j>J++*(3$7<~JT3{n_Ny@Al2P@YxjB2_Vw^wkqCh}`I22fcF6OGAlgwa8Rm6vqNq z@gqwj%M&(_EoGHKBU6VPF_>ORNYs17gqoUHqUzkA5<*vSuuV_KM*#de7nB6<#0#*| zI22su9P^v^jII{t!axFdgCa@$-;GCWpcl!|Ig&K`>W%Moth3Bo(`-O0HSjveMR>@z zJgpDJu33|!^DK}k-95}~dQMU3M8PUNpbDC{ux>*gSatVO?u-n|99n=tD;A7ZI>x8t z7kG^nWSv;B>F#MPN}srlmkHjR2P1zcZPKEI7aMkX#XV?<7w89 z)o9-US%6&0CMoz^J{+dcBGtiBSnkGg;A@6dEmsC-ArxEQ&Q&~o>4x~eeQ7phU*VArkQ zTmIWq#kTlZ^h%)A=w!Da>@g)e-=(7@Xh5HD?qUV!id@hQ&gD&OV4Qa<` zv*`k2tlF~*4Sh|95m`55*6^IHi!`l^cl@V?U4kR<!m6qO9q(kY3&qL3(h5+&4#6minQFMh;u6Wfkh9mA0& z7y>#uIXQcHdSbAlz)=f0S6CyLE0OCe%C;!1ImF0T5fj0_1z=sGT!s@1xKkFS8RP~T zo4GK|0M!f=$W2G@h<3o+%MJ=dQ6s6<9vB31+FOinO@B(vkR&i?c;==JOf|oTEJbP< zFd))|CBvrtJ#jWbkob`eLb&XzYw9zvt`b0o?wpUdATcjj{a_G54uK{aY1S+do2>_BLx(SJzT$ zn`TH5+G$P(%YA%((L{x_Rlu2^Z=wqWA-a1u;bf{D@@nXM3<%KOixT1+rcwlj$YbS7 zCDCaGDeEUhuaSIit0xhR)MBxy3_P3(1sQO2QM)o8wvMy5bdlHaqQ$Su(K;mpGiycK05CQf>-OwWUba27$&0&G2I0i^l}ip&f{TI ztERO7)q8@7Uth8=#gwcJEfY=Ga*ZHsefV{KMl6rAL8mK%jlW}^hSp`I2AO8DO`eNH zUzgn>gk7OF%`b!3NW>Y`)*fc0ofqQUaj^?5YC}v*HRg3=Kn_JsgveSU;zudmm>Q=p zL9XtenO}SAMS)DuP3{2b&@$l7@eZ;_QR-my7 zRv{2X2oDg0!_8P)qhqji1OC0SwRXBN5Td)MB}wouQE1qSUic@rovM|(y{vI_{t&Qk z!eL+@Zr*mDy3@dflh3q&$58uM3m5S$5}EcKG4UVc=BH)5z~#q~1AmsA5d z@jrUw#Ovp)(=#TsB(iw5>C>h+ndh6O<2R7bPW+j1LMlan36e`rMOq%W>lJ#QSzsdPd(x7xbLR05IR!bR#5cLsbgsqrah%$zmNfczXg~U zG8U=9n1?ao_xeJZ`-g`7lx)c3R$vS#N6HcBWAp`sp|KG^qKr+fZYlShMvvY$N2;qP z{pVsEE33t^<(Yx9wV7!0<>OFs{nuZnPVE!(uVeD(eg$x`j?7~At4TzK+>g|f}NO83acHC1l4(9SGQZC8Jz@rYk z>K>v--eR`Jbva?o8F>!63Z3lVNVQyaifVK+ii9-;WFt)epMBJFBX0Brc=bX%A5VyU z#gosI7rH^o_u?MN;nxx#Q1NyA=JKXFw*V}qOW|T_9-Z}ohJT!O^B-CZzcpv!wF@)c zRc49n1PY4`WvivE8gq| zP9(*pm4U)gK)=meZ?o1hD@faVD3W^gU=-&?Onog&_!0<>&`gQ_bQsOj}NNe3tCk+q4taQT$Ch0{f zsILz|k15lKu~B#6Ot9D6;m~GAoLVM{UN~mR%6&>MEMWn6XOzYVp-^D1Fv-OH-szMGynhq diff --git a/package.json b/package.json index 8781544..a53b02d 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,8 @@ "clean:cache": "rm -f tsconfig.tsbuildinfo", "clean:dist": "rm -rf dist", "clean:node": "rm -rf node_modules" + }, + "devDependencies": { + "npm-check-updates": "^17.1.13" } } diff --git a/packages/example/.gitignore b/packages/example/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/packages/example/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/example/README.md b/packages/example/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/packages/example/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/packages/example/eslint.config.js b/packages/example/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/packages/example/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/packages/example/index.html b/packages/example/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/packages/example/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/packages/example/package.json b/packages/example/package.json new file mode 100644 index 0000000..c814829 --- /dev/null +++ b/packages/example/package.json @@ -0,0 +1,27 @@ +{ + "name": "example", + "version": "0.0.0", + "type": "module", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^19.0.4", + "@types/react-dom": "^19.0.2", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } +} diff --git a/packages/example/public/vite.svg b/packages/example/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/packages/example/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/example/src/App.css b/packages/example/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/packages/example/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx new file mode 100644 index 0000000..3d7ded3 --- /dev/null +++ b/packages/example/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/packages/example/src/assets/react.svg b/packages/example/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/packages/example/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/example/src/index.css b/packages/example/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/packages/example/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/packages/example/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/packages/example/src/vite-env.d.ts b/packages/example/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/packages/example/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/example/tsconfig.app.json b/packages/example/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/packages/example/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/packages/example/tsconfig.json b/packages/example/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/packages/example/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/packages/example/tsconfig.node.json b/packages/example/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/packages/example/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/example/vite.config.ts b/packages/example/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/packages/example/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) -- 2.49.1 From 100169952c7b6eb96d1fc11ac6179582a6c739b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 22:00:14 +0100 Subject: [PATCH 06/87] Example tests --- bun.lockb | Bin 90664 -> 90696 bytes packages/example/package.json | 1 + packages/example/src/App.tsx | 23 +++++++++++------------ packages/example/src/Reffuse.ts | 5 +++++ packages/example/src/main.tsx | 20 ++++++++++++-------- packages/example/tsconfig.app.json | 6 +++++- packages/reffuse/src/Reffuse.ts | 8 ++++++-- 7 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 packages/example/src/Reffuse.ts diff --git a/bun.lockb b/bun.lockb index 1f746d8a0b5c7e352219469e1d85dd87b073a803..3a7a538d3a29a24196e9f1365227be783685dda6 100755 GIT binary patch delta 7154 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 diff --git a/packages/example/package.json b/packages/example/package.json index c814829..75b4953 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -14,6 +14,7 @@ "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", + "effect": "^3.12.1", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 3d7ded3..34163d6 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,13 +1,14 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import "./App.css" +import reactLogo from "./assets/react.svg" +import { Reffuse } from "./Reffuse" +import viteLogo from "/vite.svg" -function App() { - const [count, setCount] = useState(0) - return ( - <> +export function App() { + + const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) + + return <>
Vite logo @@ -28,8 +29,6 @@ function App() {

Click on the Vite and React logos to learn more

- - ) -} + -export default App +} diff --git a/packages/example/src/Reffuse.ts b/packages/example/src/Reffuse.ts new file mode 100644 index 0000000..9037b3a --- /dev/null +++ b/packages/example/src/Reffuse.ts @@ -0,0 +1,5 @@ +import { Reffuse as TReffuse } from "@thilawyn/reffuse" +import { Layer } from "effect" + + +export const Reffuse = TReffuse.make(Layer.empty) diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index bef5202..e263ba2 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,10 +1,14 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { App } from "./App.tsx" +import "./index.css" +import { Reffuse } from "./Reffuse.ts" -createRoot(document.getElementById('root')!).render( - - - , + +createRoot(document.getElementById("root")!).render( + + + + + ) diff --git a/packages/example/tsconfig.app.json b/packages/example/tsconfig.app.json index 358ca9b..0592374 100644 --- a/packages/example/tsconfig.app.json +++ b/packages/example/tsconfig.app.json @@ -20,7 +20,11 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"] } diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 971d359..566de63 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,4 +1,4 @@ -import { Effect, Exit, Fiber, ManagedRuntime, Ref, Runtime, Stream, SubscriptionRef } from "effect" +import { Effect, Exit, Fiber, Layer, ManagedRuntime, Ref, Runtime, Stream, SubscriptionRef } from "effect" import React from "react" @@ -9,7 +9,7 @@ export class Reffuse { ) { this.Context = React.createContext(runtime) - this.Provider = (props: { readonly children?: React.ReactNode }) => this.Context.Provider({ + this.Provider = (props: { readonly children?: React.ReactNode }) => this.Context({ ...props, value: this.runtime, }) @@ -90,3 +90,7 @@ export class Reffuse { } } + + +export const make = (layer: Layer.Layer): Reffuse => + new Reffuse(ManagedRuntime.make(layer)) -- 2.49.1 From 64c07a62e6739bdb89bea51de6fb7377c4bb91b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 22:11:50 +0100 Subject: [PATCH 07/87] Fix --- packages/example/src/App.tsx | 8 +++++++- packages/reffuse/src/{Reffuse.ts => Reffuse.tsx} | 13 ++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) rename packages/reffuse/src/{Reffuse.ts => Reffuse.tsx} (93%) diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 34163d6..0e6f3ec 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,3 +1,5 @@ +import { Console } from "effect" +import { useState } from "react" import "./App.css" import reactLogo from "./assets/react.svg" import { Reffuse } from "./Reffuse" @@ -6,7 +8,11 @@ import viteLogo from "/vite.svg" export function App() { - const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) + Reffuse.runSync(Console.log("test")) + + // const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) + const [count, setCount] = useState(0) + return <>
diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.tsx similarity index 93% rename from packages/reffuse/src/Reffuse.ts rename to packages/reffuse/src/Reffuse.tsx index 566de63..5717a89 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.tsx @@ -7,12 +7,15 @@ export class Reffuse { constructor( private readonly runtime: ManagedRuntime.ManagedRuntime ) { - this.Context = React.createContext(runtime) + const Context = React.createContext(runtime) + this.Context = Context - this.Provider = (props: { readonly children?: React.ReactNode }) => this.Context({ - ...props, - value: this.runtime, - }) + this.Provider = (props: { readonly children?: React.ReactNode }) => ( + + ) } readonly Context: React.Context> -- 2.49.1 From 9aa86f19f047c4d8d9b1db3fd989233b724e331d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 22:14:24 +0100 Subject: [PATCH 08/87] Fix --- packages/reffuse/src/Reffuse.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 5717a89..14c4ddf 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -7,11 +7,10 @@ export class Reffuse { constructor( private readonly runtime: ManagedRuntime.ManagedRuntime ) { - const Context = React.createContext(runtime) - this.Context = Context + this.Context = React.createContext(runtime) this.Provider = (props: { readonly children?: React.ReactNode }) => ( - -- 2.49.1 From 00bf5a3c63a7cca8735e27c80bad0efbab1fbd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 22:21:54 +0100 Subject: [PATCH 09/87] Fix --- packages/example/src/App.tsx | 7 +------ packages/reffuse/src/Reffuse.tsx | 17 +++++++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 0e6f3ec..268e5b5 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,5 +1,3 @@ -import { Console } from "effect" -import { useState } from "react" import "./App.css" import reactLogo from "./assets/react.svg" import { Reffuse } from "./Reffuse" @@ -8,10 +6,7 @@ import viteLogo from "/vite.svg" export function App() { - Reffuse.runSync(Console.log("test")) - - // const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) - const [count, setCount] = useState(0) + const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) return <> diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 14c4ddf..0c1f6f8 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -5,7 +5,7 @@ import React from "react" export class Reffuse { constructor( - private readonly runtime: ManagedRuntime.ManagedRuntime + runtime: ManagedRuntime.ManagedRuntime ) { this.Context = React.createContext(runtime) @@ -61,19 +61,24 @@ export class Reffuse { deps?: React.DependencyList, options?: Runtime.RunForkOptions, ): void { + const runtime = this.useRuntime() + return React.useEffect(() => { - const fiber = this.runFork(self.pipe(Effect.scoped), options) - return () => { this.runFork(Fiber.interrupt(fiber)) } + const fiber = runtime.runFork(self.pipe(Effect.scoped), options) + return () => { runtime.runFork(Fiber.interrupt(fiber)) } }, deps) } useRef(value: A): SubscriptionRef.SubscriptionRef { - return React.useMemo(() => this.runSync(SubscriptionRef.make(value)), []) + const runtime = this.useRuntime() + return React.useMemo(() => runtime.runSync(SubscriptionRef.make(value)), []) } useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { - const initialState = React.useMemo(() => this.runSync(ref), [ref]) + const runtime = this.useRuntime() + + const initialState = React.useMemo(() => runtime.runSync(ref), [ref]) const [reactStateValue, setReactStateValue] = React.useState(initialState) this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => @@ -81,7 +86,7 @@ export class Reffuse { )), [ref]) const setValue = React.useCallback((setStateAction: React.SetStateAction) => - this.runSync(Ref.update(ref, previousState => + runtime.runSync(Ref.update(ref, previousState => typeof setStateAction === "function" ? (setStateAction as (prevState: A) => A)(previousState) : setStateAction -- 2.49.1 From 5f455295ada94204154c15340a8d9cf6f7c8d705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 10 Jan 2025 22:34:53 +0100 Subject: [PATCH 10/87] Tests --- packages/example/src/App.tsx | 9 +++++++-- packages/reffuse/src/Reffuse.tsx | 12 ++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 268e5b5..f7ec0ee 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,3 +1,4 @@ +import { Ref } from "effect" import "./App.css" import reactLogo from "./assets/react.svg" import { Reffuse } from "./Reffuse" @@ -6,7 +7,10 @@ import viteLogo from "/vite.svg" export function App() { - const [count, setCount] = Reffuse.useRefState(Reffuse.useRef(0)) + const runtime = Reffuse.useRuntime() + + const countRef = Reffuse.useRef(0) + const [count] = Reffuse.useRefState(countRef) return <> @@ -20,7 +24,8 @@ export function App() {

Vite + React

-

diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 0c1f6f8..7038090 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -26,29 +26,29 @@ export class Reffuse { } - runSync(effect: Effect.Effect): A { + useRunSync(effect: Effect.Effect): A { return this.useRuntime().runSync(effect) } - runSyncExit(effect: Effect.Effect): Exit.Exit { + useRunSyncExit(effect: Effect.Effect): Exit.Exit { return this.useRuntime().runSyncExit(effect) } - runPromise( + useRunPromise( effect: Effect.Effect, options?: { readonly signal?: AbortSignal }, ): Promise { return this.useRuntime().runPromise(effect, options) } - runPromiseExit( + useRunPromiseExit( effect: Effect.Effect, options?: { readonly signal?: AbortSignal }, ): Promise> { return this.useRuntime().runPromiseExit(effect, options) } - runFork( + useRunFork( self: Effect.Effect, options?: Runtime.RunForkOptions, ): Fiber.RuntimeFiber { @@ -66,7 +66,7 @@ export class Reffuse { return React.useEffect(() => { const fiber = runtime.runFork(self.pipe(Effect.scoped), options) return () => { runtime.runFork(Fiber.interrupt(fiber)) } - }, deps) + }, [runtime, ...deps ?? []]) } -- 2.49.1 From 6373919fc40f7e041e09d22a568de0501de644de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 16:36:06 +0100 Subject: [PATCH 11/87] Router setup --- bun.lockb | Bin 90696 -> 135736 bytes packages/example/package.json | 6 ++ packages/example/src/App.css | 42 ---------- packages/example/src/App.tsx | 40 --------- packages/example/src/Reffuse.ts | 5 -- packages/example/src/assets/react.svg | 1 - packages/example/src/index.css | 71 +--------------- packages/example/src/main.tsx | 16 ++-- packages/example/src/reffuse.ts | 5 ++ packages/example/src/routeTree.gen.ts | 111 +++++++++++++++++++++++++ packages/example/src/routes/__root.tsx | 22 +++++ packages/example/src/routes/count.tsx | 27 ++++++ packages/example/src/routes/index.tsx | 10 +++ packages/example/tailwind.config.js | 11 +++ packages/example/vite.config.ts | 18 +++- packages/reffuse/src/Reffuse.tsx | 2 +- 16 files changed, 222 insertions(+), 165 deletions(-) delete mode 100644 packages/example/src/App.css delete mode 100644 packages/example/src/App.tsx delete mode 100644 packages/example/src/Reffuse.ts delete mode 100644 packages/example/src/assets/react.svg create mode 100644 packages/example/src/reffuse.ts create mode 100644 packages/example/src/routeTree.gen.ts create mode 100644 packages/example/src/routes/__root.tsx create mode 100644 packages/example/src/routes/count.tsx create mode 100644 packages/example/src/routes/index.tsx create mode 100644 packages/example/tailwind.config.js diff --git a/bun.lockb b/bun.lockb index 3a7a538d3a29a24196e9f1365227be783685dda6..8da25e415c9c6f20f454a380650b7ada14e3c489 100755 GIT binary patch delta 41116 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 delta 14070 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 diff --git a/packages/example/package.json b/packages/example/package.json index 75b4953..eb4afd7 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -11,16 +11,22 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@tanstack/react-router": "^1.95.3", + "@tanstack/router-devtools": "^1.95.3", + "@tanstack/router-plugin": "^1.95.3", "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", "effect": "^3.12.1", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", + "postcss": "^8.4.49", "react": "^19.0.0", "react-dom": "^19.0.0", + "tailwindcss": "^3.4.17", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" diff --git a/packages/example/src/App.css b/packages/example/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/packages/example/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx deleted file mode 100644 index f7ec0ee..0000000 --- a/packages/example/src/App.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Ref } from "effect" -import "./App.css" -import reactLogo from "./assets/react.svg" -import { Reffuse } from "./Reffuse" -import viteLogo from "/vite.svg" - - -export function App() { - - const runtime = Reffuse.useRuntime() - - const countRef = Reffuse.useRef(0) - const [count] = Reffuse.useRefState(countRef) - - - return <> -

-

Vite + React

-
- {/* -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - -} diff --git a/packages/example/src/Reffuse.ts b/packages/example/src/Reffuse.ts deleted file mode 100644 index 9037b3a..0000000 --- a/packages/example/src/Reffuse.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Reffuse as TReffuse } from "@thilawyn/reffuse" -import { Layer } from "effect" - - -export const Reffuse = TReffuse.make(Layer.empty) diff --git a/packages/example/src/assets/react.svg b/packages/example/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/packages/example/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/example/src/index.css b/packages/example/src/index.css index 6119ad9..b5c61c9 100644 --- a/packages/example/src/index.css +++ b/packages/example/src/index.css @@ -1,68 +1,3 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index e263ba2..0f57e0d 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,14 +1,20 @@ +import { createRouter, RouterProvider } from "@tanstack/react-router" import { StrictMode } from "react" import { createRoot } from "react-dom/client" -import { App } from "./App.tsx" import "./index.css" -import { Reffuse } from "./Reffuse.ts" +import { routeTree } from './routeTree.gen' +const router = createRouter({ routeTree }) + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router + } +} + createRoot(document.getElementById("root")!).render( - - - + ) diff --git a/packages/example/src/reffuse.ts b/packages/example/src/reffuse.ts new file mode 100644 index 0000000..1a73e66 --- /dev/null +++ b/packages/example/src/reffuse.ts @@ -0,0 +1,5 @@ +import { make } from "@thilawyn/reffuse/Reffuse" +import { Layer } from "effect" + + +export const Reffuse = make(Layer.empty) diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts new file mode 100644 index 0000000..640dfa0 --- /dev/null +++ b/packages/example/src/routeTree.gen.ts @@ -0,0 +1,111 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as CountImport } from './routes/count' +import { Route as IndexImport } from './routes/index' + +// Create/Update Routes + +const CountRoute = CountImport.update({ + id: '/count', + path: '/count', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/count': { + id: '/count' + path: '/count' + fullPath: '/count' + preLoaderRoute: typeof CountImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/count': typeof CountRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/count': typeof CountRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/count': typeof CountRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/count' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/count' + id: '__root__' | '/' | '/count' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + CountRoute: typeof CountRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + CountRoute: CountRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/count" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/count": { + "filePath": "count.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx new file mode 100644 index 0000000..7749f30 --- /dev/null +++ b/packages/example/src/routes/__root.tsx @@ -0,0 +1,22 @@ +import { Reffuse } from "@/reffuse" +import { createRootRoute, Link, Outlet } from "@tanstack/react-router" +import { TanStackRouterDevtools } from "@tanstack/router-devtools" + + +export const Route = createRootRoute({ + component: Root +}) + +function Root() { + return ( + +
+ Index + Count +
+ + + +
+ ) +} diff --git a/packages/example/src/routes/count.tsx b/packages/example/src/routes/count.tsx new file mode 100644 index 0000000..f36f36a --- /dev/null +++ b/packages/example/src/routes/count.tsx @@ -0,0 +1,27 @@ +import { Reffuse } from "@/reffuse" +import { createFileRoute } from "@tanstack/react-router" +import { Ref } from "effect" + + +export const Route = createFileRoute("/count")({ + component: Count +}) + +function Count() { + + const runtime = Reffuse.useRuntime() + + const countRef = Reffuse.useRef(0) + const [count] = Reffuse.useRefState(countRef) + + + return ( +
+ {/* +
+ ) + +} diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx new file mode 100644 index 0000000..25b530d --- /dev/null +++ b/packages/example/src/routes/index.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from "@tanstack/react-router" + + +export const Route = createFileRoute("/")({ + component: Index +}) + +function Index() { + return <> +} diff --git a/packages/example/tailwind.config.js b/packages/example/tailwind.config.js new file mode 100644 index 0000000..f802d2c --- /dev/null +++ b/packages/example/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import("tailwindcss").Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/packages/example/vite.config.ts b/packages/example/vite.config.ts index 8b0f57b..6b20c32 100644 --- a/packages/example/vite.config.ts +++ b/packages/example/vite.config.ts @@ -1,7 +1,19 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { TanStackRouterVite } from "@tanstack/router-plugin/vite" +import react from "@vitejs/plugin-react" +import path from "node:path" +import { defineConfig } from "vite" + // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + TanStackRouterVite(), + react(), + ], + + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, }) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 7038090..c1bdf2a 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -64,7 +64,7 @@ export class Reffuse { const runtime = this.useRuntime() return React.useEffect(() => { - const fiber = runtime.runFork(self.pipe(Effect.scoped), options) + const fiber = runtime.runFork(Effect.scoped(self), options) return () => { runtime.runFork(Fiber.interrupt(fiber)) } }, [runtime, ...deps ?? []]) } -- 2.49.1 From 4e0cec051f6df5995c469e656a1c4c69edd41573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 16:42:49 +0100 Subject: [PATCH 12/87] Fix --- packages/example/postcss.config.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/example/postcss.config.js diff --git a/packages/example/postcss.config.js b/packages/example/postcss.config.js new file mode 100644 index 0000000..72588e6 --- /dev/null +++ b/packages/example/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + } +} -- 2.49.1 From e1a85fbb7ebec9e339a827c7e9d8517543c652a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 16:55:48 +0100 Subject: [PATCH 13/87] Teardown --- packages/example/src/routeTree.gen.ts | 31 +++++++++++++++++++++--- packages/example/src/routes/teardown.tsx | 30 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 packages/example/src/routes/teardown.tsx diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts index 640dfa0..4cb8a51 100644 --- a/packages/example/src/routeTree.gen.ts +++ b/packages/example/src/routeTree.gen.ts @@ -11,11 +11,18 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as TeardownImport } from './routes/teardown' import { Route as CountImport } from './routes/count' import { Route as IndexImport } from './routes/index' // Create/Update Routes +const TeardownRoute = TeardownImport.update({ + id: '/teardown', + path: '/teardown', + getParentRoute: () => rootRoute, +} as any) + const CountRoute = CountImport.update({ id: '/count', path: '/count', @@ -46,6 +53,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CountImport parentRoute: typeof rootRoute } + '/teardown': { + id: '/teardown' + path: '/teardown' + fullPath: '/teardown' + preLoaderRoute: typeof TeardownImport + parentRoute: typeof rootRoute + } } } @@ -54,36 +68,41 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexRoute '/count': typeof CountRoute + '/teardown': typeof TeardownRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/count': typeof CountRoute + '/teardown': typeof TeardownRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute '/count': typeof CountRoute + '/teardown': typeof TeardownRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/count' + fullPaths: '/' | '/count' | '/teardown' fileRoutesByTo: FileRoutesByTo - to: '/' | '/count' - id: '__root__' | '/' | '/count' + to: '/' | '/count' | '/teardown' + id: '__root__' | '/' | '/count' | '/teardown' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute CountRoute: typeof CountRoute + TeardownRoute: typeof TeardownRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, CountRoute: CountRoute, + TeardownRoute: TeardownRoute, } export const routeTree = rootRoute @@ -97,7 +116,8 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", - "/count" + "/count", + "/teardown" ] }, "/": { @@ -105,6 +125,9 @@ export const routeTree = rootRoute }, "/count": { "filePath": "count.tsx" + }, + "/teardown": { + "filePath": "teardown.tsx" } } } diff --git a/packages/example/src/routes/teardown.tsx b/packages/example/src/routes/teardown.tsx new file mode 100644 index 0000000..3a38383 --- /dev/null +++ b/packages/example/src/routes/teardown.tsx @@ -0,0 +1,30 @@ +import { Reffuse } from "@/reffuse" +import { createFileRoute } from "@tanstack/react-router" +import { DateTime, Effect, Ref, SubscriptionRef } from "effect" +import { useMemo } from "react" + + +export const Route = createFileRoute("/teardown")({ + component: Teardown +}) + +function Teardown() { + + const runtime = Reffuse.useRuntime() + + const timeRef = useMemo(() => DateTime.now.pipe( + Effect.flatMap(SubscriptionRef.make), + runtime.runSync, + // eslint-disable-next-line react-hooks/exhaustive-deps + ), []) + + Reffuse.useFork(DateTime.now.pipe( + Effect.flatMap(Ref.set(timeRef)) + )) + + const [time] = Reffuse.useRef(timeRef) + + + return <> + +} -- 2.49.1 From 8ae59bdd9374b01d4a3c268bb2512c5ced738ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 17:51:36 +0100 Subject: [PATCH 14/87] Time fork test --- packages/example/src/routeTree.gen.ts | 40 +++++++++++----------- packages/example/src/routes/__root.tsx | 1 + packages/example/src/routes/teardown.tsx | 30 ----------------- packages/example/src/routes/time.tsx | 42 ++++++++++++++++++++++++ packages/reffuse/src/Reffuse.tsx | 4 +-- 5 files changed, 65 insertions(+), 52 deletions(-) delete mode 100644 packages/example/src/routes/teardown.tsx create mode 100644 packages/example/src/routes/time.tsx diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts index 4cb8a51..2cec328 100644 --- a/packages/example/src/routeTree.gen.ts +++ b/packages/example/src/routeTree.gen.ts @@ -11,15 +11,15 @@ // Import Routes import { Route as rootRoute } from './routes/__root' -import { Route as TeardownImport } from './routes/teardown' +import { Route as TimeImport } from './routes/time' import { Route as CountImport } from './routes/count' import { Route as IndexImport } from './routes/index' // Create/Update Routes -const TeardownRoute = TeardownImport.update({ - id: '/teardown', - path: '/teardown', +const TimeRoute = TimeImport.update({ + id: '/time', + path: '/time', getParentRoute: () => rootRoute, } as any) @@ -53,11 +53,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CountImport parentRoute: typeof rootRoute } - '/teardown': { - id: '/teardown' - path: '/teardown' - fullPath: '/teardown' - preLoaderRoute: typeof TeardownImport + '/time': { + id: '/time' + path: '/time' + fullPath: '/time' + preLoaderRoute: typeof TimeImport parentRoute: typeof rootRoute } } @@ -68,41 +68,41 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexRoute '/count': typeof CountRoute - '/teardown': typeof TeardownRoute + '/time': typeof TimeRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/count': typeof CountRoute - '/teardown': typeof TeardownRoute + '/time': typeof TimeRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute '/count': typeof CountRoute - '/teardown': typeof TeardownRoute + '/time': typeof TimeRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/count' | '/teardown' + fullPaths: '/' | '/count' | '/time' fileRoutesByTo: FileRoutesByTo - to: '/' | '/count' | '/teardown' - id: '__root__' | '/' | '/count' | '/teardown' + to: '/' | '/count' | '/time' + id: '__root__' | '/' | '/count' | '/time' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute CountRoute: typeof CountRoute - TeardownRoute: typeof TeardownRoute + TimeRoute: typeof TimeRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, CountRoute: CountRoute, - TeardownRoute: TeardownRoute, + TimeRoute: TimeRoute, } export const routeTree = rootRoute @@ -117,7 +117,7 @@ export const routeTree = rootRoute "children": [ "/", "/count", - "/teardown" + "/time" ] }, "/": { @@ -126,8 +126,8 @@ export const routeTree = rootRoute "/count": { "filePath": "count.tsx" }, - "/teardown": { - "filePath": "teardown.tsx" + "/time": { + "filePath": "time.tsx" } } } diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index 7749f30..900d988 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -12,6 +12,7 @@ function Root() {
Index + Time Count
diff --git a/packages/example/src/routes/teardown.tsx b/packages/example/src/routes/teardown.tsx deleted file mode 100644 index 3a38383..0000000 --- a/packages/example/src/routes/teardown.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Reffuse } from "@/reffuse" -import { createFileRoute } from "@tanstack/react-router" -import { DateTime, Effect, Ref, SubscriptionRef } from "effect" -import { useMemo } from "react" - - -export const Route = createFileRoute("/teardown")({ - component: Teardown -}) - -function Teardown() { - - const runtime = Reffuse.useRuntime() - - const timeRef = useMemo(() => DateTime.now.pipe( - Effect.flatMap(SubscriptionRef.make), - runtime.runSync, - // eslint-disable-next-line react-hooks/exhaustive-deps - ), []) - - Reffuse.useFork(DateTime.now.pipe( - Effect.flatMap(Ref.set(timeRef)) - )) - - const [time] = Reffuse.useRef(timeRef) - - - return <> - -} diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx new file mode 100644 index 0000000..ce6c9ff --- /dev/null +++ b/packages/example/src/routes/time.tsx @@ -0,0 +1,42 @@ +import { Reffuse } from "@/reffuse" +import { createFileRoute } from "@tanstack/react-router" +import { Console, DateTime, Effect, Ref, Schedule, SubscriptionRef } from "effect" +import { useMemo } from "react" + + +export const Route = createFileRoute("/time")({ + component: Time +}) + +function Time() { + + const runtime = Reffuse.useRuntime() + + const timeRef = useMemo(() => DateTime.now.pipe( + Effect.flatMap(SubscriptionRef.make), + runtime.runSync, + // eslint-disable-next-line react-hooks/exhaustive-deps + ), []) + + Reffuse.useFork(Effect.addFinalizer(() => Console.log("Component unmounted.")).pipe( + Effect.flatMap(() => DateTime.now), + Effect.flatMap(v => Ref.set(timeRef, v)), + Effect.repeat(Schedule.spaced("1 second")), + )) + + const [time] = Reffuse.useRefState(timeRef) + + + return ( +
+

+ {DateTime.format(time, { + hour: "numeric", + minute: "numeric", + second: "numeric", + })} +

+
+ ) + +} diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index c1bdf2a..49cf11f 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -1,4 +1,4 @@ -import { Effect, Exit, Fiber, Layer, ManagedRuntime, Ref, Runtime, Stream, SubscriptionRef } from "effect" +import { Effect, Exit, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" @@ -57,7 +57,7 @@ export class Reffuse { useFork( - self: Effect.Effect, + self: Effect.Effect, deps?: React.DependencyList, options?: Runtime.RunForkOptions, ): void { -- 2.49.1 From e8580ec49e82ea59fcf35e2319e1d4c6ebfd3ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 17:54:59 +0100 Subject: [PATCH 15/87] Time work --- packages/example/src/routes/time.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx index ce6c9ff..077922c 100644 --- a/packages/example/src/routes/time.tsx +++ b/packages/example/src/routes/time.tsx @@ -18,10 +18,17 @@ function Time() { // eslint-disable-next-line react-hooks/exhaustive-deps ), []) - Reffuse.useFork(Effect.addFinalizer(() => Console.log("Component unmounted.")).pipe( - Effect.flatMap(() => DateTime.now), + // Reffuse.useFork(Effect.addFinalizer(() => Console.log("Component unmounted.")).pipe( + // Effect.flatMap(() => DateTime.now), + // Effect.flatMap(v => Ref.set(timeRef, v)), + // Effect.repeat(Schedule.spaced("1 second")), + // )) + Reffuse.useFork(DateTime.now.pipe( Effect.flatMap(v => Ref.set(timeRef, v)), - Effect.repeat(Schedule.spaced("1 second")), + Effect.repeat(Schedule.intersect( + Schedule.forever, + Schedule.spaced("1 second"), + )), )) const [time] = Reffuse.useRefState(timeRef) -- 2.49.1 From 038f38d32c091498f1f6b9b81395e0d3ac9c3c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 18:26:43 +0100 Subject: [PATCH 16/87] Time --- packages/example/src/routes/time.tsx | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx index 077922c..754c51f 100644 --- a/packages/example/src/routes/time.tsx +++ b/packages/example/src/routes/time.tsx @@ -1,9 +1,15 @@ import { Reffuse } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" -import { Console, DateTime, Effect, Ref, Schedule, SubscriptionRef } from "effect" +import { Console, DateTime, Effect, Ref, Schedule, Stream, SubscriptionRef } from "effect" import { useMemo } from "react" +const timeEverySecond = Stream.repeatEffectWithSchedule( + DateTime.now, + Schedule.intersect(Schedule.forever, Schedule.spaced("1 second")), +) + + export const Route = createFileRoute("/time")({ component: Time }) @@ -18,18 +24,19 @@ function Time() { // eslint-disable-next-line react-hooks/exhaustive-deps ), []) - // Reffuse.useFork(Effect.addFinalizer(() => Console.log("Component unmounted.")).pipe( + Reffuse.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.spaced("1 second")), - // )) - Reffuse.useFork(DateTime.now.pipe( - Effect.flatMap(v => Ref.set(timeRef, v)), - Effect.repeat(Schedule.intersect( - Schedule.forever, - Schedule.spaced("1 second"), - )), - )) + // Effect.repeat(Schedule.intersect( + // Schedule.forever, + // Schedule.spaced("1 second"), + // )), + // ), [timeRef]) const [time] = Reffuse.useRefState(timeRef) -- 2.49.1 From 7e239b0d1e43075779348dbdafc2ce33adb2a42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 18:45:18 +0100 Subject: [PATCH 17/87] useRefFromEffect --- packages/example/src/routes/time.tsx | 11 ++--------- packages/reffuse/src/Reffuse.tsx | 8 ++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx index 754c51f..1f01ec6 100644 --- a/packages/example/src/routes/time.tsx +++ b/packages/example/src/routes/time.tsx @@ -1,7 +1,6 @@ import { Reffuse } from "@/reffuse" import { createFileRoute } from "@tanstack/react-router" -import { Console, DateTime, Effect, Ref, Schedule, Stream, SubscriptionRef } from "effect" -import { useMemo } from "react" +import { Console, DateTime, Effect, Ref, Schedule, Stream } from "effect" const timeEverySecond = Stream.repeatEffectWithSchedule( @@ -16,13 +15,7 @@ export const Route = createFileRoute("/time")({ function Time() { - const runtime = Reffuse.useRuntime() - - const timeRef = useMemo(() => DateTime.now.pipe( - Effect.flatMap(SubscriptionRef.make), - runtime.runSync, - // eslint-disable-next-line react-hooks/exhaustive-deps - ), []) + const timeRef = Reffuse.useRefFromEffect(DateTime.now) Reffuse.useFork(Effect.addFinalizer(() => Console.log("Cleanup")).pipe( Effect.flatMap(() => diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 49cf11f..f324fff 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -75,6 +75,14 @@ export class Reffuse { return React.useMemo(() => runtime.runSync(SubscriptionRef.make(value)), []) } + useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { + const runtime = this.useRuntime() + + return React.useMemo(() => runtime.runSync(effect.pipe( + Effect.flatMap(SubscriptionRef.make) + )), []) + } + useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { const runtime = this.useRuntime() -- 2.49.1 From c4b902b1109eed75a5165f36a29ade520e4300ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 21:36:48 +0100 Subject: [PATCH 18/87] Cleanup --- packages/reffuse/src/Reffuse.tsx | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index f324fff..aff1cc5 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -1,4 +1,4 @@ -import { Effect, Exit, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" +import { Effect, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" @@ -26,36 +26,6 @@ export class Reffuse { } - useRunSync(effect: Effect.Effect): A { - return this.useRuntime().runSync(effect) - } - - useRunSyncExit(effect: Effect.Effect): Exit.Exit { - return this.useRuntime().runSyncExit(effect) - } - - useRunPromise( - effect: Effect.Effect, - options?: { readonly signal?: AbortSignal }, - ): Promise { - return this.useRuntime().runPromise(effect, options) - } - - useRunPromiseExit( - effect: Effect.Effect, - options?: { readonly signal?: AbortSignal }, - ): Promise> { - return this.useRuntime().runPromiseExit(effect, options) - } - - useRunFork( - self: Effect.Effect, - options?: Runtime.RunForkOptions, - ): Fiber.RuntimeFiber { - return this.useRuntime().runFork(self, options) - } - - useFork( self: Effect.Effect, deps?: React.DependencyList, -- 2.49.1 From 6aafadb4ad38449cdf9dc70d98d4466a8e8e39c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 11 Jan 2025 23:36:07 +0100 Subject: [PATCH 19/87] Fix --- packages/reffuse/src/Reffuse.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index aff1cc5..4ee5774 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -7,7 +7,7 @@ export class Reffuse { constructor( runtime: ManagedRuntime.ManagedRuntime ) { - this.Context = React.createContext(runtime) + this.Context = React.createContext>(null!) this.Provider = (props: { readonly children?: React.ReactNode }) => ( Date: Sun, 12 Jan 2025 18:35:25 +0100 Subject: [PATCH 20/87] Context --- packages/reffuse/src/Reffuse.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 4ee5774..00899ab 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -15,6 +15,11 @@ export class Reffuse { value={runtime} /> ) + + const context = runtime.runtimeEffect.pipe( + Effect.map(r => Layer.succeedContext(r.context)), + runtime.runSync, + ) } readonly Context: React.Context> -- 2.49.1 From 79a3779005b5addc871dc41ad62b824d6211bda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 19:14:01 +0100 Subject: [PATCH 21/87] Tests --- packages/reffuse/src/Reffuse.tsx | 45 ++++++++++++++++++-------------- packages/reffuse/src/tests.ts | 31 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 packages/reffuse/src/tests.ts diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 00899ab..2f6fdd0 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -1,32 +1,39 @@ -import { Effect, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" +import { Context, Effect, Fiber, FiberRefs, Layer, ManagedRuntime, Ref, Runtime, RuntimeFlags, Scope, Stream, SubscriptionRef } from "effect" import React from "react" -export class Reffuse { +export interface ProviderProps { + readonly layer: Layer.Layer + readonly children?: React.ReactNode +} + + +export class Reffuse { + + readonly Context = React.createContext>(null!) + readonly Provider: React.FC> constructor( - runtime: ManagedRuntime.ManagedRuntime + runtime: Runtime.Runtime ) { - this.Context = React.createContext>(null!) + this.Provider = (props: ProviderProps) => { + const runtime = React.useMemo(() => Runtime.make({ + context: Context.empty(), + runtimeFlags: RuntimeFlags.make(), + fiberRefs: FiberRefs.empty(), + }), []) - this.Provider = (props: { readonly children?: React.ReactNode }) => ( - - ) - - const context = runtime.runtimeEffect.pipe( - Effect.map(r => Layer.succeedContext(r.context)), - runtime.runSync, - ) + return ( + + ) + } } - readonly Context: React.Context> - readonly Provider: React.FC<{ readonly children?: React.ReactNode }> - - useRuntime(): ManagedRuntime.ManagedRuntime { + useRuntime(): Runtime.Runtime { return React.useContext(this.Context) } diff --git a/packages/reffuse/src/tests.ts b/packages/reffuse/src/tests.ts new file mode 100644 index 0000000..1db3454 --- /dev/null +++ b/packages/reffuse/src/tests.ts @@ -0,0 +1,31 @@ +import { Context, Effect, FiberRefs, Layer, Ref, Runtime, RuntimeFlags } from "effect" + + +const runtime = Runtime.make({ + context: Context.empty(), + runtimeFlags: RuntimeFlags.make(), + fiberRefs: FiberRefs.empty(), +}) + + +class MyService extends Effect.Service()("MyServer", { + effect: Effect.gen(function*() { + return { + ref: yield* Ref.make("initial value") + } as const + }) +}) {} + +const MyLayer = Layer.empty.pipe( + Layer.provideMerge(MyService.Default) +) + +const setMyServiceValue = (value: string) => Effect.gen(function*() { + console.log("previous value: ", yield* (yield* MyService).ref) + yield* Ref.set((yield* MyService).ref, value) + console.log("new value: ", yield* (yield* MyService).ref) +}) + + +Runtime.runSync(runtime)(setMyServiceValue("1").pipe(Effect.provide(MyLayer))) +Runtime.runSync(runtime)(setMyServiceValue("2").pipe(Effect.provide(MyLayer))) -- 2.49.1 From cd2df017ec3c1292464725db57a36a311e11231b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 19:55:31 +0100 Subject: [PATCH 22/87] Working tests --- packages/reffuse/src/tests.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/reffuse/src/tests.ts b/packages/reffuse/src/tests.ts index 1db3454..b7eb6b0 100644 --- a/packages/reffuse/src/tests.ts +++ b/packages/reffuse/src/tests.ts @@ -7,6 +7,16 @@ const runtime = Runtime.make({ fiberRefs: FiberRefs.empty(), }) +const createRunSync = (runtime: Runtime.Runtime, layer: Layer.Layer) => { + const context = Effect.context().pipe( + Effect.provide(layer), + Runtime.runSync(runtime), + ) + + return (effect: Effect.Effect) => + Runtime.runSync(runtime)(effect.pipe(Effect.provide(context))) +} + class MyService extends Effect.Service()("MyServer", { effect: Effect.gen(function*() { @@ -20,12 +30,14 @@ const MyLayer = Layer.empty.pipe( Layer.provideMerge(MyService.Default) ) + +const runSync = createRunSync(runtime, MyLayer) + const setMyServiceValue = (value: string) => Effect.gen(function*() { console.log("previous value: ", yield* (yield* MyService).ref) yield* Ref.set((yield* MyService).ref, value) console.log("new value: ", yield* (yield* MyService).ref) }) - -Runtime.runSync(runtime)(setMyServiceValue("1").pipe(Effect.provide(MyLayer))) -Runtime.runSync(runtime)(setMyServiceValue("2").pipe(Effect.provide(MyLayer))) +runSync(setMyServiceValue("1")) +runSync(setMyServiceValue("2")) -- 2.49.1 From ed85f9804c342b5c26b0de5537871ad44b4cb49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 23:17:28 +0100 Subject: [PATCH 23/87] Reffuse work --- packages/reffuse/src/Reffuse.tsx | 89 ++++++++++++++----- packages/reffuse/src/ReffuseReactContext.ts | 7 ++ .../src/ReffuseReactContextProvider.tsx | 0 3 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 packages/reffuse/src/ReffuseReactContext.ts create mode 100644 packages/reffuse/src/ReffuseReactContextProvider.tsx diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.tsx index 2f6fdd0..5b4f062 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.tsx @@ -1,5 +1,6 @@ -import { Context, Effect, Fiber, FiberRefs, Layer, ManagedRuntime, Ref, Runtime, RuntimeFlags, Scope, Stream, SubscriptionRef } from "effect" +import { Context, Effect, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" +import * as ReffuseReactContext from "./ReffuseReactContext.js" export interface ProviderProps { @@ -10,31 +11,71 @@ export interface ProviderProps { export class Reffuse { - readonly Context = React.createContext>(null!) - readonly Provider: React.FC> + readonly Context = React.createContext>(null!) + readonly Provider: React.FC> constructor( runtime: Runtime.Runtime ) { - this.Provider = (props: ProviderProps) => { - const runtime = React.useMemo(() => Runtime.make({ - context: Context.empty(), - runtimeFlags: RuntimeFlags.make(), - fiberRefs: FiberRefs.empty(), - }), []) + this.Provider = (props: ProviderProps) => { + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Runtime.runSync(runtime), + ), + }), [props.layer]) return ( ) } } - useRuntime(): Runtime.Runtime { - return React.useContext(this.Context) + useRuntime(): Runtime.Runtime { + return React.useContext(this.Context).runtime + } + + useContext(): Context.Context { + return React.useContext(this.Context).context + } + + + useRunSync() { + const { runtime, context } = React.useContext(this.Context) + + return React.useCallback((effect: Effect.Effect): A => effect.pipe( + Effect.provide(context), + Runtime.runSync(runtime), + ), [runtime, context]) + } + + useRunPromise() { + const { runtime, context } = React.useContext(this.Context) + + return React.useCallback(( + effect: Effect.Effect, + options?: { readonly signal?: AbortSignal }, + ): Promise => effect.pipe( + Effect.provide(context), + effect => Runtime.runPromise(runtime)(effect, options), + ), [runtime, context]) + } + + useRunFork() { + const { runtime, context } = React.useContext(this.Context) + + return React.useCallback(( + effect: Effect.Effect, + options?: Runtime.RunForkOptions, + ): Fiber.RuntimeFiber => effect.pipe( + Effect.provide(context), + effect => Runtime.runFork(runtime)(effect, options), + ), [runtime, context]) } @@ -43,32 +84,32 @@ export class Reffuse { deps?: React.DependencyList, options?: Runtime.RunForkOptions, ): void { - const runtime = this.useRuntime() + const runFork = this.useRunFork() return React.useEffect(() => { - const fiber = runtime.runFork(Effect.scoped(self), options) - return () => { runtime.runFork(Fiber.interrupt(fiber)) } - }, [runtime, ...deps ?? []]) + const fiber = runFork(Effect.scoped(self), options) + return () => { runFork(Fiber.interrupt(fiber)) } + }, [runFork, ...deps ?? []]) } useRef(value: A): SubscriptionRef.SubscriptionRef { - const runtime = this.useRuntime() - return React.useMemo(() => runtime.runSync(SubscriptionRef.make(value)), []) + const runSync = this.useRunSync() + return React.useMemo(() => runSync(SubscriptionRef.make(value)), []) } useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { - const runtime = this.useRuntime() + const runSync = this.useRunSync() - return React.useMemo(() => runtime.runSync(effect.pipe( + return React.useMemo(() => runSync(effect.pipe( Effect.flatMap(SubscriptionRef.make) )), []) } useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { - const runtime = this.useRuntime() + const runSync = this.useRunSync() - const initialState = React.useMemo(() => runtime.runSync(ref), [ref]) + const initialState = React.useMemo(() => runSync(ref), [ref]) const [reactStateValue, setReactStateValue] = React.useState(initialState) this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => @@ -76,7 +117,7 @@ export class Reffuse { )), [ref]) const setValue = React.useCallback((setStateAction: React.SetStateAction) => - runtime.runSync(Ref.update(ref, previousState => + runSync(Ref.update(ref, previousState => typeof setStateAction === "function" ? (setStateAction as (prevState: A) => A)(previousState) : setStateAction @@ -89,5 +130,5 @@ export class Reffuse { } -export const make = (layer: Layer.Layer): Reffuse => +export const make = (): Reffuse => new Reffuse(ManagedRuntime.make(layer)) diff --git a/packages/reffuse/src/ReffuseReactContext.ts b/packages/reffuse/src/ReffuseReactContext.ts new file mode 100644 index 0000000..54c6552 --- /dev/null +++ b/packages/reffuse/src/ReffuseReactContext.ts @@ -0,0 +1,7 @@ +import type { Context, Runtime } from "effect" + + +export interface ReffuseReactContext { + readonly runtime: Runtime.Runtime + readonly context: Context.Context +} diff --git a/packages/reffuse/src/ReffuseReactContextProvider.tsx b/packages/reffuse/src/ReffuseReactContextProvider.tsx new file mode 100644 index 0000000..e69de29 -- 2.49.1 From f6dc7a0722de3caa115c23824d22e77ae1d69e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 23:36:28 +0100 Subject: [PATCH 24/87] Reffuse work --- .../reffuse/src/{Reffuse.tsx => Reffuse.ts} | 38 ++++++------------- .../src/ReffuseReactContextProvider.tsx | 34 +++++++++++++++++ 2 files changed, 45 insertions(+), 27 deletions(-) rename packages/reffuse/src/{Reffuse.tsx => Reffuse.ts} (77%) diff --git a/packages/reffuse/src/Reffuse.tsx b/packages/reffuse/src/Reffuse.ts similarity index 77% rename from packages/reffuse/src/Reffuse.tsx rename to packages/reffuse/src/Reffuse.ts index 5b4f062..ce959b4 100644 --- a/packages/reffuse/src/Reffuse.tsx +++ b/packages/reffuse/src/Reffuse.ts @@ -1,38 +1,18 @@ -import { Context, Effect, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" +import { Context, Effect, Fiber, FiberRefs, Ref, Runtime, RuntimeFlags, Scope, Stream, SubscriptionRef } from "effect" import React from "react" -import * as ReffuseReactContext from "./ReffuseReactContext.js" - - -export interface ProviderProps { - readonly layer: Layer.Layer - readonly children?: React.ReactNode -} +import type * as ReffuseReactContext from "./ReffuseReactContext.js" +import * as ReffuseReactContextProvider from "./ReffuseReactContextProvider.js" export class Reffuse { readonly Context = React.createContext>(null!) - readonly Provider: React.FC> + readonly Provider: ReffuseReactContextProvider.ReffuseReactContextProvider constructor( - runtime: Runtime.Runtime + runtime: Runtime.Runtime ) { - this.Provider = (props: ProviderProps) => { - const value = React.useMemo(() => ({ - runtime, - context: Effect.context().pipe( - Effect.provide(props.layer), - Runtime.runSync(runtime), - ), - }), [props.layer]) - - return ( - - ) - } + this.Provider = ReffuseReactContextProvider.make(runtime, this.Context) } @@ -131,4 +111,8 @@ export class Reffuse { export const make = (): Reffuse => - new Reffuse(ManagedRuntime.make(layer)) + new Reffuse(Runtime.make({ + context: Context.empty(), + runtimeFlags: RuntimeFlags.make(), + fiberRefs: FiberRefs.empty(), + })) diff --git a/packages/reffuse/src/ReffuseReactContextProvider.tsx b/packages/reffuse/src/ReffuseReactContextProvider.tsx index e69de29..f0d0ad7 100644 --- a/packages/reffuse/src/ReffuseReactContextProvider.tsx +++ b/packages/reffuse/src/ReffuseReactContextProvider.tsx @@ -0,0 +1,34 @@ +import { Effect, Runtime, type Layer } from "effect" +import React from "react" +import type * as ReffuseReactContext from "./ReffuseReactContext.js" + + +export interface Props { + readonly layer: Layer.Layer + readonly children?: React.ReactNode +} + +export type ReffuseReactContextProvider = React.FC> + + +export function make( + runtime: Runtime.Runtime, + Context: React.Context>, +) { + return function ReffuseReactContextProvider(props: Props) { + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Runtime.runSync(runtime), + ), + }), [props.layer]) + + return ( + + ) + } +} -- 2.49.1 From d8553e95e21524abcc729e07d57ac9a0f2621fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 23:41:32 +0100 Subject: [PATCH 25/87] Working --- packages/example/src/reffuse.ts | 3 +-- packages/example/src/routes/__root.tsx | 8 ++++++-- packages/example/src/routes/count.tsx | 4 ++-- packages/reffuse/src/Reffuse.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/example/src/reffuse.ts b/packages/example/src/reffuse.ts index 1a73e66..9c41d25 100644 --- a/packages/example/src/reffuse.ts +++ b/packages/example/src/reffuse.ts @@ -1,5 +1,4 @@ import { make } from "@thilawyn/reffuse/Reffuse" -import { Layer } from "effect" -export const Reffuse = make(Layer.empty) +export const Reffuse = make() diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index 900d988..19fd034 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -1,6 +1,8 @@ import { Reffuse } from "@/reffuse" import { createRootRoute, Link, Outlet } from "@tanstack/react-router" import { TanStackRouterDevtools } from "@tanstack/router-devtools" +import { Layer } from "effect" +import { useMemo } from "react" export const Route = createRootRoute({ @@ -8,9 +10,11 @@ export const Route = createRootRoute({ }) function Root() { + const layer = useMemo(() => Layer.empty, []) + return ( - -
+ +
Index Time Count diff --git a/packages/example/src/routes/count.tsx b/packages/example/src/routes/count.tsx index f36f36a..081162b 100644 --- a/packages/example/src/routes/count.tsx +++ b/packages/example/src/routes/count.tsx @@ -9,7 +9,7 @@ export const Route = createFileRoute("/count")({ function Count() { - const runtime = Reffuse.useRuntime() + const runSync = Reffuse.useRunSync() const countRef = Reffuse.useRef(0) const [count] = Reffuse.useRefState(countRef) @@ -18,7 +18,7 @@ function Count() { return (
{/*
diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index ce959b4..970f7aa 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -110,7 +110,7 @@ export class Reffuse { } -export const make = (): Reffuse => +export const make = (): Reffuse => new Reffuse(Runtime.make({ context: Context.empty(), runtimeFlags: RuntimeFlags.make(), -- 2.49.1 From edec837a876222a7652ee196d8ba0862175f767c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 12 Jan 2025 23:50:49 +0100 Subject: [PATCH 26/87] Fix --- packages/reffuse/src/Reffuse.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 970f7aa..bf6e4e8 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,4 +1,4 @@ -import { Context, Effect, Fiber, FiberRefs, Ref, Runtime, RuntimeFlags, Scope, Stream, SubscriptionRef } from "effect" +import { Context, Effect, Fiber, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" import type * as ReffuseReactContext from "./ReffuseReactContext.js" import * as ReffuseReactContextProvider from "./ReffuseReactContextProvider.js" @@ -110,9 +110,6 @@ export class Reffuse { } +// TODO: allow passing runtime export const make = (): Reffuse => - new Reffuse(Runtime.make({ - context: Context.empty(), - runtimeFlags: RuntimeFlags.make(), - fiberRefs: FiberRefs.empty(), - })) + new Reffuse(Runtime.defaultRuntime) -- 2.49.1 From 17202667615df7aecfab24bb8cfd318e78a084d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 13 Jan 2025 01:32:07 +0100 Subject: [PATCH 27/87] 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 bf6e4e8..7b307d1 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -69,7 +69,7 @@ export class Reffuse { return React.useEffect(() => { const fiber = runFork(Effect.scoped(self), options) return () => { runFork(Fiber.interrupt(fiber)) } - }, [runFork, ...deps ?? []]) + }, [runFork, ...(deps ?? [])]) } -- 2.49.1 From c60c396054126da9b180eea2da236f29f3d7cf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 13 Jan 2025 23:38:46 +0100 Subject: [PATCH 28/87] Inheritance work --- packages/reffuse/src/Reffuse.ts | 15 +++++--- packages/reffuse/src/ReffuseReactContext.ts | 7 ---- packages/reffuse/src/ReffuseReactContext.tsx | 39 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) delete mode 100644 packages/reffuse/src/ReffuseReactContext.ts create mode 100644 packages/reffuse/src/ReffuseReactContext.tsx diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 7b307d1..5b5a4e9 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -4,23 +4,28 @@ import type * as ReffuseReactContext from "./ReffuseReactContext.js" import * as ReffuseReactContextProvider from "./ReffuseReactContextProvider.js" -export class Reffuse { +export class Reffuse< + R extends RuntimeR | ContextR, + RuntimeR, + ContextR, + Parent extends Reffuse, +> { - readonly Context = React.createContext>(null!) + readonly Context = React.createContext>(null!) readonly Provider: ReffuseReactContextProvider.ReffuseReactContextProvider constructor( - runtime: Runtime.Runtime + runtime: Runtime.Runtime ) { this.Provider = ReffuseReactContextProvider.make(runtime, this.Context) } - useRuntime(): Runtime.Runtime { + useRuntime(): Runtime.Runtime { return React.useContext(this.Context).runtime } - useContext(): Context.Context { + useContext(): Context.Context { return React.useContext(this.Context).context } diff --git a/packages/reffuse/src/ReffuseReactContext.ts b/packages/reffuse/src/ReffuseReactContext.ts deleted file mode 100644 index 54c6552..0000000 --- a/packages/reffuse/src/ReffuseReactContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Context, Runtime } from "effect" - - -export interface ReffuseReactContext { - readonly runtime: Runtime.Runtime - readonly context: Context.Context -} diff --git a/packages/reffuse/src/ReffuseReactContext.tsx b/packages/reffuse/src/ReffuseReactContext.tsx new file mode 100644 index 0000000..9d6ea48 --- /dev/null +++ b/packages/reffuse/src/ReffuseReactContext.tsx @@ -0,0 +1,39 @@ +import { Effect, Runtime, type Context, type Layer } from "effect" +import React from "react" + + +export interface Value { + readonly runtime: Runtime.Runtime + readonly context: Context.Context +} + + +export interface ProviderProps { + readonly layer: Layer.Layer + readonly children?: React.ReactNode +} + +export type Provider = React.FC> + + +export function makeProvider( + runtime: Runtime.Runtime, + Context: React.Context>, +) { + return function ReffuseReactContextProvider(props: ProviderProps) { + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Runtime.runSync(runtime), + ), + }), [props.layer]) + + return ( + + ) + } +} -- 2.49.1 From 12849d37da22bd4b763158e59d5a62eac1fbae18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 00:54:39 +0100 Subject: [PATCH 29/87] Work --- packages/reffuse/src/Reffuse.ts | 29 ++++--- packages/reffuse/src/ReffuseReactContext.tsx | 81 +++++++++++++------ .../src/ReffuseReactContextProvider.tsx | 34 -------- 3 files changed, 74 insertions(+), 70 deletions(-) delete mode 100644 packages/reffuse/src/ReffuseReactContextProvider.tsx diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 5b5a4e9..19a4748 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,23 +1,24 @@ import { Context, Effect, Fiber, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect" import React from "react" -import type * as ReffuseReactContext from "./ReffuseReactContext.js" -import * as ReffuseReactContextProvider from "./ReffuseReactContextProvider.js" +import * as ReffuseReactContext from "./ReffuseReactContext.js" export class Reffuse< - R extends RuntimeR | ContextR, RuntimeR, - ContextR, - Parent extends Reffuse, + ContextR extends ParentContextR | OwnContextR, + OwnContextR, + ParentContextR = never, > { readonly Context = React.createContext>(null!) - readonly Provider: ReffuseReactContextProvider.ReffuseReactContextProvider + readonly Provider: ReffuseReactContext.Provider constructor( - runtime: Runtime.Runtime + runtime: Runtime.Runtime, + parent?: Reffuse, ) { - this.Provider = ReffuseReactContextProvider.make(runtime, this.Context) + // TODO: split into makeProvider and makeScopedProvider + this.Provider = ReffuseReactContext.makeProvider(runtime, this.Context, parent) } @@ -33,7 +34,9 @@ export class Reffuse< useRunSync() { const { runtime, context } = React.useContext(this.Context) - return React.useCallback((effect: Effect.Effect): A => effect.pipe( + return React.useCallback(( + effect: Effect.Effect + ): A => effect.pipe( Effect.provide(context), Runtime.runSync(runtime), ), [runtime, context]) @@ -43,7 +46,7 @@ export class Reffuse< const { runtime, context } = React.useContext(this.Context) return React.useCallback(( - effect: Effect.Effect, + effect: Effect.Effect, options?: { readonly signal?: AbortSignal }, ): Promise
=> effect.pipe( Effect.provide(context), @@ -55,7 +58,7 @@ export class Reffuse< const { runtime, context } = React.useContext(this.Context) return React.useCallback(( - effect: Effect.Effect, + effect: Effect.Effect, options?: Runtime.RunForkOptions, ): Fiber.RuntimeFiber => effect.pipe( Effect.provide(context), @@ -65,7 +68,7 @@ export class Reffuse< useFork( - self: Effect.Effect, + self: Effect.Effect, deps?: React.DependencyList, options?: Runtime.RunForkOptions, ): void { @@ -83,7 +86,7 @@ export class Reffuse< return React.useMemo(() => runSync(SubscriptionRef.make(value)), []) } - useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { + useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { const runSync = this.useRunSync() return React.useMemo(() => runSync(effect.pipe( diff --git a/packages/reffuse/src/ReffuseReactContext.tsx b/packages/reffuse/src/ReffuseReactContext.tsx index 9d6ea48..dee25a0 100644 --- a/packages/reffuse/src/ReffuseReactContext.tsx +++ b/packages/reffuse/src/ReffuseReactContext.tsx @@ -1,5 +1,6 @@ -import { Effect, Runtime, type Context, type Layer } from "effect" +import { Context, Effect, Runtime, type Layer } from "effect" import React from "react" +import type * as Reffuse from "./Reffuse.js" export interface Value { @@ -8,32 +9,66 @@ export interface Value { } -export interface ProviderProps { - readonly layer: Layer.Layer +export type Provider< + RuntimeR, + OwnContextR, + ParentContextR, +> = React.FC> + +export interface ProviderProps< + RuntimeR, + OwnContextR, + ParentContextR, +> { + readonly layer: Layer.Layer readonly children?: React.ReactNode } -export type Provider = React.FC> +export function makeProvider< + RuntimeR, + ContextR extends ParentContextR | OwnContextR, + OwnContextR, + ParentContextR, +>( + runtime: Runtime.Runtime, + ReactContext: React.Context>, + parent?: Reffuse.Reffuse, +): Provider { + return parent + ? function ReffuseReactContextProvider(props) { + const parentContext = parent.useContext() + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Effect.provide(parentContext), + Runtime.runSync(runtime), + ), + }), [props.layer, parentContext]) -export function makeProvider( - runtime: Runtime.Runtime, - Context: React.Context>, -) { - return function ReffuseReactContextProvider(props: ProviderProps) { - const value = React.useMemo(() => ({ - runtime, - context: Effect.context().pipe( - Effect.provide(props.layer), - Runtime.runSync(runtime), - ), - }), [props.layer]) + return ( + + ) + } + : function ReffuseReactContextProvider(props) { + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Effect.provide(Context.empty() as Context.Context), // Required for type safety + Runtime.runSync(runtime), + ), + }), [props.layer]) - return ( - - ) - } + return ( + + ) + } } diff --git a/packages/reffuse/src/ReffuseReactContextProvider.tsx b/packages/reffuse/src/ReffuseReactContextProvider.tsx deleted file mode 100644 index f0d0ad7..0000000 --- a/packages/reffuse/src/ReffuseReactContextProvider.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Effect, Runtime, type Layer } from "effect" -import React from "react" -import type * as ReffuseReactContext from "./ReffuseReactContext.js" - - -export interface Props { - readonly layer: Layer.Layer - readonly children?: React.ReactNode -} - -export type ReffuseReactContextProvider = React.FC> - - -export function make( - runtime: Runtime.Runtime, - Context: React.Context>, -) { - return function ReffuseReactContextProvider(props: Props) { - const value = React.useMemo(() => ({ - runtime, - context: Effect.context().pipe( - Effect.provide(props.layer), - Runtime.runSync(runtime), - ), - }), [props.layer]) - - return ( - - ) - } -} -- 2.49.1 From 4b6cf9a46e6cabd031956370f1309481d976775d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 15:16:51 +0100 Subject: [PATCH 30/87] ReffuseReactContext provider split --- packages/reffuse/src/Reffuse.ts | 5 +- packages/reffuse/src/ReffuseReactContext.tsx | 78 ++++++++++---------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 19a4748..f09baa6 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -17,8 +17,9 @@ export class Reffuse< runtime: Runtime.Runtime, parent?: Reffuse, ) { - // TODO: split into makeProvider and makeScopedProvider - this.Provider = ReffuseReactContext.makeProvider(runtime, this.Context, parent) + this.Provider = parent + ? ReffuseReactContext.makeNestedProvider(runtime, this.Context, parent) + : ReffuseReactContext.makeRootProvider(runtime, this.Context) } diff --git a/packages/reffuse/src/ReffuseReactContext.tsx b/packages/reffuse/src/ReffuseReactContext.tsx index dee25a0..a712014 100644 --- a/packages/reffuse/src/ReffuseReactContext.tsx +++ b/packages/reffuse/src/ReffuseReactContext.tsx @@ -24,7 +24,29 @@ export interface ProviderProps< readonly children?: React.ReactNode } -export function makeProvider< +export function makeRootProvider( + runtime: Runtime.Runtime, + ReactContext: React.Context>, +): Provider { + return function ReffuseRootReactContextProvider(props) { + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Runtime.runSync(runtime), + ), + }), [props.layer]) + + return ( + + ) + } +} + +export function makeNestedProvider< RuntimeR, ContextR extends ParentContextR | OwnContextR, OwnContextR, @@ -32,43 +54,25 @@ export function makeProvider< >( runtime: Runtime.Runtime, ReactContext: React.Context>, - parent?: Reffuse.Reffuse, + parent: Reffuse.Reffuse, ): Provider { - return parent - ? function ReffuseReactContextProvider(props) { - const parentContext = parent.useContext() + return function ReffuseNestedReactContextProvider(props) { + const parentContext = parent.useContext() - const value = React.useMemo(() => ({ - runtime, - context: Effect.context().pipe( - Effect.provide(props.layer), - Effect.provide(parentContext), - Runtime.runSync(runtime), - ), - }), [props.layer, parentContext]) + const value = React.useMemo(() => ({ + runtime, + context: Effect.context().pipe( + Effect.provide(props.layer), + Effect.provide(parentContext), + Runtime.runSync(runtime), + ), + }), [props.layer, parentContext]) - return ( - - ) - } - : function ReffuseReactContextProvider(props) { - const value = React.useMemo(() => ({ - runtime, - context: Effect.context().pipe( - Effect.provide(props.layer), - Effect.provide(Context.empty() as Context.Context), // Required for type safety - Runtime.runSync(runtime), - ), - }), [props.layer]) - - return ( - - ) - } + return ( + + ) + } } -- 2.49.1 From cf0951039cf414bcddf820905d2515e5c24b37b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 15:29:09 +0100 Subject: [PATCH 31/87] Fix --- packages/reffuse/src/Reffuse.ts | 2 +- packages/reffuse/src/ReffuseReactContext.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index f09baa6..9fb7f53 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -19,7 +19,7 @@ export class Reffuse< ) { this.Provider = parent ? ReffuseReactContext.makeNestedProvider(runtime, this.Context, parent) - : ReffuseReactContext.makeRootProvider(runtime, this.Context) + : ReffuseReactContext.makeRootProvider(runtime, this.Context) } diff --git a/packages/reffuse/src/ReffuseReactContext.tsx b/packages/reffuse/src/ReffuseReactContext.tsx index a712014..d1bcf0e 100644 --- a/packages/reffuse/src/ReffuseReactContext.tsx +++ b/packages/reffuse/src/ReffuseReactContext.tsx @@ -24,15 +24,21 @@ export interface ProviderProps< readonly children?: React.ReactNode } -export function makeRootProvider( +export function makeRootProvider< + RuntimeR, + ContextR extends ParentContextR | OwnContextR, + OwnContextR, + ParentContextR, +>( runtime: Runtime.Runtime, ReactContext: React.Context>, -): Provider { +): Provider { return function ReffuseRootReactContextProvider(props) { const value = React.useMemo(() => ({ runtime, context: Effect.context().pipe( Effect.provide(props.layer), + Effect.provide(Context.empty() as Context.Context), // Just there for type safety. ParentContextR is always never here anyway Runtime.runSync(runtime), ), }), [props.layer]) -- 2.49.1 From f50adbf1198b763e9abcece015e9633d7c383bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 15:40:02 +0100 Subject: [PATCH 32/87] Done --- packages/reffuse/src/Reffuse.ts | 7 +++++-- packages/reffuse/src/index.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 9fb7f53..c91b6b9 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -119,6 +119,9 @@ export class Reffuse< } -// TODO: allow passing runtime -export const make = (): Reffuse => +export const make = (): Reffuse => new Reffuse(Runtime.defaultRuntime) + +export const makeWithRuntime = () => + (runtime: Runtime.Runtime): Reffuse => + new Reffuse(runtime) diff --git a/packages/reffuse/src/index.ts b/packages/reffuse/src/index.ts index b5dfcef..9a6c16c 100644 --- a/packages/reffuse/src/index.ts +++ b/packages/reffuse/src/index.ts @@ -1 +1,2 @@ export * as Reffuse from "./Reffuse.js" +export * as ReffuseReactContext from "./ReffuseReactContext.js" -- 2.49.1 From 2aaee4826b52fe47477bec252e037e46364e4757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 15:53:20 +0100 Subject: [PATCH 33/87] extend --- packages/reffuse/src/Reffuse.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index c91b6b9..1185e55 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -14,7 +14,7 @@ export class Reffuse< readonly Provider: ReffuseReactContext.Provider constructor( - runtime: Runtime.Runtime, + readonly runtime: Runtime.Runtime, parent?: Reffuse, ) { this.Provider = parent @@ -22,6 +22,15 @@ export class Reffuse< : ReffuseReactContext.makeRootProvider(runtime, this.Context) } + extend() { + return new Reffuse< + RuntimeR, + ContextR | OwnContextR, + OwnContextR, + ContextR + >(this.runtime, this) + } + useRuntime(): Runtime.Runtime { return React.useContext(this.Context).runtime -- 2.49.1 From ae6bb410a3fbf7aec486cfaea5d1957e4936413d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 15:53:42 +0100 Subject: [PATCH 34/87] 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 1185e55..131c563 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -14,7 +14,7 @@ export class Reffuse< readonly Provider: ReffuseReactContext.Provider constructor( - readonly runtime: Runtime.Runtime, + private readonly runtime: Runtime.Runtime, parent?: Reffuse, ) { this.Provider = parent -- 2.49.1 From 249de93047fc74787f0d8b397bbf67a11c746a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 16:03:07 +0100 Subject: [PATCH 35/87] Fix --- packages/example/src/main.tsx | 11 +++++++++-- packages/example/src/routes/__root.tsx | 25 +++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index 0f57e0d..ba5c328 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,10 +1,14 @@ import { createRouter, RouterProvider } from "@tanstack/react-router" +import { Layer } from "effect" import { StrictMode } from "react" import { createRoot } from "react-dom/client" import "./index.css" -import { routeTree } from './routeTree.gen' +import { Reffuse } from "./reffuse" +import { routeTree } from "./routeTree.gen" +const layer = Layer.empty + const router = createRouter({ routeTree }) declare module "@tanstack/react-router" { @@ -13,8 +17,11 @@ declare module "@tanstack/react-router" { } } + createRoot(document.getElementById("root")!).render( - + + + ) diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index 19fd034..b152a32 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -1,8 +1,5 @@ -import { Reffuse } from "@/reffuse" import { createRootRoute, Link, Outlet } from "@tanstack/react-router" import { TanStackRouterDevtools } from "@tanstack/router-devtools" -import { Layer } from "effect" -import { useMemo } from "react" export const Route = createRootRoute({ @@ -10,18 +7,14 @@ export const Route = createRootRoute({ }) function Root() { - const layer = useMemo(() => Layer.empty, []) + return <> +
+ Index + Time + Count +
- return ( - -
- Index - Time - Count -
- - - -
- ) + + + } -- 2.49.1 From 671a80b6ffad9876dd918958598f5dacf5e06a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 17:09:04 +0100 Subject: [PATCH 36/87] Post work --- packages/example/src/domain/Post.ts | 8 ++++++++ packages/example/src/domain/index.ts | 1 + packages/example/src/main.tsx | 5 ++++- packages/example/src/reffuse.ts | 3 ++- packages/example/src/services/FetchData.ts | 18 ++++++++++++++++++ packages/example/src/services/PostState.ts | 7 +++++++ packages/example/src/services/index.ts | 2 ++ packages/example/src/views/post/VPost.tsx | 7 +++++++ packages/example/src/views/post/reffuse.ts | 5 +++++ 9 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 packages/example/src/domain/Post.ts create mode 100644 packages/example/src/domain/index.ts create mode 100644 packages/example/src/services/FetchData.ts create mode 100644 packages/example/src/services/PostState.ts create mode 100644 packages/example/src/services/index.ts create mode 100644 packages/example/src/views/post/VPost.tsx create mode 100644 packages/example/src/views/post/reffuse.ts diff --git a/packages/example/src/domain/Post.ts b/packages/example/src/domain/Post.ts new file mode 100644 index 0000000..d89a6fb --- /dev/null +++ b/packages/example/src/domain/Post.ts @@ -0,0 +1,8 @@ +import { Schema } from "effect" + + +export class Post extends Schema.Class("Post")({ + id: Schema.String, + title: Schema.String, + content: Schema.String, +}) {} diff --git a/packages/example/src/domain/index.ts b/packages/example/src/domain/index.ts new file mode 100644 index 0000000..a099a8a --- /dev/null +++ b/packages/example/src/domain/index.ts @@ -0,0 +1 @@ +export * as Post from "./Post" diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index ba5c328..aab4b02 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -5,9 +5,12 @@ import { createRoot } from "react-dom/client" import "./index.css" import { Reffuse } from "./reffuse" import { routeTree } from "./routeTree.gen" +import { FetchData } from "./services" -const layer = Layer.empty +const layer = Layer.empty.pipe( + Layer.provideMerge(FetchData.mockLayer) +) const router = createRouter({ routeTree }) diff --git a/packages/example/src/reffuse.ts b/packages/example/src/reffuse.ts index 9c41d25..8fe05c7 100644 --- a/packages/example/src/reffuse.ts +++ b/packages/example/src/reffuse.ts @@ -1,4 +1,5 @@ import { make } from "@thilawyn/reffuse/Reffuse" +import { FetchData } from "./services" -export const Reffuse = make() +export const Reffuse = make() diff --git a/packages/example/src/services/FetchData.ts b/packages/example/src/services/FetchData.ts new file mode 100644 index 0000000..b0e2462 --- /dev/null +++ b/packages/example/src/services/FetchData.ts @@ -0,0 +1,18 @@ +import { Post } from "@/domain" +import { Context, Effect, Layer } from "effect" + + +export class FetchData extends Context.Tag("FetchData") +}>() {} + + +export const mockLayer = Layer.succeed(FetchData, { + fetchPosts: Effect.succeed([ + Post.Post.make({ + id: "1", + title: "Lorem ipsum", + content: "Lorem ipsum", + }) + ]) +}) diff --git a/packages/example/src/services/PostState.ts b/packages/example/src/services/PostState.ts new file mode 100644 index 0000000..e14ced3 --- /dev/null +++ b/packages/example/src/services/PostState.ts @@ -0,0 +1,7 @@ +import { Post } from "@/domain" +import { Context, SubscriptionRef } from "effect" + + +export class PostState extends Context.Tag("PostState") +}>() {} diff --git a/packages/example/src/services/index.ts b/packages/example/src/services/index.ts new file mode 100644 index 0000000..c49d4f1 --- /dev/null +++ b/packages/example/src/services/index.ts @@ -0,0 +1,2 @@ +export * as FetchData from "./FetchData" +export * as PostState from "./PostState" diff --git a/packages/example/src/views/post/VPost.tsx b/packages/example/src/views/post/VPost.tsx new file mode 100644 index 0000000..6f7a972 --- /dev/null +++ b/packages/example/src/views/post/VPost.tsx @@ -0,0 +1,7 @@ + + +export function VPost() { + + + +} diff --git a/packages/example/src/views/post/reffuse.ts b/packages/example/src/views/post/reffuse.ts new file mode 100644 index 0000000..947aad7 --- /dev/null +++ b/packages/example/src/views/post/reffuse.ts @@ -0,0 +1,5 @@ +import { Reffuse as RootReffuse } from "@/reffuse" +import { PostState } from "@/services" + + +export const Reffuse = RootReffuse.extend() -- 2.49.1 From 4f091ae22109f5ca142ce89fee61e8bf3d1e9bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 21:29:38 +0100 Subject: [PATCH 37/87] API work --- packages/reffuse/src/Reffuse.ts | 68 +++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 131c563..a638b6b 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -77,31 +77,71 @@ export class Reffuse< } + useMemo( + effect: Effect.Effect, + deps: React.DependencyList, + options?: RenderOptions, + ): A { + const runSync = this.useRunSync() + + return React.useMemo(() => runSync(effect), [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + ...deps, + ]) + } + + // useEffect( + // effect: Effect.Effect, + // deps?: React.DependencyList, + // options?: RenderOptions, + // ): void { + // const runSync = this.useRunSync() + + // return React.useEffect(() => { runSync(effect) }, [ + // ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], + // ...(deps ?? []), + // ]) + // } + + useSuspense( + effect: Effect.Effect, + options?: { readonly signal?: AbortSignal }, + ): A { + const runPromise = this.useRunPromise() + return React.use(runPromise(effect, options)) + } + useFork( - self: Effect.Effect, + effect: Effect.Effect, deps?: React.DependencyList, - options?: Runtime.RunForkOptions, + options?: Runtime.RunForkOptions & RenderOptions, ): void { const runFork = this.useRunFork() return React.useEffect(() => { - const fiber = runFork(Effect.scoped(self), options) + const fiber = runFork(Effect.scoped(effect), options) return () => { runFork(Fiber.interrupt(fiber)) } - }, [runFork, ...(deps ?? [])]) + }, [ + ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runFork], + ...(deps ?? []), + ]) } useRef
(value: A): SubscriptionRef.SubscriptionRef { - const runSync = this.useRunSync() - return React.useMemo(() => runSync(SubscriptionRef.make(value)), []) + return this.useMemo( + SubscriptionRef.make(value), + [], + { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes + ) } useRefFromEffect(effect: Effect.Effect): SubscriptionRef.SubscriptionRef { - const runSync = this.useRunSync() - - return React.useMemo(() => runSync(effect.pipe( - Effect.flatMap(SubscriptionRef.make) - )), []) + return this.useMemo( + effect.pipe(Effect.flatMap(SubscriptionRef.make)), + [], + { doNotReExecuteOnRuntimeOrContextChange: false }, // Do not recreate the ref when the context changes + ) } useRefState(ref: SubscriptionRef.SubscriptionRef): [A, React.Dispatch>] { @@ -128,6 +168,12 @@ export class Reffuse< } +export interface RenderOptions { + /** Prevents re-executing the effect when the Effect runtime or context changes. Defaults to `false`. */ + readonly doNotReExecuteOnRuntimeOrContextChange?: boolean +} + + export const make = (): Reffuse => new Reffuse(Runtime.defaultRuntime) -- 2.49.1 From 18d94c77e22d96e3d455f310dc5a3af97bb2500e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 22:11:13 +0100 Subject: [PATCH 38/87] Posts --- packages/example/src/routes/index.tsx | 23 ++++++++++++++- packages/example/src/services/FetchData.ts | 20 ++++++++----- packages/example/src/services/PostState.ts | 7 ----- packages/example/src/services/index.ts | 1 - packages/example/src/views/post/VPost.tsx | 29 ++++++++++++++++++- packages/example/src/views/post/reffuse.ts | 6 +--- packages/example/src/views/posts/VPosts.tsx | 25 ++++++++++++++++ packages/example/src/views/posts/reffuse.ts | 5 ++++ .../src/views/posts/services/PostsState.ts | 7 +++++ .../example/src/views/posts/services/index.ts | 1 + packages/reffuse/src/Reffuse.ts | 4 +-- 11 files changed, 104 insertions(+), 24 deletions(-) delete mode 100644 packages/example/src/services/PostState.ts create mode 100644 packages/example/src/views/posts/VPosts.tsx create mode 100644 packages/example/src/views/posts/reffuse.ts create mode 100644 packages/example/src/views/posts/services/PostsState.ts create mode 100644 packages/example/src/views/posts/services/index.ts diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index 25b530d..cde72ca 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -1,4 +1,10 @@ +import { Reffuse } from "@/reffuse" +import { FetchData } from "@/services" +import { Reffuse as PostsReffuse } from "@/views/posts/reffuse" +import { PostsState } from "@/views/posts/services" +import { VPosts } from "@/views/posts/VPosts" import { createFileRoute } from "@tanstack/react-router" +import { Effect, Layer, SubscriptionRef } from "effect" export const Route = createFileRoute("/")({ @@ -6,5 +12,20 @@ export const Route = createFileRoute("/")({ }) function Index() { - return <> + + const postsLayer = Reffuse.useMemo(FetchData.FetchData.pipe( + Effect.flatMap(({ fetchPosts }) => fetchPosts), + Effect.flatMap(SubscriptionRef.make), + Effect.map(posts => Layer.succeed(PostsState.PostsState, { posts })), + )) + + + return ( +
+ + + +
+ ) + } diff --git a/packages/example/src/services/FetchData.ts b/packages/example/src/services/FetchData.ts index b0e2462..fa8f555 100644 --- a/packages/example/src/services/FetchData.ts +++ b/packages/example/src/services/FetchData.ts @@ -1,18 +1,24 @@ import { Post } from "@/domain" -import { Context, Effect, Layer } from "effect" +import { Chunk, Context, Effect, Layer } from "effect" export class FetchData extends Context.Tag("FetchData") + readonly fetchPosts: Effect.Effect> }>() {} export const mockLayer = Layer.succeed(FetchData, { - fetchPosts: Effect.succeed([ + fetchPosts: Effect.succeed(Chunk.make( Post.Post.make({ id: "1", - title: "Lorem ipsum", - content: "Lorem ipsum", - }) - ]) + title: "Lorem ipsum dolor sit amet", + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget lacus sit amet diam suscipit porttitor non at felis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla risus ligula, elementum nec scelerisque eget, volutpat vel sapien. Phasellus aliquam ac neque vitae sodales. Nunc sodales congue odio. Nulla eget nisl cursus, convallis lorem at, varius lectus. Aliquam vitae mauris vel mi dignissim condimentum. Proin sed dignissim sapien, ut cursus ex. Donec eget sapien sagittis, auctor metus vitae, fringilla lacus. Donec ut elit a quam aliquet consectetur interdum eu nisl. Etiam nec convallis purus, eu venenatis nulla. Phasellus non metus id mauris tincidunt cursus. Cras varius aliquet diam eu blandit. In hac habitasse platea dictumst.", + }), + + Post.Post.make({ + id: "2", + title: "Vestibulum non bibendum ligula", + content: "Vestibulum non bibendum ligula. Integer pellentesque, diam ac faucibus volutpat, nulla libero porttitor nunc, ac pulvinar tortor diam id ipsum. Sed id enim at odio euismod imperdiet et ac purus. Etiam tempus ipsum semper scelerisque mollis. Integer auctor, magna et tristique tempus, nisi mi euismod est, nec finibus quam nunc nec libero. Maecenas aliquet viverra magna, vitae blandit ligula pharetra id. Vestibulum vel lacus at nibh placerat tincidunt. Sed suscipit tellus vel felis euismod, et sollicitudin neque cursus. Curabitur dapibus eros vitae ligula suscipit, at facilisis risus venenatis. Sed pharetra blandit pulvinar. Vivamus vestibulum at ligula pulvinar fringilla. Suspendisse vel mattis libero, eget vulputate massa. Vivamus vehicula, lectus id tempor maximus, erat tortor blandit purus, at scelerisque nunc urna faucibus sapien.", + }), + )) }) diff --git a/packages/example/src/services/PostState.ts b/packages/example/src/services/PostState.ts deleted file mode 100644 index e14ced3..0000000 --- a/packages/example/src/services/PostState.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Post } from "@/domain" -import { Context, SubscriptionRef } from "effect" - - -export class PostState extends Context.Tag("PostState") -}>() {} diff --git a/packages/example/src/services/index.ts b/packages/example/src/services/index.ts index c49d4f1..d677cf1 100644 --- a/packages/example/src/services/index.ts +++ b/packages/example/src/services/index.ts @@ -1,2 +1 @@ export * as FetchData from "./FetchData" -export * as PostState from "./PostState" diff --git a/packages/example/src/views/post/VPost.tsx b/packages/example/src/views/post/VPost.tsx index 6f7a972..0effa47 100644 --- a/packages/example/src/views/post/VPost.tsx +++ b/packages/example/src/views/post/VPost.tsx @@ -1,7 +1,34 @@ +import { Post } from "@/domain" +import { Chunk, Effect, Ref } from "effect" +import { PostsState } from "../posts/services" +import { Reffuse } from "./reffuse" -export function VPost() { +export interface VPostProps { + readonly index: number + readonly post: Post.Post +} +export function VPost({ post, index }: VPostProps) { + + const runSync = Reffuse.useRunSync() + + + return ( +
+

{post.title}

+

{post.content}

+ + +
+ ) } diff --git a/packages/example/src/views/post/reffuse.ts b/packages/example/src/views/post/reffuse.ts index 947aad7..25017fe 100644 --- a/packages/example/src/views/post/reffuse.ts +++ b/packages/example/src/views/post/reffuse.ts @@ -1,5 +1 @@ -import { Reffuse as RootReffuse } from "@/reffuse" -import { PostState } from "@/services" - - -export const Reffuse = RootReffuse.extend() +export { Reffuse } from "../posts/reffuse" diff --git a/packages/example/src/views/posts/VPosts.tsx b/packages/example/src/views/posts/VPosts.tsx new file mode 100644 index 0000000..23fedcb --- /dev/null +++ b/packages/example/src/views/posts/VPosts.tsx @@ -0,0 +1,25 @@ +import { Chunk } from "effect" +import { VPost } from "../post/VPost" +import { Reffuse } from "./reffuse" +import { PostsState } from "./services" + + +export function VPosts() { + + const state = Reffuse.useMemo(PostsState.PostsState) + const [posts] = Reffuse.useRefState(state.posts) + + + return ( +
+ {Chunk.map(posts, (post, index) => ( + + ))} +
+ ) + +} diff --git a/packages/example/src/views/posts/reffuse.ts b/packages/example/src/views/posts/reffuse.ts new file mode 100644 index 0000000..99ce681 --- /dev/null +++ b/packages/example/src/views/posts/reffuse.ts @@ -0,0 +1,5 @@ +import { Reffuse as RootReffuse } from "@/reffuse" +import { PostsState } from "./services" + + +export const Reffuse = RootReffuse.extend() diff --git a/packages/example/src/views/posts/services/PostsState.ts b/packages/example/src/views/posts/services/PostsState.ts new file mode 100644 index 0000000..74d3aca --- /dev/null +++ b/packages/example/src/views/posts/services/PostsState.ts @@ -0,0 +1,7 @@ +import { Post } from "@/domain" +import { Chunk, Context, SubscriptionRef } from "effect" + + +export class PostsState extends Context.Tag("PostsState")> +}>() {} diff --git a/packages/example/src/views/posts/services/index.ts b/packages/example/src/views/posts/services/index.ts new file mode 100644 index 0000000..e8a8ec6 --- /dev/null +++ b/packages/example/src/views/posts/services/index.ts @@ -0,0 +1 @@ +export * as PostsState from "./PostsState" diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index a638b6b..801f7bb 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -79,14 +79,14 @@ export class Reffuse< useMemo( effect: Effect.Effect, - deps: React.DependencyList, + deps?: React.DependencyList, options?: RenderOptions, ): A { const runSync = this.useRunSync() return React.useMemo(() => runSync(effect), [ ...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync], - ...deps, + ...(deps ?? []), ]) } -- 2.49.1 From ec264e03815a676393b5f4681592cb856bfa7498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 14 Jan 2025 22:22:10 +0100 Subject: [PATCH 39/87] State --- packages/example/src/routes/index.tsx | 5 ++--- packages/example/src/views/post/VPost.tsx | 4 ++-- .../example/src/views/posts/services/PostsState.ts | 10 +++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index cde72ca..4cc7ad2 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -4,7 +4,7 @@ import { Reffuse as PostsReffuse } from "@/views/posts/reffuse" import { PostsState } from "@/views/posts/services" import { VPosts } from "@/views/posts/VPosts" import { createFileRoute } from "@tanstack/react-router" -import { Effect, Layer, SubscriptionRef } from "effect" +import { Effect } from "effect" export const Route = createFileRoute("/")({ @@ -15,8 +15,7 @@ function Index() { const postsLayer = Reffuse.useMemo(FetchData.FetchData.pipe( Effect.flatMap(({ fetchPosts }) => fetchPosts), - Effect.flatMap(SubscriptionRef.make), - Effect.map(posts => Layer.succeed(PostsState.PostsState, { posts })), + Effect.map(PostsState.make), )) diff --git a/packages/example/src/views/post/VPost.tsx b/packages/example/src/views/post/VPost.tsx index 0effa47..6fceed6 100644 --- a/packages/example/src/views/post/VPost.tsx +++ b/packages/example/src/views/post/VPost.tsx @@ -1,5 +1,5 @@ import { Post } from "@/domain" -import { Chunk, Effect, Ref } from "effect" +import { Effect } from "effect" import { PostsState } from "../posts/services" import { Reffuse } from "./reffuse" @@ -22,7 +22,7 @@ export function VPost({ post, index }: VPostProps) {