valtio
Version:
🧙 Valtio makes proxy-state simple for React and Vanilla
269 lines (265 loc) • 8.7 kB
JavaScript
;
var proxyCompare = require('proxy-compare');
const isObject = (x) => typeof x === "object" && x !== null;
const canProxyDefault = (x) => isObject(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer) && !(x instanceof Promise);
const createSnapshotDefault = (target, version) => {
const cache = snapCache.get(target);
if ((cache == null ? void 0 : cache[0]) === version) {
return cache[1];
}
const snap = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
proxyCompare.markToTrack(snap, true);
snapCache.set(target, [version, snap]);
Reflect.ownKeys(target).forEach((key) => {
if (Object.getOwnPropertyDescriptor(snap, key)) {
return;
}
const value = Reflect.get(target, key);
const { enumerable } = Reflect.getOwnPropertyDescriptor(
target,
key
);
const desc = {
value,
enumerable,
// This is intentional to avoid copying with proxy-compare.
// It's still non-writable, so it avoids assigning a value.
configurable: true
};
if (refSet.has(value)) {
proxyCompare.markToTrack(value, false);
} else if (proxyStateMap.has(value)) {
const [target2, ensureVersion] = proxyStateMap.get(
value
);
desc.value = createSnapshotDefault(target2, ensureVersion());
}
Object.defineProperty(snap, key, desc);
});
return Object.preventExtensions(snap);
};
const createHandlerDefault = (isInitializing, addPropListener, removePropListener, notifyUpdate) => ({
deleteProperty(target, prop) {
const prevValue = Reflect.get(target, prop);
removePropListener(prop);
const deleted = Reflect.deleteProperty(target, prop);
if (deleted) {
notifyUpdate(["delete", [prop], prevValue]);
}
return deleted;
},
set(target, prop, value, receiver) {
const hasPrevValue = !isInitializing() && Reflect.has(target, prop);
const prevValue = Reflect.get(target, prop, receiver);
if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) {
return true;
}
removePropListener(prop);
if (isObject(value)) {
value = proxyCompare.getUntracked(value) || value;
}
const nextValue = !proxyStateMap.has(value) && canProxy(value) ? proxy(value) : value;
addPropListener(prop, nextValue);
Reflect.set(target, prop, nextValue, receiver);
notifyUpdate(["set", [prop], value, prevValue]);
return true;
}
});
const proxyStateMap = /* @__PURE__ */ new WeakMap();
const refSet = /* @__PURE__ */ new WeakSet();
const snapCache = /* @__PURE__ */ new WeakMap();
const versionHolder = [1, 1];
const proxyCache = /* @__PURE__ */ new WeakMap();
let objectIs = Object.is;
let newProxy = (target, handler) => new Proxy(target, handler);
let canProxy = canProxyDefault;
let createSnapshot = createSnapshotDefault;
let createHandler = createHandlerDefault;
function proxy(baseObject = {}) {
if (!isObject(baseObject)) {
throw new Error("object required");
}
const found = proxyCache.get(baseObject);
if (found) {
return found;
}
let version = versionHolder[0];
const listeners = /* @__PURE__ */ new Set();
const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => {
if (version !== nextVersion) {
version = nextVersion;
listeners.forEach((listener) => listener(op, nextVersion));
}
};
let checkVersion = versionHolder[1];
const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => {
if (checkVersion !== nextCheckVersion && !listeners.size) {
checkVersion = nextCheckVersion;
propProxyStates.forEach(([propProxyState]) => {
const propVersion = propProxyState[1](nextCheckVersion);
if (propVersion > version) {
version = propVersion;
}
});
}
return version;
};
const createPropListener = (prop) => (op, nextVersion) => {
const newOp = [...op];
newOp[1] = [prop, ...newOp[1]];
notifyUpdate(newOp, nextVersion);
};
const propProxyStates = /* @__PURE__ */ new Map();
const addPropListener = (prop, propValue) => {
const propProxyState = !refSet.has(propValue) && proxyStateMap.get(propValue);
if (propProxyState) {
if (process.env.NODE_ENV !== "production" && propProxyStates.has(prop)) {
throw new Error("prop listener already exists");
}
if (listeners.size) {
const remove = propProxyState[2](createPropListener(prop));
propProxyStates.set(prop, [propProxyState, remove]);
} else {
propProxyStates.set(prop, [propProxyState]);
}
}
};
const removePropListener = (prop) => {
var _a;
const entry = propProxyStates.get(prop);
if (entry) {
propProxyStates.delete(prop);
(_a = entry[1]) == null ? void 0 : _a.call(entry);
}
};
const addListener = (listener) => {
listeners.add(listener);
if (listeners.size === 1) {
propProxyStates.forEach(([propProxyState, prevRemove], prop) => {
if (process.env.NODE_ENV !== "production" && prevRemove) {
throw new Error("remove already exists");
}
const remove = propProxyState[2](createPropListener(prop));
propProxyStates.set(prop, [propProxyState, remove]);
});
}
const removeListener = () => {
listeners.delete(listener);
if (listeners.size === 0) {
propProxyStates.forEach(([propProxyState, remove], prop) => {
if (remove) {
remove();
propProxyStates.set(prop, [propProxyState]);
}
});
}
};
return removeListener;
};
let initializing = true;
const handler = createHandler(
() => initializing,
addPropListener,
removePropListener,
notifyUpdate
);
const proxyObject = newProxy(baseObject, handler);
proxyCache.set(baseObject, proxyObject);
const proxyState = [baseObject, ensureVersion, addListener];
proxyStateMap.set(proxyObject, proxyState);
Reflect.ownKeys(baseObject).forEach((key) => {
const desc = Object.getOwnPropertyDescriptor(
baseObject,
key
);
if ("value" in desc && desc.writable) {
proxyObject[key] = baseObject[key];
}
});
initializing = false;
return proxyObject;
}
function getVersion(proxyObject) {
const proxyState = proxyStateMap.get(proxyObject);
return proxyState == null ? void 0 : proxyState[1]();
}
function subscribe(proxyObject, callback, notifyInSync) {
const proxyState = proxyStateMap.get(proxyObject);
if (process.env.NODE_ENV !== "production" && !proxyState) {
console.warn("Please use proxy object");
}
let promise;
const ops = [];
const addListener = proxyState[2];
let isListenerActive = false;
const listener = (op) => {
ops.push(op);
if (notifyInSync) {
callback(ops.splice(0));
return;
}
if (!promise) {
promise = Promise.resolve().then(() => {
promise = void 0;
if (isListenerActive) {
callback(ops.splice(0));
}
});
}
};
const removeListener = addListener(listener);
isListenerActive = true;
return () => {
isListenerActive = false;
removeListener();
};
}
function snapshot(proxyObject) {
const proxyState = proxyStateMap.get(proxyObject);
if (process.env.NODE_ENV !== "production" && !proxyState) {
console.warn("Please use proxy object");
}
const [target, ensureVersion] = proxyState;
return createSnapshot(target, ensureVersion());
}
function ref(obj) {
refSet.add(obj);
return obj;
}
function unstable_getInternalStates() {
return {
proxyStateMap,
refSet,
snapCache,
versionHolder,
proxyCache
};
}
function unstable_replaceInternalFunction(name, fn) {
switch (name) {
case "objectIs":
objectIs = fn(objectIs);
break;
case "newProxy":
newProxy = fn(newProxy);
break;
case "canProxy":
canProxy = fn(canProxy);
break;
case "createSnapshot":
createSnapshot = fn(createSnapshot);
break;
case "createHandler":
createHandler = fn(createHandler);
break;
default:
throw new Error("unknown function");
}
}
exports.getVersion = getVersion;
exports.proxy = proxy;
exports.ref = ref;
exports.snapshot = snapshot;
exports.subscribe = subscribe;
exports.unstable_getInternalStates = unstable_getInternalStates;
exports.unstable_replaceInternalFunction = unstable_replaceInternalFunction;