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