@signaltree/core 8.0.1 → 9.0.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.
- package/dist/edit-session.js +1 -0
- package/dist/enhancers/batching/batching.js +19 -16
- package/dist/enhancers/devtools/devtools.js +15 -4
- package/dist/enhancers/effects/effects.js +9 -1
- package/dist/enhancers/memoization/memoization.js +16 -111
- package/dist/enhancers/serialization/serialization.js +38 -17
- package/dist/enhancers/time-travel/time-travel.js +15 -7
- package/dist/index.js +7 -13
- package/dist/lib/constants.js +2 -1
- package/dist/lib/presets.js +11 -1
- package/dist/lib/signal-tree.js +59 -1
- package/dist/presets.js +2 -0
- package/dist/security.js +1 -0
- package/dist/storage.js +1 -0
- package/package.json +21 -1
- package/src/edit-session.d.ts +1 -0
- package/src/index.d.ts +8 -13
- package/src/lib/types.d.ts +3 -0
- package/src/presets.d.ts +2 -0
- package/src/security.d.ts +1 -0
- package/src/storage.d.ts +2 -0
- package/dist/enhancers/entities/entities.js +0 -7
- package/dist/lib/async-helpers.js +0 -77
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createEditSession } from './lib/edit-session.js';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { copyTreeProperties } from '../utils/copy-tree-properties.js';
|
|
2
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
2
3
|
|
|
3
4
|
function batching(config = {}) {
|
|
4
5
|
const enabled = config.enabled ?? true;
|
|
5
6
|
const notificationDelayMs = config.notificationDelayMs ?? 0;
|
|
6
|
-
|
|
7
|
+
const enhancerFn = tree => {
|
|
7
8
|
if (!enabled) {
|
|
8
9
|
const passthrough = {
|
|
9
10
|
batch: fn => fn(),
|
|
@@ -200,8 +201,24 @@ function batching(config = {}) {
|
|
|
200
201
|
});
|
|
201
202
|
});
|
|
202
203
|
};
|
|
204
|
+
if (typeof tree.registerCleanup === 'function') {
|
|
205
|
+
tree.registerCleanup(() => {
|
|
206
|
+
if (notificationTimeoutId !== undefined) {
|
|
207
|
+
clearTimeout(notificationTimeoutId);
|
|
208
|
+
notificationTimeoutId = undefined;
|
|
209
|
+
}
|
|
210
|
+
coalescedUpdates.clear();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
203
213
|
return enhancedTree;
|
|
204
214
|
};
|
|
215
|
+
const meta = {
|
|
216
|
+
name: 'batching',
|
|
217
|
+
provides: ['batching']
|
|
218
|
+
};
|
|
219
|
+
enhancerFn.metadata = meta;
|
|
220
|
+
enhancerFn[ENHANCER_META] = meta;
|
|
221
|
+
return enhancerFn;
|
|
205
222
|
}
|
|
206
223
|
function highPerformanceBatching() {
|
|
207
224
|
return batching({
|
|
@@ -209,22 +226,8 @@ function highPerformanceBatching() {
|
|
|
209
226
|
notificationDelayMs: 0
|
|
210
227
|
});
|
|
211
228
|
}
|
|
212
|
-
function batchingWithConfig(config = {}) {
|
|
213
|
-
return batching(config);
|
|
214
|
-
}
|
|
215
|
-
function flushBatchedUpdates() {
|
|
216
|
-
console.warn('[SignalTree] flushBatchedUpdates() is deprecated. Use tree.flushNotifications() instead.');
|
|
217
|
-
}
|
|
218
|
-
function hasPendingUpdates() {
|
|
219
|
-
console.warn('[SignalTree] hasPendingUpdates() is deprecated. Use tree.hasPendingNotifications() instead.');
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
function getBatchQueueSize() {
|
|
223
|
-
console.warn('[SignalTree] getBatchQueueSize() is deprecated. Signal writes are now synchronous.');
|
|
224
|
-
return 0;
|
|
225
|
-
}
|
|
226
229
|
Object.assign((config = {}) => batching(config), {
|
|
227
230
|
highPerformance: highPerformanceBatching
|
|
228
231
|
});
|
|
229
232
|
|
|
230
|
-
export { batching,
|
|
233
|
+
export { batching, highPerformanceBatching };
|
|
@@ -2,6 +2,7 @@ import { signal } from '@angular/core';
|
|
|
2
2
|
import { copyTreeProperties } from '../utils/copy-tree-properties.js';
|
|
3
3
|
import { applyState, snapshotState } from '../../lib/utils.js';
|
|
4
4
|
import { getPathNotifier } from '../../lib/path-notifier.js';
|
|
5
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
5
6
|
|
|
6
7
|
function createActivityTracker(options) {
|
|
7
8
|
const modules = new Map();
|
|
@@ -698,7 +699,7 @@ function devTools(config = {}) {
|
|
|
698
699
|
const pathExclude = toArray(excludePaths);
|
|
699
700
|
const sendRateLimitMs = maxSendsPerSecond && maxSendsPerSecond > 0 ? Math.ceil(1000 / maxSendsPerSecond) : rateLimitMs ?? 0;
|
|
700
701
|
const formatPathFn = formatPath ?? defaultFormatPath;
|
|
701
|
-
|
|
702
|
+
const enhancerFn = tree => {
|
|
702
703
|
if (!enabled) {
|
|
703
704
|
const noopMethods = {
|
|
704
705
|
connectDevTools() {},
|
|
@@ -794,7 +795,7 @@ function devTools(config = {}) {
|
|
|
794
795
|
}
|
|
795
796
|
const stateActuallyChanged = lastSerializedJson === undefined || currentSerializedJson !== lastSerializedJson;
|
|
796
797
|
const pendingAllowedPaths = pendingPaths.filter(path => path && isPathAllowed(path));
|
|
797
|
-
if (!stateActuallyChanged && !pendingExplicitAction
|
|
798
|
+
if (!stateActuallyChanged && !pendingExplicitAction) {
|
|
798
799
|
pendingAction = null;
|
|
799
800
|
pendingExplicitAction = false;
|
|
800
801
|
pendingSource = undefined;
|
|
@@ -1160,7 +1161,6 @@ function devTools(config = {}) {
|
|
|
1160
1161
|
};
|
|
1161
1162
|
}
|
|
1162
1163
|
};
|
|
1163
|
-
const shouldFilterByOwnership = Boolean(aggregatedReduxInstance);
|
|
1164
1164
|
const treeTopKeys = new Set();
|
|
1165
1165
|
const refreshTreeTopKeys = () => {
|
|
1166
1166
|
treeTopKeys.clear();
|
|
@@ -1174,7 +1174,6 @@ function devTools(config = {}) {
|
|
|
1174
1174
|
};
|
|
1175
1175
|
refreshTreeTopKeys();
|
|
1176
1176
|
const isPathOwnedByTree = path => {
|
|
1177
|
-
if (!shouldFilterByOwnership) return true;
|
|
1178
1177
|
if (treeTopKeys.size === 0) return true;
|
|
1179
1178
|
const root = path.split('.')[0];
|
|
1180
1179
|
if (treeTopKeys.has(root)) return true;
|
|
@@ -1308,8 +1307,20 @@ function devTools(config = {}) {
|
|
|
1308
1307
|
if (enableBrowserDevTools) {
|
|
1309
1308
|
initBrowserDevTools();
|
|
1310
1309
|
}
|
|
1310
|
+
if (typeof tree.registerCleanup === 'function') {
|
|
1311
|
+
tree.registerCleanup(() => {
|
|
1312
|
+
result.disconnectDevTools();
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1311
1315
|
return result;
|
|
1312
1316
|
};
|
|
1317
|
+
const meta = {
|
|
1318
|
+
name: 'devTools',
|
|
1319
|
+
provides: ['devTools']
|
|
1320
|
+
};
|
|
1321
|
+
enhancerFn.metadata = meta;
|
|
1322
|
+
enhancerFn[ENHANCER_META] = meta;
|
|
1323
|
+
return enhancerFn;
|
|
1313
1324
|
}
|
|
1314
1325
|
function enableDevTools(treeName = 'SignalTree') {
|
|
1315
1326
|
return devTools({
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { effect, untracked } from '@angular/core';
|
|
2
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
2
3
|
|
|
3
4
|
function effects(config = {}) {
|
|
4
5
|
const {
|
|
5
6
|
enabled = true
|
|
6
7
|
} = config;
|
|
7
|
-
|
|
8
|
+
const enhancerFn = tree => {
|
|
8
9
|
const cleanupFns = [];
|
|
9
10
|
const methods = {
|
|
10
11
|
effect(effectFn) {
|
|
@@ -53,6 +54,13 @@ function effects(config = {}) {
|
|
|
53
54
|
};
|
|
54
55
|
return Object.assign(tree, methods);
|
|
55
56
|
};
|
|
57
|
+
const meta = {
|
|
58
|
+
name: 'effects',
|
|
59
|
+
provides: ['effects']
|
|
60
|
+
};
|
|
61
|
+
enhancerFn.metadata = meta;
|
|
62
|
+
enhancerFn[ENHANCER_META] = meta;
|
|
63
|
+
return enhancerFn;
|
|
56
64
|
}
|
|
57
65
|
function enableEffects() {
|
|
58
66
|
return effects({
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { computed } from '@angular/core';
|
|
2
2
|
import { isNodeAccessor } from '../../lib/utils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
4
4
|
import { LRUCache } from '../../shared/lib/lru-cache.js';
|
|
5
|
+
import { deepEqual } from '../../shared/lib/deep-equal.js';
|
|
5
6
|
|
|
6
7
|
function isDevMode() {
|
|
7
8
|
if (typeof __DEV__ !== 'undefined') {
|
|
@@ -10,7 +11,6 @@ function isDevMode() {
|
|
|
10
11
|
return false;
|
|
11
12
|
}
|
|
12
13
|
const MAX_CACHE_SIZE = 1000;
|
|
13
|
-
const DEFAULT_TTL = 5 * 60 * 1000;
|
|
14
14
|
const memoizationCache = new Map();
|
|
15
15
|
function createMemoCacheStore(maxSize, enableLRU) {
|
|
16
16
|
if (enableLRU) {
|
|
@@ -90,13 +90,6 @@ function clearCleanupInterval(tree) {
|
|
|
90
90
|
} catch {}
|
|
91
91
|
setCleanupInterval(tree);
|
|
92
92
|
}
|
|
93
|
-
function resetMemoizationCaches() {
|
|
94
|
-
memoizationCache.forEach((cache, tree) => {
|
|
95
|
-
cache.clear();
|
|
96
|
-
clearCleanupInterval(tree);
|
|
97
|
-
});
|
|
98
|
-
memoizationCache.clear();
|
|
99
|
-
}
|
|
100
93
|
function shallowEqual(a, b) {
|
|
101
94
|
if (a === b) return true;
|
|
102
95
|
if (a == null || b == null) return false;
|
|
@@ -136,59 +129,6 @@ function getEqualityFn(strategy) {
|
|
|
136
129
|
return deepEqual;
|
|
137
130
|
}
|
|
138
131
|
}
|
|
139
|
-
function memoize(fn, keyFn, config = {}) {
|
|
140
|
-
const maxSize = config.maxCacheSize ?? MAX_CACHE_SIZE;
|
|
141
|
-
const ttl = config.ttl ?? DEFAULT_TTL;
|
|
142
|
-
const equality = getEqualityFn(config.equality ?? 'shallow');
|
|
143
|
-
const enableLRU = config.enableLRU ?? false;
|
|
144
|
-
const cache = createMemoCacheStore(maxSize, enableLRU);
|
|
145
|
-
const cleanExpiredEntries = () => {
|
|
146
|
-
if (!ttl) return;
|
|
147
|
-
const now = Date.now();
|
|
148
|
-
cache.forEach((entry, key) => {
|
|
149
|
-
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
150
|
-
cache.delete(key);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
};
|
|
154
|
-
return (...args) => {
|
|
155
|
-
if (ttl && Math.random() < 0.01) {
|
|
156
|
-
cleanExpiredEntries();
|
|
157
|
-
}
|
|
158
|
-
const key = keyFn ? keyFn(...args) : generateCacheKey(fn, args);
|
|
159
|
-
const cached = cache.get(key);
|
|
160
|
-
if (cached && (keyFn || equality(cached.deps, args))) {
|
|
161
|
-
if (enableLRU) {
|
|
162
|
-
cached.hitCount += 1;
|
|
163
|
-
}
|
|
164
|
-
return cached.value;
|
|
165
|
-
}
|
|
166
|
-
const result = fn(...args);
|
|
167
|
-
cache.set(key, {
|
|
168
|
-
value: result,
|
|
169
|
-
deps: args,
|
|
170
|
-
timestamp: Date.now(),
|
|
171
|
-
hitCount: 1
|
|
172
|
-
});
|
|
173
|
-
return result;
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
function memoizeShallow(fn, keyFn) {
|
|
177
|
-
return memoize(fn, keyFn, {
|
|
178
|
-
equality: 'shallow',
|
|
179
|
-
enableLRU: false,
|
|
180
|
-
ttl: undefined,
|
|
181
|
-
maxCacheSize: 100
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
function memoizeReference(fn, keyFn) {
|
|
185
|
-
return memoize(fn, keyFn, {
|
|
186
|
-
equality: 'reference',
|
|
187
|
-
enableLRU: false,
|
|
188
|
-
ttl: undefined,
|
|
189
|
-
maxCacheSize: 50
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
132
|
const MEMOIZATION_PRESETS = {
|
|
193
133
|
selector: {
|
|
194
134
|
equality: 'reference',
|
|
@@ -364,57 +304,22 @@ function memoization(config = {}) {
|
|
|
364
304
|
const intervalId = setInterval(cleanup, ttl);
|
|
365
305
|
setCleanupInterval(tree, intervalId);
|
|
366
306
|
}
|
|
307
|
+
if (typeof tree.registerCleanup === 'function') {
|
|
308
|
+
tree.registerCleanup(() => {
|
|
309
|
+
clearCleanupInterval(tree);
|
|
310
|
+
cache.clear();
|
|
311
|
+
memoizationCache.delete(tree);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
367
314
|
return tree;
|
|
368
315
|
};
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return memoization({
|
|
373
|
-
enabled: true,
|
|
374
|
-
maxCacheSize: 10000,
|
|
375
|
-
ttl: 300000,
|
|
376
|
-
equality: 'shallow',
|
|
377
|
-
enableLRU: true
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
function lightweightMemoization() {
|
|
381
|
-
return memoization({
|
|
382
|
-
enabled: true,
|
|
383
|
-
maxCacheSize: 100,
|
|
384
|
-
ttl: undefined,
|
|
385
|
-
equality: 'reference',
|
|
386
|
-
enableLRU: false
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
function shallowMemoization() {
|
|
390
|
-
return memoization({
|
|
391
|
-
enabled: true,
|
|
392
|
-
maxCacheSize: 1000,
|
|
393
|
-
ttl: 60000,
|
|
394
|
-
equality: 'shallow',
|
|
395
|
-
enableLRU: true
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
function clearAllCaches() {
|
|
399
|
-
resetMemoizationCaches();
|
|
400
|
-
}
|
|
401
|
-
function getGlobalCacheStats() {
|
|
402
|
-
let totalSize = 0;
|
|
403
|
-
let totalHits = 0;
|
|
404
|
-
let treeCount = 0;
|
|
405
|
-
memoizationCache.forEach(cache => {
|
|
406
|
-
treeCount++;
|
|
407
|
-
totalSize += cache.size();
|
|
408
|
-
cache.forEach(entry => {
|
|
409
|
-
totalHits += entry.hitCount || 0;
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
return {
|
|
413
|
-
treeCount,
|
|
414
|
-
totalSize,
|
|
415
|
-
totalHits,
|
|
416
|
-
averageCacheSize: treeCount > 0 ? totalSize / treeCount : 0
|
|
316
|
+
const meta = {
|
|
317
|
+
name: 'memoization',
|
|
318
|
+
provides: ['memoization']
|
|
417
319
|
};
|
|
320
|
+
enhancer.metadata = meta;
|
|
321
|
+
enhancer[ENHANCER_META] = meta;
|
|
322
|
+
return enhancer;
|
|
418
323
|
}
|
|
419
324
|
|
|
420
|
-
export { MEMOIZATION_PRESETS,
|
|
325
|
+
export { MEMOIZATION_PRESETS, computedMemoization, deepStateMemoization, highFrequencyMemoization, memoization, selectorMemoization };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSignal } from '@angular/core';
|
|
2
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
2
3
|
import { TYPE_MARKERS } from './constants.js';
|
|
3
4
|
|
|
4
5
|
const DEFAULT_CONFIG = {
|
|
@@ -181,7 +182,7 @@ function resolveCircularReferences(obj, circularPaths) {
|
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
function serialization(defaultConfig = {}) {
|
|
184
|
-
|
|
185
|
+
const enhancerFn = tree => {
|
|
185
186
|
const enhanced = tree;
|
|
186
187
|
enhanced.toJSON = () => {
|
|
187
188
|
return tree();
|
|
@@ -455,15 +456,15 @@ function serialization(defaultConfig = {}) {
|
|
|
455
456
|
};
|
|
456
457
|
return enhanced;
|
|
457
458
|
};
|
|
459
|
+
const meta = {
|
|
460
|
+
name: 'serialization',
|
|
461
|
+
provides: ['serialization']
|
|
462
|
+
};
|
|
463
|
+
enhancerFn.metadata = meta;
|
|
464
|
+
enhancerFn[ENHANCER_META] = meta;
|
|
465
|
+
return enhancerFn;
|
|
458
466
|
}
|
|
459
467
|
Object.assign((defaultConfig = {}) => serialization(defaultConfig), {});
|
|
460
|
-
function enableSerialization() {
|
|
461
|
-
return serialization({
|
|
462
|
-
includeMetadata: true,
|
|
463
|
-
preserveTypes: true,
|
|
464
|
-
handleCircular: true
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
468
|
function persistence(config) {
|
|
468
469
|
const {
|
|
469
470
|
key,
|
|
@@ -477,7 +478,7 @@ function persistence(config) {
|
|
|
477
478
|
throw new Error('No storage adapter available. Provide a storage adapter in the config.');
|
|
478
479
|
}
|
|
479
480
|
const storageAdapter = storage;
|
|
480
|
-
|
|
481
|
+
const persistenceFn = tree => {
|
|
481
482
|
const serializable = serialization(serializationConfig)(tree);
|
|
482
483
|
const enhanced = serializable;
|
|
483
484
|
let lastCacheKey = null;
|
|
@@ -553,8 +554,9 @@ function persistence(config) {
|
|
|
553
554
|
});
|
|
554
555
|
}, debounceMs);
|
|
555
556
|
};
|
|
557
|
+
let unsubscribeAutoSave = null;
|
|
556
558
|
try {
|
|
557
|
-
tree.subscribe(() => {
|
|
559
|
+
unsubscribeAutoSave = tree.subscribe(() => {
|
|
558
560
|
const currentState = JSON.stringify(tree());
|
|
559
561
|
if (currentState !== previousState) {
|
|
560
562
|
previousState = currentState;
|
|
@@ -575,6 +577,10 @@ function persistence(config) {
|
|
|
575
577
|
}
|
|
576
578
|
enhanced.__flushAutoSave = () => {
|
|
577
579
|
pollingActive = false;
|
|
580
|
+
if (unsubscribeAutoSave) {
|
|
581
|
+
unsubscribeAutoSave();
|
|
582
|
+
unsubscribeAutoSave = null;
|
|
583
|
+
}
|
|
578
584
|
if (saveTimeout) {
|
|
579
585
|
clearTimeout(saveTimeout);
|
|
580
586
|
saveTimeout = undefined;
|
|
@@ -582,9 +588,30 @@ function persistence(config) {
|
|
|
582
588
|
}
|
|
583
589
|
return Promise.resolve();
|
|
584
590
|
};
|
|
591
|
+
if (typeof tree.registerCleanup === 'function') {
|
|
592
|
+
tree.registerCleanup(() => {
|
|
593
|
+
pollingActive = false;
|
|
594
|
+
if (unsubscribeAutoSave) {
|
|
595
|
+
unsubscribeAutoSave();
|
|
596
|
+
unsubscribeAutoSave = null;
|
|
597
|
+
}
|
|
598
|
+
if (saveTimeout) {
|
|
599
|
+
clearTimeout(saveTimeout);
|
|
600
|
+
saveTimeout = undefined;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
585
604
|
}
|
|
586
605
|
return enhanced;
|
|
587
606
|
};
|
|
607
|
+
const meta = {
|
|
608
|
+
name: 'persistence',
|
|
609
|
+
provides: ['persistence', 'serialization'],
|
|
610
|
+
requires: []
|
|
611
|
+
};
|
|
612
|
+
persistenceFn.metadata = meta;
|
|
613
|
+
persistenceFn[ENHANCER_META] = meta;
|
|
614
|
+
return persistenceFn;
|
|
588
615
|
}
|
|
589
616
|
Object.assign(cfg => persistence(cfg), {});
|
|
590
617
|
function createStorageAdapter(getItem, setItem, removeItem) {
|
|
@@ -646,11 +673,5 @@ function createIndexedDBAdapter(dbName = 'SignalTreeDB', storeName = 'states') {
|
|
|
646
673
|
}
|
|
647
674
|
};
|
|
648
675
|
}
|
|
649
|
-
function applySerialization(tree) {
|
|
650
|
-
return serialization()(tree);
|
|
651
|
-
}
|
|
652
|
-
function applyPersistence(tree, cfg) {
|
|
653
|
-
return persistence(cfg)(tree);
|
|
654
|
-
}
|
|
655
676
|
|
|
656
|
-
export {
|
|
677
|
+
export { createIndexedDBAdapter, createStorageAdapter, persistence, serialization };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { snapshotState } from '../../lib/utils.js';
|
|
2
2
|
import { deepEqual, deepClone } from './utils.js';
|
|
3
|
+
import { ENHANCER_META } from '../../lib/types.js';
|
|
3
4
|
|
|
4
5
|
class TimeTravelManager {
|
|
5
6
|
tree;
|
|
@@ -130,7 +131,7 @@ function timeTravel(config = {}) {
|
|
|
130
131
|
const {
|
|
131
132
|
enabled = true
|
|
132
133
|
} = config;
|
|
133
|
-
|
|
134
|
+
const enhancerFn = tree => {
|
|
134
135
|
if (!enabled) {
|
|
135
136
|
const noopMethods = {
|
|
136
137
|
undo() {},
|
|
@@ -255,13 +256,20 @@ function timeTravel(config = {}) {
|
|
|
255
256
|
enhancedTree['canRedo'] = () => timeTravelManager.canRedo();
|
|
256
257
|
enhancedTree['getCurrentIndex'] = () => timeTravelManager.getCurrentIndex();
|
|
257
258
|
enhancedTree['__timeTravel'] = timeTravelManager;
|
|
259
|
+
if (typeof tree.registerCleanup === 'function') {
|
|
260
|
+
tree.registerCleanup(() => {
|
|
261
|
+
timeTravelManager.resetHistory();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
258
264
|
return enhancedTree;
|
|
259
265
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
266
|
+
const meta = {
|
|
267
|
+
name: 'timeTravel',
|
|
268
|
+
provides: ['timeTravel']
|
|
269
|
+
};
|
|
270
|
+
enhancerFn.metadata = meta;
|
|
271
|
+
enhancerFn[ENHANCER_META] = meta;
|
|
272
|
+
return enhancerFn;
|
|
265
273
|
}
|
|
266
274
|
function timeTravelHistory(maxHistorySize) {
|
|
267
275
|
return timeTravel({
|
|
@@ -280,4 +288,4 @@ Object.assign((config = {}) => timeTravel(config), {
|
|
|
280
288
|
history: timeTravelHistory
|
|
281
289
|
});
|
|
282
290
|
|
|
283
|
-
export {
|
|
291
|
+
export { timeTravel, timeTravelHistory };
|
package/dist/index.js
CHANGED
|
@@ -6,22 +6,16 @@ export { LoadingState, isStatusMarker, status } from './lib/markers/status.js';
|
|
|
6
6
|
export { clearStoragePrefix, createStorageKeys, isStoredMarker, stored } from './lib/markers/stored.js';
|
|
7
7
|
export { FORM_MARKER, createFormSignal, form, isFormMarker, validators } from './lib/markers/form.js';
|
|
8
8
|
export { registerMarkerProcessor } from './lib/internals/materialize-markers.js';
|
|
9
|
-
export { composeEnhancers,
|
|
10
|
-
export { createEditSession } from './lib/edit-session.js';
|
|
9
|
+
export { composeEnhancers, isAnySignal, isNodeAccessor, toWritableSignal } from './lib/utils.js';
|
|
11
10
|
export { getPathNotifier } from './lib/path-notifier.js';
|
|
12
|
-
export { SecurityPresets, SecurityValidator } from './lib/security/security-validator.js';
|
|
13
11
|
export { createEnhancer, resolveEnhancerOrder } from './enhancers/index.js';
|
|
14
|
-
export { batching
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export { createAsyncOperation, trackAsync } from './lib/async-helpers.js';
|
|
21
|
-
export { TREE_PRESETS, combinePresets, createPresetConfig, getAvailablePresets, validatePreset } from './enhancers/presets/lib/presets.js';
|
|
22
|
-
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES } from './lib/constants.js';
|
|
12
|
+
export { batching } from './enhancers/batching/batching.js';
|
|
13
|
+
export { memoization } from './enhancers/memoization/memoization.js';
|
|
14
|
+
export { timeTravel } from './enhancers/time-travel/time-travel.js';
|
|
15
|
+
export { persistence, serialization } from './enhancers/serialization/serialization.js';
|
|
16
|
+
export { devTools } from './enhancers/devtools/devtools.js';
|
|
17
|
+
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, isDev } from './lib/constants.js';
|
|
23
18
|
export { entityMap } from './lib/markers/entity-map.js';
|
|
24
19
|
export { deepEqual, deepEqual as equal } from './shared/lib/deep-equal.js';
|
|
25
20
|
export { parsePath } from './shared/lib/parse-path.js';
|
|
26
21
|
export { isBuiltInObject } from './shared/lib/is-built-in-object.js';
|
|
27
|
-
export { createDevTree } from './lib/presets.js';
|
package/dist/lib/constants.js
CHANGED
|
@@ -51,6 +51,7 @@ const PROD_MESSAGES = (() => {
|
|
|
51
51
|
})();
|
|
52
52
|
const _isProdByEnv = Boolean(typeof globalThis === 'object' && globalThis !== null && 'process' in globalThis && typeof globalThis.process === 'object' && 'env' in globalThis.process && globalThis.process.env.NODE_ENV === 'production');
|
|
53
53
|
const _isDev = typeof ngDevMode !== 'undefined' ? Boolean(ngDevMode) : !_isProdByEnv;
|
|
54
|
+
const isDev = _isDev;
|
|
54
55
|
const SIGNAL_TREE_MESSAGES = Object.freeze(_isDev ? DEV_MESSAGES : PROD_MESSAGES);
|
|
55
56
|
|
|
56
|
-
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES };
|
|
57
|
+
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, isDev };
|
package/dist/lib/presets.js
CHANGED
|
@@ -16,5 +16,15 @@ function createDevTree(initialState, config = {}) {
|
|
|
16
16
|
const enhanced = base.with(effects()).with(batching(config.batching)).with(memoization(config.memoization)).with(timeTravel(config.timeTravel)).with(devTools(config.devTools));
|
|
17
17
|
return enhanced;
|
|
18
18
|
}
|
|
19
|
+
function createProdTree(initialState, config = {}) {
|
|
20
|
+
const base = signalTree(initialState, config);
|
|
21
|
+
const enhanced = base.with(effects()).with(batching(config.batching)).with(memoization(config.memoization));
|
|
22
|
+
return enhanced;
|
|
23
|
+
}
|
|
24
|
+
function createMinimalTree(initialState, config = {}) {
|
|
25
|
+
const base = signalTree(initialState, config);
|
|
26
|
+
const enhanced = base.with(effects());
|
|
27
|
+
return enhanced;
|
|
28
|
+
}
|
|
19
29
|
|
|
20
|
-
export { createDevTree };
|
|
30
|
+
export { createDevTree, createMinimalTree, createProdTree };
|
package/dist/lib/signal-tree.js
CHANGED
|
@@ -9,6 +9,7 @@ import { SignalMemoryManager } from './memory/memory-manager.js';
|
|
|
9
9
|
import { getPathNotifier } from './path-notifier.js';
|
|
10
10
|
import { SecurityValidator } from './security/security-validator.js';
|
|
11
11
|
import { createLazySignalTree, unwrap } from './utils.js';
|
|
12
|
+
import { ENHANCER_META } from './types.js';
|
|
12
13
|
import { deepEqual } from '../shared/lib/deep-equal.js';
|
|
13
14
|
import { isBuiltInObject } from '../shared/lib/is-built-in-object.js';
|
|
14
15
|
|
|
@@ -228,6 +229,9 @@ function create(initialState, config) {
|
|
|
228
229
|
}
|
|
229
230
|
};
|
|
230
231
|
tree[NODE_ACCESSOR_SYMBOL] = true;
|
|
232
|
+
const cleanupFns = [];
|
|
233
|
+
const destroyedSig = signal(false);
|
|
234
|
+
const appliedEnhancers = new Set();
|
|
231
235
|
Object.defineProperty(tree, 'state', {
|
|
232
236
|
value: signalState,
|
|
233
237
|
enumerable: false,
|
|
@@ -243,6 +247,20 @@ function create(initialState, config) {
|
|
|
243
247
|
if (typeof enhancer !== 'function') {
|
|
244
248
|
throw new Error('Enhancer must be a function');
|
|
245
249
|
}
|
|
250
|
+
const meta = enhancer[ENHANCER_META] ?? enhancer.metadata;
|
|
251
|
+
if (meta?.name) {
|
|
252
|
+
if (appliedEnhancers.has(meta.name)) {
|
|
253
|
+
throw new Error(`Enhancer "${meta.name}" has already been applied to this tree. ` + `Each enhancer can only be applied once.`);
|
|
254
|
+
}
|
|
255
|
+
if (meta.requires) {
|
|
256
|
+
for (const dep of meta.requires) {
|
|
257
|
+
if (!appliedEnhancers.has(dep)) {
|
|
258
|
+
throw new Error(`Enhancer "${meta.name}" requires "${dep}" to be applied first.`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
appliedEnhancers.add(meta.name);
|
|
263
|
+
}
|
|
246
264
|
return enhancer(tree);
|
|
247
265
|
},
|
|
248
266
|
enumerable: false,
|
|
@@ -259,6 +277,14 @@ function create(initialState, config) {
|
|
|
259
277
|
});
|
|
260
278
|
Object.defineProperty(tree, 'destroy', {
|
|
261
279
|
value: function () {
|
|
280
|
+
if (destroyedSig()) return;
|
|
281
|
+
destroyedSig.set(true);
|
|
282
|
+
for (const fn of cleanupFns) {
|
|
283
|
+
try {
|
|
284
|
+
fn();
|
|
285
|
+
} catch {}
|
|
286
|
+
}
|
|
287
|
+
cleanupFns.length = 0;
|
|
262
288
|
if (memoryManager) {
|
|
263
289
|
memoryManager.dispose();
|
|
264
290
|
}
|
|
@@ -270,6 +296,22 @@ function create(initialState, config) {
|
|
|
270
296
|
writable: true,
|
|
271
297
|
configurable: true
|
|
272
298
|
});
|
|
299
|
+
Object.defineProperty(tree, 'destroyed', {
|
|
300
|
+
value: destroyedSig.asReadonly(),
|
|
301
|
+
enumerable: false,
|
|
302
|
+
writable: false,
|
|
303
|
+
configurable: true
|
|
304
|
+
});
|
|
305
|
+
Object.defineProperty(tree, 'registerCleanup', {
|
|
306
|
+
value: function (fn) {
|
|
307
|
+
if (typeof fn === 'function') {
|
|
308
|
+
cleanupFns.push(fn);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
enumerable: false,
|
|
312
|
+
writable: false,
|
|
313
|
+
configurable: true
|
|
314
|
+
});
|
|
273
315
|
Object.defineProperty(tree, 'clearCache', {
|
|
274
316
|
value: () => {},
|
|
275
317
|
enumerable: false,
|
|
@@ -353,7 +395,7 @@ function createBuilder(baseTree) {
|
|
|
353
395
|
const enhanced = baseTree.with(enhancer);
|
|
354
396
|
const newBuilder = createBuilder(enhanced);
|
|
355
397
|
for (const key of Object.keys(enhanced)) {
|
|
356
|
-
if (key !== '$' && key !== 'state' && key !== 'with' && key !== 'bind' && key !== 'destroy' && key !== 'derived') {
|
|
398
|
+
if (key !== '$' && key !== 'state' && key !== 'with' && key !== 'bind' && key !== 'destroy' && key !== 'destroyed' && key !== 'registerCleanup' && key !== 'derived') {
|
|
357
399
|
try {
|
|
358
400
|
newBuilder[key] = enhanced[key];
|
|
359
401
|
} catch {}
|
|
@@ -395,6 +437,22 @@ function createBuilder(baseTree) {
|
|
|
395
437
|
configurable: true
|
|
396
438
|
});
|
|
397
439
|
}
|
|
440
|
+
if (baseTree.destroyed) {
|
|
441
|
+
Object.defineProperty(builder, 'destroyed', {
|
|
442
|
+
value: baseTree.destroyed,
|
|
443
|
+
enumerable: false,
|
|
444
|
+
writable: false,
|
|
445
|
+
configurable: true
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (typeof baseTree.registerCleanup === 'function') {
|
|
449
|
+
Object.defineProperty(builder, 'registerCleanup', {
|
|
450
|
+
value: baseTree.registerCleanup.bind(baseTree),
|
|
451
|
+
enumerable: false,
|
|
452
|
+
writable: false,
|
|
453
|
+
configurable: true
|
|
454
|
+
});
|
|
455
|
+
}
|
|
398
456
|
Object.defineProperty(builder, 'derived', {
|
|
399
457
|
value: function (factory) {
|
|
400
458
|
if (isFinalized) {
|
package/dist/presets.js
ADDED
package/dist/security.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SecurityPresets, SecurityValidator } from './lib/security/security-validator.js';
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createIndexedDBAdapter, createStorageAdapter } from './enhancers/serialization/serialization.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Reactive JSON for Angular. JSON branches, reactive leaves. No actions. No reducers. No selectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,26 @@
|
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
|
+
"./presets": {
|
|
18
|
+
"types": "./src/presets.d.ts",
|
|
19
|
+
"import": "./dist/presets.js",
|
|
20
|
+
"default": "./dist/presets.js"
|
|
21
|
+
},
|
|
22
|
+
"./security": {
|
|
23
|
+
"types": "./src/security.d.ts",
|
|
24
|
+
"import": "./dist/security.js",
|
|
25
|
+
"default": "./dist/security.js"
|
|
26
|
+
},
|
|
27
|
+
"./edit-session": {
|
|
28
|
+
"types": "./src/edit-session.d.ts",
|
|
29
|
+
"import": "./dist/edit-session.js",
|
|
30
|
+
"default": "./dist/edit-session.js"
|
|
31
|
+
},
|
|
32
|
+
"./storage": {
|
|
33
|
+
"types": "./src/storage.d.ts",
|
|
34
|
+
"import": "./dist/storage.js",
|
|
35
|
+
"default": "./dist/storage.js"
|
|
36
|
+
},
|
|
17
37
|
"./package.json": "./package.json"
|
|
18
38
|
},
|
|
19
39
|
"peerDependencies": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createEditSession, type EditSession, type UndoRedoHistory, } from './lib/edit-session';
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { signalTree } from './lib/signal-tree';
|
|
2
|
-
export type { ISignalTree, SignalTree, SignalTreeBase, FullSignalTree, ProdSignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, } from './lib/types';
|
|
2
|
+
export type { ISignalTree, SignalTree, SignalTreeBase, FullSignalTree, ProdSignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, EnhancerCleanup, } from './lib/types';
|
|
3
3
|
export { entityMap } from './lib/types';
|
|
4
4
|
export type { ProcessDerived, DeepMergeTree, DerivedFactory, WithDerived, } from './lib/internals/derived-types';
|
|
5
5
|
export { derivedFrom, externalDerived } from './lib/internals/derived-types';
|
|
@@ -9,19 +9,14 @@ export { status, isStatusMarker, LoadingState, type StatusMarker, type StatusSig
|
|
|
9
9
|
export { stored, isStoredMarker, createStorageKeys, clearStoragePrefix, type StoredMarker, type StoredSignal, type StoredOptions, } from './lib/markers/stored';
|
|
10
10
|
export { form, isFormMarker, createFormSignal, validators, FORM_MARKER, type FormMarker, type FormSignal, type FormConfig, type FormFields, type FormWizard, type WizardConfig, type WizardStepConfig, type Validator, type AsyncValidator, } from './lib/markers/form';
|
|
11
11
|
export { registerMarkerProcessor } from './lib/internals/materialize-markers';
|
|
12
|
-
export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject,
|
|
13
|
-
export { createEditSession, type EditSession, type UndoRedoHistory, } from './lib/edit-session';
|
|
12
|
+
export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, } from './lib/utils';
|
|
14
13
|
export { getPathNotifier } from './lib/path-notifier';
|
|
15
|
-
export { SecurityValidator, SecurityPresets, type SecurityEvent, type SecurityEventType, type SecurityValidatorConfig, } from './lib/security/security-validator';
|
|
16
14
|
export { createEnhancer, resolveEnhancerOrder } from './enhancers/index';
|
|
17
15
|
export { ENHANCER_META } from './lib/types';
|
|
18
|
-
export { batching
|
|
16
|
+
export { batching } from './enhancers/batching/batching';
|
|
19
17
|
export type { BatchingConfig, BatchingMethods } from './lib/types';
|
|
20
|
-
export { memoization
|
|
21
|
-
export { timeTravel
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export { createAsyncOperation, trackAsync } from './lib/async-helpers';
|
|
26
|
-
export { TREE_PRESETS, createPresetConfig, validatePreset, getAvailablePresets, combinePresets, createDevTree, } from './enhancers/presets/lib/presets';
|
|
27
|
-
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES } from './lib/constants';
|
|
18
|
+
export { memoization } from './enhancers/memoization/memoization';
|
|
19
|
+
export { timeTravel } from './enhancers/time-travel/time-travel';
|
|
20
|
+
export { serialization, persistence, } from './enhancers/serialization/serialization';
|
|
21
|
+
export { devTools } from './enhancers/devtools/devtools';
|
|
22
|
+
export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, isDev } from './lib/constants';
|
package/src/lib/types.d.ts
CHANGED
|
@@ -43,7 +43,10 @@ export interface ISignalTree<T> extends NodeAccessor<T> {
|
|
|
43
43
|
with<TAdded>(enhancer: (tree: ISignalTree<T>) => ISignalTree<T> & TAdded): this & TAdded;
|
|
44
44
|
bind(thisArg?: unknown): NodeAccessor<T>;
|
|
45
45
|
destroy(): void;
|
|
46
|
+
readonly destroyed: Signal<boolean>;
|
|
47
|
+
registerCleanup(fn: EnhancerCleanup): void;
|
|
46
48
|
}
|
|
49
|
+
export type EnhancerCleanup = () => void;
|
|
47
50
|
export interface EffectsMethods<T> {
|
|
48
51
|
effect(fn: (state: T) => void | (() => void)): () => void;
|
|
49
52
|
subscribe(fn: (state: T) => void): () => void;
|
package/src/presets.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SecurityValidator, SecurityPresets, type SecurityEvent, type SecurityEventType, type SecurityValidatorConfig, } from './lib/security/security-validator';
|
package/src/storage.d.ts
ADDED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
function entities(config = {}) {
|
|
2
|
-
throw new Error('entities() has been removed. Remove `.with(entities())` from your code; v7+ auto-processes EntityMap markers.');
|
|
3
|
-
}
|
|
4
|
-
const enableEntities = entities;
|
|
5
|
-
const highPerformanceEntities = entities;
|
|
6
|
-
|
|
7
|
-
export { enableEntities, entities, highPerformanceEntities };
|
|
@@ -1,77 +0,0 @@
|
|
|
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 };
|