|
|
|
@ -118,6 +118,50 @@ function queuePatch(patch: DeepPatch) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Recursively emit patches for all nested properties of a newly attached object. */ |
|
|
|
|
function queueDeepPatches( |
|
|
|
|
val: any, |
|
|
|
|
rootId: symbol, |
|
|
|
|
basePath: (string | number)[] |
|
|
|
|
) { |
|
|
|
|
if (!val || typeof val !== "object") { |
|
|
|
|
// Emit patch for primitive leaf
|
|
|
|
|
queuePatch({ |
|
|
|
|
root: rootId, |
|
|
|
|
path: basePath, |
|
|
|
|
op: "add", |
|
|
|
|
value: val, |
|
|
|
|
}); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Emit patch for the object/array/Set itself
|
|
|
|
|
queuePatch({ |
|
|
|
|
root: rootId, |
|
|
|
|
path: basePath, |
|
|
|
|
op: "add", |
|
|
|
|
type: "object", |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Recursively process nested properties
|
|
|
|
|
if (Array.isArray(val)) { |
|
|
|
|
for (let i = 0; i < val.length; i++) { |
|
|
|
|
queueDeepPatches(val[i], rootId, [...basePath, i]); |
|
|
|
|
} |
|
|
|
|
} else if (val instanceof Set) { |
|
|
|
|
for (const entry of val) { |
|
|
|
|
const key = getSetEntryKey(entry); |
|
|
|
|
queueDeepPatches(entry, rootId, [...basePath, key]); |
|
|
|
|
} |
|
|
|
|
} else if (val.constructor === Object) { |
|
|
|
|
for (const key in val) { |
|
|
|
|
if (Object.prototype.hasOwnProperty.call(val, key)) { |
|
|
|
|
queueDeepPatches(val[key], rootId, [...basePath, key]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Subscribe to microtask-batched deep patches for a root (returns unsubscribe). */ |
|
|
|
|
export function subscribeDeepMutations( |
|
|
|
|
root: object | symbol, |
|
|
|
@ -336,7 +380,7 @@ export const deepSignal = <T extends object>(obj: T): DeepSignal<T> => { |
|
|
|
|
/** Read property without tracking (untracked read). */ |
|
|
|
|
export const peek = < |
|
|
|
|
T extends DeepSignalObject<object>, |
|
|
|
|
K extends keyof RevertDeepSignalObject<T> |
|
|
|
|
K extends keyof RevertDeepSignalObject<T>, |
|
|
|
|
>( |
|
|
|
|
obj: T, |
|
|
|
|
key: K |
|
|
|
@ -365,7 +409,9 @@ const createProxy = ( |
|
|
|
|
ignore.add(proxy); |
|
|
|
|
// Initialize proxy metadata if not present. Root proxies provide a stable root id.
|
|
|
|
|
if (!proxyMeta.has(proxy)) { |
|
|
|
|
proxyMeta.set(proxy, { root: rootId || Symbol("deepSignalDetachedRoot") }); |
|
|
|
|
proxyMeta.set(proxy, { |
|
|
|
|
root: rootId || Symbol("deepSignalDetachedRoot"), |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
return proxy; |
|
|
|
|
}; |
|
|
|
@ -410,7 +456,10 @@ function getFromSet( |
|
|
|
|
metaNow.parent !== undefined && |
|
|
|
|
metaNow.key !== undefined |
|
|
|
|
) { |
|
|
|
|
const containerPath = buildPath(metaNow.parent, metaNow.key); |
|
|
|
|
const containerPath = buildPath( |
|
|
|
|
metaNow.parent, |
|
|
|
|
metaNow.key |
|
|
|
|
); |
|
|
|
|
if (key === "add") { |
|
|
|
|
const entry = args[0]; |
|
|
|
|
let synthetic = getSetEntryKey(entry); |
|
|
|
@ -473,8 +522,11 @@ function getFromSet( |
|
|
|
|
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) |
|
|
|
|
p.path.length === |
|
|
|
|
containerPath.length + 1 && |
|
|
|
|
containerPath.every( |
|
|
|
|
(seg, idx) => p.path[idx] === seg |
|
|
|
|
) |
|
|
|
|
) { |
|
|
|
|
group.splice(i, 1); |
|
|
|
|
} |
|
|
|
@ -504,7 +556,10 @@ function getFromSet( |
|
|
|
|
const n = iterable.next(); |
|
|
|
|
if (n.done) return n; |
|
|
|
|
const entry = ensureEntryProxy(n.value); |
|
|
|
|
return { value: pair ? [entry, entry] : entry, done: false }; |
|
|
|
|
return { |
|
|
|
|
value: pair ? [entry, entry] : entry, |
|
|
|
|
done: false, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
@ -516,7 +571,12 @@ function getFromSet( |
|
|
|
|
if (key === "forEach") { |
|
|
|
|
return function thisForEach(this: any, cb: any, thisArg?: any) { |
|
|
|
|
raw.forEach((entry: any) => { |
|
|
|
|
cb.call(thisArg, ensureEntryProxy(entry), ensureEntryProxy(entry), raw); |
|
|
|
|
cb.call( |
|
|
|
|
thisArg, |
|
|
|
|
ensureEntryProxy(entry), |
|
|
|
|
ensureEntryProxy(entry), |
|
|
|
|
raw |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
@ -592,7 +652,11 @@ function normalizeKey( |
|
|
|
|
if (!arrayToArrayOfSignals.has(target)) { |
|
|
|
|
arrayToArrayOfSignals.set( |
|
|
|
|
target, |
|
|
|
|
createProxy(target, arrayHandlers, proxyMeta.get(receiver)?.root) |
|
|
|
|
createProxy( |
|
|
|
|
target, |
|
|
|
|
arrayHandlers, |
|
|
|
|
proxyMeta.get(receiver)?.root |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
return { shortCircuit: arrayToArrayOfSignals.get(target) }; |
|
|
|
@ -655,7 +719,8 @@ const objectHandlers = { |
|
|
|
|
// Respect original getter/setter semantics
|
|
|
|
|
if (typeof descriptor(target, fullKey)?.set === "function") |
|
|
|
|
return Reflect.set(target, fullKey, val, receiver); |
|
|
|
|
if (!proxyToSignals.has(receiver)) proxyToSignals.set(receiver, new Map()); |
|
|
|
|
if (!proxyToSignals.has(receiver)) |
|
|
|
|
proxyToSignals.set(receiver, new Map()); |
|
|
|
|
const signals = proxyToSignals.get(receiver); |
|
|
|
|
if (fullKey[0] === "$") { |
|
|
|
|
if (!isSignal(val)) throwOnMutation(); |
|
|
|
@ -679,7 +744,11 @@ const objectHandlers = { |
|
|
|
|
proxyMeta.set(receiver, created); |
|
|
|
|
parentMeta = created; |
|
|
|
|
} |
|
|
|
|
const childProxy = createProxy(val, objectHandlers, parentMeta!.root); |
|
|
|
|
const childProxy = createProxy( |
|
|
|
|
val, |
|
|
|
|
objectHandlers, |
|
|
|
|
parentMeta!.root |
|
|
|
|
); |
|
|
|
|
const childMeta = proxyMeta.get(childProxy)!; |
|
|
|
|
childMeta.parent = receiver; |
|
|
|
|
childMeta.key = fullKey; |
|
|
|
@ -697,28 +766,15 @@ const objectHandlers = { |
|
|
|
|
// Subsequent writes -> update underlying signal.
|
|
|
|
|
signals.get(fullKey).set(internal); |
|
|
|
|
} |
|
|
|
|
if (isNew && objToIterable.has(target)) objToIterable.get(target).value++; |
|
|
|
|
if (isNew && objToIterable.has(target)) |
|
|
|
|
objToIterable.get(target).value++; |
|
|
|
|
if (Array.isArray(target) && signals.has("length")) |
|
|
|
|
signals.get("length").set(target.length); |
|
|
|
|
// Emit patch (after mutation) so subscribers get final value snapshot.
|
|
|
|
|
const meta = proxyMeta.get(receiver); |
|
|
|
|
if (meta) { |
|
|
|
|
// 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, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
// Recursively emit patches for all nested properties of newly attached objects
|
|
|
|
|
queueDeepPatches(val, meta.root, buildPath(receiver, fullKey)); |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
@ -810,7 +866,8 @@ type RevertDeepSignalObject<T> = Pick<T, FilterSignals<keyof T>>; |
|
|
|
|
type RevertDeepSignalArray<T> = Omit<T, "$" | "$length">; |
|
|
|
|
|
|
|
|
|
/** Inverse mapped type removing deepSignal wrapper affordances. */ |
|
|
|
|
export type RevertDeepSignal<T> = T extends Array<unknown> |
|
|
|
|
export type RevertDeepSignal<T> = |
|
|
|
|
T extends Array<unknown> |
|
|
|
|
? RevertDeepSignalArray<T> |
|
|
|
|
: T extends object |
|
|
|
|
? RevertDeepSignalObject<T> |
|
|
|
|