@ -3,16 +3,14 @@ import { DEV } from 'esm-env';
import { get , current _component _context , untrack , current _effect } from './runtime.js' ;
import {
array _prototype ,
define _property ,
get _descriptor ,
get _prototype _of ,
is _array ,
is _frozen ,
object _prototype
} from '../shared/utils.js' ;
import { check _ownership , widen _ownership } from './dev/ownership.js' ;
import { source , set } from './reactivity/sources.js' ;
import { STATE _SYMBOL } from './constants.js' ;
import { STATE _SYMBOL , STATE _SYMBOL _METADATA } from './constants.js' ;
import { UNINITIALIZED } from '../../constants.js' ;
import * as e from './errors.js' ;
@ -24,261 +22,262 @@ import * as e from './errors.js';
* @ returns { ProxyStateObject < T > | T }
* /
export function proxy ( value , parent = null , prev ) {
if ( typeof value === 'object' && value != null && ! is _frozen ( value ) ) {
// If we have an existing proxy, return it...
if ( STATE _SYMBOL in value ) {
const metadata = /** @type {ProxyMetadata<T>} */ ( value [ STATE _SYMBOL ] ) ;
// ...unless the proxy belonged to a different object, because
// someone copied the state symbol using `Reflect.ownKeys(...)`
if ( metadata . t === value || metadata . p === value ) {
if ( DEV ) {
// Since original parent relationship gets lost, we need to copy over ancestor owners
// into current metadata. The object might still exist on both, so we need to widen it.
widen _ownership ( metadata , metadata ) ;
metadata . parent = parent ;
}
// if non-proxyable, or is already a proxy, return `value`
if ( typeof value !== 'object' || value === null || STATE _SYMBOL in value ) {
return value ;
}
return metadata . p ;
}
const prototype = get _prototype _of ( value ) ;
if ( prototype !== object _prototype && prototype !== array _prototype ) {
return value ;
}
var sources = new Map ( ) ;
var is _proxied _array = is _array ( value ) ;
var version = source ( 0 ) ;
/** @type {ProxyMetadata} */
var metadata ;
if ( DEV ) {
metadata = {
parent ,
owners : null
} ;
if ( prev ) {
// Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context.
// If no previous proxy exists we play it safe and assume ownerless state
// @ts-expect-error
const prev _owners = prev . v ? . [ STATE _SYMBOL _METADATA ] ? . owners ;
metadata . owners = prev _owners ? new Set ( prev _owners ) : null ;
} else {
metadata . owners =
parent === null
? current _component _context !== null
? new Set ( [ current _component _context . function ] )
: null
: new Set ( ) ;
}
}
const prototype = get _prototype _of ( value ) ;
if ( prototype === object _prototype || prototype === array _prototype ) {
const proxy = new Proxy ( value , state _proxy _handler ) ;
define _property ( value , STATE _SYMBOL , {
value : /** @type {ProxyMetadata} */ ( {
s : new Map ( ) ,
v : source ( 0 ) ,
a : is _array ( value ) ,
p : proxy ,
t : value
} ) ,
writable : true ,
enumerable : false
} ) ;
return new Proxy ( /** @type {any} */ ( value ) , {
defineProperty ( _ , prop , descriptor ) {
if (
! ( 'value' in descriptor ) ||
descriptor . configurable === false ||
descriptor . enumerable === false ||
descriptor . writable === false
) {
// we disallow non-basic descriptors, because unless they are applied to the
// target object — which we avoid, so that state can be forked — we will run
// afoul of the various invariants
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor#invariants
e . state _descriptors _fixed ( ) ;
}
if ( DEV ) {
// @ts-expect-error
value [ STATE _SYMBOL ] . parent = parent ;
if ( prev ) {
// Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context.
// If no previous proxy exists we play it safe and assume ownerless state
// @ts-expect-error
const prev _owners = prev ? . v ? . [ STATE _SYMBOL ] ? . owners ;
// @ts-expect-error
value [ STATE _SYMBOL ] . owners = prev _owners ? new Set ( prev _owners ) : null ;
} else {
// @ts-expect-error
value [ STATE _SYMBOL ] . owners =
parent === null
? current _component _context !== null
? new Set ( [ current _component _context . function ] )
: null
: new Set ( ) ;
}
var s = sources . get ( prop ) ;
if ( s === undefined ) {
s = source ( descriptor . value ) ;
sources . set ( prop , s ) ;
} else {
set ( s , proxy ( descriptor . value , metadata ) ) ;
}
return proxy ;
}
}
return true ;
} ,
return value ;
}
deleteProperty ( target , prop ) {
var s = sources . get ( prop ) ;
var exists = s !== undefined ? s . v !== UNINITIALIZED : prop in target ;
/ * *
* @ param { Source < number > } signal
* @ param { 1 | - 1 } [ d ]
* /
function update _version ( signal , d = 1 ) {
set ( signal , signal . v + d ) ;
}
if ( s !== undefined ) {
set ( s , UNINITIALIZED ) ;
}
/** @type {ProxyHandler<ProxyStateObject<any>>} */
const state _proxy _handler = {
defineProperty ( target , prop , descriptor ) {
if ( descriptor . value ) {
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
if ( exists ) {
update _version ( version ) ;
}
const s = metadata . s . get ( prop ) ;
if ( s !== undefined ) set ( s , proxy ( descriptor . value , metadata ) ) ;
}
return exists ;
} ,
return Reflect . defineProperty ( target , prop , descriptor ) ;
} ,
deleteProperty ( target , prop ) {
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
const s = metadata . s . get ( prop ) ;
const is _array = metadata . a ;
const boolean = delete target [ prop ] ;
// If we have mutated an array directly, and the deletion
// was successful we will also need to update the length
// before updating the field or the version. This is to
// ensure any effects observing length can execute before
// effects that listen to the fields – otherwise they will
// operate an an index that no longer exists.
if ( is _array && boolean ) {
const ls = metadata . s . get ( 'length' ) ;
const length = target . length - 1 ;
if ( ls !== undefined && ls . v !== length ) {
set ( ls , length ) ;
get ( target , prop , receiver ) {
if ( DEV && prop === STATE _SYMBOL _METADATA ) {
return metadata ;
}
}
if ( s !== undefined ) set ( s , UNINITIALIZED ) ;
if ( boolean ) {
update _version ( metadata . v ) ;
}
if ( prop === STATE _SYMBOL ) {
return value ;
}
return boolean ;
} ,
var s = sources . get ( prop ) ;
var exists = prop in target ;
get ( target , prop , receiver ) {
if ( prop === STATE _SYMBOL ) {
return Reflect . get ( target , STATE _SYMBOL ) ;
}
// create a source, but only if it's an own property and not a prototype property
if ( s === undefined && ( ! exists || get _descriptor ( target , prop ) ? . writable ) ) {
s = source ( proxy ( exists ? target [ prop ] : UNINITIALIZED , metadata ) ) ;
sources . set ( prop , s ) ;
}
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
let s = metadata . s . get ( prop ) ;
if ( s !== undefined ) {
var v = get ( s ) ;
return v === UNINITIALIZED ? undefined : v ;
}
// create a source, but only if it's an own property and not a prototype property
if ( s === undefined && ( ! ( prop in target ) || get _descriptor ( target , prop ) ? . writable ) ) {
s = source ( proxy ( target [ prop ] , metadata ) ) ;
metadata . s . set ( prop , s ) ;
}
return Reflect . get ( target , prop , receiver ) ;
} ,
getOwnPropertyDescriptor ( target , prop ) {
var descriptor = Reflect . getOwnPropertyDescriptor ( target , prop ) ;
if ( descriptor && 'value' in descriptor ) {
var s = sources . get ( prop ) ;
if ( s ) descriptor . value = get ( s ) ;
} else if ( descriptor === undefined ) {
var source = sources . get ( prop ) ;
var value = source ? . v ;
if ( source !== undefined && value !== UNINITIALIZED ) {
return {
enumerable : true ,
configurable : true ,
value ,
writable : true
} ;
}
}
if ( s !== undefined ) {
const value = get ( s ) ;
return value === UNINITIALIZED ? undefined : value ;
}
return descriptor ;
} ,
return Reflect . get ( target , prop , receiver ) ;
} ,
has ( target , prop ) {
if ( DEV && prop === STATE _SYMBOL _METADATA ) {
return true ;
}
if ( prop === STATE _SYMBOL ) {
return true ;
}
getOwnPropertyDescriptor ( target , prop ) {
const descriptor = Reflect . getOwnPropertyDescriptor ( target , prop ) ;
if ( descriptor && 'value' in descriptor ) {
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
const s = metadata . s . get ( prop ) ;
var s = sources . get ( prop ) ;
var has = ( s !== undefined && s . v !== UNINITIALIZED ) || Reflect . has ( target , prop ) ;
if ( s ) {
descriptor . value = get ( s ) ;
if (
s !== undefined ||
( current _effect !== null && ( ! has || get _descriptor ( target , prop ) ? . writable ) )
) {
if ( s === undefined ) {
s = source ( has ? proxy ( target [ prop ] , metadata ) : UNINITIALIZED ) ;
sources . set ( prop , s ) ;
}
var value = get ( s ) ;
if ( value === UNINITIALIZED ) {
return false ;
}
}
}
return descriptor ;
} ,
return has ;
} ,
has ( target , prop ) {
if ( prop === STATE _SYMBOL ) {
return true ;
}
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
const has = Reflect . has ( target , prop ) ;
let s = metadata . s . get ( prop ) ;
if (
s !== undefined ||
( current _effect !== null && ( ! has || get _descriptor ( target , prop ) ? . writable ) )
) {
set ( target , prop , value , receiver ) {
var s = sources . get ( prop ) ;
var has = prop in target ;
// If we haven't yet created a source for this property, we need to ensure
// we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property.
if ( s === undefined ) {
s = source ( has ? proxy ( target [ prop ] , metadata ) : UNINITIALIZED ) ;
metadata . s . set ( prop , s ) ;
}
const value = get ( s ) ;
if ( value === UNINITIALIZED ) {
return false ;
if ( ! has || get _descriptor ( target , prop ) ? . writable ) {
s = source ( undefined ) ;
set ( s , proxy ( value , metadata ) ) ;
sources . set ( prop , s ) ;
}
} else {
has = s . v !== UNINITIALIZED ;
set ( s , proxy ( value , metadata ) ) ;
}
}
return has ;
} ,
set ( target , prop , value , receiver ) {
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
let s = metadata . s . get ( prop ) ;
// If we haven't yet created a source for this property, we need to ensure
// we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property.
if ( s === undefined ) {
// the read creates a signal
untrack ( ( ) => receiver [ prop ] ) ;
s = metadata . s . get ( prop ) ;
}
if ( s !== undefined ) {
set ( s , proxy ( value , metadata ) ) ;
}
const is _array = metadata . a ;
const not _has = ! ( prop in target ) ;
if ( DEV ) {
/** @type {ProxyMetadata | undefined} */
const prop _metadata = value ? . [ STATE _SYMBOL ] ;
if ( prop _metadata && prop _metadata ? . parent !== metadata ) {
widen _ownership ( metadata , prop _metadata ) ;
if ( DEV ) {
/** @type {ProxyMetadata | undefined} */
var prop _metadata = value ? . [ STATE _SYMBOL _METADATA ] ;
if ( prop _metadata && prop _metadata ? . parent !== metadata ) {
widen _ownership ( metadata , prop _metadata ) ;
}
check _ownership ( metadata ) ;
}
check _ownership ( metadata ) ;
}
// variable.length = value -> clear all signals with index >= value
if ( is _array && prop === 'length' ) {
for ( let i = value ; i < target . length ; i += 1 ) {
const s = metadata . s . get ( i + '' ) ;
if ( s !== undefined ) set ( s , UNINITIALIZED ) ;
// variable.length = value -> clear all signals with index >= value
if ( is _proxied _array && prop === 'length' ) {
for ( var i = value ; i < target . length ; i += 1 ) {
var other _s = sources . get ( i + '' ) ;
if ( other _s !== undefined ) set ( other _s , UNINITIALIZED ) ;
}
}
}
var descriptor = Reflect . getOwnPropertyDescriptor ( target , prop ) ;
var descriptor = Reflect . getOwnPropertyDescriptor ( target , prop ) ;
// Set the new value before updating any signals so that any listeners get the new value
if ( descriptor ? . set ) {
descriptor . set . call ( receiver , value ) ;
} else {
target [ prop ] = value ;
}
// Set the new value before updating any signals so that any listeners get the new value
if ( descriptor ? . set ) {
descriptor . set . call ( receiver , value ) ;
}
if ( not _has ) {
// If we have mutated an array directly, we might need to
// signal that length has also changed. Do it before updating metadata
// to ensure that iterating over the array as a result of a metadata update
// will not cause the length to be out of sync.
if ( is _array ) {
const ls = metadata . s . get ( 'length' ) ;
const length = target . length ;
if ( ls !== undefined && ls . v !== length ) {
set ( ls , length ) ;
if ( ! has ) {
// If we have mutated an array directly, we might need to
// signal that length has also changed. Do it before updating metadata
// to ensure that iterating over the array as a result of a metadata update
// will not cause the length to be out of sync.
if ( is _proxied _array && typeof prop === 'string' ) {
var ls = sources . get ( 'length' ) ;
if ( ls !== undefined ) {
var n = Number ( prop ) ;
if ( Number . isInteger ( n ) && n >= ls . v ) {
set ( ls , n + 1 ) ;
}
}
}
update _version ( version ) ;
}
update _version ( metadata . v ) ;
}
return true ;
} ,
return true ;
} ,
ownKeys ( target ) {
/** @type {ProxyMetadata} */
const metadata = target [ STATE _SYMBOL ] ;
ownKeys ( target ) {
get ( version ) ;
get ( metadata . v ) ;
return Reflect . ownKeys ( target ) ;
}
} ;
var own _keys = Reflect . ownKeys ( target ) . filter ( ( key ) => {
var source = sources . get ( key ) ;
return source === undefined || source . v !== UNINITIALIZED ;
} ) ;
for ( var [ key , source ] of sources ) {
if ( source . v !== UNINITIALIZED && ! ( key in target ) ) {
own _keys . push ( key ) ;
}
}
return own _keys ;
} ,
setPrototypeOf ( ) {
e . state _prototype _fixed ( ) ;
}
} ) ;
}
if ( DEV ) {
state _proxy _handler . setPrototypeOf = ( ) => {
e . state _prototype _fixed ( ) ;
} ;
/ * *
* @ param { Source < number > } signal
* @ param { 1 | - 1 } [ d ]
* /
function update _version ( signal , d = 1 ) {
set ( signal , signal . v + d ) ;
}
/ * *
@ -286,11 +285,9 @@ if (DEV) {
* /
export function get _proxied _value ( value ) {
if ( value !== null && typeof value === 'object' && STATE _SYMBOL in value ) {
var metadata = value [ STATE _SYMBOL ] ;
if ( metadata ) {
return metadata . p ;
}
return value [ STATE _SYMBOL ] ;
}
return value ;
}