@@ -59,6 +59,9 @@ let current_queued_pre_and_render_effects = [];
59
59
/** @type {import('./types.js').EffectSignal[] } */
60
60
let current_queued_effects = [ ] ;
61
61
62
+ /** @type {{t: import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } | null } */
63
+ let current_path = null ;
64
+
62
65
/** @type {Array<() => void> } */
63
66
let current_queued_tasks = [ ] ;
64
67
/** @type {Array<() => void> } */
@@ -79,6 +82,7 @@ let current_dependencies_index = 0;
79
82
let current_untracked_writes = null ;
80
83
/** @type {null | import('./types.js').SignalDebug } */
81
84
let last_inspected_signal = null ;
85
+
82
86
/** If `true`, `get`ting the signal should not register it as a dependency */
83
87
export let current_untracking = false ;
84
88
/** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */
@@ -327,23 +331,26 @@ function is_signal_dirty(signal) {
327
331
*/
328
332
function execute_signal_fn ( signal ) {
329
333
const init = signal . i ;
334
+ const flags = signal . f ;
330
335
const previous_dependencies = current_dependencies ;
331
336
const previous_dependencies_index = current_dependencies_index ;
332
337
const previous_untracked_writes = current_untracked_writes ;
333
338
const previous_consumer = current_consumer ;
334
339
const previous_block = current_block ;
335
340
const previous_component_context = current_component_context ;
336
341
const previous_skip_consumer = current_skip_consumer ;
337
- const is_render_effect = ( signal . f & RENDER_EFFECT ) !== 0 ;
342
+ const is_render_effect = ( flags & RENDER_EFFECT ) !== 0 ;
338
343
const previous_untracking = current_untracking ;
344
+ const previous_path = current_path ;
339
345
current_dependencies = /** @type {null | import('./types.js').Signal[] } */ ( null ) ;
340
346
current_dependencies_index = 0 ;
341
347
current_untracked_writes = null ;
342
348
current_consumer = signal ;
343
349
current_block = signal . b ;
344
350
current_component_context = signal . x ;
345
- current_skip_consumer = ! is_flushing_effect && ( signal . f & UNOWNED ) !== 0 ;
351
+ current_skip_consumer = ! is_flushing_effect && ( flags & UNOWNED ) !== 0 ;
346
352
current_untracking = false ;
353
+ current_path = null ;
347
354
348
355
// Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point
349
356
if ( is_render_effect && current_component_context ?. u != null ) {
@@ -364,6 +371,9 @@ function execute_signal_fn(signal) {
364
371
} else {
365
372
res = /** @type {() => V } */ ( init ) ( ) ;
366
373
}
374
+ if ( current_path !== null ) {
375
+ push_derived_path ( ) ;
376
+ }
367
377
let dependencies = /** @type {import('./types.js').Signal<unknown>[] } **/ ( signal . d ) ;
368
378
if ( current_dependencies !== null ) {
369
379
let i ;
@@ -412,6 +422,10 @@ function execute_signal_fn(signal) {
412
422
if ( consumers === null ) {
413
423
dependency . c = [ signal ] ;
414
424
} else if ( consumers [ consumers . length - 1 ] !== signal ) {
425
+ // TODO: should this be:
426
+ //
427
+ // } else if (!consumers.includes(signal)) {
428
+ //
415
429
consumers . push ( signal ) ;
416
430
}
417
431
}
@@ -430,6 +444,7 @@ function execute_signal_fn(signal) {
430
444
current_component_context = previous_component_context ;
431
445
current_skip_consumer = previous_skip_consumer ;
432
446
current_untracking = previous_untracking ;
447
+ current_path = previous_path ;
433
448
}
434
449
}
435
450
@@ -495,13 +510,7 @@ function destroy_references(signal) {
495
510
let i ;
496
511
for ( i = 0 ; i < references . length ; i ++ ) {
497
512
const reference = references [ i ] ;
498
- if ( ( reference . f & IS_EFFECT ) !== 0 ) {
499
- destroy_signal ( reference ) ;
500
- } else {
501
- destroy_references ( reference ) ;
502
- remove_consumers ( reference , 0 ) ;
503
- reference . d = null ;
504
- }
513
+ destroy_signal ( reference ) ;
505
514
}
506
515
}
507
516
}
@@ -931,6 +940,30 @@ export function unsubscribe_on_destroy(stores) {
931
940
} ) ;
932
941
}
933
942
943
+ function push_derived_path ( ) {
944
+ const path =
945
+ /** @type {{t: import('./types.js').ComputationSignal, p: Array<string | symbol>, r: import('./types.js').ComputationSignal } } */ (
946
+ current_path
947
+ ) ;
948
+ if ( is_last_current_dependency ( path . r ) ) {
949
+ if ( current_dependencies === null ) {
950
+ current_dependencies_index -- ;
951
+ } else {
952
+ current_dependencies . pop ( ) ;
953
+ }
954
+ }
955
+ const derived_prop = derived ( ( ) => {
956
+ let value = /** @type {any } */ ( get ( path . t ) ) ;
957
+ const property_path = path . p ;
958
+ for ( let i = 0 ; i < property_path . length ; i ++ ) {
959
+ value = value ?. [ property_path [ i ] ] ;
960
+ }
961
+ return value ;
962
+ } ) ;
963
+ current_path = null ;
964
+ get ( derived_prop ) ;
965
+ }
966
+
934
967
/**
935
968
* @template V
936
969
* @param {import('./types.js').Signal<V> } signal
@@ -949,6 +982,10 @@ export function get(signal) {
949
982
return signal . v ;
950
983
}
951
984
985
+ if ( current_path !== null ) {
986
+ push_derived_path ( ) ;
987
+ }
988
+
952
989
if ( is_signals_recorded ) {
953
990
captured_signals . add ( signal ) ;
954
991
}
@@ -971,7 +1008,7 @@ export function get(signal) {
971
1008
) {
972
1009
if ( current_dependencies === null ) {
973
1010
current_dependencies = [ signal ] ;
974
- } else if ( signal !== current_dependencies [ current_dependencies . length - 1 ] ) {
1011
+ } else {
975
1012
current_dependencies . push ( signal ) ;
976
1013
}
977
1014
}
@@ -1268,16 +1305,7 @@ export function destroy_signal(signal) {
1268
1305
const flags = signal . f ;
1269
1306
destroy_references ( signal ) ;
1270
1307
remove_consumers ( signal , 0 ) ;
1271
- signal . i =
1272
- signal . r =
1273
- signal . y =
1274
- signal . x =
1275
- signal . b =
1276
- // @ts -expect-error - this is fine, since we're assigning to null to clear out a destroyed signal
1277
- signal . v =
1278
- signal . d =
1279
- signal . c =
1280
- null ;
1308
+ signal . i = signal . r = signal . y = signal . x = signal . b = signal . d = signal . c = null ;
1281
1309
set_signal_status ( signal , DESTROYED ) ;
1282
1310
if ( destroy !== null ) {
1283
1311
if ( is_array ( destroy ) ) {
@@ -1312,6 +1340,126 @@ export function derived(init) {
1312
1340
return signal ;
1313
1341
}
1314
1342
1343
+ /**
1344
+ * @param {any } value
1345
+ */
1346
+ function should_proxy_derived_value ( value ) {
1347
+ let prototype ;
1348
+ return (
1349
+ ( typeof value === 'object' &&
1350
+ value !== null &&
1351
+ ( prototype = get_prototype_of ( value ) ) === object_prototype ) ||
1352
+ prototype === array_prototype
1353
+ ) ;
1354
+ }
1355
+
1356
+ /**
1357
+ * @param {import("./types.js").SourceSignal<unknown> } signal
1358
+ */
1359
+ function is_last_current_dependency ( signal ) {
1360
+ if ( current_dependencies !== null ) {
1361
+ return current_dependencies [ current_dependencies . length - 1 ] === signal ;
1362
+ } else if ( current_consumer !== null && current_dependencies_index > 0 ) {
1363
+ return current_consumer . d ?. [ current_dependencies_index - 1 ] === signal ;
1364
+ }
1365
+ return false ;
1366
+ }
1367
+
1368
+ /**
1369
+ * @template V
1370
+ * @param {() => any } init
1371
+ * @returns {import('./types.js').ComputationSignal<V> }
1372
+ */
1373
+ /*#__NO_SIDE_EFFECTS__*/
1374
+ export function derived_proxy ( init ) {
1375
+ const derived_object = derived ( init ) ;
1376
+ const proxied_objects = new Map ( ) ;
1377
+
1378
+ /**
1379
+ * @param {V } value
1380
+ * @param {(string | symbol)[] } path
1381
+ * @returns {V }
1382
+ */
1383
+ function proxify_object ( value , path ) {
1384
+ const keys = new Set ( Reflect . ownKeys ( /** @type {object } */ ( value ) ) ) ;
1385
+ const proxy = new Proxy ( value , handler ) ;
1386
+ proxied_objects . set ( value , {
1387
+ x : proxy ,
1388
+ k : keys ,
1389
+ p : path
1390
+ } ) ;
1391
+ return proxy ;
1392
+ }
1393
+
1394
+ const handler = {
1395
+ /**
1396
+ * @param {any } target
1397
+ * @param {string | symbol } prop
1398
+ * @param {any } receiver
1399
+ */
1400
+ get ( target , prop , receiver ) {
1401
+ const value = Reflect . get ( target , prop , receiver ) ;
1402
+ const { k : keys , p : path } = proxied_objects . get ( target ) ;
1403
+
1404
+ if (
1405
+ ( effect_active_and_not_render_effect ( ) || updating_derived ) &&
1406
+ keys . has ( prop ) &&
1407
+ is_last_current_dependency ( proxied_derived )
1408
+ ) {
1409
+ const type = typeof value ;
1410
+ let new_path ;
1411
+ // We only track paths for primitives or state objects, to avoid tracking objects
1412
+ // that likely change all the time.
1413
+ if (
1414
+ value === void 0 ||
1415
+ type === 'string' ||
1416
+ type === 'number' ||
1417
+ type === 'boolean' ||
1418
+ type === 'symbol' ||
1419
+ type === 'bigint' ||
1420
+ type === null ||
1421
+ STATE_SYMBOL in value
1422
+ ) {
1423
+ new_path = [ ...path , prop ] ;
1424
+ if ( current_path !== null ) {
1425
+ push_derived_path ( ) ;
1426
+ } else {
1427
+ current_path = { t : derived_object , p : new_path , r : proxied_derived } ;
1428
+ }
1429
+ }
1430
+ if ( should_proxy_derived_value ( value ) ) {
1431
+ const possible_proxy = proxied_objects . get ( value ) ;
1432
+ if ( possible_proxy !== undefined ) {
1433
+ return possible_proxy . x ;
1434
+ }
1435
+ if ( ! new_path ) {
1436
+ new_path = [ ...path , prop ] ;
1437
+ }
1438
+ return proxify_object ( value , new_path ) ;
1439
+ }
1440
+ }
1441
+ return value ;
1442
+ }
1443
+ } ;
1444
+
1445
+ const proxied_derived = derived ( ( ) => {
1446
+ const value = get ( derived_object ) ;
1447
+ if ( should_proxy_derived_value ( value ) ) {
1448
+ return proxify_object ( value , [ ] ) ;
1449
+ } else if ( proxied_objects . size > 0 ) {
1450
+ proxied_objects . clear ( ) ;
1451
+ }
1452
+ return value ;
1453
+ } ) ;
1454
+
1455
+ // Cleanup when the derived is destroyed
1456
+ proxied_derived . y = ( ) => {
1457
+ proxied_objects . clear ( ) ;
1458
+ } ;
1459
+
1460
+ return proxied_derived ;
1461
+ }
1462
+
1315
1463
/**
1316
1464
* @template V
1317
1465
* @param {() => V } init
@@ -1397,6 +1545,13 @@ export function effect_active() {
1397
1545
return current_effect ? ( current_effect . f & MANAGED ) === 0 : false ;
1398
1546
}
1399
1547
1548
+ /**
1549
+ * @returns {boolean }
1550
+ */
1551
+ function effect_active_and_not_render_effect ( ) {
1552
+ return current_effect ? ( current_effect . f & ( MANAGED | RENDER_EFFECT ) ) === 0 : false ;
1553
+ }
1554
+
1400
1555
/**
1401
1556
* @param {() => void | (() => void) } init
1402
1557
* @returns {import('./types.js').EffectSignal }
0 commit comments