@signaltree/core 5.1.1 → 5.1.3
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 +116 -55
- 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 +161 -0
- package/dist/enhancers/computed/lib/computed.js +21 -0
- package/dist/enhancers/devtools/lib/devtools.js +321 -0
- package/dist/enhancers/entities/lib/entities.js +93 -0
- package/dist/enhancers/index.js +72 -0
- package/dist/enhancers/memoization/lib/memoization.js +410 -0
- package/dist/enhancers/presets/lib/presets.js +87 -0
- package/dist/enhancers/serialization/constants.js +15 -0
- package/dist/enhancers/serialization/lib/serialization.js +662 -0
- package/dist/enhancers/time-travel/lib/time-travel.js +193 -0
- package/dist/index.js +19 -0
- package/dist/is-built-in-object.js +23 -0
- package/dist/lib/async-helpers.js +77 -0
- package/dist/lib/constants.js +56 -0
- package/dist/lib/entity-signal.js +280 -0
- package/dist/lib/memory/memory-manager.js +164 -0
- package/dist/lib/path-notifier.js +106 -0
- package/dist/lib/performance/diff-engine.js +156 -0
- package/dist/lib/performance/path-index.js +156 -0
- package/dist/lib/performance/update-engine.js +188 -0
- package/dist/lib/security/security-validator.js +121 -0
- package/dist/lib/signal-tree.js +626 -0
- package/dist/lib/types.js +9 -0
- package/dist/lib/utils.js +261 -0
- package/dist/lru-cache.js +64 -0
- package/dist/parse-path.js +13 -0
- package/package.json +1 -1
- package/src/async-helpers.d.ts +8 -0
- package/src/batching.d.ts +16 -0
- package/src/computed.d.ts +12 -0
- package/src/constants.d.ts +14 -0
- package/src/devtools.d.ts +77 -0
- package/src/diff-engine.d.ts +33 -0
- package/src/enhancers/batching/index.d.ts +1 -0
- package/src/enhancers/batching/lib/batching.d.ts +16 -0
- package/src/enhancers/batching/test-setup.d.ts +3 -0
- package/src/enhancers/computed/index.d.ts +1 -0
- package/src/enhancers/computed/lib/computed.d.ts +12 -0
- package/src/enhancers/devtools/index.d.ts +1 -0
- package/src/enhancers/devtools/lib/devtools.d.ts +77 -0
- package/src/enhancers/devtools/test-setup.d.ts +3 -0
- package/src/enhancers/entities/index.d.ts +1 -0
- package/src/enhancers/entities/lib/entities.d.ts +20 -0
- package/src/enhancers/entities/test-setup.d.ts +3 -0
- package/src/enhancers/index.d.ts +3 -0
- package/src/enhancers/memoization/index.d.ts +1 -0
- package/src/enhancers/memoization/lib/memoization.d.ts +65 -0
- package/src/enhancers/memoization/test-setup.d.ts +3 -0
- package/src/enhancers/presets/index.d.ts +1 -0
- package/src/enhancers/presets/lib/presets.d.ts +11 -0
- package/src/enhancers/presets/test-setup.d.ts +3 -0
- package/src/enhancers/serialization/constants.d.ts +14 -0
- package/src/enhancers/serialization/index.d.ts +2 -0
- package/src/enhancers/serialization/lib/serialization.d.ts +59 -0
- package/src/enhancers/serialization/test-setup.d.ts +3 -0
- package/src/enhancers/time-travel/index.d.ts +1 -0
- package/src/enhancers/time-travel/lib/time-travel.d.ts +36 -0
- package/src/enhancers/time-travel/lib/utils.d.ts +1 -0
- package/src/enhancers/time-travel/test-setup.d.ts +3 -0
- package/src/enhancers/types.d.ts +74 -0
- package/src/entities.d.ts +20 -0
- package/src/entity-signal.d.ts +1 -0
- package/src/index.d.ts +18 -0
- package/src/lib/async-helpers.d.ts +8 -0
- package/src/lib/constants.d.ts +41 -0
- package/src/lib/entity-signal.d.ts +1 -0
- package/src/lib/memory/memory-manager.d.ts +30 -0
- package/src/lib/path-notifier.d.ts +4 -0
- package/src/lib/performance/diff-engine.d.ts +33 -0
- package/src/lib/performance/path-index.d.ts +25 -0
- package/src/lib/performance/update-engine.d.ts +32 -0
- package/src/lib/security/security-validator.d.ts +33 -0
- package/src/lib/signal-tree.d.ts +8 -0
- package/src/lib/types.d.ts +278 -0
- package/src/lib/utils.d.ts +28 -0
- package/src/memoization.d.ts +65 -0
- package/src/memory-manager.d.ts +30 -0
- package/src/path-index.d.ts +25 -0
- package/src/path-notifier.d.ts +12 -0
- package/src/presets.d.ts +11 -0
- package/src/security-validator.d.ts +33 -0
- package/src/serialization.d.ts +59 -0
- package/src/signal-tree.d.ts +8 -0
- package/src/test-setup.d.ts +3 -0
- package/src/time-travel.d.ts +36 -0
- package/src/types.d.ts +278 -0
- package/src/update-engine.d.ts +32 -0
- package/src/utils.d.ts +1 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { signal, isSignal, computed, effect, inject, DestroyRef } from '@angular/core';
|
|
2
|
+
import { resolveEnhancerOrder } from '../enhancers/index.js';
|
|
3
|
+
import { SIGNAL_TREE_MESSAGES, SIGNAL_TREE_CONSTANTS } from './constants.js';
|
|
4
|
+
import { SignalMemoryManager } from './memory/memory-manager.js';
|
|
5
|
+
import { OptimizedUpdateEngine } from './performance/update-engine.js';
|
|
6
|
+
import { SecurityValidator } from './security/security-validator.js';
|
|
7
|
+
import { createLazySignalTree, unwrap } from './utils.js';
|
|
8
|
+
import { deepEqual } from '../deep-equal.js';
|
|
9
|
+
import { isBuiltInObject } from '../is-built-in-object.js';
|
|
10
|
+
|
|
11
|
+
const NODE_ACCESSOR_SYMBOL = Symbol.for('NodeAccessor');
|
|
12
|
+
function makeNodeAccessor() {
|
|
13
|
+
const accessor = function (arg) {
|
|
14
|
+
if (arguments.length === 0) {
|
|
15
|
+
return unwrap(accessor);
|
|
16
|
+
}
|
|
17
|
+
if (typeof arg === 'function') {
|
|
18
|
+
const updater = arg;
|
|
19
|
+
const currentValue = unwrap(accessor);
|
|
20
|
+
const newValue = updater(currentValue);
|
|
21
|
+
recursiveUpdate(accessor, newValue);
|
|
22
|
+
} else {
|
|
23
|
+
recursiveUpdate(accessor, arg);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
accessor[NODE_ACCESSOR_SYMBOL] = true;
|
|
27
|
+
return accessor;
|
|
28
|
+
}
|
|
29
|
+
function makeRootNodeAccessor(readSignal, writeSignal) {
|
|
30
|
+
const accessor = function (arg) {
|
|
31
|
+
if (arguments.length === 0) {
|
|
32
|
+
return readSignal();
|
|
33
|
+
}
|
|
34
|
+
if (typeof arg === 'function') {
|
|
35
|
+
const updater = arg;
|
|
36
|
+
writeSignal.set(updater(readSignal()));
|
|
37
|
+
} else {
|
|
38
|
+
writeSignal.set(arg);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
accessor[NODE_ACCESSOR_SYMBOL] = true;
|
|
42
|
+
return accessor;
|
|
43
|
+
}
|
|
44
|
+
function recursiveUpdate(target, updates) {
|
|
45
|
+
if (!updates || typeof updates !== 'object') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let targetObj;
|
|
49
|
+
if (isNodeAccessor(target)) {
|
|
50
|
+
targetObj = target;
|
|
51
|
+
} else if (target && typeof target === 'object') {
|
|
52
|
+
targetObj = target;
|
|
53
|
+
} else {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const updatesObj = updates;
|
|
57
|
+
for (const key in updatesObj) {
|
|
58
|
+
if (!(key in targetObj)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const targetProp = targetObj[key];
|
|
62
|
+
const updateValue = updatesObj[key];
|
|
63
|
+
if (isSignal(targetProp)) {
|
|
64
|
+
if ('set' in targetProp && typeof targetProp.set === 'function') {
|
|
65
|
+
targetProp.set(updateValue);
|
|
66
|
+
}
|
|
67
|
+
} else if (isNodeAccessor(targetProp)) {
|
|
68
|
+
if (updateValue && typeof updateValue === 'object') {
|
|
69
|
+
recursiveUpdate(targetProp, updateValue);
|
|
70
|
+
} else {
|
|
71
|
+
targetProp(updateValue);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function isNodeAccessor(value) {
|
|
77
|
+
return typeof value === 'function' && value && value[NODE_ACCESSOR_SYMBOL] === true;
|
|
78
|
+
}
|
|
79
|
+
function estimateObjectSize(obj, maxDepth = SIGNAL_TREE_CONSTANTS.ESTIMATE_MAX_DEPTH, currentDepth = 0) {
|
|
80
|
+
if (currentDepth >= maxDepth) return 1;
|
|
81
|
+
if (obj === null || obj === undefined) return 0;
|
|
82
|
+
if (typeof obj !== 'object') return 1;
|
|
83
|
+
let size = 0;
|
|
84
|
+
try {
|
|
85
|
+
if (Array.isArray(obj)) {
|
|
86
|
+
size = obj.length;
|
|
87
|
+
const sampleSize = Math.min(SIGNAL_TREE_CONSTANTS.ESTIMATE_SAMPLE_SIZE_ARRAY, obj.length);
|
|
88
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
89
|
+
size += estimateObjectSize(obj[i], maxDepth, currentDepth + 1) * 0.1;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const keys = Object.keys(obj);
|
|
93
|
+
size = keys.length;
|
|
94
|
+
const sampleSize = Math.min(SIGNAL_TREE_CONSTANTS.ESTIMATE_SAMPLE_SIZE_OBJECT, keys.length);
|
|
95
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
96
|
+
const value = obj[keys[i]];
|
|
97
|
+
size += estimateObjectSize(value, maxDepth, currentDepth + 1) * 0.5;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
return Math.floor(size);
|
|
104
|
+
}
|
|
105
|
+
function shouldUseLazy(obj, config, precomputedSize) {
|
|
106
|
+
if (config.useLazySignals !== undefined) return config.useLazySignals;
|
|
107
|
+
if (config.debugMode || config.enableDevTools) return false;
|
|
108
|
+
if (config.batchUpdates && config.useMemoization) return true;
|
|
109
|
+
const estimatedSize = precomputedSize ?? estimateObjectSize(obj);
|
|
110
|
+
return estimatedSize > SIGNAL_TREE_CONSTANTS.LAZY_THRESHOLD;
|
|
111
|
+
}
|
|
112
|
+
function validateTree(obj, config, path = []) {
|
|
113
|
+
if (!config.security) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const validator = new SecurityValidator(config.security);
|
|
117
|
+
function validate(value, currentPath) {
|
|
118
|
+
if (value === null || value === undefined) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value !== 'object') {
|
|
122
|
+
validator.validateValue(value);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (isBuiltInObject(value)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
value.forEach((item, index) => {
|
|
130
|
+
validate(item, [...currentPath, String(index)]);
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const keys = [...Object.keys(value), ...Object.getOwnPropertyNames(value)];
|
|
135
|
+
const uniqueKeys = [...new Set(keys)];
|
|
136
|
+
for (const key of uniqueKeys) {
|
|
137
|
+
if (typeof key === 'symbol') continue;
|
|
138
|
+
try {
|
|
139
|
+
validator.validateKey(key);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const err = error;
|
|
142
|
+
throw new Error(`${err.message}\nPath: ${[...currentPath, key].join('.')}`);
|
|
143
|
+
}
|
|
144
|
+
const val = value[key];
|
|
145
|
+
try {
|
|
146
|
+
validator.validateValue(val);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const err = error;
|
|
149
|
+
throw new Error(`${err.message}\nPath: ${[...currentPath, key].join('.')}`);
|
|
150
|
+
}
|
|
151
|
+
validate(val, [...currentPath, key]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
validate(obj, path);
|
|
155
|
+
}
|
|
156
|
+
function createEqualityFn(useShallowComparison) {
|
|
157
|
+
return useShallowComparison ? Object.is : deepEqual;
|
|
158
|
+
}
|
|
159
|
+
function createSignalStore(obj, equalityFn) {
|
|
160
|
+
if (obj === null || obj === undefined || typeof obj !== 'object') {
|
|
161
|
+
return signal(obj, {
|
|
162
|
+
equal: equalityFn
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(obj)) {
|
|
166
|
+
return signal(obj, {
|
|
167
|
+
equal: equalityFn
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (isBuiltInObject(obj)) {
|
|
171
|
+
return signal(obj, {
|
|
172
|
+
equal: equalityFn
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const store = {};
|
|
176
|
+
const processedObjects = new WeakSet();
|
|
177
|
+
if (processedObjects.has(obj)) {
|
|
178
|
+
console.warn(SIGNAL_TREE_MESSAGES.CIRCULAR_REF);
|
|
179
|
+
return signal(obj, {
|
|
180
|
+
equal: equalityFn
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
processedObjects.add(obj);
|
|
184
|
+
try {
|
|
185
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
186
|
+
try {
|
|
187
|
+
if (typeof key === 'symbol') continue;
|
|
188
|
+
if (isEntityMapMarker(value)) {
|
|
189
|
+
store[key] = value;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (isSignal(value)) {
|
|
193
|
+
store[key] = value;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (value === null || value === undefined) {
|
|
197
|
+
store[key] = signal(value, {
|
|
198
|
+
equal: equalityFn
|
|
199
|
+
});
|
|
200
|
+
} else if (typeof value !== 'object') {
|
|
201
|
+
store[key] = signal(value, {
|
|
202
|
+
equal: equalityFn
|
|
203
|
+
});
|
|
204
|
+
} else if (Array.isArray(value) || isBuiltInObject(value)) {
|
|
205
|
+
store[key] = signal(value, {
|
|
206
|
+
equal: equalityFn
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
const branch = createSignalStore(value, equalityFn);
|
|
210
|
+
const callableBranch = makeNodeAccessor();
|
|
211
|
+
for (const branchKey in branch) {
|
|
212
|
+
if (Object.prototype.hasOwnProperty.call(branch, branchKey)) {
|
|
213
|
+
try {
|
|
214
|
+
Object.defineProperty(callableBranch, branchKey, {
|
|
215
|
+
value: branch[branchKey],
|
|
216
|
+
enumerable: true,
|
|
217
|
+
configurable: true
|
|
218
|
+
});
|
|
219
|
+
} catch {}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
store[key] = callableBranch;
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.warn(`${SIGNAL_TREE_MESSAGES.SIGNAL_CREATION_FAILED} "${key}":`, error);
|
|
226
|
+
store[key] = signal(value, {
|
|
227
|
+
equal: equalityFn
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const symbols = Object.getOwnPropertySymbols(obj);
|
|
232
|
+
for (const sym of symbols) {
|
|
233
|
+
const value = obj[sym];
|
|
234
|
+
try {
|
|
235
|
+
if (isEntityMapMarker(value)) {
|
|
236
|
+
store[sym] = value;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (isSignal(value)) {
|
|
240
|
+
store[sym] = value;
|
|
241
|
+
} else {
|
|
242
|
+
store[sym] = signal(value, {
|
|
243
|
+
equal: equalityFn
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.warn(SIGNAL_TREE_MESSAGES.SIGNAL_CREATION_FAILED, error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
throw new Error(`Failed to create signal store: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
252
|
+
}
|
|
253
|
+
return store;
|
|
254
|
+
}
|
|
255
|
+
function isEntityMapMarker(value) {
|
|
256
|
+
return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
|
|
257
|
+
}
|
|
258
|
+
function enhanceTree(tree, config = {}) {
|
|
259
|
+
const isLazy = config.useLazySignals ?? shouldUseLazy(tree.state, config);
|
|
260
|
+
tree.with = (...enhancers) => {
|
|
261
|
+
if (enhancers.length === 0) {
|
|
262
|
+
return tree;
|
|
263
|
+
}
|
|
264
|
+
const coreCapabilities = new Set();
|
|
265
|
+
if (config.batchUpdates) coreCapabilities.add('batchUpdate');
|
|
266
|
+
if (config.useMemoization) coreCapabilities.add('memoize');
|
|
267
|
+
if (config.enableTimeTravel) coreCapabilities.add('undo');
|
|
268
|
+
if (config.enableDevTools) coreCapabilities.add('connectDevTools');
|
|
269
|
+
try {
|
|
270
|
+
for (const key of Object.keys(tree)) coreCapabilities.add(String(key));
|
|
271
|
+
} catch {}
|
|
272
|
+
const hasMetadata = enhancers.some(e => Boolean(e.metadata && (e.metadata.requires || e.metadata.provides)));
|
|
273
|
+
let orderedEnhancers = enhancers;
|
|
274
|
+
if (hasMetadata) {
|
|
275
|
+
try {
|
|
276
|
+
orderedEnhancers = resolveEnhancerOrder(enhancers, coreCapabilities, config.debugMode);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.warn(SIGNAL_TREE_MESSAGES.ENHANCER_ORDER_FAILED, err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const provided = new Set(coreCapabilities);
|
|
282
|
+
let currentTree = tree;
|
|
283
|
+
for (let i = 0; i < orderedEnhancers.length; i++) {
|
|
284
|
+
const enhancer = orderedEnhancers[i];
|
|
285
|
+
if (typeof enhancer !== 'function') {
|
|
286
|
+
throw new Error(SIGNAL_TREE_MESSAGES.ENHANCER_NOT_FUNCTION.replace('%d', String(i)));
|
|
287
|
+
}
|
|
288
|
+
const reqs = enhancer.metadata?.requires ?? [];
|
|
289
|
+
for (const r of reqs) {
|
|
290
|
+
if (!(r in currentTree) && !provided.has(r)) {
|
|
291
|
+
const name = enhancer.metadata?.name ?? `enhancer#${i}`;
|
|
292
|
+
const msg = SIGNAL_TREE_MESSAGES.ENHANCER_REQUIREMENT_MISSING.replace('%s', name).replace('%s', r);
|
|
293
|
+
if (config.debugMode) {
|
|
294
|
+
throw new Error(msg);
|
|
295
|
+
} else {
|
|
296
|
+
console.warn(msg);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const result = enhancer(currentTree);
|
|
302
|
+
if (result !== currentTree) currentTree = result;
|
|
303
|
+
const provs = enhancer.metadata?.provides ?? [];
|
|
304
|
+
for (const p of provs) provided.add(p);
|
|
305
|
+
if (config.debugMode && provs.length > 0) {
|
|
306
|
+
for (const p of provs) {
|
|
307
|
+
if (!(p in currentTree)) {
|
|
308
|
+
console.warn(SIGNAL_TREE_MESSAGES.ENHANCER_PROVIDES_MISSING.replace('%s', enhancer.metadata?.name ?? String(i)).replace('%s', p));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const name = enhancer.metadata?.name || `enhancer at index ${i}`;
|
|
314
|
+
console.error(SIGNAL_TREE_MESSAGES.ENHANCER_FAILED.replace('%s', name), error);
|
|
315
|
+
if (config.debugMode) {
|
|
316
|
+
console.error('[SignalTree] Enhancer stack trace:', enhancer);
|
|
317
|
+
console.error('[SignalTree] Tree state at failure:', currentTree);
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return currentTree;
|
|
323
|
+
};
|
|
324
|
+
tree.destroy = () => {
|
|
325
|
+
try {
|
|
326
|
+
if (isLazy) {
|
|
327
|
+
const state = tree.state;
|
|
328
|
+
if (state && typeof state === 'object' && '__cleanup__' in state) {
|
|
329
|
+
const cleanup = state.__cleanup__;
|
|
330
|
+
if (typeof cleanup === 'function') {
|
|
331
|
+
cleanup();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (config.debugMode) {
|
|
336
|
+
console.log(SIGNAL_TREE_MESSAGES.TREE_DESTROYED);
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error(SIGNAL_TREE_MESSAGES.CLEANUP_ERROR, error);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
addStubMethods(tree, config);
|
|
343
|
+
return tree;
|
|
344
|
+
}
|
|
345
|
+
function addStubMethods(tree, config) {
|
|
346
|
+
tree.batchUpdate = updater => {
|
|
347
|
+
console.warn(SIGNAL_TREE_MESSAGES.BATCH_NOT_ENABLED);
|
|
348
|
+
tree(current => {
|
|
349
|
+
const partial = updater(current);
|
|
350
|
+
return {
|
|
351
|
+
...current,
|
|
352
|
+
...partial
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
tree.memoize = (fn, cacheKey) => {
|
|
357
|
+
console.warn(SIGNAL_TREE_MESSAGES.MEMOIZE_NOT_ENABLED);
|
|
358
|
+
return computed(() => fn(tree()));
|
|
359
|
+
};
|
|
360
|
+
tree.memoizedUpdate = updater => {
|
|
361
|
+
if (config.debugMode) {
|
|
362
|
+
console.warn(SIGNAL_TREE_MESSAGES.MEMOIZE_NOT_ENABLED);
|
|
363
|
+
}
|
|
364
|
+
tree(current => ({
|
|
365
|
+
...current,
|
|
366
|
+
...updater(current)
|
|
367
|
+
}));
|
|
368
|
+
};
|
|
369
|
+
tree.clearMemoCache = () => {
|
|
370
|
+
if (config.debugMode) {
|
|
371
|
+
console.warn(SIGNAL_TREE_MESSAGES.MEMOIZE_NOT_ENABLED);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
tree.getCacheStats = () => ({
|
|
375
|
+
size: 0,
|
|
376
|
+
hitRate: 0,
|
|
377
|
+
totalHits: 0,
|
|
378
|
+
totalMisses: 0,
|
|
379
|
+
keys: []
|
|
380
|
+
});
|
|
381
|
+
tree.effect = fn => {
|
|
382
|
+
try {
|
|
383
|
+
effect(() => fn(tree()));
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (config.debugMode) {
|
|
386
|
+
console.warn(SIGNAL_TREE_MESSAGES.EFFECT_NO_CONTEXT, error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
tree.subscribe = fn => {
|
|
391
|
+
try {
|
|
392
|
+
const destroyRef = inject(DestroyRef);
|
|
393
|
+
let isDestroyed = false;
|
|
394
|
+
const effectRef = effect(() => {
|
|
395
|
+
if (!isDestroyed) {
|
|
396
|
+
fn(tree());
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
const unsubscribe = () => {
|
|
400
|
+
isDestroyed = true;
|
|
401
|
+
effectRef.destroy();
|
|
402
|
+
};
|
|
403
|
+
destroyRef.onDestroy(unsubscribe);
|
|
404
|
+
return unsubscribe;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (config.debugMode) {
|
|
407
|
+
console.warn(SIGNAL_TREE_MESSAGES.SUBSCRIBE_NO_CONTEXT, error);
|
|
408
|
+
}
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
tree.optimize = () => {
|
|
413
|
+
if (config.debugMode) {
|
|
414
|
+
console.warn(SIGNAL_TREE_MESSAGES.OPTIMIZE_NOT_AVAILABLE);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
tree.clearCache = () => {
|
|
418
|
+
if (config.debugMode) {
|
|
419
|
+
console.warn(SIGNAL_TREE_MESSAGES.CACHE_NOT_AVAILABLE);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
tree.invalidatePattern = () => {
|
|
423
|
+
if (config.debugMode) {
|
|
424
|
+
console.warn(SIGNAL_TREE_MESSAGES.PERFORMANCE_NOT_ENABLED);
|
|
425
|
+
}
|
|
426
|
+
return 0;
|
|
427
|
+
};
|
|
428
|
+
const treeWithEngine = tree;
|
|
429
|
+
if (!treeWithEngine.updateEngine) {
|
|
430
|
+
treeWithEngine.updateEngine = new OptimizedUpdateEngine(tree);
|
|
431
|
+
}
|
|
432
|
+
tree.updateOptimized = (updates, options = {}) => {
|
|
433
|
+
const treeWithEngine = tree;
|
|
434
|
+
const engine = treeWithEngine.updateEngine;
|
|
435
|
+
if (!engine) {
|
|
436
|
+
if (config.debugMode) {
|
|
437
|
+
console.warn(SIGNAL_TREE_MESSAGES.UPDATE_OPTIMIZED_NOT_AVAILABLE);
|
|
438
|
+
}
|
|
439
|
+
tree(current => ({
|
|
440
|
+
...current,
|
|
441
|
+
...updates
|
|
442
|
+
}));
|
|
443
|
+
return {
|
|
444
|
+
changed: true,
|
|
445
|
+
duration: 0,
|
|
446
|
+
changedPaths: Object.keys(updates),
|
|
447
|
+
stats: undefined
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return engine.update(tree(), updates, options);
|
|
451
|
+
};
|
|
452
|
+
tree.getMetrics = () => {
|
|
453
|
+
return {
|
|
454
|
+
updates: 0,
|
|
455
|
+
computations: 0,
|
|
456
|
+
cacheHits: 0,
|
|
457
|
+
cacheMisses: 0,
|
|
458
|
+
averageUpdateTime: 0
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
tree.entities = () => {
|
|
462
|
+
console.warn('[@signaltree/core] tree.entities() is deprecated and will be removed in v6.0. ' + 'Use entityMap<E>() + withEntities() + tree.$.collectionName instead. ' + 'See https://signaltree.dev/docs/migration for migration guide.');
|
|
463
|
+
if (config.debugMode) {
|
|
464
|
+
console.warn(SIGNAL_TREE_MESSAGES.ENTITY_HELPERS_NOT_AVAILABLE);
|
|
465
|
+
}
|
|
466
|
+
return {};
|
|
467
|
+
};
|
|
468
|
+
tree.undo = () => {
|
|
469
|
+
if (config.debugMode) {
|
|
470
|
+
console.warn(SIGNAL_TREE_MESSAGES.TIME_TRAVEL_NOT_AVAILABLE);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
tree.redo = () => {
|
|
474
|
+
if (config.debugMode) {
|
|
475
|
+
console.warn(SIGNAL_TREE_MESSAGES.TIME_TRAVEL_NOT_AVAILABLE);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
tree.getHistory = () => {
|
|
479
|
+
if (config.debugMode) {
|
|
480
|
+
console.warn(SIGNAL_TREE_MESSAGES.TIME_TRAVEL_NOT_AVAILABLE);
|
|
481
|
+
}
|
|
482
|
+
return [];
|
|
483
|
+
};
|
|
484
|
+
tree.resetHistory = () => {
|
|
485
|
+
if (config.debugMode) {
|
|
486
|
+
console.warn(SIGNAL_TREE_MESSAGES.TIME_TRAVEL_NOT_AVAILABLE);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function create(obj, config = {}) {
|
|
491
|
+
if (obj === null || obj === undefined) {
|
|
492
|
+
throw new Error(SIGNAL_TREE_MESSAGES.NULL_OR_UNDEFINED);
|
|
493
|
+
}
|
|
494
|
+
validateTree(obj, config);
|
|
495
|
+
const estimatedSize = estimateObjectSize(obj);
|
|
496
|
+
const equalityFn = createEqualityFn(config.useShallowComparison ?? false);
|
|
497
|
+
if (Array.isArray(obj)) {
|
|
498
|
+
const signalState = signal(obj, {
|
|
499
|
+
equal: equalityFn
|
|
500
|
+
});
|
|
501
|
+
const tree = makeRootNodeAccessor(signalState, signalState);
|
|
502
|
+
Object.defineProperty(tree, 'state', {
|
|
503
|
+
value: signalState,
|
|
504
|
+
enumerable: false
|
|
505
|
+
});
|
|
506
|
+
Object.defineProperty(tree, '$', {
|
|
507
|
+
value: signalState,
|
|
508
|
+
enumerable: false
|
|
509
|
+
});
|
|
510
|
+
enhanceTree(tree, config);
|
|
511
|
+
return tree;
|
|
512
|
+
}
|
|
513
|
+
const useLazy = shouldUseLazy(obj, config, estimatedSize);
|
|
514
|
+
if (config.debugMode) {
|
|
515
|
+
console.log(SIGNAL_TREE_MESSAGES.STRATEGY_SELECTION.replace('%s', useLazy ? 'lazy' : 'eager').replace('%d', String(estimatedSize)));
|
|
516
|
+
}
|
|
517
|
+
let signalState;
|
|
518
|
+
let memoryManager;
|
|
519
|
+
try {
|
|
520
|
+
if (useLazy && typeof obj === 'object') {
|
|
521
|
+
memoryManager = new SignalMemoryManager();
|
|
522
|
+
signalState = createLazySignalTree(obj, equalityFn, '', memoryManager);
|
|
523
|
+
} else {
|
|
524
|
+
signalState = createSignalStore(obj, equalityFn);
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (useLazy) {
|
|
528
|
+
console.warn(SIGNAL_TREE_MESSAGES.LAZY_FALLBACK, error);
|
|
529
|
+
signalState = createSignalStore(obj, equalityFn);
|
|
530
|
+
memoryManager = undefined;
|
|
531
|
+
} else {
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const tree = function (arg) {
|
|
536
|
+
if (arguments.length === 0) {
|
|
537
|
+
return unwrap(signalState);
|
|
538
|
+
}
|
|
539
|
+
if (typeof arg === 'function') {
|
|
540
|
+
const updater = arg;
|
|
541
|
+
const currentValue = unwrap(signalState);
|
|
542
|
+
const newValue = updater(currentValue);
|
|
543
|
+
recursiveUpdate(signalState, newValue);
|
|
544
|
+
} else {
|
|
545
|
+
recursiveUpdate(signalState, arg);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
Object.defineProperty(tree, NODE_ACCESSOR_SYMBOL, {
|
|
549
|
+
value: true,
|
|
550
|
+
enumerable: false
|
|
551
|
+
});
|
|
552
|
+
Object.defineProperty(tree, 'state', {
|
|
553
|
+
value: signalState,
|
|
554
|
+
enumerable: false
|
|
555
|
+
});
|
|
556
|
+
Object.defineProperty(tree, '$', {
|
|
557
|
+
value: signalState,
|
|
558
|
+
enumerable: false
|
|
559
|
+
});
|
|
560
|
+
if (memoryManager) {
|
|
561
|
+
Object.defineProperty(tree, 'dispose', {
|
|
562
|
+
value: () => {
|
|
563
|
+
memoryManager?.dispose();
|
|
564
|
+
const cleanup = signalState.__cleanup__;
|
|
565
|
+
if (typeof cleanup === 'function') {
|
|
566
|
+
cleanup();
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
enumerable: false,
|
|
570
|
+
writable: false
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
enhanceTree(tree, config);
|
|
574
|
+
for (const key in signalState) {
|
|
575
|
+
if (Object.prototype.hasOwnProperty.call(signalState, key)) {
|
|
576
|
+
if (!(key in tree)) {
|
|
577
|
+
try {
|
|
578
|
+
Object.defineProperty(tree, key, {
|
|
579
|
+
value: signalState[key],
|
|
580
|
+
enumerable: true,
|
|
581
|
+
configurable: true
|
|
582
|
+
});
|
|
583
|
+
} catch {}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return tree;
|
|
588
|
+
}
|
|
589
|
+
const presetConfigs = {
|
|
590
|
+
basic: {
|
|
591
|
+
useLazySignals: false,
|
|
592
|
+
debugMode: false
|
|
593
|
+
},
|
|
594
|
+
performance: {
|
|
595
|
+
useLazySignals: true,
|
|
596
|
+
batchUpdates: true,
|
|
597
|
+
useMemoization: true,
|
|
598
|
+
useShallowComparison: true
|
|
599
|
+
},
|
|
600
|
+
development: {
|
|
601
|
+
useLazySignals: false,
|
|
602
|
+
debugMode: true,
|
|
603
|
+
enableDevTools: true,
|
|
604
|
+
trackPerformance: true
|
|
605
|
+
},
|
|
606
|
+
production: {
|
|
607
|
+
useLazySignals: true,
|
|
608
|
+
batchUpdates: true,
|
|
609
|
+
useMemoization: true,
|
|
610
|
+
debugMode: false
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
function signalTree(obj, configOrPreset) {
|
|
614
|
+
if (typeof configOrPreset === 'string') {
|
|
615
|
+
const config = presetConfigs[configOrPreset];
|
|
616
|
+
if (!config) {
|
|
617
|
+
console.warn(SIGNAL_TREE_MESSAGES.PRESET_UNKNOWN.replace('%s', configOrPreset));
|
|
618
|
+
return create(obj, {});
|
|
619
|
+
}
|
|
620
|
+
return create(obj, config);
|
|
621
|
+
}
|
|
622
|
+
const config = configOrPreset || {};
|
|
623
|
+
return create(obj, config);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export { isNodeAccessor, signalTree };
|