Socket
Socket
Sign inDemoInstall

valtio

Package Overview
Dependencies
Maintainers
2
Versions
110
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

valtio - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

61

index.js

@@ -9,8 +9,2 @@ 'use strict';

/*
export {
unstable_createMutableSource as createMutableSource,
unstable_useMutableSource as useMutableSource,
} from 'react'
*/
var TARGET = Symbol();

@@ -28,13 +22,3 @@ var GET_VERSION = Symbol();

var _useState = react.useState(function () {
return [
/* [0] */
source,
/* [1] */
getSnapshot,
/* [2] */
subscribe,
/* [3] */
currentVersion,
/* [4] */
getSnapshot(source[TARGET])];
return [source, getSnapshot, subscribe, currentVersion, getSnapshot(source[TARGET])];
}),

@@ -46,15 +30,11 @@ state = _useState[0],

if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe || currentVersion !== state[3] && currentVersion !== lastVersion.current) {
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe) {
currentSnapshot = getSnapshot(source[TARGET]);
setState([
/* [0] */
source,
/* [1] */
getSnapshot,
/* [2] */
subscribe,
/* [3] */
currentVersion,
/* [4] */
currentSnapshot]);
setState([source, getSnapshot, subscribe, currentVersion, currentSnapshot]);
} else if (currentVersion !== state[3] && currentVersion !== lastVersion.current) {
currentSnapshot = getSnapshot(source[TARGET]);
if (!Object.is(currentSnapshot, state[4])) {
setState([source, getSnapshot, subscribe, currentVersion, currentSnapshot]);
}
}

@@ -79,20 +59,9 @@

if (prev[4] === nextSnapshot) {
if (Object.is(prev[4], nextSnapshot)) {
return prev;
}
return [
/* [0] */
prev[0],
/* [1] */
prev[1],
/* [2] */
prev[2],
/* [3] */
nextVersion,
/* [4] */
nextSnapshot];
return [prev[0], prev[1], prev[2], nextVersion, nextSnapshot];
});
} catch (e) {
// schedule update
setState(function (prev) {

@@ -164,7 +133,5 @@ return [].concat(prev);

if (lastAffected.current && !proxyCompare.isDeepChanged(prevSnapshot.current, nextSnapshot, lastAffected.current, new WeakMap())) {
// not changed
return;
}
} catch (e) {// ignore if a promise or something is thrown
}
} catch (e) {}

@@ -178,3 +145,2 @@ prevSnapshot.current = nextSnapshot;

if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useAffectedDebugValue(currSnapshot, affected);

@@ -185,4 +151,3 @@ }

return new WeakMap();
}, []); // per-hook proxyCache
}, []);
return proxyCompare.createDeepProxy(currSnapshot, affected, proxyCache);

@@ -189,0 +154,0 @@ };

@@ -6,8 +6,2 @@ import { snapshot, subscribe, getVersion } from './vanilla';

/*
export {
unstable_createMutableSource as createMutableSource,
unstable_useMutableSource as useMutableSource,
} from 'react'
*/
const TARGET = Symbol();

@@ -23,32 +17,32 @@ const GET_VERSION = Symbol();

const [state, setState] = useState(() => [
/* [0] */
source,
/* [1] */
getSnapshot,
/* [2] */
subscribe,
/* [3] */
currentVersion,
/* [4] */
getSnapshot(source[TARGET])]);
let currentSnapshot = state[4];
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe || currentVersion !== state[3] && currentVersion !== lastVersion.current) {
currentSnapshot = getSnapshot(source[TARGET]);
setState([
/* [0] */
source,
/* [1] */
getSnapshot,
/* [2] */
subscribe,
/* [3] */
currentVersion,
/* [4] */
currentSnapshot]);
getSnapshot(source[TARGET])
]);
let currentSnapshot = state[4];
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe) {
currentSnapshot = getSnapshot(source[TARGET]);
setState([
source,
getSnapshot,
subscribe,
currentVersion,
currentSnapshot
]);
} else if (currentVersion !== state[3] && currentVersion !== lastVersion.current) {
currentSnapshot = getSnapshot(source[TARGET]);
if (!Object.is(currentSnapshot, state[4])) {
setState([
source,
getSnapshot,
subscribe,
currentVersion,
currentSnapshot
]);
}
}
useEffect(() => {
let didUnsubscribe = false;
const checkForUpdates = () => {

@@ -58,3 +52,2 @@ if (didUnsubscribe) {

}
try {

@@ -64,29 +57,21 @@ const nextSnapshot = getSnapshot(source[TARGET]);

lastVersion.current = nextVersion;
setState(prev => {
setState((prev) => {
if (prev[0] !== source || prev[1] !== getSnapshot || prev[2] !== subscribe) {
return prev;
}
if (prev[4] === nextSnapshot) {
if (Object.is(prev[4], nextSnapshot)) {
return prev;
}
return [
/* [0] */
prev[0],
/* [1] */
prev[1],
/* [2] */
prev[2],
/* [3] */
nextVersion,
/* [4] */
nextSnapshot];
prev[0],
prev[1],
prev[2],
nextVersion,
nextSnapshot
];
});
} catch (e) {
// schedule update
setState(prev => [...prev]);
setState((prev) => [...prev]);
}
};
const unsubscribe = subscribe(source[TARGET], checkForUpdates);

@@ -102,5 +87,4 @@ checkForUpdates();

const isSSR = typeof window === 'undefined' || /ServerSideRendering/.test(window.navigator && window.navigator.userAgent);
const isSSR = typeof window === "undefined" || /ServerSideRendering/.test(window.navigator && window.navigator.userAgent);
const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect;
const useAffectedDebugValue = (state, affected) => {

@@ -113,15 +97,11 @@ const pathList = useRef();

};
const mutableSourceCache = new WeakMap();
const getMutableSource = proxyObject => {
const getMutableSource = (proxyObject) => {
if (!mutableSourceCache.has(proxyObject)) {
mutableSourceCache.set(proxyObject, createMutableSource(proxyObject, getVersion));
}
return mutableSourceCache.get(proxyObject);
};
const useSnapshot = (proxyObject, options) => {
const [, forceUpdate] = useReducer(c => c + 1, 0);
const [, forceUpdate] = useReducer((c) => c + 1, 0);
const affected = new WeakMap();

@@ -136,3 +116,2 @@ const lastAffected = useRef();

lastAffected.current = affected;
if (prevSnapshot.current !== lastSnapshot.current && isDeepChanged(prevSnapshot.current, lastSnapshot.current, affected, new WeakMap())) {

@@ -144,14 +123,11 @@ prevSnapshot.current = lastSnapshot.current;

const notifyInSync = options == null ? void 0 : options.sync;
const sub = useCallback((proxyObject, cb) => subscribe(proxyObject, () => {
const nextSnapshot = snapshot(proxyObject);
const sub = useCallback((proxyObject2, cb) => subscribe(proxyObject2, () => {
const nextSnapshot = snapshot(proxyObject2);
lastSnapshot.current = nextSnapshot;
try {
if (lastAffected.current && !isDeepChanged(prevSnapshot.current, nextSnapshot, lastAffected.current, new WeakMap())) {
// not changed
return;
}
} catch (e) {// ignore if a promise or something is thrown
} catch (e) {
}
prevSnapshot.current = nextSnapshot;

@@ -161,10 +137,6 @@ cb();

const currSnapshot = useMutableSource(getMutableSource(proxyObject), snapshot, sub);
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
if (typeof process === "object" && process.env.NODE_ENV !== "production") {
useAffectedDebugValue(currSnapshot, affected);
}
const proxyCache = useMemo(() => new WeakMap(), []); // per-hook proxyCache
const proxyCache = useMemo(() => new WeakMap(), []);
return createDeepProxy(currSnapshot, affected, proxyCache);

@@ -171,0 +143,0 @@ };

@@ -10,19 +10,19 @@ 'use strict';

function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
n['default'] = e;
return Object.freeze(n);
}
});
}
n['default'] = e;
return Object.freeze(n);
}

@@ -47,4 +47,3 @@

Identifier: function Identifier(p) {
if (inFunction === 0 && // in render
p.node !== proxy && p.node.name === proxy.name) {
if (inFunction === 0 && p.node !== proxy && p.node.name === proxy.name) {
p.node.name = snap.name;

@@ -51,0 +50,0 @@ }

@@ -5,24 +5,21 @@ import * as t from '@babel/types';

const macro = ({
references
}) => {
var _references$useProxy;
(_references$useProxy = references.useProxy) == null ? void 0 : _references$useProxy.forEach(path => {
var _path$parentPath$getF;
const hook = addNamed(path, 'useSnapshot', 'valtio');
const proxy = path.parentPath.get('arguments.0').node;
if (!t.isIdentifier(proxy)) throw new MacroError('no proxy object');
const macro = ({references}) => {
var _a;
(_a = references.useProxy) == null ? void 0 : _a.forEach((path) => {
var _a2;
const hook = addNamed(path, "useSnapshot", "valtio");
const proxy = path.parentPath.get("arguments.0").node;
if (!t.isIdentifier(proxy))
throw new MacroError("no proxy object");
const snap = t.identifier(`valtio_macro_snap_${proxy.name}`);
path.parentPath.parentPath.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(snap, t.callExpression(hook, [proxy]))]));
path.parentPath.parentPath.replaceWith(t.variableDeclaration("const", [
t.variableDeclarator(snap, t.callExpression(hook, [proxy]))
]));
let inFunction = 0;
(_path$parentPath$getF = path.parentPath.getFunctionParent()) == null ? void 0 : _path$parentPath$getF.traverse({
(_a2 = path.parentPath.getFunctionParent()) == null ? void 0 : _a2.traverse({
Identifier(p) {
if (inFunction === 0 && // in render
p.node !== proxy && p.node.name === proxy.name) {
if (inFunction === 0 && p.node !== proxy && p.node.name === proxy.name) {
p.node.name = snap.name;
}
},
Function: {

@@ -32,7 +29,5 @@ enter() {

},
exit() {
--inFunction;
}
}

@@ -42,7 +37,4 @@ });

};
var macro$1 = createMacro(macro, {configName: "valtio"});
var macro$1 = createMacro(macro, {
configName: 'valtio'
});
export default macro$1;
{
"name": "valtio",
"private": false,
"version": "1.0.0",
"version": "1.0.1",
"description": "💊 Valtio makes proxy-state simple for React and Vanilla",

@@ -6,0 +6,0 @@ "main": "index.js",

@@ -5,3 +5,75 @@ import type { NonPromise } from './vanilla';

};
/**
* useSnapshot
*
* Create a local snapshot that catches changes. This hook actually returns a wrapped snapshot in a proxy for
* render optimization instead of a plain object compared to `snapshot()` method.
* Rule of thumb: read from snapshots, mutate the source.
* The component will only re-render when the parts of the state you access have changed, it is render-optimized.
*
* @example A
* function Counter() {
* const snap = useSnapshot(state)
* return (
* <div>
* {snap.count}
* <button onClick={() => ++state.count}>+1</button>
* </div>
* )
* }
*
* [Notes]
* Every object inside your proxy also becomes a proxy (if you don't use "ref"), so you can also use them to create
* the local snapshot as seen on example B.
*
* @example B
* function ProfileName() {
* const snap = useSnapshot(state.profile)
* return (
* <div>
* {snap.name}
* </div>
* )
* }
*
* Beware that you still can replace the child proxy with something else so it will break your snapshot. You can see
* above what happens with the original proxy when you replace the child proxy.
*
* > console.log(state)
* { profile: { name: "valtio" } }
* > childState = state.profile
* > console.log(childState)
* { name: "valtio" }
* > state.profile.name = "react"
* > console.log(childState)
* { name: "react" }
* > state.profile = { name: "new name" }
* > console.log(childState)
* { name: "react" }
* > console.log(state)
* { profile: { name: "new name" } }
*
* `useSnapshot()` depends on the original reference of the child proxy so if you replace it with a new one, the component
* that is subscribed to the old proxy won't receive new updates because it is still subscribed to the old one.
*
* In this case we recommend the example C or D. On both examples you don't need to worry with re-render,
* because it is render-optimized.
*
* @example C
* const snap = useSnapshot(state)
* return (
* <div>
* {snap.profile.name}
* </div>
* )
*
* @example D
* const { profile } = useSnapshot(state)
* return (
* <div>
* {profile.name}
* </div>
* )
*/
export declare const useSnapshot: <T extends object>(proxyObject: T, options?: Options | undefined) => NonPromise<T>;
export {};

@@ -185,3 +185,3 @@ <img src="logo.svg" alt="valtio">

// the code above becomes the code blow.
// the code above becomes the code below.

@@ -188,0 +188,0 @@ import { useSnapshot } from 'valtio'

@@ -5,3 +5,75 @@ import { NonPromise } from './vanilla';

};
/**
* useSnapshot
*
* Create a local snapshot that catches changes. This hook actually returns a wrapped snapshot in a proxy for
* render optimization instead of a plain object compared to `snapshot()` method.
* Rule of thumb: read from snapshots, mutate the source.
* The component will only re-render when the parts of the state you access have changed, it is render-optimized.
*
* @example A
* function Counter() {
* const snap = useSnapshot(state)
* return (
* <div>
* {snap.count}
* <button onClick={() => ++state.count}>+1</button>
* </div>
* )
* }
*
* [Notes]
* Every object inside your proxy also becomes a proxy (if you don't use "ref"), so you can also use them to create
* the local snapshot as seen on example B.
*
* @example B
* function ProfileName() {
* const snap = useSnapshot(state.profile)
* return (
* <div>
* {snap.name}
* </div>
* )
* }
*
* Beware that you still can replace the child proxy with something else so it will break your snapshot. You can see
* above what happens with the original proxy when you replace the child proxy.
*
* > console.log(state)
* { profile: { name: "valtio" } }
* > childState = state.profile
* > console.log(childState)
* { name: "valtio" }
* > state.profile.name = "react"
* > console.log(childState)
* { name: "react" }
* > state.profile = { name: "new name" }
* > console.log(childState)
* { name: "react" }
* > console.log(state)
* { profile: { name: "new name" } }
*
* `useSnapshot()` depends on the original reference of the child proxy so if you replace it with a new one, the component
* that is subscribed to the old proxy won't receive new updates because it is still subscribed to the old one.
*
* In this case we recommend the example C or D. On both examples you don't need to worry with re-render,
* because it is render-optimized.
*
* @example C
* const snap = useSnapshot(state)
* return (
* <div>
* {snap.profile.name}
* </div>
* )
*
* @example D
* const { profile } = useSnapshot(state)
* return (
* <div>
* {profile.name}
* </div>
* )
*/
export declare const useSnapshot: <T extends object>(proxyObject: T, options?: Options | undefined) => NonPromise<T>;
export {};

@@ -8,14 +8,2 @@ 'use strict';

/**
* subscribeKey
*
* The subscribeKey utility enables subscription to a primitive subproperty of a given state proxy.
* Subscriptions created with subscribeKey will only fire when the specified property changes.
* notifyInSync: same as the parameter to subscribe(); true disables batching of subscriptions.
*
* @example
* import { subscribeKey } from 'valtio/utils'
* subscribeKey(state, 'count', (v) => console.log('state.count has changed to', v))
*/
var subscribeKey = function subscribeKey(proxyObject, key, callback, notifyInSync) {

@@ -31,14 +19,2 @@ var prevValue = proxyObject[key];

};
/**
* devtools
*
* This is to connect with [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension).
* Limitation: Only plain objects/values are supported.
*
* @example
* import { devtools } from 'valtio/utils'
* const state = proxy({ count: 0, text: 'hello' })
* const unsub = devtools(state, 'state name')
*/
var devtools = function devtools(proxyObject, name) {

@@ -94,23 +70,2 @@ var extension;

};
/**
* addComputed
*
* This adds computed values to an existing proxy object.
*
* [Notes]
* This comes with a cost and overlaps with useSnapshot.
* Do not try to optimize too early. It can worsen the performance.
* Measurement and comparison will be very important.
*
* @example
* import { proxy } from 'valtio'
* import { addComputed } from 'valtio/utils'
* const state = proxy({
* count: 1,
* })
* addComputed(state, {
* doubled: snap => snap.count * 2,
* })
*/
var addComputed = function addComputed(proxyObject, computedFns, targetObject) {

@@ -135,11 +90,13 @@ if (targetObject === void 0) {

affected = new WeakMap();
var value = get(proxyCompare.createDeepProxy(nextSnapshot, affected));
var _value = get(proxyCompare.createDeepProxy(nextSnapshot, affected));
prevSnapshot = nextSnapshot;
if (value instanceof Promise) {
if (_value instanceof Promise) {
pending = true;
value.then(function (v) {
_value.then(function (v) {
targetObject[key] = v;
}).catch(function (e) {
// not ideal but best effort for throwing error with proxy
targetObject[key] = new Proxy({}, {

@@ -155,35 +112,10 @@ get: function get() {

targetObject[key] = value;
targetObject[key] = _value;
}
};
vanilla.subscribe(proxyObject, callback, true);
vanilla.subscribe(proxyObject, callback);
callback();
});
};
/**
* proxyWithComputed
*
* This is to create a proxy with initial object and additional object,
* which specifies getters for computed values with dependency tracking.
* It also accepts optional setters for computed values.
*
* [Notes]
* This comes with a cost and overlaps with useSnapshot.
* Do not try to optimize too early. It can worsen the performance.
* Measurement and comparison will be very important.
*
* @example
* import { proxyWithComputed } from 'valtio/utils'
* const state = proxyWithComputed({
* count: 1,
* }, {
* doubled: snap => snap.count * 2, // getter only
* tripled: {
* get: snap => snap.count * 3,
* set: (state, newValue) => { state.count = newValue / 3 }
* }, // with optional setter
* })
*/
var proxyWithComputed = function proxyWithComputed(initialObject, computedFns) {

@@ -190,0 +122,0 @@ Object.keys(computedFns).forEach(function (key) {

import { createDeepProxy, isDeepChanged } from 'proxy-compare';
import { subscribe, snapshot, proxy } from './vanilla';
/**
* subscribeKey
*
* The subscribeKey utility enables subscription to a primitive subproperty of a given state proxy.
* Subscriptions created with subscribeKey will only fire when the specified property changes.
* notifyInSync: same as the parameter to subscribe(); true disables batching of subscriptions.
*
* @example
* import { subscribeKey } from 'valtio/utils'
* subscribeKey(state, 'count', (v) => console.log('state.count has changed to', v))
*/
const subscribeKey = (proxyObject, key, callback, notifyInSync) => {

@@ -20,3 +8,2 @@ let prevValue = proxyObject[key];

const nextValue = proxyObject[key];
if (!Object.is(prevValue, nextValue)) {

@@ -27,33 +14,16 @@ callback(prevValue = nextValue);

};
/**
* devtools
*
* This is to connect with [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension).
* Limitation: Only plain objects/values are supported.
*
* @example
* import { devtools } from 'valtio/utils'
* const state = proxy({ count: 0, text: 'hello' })
* const unsub = devtools(state, 'state name')
*/
const devtools = (proxyObject, name) => {
let extension;
try {
extension = window.__REDUX_DEVTOOLS_EXTENSION__;
} catch (_unused) {}
} catch {
}
if (!extension) {
if (typeof process === 'object' && process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
console.warn('[Warning] Please install/enable Redux devtools extension');
if (typeof process === "object" && process.env.NODE_ENV === "development" && typeof window !== "undefined") {
console.warn("[Warning] Please install/enable Redux devtools extension");
}
return;
}
let isTimeTraveling = false;
const devtools = extension.connect({
name
});
const devtools2 = extension.connect({name});
const unsub1 = subscribe(proxyObject, () => {

@@ -63,24 +33,20 @@ if (isTimeTraveling) {

} else {
devtools.send(`Update - ${new Date().toLocaleString()}`, snapshot(proxyObject));
devtools2.send(`Update - ${new Date().toLocaleString()}`, snapshot(proxyObject));
}
});
const unsub2 = devtools.subscribe(message => {
var _message$payload3;
if (message.type === 'DISPATCH' && message.state) {
var _message$payload, _message$payload2;
if (((_message$payload = message.payload) == null ? void 0 : _message$payload.type) === 'JUMP_TO_ACTION' || ((_message$payload2 = message.payload) == null ? void 0 : _message$payload2.type) === 'JUMP_TO_STATE') {
const unsub2 = devtools2.subscribe((message) => {
var _a, _b, _c;
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 nextValue = JSON.parse(message.state);
Object.keys(nextValue).forEach(key => {
Object.keys(nextValue).forEach((key) => {
proxyObject[key] = nextValue[key];
});
} else if (message.type === 'DISPATCH' && ((_message$payload3 = message.payload) == null ? void 0 : _message$payload3.type) === 'COMMIT') {
devtools.init(snapshot(proxyObject));
} else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") {
devtools2.init(snapshot(proxyObject));
}
});
devtools.init(snapshot(proxyObject));
devtools2.init(snapshot(proxyObject));
return () => {

@@ -91,29 +57,7 @@ unsub1();

};
/**
* addComputed
*
* This adds computed values to an existing proxy object.
*
* [Notes]
* This comes with a cost and overlaps with useSnapshot.
* Do not try to optimize too early. It can worsen the performance.
* Measurement and comparison will be very important.
*
* @example
* import { proxy } from 'valtio'
* import { addComputed } from 'valtio/utils'
* const state = proxy({
* count: 1,
* })
* addComputed(state, {
* doubled: snap => snap.count * 2,
* })
*/
const addComputed = (proxyObject, computedFns, targetObject = proxyObject) => {
Object.keys(computedFns).forEach(key => {
Object.keys(computedFns).forEach((key) => {
if (Object.getOwnPropertyDescriptor(targetObject, key)) {
throw new Error('object property already defined');
throw new Error("object property already defined");
}
const get = computedFns[key];

@@ -123,6 +67,4 @@ let prevSnapshot;

let pending = false;
const callback = () => {
const nextSnapshot = snapshot(proxyObject);
if (!pending && (!prevSnapshot || isDeepChanged(prevSnapshot, nextSnapshot, affected))) {

@@ -132,9 +74,7 @@ affected = new WeakMap();

prevSnapshot = nextSnapshot;
if (value instanceof Promise) {
pending = true;
value.then(v => {
value.then((v) => {
targetObject[key] = v;
}).catch(e => {
// not ideal but best effort for throwing error with proxy
}).catch((e) => {
targetObject[key] = new Proxy({}, {

@@ -144,3 +84,2 @@ get() {

}
});

@@ -151,49 +90,16 @@ }).finally(() => {

}
targetObject[key] = value;
}
};
subscribe(proxyObject, callback, true);
subscribe(proxyObject, callback);
callback();
});
};
/**
* proxyWithComputed
*
* This is to create a proxy with initial object and additional object,
* which specifies getters for computed values with dependency tracking.
* It also accepts optional setters for computed values.
*
* [Notes]
* This comes with a cost and overlaps with useSnapshot.
* Do not try to optimize too early. It can worsen the performance.
* Measurement and comparison will be very important.
*
* @example
* import { proxyWithComputed } from 'valtio/utils'
* const state = proxyWithComputed({
* count: 1,
* }, {
* doubled: snap => snap.count * 2, // getter only
* tripled: {
* get: snap => snap.count * 3,
* set: (state, newValue) => { state.count = newValue / 3 }
* }, // with optional setter
* })
*/
const proxyWithComputed = (initialObject, computedFns) => {
Object.keys(computedFns).forEach(key => {
Object.keys(computedFns).forEach((key) => {
if (Object.getOwnPropertyDescriptor(initialObject, key)) {
throw new Error('object property already defined');
throw new Error("object property already defined");
}
const computedFn = computedFns[key];
const {
get,
set
} = typeof computedFn === 'function' ? {
get: computedFn
} : computedFn;
const {get, set} = typeof computedFn === "function" ? {get: computedFn} : computedFn;
let computedValue;

@@ -203,6 +109,4 @@ let prevSnapshot;

const desc = {};
desc.get = () => {
const nextSnapshot = snapshot(proxyObject);
if (!prevSnapshot || isDeepChanged(prevSnapshot, nextSnapshot, affected)) {

@@ -213,10 +117,7 @@ affected = new WeakMap();

}
return computedValue;
};
if (set) {
desc.set = newValue => set(proxyObject, newValue);
desc.set = (newValue) => set(proxyObject, newValue);
}
Object.defineProperty(initialObject, key, desc);

@@ -223,0 +124,0 @@ });

@@ -62,4 +62,3 @@ 'use strict';

var snapshot = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
proxyCompare.markToTrack(snapshot, true); // mark to track
proxyCompare.markToTrack(snapshot, true);
snapshotCache.set(receiver, {

@@ -73,4 +72,3 @@ version: version,

if (refSet.has(value)) {
proxyCompare.markToTrack(value, false); // mark not to track
proxyCompare.markToTrack(value, false);
snapshot[key] = value;

@@ -77,0 +75,0 @@ } else if (!isSupportedObject(value)) {

@@ -9,9 +9,7 @@ import { getUntrackedObject, markToTrack } from 'proxy-compare';

const refSet = new WeakSet();
const ref = o => {
const ref = (o) => {
refSet.add(o);
return o;
};
const isSupportedObject = x => typeof x === 'object' && x !== null && (Array.isArray(x) || !x[Symbol.iterator]) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer);
const isSupportedObject = (x) => typeof x === "object" && x !== null && (Array.isArray(x) || !x[Symbol.iterator]) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer);
const proxyCache = new WeakMap();

@@ -22,68 +20,53 @@ let globalVersion = 1;

if (!isSupportedObject(initialObject)) {
throw new Error('unsupported object type');
throw new Error("unsupported object type");
}
if (proxyCache.has(initialObject)) {
return proxyCache.get(initialObject);
}
let version = globalVersion;
const listeners = new Set();
const notifyUpdate = nextVersion => {
const notifyUpdate = (nextVersion) => {
if (!nextVersion) {
nextVersion = ++globalVersion;
}
if (version !== nextVersion) {
version = nextVersion;
listeners.forEach(listener => listener(nextVersion));
listeners.forEach((listener) => listener(nextVersion));
}
};
const createSnapshot = (target, receiver) => {
const cache = snapshotCache.get(receiver);
if (cache && cache.version === version) {
return cache.snapshot;
}
const snapshot = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
markToTrack(snapshot, true); // mark to track
snapshotCache.set(receiver, {
version,
snapshot
});
Reflect.ownKeys(target).forEach(key => {
const snapshot2 = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
markToTrack(snapshot2, true);
snapshotCache.set(receiver, {version, snapshot: snapshot2});
Reflect.ownKeys(target).forEach((key) => {
const value = target[key];
if (refSet.has(value)) {
markToTrack(value, false); // mark not to track
snapshot[key] = value;
markToTrack(value, false);
snapshot2[key] = value;
} else if (!isSupportedObject(value)) {
snapshot[key] = value;
snapshot2[key] = value;
} else if (value instanceof Promise) {
if (PROMISE_RESULT in value) {
snapshot[key] = value[PROMISE_RESULT];
snapshot2[key] = value[PROMISE_RESULT];
} else {
const errorOrPromise = value[PROMISE_ERROR] || value;
Object.defineProperty(snapshot, key, {
Object.defineProperty(snapshot2, key, {
get() {
throw errorOrPromise;
}
});
}
} else if (value[VERSION]) {
snapshot[key] = value[SNAPSHOT];
snapshot2[key] = value[SNAPSHOT];
} else {
snapshot[key] = value;
snapshot2[key] = value;
}
});
Object.freeze(snapshot);
return snapshot;
Object.freeze(snapshot2);
return snapshot2;
};
const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject));

@@ -95,54 +78,40 @@ const proxyObject = new Proxy(baseObject, {

}
if (prop === LISTENERS) {
return listeners;
}
if (prop === SNAPSHOT) {
return createSnapshot(target, receiver);
}
return target[prop];
},
deleteProperty(target, prop) {
const prevValue = target[prop];
const childListeners = prevValue && prevValue[LISTENERS];
if (childListeners) {
childListeners.delete(notifyUpdate);
}
const deleted = Reflect.deleteProperty(target, prop);
if (deleted) {
notifyUpdate();
}
return deleted;
},
set(target, prop, value) {
var _Object$getOwnPropert;
var _a;
const prevValue = target[prop];
if (Object.is(prevValue, value)) {
return true;
}
const childListeners = prevValue && prevValue[LISTENERS];
if (childListeners) {
childListeners.delete(notifyUpdate);
}
if (refSet.has(value) || !isSupportedObject(value) || (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(target, prop)) != null && _Object$getOwnPropert.set) {
if (refSet.has(value) || !isSupportedObject(value) || ((_a = Object.getOwnPropertyDescriptor(target, prop)) == null ? void 0 : _a.set)) {
target[prop] = value;
} else if (value instanceof Promise) {
target[prop] = value.then(v => {
target[prop] = value.then((v) => {
target[prop][PROMISE_RESULT] = v;
notifyUpdate();
return v;
}).catch(e => {
}).catch((e) => {
target[prop][PROMISE_ERROR] = e;

@@ -153,3 +122,2 @@ notifyUpdate();

value = getUntrackedObject(value) || value;
if (value[LISTENERS]) {

@@ -160,15 +128,11 @@ target[prop] = value;

}
target[prop][LISTENERS].add(notifyUpdate);
}
notifyUpdate();
return true;
}
});
proxyCache.set(initialObject, proxyObject);
Reflect.ownKeys(initialObject).forEach(key => {
Reflect.ownKeys(initialObject).forEach((key) => {
const desc = Object.getOwnPropertyDescriptor(initialObject, key);
if (desc.get || desc.set) {

@@ -182,17 +146,14 @@ Object.defineProperty(baseObject, key, desc);

};
const getVersion = proxyObject => {
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[VERSION])) {
throw new Error('Please use proxy object');
const getVersion = (proxyObject) => {
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[VERSION])) {
throw new Error("Please use proxy object");
}
return proxyObject[VERSION];
};
const subscribe = (proxyObject, callback, notifyInSync) => {
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[LISTENERS])) {
throw new Error('Please use proxy object');
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[LISTENERS])) {
throw new Error("Please use proxy object");
}
let pendingVersion = 0;
const listener = nextVersion => {
const listener = (nextVersion) => {
if (notifyInSync) {

@@ -202,3 +163,2 @@ callback();

}
pendingVersion = nextVersion;

@@ -211,3 +171,2 @@ Promise.resolve().then(() => {

};
proxyObject[LISTENERS].add(listener);

@@ -218,7 +177,6 @@ return () => {

};
const snapshot = proxyObject => {
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[SNAPSHOT])) {
throw new Error('Please use proxy object');
const snapshot = (proxyObject) => {
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[SNAPSHOT])) {
throw new Error("Please use proxy object");
}
return proxyObject[SNAPSHOT];

@@ -225,0 +183,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc