@signaltree/core 4.0.15 → 4.1.0
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 +106 -38
- package/dist/constants.js +6 -0
- package/dist/deep-clone.js +80 -0
- package/dist/deep-equal.js +41 -0
- package/dist/enhancers/batching/lib/batching.js +141 -135
- package/dist/enhancers/computed/lib/computed.js +18 -16
- package/dist/enhancers/devtools/lib/devtools.js +303 -260
- package/dist/enhancers/entities/lib/entities.js +109 -104
- package/dist/enhancers/index.js +65 -77
- package/dist/enhancers/memoization/lib/memoization.js +339 -351
- package/dist/enhancers/middleware/lib/async-helpers.js +71 -79
- package/dist/enhancers/middleware/lib/middleware.js +126 -169
- package/dist/enhancers/presets/lib/presets.js +82 -71
- package/dist/enhancers/serialization/constants.js +14 -13
- package/dist/enhancers/serialization/lib/serialization.js +615 -623
- package/dist/enhancers/time-travel/lib/time-travel.js +178 -177
- package/dist/index.d.ts +1 -17
- package/dist/index.js +19 -16
- package/dist/is-built-in-object.js +23 -0
- package/dist/lib/constants.js +51 -55
- package/dist/lib/memory/memory-manager.js +152 -154
- package/dist/lib/performance/diff-engine.js +141 -141
- package/dist/lib/performance/path-index.js +139 -137
- package/dist/lib/performance/update-engine.js +171 -176
- package/dist/lib/security/security-validator.js +110 -128
- package/dist/lib/signal-tree.js +577 -611
- package/dist/lib/types.js +3 -9
- package/dist/lib/utils.js +236 -268
- package/dist/lru-cache.js +64 -0
- package/dist/parse-path.js +13 -0
- package/package.json +30 -16
- package/src/index.d.ts +17 -0
- package/{dist → src}/lib/utils.d.ts +1 -0
- package/dist/enhancers/batching/index.js +0 -1
- package/dist/enhancers/batching/jest.config.js +0 -21
- package/dist/enhancers/batching/test-setup.js +0 -5
- package/dist/enhancers/computed/index.js +0 -1
- package/dist/enhancers/computed/jest.config.js +0 -21
- package/dist/enhancers/devtools/index.js +0 -1
- package/dist/enhancers/devtools/jest.config.js +0 -21
- package/dist/enhancers/devtools/test-setup.js +0 -5
- package/dist/enhancers/entities/index.js +0 -1
- package/dist/enhancers/entities/jest.config.js +0 -21
- package/dist/enhancers/entities/test-setup.js +0 -5
- package/dist/enhancers/memoization/index.js +0 -1
- package/dist/enhancers/memoization/jest.config.js +0 -21
- package/dist/enhancers/memoization/test-setup.js +0 -5
- package/dist/enhancers/middleware/index.js +0 -2
- package/dist/enhancers/middleware/jest.config.js +0 -21
- package/dist/enhancers/middleware/test-setup.js +0 -5
- package/dist/enhancers/presets/index.js +0 -1
- package/dist/enhancers/presets/jest.config.js +0 -21
- package/dist/enhancers/presets/test-setup.js +0 -5
- package/dist/enhancers/serialization/index.js +0 -2
- package/dist/enhancers/serialization/jest.config.js +0 -21
- package/dist/enhancers/serialization/test-setup.js +0 -5
- package/dist/enhancers/time-travel/index.js +0 -1
- package/dist/enhancers/time-travel/jest.config.js +0 -21
- package/dist/enhancers/time-travel/lib/utils.js +0 -1
- package/dist/enhancers/time-travel/test-setup.js +0 -5
- package/dist/enhancers/types.js +0 -0
- /package/{dist → src}/enhancers/batching/index.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/lib/batching.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/index.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/lib/computed.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/index.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/lib/devtools.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/index.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/lib/entities.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/index.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/index.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/lib/memoization.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/index.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/lib/async-helpers.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/lib/middleware.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/index.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/lib/presets.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/constants.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/index.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/lib/serialization.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/index.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/lib/time-travel.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/lib/utils.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/types.d.ts +0 -0
- /package/{dist → src}/lib/constants.d.ts +0 -0
- /package/{dist → src}/lib/memory/memory-manager.d.ts +0 -0
- /package/{dist → src}/lib/performance/diff-engine.d.ts +0 -0
- /package/{dist → src}/lib/performance/path-index.d.ts +0 -0
- /package/{dist → src}/lib/performance/update-engine.d.ts +0 -0
- /package/{dist → src}/lib/security/security-validator.d.ts +0 -0
- /package/{dist → src}/lib/signal-tree.d.ts +0 -0
- /package/{dist → src}/lib/types.d.ts +0 -0
|
@@ -1,391 +1,379 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isNodeAccessor } from '../../../lib/utils.js';
|
|
2
|
+
import { LRUCache } from '../../../lru-cache.js';
|
|
3
|
+
import { deepEqual } from '../../../deep-equal.js';
|
|
4
|
+
|
|
3
5
|
const MAX_CACHE_SIZE = 1000;
|
|
4
6
|
const DEFAULT_TTL = 5 * 60 * 1000;
|
|
5
7
|
const memoizationCache = new Map();
|
|
6
8
|
function createMemoCacheStore(maxSize, enableLRU) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
get: (key) => {
|
|
21
|
-
const value = cache.get(key);
|
|
22
|
-
if (value !== undefined) {
|
|
23
|
-
shadow.set(key, value);
|
|
24
|
-
}
|
|
25
|
-
else if (shadow.has(key)) {
|
|
26
|
-
shadow.delete(key);
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
},
|
|
30
|
-
set: (key, value) => {
|
|
31
|
-
cache.set(key, value);
|
|
32
|
-
shadow.set(key, value);
|
|
33
|
-
pruneShadow();
|
|
34
|
-
},
|
|
35
|
-
delete: (key) => {
|
|
36
|
-
cache.delete(key);
|
|
37
|
-
shadow.delete(key);
|
|
38
|
-
},
|
|
39
|
-
clear: () => {
|
|
40
|
-
cache.clear();
|
|
41
|
-
shadow.clear();
|
|
42
|
-
},
|
|
43
|
-
size: () => shadow.size,
|
|
44
|
-
forEach: (callback) => {
|
|
45
|
-
shadow.forEach((value, key) => callback(value, key));
|
|
46
|
-
},
|
|
47
|
-
keys: () => shadow.keys(),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const store = new Map();
|
|
9
|
+
if (enableLRU) {
|
|
10
|
+
const cache = new LRUCache(maxSize);
|
|
11
|
+
const shadow = new Map();
|
|
12
|
+
const pruneShadow = () => {
|
|
13
|
+
while (shadow.size > cache.size()) {
|
|
14
|
+
const oldestKey = shadow.keys().next().value;
|
|
15
|
+
if (oldestKey === undefined) {
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
shadow.delete(oldestKey);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
51
21
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
22
|
+
get: key => {
|
|
23
|
+
const value = cache.get(key);
|
|
24
|
+
if (value !== undefined) {
|
|
25
|
+
shadow.set(key, value);
|
|
26
|
+
} else if (shadow.has(key)) {
|
|
27
|
+
shadow.delete(key);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
set: (key, value) => {
|
|
32
|
+
cache.set(key, value);
|
|
33
|
+
shadow.set(key, value);
|
|
34
|
+
pruneShadow();
|
|
35
|
+
},
|
|
36
|
+
delete: key => {
|
|
37
|
+
cache.delete(key);
|
|
38
|
+
shadow.delete(key);
|
|
39
|
+
},
|
|
40
|
+
clear: () => {
|
|
41
|
+
cache.clear();
|
|
42
|
+
shadow.clear();
|
|
43
|
+
},
|
|
44
|
+
size: () => shadow.size,
|
|
45
|
+
forEach: callback => {
|
|
46
|
+
shadow.forEach((value, key) => callback(value, key));
|
|
47
|
+
},
|
|
48
|
+
keys: () => shadow.keys()
|
|
63
49
|
};
|
|
50
|
+
}
|
|
51
|
+
const store = new Map();
|
|
52
|
+
return {
|
|
53
|
+
get: key => store.get(key),
|
|
54
|
+
set: (key, value) => {
|
|
55
|
+
store.set(key, value);
|
|
56
|
+
},
|
|
57
|
+
delete: key => store.delete(key),
|
|
58
|
+
clear: () => store.clear(),
|
|
59
|
+
size: () => store.size,
|
|
60
|
+
forEach: callback => {
|
|
61
|
+
store.forEach((value, key) => callback(value, key));
|
|
62
|
+
},
|
|
63
|
+
keys: () => store.keys()
|
|
64
|
+
};
|
|
64
65
|
}
|
|
65
66
|
function getCleanupInterval(tree) {
|
|
66
|
-
|
|
67
|
+
return tree._memoCleanupInterval;
|
|
67
68
|
}
|
|
68
69
|
function setCleanupInterval(tree, interval) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
if (interval === undefined) {
|
|
71
|
+
delete tree._memoCleanupInterval;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
tree._memoCleanupInterval = interval;
|
|
74
75
|
}
|
|
75
76
|
function clearCleanupInterval(tree) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
setCleanupInterval(tree);
|
|
77
|
+
const interval = getCleanupInterval(tree);
|
|
78
|
+
if (!interval) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
clearInterval(interval);
|
|
83
|
+
} catch {}
|
|
84
|
+
setCleanupInterval(tree);
|
|
86
85
|
}
|
|
87
86
|
function resetMemoizationCaches() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
memoizationCache.forEach((cache, tree) => {
|
|
88
|
+
cache.clear();
|
|
89
|
+
clearCleanupInterval(tree);
|
|
90
|
+
});
|
|
91
|
+
memoizationCache.clear();
|
|
93
92
|
}
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
function cleanupMemoizationCache() {
|
|
94
|
+
resetMemoizationCaches();
|
|
96
95
|
}
|
|
97
96
|
function shallowEqual(a, b) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!Object.prototype.hasOwnProperty.call(objA, key))
|
|
110
|
-
continue;
|
|
111
|
-
countA++;
|
|
112
|
-
if (!(key in objB) || objA[key] !== objB[key])
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
let countB = 0;
|
|
116
|
-
for (const key in objB) {
|
|
117
|
-
if (Object.prototype.hasOwnProperty.call(objB, key))
|
|
118
|
-
countB++;
|
|
119
|
-
}
|
|
120
|
-
return countA === countB;
|
|
97
|
+
if (a === b) return true;
|
|
98
|
+
if (a == null || b == null) return false;
|
|
99
|
+
if (typeof a !== typeof b) return false;
|
|
100
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
101
|
+
const objA = a;
|
|
102
|
+
const objB = b;
|
|
103
|
+
let countA = 0;
|
|
104
|
+
for (const key in objA) {
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(objA, key)) continue;
|
|
106
|
+
countA++;
|
|
107
|
+
if (!(key in objB) || objA[key] !== objB[key]) return false;
|
|
121
108
|
}
|
|
122
|
-
|
|
109
|
+
let countB = 0;
|
|
110
|
+
for (const key in objB) {
|
|
111
|
+
if (Object.prototype.hasOwnProperty.call(objB, key)) countB++;
|
|
112
|
+
}
|
|
113
|
+
return countA === countB;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
123
116
|
}
|
|
124
117
|
function generateCacheKey(fn, args) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
118
|
+
try {
|
|
119
|
+
return `${fn.name || 'anonymous'}_${JSON.stringify(args)}`;
|
|
120
|
+
} catch {
|
|
121
|
+
return `${fn.name || 'anonymous'}_${args.length}`;
|
|
122
|
+
}
|
|
131
123
|
}
|
|
132
124
|
function getEqualityFn(strategy) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
export function memoize(fn, keyFn, config = {}) {
|
|
144
|
-
const maxSize = config.maxCacheSize ?? MAX_CACHE_SIZE;
|
|
145
|
-
const ttl = config.ttl ?? DEFAULT_TTL;
|
|
146
|
-
const equality = getEqualityFn(config.equality ?? 'shallow');
|
|
147
|
-
const enableLRU = config.enableLRU ?? false;
|
|
148
|
-
const cache = createMemoCacheStore(maxSize, enableLRU);
|
|
149
|
-
const cleanExpiredEntries = () => {
|
|
150
|
-
if (!ttl)
|
|
151
|
-
return;
|
|
152
|
-
const now = Date.now();
|
|
153
|
-
cache.forEach((entry, key) => {
|
|
154
|
-
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
155
|
-
cache.delete(key);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
return (...args) => {
|
|
160
|
-
if (ttl && Math.random() < 0.01) {
|
|
161
|
-
cleanExpiredEntries();
|
|
162
|
-
}
|
|
163
|
-
const key = keyFn
|
|
164
|
-
? keyFn(...args)
|
|
165
|
-
: generateCacheKey(fn, args);
|
|
166
|
-
const cached = cache.get(key);
|
|
167
|
-
if (cached && (keyFn || equality(cached.deps, args))) {
|
|
168
|
-
if (enableLRU) {
|
|
169
|
-
cached.hitCount += 1;
|
|
170
|
-
}
|
|
171
|
-
return cached.value;
|
|
172
|
-
}
|
|
173
|
-
const result = fn(...args);
|
|
174
|
-
cache.set(key, {
|
|
175
|
-
value: result,
|
|
176
|
-
deps: args,
|
|
177
|
-
timestamp: Date.now(),
|
|
178
|
-
hitCount: 1,
|
|
179
|
-
});
|
|
180
|
-
return result;
|
|
181
|
-
};
|
|
125
|
+
switch (strategy) {
|
|
126
|
+
case 'shallow':
|
|
127
|
+
return shallowEqual;
|
|
128
|
+
case 'reference':
|
|
129
|
+
return (a, b) => a === b;
|
|
130
|
+
case 'deep':
|
|
131
|
+
default:
|
|
132
|
+
return deepEqual;
|
|
133
|
+
}
|
|
182
134
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
135
|
+
function memoize(fn, keyFn, config = {}) {
|
|
136
|
+
const maxSize = config.maxCacheSize ?? MAX_CACHE_SIZE;
|
|
137
|
+
const ttl = config.ttl ?? DEFAULT_TTL;
|
|
138
|
+
const equality = getEqualityFn(config.equality ?? 'shallow');
|
|
139
|
+
const enableLRU = config.enableLRU ?? false;
|
|
140
|
+
const cache = createMemoCacheStore(maxSize, enableLRU);
|
|
141
|
+
const cleanExpiredEntries = () => {
|
|
142
|
+
if (!ttl) return;
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
cache.forEach((entry, key) => {
|
|
145
|
+
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
146
|
+
cache.delete(key);
|
|
147
|
+
}
|
|
189
148
|
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
149
|
+
};
|
|
150
|
+
return (...args) => {
|
|
151
|
+
if (ttl && Math.random() < 0.01) {
|
|
152
|
+
cleanExpiredEntries();
|
|
153
|
+
}
|
|
154
|
+
const key = keyFn ? keyFn(...args) : generateCacheKey(fn, args);
|
|
155
|
+
const cached = cache.get(key);
|
|
156
|
+
if (cached && (keyFn || equality(cached.deps, args))) {
|
|
157
|
+
if (enableLRU) {
|
|
158
|
+
cached.hitCount += 1;
|
|
159
|
+
}
|
|
160
|
+
return cached.value;
|
|
161
|
+
}
|
|
162
|
+
const result = fn(...args);
|
|
163
|
+
cache.set(key, {
|
|
164
|
+
value: result,
|
|
165
|
+
deps: args,
|
|
166
|
+
timestamp: Date.now(),
|
|
167
|
+
hitCount: 1
|
|
197
168
|
});
|
|
169
|
+
return result;
|
|
170
|
+
};
|
|
198
171
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
172
|
+
function memoizeShallow(fn, keyFn) {
|
|
173
|
+
return memoize(fn, keyFn, {
|
|
174
|
+
equality: 'shallow',
|
|
175
|
+
enableLRU: false,
|
|
176
|
+
ttl: undefined,
|
|
177
|
+
maxCacheSize: 100
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function memoizeReference(fn, keyFn) {
|
|
181
|
+
return memoize(fn, keyFn, {
|
|
182
|
+
equality: 'reference',
|
|
183
|
+
enableLRU: false,
|
|
184
|
+
ttl: undefined,
|
|
185
|
+
maxCacheSize: 50
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const MEMOIZATION_PRESETS = {
|
|
189
|
+
selector: {
|
|
190
|
+
equality: 'reference',
|
|
191
|
+
maxCacheSize: 10,
|
|
192
|
+
enableLRU: false,
|
|
193
|
+
ttl: undefined
|
|
194
|
+
},
|
|
195
|
+
computed: {
|
|
196
|
+
equality: 'shallow',
|
|
197
|
+
maxCacheSize: 100,
|
|
198
|
+
enableLRU: false,
|
|
199
|
+
ttl: undefined
|
|
200
|
+
},
|
|
201
|
+
deepState: {
|
|
202
|
+
equality: 'deep',
|
|
203
|
+
maxCacheSize: 1000,
|
|
204
|
+
enableLRU: true,
|
|
205
|
+
ttl: 5 * 60 * 1000
|
|
206
|
+
},
|
|
207
|
+
highFrequency: {
|
|
208
|
+
equality: 'reference',
|
|
209
|
+
maxCacheSize: 5,
|
|
210
|
+
enableLRU: false,
|
|
211
|
+
ttl: undefined
|
|
212
|
+
}
|
|
224
213
|
};
|
|
225
|
-
|
|
226
|
-
|
|
214
|
+
function withSelectorMemoization() {
|
|
215
|
+
return withMemoization(MEMOIZATION_PRESETS.selector);
|
|
227
216
|
}
|
|
228
|
-
|
|
229
|
-
|
|
217
|
+
function withComputedMemoization() {
|
|
218
|
+
return withMemoization(MEMOIZATION_PRESETS.computed);
|
|
230
219
|
}
|
|
231
|
-
|
|
232
|
-
|
|
220
|
+
function withDeepStateMemoization() {
|
|
221
|
+
return withMemoization(MEMOIZATION_PRESETS.deepState);
|
|
233
222
|
}
|
|
234
|
-
|
|
235
|
-
|
|
223
|
+
function withHighFrequencyMemoization() {
|
|
224
|
+
return withMemoization(MEMOIZATION_PRESETS.highFrequency);
|
|
236
225
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
memoTree.memoizedUpdate = (updater) => {
|
|
255
|
-
const currentState = originalTreeCall();
|
|
256
|
-
const result = updater(currentState);
|
|
257
|
-
applyUpdateResult(result);
|
|
258
|
-
};
|
|
259
|
-
memoTree.clearMemoCache = () => {
|
|
260
|
-
};
|
|
261
|
-
memoTree.getCacheStats = () => ({
|
|
262
|
-
size: 0,
|
|
263
|
-
hitRate: 0,
|
|
264
|
-
totalHits: 0,
|
|
265
|
-
totalMisses: 0,
|
|
266
|
-
keys: [],
|
|
267
|
-
});
|
|
268
|
-
return memoTree;
|
|
226
|
+
function withMemoization(config = {}) {
|
|
227
|
+
const {
|
|
228
|
+
enabled = true,
|
|
229
|
+
maxCacheSize = 1000,
|
|
230
|
+
ttl,
|
|
231
|
+
equality = 'deep',
|
|
232
|
+
enableLRU = true
|
|
233
|
+
} = config;
|
|
234
|
+
return tree => {
|
|
235
|
+
const originalTreeCall = tree.bind(tree);
|
|
236
|
+
const applyUpdateResult = result => {
|
|
237
|
+
Object.entries(result).forEach(([propKey, value]) => {
|
|
238
|
+
const property = tree.state[propKey];
|
|
239
|
+
if (property && 'set' in property) {
|
|
240
|
+
property.set(value);
|
|
241
|
+
} else if (isNodeAccessor(property)) {
|
|
242
|
+
property(value);
|
|
269
243
|
}
|
|
270
|
-
|
|
271
|
-
memoizationCache.set(tree, cache);
|
|
272
|
-
const equalityFn = getEqualityFn(equality);
|
|
273
|
-
tree.memoizedUpdate = (updater, cacheKey) => {
|
|
274
|
-
const currentState = originalTreeCall();
|
|
275
|
-
const key = cacheKey ||
|
|
276
|
-
generateCacheKey(updater, [
|
|
277
|
-
currentState,
|
|
278
|
-
]);
|
|
279
|
-
const cached = cache.get(key);
|
|
280
|
-
if (cached && equalityFn(cached.deps, [currentState])) {
|
|
281
|
-
const cachedUpdate = cached.value;
|
|
282
|
-
applyUpdateResult(cachedUpdate);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const result = updater(currentState);
|
|
286
|
-
cache.set(key, {
|
|
287
|
-
value: result,
|
|
288
|
-
deps: [currentState],
|
|
289
|
-
timestamp: Date.now(),
|
|
290
|
-
hitCount: 1,
|
|
291
|
-
});
|
|
292
|
-
applyUpdateResult(result);
|
|
293
|
-
};
|
|
294
|
-
tree.clearMemoCache = (key) => {
|
|
295
|
-
if (key) {
|
|
296
|
-
cache.delete(key);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
cache.clear();
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
tree.getCacheStats = () => {
|
|
303
|
-
let totalHits = 0;
|
|
304
|
-
let totalMisses = 0;
|
|
305
|
-
cache.forEach((entry) => {
|
|
306
|
-
totalHits += entry.hitCount || 0;
|
|
307
|
-
totalMisses += Math.floor((entry.hitCount || 0) / 2);
|
|
308
|
-
});
|
|
309
|
-
const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
|
|
310
|
-
return {
|
|
311
|
-
size: cache.size(),
|
|
312
|
-
hitRate,
|
|
313
|
-
totalHits,
|
|
314
|
-
totalMisses,
|
|
315
|
-
keys: Array.from(cache.keys()),
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
|
-
const maybeInterval = getCleanupInterval(tree);
|
|
319
|
-
if (maybeInterval) {
|
|
320
|
-
const origClear = tree.clearMemoCache.bind(tree);
|
|
321
|
-
tree.clearMemoCache = (key) => {
|
|
322
|
-
origClear(key);
|
|
323
|
-
clearCleanupInterval(tree);
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
if (ttl) {
|
|
327
|
-
const cleanup = () => {
|
|
328
|
-
const now = Date.now();
|
|
329
|
-
cache.forEach((entry, key) => {
|
|
330
|
-
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
331
|
-
cache.delete(key);
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
};
|
|
335
|
-
const intervalId = setInterval(cleanup, ttl);
|
|
336
|
-
setCleanupInterval(tree, intervalId);
|
|
337
|
-
}
|
|
338
|
-
return tree;
|
|
244
|
+
});
|
|
339
245
|
};
|
|
246
|
+
if (!enabled) {
|
|
247
|
+
const memoTree = tree;
|
|
248
|
+
memoTree.memoizedUpdate = updater => {
|
|
249
|
+
const currentState = originalTreeCall();
|
|
250
|
+
const result = updater(currentState);
|
|
251
|
+
applyUpdateResult(result);
|
|
252
|
+
};
|
|
253
|
+
memoTree.clearMemoCache = () => {};
|
|
254
|
+
memoTree.getCacheStats = () => ({
|
|
255
|
+
size: 0,
|
|
256
|
+
hitRate: 0,
|
|
257
|
+
totalHits: 0,
|
|
258
|
+
totalMisses: 0,
|
|
259
|
+
keys: []
|
|
260
|
+
});
|
|
261
|
+
return memoTree;
|
|
262
|
+
}
|
|
263
|
+
const cache = createMemoCacheStore(maxCacheSize, enableLRU);
|
|
264
|
+
memoizationCache.set(tree, cache);
|
|
265
|
+
const equalityFn = getEqualityFn(equality);
|
|
266
|
+
tree.memoizedUpdate = (updater, cacheKey) => {
|
|
267
|
+
const currentState = originalTreeCall();
|
|
268
|
+
const key = cacheKey || generateCacheKey(updater, [currentState]);
|
|
269
|
+
const cached = cache.get(key);
|
|
270
|
+
if (cached && equalityFn(cached.deps, [currentState])) {
|
|
271
|
+
const cachedUpdate = cached.value;
|
|
272
|
+
applyUpdateResult(cachedUpdate);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const result = updater(currentState);
|
|
276
|
+
cache.set(key, {
|
|
277
|
+
value: result,
|
|
278
|
+
deps: [currentState],
|
|
279
|
+
timestamp: Date.now(),
|
|
280
|
+
hitCount: 1
|
|
281
|
+
});
|
|
282
|
+
applyUpdateResult(result);
|
|
283
|
+
};
|
|
284
|
+
tree.clearMemoCache = key => {
|
|
285
|
+
if (key) {
|
|
286
|
+
cache.delete(key);
|
|
287
|
+
} else {
|
|
288
|
+
cache.clear();
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
tree.getCacheStats = () => {
|
|
292
|
+
let totalHits = 0;
|
|
293
|
+
let totalMisses = 0;
|
|
294
|
+
cache.forEach(entry => {
|
|
295
|
+
totalHits += entry.hitCount || 0;
|
|
296
|
+
totalMisses += Math.floor((entry.hitCount || 0) / 2);
|
|
297
|
+
});
|
|
298
|
+
const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
|
|
299
|
+
return {
|
|
300
|
+
size: cache.size(),
|
|
301
|
+
hitRate,
|
|
302
|
+
totalHits,
|
|
303
|
+
totalMisses,
|
|
304
|
+
keys: Array.from(cache.keys())
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
const maybeInterval = getCleanupInterval(tree);
|
|
308
|
+
if (maybeInterval) {
|
|
309
|
+
const origClear = tree.clearMemoCache.bind(tree);
|
|
310
|
+
tree.clearMemoCache = key => {
|
|
311
|
+
origClear(key);
|
|
312
|
+
clearCleanupInterval(tree);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (ttl) {
|
|
316
|
+
const cleanup = () => {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
cache.forEach((entry, key) => {
|
|
319
|
+
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
320
|
+
cache.delete(key);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
const intervalId = setInterval(cleanup, ttl);
|
|
325
|
+
setCleanupInterval(tree, intervalId);
|
|
326
|
+
}
|
|
327
|
+
return tree;
|
|
328
|
+
};
|
|
340
329
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
equality: 'shallow',
|
|
350
|
-
enableLRU: true,
|
|
351
|
-
});
|
|
330
|
+
function withHighPerformanceMemoization() {
|
|
331
|
+
return withMemoization({
|
|
332
|
+
enabled: true,
|
|
333
|
+
maxCacheSize: 10000,
|
|
334
|
+
ttl: 300000,
|
|
335
|
+
equality: 'shallow',
|
|
336
|
+
enableLRU: true
|
|
337
|
+
});
|
|
352
338
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
339
|
+
function withLightweightMemoization() {
|
|
340
|
+
return withMemoization({
|
|
341
|
+
enabled: true,
|
|
342
|
+
maxCacheSize: 100,
|
|
343
|
+
ttl: undefined,
|
|
344
|
+
equality: 'reference',
|
|
345
|
+
enableLRU: false
|
|
346
|
+
});
|
|
361
347
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
348
|
+
function withShallowMemoization() {
|
|
349
|
+
return withMemoization({
|
|
350
|
+
enabled: true,
|
|
351
|
+
maxCacheSize: 1000,
|
|
352
|
+
ttl: 60000,
|
|
353
|
+
equality: 'shallow',
|
|
354
|
+
enableLRU: true
|
|
355
|
+
});
|
|
370
356
|
}
|
|
371
|
-
|
|
372
|
-
|
|
357
|
+
function clearAllCaches() {
|
|
358
|
+
resetMemoizationCaches();
|
|
373
359
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
360
|
+
function getGlobalCacheStats() {
|
|
361
|
+
let totalSize = 0;
|
|
362
|
+
let totalHits = 0;
|
|
363
|
+
let treeCount = 0;
|
|
364
|
+
memoizationCache.forEach(cache => {
|
|
365
|
+
treeCount++;
|
|
366
|
+
totalSize += cache.size();
|
|
367
|
+
cache.forEach(entry => {
|
|
368
|
+
totalHits += entry.hitCount || 0;
|
|
384
369
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
treeCount,
|
|
373
|
+
totalSize,
|
|
374
|
+
totalHits,
|
|
375
|
+
averageCacheSize: treeCount > 0 ? totalSize / treeCount : 0
|
|
376
|
+
};
|
|
391
377
|
}
|
|
378
|
+
|
|
379
|
+
export { MEMOIZATION_PRESETS, cleanupMemoizationCache, clearAllCaches, getGlobalCacheStats, memoize, memoizeReference, memoizeShallow, withComputedMemoization, withDeepStateMemoization, withHighFrequencyMemoization, withHighPerformanceMemoization, withLightweightMemoization, withMemoization, withSelectorMemoization, withShallowMemoization };
|