@fictjs/runtime 0.2.3 → 0.3.0

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.
Files changed (58) hide show
  1. package/dist/advanced.cjs +10 -8
  2. package/dist/advanced.cjs.map +1 -1
  3. package/dist/advanced.d.cts +8 -16
  4. package/dist/advanced.d.ts +8 -16
  5. package/dist/advanced.js +5 -3
  6. package/dist/advanced.js.map +1 -1
  7. package/dist/{chunk-2U6M3LKS.cjs → chunk-ID3WBWNO.cjs} +452 -219
  8. package/dist/chunk-ID3WBWNO.cjs.map +1 -0
  9. package/dist/{chunk-5YTFFAVU.cjs → chunk-L4DIV3RC.cjs} +7 -7
  10. package/dist/{chunk-5YTFFAVU.cjs.map → chunk-L4DIV3RC.cjs.map} +1 -1
  11. package/dist/{chunk-W525IQWC.cjs → chunk-M2TSXZ4C.cjs} +16 -16
  12. package/dist/{chunk-W525IQWC.cjs.map → chunk-M2TSXZ4C.cjs.map} +1 -1
  13. package/dist/{chunk-YVDWXY44.js → chunk-SO6X7G5S.js} +450 -217
  14. package/dist/chunk-SO6X7G5S.js.map +1 -0
  15. package/dist/{chunk-UHXUEGQH.js → chunk-TWELIZRY.js} +2 -2
  16. package/dist/{chunk-3WD7QD5G.js → chunk-XLIZJMMJ.js} +2 -2
  17. package/dist/{context-9gFXOdJl.d.cts → context-B25xyQrJ.d.cts} +36 -2
  18. package/dist/{context-4woHo7-L.d.ts → context-CGdP7_Jb.d.ts} +36 -2
  19. package/dist/{effect-ClARNUCc.d.cts → effect-D6kaLM2-.d.cts} +80 -1
  20. package/dist/{effect-ClARNUCc.d.ts → effect-D6kaLM2-.d.ts} +80 -1
  21. package/dist/index.cjs +40 -38
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +4 -4
  24. package/dist/index.d.ts +4 -4
  25. package/dist/index.dev.js +322 -145
  26. package/dist/index.dev.js.map +1 -1
  27. package/dist/index.js +4 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal.cjs +39 -35
  30. package/dist/internal.cjs.map +1 -1
  31. package/dist/internal.d.cts +8 -6
  32. package/dist/internal.d.ts +8 -6
  33. package/dist/internal.js +7 -3
  34. package/dist/internal.js.map +1 -1
  35. package/dist/{props-DAyeRPwH.d.ts → props-BEgIVMRx.d.ts} +8 -15
  36. package/dist/{props-CBwuh35e.d.cts → props-BIfromL0.d.cts} +8 -15
  37. package/dist/scope-Cx_3CjIZ.d.cts +18 -0
  38. package/dist/scope-CzNkn587.d.ts +18 -0
  39. package/package.json +1 -1
  40. package/src/advanced.ts +1 -0
  41. package/src/binding.ts +30 -4
  42. package/src/constants.ts +5 -0
  43. package/src/cycle-guard.ts +59 -7
  44. package/src/devtools.ts +22 -2
  45. package/src/dom.ts +84 -10
  46. package/src/hooks.ts +60 -13
  47. package/src/index.ts +3 -1
  48. package/src/internal.ts +2 -2
  49. package/src/lifecycle.ts +13 -5
  50. package/src/memo.ts +3 -4
  51. package/src/props.ts +16 -0
  52. package/src/signal.ts +204 -36
  53. package/dist/chunk-2U6M3LKS.cjs.map +0 -1
  54. package/dist/chunk-YVDWXY44.js.map +0 -1
  55. package/dist/scope-DvgMquEy.d.ts +0 -55
  56. package/dist/scope-xmdo6lVU.d.cts +0 -55
  57. /package/dist/{chunk-UHXUEGQH.js.map → chunk-TWELIZRY.js.map} +0 -0
  58. /package/dist/{chunk-3WD7QD5G.js.map → chunk-XLIZJMMJ.js.map} +0 -0
package/src/signal.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { beginFlushGuard, beforeEffectRunGuard, endFlushGuard } from './cycle-guard'
2
2
  import { getDevtoolsHook } from './devtools'
3
+ import { __fictGetCurrentComponentId } from './hooks'
3
4
  import {
4
5
  getCurrentRoot,
5
6
  handleError,
@@ -70,6 +71,30 @@ export interface BaseNode {
70
71
  flags: number
71
72
  }
72
73
 
74
+ /**
75
+ * Options for creating a signal
76
+ */
77
+ export interface SignalOptions<T> {
78
+ /** Custom equality check */
79
+ equals?: false | ((prev: T, next: T) => boolean)
80
+ /** Debug name */
81
+ name?: string
82
+ /** Source location */
83
+ devToolsSource?: string
84
+ }
85
+
86
+ /**
87
+ * Options for creating a memo
88
+ */
89
+ export interface MemoOptions<T> {
90
+ /** Custom equality check */
91
+ equals?: false | ((prev: T, next: T) => boolean)
92
+ /** Debug name */
93
+ name?: string
94
+ /** Source location */
95
+ devToolsSource?: string
96
+ }
97
+
73
98
  /**
74
99
  * Signal node - mutable reactive value
75
100
  */
@@ -82,6 +107,14 @@ export interface SignalNode<T = unknown> extends BaseNode {
82
107
  deps?: undefined
83
108
  depsTail?: undefined
84
109
  getter?: undefined
110
+ /** DevTools ID */
111
+ __id?: number | undefined
112
+ /** Equality check */
113
+ equals?: false | ((prev: T, next: T) => boolean)
114
+ /** Debug name */
115
+ name?: string
116
+ /** Source location */
117
+ devToolsSource?: string
85
118
  }
86
119
 
87
120
  /**
@@ -96,6 +129,14 @@ export interface ComputedNode<T = unknown> extends BaseNode {
96
129
  depsTail: Link | undefined
97
130
  /** Getter function to compute the value */
98
131
  getter: (oldValue: T | undefined) => T
132
+ /** DevTools ID */
133
+ __id?: number | undefined
134
+ /** Equality check */
135
+ equals?: false | ((prev: T, next: T) => boolean)
136
+ /** Debug name */
137
+ name?: string
138
+ /** Source location */
139
+ devToolsSource?: string
99
140
  }
100
141
 
101
142
  /**
@@ -464,6 +505,9 @@ function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void {
464
505
  else sub.deps = newLink
465
506
  if (prevSub !== undefined) prevSub.nextSub = newLink
466
507
  else dep.subs = newLink
508
+
509
+ // Track dependency for devtools
510
+ if (isDev) trackDependencyDevtools(dep, sub)
467
511
  }
468
512
  /**
469
513
  * Remove a link between a dependency and a subscriber
@@ -488,6 +532,9 @@ function unlink(lnk: Link, sub: ReactiveNode = lnk.sub): Link | undefined {
488
532
  if (prevSub !== undefined) prevSub.nextSub = nextSub
489
533
  else if ((dep.subs = nextSub) === undefined) unwatched(dep)
490
534
 
535
+ // Notify devtools that dependency edge is removed
536
+ if (isDev) untrackDependencyDevtools(dep, sub)
537
+
491
538
  return nextDep
492
539
  }
493
540
  /**
@@ -684,6 +731,16 @@ function update(node: ReactiveNode): boolean {
684
731
  ? updateComputed(node as ComputedNode)
685
732
  : updateSignal(node as SignalNode)
686
733
  }
734
+
735
+ function valuesDiffer<T>(
736
+ node: { equals?: false | ((prev: T, next: T) => boolean) },
737
+ prev: T,
738
+ next: T,
739
+ ): boolean {
740
+ if (node.equals === false) return true
741
+ if (typeof node.equals === 'function') return !node.equals(prev, next)
742
+ return prev !== next
743
+ }
687
744
  /**
688
745
  * Notify an effect and add it to the queue
689
746
  * @param effect - The effect to notify
@@ -740,7 +797,7 @@ function updateSignal(s: SignalNode): boolean {
740
797
  s.flags = Mutable
741
798
  const current = s.currentValue
742
799
  const pending = s.pendingValue
743
- if (current !== pending) {
800
+ if (valuesDiffer(s, current, pending)) {
744
801
  s.currentValue = pending
745
802
  return true
746
803
  }
@@ -764,8 +821,9 @@ function updateComputed<T>(c: ComputedNode<T>): boolean {
764
821
  activeSub = prevSub
765
822
  c.flags &= ~Running
766
823
  purgeDeps(c)
767
- if (oldValue !== newValue) {
824
+ if (valuesDiffer(c, oldValue, newValue)) {
768
825
  c.value = newValue
826
+ if (isDev) updateComputedDevtools(c, newValue)
769
827
  return true
770
828
  }
771
829
  return false
@@ -792,7 +850,7 @@ function runEffect(e: EffectNode): void {
792
850
  }
793
851
  }
794
852
  ++cycle
795
- effectRunDevtools(e)
853
+ if (isDev) effectRunDevtools(e)
796
854
  e.depsTail = undefined
797
855
  e.flags = WatchingRunning
798
856
  const prevSub = activeSub
@@ -837,7 +895,7 @@ function runEffect(e: EffectNode): void {
837
895
  }
838
896
  if (isDirty) {
839
897
  ++cycle
840
- effectRunDevtools(e)
898
+ if (isDev) effectRunDevtools(e)
841
899
  e.depsTail = undefined
842
900
  e.flags = WatchingRunning
843
901
  const prevSub = activeSub
@@ -896,10 +954,23 @@ function flush(): void {
896
954
  while (highIndex < highPriorityQueue.length) {
897
955
  const e = highPriorityQueue[highIndex]!
898
956
  if (!beforeEffectRunGuard()) {
899
- if (highIndex > 0) {
900
- highPriorityQueue.copyWithin(0, highIndex)
901
- highPriorityQueue.length -= highIndex
957
+ // fix: When cycle guard fails, drop the current queues to avoid microtask spin.
958
+ // Dev mode will throw inside beforeEffectRunGuard; this branch is for prod warnings.
959
+ for (let i = 0; i < highPriorityQueue.length; i++) {
960
+ const queued = highPriorityQueue[i]
961
+ if (queued && queued.flags !== 0) {
962
+ queued.flags = Watching
963
+ }
902
964
  }
965
+ for (let i = 0; i < lowPriorityQueue.length; i++) {
966
+ const queued = lowPriorityQueue[i]
967
+ if (queued && queued.flags !== 0) {
968
+ queued.flags = Watching
969
+ }
970
+ }
971
+ highPriorityQueue.length = 0
972
+ lowPriorityQueue.length = 0
973
+ flushScheduled = false
903
974
  endFlushGuard()
904
975
  return
905
976
  }
@@ -923,10 +994,23 @@ function flush(): void {
923
994
  }
924
995
  const e = lowPriorityQueue[lowIndex]!
925
996
  if (!beforeEffectRunGuard()) {
926
- if (lowIndex > 0) {
927
- lowPriorityQueue.copyWithin(0, lowIndex)
928
- lowPriorityQueue.length -= lowIndex
997
+ // fix: When cycle guard fails, drop the current queues to avoid microtask spin.
998
+ // Dev mode will throw inside beforeEffectRunGuard; this branch is for prod warnings.
999
+ for (let i = 0; i < highPriorityQueue.length; i++) {
1000
+ const queued = highPriorityQueue[i]
1001
+ if (queued && queued.flags !== 0) {
1002
+ queued.flags = Watching
1003
+ }
929
1004
  }
1005
+ for (let i = 0; i < lowPriorityQueue.length; i++) {
1006
+ const queued = lowPriorityQueue[i]
1007
+ if (queued && queued.flags !== 0) {
1008
+ queued.flags = Watching
1009
+ }
1010
+ }
1011
+ highPriorityQueue.length = 0
1012
+ lowPriorityQueue.length = 0
1013
+ flushScheduled = false
930
1014
  endFlushGuard()
931
1015
  return
932
1016
  }
@@ -945,26 +1029,31 @@ function flush(): void {
945
1029
  * @param initialValue - The initial value
946
1030
  * @returns A signal accessor function
947
1031
  */
948
- export function signal<T>(initialValue: T): SignalAccessor<T> {
949
- const s = {
1032
+ export function signal<T>(initialValue: T, options?: SignalOptions<T>): SignalAccessor<T> {
1033
+ const s: SignalNode<T> = {
950
1034
  currentValue: initialValue,
951
1035
  pendingValue: initialValue,
952
1036
  subs: undefined,
953
1037
  subsTail: undefined,
954
1038
  flags: Mutable,
955
1039
  __id: undefined as number | undefined,
1040
+ ...(options?.equals !== undefined ? { equals: options.equals } : {}),
1041
+ ...(options?.name !== undefined ? { name: options.name } : {}),
1042
+ ...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
956
1043
  }
957
- registerSignalDevtools(initialValue, s)
958
- const accessor = signalOper.bind(s) as SignalAccessor<T> & Record<symbol, boolean>
1044
+ if (isDev) registerSignalDevtools(s)
1045
+ const accessor = signalOper.bind(s as any) as SignalAccessor<T> & Record<symbol, boolean>
959
1046
  accessor[SIGNAL_MARKER] = true
960
1047
  return accessor as SignalAccessor<T>
961
1048
  }
962
1049
  function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
963
1050
  if (arguments.length > 0) {
964
- if (this.pendingValue !== value) {
965
- this.pendingValue = value as T
1051
+ const next = value as T
1052
+ const prev = this.pendingValue
1053
+ if (valuesDiffer(this, prev as T, next)) {
1054
+ this.pendingValue = next
966
1055
  this.flags = MutableDirty
967
- updateSignalDevtools(this, value)
1056
+ if (isDev) updateSignalDevtools(this, next)
968
1057
  const subs = this.subs
969
1058
  if (subs !== undefined) {
970
1059
  propagate(subs)
@@ -977,7 +1066,7 @@ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
977
1066
  const flags = this.flags
978
1067
  // During cleanup, don't update signal - return currentValue as-is
979
1068
  if (flags & Dirty && !inCleanup) {
980
- if (updateSignal(this)) {
1069
+ if (updateSignal(this as any)) {
981
1070
  const subs = this.subs
982
1071
  if (subs !== undefined) shallowPropagate(subs)
983
1072
  }
@@ -986,7 +1075,7 @@ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
986
1075
  let sub = activeSub
987
1076
  while (sub !== undefined) {
988
1077
  if (sub.flags & 3) {
989
- link(this, sub, cycle)
1078
+ link(this as any, sub, cycle)
990
1079
  break
991
1080
  }
992
1081
  const subSubs = sub.subs
@@ -1001,9 +1090,13 @@ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
1001
1090
  /**
1002
1091
  * Create a computed reactive value
1003
1092
  * @param getter - The getter function
1093
+ * @param options - Computed options
1004
1094
  * @returns A computed accessor function
1005
1095
  */
1006
- export function computed<T>(getter: (oldValue?: T) => T): ComputedAccessor<T> {
1096
+ export function computed<T>(
1097
+ getter: (oldValue?: T) => T,
1098
+ options?: MemoOptions<T>,
1099
+ ): ComputedAccessor<T> {
1007
1100
  const c: ComputedNode<T> = {
1008
1101
  value: undefined as unknown as T,
1009
1102
  subs: undefined,
@@ -1012,13 +1105,24 @@ export function computed<T>(getter: (oldValue?: T) => T): ComputedAccessor<T> {
1012
1105
  depsTail: undefined,
1013
1106
  flags: 0,
1014
1107
  getter,
1108
+ __id: undefined as number | undefined,
1109
+ ...(options?.equals !== undefined ? { equals: options.equals } : {}),
1110
+ ...(options?.name !== undefined ? { name: options.name } : {}),
1111
+ ...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
1015
1112
  }
1016
- const bound = (computedOper as (this: ComputedNode<T>) => T).bind(c) as ComputedAccessor<T> &
1017
- Record<symbol, boolean>
1113
+ if (isDev) registerComputedDevtools(c)
1114
+ const bound = (computedOper as (this: ComputedNode<T>) => T).bind(
1115
+ c as any,
1116
+ ) as ComputedAccessor<T> & Record<symbol, boolean>
1018
1117
  bound[COMPUTED_MARKER] = true
1019
1118
  return bound as ComputedAccessor<T>
1020
1119
  }
1021
1120
  function computedOper<T>(this: ComputedNode<T>): T {
1121
+ // fix: During cleanup, return cached value without triggering any updates.
1122
+ // This ensures cleanup functions see the previous state, not the new pending values.
1123
+ // Without this check, checkDirty() could commit pending signal values during cleanup.
1124
+ if (inCleanup) return this.value
1125
+
1022
1126
  const flags = this.flags
1023
1127
 
1024
1128
  if (flags & Dirty) {
@@ -1040,6 +1144,7 @@ function computedOper<T>(this: ComputedNode<T>): T {
1040
1144
  const prevSub = setActiveSub(this)
1041
1145
  try {
1042
1146
  this.value = this.getter(undefined)
1147
+ if (isDev) updateComputedDevtools(this, this.value)
1043
1148
  } finally {
1044
1149
  setActiveSub(prevSub)
1045
1150
  this.flags &= ~Running
@@ -1072,14 +1177,14 @@ export function effect(fn: () => void): EffectDisposer {
1072
1177
  e.root = root
1073
1178
  }
1074
1179
 
1075
- registerEffectDevtools(e)
1180
+ if (isDev) registerEffectDevtools(e)
1076
1181
 
1077
1182
  const prevSub = activeSub
1078
1183
  if (prevSub !== undefined) link(e, prevSub, 0)
1079
1184
  activeSub = e
1080
1185
 
1081
1186
  try {
1082
- effectRunDevtools(e)
1187
+ if (isDev) effectRunDevtools(e)
1083
1188
  fn()
1084
1189
  } finally {
1085
1190
  activeSub = prevSub
@@ -1120,14 +1225,14 @@ export function effectWithCleanup(
1120
1225
  e.root = resolvedRoot
1121
1226
  }
1122
1227
 
1123
- registerEffectDevtools(e)
1228
+ if (isDev) registerEffectDevtools(e)
1124
1229
 
1125
1230
  const prevSub = activeSub
1126
1231
  if (prevSub !== undefined) link(e, prevSub, 0)
1127
1232
  activeSub = e
1128
1233
 
1129
1234
  try {
1130
- effectRunDevtools(e)
1235
+ if (isDev) effectRunDevtools(e)
1131
1236
  fn()
1132
1237
  } finally {
1133
1238
  activeSub = prevSub
@@ -1269,6 +1374,21 @@ export function setActiveSub(sub: ReactiveNode | undefined): ReactiveNode | unde
1269
1374
  export function getBatchDepth(): number {
1270
1375
  return batchDepth
1271
1376
  }
1377
+ /**
1378
+ * Reset all global reactive state for test isolation.
1379
+ * ONLY use this in test setup/teardown - never in production code.
1380
+ * This clears effect queues, resets batch depth, and clears pending flushes.
1381
+ */
1382
+ export function __resetReactiveState(): void {
1383
+ highPriorityQueue.length = 0
1384
+ lowPriorityQueue.length = 0
1385
+ batchDepth = 0
1386
+ activeSub = undefined
1387
+ flushScheduled = false
1388
+ isInTransition = false
1389
+ inCleanup = false
1390
+ cycle = 0
1391
+ }
1272
1392
  /**
1273
1393
  * Execute a function without tracking dependencies
1274
1394
  * @param fn - The function to execute
@@ -1391,21 +1511,30 @@ interface DevtoolsIdentifiable {
1391
1511
  __id?: number
1392
1512
  }
1393
1513
 
1394
- let registerSignalDevtools: (value: unknown, node: SignalNode) => number | undefined = () =>
1395
- undefined
1396
- let updateSignalDevtools: (node: SignalNode, value: unknown) => void = () => {}
1514
+ let registerSignalDevtools: <T>(node: SignalNode<T>) => number | undefined = () => undefined
1515
+ let updateSignalDevtools: <T>(node: SignalNode<T>, value: unknown) => void = () => {}
1516
+ let registerComputedDevtools: <T>(node: ComputedNode<T>) => number | undefined = () => undefined
1517
+ let updateComputedDevtools: <T>(node: ComputedNode<T>, value: unknown) => void = () => {}
1397
1518
  let registerEffectDevtools: (node: EffectNode) => number | undefined = () => undefined
1398
1519
  let effectRunDevtools: (node: EffectNode) => void = () => {}
1520
+ let trackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
1521
+ let untrackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
1399
1522
 
1400
1523
  if (isDev) {
1401
- let devtoolsSignalId = 0
1402
- let devtoolsEffectId = 0
1524
+ // Unified ID counter for all reactive nodes (signal/computed/effect)
1525
+ // to prevent ID collisions when storing in single devtools maps
1526
+ let nextDevtoolsId = 0
1403
1527
 
1404
- registerSignalDevtools = (value, node) => {
1528
+ registerSignalDevtools = node => {
1405
1529
  const hook = getDevtoolsHook()
1406
1530
  if (!hook) return undefined
1407
- const id = ++devtoolsSignalId
1408
- hook.registerSignal(id, value)
1531
+ const id = ++nextDevtoolsId
1532
+ const options: { name?: string; source?: string } = {}
1533
+ if (node.name !== undefined) options.name = node.name
1534
+ if (node.devToolsSource !== undefined) options.source = node.devToolsSource
1535
+ const ownerId = __fictGetCurrentComponentId()
1536
+ if (ownerId !== undefined) (options as any).ownerId = ownerId
1537
+ hook.registerSignal(id, node.currentValue, options)
1409
1538
  ;(node as SignalNode & DevtoolsIdentifiable).__id = id
1410
1539
  return id
1411
1540
  }
@@ -1417,11 +1546,34 @@ if (isDev) {
1417
1546
  if (id) hook.updateSignal(id, value)
1418
1547
  }
1419
1548
 
1549
+ registerComputedDevtools = node => {
1550
+ const hook = getDevtoolsHook()
1551
+ if (!hook) return undefined
1552
+ const id = ++nextDevtoolsId
1553
+ const options: { name?: string; source?: string } = {}
1554
+ if (node.name !== undefined) options.name = node.name
1555
+ if (node.devToolsSource !== undefined) options.source = node.devToolsSource
1556
+ const ownerId = __fictGetCurrentComponentId()
1557
+ if (ownerId !== undefined) (options as any).ownerId = ownerId
1558
+ ;(options as any).hasValue = false
1559
+ hook.registerComputed(id, node.value, options)
1560
+ ;(node as ComputedNode & DevtoolsIdentifiable).__id = id
1561
+ return id
1562
+ }
1563
+
1564
+ updateComputedDevtools = (node, value) => {
1565
+ const hook = getDevtoolsHook()
1566
+ if (!hook) return
1567
+ const id = (node as ComputedNode & DevtoolsIdentifiable).__id
1568
+ if (id) hook.updateComputed(id, value)
1569
+ }
1570
+
1420
1571
  registerEffectDevtools = node => {
1421
1572
  const hook = getDevtoolsHook()
1422
1573
  if (!hook) return undefined
1423
- const id = ++devtoolsEffectId
1424
- hook.registerEffect(id)
1574
+ const id = ++nextDevtoolsId
1575
+ const ownerId = __fictGetCurrentComponentId()
1576
+ hook.registerEffect(id, ownerId !== undefined ? { ownerId } : undefined)
1425
1577
  ;(node as EffectNode & DevtoolsIdentifiable).__id = id
1426
1578
  return id
1427
1579
  }
@@ -1432,6 +1584,22 @@ if (isDev) {
1432
1584
  const id = (node as EffectNode & DevtoolsIdentifiable).__id
1433
1585
  if (id) hook.effectRun(id)
1434
1586
  }
1587
+
1588
+ trackDependencyDevtools = (dep, sub) => {
1589
+ const hook = getDevtoolsHook()
1590
+ if (!hook?.trackDependency) return
1591
+ const depId = (dep as ReactiveNode & DevtoolsIdentifiable).__id
1592
+ const subId = (sub as ReactiveNode & DevtoolsIdentifiable).__id
1593
+ if (depId && subId) hook.trackDependency(subId, depId)
1594
+ }
1595
+
1596
+ untrackDependencyDevtools = (dep, sub) => {
1597
+ const hook = getDevtoolsHook()
1598
+ if (!hook?.untrackDependency) return
1599
+ const depId = (dep as ReactiveNode & DevtoolsIdentifiable).__id
1600
+ const subId = (sub as ReactiveNode & DevtoolsIdentifiable).__id
1601
+ if (depId && subId) hook.untrackDependency(subId, depId)
1602
+ }
1435
1603
  }
1436
1604
 
1437
1605
  // ============================================================================