@signaltree/core 4.0.12 → 4.0.15
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 +312 -0
- package/dist/enhancers/batching/index.d.ts +1 -0
- package/dist/enhancers/batching/index.js +1 -0
- package/dist/enhancers/batching/jest.config.d.ts +15 -0
- package/dist/enhancers/batching/jest.config.js +21 -0
- package/dist/enhancers/batching/lib/batching.d.ts +16 -0
- package/dist/enhancers/batching/lib/batching.js +155 -0
- package/dist/enhancers/batching/test-setup.d.ts +1 -0
- package/dist/enhancers/batching/test-setup.js +5 -0
- package/dist/enhancers/computed/index.d.ts +1 -0
- package/dist/enhancers/computed/index.js +1 -0
- package/dist/enhancers/computed/jest.config.d.ts +15 -0
- package/dist/enhancers/computed/jest.config.js +21 -0
- package/dist/enhancers/computed/lib/computed.d.ts +12 -0
- package/dist/enhancers/computed/lib/computed.js +19 -0
- package/dist/enhancers/devtools/index.d.ts +1 -0
- package/dist/enhancers/devtools/index.js +1 -0
- package/dist/enhancers/devtools/jest.config.d.ts +15 -0
- package/dist/enhancers/devtools/jest.config.js +21 -0
- package/dist/enhancers/devtools/lib/devtools.d.ts +77 -0
- package/dist/enhancers/devtools/lib/devtools.js +278 -0
- package/dist/enhancers/devtools/test-setup.d.ts +1 -0
- package/dist/enhancers/devtools/test-setup.js +5 -0
- package/dist/enhancers/entities/index.d.ts +1 -0
- package/dist/enhancers/entities/index.js +1 -0
- package/dist/enhancers/entities/jest.config.d.ts +15 -0
- package/dist/enhancers/entities/jest.config.js +21 -0
- package/dist/enhancers/entities/lib/entities.d.ts +22 -0
- package/dist/enhancers/entities/lib/entities.js +110 -0
- package/dist/enhancers/entities/test-setup.d.ts +1 -0
- package/dist/enhancers/entities/test-setup.js +5 -0
- package/dist/enhancers/index.d.ts +3 -0
- package/dist/enhancers/index.js +84 -0
- package/dist/enhancers/memoization/index.d.ts +1 -0
- package/dist/enhancers/memoization/index.js +1 -0
- package/dist/enhancers/memoization/jest.config.d.ts +15 -0
- package/dist/enhancers/memoization/jest.config.js +21 -0
- package/dist/enhancers/memoization/lib/memoization.d.ts +65 -0
- package/dist/enhancers/memoization/lib/memoization.js +391 -0
- package/dist/enhancers/memoization/test-setup.d.ts +1 -0
- package/dist/enhancers/memoization/test-setup.js +5 -0
- package/dist/enhancers/middleware/index.d.ts +2 -0
- package/dist/enhancers/middleware/index.js +2 -0
- package/dist/enhancers/middleware/jest.config.d.ts +15 -0
- package/dist/enhancers/middleware/jest.config.js +21 -0
- package/dist/enhancers/middleware/lib/async-helpers.d.ts +8 -0
- package/dist/enhancers/middleware/lib/async-helpers.js +85 -0
- package/dist/enhancers/middleware/lib/middleware.d.ts +11 -0
- package/dist/enhancers/middleware/lib/middleware.js +179 -0
- package/dist/enhancers/middleware/test-setup.d.ts +1 -0
- package/dist/enhancers/middleware/test-setup.js +5 -0
- package/dist/enhancers/presets/index.d.ts +1 -0
- package/dist/enhancers/presets/index.js +1 -0
- package/dist/enhancers/presets/jest.config.d.ts +15 -0
- package/dist/enhancers/presets/jest.config.js +21 -0
- package/dist/enhancers/presets/lib/presets.d.ts +11 -0
- package/dist/enhancers/presets/lib/presets.js +77 -0
- package/dist/enhancers/presets/test-setup.d.ts +1 -0
- package/dist/enhancers/presets/test-setup.js +5 -0
- package/dist/enhancers/serialization/constants.d.ts +14 -0
- package/dist/enhancers/serialization/constants.js +14 -0
- package/dist/enhancers/serialization/index.d.ts +2 -0
- package/dist/enhancers/serialization/index.js +2 -0
- package/dist/enhancers/serialization/jest.config.d.ts +15 -0
- package/dist/enhancers/serialization/jest.config.js +21 -0
- package/dist/enhancers/serialization/lib/serialization.d.ts +59 -0
- package/dist/enhancers/serialization/lib/serialization.js +668 -0
- package/dist/enhancers/serialization/test-setup.d.ts +1 -0
- package/dist/enhancers/serialization/test-setup.js +5 -0
- package/dist/enhancers/time-travel/index.d.ts +1 -0
- package/dist/enhancers/time-travel/index.js +1 -0
- package/dist/enhancers/time-travel/jest.config.d.ts +15 -0
- package/dist/enhancers/time-travel/jest.config.js +21 -0
- package/dist/enhancers/time-travel/lib/time-travel.d.ts +36 -0
- package/dist/enhancers/time-travel/lib/time-travel.js +192 -0
- package/dist/enhancers/time-travel/lib/utils.d.ts +1 -0
- package/dist/enhancers/time-travel/lib/utils.js +1 -0
- package/dist/enhancers/time-travel/test-setup.d.ts +1 -0
- package/dist/enhancers/time-travel/test-setup.js +5 -0
- package/dist/enhancers/types.d.ts +105 -0
- package/dist/enhancers/types.js +0 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +16 -0
- package/dist/lib/constants.d.ts +42 -0
- package/dist/lib/constants.js +61 -0
- package/dist/lib/memory/memory-manager.d.ts +30 -0
- package/dist/lib/memory/memory-manager.js +166 -0
- package/dist/lib/performance/diff-engine.d.ts +33 -0
- package/dist/lib/performance/diff-engine.js +156 -0
- package/dist/lib/performance/path-index.d.ts +25 -0
- package/dist/lib/performance/path-index.js +154 -0
- package/dist/lib/performance/update-engine.d.ts +32 -0
- package/dist/lib/performance/update-engine.js +193 -0
- package/dist/lib/security/security-validator.d.ts +33 -0
- package/dist/lib/security/security-validator.js +139 -0
- package/dist/lib/signal-tree.d.ts +8 -0
- package/dist/lib/signal-tree.js +665 -0
- package/dist/lib/types.d.ts +164 -0
- package/dist/lib/types.js +9 -0
- package/dist/lib/utils.d.ts +27 -0
- package/dist/lib/utils.js +286 -0
- package/package.json +2 -11
package/README.md
CHANGED
|
@@ -1451,6 +1451,318 @@ import {
|
|
|
1451
1451
|
} from '@signaltree/core';
|
|
1452
1452
|
```
|
|
1453
1453
|
|
|
1454
|
+
## Companion Packages
|
|
1455
|
+
|
|
1456
|
+
While `@signaltree/core` includes comprehensive built-in enhancers for most use cases, the SignalTree ecosystem also provides specialized companion packages for specific needs:
|
|
1457
|
+
|
|
1458
|
+
### 📝 @signaltree/ng-forms
|
|
1459
|
+
|
|
1460
|
+
**Angular Reactive Forms integration for SignalTree**
|
|
1461
|
+
|
|
1462
|
+
Seamlessly connect Angular Reactive Forms with your SignalTree state for two-way data binding, validation, and form control.
|
|
1463
|
+
|
|
1464
|
+
```bash
|
|
1465
|
+
npm install @signaltree/ng-forms
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
**Features:**
|
|
1469
|
+
- 🔗 Two-way binding between forms and SignalTree state
|
|
1470
|
+
- ✅ Built-in validation integration
|
|
1471
|
+
- 🎯 Type-safe form controls
|
|
1472
|
+
- 🔄 Automatic sync between form state and tree state
|
|
1473
|
+
- 📊 Form status tracking (valid, pristine, touched, etc.)
|
|
1474
|
+
|
|
1475
|
+
**Quick Example:**
|
|
1476
|
+
|
|
1477
|
+
```typescript
|
|
1478
|
+
import { signalTree } from '@signaltree/core';
|
|
1479
|
+
import { bindFormToTree } from '@signaltree/ng-forms';
|
|
1480
|
+
|
|
1481
|
+
const tree = signalTree({
|
|
1482
|
+
user: { name: '', email: '', age: 0 }
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
@Component({
|
|
1486
|
+
template: `
|
|
1487
|
+
<form [formGroup]="form">
|
|
1488
|
+
<input formControlName="name" />
|
|
1489
|
+
<input formControlName="email" type="email" />
|
|
1490
|
+
<input formControlName="age" type="number" />
|
|
1491
|
+
</form>
|
|
1492
|
+
`
|
|
1493
|
+
})
|
|
1494
|
+
class UserFormComponent {
|
|
1495
|
+
form = new FormGroup({
|
|
1496
|
+
name: new FormControl(''),
|
|
1497
|
+
email: new FormControl(''),
|
|
1498
|
+
age: new FormControl(0)
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
constructor() {
|
|
1502
|
+
// Automatically sync form with tree state
|
|
1503
|
+
bindFormToTree(this.form, tree.$.user);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
**When to use:**
|
|
1509
|
+
- Building forms with Angular Reactive Forms
|
|
1510
|
+
- Need validation integration
|
|
1511
|
+
- Two-way data binding between forms and state
|
|
1512
|
+
- Complex form scenarios with nested form groups
|
|
1513
|
+
|
|
1514
|
+
**Learn more:** [npm package](https://www.npmjs.com/package/@signaltree/ng-forms)
|
|
1515
|
+
|
|
1516
|
+
---
|
|
1517
|
+
|
|
1518
|
+
### 🏢 @signaltree/enterprise
|
|
1519
|
+
|
|
1520
|
+
**Enterprise-scale optimizations for large applications**
|
|
1521
|
+
|
|
1522
|
+
Advanced performance optimizations designed for applications with 500+ signals and complex state trees.
|
|
1523
|
+
|
|
1524
|
+
```bash
|
|
1525
|
+
npm install @signaltree/enterprise
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
**Features:**
|
|
1529
|
+
- ⚡ PathIndex for O(k) lookup time regardless of tree size
|
|
1530
|
+
- 🗜️ Advanced memory optimization algorithms
|
|
1531
|
+
- 📊 Performance profiling and monitoring
|
|
1532
|
+
- 🔍 Efficient path-based signal resolution
|
|
1533
|
+
- 🎯 Optimized for large-scale applications
|
|
1534
|
+
|
|
1535
|
+
**Quick Example:**
|
|
1536
|
+
|
|
1537
|
+
```typescript
|
|
1538
|
+
import { signalTree } from '@signaltree/core';
|
|
1539
|
+
import { withEnterpriseOptimizations } from '@signaltree/enterprise';
|
|
1540
|
+
|
|
1541
|
+
const tree = signalTree({
|
|
1542
|
+
// Large application state with hundreds of signals
|
|
1543
|
+
modules: {
|
|
1544
|
+
auth: { /* ... */ },
|
|
1545
|
+
data: { /* ... */ },
|
|
1546
|
+
ui: { /* ... */ },
|
|
1547
|
+
// ... many more modules
|
|
1548
|
+
}
|
|
1549
|
+
}).with(withEnterpriseOptimizations({
|
|
1550
|
+
enablePathIndex: true,
|
|
1551
|
+
enableMemoryOptimizations: true,
|
|
1552
|
+
enablePerformanceMonitoring: true
|
|
1553
|
+
}));
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
**Performance Benefits:**
|
|
1557
|
+
- **Constant-time lookups:** O(k) lookup where k is path depth, not total signal count
|
|
1558
|
+
- **Memory efficiency:** Up to 40% reduction in memory usage for large trees
|
|
1559
|
+
- **Faster updates:** Optimized update batching for high-frequency scenarios
|
|
1560
|
+
|
|
1561
|
+
**When to use:**
|
|
1562
|
+
- Applications with 500+ signals
|
|
1563
|
+
- Complex nested state structures (10+ levels deep)
|
|
1564
|
+
- High-frequency state updates
|
|
1565
|
+
- Enterprise-scale applications with performance requirements
|
|
1566
|
+
- Need detailed performance profiling
|
|
1567
|
+
|
|
1568
|
+
**Learn more:** [npm package](https://www.npmjs.com/package/@signaltree/enterprise)
|
|
1569
|
+
|
|
1570
|
+
---
|
|
1571
|
+
|
|
1572
|
+
### 🛡️ @signaltree/guardrails
|
|
1573
|
+
|
|
1574
|
+
**Development-only performance monitoring and debugging**
|
|
1575
|
+
|
|
1576
|
+
Comprehensive development tools for detecting performance issues, memory leaks, and anti-patterns during development. **Zero production overhead** via conditional exports.
|
|
1577
|
+
|
|
1578
|
+
```bash
|
|
1579
|
+
npm install --save-dev @signaltree/guardrails
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
**Features:**
|
|
1583
|
+
- 🔥 Hot-path detection - identifies frequently accessed signals
|
|
1584
|
+
- 💾 Memory leak detection - tracks signal cleanup issues
|
|
1585
|
+
- 📊 Performance budgets - enforces performance thresholds
|
|
1586
|
+
- ⚠️ Anti-pattern warnings - detects common mistakes
|
|
1587
|
+
- 📈 Real-time performance metrics
|
|
1588
|
+
- 🎯 Zero production overhead (no-op in production builds)
|
|
1589
|
+
|
|
1590
|
+
**Quick Example:**
|
|
1591
|
+
|
|
1592
|
+
```typescript
|
|
1593
|
+
import { signalTree } from '@signaltree/core';
|
|
1594
|
+
import { withGuardrails } from '@signaltree/guardrails';
|
|
1595
|
+
|
|
1596
|
+
const tree = signalTree({
|
|
1597
|
+
users: [] as User[],
|
|
1598
|
+
posts: [] as Post[]
|
|
1599
|
+
}).with(withGuardrails({
|
|
1600
|
+
hotPathThreshold: 100, // Warn if signal accessed >100 times/sec
|
|
1601
|
+
memoryLeakThreshold: 50, // Warn if >50 uncleaned signals
|
|
1602
|
+
budgets: {
|
|
1603
|
+
updateTime: 16, // Warn if updates take >16ms
|
|
1604
|
+
signalCount: 1000 // Warn if >1000 signals created
|
|
1605
|
+
}
|
|
1606
|
+
}));
|
|
1607
|
+
|
|
1608
|
+
// Development warnings will appear in console
|
|
1609
|
+
// Production builds get no-op functions (0 overhead)
|
|
1610
|
+
```
|
|
1611
|
+
|
|
1612
|
+
**Development Features:**
|
|
1613
|
+
|
|
1614
|
+
```typescript
|
|
1615
|
+
import { getPerformanceMetrics, getHotPaths, checkMemoryLeaks } from '@signaltree/guardrails';
|
|
1616
|
+
|
|
1617
|
+
// Get detailed performance metrics
|
|
1618
|
+
const metrics = getPerformanceMetrics();
|
|
1619
|
+
console.log('Update time:', metrics.avgUpdateTime);
|
|
1620
|
+
console.log('Signal count:', metrics.totalSignals);
|
|
1621
|
+
|
|
1622
|
+
// Identify hot paths
|
|
1623
|
+
const hotPaths = getHotPaths();
|
|
1624
|
+
console.log('Most accessed signals:', hotPaths);
|
|
1625
|
+
|
|
1626
|
+
// Check for memory leaks
|
|
1627
|
+
const leaks = checkMemoryLeaks();
|
|
1628
|
+
if (leaks.length > 0) {
|
|
1629
|
+
console.warn('Potential memory leaks:', leaks);
|
|
1630
|
+
}
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
**Conditional Exports (Zero Production Overhead):**
|
|
1634
|
+
|
|
1635
|
+
```json
|
|
1636
|
+
{
|
|
1637
|
+
"exports": {
|
|
1638
|
+
".": {
|
|
1639
|
+
"development": "./dist/index.js",
|
|
1640
|
+
"production": "./dist/noop.js"
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
In production builds, all guardrails functions become no-ops with zero runtime cost.
|
|
1647
|
+
|
|
1648
|
+
**When to use:**
|
|
1649
|
+
- During active development
|
|
1650
|
+
- Performance optimization phase
|
|
1651
|
+
- Debugging state management issues
|
|
1652
|
+
- Team onboarding and code reviews
|
|
1653
|
+
- CI/CD performance regression detection
|
|
1654
|
+
|
|
1655
|
+
**Learn more:** [npm package](https://www.npmjs.com/package/@signaltree/guardrails)
|
|
1656
|
+
|
|
1657
|
+
---
|
|
1658
|
+
|
|
1659
|
+
### 🎯 @signaltree/callable-syntax
|
|
1660
|
+
|
|
1661
|
+
**Build-time transform for callable signal syntax**
|
|
1662
|
+
|
|
1663
|
+
A TypeScript transformer that enables callable syntax sugar for setting signal values. This is **purely a developer experience enhancement** with zero runtime overhead.
|
|
1664
|
+
|
|
1665
|
+
```bash
|
|
1666
|
+
npm install --save-dev @signaltree/callable-syntax
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
**Features:**
|
|
1670
|
+
- 🍬 Syntactic sugar for signal updates
|
|
1671
|
+
- ⚡ Zero runtime overhead (build-time transform)
|
|
1672
|
+
- ✅ Full TypeScript type safety
|
|
1673
|
+
- 🔧 Works with any build tool (Rollup, Webpack, esbuild, etc.)
|
|
1674
|
+
- 📝 Optional - use direct `.set/.update` if preferred
|
|
1675
|
+
|
|
1676
|
+
**Syntax Transformation:**
|
|
1677
|
+
|
|
1678
|
+
```typescript
|
|
1679
|
+
// With callable-syntax transform
|
|
1680
|
+
tree.$.name('Jane'); // Transformed to: tree.$.name.set('Jane')
|
|
1681
|
+
tree.$.count((n) => n + 1); // Transformed to: tree.$.count.update((n) => n + 1)
|
|
1682
|
+
|
|
1683
|
+
// Reading always works directly (no transform needed)
|
|
1684
|
+
const name = tree.$.name(); // Direct Angular signal API
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
**Setup (tsconfig.json):**
|
|
1688
|
+
|
|
1689
|
+
```json
|
|
1690
|
+
{
|
|
1691
|
+
"compilerOptions": {
|
|
1692
|
+
"plugins": [
|
|
1693
|
+
{ "transform": "@signaltree/callable-syntax" }
|
|
1694
|
+
]
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
**Setup (Rollup):**
|
|
1700
|
+
|
|
1701
|
+
```javascript
|
|
1702
|
+
import { callableSyntaxTransform } from '@signaltree/callable-syntax/rollup';
|
|
1703
|
+
|
|
1704
|
+
export default {
|
|
1705
|
+
plugins: [
|
|
1706
|
+
callableSyntaxTransform()
|
|
1707
|
+
]
|
|
1708
|
+
};
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
**Important Notes:**
|
|
1712
|
+
- **Optional:** You can always use direct `.set(value)` or `.update(fn)` syntax
|
|
1713
|
+
- **Build-time only:** No runtime code is added to your bundle
|
|
1714
|
+
- **Function-valued leaves:** When storing functions, use `.set(fn)` directly
|
|
1715
|
+
- **Type-safe:** Full TypeScript support via module augmentation
|
|
1716
|
+
|
|
1717
|
+
**When to use:**
|
|
1718
|
+
- Prefer shorter, more concise syntax
|
|
1719
|
+
- Team convention favors callable style
|
|
1720
|
+
- Migrating from other signal libraries with similar syntax
|
|
1721
|
+
- Want familiar DX without runtime overhead
|
|
1722
|
+
|
|
1723
|
+
**When to skip:**
|
|
1724
|
+
- Team prefers explicit `.set/.update` syntax
|
|
1725
|
+
- Build pipeline doesn't support transformers
|
|
1726
|
+
- Storing functions as signal values (use direct `.set`)
|
|
1727
|
+
|
|
1728
|
+
**Learn more:** [npm package](https://www.npmjs.com/package/@signaltree/callable-syntax)
|
|
1729
|
+
|
|
1730
|
+
---
|
|
1731
|
+
|
|
1732
|
+
## Package Selection Guide
|
|
1733
|
+
|
|
1734
|
+
**Start with just `@signaltree/core`** - it includes comprehensive enhancers for most applications:
|
|
1735
|
+
- Performance optimization (batching, memoization)
|
|
1736
|
+
- Data management (entities, async operations)
|
|
1737
|
+
- Development tools (devtools, time-travel)
|
|
1738
|
+
- State persistence (serialization)
|
|
1739
|
+
|
|
1740
|
+
**Add companion packages when you need:**
|
|
1741
|
+
|
|
1742
|
+
| Package | When to Add | Bundle Impact |
|
|
1743
|
+
|---------|------------|---------------|
|
|
1744
|
+
| `@signaltree/ng-forms` | Angular Reactive Forms integration | ~10KB gzipped |
|
|
1745
|
+
| `@signaltree/enterprise` | 500+ signals, large-scale apps | ~8KB gzipped |
|
|
1746
|
+
| `@signaltree/guardrails` | Development performance monitoring | 0KB (dev-only) |
|
|
1747
|
+
| `@signaltree/callable-syntax` | Prefer callable syntax sugar | 0KB (build-time) |
|
|
1748
|
+
|
|
1749
|
+
**Typical Installation Patterns:**
|
|
1750
|
+
|
|
1751
|
+
```bash
|
|
1752
|
+
# Basic application
|
|
1753
|
+
npm install @signaltree/core
|
|
1754
|
+
|
|
1755
|
+
# Application with forms
|
|
1756
|
+
npm install @signaltree/core @signaltree/ng-forms
|
|
1757
|
+
|
|
1758
|
+
# Large enterprise application
|
|
1759
|
+
npm install @signaltree/core @signaltree/enterprise
|
|
1760
|
+
|
|
1761
|
+
# Development with all tools
|
|
1762
|
+
npm install @signaltree/core @signaltree/enterprise @signaltree/ng-forms
|
|
1763
|
+
npm install --save-dev @signaltree/guardrails @signaltree/callable-syntax
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1454
1766
|
## Links
|
|
1455
1767
|
|
|
1456
1768
|
- [SignalTree Documentation](https://signaltree.io)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/batching';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/batching';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
displayName: string;
|
|
3
|
+
preset: string;
|
|
4
|
+
setupFilesAfterEnv: string[];
|
|
5
|
+
coverageDirectory: string;
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': (string | {
|
|
8
|
+
tsconfig: string;
|
|
9
|
+
stringifyContentPathRegex: string;
|
|
10
|
+
})[];
|
|
11
|
+
};
|
|
12
|
+
transformIgnorePatterns: string[];
|
|
13
|
+
snapshotSerializers: string[];
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'batching',
|
|
3
|
+
preset: '../../../../../jest.preset.js',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],
|
|
5
|
+
coverageDirectory: '../../../../../coverage/packages/core/enhancers/batching',
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
8
|
+
'jest-preset-angular',
|
|
9
|
+
{
|
|
10
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
11
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
16
|
+
snapshotSerializers: [
|
|
17
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
18
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
19
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SignalTree } from '../../../lib/types';
|
|
2
|
+
interface BatchingConfig {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
maxBatchSize?: number;
|
|
5
|
+
autoFlushDelay?: number;
|
|
6
|
+
batchTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
interface BatchingSignalTree<T> extends SignalTree<T> {
|
|
9
|
+
batchUpdate(updater: (current: T) => Partial<T>): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function withBatching<T>(config?: BatchingConfig): (tree: SignalTree<T>) => BatchingSignalTree<T>;
|
|
12
|
+
export declare function withHighPerformanceBatching<T>(): (tree: SignalTree<T>) => BatchingSignalTree<T>;
|
|
13
|
+
export declare function flushBatchedUpdates(): void;
|
|
14
|
+
export declare function hasPendingUpdates(): boolean;
|
|
15
|
+
export declare function getBatchQueueSize(): number;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { parsePath } from '@signaltree/shared';
|
|
2
|
+
import { isNodeAccessor } from '../../../lib/utils';
|
|
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 ?? 16;
|
|
25
|
+
flushTimeoutId = setTimeout(() => {
|
|
26
|
+
flushUpdates();
|
|
27
|
+
}, delay);
|
|
28
|
+
}
|
|
29
|
+
function flushUpdates() {
|
|
30
|
+
if (isUpdating)
|
|
31
|
+
return;
|
|
32
|
+
let queue;
|
|
33
|
+
do {
|
|
34
|
+
if (updateQueue.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
isUpdating = true;
|
|
37
|
+
queue = updateQueue;
|
|
38
|
+
updateQueue = [];
|
|
39
|
+
if (flushTimeoutId !== undefined) {
|
|
40
|
+
clearTimeout(flushTimeoutId);
|
|
41
|
+
flushTimeoutId = undefined;
|
|
42
|
+
}
|
|
43
|
+
queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
|
|
44
|
+
try {
|
|
45
|
+
queue.forEach(({ fn }) => fn());
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
isUpdating = false;
|
|
49
|
+
}
|
|
50
|
+
} while (updateQueue.length > 0);
|
|
51
|
+
}
|
|
52
|
+
function batchUpdates(fn, path) {
|
|
53
|
+
const startTime = performance.now();
|
|
54
|
+
const depth = path ? parsePath(path).length : 0;
|
|
55
|
+
const update = { fn, startTime, depth, path };
|
|
56
|
+
const wasFlushed = addToQueue(update, currentBatchingConfig);
|
|
57
|
+
if (!wasFlushed) {
|
|
58
|
+
const isTimedOut = currentBatchingConfig.batchTimeoutMs &&
|
|
59
|
+
updateQueue.length > 0 &&
|
|
60
|
+
startTime - updateQueue[0].startTime >=
|
|
61
|
+
currentBatchingConfig.batchTimeoutMs;
|
|
62
|
+
if (isTimedOut) {
|
|
63
|
+
flushUpdates();
|
|
64
|
+
}
|
|
65
|
+
else if (!isUpdating && updateQueue.length > 0) {
|
|
66
|
+
queueMicrotask(() => {
|
|
67
|
+
flushUpdates();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function withBatching(config = {}) {
|
|
73
|
+
const { enabled = true } = config;
|
|
74
|
+
currentBatchingConfig = { ...currentBatchingConfig, ...config };
|
|
75
|
+
return (tree) => {
|
|
76
|
+
if (!enabled) {
|
|
77
|
+
return tree;
|
|
78
|
+
}
|
|
79
|
+
const originalTreeCall = tree.bind(tree);
|
|
80
|
+
const enhancedTree = function (...args) {
|
|
81
|
+
if (args.length === 0) {
|
|
82
|
+
return originalTreeCall();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
batchUpdates(() => {
|
|
86
|
+
if (args.length === 1) {
|
|
87
|
+
const arg = args[0];
|
|
88
|
+
if (typeof arg === 'function') {
|
|
89
|
+
originalTreeCall(arg);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
originalTreeCall(arg);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
|
|
99
|
+
Object.assign(enhancedTree, tree);
|
|
100
|
+
if ('state' in tree) {
|
|
101
|
+
Object.defineProperty(enhancedTree, 'state', {
|
|
102
|
+
value: tree.state,
|
|
103
|
+
enumerable: false,
|
|
104
|
+
configurable: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if ('$' in tree) {
|
|
108
|
+
Object.defineProperty(enhancedTree, '$', {
|
|
109
|
+
value: tree['$'],
|
|
110
|
+
enumerable: false,
|
|
111
|
+
configurable: true,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
enhancedTree.batchUpdate = (updater) => {
|
|
115
|
+
batchUpdates(() => {
|
|
116
|
+
const current = originalTreeCall();
|
|
117
|
+
const updates = updater(current);
|
|
118
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
119
|
+
const property = enhancedTree.state[key];
|
|
120
|
+
if (property && 'set' in property) {
|
|
121
|
+
property.set(value);
|
|
122
|
+
}
|
|
123
|
+
else if (isNodeAccessor(property)) {
|
|
124
|
+
property(value);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
return enhancedTree;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export function withHighPerformanceBatching() {
|
|
133
|
+
return withBatching({
|
|
134
|
+
enabled: true,
|
|
135
|
+
maxBatchSize: 200,
|
|
136
|
+
batchTimeoutMs: 0,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export function flushBatchedUpdates() {
|
|
140
|
+
if (updateQueue.length > 0) {
|
|
141
|
+
const queue = updateQueue.slice();
|
|
142
|
+
updateQueue = [];
|
|
143
|
+
isUpdating = false;
|
|
144
|
+
queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
|
|
145
|
+
queue.forEach(({ fn }) => {
|
|
146
|
+
fn();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function hasPendingUpdates() {
|
|
151
|
+
return updateQueue.length > 0;
|
|
152
|
+
}
|
|
153
|
+
export function getBatchQueueSize() {
|
|
154
|
+
return updateQueue.length;
|
|
155
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/computed';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/computed';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
displayName: string;
|
|
3
|
+
preset: string;
|
|
4
|
+
setupFilesAfterEnv: string[];
|
|
5
|
+
coverageDirectory: string;
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': (string | {
|
|
8
|
+
tsconfig: string;
|
|
9
|
+
stringifyContentPathRegex: string;
|
|
10
|
+
})[];
|
|
11
|
+
};
|
|
12
|
+
transformIgnorePatterns: string[];
|
|
13
|
+
snapshotSerializers: string[];
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'computed',
|
|
3
|
+
preset: '../../../jest.preset.js',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
5
|
+
coverageDirectory: '../../../coverage/packages/core/enhancers/computed',
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
8
|
+
'jest-preset-angular',
|
|
9
|
+
{
|
|
10
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
11
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
16
|
+
snapshotSerializers: [
|
|
17
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
18
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
19
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
import type { TreeNode, SignalTree } from '../../../lib/types';
|
|
3
|
+
export interface ComputedConfig {
|
|
4
|
+
lazy?: boolean;
|
|
5
|
+
memoize?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type ComputedSignal<T> = Signal<T>;
|
|
8
|
+
export interface ComputedSignalTree<T extends Record<string, unknown>> extends SignalTree<T> {
|
|
9
|
+
computed<U>(computeFn: (tree: TreeNode<T>) => U): ComputedSignal<U>;
|
|
10
|
+
}
|
|
11
|
+
export declare function computedEnhancer(_config?: ComputedConfig): import("../../../lib/types").EnhancerWithMeta<SignalTree<Record<string, unknown>>, ComputedSignalTree<Record<string, unknown>>>;
|
|
12
|
+
export declare function createComputed<T>(dependencies: readonly Signal<unknown>[], computeFn: () => T): ComputedSignal<T>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { computed } from '@angular/core';
|
|
2
|
+
import { createEnhancer } from '../..';
|
|
3
|
+
export function computedEnhancer(_config = {}) {
|
|
4
|
+
void _config;
|
|
5
|
+
return createEnhancer({
|
|
6
|
+
name: 'computed',
|
|
7
|
+
provides: ['computed'],
|
|
8
|
+
requires: [],
|
|
9
|
+
}, (tree) => {
|
|
10
|
+
const computedTree = tree;
|
|
11
|
+
computedTree.computed = function (computeFn) {
|
|
12
|
+
return computed(() => computeFn(tree.state));
|
|
13
|
+
};
|
|
14
|
+
return computedTree;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function createComputed(dependencies, computeFn) {
|
|
18
|
+
return computed(computeFn);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/devtools';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/devtools';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
displayName: string;
|
|
3
|
+
preset: string;
|
|
4
|
+
setupFilesAfterEnv: string[];
|
|
5
|
+
coverageDirectory: string;
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': (string | {
|
|
8
|
+
tsconfig: string;
|
|
9
|
+
stringifyContentPathRegex: string;
|
|
10
|
+
})[];
|
|
11
|
+
};
|
|
12
|
+
transformIgnorePatterns: string[];
|
|
13
|
+
snapshotSerializers: string[];
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'devtools',
|
|
3
|
+
preset: '../../../jest.preset.js',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
5
|
+
coverageDirectory: '../../../coverage/packages/core/enhancers/devtools',
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
8
|
+
'jest-preset-angular',
|
|
9
|
+
{
|
|
10
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
11
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
16
|
+
snapshotSerializers: [
|
|
17
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
18
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
19
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
20
|
+
],
|
|
21
|
+
};
|