@signaltree/core 4.0.15 → 4.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.
Files changed (109) hide show
  1. package/README.md +106 -38
  2. package/dist/constants.js +6 -0
  3. package/dist/deep-clone.js +80 -0
  4. package/dist/deep-equal.js +41 -0
  5. package/dist/enhancers/batching/lib/batching.js +141 -135
  6. package/dist/enhancers/computed/lib/computed.js +18 -16
  7. package/dist/enhancers/devtools/lib/devtools.js +303 -260
  8. package/dist/enhancers/entities/lib/entities.js +109 -104
  9. package/dist/enhancers/index.js +65 -77
  10. package/dist/enhancers/memoization/lib/memoization.js +339 -351
  11. package/dist/enhancers/middleware/lib/async-helpers.js +71 -79
  12. package/dist/enhancers/middleware/lib/middleware.js +126 -169
  13. package/dist/enhancers/presets/lib/presets.js +82 -71
  14. package/dist/enhancers/serialization/constants.js +14 -13
  15. package/dist/enhancers/serialization/lib/serialization.js +615 -623
  16. package/dist/enhancers/time-travel/lib/time-travel.js +178 -177
  17. package/dist/index.d.ts +1 -17
  18. package/dist/index.js +19 -16
  19. package/dist/is-built-in-object.js +23 -0
  20. package/dist/lib/constants.js +51 -55
  21. package/dist/lib/memory/memory-manager.js +152 -154
  22. package/dist/lib/performance/diff-engine.js +141 -141
  23. package/dist/lib/performance/path-index.js +139 -137
  24. package/dist/lib/performance/update-engine.js +171 -176
  25. package/dist/lib/security/security-validator.js +110 -128
  26. package/dist/lib/signal-tree.js +577 -611
  27. package/dist/lib/types.js +3 -9
  28. package/dist/lib/utils.js +236 -268
  29. package/dist/lru-cache.js +64 -0
  30. package/dist/parse-path.js +13 -0
  31. package/package.json +30 -16
  32. package/src/index.d.ts +17 -0
  33. package/{dist → src}/lib/utils.d.ts +1 -0
  34. package/dist/enhancers/batching/index.js +0 -1
  35. package/dist/enhancers/batching/jest.config.js +0 -21
  36. package/dist/enhancers/batching/test-setup.js +0 -5
  37. package/dist/enhancers/computed/index.js +0 -1
  38. package/dist/enhancers/computed/jest.config.js +0 -21
  39. package/dist/enhancers/devtools/index.js +0 -1
  40. package/dist/enhancers/devtools/jest.config.js +0 -21
  41. package/dist/enhancers/devtools/test-setup.js +0 -5
  42. package/dist/enhancers/entities/index.js +0 -1
  43. package/dist/enhancers/entities/jest.config.js +0 -21
  44. package/dist/enhancers/entities/test-setup.js +0 -5
  45. package/dist/enhancers/memoization/index.js +0 -1
  46. package/dist/enhancers/memoization/jest.config.js +0 -21
  47. package/dist/enhancers/memoization/test-setup.js +0 -5
  48. package/dist/enhancers/middleware/index.js +0 -2
  49. package/dist/enhancers/middleware/jest.config.js +0 -21
  50. package/dist/enhancers/middleware/test-setup.js +0 -5
  51. package/dist/enhancers/presets/index.js +0 -1
  52. package/dist/enhancers/presets/jest.config.js +0 -21
  53. package/dist/enhancers/presets/test-setup.js +0 -5
  54. package/dist/enhancers/serialization/index.js +0 -2
  55. package/dist/enhancers/serialization/jest.config.js +0 -21
  56. package/dist/enhancers/serialization/test-setup.js +0 -5
  57. package/dist/enhancers/time-travel/index.js +0 -1
  58. package/dist/enhancers/time-travel/jest.config.js +0 -21
  59. package/dist/enhancers/time-travel/lib/utils.js +0 -1
  60. package/dist/enhancers/time-travel/test-setup.js +0 -5
  61. package/dist/enhancers/types.js +0 -0
  62. /package/{dist → src}/enhancers/batching/index.d.ts +0 -0
  63. /package/{dist → src}/enhancers/batching/jest.config.d.ts +0 -0
  64. /package/{dist → src}/enhancers/batching/lib/batching.d.ts +0 -0
  65. /package/{dist → src}/enhancers/batching/test-setup.d.ts +0 -0
  66. /package/{dist → src}/enhancers/computed/index.d.ts +0 -0
  67. /package/{dist → src}/enhancers/computed/jest.config.d.ts +0 -0
  68. /package/{dist → src}/enhancers/computed/lib/computed.d.ts +0 -0
  69. /package/{dist → src}/enhancers/devtools/index.d.ts +0 -0
  70. /package/{dist → src}/enhancers/devtools/jest.config.d.ts +0 -0
  71. /package/{dist → src}/enhancers/devtools/lib/devtools.d.ts +0 -0
  72. /package/{dist → src}/enhancers/devtools/test-setup.d.ts +0 -0
  73. /package/{dist → src}/enhancers/entities/index.d.ts +0 -0
  74. /package/{dist → src}/enhancers/entities/jest.config.d.ts +0 -0
  75. /package/{dist → src}/enhancers/entities/lib/entities.d.ts +0 -0
  76. /package/{dist → src}/enhancers/entities/test-setup.d.ts +0 -0
  77. /package/{dist → src}/enhancers/index.d.ts +0 -0
  78. /package/{dist → src}/enhancers/memoization/index.d.ts +0 -0
  79. /package/{dist → src}/enhancers/memoization/jest.config.d.ts +0 -0
  80. /package/{dist → src}/enhancers/memoization/lib/memoization.d.ts +0 -0
  81. /package/{dist → src}/enhancers/memoization/test-setup.d.ts +0 -0
  82. /package/{dist → src}/enhancers/middleware/index.d.ts +0 -0
  83. /package/{dist → src}/enhancers/middleware/jest.config.d.ts +0 -0
  84. /package/{dist → src}/enhancers/middleware/lib/async-helpers.d.ts +0 -0
  85. /package/{dist → src}/enhancers/middleware/lib/middleware.d.ts +0 -0
  86. /package/{dist → src}/enhancers/middleware/test-setup.d.ts +0 -0
  87. /package/{dist → src}/enhancers/presets/index.d.ts +0 -0
  88. /package/{dist → src}/enhancers/presets/jest.config.d.ts +0 -0
  89. /package/{dist → src}/enhancers/presets/lib/presets.d.ts +0 -0
  90. /package/{dist → src}/enhancers/presets/test-setup.d.ts +0 -0
  91. /package/{dist → src}/enhancers/serialization/constants.d.ts +0 -0
  92. /package/{dist → src}/enhancers/serialization/index.d.ts +0 -0
  93. /package/{dist → src}/enhancers/serialization/jest.config.d.ts +0 -0
  94. /package/{dist → src}/enhancers/serialization/lib/serialization.d.ts +0 -0
  95. /package/{dist → src}/enhancers/serialization/test-setup.d.ts +0 -0
  96. /package/{dist → src}/enhancers/time-travel/index.d.ts +0 -0
  97. /package/{dist → src}/enhancers/time-travel/jest.config.d.ts +0 -0
  98. /package/{dist → src}/enhancers/time-travel/lib/time-travel.d.ts +0 -0
  99. /package/{dist → src}/enhancers/time-travel/lib/utils.d.ts +0 -0
  100. /package/{dist → src}/enhancers/time-travel/test-setup.d.ts +0 -0
  101. /package/{dist → src}/enhancers/types.d.ts +0 -0
  102. /package/{dist → src}/lib/constants.d.ts +0 -0
  103. /package/{dist → src}/lib/memory/memory-manager.d.ts +0 -0
  104. /package/{dist → src}/lib/performance/diff-engine.d.ts +0 -0
  105. /package/{dist → src}/lib/performance/path-index.d.ts +0 -0
  106. /package/{dist → src}/lib/performance/update-engine.d.ts +0 -0
  107. /package/{dist → src}/lib/security/security-validator.d.ts +0 -0
  108. /package/{dist → src}/lib/signal-tree.d.ts +0 -0
  109. /package/{dist → src}/lib/types.d.ts +0 -0
@@ -1,668 +1,660 @@
1
1
  import { isSignal } from '@angular/core';
2
- import { TYPE_MARKERS } from '../constants';
2
+ import { TYPE_MARKERS } from '../constants.js';
3
+
3
4
  const DEFAULT_CONFIG = {
4
- includeMetadata: true,
5
- replacer: undefined,
6
- reviver: undefined,
7
- preserveTypes: true,
8
- maxDepth: 50,
9
- handleCircular: true,
5
+ includeMetadata: true,
6
+ replacer: undefined,
7
+ reviver: undefined,
8
+ preserveTypes: true,
9
+ maxDepth: 50,
10
+ handleCircular: true
10
11
  };
11
12
  function unwrapObjectSafely(obj, visited = new WeakSet(), depth = 0, maxDepth = 50, preserveTypes = true) {
12
- if (depth > maxDepth)
13
- return '[Max Depth Exceeded]';
14
- if (obj === null || typeof obj !== 'object') {
15
- if (!preserveTypes)
16
- return obj;
17
- if (obj === undefined)
18
- return { [TYPE_MARKERS.UNDEFINED]: true };
19
- if (typeof obj === 'number') {
20
- if (Number.isNaN(obj))
21
- return { [TYPE_MARKERS.NAN]: true };
22
- if (obj === Infinity)
23
- return { [TYPE_MARKERS.INFINITY]: true };
24
- if (obj === -Infinity)
25
- return { [TYPE_MARKERS.NEG_INFINITY]: true };
26
- return obj;
27
- }
28
- if (typeof obj === 'bigint')
29
- return { [TYPE_MARKERS.BIGINT]: String(obj) };
30
- if (typeof obj === 'symbol')
31
- return { [TYPE_MARKERS.SYMBOL]: String(obj) };
32
- return obj;
13
+ if (depth > maxDepth) return '[Max Depth Exceeded]';
14
+ if (obj === null || typeof obj !== 'object') {
15
+ if (!preserveTypes) return obj;
16
+ if (obj === undefined) return {
17
+ [TYPE_MARKERS.UNDEFINED]: true
18
+ };
19
+ if (typeof obj === 'number') {
20
+ if (Number.isNaN(obj)) return {
21
+ [TYPE_MARKERS.NAN]: true
22
+ };
23
+ if (obj === Infinity) return {
24
+ [TYPE_MARKERS.INFINITY]: true
25
+ };
26
+ if (obj === -Infinity) return {
27
+ [TYPE_MARKERS.NEG_INFINITY]: true
28
+ };
29
+ return obj;
33
30
  }
34
- if (typeof obj === 'function') {
35
- try {
36
- const result = obj();
37
- return unwrapObjectSafely(result, visited, depth + 1, maxDepth, preserveTypes);
38
- }
39
- catch {
40
- return '[Function]';
41
- }
31
+ if (typeof obj === 'bigint') return {
32
+ [TYPE_MARKERS.BIGINT]: String(obj)
33
+ };
34
+ if (typeof obj === 'symbol') return {
35
+ [TYPE_MARKERS.SYMBOL]: String(obj)
36
+ };
37
+ return obj;
38
+ }
39
+ if (typeof obj === 'function') {
40
+ try {
41
+ const result = obj();
42
+ return unwrapObjectSafely(result, visited, depth + 1, maxDepth, preserveTypes);
43
+ } catch {
44
+ return '[Function]';
42
45
  }
43
- if (visited.has(obj))
44
- return '[Circular Reference]';
45
- if (isSignal(obj))
46
- return obj();
47
- if (preserveTypes) {
48
- if (obj instanceof Date)
49
- return { [TYPE_MARKERS.DATE]: obj.toISOString() };
50
- if (obj instanceof RegExp)
51
- return {
52
- [TYPE_MARKERS.REGEXP]: { source: obj.source, flags: obj.flags },
53
- };
54
- if (obj instanceof Map) {
55
- return { [TYPE_MARKERS.MAP]: Array.from(obj.entries()) };
56
- }
57
- if (obj instanceof Set) {
58
- return { [TYPE_MARKERS.SET]: Array.from(obj.values()) };
59
- }
46
+ }
47
+ if (visited.has(obj)) return '[Circular Reference]';
48
+ if (isSignal(obj)) return obj();
49
+ if (preserveTypes) {
50
+ if (obj instanceof Date) return {
51
+ [TYPE_MARKERS.DATE]: obj.toISOString()
52
+ };
53
+ if (obj instanceof RegExp) return {
54
+ [TYPE_MARKERS.REGEXP]: {
55
+ source: obj.source,
56
+ flags: obj.flags
57
+ }
58
+ };
59
+ if (obj instanceof Map) {
60
+ return {
61
+ [TYPE_MARKERS.MAP]: Array.from(obj.entries())
62
+ };
60
63
  }
61
- else {
62
- if (obj instanceof Date || obj instanceof RegExp)
63
- return obj;
64
+ if (obj instanceof Set) {
65
+ return {
66
+ [TYPE_MARKERS.SET]: Array.from(obj.values())
67
+ };
64
68
  }
65
- visited.add(obj);
66
- try {
67
- if (Array.isArray(obj)) {
68
- const arr = obj.map((item) => unwrapObjectSafely(item, visited, depth + 1, maxDepth, preserveTypes));
69
- visited.delete(obj);
70
- return arr;
71
- }
72
- const out = {};
73
- for (const [k, v] of Object.entries(obj)) {
74
- if ((k === 'set' || k === 'update') &&
75
- typeof v === 'function' &&
76
- !isSignal(v))
77
- continue;
78
- if (isSignal(v)) {
79
- out[k] = unwrapObjectSafely(v(), visited, depth + 1, maxDepth, preserveTypes);
80
- }
81
- else if (typeof v === 'function' && k !== 'set' && k !== 'update') {
82
- try {
83
- const callResult = v();
84
- out[k] = unwrapObjectSafely(callResult, visited, depth + 1, maxDepth, preserveTypes);
85
- }
86
- catch {
87
- continue;
88
- }
89
- }
90
- else {
91
- out[k] = unwrapObjectSafely(v, visited, depth + 1, maxDepth, preserveTypes);
92
- }
93
- }
94
- visited.delete(obj);
95
- return out;
69
+ } else {
70
+ if (obj instanceof Date || obj instanceof RegExp) return obj;
71
+ }
72
+ visited.add(obj);
73
+ try {
74
+ if (Array.isArray(obj)) {
75
+ const arr = obj.map(item => unwrapObjectSafely(item, visited, depth + 1, maxDepth, preserveTypes));
76
+ visited.delete(obj);
77
+ return arr;
96
78
  }
97
- catch {
98
- visited.delete(obj);
99
- return '[Serialization Error]';
79
+ const out = {};
80
+ for (const [k, v] of Object.entries(obj)) {
81
+ if ((k === 'set' || k === 'update') && typeof v === 'function' && !isSignal(v)) continue;
82
+ if (isSignal(v)) {
83
+ out[k] = unwrapObjectSafely(v(), visited, depth + 1, maxDepth, preserveTypes);
84
+ } else if (typeof v === 'function' && k !== 'set' && k !== 'update') {
85
+ try {
86
+ const callResult = v();
87
+ out[k] = unwrapObjectSafely(callResult, visited, depth + 1, maxDepth, preserveTypes);
88
+ } catch {
89
+ continue;
90
+ }
91
+ } else {
92
+ out[k] = unwrapObjectSafely(v, visited, depth + 1, maxDepth, preserveTypes);
93
+ }
100
94
  }
95
+ visited.delete(obj);
96
+ return out;
97
+ } catch {
98
+ visited.delete(obj);
99
+ return '[Serialization Error]';
100
+ }
101
101
  }
102
102
  function detectCircularReferences(obj, path = '', seen = new WeakSet(), paths = new Map()) {
103
- const circular = [];
104
- if (obj === null || typeof obj !== 'object') {
105
- return circular;
103
+ const circular = [];
104
+ if (obj === null || typeof obj !== 'object') {
105
+ return circular;
106
+ }
107
+ if (seen.has(obj)) {
108
+ const targetPath = paths.get(obj) || '';
109
+ circular.push({
110
+ path,
111
+ targetPath
112
+ });
113
+ return circular;
114
+ }
115
+ seen.add(obj);
116
+ paths.set(obj, path);
117
+ if (Array.isArray(obj)) {
118
+ const arrObj = obj;
119
+ for (let i = 0; i < arrObj.length; i++) {
120
+ const itemPath = path ? `${path}[${i}]` : `[${i}]`;
121
+ const childCircular = detectCircularReferences(arrObj[i], itemPath, seen, paths);
122
+ circular.push(...childCircular);
106
123
  }
107
- if (seen.has(obj)) {
108
- const targetPath = paths.get(obj) || '';
109
- circular.push({ path, targetPath });
110
- return circular;
124
+ } else {
125
+ for (const [key, value] of Object.entries(obj)) {
126
+ const propPath = path ? `${path}.${key}` : key;
127
+ const childCircular = detectCircularReferences(value, propPath, seen, paths);
128
+ circular.push(...childCircular);
111
129
  }
112
- seen.add(obj);
113
- paths.set(obj, path);
114
- if (Array.isArray(obj)) {
115
- const arrObj = obj;
116
- for (let i = 0; i < arrObj.length; i++) {
117
- const itemPath = path ? `${path}[${i}]` : `[${i}]`;
118
- const childCircular = detectCircularReferences(arrObj[i], itemPath, seen, paths);
119
- circular.push(...childCircular);
120
- }
130
+ }
131
+ seen.delete(obj);
132
+ return circular;
133
+ }
134
+ function createReplacer(config) {
135
+ const seen = new WeakSet();
136
+ const circularPaths = new Map();
137
+ return function replacer(key, value) {
138
+ if (config.replacer) {
139
+ value = config.replacer.call(this, key, value);
121
140
  }
122
- else {
123
- for (const [key, value] of Object.entries(obj)) {
124
- const propPath = path ? `${path}.${key}` : key;
125
- const childCircular = detectCircularReferences(value, propPath, seen, paths);
126
- circular.push(...childCircular);
141
+ if (isSignal(value)) {
142
+ return value();
143
+ }
144
+ if (value && typeof value === 'object') {
145
+ if (seen.has(value)) {
146
+ if (config.handleCircular) {
147
+ const targetPath = circularPaths.get(value) || '';
148
+ return {
149
+ [TYPE_MARKERS.CIRCULAR]: targetPath
150
+ };
127
151
  }
152
+ return undefined;
153
+ }
154
+ seen.add(value);
155
+ const currentPath = key || '';
156
+ circularPaths.set(value, currentPath);
128
157
  }
129
- seen.delete(obj);
130
- return circular;
158
+ return value;
159
+ };
131
160
  }
132
- function createReplacer(config) {
133
- const seen = new WeakSet();
134
- const circularPaths = new Map();
135
- return function replacer(key, value) {
136
- if (config.replacer) {
137
- value = config.replacer.call(this, key, value);
161
+ function resolveCircularReferences(obj, circularPaths) {
162
+ for (const {
163
+ path,
164
+ targetPath
165
+ } of circularPaths) {
166
+ const pathParts = path.split(/\.|\[|\]/).filter(Boolean);
167
+ const targetParts = targetPath.split(/\.|\[|\]/).filter(Boolean);
168
+ let current = obj;
169
+ for (let i = 0; i < pathParts.length - 1; i++) {
170
+ current = current[pathParts[i]];
171
+ if (!current) break;
172
+ }
173
+ let target = obj;
174
+ for (const part of targetParts) {
175
+ target = target[part];
176
+ if (!target) break;
177
+ }
178
+ if (current && target) {
179
+ current[pathParts[pathParts.length - 1]] = target;
180
+ }
181
+ }
182
+ }
183
+ function withSerialization(defaultConfig = {}) {
184
+ const enhancer = tree => {
185
+ const enhanced = tree;
186
+ enhanced.toJSON = () => {
187
+ return tree();
188
+ };
189
+ enhanced.fromJSON = (data, metadata) => {
190
+ const restoreSpecialTypes = value => {
191
+ if (!value || typeof value !== 'object') {
192
+ return value;
138
193
  }
139
- if (isSignal(value)) {
140
- return value();
194
+ if (TYPE_MARKERS.UNDEFINED in value) {
195
+ return undefined;
141
196
  }
142
- if (value && typeof value === 'object') {
143
- if (seen.has(value)) {
144
- if (config.handleCircular) {
145
- const targetPath = circularPaths.get(value) || '';
146
- return { [TYPE_MARKERS.CIRCULAR]: targetPath };
147
- }
148
- return undefined;
149
- }
150
- seen.add(value);
151
- const currentPath = key || '';
152
- circularPaths.set(value, currentPath);
197
+ if (TYPE_MARKERS.NAN in value) {
198
+ return NaN;
153
199
  }
154
- return value;
155
- };
156
- }
157
- function resolveCircularReferences(obj, circularPaths) {
158
- for (const { path, targetPath } of circularPaths) {
159
- const pathParts = path.split(/\.|\[|\]/).filter(Boolean);
160
- const targetParts = targetPath.split(/\.|\[|\]/).filter(Boolean);
161
- let current = obj;
162
- for (let i = 0; i < pathParts.length - 1; i++) {
163
- current = current[pathParts[i]];
164
- if (!current)
165
- break;
200
+ if (TYPE_MARKERS.INFINITY in value) {
201
+ return Infinity;
166
202
  }
167
- let target = obj;
168
- for (const part of targetParts) {
169
- target = target[part];
170
- if (!target)
171
- break;
203
+ if (TYPE_MARKERS.NEG_INFINITY in value) {
204
+ return -Infinity;
172
205
  }
173
- if (current && target) {
174
- current[pathParts[pathParts.length - 1]] = target;
206
+ if (TYPE_MARKERS.BIGINT in value) {
207
+ return BigInt(value[TYPE_MARKERS.BIGINT]);
175
208
  }
176
- }
177
- }
178
- export function withSerialization(defaultConfig = {}) {
179
- const enhancer = (tree) => {
180
- const enhanced = tree;
181
- enhanced.toJSON = () => {
182
- return tree();
183
- };
184
- enhanced.fromJSON = (data, metadata) => {
185
- const restoreSpecialTypes = (value) => {
186
- if (!value || typeof value !== 'object') {
187
- return value;
188
- }
189
- if (TYPE_MARKERS.UNDEFINED in value) {
190
- return undefined;
191
- }
192
- if (TYPE_MARKERS.NAN in value) {
193
- return NaN;
194
- }
195
- if (TYPE_MARKERS.INFINITY in value) {
196
- return Infinity;
197
- }
198
- if (TYPE_MARKERS.NEG_INFINITY in value) {
199
- return -Infinity;
200
- }
201
- if (TYPE_MARKERS.BIGINT in value) {
202
- return BigInt(value[TYPE_MARKERS.BIGINT]);
203
- }
204
- if (TYPE_MARKERS.SYMBOL in value) {
205
- return Symbol.for(value[TYPE_MARKERS.SYMBOL]);
206
- }
207
- if (TYPE_MARKERS.DATE in value) {
208
- return new Date(value[TYPE_MARKERS.DATE]);
209
- }
210
- if (TYPE_MARKERS.REGEXP in value) {
211
- const regexpData = value[TYPE_MARKERS.REGEXP];
212
- return new RegExp(regexpData.source, regexpData.flags);
213
- }
214
- if (TYPE_MARKERS.MAP in value) {
215
- return new Map(value[TYPE_MARKERS.MAP]);
216
- }
217
- if (TYPE_MARKERS.SET in value) {
218
- return new Set(value[TYPE_MARKERS.SET]);
219
- }
220
- if (Array.isArray(value)) {
221
- return value.map(restoreSpecialTypes);
222
- }
223
- const result = {};
224
- for (const [k, v] of Object.entries(value)) {
225
- result[k] = restoreSpecialTypes(v);
226
- }
227
- return result;
228
- };
229
- const restoredData = restoreSpecialTypes(data);
230
- function resolveAliasSignal(path, key) {
231
- let node = tree.$;
232
- if (path && node) {
233
- for (const part of path.split('.')) {
234
- if (!part)
235
- continue;
236
- const next = node[part];
237
- if (!next ||
238
- (typeof next !== 'object' && typeof next !== 'function')) {
239
- node = undefined;
240
- break;
241
- }
242
- node = next;
243
- }
244
- }
245
- const candidate = node?.[key];
246
- return isSignal(candidate)
247
- ? candidate
248
- : undefined;
249
- }
250
- function updateSignals(target, source, path = '') {
251
- if (!target || !source)
252
- return;
253
- for (const key in source) {
254
- if (!Object.prototype.hasOwnProperty.call(source, key))
255
- continue;
256
- const sourceValue = source[key];
257
- const direct = target[key];
258
- const targetSignal = isSignal(direct)
259
- ? direct
260
- :
261
- typeof direct === 'function' &&
262
- 'set' in direct &&
263
- typeof direct.set === 'function'
264
- ? direct
265
- : resolveAliasSignal(path, key);
266
- if (targetSignal) {
267
- targetSignal.set(sourceValue);
268
- continue;
269
- }
270
- if (sourceValue &&
271
- typeof sourceValue === 'object' &&
272
- !Array.isArray(sourceValue) &&
273
- direct &&
274
- (typeof direct === 'object' ||
275
- (typeof direct === 'function' && !('set' in direct))) &&
276
- !isSignal(direct)) {
277
- updateSignals(direct, sourceValue, path ? `${path}.${key}` : key);
278
- }
279
- }
280
- }
281
- const nodeMap = metadata?.nodeMap;
282
- if (nodeMap && Object.keys(nodeMap).length > 0) {
283
- if (nodeMap[''] === 'r') {
284
- const rootAlias = tree.$;
285
- if (rootAlias && typeof rootAlias.set === 'function') {
286
- rootAlias.set(restoredData);
287
- }
288
- }
289
- for (const [path, kind] of Object.entries(nodeMap)) {
290
- if (path === '')
291
- continue;
292
- if (kind !== 'b')
293
- continue;
294
- const parts = path.split(/\.|\[|\]/).filter(Boolean);
295
- let node = tree.$;
296
- for (const p of parts) {
297
- if (!node)
298
- break;
299
- node =
300
- node[p] ?? undefined;
301
- }
302
- if (node &&
303
- (isSignal(node) ||
304
- (typeof node === 'function' &&
305
- 'set' in node &&
306
- typeof node.set === 'function'))) {
307
- let current = restoredData;
308
- for (const p of parts) {
309
- if (current == null) {
310
- current = undefined;
311
- break;
312
- }
313
- if (typeof current === 'object') {
314
- current = current[p];
315
- }
316
- else {
317
- current = undefined;
318
- break;
319
- }
320
- }
321
- try {
322
- node.set(current);
323
- }
324
- catch {
325
- }
326
- }
327
- }
328
- updateSignals(tree.state, restoredData);
329
- return;
330
- }
331
- updateSignals(tree.state, restoredData);
332
- };
333
- function encodeSpecials(v, preserveTypes) {
334
- if (!preserveTypes)
335
- return v;
336
- if (v === undefined)
337
- return { [TYPE_MARKERS.UNDEFINED]: true };
338
- if (typeof v === 'number') {
339
- if (Number.isNaN(v))
340
- return { [TYPE_MARKERS.NAN]: true };
341
- if (v === Infinity)
342
- return { [TYPE_MARKERS.INFINITY]: true };
343
- if (v === -Infinity)
344
- return { [TYPE_MARKERS.NEG_INFINITY]: true };
345
- return v;
346
- }
347
- if (typeof v === 'bigint')
348
- return { [TYPE_MARKERS.BIGINT]: String(v) };
349
- if (typeof v === 'symbol')
350
- return { [TYPE_MARKERS.SYMBOL]: String(v) };
351
- if (v instanceof Date)
352
- return { [TYPE_MARKERS.DATE]: v.toISOString() };
353
- if (v instanceof RegExp)
354
- return {
355
- [TYPE_MARKERS.REGEXP]: { source: v.source, flags: v.flags },
356
- };
357
- if (v instanceof Map)
358
- return { [TYPE_MARKERS.MAP]: Array.from(v.entries()) };
359
- if (v instanceof Set)
360
- return { [TYPE_MARKERS.SET]: Array.from(v.values()) };
361
- if (Array.isArray(v))
362
- return v.map((x) => encodeSpecials(x, preserveTypes));
363
- if (v && typeof v === 'object') {
364
- const out = {};
365
- for (const [k, val] of Object.entries(v))
366
- out[k] = encodeSpecials(val, preserveTypes);
367
- return out;
368
- }
369
- return v;
209
+ if (TYPE_MARKERS.SYMBOL in value) {
210
+ return Symbol.for(value[TYPE_MARKERS.SYMBOL]);
370
211
  }
371
- enhanced.serialize = (config) => {
372
- const fullConfig = {
373
- ...DEFAULT_CONFIG,
374
- ...defaultConfig,
375
- ...config,
376
- };
377
- const raw = unwrapObjectSafely(tree.state, new WeakSet(), 0, fullConfig.maxDepth, fullConfig.preserveTypes);
378
- const state = encodeSpecials(raw, fullConfig.preserveTypes);
379
- const circularPaths = fullConfig.handleCircular
380
- ? detectCircularReferences(state)
381
- : [];
382
- const data = {
383
- data: state,
384
- };
385
- const nodeMap = {};
386
- try {
387
- const rootAlias = tree.$;
388
- if (rootAlias && typeof rootAlias.set === 'function') {
389
- nodeMap[''] = 'r';
390
- }
391
- const visited = new WeakSet();
392
- const isBranch = (v) => isSignal(v) ||
393
- (typeof v === 'function' &&
394
- 'set' in v &&
395
- typeof v.set === 'function');
396
- const walkAlias = (obj, path = '') => {
397
- if (!obj || (typeof obj !== 'object' && typeof obj !== 'function'))
398
- return;
399
- const ref = obj;
400
- if (visited.has(ref))
401
- return;
402
- visited.add(ref);
403
- if (path && isBranch(obj)) {
404
- nodeMap[path] = 'b';
405
- }
406
- for (const [k, v] of Object.entries(obj)) {
407
- const childPath = path ? `${path}.${k}` : k;
408
- walkAlias(v, childPath);
409
- }
410
- };
411
- if (rootAlias)
412
- walkAlias(rootAlias);
413
- }
414
- catch {
212
+ if (TYPE_MARKERS.DATE in value) {
213
+ return new Date(value[TYPE_MARKERS.DATE]);
214
+ }
215
+ if (TYPE_MARKERS.REGEXP in value) {
216
+ const regexpData = value[TYPE_MARKERS.REGEXP];
217
+ return new RegExp(regexpData.source, regexpData.flags);
218
+ }
219
+ if (TYPE_MARKERS.MAP in value) {
220
+ return new Map(value[TYPE_MARKERS.MAP]);
221
+ }
222
+ if (TYPE_MARKERS.SET in value) {
223
+ return new Set(value[TYPE_MARKERS.SET]);
224
+ }
225
+ if (Array.isArray(value)) {
226
+ return value.map(restoreSpecialTypes);
227
+ }
228
+ const result = {};
229
+ for (const [k, v] of Object.entries(value)) {
230
+ result[k] = restoreSpecialTypes(v);
231
+ }
232
+ return result;
233
+ };
234
+ const restoredData = restoreSpecialTypes(data);
235
+ function resolveAliasSignal(path, key) {
236
+ let node = tree.$;
237
+ if (path && node) {
238
+ for (const part of path.split('.')) {
239
+ if (!part) continue;
240
+ const next = node[part];
241
+ if (!next || typeof next !== 'object' && typeof next !== 'function') {
242
+ node = undefined;
243
+ break;
415
244
  }
416
- if (fullConfig.includeMetadata) {
417
- data.metadata = {
418
- timestamp: Date.now(),
419
- version: '1.0.0',
420
- ...(circularPaths.length > 0 && { circularRefs: circularPaths }),
421
- ...(Object.keys(nodeMap).length > 0 && { nodeMap }),
422
- };
245
+ node = next;
246
+ }
247
+ }
248
+ const candidate = node?.[key];
249
+ return isSignal(candidate) ? candidate : undefined;
250
+ }
251
+ function updateSignals(target, source, path = '') {
252
+ if (!target || !source) return;
253
+ for (const key in source) {
254
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
255
+ const sourceValue = source[key];
256
+ const direct = target[key];
257
+ const targetSignal = isSignal(direct) ? direct : typeof direct === 'function' && 'set' in direct && typeof direct.set === 'function' ? direct : resolveAliasSignal(path, key);
258
+ if (targetSignal) {
259
+ targetSignal.set(sourceValue);
260
+ continue;
261
+ }
262
+ if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue) && direct && (typeof direct === 'object' || typeof direct === 'function' && !('set' in direct)) && !isSignal(direct)) {
263
+ updateSignals(direct, sourceValue, path ? `${path}.${key}` : key);
264
+ }
265
+ }
266
+ }
267
+ const nodeMap = metadata?.nodeMap;
268
+ if (nodeMap && Object.keys(nodeMap).length > 0) {
269
+ if (nodeMap[''] === 'r') {
270
+ const rootAlias = tree.$;
271
+ if (rootAlias && typeof rootAlias.set === 'function') {
272
+ rootAlias.set(restoredData);
273
+ }
274
+ }
275
+ for (const [path, kind] of Object.entries(nodeMap)) {
276
+ if (path === '') continue;
277
+ if (kind !== 'b') continue;
278
+ const parts = path.split(/\.|\[|\]/).filter(Boolean);
279
+ let node = tree.$;
280
+ for (const p of parts) {
281
+ if (!node) break;
282
+ node = node[p] ?? undefined;
283
+ }
284
+ if (node && (isSignal(node) || typeof node === 'function' && 'set' in node && typeof node.set === 'function')) {
285
+ let current = restoredData;
286
+ for (const p of parts) {
287
+ if (current == null) {
288
+ current = undefined;
289
+ break;
290
+ }
291
+ if (typeof current === 'object') {
292
+ current = current[p];
293
+ } else {
294
+ current = undefined;
295
+ break;
296
+ }
423
297
  }
424
- const replacer = createReplacer(fullConfig);
425
- const json = JSON.stringify(data, replacer, 2);
426
- return json;
427
- };
428
- enhanced.deserialize = (json, config) => {
429
- const fullConfig = {
430
- ...DEFAULT_CONFIG,
431
- ...defaultConfig,
432
- ...config,
433
- };
434
298
  try {
435
- const parsed = JSON.parse(json);
436
- const { data, metadata } = parsed;
437
- if (metadata?.circularRefs && fullConfig.handleCircular) {
438
- resolveCircularReferences(data, metadata.circularRefs);
439
- }
440
- enhanced.fromJSON(data, metadata);
441
- if (tree.__config?.debugMode) {
442
- console.log('[SignalTree] State restored from serialized data', {
443
- timestamp: metadata?.timestamp,
444
- version: metadata?.version,
445
- });
446
- }
447
- }
448
- catch (error) {
449
- console.error('[SignalTree] Failed to deserialize:', error);
450
- throw new Error(`Failed to deserialize SignalTree state: ${error instanceof Error ? error.message : 'Unknown error'}`);
451
- }
299
+ node.set(current);
300
+ } catch {}
301
+ }
302
+ }
303
+ updateSignals(tree.state, restoredData);
304
+ return;
305
+ }
306
+ updateSignals(tree.state, restoredData);
307
+ };
308
+ function encodeSpecials(v, preserveTypes) {
309
+ if (!preserveTypes) return v;
310
+ if (v === undefined) return {
311
+ [TYPE_MARKERS.UNDEFINED]: true
312
+ };
313
+ if (typeof v === 'number') {
314
+ if (Number.isNaN(v)) return {
315
+ [TYPE_MARKERS.NAN]: true
452
316
  };
453
- enhanced.snapshot = () => {
454
- const state = enhanced.toJSON();
455
- const circularPaths = detectCircularReferences(state);
456
- return {
457
- data: JSON.parse(JSON.stringify(state)),
458
- metadata: {
459
- timestamp: Date.now(),
460
- version: '1.0.0',
461
- ...(circularPaths.length > 0 && { circularRefs: circularPaths }),
462
- },
463
- };
317
+ if (v === Infinity) return {
318
+ [TYPE_MARKERS.INFINITY]: true
464
319
  };
465
- enhanced.restore = (snapshot) => {
466
- const { data, metadata } = snapshot;
467
- if (metadata?.circularRefs) {
468
- resolveCircularReferences(data, metadata.circularRefs);
469
- }
470
- enhanced.fromJSON(data, metadata);
320
+ if (v === -Infinity) return {
321
+ [TYPE_MARKERS.NEG_INFINITY]: true
322
+ };
323
+ return v;
324
+ }
325
+ if (typeof v === 'bigint') return {
326
+ [TYPE_MARKERS.BIGINT]: String(v)
327
+ };
328
+ if (typeof v === 'symbol') return {
329
+ [TYPE_MARKERS.SYMBOL]: String(v)
330
+ };
331
+ if (v instanceof Date) return {
332
+ [TYPE_MARKERS.DATE]: v.toISOString()
333
+ };
334
+ if (v instanceof RegExp) return {
335
+ [TYPE_MARKERS.REGEXP]: {
336
+ source: v.source,
337
+ flags: v.flags
338
+ }
339
+ };
340
+ if (v instanceof Map) return {
341
+ [TYPE_MARKERS.MAP]: Array.from(v.entries())
342
+ };
343
+ if (v instanceof Set) return {
344
+ [TYPE_MARKERS.SET]: Array.from(v.values())
345
+ };
346
+ if (Array.isArray(v)) return v.map(x => encodeSpecials(x, preserveTypes));
347
+ if (v && typeof v === 'object') {
348
+ const out = {};
349
+ for (const [k, val] of Object.entries(v)) out[k] = encodeSpecials(val, preserveTypes);
350
+ return out;
351
+ }
352
+ return v;
353
+ }
354
+ enhanced.serialize = config => {
355
+ const fullConfig = {
356
+ ...DEFAULT_CONFIG,
357
+ ...defaultConfig,
358
+ ...config
359
+ };
360
+ const raw = unwrapObjectSafely(tree.state, new WeakSet(), 0, fullConfig.maxDepth, fullConfig.preserveTypes);
361
+ const state = encodeSpecials(raw, fullConfig.preserveTypes);
362
+ const circularPaths = fullConfig.handleCircular ? detectCircularReferences(state) : [];
363
+ const data = {
364
+ data: state
365
+ };
366
+ const nodeMap = {};
367
+ try {
368
+ const rootAlias = tree.$;
369
+ if (rootAlias && typeof rootAlias.set === 'function') {
370
+ nodeMap[''] = 'r';
371
+ }
372
+ const visited = new WeakSet();
373
+ const isBranch = v => isSignal(v) || typeof v === 'function' && 'set' in v && typeof v.set === 'function';
374
+ const walkAlias = (obj, path = '') => {
375
+ if (!obj || typeof obj !== 'object' && typeof obj !== 'function') return;
376
+ const ref = obj;
377
+ if (visited.has(ref)) return;
378
+ visited.add(ref);
379
+ if (path && isBranch(obj)) {
380
+ nodeMap[path] = 'b';
381
+ }
382
+ for (const [k, v] of Object.entries(obj)) {
383
+ const childPath = path ? `${path}.${k}` : k;
384
+ walkAlias(v, childPath);
385
+ }
386
+ };
387
+ if (rootAlias) walkAlias(rootAlias);
388
+ } catch {}
389
+ if (fullConfig.includeMetadata) {
390
+ data.metadata = {
391
+ timestamp: Date.now(),
392
+ version: '1.0.0',
393
+ ...(circularPaths.length > 0 && {
394
+ circularRefs: circularPaths
395
+ }),
396
+ ...(Object.keys(nodeMap).length > 0 && {
397
+ nodeMap
398
+ })
471
399
  };
472
- return enhanced;
400
+ }
401
+ const replacer = createReplacer(fullConfig);
402
+ const json = JSON.stringify(data, replacer, 2);
403
+ return json;
473
404
  };
474
- enhancer.metadata = {
475
- name: 'serialization',
405
+ enhanced.deserialize = (json, config) => {
406
+ const fullConfig = {
407
+ ...DEFAULT_CONFIG,
408
+ ...defaultConfig,
409
+ ...config
410
+ };
411
+ try {
412
+ const parsed = JSON.parse(json);
413
+ const {
414
+ data,
415
+ metadata
416
+ } = parsed;
417
+ if (metadata?.circularRefs && fullConfig.handleCircular) {
418
+ resolveCircularReferences(data, metadata.circularRefs);
419
+ }
420
+ enhanced.fromJSON(data, metadata);
421
+ if (tree.__config?.debugMode) {
422
+ console.log('[SignalTree] State restored from serialized data', {
423
+ timestamp: metadata?.timestamp,
424
+ version: metadata?.version
425
+ });
426
+ }
427
+ } catch (error) {
428
+ console.error('[SignalTree] Failed to deserialize:', error);
429
+ throw new Error(`Failed to deserialize SignalTree state: ${error instanceof Error ? error.message : 'Unknown error'}`);
430
+ }
431
+ };
432
+ enhanced.snapshot = () => {
433
+ const state = enhanced.toJSON();
434
+ const circularPaths = detectCircularReferences(state);
435
+ return {
436
+ data: JSON.parse(JSON.stringify(state)),
437
+ metadata: {
438
+ timestamp: Date.now(),
439
+ version: '1.0.0',
440
+ ...(circularPaths.length > 0 && {
441
+ circularRefs: circularPaths
442
+ })
443
+ }
444
+ };
476
445
  };
477
- return enhancer;
446
+ enhanced.restore = snapshot => {
447
+ const {
448
+ data,
449
+ metadata
450
+ } = snapshot;
451
+ if (metadata?.circularRefs) {
452
+ resolveCircularReferences(data, metadata.circularRefs);
453
+ }
454
+ enhanced.fromJSON(data, metadata);
455
+ };
456
+ return enhanced;
457
+ };
458
+ enhancer.metadata = {
459
+ name: 'serialization'
460
+ };
461
+ return enhancer;
478
462
  }
479
- export function enableSerialization() {
480
- return withSerialization({
481
- includeMetadata: true,
482
- preserveTypes: true,
483
- handleCircular: true,
484
- });
463
+ function enableSerialization() {
464
+ return withSerialization({
465
+ includeMetadata: true,
466
+ preserveTypes: true,
467
+ handleCircular: true
468
+ });
485
469
  }
486
- export function withPersistence(config) {
487
- const { key, storage = typeof window !== 'undefined' ? window.localStorage : undefined, autoSave = true, debounceMs = 1000, autoLoad = true, ...serializationConfig } = config;
488
- if (!storage) {
489
- throw new Error('No storage adapter available. Provide a storage adapter in the config.');
470
+ function withPersistence(config) {
471
+ const {
472
+ key,
473
+ storage = typeof window !== 'undefined' ? window.localStorage : undefined,
474
+ autoSave = true,
475
+ debounceMs = 1000,
476
+ autoLoad = true,
477
+ ...serializationConfig
478
+ } = config;
479
+ if (!storage) {
480
+ throw new Error('No storage adapter available. Provide a storage adapter in the config.');
481
+ }
482
+ const storageAdapter = storage;
483
+ function enhancer(tree) {
484
+ const serializable = withSerialization(serializationConfig)(tree);
485
+ const enhanced = serializable;
486
+ let lastCacheKey = null;
487
+ enhanced.save = async () => {
488
+ try {
489
+ const cacheKey = enhanced.serialize({
490
+ ...serializationConfig,
491
+ includeMetadata: false
492
+ });
493
+ if (config.skipCache || cacheKey !== lastCacheKey) {
494
+ const serialized = enhanced.serialize(serializationConfig);
495
+ await Promise.resolve(storageAdapter.setItem(key, serialized));
496
+ lastCacheKey = cacheKey;
497
+ if (tree.__config?.debugMode) {
498
+ console.log(`[SignalTree] State saved to storage key: ${key}`);
499
+ }
500
+ } else if (tree.__config?.debugMode) {
501
+ console.log(`[SignalTree] State unchanged, skipping storage write for key: ${key}`);
502
+ }
503
+ } catch (error) {
504
+ console.error('[SignalTree] Failed to save state:', error);
505
+ throw error;
506
+ }
507
+ };
508
+ enhanced.load = async () => {
509
+ try {
510
+ const data = await Promise.resolve(storageAdapter.getItem(key));
511
+ if (data) {
512
+ enhanced.deserialize(data, serializationConfig);
513
+ lastCacheKey = enhanced.serialize({
514
+ ...serializationConfig,
515
+ includeMetadata: false
516
+ });
517
+ if (tree.__config?.debugMode) {
518
+ console.log(`[SignalTree] State loaded from storage key: ${key}`);
519
+ }
520
+ }
521
+ } catch (error) {
522
+ console.error('[SignalTree] Failed to load state:', error);
523
+ throw error;
524
+ }
525
+ };
526
+ enhanced.clear = async () => {
527
+ try {
528
+ await Promise.resolve(storageAdapter.removeItem(key));
529
+ lastCacheKey = null;
530
+ if (tree.__config?.debugMode) {
531
+ console.log(`[SignalTree] State cleared from storage key: ${key}`);
532
+ }
533
+ } catch (error) {
534
+ console.error('[SignalTree] Failed to clear state:', error);
535
+ throw error;
536
+ }
537
+ };
538
+ if (autoLoad) {
539
+ setTimeout(() => {
540
+ enhanced.load().catch(error => {
541
+ console.warn('[SignalTree] Auto-load failed:', error);
542
+ });
543
+ }, 0);
490
544
  }
491
- const storageAdapter = storage;
492
- function enhancer(tree) {
493
- const serializable = withSerialization(serializationConfig)(tree);
494
- const enhanced = serializable;
495
- let lastCacheKey = null;
496
- enhanced.save = async () => {
497
- try {
498
- const cacheKey = enhanced.serialize({
499
- ...serializationConfig,
500
- includeMetadata: false,
501
- });
502
- if (config.skipCache || cacheKey !== lastCacheKey) {
503
- const serialized = enhanced.serialize(serializationConfig);
504
- await Promise.resolve(storageAdapter.setItem(key, serialized));
505
- lastCacheKey = cacheKey;
506
- if (tree.__config?.debugMode) {
507
- console.log(`[SignalTree] State saved to storage key: ${key}`);
508
- }
509
- }
510
- else if (tree.__config?.debugMode) {
511
- console.log(`[SignalTree] State unchanged, skipping storage write for key: ${key}`);
512
- }
513
- }
514
- catch (error) {
515
- console.error('[SignalTree] Failed to save state:', error);
516
- throw error;
517
- }
518
- };
519
- enhanced.load = async () => {
520
- try {
521
- const data = await Promise.resolve(storageAdapter.getItem(key));
522
- if (data) {
523
- enhanced.deserialize(data, serializationConfig);
524
- lastCacheKey = enhanced.serialize({
525
- ...serializationConfig,
526
- includeMetadata: false,
527
- });
528
- if (tree.__config?.debugMode) {
529
- console.log(`[SignalTree] State loaded from storage key: ${key}`);
530
- }
531
- }
532
- }
533
- catch (error) {
534
- console.error('[SignalTree] Failed to load state:', error);
535
- throw error;
536
- }
537
- };
538
- enhanced.clear = async () => {
539
- try {
540
- await Promise.resolve(storageAdapter.removeItem(key));
541
- lastCacheKey = null;
542
- if (tree.__config?.debugMode) {
543
- console.log(`[SignalTree] State cleared from storage key: ${key}`);
544
- }
545
- }
546
- catch (error) {
547
- console.error('[SignalTree] Failed to clear state:', error);
548
- throw error;
549
- }
550
- };
551
- if (autoLoad) {
552
- setTimeout(() => {
553
- enhanced.load().catch((error) => {
554
- console.warn('[SignalTree] Auto-load failed:', error);
555
- });
556
- }, 0);
545
+ if (autoSave) {
546
+ let saveTimeout;
547
+ const triggerAutoSave = () => {
548
+ if (saveTimeout) {
549
+ clearTimeout(saveTimeout);
557
550
  }
558
- if (autoSave) {
559
- let saveTimeout;
560
- const triggerAutoSave = () => {
561
- if (saveTimeout) {
562
- clearTimeout(saveTimeout);
563
- }
564
- saveTimeout = setTimeout(() => {
565
- enhanced.save().catch((error) => {
566
- console.error('[SignalTree] Auto-save failed:', error);
567
- });
568
- }, debounceMs);
569
- };
570
- const watchSignals = (obj, path = '') => {
571
- if (!obj || typeof obj !== 'object')
572
- return;
573
- for (const [key, value] of Object.entries(obj)) {
574
- if (isSignal(value)) {
575
- const signal = value;
576
- let previousValue = signal();
577
- const checkForChanges = () => {
578
- const currentValue = signal();
579
- if (currentValue !== previousValue) {
580
- previousValue = currentValue;
581
- triggerAutoSave();
582
- }
583
- setTimeout(checkForChanges, 50);
584
- };
585
- setTimeout(checkForChanges, 0);
586
- }
587
- else if (value && typeof value === 'object') {
588
- watchSignals(value, path ? `${path}.${key}` : key);
589
- }
590
- }
591
- };
592
- watchSignals(tree.state);
593
- enhanced.__flushAutoSave = () => {
594
- if (saveTimeout) {
595
- clearTimeout(saveTimeout);
596
- saveTimeout = undefined;
597
- return enhanced.save();
598
- }
599
- return Promise.resolve();
551
+ saveTimeout = setTimeout(() => {
552
+ enhanced.save().catch(error => {
553
+ console.error('[SignalTree] Auto-save failed:', error);
554
+ });
555
+ }, debounceMs);
556
+ };
557
+ const watchSignals = (obj, path = '') => {
558
+ if (!obj || typeof obj !== 'object') return;
559
+ for (const [key, value] of Object.entries(obj)) {
560
+ if (isSignal(value)) {
561
+ const signal = value;
562
+ let previousValue = signal();
563
+ const checkForChanges = () => {
564
+ const currentValue = signal();
565
+ if (currentValue !== previousValue) {
566
+ previousValue = currentValue;
567
+ triggerAutoSave();
568
+ }
569
+ setTimeout(checkForChanges, 50);
600
570
  };
571
+ setTimeout(checkForChanges, 0);
572
+ } else if (value && typeof value === 'object') {
573
+ watchSignals(value, path ? `${path}.${key}` : key);
574
+ }
575
+ }
576
+ };
577
+ watchSignals(tree.state);
578
+ enhanced.__flushAutoSave = () => {
579
+ if (saveTimeout) {
580
+ clearTimeout(saveTimeout);
581
+ saveTimeout = undefined;
582
+ return enhanced.save();
601
583
  }
602
- return enhanced;
584
+ return Promise.resolve();
585
+ };
603
586
  }
604
- enhancer.metadata = { name: 'persistence' };
605
- return enhancer;
587
+ return enhanced;
588
+ }
589
+ enhancer.metadata = {
590
+ name: 'persistence'
591
+ };
592
+ return enhancer;
606
593
  }
607
- export function createStorageAdapter(getItem, setItem, removeItem) {
608
- return { getItem, setItem, removeItem };
594
+ function createStorageAdapter(getItem, setItem, removeItem) {
595
+ return {
596
+ getItem,
597
+ setItem,
598
+ removeItem
599
+ };
609
600
  }
610
- export function createIndexedDBAdapter(dbName = 'SignalTreeDB', storeName = 'states') {
611
- let db = null;
612
- const openDB = async () => {
613
- if (db)
614
- return db;
615
- return new Promise((resolve, reject) => {
616
- const request = indexedDB.open(dbName, 1);
617
- request.onerror = () => reject(request.error);
618
- request.onsuccess = () => {
619
- db = request.result;
620
- resolve(db);
621
- };
622
- request.onupgradeneeded = (event) => {
623
- const database = event.target.result;
624
- if (!database.objectStoreNames.contains(storeName)) {
625
- database.createObjectStore(storeName);
626
- }
627
- };
628
- });
629
- };
630
- return {
631
- async getItem(key) {
632
- const database = await openDB();
633
- return new Promise((resolve, reject) => {
634
- const transaction = database.transaction([storeName], 'readonly');
635
- const store = transaction.objectStore(storeName);
636
- const request = store.get(key);
637
- request.onerror = () => reject(request.error);
638
- request.onsuccess = () => resolve(request.result || null);
639
- });
640
- },
641
- async setItem(key, value) {
642
- const database = await openDB();
643
- return new Promise((resolve, reject) => {
644
- const transaction = database.transaction([storeName], 'readwrite');
645
- const store = transaction.objectStore(storeName);
646
- const request = store.put(value, key);
647
- request.onerror = () => reject(request.error);
648
- request.onsuccess = () => resolve();
649
- });
650
- },
651
- async removeItem(key) {
652
- const database = await openDB();
653
- return new Promise((resolve, reject) => {
654
- const transaction = database.transaction([storeName], 'readwrite');
655
- const store = transaction.objectStore(storeName);
656
- const request = store.delete(key);
657
- request.onerror = () => reject(request.error);
658
- request.onsuccess = () => resolve();
659
- });
660
- },
661
- };
601
+ function createIndexedDBAdapter(dbName = 'SignalTreeDB', storeName = 'states') {
602
+ let db = null;
603
+ const openDB = async () => {
604
+ if (db) return db;
605
+ return new Promise((resolve, reject) => {
606
+ const request = indexedDB.open(dbName, 1);
607
+ request.onerror = () => reject(request.error);
608
+ request.onsuccess = () => {
609
+ db = request.result;
610
+ resolve(db);
611
+ };
612
+ request.onupgradeneeded = event => {
613
+ const database = event.target.result;
614
+ if (!database.objectStoreNames.contains(storeName)) {
615
+ database.createObjectStore(storeName);
616
+ }
617
+ };
618
+ });
619
+ };
620
+ return {
621
+ async getItem(key) {
622
+ const database = await openDB();
623
+ return new Promise((resolve, reject) => {
624
+ const transaction = database.transaction([storeName], 'readonly');
625
+ const store = transaction.objectStore(storeName);
626
+ const request = store.get(key);
627
+ request.onerror = () => reject(request.error);
628
+ request.onsuccess = () => resolve(request.result || null);
629
+ });
630
+ },
631
+ async setItem(key, value) {
632
+ const database = await openDB();
633
+ return new Promise((resolve, reject) => {
634
+ const transaction = database.transaction([storeName], 'readwrite');
635
+ const store = transaction.objectStore(storeName);
636
+ const request = store.put(value, key);
637
+ request.onerror = () => reject(request.error);
638
+ request.onsuccess = () => resolve();
639
+ });
640
+ },
641
+ async removeItem(key) {
642
+ const database = await openDB();
643
+ return new Promise((resolve, reject) => {
644
+ const transaction = database.transaction([storeName], 'readwrite');
645
+ const store = transaction.objectStore(storeName);
646
+ const request = store.delete(key);
647
+ request.onerror = () => reject(request.error);
648
+ request.onsuccess = () => resolve();
649
+ });
650
+ }
651
+ };
662
652
  }
663
- export function applySerialization(tree) {
664
- return withSerialization()(tree);
653
+ function applySerialization(tree) {
654
+ return withSerialization()(tree);
665
655
  }
666
- export function applyPersistence(tree, cfg) {
667
- return withPersistence(cfg)(tree);
656
+ function applyPersistence(tree, cfg) {
657
+ return withPersistence(cfg)(tree);
668
658
  }
659
+
660
+ export { applyPersistence, applySerialization, createIndexedDBAdapter, createStorageAdapter, enableSerialization, withPersistence, withSerialization };