type DataObject = Record<number | string | symbol, any>;
interface ObservableField {
var tracking: Function | undefined,
running: Function[] = [],
map = new WeakMap<DataObject, Record<string, ObservableField>>(),
nextTick: Promise<void> | undefined;
function run(callbacks: Function[]) {
running = [...new Set([...running, ...callbacks])];
nextTick ||= Promise.resolve().then(() => {
var callback: Function | undefined;
while ((callback = running.shift())) callback();
function updateOne(object: DataObject, key: string, value: any) {
const data = map.get(object);
const item = { callbacks: [], ...data?.[key], value };
map.set(object, { ...data, [key]: item });
function watchOne(object: DataObject, key: string, value?: any) {
const { callbacks } = updateOne(object, key, value);
if (value != null && typeof value === "object") observable(value);
Object.defineProperty(object, key, {
const { value } = map.get(object)?.[key] || {};
if (tracking && !callbacks.includes(tracking))
callbacks.push(tracking);
const { value: old } = map.get(object)?.[key] || {};
if (value === old) return;
updateOne(object, key, value);
export function observable<T extends DataObject>(object: T, key?: string): any {
if (key) return watchOne(object, key);
for (const [key, value] of Object.entries(object))
watchOne(object, key, value);