@signaltree/core 1.0.3 → 1.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.
@@ -1,11 +1,19 @@
1
1
  import { signal, isSignal, computed, effect, inject, DestroyRef } from '@angular/core';
2
2
 
3
- // Path parsing cache for performance optimization
4
- const pathCache = new Map();
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
+ */
5
13
  /**
6
14
  * Enhanced equality function inspired by the monolithic implementation.
7
15
  * Uses deep equality for arrays and objects, === for primitives.
8
- * More efficient than lodash while maintaining compatibility.
16
+ * Optimized with early exits and type-specific comparisons.
9
17
  */
10
18
  function equal(a, b) {
11
19
  // Fast path for reference equality
@@ -14,23 +22,31 @@ function equal(a, b) {
14
22
  // Handle null/undefined cases
15
23
  if (a == null || b == null)
16
24
  return a === b;
17
- // Handle arrays with deep comparison
18
- if (Array.isArray(a) && Array.isArray(b)) {
19
- if (a.length !== b.length)
25
+ // Type check first - most efficient early exit
26
+ const typeA = typeof a;
27
+ const typeB = typeof b;
28
+ if (typeA !== typeB)
29
+ return false;
30
+ // For primitives, === check above is sufficient
31
+ if (typeA !== 'object')
32
+ return false;
33
+ // Handle arrays with optimized comparison
34
+ if (Array.isArray(a)) {
35
+ if (!Array.isArray(b) || a.length !== b.length)
20
36
  return false;
21
37
  return a.every((item, index) => equal(item, b[index]));
22
38
  }
23
- // Handle objects with deep comparison
24
- if (typeof a === 'object' && typeof b === 'object') {
25
- const keysA = Object.keys(a);
26
- const keysB = Object.keys(b);
27
- if (keysA.length !== keysB.length)
28
- return false;
29
- return keysA.every((key) => keysB.includes(key) &&
30
- equal(a[key], b[key]));
31
- }
32
- // Fallback to strict equality
33
- return a === b;
39
+ // Arrays check above handles array vs object mismatch
40
+ if (Array.isArray(b))
41
+ return false;
42
+ // Handle objects with optimized comparison
43
+ const objA = a;
44
+ const objB = b;
45
+ const keysA = Object.keys(objA);
46
+ const keysB = Object.keys(objB);
47
+ if (keysA.length !== keysB.length)
48
+ return false;
49
+ return keysA.every((key) => key in objB && equal(objA[key], objB[key]));
34
50
  }
35
51
  /**
36
52
  * Creates a terminal signal with the enhanced equality function.
@@ -45,8 +61,48 @@ function terminalSignal(value, customEqual) {
45
61
  });
46
62
  }
47
63
  /**
48
- * Parses a dot-notation path into an array of keys with memoization.
64
+ * LRU Cache implementation for efficient memory management
65
+ */
66
+ class LRUCache {
67
+ maxSize;
68
+ cache = new Map();
69
+ constructor(maxSize) {
70
+ this.maxSize = maxSize;
71
+ }
72
+ set(key, value) {
73
+ if (this.cache.size >= this.maxSize) {
74
+ const firstKey = this.cache.keys().next().value;
75
+ if (firstKey !== undefined) {
76
+ this.cache.delete(firstKey);
77
+ }
78
+ }
79
+ // Remove if exists to update position
80
+ this.cache.delete(key);
81
+ this.cache.set(key, value); // Add to end (most recent)
82
+ }
83
+ get(key) {
84
+ const value = this.cache.get(key);
85
+ if (value !== undefined) {
86
+ // Move to end (mark as recently used)
87
+ this.cache.delete(key);
88
+ this.cache.set(key, value);
89
+ }
90
+ return value;
91
+ }
92
+ clear() {
93
+ this.cache.clear();
94
+ }
95
+ size() {
96
+ return this.cache.size;
97
+ }
98
+ }
99
+ // Path parsing cache with proper LRU eviction strategy
100
+ const MAX_CACHE_SIZE = 1000;
101
+ const pathCache = new LRUCache(MAX_CACHE_SIZE);
102
+ /**
103
+ * Parses a dot-notation path into an array of keys with LRU memoization.
49
104
  * Critical for performance when accessing nested properties frequently.
105
+ * Includes proper LRU cache management to prevent memory leaks.
50
106
  *
51
107
  * @example
52
108
  * ```typescript
@@ -55,16 +111,19 @@ function terminalSignal(value, customEqual) {
55
111
  * ```
56
112
  */
57
113
  function parsePath(path) {
58
- if (!pathCache.has(path)) {
59
- pathCache.set(path, path.split('.'));
60
- }
61
114
  const cached = pathCache.get(path);
62
- return cached ?? path.split('.');
115
+ if (cached) {
116
+ return cached;
117
+ }
118
+ const parts = path.split('.');
119
+ pathCache.set(path, parts);
120
+ return parts;
63
121
  }
64
122
  /**
65
123
  * Creates a lazy signal tree using Proxy for on-demand signal creation.
66
124
  * Only creates signals when properties are first accessed, providing
67
125
  * massive memory savings for large state objects.
126
+ * Uses WeakMap for memory-safe caching.
68
127
  *
69
128
  * @param obj - Source object to lazily signalify
70
129
  * @param equalityFn - Equality function for signal comparison
@@ -72,16 +131,68 @@ function parsePath(path) {
72
131
  * @returns Proxied object that creates signals on first access
73
132
  */
74
133
  function createLazySignalTree(obj, equalityFn, basePath = '') {
134
+ // Use Map instead of WeakMap for better control over cleanup
75
135
  const signalCache = new Map();
76
136
  const nestedProxies = new Map();
77
- return new Proxy(obj, {
137
+ // Track cleanup functions for nested proxies
138
+ const nestedCleanups = new Map();
139
+ // Enhanced cleanup function
140
+ const cleanup = () => {
141
+ // Clean up all nested proxies first
142
+ nestedCleanups.forEach((cleanupFn) => {
143
+ try {
144
+ cleanupFn();
145
+ }
146
+ catch (error) {
147
+ console.warn('Error during nested cleanup:', error);
148
+ }
149
+ });
150
+ nestedCleanups.clear();
151
+ // Clear caches
152
+ signalCache.clear();
153
+ nestedProxies.clear();
154
+ };
155
+ // Enhanced built-in object detection
156
+ const isBuiltInObject = (v) => {
157
+ if (v === null || v === undefined)
158
+ return false;
159
+ return (v instanceof Date ||
160
+ v instanceof RegExp ||
161
+ typeof v === 'function' ||
162
+ v instanceof Map ||
163
+ v instanceof Set ||
164
+ v instanceof WeakMap ||
165
+ v instanceof WeakSet ||
166
+ v instanceof ArrayBuffer ||
167
+ v instanceof DataView ||
168
+ v instanceof Error ||
169
+ v instanceof Promise ||
170
+ v instanceof URL ||
171
+ v instanceof URLSearchParams ||
172
+ v instanceof FormData ||
173
+ v instanceof Blob ||
174
+ (typeof File !== 'undefined' && v instanceof File));
175
+ };
176
+ const proxy = new Proxy(obj, {
78
177
  get(target, prop) {
178
+ // Handle cleanup method
179
+ if (prop === '__cleanup__') {
180
+ return cleanup;
181
+ }
79
182
  // Handle symbol properties (like Symbol.iterator) normally
80
183
  if (typeof prop === 'symbol') {
81
184
  return target[prop];
82
185
  }
186
+ // Handle inspection methods
187
+ if (prop === 'valueOf' || prop === 'toString') {
188
+ return target[prop];
189
+ }
83
190
  const key = prop;
84
191
  const path = basePath ? `${basePath}.${key}` : key;
192
+ // Safety check for property existence
193
+ if (!(key in target)) {
194
+ return undefined;
195
+ }
85
196
  const value = target[key];
86
197
  // If it's already a signal, return it
87
198
  if (isSignal(value)) {
@@ -99,15 +210,36 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
99
210
  if (value &&
100
211
  typeof value === 'object' &&
101
212
  !Array.isArray(value) &&
102
- !isSignal(value)) {
103
- const nestedProxy = createLazySignalTree(value, equalityFn, path);
104
- nestedProxies.set(path, nestedProxy);
105
- return nestedProxy;
213
+ !isSignal(value) &&
214
+ !isBuiltInObject(value)) {
215
+ try {
216
+ const nestedProxy = createLazySignalTree(value, equalityFn, path);
217
+ nestedProxies.set(path, nestedProxy);
218
+ // Store cleanup function for nested proxy
219
+ const proxyWithCleanup = nestedProxy;
220
+ if (typeof proxyWithCleanup.__cleanup__ === 'function') {
221
+ nestedCleanups.set(path, proxyWithCleanup.__cleanup__);
222
+ }
223
+ return nestedProxy;
224
+ }
225
+ catch (error) {
226
+ console.warn(`Failed to create lazy proxy for path "${path}":`, error);
227
+ // Fallback: create a signal for the object
228
+ const fallbackSignal = signal(value, ...(ngDevMode ? [{ debugName: "fallbackSignal", equal: equalityFn }] : [{ equal: equalityFn }]));
229
+ signalCache.set(path, fallbackSignal);
230
+ return fallbackSignal;
231
+ }
232
+ }
233
+ // Create signal for primitive values, arrays, and built-in objects
234
+ try {
235
+ const newSignal = signal(value, ...(ngDevMode ? [{ debugName: "newSignal", equal: equalityFn }] : [{ equal: equalityFn }]));
236
+ signalCache.set(path, newSignal);
237
+ return newSignal;
238
+ }
239
+ catch (error) {
240
+ console.warn(`Failed to create signal for path "${path}":`, error);
241
+ return value; // Return raw value as fallback
106
242
  }
107
- // Create signal for primitive values and arrays
108
- const newSignal = signal(value, ...(ngDevMode ? [{ debugName: "newSignal", equal: equalityFn }] : [{ equal: equalityFn }]));
109
- signalCache.set(path, newSignal);
110
- return newSignal;
111
243
  },
112
244
  set(target, prop, value) {
113
245
  if (typeof prop === 'symbol') {
@@ -116,18 +248,30 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
116
248
  }
117
249
  const key = prop;
118
250
  const path = basePath ? `${basePath}.${key}` : key;
119
- // Update the original object
120
- target[key] = value;
121
- // If we have a cached signal, update it
122
- const cachedSignal = signalCache.get(path);
123
- if (cachedSignal) {
124
- cachedSignal.set(value);
251
+ try {
252
+ // Update the original object
253
+ target[key] = value;
254
+ // If we have a cached signal, update it
255
+ const cachedSignal = signalCache.get(path);
256
+ if (cachedSignal && 'set' in cachedSignal) {
257
+ cachedSignal.set(value);
258
+ }
259
+ // Clear nested proxy cache if the value type changed
260
+ if (nestedProxies.has(path)) {
261
+ // Clean up the nested proxy
262
+ const nestedCleanup = nestedCleanups.get(path);
263
+ if (nestedCleanup) {
264
+ nestedCleanup();
265
+ nestedCleanups.delete(path);
266
+ }
267
+ nestedProxies.delete(path);
268
+ }
269
+ return true;
125
270
  }
126
- // Clear nested proxy cache if the value type changed
127
- if (nestedProxies.has(path)) {
128
- nestedProxies.delete(path);
271
+ catch (error) {
272
+ console.warn(`Failed to set value for path "${path}":`, error);
273
+ return false;
129
274
  }
130
- return true;
131
275
  },
132
276
  has(target, prop) {
133
277
  return prop in target;
@@ -139,6 +283,7 @@ function createLazySignalTree(obj, equalityFn, basePath = '') {
139
283
  return Reflect.getOwnPropertyDescriptor(target, prop);
140
284
  },
141
285
  });
286
+ return proxy;
142
287
  }
143
288
  /**
144
289
  * Native deep equality check for arrays and objects.
@@ -224,6 +369,20 @@ function shallowEqual(a, b) {
224
369
  return false;
225
370
  }
226
371
 
372
+ /**
373
+ * SignalTree Core Implementation - Recursive Typing Engine
374
+ *
375
+ * COPYRIGHT NOTICE:
376
+ * This file contains the proprietary recursive typing implementation protected
377
+ * under the SignalTree license. The signal-store pattern, recursive type-runtime
378
+ * alignment, and "initiation defines structure" paradigm are exclusive intellectual
379
+ * property of Jonathan D Borgia.
380
+ *
381
+ * The createSignalStore and createLazySignalTree functions implement patented
382
+ * approaches to recursive type preservation that are strictly protected.
383
+ *
384
+ * Licensed under Fair Source License - see LICENSE file for complete terms.
385
+ */
227
386
  /**
228
387
  * Creates an equality function based on configuration.
229
388
  *
@@ -243,10 +402,12 @@ function createEqualityFn(useShallowComparison) {
243
402
  return useShallowComparison ? Object.is : equal;
244
403
  }
245
404
  /**
246
- * Core function to create a basic SignalTree.
405
+ * Core function to create a basic SignalTree with enhanced safety.
247
406
  * This provides the minimal functionality without advanced features.
248
407
  *
249
- * @template T - The state object type
408
+ * CRITICAL: Uses flexible typing - accepts ANY type T, not T
409
+ *
410
+ * @template T - The state object type (NO constraints - maximum flexibility)
250
411
  * @param obj - The initial state object
251
412
  * @param config - Configuration options for the tree
252
413
  * @returns A basic SignalTree with core functionality
@@ -255,7 +416,10 @@ function createEqualityFn(useShallowComparison) {
255
416
  * ```typescript
256
417
  * const tree = create({
257
418
  * count: 0,
258
- * user: { name: 'John', age: 30 }
419
+ * user: { name: 'John', age: 30 },
420
+ * items: [1, 2, 3],
421
+ * metadata: new Map(), // Any object type!
422
+ * fn: () => 'hello' // Even functions!
259
423
  * }, {
260
424
  * useLazySignals: true,
261
425
  * useShallowComparison: false
@@ -268,11 +432,11 @@ function createEqualityFn(useShallowComparison) {
268
432
  */
269
433
  function create(obj, config = {}) {
270
434
  const equalityFn = createEqualityFn(config.useShallowComparison ?? false);
271
- const useLazy = config.useLazySignals ?? true; // Default to lazy loading
272
- // Choose between lazy and eager signal creation
435
+ const useLazy = config.useLazySignals ?? true;
436
+ // Create signals using signal-store pattern for perfect type inference
273
437
  const signalState = useLazy
274
438
  ? createLazySignalTree(obj, equalityFn)
275
- : createEagerSignalsFromObject(obj, equalityFn);
439
+ : createSignalStore(obj, equalityFn);
276
440
  const resultTree = {
277
441
  state: signalState,
278
442
  $: signalState, // $ points to the same state object
@@ -281,44 +445,94 @@ function create(obj, config = {}) {
281
445
  return resultTree;
282
446
  }
283
447
  /**
284
- * Creates eager signals from an object (non-lazy approach).
285
- * All signals are created immediately during initialization.
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.
286
451
  *
287
- * @template O - The object type to convert to signals
452
+ * @template T - The object type to process (preserves exact type structure)
288
453
  * @param obj - The object to convert to signals
289
454
  * @param equalityFn - Function to compare values for equality
290
- * @returns A deeply signalified version of the object
455
+ * @returns A deeply signalified version maintaining exact type structure
291
456
  *
292
457
  * @example
293
458
  * ```typescript
294
- * const eagerSignals = createEagerSignalsFromObject({
459
+ * const store = createSignalStore({
295
460
  * user: { name: 'John', age: 30 },
296
461
  * settings: { theme: 'dark' }
297
462
  * }, Object.is);
298
463
  *
299
- * // All signals are created immediately
300
- * eagerSignals.user.name.set('Jane');
301
- * console.log(eagerSignals.settings.theme()); // 'dark'
464
+ * // Perfect type inference maintained throughout
465
+ * store.user.name.set('Jane');
466
+ * console.log(store.settings.theme()); // 'dark'
302
467
  * ```
303
468
  */
304
- function createEagerSignalsFromObject(obj, equalityFn) {
305
- const result = {};
306
- for (const [key, value] of Object.entries(obj)) {
307
- const isObj = (v) => typeof v === 'object' && v !== null;
308
- if (isObj(value) && !Array.isArray(value) && !isSignal(value)) {
309
- // For nested objects, create nested signal structure directly
310
- result[key] = createEagerSignalsFromObject(value, equalityFn);
311
- }
312
- else if (isSignal(value)) {
313
- result[key] = value;
314
- }
315
- else {
316
- result[key] = signal(value, {
317
- equal: equalityFn,
318
- });
469
+ function createSignalStore(obj, equalityFn) {
470
+ // Input validation
471
+ if (obj === null || obj === undefined) {
472
+ throw new Error('Cannot create signal store from null or undefined');
473
+ }
474
+ if (typeof obj !== 'object') {
475
+ // For primitives, just return a signal
476
+ return signal(obj, { equal: equalityFn });
477
+ }
478
+ const store = {};
479
+ // Enhanced built-in object detection
480
+ const isBuiltInObject = (v) => {
481
+ if (v === null || v === undefined)
482
+ return false;
483
+ return (v instanceof Date ||
484
+ v instanceof RegExp ||
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
501
+ try {
502
+ for (const [key, value] of Object.entries(obj)) {
503
+ const isObj = (v) => typeof v === 'object' &&
504
+ v !== null &&
505
+ !Array.isArray(v) &&
506
+ !isBuiltInObject(v);
507
+ // Enhanced safety: Never double-wrap signals
508
+ if (isSignal(value)) {
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);
515
+ }
516
+ catch (error) {
517
+ console.warn(`Failed to create signal store for key "${key}":`, error);
518
+ // Fallback: treat as primitive
519
+ store[key] = signal(value, {
520
+ equal: equalityFn,
521
+ });
522
+ }
523
+ }
524
+ else {
525
+ // Create signal for primitives, arrays, and built-in objects
526
+ store[key] = signal(value, {
527
+ equal: equalityFn,
528
+ });
529
+ }
319
530
  }
320
531
  }
321
- return result;
532
+ catch (error) {
533
+ throw new Error(`Failed to create signal store: ${error instanceof Error ? error.message : 'Unknown error'}`);
534
+ }
535
+ return store;
322
536
  }
323
537
  /**
324
538
  * Enhances a tree with basic functionality (unwrap, update, pipe).
@@ -363,6 +577,9 @@ function enhanceTree(tree) {
363
577
  tree.unwrap = () => {
364
578
  // Recursively unwrap with proper typing
365
579
  const unwrapObject = (obj) => {
580
+ if (typeof obj !== 'object' || obj === null) {
581
+ return obj;
582
+ }
366
583
  const result = {};
367
584
  for (const key in obj) {
368
585
  const value = obj[key];
@@ -415,41 +632,92 @@ function enhanceTree(tree) {
415
632
  * );
416
633
  * ```
417
634
  */
635
+ /**
636
+ * Enhanced update with error handling and rollback capability
637
+ */
418
638
  tree.update = (updater) => {
419
- const currentValue = tree.unwrap();
420
- const partialObj = updater(currentValue);
421
- // Recursively update with better typing
422
- const updateObject = (target, updates) => {
423
- for (const key in updates) {
424
- if (!Object.prototype.hasOwnProperty.call(updates, key))
425
- continue;
426
- const updateValue = updates[key];
427
- const currentSignalOrState = target[key];
428
- if (isSignal(currentSignalOrState)) {
429
- // Direct signal update
430
- currentSignalOrState.set(updateValue);
639
+ const originalState = new Map();
640
+ try {
641
+ const currentValue = tree.unwrap();
642
+ const partialObj = updater(currentValue);
643
+ if (!partialObj || typeof partialObj !== 'object') {
644
+ throw new Error('Updater must return an object');
645
+ }
646
+ // Recursively update with better error handling and rollback
647
+ const updateObject = (target, updates, path = '') => {
648
+ for (const key in updates) {
649
+ if (!Object.prototype.hasOwnProperty.call(updates, key))
650
+ continue;
651
+ const updateValue = updates[key];
652
+ const currentPath = path ? `${path}.${key}` : key;
653
+ const currentSignalOrState = target[key];
654
+ try {
655
+ if (isSignal(currentSignalOrState)) {
656
+ // Store original value for potential rollback
657
+ const originalValue = currentSignalOrState();
658
+ originalState.set(currentPath, originalValue);
659
+ // Direct signal update
660
+ currentSignalOrState.set(updateValue);
661
+ }
662
+ else if (typeof updateValue === 'object' &&
663
+ updateValue !== null &&
664
+ !Array.isArray(updateValue) &&
665
+ typeof currentSignalOrState === 'object' &&
666
+ currentSignalOrState !== null &&
667
+ !isSignal(currentSignalOrState)) {
668
+ // Nested object - recurse
669
+ updateObject(currentSignalOrState, updateValue, currentPath);
670
+ }
671
+ else if (currentSignalOrState === undefined) {
672
+ console.warn(`Cannot update non-existent path: ${currentPath}`);
673
+ }
674
+ }
675
+ catch (error) {
676
+ console.error(`Failed to update path "${currentPath}":`, error);
677
+ // Continue with other updates rather than failing completely
678
+ }
431
679
  }
432
- else if (typeof updateValue === 'object' &&
433
- updateValue !== null &&
434
- !Array.isArray(updateValue) &&
435
- typeof currentSignalOrState === 'object' &&
436
- currentSignalOrState !== null) {
437
- // Nested object - recurse
438
- updateObject(currentSignalOrState, updateValue);
680
+ };
681
+ updateObject(tree.state, partialObj);
682
+ }
683
+ catch (error) {
684
+ // Rollback on error
685
+ console.error('Update failed, attempting rollback:', error);
686
+ try {
687
+ // Attempt to restore original values
688
+ for (const [path, originalValue] of originalState.entries()) {
689
+ const pathParts = path.split('.');
690
+ let current = tree.state;
691
+ for (let i = 0; i < pathParts.length - 1; i++) {
692
+ current = current[pathParts[i]];
693
+ if (!current)
694
+ break;
695
+ }
696
+ if (current &&
697
+ isSignal(current[pathParts[pathParts.length - 1]])) {
698
+ const signal = current[pathParts[pathParts.length - 1]];
699
+ signal.set(originalValue);
700
+ }
439
701
  }
440
702
  }
441
- };
442
- updateObject(tree.state, partialObj);
703
+ catch (rollbackError) {
704
+ console.error('Rollback failed:', rollbackError);
705
+ }
706
+ throw error;
707
+ }
443
708
  };
444
709
  // Pipe implementation for function composition with improved type safety
445
- tree.pipe = ((...fns // eslint-disable-line @typescript-eslint/no-explicit-any
446
- ) => {
447
- // eslint-disable-line @typescript-eslint/no-explicit-any
710
+ tree.pipe = ((...fns) => {
448
711
  if (fns.length === 0) {
449
712
  return tree;
450
713
  }
451
- // Type-safe reduce - the return type is determined by the overload signature
452
- return fns.reduce((acc, fn) => fn(acc), tree);
714
+ // Type-safe reduce with proper function composition
715
+ return fns.reduce((acc, fn) => {
716
+ if (typeof fn !== 'function') {
717
+ throw new Error('All pipe arguments must be functions');
718
+ }
719
+ return fn(acc);
720
+ }, tree);
453
721
  });
454
722
  // Stub implementations for advanced features (will log warnings)
455
723
  tree.batchUpdate = (updater) => {
@@ -509,6 +777,11 @@ function enhanceTree(tree) {
509
777
  return 0;
510
778
  };
511
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__();
784
+ }
512
785
  // Basic cleanup for non-enhanced trees
513
786
  console.log('[MEMORY-CLEANUP] Basic tree destroyed');
514
787
  };
@@ -560,9 +833,6 @@ function enhanceTree(tree) {
560
833
  };
561
834
  return tree;
562
835
  }
563
- /**
564
- * Implementation of the signalTree factory function.
565
- */
566
836
  function signalTree(obj, configOrPreset) {
567
837
  // Handle preset strings
568
838
  if (typeof configOrPreset === 'string') {
@@ -575,6 +845,20 @@ function signalTree(obj, configOrPreset) {
575
845
  return create(obj, config);
576
846
  }
577
847
 
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
+
578
862
  /**
579
863
  * Generated bundle index. Do not edit.
580
864
  */