@@ -59,8 +59,8 @@ let current_queued_pre_and_render_effects = [];
5959/** @type {import('./types.js').EffectSignal[] } */
6060let current_queued_effects = [ ] ;
6161
62- /** @type {{t : import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } | null } */
63- let current_path = null ;
62+ /** @type {{ s : import('./types.js').ComputationSignal, p: Array<string | symbol> } | null } */
63+ let current_derived_proxy_property = null ;
6464
6565/** @type {Array<() => void> } */
6666let current_queued_tasks = [ ] ;
@@ -341,7 +341,7 @@ function execute_signal_fn(signal) {
341341 const previous_skip_consumer = current_skip_consumer ;
342342 const is_render_effect = ( flags & RENDER_EFFECT ) !== 0 ;
343343 const previous_untracking = current_untracking ;
344- const previous_path = current_path ;
344+ const previous_derived_proxy_property = current_derived_proxy_property ;
345345 current_dependencies = /** @type {null | import('./types.js').Signal[] } */ ( null ) ;
346346 current_dependencies_index = 0 ;
347347 current_untracked_writes = null ;
@@ -350,7 +350,7 @@ function execute_signal_fn(signal) {
350350 current_component_context = signal . x ;
351351 current_skip_consumer = ! is_flushing_effect && ( flags & UNOWNED ) !== 0 ;
352352 current_untracking = false ;
353- current_path = null ;
353+ current_derived_proxy_property = null ;
354354
355355 // Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point
356356 if ( is_render_effect && current_component_context ?. u != null ) {
@@ -371,8 +371,8 @@ function execute_signal_fn(signal) {
371371 } else {
372372 res = /** @type {() => V } */ ( init ) ( ) ;
373373 }
374- if ( current_path !== null ) {
375- push_derived_path ( ) ;
374+ if ( current_derived_proxy_property !== null ) {
375+ capture_fine_grain_derived_property ( ) ;
376376 }
377377 let dependencies = /** @type {import('./types.js').Signal<unknown>[] } **/ ( signal . d ) ;
378378 if ( current_dependencies !== null ) {
@@ -444,7 +444,7 @@ function execute_signal_fn(signal) {
444444 current_component_context = previous_component_context ;
445445 current_skip_consumer = previous_skip_consumer ;
446446 current_untracking = previous_untracking ;
447- current_path = previous_path ;
447+ current_derived_proxy_property = previous_derived_proxy_property ;
448448 }
449449}
450450
@@ -536,7 +536,7 @@ export function execute_effect(signal) {
536536 if ( ( signal . f & DESTROYED ) !== 0 ) {
537537 return ;
538538 }
539- const teardown = signal . v ;
539+ const teardown = /** @type { null | (() => void) } */ ( signal . v ) ;
540540 const previous_effect = current_effect ;
541541 current_effect = signal ;
542542
@@ -830,16 +830,30 @@ export async function tick() {
830830 * @returns {void }
831831 */
832832function update_derived ( signal , force_schedule ) {
833+ let derived_value =
834+ /** @type {import('./types.js').DerivedSignalValue<V> | typeof UNINITIALIZED } */ ( signal . v ) ;
835+ if ( derived_value === UNINITIALIZED ) {
836+ signal . v = derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( {
837+ p : null ,
838+ v : UNINITIALIZED ,
839+ o : null
840+ } ) ;
841+ }
833842 const previous_updating_derived = updating_derived ;
834843 updating_derived = true ;
844+ if ( derived_value . p !== null ) {
845+ derived_value . p = null ;
846+ derived_value . o ?. clear ( ) ;
847+ }
835848 destroy_references ( signal ) ;
836849 const value = execute_signal_fn ( signal ) ;
837850 updating_derived = previous_updating_derived ;
838851 const status = current_skip_consumer || ( signal . f & UNOWNED ) !== 0 ? DIRTY : CLEAN ;
839852 set_signal_status ( signal , status ) ;
840853 const equals = /** @type {import('./types.js').EqualsFunctions } */ ( signal . e ) ;
841- if ( ! equals ( value , signal . v ) ) {
842- signal . v = value ;
854+
855+ if ( ! equals ( value , derived_value . v ) ) {
856+ derived_value . v = value ;
843857 mark_signal_consumers ( signal , DIRTY , force_schedule ) ;
844858
845859 // @ts -expect-error
@@ -939,36 +953,49 @@ export function unsubscribe_on_destroy(stores) {
939953 } ) ;
940954}
941955
942- function push_derived_path ( ) {
943- const path =
944- /** @type {{t: import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } } */ (
945- current_path
956+ /**
957+ * If the `current_derived_proxy_property` is not `null` then that means we should look at the current
958+ * property on the object and see if actually need original derived object dependency. For example,
959+ * if you had this:
960+ *
961+ * a.b.c
962+ *
963+ * Under-the-hood, `a` might be a derived signal, so we'd call get() on it. Resulting in `a` being a dependency
964+ * for the currently active effect. The accessors to `b` and `c` would result in the `current_derived_proxy_property`
965+ * changing to include ['b', 'c'] in the `current_derived_proxy_property.p` paths property. We can then use that to
966+ * determine a new derived temporary signal that encapsulates a.b.c. This temporarly signal then becomes the dependency
967+ * and we no longer need to the original depdency to `a` for the current effect. Thus making
968+ */
969+ function capture_fine_grain_derived_property ( ) {
970+ const derived_property =
971+ /** @type {{ s: import('./types.js').ComputationSignal, p: Array<string | symbol> } } */ (
972+ current_derived_proxy_property
946973 ) ;
947- if ( is_last_current_dependency ( path . r ) ) {
974+ if ( is_last_current_dependency ( derived_property . s ) ) {
948975 if ( current_dependencies === null ) {
949976 current_dependencies_index -- ;
950977 } else {
951978 current_dependencies . pop ( ) ;
952979 }
953980 }
954981 const derived_prop = derived ( ( ) => {
955- let value = /** @type {any } */ ( get ( path . t ) ) ;
956- const property_path = path . p ;
982+ let value = /** @type {any } */ ( get ( derived_property . s , true ) ) ;
983+ const property_path = derived_property . p ;
957984 for ( let i = 0 ; i < property_path . length ; i ++ ) {
958985 value = value ?. [ property_path [ i ] ] ;
959986 }
960987 return value ;
961988 } ) ;
962- current_path = null ;
963- get ( derived_prop ) ;
989+ current_derived_proxy_property = null ;
990+ get ( derived_prop , true ) ;
964991}
965992
966993/**
967994 * @template V
968995 * @param {import('./types.js').Signal<V> } signal
969996 * @returns {V }
970997 */
971- export function get ( signal ) {
998+ export function get ( signal , skip_derived_proxy = false ) {
972999 // @ts -expect-error
9731000 if ( DEV && signal . inspect && inspect_fn ) {
9741001 /** @type {import('./types.js').SignalDebug } */ ( signal ) . inspect . add ( inspect_fn ) ;
@@ -977,12 +1004,16 @@ export function get(signal) {
9771004 }
9781005
9791006 const flags = signal . f ;
1007+ const is_derived = ( flags & DERIVED ) !== 0 ;
1008+ let value = signal . v ;
9801009 if ( ( flags & DESTROYED ) !== 0 ) {
981- return signal . v ;
1010+ return /** @type {V } */ (
1011+ is_derived ? /** @type {import('./types.js').DerivedSignalValue<V> } */ ( value ) . v : value
1012+ ) ;
9821013 }
9831014
984- if ( current_path !== null ) {
985- push_derived_path ( ) ;
1015+ if ( current_derived_proxy_property !== null ) {
1016+ capture_fine_grain_derived_property ( ) ;
9861017 }
9871018
9881019 if ( is_signals_recorded ) {
@@ -1022,18 +1053,47 @@ export function get(signal) {
10221053 }
10231054 }
10241055
1025- if ( ( flags & DERIVED ) !== 0 && is_signal_dirty ( signal ) ) {
1026- if ( DEV ) {
1027- // we want to avoid tracking indirect dependencies
1028- const previous_inspect_fn = inspect_fn ;
1029- inspect_fn = null ;
1030- update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1031- inspect_fn = previous_inspect_fn ;
1032- } else {
1033- update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1056+ if ( is_derived ) {
1057+ if ( is_signal_dirty ( signal ) ) {
1058+ if ( DEV ) {
1059+ // we want to avoid tracking indirect dependencies
1060+ const previous_inspect_fn = inspect_fn ;
1061+ inspect_fn = null ;
1062+ update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1063+ inspect_fn = previous_inspect_fn ;
1064+ } else {
1065+ update_derived ( /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) , false ) ;
1066+ }
1067+ }
1068+ const derived_signal_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ (
1069+ signal . v
1070+ ) ;
1071+ const value = derived_signal_value . v ;
1072+ // If we are working with a derived that might be an object or array, then we might also want to
1073+ // apply the fine-grain derived property heuristic to them. However, we only need this heuristic in some cases:
1074+ // - inside a user effect ($effect or $effect.pre)
1075+ // - inside another derived ($derived)
1076+ // Else we don't need to bother doing this as render effects and the rest of the internal architecture applys
1077+ // diffing which is more optimal than creating many derived signals. However, we can't do diffing inside user
1078+ // effects (far too many complications with cleanup functions etc).
1079+ if (
1080+ ! skip_derived_proxy &&
1081+ is_runes ( signal . x ) &&
1082+ effect_active_and_not_render_effect ( ) &&
1083+ should_proxy_derived_value ( value )
1084+ ) {
1085+ let proxy = derived_signal_value . p ;
1086+ if ( proxy === null ) {
1087+ proxy = derived_signal_value . p = create_derived_proxy (
1088+ /** @type {import('./types.js').ComputationSignal<V> } **/ ( signal ) ,
1089+ value
1090+ ) ;
1091+ }
1092+ return proxy ;
10341093 }
1094+ return value ;
10351095 }
1036- return signal . v ;
1096+ return /** @type { V } */ ( signal . v ) ;
10371097}
10381098
10391099/**
@@ -1366,30 +1426,36 @@ function is_last_current_dependency(signal) {
13661426
13671427/**
13681428 * @template V
1369- * @param {() => any } init
1370- * @returns {import('./types.js').ComputationSignal<V> }
1429+ * @param {import("./types.js").ComputationSignal<V> } signal
1430+ * @param {V } value
1431+ * @param {ProxyHandler<any> } handler
1432+ * @param {(string | symbol)[] } path
1433+ * @returns {V }
13711434 */
1372- /*#__NO_SIDE_EFFECTS__*/
1373- export function derived_proxy ( init ) {
1374- const derived_object = derived ( init ) ;
1375- const proxied_objects = new Map ( ) ;
1376-
1377- /**
1378- * @param {V } value
1379- * @param {(string | symbol)[] } path
1380- * @returns {V }
1381- */
1382- function proxify_object ( value , path ) {
1383- const keys = new Set ( Reflect . ownKeys ( /** @type {object } */ ( value ) ) ) ;
1384- const proxy = new Proxy ( value , handler ) ;
1385- proxied_objects . set ( value , {
1386- x : proxy ,
1387- k : keys ,
1388- p : path
1389- } ) ;
1390- return proxy ;
1391- }
1435+ function proxify_object ( signal , value , handler , path ) {
1436+ const keys = new Set ( Reflect . ownKeys ( /** @type {object } */ ( value ) ) ) ;
1437+ const proxy = new Proxy ( value , handler ) ;
1438+ const derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( signal . v ) ;
1439+ let proxied_objects = derived_value . o ;
1440+ if ( proxied_objects === null ) {
1441+ derived_value . o = proxied_objects = new Map ( ) ;
1442+ }
1443+ proxied_objects . set ( value , {
1444+ x : proxy ,
1445+ k : keys ,
1446+ p : path
1447+ } ) ;
1448+ return proxy ;
1449+ }
13921450
1451+ /**
1452+ * @template V
1453+ * @param {import("./types.js").ComputationSignal<V> } signal
1454+ * @param {V } derived_value
1455+ * @returns {V }
1456+ */
1457+ /*#__NO_SIDE_EFFECTS__*/
1458+ function create_derived_proxy ( signal , derived_value ) {
13931459 const handler = {
13941460 /**
13951461 * @param {any } target
@@ -1398,12 +1464,14 @@ export function derived_proxy(init) {
13981464 */
13991465 get ( target , prop , receiver ) {
14001466 const value = Reflect . get ( target , prop , receiver ) ;
1467+ const derived_value = /** @type {import('./types.js').DerivedSignalValue<V> } */ ( signal . v ) ;
1468+ const proxied_objects = /** @type {any } */ ( derived_value . o ) ;
14011469 const { k : keys , p : path } = proxied_objects . get ( target ) ;
14021470
14031471 if (
1404- ( effect_active_and_not_render_effect ( ) || updating_derived ) &&
1472+ effect_active_and_not_render_effect ( ) &&
14051473 keys . has ( prop ) &&
1406- is_last_current_dependency ( proxied_derived )
1474+ is_last_current_dependency ( signal )
14071475 ) {
14081476 const type = typeof value ;
14091477 let new_path ;
@@ -1420,10 +1488,10 @@ export function derived_proxy(init) {
14201488 STATE_SYMBOL in value
14211489 ) {
14221490 new_path = [ ...path , prop ] ;
1423- if ( current_path !== null ) {
1424- push_derived_path ( ) ;
1491+ if ( current_derived_proxy_property !== null ) {
1492+ capture_fine_grain_derived_property ( ) ;
14251493 } else {
1426- current_path = { t : derived_object , p : new_path , r : proxied_derived } ;
1494+ current_derived_proxy_property = { s : signal , p : new_path } ;
14271495 }
14281496 }
14291497 if ( should_proxy_derived_value ( value ) ) {
@@ -1434,29 +1502,14 @@ export function derived_proxy(init) {
14341502 if ( ! new_path ) {
14351503 new_path = [ ...path , prop ] ;
14361504 }
1437- return proxify_object ( value , new_path ) ;
1505+ return proxify_object ( signal , value , handler , new_path ) ;
14381506 }
14391507 }
14401508 return value ;
14411509 }
14421510 } ;
14431511
1444- const proxied_derived = derived ( ( ) => {
1445- const value = get ( derived_object ) ;
1446- if ( should_proxy_derived_value ( value ) ) {
1447- return proxify_object ( value , [ ] ) ;
1448- } else if ( proxied_objects . size > 0 ) {
1449- proxied_objects . clear ( ) ;
1450- }
1451- return value ;
1452- } ) ;
1453-
1454- // Cleanup when the derived is destroyed
1455- proxied_derived . y = ( ) => {
1456- proxied_objects . clear ( ) ;
1457- } ;
1458-
1459- return proxied_derived ;
1512+ return proxify_object ( signal , derived_value , handler , [ ] ) ;
14601513}
14611514
14621515/**
0 commit comments