@fictjs/runtime 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fictjs/runtime",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Fict reactive runtime",
5
5
  "publishConfig": {
6
6
  "access": "public",
package/src/effect.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  runCleanupList,
6
6
  withEffectCleanups,
7
7
  } from './lifecycle'
8
- import { effect } from './signal'
8
+ import { effectWithCleanup } from './signal'
9
9
  import type { Cleanup } from './types'
10
10
 
11
11
  export type Effect = () => void | Cleanup
@@ -14,8 +14,14 @@ export function createEffect(fn: Effect): () => void {
14
14
  let cleanups: Cleanup[] = []
15
15
  const rootForError = getCurrentRoot()
16
16
 
17
- const run = () => {
17
+ // Cleanup runner - called by runEffect BEFORE signal values are committed
18
+ const doCleanup = () => {
18
19
  runCleanupList(cleanups)
20
+ cleanups = []
21
+ }
22
+
23
+ const run = () => {
24
+ // Note: cleanups are now run by signal.ts runEffect before this function is called
19
25
  const bucket: Cleanup[] = []
20
26
  withEffectCleanups(bucket, () => {
21
27
  try {
@@ -33,7 +39,7 @@ export function createEffect(fn: Effect): () => void {
33
39
  cleanups = bucket
34
40
  }
35
41
 
36
- const disposeEffect = effect(run)
42
+ const disposeEffect = effectWithCleanup(run, doCleanup)
37
43
  const teardown = () => {
38
44
  runCleanupList(cleanups)
39
45
  disposeEffect()
@@ -50,11 +56,16 @@ export function createRenderEffect(fn: Effect): () => void {
50
56
  let cleanup: Cleanup | undefined
51
57
  const rootForError = getCurrentRoot()
52
58
 
53
- const run = () => {
59
+ // Cleanup runner - called by runEffect BEFORE signal values are committed
60
+ const doCleanup = () => {
54
61
  if (cleanup) {
55
62
  cleanup()
56
63
  cleanup = undefined
57
64
  }
65
+ }
66
+
67
+ const run = () => {
68
+ // Note: cleanups are now run by signal.ts runEffect before this function is called
58
69
  try {
59
70
  const maybeCleanup = fn()
60
71
  if (typeof maybeCleanup === 'function') {
@@ -69,7 +80,7 @@ export function createRenderEffect(fn: Effect): () => void {
69
80
  }
70
81
  }
71
82
 
72
- const disposeEffect = effect(run)
83
+ const disposeEffect = effectWithCleanup(run, doCleanup)
73
84
  const teardown = () => {
74
85
  if (cleanup) {
75
86
  cleanup()
package/src/signal.ts CHANGED
@@ -96,6 +96,10 @@ export interface EffectNode extends BaseNode {
96
96
  deps: Link | undefined
97
97
  /** Last dependency link */
98
98
  depsTail: Link | undefined
99
+ /** Optional cleanup runner to be called before checkDirty */
100
+ runCleanup?: () => void
101
+ /** Devtools ID */
102
+ __id?: number | undefined
99
103
  }
100
104
 
101
105
  /**
@@ -202,6 +206,8 @@ const enqueueMicrotask =
202
206
  : (fn: () => void) => {
203
207
  Promise.resolve().then(fn)
204
208
  }
209
+ // Flag to indicate cleanup is running - signal reads should return currentValue without updating
210
+ let inCleanup = false
205
211
  export const ReactiveFlags = {
206
212
  None: 0,
207
213
  Mutable,
@@ -735,7 +741,16 @@ function updateComputed<T>(c: ComputedNode<T>): boolean {
735
741
  */
736
742
  function runEffect(e: EffectNode): void {
737
743
  const flags = e.flags
738
- if (flags & Dirty || (flags & Pending && e.deps && checkDirty(e.deps, e))) {
744
+ // Run cleanup BEFORE checkDirty so cleanup sees previous signal values
745
+ if (flags & Dirty) {
746
+ if (e.runCleanup) {
747
+ inCleanup = true
748
+ try {
749
+ e.runCleanup()
750
+ } finally {
751
+ inCleanup = false
752
+ }
753
+ }
739
754
  ++cycle
740
755
  effectRunDevtools(e)
741
756
  e.depsTail = undefined
@@ -752,6 +767,36 @@ function runEffect(e: EffectNode): void {
752
767
  e.flags = Watching
753
768
  throw err
754
769
  }
770
+ } else if (flags & Pending && e.deps) {
771
+ // Run cleanup before checkDirty which commits signal values
772
+ if (e.runCleanup) {
773
+ inCleanup = true
774
+ try {
775
+ e.runCleanup()
776
+ } finally {
777
+ inCleanup = false
778
+ }
779
+ }
780
+ if (checkDirty(e.deps, e)) {
781
+ ++cycle
782
+ effectRunDevtools(e)
783
+ e.depsTail = undefined
784
+ e.flags = WatchingRunning
785
+ const prevSub = activeSub
786
+ activeSub = e
787
+ try {
788
+ e.fn()
789
+ activeSub = prevSub
790
+ e.flags = Watching
791
+ purgeDeps(e)
792
+ } catch (err) {
793
+ activeSub = prevSub
794
+ e.flags = Watching
795
+ throw err
796
+ }
797
+ } else {
798
+ e.flags = Watching
799
+ }
755
800
  } else {
756
801
  e.flags = Watching
757
802
  }
@@ -870,7 +915,8 @@ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
870
915
  }
871
916
 
872
917
  const flags = this.flags
873
- if (flags & Dirty) {
918
+ // During cleanup, don't update signal - return currentValue as-is
919
+ if (flags & Dirty && !inCleanup) {
874
920
  if (updateSignal(this)) {
875
921
  const subs = this.subs
876
922
  if (subs !== undefined) shallowPropagate(subs)
@@ -976,6 +1022,44 @@ export function effect(fn: () => void): EffectDisposer {
976
1022
 
977
1023
  return effectOper.bind(e) as EffectDisposer
978
1024
  }
1025
+
1026
+ /**
1027
+ * Create a reactive effect with a custom cleanup runner
1028
+ * The cleanup runner is called BEFORE signal values are committed, allowing
1029
+ * cleanup functions to access the previous values of signals.
1030
+ * @param fn - The effect function
1031
+ * @param cleanupRunner - Function to run cleanups before signal value commit
1032
+ * @returns An effect disposer function
1033
+ */
1034
+ export function effectWithCleanup(fn: () => void, cleanupRunner: () => void): EffectDisposer {
1035
+ const e: EffectNode = {
1036
+ fn,
1037
+ subs: undefined,
1038
+ subsTail: undefined,
1039
+ deps: undefined,
1040
+ depsTail: undefined,
1041
+ flags: WatchingRunning,
1042
+ runCleanup: cleanupRunner,
1043
+ __id: undefined as number | undefined,
1044
+ }
1045
+
1046
+ registerEffectDevtools(e)
1047
+
1048
+ const prevSub = activeSub
1049
+ if (prevSub !== undefined) link(e, prevSub, 0)
1050
+ activeSub = e
1051
+
1052
+ try {
1053
+ effectRunDevtools(e)
1054
+ fn()
1055
+ } finally {
1056
+ activeSub = prevSub
1057
+ e.flags &= ~Running
1058
+ }
1059
+
1060
+ return effectOper.bind(e) as EffectDisposer
1061
+ }
1062
+
979
1063
  function effectOper(this: EffectNode): void {
980
1064
  disposeNode(this)
981
1065
  }