Skip to content

Instantly share code, notes, and snippets.

@bigmistqke
Created January 5, 2025 22:57
Show Gist options
  • Save bigmistqke/261c62650d87953c804cc9ff62197b1b to your computer and use it in GitHub Desktop.
Save bigmistqke/261c62650d87953c804cc9ff62197b1b to your computer and use it in GitHub Desktop.
custom mutable store
import { createSignal, batch } from "solid-js";
const $LENGTH = Symbol();
const PROXIES = new WeakMap();
function createMutable<T extends object>(root: T) {
function signal<T>(initialValue: T) {
const [get, set] = createSignal(initialValue);
return { get, set };
}
function createProxy<T extends object>(source: T): T {
if (PROXIES.has(source)) return PROXIES.get(source);
const map = new Map();
const proxy = new Proxy(source, {
get(target, property) {
const value = target[property];
if (typeof property === "symbol") {
return value;
}
if (!map.get($LENGTH)) {
map.set($LENGTH, signal(Object.keys(target).length));
}
if (!map.has(property)) {
map.set(
property,
signal(
typeof value === "object"
? createProxy(value)
: value,
),
);
}
return map.get(property)!.get();
},
set(target, property, value) {
if (typeof property === "symbol") {
target[property] = value;
return true;
}
const updateLength = !(property in target);
target[property] = value;
if (map.has(property)) {
map.get(property)!.set(value);
}
if (updateLength) {
const length =
Array.isArray(target) && !isNaN(Number(property))
? +property + 1
: Object.keys(value).length;
if (!map.has($LENGTH)) {
map.set($LENGTH, signal(Object.keys(target).length));
} else {
map.get($LENGTH).set(length);
}
}
return true;
},
deleteProperty(target, property) {
if (typeof property === "symbol") {
delete target[property];
return true;
}
if (property in target) {
delete target[property];
batch(() => {
map.get(property)?.set(undefined);
map.get($LENGTH)?.set(Object.keys(target).length);
});
return true;
}
return false;
},
has(target: T, property: PropertyKey) {
this.ownKeys?.(target);
return property in target;
},
ownKeys(target) {
if (!map.has($LENGTH)) {
map.set($LENGTH, signal(Object.keys(target).length));
}
map.get($LENGTH)!.get();
return Reflect.ownKeys(target);
},
getOwnPropertyDescriptor(target, property) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
if (descriptor) {
if (descriptor.get) {
descriptor.get = this.get!.bind(this, target, property, this);
delete descriptor.writable;
} else {
descriptor.value = this.get!(target, property, this);
}
return descriptor;
}
if (this.has!(target, property)) {
return {
enumerable: true,
configurable: true,
get: this.get!.bind(this, target, property, this),
};
} else {
return undefined;
}
},
});
PROXIES.set(source, proxy);
return proxy;
}
return createProxy(root);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment