@yiin/reactive-proxy-state 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -17
- package/dist/computed.d.ts +9 -4
- package/dist/computed.js +28 -29
- package/dist/reactive.js +47 -32
- package/dist/ref.d.ts +8 -7
- package/dist/ref.js +22 -22
- package/dist/state.js +89 -85
- package/dist/types.d.ts +1 -2
- package/dist/utils.d.ts +5 -10
- package/dist/utils.js +50 -87
- package/dist/watch.d.ts +3 -8
- package/dist/watch.js +15 -45
- package/dist/watchEffect.d.ts +10 -5
- package/dist/watchEffect.js +61 -46
- package/dist/wrapArray.js +22 -32
- package/dist/wrapMap.js +26 -37
- package/dist/wrapSet.js +27 -44
- package/package.json +8 -3
package/dist/state.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { pathCache, setInPathCache } from './utils';
|
|
2
|
-
//
|
|
2
|
+
// helper to abstract getting a value from a map or object property
|
|
3
3
|
function getValue(obj, key) {
|
|
4
4
|
if (obj instanceof Map)
|
|
5
5
|
return obj.get(key);
|
|
6
6
|
return obj[key];
|
|
7
7
|
}
|
|
8
|
+
// helper to abstract setting a value on a map or object property
|
|
8
9
|
function setValue(obj, key, value) {
|
|
9
10
|
if (obj instanceof Map)
|
|
10
11
|
obj.set(key, value);
|
|
11
12
|
else
|
|
12
13
|
obj[key] = value;
|
|
13
14
|
}
|
|
15
|
+
// helper to abstract deleting a key from a map or object
|
|
14
16
|
function deleteValue(obj, key) {
|
|
15
17
|
if (obj instanceof Map)
|
|
16
18
|
obj.delete(key);
|
|
17
19
|
else
|
|
18
20
|
delete obj[key];
|
|
19
21
|
}
|
|
20
|
-
//
|
|
22
|
+
// dispatch table for applying state events based on action type
|
|
23
|
+
// this avoids a large switch statement and makes adding new actions easier
|
|
21
24
|
const actionHandlers = {
|
|
22
25
|
'set': function (parent, key, event) {
|
|
23
26
|
setValue(parent, key, event.newValue);
|
|
@@ -27,36 +30,36 @@ const actionHandlers = {
|
|
|
27
30
|
},
|
|
28
31
|
'array-push': function (targetArray, _keyIgnored, event) {
|
|
29
32
|
if (!Array.isArray(targetArray)) {
|
|
30
|
-
console.warn(`
|
|
33
|
+
console.warn(`expected array at path ${event.path.join('.')}`);
|
|
31
34
|
return;
|
|
32
35
|
}
|
|
33
36
|
if (!event.items) {
|
|
34
37
|
console.warn('array-push event missing items');
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
37
|
-
//
|
|
40
|
+
// event.key is the starting index, but push always adds to end
|
|
38
41
|
targetArray.push(...event.items);
|
|
39
42
|
},
|
|
40
43
|
'array-pop': function (targetArray, _keyIgnored, event) {
|
|
41
44
|
if (!Array.isArray(targetArray)) {
|
|
42
|
-
console.warn(`
|
|
45
|
+
console.warn(`expected array at path ${event.path.join('.')}`);
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
|
-
//
|
|
48
|
+
// event info (key, oldvalue) not needed to perform pop
|
|
46
49
|
if (targetArray.length > 0) {
|
|
47
50
|
targetArray.pop();
|
|
48
51
|
}
|
|
49
52
|
},
|
|
50
53
|
'array-splice': function (targetArray, _keyIgnored, event) {
|
|
51
54
|
if (!Array.isArray(targetArray)) {
|
|
52
|
-
console.warn(`
|
|
55
|
+
console.warn(`expected array at path ${event.path.join('.')}`);
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
58
|
if (event.key === undefined || event.deleteCount === undefined) {
|
|
56
|
-
console.warn('array-splice event missing key or
|
|
59
|
+
console.warn('array-splice event missing key or deletecount');
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
|
-
//
|
|
62
|
+
// handle splice with or without items to insert
|
|
60
63
|
if (event.items && event.items.length > 0) {
|
|
61
64
|
targetArray.splice(event.key, event.deleteCount, ...event.items);
|
|
62
65
|
}
|
|
@@ -66,142 +69,143 @@ const actionHandlers = {
|
|
|
66
69
|
},
|
|
67
70
|
'array-shift': function (targetArray, _keyIgnored, event) {
|
|
68
71
|
if (!Array.isArray(targetArray)) {
|
|
69
|
-
console.warn(`
|
|
72
|
+
console.warn(`expected array at path ${event.path.join('.')}`);
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
72
|
-
//
|
|
75
|
+
// event info not needed to perform shift
|
|
73
76
|
if (targetArray.length > 0) {
|
|
74
77
|
targetArray.shift();
|
|
75
78
|
}
|
|
76
79
|
},
|
|
77
80
|
'array-unshift': function (targetArray, _keyIgnored, event) {
|
|
78
81
|
if (!Array.isArray(targetArray)) {
|
|
79
|
-
console.warn(`
|
|
82
|
+
console.warn(`expected array at path ${event.path.join('.')}`);
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
if (!event.items) {
|
|
83
86
|
console.warn('array-unshift event missing items');
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
|
-
//
|
|
89
|
+
// event.key (always 0) not needed to perform unshift
|
|
87
90
|
targetArray.unshift(...event.items);
|
|
88
91
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.warn(`
|
|
92
|
+
// note: for map/set operations originating from the proxy wrapper (e.g., map.set(k,v)),
|
|
93
|
+
// the *event path* points to the map itself, and event.key/event.value hold the map's key/value.
|
|
94
|
+
// however, the actionHandlers expect `target` to be the collection and `key` to be the map/set key.
|
|
95
|
+
// the logic in `updateState` below handles finding the correct target collection first.
|
|
96
|
+
'map-set': function (targetMap, _parentKeyIgnored, event) {
|
|
97
|
+
if (!(targetMap instanceof Map)) {
|
|
98
|
+
console.warn(`expected map at path ${event.path.join('.')}`);
|
|
99
|
+
return;
|
|
96
100
|
}
|
|
101
|
+
targetMap.set(event.key, event.newValue);
|
|
97
102
|
},
|
|
98
|
-
'map-delete': function (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
console.warn(`Expected Map at path ${key}`);
|
|
103
|
+
'map-delete': function (targetMap, _parentKeyIgnored, event) {
|
|
104
|
+
if (!(targetMap instanceof Map)) {
|
|
105
|
+
console.warn(`expected map at path ${event.path.join('.')}`);
|
|
106
|
+
return;
|
|
105
107
|
}
|
|
108
|
+
targetMap.delete(event.key);
|
|
106
109
|
},
|
|
107
|
-
'map-clear': function (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
console.warn(`Expected Map at path ${key}`);
|
|
110
|
+
'map-clear': function (targetMap, _parentKeyIgnored, event) {
|
|
111
|
+
if (!(targetMap instanceof Map)) {
|
|
112
|
+
console.warn(`expected map at path ${event.path.join('.')}`);
|
|
113
|
+
return;
|
|
114
114
|
}
|
|
115
|
+
targetMap.clear();
|
|
115
116
|
},
|
|
116
117
|
'set-add': function (targetSet, _keyIgnored, event) {
|
|
117
|
-
if (targetSet instanceof Set) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
else {
|
|
121
|
-
console.warn(`Expected Set at path ${event.path.join('.')}`);
|
|
118
|
+
if (!(targetSet instanceof Set)) {
|
|
119
|
+
console.warn(`expected set at path ${event.path.join('.')}`);
|
|
120
|
+
return;
|
|
122
121
|
}
|
|
122
|
+
targetSet.add(event.value);
|
|
123
123
|
},
|
|
124
124
|
'set-delete': function (targetSet, _keyIgnored, event) {
|
|
125
|
-
if (targetSet instanceof Set) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
else {
|
|
129
|
-
console.warn(`Expected Set at path ${event.path.join('.')}`);
|
|
125
|
+
if (!(targetSet instanceof Set)) {
|
|
126
|
+
console.warn(`expected set at path ${event.path.join('.')}`);
|
|
127
|
+
return;
|
|
130
128
|
}
|
|
129
|
+
targetSet.delete(event.value);
|
|
131
130
|
},
|
|
132
131
|
'set-clear': function (targetSet, _keyIgnored, event) {
|
|
133
|
-
if (targetSet instanceof Set) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else {
|
|
137
|
-
console.warn(`Expected Set at path ${event.path.join('.')}`);
|
|
132
|
+
if (!(targetSet instanceof Set)) {
|
|
133
|
+
console.warn(`expected set at path ${event.path.join('.')}`);
|
|
134
|
+
return;
|
|
138
135
|
}
|
|
136
|
+
targetSet.clear();
|
|
139
137
|
}
|
|
140
138
|
};
|
|
139
|
+
// applies a state change event to a plain javascript object/array (the target state)
|
|
141
140
|
export function updateState(root, event) {
|
|
142
141
|
const { action, path } = event;
|
|
143
|
-
if (path.length === 0) {
|
|
144
|
-
console.warn('
|
|
142
|
+
if (!path || path.length === 0) {
|
|
143
|
+
console.warn('event path is empty, cannot apply update', event);
|
|
145
144
|
return;
|
|
146
145
|
}
|
|
147
146
|
const handler = actionHandlers[action];
|
|
148
147
|
if (!handler) {
|
|
149
|
-
|
|
148
|
+
// maybe allow custom handlers or ignore unknown actions?
|
|
149
|
+
console.error(`unhandled action type: ${action}`, event);
|
|
150
|
+
return;
|
|
151
|
+
// throw new Error(`Unhandled action: ${action}`);
|
|
150
152
|
}
|
|
151
|
-
//
|
|
153
|
+
// determine the target object/collection and the specific key (if applicable)
|
|
154
|
+
// based on the action type and the event path.
|
|
152
155
|
let targetForHandler;
|
|
153
|
-
let keyForHandler = null; //
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
let keyForHandler = null; // key passed to handler, relevant for set/delete
|
|
157
|
+
// path resolution logic differs based on action type:
|
|
158
|
+
// - set/delete: path leads to the *value* being set/deleted, so we need the parent object and the final path segment (key).
|
|
159
|
+
// - array-*/map-*/set-*: path leads to the *collection* itself.
|
|
160
|
+
if (action === 'set' || action === 'delete') {
|
|
161
|
+
// need the parent object and the final key
|
|
156
162
|
if (path.length === 1) {
|
|
157
|
-
targetForHandler = root;
|
|
163
|
+
targetForHandler = root; // parent is the root object
|
|
158
164
|
keyForHandler = path[0];
|
|
159
165
|
}
|
|
160
166
|
else {
|
|
161
|
-
const
|
|
167
|
+
const parentPath = path.slice(0, -1);
|
|
168
|
+
const parentPathKey = parentPath.join('.');
|
|
169
|
+
// try cache first for performance
|
|
162
170
|
let parent = pathCache.get(root)?.get(parentPathKey);
|
|
163
171
|
if (parent === undefined) {
|
|
164
|
-
|
|
172
|
+
// traverse path manually if not in cache
|
|
173
|
+
parent = parentPath.reduce((acc, key) => acc ? getValue(acc, key) : undefined, root);
|
|
165
174
|
if (parent !== undefined)
|
|
166
|
-
setInPathCache(root, parentPathKey, parent);
|
|
175
|
+
setInPathCache(root, parentPathKey, parent); // cache if found
|
|
167
176
|
}
|
|
168
177
|
if (parent === undefined) {
|
|
169
|
-
console.warn(`
|
|
178
|
+
console.warn(`parent path ${parentPathKey} not found for action ${action}`);
|
|
170
179
|
return;
|
|
171
180
|
}
|
|
172
181
|
targetForHandler = parent;
|
|
173
182
|
keyForHandler = path[path.length - 1];
|
|
174
183
|
}
|
|
175
184
|
}
|
|
176
|
-
else if (action.startsWith('array-') || action.startsWith('set-')) {
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
console.warn(`Parent path ${parentPathKey} not found for action ${action}`);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
targetForHandler = getValue(parent, path[path.length - 1]);
|
|
194
|
-
}
|
|
195
|
-
if (targetForHandler === undefined) {
|
|
196
|
-
console.warn(`Target collection at path ${path.join('.')} not found for action ${action}`);
|
|
185
|
+
else if (action.startsWith('array-') || action.startsWith('map-') || action.startsWith('set-')) {
|
|
186
|
+
// need the collection object itself (array, map, or set)
|
|
187
|
+
const targetPath = path;
|
|
188
|
+
const targetPathKey = targetPath.join('.');
|
|
189
|
+
// try cache first
|
|
190
|
+
let targetCollection = pathCache.get(root)?.get(targetPathKey);
|
|
191
|
+
if (targetCollection === undefined) {
|
|
192
|
+
// traverse path manually if not in cache
|
|
193
|
+
targetCollection = targetPath.reduce((acc, key) => acc ? getValue(acc, key) : undefined, root);
|
|
194
|
+
if (targetCollection !== undefined)
|
|
195
|
+
setInPathCache(root, targetPathKey, targetCollection); // cache if found
|
|
196
|
+
}
|
|
197
|
+
if (targetCollection === undefined) {
|
|
198
|
+
console.warn(`target collection at path ${targetPathKey} not found for action ${action}`);
|
|
197
199
|
return;
|
|
198
200
|
}
|
|
201
|
+
targetForHandler = targetCollection;
|
|
202
|
+
// keyForHandler remains null for these actions as the handler operates directly on the collection
|
|
199
203
|
}
|
|
200
204
|
else {
|
|
201
|
-
//
|
|
202
|
-
console.error(`
|
|
205
|
+
// this should not be reachable if handler lookup succeeded
|
|
206
|
+
console.error(`unexpected action type passed checks: ${action}`);
|
|
203
207
|
return;
|
|
204
208
|
}
|
|
205
|
-
//
|
|
209
|
+
// call the appropriate handler with the resolved target and key
|
|
206
210
|
handler(targetForHandler, keyForHandler, event);
|
|
207
211
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,11 +3,10 @@ export type ActionType = 'set' | 'delete' | 'array-push' | 'array-pop' | 'array-
|
|
|
3
3
|
export interface StateEvent {
|
|
4
4
|
action: ActionType;
|
|
5
5
|
path: Path;
|
|
6
|
-
oldValue?: any;
|
|
7
6
|
newValue?: any;
|
|
7
|
+
oldValue?: any;
|
|
8
8
|
key?: any;
|
|
9
9
|
value?: any;
|
|
10
|
-
args?: any[];
|
|
11
10
|
items?: any[];
|
|
12
11
|
deleteCount?: number;
|
|
13
12
|
oldValues?: any[];
|
package/dist/utils.d.ts
CHANGED
|
@@ -8,18 +8,13 @@ export declare function setInPathCache(root: object, pathKey: string, value: any
|
|
|
8
8
|
export declare function getPathConcat(path: string): any[] | undefined;
|
|
9
9
|
export declare function setPathConcat(path: string, value: any[]): void;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param seen - A Set to handle circular references.
|
|
11
|
+
* recursively traverses an object or array, accessing each property/element.
|
|
12
|
+
* used by `watch` with `deep: true` to establish dependencies on all nested properties.
|
|
13
|
+
* the actual tracking is done by the proxy `get` handlers triggered during traversal.
|
|
15
14
|
*/
|
|
16
15
|
export declare function traverse(value: any, seen?: Set<any>): any;
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* Includes cycle detection.
|
|
21
|
-
* @param value The value to clone.
|
|
22
|
-
* @param seen A WeakMap to handle circular references during recursion.
|
|
23
|
-
* @returns A deep clone of the value.
|
|
17
|
+
* creates a deep clone of a value.
|
|
18
|
+
* includes cycle detection using a weakmap.
|
|
24
19
|
*/
|
|
25
20
|
export declare function deepClone<T>(value: T, seen?: WeakMap<WeakKey, any>): T;
|
package/dist/utils.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
//
|
|
1
|
+
// cache for memoized deepEqual results, using weakmap to avoid memory leaks
|
|
2
2
|
const deepEqualCache = new WeakMap();
|
|
3
|
-
const MAX_CACHE_SIZE = 1000;
|
|
4
|
-
//
|
|
3
|
+
const MAX_CACHE_SIZE = 1000;
|
|
4
|
+
// path traversal cache (object -> pathString -> value) with lru-like behavior
|
|
5
|
+
// weakmap for the root object ensures the cache entry is garbage collected when the object is
|
|
5
6
|
export const pathCache = new WeakMap();
|
|
6
|
-
const pathCacheSize = new WeakMap();
|
|
7
|
-
//
|
|
7
|
+
const pathCacheSize = new WeakMap(); // track individual map sizes
|
|
8
|
+
// path concatenation cache ('a.b.c' -> ['a', 'b', 'c']) with size limit
|
|
8
9
|
export const pathConcatCache = new Map();
|
|
9
10
|
const MAX_PATH_CACHE_SIZE = 1000;
|
|
10
|
-
//
|
|
11
|
+
// global weakmap for circular reference detection during deep operations like clone or equal
|
|
11
12
|
export const globalSeen = new WeakMap();
|
|
12
|
-
//
|
|
13
|
+
// global cache to reuse proxy wrappers for the same original object
|
|
13
14
|
export const wrapperCache = new WeakMap();
|
|
15
|
+
// simple lru-like cleanup for individual object path caches
|
|
14
16
|
function cleanupPathCache(root) {
|
|
15
17
|
const cache = pathCache.get(root);
|
|
16
18
|
if (cache && pathCacheSize.get(root) > MAX_CACHE_SIZE) {
|
|
17
|
-
//
|
|
19
|
+
// clear oldest entries (first 20%)
|
|
18
20
|
const entriesToRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
|
|
19
21
|
let count = 0;
|
|
20
22
|
for (const key of cache.keys()) {
|
|
@@ -26,9 +28,10 @@ function cleanupPathCache(root) {
|
|
|
26
28
|
pathCacheSize.set(root, MAX_CACHE_SIZE - entriesToRemove);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
// simple lru-like cleanup for the global path concatenation cache
|
|
29
32
|
function cleanupPathConcatCache() {
|
|
30
33
|
if (pathConcatCache.size > MAX_PATH_CACHE_SIZE) {
|
|
31
|
-
//
|
|
34
|
+
// remove oldest entries (first 20%)
|
|
32
35
|
const entriesToRemove = Math.floor(MAX_PATH_CACHE_SIZE * 0.2);
|
|
33
36
|
let count = 0;
|
|
34
37
|
for (const key of pathConcatCache.keys()) {
|
|
@@ -40,216 +43,176 @@ function cleanupPathConcatCache() {
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
export function deepEqual(a, b, seen = globalSeen) {
|
|
43
|
-
// Fast path for primitive equality
|
|
44
46
|
if (a === b)
|
|
45
47
|
return true;
|
|
46
|
-
// Fast path for null/undefined
|
|
47
48
|
if (a == null || b == null)
|
|
48
49
|
return a === b;
|
|
49
|
-
// Fast path for different types
|
|
50
50
|
if (typeof a !== typeof b)
|
|
51
51
|
return false;
|
|
52
|
-
// Fast path for Date objects
|
|
53
52
|
if (a instanceof Date && b instanceof Date)
|
|
54
53
|
return a.getTime() === b.getTime();
|
|
55
|
-
// Fast path for non-objects
|
|
56
54
|
if (typeof a !== 'object')
|
|
57
55
|
return false;
|
|
58
|
-
// Fast path for different array types
|
|
59
56
|
if (Array.isArray(a) !== Array.isArray(b))
|
|
60
57
|
return false;
|
|
61
|
-
//
|
|
58
|
+
// handle circular references
|
|
62
59
|
if (seen.has(a))
|
|
63
60
|
return seen.get(a) === b;
|
|
64
61
|
seen.set(a, b);
|
|
65
|
-
//
|
|
62
|
+
// check memoization cache before diving deeper
|
|
66
63
|
if (deepEqualCache.has(a) && deepEqualCache.get(a)?.has(b)) {
|
|
67
64
|
return deepEqualCache.get(a).get(b);
|
|
68
65
|
}
|
|
69
|
-
// Initialize cache for this object if needed
|
|
70
66
|
if (!deepEqualCache.has(a)) {
|
|
71
67
|
deepEqualCache.set(a, new WeakMap());
|
|
72
68
|
}
|
|
73
69
|
let result;
|
|
74
|
-
// Compare arrays
|
|
75
70
|
if (Array.isArray(a)) {
|
|
76
|
-
|
|
77
|
-
result = false;
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
result = a.every((val, idx) => deepEqual(val, b[idx], seen));
|
|
81
|
-
}
|
|
71
|
+
result = a.length === b.length && a.every((val, idx) => deepEqual(val, b[idx], seen));
|
|
82
72
|
}
|
|
83
73
|
else {
|
|
84
|
-
// Compare objects
|
|
85
74
|
const keysA = Object.keys(a);
|
|
86
75
|
const keysB = Object.keys(b);
|
|
87
|
-
|
|
88
|
-
result = false;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
result = keysA.every(key => deepEqual(a[key], b[key], seen));
|
|
92
|
-
}
|
|
76
|
+
result = keysA.length === keysB.length && keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(a[key], b[key], seen));
|
|
93
77
|
}
|
|
94
|
-
//
|
|
78
|
+
// cache the result before returning
|
|
95
79
|
deepEqualCache.get(a).set(b, result);
|
|
96
80
|
return result;
|
|
97
81
|
}
|
|
98
|
-
// Helper to safely get from path cache with cleanup
|
|
99
82
|
export function getFromPathCache(root, pathKey) {
|
|
100
83
|
const cache = pathCache.get(root);
|
|
101
84
|
if (!cache)
|
|
102
85
|
return undefined;
|
|
103
86
|
const result = cache.get(pathKey);
|
|
104
87
|
if (result !== undefined) {
|
|
105
|
-
//
|
|
88
|
+
// simulate lru by deleting and re-setting the key
|
|
106
89
|
cache.delete(pathKey);
|
|
107
90
|
cache.set(pathKey, result);
|
|
108
91
|
}
|
|
109
92
|
return result;
|
|
110
93
|
}
|
|
111
|
-
// Helper to safely set in path cache with cleanup
|
|
112
94
|
export function setInPathCache(root, pathKey, value) {
|
|
113
95
|
if (!pathCache.has(root)) {
|
|
114
96
|
pathCache.set(root, new Map());
|
|
115
97
|
pathCacheSize.set(root, 0);
|
|
116
98
|
}
|
|
117
99
|
const cache = pathCache.get(root);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
100
|
+
// adjust size only if the key is new
|
|
101
|
+
if (!cache.has(pathKey)) {
|
|
102
|
+
pathCacheSize.set(root, pathCacheSize.get(root) + 1);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
cache.delete(pathKey); // remove old entry before adding to end
|
|
122
106
|
}
|
|
123
107
|
cache.set(pathKey, value);
|
|
124
|
-
|
|
125
|
-
cleanupPathCache(root);
|
|
108
|
+
cleanupPathCache(root); // check if cleanup is needed after adding
|
|
126
109
|
}
|
|
127
|
-
// Helper to safely get/set path concatenation with cleanup
|
|
128
110
|
export function getPathConcat(path) {
|
|
129
111
|
const result = pathConcatCache.get(path);
|
|
130
112
|
if (result !== undefined) {
|
|
131
|
-
//
|
|
113
|
+
// simulate lru
|
|
132
114
|
pathConcatCache.delete(path);
|
|
133
115
|
pathConcatCache.set(path, result);
|
|
134
116
|
}
|
|
135
117
|
return result;
|
|
136
118
|
}
|
|
137
119
|
export function setPathConcat(path, value) {
|
|
120
|
+
// delete first to ensure it's added at the end (most recent)
|
|
121
|
+
if (pathConcatCache.has(path)) {
|
|
122
|
+
pathConcatCache.delete(path);
|
|
123
|
+
}
|
|
138
124
|
pathConcatCache.set(path, value);
|
|
139
|
-
cleanupPathConcatCache();
|
|
125
|
+
cleanupPathConcatCache(); // check size limit
|
|
140
126
|
}
|
|
141
|
-
// Helper to check if a value is an object (and not null)
|
|
142
127
|
function isObject(val) {
|
|
143
128
|
return val !== null && typeof val === 'object';
|
|
144
129
|
}
|
|
145
130
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* @param seen - A Set to handle circular references.
|
|
131
|
+
* recursively traverses an object or array, accessing each property/element.
|
|
132
|
+
* used by `watch` with `deep: true` to establish dependencies on all nested properties.
|
|
133
|
+
* the actual tracking is done by the proxy `get` handlers triggered during traversal.
|
|
150
134
|
*/
|
|
151
135
|
export function traverse(value, seen = new Set()) {
|
|
152
136
|
if (!isObject(value) || seen.has(value)) {
|
|
153
137
|
return value;
|
|
154
138
|
}
|
|
155
139
|
seen.add(value);
|
|
156
|
-
// Traverse arrays
|
|
157
140
|
if (Array.isArray(value)) {
|
|
158
|
-
//
|
|
159
|
-
value.length;
|
|
141
|
+
value.length; // track length
|
|
160
142
|
for (let i = 0; i < value.length; i++) {
|
|
161
143
|
traverse(value[i], seen);
|
|
162
144
|
}
|
|
163
145
|
}
|
|
164
|
-
// Traverse Sets and Maps
|
|
165
146
|
else if (value instanceof Set || value instanceof Map) {
|
|
166
147
|
for (const v of value) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
traverse(v[
|
|
170
|
-
traverse(v[1], seen); // Value
|
|
148
|
+
if (Array.isArray(v)) { // map entries [key, value]
|
|
149
|
+
traverse(v[0], seen); // key
|
|
150
|
+
traverse(v[1], seen); // value
|
|
171
151
|
}
|
|
172
|
-
else {
|
|
173
|
-
traverse(v, seen);
|
|
152
|
+
else { // set values
|
|
153
|
+
traverse(v, seen);
|
|
174
154
|
}
|
|
175
155
|
}
|
|
176
|
-
//
|
|
177
|
-
return value;
|
|
156
|
+
return value; // no need to iterate plain object keys for map/set
|
|
178
157
|
}
|
|
179
|
-
// Traverse plain objects (Only if not Array, Map, or Set)
|
|
180
158
|
else {
|
|
181
159
|
for (const key in value) {
|
|
182
|
-
// Access the property to trigger tracking via the proxy's get handler
|
|
183
160
|
traverse(value[key], seen);
|
|
184
161
|
}
|
|
185
162
|
}
|
|
186
163
|
return value;
|
|
187
164
|
}
|
|
188
165
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* Includes cycle detection.
|
|
192
|
-
* @param value The value to clone.
|
|
193
|
-
* @param seen A WeakMap to handle circular references during recursion.
|
|
194
|
-
* @returns A deep clone of the value.
|
|
166
|
+
* creates a deep clone of a value.
|
|
167
|
+
* includes cycle detection using a weakmap.
|
|
195
168
|
*/
|
|
196
169
|
export function deepClone(value, seen = new WeakMap()) {
|
|
197
|
-
// Primitives and null are returned directly
|
|
198
170
|
if (value === null || typeof value !== 'object') {
|
|
199
171
|
return value;
|
|
200
172
|
}
|
|
201
|
-
// Handle Dates
|
|
202
173
|
if (value instanceof Date) {
|
|
203
174
|
return new Date(value.getTime());
|
|
204
175
|
}
|
|
205
|
-
// Handle cycles
|
|
206
176
|
if (seen.has(value)) {
|
|
207
177
|
return seen.get(value);
|
|
208
178
|
}
|
|
209
|
-
// Handle Arrays
|
|
210
179
|
if (Array.isArray(value)) {
|
|
211
180
|
const newArray = [];
|
|
212
|
-
seen.set(value, newArray);
|
|
181
|
+
seen.set(value, newArray); // store ref before recursing
|
|
213
182
|
for (let i = 0; i < value.length; i++) {
|
|
214
183
|
newArray[i] = deepClone(value[i], seen);
|
|
215
184
|
}
|
|
216
185
|
return newArray;
|
|
217
186
|
}
|
|
218
|
-
// Handle Maps
|
|
219
187
|
if (value instanceof Map) {
|
|
220
188
|
const newMap = new Map();
|
|
221
|
-
seen.set(value, newMap);
|
|
189
|
+
seen.set(value, newMap); // store ref before recursing
|
|
222
190
|
value.forEach((val, key) => {
|
|
223
|
-
// Clone both key and value in case they are objects
|
|
224
191
|
newMap.set(deepClone(key, seen), deepClone(val, seen));
|
|
225
192
|
});
|
|
226
193
|
return newMap;
|
|
227
194
|
}
|
|
228
|
-
// Handle Sets
|
|
229
195
|
if (value instanceof Set) {
|
|
230
196
|
const newSet = new Set();
|
|
231
|
-
seen.set(value, newSet);
|
|
197
|
+
seen.set(value, newSet); // store ref before recursing
|
|
232
198
|
value.forEach(val => {
|
|
233
199
|
newSet.add(deepClone(val, seen));
|
|
234
200
|
});
|
|
235
201
|
return newSet;
|
|
236
202
|
}
|
|
237
|
-
//
|
|
238
|
-
// Create object with same prototype
|
|
203
|
+
// handle plain objects
|
|
239
204
|
const newObject = Object.create(Object.getPrototypeOf(value));
|
|
240
|
-
seen.set(value, newObject);
|
|
241
|
-
// Copy properties
|
|
205
|
+
seen.set(value, newObject); // store ref before recursing
|
|
242
206
|
for (const key in value) {
|
|
243
207
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
244
208
|
newObject[key] = deepClone(value[key], seen);
|
|
245
209
|
}
|
|
246
210
|
}
|
|
247
|
-
//
|
|
211
|
+
// copy symbol properties
|
|
248
212
|
const symbolKeys = Object.getOwnPropertySymbols(value);
|
|
249
213
|
for (const symbolKey of symbolKeys) {
|
|
250
|
-
// Check if property descriptor exists and has get/set or is value
|
|
251
214
|
const descriptor = Object.getOwnPropertyDescriptor(value, symbolKey);
|
|
252
|
-
if (descriptor && (
|
|
215
|
+
if (descriptor && Object.prototype.propertyIsEnumerable.call(value, symbolKey)) {
|
|
253
216
|
newObject[symbolKey] = deepClone(value[symbolKey], seen);
|
|
254
217
|
}
|
|
255
218
|
}
|
package/dist/watch.d.ts
CHANGED
|
@@ -7,13 +7,8 @@ export interface WatchOptions {
|
|
|
7
7
|
deep?: boolean;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @param source - A function that returns the value to watch
|
|
13
|
-
* @param callback - Function to call when the source changes
|
|
14
|
-
* @param options - Watch options (immediate, deep)
|
|
15
|
-
* @returns A function to stop watching
|
|
10
|
+
* watches a reactive source (getter function or reactive object/ref)
|
|
11
|
+
* and runs a callback when the source's value changes.
|
|
16
12
|
*/
|
|
17
|
-
export declare function watch<T = any>(sourceInput: WatchSourceInput<T>,
|
|
18
|
-
callback: WatchCallback<T>, options?: WatchOptions): WatchStopHandle;
|
|
13
|
+
export declare function watch<T = any>(sourceInput: WatchSourceInput<T>, callback: WatchCallback<T>, options?: WatchOptions): WatchStopHandle;
|
|
19
14
|
export {};
|