@signaltree/core 1.0.3 → 1.1.1
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 +273 -116
- package/fesm2022/signaltree-core.mjs +384 -100
- package/fesm2022/signaltree-core.mjs.map +1 -1
- package/index.d.ts +115 -57
- package/package.json +1 -2
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { signal, isSignal, computed, effect, inject, DestroyRef } from '@angular/core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
//
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
cachedSignal
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
*
|
|
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;
|
|
272
|
-
//
|
|
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
|
-
:
|
|
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
|
|
285
|
-
*
|
|
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
|
|
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
|
|
455
|
+
* @returns A deeply signalified version maintaining exact type structure
|
|
291
456
|
*
|
|
292
457
|
* @example
|
|
293
458
|
* ```typescript
|
|
294
|
-
* const
|
|
459
|
+
* const store = createSignalStore({
|
|
295
460
|
* user: { name: 'John', age: 30 },
|
|
296
461
|
* settings: { theme: 'dark' }
|
|
297
462
|
* }, Object.is);
|
|
298
463
|
*
|
|
299
|
-
* //
|
|
300
|
-
*
|
|
301
|
-
* console.log(
|
|
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
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
452
|
-
return fns.reduce((acc, fn) =>
|
|
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
|
*/
|