@signaltree/core 4.0.15 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -38
- package/dist/constants.js +6 -0
- package/dist/deep-clone.js +80 -0
- package/dist/deep-equal.js +41 -0
- package/dist/enhancers/batching/lib/batching.js +141 -135
- package/dist/enhancers/computed/lib/computed.js +18 -16
- package/dist/enhancers/devtools/lib/devtools.js +303 -260
- package/dist/enhancers/entities/lib/entities.js +109 -104
- package/dist/enhancers/index.js +65 -77
- package/dist/enhancers/memoization/lib/memoization.js +339 -351
- package/dist/enhancers/middleware/lib/async-helpers.js +71 -79
- package/dist/enhancers/middleware/lib/middleware.js +126 -169
- package/dist/enhancers/presets/lib/presets.js +82 -71
- package/dist/enhancers/serialization/constants.js +14 -13
- package/dist/enhancers/serialization/lib/serialization.js +615 -623
- package/dist/enhancers/time-travel/lib/time-travel.js +178 -177
- package/dist/index.d.ts +1 -17
- package/dist/index.js +19 -16
- package/dist/is-built-in-object.js +23 -0
- package/dist/lib/constants.js +51 -55
- package/dist/lib/memory/memory-manager.js +152 -154
- package/dist/lib/performance/diff-engine.js +141 -141
- package/dist/lib/performance/path-index.js +139 -137
- package/dist/lib/performance/update-engine.js +171 -176
- package/dist/lib/security/security-validator.js +110 -128
- package/dist/lib/signal-tree.js +577 -611
- package/dist/lib/types.js +3 -9
- package/dist/lib/utils.js +236 -268
- package/dist/lru-cache.js +64 -0
- package/dist/parse-path.js +13 -0
- package/package.json +30 -16
- package/src/index.d.ts +17 -0
- package/{dist → src}/lib/utils.d.ts +1 -0
- package/dist/enhancers/batching/index.js +0 -1
- package/dist/enhancers/batching/jest.config.js +0 -21
- package/dist/enhancers/batching/test-setup.js +0 -5
- package/dist/enhancers/computed/index.js +0 -1
- package/dist/enhancers/computed/jest.config.js +0 -21
- package/dist/enhancers/devtools/index.js +0 -1
- package/dist/enhancers/devtools/jest.config.js +0 -21
- package/dist/enhancers/devtools/test-setup.js +0 -5
- package/dist/enhancers/entities/index.js +0 -1
- package/dist/enhancers/entities/jest.config.js +0 -21
- package/dist/enhancers/entities/test-setup.js +0 -5
- package/dist/enhancers/memoization/index.js +0 -1
- package/dist/enhancers/memoization/jest.config.js +0 -21
- package/dist/enhancers/memoization/test-setup.js +0 -5
- package/dist/enhancers/middleware/index.js +0 -2
- package/dist/enhancers/middleware/jest.config.js +0 -21
- package/dist/enhancers/middleware/test-setup.js +0 -5
- package/dist/enhancers/presets/index.js +0 -1
- package/dist/enhancers/presets/jest.config.js +0 -21
- package/dist/enhancers/presets/test-setup.js +0 -5
- package/dist/enhancers/serialization/index.js +0 -2
- package/dist/enhancers/serialization/jest.config.js +0 -21
- package/dist/enhancers/serialization/test-setup.js +0 -5
- package/dist/enhancers/time-travel/index.js +0 -1
- package/dist/enhancers/time-travel/jest.config.js +0 -21
- package/dist/enhancers/time-travel/lib/utils.js +0 -1
- package/dist/enhancers/time-travel/test-setup.js +0 -5
- package/dist/enhancers/types.js +0 -0
- /package/{dist → src}/enhancers/batching/index.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/lib/batching.d.ts +0 -0
- /package/{dist → src}/enhancers/batching/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/index.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/computed/lib/computed.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/index.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/lib/devtools.d.ts +0 -0
- /package/{dist → src}/enhancers/devtools/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/index.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/lib/entities.d.ts +0 -0
- /package/{dist → src}/enhancers/entities/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/index.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/index.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/lib/memoization.d.ts +0 -0
- /package/{dist → src}/enhancers/memoization/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/index.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/lib/async-helpers.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/lib/middleware.d.ts +0 -0
- /package/{dist → src}/enhancers/middleware/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/index.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/lib/presets.d.ts +0 -0
- /package/{dist → src}/enhancers/presets/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/constants.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/index.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/lib/serialization.d.ts +0 -0
- /package/{dist → src}/enhancers/serialization/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/index.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/jest.config.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/lib/time-travel.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/lib/utils.d.ts +0 -0
- /package/{dist → src}/enhancers/time-travel/test-setup.d.ts +0 -0
- /package/{dist → src}/enhancers/types.d.ts +0 -0
- /package/{dist → src}/lib/constants.d.ts +0 -0
- /package/{dist → src}/lib/memory/memory-manager.d.ts +0 -0
- /package/{dist → src}/lib/performance/diff-engine.d.ts +0 -0
- /package/{dist → src}/lib/performance/path-index.d.ts +0 -0
- /package/{dist → src}/lib/performance/update-engine.d.ts +0 -0
- /package/{dist → src}/lib/security/security-validator.d.ts +0 -0
- /package/{dist → src}/lib/signal-tree.d.ts +0 -0
- /package/{dist → src}/lib/types.d.ts +0 -0
package/README.md
CHANGED
|
@@ -17,6 +17,33 @@ SignalTree Core is a lightweight package that provides:
|
|
|
17
17
|
- Small API surface with zero-cost abstractions
|
|
18
18
|
- Compact bundle size suited for production
|
|
19
19
|
|
|
20
|
+
## Import guidance (tree-shaking)
|
|
21
|
+
|
|
22
|
+
Modern bundlers (webpack 5+, esbuild, Rollup, Vite) **automatically tree-shake barrel imports** from `@signaltree/core`. Both import styles produce identical bundle sizes:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// ✅ Recommended: Simple and clean
|
|
26
|
+
import { signalTree, withBatching } from '@signaltree/core';
|
|
27
|
+
|
|
28
|
+
// ✅ Also fine: Explicit subpath (same bundle size)
|
|
29
|
+
import { signalTree } from '@signaltree/core';
|
|
30
|
+
import { withBatching } from '@signaltree/core/enhancers/batching';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Measured impact** (with modern bundlers):
|
|
34
|
+
|
|
35
|
+
- Core only: ~8.5 KB gzipped
|
|
36
|
+
- Core + batching: ~9.3 KB gzipped (barrel vs subpath: identical)
|
|
37
|
+
- Unused enhancers: **automatically excluded** by tree-shaking
|
|
38
|
+
|
|
39
|
+
**When to use subpath imports:**
|
|
40
|
+
|
|
41
|
+
- Older bundlers (webpack <5) with poor tree-shaking
|
|
42
|
+
- Explicit control over what gets included
|
|
43
|
+
- Personal/team preference for clarity
|
|
44
|
+
|
|
45
|
+
This repo's ESLint rule is **disabled by default** since testing confirms effective tree-shaking with barrel imports.
|
|
46
|
+
|
|
20
47
|
### Callable leaf signals (DX sugar only)
|
|
21
48
|
|
|
22
49
|
SignalTree provides TypeScript support for callable syntax on leaf signals as developer experience sugar:
|
|
@@ -1457,20 +1484,44 @@ While `@signaltree/core` includes comprehensive built-in enhancers for most use
|
|
|
1457
1484
|
|
|
1458
1485
|
### 📝 @signaltree/ng-forms
|
|
1459
1486
|
|
|
1460
|
-
**Angular
|
|
1487
|
+
**Angular Forms integration for SignalTree (Angular 17+)**
|
|
1461
1488
|
|
|
1462
|
-
Seamlessly connect Angular
|
|
1489
|
+
Seamlessly connect Angular Forms with your SignalTree state for two-way data binding, validation, and form control.
|
|
1463
1490
|
|
|
1464
1491
|
```bash
|
|
1465
1492
|
npm install @signaltree/ng-forms
|
|
1466
1493
|
```
|
|
1467
1494
|
|
|
1468
1495
|
**Features:**
|
|
1496
|
+
|
|
1469
1497
|
- 🔗 Two-way binding between forms and SignalTree state
|
|
1470
1498
|
- ✅ Built-in validation integration
|
|
1471
1499
|
- 🎯 Type-safe form controls
|
|
1472
1500
|
- 🔄 Automatic sync between form state and tree state
|
|
1473
1501
|
- 📊 Form status tracking (valid, pristine, touched, etc.)
|
|
1502
|
+
- ⚡ Native Signal Forms support (Angular 20.3+)
|
|
1503
|
+
- 🔧 Legacy bridge for Angular 17-19 (deprecated, will be removed with Angular 21)
|
|
1504
|
+
|
|
1505
|
+
**Signal Forms (Angular 20.3+ recommended)**
|
|
1506
|
+
|
|
1507
|
+
Use Angular's Signal Forms `connect()` API directly with SignalTree:
|
|
1508
|
+
|
|
1509
|
+
```ts
|
|
1510
|
+
import { toWritableSignal } from '@signaltree/core';
|
|
1511
|
+
|
|
1512
|
+
const tree = signalTree({
|
|
1513
|
+
user: { name: '', email: '' },
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// Leaves are WritableSignal<T>
|
|
1517
|
+
nameControl.connect(tree.$.user.name);
|
|
1518
|
+
|
|
1519
|
+
// Convert a slice to a WritableSignal<T>
|
|
1520
|
+
const userSignal = toWritableSignal(tree.$.user);
|
|
1521
|
+
userGroupControl.connect(userSignal);
|
|
1522
|
+
```
|
|
1523
|
+
|
|
1524
|
+
The `@signaltree/ng-forms` package supports Angular 17+ and will prefer `connect()` when available (Angular 20.3+). Angular 17-19 uses a legacy bridge that will be deprecated when Angular 21 is released.
|
|
1474
1525
|
|
|
1475
1526
|
**Quick Example:**
|
|
1476
1527
|
|
|
@@ -1479,7 +1530,7 @@ import { signalTree } from '@signaltree/core';
|
|
|
1479
1530
|
import { bindFormToTree } from '@signaltree/ng-forms';
|
|
1480
1531
|
|
|
1481
1532
|
const tree = signalTree({
|
|
1482
|
-
user: { name: '', email: '', age: 0 }
|
|
1533
|
+
user: { name: '', email: '', age: 0 },
|
|
1483
1534
|
});
|
|
1484
1535
|
|
|
1485
1536
|
@Component({
|
|
@@ -1489,13 +1540,13 @@ const tree = signalTree({
|
|
|
1489
1540
|
<input formControlName="email" type="email" />
|
|
1490
1541
|
<input formControlName="age" type="number" />
|
|
1491
1542
|
</form>
|
|
1492
|
-
|
|
1543
|
+
`,
|
|
1493
1544
|
})
|
|
1494
1545
|
class UserFormComponent {
|
|
1495
1546
|
form = new FormGroup({
|
|
1496
1547
|
name: new FormControl(''),
|
|
1497
1548
|
email: new FormControl(''),
|
|
1498
|
-
age: new FormControl(0)
|
|
1549
|
+
age: new FormControl(0),
|
|
1499
1550
|
});
|
|
1500
1551
|
|
|
1501
1552
|
constructor() {
|
|
@@ -1506,6 +1557,7 @@ class UserFormComponent {
|
|
|
1506
1557
|
```
|
|
1507
1558
|
|
|
1508
1559
|
**When to use:**
|
|
1560
|
+
|
|
1509
1561
|
- Building forms with Angular Reactive Forms
|
|
1510
1562
|
- Need validation integration
|
|
1511
1563
|
- Two-way data binding between forms and state
|
|
@@ -1526,6 +1578,7 @@ npm install @signaltree/enterprise
|
|
|
1526
1578
|
```
|
|
1527
1579
|
|
|
1528
1580
|
**Features:**
|
|
1581
|
+
|
|
1529
1582
|
- ⚡ PathIndex for O(k) lookup time regardless of tree size
|
|
1530
1583
|
- 🗜️ Advanced memory optimization algorithms
|
|
1531
1584
|
- 📊 Performance profiling and monitoring
|
|
@@ -1541,24 +1594,34 @@ import { withEnterpriseOptimizations } from '@signaltree/enterprise';
|
|
|
1541
1594
|
const tree = signalTree({
|
|
1542
1595
|
// Large application state with hundreds of signals
|
|
1543
1596
|
modules: {
|
|
1544
|
-
auth: {
|
|
1545
|
-
|
|
1546
|
-
|
|
1597
|
+
auth: {
|
|
1598
|
+
/* ... */
|
|
1599
|
+
},
|
|
1600
|
+
data: {
|
|
1601
|
+
/* ... */
|
|
1602
|
+
},
|
|
1603
|
+
ui: {
|
|
1604
|
+
/* ... */
|
|
1605
|
+
},
|
|
1547
1606
|
// ... many more modules
|
|
1548
|
-
}
|
|
1549
|
-
}).with(
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1607
|
+
},
|
|
1608
|
+
}).with(
|
|
1609
|
+
withEnterpriseOptimizations({
|
|
1610
|
+
enablePathIndex: true,
|
|
1611
|
+
enableMemoryOptimizations: true,
|
|
1612
|
+
enablePerformanceMonitoring: true,
|
|
1613
|
+
})
|
|
1614
|
+
);
|
|
1554
1615
|
```
|
|
1555
1616
|
|
|
1556
1617
|
**Performance Benefits:**
|
|
1618
|
+
|
|
1557
1619
|
- **Constant-time lookups:** O(k) lookup where k is path depth, not total signal count
|
|
1558
1620
|
- **Memory efficiency:** Up to 40% reduction in memory usage for large trees
|
|
1559
1621
|
- **Faster updates:** Optimized update batching for high-frequency scenarios
|
|
1560
1622
|
|
|
1561
1623
|
**When to use:**
|
|
1624
|
+
|
|
1562
1625
|
- Applications with 500+ signals
|
|
1563
1626
|
- Complex nested state structures (10+ levels deep)
|
|
1564
1627
|
- High-frequency state updates
|
|
@@ -1580,6 +1643,7 @@ npm install --save-dev @signaltree/guardrails
|
|
|
1580
1643
|
```
|
|
1581
1644
|
|
|
1582
1645
|
**Features:**
|
|
1646
|
+
|
|
1583
1647
|
- 🔥 Hot-path detection - identifies frequently accessed signals
|
|
1584
1648
|
- 💾 Memory leak detection - tracks signal cleanup issues
|
|
1585
1649
|
- 📊 Performance budgets - enforces performance thresholds
|
|
@@ -1595,15 +1659,17 @@ import { withGuardrails } from '@signaltree/guardrails';
|
|
|
1595
1659
|
|
|
1596
1660
|
const tree = signalTree({
|
|
1597
1661
|
users: [] as User[],
|
|
1598
|
-
posts: [] as Post[]
|
|
1599
|
-
}).with(
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
}
|
|
1662
|
+
posts: [] as Post[],
|
|
1663
|
+
}).with(
|
|
1664
|
+
withGuardrails({
|
|
1665
|
+
hotPathThreshold: 100, // Warn if signal accessed >100 times/sec
|
|
1666
|
+
memoryLeakThreshold: 50, // Warn if >50 uncleaned signals
|
|
1667
|
+
budgets: {
|
|
1668
|
+
updateTime: 16, // Warn if updates take >16ms
|
|
1669
|
+
signalCount: 1000, // Warn if >1000 signals created
|
|
1670
|
+
},
|
|
1671
|
+
})
|
|
1672
|
+
);
|
|
1607
1673
|
|
|
1608
1674
|
// Development warnings will appear in console
|
|
1609
1675
|
// Production builds get no-op functions (0 overhead)
|
|
@@ -1646,6 +1712,7 @@ if (leaks.length > 0) {
|
|
|
1646
1712
|
In production builds, all guardrails functions become no-ops with zero runtime cost.
|
|
1647
1713
|
|
|
1648
1714
|
**When to use:**
|
|
1715
|
+
|
|
1649
1716
|
- During active development
|
|
1650
1717
|
- Performance optimization phase
|
|
1651
1718
|
- Debugging state management issues
|
|
@@ -1667,6 +1734,7 @@ npm install --save-dev @signaltree/callable-syntax
|
|
|
1667
1734
|
```
|
|
1668
1735
|
|
|
1669
1736
|
**Features:**
|
|
1737
|
+
|
|
1670
1738
|
- 🍬 Syntactic sugar for signal updates
|
|
1671
1739
|
- ⚡ Zero runtime overhead (build-time transform)
|
|
1672
1740
|
- ✅ Full TypeScript type safety
|
|
@@ -1677,11 +1745,11 @@ npm install --save-dev @signaltree/callable-syntax
|
|
|
1677
1745
|
|
|
1678
1746
|
```typescript
|
|
1679
1747
|
// With callable-syntax transform
|
|
1680
|
-
tree.$.name('Jane');
|
|
1681
|
-
tree.$.count((n) => n + 1);
|
|
1748
|
+
tree.$.name('Jane'); // Transformed to: tree.$.name.set('Jane')
|
|
1749
|
+
tree.$.count((n) => n + 1); // Transformed to: tree.$.count.update((n) => n + 1)
|
|
1682
1750
|
|
|
1683
1751
|
// Reading always works directly (no transform needed)
|
|
1684
|
-
const name = tree.$.name();
|
|
1752
|
+
const name = tree.$.name(); // Direct Angular signal API
|
|
1685
1753
|
```
|
|
1686
1754
|
|
|
1687
1755
|
**Setup (tsconfig.json):**
|
|
@@ -1689,9 +1757,7 @@ const name = tree.$.name(); // Direct Angular signal API
|
|
|
1689
1757
|
```json
|
|
1690
1758
|
{
|
|
1691
1759
|
"compilerOptions": {
|
|
1692
|
-
"plugins": [
|
|
1693
|
-
{ "transform": "@signaltree/callable-syntax" }
|
|
1694
|
-
]
|
|
1760
|
+
"plugins": [{ "transform": "@signaltree/callable-syntax" }]
|
|
1695
1761
|
}
|
|
1696
1762
|
}
|
|
1697
1763
|
```
|
|
@@ -1702,25 +1768,26 @@ const name = tree.$.name(); // Direct Angular signal API
|
|
|
1702
1768
|
import { callableSyntaxTransform } from '@signaltree/callable-syntax/rollup';
|
|
1703
1769
|
|
|
1704
1770
|
export default {
|
|
1705
|
-
plugins: [
|
|
1706
|
-
callableSyntaxTransform()
|
|
1707
|
-
]
|
|
1771
|
+
plugins: [callableSyntaxTransform()],
|
|
1708
1772
|
};
|
|
1709
1773
|
```
|
|
1710
1774
|
|
|
1711
1775
|
**Important Notes:**
|
|
1776
|
+
|
|
1712
1777
|
- **Optional:** You can always use direct `.set(value)` or `.update(fn)` syntax
|
|
1713
1778
|
- **Build-time only:** No runtime code is added to your bundle
|
|
1714
1779
|
- **Function-valued leaves:** When storing functions, use `.set(fn)` directly
|
|
1715
1780
|
- **Type-safe:** Full TypeScript support via module augmentation
|
|
1716
1781
|
|
|
1717
1782
|
**When to use:**
|
|
1783
|
+
|
|
1718
1784
|
- Prefer shorter, more concise syntax
|
|
1719
1785
|
- Team convention favors callable style
|
|
1720
1786
|
- Migrating from other signal libraries with similar syntax
|
|
1721
1787
|
- Want familiar DX without runtime overhead
|
|
1722
1788
|
|
|
1723
1789
|
**When to skip:**
|
|
1790
|
+
|
|
1724
1791
|
- Team prefers explicit `.set/.update` syntax
|
|
1725
1792
|
- Build pipeline doesn't support transformers
|
|
1726
1793
|
- Storing functions as signal values (use direct `.set`)
|
|
@@ -1732,6 +1799,7 @@ export default {
|
|
|
1732
1799
|
## Package Selection Guide
|
|
1733
1800
|
|
|
1734
1801
|
**Start with just `@signaltree/core`** - it includes comprehensive enhancers for most applications:
|
|
1802
|
+
|
|
1735
1803
|
- Performance optimization (batching, memoization)
|
|
1736
1804
|
- Data management (entities, async operations)
|
|
1737
1805
|
- Development tools (devtools, time-travel)
|
|
@@ -1739,12 +1807,12 @@ export default {
|
|
|
1739
1807
|
|
|
1740
1808
|
**Add companion packages when you need:**
|
|
1741
1809
|
|
|
1742
|
-
| Package
|
|
1743
|
-
|
|
1744
|
-
| `@signaltree/ng-forms`
|
|
1745
|
-
| `@signaltree/enterprise`
|
|
1746
|
-
| `@signaltree/guardrails`
|
|
1747
|
-
| `@signaltree/callable-syntax` | Prefer callable syntax sugar
|
|
1810
|
+
| Package | When to Add | Bundle Impact |
|
|
1811
|
+
| ----------------------------- | ---------------------------------- | ---------------- |
|
|
1812
|
+
| `@signaltree/ng-forms` | Angular Reactive Forms integration | ~10KB gzipped |
|
|
1813
|
+
| `@signaltree/enterprise` | 500+ signals, large-scale apps | ~8KB gzipped |
|
|
1814
|
+
| `@signaltree/guardrails` | Development performance monitoring | 0KB (dev-only) |
|
|
1815
|
+
| `@signaltree/callable-syntax` | Prefer callable syntax sugar | 0KB (build-time) |
|
|
1748
1816
|
|
|
1749
1817
|
**Typical Installation Patterns:**
|
|
1750
1818
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const globalStructuredClone = typeof globalThis === 'object' && globalThis !== null ? globalThis.structuredClone : undefined;
|
|
2
|
+
function deepClone(value) {
|
|
3
|
+
if (globalStructuredClone) {
|
|
4
|
+
try {
|
|
5
|
+
return globalStructuredClone(value);
|
|
6
|
+
} catch (_a) {}
|
|
7
|
+
}
|
|
8
|
+
return cloneValue(value, new WeakMap());
|
|
9
|
+
}
|
|
10
|
+
function cloneValue(value, seen) {
|
|
11
|
+
if (value === null || typeof value !== 'object') {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === 'function') {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const existing = seen.get(value);
|
|
18
|
+
if (existing) {
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
if (value instanceof Date) {
|
|
22
|
+
return new Date(value.getTime());
|
|
23
|
+
}
|
|
24
|
+
if (value instanceof RegExp) {
|
|
25
|
+
return new RegExp(value.source, value.flags);
|
|
26
|
+
}
|
|
27
|
+
if (value instanceof Map) {
|
|
28
|
+
const result = new Map();
|
|
29
|
+
seen.set(value, result);
|
|
30
|
+
for (const [key, entryValue] of value) {
|
|
31
|
+
result.set(cloneValue(key, seen), cloneValue(entryValue, seen));
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (value instanceof Set) {
|
|
36
|
+
const result = new Set();
|
|
37
|
+
seen.set(value, result);
|
|
38
|
+
for (const entry of value) {
|
|
39
|
+
result.add(cloneValue(entry, seen));
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
const result = new Array(value.length);
|
|
45
|
+
seen.set(value, result);
|
|
46
|
+
for (let i = 0; i < value.length; i++) {
|
|
47
|
+
result[i] = cloneValue(value[i], seen);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
if (ArrayBuffer.isView(value)) {
|
|
52
|
+
if (value instanceof DataView) {
|
|
53
|
+
const bufferClone = cloneValue(value.buffer, seen);
|
|
54
|
+
return new DataView(bufferClone, value.byteOffset, value.byteLength);
|
|
55
|
+
}
|
|
56
|
+
const viewWithSlice = value;
|
|
57
|
+
if (typeof viewWithSlice.slice === 'function') {
|
|
58
|
+
return viewWithSlice.slice();
|
|
59
|
+
}
|
|
60
|
+
const bufferClone = cloneValue(value.buffer, seen);
|
|
61
|
+
return new value.constructor(bufferClone, value.byteOffset, value.length);
|
|
62
|
+
}
|
|
63
|
+
if (value instanceof ArrayBuffer) {
|
|
64
|
+
return value.slice(0);
|
|
65
|
+
}
|
|
66
|
+
const proto = Object.getPrototypeOf(value);
|
|
67
|
+
const result = proto ? Object.create(proto) : {};
|
|
68
|
+
seen.set(value, result);
|
|
69
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
70
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
71
|
+
if (!descriptor) continue;
|
|
72
|
+
if ('value' in descriptor) {
|
|
73
|
+
descriptor.value = cloneValue(descriptor.value, seen);
|
|
74
|
+
}
|
|
75
|
+
Object.defineProperty(result, key, descriptor);
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { deepClone };
|
|
@@ -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 };
|