@@ -19,6 +19,16 @@ function _getCurrentObserver(): VoidFunction | undefined {
1919 return context [ context . length - 1 ] ;
2020}
2121
22+ const UNSET = Symbol ( "UNSET" ) ;
23+ const COMPUTING = Symbol ( "COMPUTING" ) ;
24+ const ERRORED = Symbol ( "ERRORED" ) ;
25+ const READY = Symbol ( "READY" ) ;
26+
27+ interface EffectNode {
28+ error : unknown ;
29+ state : symbol ;
30+ }
31+
2232/**
2333 * Creates a new effect that will be executed immediately and whenever
2434 * any of the signals it reads from change.
@@ -39,10 +49,22 @@ function _getCurrentObserver(): VoidFunction | undefined {
3949 * @param fn The function to execute
4050 */
4151function $effect ( fn : VoidFunction ) : void {
52+ const effectNode : EffectNode = {
53+ error : null ,
54+ state : UNSET
55+ }
56+
4257 const execute = ( ) => {
58+ if ( effectNode . state === COMPUTING ) {
59+ throw new Error ( "Circular dependency detected" ) ;
60+ }
61+
4362 context . push ( execute ) ;
4463 try {
64+ effectNode . state = COMPUTING ;
4565 fn ( ) ;
66+ effectNode . error = null ;
67+ effectNode . state = READY ;
4668 } finally {
4769 context . pop ( ) ;
4870 }
@@ -51,8 +73,22 @@ function $effect(fn: VoidFunction): void {
5173 execute ( ) ;
5274}
5375
76+ interface ComputedNode < T > {
77+ signal : Signal < T | undefined > ;
78+ error : unknown ;
79+ state : symbol ;
80+ }
81+
5482type ComputedFunction < T > = ( ) => T ;
5583
84+ function computedGetter < T > ( node : ComputedNode < T > ) {
85+ if ( node . state === ERRORED ) {
86+ throw node . error ;
87+ }
88+
89+ return node . signal . readOnly as ReadOnlySignal < T > ;
90+ }
91+
5692/**
5793 * Creates a new computed value that will be updated whenever the signals
5894 * it reads from change. Returns a read-only signal that contains the
@@ -69,15 +105,29 @@ type ComputedFunction<T> = () => T;
69105 * @param fn The function that returns the computed value.
70106 */
71107function $computed < T > ( fn : ComputedFunction < T > ) : ReadOnlySignal < T > {
72- // The initial value is undefined, as it will be computed
73- // when the effect runs for the first time
74- const computedSignal : Signal < T | undefined > = $signal ( undefined ) ;
108+ const computedNode : ComputedNode < T > = {
109+ signal : $signal < T | undefined > ( undefined ) ,
110+ error : null ,
111+ state : UNSET
112+ } ;
75113
76114 $effect ( ( ) => {
77- computedSignal . value = fn ( ) ;
115+ if ( computedNode . state === COMPUTING ) {
116+ throw new Error ( "Circular dependency detected" ) ;
117+ }
118+
119+ try {
120+ computedNode . state = COMPUTING ;
121+ computedNode . signal . value = fn ( ) ;
122+ computedNode . error = null ;
123+ computedNode . state = READY ;
124+ } catch ( error ) {
125+ computedNode . state = ERRORED ;
126+ computedNode . error = error ;
127+ }
78128 } ) ;
79129
80- return computedSignal . readOnly as ReadOnlySignal < T > ;
130+ return computedGetter ( computedNode ) ;
81131}
82132
83133type StorageFn < T > = ( value : T ) => State < T > & { [ key : string ] : unknown } ;
0 commit comments