@ -1,106 +1,62 @@
import { ReactiveFlags } from "./contents" ;
import { computed , signal , isSignal } from "./core" ;
/ * *
* Implementation overview ( supplementary to header docs ) :
*
* Data structures :
* - proxyToSignals : WeakMap < proxy , Map < prop , signalFn > > maps each proxied object to its per - key signal .
* - objToProxy : WeakMap < rawObject , proxy > ensures stable proxy instance per raw object .
* - arrayToArrayOfSignals : WeakMap < array , proxy > holds special ` $ ` array meta proxy ( index signals + length ) .
* - ignore : WeakSet marks objects already proxied or shallow - wrapped to avoid double proxying .
* - objToIterable : WeakMap < object , signal < number > > used to re - trigger enumeration dependent computed / effects .
* - proxyMeta : WeakMap < proxy , { parent , key , root } > chain info for patch path reconstruction .
*
* Key decisions :
* - Signals are created lazily : first read of value or its ` $ ` accessor .
* - Getter properties become computed signals ( readonly ) so derived values stay consistent .
* - Setting via ` $ prop ` enforces passing a signal ( allows external signal assignment path ) .
* - Deep patches reconstruct path on mutation ( O ( depth ) ) ; no bookkeeping per property upfront .
* - Arrays use ` $ ` ( returns array - of - signals proxy ) & ` $ length ` ( length signal ) while skipping function - valued entries .
*
* Performance characteristics :
* - Read of untouched property : ~ O ( 1 ) creating one signal + potential child proxy .
* - Mutation : signal update + optional patch path build ; batching coalesces multiple patches in same microtask .
* - Enumeration tracking : ownKeys / for . . in increments a dedicated counter signal ( objToIterable ) to invalidate dependents .
* /
/ * *
* deepSignal ( )
* === === === === === === === === === === === === === === === === === === === === === === ===
* Core idea : Wrap a plain object / array tree in a Proxy that lazily creates
* per - property signals on first access . Property access without a ` $ ` prefix
* returns the underlying value ( tracking the read ) . Access with ` $ ` ( e . g . obj . $foo )
* returns the signal function itself . Arrays have special ` $ ` for index signals and
* ` $ length ` for the length signal .
*
* Why function signals ? Native alien - signals v2 returns a function you call to read ,
* call with an argument to write . We keep that but provide ` .value ` in tagging layer .
*
* Getter logic summary :
* 1 . Determine if the caller wants a signal ( prefix ` $ ` or array meta ) vs value .
* 2 . If property is a getter on the original object , wrap it in a computed signal .
* 3 . Otherwise create ( once ) a writable signal seeded with ( possibly proxied ) child value .
* 4 . Return either the signal function or its invocation ( value ) depending on caller form .
*
* Setter logic summary :
* - Writes update the raw target via Reflect . set .
* - If the property signal already exists , update it ; otherwise create it .
* - Array length & object key enumeration signals are nudged ( length / ownKeys tracking ) .
* - A deep mutation patch is queued capturing : root id , type , path , new value .
*
* Patch batching :
* - Mutations push a patch into a per - microtask buffer ( pendingPatches ) .
* - A queued microtask flush delivers the accumulated array to each subscriber .
* - This enables consumers ( framework adapters ) to materialize minimal changes .
*
* Metadata chain ( proxyMeta ) : parent + key + root symbol ; used to reconstruct full path
* for patches without storing full paths on every node .
* /
/ * *
* Deep mutation patch system ( for Svelte rune integration / granular updates )
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
* This augmention adds an optional patch stream that reports every deep mutation
* ( set / delete ) with a property path relative to the root deepSignal ( ) object .
*
* Rationale : The core library already has per - property signals and is efficient
* for effect re - execution . However , consumers that want to mirror the entire
* nested structure into another reactive container ( e . g . Svelte $state ) gain
* from receiving a batch of fine - grained patches instead of re - cloning .
*
* Design :
* 1 . Each proxy created by deepSignal has lightweight metadata ( parent , key , root id ) .
* 2 . On each mutation ( set / delete ) we reconstruct the path by walking parents .
* 3 . Patches are batched in a microtask and delivered to subscribers .
* 4 . Zero cost for projects that never call subscribeDeepMutations ( ) : only minimal
* metadata storage and O ( depth ) path build per mutation .
* deepSignal : wrap an object / array / Set graph in lazy per - property signals plus an optional deep patch stream .
* - ` $ prop ` returns a signal ; plain prop returns its current value .
* - Getter props become computed signals .
* - Arrays expose ` $ ` ( index signals ) & ` $ length ` ; Sets emit structural entry patches with synthetic ids .
* - subscribeDeepMutations ( root , cb ) batches set / delete ops per microtask ( DeepPatch [ ] ) .
* - shallow ( obj ) skips deep proxying of a subtree .
* /
/ * *
* A granular description of a deep mutation originating from a { @link deepSignal } root .
* Patches are batched per microtask and delivered in order of occurrence .
*
* Invariants :
* - ` path ` is never empty ( the root object itself is not represented by a patch without a key )
* - For ` type === "delete" ` the ` value ` field is omitted
* - For ` type === "set" ` the ` value ` is the post ‑ mutation ( proxied if object / array / set ) value snapshot
* /
export interface DeepPatch {
/** A batched deep mutation (set/add/remove) from a deepSignal root. */
export type DeepPatch = {
/** Unique identifier for the deep signal root which produced this patch. */
root : symbol ;
/** Mutation kind applied at the resolved `path`. */
type : "set" | "delete" ;
/** Property path (array indices, object keys, synthetic Set entry ids) from the root to the mutated location. */
path : ( string | number ) [ ] ;
} & (
| DeepSetAddPatch
| DeepSetRemovePatch
| DeepObjectAddPatch
| DeepRemovePatch
| DeepLiteralAddPatch
) ;
export interface DeepSetAddPatch {
/** Mutation kind applied at the resolved `path`. */
op : "add" ;
type : "set" ;
/** New value for `set` mutations (omitted for `delete`). */
value? : any ;
value : ( number | string | boolean ) [ ] | { [ id : string ] : object } ;
}
export interface DeepSetRemovePatch {
/** Mutation kind applied at the resolved `path`. */
op : "remove" ;
type : "set" ;
/** The value to be removed from the set. Either a literal or the key (id) of an object. */
value : string | number | boolean ;
}
export interface DeepObjectAddPatch {
/** Mutation kind applied at the resolved `path`. */
op : "add" ;
type : "object" ;
}
/** Function signature for subscribers passed to {@link subscribeDeepMutations}. */
export interface DeepRemovePatch {
/** Mutation kind applied at the resolved `path`. */
op : "remove" ;
}
export interface DeepLiteralAddPatch {
/** Mutation kind applied at the resolved `path` */
op : "add" ;
/** The literal value to be added at the resolved `path` */
value : string | number | boolean ;
}
/** Callback signature for subscribeDeepMutations. */
export type DeepPatchSubscriber = ( patches : DeepPatch [ ] ) = > void ;
/ * *
* Lightweight metadata stored per proxy enabling reconstruction of a property ' s full path
* at mutation time without eager bookkeeping of every descendant .
* /
/** Minimal per-proxy metadata for path reconstruction. */
interface ProxyMeta {
/** Parent proxy in the object graph (undefined for root). */
parent? : object ;
@ -110,18 +66,15 @@ interface ProxyMeta {
root : symbol ;
}
/** Internal lookup from proxy -> {@link ProxyMeta}. */
// Proxy -> metadata
const proxyMeta = new WeakMap < object , ProxyMeta > ( ) ;
/** Global registry of batch mutation subscribers (filtered per root at delivery time). */
const mutationSubscribers = new Set < DeepPatchSubscriber > ( ) ;
let pendingPatches : DeepPatch [ ] | null = null ;
// Root symbol -> subscribers
const mutationSubscribers = new Map < symbol , Set < DeepPatchSubscriber > > ( ) ;
// Pending patches grouped per root (flushed once per microtask)
let pendingPatches : Map < symbol , DeepPatch [ ] > | null = null ;
let microtaskScheduled = false ;
/** Sentinal constant for root id retrieval (exported for external helpers). */
/ * *
* Sentinel symbol used internally / by helpers to retrieve a deepSignal root id .
* You normally obtain a concrete root id via { @link getDeepSignalRootId } on a proxy instance .
* /
/** Sentinel symbol; get concrete root id via getDeepSignalRootId(proxy). */
export const DEEP_SIGNAL_ROOT_ID = Symbol ( "alienDeepSignalRootId" ) ;
function buildPath (
@ -141,69 +94,75 @@ function buildPath(
}
function queuePatch ( patch : DeepPatch ) {
if ( ! pendingPatches ) pendingPatches = [ ] ;
pendingPatches . push ( patch ) ;
if ( ! pendingPatches ) pendingPatches = new Map ( ) ;
const root = patch . root ;
let list = pendingPatches . get ( root ) ;
if ( ! list ) {
list = [ ] ;
pendingPatches . set ( root , list ) ;
}
list . push ( patch ) ;
if ( ! microtaskScheduled ) {
microtaskScheduled = true ;
queueMicrotask ( ( ) = > {
microtaskScheduled = false ;
const batch = pendingPatches ;
const groups = pendingPatches ;
pendingPatches = null ;
if ( ! batch || batch . length === 0 ) return ;
mutationSubscribers . forEach ( ( sub ) = > sub ( batch ) ) ;
if ( ! groups ) return ;
for ( const [ rootId , patches ] of groups ) {
if ( ! patches . length ) continue ;
const subs = mutationSubscribers . get ( rootId ) ;
if ( subs ) subs . forEach ( ( cb ) = > cb ( patches ) ) ;
}
} ) ;
}
}
/ * *
* Register a mutation batch listener for all active deepSignal roots .
*
* Each microtask in which one or more deep mutations occur produces at most one callback
* invocation containing the ordered list of { @link DeepPatch } objects .
*
* @param sub Callback receiving a batch array ( never empty ) of deep patches .
* @returns An unsubscribe function that detaches the listener when invoked .
* /
export function subscribeDeepMutations ( sub : DeepPatchSubscriber ) : ( ) = > void {
mutationSubscribers . add ( sub ) ;
return ( ) = > mutationSubscribers . delete ( sub ) ;
/** Subscribe to microtask-batched deep patches for a root (returns unsubscribe). */
export function subscribeDeepMutations (
root : object | symbol ,
sub : DeepPatchSubscriber
) : ( ) = > void {
const rootId = typeof root === "symbol" ? root : getDeepSignalRootId ( root ) ;
if ( ! rootId )
throw new Error (
"subscribeDeepMutations() expects a deepSignal root proxy or root id symbol"
) ;
let set = mutationSubscribers . get ( rootId ) ;
if ( ! set ) {
set = new Set ( ) ;
mutationSubscribers . set ( rootId , set ) ;
}
set . add ( sub ) ;
return ( ) = > {
const bucket = mutationSubscribers . get ( rootId ) ;
if ( ! bucket ) return ;
bucket . delete ( sub ) ;
if ( bucket . size === 0 ) mutationSubscribers . delete ( rootId ) ;
} ;
}
/ * *
* Obtain the stable root id symbol for a given deepSignal proxy ( or any nested proxy ) .
* Returns ` undefined ` if the value is not a deepSignal - managed proxy .
*
* @example
* const state = deepSignal ( { a : { b : 1 } } ) ;
* const id1 = getDeepSignalRootId ( state ) ; // symbol
* const id2 = getDeepSignalRootId ( state . a ) ; // same symbol as id1
* getDeepSignalRootId ( { } ) // undefined
* /
/** Return the stable root symbol for any deepSignal proxy (undefined if not one). */
export function getDeepSignalRootId ( obj : any ) : symbol | undefined {
return proxyMeta . get ( obj ) ? . root ;
}
// Proxy -> Map of property name -> signal function
/** Proxy instance -> map of property name -> signal function (created lazil y). */
/** Proxy -> Map<propertyName, signalFn> (lazy). */
const proxyToSignals = new WeakMap ( ) ;
// Raw object/array/Set -> its stable proxy
// Raw object/array/Set -> stable proxy
const objToProxy = new WeakMap ( ) ;
// Raw array -> special `$` proxy giving index signals
// Raw array -> `$` meta proxy with index signals
const arrayToArrayOfSignals = new WeakMap ( ) ;
// Objects already proxied or intentionally shallow
// Objects already proxied or marked shallow
const ignore = new WeakSet ( ) ;
// Object -> signal counter used for key enumeration invalidation
// Object -> signal counter for enumeration invalidation
const objToIterable = new WeakMap ( ) ;
const rg = /^\$/ ;
const descriptor = Object . getOwnPropertyDescriptor ;
let peeking = false ;
// (Synthetic ID helper declarations were restored further below before usage.)
/ * *
* Deep array interface expressed as a type intersection to avoid structural extend
* incompatibilities with the native Array while still refining callback parameter
* types to their DeepSignal forms .
* /
// Deep array interface refining callback parameter types.
type DeepArray < T > = Array < T > & {
map : < U > (
callbackfn : (
@ -301,9 +260,8 @@ type DeepArray<T> = Array<T> & {
initialValue : U
) : U ;
} ;
// --- Synthetic ID helpers & ergonomics for Set entry patching (restored) ---
// Synthetic ids for Set entry objects (stable key for patches)
let __blankNodeCounter = 0 ;
/** User or auto-assigned synthetic id bookkeeping for Set entry objects. */
const setObjectIds = new WeakMap < object , string > ( ) ;
const assignBlankNodeId = ( obj : any ) = > {
if ( setObjectIds . has ( obj ) ) return setObjectIds . get ( obj ) ! ;
@ -311,7 +269,7 @@ const assignBlankNodeId = (obj: any) => {
setObjectIds . set ( obj , id ) ;
return id ;
} ;
/** Assign (or override) synthetic identifier for an object prior to Set.add(). */
/** Assign (or override) synthetic id before Set.add(). */
export function setSetEntrySyntheticId ( obj : object , id : string | number ) {
setObjectIds . set ( obj , String ( id ) ) ;
}
@ -332,9 +290,7 @@ const getSetEntryKey = (val: any): string | number => {
}
return val as any ;
} ;
/ * *
* Insert into a ( possibly proxied ) Set with a desired synthetic id ; returns proxied entry ( objects ) or primitive .
* /
/** Add entry with synthetic id; returns proxied object if applicable. */
export function addWithId < T extends object > (
set : Set < T > ,
entry : T ,
@ -349,24 +305,17 @@ export function addWithId(set: Set<any>, entry: any, id: string | number) {
return entry ;
}
/** Determine whether a given value is a deepSignal-managed proxy (any depth). */
/** Is value a deepSignal-managed proxy? */
export const isDeepSignal = ( source : any ) = > {
return proxyToSignals . has ( source ) ;
} ;
/** Predicate indicating whether a value was explicitly marked via {@link shallow}. */
/** Was value explicitly marked shallow? */
export const isShallow = ( source : any ) = > {
return ignore . has ( source ) ;
} ;
/ * *
* Create ( or retrieve ) a deep reactive proxy for the supplied plain object / array / Set .
* All nested objects / arrays / Sets are wrapped lazily on first access ; primitives are passed through .
*
* Root identity : multiple invocations with the same object return the same proxy ; each distinct root
* owns a unique symbol id available via { @link getDeepSignalRootId } and present on every emitted patch .
*
* /
/** Create (or reuse) a deep reactive proxy for an object / array / Set. */
export const deepSignal = < T extends object > ( obj : T ) : DeepSignal < T > = > {
if ( ! shouldProxy ( obj ) ) throw new Error ( "This object can't be observed." ) ;
if ( ! objToProxy . has ( obj ) ) {
@ -384,14 +333,7 @@ export const deepSignal = <T extends object>(obj: T): DeepSignal<T> => {
return objToProxy . get ( obj ) ;
} ;
/ * *
* Read a property on a deepSignal proxy without establishing reactive tracking .
* Equivalent conceptually to a non - tracking read / untracked ( ) pattern .
*
* @param obj deepSignal proxy object
* @param key property key to read
* @returns The raw ( possibly proxied ) property value without dependency collection .
* /
/** Read property without tracking (untracked read). */
export const peek = <
T extends DeepSignalObject < object > ,
K extends keyof RevertDeepSignalObject < T >
@ -408,13 +350,7 @@ export const peek = <
} ;
const shallowFlag = Symbol ( ReactiveFlags . IS_SHALLOW ) ;
/ * *
* Mark an object so that it will not be deeply proxied when used as a property value of
* another deepSignal tree . This is a shallow escape hatch for performance or semantic reasons .
*
* NOTE : The returned object itself is not reactive ; only top - level assignment of the reference
* produces patches when attached to a deepSignal structure .
* /
/** Mark object to skip deep proxying (only reference changes tracked). */
export function shallow < T extends object > ( obj : T ) : Shallow < T > {
ignore . add ( obj ) ;
return obj as Shallow < T > ;
@ -434,212 +370,262 @@ const createProxy = (
return proxy ;
} ;
const throwOnMutation = ( ) = > {
throw new Error (
"Don't mutate the signals directly (use the underlying property/value instead)."
) ;
} ;
/ * *
* Unified get trap factory .
* @param isArrayOfSignals indicates we are resolving properties on the special array ` $ ` proxy .
* /
const get =
( isArrayOfSignals : boolean ) = >
( target : object , fullKey : string , receiver : object ) : unknown = > {
if ( peeking ) return Reflect . get ( target , fullKey , receiver ) ;
let returnSignal = isArrayOfSignals || fullKey [ 0 ] === "$" ;
// Special handling for Set instances: treat as atomic & emit structural + per-entry patches
if ( target instanceof Set && typeof fullKey === "string" ) {
const raw = target as Set < any > ;
const key = fullKey ;
const meta = proxyMeta . get ( receiver ) ;
// Helper to proxy a single entry
const ensureEntryProxy = ( entry : any ) = > {
// Set-specific access & structural patch emission.
function getFromSet ( raw : Set < any > , key : string , receiver : object ) : any {
const meta = proxyMeta . get ( receiver ) ;
// Helper to proxy a single entry (object) & assign synthetic id if needed.
const ensureEntryProxy = ( entry : any ) = > {
if (
entry &&
typeof entry === "object" &&
shouldProxy ( entry ) &&
! objToProxy . has ( entry )
) {
const synthetic = getSetEntryKey ( entry ) ;
const childProxy = createProxy ( entry , objectHandlers , meta ! . root ) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = receiver ;
childMeta . key = synthetic ;
objToProxy . set ( entry , childProxy ) ;
return childProxy ;
}
if ( objToProxy . has ( entry ) ) return objToProxy . get ( entry ) ;
return entry ;
} ;
// Pre-pass to ensure any existing non-proxied object entries are proxied (enables deep patches after iteration)
if ( meta ) raw . forEach ( ensureEntryProxy ) ;
if ( key === "add" || key === "delete" || key === "clear" ) {
const fn : Function = ( raw as any ) [ key ] ;
return function ( this : any , . . . args : any [ ] ) {
const sizeBefore = raw . size ;
const result = fn . apply ( raw , args ) ;
if ( raw . size !== sizeBefore ) {
const metaNow = proxyMeta . get ( receiver ) ;
if (
entry &&
typeof entry === "object" &&
shouldProxy ( entry ) &&
! objToProxy . has ( entry )
metaNow &&
metaNow . parent !== undefined &&
metaNow . key !== undefined
) {
const synthetic = getSetEntryKey ( entry ) ;
const childProxy = createProxy ( entry , objectHandlers , meta ! . root ) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = receiver ;
childMeta . key = synthetic ;
objToProxy . set ( entry , childProxy ) ;
return childProxy ;
}
if ( objToProxy . has ( entry ) ) return objToProxy . get ( entry ) ;
return entry ;
} ;
// Pre-pass to ensure any existing non-proxied object entries are proxied
if ( meta ) raw . forEach ( ensureEntryProxy ) ;
if ( key === "add" || key === "delete" || key === "clear" ) {
const fn : Function = ( raw as any ) [ key ] ;
return function ( this : any , . . . args : any [ ] ) {
const sizeBefore = raw . size ;
const result = fn . apply ( raw , args ) ;
if ( raw . size !== sizeBefore ) {
const metaNow = proxyMeta . get ( receiver ) ;
const containerPath = buildPath ( metaNow . parent , metaNow . key ) ;
if ( key === "add" ) {
const entry = args [ 0 ] ;
let synthetic = getSetEntryKey ( entry ) ;
if ( entry && typeof entry === "object" ) {
for ( const existing of raw . values ( ) ) {
if ( existing === entry ) continue ;
if ( getSetEntryKey ( existing ) === synthetic ) {
synthetic = assignBlankNodeId ( entry ) ;
break ;
}
}
}
let entryVal = entry ;
if (
metaNow &&
metaNow . parent !== undefined &&
metaNow . key !== undefined
entryVal &&
typeof entryVal === "object" &&
shouldProxy ( entryVal ) &&
! objToProxy . has ( entryVal )
) {
const containerPath = buildPath ( metaNow . parent , metaNow . key ) ;
if ( key === "add" ) {
const entry = args [ 0 ] ;
let synthetic = getSetEntryKey ( entry ) ;
if ( entry && typeof entry === "object" ) {
for ( const existing of raw . values ( ) ) {
if ( existing === entry ) continue ;
if ( getSetEntryKey ( existing ) === synthetic ) {
synthetic = assignBlankNodeId ( entry ) ;
break ;
}
const childProxy = createProxy (
entryVal ,
objectHandlers ,
metaNow . root
) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = receiver ;
childMeta . key = synthetic ;
objToProxy . set ( entryVal , childProxy ) ;
entryVal = childProxy ;
}
// Set entry add: emit object vs literal variant.
if ( entryVal && typeof entryVal === "object" ) {
queuePatch ( {
root : metaNow.root ,
path : [ . . . containerPath , synthetic ] ,
op : "add" ,
type : "object" ,
} ) ;
} else {
queuePatch ( {
root : metaNow.root ,
path : [ . . . containerPath , synthetic ] ,
op : "add" ,
value : entryVal ,
} ) ;
}
} else if ( key === "delete" ) {
const entry = args [ 0 ] ;
const synthetic = getSetEntryKey ( entry ) ;
queuePatch ( {
root : metaNow.root ,
path : [ . . . containerPath , synthetic ] ,
op : "remove" ,
} ) ;
} else if ( key === "clear" ) {
// Structural clear: remove prior entry-level patches for this Set this tick.
if ( pendingPatches ) {
const group = pendingPatches . get ( metaNow . root ) ;
if ( group && group . length ) {
for ( let i = group . length - 1 ; i >= 0 ; i -- ) {
const p = group [ i ] ;
if (
p . path . length === containerPath . length + 1 &&
containerPath . every ( ( seg , idx ) = > p . path [ idx ] === seg )
) {
group . splice ( i , 1 ) ;
}
}
let entryVal = entry ;
if (
entryVal &&
typeof entryVal === "object" &&
shouldProxy ( entryVal ) &&
! objToProxy . has ( entryVal )
) {
const childProxy = createProxy (
entryVal ,
objectHandlers ,
metaNow . root
) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = receiver ;
childMeta . key = synthetic ;
objToProxy . set ( entryVal , childProxy ) ;
entryVal = childProxy ;
}
queuePatch ( {
root : metaNow.root ,
type : "set" ,
path : [ . . . containerPath , synthetic ] ,
value : entryVal ,
} ) ;
} else if ( key === "delete" ) {
const entry = args [ 0 ] ;
const synthetic = getSetEntryKey ( entry ) ;
queuePatch ( {
root : metaNow.root ,
type : "delete" ,
path : [ . . . containerPath , synthetic ] ,
} ) ;
} else if ( key === "clear" ) {
queuePatch ( {
root : metaNow.root ,
type : "set" ,
path : containerPath ,
value : raw ,
} ) ;
}
}
queuePatch ( {
root : metaNow.root ,
path : containerPath ,
op : "add" ,
type : "set" ,
value : [ ] ,
} ) ;
}
return result ;
} ;
}
}
const makeIterator = ( pair : boolean ) = > {
return function thisIter ( this : any ) {
const iterable = raw . values ( ) ;
return result ;
} ;
}
const makeIterator = ( pair : boolean ) = > {
return function thisIter ( this : any ) {
const iterable = raw . values ( ) ;
return {
[ Symbol . iterator ] ( ) {
return {
[ Symbol . iterator ] ( ) {
return {
next() {
const n = iterable . next ( ) ;
if ( n . done ) return n ;
const entry = ensureEntryProxy ( n . value ) ;
return { value : pair ? [ entry , entry ] : entry , done : false } ;
} ,
} ;
next() {
const n = iterable . next ( ) ;
if ( n . done ) return n ;
const entry = ensureEntryProxy ( n . value ) ;
return { value : pair ? [ entry , entry ] : entry , done : false } ;
} ,
} as Iterable < any > ;
} ;
} ;
if ( key === "values" || key === "keys" ) return makeIterator ( false ) ;
if ( key === "entries" ) return makeIterator ( true ) ;
if ( key === "forEach" ) {
return function thisForEach ( this : any , cb : any , thisArg? : any ) {
raw . forEach ( ( entry : any ) = > {
cb . call (
thisArg ,
ensureEntryProxy ( entry ) ,
ensureEntryProxy ( entry ) ,
raw
) ;
} ) ;
} ;
}
if ( key === Symbol . iterator . toString ( ) ) {
// string form access of iterator symbol; pass through
} ;
} ,
} as Iterable < any > ;
} ;
} ;
if ( key === "values" || key === "keys" ) return makeIterator ( false ) ;
if ( key === "entries" ) return makeIterator ( true ) ;
if ( key === "forEach" ) {
return function thisForEach ( this : any , cb : any , thisArg? : any ) {
raw . forEach ( ( entry : any ) = > {
cb . call ( thisArg , ensureEntryProxy ( entry ) , ensureEntryProxy ( entry ) , raw ) ;
} ) ;
} ;
}
if ( key === Symbol . iterator . toString ( ) ) {
// string form access of iterator symbol; pass through
}
const val = ( raw as any ) [ key ] ;
if ( typeof val === "function" ) return val . bind ( raw ) ;
return val ;
}
const throwOnMutation = ( ) = > {
throw new Error (
"Don't mutate the signals directly (use the underlying property/value instead)."
) ;
} ;
// Does target define a getter for key?
function hasGetter ( target : any , key : any ) {
return typeof descriptor ( target , key ) ? . get === "function" ;
}
// Lazily allocate / fetch signal map for a proxy receiver.
function getSignals ( receiver : object ) {
if ( ! proxyToSignals . has ( receiver ) ) proxyToSignals . set ( receiver , new Map ( ) ) ;
return proxyToSignals . get ( receiver ) ! ;
}
// Wrap & link child object/array/Set if needed.
function ensureChildProxy ( value : any , parent : object , key : string | number ) {
if ( ! shouldProxy ( value ) ) return value ;
if ( ! objToProxy . has ( value ) ) {
const parentMeta = proxyMeta . get ( parent ) ! ;
const childProxy = createProxy ( value , objectHandlers , parentMeta . root ) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = parent ;
childMeta . key = key as string ;
objToProxy . set ( value , childProxy ) ;
}
return objToProxy . get ( value ) ;
}
// Normalize raw property key (handles $-prefix & array meta) -> { key, returnSignal }
function normalizeKey (
target : any ,
fullKey : string ,
isArrayMeta : boolean ,
receiver : object
) {
let returnSignal = isArrayMeta || fullKey [ 0 ] === "$" ;
if ( ! isArrayMeta && Array . isArray ( target ) && returnSignal ) {
if ( fullKey === "$" ) {
// Provide $ meta proxy for array index signals
if ( ! arrayToArrayOfSignals . has ( target ) ) {
arrayToArrayOfSignals . set (
target ,
createProxy ( target , arrayHandlers , proxyMeta . get ( receiver ) ? . root )
) ;
}
const val = Reflect . get ( raw , key , raw ) ;
if ( typeof val === "function" ) return val . bind ( raw ) ;
return val ;
return { shortCircuit : arrayToArrayOfSignals.get ( target ) } ;
}
if ( ! isArrayOfSignals && returnSignal && Array . isArray ( target ) ) {
if ( fullKey === "$" ) {
if ( ! arrayToArrayOfSignals . has ( target ) )
arrayToArrayOfSignals . set (
target ,
createProxy (
target ,
arrayHandlers ,
proxyMeta . get ( receiver ) ? . root // propagate root id to $ array proxy
)
) ;
return arrayToArrayOfSignals . get ( target ) ;
}
returnSignal = fullKey === "$length" ;
returnSignal = fullKey === "$length" ;
}
const key = returnSignal ? fullKey . replace ( rg , "" ) : fullKey ;
return { key , returnSignal } as any ;
}
// Create computed signal for getter property if needed.
function ensureComputed (
signals : Map < any , any > ,
target : any ,
key : any ,
receiver : any
) {
if ( ! signals . has ( key ) && hasGetter ( target , key ) ) {
signals . set (
key ,
computed ( ( ) = > Reflect . get ( target , key , receiver ) )
) ;
}
}
// Unified get trap factory (object / array meta variant)
const get =
( isArrayMeta : boolean ) = >
( target : object , fullKey : string , receiver : object ) : unknown = > {
if ( peeking ) return Reflect . get ( target , fullKey , receiver ) ;
// Set handling delegated completely.
if ( target instanceof Set && typeof fullKey === "string" ) {
return getFromSet ( target as Set < any > , fullKey , receiver ) ;
}
if ( ! proxyToSignals . has ( receiver ) ) proxyToSignals . set ( receiver , new Map ( ) ) ; // allocate map lazily
const signals = proxyToSignals . get ( receiver ) ;
const key = returnSignal ? fullKey . replace ( rg , "" ) : fullKey ;
if (
! signals . has ( key ) &&
typeof descriptor ( target , key ) ? . get === "function"
) {
signals . set (
key ,
computed ( ( ) = > Reflect . get ( target , key , receiver ) )
) ;
} else {
const norm = normalizeKey ( target , fullKey , isArrayMeta , receiver ) ;
if ( ( norm as any ) . shortCircuit ) return ( norm as any ) . shortCircuit ; // returned meta proxy
const { key , returnSignal } = norm as {
key : string ;
returnSignal : boolean ;
} ;
// Symbol fast-path
if ( typeof key === "symbol" && wellKnownSymbols . has ( key ) )
return Reflect . get ( target , key , receiver ) ;
const signals = getSignals ( receiver ) ;
ensureComputed ( signals , target , key , receiver ) ;
if ( ! signals . has ( key ) ) {
let value = Reflect . get ( target , key , receiver ) ;
if ( returnSignal && typeof value === "function" ) return ; // functions never wrapped as signals
if ( typeof key === "symbol" && wellKnownSymbols . has ( key ) ) return value ;
if ( ! signals . has ( key ) ) {
if ( shouldProxy ( value ) ) {
if ( ! objToProxy . has ( value ) ) {
// Child object/array lazily wrapped: link to parent for path reconstruction.
const parentMeta = proxyMeta . get ( receiver ) ! ;
const childProxy = createProxy (
value ,
objectHandlers ,
parentMeta . root
) ;
const childMeta = proxyMeta . get ( childProxy ) ! ;
childMeta . parent = receiver ;
childMeta . key = key as string ;
objToProxy . set ( value , childProxy ) ;
}
value = objToProxy . get ( value ) ;
}
signals . set ( key , signal ( value ) ) ;
}
if ( returnSignal && typeof value === "function" ) return ; // user asked for signal wrapper of function => ignore
value = ensureChildProxy ( value , receiver , key ) ;
signals . set ( key , signal ( value ) ) ;
}
// deep getter: function signals are callable; non-signal access returns current value.
// We intentionally return the raw function (signal) when `$`-prefixed so callers can set `.value` or invoke.
const sig = signals . get ( key ) ;
return returnSignal ? sig : sig ( ) ;
} ;
// Handlers for standard object/array (non `$` array meta proxy)
// Standard object / array handlers
const objectHandlers = {
get : get ( false ) ,
set ( target : object , fullKey : string , val : any , receiver : object ) : boolean {
@ -683,12 +669,22 @@ const objectHandlers = {
// Emit patch (after mutation) so subscribers get final value snapshot.
const meta = proxyMeta . get ( receiver ) ;
if ( meta ) {
queuePatch ( {
root : meta.root ,
type : "set" ,
path : buildPath ( receiver , fullKey ) ,
value : val ,
} ) ;
// Object/Array/Set assignment at property path.
if ( val && typeof val === "object" ) {
queuePatch ( {
root : meta.root ,
path : buildPath ( receiver , fullKey ) ,
op : "add" ,
type : "object" ,
} ) ;
} else {
queuePatch ( {
root : meta.root ,
path : buildPath ( receiver , fullKey ) ,
op : "add" ,
value : val ,
} ) ;
}
}
return result ;
}
@ -705,8 +701,8 @@ const objectHandlers = {
if ( meta ) {
queuePatch ( {
root : meta.root ,
type : "delete" ,
path : buildPath ( receiverProxy , key ) ,
op : "remove" ,
} ) ;
}
return result ;
@ -718,7 +714,7 @@ const objectHandlers = {
} ,
} ;
// Handlers for special `$` proxy wrapping an array (index signals only)
// Array `$` meta proxy handlers (index signals only)
const arrayHandlers = {
get : get ( true ) ,
set : throwOnMutation ,
@ -730,20 +726,14 @@ const wellKnownSymbols = new Set(
. map ( ( key ) = > Symbol [ key as WellKnownSymbols ] )
. filter ( ( value ) = > typeof value === "symbol" )
) ;
// Support Set so structural mutations can emit patches (Map still unsupport ed for now)
// Supported constructors (Map intentionally exclud ed for now)
const supported = new Set ( [ Object , Array , Set ] ) ;
const shouldProxy = ( val : any ) : boolean = > {
if ( typeof val !== "object" || val === null ) return false ;
return supported . has ( val . constructor ) && ! ignore . has ( val ) ;
} ;
/** TYPES **/
/ * *
* Structural deep reactive view of an input type . Functions and values marked with { @link Shallow }
* are passed through untouched ; arrays and plain objects become recursively deep - signal aware ;
* Sets are proxied so structural & deep entry mutations emit patches .
* /
/** TYPES **/ // Structural deep reactive view of an input type.
export type DeepSignal < T > = T extends Function
? T
: T extends { [ shallowFlag ] : true }
@ -775,17 +765,17 @@ type DeepSignalArray<T> = DeepArray<ArrayType<T>> & {
/** Marker utility type for objects passed through without deep proxying. */
export type Shallow < T extends object > = T & { [ shallowFlag ] : true } ;
/** Framework adapter hook (declared for consumers) that returns a {@link DeepSignal} proxy. */
/** Framework adapter hook returning a DeepSignal proxy. */
export declare const useDeepSignal : < T extends object > ( obj : T ) = > DeepSignal < T > ;
// @ts-ignore
/** Utility: strip `$`-prefixed synthetic signal accessors from key union. */
// Strip `$`-prefixed synthetic signal accessors from key union.
type FilterSignals < K > = K extends ` $ ${ string } ` ? never : K ;
/** Reverse of {@link DeepSignalObject} : remove signal accessors to obtain original object shape. */
/** Reverse of DeepSignalObject: remove signal accessors to obtain original object shape. */
type RevertDeepSignalObject < T > = Pick < T , FilterSignals < keyof T > > ;
/** Reverse of {@link DeepSignalArray} : omit meta accessors. */
/** Reverse of DeepSignalArray: omit meta accessors. */
type RevertDeepSignalArray < T > = Omit < T , " $ " | " $ length " > ;
/** Inverse mapped type that removes deepSignal wrapper affordances (`$` accessors) . */
/** Inverse mapped type removing deepSignal wrapper affordances . */
export type RevertDeepSignal < T > = T extends Array < unknown >
? RevertDeepSignalArray < T >
: T extends object