@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,379 @@
1
+ import { isNodeAccessor } from '../../../lib/utils.js';
2
+ import { LRUCache } from '../../../lru-cache.js';
3
+ import { deepEqual } from '../../../deep-equal.js';
4
+
5
+ const MAX_CACHE_SIZE = 1000;
6
+ const DEFAULT_TTL = 5 * 60 * 1000;
7
+ const memoizationCache = new Map();
8
+ function createMemoCacheStore(maxSize, enableLRU) {
9
+ if (enableLRU) {
10
+ const cache = new LRUCache(maxSize);
11
+ const shadow = new Map();
12
+ const pruneShadow = () => {
13
+ while (shadow.size > cache.size()) {
14
+ const oldestKey = shadow.keys().next().value;
15
+ if (oldestKey === undefined) {
16
+ break;
17
+ }
18
+ shadow.delete(oldestKey);
19
+ }
20
+ };
21
+ return {
22
+ get: key => {
23
+ const value = cache.get(key);
24
+ if (value !== undefined) {
25
+ shadow.set(key, value);
26
+ } else if (shadow.has(key)) {
27
+ shadow.delete(key);
28
+ }
29
+ return value;
30
+ },
31
+ set: (key, value) => {
32
+ cache.set(key, value);
33
+ shadow.set(key, value);
34
+ pruneShadow();
35
+ },
36
+ delete: key => {
37
+ cache.delete(key);
38
+ shadow.delete(key);
39
+ },
40
+ clear: () => {
41
+ cache.clear();
42
+ shadow.clear();
43
+ },
44
+ size: () => shadow.size,
45
+ forEach: callback => {
46
+ shadow.forEach((value, key) => callback(value, key));
47
+ },
48
+ keys: () => shadow.keys()
49
+ };
50
+ }
51
+ const store = new Map();
52
+ return {
53
+ get: key => store.get(key),
54
+ set: (key, value) => {
55
+ store.set(key, value);
56
+ },
57
+ delete: key => store.delete(key),
58
+ clear: () => store.clear(),
59
+ size: () => store.size,
60
+ forEach: callback => {
61
+ store.forEach((value, key) => callback(value, key));
62
+ },
63
+ keys: () => store.keys()
64
+ };
65
+ }
66
+ function getCleanupInterval(tree) {
67
+ return tree._memoCleanupInterval;
68
+ }
69
+ function setCleanupInterval(tree, interval) {
70
+ if (interval === undefined) {
71
+ delete tree._memoCleanupInterval;
72
+ return;
73
+ }
74
+ tree._memoCleanupInterval = interval;
75
+ }
76
+ function clearCleanupInterval(tree) {
77
+ const interval = getCleanupInterval(tree);
78
+ if (!interval) {
79
+ return;
80
+ }
81
+ try {
82
+ clearInterval(interval);
83
+ } catch {}
84
+ setCleanupInterval(tree);
85
+ }
86
+ function resetMemoizationCaches() {
87
+ memoizationCache.forEach((cache, tree) => {
88
+ cache.clear();
89
+ clearCleanupInterval(tree);
90
+ });
91
+ memoizationCache.clear();
92
+ }
93
+ function cleanupMemoizationCache() {
94
+ resetMemoizationCaches();
95
+ }
96
+ function shallowEqual(a, b) {
97
+ if (a === b) return true;
98
+ if (a == null || b == null) return false;
99
+ if (typeof a !== typeof b) return false;
100
+ if (typeof a === 'object' && typeof b === 'object') {
101
+ const objA = a;
102
+ const objB = b;
103
+ let countA = 0;
104
+ for (const key in objA) {
105
+ if (!Object.prototype.hasOwnProperty.call(objA, key)) continue;
106
+ countA++;
107
+ if (!(key in objB) || objA[key] !== objB[key]) return false;
108
+ }
109
+ let countB = 0;
110
+ for (const key in objB) {
111
+ if (Object.prototype.hasOwnProperty.call(objB, key)) countB++;
112
+ }
113
+ return countA === countB;
114
+ }
115
+ return false;
116
+ }
117
+ function generateCacheKey(fn, args) {
118
+ try {
119
+ return `${fn.name || 'anonymous'}_${JSON.stringify(args)}`;
120
+ } catch {
121
+ return `${fn.name || 'anonymous'}_${args.length}`;
122
+ }
123
+ }
124
+ function getEqualityFn(strategy) {
125
+ switch (strategy) {
126
+ case 'shallow':
127
+ return shallowEqual;
128
+ case 'reference':
129
+ return (a, b) => a === b;
130
+ case 'deep':
131
+ default:
132
+ return deepEqual;
133
+ }
134
+ }
135
+ function memoize(fn, keyFn, config = {}) {
136
+ const maxSize = config.maxCacheSize ?? MAX_CACHE_SIZE;
137
+ const ttl = config.ttl ?? DEFAULT_TTL;
138
+ const equality = getEqualityFn(config.equality ?? 'shallow');
139
+ const enableLRU = config.enableLRU ?? false;
140
+ const cache = createMemoCacheStore(maxSize, enableLRU);
141
+ const cleanExpiredEntries = () => {
142
+ if (!ttl) return;
143
+ const now = Date.now();
144
+ cache.forEach((entry, key) => {
145
+ if (entry.timestamp && now - entry.timestamp > ttl) {
146
+ cache.delete(key);
147
+ }
148
+ });
149
+ };
150
+ return (...args) => {
151
+ if (ttl && Math.random() < 0.01) {
152
+ cleanExpiredEntries();
153
+ }
154
+ const key = keyFn ? keyFn(...args) : generateCacheKey(fn, args);
155
+ const cached = cache.get(key);
156
+ if (cached && (keyFn || equality(cached.deps, args))) {
157
+ if (enableLRU) {
158
+ cached.hitCount += 1;
159
+ }
160
+ return cached.value;
161
+ }
162
+ const result = fn(...args);
163
+ cache.set(key, {
164
+ value: result,
165
+ deps: args,
166
+ timestamp: Date.now(),
167
+ hitCount: 1
168
+ });
169
+ return result;
170
+ };
171
+ }
172
+ function memoizeShallow(fn, keyFn) {
173
+ return memoize(fn, keyFn, {
174
+ equality: 'shallow',
175
+ enableLRU: false,
176
+ ttl: undefined,
177
+ maxCacheSize: 100
178
+ });
179
+ }
180
+ function memoizeReference(fn, keyFn) {
181
+ return memoize(fn, keyFn, {
182
+ equality: 'reference',
183
+ enableLRU: false,
184
+ ttl: undefined,
185
+ maxCacheSize: 50
186
+ });
187
+ }
188
+ const MEMOIZATION_PRESETS = {
189
+ selector: {
190
+ equality: 'reference',
191
+ maxCacheSize: 10,
192
+ enableLRU: false,
193
+ ttl: undefined
194
+ },
195
+ computed: {
196
+ equality: 'shallow',
197
+ maxCacheSize: 100,
198
+ enableLRU: false,
199
+ ttl: undefined
200
+ },
201
+ deepState: {
202
+ equality: 'deep',
203
+ maxCacheSize: 1000,
204
+ enableLRU: true,
205
+ ttl: 5 * 60 * 1000
206
+ },
207
+ highFrequency: {
208
+ equality: 'reference',
209
+ maxCacheSize: 5,
210
+ enableLRU: false,
211
+ ttl: undefined
212
+ }
213
+ };
214
+ function withSelectorMemoization() {
215
+ return withMemoization(MEMOIZATION_PRESETS.selector);
216
+ }
217
+ function withComputedMemoization() {
218
+ return withMemoization(MEMOIZATION_PRESETS.computed);
219
+ }
220
+ function withDeepStateMemoization() {
221
+ return withMemoization(MEMOIZATION_PRESETS.deepState);
222
+ }
223
+ function withHighFrequencyMemoization() {
224
+ return withMemoization(MEMOIZATION_PRESETS.highFrequency);
225
+ }
226
+ function withMemoization(config = {}) {
227
+ const {
228
+ enabled = true,
229
+ maxCacheSize = 1000,
230
+ ttl,
231
+ equality = 'deep',
232
+ enableLRU = true
233
+ } = config;
234
+ return tree => {
235
+ const originalTreeCall = tree.bind(tree);
236
+ const applyUpdateResult = result => {
237
+ Object.entries(result).forEach(([propKey, value]) => {
238
+ const property = tree.state[propKey];
239
+ if (property && 'set' in property) {
240
+ property.set(value);
241
+ } else if (isNodeAccessor(property)) {
242
+ property(value);
243
+ }
244
+ });
245
+ };
246
+ if (!enabled) {
247
+ const memoTree = tree;
248
+ memoTree.memoizedUpdate = updater => {
249
+ const currentState = originalTreeCall();
250
+ const result = updater(currentState);
251
+ applyUpdateResult(result);
252
+ };
253
+ memoTree.clearMemoCache = () => {};
254
+ memoTree.getCacheStats = () => ({
255
+ size: 0,
256
+ hitRate: 0,
257
+ totalHits: 0,
258
+ totalMisses: 0,
259
+ keys: []
260
+ });
261
+ return memoTree;
262
+ }
263
+ const cache = createMemoCacheStore(maxCacheSize, enableLRU);
264
+ memoizationCache.set(tree, cache);
265
+ const equalityFn = getEqualityFn(equality);
266
+ tree.memoizedUpdate = (updater, cacheKey) => {
267
+ const currentState = originalTreeCall();
268
+ const key = cacheKey || generateCacheKey(updater, [currentState]);
269
+ const cached = cache.get(key);
270
+ if (cached && equalityFn(cached.deps, [currentState])) {
271
+ const cachedUpdate = cached.value;
272
+ applyUpdateResult(cachedUpdate);
273
+ return;
274
+ }
275
+ const result = updater(currentState);
276
+ cache.set(key, {
277
+ value: result,
278
+ deps: [currentState],
279
+ timestamp: Date.now(),
280
+ hitCount: 1
281
+ });
282
+ applyUpdateResult(result);
283
+ };
284
+ tree.clearMemoCache = key => {
285
+ if (key) {
286
+ cache.delete(key);
287
+ } else {
288
+ cache.clear();
289
+ }
290
+ };
291
+ tree.getCacheStats = () => {
292
+ let totalHits = 0;
293
+ let totalMisses = 0;
294
+ cache.forEach(entry => {
295
+ totalHits += entry.hitCount || 0;
296
+ totalMisses += Math.floor((entry.hitCount || 0) / 2);
297
+ });
298
+ const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
299
+ return {
300
+ size: cache.size(),
301
+ hitRate,
302
+ totalHits,
303
+ totalMisses,
304
+ keys: Array.from(cache.keys())
305
+ };
306
+ };
307
+ const maybeInterval = getCleanupInterval(tree);
308
+ if (maybeInterval) {
309
+ const origClear = tree.clearMemoCache.bind(tree);
310
+ tree.clearMemoCache = key => {
311
+ origClear(key);
312
+ clearCleanupInterval(tree);
313
+ };
314
+ }
315
+ if (ttl) {
316
+ const cleanup = () => {
317
+ const now = Date.now();
318
+ cache.forEach((entry, key) => {
319
+ if (entry.timestamp && now - entry.timestamp > ttl) {
320
+ cache.delete(key);
321
+ }
322
+ });
323
+ };
324
+ const intervalId = setInterval(cleanup, ttl);
325
+ setCleanupInterval(tree, intervalId);
326
+ }
327
+ return tree;
328
+ };
329
+ }
330
+ function withHighPerformanceMemoization() {
331
+ return withMemoization({
332
+ enabled: true,
333
+ maxCacheSize: 10000,
334
+ ttl: 300000,
335
+ equality: 'shallow',
336
+ enableLRU: true
337
+ });
338
+ }
339
+ function withLightweightMemoization() {
340
+ return withMemoization({
341
+ enabled: true,
342
+ maxCacheSize: 100,
343
+ ttl: undefined,
344
+ equality: 'reference',
345
+ enableLRU: false
346
+ });
347
+ }
348
+ function withShallowMemoization() {
349
+ return withMemoization({
350
+ enabled: true,
351
+ maxCacheSize: 1000,
352
+ ttl: 60000,
353
+ equality: 'shallow',
354
+ enableLRU: true
355
+ });
356
+ }
357
+ function clearAllCaches() {
358
+ resetMemoizationCaches();
359
+ }
360
+ function getGlobalCacheStats() {
361
+ let totalSize = 0;
362
+ let totalHits = 0;
363
+ let treeCount = 0;
364
+ memoizationCache.forEach(cache => {
365
+ treeCount++;
366
+ totalSize += cache.size();
367
+ cache.forEach(entry => {
368
+ totalHits += entry.hitCount || 0;
369
+ });
370
+ });
371
+ return {
372
+ treeCount,
373
+ totalSize,
374
+ totalHits,
375
+ averageCacheSize: treeCount > 0 ? totalSize / treeCount : 0
376
+ };
377
+ }
378
+
379
+ export { MEMOIZATION_PRESETS, cleanupMemoizationCache, clearAllCaches, getGlobalCacheStats, memoize, memoizeReference, memoizeShallow, withComputedMemoization, withDeepStateMemoization, withHighFrequencyMemoization, withHighPerformanceMemoization, withLightweightMemoization, withMemoization, withSelectorMemoization, withShallowMemoization };
@@ -0,0 +1,77 @@
1
+ import { signal } from '@angular/core';
2
+
3
+ function createAsyncOperation(name, operation) {
4
+ return async tree => {
5
+ if (typeof tree.batchUpdate === 'function') {
6
+ const pendingPatch = {
7
+ [`${name}_PENDING`]: true
8
+ };
9
+ tree.batchUpdate(() => pendingPatch);
10
+ } else if ('$' in tree) {
11
+ try {
12
+ tree['$'][`${name}_PENDING`]?.set?.(true);
13
+ } catch (e) {
14
+ }
15
+ }
16
+ try {
17
+ const result = await operation();
18
+ if (typeof tree.batchUpdate === 'function') {
19
+ const resultPatch = {
20
+ [`${name}_RESULT`]: result,
21
+ [`${name}_PENDING`]: false
22
+ };
23
+ tree.batchUpdate(() => resultPatch);
24
+ } else if ('$' in tree) {
25
+ try {
26
+ tree['$'][`${name}_RESULT`]?.set?.(result);
27
+ tree['$'][`${name}_PENDING`]?.set?.(false);
28
+ } catch (e) {
29
+ void e;
30
+ }
31
+ }
32
+ return result;
33
+ } catch (error) {
34
+ if (typeof tree.batchUpdate === 'function') {
35
+ const errorPatch = {
36
+ [`${name}_ERROR`]: error,
37
+ [`${name}_PENDING`]: false
38
+ };
39
+ tree.batchUpdate(() => errorPatch);
40
+ } else if ('$' in tree) {
41
+ try {
42
+ tree['$'][`${name}_ERROR`]?.set?.(error);
43
+ tree['$'][`${name}_PENDING`]?.set?.(false);
44
+ } catch (e) {
45
+ }
46
+ }
47
+ throw error;
48
+ }
49
+ };
50
+ }
51
+ function trackAsync(operation) {
52
+ const pending = signal(false);
53
+ const error = signal(null);
54
+ const result = signal(null);
55
+ return {
56
+ pending: pending.asReadonly(),
57
+ error: error.asReadonly(),
58
+ result: result.asReadonly(),
59
+ execute: async () => {
60
+ pending.set(true);
61
+ error.set(null);
62
+ try {
63
+ const res = await operation();
64
+ result.set(res);
65
+ return res;
66
+ } catch (e) {
67
+ const err = e instanceof Error ? e : new Error(String(e));
68
+ error.set(err);
69
+ throw err;
70
+ } finally {
71
+ pending.set(false);
72
+ }
73
+ }
74
+ };
75
+ }
76
+
77
+ export { createAsyncOperation, trackAsync };
@@ -0,0 +1,136 @@
1
+ const middlewareMap = new WeakMap();
2
+ function withMiddleware(middlewares = []) {
3
+ return tree => {
4
+ middlewareMap.set(tree, [...middlewares]);
5
+ const originalTreeCall = tree.bind(tree);
6
+ const enhancedTree = function (...args) {
7
+ if (args.length === 0) {
8
+ return originalTreeCall();
9
+ } else {
10
+ const action = 'UPDATE';
11
+ const currentState = originalTreeCall();
12
+ const treeMiddlewares = middlewareMap.get(tree) || [];
13
+ let updateResult;
14
+ if (args.length === 1) {
15
+ const arg = args[0];
16
+ if (typeof arg === 'function') {
17
+ updateResult = arg(currentState);
18
+ } else {
19
+ updateResult = arg;
20
+ }
21
+ } else {
22
+ return;
23
+ }
24
+ for (const middleware of treeMiddlewares) {
25
+ if (middleware.before && !middleware.before(action, updateResult, currentState)) {
26
+ return;
27
+ }
28
+ }
29
+ const previousState = currentState;
30
+ if (args.length === 1) {
31
+ const arg = args[0];
32
+ if (typeof arg === 'function') {
33
+ originalTreeCall(arg);
34
+ } else {
35
+ originalTreeCall(arg);
36
+ }
37
+ }
38
+ const newState = originalTreeCall();
39
+ for (const middleware of treeMiddlewares) {
40
+ if (middleware.after) {
41
+ middleware.after(action, updateResult, previousState, newState);
42
+ }
43
+ }
44
+ }
45
+ };
46
+ Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
47
+ Object.assign(enhancedTree, tree);
48
+ if ('state' in tree) {
49
+ Object.defineProperty(enhancedTree, 'state', {
50
+ value: tree.state,
51
+ enumerable: false,
52
+ configurable: true
53
+ });
54
+ }
55
+ if ('$' in tree) {
56
+ Object.defineProperty(enhancedTree, '$', {
57
+ value: tree['$'],
58
+ enumerable: false,
59
+ configurable: true
60
+ });
61
+ }
62
+ enhancedTree.addTap = middleware => {
63
+ const treeMiddlewares = middlewareMap.get(tree) || [];
64
+ const existingIndex = treeMiddlewares.findIndex(m => m.id === middleware.id);
65
+ if (existingIndex >= 0) {
66
+ treeMiddlewares[existingIndex] = middleware;
67
+ } else {
68
+ treeMiddlewares.push(middleware);
69
+ }
70
+ middlewareMap.set(tree, treeMiddlewares);
71
+ };
72
+ enhancedTree.removeTap = id => {
73
+ const treeMiddlewares = middlewareMap.get(tree) || [];
74
+ const filtered = treeMiddlewares.filter(m => m.id !== id);
75
+ middlewareMap.set(tree, filtered);
76
+ };
77
+ const originalBatchUpdate = tree.batchUpdate;
78
+ if (originalBatchUpdate) {
79
+ enhancedTree.batchUpdate = updater => {
80
+ const action = 'BATCH_UPDATE';
81
+ const currentState = originalTreeCall();
82
+ const updateResult = updater(currentState);
83
+ const treeMiddlewares = middlewareMap.get(tree) || [];
84
+ for (const middleware of treeMiddlewares) {
85
+ if (middleware.before && !middleware.before(action, updateResult, currentState)) {
86
+ return;
87
+ }
88
+ }
89
+ const previousState = currentState;
90
+ originalBatchUpdate.call(tree, updater);
91
+ const newState = originalTreeCall();
92
+ for (const middleware of treeMiddlewares) {
93
+ if (middleware.after) {
94
+ middleware.after(action, updateResult, previousState, newState);
95
+ }
96
+ }
97
+ };
98
+ }
99
+ const originalDestroy = tree.destroy;
100
+ enhancedTree.destroy = () => {
101
+ middlewareMap.delete(tree);
102
+ if (originalDestroy) {
103
+ originalDestroy.call(tree);
104
+ }
105
+ };
106
+ return enhancedTree;
107
+ };
108
+ }
109
+ function createLoggingMiddleware(treeName) {
110
+ return {
111
+ id: 'logging',
112
+ before: (action, payload, state) => {
113
+ console.group(`🏪 ${treeName}: ${action}`);
114
+ console.log('Previous state:', state);
115
+ console.log('Payload:', typeof payload === 'function' ? 'Function' : payload);
116
+ return true;
117
+ },
118
+ after: (action, payload, state, newState) => {
119
+ console.log('New state:', newState);
120
+ console.groupEnd();
121
+ }
122
+ };
123
+ }
124
+ function createValidationMiddleware(validator) {
125
+ return {
126
+ id: 'validation',
127
+ after: (action, payload, state, newState) => {
128
+ const error = validator(newState);
129
+ if (error) {
130
+ console.error(`Validation failed after ${action}:`, error);
131
+ }
132
+ }
133
+ };
134
+ }
135
+
136
+ export { createLoggingMiddleware, createValidationMiddleware, withMiddleware };
@@ -0,0 +1,88 @@
1
+ import { composeEnhancers } from '../../../lib/utils.js';
2
+ import { withHighPerformanceBatching, withBatching } from '../../batching/lib/batching.js';
3
+ import { withDevTools } from '../../devtools/lib/devtools.js';
4
+ import { withHighPerformanceMemoization, withMemoization } from '../../memoization/lib/memoization.js';
5
+ import { withMiddleware } from '../../middleware/lib/middleware.js';
6
+ import { withTimeTravel } from '../../time-travel/lib/time-travel.js';
7
+
8
+ const TREE_PRESETS = {
9
+ basic: {
10
+ batchUpdates: false,
11
+ useMemoization: false,
12
+ trackPerformance: false,
13
+ enableTimeTravel: false,
14
+ enableDevTools: false,
15
+ debugMode: false
16
+ },
17
+ performance: {
18
+ batchUpdates: true,
19
+ useMemoization: true,
20
+ trackPerformance: false,
21
+ enableTimeTravel: false,
22
+ enableDevTools: false,
23
+ debugMode: false,
24
+ useShallowComparison: true,
25
+ maxCacheSize: 200
26
+ },
27
+ development: {
28
+ batchUpdates: true,
29
+ useMemoization: true,
30
+ trackPerformance: true,
31
+ enableTimeTravel: true,
32
+ enableDevTools: true,
33
+ debugMode: true,
34
+ maxCacheSize: 100
35
+ },
36
+ production: {
37
+ batchUpdates: true,
38
+ useMemoization: true,
39
+ trackPerformance: false,
40
+ enableTimeTravel: false,
41
+ enableDevTools: false,
42
+ debugMode: false,
43
+ useShallowComparison: true,
44
+ maxCacheSize: 200
45
+ }
46
+ };
47
+ function createPresetConfig(preset, overrides = {}) {
48
+ const baseConfig = TREE_PRESETS[preset];
49
+ return {
50
+ ...baseConfig,
51
+ ...overrides
52
+ };
53
+ }
54
+ function validatePreset(preset) {
55
+ if (!TREE_PRESETS[preset]) {
56
+ throw new Error(`Invalid preset: ${preset}. Valid presets are: ${Object.keys(TREE_PRESETS).join(', ')}`);
57
+ }
58
+ return true;
59
+ }
60
+ function getAvailablePresets() {
61
+ return Object.keys(TREE_PRESETS);
62
+ }
63
+ function combinePresets(presets, overrides = {}) {
64
+ let combined = {};
65
+ for (const preset of presets) {
66
+ validatePreset(preset);
67
+ combined = {
68
+ ...combined,
69
+ ...TREE_PRESETS[preset]
70
+ };
71
+ }
72
+ return {
73
+ ...combined,
74
+ ...overrides
75
+ };
76
+ }
77
+ function createDevTree(overrides = {}) {
78
+ const config = createPresetConfig('development', overrides);
79
+ const composed = composeEnhancers(withBatching(), withHighPerformanceBatching(), withMemoization(), withHighPerformanceMemoization(), withMiddleware(), withTimeTravel(), withDevTools({
80
+ treeName: config.treeName ?? 'SignalTree Dev'
81
+ }));
82
+ return {
83
+ config,
84
+ enhancer: composed
85
+ };
86
+ }
87
+
88
+ export { TREE_PRESETS, combinePresets, createDevTree, createPresetConfig, getAvailablePresets, validatePreset };
@@ -0,0 +1,15 @@
1
+ const TYPE_MARKERS = {
2
+ DATE: '§d',
3
+ REGEXP: '§r',
4
+ MAP: '§m',
5
+ SET: '§s',
6
+ UNDEFINED: '§u',
7
+ NAN: '§n',
8
+ INFINITY: '§i',
9
+ NEG_INFINITY: '§-i',
10
+ BIGINT: '§b',
11
+ SYMBOL: '§y',
12
+ CIRCULAR: '§c'
13
+ };
14
+
15
+ export { TYPE_MARKERS };