@yiin/reactive-proxy-state 1.0.12 → 1.0.14

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/dist/wrapArray.js DELETED
@@ -1,227 +0,0 @@
1
- import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapMap } from './wrap-map';
4
- import { wrapSet } from './wrap-set';
5
- import { track, trigger } from './watch-effect';
6
- // avoid repeated typeof checks
7
- function isObject(v) {
8
- return v && typeof v === 'object';
9
- }
10
- export function wrapArray(arr, emit, path) {
11
- // reuse existing proxy if available for performance
12
- const cachedProxy = wrapperCache.get(arr);
13
- if (cachedProxy)
14
- return cachedProxy;
15
- const proxy = new Proxy(arr, {
16
- get(target, prop, receiver) {
17
- track(target, prop);
18
- // handle specific array mutation methods that require custom logic and event emission
19
- switch (prop) {
20
- case 'push':
21
- track(target, 'length');
22
- return function (...items) {
23
- const oldLength = target.length;
24
- const result = target.push(...items);
25
- const newLength = target.length;
26
- if (items.length > 0) {
27
- const event = {
28
- action: 'array-push',
29
- path: path,
30
- key: oldLength, // start index was the old length
31
- items: items
32
- };
33
- emit(event);
34
- trigger(target, Symbol.iterator);
35
- if (oldLength !== newLength) {
36
- trigger(target, 'length');
37
- }
38
- }
39
- return result;
40
- };
41
- case 'pop':
42
- track(target, 'length');
43
- return function () {
44
- if (target.length === 0)
45
- return undefined;
46
- const oldLength = target.length;
47
- const poppedIndex = oldLength - 1;
48
- const oldValue = target[poppedIndex];
49
- const result = target.pop();
50
- const newLength = target.length;
51
- const event = {
52
- action: 'array-pop',
53
- path: path,
54
- key: poppedIndex,
55
- oldValue: oldValue
56
- };
57
- emit(event);
58
- trigger(target, Symbol.iterator);
59
- if (oldLength !== newLength) {
60
- trigger(target, 'length');
61
- }
62
- return result;
63
- };
64
- case 'shift':
65
- track(target, 'length');
66
- return function () {
67
- if (target.length === 0)
68
- return undefined;
69
- const oldLength = target.length;
70
- const oldValue = target[0];
71
- const result = target.shift();
72
- const newLength = target.length;
73
- const event = {
74
- action: 'array-shift',
75
- path: path,
76
- key: 0,
77
- oldValue: oldValue
78
- };
79
- emit(event);
80
- trigger(target, Symbol.iterator);
81
- if (oldLength !== newLength) {
82
- trigger(target, 'length');
83
- }
84
- return result;
85
- };
86
- case 'unshift':
87
- track(target, 'length');
88
- return function (...items) {
89
- const oldLength = target.length;
90
- const result = target.unshift(...items);
91
- const newLength = target.length;
92
- if (items.length > 0) {
93
- const event = {
94
- action: 'array-unshift',
95
- path: path,
96
- key: 0,
97
- items: items
98
- };
99
- emit(event);
100
- trigger(target, Symbol.iterator);
101
- if (oldLength !== newLength) {
102
- trigger(target, 'length');
103
- }
104
- }
105
- return result;
106
- };
107
- case 'splice':
108
- track(target, 'length');
109
- return function (start, deleteCount, ...items) {
110
- const oldLength = target.length;
111
- const actualStart = start < 0 ? Math.max(target.length + start, 0) : Math.min(start, target.length);
112
- const deleteCountNum = deleteCount === undefined ? target.length - actualStart : Number(deleteCount);
113
- const actualDeleteCount = Math.min(deleteCountNum, target.length - actualStart);
114
- const deletedItems = target.slice(actualStart, actualStart + actualDeleteCount);
115
- const result = target.splice(start, deleteCountNum, ...items);
116
- const newLength = target.length;
117
- if (actualDeleteCount > 0 || items.length > 0) {
118
- const event = {
119
- action: 'array-splice',
120
- path: path,
121
- key: actualStart,
122
- deleteCount: actualDeleteCount,
123
- items: items.length > 0 ? items : undefined,
124
- oldValues: deletedItems.length > 0 ? deletedItems : undefined
125
- };
126
- emit(event);
127
- trigger(target, Symbol.iterator);
128
- if (oldLength !== newLength) {
129
- trigger(target, 'length');
130
- }
131
- }
132
- return result;
133
- };
134
- // handle methods that rely on iteration state
135
- case Symbol.iterator:
136
- case 'values':
137
- case 'keys':
138
- case 'entries':
139
- case 'forEach':
140
- case 'map':
141
- case 'filter':
142
- case 'reduce':
143
- case 'reduceRight':
144
- case 'find':
145
- case 'findIndex':
146
- case 'every':
147
- case 'some':
148
- case 'join':
149
- track(target, Symbol.iterator);
150
- // fall through to default behavior (usually binding)
151
- break;
152
- case 'length':
153
- track(target, 'length');
154
- return Reflect.get(target, prop, receiver);
155
- }
156
- const value = Reflect.get(target, prop, receiver);
157
- // determine if the property access is numeric array index access
158
- const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(prop, 10)));
159
- if (isNumericIndex) {
160
- track(target, String(prop));
161
- if (!isObject(value))
162
- return value;
163
- // reuse existing proxy for nested object/array if available
164
- const cachedValueProxy = wrapperCache.get(value);
165
- if (cachedValueProxy)
166
- return cachedValueProxy;
167
- // calculate the nested path for the element, optimizing with caching
168
- const propKey = String(prop);
169
- const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
170
- let newPath = getPathConcat(pathKey);
171
- if (newPath === undefined) {
172
- newPath = path.concat(propKey);
173
- setPathConcat(pathKey, newPath);
174
- }
175
- // recursively wrap nested structures
176
- if (Array.isArray(value))
177
- return wrapArray(value, emit, newPath);
178
- if (value instanceof Map)
179
- return wrapMap(value, emit, newPath);
180
- if (value instanceof Set)
181
- return wrapSet(value, emit, newPath);
182
- if (value instanceof Date)
183
- return new Date(value.getTime()); // dates are not proxied, return a copy
184
- return reactive(value, emit, newPath);
185
- }
186
- // ensure functions accessed directly are bound to the original target
187
- if (typeof value === 'function') {
188
- return value.bind(target);
189
- }
190
- return value;
191
- },
192
- set(target, prop, value, receiver) {
193
- const oldValue = target[prop];
194
- // avoid unnecessary triggers if value hasn't changed
195
- if (oldValue === value)
196
- return true;
197
- if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
198
- return true;
199
- const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
200
- const result = Reflect.set(target, prop, value, receiver);
201
- const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(String(prop))));
202
- // emit event and trigger effects only if the set was successful and wasn't intercepted by a setter
203
- // (unless it's a direct numeric index set, which doesn't have a descriptor.set)
204
- if (result && (!descriptor || !descriptor.set || isNumericIndex)) {
205
- const propKey = String(prop);
206
- const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
207
- let newPath = getPathConcat(pathKey);
208
- if (newPath === undefined) {
209
- newPath = path.concat(propKey);
210
- setPathConcat(pathKey, newPath);
211
- }
212
- const event = {
213
- action: 'set',
214
- path: newPath,
215
- oldValue,
216
- newValue: value
217
- };
218
- emit(event);
219
- trigger(target, prop);
220
- }
221
- return result;
222
- }
223
- });
224
- // cache the newly created proxy before returning
225
- wrapperCache.set(arr, proxy);
226
- return proxy;
227
- }
package/dist/wrapMap.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { EmitFunction, Path } from './types';
2
- export declare function wrapMap<K, V>(map: Map<K, V>, emit: EmitFunction, path: Path): Map<K, V>;
package/dist/wrapMap.js DELETED
@@ -1,230 +0,0 @@
1
- import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapArray } from './wrap-array';
4
- import { wrapSet } from './wrap-set';
5
- import { track, trigger } from './watch-effect';
6
- export function wrapMap(map, emit, path) {
7
- // reuse existing proxy if available for performance
8
- const cachedProxy = wrapperCache.get(map);
9
- if (cachedProxy)
10
- return cachedProxy;
11
- const proxy = new Proxy(map, {
12
- get(target, prop, receiver) {
13
- track(target, prop);
14
- if (prop === 'set') {
15
- return function (key, value) {
16
- const existed = target.has(key);
17
- const oldValue = target.get(key);
18
- const oldSize = target.size;
19
- // avoid unnecessary work if value hasn't changed
20
- if (oldValue === value)
21
- return receiver;
22
- if (oldValue && typeof oldValue === 'object' && value && typeof value === 'object' && deepEqual(oldValue, value, new WeakMap()))
23
- return receiver;
24
- target.set(key, value);
25
- const newSize = target.size;
26
- // optimize path calculation by caching concatenated paths
27
- const pathKey = path.join('.');
28
- let cachedPath = getPathConcat(pathKey);
29
- if (cachedPath === undefined) {
30
- cachedPath = path;
31
- setPathConcat(pathKey, cachedPath);
32
- }
33
- const event = {
34
- action: 'map-set',
35
- path: cachedPath,
36
- key,
37
- oldValue,
38
- newValue: value
39
- };
40
- emit(event);
41
- // trigger effects based on whether it was an add or update
42
- if (!existed) {
43
- trigger(target, Symbol.iterator);
44
- if (oldSize !== newSize) {
45
- trigger(target, 'size');
46
- }
47
- }
48
- else {
49
- trigger(target, String(key));
50
- }
51
- return receiver;
52
- };
53
- }
54
- if (prop === 'delete') {
55
- return function (key) {
56
- const existed = target.has(key);
57
- if (!existed)
58
- return false;
59
- const oldValue = target.get(key);
60
- const oldSize = target.size;
61
- const result = target.delete(key);
62
- const newSize = target.size;
63
- if (result) { // only emit and trigger if delete was successful
64
- const pathKey = path.join('.');
65
- let cachedPath = getPathConcat(pathKey);
66
- if (cachedPath === undefined) {
67
- cachedPath = path;
68
- setPathConcat(pathKey, cachedPath);
69
- }
70
- const event = {
71
- action: 'map-delete',
72
- path: cachedPath,
73
- key,
74
- oldValue
75
- };
76
- emit(event);
77
- trigger(target, Symbol.iterator);
78
- if (oldSize !== newSize) {
79
- trigger(target, 'size');
80
- }
81
- }
82
- return result;
83
- };
84
- }
85
- if (prop === 'clear') {
86
- return function () {
87
- const oldSize = target.size;
88
- if (oldSize === 0)
89
- return;
90
- target.clear();
91
- const newSize = target.size;
92
- const event = {
93
- action: 'map-clear',
94
- path: path,
95
- key: null
96
- };
97
- emit(event);
98
- trigger(target, Symbol.iterator);
99
- if (oldSize !== newSize) {
100
- trigger(target, 'size');
101
- }
102
- };
103
- }
104
- if (prop === 'get') {
105
- // return a function that tracks the specific key only when called
106
- return function (key) {
107
- track(target, String(key));
108
- const value = target.get(key);
109
- if (!value || typeof value !== 'object')
110
- return value;
111
- const cachedValueProxy = wrapperCache.get(value);
112
- if (cachedValueProxy)
113
- return cachedValueProxy;
114
- const keyString = String(key);
115
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
116
- let newPath = getPathConcat(pathKey);
117
- if (newPath === undefined) {
118
- newPath = path.concat(keyString);
119
- setPathConcat(pathKey, newPath);
120
- }
121
- // recursively wrap nested structures
122
- if (value instanceof Map)
123
- return wrapMap(value, emit, newPath);
124
- if (value instanceof Set)
125
- return wrapSet(value, emit, newPath);
126
- if (Array.isArray(value))
127
- return wrapArray(value, emit, newPath);
128
- if (value instanceof Date)
129
- return new Date(value.getTime()); // dates are not proxied, return a copy
130
- return reactive(value, emit, newPath);
131
- };
132
- }
133
- if (prop === 'has') {
134
- track(target, Symbol.iterator);
135
- return function (key) {
136
- // track the specific key only when 'has' is called
137
- track(target, String(key));
138
- return target.has(key);
139
- }.bind(target);
140
- }
141
- // handle iteration methods
142
- if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
143
- track(target, Symbol.iterator);
144
- const originalMethod = Reflect.get(target, prop, receiver);
145
- // return custom iterators/foreach that wrap values during iteration
146
- if (prop === 'forEach') {
147
- return (callbackfn, thisArg) => {
148
- // use the proxied .entries() to ensure values passed to callback are wrapped and tracked
149
- const entriesIterator = proxy.entries();
150
- for (const [key, value] of entriesIterator) {
151
- callbackfn.call(thisArg, value, key, proxy);
152
- }
153
- };
154
- }
155
- // handle symbol.iterator, entries, values, keys by creating generator functions
156
- return function* (...args) {
157
- const iterator = originalMethod.apply(target, args);
158
- for (const entry of iterator) {
159
- let keyToWrap = entry;
160
- let valueToWrap = entry;
161
- let isEntry = false;
162
- if (prop === 'entries' || prop === Symbol.iterator) {
163
- keyToWrap = entry[0];
164
- valueToWrap = entry[1];
165
- isEntry = true;
166
- }
167
- // wrap key if it's an object
168
- // note: reactivity on map keys can be complex/unexpected
169
- let wrappedKey = keyToWrap;
170
- if (isEntry && keyToWrap && typeof keyToWrap === 'object') {
171
- const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap);
172
- let keyPath = getPathConcat(pathKey);
173
- if (keyPath === undefined) {
174
- keyPath = path.concat(String(keyToWrap));
175
- setPathConcat(pathKey, keyPath);
176
- }
177
- // todo: decide if map keys should be deeply reactive
178
- wrappedKey = reactive(keyToWrap, emit, keyPath);
179
- }
180
- // wrap value if it's an object
181
- let wrappedValue = valueToWrap;
182
- if (valueToWrap && typeof valueToWrap === 'object') {
183
- const cachedValueProxy = wrapperCache.get(valueToWrap);
184
- if (cachedValueProxy) {
185
- wrappedValue = cachedValueProxy;
186
- }
187
- else {
188
- const keyString = String(keyToWrap); // use original key for path
189
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
190
- let newPath = getPathConcat(pathKey);
191
- if (newPath === undefined) {
192
- newPath = path.concat(keyString);
193
- setPathConcat(pathKey, newPath);
194
- }
195
- if (valueToWrap instanceof Map)
196
- wrappedValue = wrapMap(valueToWrap, emit, newPath);
197
- else if (valueToWrap instanceof Set)
198
- wrappedValue = wrapSet(valueToWrap, emit, newPath);
199
- else if (Array.isArray(valueToWrap))
200
- wrappedValue = wrapArray(valueToWrap, emit, newPath);
201
- else if (valueToWrap instanceof Date)
202
- wrappedValue = new Date(valueToWrap.getTime());
203
- else
204
- wrappedValue = reactive(valueToWrap, emit, newPath);
205
- }
206
- }
207
- if (prop === 'entries' || prop === Symbol.iterator) {
208
- yield [wrappedKey, wrappedValue];
209
- }
210
- else if (prop === 'values') {
211
- yield wrappedValue;
212
- }
213
- else { // keys
214
- yield wrappedKey;
215
- }
216
- }
217
- };
218
- }
219
- if (prop === 'size') {
220
- track(target, 'size');
221
- return target.size;
222
- }
223
- const value = Reflect.get(target, prop, receiver);
224
- return value;
225
- }
226
- });
227
- // cache the newly created proxy before returning
228
- wrapperCache.set(map, proxy);
229
- return proxy;
230
- }
package/dist/wrapSet.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { EmitFunction, Path } from './types';
2
- export declare function wrapSet<T>(set: Set<T>, emit: EmitFunction, path: Path): Set<T>;
package/dist/wrapSet.js DELETED
@@ -1,167 +0,0 @@
1
- import { getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapArray } from './wrap-array';
4
- import { wrapMap } from './wrap-map';
5
- import { track, trigger } from './watch-effect';
6
- export function wrapSet(set, emit, path) {
7
- // reuse existing proxy if available for performance
8
- const cachedProxy = wrapperCache.get(set);
9
- if (cachedProxy)
10
- return cachedProxy;
11
- const proxy = new Proxy(set, {
12
- get(target, prop, receiver) {
13
- track(target, prop);
14
- if (prop === 'add') {
15
- return function (value) {
16
- const existed = target.has(value);
17
- const oldSize = target.size;
18
- // only add and trigger if the value doesn't already exist
19
- if (!existed) {
20
- target.add(value);
21
- const newSize = target.size;
22
- const event = {
23
- action: 'set-add',
24
- path: path,
25
- value: value
26
- };
27
- emit(event);
28
- trigger(target, Symbol.iterator);
29
- if (oldSize !== newSize) {
30
- trigger(target, 'size');
31
- }
32
- }
33
- return receiver; // return the proxy itself for chaining
34
- };
35
- }
36
- if (prop === 'delete') {
37
- return function (value) {
38
- const existed = target.has(value);
39
- const oldSize = target.size;
40
- if (existed) {
41
- const oldValue = value;
42
- const result = target.delete(value);
43
- const newSize = target.size;
44
- if (result) { // only emit and trigger if delete was successful
45
- const event = {
46
- action: 'set-delete',
47
- path: path,
48
- value: value,
49
- oldValue: oldValue
50
- };
51
- emit(event);
52
- trigger(target, Symbol.iterator);
53
- if (oldSize !== newSize) {
54
- trigger(target, 'size');
55
- }
56
- }
57
- return result;
58
- }
59
- return false;
60
- };
61
- }
62
- if (prop === 'clear') {
63
- return function () {
64
- const oldSize = target.size;
65
- if (oldSize === 0)
66
- return;
67
- target.clear();
68
- const newSize = target.size;
69
- const event = {
70
- action: 'set-clear',
71
- path: path,
72
- value: null
73
- };
74
- emit(event);
75
- trigger(target, Symbol.iterator);
76
- if (oldSize !== newSize) {
77
- trigger(target, 'size');
78
- }
79
- };
80
- }
81
- if (prop === 'has') {
82
- track(target, Symbol.iterator);
83
- return function (value) {
84
- // track specific primitive value when 'has' is called
85
- // tracking object values for existence is complex and less common, handled by iteration track
86
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol') {
87
- track(target, String(value));
88
- }
89
- return target.has(value);
90
- }.bind(target);
91
- }
92
- // handle iteration methods
93
- if (prop === 'values' || prop === Symbol.iterator || prop === 'entries' || prop === 'keys' || prop === 'forEach') {
94
- track(target, Symbol.iterator);
95
- const originalMethod = Reflect.get(target, prop, receiver);
96
- // return custom iterators/foreach that wrap values during iteration
97
- if (prop === 'forEach') {
98
- return (callbackfn, thisArg) => {
99
- // use the proxied values() to ensure values passed to callback are wrapped and tracked
100
- const valuesIterator = proxy.values();
101
- for (const value of valuesIterator) {
102
- callbackfn.call(thisArg, value, value, proxy);
103
- }
104
- };
105
- }
106
- // handle symbol.iterator, values, keys, entries by creating generator functions
107
- return function* (...args) {
108
- let index = 0; // use index for path generation if value is not primitive
109
- const iterator = originalMethod.apply(target, args);
110
- for (const entry of iterator) {
111
- let valueToWrap = entry;
112
- let mapKey = undefined; // key for entries() which yields [value, value]
113
- if (prop === 'entries') {
114
- mapKey = entry[0]; // for Set.entries(), key and value are the same
115
- valueToWrap = entry[1];
116
- }
117
- track(target, String(index));
118
- let wrappedValue = valueToWrap;
119
- if (valueToWrap && typeof valueToWrap === 'object') {
120
- const cachedValueProxy = wrapperCache.get(valueToWrap);
121
- if (cachedValueProxy) {
122
- wrappedValue = cachedValueProxy;
123
- }
124
- else {
125
- // calculate path using index as key, as set values don't have inherent keys
126
- const keyForPath = String(index);
127
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyForPath}` : keyForPath;
128
- let newPath = getPathConcat(pathKey);
129
- if (newPath === undefined) {
130
- newPath = path.concat(keyForPath);
131
- setPathConcat(pathKey, newPath);
132
- }
133
- // recursively wrap nested structures
134
- if (valueToWrap instanceof Map)
135
- wrappedValue = wrapMap(valueToWrap, emit, newPath);
136
- else if (valueToWrap instanceof Set)
137
- wrappedValue = wrapSet(valueToWrap, emit, newPath);
138
- else if (Array.isArray(valueToWrap))
139
- wrappedValue = wrapArray(valueToWrap, emit, newPath);
140
- else if (valueToWrap instanceof Date)
141
- wrappedValue = new Date(valueToWrap.getTime()); // dates are not proxied, return copy
142
- else
143
- wrappedValue = reactive(valueToWrap, emit, newPath);
144
- }
145
- }
146
- if (prop === 'entries') {
147
- yield [wrappedValue, wrappedValue]; // set entries yield [value, value]
148
- }
149
- else {
150
- yield wrappedValue;
151
- }
152
- index++;
153
- }
154
- };
155
- }
156
- if (prop === 'size') {
157
- track(target, 'size');
158
- return target.size;
159
- }
160
- const value = Reflect.get(target, prop, receiver);
161
- return value;
162
- }
163
- });
164
- // cache the newly created proxy before returning
165
- wrapperCache.set(set, proxy);
166
- return proxy;
167
- }