@signaltree/core 1.0.1 → 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,59 +369,217 @@ 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.
388
+ *
389
+ * @param useShallowComparison - If true, uses Object.is for comparison; otherwise uses deep equality
390
+ * @returns A function that compares two values for equality
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const shallowEqual = createEqualityFn(true);
395
+ * const deepEqual = createEqualityFn(false);
396
+ *
397
+ * shallowEqual({ a: 1 }, { a: 1 }); // false (different objects)
398
+ * deepEqual({ a: 1 }, { a: 1 }); // true (same structure and values)
399
+ * ```
229
400
  */
230
401
  function createEqualityFn(useShallowComparison) {
231
402
  return useShallowComparison ? Object.is : equal;
232
403
  }
233
404
  /**
234
- * Core function to create a basic SignalTree.
405
+ * Core function to create a basic SignalTree with enhanced safety.
235
406
  * This provides the minimal functionality without advanced features.
407
+ *
408
+ * CRITICAL: Uses flexible typing - accepts ANY type T, not T
409
+ *
410
+ * @template T - The state object type (NO constraints - maximum flexibility)
411
+ * @param obj - The initial state object
412
+ * @param config - Configuration options for the tree
413
+ * @returns A basic SignalTree with core functionality
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const tree = create({
418
+ * count: 0,
419
+ * user: { name: 'John', age: 30 },
420
+ * items: [1, 2, 3],
421
+ * metadata: new Map(), // Any object type!
422
+ * fn: () => 'hello' // Even functions!
423
+ * }, {
424
+ * useLazySignals: true,
425
+ * useShallowComparison: false
426
+ * });
427
+ *
428
+ * // Access nested signals
429
+ * console.log(tree.$.count()); // 0
430
+ * tree.$.user.name.set('Jane');
431
+ * ```
236
432
  */
237
433
  function create(obj, config = {}) {
238
434
  const equalityFn = createEqualityFn(config.useShallowComparison ?? false);
239
- const useLazy = config.useLazySignals ?? true; // Default to lazy loading
240
- // Choose between lazy and eager signal creation
435
+ const useLazy = config.useLazySignals ?? true;
436
+ // Create signals using signal-store pattern for perfect type inference
241
437
  const signalState = useLazy
242
438
  ? createLazySignalTree(obj, equalityFn)
243
- : createEagerSignalsFromObject(obj, equalityFn);
439
+ : createSignalStore(obj, equalityFn);
244
440
  const resultTree = {
245
441
  state: signalState,
246
442
  $: signalState, // $ points to the same state object
247
443
  };
248
- enhanceTreeBasic(resultTree);
444
+ enhanceTree(resultTree);
249
445
  return resultTree;
250
446
  }
251
447
  /**
252
- * Creates eager signals from an object (non-lazy approach).
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
+ * ```
253
468
  */
254
- function createEagerSignalsFromObject(obj, equalityFn) {
255
- const result = {};
256
- for (const [key, value] of Object.entries(obj)) {
257
- const isObj = (v) => typeof v === 'object' && v !== null;
258
- if (isObj(value) && !Array.isArray(value) && !isSignal(value)) {
259
- // For nested objects, create nested signal structure directly
260
- result[key] = createEagerSignalsFromObject(value, equalityFn);
261
- }
262
- else if (isSignal(value)) {
263
- result[key] = value;
264
- }
265
- else {
266
- result[key] = signal(value, {
267
- equal: equalityFn,
268
- });
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
+ }
269
530
  }
270
531
  }
271
- 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;
272
536
  }
273
537
  /**
274
538
  * Enhances a tree with basic functionality (unwrap, update, pipe).
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
+ * ```
275
555
  */
276
- function enhanceTreeBasic(tree) {
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
+ */
277
577
  tree.unwrap = () => {
278
578
  // Recursively unwrap with proper typing
279
579
  const unwrapObject = (obj) => {
580
+ if (typeof obj !== 'object' || obj === null) {
581
+ return obj;
582
+ }
280
583
  const result = {};
281
584
  for (const key in obj) {
282
585
  const value = obj[key];
@@ -297,41 +600,124 @@ function enhanceTreeBasic(tree) {
297
600
  };
298
601
  return unwrapObject(tree.state);
299
602
  };
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
+ */
300
638
  tree.update = (updater) => {
301
- const currentValue = tree.unwrap();
302
- const partialObj = updater(currentValue);
303
- // Recursively update with better typing
304
- const updateObject = (target, updates) => {
305
- for (const key in updates) {
306
- if (!Object.prototype.hasOwnProperty.call(updates, key))
307
- continue;
308
- const updateValue = updates[key];
309
- const currentSignalOrState = target[key];
310
- if (isSignal(currentSignalOrState)) {
311
- // Direct signal update
312
- 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
+ }
313
679
  }
314
- else if (typeof updateValue === 'object' &&
315
- updateValue !== null &&
316
- !Array.isArray(updateValue) &&
317
- typeof currentSignalOrState === 'object' &&
318
- currentSignalOrState !== null) {
319
- // Nested object - recurse
320
- 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
+ }
321
701
  }
322
702
  }
323
- };
324
- updateObject(tree.state, partialObj);
703
+ catch (rollbackError) {
704
+ console.error('Rollback failed:', rollbackError);
705
+ }
706
+ throw error;
707
+ }
325
708
  };
326
709
  // Pipe implementation for function composition with improved type safety
327
- tree.pipe = ((...fns // eslint-disable-line @typescript-eslint/no-explicit-any
328
- ) => {
329
- // eslint-disable-line @typescript-eslint/no-explicit-any
710
+ tree.pipe = ((...fns) => {
330
711
  if (fns.length === 0) {
331
712
  return tree;
332
713
  }
333
- // Type-safe reduce - the return type is determined by the overload signature
334
- 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);
335
721
  });
336
722
  // Stub implementations for advanced features (will log warnings)
337
723
  tree.batchUpdate = (updater) => {
@@ -391,6 +777,11 @@ function enhanceTreeBasic(tree) {
391
777
  return 0;
392
778
  };
393
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
+ }
394
785
  // Basic cleanup for non-enhanced trees
395
786
  console.log('[MEMORY-CLEANUP] Basic tree destroyed');
396
787
  };
@@ -442,9 +833,6 @@ function enhanceTreeBasic(tree) {
442
833
  };
443
834
  return tree;
444
835
  }
445
- /**
446
- * Implementation of the signalTree factory function.
447
- */
448
836
  function signalTree(obj, configOrPreset) {
449
837
  // Handle preset strings
450
838
  if (typeof configOrPreset === 'string') {
@@ -457,6 +845,20 @@ function signalTree(obj, configOrPreset) {
457
845
  return create(obj, config);
458
846
  }
459
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
+
460
862
  /**
461
863
  * Generated bundle index. Do not edit.
462
864
  */