@signaltree/core 1.1.1 → 1.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 +19 -18
- package/fesm2022/signaltree-core.mjs +361 -434
- package/fesm2022/signaltree-core.mjs.map +1 -1
- package/index.d.ts +1 -213
- package/package.json +1 -1
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
import { signal, isSignal, computed, effect, inject, DestroyRef } from '@angular/core';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* SignalTree Utility Functions - Recursive Typing Implementation
|
|
5
|
-
*
|
|
6
|
-
* COPYRIGHT NOTICE:
|
|
7
|
-
* This file contains proprietary utility functions for the recursive typing system.
|
|
8
|
-
* The createLazySignalTree function and built-in object detection methods are
|
|
9
|
-
* protected intellectual property of Jonathan D Borgia.
|
|
10
|
-
*
|
|
11
|
-
* Licensed under Fair Source License - see LICENSE file for complete terms.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Enhanced equality function inspired by the monolithic implementation.
|
|
15
|
-
* Uses deep equality for arrays and objects, === for primitives.
|
|
16
|
-
* Optimized with early exits and type-specific comparisons.
|
|
17
|
-
*/
|
|
18
3
|
function equal(a, b) {
|
|
19
|
-
// Fast path for reference equality
|
|
20
4
|
if (a === b)
|
|
21
5
|
return true;
|
|
22
|
-
// Handle null/undefined cases
|
|
23
6
|
if (a == null || b == null)
|
|
24
7
|
return a === b;
|
|
25
|
-
// Type check first - most efficient early exit
|
|
26
8
|
const typeA = typeof a;
|
|
27
9
|
const typeB = typeof b;
|
|
28
10
|
if (typeA !== typeB)
|
|
29
11
|
return false;
|
|
30
|
-
// For primitives, === check above is sufficient
|
|
31
12
|
if (typeA !== 'object')
|
|
32
13
|
return false;
|
|
33
|
-
|
|
14
|
+
if (a instanceof Date && b instanceof Date) {
|
|
15
|
+
return a.getTime() === b.getTime();
|
|
16
|
+
}
|
|
17
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
18
|
+
return a.source === b.source && a.flags === b.flags;
|
|
19
|
+
}
|
|
20
|
+
if (a instanceof Map && b instanceof Map) {
|
|
21
|
+
if (a.size !== b.size)
|
|
22
|
+
return false;
|
|
23
|
+
for (const [key, value] of a) {
|
|
24
|
+
if (!b.has(key) || !equal(value, b.get(key)))
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (a instanceof Set && b instanceof Set) {
|
|
30
|
+
if (a.size !== b.size)
|
|
31
|
+
return false;
|
|
32
|
+
for (const value of a) {
|
|
33
|
+
if (!b.has(value))
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
34
38
|
if (Array.isArray(a)) {
|
|
35
39
|
if (!Array.isArray(b) || a.length !== b.length)
|
|
36
40
|
return false;
|
|
37
41
|
return a.every((item, index) => equal(item, b[index]));
|
|
38
42
|
}
|
|
39
|
-
// Arrays check above handles array vs object mismatch
|
|
40
43
|
if (Array.isArray(b))
|
|
41
44
|
return false;
|
|
42
|
-
// Handle objects with optimized comparison
|
|
43
45
|
const objA = a;
|
|
44
46
|
const objB = b;
|
|
45
47
|
const keysA = Object.keys(objA);
|
|
@@ -48,21 +50,11 @@ function equal(a, b) {
|
|
|
48
50
|
return false;
|
|
49
51
|
return keysA.every((key) => key in objB && equal(objA[key], objB[key]));
|
|
50
52
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Creates a terminal signal with the enhanced equality function.
|
|
53
|
-
* This should be used instead of Angular's signal() when you want
|
|
54
|
-
* the same deep equality behavior as signalTree.
|
|
55
|
-
*
|
|
56
|
-
* Inspired by the monolithic implementation's terminal signal creation.
|
|
57
|
-
*/
|
|
58
53
|
function terminalSignal(value, customEqual) {
|
|
59
54
|
return signal(value, {
|
|
60
55
|
equal: customEqual || equal,
|
|
61
56
|
});
|
|
62
57
|
}
|
|
63
|
-
/**
|
|
64
|
-
* LRU Cache implementation for efficient memory management
|
|
65
|
-
*/
|
|
66
58
|
class LRUCache {
|
|
67
59
|
maxSize;
|
|
68
60
|
cache = new Map();
|
|
@@ -76,14 +68,12 @@ class LRUCache {
|
|
|
76
68
|
this.cache.delete(firstKey);
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
|
-
// Remove if exists to update position
|
|
80
71
|
this.cache.delete(key);
|
|
81
|
-
this.cache.set(key, value);
|
|
72
|
+
this.cache.set(key, value);
|
|
82
73
|
}
|
|
83
74
|
get(key) {
|
|
84
75
|
const value = this.cache.get(key);
|
|
85
76
|
if (value !== undefined) {
|
|
86
|
-
// Move to end (mark as recently used)
|
|
87
77
|
this.cache.delete(key);
|
|
88
78
|
this.cache.set(key, value);
|
|
89
79
|
}
|
|
@@ -96,20 +86,8 @@ class LRUCache {
|
|
|
96
86
|
return this.cache.size;
|
|
97
87
|
}
|
|
98
88
|
}
|
|
99
|
-
// Path parsing cache with proper LRU eviction strategy
|
|
100
89
|
const MAX_CACHE_SIZE = 1000;
|
|
101
90
|
const pathCache = new LRUCache(MAX_CACHE_SIZE);
|
|
102
|
-
/**
|
|
103
|
-
* Parses a dot-notation path into an array of keys with LRU memoization.
|
|
104
|
-
* Critical for performance when accessing nested properties frequently.
|
|
105
|
-
* Includes proper LRU cache management to prevent memory leaks.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```typescript
|
|
109
|
-
* const keys1 = parsePath('user.name'); // Splits and caches
|
|
110
|
-
* const keys2 = parsePath('user.name'); // Returns cached result
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
91
|
function parsePath(path) {
|
|
114
92
|
const cached = pathCache.get(path);
|
|
115
93
|
if (cached) {
|
|
@@ -119,26 +97,11 @@ function parsePath(path) {
|
|
|
119
97
|
pathCache.set(path, parts);
|
|
120
98
|
return parts;
|
|
121
99
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Creates a lazy signal tree using Proxy for on-demand signal creation.
|
|
124
|
-
* Only creates signals when properties are first accessed, providing
|
|
125
|
-
* massive memory savings for large state objects.
|
|
126
|
-
* Uses WeakMap for memory-safe caching.
|
|
127
|
-
*
|
|
128
|
-
* @param obj - Source object to lazily signalify
|
|
129
|
-
* @param equalityFn - Equality function for signal comparison
|
|
130
|
-
* @param basePath - Base path for nested objects (internal use)
|
|
131
|
-
* @returns Proxied object that creates signals on first access
|
|
132
|
-
*/
|
|
133
100
|
function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
134
|
-
// Use Map instead of WeakMap for better control over cleanup
|
|
135
101
|
const signalCache = new Map();
|
|
136
102
|
const nestedProxies = new Map();
|
|
137
|
-
// Track cleanup functions for nested proxies
|
|
138
103
|
const nestedCleanups = new Map();
|
|
139
|
-
// Enhanced cleanup function
|
|
140
104
|
const cleanup = () => {
|
|
141
|
-
// Clean up all nested proxies first
|
|
142
105
|
nestedCleanups.forEach((cleanupFn) => {
|
|
143
106
|
try {
|
|
144
107
|
cleanupFn();
|
|
@@ -148,11 +111,9 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
148
111
|
}
|
|
149
112
|
});
|
|
150
113
|
nestedCleanups.clear();
|
|
151
|
-
// Clear caches
|
|
152
114
|
signalCache.clear();
|
|
153
115
|
nestedProxies.clear();
|
|
154
116
|
};
|
|
155
|
-
// Enhanced built-in object detection
|
|
156
117
|
const isBuiltInObject = (v) => {
|
|
157
118
|
if (v === null || v === undefined)
|
|
158
119
|
return false;
|
|
@@ -175,38 +136,30 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
175
136
|
};
|
|
176
137
|
const proxy = new Proxy(obj, {
|
|
177
138
|
get(target, prop) {
|
|
178
|
-
// Handle cleanup method
|
|
179
139
|
if (prop === '__cleanup__') {
|
|
180
140
|
return cleanup;
|
|
181
141
|
}
|
|
182
|
-
// Handle symbol properties (like Symbol.iterator) normally
|
|
183
142
|
if (typeof prop === 'symbol') {
|
|
184
143
|
return target[prop];
|
|
185
144
|
}
|
|
186
|
-
// Handle inspection methods
|
|
187
145
|
if (prop === 'valueOf' || prop === 'toString') {
|
|
188
146
|
return target[prop];
|
|
189
147
|
}
|
|
190
148
|
const key = prop;
|
|
191
149
|
const path = basePath ? `${basePath}.${key}` : key;
|
|
192
|
-
// Safety check for property existence
|
|
193
150
|
if (!(key in target)) {
|
|
194
151
|
return undefined;
|
|
195
152
|
}
|
|
196
153
|
const value = target[key];
|
|
197
|
-
// If it's already a signal, return it
|
|
198
154
|
if (isSignal(value)) {
|
|
199
155
|
return value;
|
|
200
156
|
}
|
|
201
|
-
// Check if we already have a signal for this path
|
|
202
157
|
if (signalCache.has(path)) {
|
|
203
158
|
return signalCache.get(path);
|
|
204
159
|
}
|
|
205
|
-
// Check if we have a nested proxy cached
|
|
206
160
|
if (nestedProxies.has(path)) {
|
|
207
161
|
return nestedProxies.get(path);
|
|
208
162
|
}
|
|
209
|
-
// Handle nested objects - create lazy proxy
|
|
210
163
|
if (value &&
|
|
211
164
|
typeof value === 'object' &&
|
|
212
165
|
!Array.isArray(value) &&
|
|
@@ -215,7 +168,6 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
215
168
|
try {
|
|
216
169
|
const nestedProxy = createLazySignalTree(value, equalityFn, path);
|
|
217
170
|
nestedProxies.set(path, nestedProxy);
|
|
218
|
-
// Store cleanup function for nested proxy
|
|
219
171
|
const proxyWithCleanup = nestedProxy;
|
|
220
172
|
if (typeof proxyWithCleanup.__cleanup__ === 'function') {
|
|
221
173
|
nestedCleanups.set(path, proxyWithCleanup.__cleanup__);
|
|
@@ -224,13 +176,11 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
224
176
|
}
|
|
225
177
|
catch (error) {
|
|
226
178
|
console.warn(`Failed to create lazy proxy for path "${path}":`, error);
|
|
227
|
-
// Fallback: create a signal for the object
|
|
228
179
|
const fallbackSignal = signal(value, ...(ngDevMode ? [{ debugName: "fallbackSignal", equal: equalityFn }] : [{ equal: equalityFn }]));
|
|
229
180
|
signalCache.set(path, fallbackSignal);
|
|
230
181
|
return fallbackSignal;
|
|
231
182
|
}
|
|
232
183
|
}
|
|
233
|
-
// Create signal for primitive values, arrays, and built-in objects
|
|
234
184
|
try {
|
|
235
185
|
const newSignal = signal(value, ...(ngDevMode ? [{ debugName: "newSignal", equal: equalityFn }] : [{ equal: equalityFn }]));
|
|
236
186
|
signalCache.set(path, newSignal);
|
|
@@ -238,7 +188,7 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
238
188
|
}
|
|
239
189
|
catch (error) {
|
|
240
190
|
console.warn(`Failed to create signal for path "${path}":`, error);
|
|
241
|
-
return value;
|
|
191
|
+
return value;
|
|
242
192
|
}
|
|
243
193
|
},
|
|
244
194
|
set(target, prop, value) {
|
|
@@ -249,16 +199,12 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
249
199
|
const key = prop;
|
|
250
200
|
const path = basePath ? `${basePath}.${key}` : key;
|
|
251
201
|
try {
|
|
252
|
-
// Update the original object
|
|
253
202
|
target[key] = value;
|
|
254
|
-
// If we have a cached signal, update it
|
|
255
203
|
const cachedSignal = signalCache.get(path);
|
|
256
204
|
if (cachedSignal && 'set' in cachedSignal) {
|
|
257
205
|
cachedSignal.set(value);
|
|
258
206
|
}
|
|
259
|
-
// Clear nested proxy cache if the value type changed
|
|
260
207
|
if (nestedProxies.has(path)) {
|
|
261
|
-
// Clean up the nested proxy
|
|
262
208
|
const nestedCleanup = nestedCleanups.get(path);
|
|
263
209
|
if (nestedCleanup) {
|
|
264
210
|
nestedCleanup();
|
|
@@ -285,61 +231,7 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
|
|
|
285
231
|
});
|
|
286
232
|
return proxy;
|
|
287
233
|
}
|
|
288
|
-
|
|
289
|
-
* Native deep equality check for arrays and objects.
|
|
290
|
-
* Handles all common cases that lodash.isEqual handles for our use cases.
|
|
291
|
-
*/
|
|
292
|
-
function deepEqual(a, b) {
|
|
293
|
-
// Same reference or primitives
|
|
294
|
-
if (a === b)
|
|
295
|
-
return true;
|
|
296
|
-
// Handle null/undefined
|
|
297
|
-
if (a == null || b == null)
|
|
298
|
-
return false;
|
|
299
|
-
// Different types
|
|
300
|
-
if (typeof a !== typeof b)
|
|
301
|
-
return false;
|
|
302
|
-
// Handle dates
|
|
303
|
-
if (a instanceof Date && b instanceof Date) {
|
|
304
|
-
return a.getTime() === b.getTime();
|
|
305
|
-
}
|
|
306
|
-
// Handle arrays
|
|
307
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
308
|
-
if (a.length !== b.length)
|
|
309
|
-
return false;
|
|
310
|
-
for (let i = 0; i < a.length; i++) {
|
|
311
|
-
if (!deepEqual(a[i], b[i]))
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
// Handle objects (but not arrays, dates, or other special objects)
|
|
317
|
-
if (typeof a === 'object' &&
|
|
318
|
-
typeof b === 'object' &&
|
|
319
|
-
!Array.isArray(a) &&
|
|
320
|
-
!Array.isArray(b) &&
|
|
321
|
-
!(a instanceof Date) &&
|
|
322
|
-
!(b instanceof Date)) {
|
|
323
|
-
const objA = a;
|
|
324
|
-
const objB = b;
|
|
325
|
-
const keysA = Object.keys(objA);
|
|
326
|
-
const keysB = Object.keys(objB);
|
|
327
|
-
if (keysA.length !== keysB.length)
|
|
328
|
-
return false;
|
|
329
|
-
for (const key of keysA) {
|
|
330
|
-
if (!(key in objB))
|
|
331
|
-
return false;
|
|
332
|
-
if (!deepEqual(objA[key], objB[key]))
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
// For all other cases (primitives that aren't equal)
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Shallow equality check for objects and arrays.
|
|
342
|
-
*/
|
|
234
|
+
const deepEqual = equal;
|
|
343
235
|
function shallowEqual(a, b) {
|
|
344
236
|
if (a === b)
|
|
345
237
|
return true;
|
|
@@ -369,281 +261,274 @@ function shallowEqual(a, b) {
|
|
|
369
261
|
return false;
|
|
370
262
|
}
|
|
371
263
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
264
|
+
function estimateObjectSize(obj, maxDepth = 3, currentDepth = 0) {
|
|
265
|
+
if (currentDepth >= maxDepth)
|
|
266
|
+
return 1;
|
|
267
|
+
if (obj === null || obj === undefined)
|
|
268
|
+
return 0;
|
|
269
|
+
if (typeof obj !== 'object')
|
|
270
|
+
return 1;
|
|
271
|
+
let size = 0;
|
|
272
|
+
try {
|
|
273
|
+
if (Array.isArray(obj)) {
|
|
274
|
+
size = obj.length;
|
|
275
|
+
const sampleSize = Math.min(3, obj.length);
|
|
276
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
277
|
+
size += estimateObjectSize(obj[i], maxDepth, currentDepth + 1) * 0.1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const keys = Object.keys(obj);
|
|
282
|
+
size = keys.length;
|
|
283
|
+
const sampleSize = Math.min(5, keys.length);
|
|
284
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
285
|
+
const value = obj[keys[i]];
|
|
286
|
+
size += estimateObjectSize(value, maxDepth, currentDepth + 1) * 0.5;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
return Math.floor(size);
|
|
294
|
+
}
|
|
295
|
+
function shouldUseLazy(obj, config) {
|
|
296
|
+
if (config.useLazySignals !== undefined) {
|
|
297
|
+
return config.useLazySignals;
|
|
298
|
+
}
|
|
299
|
+
if (config.debugMode || config.enableDevTools) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (config.batchUpdates && config.useMemoization) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
const estimatedSize = estimateObjectSize(obj);
|
|
306
|
+
return estimatedSize > 50;
|
|
307
|
+
}
|
|
401
308
|
function createEqualityFn(useShallowComparison) {
|
|
402
309
|
return useShallowComparison ? Object.is : equal;
|
|
403
310
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
311
|
+
function isBuiltInObject(v) {
|
|
312
|
+
if (v === null || v === undefined)
|
|
313
|
+
return false;
|
|
314
|
+
if (v instanceof Date ||
|
|
315
|
+
v instanceof RegExp ||
|
|
316
|
+
typeof v === 'function' ||
|
|
317
|
+
v instanceof Map ||
|
|
318
|
+
v instanceof Set ||
|
|
319
|
+
v instanceof WeakMap ||
|
|
320
|
+
v instanceof WeakSet ||
|
|
321
|
+
v instanceof ArrayBuffer ||
|
|
322
|
+
v instanceof DataView ||
|
|
323
|
+
v instanceof Error ||
|
|
324
|
+
v instanceof Promise) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (v instanceof Int8Array ||
|
|
328
|
+
v instanceof Uint8Array ||
|
|
329
|
+
v instanceof Uint8ClampedArray ||
|
|
330
|
+
v instanceof Int16Array ||
|
|
331
|
+
v instanceof Uint16Array ||
|
|
332
|
+
v instanceof Int32Array ||
|
|
333
|
+
v instanceof Uint32Array ||
|
|
334
|
+
v instanceof Float32Array ||
|
|
335
|
+
v instanceof Float64Array ||
|
|
336
|
+
v instanceof BigInt64Array ||
|
|
337
|
+
v instanceof BigUint64Array) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (typeof window !== 'undefined') {
|
|
341
|
+
if (v instanceof URL ||
|
|
342
|
+
v instanceof URLSearchParams ||
|
|
343
|
+
v instanceof FormData ||
|
|
344
|
+
v instanceof Blob ||
|
|
345
|
+
(typeof File !== 'undefined' && v instanceof File) ||
|
|
346
|
+
(typeof FileList !== 'undefined' && v instanceof FileList) ||
|
|
347
|
+
(typeof Headers !== 'undefined' && v instanceof Headers) ||
|
|
348
|
+
(typeof Request !== 'undefined' && v instanceof Request) ||
|
|
349
|
+
(typeof Response !== 'undefined' && v instanceof Response) ||
|
|
350
|
+
(typeof AbortController !== 'undefined' &&
|
|
351
|
+
v instanceof AbortController) ||
|
|
352
|
+
(typeof AbortSignal !== 'undefined' && v instanceof AbortSignal)) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const NodeBuffer = globalThis?.Buffer;
|
|
358
|
+
if (NodeBuffer && v instanceof NodeBuffer) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
433
366
|
function create(obj, config = {}) {
|
|
367
|
+
if (obj === null || obj === undefined) {
|
|
368
|
+
throw new Error('Cannot create SignalTree from null or undefined');
|
|
369
|
+
}
|
|
434
370
|
const equalityFn = createEqualityFn(config.useShallowComparison ?? false);
|
|
435
|
-
const useLazy = config
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
?
|
|
439
|
-
|
|
371
|
+
const useLazy = shouldUseLazy(obj, config);
|
|
372
|
+
if (config.debugMode) {
|
|
373
|
+
const estimatedSize = estimateObjectSize(obj);
|
|
374
|
+
console.log(`[SignalTree] Creating tree with ${useLazy ? 'lazy' : 'eager'} strategy (estimated size: ${estimatedSize})`);
|
|
375
|
+
}
|
|
376
|
+
let signalState;
|
|
377
|
+
try {
|
|
378
|
+
if (useLazy && typeof obj === 'object') {
|
|
379
|
+
signalState = createLazySignalTree(obj, equalityFn);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
signalState = createSignalStore(obj, equalityFn);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
if (useLazy) {
|
|
387
|
+
console.warn('[SignalTree] Lazy creation failed, falling back to eager:', error);
|
|
388
|
+
signalState = createSignalStore(obj, equalityFn);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
440
394
|
const resultTree = {
|
|
441
395
|
state: signalState,
|
|
442
|
-
$: signalState,
|
|
396
|
+
$: signalState,
|
|
443
397
|
};
|
|
444
|
-
enhanceTree(resultTree);
|
|
398
|
+
enhanceTree(resultTree, config);
|
|
445
399
|
return resultTree;
|
|
446
400
|
}
|
|
447
|
-
/**
|
|
448
|
-
* Creates signals using signal-store pattern for perfect type inference.
|
|
449
|
-
* This is the key function that preserves exact type relationships recursively.
|
|
450
|
-
* Based on the original signal-store pattern that maintains type information.
|
|
451
|
-
*
|
|
452
|
-
* @template T - The object type to process (preserves exact type structure)
|
|
453
|
-
* @param obj - The object to convert to signals
|
|
454
|
-
* @param equalityFn - Function to compare values for equality
|
|
455
|
-
* @returns A deeply signalified version maintaining exact type structure
|
|
456
|
-
*
|
|
457
|
-
* @example
|
|
458
|
-
* ```typescript
|
|
459
|
-
* const store = createSignalStore({
|
|
460
|
-
* user: { name: 'John', age: 30 },
|
|
461
|
-
* settings: { theme: 'dark' }
|
|
462
|
-
* }, Object.is);
|
|
463
|
-
*
|
|
464
|
-
* // Perfect type inference maintained throughout
|
|
465
|
-
* store.user.name.set('Jane');
|
|
466
|
-
* console.log(store.settings.theme()); // 'dark'
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
401
|
function createSignalStore(obj, equalityFn) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
402
|
+
if (obj === null || obj === undefined || typeof obj !== 'object') {
|
|
403
|
+
return signal(obj, { equal: equalityFn });
|
|
404
|
+
}
|
|
405
|
+
if (Array.isArray(obj)) {
|
|
406
|
+
return signal(obj, { equal: equalityFn });
|
|
473
407
|
}
|
|
474
|
-
if (
|
|
475
|
-
// For primitives, just return a signal
|
|
408
|
+
if (isBuiltInObject(obj)) {
|
|
476
409
|
return signal(obj, { equal: equalityFn });
|
|
477
410
|
}
|
|
478
411
|
const store = {};
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
typeof v === 'function' ||
|
|
486
|
-
v instanceof Map ||
|
|
487
|
-
v instanceof Set ||
|
|
488
|
-
v instanceof WeakMap ||
|
|
489
|
-
v instanceof WeakSet ||
|
|
490
|
-
v instanceof ArrayBuffer ||
|
|
491
|
-
v instanceof DataView ||
|
|
492
|
-
v instanceof Error ||
|
|
493
|
-
v instanceof Promise ||
|
|
494
|
-
v instanceof URL ||
|
|
495
|
-
v instanceof URLSearchParams ||
|
|
496
|
-
v instanceof FormData ||
|
|
497
|
-
v instanceof Blob ||
|
|
498
|
-
(typeof File !== 'undefined' && v instanceof File));
|
|
499
|
-
};
|
|
500
|
-
// Safe object iteration
|
|
412
|
+
const processedObjects = new WeakSet();
|
|
413
|
+
if (processedObjects.has(obj)) {
|
|
414
|
+
console.warn('[SignalTree] Circular reference detected, creating reference signal');
|
|
415
|
+
return signal(obj, { equal: equalityFn });
|
|
416
|
+
}
|
|
417
|
+
processedObjects.add(obj);
|
|
501
418
|
try {
|
|
502
419
|
for (const [key, value] of Object.entries(obj)) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
store[key] = value;
|
|
510
|
-
}
|
|
511
|
-
else if (isObj(value)) {
|
|
512
|
-
// CRITICAL: Recursive call with type preservation
|
|
513
|
-
try {
|
|
514
|
-
store[key] = createSignalStore(value, equalityFn);
|
|
420
|
+
try {
|
|
421
|
+
if (typeof key === 'symbol')
|
|
422
|
+
continue;
|
|
423
|
+
if (isSignal(value)) {
|
|
424
|
+
store[key] = value;
|
|
425
|
+
continue;
|
|
515
426
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
427
|
+
if (value === null || value === undefined) {
|
|
428
|
+
store[key] = signal(value, {
|
|
429
|
+
equal: equalityFn,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else if (typeof value !== 'object') {
|
|
433
|
+
store[key] = signal(value, {
|
|
434
|
+
equal: equalityFn,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
else if (Array.isArray(value) || isBuiltInObject(value)) {
|
|
519
438
|
store[key] = signal(value, {
|
|
520
439
|
equal: equalityFn,
|
|
521
440
|
});
|
|
522
441
|
}
|
|
442
|
+
else {
|
|
443
|
+
store[key] = createSignalStore(value, equalityFn);
|
|
444
|
+
}
|
|
523
445
|
}
|
|
524
|
-
|
|
525
|
-
|
|
446
|
+
catch (error) {
|
|
447
|
+
console.warn(`[SignalTree] Failed to create signal for key "${key}":`, error);
|
|
526
448
|
store[key] = signal(value, {
|
|
527
449
|
equal: equalityFn,
|
|
528
450
|
});
|
|
529
451
|
}
|
|
530
452
|
}
|
|
453
|
+
const symbols = Object.getOwnPropertySymbols(obj);
|
|
454
|
+
for (const sym of symbols) {
|
|
455
|
+
const value = obj[sym];
|
|
456
|
+
try {
|
|
457
|
+
if (isSignal(value)) {
|
|
458
|
+
store[sym] = value;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
store[sym] = signal(value, {
|
|
462
|
+
equal: equalityFn,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
console.warn(`[SignalTree] Failed to create signal for symbol key:`, error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
531
470
|
}
|
|
532
471
|
catch (error) {
|
|
533
472
|
throw new Error(`Failed to create signal store: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
534
473
|
}
|
|
535
474
|
return store;
|
|
536
475
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
* Adds core methods that every SignalTree needs for basic operation.
|
|
540
|
-
*
|
|
541
|
-
* @template T - The state object type
|
|
542
|
-
* @param tree - The tree to enhance with basic functionality
|
|
543
|
-
* @returns The enhanced tree with unwrap, update, and pipe methods
|
|
544
|
-
*
|
|
545
|
-
* @example
|
|
546
|
-
* ```typescript
|
|
547
|
-
* const basicTree = { state: signalState, $: signalState };
|
|
548
|
-
* enhanceTree(basicTree);
|
|
549
|
-
*
|
|
550
|
-
* // Now has basic methods:
|
|
551
|
-
* const currentState = basicTree.unwrap();
|
|
552
|
-
* basicTree.update(state => ({ ...state, count: state.count + 1 }));
|
|
553
|
-
* const enhancedTree = basicTree.pipe(withSomeFeature());
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
function enhanceTree(tree) {
|
|
557
|
-
/**
|
|
558
|
-
* Unwraps the current state by reading all signal values.
|
|
559
|
-
* Recursively converts the signal tree back to plain JavaScript values.
|
|
560
|
-
*
|
|
561
|
-
* @returns The current state as a plain object
|
|
562
|
-
*
|
|
563
|
-
* @example
|
|
564
|
-
* ```typescript
|
|
565
|
-
* const tree = signalTree({
|
|
566
|
-
* user: { name: 'John', age: 30 },
|
|
567
|
-
* count: 0
|
|
568
|
-
* });
|
|
569
|
-
*
|
|
570
|
-
* tree.$.user.name.set('Jane');
|
|
571
|
-
* tree.$.count.set(5);
|
|
572
|
-
*
|
|
573
|
-
* const currentState = tree.unwrap();
|
|
574
|
-
* // { user: { name: 'Jane', age: 30 }, count: 5 }
|
|
575
|
-
* ```
|
|
576
|
-
*/
|
|
476
|
+
function enhanceTree(tree, config = {}) {
|
|
477
|
+
const isLazy = config.useLazySignals ?? shouldUseLazy(tree.state, config);
|
|
577
478
|
tree.unwrap = () => {
|
|
578
|
-
// Recursively unwrap with proper typing
|
|
579
479
|
const unwrapObject = (obj) => {
|
|
580
480
|
if (typeof obj !== 'object' || obj === null) {
|
|
581
481
|
return obj;
|
|
582
482
|
}
|
|
483
|
+
if (isSignal(obj)) {
|
|
484
|
+
return obj();
|
|
485
|
+
}
|
|
486
|
+
if (isSignal(obj)) {
|
|
487
|
+
const value = obj();
|
|
488
|
+
if (Array.isArray(value)) {
|
|
489
|
+
return value;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
583
492
|
const result = {};
|
|
584
493
|
for (const key in obj) {
|
|
494
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key))
|
|
495
|
+
continue;
|
|
585
496
|
const value = obj[key];
|
|
586
497
|
if (isSignal(value)) {
|
|
587
498
|
result[key] = value();
|
|
588
499
|
}
|
|
589
500
|
else if (typeof value === 'object' &&
|
|
590
501
|
value !== null &&
|
|
591
|
-
!Array.isArray(value)
|
|
592
|
-
|
|
502
|
+
!Array.isArray(value) &&
|
|
503
|
+
!isBuiltInObject(value)) {
|
|
593
504
|
result[key] = unwrapObject(value);
|
|
594
505
|
}
|
|
595
506
|
else {
|
|
596
507
|
result[key] = value;
|
|
597
508
|
}
|
|
598
509
|
}
|
|
510
|
+
const symbols = Object.getOwnPropertySymbols(obj);
|
|
511
|
+
for (const sym of symbols) {
|
|
512
|
+
const value = obj[sym];
|
|
513
|
+
if (isSignal(value)) {
|
|
514
|
+
result[sym] = value();
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
result[sym] = value;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
599
520
|
return result;
|
|
600
521
|
};
|
|
601
522
|
return unwrapObject(tree.state);
|
|
602
523
|
};
|
|
603
|
-
/**
|
|
604
|
-
* Updates the state using an updater function.
|
|
605
|
-
* The updater receives the current state and returns a partial update.
|
|
606
|
-
* Automatically handles nested signal updates.
|
|
607
|
-
*
|
|
608
|
-
* @param updater - Function that receives current state and returns partial updates
|
|
609
|
-
*
|
|
610
|
-
* @example
|
|
611
|
-
* ```typescript
|
|
612
|
-
* const tree = signalTree({
|
|
613
|
-
* user: { name: 'John', age: 30 },
|
|
614
|
-
* count: 0,
|
|
615
|
-
* todos: []
|
|
616
|
-
* });
|
|
617
|
-
*
|
|
618
|
-
* // Simple update
|
|
619
|
-
* tree.update(state => ({ count: state.count + 1 }));
|
|
620
|
-
*
|
|
621
|
-
* // Nested update
|
|
622
|
-
* tree.update(state => ({
|
|
623
|
-
* user: { ...state.user, age: state.user.age + 1 },
|
|
624
|
-
* todos: [...state.todos, { id: 1, text: 'New todo' }]
|
|
625
|
-
* }));
|
|
626
|
-
*
|
|
627
|
-
* // Conditional update
|
|
628
|
-
* tree.update(state =>
|
|
629
|
-
* state.count < 10
|
|
630
|
-
* ? { count: state.count + 1 }
|
|
631
|
-
* : { count: 0, user: { ...state.user, name: 'Reset' } }
|
|
632
|
-
* );
|
|
633
|
-
* ```
|
|
634
|
-
*/
|
|
635
|
-
/**
|
|
636
|
-
* Enhanced update with error handling and rollback capability
|
|
637
|
-
*/
|
|
638
524
|
tree.update = (updater) => {
|
|
639
|
-
const
|
|
525
|
+
const transactionLog = [];
|
|
640
526
|
try {
|
|
641
527
|
const currentValue = tree.unwrap();
|
|
642
528
|
const partialObj = updater(currentValue);
|
|
643
529
|
if (!partialObj || typeof partialObj !== 'object') {
|
|
644
530
|
throw new Error('Updater must return an object');
|
|
645
531
|
}
|
|
646
|
-
// Recursively update with better error handling and rollback
|
|
647
532
|
const updateObject = (target, updates, path = '') => {
|
|
648
533
|
for (const key in updates) {
|
|
649
534
|
if (!Object.prototype.hasOwnProperty.call(updates, key))
|
|
@@ -653,39 +538,42 @@ function enhanceTree(tree) {
|
|
|
653
538
|
const currentSignalOrState = target[key];
|
|
654
539
|
try {
|
|
655
540
|
if (isSignal(currentSignalOrState)) {
|
|
656
|
-
// Store original value for potential rollback
|
|
657
541
|
const originalValue = currentSignalOrState();
|
|
658
|
-
|
|
659
|
-
|
|
542
|
+
transactionLog.push({
|
|
543
|
+
path: currentPath,
|
|
544
|
+
oldValue: originalValue,
|
|
545
|
+
newValue: updateValue,
|
|
546
|
+
});
|
|
660
547
|
currentSignalOrState.set(updateValue);
|
|
661
548
|
}
|
|
662
549
|
else if (typeof updateValue === 'object' &&
|
|
663
550
|
updateValue !== null &&
|
|
664
551
|
!Array.isArray(updateValue) &&
|
|
552
|
+
!isBuiltInObject(updateValue) &&
|
|
665
553
|
typeof currentSignalOrState === 'object' &&
|
|
666
554
|
currentSignalOrState !== null &&
|
|
667
555
|
!isSignal(currentSignalOrState)) {
|
|
668
|
-
// Nested object - recurse
|
|
669
556
|
updateObject(currentSignalOrState, updateValue, currentPath);
|
|
670
557
|
}
|
|
671
558
|
else if (currentSignalOrState === undefined) {
|
|
672
|
-
console.warn(`Cannot update non-existent path: ${currentPath}`);
|
|
559
|
+
console.warn(`[SignalTree] Cannot update non-existent path: ${currentPath}`);
|
|
673
560
|
}
|
|
674
561
|
}
|
|
675
562
|
catch (error) {
|
|
676
|
-
console.error(`Failed to update path "${currentPath}":`, error);
|
|
677
|
-
|
|
563
|
+
console.error(`[SignalTree] Failed to update path "${currentPath}":`, error);
|
|
564
|
+
throw error;
|
|
678
565
|
}
|
|
679
566
|
}
|
|
680
567
|
};
|
|
681
568
|
updateObject(tree.state, partialObj);
|
|
569
|
+
if (config.debugMode && transactionLog.length > 0) {
|
|
570
|
+
console.log('[SignalTree] Update transaction:', transactionLog);
|
|
571
|
+
}
|
|
682
572
|
}
|
|
683
573
|
catch (error) {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
// Attempt to restore original values
|
|
688
|
-
for (const [path, originalValue] of originalState.entries()) {
|
|
574
|
+
console.error('[SignalTree] Update failed, attempting rollback:', error);
|
|
575
|
+
for (const { path, oldValue } of transactionLog.reverse()) {
|
|
576
|
+
try {
|
|
689
577
|
const pathParts = path.split('.');
|
|
690
578
|
let current = tree.state;
|
|
691
579
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
@@ -693,42 +581,68 @@ function enhanceTree(tree) {
|
|
|
693
581
|
if (!current)
|
|
694
582
|
break;
|
|
695
583
|
}
|
|
696
|
-
if (current
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
584
|
+
if (current) {
|
|
585
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
586
|
+
const targetSignal = current[lastKey];
|
|
587
|
+
if (isSignal(targetSignal)) {
|
|
588
|
+
targetSignal.set(oldValue);
|
|
589
|
+
}
|
|
700
590
|
}
|
|
701
591
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
592
|
+
catch (rollbackError) {
|
|
593
|
+
console.error('[SignalTree] Rollback failed for path:', path, rollbackError);
|
|
594
|
+
}
|
|
705
595
|
}
|
|
706
596
|
throw error;
|
|
707
597
|
}
|
|
708
598
|
};
|
|
709
|
-
// Pipe implementation for function composition with improved type safety
|
|
710
599
|
tree.pipe = ((...fns) => {
|
|
711
600
|
if (fns.length === 0) {
|
|
712
601
|
return tree;
|
|
713
602
|
}
|
|
714
|
-
// Type-safe reduce with proper function composition
|
|
715
603
|
return fns.reduce((acc, fn) => {
|
|
716
604
|
if (typeof fn !== 'function') {
|
|
717
605
|
throw new Error('All pipe arguments must be functions');
|
|
718
606
|
}
|
|
719
|
-
|
|
607
|
+
try {
|
|
608
|
+
return fn(acc);
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error('[SignalTree] Pipe function failed:', error);
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
720
614
|
}, tree);
|
|
721
615
|
});
|
|
722
|
-
|
|
616
|
+
tree.destroy = () => {
|
|
617
|
+
try {
|
|
618
|
+
if (isLazy) {
|
|
619
|
+
const state = tree.state;
|
|
620
|
+
if (state && typeof state === 'object' && '__cleanup__' in state) {
|
|
621
|
+
const cleanup = state.__cleanup__;
|
|
622
|
+
if (typeof cleanup === 'function') {
|
|
623
|
+
cleanup();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (config.debugMode) {
|
|
628
|
+
console.log('[SignalTree] Tree destroyed');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
console.error('[SignalTree] Error during cleanup:', error);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
addStubMethods(tree, config);
|
|
636
|
+
return tree;
|
|
637
|
+
}
|
|
638
|
+
function addStubMethods(tree, config) {
|
|
723
639
|
tree.batchUpdate = (updater) => {
|
|
724
|
-
console.warn('⚠️ batchUpdate() called but batching is not enabled.', '
|
|
725
|
-
// Fallback: Just call update directly
|
|
640
|
+
console.warn('⚠️ batchUpdate() called but batching is not enabled.', 'To enable batch updates, install @signaltree/batching');
|
|
726
641
|
tree.update(updater);
|
|
727
642
|
};
|
|
728
643
|
tree.memoize = (fn, cacheKey) => {
|
|
729
|
-
console.warn('⚠️ memoize() called but memoization is not enabled.', '
|
|
730
|
-
|
|
731
|
-
void cacheKey; // Mark as intentionally unused
|
|
644
|
+
console.warn('⚠️ memoize() called but memoization is not enabled.', 'To enable memoized computations, install @signaltree/memoization');
|
|
645
|
+
void cacheKey;
|
|
732
646
|
return computed(() => fn(tree.unwrap()));
|
|
733
647
|
};
|
|
734
648
|
tree.effect = (fn) => {
|
|
@@ -736,8 +650,9 @@ function enhanceTree(tree) {
|
|
|
736
650
|
effect(() => fn(tree.unwrap()));
|
|
737
651
|
}
|
|
738
652
|
catch (error) {
|
|
739
|
-
|
|
740
|
-
|
|
653
|
+
if (config.debugMode) {
|
|
654
|
+
console.warn('Effect requires Angular injection context', error);
|
|
655
|
+
}
|
|
741
656
|
}
|
|
742
657
|
};
|
|
743
658
|
tree.subscribe = (fn) => {
|
|
@@ -757,37 +672,31 @@ function enhanceTree(tree) {
|
|
|
757
672
|
return unsubscribe;
|
|
758
673
|
}
|
|
759
674
|
catch (error) {
|
|
760
|
-
|
|
761
|
-
|
|
675
|
+
if (config.debugMode) {
|
|
676
|
+
console.warn('Subscribe requires Angular injection context', error);
|
|
677
|
+
}
|
|
762
678
|
fn(tree.unwrap());
|
|
763
679
|
return () => {
|
|
764
|
-
// No-op unsubscribe
|
|
765
680
|
};
|
|
766
681
|
}
|
|
767
682
|
};
|
|
768
|
-
// Stub implementations for performance features
|
|
769
683
|
tree.optimize = () => {
|
|
770
|
-
|
|
684
|
+
if (config.debugMode) {
|
|
685
|
+
console.warn('⚠️ optimize() called but tree optimization is not available.');
|
|
686
|
+
}
|
|
771
687
|
};
|
|
772
688
|
tree.clearCache = () => {
|
|
773
|
-
|
|
689
|
+
if (config.debugMode) {
|
|
690
|
+
console.warn('⚠️ clearCache() called but caching is not available.');
|
|
691
|
+
}
|
|
774
692
|
};
|
|
775
693
|
tree.invalidatePattern = () => {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
};
|
|
779
|
-
tree.destroy = () => {
|
|
780
|
-
// Clean up lazy signal proxies if they exist
|
|
781
|
-
const state = tree.state;
|
|
782
|
-
if (state && typeof state === 'object' && '__cleanup__' in state) {
|
|
783
|
-
state.__cleanup__();
|
|
694
|
+
if (config.debugMode) {
|
|
695
|
+
console.warn('⚠️ invalidatePattern() called but performance optimization is not enabled.');
|
|
784
696
|
}
|
|
785
|
-
|
|
786
|
-
console.log('[MEMORY-CLEANUP] Basic tree destroyed');
|
|
697
|
+
return 0;
|
|
787
698
|
};
|
|
788
699
|
tree.getMetrics = () => {
|
|
789
|
-
console.warn('⚠️ getMetrics() called but performance tracking is not enabled.', '\nTo enable performance tracking, install @signaltree/middleware');
|
|
790
|
-
// Return minimal metrics when tracking not enabled
|
|
791
700
|
return {
|
|
792
701
|
updates: 0,
|
|
793
702
|
computations: 0,
|
|
@@ -796,72 +705,90 @@ function enhanceTree(tree) {
|
|
|
796
705
|
averageUpdateTime: 0,
|
|
797
706
|
};
|
|
798
707
|
};
|
|
799
|
-
// Stub implementations for middleware
|
|
800
708
|
tree.addTap = (middleware) => {
|
|
801
|
-
|
|
802
|
-
|
|
709
|
+
if (config.debugMode) {
|
|
710
|
+
console.warn('⚠️ addTap() called but middleware support is not available.');
|
|
711
|
+
}
|
|
712
|
+
void middleware;
|
|
803
713
|
};
|
|
804
714
|
tree.removeTap = (id) => {
|
|
805
|
-
|
|
806
|
-
|
|
715
|
+
if (config.debugMode) {
|
|
716
|
+
console.warn('⚠️ removeTap() called but middleware support is not available.');
|
|
717
|
+
}
|
|
718
|
+
void id;
|
|
807
719
|
};
|
|
808
|
-
// Stub implementations for entity helpers
|
|
809
720
|
tree.asCrud = () => {
|
|
810
|
-
|
|
721
|
+
if (config.debugMode) {
|
|
722
|
+
console.warn('⚠️ asCrud() called but entity helpers are not available.');
|
|
723
|
+
}
|
|
811
724
|
return {};
|
|
812
725
|
};
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
726
|
+
tree.asyncAction = (operation, asyncConfig = {}) => {
|
|
727
|
+
if (config.debugMode) {
|
|
728
|
+
console.warn('⚠️ asyncAction() called but async actions are not available.');
|
|
729
|
+
}
|
|
816
730
|
void operation;
|
|
817
|
-
void
|
|
731
|
+
void asyncConfig;
|
|
818
732
|
return {};
|
|
819
733
|
};
|
|
820
|
-
// Stub implementations for time travel
|
|
821
734
|
tree.undo = () => {
|
|
822
|
-
|
|
735
|
+
if (config.debugMode) {
|
|
736
|
+
console.warn('⚠️ undo() called but time travel is not available.');
|
|
737
|
+
}
|
|
823
738
|
};
|
|
824
739
|
tree.redo = () => {
|
|
825
|
-
|
|
740
|
+
if (config.debugMode) {
|
|
741
|
+
console.warn('⚠️ redo() called but time travel is not available.');
|
|
742
|
+
}
|
|
826
743
|
};
|
|
827
744
|
tree.getHistory = () => {
|
|
828
|
-
|
|
745
|
+
if (config.debugMode) {
|
|
746
|
+
console.warn('⚠️ getHistory() called but time travel is not available.');
|
|
747
|
+
}
|
|
829
748
|
return [];
|
|
830
749
|
};
|
|
831
750
|
tree.resetHistory = () => {
|
|
832
|
-
|
|
751
|
+
if (config.debugMode) {
|
|
752
|
+
console.warn('⚠️ resetHistory() called but time travel is not available.');
|
|
753
|
+
}
|
|
833
754
|
};
|
|
834
|
-
return tree;
|
|
835
755
|
}
|
|
836
756
|
function signalTree(obj, configOrPreset) {
|
|
837
|
-
// Handle preset strings
|
|
838
757
|
if (typeof configOrPreset === 'string') {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
758
|
+
const presetConfigs = {
|
|
759
|
+
basic: {
|
|
760
|
+
useLazySignals: false,
|
|
761
|
+
debugMode: false,
|
|
762
|
+
},
|
|
763
|
+
performance: {
|
|
764
|
+
useLazySignals: true,
|
|
765
|
+
batchUpdates: true,
|
|
766
|
+
useMemoization: true,
|
|
767
|
+
useShallowComparison: true,
|
|
768
|
+
},
|
|
769
|
+
development: {
|
|
770
|
+
useLazySignals: false,
|
|
771
|
+
debugMode: true,
|
|
772
|
+
enableDevTools: true,
|
|
773
|
+
trackPerformance: true,
|
|
774
|
+
},
|
|
775
|
+
production: {
|
|
776
|
+
useLazySignals: true,
|
|
777
|
+
batchUpdates: true,
|
|
778
|
+
useMemoization: true,
|
|
779
|
+
debugMode: false,
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
const config = presetConfigs[configOrPreset];
|
|
783
|
+
if (!config) {
|
|
784
|
+
console.warn(`Unknown preset: ${configOrPreset}, using default configuration`);
|
|
785
|
+
return create(obj, {});
|
|
786
|
+
}
|
|
787
|
+
return create(obj, config);
|
|
842
788
|
}
|
|
843
|
-
// Handle configuration objects or default (smart enhancement)
|
|
844
789
|
const config = configOrPreset || {};
|
|
845
790
|
return create(obj, config);
|
|
846
791
|
}
|
|
847
792
|
|
|
848
|
-
/**
|
|
849
|
-
* SignalTree Core Types - Recursive Typing System
|
|
850
|
-
*
|
|
851
|
-
* COPYRIGHT NOTICE:
|
|
852
|
-
* This file contains proprietary recursive typing innovations protected under
|
|
853
|
-
* the SignalTree license. The DeepSignalify<T> recursive type system and
|
|
854
|
-
* related implementations are exclusive intellectual property of Jonathan D Borgia.
|
|
855
|
-
*
|
|
856
|
-
* Unauthorized extraction, copying, or reimplementation of these recursive typing
|
|
857
|
-
* concepts is strictly prohibited and constitutes copyright infringement.
|
|
858
|
-
*
|
|
859
|
-
* Licensed under Fair Source License - see LICENSE file for complete terms.
|
|
860
|
-
*/
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Generated bundle index. Do not edit.
|
|
864
|
-
*/
|
|
865
|
-
|
|
866
793
|
export { createLazySignalTree, deepEqual, equal, parsePath, shallowEqual, signalTree, terminalSignal };
|
|
867
794
|
//# sourceMappingURL=signaltree-core.mjs.map
|