Fork of https://github.com/CCherry07/alien-deepsignals ported to alien-signals v2 with support for per-value modification tracking.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
alien-deepsignals-fork/src/deepSignal.ts

305 lines
8.9 KiB

import { computed } from "./computed";
import { signal, Signal } from "./signal"
const proxyToSignals = new WeakMap();
const objToProxy = new WeakMap();
const arrayToArrayOfSignals = new WeakMap();
const ignore = new WeakSet();
const objToIterable = new WeakMap();
const rg = /^\$/;
const descriptor = Object.getOwnPropertyDescriptor;
let peeking = false;
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))
objToProxy.set(obj, createProxy(obj, objectHandlers) as DeepSignal<T>);
return objToProxy.get(obj);
};
export const peek = <
T extends DeepSignalObject<object>,
K extends keyof RevertDeepSignalObject<T>
>(
obj: T,
key: K
): RevertDeepSignal<RevertDeepSignalObject<T>[K]> => {
peeking = true;
const value = obj[key];
try {
peeking = false;
} catch (e) { }
return value as RevertDeepSignal<RevertDeepSignalObject<T>[K]>;
};
const isShallow = Symbol("shallow");
export function shallow<T extends object>(obj: T): Shallow<T> {
ignore.add(obj);
return obj as Shallow<T>;
}
const createProxy = (target: object, handlers: ProxyHandler<object>) => {
const proxy = new Proxy(target, handlers);
ignore.add(proxy);
return proxy;
};
const throwOnMutation = () => {
throw new Error("Don't mutate the signals directly.");
};
const get =
(isArrayOfSignals: boolean) =>
(target: object, fullKey: string, receiver: object): unknown => {
if (peeking) return Reflect.get(target, fullKey, receiver);
let returnSignal = isArrayOfSignals || fullKey[0] === "$";
if (!isArrayOfSignals && returnSignal && Array.isArray(target)) {
if (fullKey === "$") {
if (!arrayToArrayOfSignals.has(target))
arrayToArrayOfSignals.set(target, createProxy(target, arrayHandlers));
return arrayToArrayOfSignals.get(target);
}
returnSignal = fullKey === "$length";
}
if (!proxyToSignals.has(receiver)) proxyToSignals.set(receiver, new Map());
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 {
let value = Reflect.get(target, key, receiver);
if (returnSignal && typeof value === "function") return;
if (typeof key === "symbol" && wellKnownSymbols.has(key)) return value;
if (!signals.has(key)) {
if (shouldProxy(value)) {
if (!objToProxy.has(value))
objToProxy.set(value, createProxy(value, objectHandlers));
value = objToProxy.get(value);
}
signals.set(key, signal(value));
}
}
return returnSignal ? signals.get(key) : signals.get(key).get();
};
const objectHandlers = {
get: get(false),
set(target: object, fullKey: string, val: any, receiver: object): boolean {
if (typeof descriptor(target, fullKey)?.set === "function")
return Reflect.set(target, fullKey, val, receiver);
if (!proxyToSignals.has(receiver)) proxyToSignals.set(receiver, new Map());
const signals = proxyToSignals.get(receiver);
if (fullKey[0] === "$") {
if (!(val instanceof Signal)) throwOnMutation();
const key = fullKey.replace(rg, "");
signals.set(key, val);
return Reflect.set(target, key, val.peek(), receiver);
} else {
let internal = val;
if (shouldProxy(val)) {
if (!objToProxy.has(val))
objToProxy.set(val, createProxy(val, objectHandlers));
internal = objToProxy.get(val);
}
const isNew = !(fullKey in target);
const result = Reflect.set(target, fullKey, val, receiver);
if (!signals.has(fullKey)) signals.set(fullKey, signal(internal));
else signals.get(fullKey).set(internal);
if (isNew && objToIterable.has(target)) objToIterable.get(target).value++;
if (Array.isArray(target) && signals.has("length"))
signals.get("length").set(target.length);
return result;
}
},
deleteProperty(target: object, key: string): boolean {
if (key[0] === "$") throwOnMutation();
const signals = proxyToSignals.get(objToProxy.get(target));
const result = Reflect.deleteProperty(target, key);
if (signals && signals.has(key)) signals.get(key).value = undefined;
objToIterable.has(target) && objToIterable.get(target).value++;
return result;
},
ownKeys(target: object): (string | symbol)[] {
if (!objToIterable.has(target)) objToIterable.set(target, signal(0));
(objToIterable as any)._ = objToIterable.get(target).get();
return Reflect.ownKeys(target);
},
};
const arrayHandlers = {
get: get(true),
set: throwOnMutation,
deleteProperty: throwOnMutation,
};
const wellKnownSymbols = new Set(
Object.getOwnPropertyNames(Symbol)
.map(key => Symbol[key as WellKnownSymbols])
.filter(value => typeof value === "symbol")
);
const supported = new Set([Object, Array]);
const shouldProxy = (val: any): boolean => {
if (typeof val !== "object" || val === null) return false;
return supported.has(val.constructor) && !ignore.has(val);
};
/** TYPES **/
export type DeepSignal<T> = T extends Function
? T
: T extends { [isShallow]: true }
? T
: T extends Array<unknown>
? DeepSignalArray<T>
: T extends object
? DeepSignalObject<T>
: T;
type DeepSignalObject<T extends object> = {
[P in keyof T & string as `$${P}`]?: T[P] extends Function
? never
: Signal<T[P]>;
} & {
[P in keyof T]: DeepSignal<T[P]>;
};
/** @ts-expect-error **/
interface DeepArray<T> extends Array<T> {
map: <U>(
callbackfn: (
value: DeepSignal<T>,
index: number,
array: DeepSignalArray<T[]>
) => U,
thisArg?: any
) => U[];
forEach: (
callbackfn: (
value: DeepSignal<T>,
index: number,
array: DeepSignalArray<T[]>
) => void,
thisArg?: any
) => void;
concat(...items: ConcatArray<T>[]): DeepSignalArray<T[]>;
concat(...items: (T | ConcatArray<T>)[]): DeepSignalArray<T[]>;
reverse(): DeepSignalArray<T[]>;
shift(): DeepSignal<T> | undefined;
slice(start?: number, end?: number): DeepSignalArray<T[]>;
splice(start: number, deleteCount?: number): DeepSignalArray<T[]>;
splice(
start: number,
deleteCount: number,
...items: T[]
): DeepSignalArray<T[]>;
filter<S extends T>(
predicate: (
value: DeepSignal<T>,
index: number,
array: DeepSignalArray<T[]>
) => value is DeepSignal<S>,
thisArg?: any
): DeepSignalArray<S[]>;
filter(
predicate: (
value: DeepSignal<T>,
index: number,
array: DeepSignalArray<T[]>
) => unknown,
thisArg?: any
): DeepSignalArray<T[]>;
reduce(
callbackfn: (
previousValue: DeepSignal<T>,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => T
): DeepSignal<T>;
reduce(
callbackfn: (
previousValue: DeepSignal<T>,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => DeepSignal<T>,
initialValue: T
): DeepSignal<T>;
reduce<U>(
callbackfn: (
previousValue: U,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => U,
initialValue: U
): U;
reduceRight(
callbackfn: (
previousValue: DeepSignal<T>,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => T
): DeepSignal<T>;
reduceRight(
callbackfn: (
previousValue: DeepSignal<T>,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => DeepSignal<T>,
initialValue: T
): DeepSignal<T>;
reduceRight<U>(
callbackfn: (
previousValue: U,
currentValue: DeepSignal<T>,
currentIndex: number,
array: DeepSignalArray<T[]>
) => U,
initialValue: U
): U;
}
type ArrayType<T> = T extends Array<infer I> ? I : T;
type DeepSignalArray<T> = DeepArray<ArrayType<T>> & {
[key: number]: DeepSignal<ArrayType<T>>;
$?: { [key: number]: Signal<ArrayType<T>> };
$length?: Signal<number>;
};
export type Shallow<T extends object> = T & { [isShallow]: true };
export declare const useDeepSignal: <T extends object>(obj: T) => DeepSignal<T>;
// @ts-ignore
type FilterSignals<K> = K extends `$${infer P}` ? never : K;
type RevertDeepSignalObject<T> = Pick<T, FilterSignals<keyof T>>;
type RevertDeepSignalArray<T> = Omit<T, "$" | "$length">;
export type RevertDeepSignal<T> = T extends Array<unknown>
? RevertDeepSignalArray<T>
: T extends object
? RevertDeepSignalObject<T>
: T;
type WellKnownSymbols =
| "asyncIterator"
| "hasInstance"
| "isConcatSpreadable"
| "iterator"
| "match"
| "matchAll"
| "replace"
| "search"
| "species"
| "split"
| "toPrimitive"
| "toStringTag"
| "unscopables";