UNPKG

valtio

Version:

🧙 Valtio makes proxy-state simple for React and Vanilla

269 lines (265 loc) • 8.7 kB
'use strict'; 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;