UNPKG

valtio

Version:

🧙 Valtio makes proxy-state simple for React and Vanilla

524 lines (515 loc) • 15.2 kB
'use strict'; var vanilla = require('valtio/vanilla'); function subscribeKey(proxyObject, key, callback, notifyInSync) { let prevValue = proxyObject[key]; return vanilla.subscribe( proxyObject, () => { const nextValue = proxyObject[key]; if (!Object.is(prevValue, nextValue)) { callback(prevValue = nextValue); } }, notifyInSync ); } let currentCleanups; function watch(callback, options) { let alive = true; const cleanups = /* @__PURE__ */ new Set(); const subscriptions = /* @__PURE__ */ new Map(); const cleanup = () => { if (alive) { alive = false; cleanups.forEach((clean) => clean()); cleanups.clear(); subscriptions.forEach((unsubscribe) => unsubscribe()); subscriptions.clear(); } }; const revalidate = async () => { if (!alive) { return; } cleanups.forEach((clean) => clean()); cleanups.clear(); const proxiesToSubscribe = /* @__PURE__ */ new Set(); const parent = currentCleanups; currentCleanups = cleanups; try { const promiseOrPossibleCleanup = callback((proxyObject) => { proxiesToSubscribe.add(proxyObject); if (alive && !subscriptions.has(proxyObject)) { const unsubscribe = vanilla.subscribe(proxyObject, revalidate, options == null ? void 0 : options.sync); subscriptions.set(proxyObject, unsubscribe); } return proxyObject; }); const couldBeCleanup = promiseOrPossibleCleanup && promiseOrPossibleCleanup instanceof Promise ? await promiseOrPossibleCleanup : promiseOrPossibleCleanup; if (couldBeCleanup) { if (alive) { cleanups.add(couldBeCleanup); } else { cleanup(); } } } finally { currentCleanups = parent; } subscriptions.forEach((unsubscribe, proxyObject) => { if (!proxiesToSubscribe.has(proxyObject)) { subscriptions.delete(proxyObject); unsubscribe(); } }); }; if (currentCleanups) { currentCleanups.add(cleanup); } revalidate(); return cleanup; } const DEVTOOLS = Symbol(); function devtools(proxyObject, options) { const { enabled, name = "", ...rest } = options || {}; let extension; try { extension = (enabled != null ? enabled : process.env.NODE_ENV !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__; } catch (e) { } if (!extension) { if (process.env.NODE_ENV !== "production" && enabled) { console.warn("[Warning] Please install/enable Redux devtools extension"); } return; } let isTimeTraveling = false; const devtools2 = extension.connect({ name, ...rest }); const unsub1 = vanilla.subscribe(proxyObject, (ops) => { const action = ops.filter(([_, path]) => path[0] !== DEVTOOLS).map(([op, path]) => `${op}:${path.map(String).join(".")}`).join(", "); if (!action) { return; } if (isTimeTraveling) { isTimeTraveling = false; } else { const snapWithoutDevtools = Object.assign({}, vanilla.snapshot(proxyObject)); delete snapWithoutDevtools[DEVTOOLS]; devtools2.send( { type: action, updatedAt: (/* @__PURE__ */ new Date()).toLocaleString() }, snapWithoutDevtools ); } }); const unsub2 = devtools2.subscribe((message) => { var _a, _b, _c, _d, _e, _f; if (message.type === "ACTION" && message.payload) { try { Object.assign(proxyObject, JSON.parse(message.payload)); } catch (e) { console.error( "please dispatch a serializable value that JSON.parse() and proxy() support\n", e ); } } if (message.type === "DISPATCH" && message.state) { if (((_a = message.payload) == null ? void 0 : _a.type) === "JUMP_TO_ACTION" || ((_b = message.payload) == null ? void 0 : _b.type) === "JUMP_TO_STATE") { isTimeTraveling = true; const state = JSON.parse(message.state); Object.assign(proxyObject, state); } proxyObject[DEVTOOLS] = message; } else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") { devtools2.init(vanilla.snapshot(proxyObject)); } else if (message.type === "DISPATCH" && ((_d = message.payload) == null ? void 0 : _d.type) === "IMPORT_STATE") { const actions = (_e = message.payload.nextLiftedState) == null ? void 0 : _e.actionsById; const computedStates = ((_f = message.payload.nextLiftedState) == null ? void 0 : _f.computedStates) || []; isTimeTraveling = true; computedStates.forEach(({ state }, index) => { const action = actions[index] || "No action found"; Object.assign(proxyObject, state); if (index === 0) { devtools2.init(vanilla.snapshot(proxyObject)); } else { devtools2.send(action, vanilla.snapshot(proxyObject)); } }); } }); devtools2.init(vanilla.snapshot(proxyObject)); return () => { unsub1(); unsub2 == null ? void 0 : unsub2(); }; } const { proxyStateMap: proxyStateMap$1, snapCache: snapCache$1 } = vanilla.unstable_getInternalStates(); const isProxy$1 = (x) => proxyStateMap$1.has(x); const isProxyMap = (obj) => { return Symbol.toStringTag in obj && obj[Symbol.toStringTag] === "Map" && proxyStateMap$1.has(obj); }; function proxyMap(entries) { const initialData = []; let initialIndex = 0; const indexMap = /* @__PURE__ */ new Map(); const snapMapCache = /* @__PURE__ */ new WeakMap(); const registerSnapMap = () => { const cache = snapCache$1.get(vObject); const latestSnap = cache == null ? void 0 : cache[1]; if (latestSnap && !snapMapCache.has(latestSnap)) { const clonedMap = new Map(indexMap); snapMapCache.set(latestSnap, clonedMap); } }; const getMapForThis = (x) => snapMapCache.get(x) || indexMap; if (entries) { if (typeof entries[Symbol.iterator] !== "function") { throw new TypeError( "proxyMap:\n initial state must be iterable\n tip: structure should be [[key, value]]" ); } for (const [key, value] of entries) { indexMap.set(key, initialIndex); initialData[initialIndex++] = value; } } const vObject = { data: initialData, index: initialIndex, epoch: 0, get size() { if (!isProxy$1(this)) { registerSnapMap(); } const map = getMapForThis(this); return map.size; }, get(key) { const map = getMapForThis(this); const index = map.get(key); if (index === void 0) { this.epoch; return void 0; } return this.data[index]; }, has(key) { const map = getMapForThis(this); this.epoch; return map.has(key); }, set(key, value) { if (!isProxy$1(this)) { throw new Error("Cannot perform mutations on a snapshot"); } const index = indexMap.get(key); if (index === void 0) { indexMap.set(key, this.index); this.data[this.index++] = value; } else { this.data[index] = value; } this.epoch++; return this; }, delete(key) { if (!isProxy$1(this)) { throw new Error("Cannot perform mutations on a snapshot"); } const index = indexMap.get(key); if (index === void 0) { return false; } delete this.data[index]; indexMap.delete(key); this.epoch++; return true; }, clear() { if (!isProxy$1(this)) { throw new Error("Cannot perform mutations on a snapshot"); } this.data.length = 0; this.index = 0; this.epoch++; indexMap.clear(); }, forEach(cb) { this.epoch; const map = getMapForThis(this); map.forEach((index, key) => { cb(this.data[index], key, this); }); }, *entries() { this.epoch; const map = getMapForThis(this); for (const [key, index] of map) { yield [key, this.data[index]]; } }, *keys() { this.epoch; const map = getMapForThis(this); for (const key of map.keys()) { yield key; } }, *values() { this.epoch; const map = getMapForThis(this); for (const index of map.values()) { yield this.data[index]; } }, [Symbol.iterator]() { return this.entries(); }, get [Symbol.toStringTag]() { return "Map"; }, toJSON() { return new Map(this.entries()); } }; const proxiedObject = vanilla.proxy(vObject); Object.defineProperties(proxiedObject, { size: { enumerable: false }, index: { enumerable: false }, epoch: { enumerable: false }, data: { enumerable: false }, toJSON: { enumerable: false } }); Object.seal(proxiedObject); return proxiedObject; } const { proxyStateMap, snapCache } = vanilla.unstable_getInternalStates(); const maybeProxify = (x) => typeof x === "object" ? vanilla.proxy({ x }).x : x; const isProxy = (x) => proxyStateMap.has(x); const isProxySet = (obj) => { return Symbol.toStringTag in obj && obj[Symbol.toStringTag] === "Set" && proxyStateMap.has(obj); }; function proxySet(initialValues) { const initialData = []; const indexMap = /* @__PURE__ */ new Map(); let initialIndex = 0; const snapMapCache = /* @__PURE__ */ new WeakMap(); const registerSnapMap = () => { const cache = snapCache.get(vObject); const latestSnap = cache == null ? void 0 : cache[1]; if (latestSnap && !snapMapCache.has(latestSnap)) { const clonedMap = new Map(indexMap); snapMapCache.set(latestSnap, clonedMap); } }; const getMapForThis = (x) => snapMapCache.get(x) || indexMap; if (initialValues) { if (typeof initialValues[Symbol.iterator] !== "function") { throw new TypeError("not iterable"); } for (const value of initialValues) { if (!indexMap.has(value)) { const v = maybeProxify(value); indexMap.set(v, initialIndex); initialData[initialIndex++] = v; } } } const vObject = { data: initialData, index: initialIndex, epoch: 0, get size() { if (!isProxy(this)) { registerSnapMap(); } return indexMap.size; }, has(value) { const map = getMapForThis(this); const v = maybeProxify(value); this.epoch; return map.has(v); }, add(value) { if (!isProxy(this)) { throw new Error("Cannot perform mutations on a snapshot"); } const v = maybeProxify(value); if (!indexMap.has(v)) { indexMap.set(v, this.index); this.data[this.index++] = v; this.epoch++; } return this; }, delete(value) { if (!isProxy(this)) { throw new Error("Cannot perform mutations on a snapshot"); } const v = maybeProxify(value); const index = indexMap.get(v); if (index === void 0) { return false; } delete this.data[index]; indexMap.delete(v); this.epoch++; return true; }, clear() { if (!isProxy(this)) { throw new Error("Cannot perform mutations on a snapshot"); } this.data.length = 0; this.index = 0; this.epoch++; indexMap.clear(); }, forEach(cb) { this.epoch; const map = getMapForThis(this); map.forEach((index) => { cb(this.data[index], this.data[index], this); }); }, *values() { this.epoch; const map = getMapForThis(this); for (const index of map.values()) { yield this.data[index]; } }, keys() { this.epoch; return this.values(); }, *entries() { this.epoch; const map = getMapForThis(this); for (const index of map.values()) { const value = this.data[index]; yield [value, value]; } }, toJSON() { return new Set(this.values()); }, [Symbol.iterator]() { return this.values(); }, get [Symbol.toStringTag]() { return "Set"; }, intersection(other) { this.epoch; const otherSet = proxySet(other); const resultSet = proxySet(); for (const value of this.values()) { if (otherSet.has(value)) { resultSet.add(value); } } return proxySet(resultSet); }, union(other) { this.epoch; const resultSet = proxySet(); const otherSet = proxySet(other); for (const value of this.values()) { resultSet.add(value); } for (const value of otherSet) { resultSet.add(value); } return proxySet(resultSet); }, difference(other) { this.epoch; const resultSet = proxySet(); const otherSet = proxySet(other); for (const value of this.values()) { if (!otherSet.has(value)) { resultSet.add(value); } } return proxySet(resultSet); }, symmetricDifference(other) { this.epoch; const resultSet = proxySet(); const otherSet = proxySet(other); for (const value of this.values()) { if (!otherSet.has(value)) { resultSet.add(value); } } for (const value of otherSet.values()) { if (!this.has(value)) { resultSet.add(value); } } return proxySet(resultSet); }, isSubsetOf(other) { this.epoch; const otherSet = proxySet(other); return this.size <= other.size && [...this.values()].every((value) => otherSet.has(value)); }, isSupersetOf(other) { this.epoch; const otherSet = proxySet(other); return this.size >= other.size && [...otherSet].every((value) => this.has(value)); }, isDisjointFrom(other) { this.epoch; const otherSet = proxySet(other); return [...this.values()].every((value) => !otherSet.has(value)); } }; const proxiedObject = vanilla.proxy(vObject); Object.defineProperties(proxiedObject, { size: { enumerable: false }, data: { enumerable: false }, index: { enumerable: false }, epoch: { enumerable: false }, toJSON: { enumerable: false } }); Object.seal(proxiedObject); return proxiedObject; } const isObject = (x) => typeof x === "object" && x !== null; let defaultRefSet; const getDefaultRefSet = () => { if (!defaultRefSet) { defaultRefSet = vanilla.unstable_getInternalStates().refSet; } return defaultRefSet; }; function deepClone(obj, getRefSet = getDefaultRefSet) { if (!isObject(obj) || getRefSet().has(obj)) { return obj; } if (isProxySet(obj)) { return proxySet([...obj]); } if (isProxyMap(obj)) { return proxyMap([ ...obj.entries() ]); } const baseObject = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj)); Reflect.ownKeys(obj).forEach((key) => { baseObject[key] = deepClone(obj[key], getRefSet); }); return baseObject; } exports.deepClone = deepClone; exports.devtools = devtools; exports.proxyMap = proxyMap; exports.proxySet = proxySet; exports.subscribeKey = subscribeKey; exports.watch = watch;