@signaltree/core 6.0.0 → 6.0.3
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/README.md +76 -76
- package/dist/constants.js +6 -0
- package/dist/deep-equal.js +41 -0
- package/dist/enhancers/batching/batching.js +189 -0
- package/dist/enhancers/devtools/devtools.js +306 -0
- package/dist/enhancers/effects/effects.js +66 -0
- package/dist/enhancers/entities/entities.js +51 -0
- package/dist/enhancers/index.js +72 -0
- package/dist/enhancers/memoization/memoization.js +420 -0
- package/dist/enhancers/presets/lib/presets.js +27 -0
- package/dist/enhancers/serialization/constants.js +15 -0
- package/dist/enhancers/serialization/serialization.js +656 -0
- package/dist/enhancers/time-travel/time-travel.js +231 -0
- package/dist/enhancers/time-travel/utils.js +11 -0
- package/dist/index.js +19 -0
- package/dist/is-built-in-object.js +23 -0
- package/dist/lib/async-helpers.js +77 -0
- package/dist/lib/constants.js +56 -0
- package/dist/lib/entity-signal.js +283 -0
- package/dist/lib/memory/memory-manager.js +164 -0
- package/dist/lib/path-notifier.js +106 -0
- package/dist/lib/presets.js +21 -0
- package/dist/lib/security/security-validator.js +121 -0
- package/dist/lib/signal-tree.js +277 -0
- package/dist/lib/types.js +9 -0
- package/dist/lib/utils.js +299 -0
- package/dist/lru-cache.js +64 -0
- package/dist/parse-path.js +13 -0
- package/package.json +1 -1
- package/src/enhancers/batching/batching.d.ts +11 -0
- package/src/enhancers/batching/batching.types.d.ts +1 -0
- package/src/enhancers/batching/index.d.ts +1 -0
- package/src/enhancers/batching/test-setup.d.ts +3 -0
- package/src/enhancers/devtools/devtools.d.ts +68 -0
- package/src/enhancers/devtools/devtools.types.d.ts +1 -0
- package/src/enhancers/devtools/index.d.ts +1 -0
- package/src/enhancers/devtools/test-setup.d.ts +3 -0
- package/src/enhancers/effects/effects.d.ts +9 -0
- package/src/enhancers/effects/effects.types.d.ts +1 -0
- package/src/enhancers/effects/index.d.ts +1 -0
- package/src/enhancers/entities/entities.d.ts +11 -0
- package/src/enhancers/entities/entities.types.d.ts +1 -0
- package/src/enhancers/entities/index.d.ts +1 -0
- package/src/enhancers/entities/test-setup.d.ts +3 -0
- package/src/enhancers/index.d.ts +3 -0
- package/src/enhancers/memoization/index.d.ts +1 -0
- package/src/enhancers/memoization/memoization.d.ts +54 -0
- package/src/enhancers/memoization/memoization.types.d.ts +1 -0
- package/src/enhancers/memoization/test-setup.d.ts +3 -0
- package/src/enhancers/presets/index.d.ts +1 -0
- package/src/enhancers/presets/lib/presets.d.ts +8 -0
- package/src/enhancers/serialization/constants.d.ts +14 -0
- package/src/enhancers/serialization/index.d.ts +2 -0
- package/src/enhancers/serialization/serialization.d.ts +68 -0
- package/src/enhancers/serialization/test-setup.d.ts +3 -0
- package/src/enhancers/test-helpers/types-equals.d.ts +2 -0
- package/src/enhancers/time-travel/index.d.ts +1 -0
- package/src/enhancers/time-travel/test-setup.d.ts +3 -0
- package/src/enhancers/time-travel/time-travel.d.ts +10 -0
- package/src/enhancers/time-travel/time-travel.types.d.ts +1 -0
- package/src/enhancers/time-travel/utils.d.ts +2 -0
- package/src/enhancers/types.d.ts +1 -0
- package/src/enhancers/typing/helpers-types.d.ts +2 -0
- package/src/index.d.ts +17 -0
- package/src/lib/async-helpers.d.ts +8 -0
- package/src/lib/constants.d.ts +41 -0
- package/src/lib/dev-proxy.d.ts +3 -0
- package/src/lib/entity-signal.d.ts +1 -0
- package/src/lib/memory/memory-manager.d.ts +30 -0
- package/src/lib/path-notifier.d.ts +4 -0
- package/src/lib/performance/diff-engine.d.ts +33 -0
- package/src/lib/performance/path-index.d.ts +25 -0
- package/src/lib/performance/update-engine.d.ts +32 -0
- package/src/lib/presets.d.ts +34 -0
- package/src/lib/security/security-validator.d.ts +33 -0
- package/src/lib/signal-tree.d.ts +3 -0
- package/src/lib/types.d.ts +301 -0
- package/src/lib/utils.d.ts +32 -0
package/README.md
CHANGED
|
@@ -23,11 +23,11 @@ Modern bundlers (webpack 5+, esbuild, Rollup, Vite) **automatically tree-shake b
|
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
// ✅ Recommended: Simple and clean
|
|
26
|
-
import { signalTree,
|
|
26
|
+
import { signalTree, batching } from '@signaltree/core';
|
|
27
27
|
|
|
28
28
|
// ✅ Also fine: Explicit subpath (same bundle size)
|
|
29
29
|
import { signalTree } from '@signaltree/core';
|
|
30
|
-
import {
|
|
30
|
+
import { batching } from '@signaltree/core/enhancers/batching';
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
**Measured impact** (with modern bundlers):
|
|
@@ -90,7 +90,7 @@ Follow these principles for idiomatic SignalTree code:
|
|
|
90
90
|
### 1. Expose signals directly (no computed wrappers)
|
|
91
91
|
|
|
92
92
|
```typescript
|
|
93
|
-
const tree = signalTree(initialState).with(
|
|
93
|
+
const tree = signalTree(initialState).with(entities());
|
|
94
94
|
const $ = tree.$; // Shorthand for state access
|
|
95
95
|
|
|
96
96
|
// ✅ SignalTree-first: Direct signal exposure
|
|
@@ -115,7 +115,7 @@ export type UserTree = ReturnType<typeof createUserTree>;
|
|
|
115
115
|
|
|
116
116
|
// Factory function - no explicit return type needed
|
|
117
117
|
export function createUserTree() {
|
|
118
|
-
const tree = signalTree(initialState).with(
|
|
118
|
+
const tree = signalTree(initialState).with(entities());
|
|
119
119
|
return {
|
|
120
120
|
selectedUserId: tree.$.selected.userId, // Type inferred automatically
|
|
121
121
|
// ...
|
|
@@ -351,7 +351,7 @@ effect(() => {
|
|
|
351
351
|
Computed values become even more powerful with the built-in memoization enhancer:
|
|
352
352
|
|
|
353
353
|
```typescript
|
|
354
|
-
import { signalTree,
|
|
354
|
+
import { signalTree, memoization } from '@signaltree/core';
|
|
355
355
|
|
|
356
356
|
const tree = signalTree({
|
|
357
357
|
items: Array.from({ length: 10000 }, (_, i) => ({
|
|
@@ -359,7 +359,7 @@ const tree = signalTree({
|
|
|
359
359
|
value: Math.random(),
|
|
360
360
|
category: `cat-${i % 10}`,
|
|
361
361
|
})),
|
|
362
|
-
}).with(
|
|
362
|
+
}).with(memoization());
|
|
363
363
|
|
|
364
364
|
// Expensive computation - automatically cached by memoization enhancer
|
|
365
365
|
const expensiveComputation = computed(() => {
|
|
@@ -479,7 +479,7 @@ tree.$.config.settings.nested.set(false); // ✅ boolean
|
|
|
479
479
|
|
|
480
480
|
### 3) Manual state management
|
|
481
481
|
|
|
482
|
-
Core provides basic state updates. For advanced entity management, use the built-in `
|
|
482
|
+
Core provides basic state updates. For advanced entity management, use the built-in `entities` enhancer:
|
|
483
483
|
|
|
484
484
|
```typescript
|
|
485
485
|
interface User {
|
|
@@ -568,22 +568,22 @@ All enhancers are exported directly from `@signaltree/core`:
|
|
|
568
568
|
|
|
569
569
|
**Performance Enhancers:**
|
|
570
570
|
|
|
571
|
-
- `
|
|
572
|
-
- `
|
|
573
|
-
- `
|
|
571
|
+
- `batching()` - Batch updates to reduce recomputation and rendering
|
|
572
|
+
- `memoization()` - Intelligent caching for expensive computations
|
|
573
|
+
- `highPerformanceBatching()` - Advanced batching for high-frequency updates
|
|
574
574
|
- `withHighPerformanceMemoization()` - Optimized memoization for large state trees
|
|
575
575
|
|
|
576
576
|
**Data Management:**
|
|
577
577
|
|
|
578
|
-
- `
|
|
578
|
+
- `entities()` - Advanced CRUD operations for collections
|
|
579
579
|
- `createAsyncOperation()` - Async operation management with loading/error states
|
|
580
580
|
- `trackAsync()` - Track async operations in your state
|
|
581
|
-
- `
|
|
582
|
-
- `
|
|
581
|
+
- `serialization()` - State persistence and SSR support
|
|
582
|
+
- `persistence()` - Auto-save to localStorage/IndexedDB
|
|
583
583
|
|
|
584
584
|
**Development Tools:**
|
|
585
585
|
|
|
586
|
-
- `
|
|
586
|
+
- `devTools()` - Redux DevTools integration
|
|
587
587
|
- `withTimeTravel()` - Undo/redo functionality
|
|
588
588
|
|
|
589
589
|
**Presets:**
|
|
@@ -604,26 +604,26 @@ These are the **only** separate packages in the SignalTree ecosystem:
|
|
|
604
604
|
**Basic Enhancement:**
|
|
605
605
|
|
|
606
606
|
```typescript
|
|
607
|
-
import { signalTree,
|
|
607
|
+
import { signalTree, batching, devTools } from '@signaltree/core';
|
|
608
608
|
|
|
609
609
|
// Apply enhancers in order
|
|
610
610
|
const tree = signalTree({ count: 0 }).with(
|
|
611
|
-
|
|
612
|
-
|
|
611
|
+
batching(), // Performance optimization
|
|
612
|
+
devTools() // Development tools
|
|
613
613
|
);
|
|
614
614
|
```
|
|
615
615
|
|
|
616
616
|
**Performance-Focused Stack:**
|
|
617
617
|
|
|
618
618
|
```typescript
|
|
619
|
-
import { signalTree,
|
|
619
|
+
import { signalTree, batching, memoization, entities } from '@signaltree/core';
|
|
620
620
|
|
|
621
621
|
const tree = signalTree({
|
|
622
622
|
products: entityMap<Product>(),
|
|
623
623
|
ui: { loading: false },
|
|
624
624
|
})
|
|
625
|
-
.with(
|
|
626
|
-
.with(
|
|
625
|
+
.with(entities()) // Efficient CRUD operations (auto-detects entityMap)
|
|
626
|
+
.with(batching()); // Batch updates for optimal rendering
|
|
627
627
|
|
|
628
628
|
// Entity CRUD operations
|
|
629
629
|
tree.$.products.addOne(newProduct);
|
|
@@ -636,14 +636,14 @@ const electronics = tree.$.products.all.filter((p) => p.category === 'electronic
|
|
|
636
636
|
**Full-Stack Application:**
|
|
637
637
|
|
|
638
638
|
```typescript
|
|
639
|
-
import { signalTree,
|
|
639
|
+
import { signalTree, serialization, withTimeTravel } from '@signaltree/core';
|
|
640
640
|
|
|
641
641
|
const tree = signalTree({
|
|
642
642
|
user: null as User | null,
|
|
643
643
|
preferences: { theme: 'light' },
|
|
644
644
|
}).with(
|
|
645
645
|
// withAsync removed — API integration patterns are now covered by async helpers
|
|
646
|
-
|
|
646
|
+
serialization({
|
|
647
647
|
// Auto-save to localStorage
|
|
648
648
|
autoSave: true,
|
|
649
649
|
storage: 'localStorage',
|
|
@@ -678,9 +678,9 @@ Enhancers can declare metadata for automatic dependency resolution:
|
|
|
678
678
|
```typescript
|
|
679
679
|
// Enhancers are automatically ordered based on requirements
|
|
680
680
|
const tree = signalTree(state).with(
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
681
|
+
devTools(), // Requires: core, provides: debugging
|
|
682
|
+
batching(), // Requires: core, provides: batching
|
|
683
|
+
memoization() // Requires: batching, provides: caching
|
|
684
684
|
);
|
|
685
685
|
// Automatically ordered: batching -> memoization -> devtools
|
|
686
686
|
```
|
|
@@ -707,16 +707,16 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
|
|
|
707
707
|
SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
|
|
708
708
|
|
|
709
709
|
```typescript
|
|
710
|
-
import { signalTree, entityMap,
|
|
710
|
+
import { signalTree, entityMap, entities } from '@signaltree/core';
|
|
711
711
|
|
|
712
712
|
// Without entityMap - use manual array updates
|
|
713
713
|
const basic = signalTree({ users: [] as User[] });
|
|
714
714
|
basic.$.users.update((users) => [...users, newUser]);
|
|
715
715
|
|
|
716
|
-
// With entityMap +
|
|
716
|
+
// With entityMap + entities - use entity helpers
|
|
717
717
|
const enhanced = signalTree({
|
|
718
718
|
users: entityMap<User>(),
|
|
719
|
-
}).with(
|
|
719
|
+
}).with(entities());
|
|
720
720
|
|
|
721
721
|
enhanced.$.users.addOne(newUser); // ✅ Advanced CRUD operations
|
|
722
722
|
enhanced.$.users.byId(123)(); // ✅ O(1) lookups
|
|
@@ -881,15 +881,15 @@ tree.effect(() => console.log('State changed'));
|
|
|
881
881
|
### Performance-Enhanced Composition
|
|
882
882
|
|
|
883
883
|
```typescript
|
|
884
|
-
import { signalTree,
|
|
884
|
+
import { signalTree, batching, memoization } from '@signaltree/core';
|
|
885
885
|
|
|
886
886
|
// Add performance optimizations
|
|
887
887
|
const tree = signalTree({
|
|
888
888
|
products: [] as Product[],
|
|
889
889
|
filters: { category: '', search: '' },
|
|
890
890
|
}).with(
|
|
891
|
-
|
|
892
|
-
|
|
891
|
+
batching(), // Batch updates for optimal rendering
|
|
892
|
+
memoization() // Cache expensive computations
|
|
893
893
|
);
|
|
894
894
|
|
|
895
895
|
// Now supports batched updates
|
|
@@ -909,14 +909,14 @@ const filteredProducts = computed(() => {
|
|
|
909
909
|
### Data Management Composition
|
|
910
910
|
|
|
911
911
|
```typescript
|
|
912
|
-
import { signalTree, entityMap,
|
|
912
|
+
import { signalTree, entityMap, entities } from '@signaltree/core';
|
|
913
913
|
|
|
914
914
|
// Add data management capabilities (+2.77KB total)
|
|
915
915
|
const tree = signalTree({
|
|
916
916
|
users: entityMap<User>(),
|
|
917
917
|
posts: entityMap<Post>(),
|
|
918
918
|
ui: { loading: false, error: null as string | null },
|
|
919
|
-
}).with(
|
|
919
|
+
}).with(entities());
|
|
920
920
|
|
|
921
921
|
// Advanced entity operations via tree.$ accessor
|
|
922
922
|
tree.$.users.addOne(newUser);
|
|
@@ -938,7 +938,7 @@ const appTree = signalTree({
|
|
|
938
938
|
reports: entityMap<Report>(),
|
|
939
939
|
},
|
|
940
940
|
},
|
|
941
|
-
}).with(
|
|
941
|
+
}).with(entities());
|
|
942
942
|
|
|
943
943
|
// Access nested entities using tree.$ accessor
|
|
944
944
|
appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
|
|
@@ -963,7 +963,7 @@ async function fetchUsers() {
|
|
|
963
963
|
### Full-Featured Development Composition
|
|
964
964
|
|
|
965
965
|
```typescript
|
|
966
|
-
import { signalTree,
|
|
966
|
+
import { signalTree, batching, entities, serialization, withTimeTravel, devTools } from '@signaltree/core';
|
|
967
967
|
|
|
968
968
|
// Full development stack (example)
|
|
969
969
|
const tree = signalTree({
|
|
@@ -973,10 +973,10 @@ const tree = signalTree({
|
|
|
973
973
|
data: { users: [], posts: [] },
|
|
974
974
|
},
|
|
975
975
|
}).with(
|
|
976
|
-
|
|
977
|
-
|
|
976
|
+
batching(), // Performance
|
|
977
|
+
entities(), // Data management
|
|
978
978
|
// withAsync removed — use async helpers for API integration
|
|
979
|
-
|
|
979
|
+
serialization({
|
|
980
980
|
// State persistence
|
|
981
981
|
autoSave: true,
|
|
982
982
|
storage: 'localStorage',
|
|
@@ -985,7 +985,7 @@ const tree = signalTree({
|
|
|
985
985
|
// Undo/redo
|
|
986
986
|
maxHistory: 50,
|
|
987
987
|
}),
|
|
988
|
-
|
|
988
|
+
devTools({
|
|
989
989
|
// Debug tools (dev only)
|
|
990
990
|
name: 'MyApp',
|
|
991
991
|
trace: true,
|
|
@@ -1004,14 +1004,14 @@ tree.save(); // Persistence
|
|
|
1004
1004
|
### Production-Ready Composition
|
|
1005
1005
|
|
|
1006
1006
|
```typescript
|
|
1007
|
-
import { signalTree,
|
|
1007
|
+
import { signalTree, batching, entities, serialization } from '@signaltree/core';
|
|
1008
1008
|
|
|
1009
1009
|
// Production build (no dev tools)
|
|
1010
1010
|
const tree = signalTree(initialState).with(
|
|
1011
|
-
|
|
1012
|
-
|
|
1011
|
+
batching(), // Performance optimization
|
|
1012
|
+
entities(), // Data management
|
|
1013
1013
|
// withAsync removed — use async helpers for API integration
|
|
1014
|
-
|
|
1014
|
+
serialization({
|
|
1015
1015
|
// User preferences
|
|
1016
1016
|
autoSave: true,
|
|
1017
1017
|
storage: 'localStorage',
|
|
@@ -1025,18 +1025,18 @@ const tree = signalTree(initialState).with(
|
|
|
1025
1025
|
### Conditional Enhancement
|
|
1026
1026
|
|
|
1027
1027
|
```typescript
|
|
1028
|
-
import { signalTree,
|
|
1028
|
+
import { signalTree, batching, entities, devTools, withTimeTravel } from '@signaltree/core';
|
|
1029
1029
|
|
|
1030
1030
|
const isDevelopment = process.env['NODE_ENV'] === 'development';
|
|
1031
1031
|
|
|
1032
1032
|
// Conditional enhancement based on environment
|
|
1033
1033
|
const tree = signalTree(state).with(
|
|
1034
|
-
|
|
1035
|
-
|
|
1034
|
+
batching(), // Always include performance
|
|
1035
|
+
entities(), // Always include data management
|
|
1036
1036
|
...(isDevelopment
|
|
1037
1037
|
? [
|
|
1038
1038
|
// Development-only features
|
|
1039
|
-
|
|
1039
|
+
devTools(),
|
|
1040
1040
|
withTimeTravel(),
|
|
1041
1041
|
]
|
|
1042
1042
|
: [])
|
|
@@ -1074,10 +1074,10 @@ Start with core and grow incrementally:
|
|
|
1074
1074
|
const tree = signalTree(state);
|
|
1075
1075
|
|
|
1076
1076
|
// Phase 2: Add performance when needed
|
|
1077
|
-
const tree2 = tree.with(
|
|
1077
|
+
const tree2 = tree.with(batching());
|
|
1078
1078
|
|
|
1079
1079
|
// Phase 3: Add data management for collections
|
|
1080
|
-
const tree3 = tree2.with(
|
|
1080
|
+
const tree3 = tree2.with(entities());
|
|
1081
1081
|
|
|
1082
1082
|
// Phase 4: Add async for API integration
|
|
1083
1083
|
// withAsync removed — no explicit async enhancer; use async helpers instead
|
|
@@ -1090,11 +1090,11 @@ const tree3 = tree2.with(withEntities());
|
|
|
1090
1090
|
let tree = signalTree(initialState);
|
|
1091
1091
|
|
|
1092
1092
|
if (isDevelopment) {
|
|
1093
|
-
tree = tree.with(
|
|
1093
|
+
tree = tree.with(devTools());
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
if (needsPerformance) {
|
|
1097
|
-
tree = tree.with(
|
|
1097
|
+
tree = tree.with(batching(), memoization());
|
|
1098
1098
|
}
|
|
1099
1099
|
|
|
1100
1100
|
if (needsTimeTravel) {
|
|
@@ -1365,7 +1365,7 @@ tree.effect(fn); // Create reactive effects
|
|
|
1365
1365
|
tree.subscribe(fn); // Manual subscriptions
|
|
1366
1366
|
tree.destroy(); // Cleanup resources
|
|
1367
1367
|
|
|
1368
|
-
// Entity helpers (when using entityMap +
|
|
1368
|
+
// Entity helpers (when using entityMap + entities)
|
|
1369
1369
|
// tree.$.users.addOne(user); // Add single entity
|
|
1370
1370
|
// tree.$.users.byId(id)(); // O(1) lookup by ID
|
|
1371
1371
|
// tree.$.users.all; // Get all as array
|
|
@@ -1377,22 +1377,22 @@ tree.destroy(); // Cleanup resources
|
|
|
1377
1377
|
SignalTree Core includes all enhancers built-in:
|
|
1378
1378
|
|
|
1379
1379
|
```typescript
|
|
1380
|
-
import { signalTree,
|
|
1380
|
+
import { signalTree, batching, memoization, withTimeTravel } from '@signaltree/core';
|
|
1381
1381
|
|
|
1382
1382
|
// All enhancers available from @signaltree/core
|
|
1383
|
-
const tree = signalTree(initialState).with(
|
|
1383
|
+
const tree = signalTree(initialState).with(batching(), memoization(), withTimeTravel());
|
|
1384
1384
|
```
|
|
1385
1385
|
|
|
1386
1386
|
### Available enhancers
|
|
1387
1387
|
|
|
1388
1388
|
All enhancers are included in `@signaltree/core`:
|
|
1389
1389
|
|
|
1390
|
-
- **
|
|
1391
|
-
- **
|
|
1392
|
-
- **
|
|
1393
|
-
- **
|
|
1390
|
+
- **batching()** - Batch multiple updates for better performance
|
|
1391
|
+
- **memoization()** - Intelligent caching & performance optimization
|
|
1392
|
+
- **entities()** - Advanced entity management & CRUD operations
|
|
1393
|
+
- **devTools()** - Redux DevTools integration for debugging
|
|
1394
1394
|
- **withTimeTravel()** - Undo/redo functionality & state history
|
|
1395
|
-
- **
|
|
1395
|
+
- **serialization()** - State persistence & SSR support
|
|
1396
1396
|
- **createDevTree()** - Pre-configured development setup
|
|
1397
1397
|
- **TREE_PRESETS** - Common configuration patterns (PERFORMANCE, DASHBOARD, etc.)
|
|
1398
1398
|
|
|
@@ -1408,9 +1408,9 @@ Perfect for:
|
|
|
1408
1408
|
|
|
1409
1409
|
Consider enhancers when you need:
|
|
1410
1410
|
|
|
1411
|
-
- ⚡ Performance optimization (
|
|
1412
|
-
- 🐛 Advanced debugging (
|
|
1413
|
-
- 📦 Entity management (
|
|
1411
|
+
- ⚡ Performance optimization (batching, memoization)
|
|
1412
|
+
- 🐛 Advanced debugging (devTools, withTimeTravel)
|
|
1413
|
+
- 📦 Entity management (entities)
|
|
1414
1414
|
|
|
1415
1415
|
Consider separate packages when you need:
|
|
1416
1416
|
|
|
@@ -1540,21 +1540,21 @@ All enhancers are now consolidated in the core package. The following features a
|
|
|
1540
1540
|
|
|
1541
1541
|
### Performance & Optimization
|
|
1542
1542
|
|
|
1543
|
-
- **
|
|
1544
|
-
- **
|
|
1543
|
+
- **batching()** (+1.27KB gzipped) - Batch multiple updates for better performance
|
|
1544
|
+
- **memoization()** (+2.33KB gzipped) - Intelligent caching & performance optimization
|
|
1545
1545
|
|
|
1546
1546
|
### Advanced Features
|
|
1547
1547
|
|
|
1548
|
-
- **
|
|
1548
|
+
- **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
|
|
1549
1549
|
|
|
1550
1550
|
### Development Tools
|
|
1551
1551
|
|
|
1552
|
-
- **
|
|
1552
|
+
- **devTools()** (+2.49KB gzipped) - Development tools & Redux DevTools integration
|
|
1553
1553
|
- **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
|
|
1554
1554
|
|
|
1555
1555
|
### Integration & Convenience
|
|
1556
1556
|
|
|
1557
|
-
- **
|
|
1557
|
+
- **serialization()** (+0.84KB gzipped) - State persistence & SSR support
|
|
1558
1558
|
- **ecommercePreset()** - Pre-configured setups for e-commerce applications
|
|
1559
1559
|
- **dashboardPreset()** - Pre-configured setups for dashboard applications
|
|
1560
1560
|
|
|
@@ -1569,12 +1569,12 @@ npm install @signaltree/core
|
|
|
1569
1569
|
# Everything is available from @signaltree/core:
|
|
1570
1570
|
import {
|
|
1571
1571
|
signalTree,
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1572
|
+
batching,
|
|
1573
|
+
memoization,
|
|
1574
|
+
entities,
|
|
1575
|
+
devTools,
|
|
1576
1576
|
withTimeTravel,
|
|
1577
|
-
|
|
1577
|
+
serialization,
|
|
1578
1578
|
ecommercePreset,
|
|
1579
1579
|
dashboardPreset
|
|
1580
1580
|
} from '@signaltree/core';
|
|
@@ -1691,7 +1691,7 @@ npm install @signaltree/enterprise
|
|
|
1691
1691
|
|
|
1692
1692
|
```typescript
|
|
1693
1693
|
import { signalTree } from '@signaltree/core';
|
|
1694
|
-
import {
|
|
1694
|
+
import { enterpriseOptimizations } from '@signaltree/enterprise';
|
|
1695
1695
|
|
|
1696
1696
|
const tree = signalTree({
|
|
1697
1697
|
// Large application state with hundreds of signals
|
|
@@ -1708,7 +1708,7 @@ const tree = signalTree({
|
|
|
1708
1708
|
// ... many more modules
|
|
1709
1709
|
},
|
|
1710
1710
|
}).with(
|
|
1711
|
-
|
|
1711
|
+
enterpriseOptimizations({
|
|
1712
1712
|
enablePathIndex: true,
|
|
1713
1713
|
enableMemoryOptimizations: true,
|
|
1714
1714
|
enablePerformanceMonitoring: true,
|
|
@@ -1757,13 +1757,13 @@ npm install --save-dev @signaltree/guardrails
|
|
|
1757
1757
|
|
|
1758
1758
|
```typescript
|
|
1759
1759
|
import { signalTree } from '@signaltree/core';
|
|
1760
|
-
import {
|
|
1760
|
+
import { guardrails } from '@signaltree/guardrails';
|
|
1761
1761
|
|
|
1762
1762
|
const tree = signalTree({
|
|
1763
1763
|
users: [] as User[],
|
|
1764
1764
|
posts: [] as Post[],
|
|
1765
1765
|
}).with(
|
|
1766
|
-
|
|
1766
|
+
guardrails({
|
|
1767
1767
|
hotPathThreshold: 100, // Warn if signal accessed >100 times/sec
|
|
1768
1768
|
memoryLeakThreshold: 50, // Warn if >50 uncleaned signals
|
|
1769
1769
|
budgets: {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function deepEqual(a, b) {
|
|
2
|
+
if (a === b) return true;
|
|
3
|
+
if (a == null || b == null) return a === b;
|
|
4
|
+
const typeA = typeof a;
|
|
5
|
+
const typeB = typeof b;
|
|
6
|
+
if (typeA !== typeB) return false;
|
|
7
|
+
if (typeA !== 'object') return false;
|
|
8
|
+
if (a instanceof Date && b instanceof Date) {
|
|
9
|
+
return a.getTime() === b.getTime();
|
|
10
|
+
}
|
|
11
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
12
|
+
return a.source === b.source && a.flags === b.flags;
|
|
13
|
+
}
|
|
14
|
+
if (a instanceof Map && b instanceof Map) {
|
|
15
|
+
if (a.size !== b.size) return false;
|
|
16
|
+
for (const [key, value] of a) {
|
|
17
|
+
if (!b.has(key) || !deepEqual(value, b.get(key))) return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (a instanceof Set && b instanceof Set) {
|
|
22
|
+
if (a.size !== b.size) return false;
|
|
23
|
+
for (const value of a) {
|
|
24
|
+
if (!b.has(value)) return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(a)) {
|
|
29
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
30
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(b)) return false;
|
|
33
|
+
const objA = a;
|
|
34
|
+
const objB = b;
|
|
35
|
+
const keysA = Object.keys(objA);
|
|
36
|
+
const keysB = Object.keys(objB);
|
|
37
|
+
if (keysA.length !== keysB.length) return false;
|
|
38
|
+
return keysA.every(key => key in objB && deepEqual(objA[key], objB[key]));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { deepEqual };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { applyState, isNodeAccessor } from '../../lib/utils.js';
|
|
2
|
+
|
|
3
|
+
let updateQueue = [];
|
|
4
|
+
let isUpdating = false;
|
|
5
|
+
let flushTimeoutId;
|
|
6
|
+
let currentBatchingConfig = {};
|
|
7
|
+
function addToQueue(update, config = currentBatchingConfig) {
|
|
8
|
+
const maxSize = config.maxBatchSize ?? 100;
|
|
9
|
+
if (update.path) {
|
|
10
|
+
updateQueue = updateQueue.filter(existing => existing.path !== update.path);
|
|
11
|
+
}
|
|
12
|
+
updateQueue.push(update);
|
|
13
|
+
if (updateQueue.length > maxSize) {
|
|
14
|
+
flushUpdates();
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
scheduleFlush(config);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function scheduleFlush(config) {
|
|
21
|
+
if (flushTimeoutId !== undefined) {
|
|
22
|
+
clearTimeout(flushTimeoutId);
|
|
23
|
+
}
|
|
24
|
+
const delay = config.autoFlushDelay ?? config.debounceMs ?? 16;
|
|
25
|
+
flushTimeoutId = setTimeout(() => {
|
|
26
|
+
flushUpdates();
|
|
27
|
+
}, delay);
|
|
28
|
+
}
|
|
29
|
+
function flushUpdates() {
|
|
30
|
+
if (isUpdating) return;
|
|
31
|
+
let queue;
|
|
32
|
+
do {
|
|
33
|
+
if (updateQueue.length === 0) return;
|
|
34
|
+
isUpdating = true;
|
|
35
|
+
queue = updateQueue;
|
|
36
|
+
updateQueue = [];
|
|
37
|
+
if (flushTimeoutId !== undefined) {
|
|
38
|
+
clearTimeout(flushTimeoutId);
|
|
39
|
+
flushTimeoutId = undefined;
|
|
40
|
+
}
|
|
41
|
+
queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
|
|
42
|
+
try {
|
|
43
|
+
queue.forEach(({
|
|
44
|
+
fn
|
|
45
|
+
}) => fn());
|
|
46
|
+
} finally {
|
|
47
|
+
isUpdating = false;
|
|
48
|
+
}
|
|
49
|
+
} while (updateQueue.length > 0);
|
|
50
|
+
}
|
|
51
|
+
function batchUpdates(fn, path) {
|
|
52
|
+
const startTime = performance.now();
|
|
53
|
+
const depth = 0;
|
|
54
|
+
const update = {
|
|
55
|
+
fn,
|
|
56
|
+
startTime,
|
|
57
|
+
depth,
|
|
58
|
+
path
|
|
59
|
+
};
|
|
60
|
+
const wasFlushed = addToQueue(update, currentBatchingConfig);
|
|
61
|
+
if (!wasFlushed) {
|
|
62
|
+
const isTimedOut = currentBatchingConfig.batchTimeoutMs && updateQueue.length > 0 && startTime - updateQueue[0].startTime >= currentBatchingConfig.batchTimeoutMs;
|
|
63
|
+
if (isTimedOut) {
|
|
64
|
+
flushUpdates();
|
|
65
|
+
} else if (!isUpdating && updateQueue.length > 0) {
|
|
66
|
+
queueMicrotask(() => {
|
|
67
|
+
flushUpdates();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function batchingWithConfig(config = {}) {
|
|
73
|
+
const enabled = config.enabled ?? true;
|
|
74
|
+
if (enabled) {
|
|
75
|
+
currentBatchingConfig = {
|
|
76
|
+
...currentBatchingConfig,
|
|
77
|
+
...config
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const enhancer = tree => {
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
const enhanced = tree;
|
|
83
|
+
enhanced.batch = updater => {
|
|
84
|
+
try {
|
|
85
|
+
tree.batchUpdate(updater);
|
|
86
|
+
} catch {
|
|
87
|
+
try {
|
|
88
|
+
updater(enhanced.state);
|
|
89
|
+
} catch {}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
enhanced.batchUpdate = updater => {
|
|
93
|
+
try {
|
|
94
|
+
const current = tree();
|
|
95
|
+
const updates = updater(current);
|
|
96
|
+
applyState(enhanced.state, updates);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
try {
|
|
99
|
+
tree.batchUpdate(updater);
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
return enhanced;
|
|
104
|
+
}
|
|
105
|
+
const originalTreeCall = tree.bind(tree);
|
|
106
|
+
const enhancedTree = function (...args) {
|
|
107
|
+
if (args.length === 0) {
|
|
108
|
+
return originalTreeCall();
|
|
109
|
+
} else {
|
|
110
|
+
batchUpdates(() => {
|
|
111
|
+
if (args.length === 1) {
|
|
112
|
+
const arg = args[0];
|
|
113
|
+
if (typeof arg === 'function') {
|
|
114
|
+
originalTreeCall(arg);
|
|
115
|
+
} else {
|
|
116
|
+
originalTreeCall(arg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
|
|
123
|
+
Object.assign(enhancedTree, tree);
|
|
124
|
+
if ('state' in tree) {
|
|
125
|
+
Object.defineProperty(enhancedTree, 'state', {
|
|
126
|
+
value: tree.state,
|
|
127
|
+
enumerable: false,
|
|
128
|
+
configurable: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if ('$' in tree) {
|
|
132
|
+
Object.defineProperty(enhancedTree, '$', {
|
|
133
|
+
value: tree.$,
|
|
134
|
+
enumerable: false,
|
|
135
|
+
configurable: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
enhancedTree.batchUpdate = updater => {
|
|
139
|
+
batchUpdates(() => {
|
|
140
|
+
const current = originalTreeCall();
|
|
141
|
+
const updates = updater(current);
|
|
142
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
143
|
+
const property = enhancedTree.state[key];
|
|
144
|
+
if (property && 'set' in property) {
|
|
145
|
+
property.set(value);
|
|
146
|
+
} else if (isNodeAccessor(property)) {
|
|
147
|
+
property(value);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
return enhancedTree;
|
|
153
|
+
};
|
|
154
|
+
return enhancer;
|
|
155
|
+
}
|
|
156
|
+
function batching(config = {}) {
|
|
157
|
+
return batchingWithConfig(config);
|
|
158
|
+
}
|
|
159
|
+
function highPerformanceBatching() {
|
|
160
|
+
return batchingWithConfig({
|
|
161
|
+
enabled: true,
|
|
162
|
+
maxBatchSize: 200,
|
|
163
|
+
debounceMs: 0
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function flushBatchedUpdates() {
|
|
167
|
+
if (updateQueue.length > 0) {
|
|
168
|
+
const queue = updateQueue.slice();
|
|
169
|
+
updateQueue = [];
|
|
170
|
+
isUpdating = false;
|
|
171
|
+
queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
|
|
172
|
+
queue.forEach(({
|
|
173
|
+
fn
|
|
174
|
+
}) => {
|
|
175
|
+
fn();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function hasPendingUpdates() {
|
|
180
|
+
return updateQueue.length > 0;
|
|
181
|
+
}
|
|
182
|
+
function getBatchQueueSize() {
|
|
183
|
+
return updateQueue.length;
|
|
184
|
+
}
|
|
185
|
+
Object.assign((config = {}) => batchingWithConfig(config), {
|
|
186
|
+
highPerformance: highPerformanceBatching
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export { batching, batchingWithConfig, flushBatchedUpdates, getBatchQueueSize, hasPendingUpdates, highPerformanceBatching };
|