@signaltree/core 3.0.1 → 3.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 (104) hide show
  1. package/package.json +59 -12
  2. package/src/enhancers/batching/index.d.ts +1 -0
  3. package/src/enhancers/batching/index.js +1 -0
  4. package/src/enhancers/batching/jest.config.d.ts +15 -0
  5. package/src/enhancers/batching/jest.config.js +21 -0
  6. package/src/enhancers/batching/lib/batching.d.ts +16 -0
  7. package/src/enhancers/batching/lib/batching.js +155 -0
  8. package/src/enhancers/batching/test-setup.d.ts +1 -0
  9. package/src/enhancers/batching/test-setup.js +5 -0
  10. package/src/enhancers/computed/index.d.ts +1 -0
  11. package/src/enhancers/computed/index.js +1 -0
  12. package/src/enhancers/computed/jest.config.d.ts +15 -0
  13. package/src/enhancers/computed/jest.config.js +21 -0
  14. package/src/enhancers/computed/lib/computed.d.ts +12 -0
  15. package/src/enhancers/computed/lib/computed.js +18 -0
  16. package/src/enhancers/devtools/index.d.ts +1 -0
  17. package/src/enhancers/devtools/index.js +1 -0
  18. package/src/enhancers/devtools/jest.config.d.ts +15 -0
  19. package/src/enhancers/devtools/jest.config.js +21 -0
  20. package/src/enhancers/devtools/lib/devtools.d.ts +77 -0
  21. package/src/enhancers/devtools/lib/devtools.js +278 -0
  22. package/src/enhancers/devtools/test-setup.d.ts +1 -0
  23. package/src/enhancers/devtools/test-setup.js +5 -0
  24. package/src/enhancers/entities/index.d.ts +1 -0
  25. package/src/enhancers/entities/index.js +1 -0
  26. package/src/enhancers/entities/jest.config.d.ts +15 -0
  27. package/src/enhancers/entities/jest.config.js +21 -0
  28. package/src/enhancers/entities/lib/entities.d.ts +22 -0
  29. package/src/enhancers/entities/lib/entities.js +110 -0
  30. package/src/enhancers/entities/test-setup.d.ts +1 -0
  31. package/src/enhancers/entities/test-setup.js +5 -0
  32. package/src/enhancers/index.d.ts +3 -0
  33. package/src/enhancers/index.js +84 -0
  34. package/src/enhancers/memoization/index.d.ts +1 -0
  35. package/src/enhancers/memoization/index.js +1 -0
  36. package/src/enhancers/memoization/jest.config.d.ts +15 -0
  37. package/src/enhancers/memoization/jest.config.js +21 -0
  38. package/src/enhancers/memoization/lib/memoization.d.ts +65 -0
  39. package/src/enhancers/memoization/lib/memoization.js +391 -0
  40. package/src/enhancers/memoization/test-setup.d.ts +1 -0
  41. package/src/enhancers/memoization/test-setup.js +5 -0
  42. package/src/enhancers/middleware/index.d.ts +2 -0
  43. package/src/enhancers/middleware/index.js +2 -0
  44. package/src/enhancers/middleware/jest.config.d.ts +15 -0
  45. package/src/enhancers/middleware/jest.config.js +21 -0
  46. package/src/enhancers/middleware/lib/async-helpers.d.ts +8 -0
  47. package/src/enhancers/middleware/lib/async-helpers.js +85 -0
  48. package/src/enhancers/middleware/lib/middleware.d.ts +11 -0
  49. package/src/enhancers/middleware/lib/middleware.js +179 -0
  50. package/src/enhancers/middleware/test-setup.d.ts +1 -0
  51. package/src/enhancers/middleware/test-setup.js +5 -0
  52. package/src/enhancers/presets/index.d.ts +1 -0
  53. package/src/enhancers/presets/index.js +1 -0
  54. package/src/enhancers/presets/jest.config.d.ts +15 -0
  55. package/src/enhancers/presets/jest.config.js +21 -0
  56. package/src/enhancers/presets/lib/presets.d.ts +11 -0
  57. package/src/enhancers/presets/lib/presets.js +102 -0
  58. package/src/enhancers/presets/test-setup.d.ts +1 -0
  59. package/src/enhancers/presets/test-setup.js +5 -0
  60. package/src/enhancers/serialization/constants.d.ts +14 -0
  61. package/src/enhancers/serialization/constants.js +14 -0
  62. package/src/enhancers/serialization/index.d.ts +2 -0
  63. package/src/enhancers/serialization/index.js +2 -0
  64. package/src/enhancers/serialization/jest.config.d.ts +15 -0
  65. package/src/enhancers/serialization/jest.config.js +21 -0
  66. package/src/enhancers/serialization/lib/serialization.d.ts +59 -0
  67. package/src/enhancers/serialization/lib/serialization.js +668 -0
  68. package/src/enhancers/serialization/test-setup.d.ts +1 -0
  69. package/src/enhancers/serialization/test-setup.js +5 -0
  70. package/src/enhancers/time-travel/index.d.ts +1 -0
  71. package/src/enhancers/time-travel/index.js +1 -0
  72. package/src/enhancers/time-travel/jest.config.d.ts +15 -0
  73. package/src/enhancers/time-travel/jest.config.js +21 -0
  74. package/src/enhancers/time-travel/lib/time-travel.d.ts +36 -0
  75. package/src/enhancers/time-travel/lib/time-travel.js +192 -0
  76. package/src/enhancers/time-travel/lib/utils.d.ts +1 -0
  77. package/src/enhancers/time-travel/lib/utils.js +1 -0
  78. package/src/enhancers/time-travel/test-setup.d.ts +1 -0
  79. package/src/enhancers/time-travel/test-setup.js +5 -0
  80. package/src/enhancers/types.d.ts +105 -0
  81. package/src/enhancers/types.js +0 -0
  82. package/src/index.d.ts +16 -0
  83. package/src/index.js +15 -0
  84. package/src/lib/constants.d.ts +42 -0
  85. package/src/lib/constants.js +61 -0
  86. package/src/lib/memory/memory-manager.d.ts +30 -0
  87. package/src/lib/memory/memory-manager.js +166 -0
  88. package/src/lib/performance/diff-engine.d.ts +33 -0
  89. package/src/lib/performance/diff-engine.js +156 -0
  90. package/src/lib/performance/path-index.d.ts +25 -0
  91. package/src/lib/performance/path-index.js +154 -0
  92. package/src/lib/performance/update-engine.d.ts +32 -0
  93. package/src/lib/performance/update-engine.js +193 -0
  94. package/src/lib/security/security-validator.d.ts +33 -0
  95. package/src/lib/security/security-validator.js +139 -0
  96. package/src/lib/signal-tree.d.ts +8 -0
  97. package/{fesm2022/signaltree-core.mjs → src/lib/signal-tree.js} +100 -573
  98. package/src/lib/types.d.ts +164 -0
  99. package/src/lib/types.js +9 -0
  100. package/src/lib/utils.d.ts +27 -0
  101. package/src/lib/utils.js +286 -0
  102. package/README.md +0 -1455
  103. package/fesm2022/signaltree-core.mjs.map +0 -1
  104. package/index.d.ts +0 -216
@@ -0,0 +1,668 @@
1
+ import { isSignal } from '@angular/core';
2
+ import { TYPE_MARKERS } from '../constants';
3
+ const DEFAULT_CONFIG = {
4
+ includeMetadata: true,
5
+ replacer: undefined,
6
+ reviver: undefined,
7
+ preserveTypes: true,
8
+ maxDepth: 50,
9
+ handleCircular: true,
10
+ };
11
+ 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;
33
+ }
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
+ }
42
+ }
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
+ }
60
+ }
61
+ else {
62
+ if (obj instanceof Date || obj instanceof RegExp)
63
+ return obj;
64
+ }
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;
96
+ }
97
+ catch {
98
+ visited.delete(obj);
99
+ return '[Serialization Error]';
100
+ }
101
+ }
102
+ function detectCircularReferences(obj, path = '', seen = new WeakSet(), paths = new Map()) {
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({ path, targetPath });
110
+ return circular;
111
+ }
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
+ }
121
+ }
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);
127
+ }
128
+ }
129
+ seen.delete(obj);
130
+ return circular;
131
+ }
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);
138
+ }
139
+ if (isSignal(value)) {
140
+ return value();
141
+ }
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);
153
+ }
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;
166
+ }
167
+ let target = obj;
168
+ for (const part of targetParts) {
169
+ target = target[part];
170
+ if (!target)
171
+ break;
172
+ }
173
+ if (current && target) {
174
+ current[pathParts[pathParts.length - 1]] = target;
175
+ }
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;
370
+ }
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 {
415
+ }
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
+ };
423
+ }
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
+ 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
+ }
452
+ };
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
+ };
464
+ };
465
+ enhanced.restore = (snapshot) => {
466
+ const { data, metadata } = snapshot;
467
+ if (metadata?.circularRefs) {
468
+ resolveCircularReferences(data, metadata.circularRefs);
469
+ }
470
+ enhanced.fromJSON(data, metadata);
471
+ };
472
+ return enhanced;
473
+ };
474
+ enhancer.metadata = {
475
+ name: 'serialization',
476
+ };
477
+ return enhancer;
478
+ }
479
+ export function enableSerialization() {
480
+ return withSerialization({
481
+ includeMetadata: true,
482
+ preserveTypes: true,
483
+ handleCircular: true,
484
+ });
485
+ }
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.');
490
+ }
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);
557
+ }
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();
600
+ };
601
+ }
602
+ return enhanced;
603
+ }
604
+ enhancer.metadata = { name: 'persistence' };
605
+ return enhancer;
606
+ }
607
+ export function createStorageAdapter(getItem, setItem, removeItem) {
608
+ return { getItem, setItem, removeItem };
609
+ }
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
+ };
662
+ }
663
+ export function applySerialization(tree) {
664
+ return withSerialization()(tree);
665
+ }
666
+ export function applyPersistence(tree, cfg) {
667
+ return withPersistence(cfg)(tree);
668
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
2
+ setupZoneTestEnv({
3
+ errorOnUnknownElements: true,
4
+ errorOnUnknownProperties: true,
5
+ });
@@ -0,0 +1 @@
1
+ export * from './lib/time-travel';
@@ -0,0 +1 @@
1
+ export * from './lib/time-travel';
@@ -0,0 +1,15 @@
1
+ declare const _default: {
2
+ displayName: string;
3
+ preset: string;
4
+ setupFilesAfterEnv: string[];
5
+ coverageDirectory: string;
6
+ transform: {
7
+ '^.+\\.(ts|mjs|js|html)$': (string | {
8
+ tsconfig: string;
9
+ stringifyContentPathRegex: string;
10
+ })[];
11
+ };
12
+ transformIgnorePatterns: string[];
13
+ snapshotSerializers: string[];
14
+ };
15
+ export default _default;