@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/wrapMap.js
CHANGED
|
@@ -4,26 +4,26 @@ import { wrapArray } from './wrapArray';
|
|
|
4
4
|
import { wrapSet } from './wrapSet';
|
|
5
5
|
import { track, trigger } from './watchEffect';
|
|
6
6
|
export function wrapMap(map, emit, path) {
|
|
7
|
-
//
|
|
7
|
+
// reuse existing proxy if available for performance
|
|
8
8
|
const cachedProxy = wrapperCache.get(map);
|
|
9
9
|
if (cachedProxy)
|
|
10
10
|
return cachedProxy;
|
|
11
11
|
const proxy = new Proxy(map, {
|
|
12
12
|
get(target, prop, receiver) {
|
|
13
|
-
// Original track call - Keep for properties not explicitly handled
|
|
14
13
|
track(target, prop);
|
|
15
|
-
// --- Specific Method Handlers ---
|
|
16
14
|
if (prop === 'set') {
|
|
17
15
|
return function (key, value) {
|
|
18
16
|
const existed = target.has(key);
|
|
19
17
|
const oldValue = target.get(key);
|
|
20
18
|
const oldSize = target.size;
|
|
19
|
+
// avoid unnecessary work if value hasn't changed
|
|
21
20
|
if (oldValue === value)
|
|
22
21
|
return receiver;
|
|
23
22
|
if (oldValue && typeof oldValue === 'object' && value && typeof value === 'object' && deepEqual(oldValue, value, new WeakMap()))
|
|
24
23
|
return receiver;
|
|
25
24
|
target.set(key, value);
|
|
26
25
|
const newSize = target.size;
|
|
26
|
+
// optimize path calculation by caching concatenated paths
|
|
27
27
|
const pathKey = path.join('.');
|
|
28
28
|
let cachedPath = getPathConcat(pathKey);
|
|
29
29
|
if (cachedPath === undefined) {
|
|
@@ -38,6 +38,7 @@ export function wrapMap(map, emit, path) {
|
|
|
38
38
|
newValue: value
|
|
39
39
|
};
|
|
40
40
|
emit(event);
|
|
41
|
+
// trigger effects based on whether it was an add or update
|
|
41
42
|
if (!existed) {
|
|
42
43
|
trigger(target, Symbol.iterator);
|
|
43
44
|
if (oldSize !== newSize) {
|
|
@@ -59,7 +60,7 @@ export function wrapMap(map, emit, path) {
|
|
|
59
60
|
const oldSize = target.size;
|
|
60
61
|
const result = target.delete(key);
|
|
61
62
|
const newSize = target.size;
|
|
62
|
-
if (result) {
|
|
63
|
+
if (result) { // only emit and trigger if delete was successful
|
|
63
64
|
const pathKey = path.join('.');
|
|
64
65
|
let cachedPath = getPathConcat(pathKey);
|
|
65
66
|
if (cachedPath === undefined) {
|
|
@@ -94,7 +95,6 @@ export function wrapMap(map, emit, path) {
|
|
|
94
95
|
key: null
|
|
95
96
|
};
|
|
96
97
|
emit(event);
|
|
97
|
-
// Clear triggers both iterator and size
|
|
98
98
|
trigger(target, Symbol.iterator);
|
|
99
99
|
if (oldSize !== newSize) {
|
|
100
100
|
trigger(target, 'size');
|
|
@@ -102,18 +102,15 @@ export function wrapMap(map, emit, path) {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
if (prop === 'get') {
|
|
105
|
-
//
|
|
105
|
+
// return a function that tracks the specific key only when called
|
|
106
106
|
return function (key) {
|
|
107
|
-
// Track access to this specific key when 'get' is called
|
|
108
107
|
track(target, String(key));
|
|
109
108
|
const value = target.get(key);
|
|
110
109
|
if (!value || typeof value !== 'object')
|
|
111
|
-
return value;
|
|
112
|
-
// Check wrapper cache for the value
|
|
110
|
+
return value;
|
|
113
111
|
const cachedValueProxy = wrapperCache.get(value);
|
|
114
112
|
if (cachedValueProxy)
|
|
115
113
|
return cachedValueProxy;
|
|
116
|
-
// Calculate path for the value
|
|
117
114
|
const keyString = String(key);
|
|
118
115
|
const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
|
|
119
116
|
let newPath = getPathConcat(pathKey);
|
|
@@ -121,7 +118,7 @@ export function wrapMap(map, emit, path) {
|
|
|
121
118
|
newPath = path.concat(keyString);
|
|
122
119
|
setPathConcat(pathKey, newPath);
|
|
123
120
|
}
|
|
124
|
-
//
|
|
121
|
+
// recursively wrap nested structures
|
|
125
122
|
if (value instanceof Map)
|
|
126
123
|
return wrapMap(value, emit, newPath);
|
|
127
124
|
if (value instanceof Set)
|
|
@@ -129,60 +126,58 @@ export function wrapMap(map, emit, path) {
|
|
|
129
126
|
if (Array.isArray(value))
|
|
130
127
|
return wrapArray(value, emit, newPath);
|
|
131
128
|
if (value instanceof Date)
|
|
132
|
-
return new Date(value.getTime()); //
|
|
133
|
-
// Default to reactive for plain objects
|
|
129
|
+
return new Date(value.getTime()); // dates are not proxied, return a copy
|
|
134
130
|
return reactive(value, emit, newPath);
|
|
135
131
|
};
|
|
136
132
|
}
|
|
137
133
|
if (prop === 'has') {
|
|
138
|
-
// Track dependency on iteration/structure when 'has' is accessed
|
|
139
134
|
track(target, Symbol.iterator);
|
|
140
135
|
return function (key) {
|
|
141
|
-
//
|
|
136
|
+
// track the specific key only when 'has' is called
|
|
142
137
|
track(target, String(key));
|
|
143
138
|
return target.has(key);
|
|
144
|
-
}.bind(target);
|
|
139
|
+
}.bind(target);
|
|
145
140
|
}
|
|
146
|
-
//
|
|
141
|
+
// handle iteration methods
|
|
147
142
|
if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
|
|
148
|
-
// Track dependency on iteration when these methods are accessed
|
|
149
143
|
track(target, Symbol.iterator);
|
|
150
144
|
const originalMethod = Reflect.get(target, prop, receiver);
|
|
151
|
-
//
|
|
145
|
+
// return custom iterators/foreach that wrap values during iteration
|
|
152
146
|
if (prop === 'forEach') {
|
|
153
147
|
return (callbackfn, thisArg) => {
|
|
154
|
-
//
|
|
148
|
+
// use the proxied .entries() to ensure values passed to callback are wrapped and tracked
|
|
155
149
|
const entriesIterator = proxy.entries();
|
|
156
150
|
for (const [key, value] of entriesIterator) {
|
|
157
151
|
callbackfn.call(thisArg, value, key, proxy);
|
|
158
152
|
}
|
|
159
153
|
};
|
|
160
154
|
}
|
|
161
|
-
//
|
|
155
|
+
// handle symbol.iterator, entries, values, keys by creating generator functions
|
|
162
156
|
return function* (...args) {
|
|
163
157
|
const iterator = originalMethod.apply(target, args);
|
|
164
158
|
for (const entry of iterator) {
|
|
165
|
-
let keyToWrap = entry;
|
|
166
|
-
let valueToWrap = entry;
|
|
159
|
+
let keyToWrap = entry;
|
|
160
|
+
let valueToWrap = entry;
|
|
167
161
|
let isEntry = false;
|
|
168
162
|
if (prop === 'entries' || prop === Symbol.iterator) {
|
|
169
163
|
keyToWrap = entry[0];
|
|
170
164
|
valueToWrap = entry[1];
|
|
171
165
|
isEntry = true;
|
|
172
166
|
}
|
|
173
|
-
//
|
|
167
|
+
// wrap key if it's an object
|
|
168
|
+
// note: reactivity on map keys can be complex/unexpected
|
|
174
169
|
let wrappedKey = keyToWrap;
|
|
175
170
|
if (isEntry && keyToWrap && typeof keyToWrap === 'object') {
|
|
176
|
-
const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap);
|
|
177
|
-
let keyPath = getPathConcat(pathKey);
|
|
171
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap);
|
|
172
|
+
let keyPath = getPathConcat(pathKey);
|
|
178
173
|
if (keyPath === undefined) {
|
|
179
|
-
keyPath = path.concat(String(keyToWrap));
|
|
174
|
+
keyPath = path.concat(String(keyToWrap));
|
|
180
175
|
setPathConcat(pathKey, keyPath);
|
|
181
176
|
}
|
|
182
|
-
//
|
|
177
|
+
// todo: decide if map keys should be deeply reactive
|
|
183
178
|
wrappedKey = reactive(keyToWrap, emit, keyPath);
|
|
184
179
|
}
|
|
185
|
-
//
|
|
180
|
+
// wrap value if it's an object
|
|
186
181
|
let wrappedValue = valueToWrap;
|
|
187
182
|
if (valueToWrap && typeof valueToWrap === 'object') {
|
|
188
183
|
const cachedValueProxy = wrapperCache.get(valueToWrap);
|
|
@@ -190,7 +185,7 @@ export function wrapMap(map, emit, path) {
|
|
|
190
185
|
wrappedValue = cachedValueProxy;
|
|
191
186
|
}
|
|
192
187
|
else {
|
|
193
|
-
const keyString = String(keyToWrap);
|
|
188
|
+
const keyString = String(keyToWrap); // use original key for path
|
|
194
189
|
const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
|
|
195
190
|
let newPath = getPathConcat(pathKey);
|
|
196
191
|
if (newPath === undefined) {
|
|
@@ -209,7 +204,6 @@ export function wrapMap(map, emit, path) {
|
|
|
209
204
|
wrappedValue = reactive(valueToWrap, emit, newPath);
|
|
210
205
|
}
|
|
211
206
|
}
|
|
212
|
-
// Yield based on iterator type
|
|
213
207
|
if (prop === 'entries' || prop === Symbol.iterator) {
|
|
214
208
|
yield [wrappedKey, wrappedValue];
|
|
215
209
|
}
|
|
@@ -222,20 +216,15 @@ export function wrapMap(map, emit, path) {
|
|
|
222
216
|
}
|
|
223
217
|
};
|
|
224
218
|
}
|
|
225
|
-
// --- Size Property ---
|
|
226
219
|
if (prop === 'size') {
|
|
227
|
-
// Explicitly track size access
|
|
228
220
|
track(target, 'size');
|
|
229
|
-
// Return the size directly from the target to avoid potential 'this' issues with Reflect.get
|
|
230
221
|
return target.size;
|
|
231
222
|
}
|
|
232
|
-
// --- Fallback for other properties/methods ---
|
|
233
223
|
const value = Reflect.get(target, prop, receiver);
|
|
234
|
-
// For non-function properties or bound functions, return the value as is.
|
|
235
224
|
return value;
|
|
236
225
|
}
|
|
237
226
|
});
|
|
238
|
-
//
|
|
227
|
+
// cache the newly created proxy before returning
|
|
239
228
|
wrapperCache.set(map, proxy);
|
|
240
229
|
return proxy;
|
|
241
230
|
}
|
package/dist/wrapSet.js
CHANGED
|
@@ -4,18 +4,18 @@ import { wrapArray } from './wrapArray';
|
|
|
4
4
|
import { wrapMap } from './wrapMap';
|
|
5
5
|
import { track, trigger } from './watchEffect';
|
|
6
6
|
export function wrapSet(set, emit, path) {
|
|
7
|
-
//
|
|
7
|
+
// reuse existing proxy if available for performance
|
|
8
8
|
const cachedProxy = wrapperCache.get(set);
|
|
9
9
|
if (cachedProxy)
|
|
10
10
|
return cachedProxy;
|
|
11
11
|
const proxy = new Proxy(set, {
|
|
12
12
|
get(target, prop, receiver) {
|
|
13
|
-
// Original track call
|
|
14
13
|
track(target, prop);
|
|
15
14
|
if (prop === 'add') {
|
|
16
15
|
return function (value) {
|
|
17
16
|
const existed = target.has(value);
|
|
18
17
|
const oldSize = target.size;
|
|
18
|
+
// only add and trigger if the value doesn't already exist
|
|
19
19
|
if (!existed) {
|
|
20
20
|
target.add(value);
|
|
21
21
|
const newSize = target.size;
|
|
@@ -30,7 +30,7 @@ export function wrapSet(set, emit, path) {
|
|
|
30
30
|
trigger(target, 'size');
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
return receiver;
|
|
33
|
+
return receiver; // return the proxy itself for chaining
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
if (prop === 'delete') {
|
|
@@ -38,15 +38,15 @@ export function wrapSet(set, emit, path) {
|
|
|
38
38
|
const existed = target.has(value);
|
|
39
39
|
const oldSize = target.size;
|
|
40
40
|
if (existed) {
|
|
41
|
-
const oldValue = value;
|
|
41
|
+
const oldValue = value;
|
|
42
42
|
const result = target.delete(value);
|
|
43
43
|
const newSize = target.size;
|
|
44
|
-
if (result) {
|
|
44
|
+
if (result) { // only emit and trigger if delete was successful
|
|
45
45
|
const event = {
|
|
46
46
|
action: 'set-delete',
|
|
47
47
|
path: path,
|
|
48
|
-
value: value,
|
|
49
|
-
oldValue: oldValue
|
|
48
|
+
value: value,
|
|
49
|
+
oldValue: oldValue
|
|
50
50
|
};
|
|
51
51
|
emit(event);
|
|
52
52
|
trigger(target, Symbol.iterator);
|
|
@@ -67,12 +67,11 @@ export function wrapSet(set, emit, path) {
|
|
|
67
67
|
target.clear();
|
|
68
68
|
const newSize = target.size;
|
|
69
69
|
const event = {
|
|
70
|
-
action: 'set-clear',
|
|
70
|
+
action: 'set-clear',
|
|
71
71
|
path: path,
|
|
72
72
|
value: null
|
|
73
73
|
};
|
|
74
74
|
emit(event);
|
|
75
|
-
// Clear triggers both iterator and size
|
|
76
75
|
trigger(target, Symbol.iterator);
|
|
77
76
|
if (oldSize !== newSize) {
|
|
78
77
|
trigger(target, 'size');
|
|
@@ -80,64 +79,58 @@ export function wrapSet(set, emit, path) {
|
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
81
|
if (prop === 'has') {
|
|
83
|
-
|
|
84
|
-
track(target, Symbol.iterator); // Track iteration implicitly
|
|
82
|
+
track(target, Symbol.iterator);
|
|
85
83
|
return function (value) {
|
|
86
|
-
//
|
|
84
|
+
// track specific primitive value when 'has' is called
|
|
85
|
+
// tracking object values for existence is complex and less common, handled by iteration track
|
|
87
86
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol') {
|
|
88
87
|
track(target, String(value));
|
|
89
88
|
}
|
|
90
|
-
// How to track non-primitive values?
|
|
91
|
-
// We might need a different strategy for object keys in Sets/Maps if deep reactivity on keys is needed
|
|
92
89
|
return target.has(value);
|
|
93
|
-
}.bind(target);
|
|
90
|
+
}.bind(target);
|
|
94
91
|
}
|
|
95
|
-
//
|
|
92
|
+
// handle iteration methods
|
|
96
93
|
if (prop === 'values' || prop === Symbol.iterator || prop === 'entries' || prop === 'keys' || prop === 'forEach') {
|
|
97
|
-
// Track dependency on iteration
|
|
98
94
|
track(target, Symbol.iterator);
|
|
99
95
|
const originalMethod = Reflect.get(target, prop, receiver);
|
|
100
|
-
//
|
|
96
|
+
// return custom iterators/foreach that wrap values during iteration
|
|
101
97
|
if (prop === 'forEach') {
|
|
102
98
|
return (callbackfn, thisArg) => {
|
|
103
|
-
//
|
|
104
|
-
const valuesIterator = proxy.values();
|
|
99
|
+
// use the proxied values() to ensure values passed to callback are wrapped and tracked
|
|
100
|
+
const valuesIterator = proxy.values();
|
|
105
101
|
for (const value of valuesIterator) {
|
|
106
102
|
callbackfn.call(thisArg, value, value, proxy);
|
|
107
103
|
}
|
|
108
104
|
};
|
|
109
105
|
}
|
|
110
|
-
//
|
|
106
|
+
// handle symbol.iterator, values, keys, entries by creating generator functions
|
|
111
107
|
return function* (...args) {
|
|
112
|
-
let index = 0; //
|
|
108
|
+
let index = 0; // use index for path generation if value is not primitive
|
|
113
109
|
const iterator = originalMethod.apply(target, args);
|
|
114
110
|
for (const entry of iterator) {
|
|
115
|
-
let valueToWrap = entry;
|
|
116
|
-
let mapKey = undefined; //
|
|
117
|
-
// For entries(), the entry is [key, value]
|
|
111
|
+
let valueToWrap = entry;
|
|
112
|
+
let mapKey = undefined; // key for entries() which yields [value, value]
|
|
118
113
|
if (prop === 'entries') {
|
|
119
|
-
mapKey = entry[0];
|
|
114
|
+
mapKey = entry[0]; // for Set.entries(), key and value are the same
|
|
120
115
|
valueToWrap = entry[1];
|
|
121
116
|
}
|
|
122
|
-
// Track access to each element during iteration
|
|
123
117
|
track(target, String(index));
|
|
124
118
|
let wrappedValue = valueToWrap;
|
|
125
119
|
if (valueToWrap && typeof valueToWrap === 'object') {
|
|
126
|
-
// Check wrapper cache first
|
|
127
120
|
const cachedValueProxy = wrapperCache.get(valueToWrap);
|
|
128
121
|
if (cachedValueProxy) {
|
|
129
122
|
wrappedValue = cachedValueProxy;
|
|
130
123
|
}
|
|
131
124
|
else {
|
|
132
|
-
//
|
|
133
|
-
const keyForPath =
|
|
125
|
+
// calculate path using index as key, as set values don't have inherent keys
|
|
126
|
+
const keyForPath = String(index);
|
|
134
127
|
const pathKey = path.length > 0 ? `${path.join('.')}.${keyForPath}` : keyForPath;
|
|
135
128
|
let newPath = getPathConcat(pathKey);
|
|
136
129
|
if (newPath === undefined) {
|
|
137
130
|
newPath = path.concat(keyForPath);
|
|
138
131
|
setPathConcat(pathKey, newPath);
|
|
139
132
|
}
|
|
140
|
-
//
|
|
133
|
+
// recursively wrap nested structures
|
|
141
134
|
if (valueToWrap instanceof Map)
|
|
142
135
|
wrappedValue = wrapMap(valueToWrap, emit, newPath);
|
|
143
136
|
else if (valueToWrap instanceof Set)
|
|
@@ -145,15 +138,13 @@ export function wrapSet(set, emit, path) {
|
|
|
145
138
|
else if (Array.isArray(valueToWrap))
|
|
146
139
|
wrappedValue = wrapArray(valueToWrap, emit, newPath);
|
|
147
140
|
else if (valueToWrap instanceof Date)
|
|
148
|
-
wrappedValue = new Date(valueToWrap.getTime());
|
|
141
|
+
wrappedValue = new Date(valueToWrap.getTime()); // dates are not proxied, return copy
|
|
149
142
|
else
|
|
150
143
|
wrappedValue = reactive(valueToWrap, emit, newPath);
|
|
151
|
-
// Note: We don't cache the *wrapped* value here, wrap functions handle caching
|
|
152
144
|
}
|
|
153
145
|
}
|
|
154
|
-
// Yield the original or wrapped value/entry
|
|
155
146
|
if (prop === 'entries') {
|
|
156
|
-
yield [
|
|
147
|
+
yield [wrappedValue, wrappedValue]; // set entries yield [value, value]
|
|
157
148
|
}
|
|
158
149
|
else {
|
|
159
150
|
yield wrappedValue;
|
|
@@ -163,22 +154,14 @@ export function wrapSet(set, emit, path) {
|
|
|
163
154
|
};
|
|
164
155
|
}
|
|
165
156
|
if (prop === 'size') {
|
|
166
|
-
// Explicitly track size access
|
|
167
157
|
track(target, 'size');
|
|
168
|
-
// Return the size directly from the target to avoid potential 'this' issues with Reflect.get
|
|
169
158
|
return target.size;
|
|
170
159
|
}
|
|
171
|
-
// Fallback for other properties (should be minimal for Set)
|
|
172
160
|
const value = Reflect.get(target, prop, receiver);
|
|
173
|
-
// REMOVED - Tracking handled above
|
|
174
|
-
// if (prop === 'size') { ... }
|
|
175
|
-
// If the property is a function (and not handled above), return it directly.
|
|
176
|
-
// Reflect.get preserves the correct 'this' binding (receiver).
|
|
177
|
-
// REMOVED: return value.bind(target);
|
|
178
161
|
return value;
|
|
179
162
|
}
|
|
180
163
|
});
|
|
181
|
-
//
|
|
164
|
+
// cache the newly created proxy before returning
|
|
182
165
|
wrapperCache.set(set, proxy);
|
|
183
166
|
return proxy;
|
|
184
167
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yiin/reactive-proxy-state",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.6",
|
|
5
5
|
"description": "A simple, standalone reactivity library using Proxies",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"test": "bun test",
|
|
22
22
|
"test:watch": "bun test --watch",
|
|
23
|
-
"test:bench": "bun --bun ./benchmarks/state-sync.bench.ts"
|
|
23
|
+
"test:bench": "bun --bun ./benchmarks/state-sync.bench.ts",
|
|
24
|
+
"docs:dev": "vitepress dev docs",
|
|
25
|
+
"docs:build": "vitepress build docs",
|
|
26
|
+
"docs:preview": "vitepress preview docs"
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
26
29
|
"state",
|
|
@@ -47,6 +50,8 @@
|
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
49
52
|
"bun-types": "^1.2.8",
|
|
50
|
-
"typescript": "^5.0.4"
|
|
53
|
+
"typescript": "^5.0.4",
|
|
54
|
+
"vitepress": "^1.6.3",
|
|
55
|
+
"vue": "^3.5.13"
|
|
51
56
|
}
|
|
52
57
|
}
|