mutts 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/chunks/{decorator-BXsign4Z.js → decorator-8qjFb7dw.js} +2 -2
  2. package/dist/chunks/decorator-8qjFb7dw.js.map +1 -0
  3. package/dist/chunks/{decorator-CPbZNnsX.esm.js → decorator-AbRkXM5O.esm.js} +2 -2
  4. package/dist/chunks/decorator-AbRkXM5O.esm.js.map +1 -0
  5. package/dist/decorator.d.ts +1 -1
  6. package/dist/decorator.esm.js +1 -1
  7. package/dist/decorator.js +1 -1
  8. package/dist/destroyable.esm.js +1 -1
  9. package/dist/destroyable.js +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.esm.js +2 -2
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/mutts.umd.js +1 -1
  15. package/dist/mutts.umd.js.map +1 -1
  16. package/dist/mutts.umd.min.js +1 -1
  17. package/dist/mutts.umd.min.js.map +1 -1
  18. package/dist/reactive.d.ts +4 -3
  19. package/dist/reactive.esm.js +61 -57
  20. package/dist/reactive.esm.js.map +1 -1
  21. package/dist/reactive.js +61 -56
  22. package/dist/reactive.js.map +1 -1
  23. package/dist/std-decorators.esm.js +1 -1
  24. package/dist/std-decorators.js +1 -1
  25. package/docs/reactive.md +616 -0
  26. package/package.json +1 -2
  27. package/dist/chunks/decorator-BXsign4Z.js.map +0 -1
  28. package/dist/chunks/decorator-CPbZNnsX.esm.js.map +0 -1
  29. package/src/decorator.test.ts +0 -495
  30. package/src/decorator.ts +0 -205
  31. package/src/destroyable.test.ts +0 -155
  32. package/src/destroyable.ts +0 -158
  33. package/src/eventful.test.ts +0 -380
  34. package/src/eventful.ts +0 -69
  35. package/src/index.ts +0 -7
  36. package/src/indexable.test.ts +0 -388
  37. package/src/indexable.ts +0 -124
  38. package/src/promiseChain.test.ts +0 -201
  39. package/src/promiseChain.ts +0 -99
  40. package/src/reactive/array.test.ts +0 -923
  41. package/src/reactive/array.ts +0 -352
  42. package/src/reactive/core.test.ts +0 -1663
  43. package/src/reactive/core.ts +0 -866
  44. package/src/reactive/index.ts +0 -28
  45. package/src/reactive/interface.test.ts +0 -1477
  46. package/src/reactive/interface.ts +0 -231
  47. package/src/reactive/map.test.ts +0 -866
  48. package/src/reactive/map.ts +0 -162
  49. package/src/reactive/set.test.ts +0 -289
  50. package/src/reactive/set.ts +0 -142
  51. package/src/std-decorators.test.ts +0 -679
  52. package/src/std-decorators.ts +0 -182
  53. package/src/utils.ts +0 -52
package/docs/reactive.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Reactive Documentation
2
2
 
3
+ ## Table of Contents
4
+
5
+ - [Introduction](#introduction)
6
+ - [Getting Started](#getting-started)
7
+ - [Core API](#core-api)
8
+ - [Effect System](#effect-system)
9
+ - [Evolution Tracking](#evolution-tracking)
10
+ - [Collections](#collections)
11
+ - [ReactiveArray](#reactivearray)
12
+ - [Class Reactivity](#class-reactivity)
13
+ - [Non-Reactive System](#non-reactive-system)
14
+ - [Computed Properties](#computed-properties)
15
+ - [Atomic Operations](#atomic-operations)
16
+ - [Advanced Patterns](#advanced-patterns)
17
+ - [Debugging and Development](#debugging-and-development)
18
+ - [API Reference](#api-reference)
3
19
 
4
20
  ## Introduction
5
21
 
@@ -12,6 +28,7 @@ Reactivity is a programming paradigm where the system automatically tracks depen
12
28
  - **Reactive Objects**: Plain JavaScript objects wrapped with reactive capabilities
13
29
  - **Effects**: Functions that automatically re-run when their dependencies change
14
30
  - **Dependencies**: Reactive properties that an effect depends on
31
+ - **Atomic Operations**: Batching multiple state changes to execute effects only once
15
32
  - **Evolution Tracking**: Built-in change history for reactive objects
16
33
  - **Collections**: Reactive wrappers for Array, Map, Set, WeakMap, and WeakSet
17
34
 
@@ -1509,6 +1526,544 @@ state.count = 5
1509
1526
  // effect logs and updates title
1510
1527
  ```
1511
1528
 
1529
+ ## Atomic Operations
1530
+
1531
+ The `atomic` function and `@atomic` decorator are powerful tools for batching reactive effects. When applied to a method or function, they ensure that all effects triggered by reactive state changes within that scope are batched together and executed only once, rather than after each individual change.
1532
+
1533
+ ### Overview
1534
+
1535
+ In reactive systems, each state change typically triggers its dependent effects immediately. However, when you need to make multiple related changes as a single unit, this can lead to:
1536
+
1537
+ - Multiple unnecessary effect executions
1538
+ - Inconsistent intermediate states being observed
1539
+ - Performance overhead from redundant computations
1540
+
1541
+ The `atomic` function and `@atomic` decorator solve this by deferring effect execution until the function or method completes, treating all changes as a single atomic operation.
1542
+
1543
+ ### Basic Usage
1544
+
1545
+ #### Decorator Syntax
1546
+
1547
+ ```typescript
1548
+ import { reactive, effect, atomic } from 'mutts'
1549
+
1550
+ const state = reactive({ a: 0, b: 0, c: 0 })
1551
+ let effectCount = 0
1552
+
1553
+ effect(() => {
1554
+ effectCount++
1555
+ console.log(`Effect ran: a=${state.a}, b=${state.b}, c=${state.c}`)
1556
+ })
1557
+
1558
+ class StateManager {
1559
+ @atomic
1560
+ updateAll() {
1561
+ state.a = 1
1562
+ state.b = 2
1563
+ state.c = 3
1564
+ }
1565
+ }
1566
+
1567
+ const manager = new StateManager()
1568
+ manager.updateAll()
1569
+
1570
+ // Output:
1571
+ // Effect ran: a=0, b=0, c=0 (initial run)
1572
+ // Effect ran: a=1, b=2, c=3 (only one additional run despite 3 changes)
1573
+ ```
1574
+
1575
+ #### Function Syntax
1576
+
1577
+ For standalone functions or when you need more flexibility, you can use the `atomic` function directly:
1578
+
1579
+ ```typescript
1580
+ import { reactive, effect, atomic } from 'mutts'
1581
+
1582
+ const state = reactive({ a: 0, b: 0, c: 0 })
1583
+ let effectCount = 0
1584
+
1585
+ effect(() => {
1586
+ effectCount++
1587
+ console.log(`Effect ran: a=${state.a}, b=${state.b}, c=${state.c}`)
1588
+ })
1589
+
1590
+ // Using atomic function
1591
+ atomic(() => {
1592
+ state.a = 1
1593
+ state.b = 2
1594
+ state.c = 3
1595
+ })
1596
+
1597
+ // Output:
1598
+ // Effect ran: a=0, b=0, c=0 (initial run)
1599
+ // Effect ran: a=1, b=2, c=3 (only one additional run despite 3 changes)
1600
+ ```
1601
+
1602
+ #### Returning Values
1603
+
1604
+ The atomic function can return values:
1605
+
1606
+ ```typescript
1607
+ const result = atomic(() => {
1608
+ state.a = 10
1609
+ state.b = 20
1610
+ return state.a + state.b
1611
+ })
1612
+
1613
+ console.log(result) // 30
1614
+ ```
1615
+
1616
+ #### Atomic Method Return Values
1617
+
1618
+ The `@atomic` decorator also supports return values from methods:
1619
+
1620
+ ```typescript
1621
+ @reactive
1622
+ class Calculator {
1623
+ @atomic
1624
+ updateAndCalculate(a: number, b: number) {
1625
+ this.a = a
1626
+ this.b = b
1627
+ return { sum: a + b, product: a * b }
1628
+ }
1629
+ }
1630
+
1631
+ const calc = new Calculator()
1632
+ const result = calc.updateAndCalculate(5, 10)
1633
+ console.log(result) // { sum: 15, product: 50 }
1634
+ ```
1635
+
1636
+ **Key Points:**
1637
+ - Atomic methods can return any value type (primitives, objects, functions)
1638
+ - Return values are computed during method execution
1639
+ - Effects are batched until the method completes, regardless of return values
1640
+ - Both read-only and state-modifying methods can return values
1641
+
1642
+ ```typescript
1643
+ @reactive
1644
+ class DataProcessor {
1645
+ @atomic
1646
+ processData(items: Item[]) {
1647
+ // Read-only method with return value
1648
+ const total = items.reduce((sum, item) => sum + item.value, 0)
1649
+ return total
1650
+ }
1651
+
1652
+ @atomic
1653
+ updateAndProcess(items: Item[]) {
1654
+ // State-modifying method with return value
1655
+ this.items = items
1656
+ this.processedCount = items.length
1657
+ this.lastProcessed = new Date()
1658
+
1659
+ return {
1660
+ count: items.length,
1661
+ total: items.reduce((sum, item) => sum + item.value, 0)
1662
+ }
1663
+ }
1664
+ }
1665
+ ```
1666
+
1667
+ ### When to Use Each Approach
1668
+
1669
+ #### Use `@atomic` Decorator When:
1670
+ - You're working with class methods
1671
+ - You want to declare atomic behavior at the method level
1672
+ - You prefer declarative syntax
1673
+ - The method is part of a reactive class
1674
+
1675
+ ```typescript
1676
+ @reactive
1677
+ class TodoManager {
1678
+ @atomic
1679
+ addTodo(text: string) {
1680
+ this.todos.push({ id: Date.now(), text, completed: false })
1681
+ this.updateStats()
1682
+ }
1683
+ }
1684
+ ```
1685
+
1686
+ #### Use `atomic()` Function When:
1687
+ - You need atomic behavior for standalone functions
1688
+ - You're working with functional code
1689
+ - You need to conditionally apply atomic behavior
1690
+ - You're working outside of classes
1691
+
1692
+ ```typescript
1693
+ // Conditional atomic behavior
1694
+ const updateState = (shouldBatch: boolean) => {
1695
+ const updateFn = () => {
1696
+ state.a = 1
1697
+ state.b = 2
1698
+ }
1699
+
1700
+ return shouldBatch ? atomic(updateFn) : updateFn()
1701
+ }
1702
+
1703
+ // Functional approach
1704
+ const processItems = (items: Item[]) => {
1705
+ return atomic(() => {
1706
+ items.forEach(item => state.items.set(item.id, item))
1707
+ state.count = state.items.size
1708
+ return state.count
1709
+ })
1710
+ }
1711
+ ```
1712
+
1713
+ ### Key Behaviors
1714
+
1715
+ #### Immediate Execution, Batched Effects
1716
+
1717
+ The decorated method executes immediately, but effects are deferred until completion:
1718
+
1719
+ ```typescript
1720
+ class TestClass {
1721
+ @atomic
1722
+ updateAndLog() {
1723
+ console.log('Method starts')
1724
+ state.a = 1
1725
+ console.log('After setting a')
1726
+ state.b = 2
1727
+ console.log('After setting b')
1728
+ console.log('Method ends')
1729
+ }
1730
+ }
1731
+
1732
+ // Execution order:
1733
+ // Method starts
1734
+ // After setting a
1735
+ // After setting b
1736
+ // Method ends
1737
+ // Effect runs with final values
1738
+ ```
1739
+
1740
+ #### Nested Atomic Methods
1741
+
1742
+ Multiple atomic methods can be nested, and all effects are batched at the outermost level:
1743
+
1744
+ ```typescript
1745
+ class TestClass {
1746
+ @atomic
1747
+ updateA() {
1748
+ state.a = 1
1749
+ }
1750
+
1751
+ @atomic
1752
+ updateB() {
1753
+ state.b = 2
1754
+ }
1755
+
1756
+ @atomic
1757
+ updateAll() {
1758
+ this.updateA()
1759
+ this.updateB()
1760
+ state.c = 3
1761
+ }
1762
+ }
1763
+
1764
+ // Calling updateAll() will batch all effects from updateA, updateB, and the direct assignment
1765
+ ```
1766
+
1767
+ #### Cascading Effects
1768
+
1769
+ Effects that trigger other effects are also batched within atomic methods:
1770
+
1771
+ ```typescript
1772
+ // Create cascading effects
1773
+ effect(() => {
1774
+ state.b = state.a * 2
1775
+ })
1776
+ effect(() => {
1777
+ state.c = state.b + 1
1778
+ })
1779
+
1780
+ class TestClass {
1781
+ @atomic
1782
+ triggerCascade() {
1783
+ state.a = 5 // This triggers the cascade
1784
+ }
1785
+ }
1786
+
1787
+ // All cascading effects are batched together
1788
+ ```
1789
+
1790
+ ### Advanced Usage
1791
+
1792
+ #### Working with Reactive Classes
1793
+
1794
+ The `@atomic` decorator works seamlessly with `@reactive` classes:
1795
+
1796
+ ```typescript
1797
+ @reactive
1798
+ class Counter {
1799
+ value = 0
1800
+ multiplier = 1
1801
+
1802
+ @atomic
1803
+ updateBoth(newValue: number, newMultiplier: number) {
1804
+ this.value = newValue
1805
+ this.multiplier = newMultiplier
1806
+ }
1807
+ }
1808
+
1809
+ const counter = new Counter()
1810
+ let effectCount = 0
1811
+
1812
+ effect(() => {
1813
+ effectCount++
1814
+ console.log(`Counter: ${counter.value} * ${counter.multiplier}`)
1815
+ })
1816
+
1817
+ counter.updateBoth(5, 2)
1818
+ // Effect runs only once despite two property changes
1819
+ ```
1820
+
1821
+ #### Complex Data Structures
1822
+
1823
+ Atomic methods work with arrays, maps, sets, and other complex data structures:
1824
+
1825
+ ```typescript
1826
+ const state = reactive({
1827
+ items: [1, 2, 3],
1828
+ metadata: new Map([['count', 3]]),
1829
+ tags: new Set(['active'])
1830
+ })
1831
+
1832
+ class DataManager {
1833
+ @atomic
1834
+ addItem(item: number) {
1835
+ state.items.push(item)
1836
+ state.metadata.set('count', state.items.length)
1837
+ state.tags.add('modified')
1838
+ }
1839
+
1840
+ @atomic
1841
+ clearAll() {
1842
+ state.items.length = 0
1843
+ state.metadata.clear()
1844
+ state.tags.clear()
1845
+ }
1846
+ }
1847
+ ```
1848
+
1849
+ #### Error Handling
1850
+
1851
+ If an atomic method throws an error, effects are still executed for the changes that were made before the error:
1852
+
1853
+ ```typescript
1854
+ class TestClass {
1855
+ @atomic
1856
+ updateWithError() {
1857
+ state.a = 1
1858
+ throw new Error('Something went wrong')
1859
+ // state.b = 2 // This line never executes
1860
+ }
1861
+ }
1862
+
1863
+ // Effect will run once for the change to state.a
1864
+ // state.b remains unchanged due to the error
1865
+ ```
1866
+
1867
+ #### Async Operations
1868
+
1869
+ Atomic methods can be async, but effects are still batched:
1870
+
1871
+ ```typescript
1872
+ class TestClass {
1873
+ @atomic
1874
+ async updateAsync() {
1875
+ state.a = 1
1876
+ await someAsyncOperation()
1877
+ state.b = 2
1878
+ }
1879
+ }
1880
+
1881
+ // Effects are batched even with async operations
1882
+ ```
1883
+
1884
+ ### Performance Benefits
1885
+
1886
+ #### Reduced Effect Executions
1887
+
1888
+ Without `atomic`:
1889
+ ```typescript
1890
+ // This would trigger the effect 3 times
1891
+ state.a = 1 // Effect runs
1892
+ state.b = 2 // Effect runs
1893
+ state.c = 3 // Effect runs
1894
+ ```
1895
+
1896
+ With `atomic`:
1897
+ ```typescript
1898
+ @atomic
1899
+ updateAll() {
1900
+ state.a = 1
1901
+ state.b = 2
1902
+ state.c = 3
1903
+ }
1904
+ // Effect runs only once
1905
+ ```
1906
+
1907
+ #### Consistent State
1908
+
1909
+ Atomic operations ensure that effects always see a consistent state:
1910
+
1911
+ ```typescript
1912
+ effect(() => {
1913
+ // This will never see inconsistent intermediate states
1914
+ if (state.a > 0 && state.b > 0) {
1915
+ console.log('Both values are positive')
1916
+ }
1917
+ })
1918
+
1919
+ @atomic
1920
+ updateBoth() {
1921
+ state.a = 1 // Effect doesn't run yet
1922
+ state.b = 2 // Effect doesn't run yet
1923
+ // Effect runs once with both values updated
1924
+ }
1925
+ ```
1926
+
1927
+ ### Best Practices
1928
+
1929
+ #### Use for Related Changes
1930
+
1931
+ Apply `atomic` to methods that make logically related changes:
1932
+
1933
+ ```typescript
1934
+ // Good: Related user profile updates
1935
+ @atomic
1936
+ updateProfile(name: string, age: number, email: string) {
1937
+ this.name = name
1938
+ this.age = age
1939
+ this.email = email
1940
+ }
1941
+
1942
+ // Good: Complex state initialization
1943
+ @atomic
1944
+ initialize() {
1945
+ this.loading = false
1946
+ this.data = fetchedData
1947
+ this.error = null
1948
+ this.lastUpdated = new Date()
1949
+ }
1950
+ ```
1951
+
1952
+ #### Combine with Other Decorators
1953
+
1954
+ The `@atomic` decorator works well with other decorators:
1955
+
1956
+ ```typescript
1957
+ @reactive
1958
+ class UserManager {
1959
+ @atomic
1960
+ updateUser(id: string, updates: Partial<User>) {
1961
+ this.users.set(id, { ...this.users.get(id), ...updates })
1962
+ this.lastModified = new Date()
1963
+ }
1964
+ }
1965
+ ```
1966
+
1967
+ #### Consider Performance
1968
+
1969
+ For methods that make many changes, `atomic` provides significant performance benefits:
1970
+
1971
+ ```typescript
1972
+ @atomic
1973
+ updateManyItems(items: Item[]) {
1974
+ for (const item of items) {
1975
+ this.items.set(item.id, item)
1976
+ }
1977
+ this.count = this.items.size
1978
+ this.lastUpdate = new Date()
1979
+ }
1980
+ // Without @atomic: effect would run for each item + count + timestamp
1981
+ // With @atomic: effect runs only once
1982
+ ```
1983
+
1984
+ ### Limitations
1985
+
1986
+ - **Method-only**: The decorator only works on class methods, not standalone functions (use `atomic()` function instead)
1987
+ - **Synchronous batching**: Effects are batched until the method completes, but async operations within the method don't affect the batching
1988
+ - **Error handling**: If a method throws, effects still run for changes made before the error
1989
+
1990
+ ### Integration
1991
+
1992
+ The `atomic` function and `@atomic` decorator integrate seamlessly with:
1993
+
1994
+ - `@reactive` classes
1995
+ - `@unreactive` property marking
1996
+ - `effect()` functions
1997
+ - `computed()` values
1998
+ - Native collection types (Array, Map, Set, etc.)
1999
+
2000
+ ### Complete Example
2001
+
2002
+ ```typescript
2003
+ import { reactive, effect, atomic } from 'mutts'
2004
+
2005
+ @reactive
2006
+ class TodoManager {
2007
+ todos: Todo[] = []
2008
+ filter: 'all' | 'active' | 'completed' = 'all'
2009
+ loading = false
2010
+
2011
+ @atomic
2012
+ addTodo(text: string) {
2013
+ const todo: Todo = {
2014
+ id: Date.now().toString(),
2015
+ text,
2016
+ completed: false,
2017
+ createdAt: new Date()
2018
+ }
2019
+ this.todos.push(todo)
2020
+ this.updateStats()
2021
+ }
2022
+
2023
+ @atomic
2024
+ toggleTodo(id: string) {
2025
+ const todo = this.todos.find(t => t.id === id)
2026
+ if (todo) {
2027
+ todo.completed = !todo.completed
2028
+ this.updateStats()
2029
+ }
2030
+ }
2031
+
2032
+ @atomic
2033
+ setFilter(filter: 'all' | 'active' | 'completed') {
2034
+ this.filter = filter
2035
+ this.loading = false
2036
+ }
2037
+
2038
+ private updateStats() {
2039
+ // This method is called from within atomic methods
2040
+ // Effects will be batched until the calling atomic method completes
2041
+ const activeCount = this.todos.filter(t => !t.completed).length
2042
+ const completedCount = this.todos.length - activeCount
2043
+
2044
+ // Update derived state
2045
+ this.activeCount = activeCount
2046
+ this.completedCount = completedCount
2047
+ this.allCompleted = completedCount === this.todos.length && this.todos.length > 0
2048
+ }
2049
+ }
2050
+
2051
+ // Usage
2052
+ const todoManager = new TodoManager()
2053
+
2054
+ effect(() => {
2055
+ console.log(`Active: ${todoManager.activeCount}, Completed: ${todoManager.completedCount}`)
2056
+ })
2057
+
2058
+ // Adding a todo triggers updateStats, but effect runs only once
2059
+ todoManager.addTodo('Learn MutTs atomic operations')
2060
+
2061
+ // Toggling a todo also triggers updateStats, effect runs only once
2062
+ todoManager.toggleTodo('some-id')
2063
+ ```
2064
+
2065
+ This example demonstrates how `atomic` ensures that complex state updates are treated as single, consistent operations, improving both performance and reliability.
2066
+
1512
2067
  ## Advanced Patterns
1513
2068
 
1514
2069
  ### Custom Reactive Objects
@@ -1738,6 +2293,49 @@ const result = computed(myExpensiveCalculus);
1738
2293
  By how JS works, writing `computed(()=> ...)` will always be wrong, as the notation `()=> ...` internally is a `new Function(...)`.
1739
2294
  So, even if the return value is cached, it will never be used.
1740
2295
 
2296
+ #### `@atomic`
2297
+
2298
+ Marks a class method as atomic, batching all effects triggered within the method until it completes.
2299
+
2300
+ ```typescript
2301
+ class MyClass {
2302
+ @atomic
2303
+ updateMultiple() {
2304
+ this.a = 1
2305
+ this.b = 2
2306
+ this.c = 3
2307
+ // Effects are batched and run only once after this method completes
2308
+ }
2309
+
2310
+ @atomic
2311
+ updateAndReturn() {
2312
+ this.a = 10
2313
+ this.b = 20
2314
+ return { sum: this.a + this.b, product: this.a * this.b }
2315
+ }
2316
+ }
2317
+
2318
+ // Function usage
2319
+ const result = atomic(() => {
2320
+ state.a = 1
2321
+ state.b = 2
2322
+ return state.a + state.b
2323
+ })
2324
+ ```
2325
+
2326
+ **Use Cases:**
2327
+ - Batching multiple related state changes
2328
+ - Performance optimization for methods with multiple updates
2329
+ - Ensuring consistent state in effects
2330
+ - Reducing unnecessary effect executions
2331
+ - Returning computed values from atomic operations
2332
+
2333
+ **Notes:**
2334
+ - Effects are deferred until the method/function completes
2335
+ - Nested atomic methods are batched at the outermost level
2336
+ - Works with both class methods and standalone functions
2337
+ - Methods and functions can return values (primitives, objects, functions)
2338
+
1741
2339
  #### `@unreactive`
1742
2340
 
1743
2341
  Marks a class property as non-reactive. The property change will not be tracked by the reactive system.
@@ -1803,6 +2401,24 @@ const cleanup = effect((dep) => {
1803
2401
  });
1804
2402
  ```
1805
2403
 
2404
+ #### `atomic<T>(fn: () => T): T`
2405
+
2406
+ Creates an atomic operation that batches all effects triggered within the function until it completes.
2407
+
2408
+ **Use Cases:**
2409
+ - Batching multiple related state changes
2410
+ - Performance optimization for functions with multiple updates
2411
+ - Ensuring consistent state in effects
2412
+ - Reducing unnecessary effect executions
2413
+
2414
+ ```typescript
2415
+ const result = atomic(() => {
2416
+ state.a = 1
2417
+ state.b = 2
2418
+ return state.a + state.b
2419
+ })
2420
+ ```
2421
+
1806
2422
  #### `computed<T>(getter: ComputedFunction<T>): T`
1807
2423
 
1808
2424
  Creates a computed value that caches its result and recomputes when dependencies change.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mutts",
3
3
  "description": "Modern UTility TS: A collection of TypeScript utilities",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
@@ -70,7 +70,6 @@
70
70
  },
71
71
  "files": [
72
72
  "dist",
73
- "src",
74
73
  "README.md",
75
74
  "docs"
76
75
  ],
@@ -1 +0,0 @@
1
- {"version":3,"file":"decorator-BXsign4Z.js","sources":["../../src/utils.ts","../../src/decorator.ts"],"sourcesContent":["type ElementTypes<T extends readonly unknown[]> = {\n\t[K in keyof T]: T[K] extends readonly (infer U)[] ? U : T[K]\n}\n\nexport function zip<T extends (readonly unknown[])[]>(...args: T): ElementTypes<T>[] {\n\tif (!args.length) return []\n\tconst minLength = Math.min(...args.map((arr) => arr.length))\n\tconst result: ElementTypes<T>[] = []\n\n\tfor (let i = 0; i < minLength; i++) {\n\t\tconst tuple = args.map((arr) => arr[i]) as ElementTypes<T>\n\t\tresult.push(tuple)\n\t}\n\n\treturn result\n}\n\nconst nativeConstructors = new Set<Function>([\n\tObject,\n\tArray,\n\tDate,\n\tFunction,\n\tSet,\n\tMap,\n\tWeakMap,\n\tWeakSet,\n\tPromise,\n\tError,\n\tTypeError,\n\tReferenceError,\n\tSyntaxError,\n\tRangeError,\n\tURIError,\n\tEvalError,\n\tReflect,\n\tProxy,\n\tRegExp,\n\tString,\n\tNumber,\n\tBoolean,\n] as Function[])\nexport function isConstructor(fn: Function): boolean {\n\treturn fn && (nativeConstructors.has(fn) || fn.toString().startsWith('class '))\n}\n\nexport function renamed<F extends Function>(fct: F, name: string): F {\n\treturn Object.defineProperties(fct, {\n\t\tname: {\n\t\t\tvalue: name,\n\t\t},\n\t})\n}\n","// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids\n// Standardized decorator system that works with both Legacy and Modern decorators\n\nimport { isConstructor } from './utils'\n\nexport class DecoratorError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message)\n\t\tthis.name = 'DecoratorException'\n\t}\n}\n//#region all decorator types\n\n// Used for get/set and method decorators\nexport type LegacyPropertyDecorator<T> = (\n\ttarget: T,\n\tname: string | symbol,\n\tdescriptor: PropertyDescriptor\n) => any\n\nexport type LegacyClassDecorator<T> = (target: T) => any\n\nexport type ModernMethodDecorator<T> = (target: T, context: ClassMethodDecoratorContext) => any\n\nexport type ModernGetterDecorator<T> = (target: T, context: ClassGetterDecoratorContext) => any\n\nexport type ModernSetterDecorator<T> = (target: T, context: ClassSetterDecoratorContext) => any\n\nexport type ModernAccessorDecorator<T> = (target: T, context: ClassAccessorDecoratorContext) => any\n\nexport type ModernClassDecorator<T> = (target: T, context: ClassDecoratorContext) => any\n\n//#endregion\n\ntype DDMethod<T> = (\n\toriginal: (this: T, ...args: any[]) => any,\n\tname: PropertyKey\n) => ((this: T, ...args: any[]) => any) | void\n\ntype DDGetter<T> = (original: (this: T) => any, name: PropertyKey) => ((this: T) => any) | void\n\ntype DDSetter<T> = (\n\toriginal: (this: T, value: any) => void,\n\tname: PropertyKey\n) => ((this: T, value: any) => void) | void\n\ntype DDClass<T> = <Ctor extends new (...args: any[]) => T = new (...args: any[]) => T>(\n\ttarget: Ctor\n) => Ctor | void\nexport interface DecoratorDescription<T> {\n\tmethod?: DDMethod<T>\n\tclass?: DDClass<T>\n\tgetter?: DDGetter<T>\n\tsetter?: DDSetter<T>\n\tdefault?: (...args: any[]) => any\n}\n\nexport type Decorator<T, Description extends DecoratorDescription<T>> = (Description extends {\n\tmethod: DDMethod<T>\n}\n\t? LegacyPropertyDecorator<T> & ModernMethodDecorator<T>\n\t: unknown) &\n\t(Description extends { class: DDClass<new (...args: any[]) => T> }\n\t\t? LegacyClassDecorator<new (...args: any[]) => T> &\n\t\t\t\tModernClassDecorator<new (...args: any[]) => T>\n\t\t: unknown) &\n\t(Description extends { getter: DDGetter<T> }\n\t\t? LegacyPropertyDecorator<T> & ModernGetterDecorator<T> & ModernAccessorDecorator<T>\n\t\t: unknown) &\n\t(Description extends { setter: DDSetter<T> }\n\t\t? LegacyPropertyDecorator<T> & ModernSetterDecorator<T> & ModernAccessorDecorator<T>\n\t\t: unknown) &\n\t(Description extends { default: infer Signature } ? Signature : unknown)\n\nexport type DecoratorFactory<T> = <Description extends DecoratorDescription<T>>(\n\tdescription: Description\n) => (Description extends { method: DDMethod<T> }\n\t? LegacyPropertyDecorator<T> & ModernMethodDecorator<T>\n\t: unknown) &\n\t(Description extends { class: DDClass<new (...args: any[]) => T> }\n\t\t? LegacyClassDecorator<new (...args: any[]) => T> &\n\t\t\t\tModernClassDecorator<new (...args: any[]) => T>\n\t\t: unknown) &\n\t(Description extends { getter: DDGetter<T> }\n\t\t? LegacyPropertyDecorator<T> & ModernGetterDecorator<T> & ModernAccessorDecorator<T>\n\t\t: unknown) &\n\t(Description extends { setter: DDSetter<T> }\n\t\t? LegacyPropertyDecorator<T> & ModernSetterDecorator<T> & ModernAccessorDecorator<T>\n\t\t: unknown) &\n\t(Description extends { default: infer Signature } ? Signature : unknown)\n\nexport function legacyDecorator<T = any>(description: DecoratorDescription<T>): any {\n\treturn function (\n\t\ttarget: any,\n\t\tpropertyKey?: PropertyKey,\n\t\tdescriptor?: PropertyDescriptor,\n\t\t...args: any[]\n\t) {\n\t\tif (propertyKey === undefined) {\n\t\t\tif (isConstructor(target)) {\n\t\t\t\tif (!('class' in description)) throw new Error('Decorator cannot be applied to a class')\n\t\t\t\treturn description.class?.(target)\n\t\t\t}\n\t\t} else if (typeof target === 'object' && ['string', 'symbol'].includes(typeof propertyKey)) {\n\t\t\tif (!descriptor) throw new Error('Decorator cannot be applied to a field')\n\t\t\telse if (typeof descriptor === 'object' && 'configurable' in descriptor) {\n\t\t\t\tif ('get' in descriptor || 'set' in descriptor) {\n\t\t\t\t\tif (!('getter' in description || 'setter' in description))\n\t\t\t\t\t\tthrow new Error('Decorator cannot be applied to a getter or setter')\n\t\t\t\t\tif ('getter' in description) {\n\t\t\t\t\t\tconst newGetter = description.getter?.(descriptor.get, propertyKey)\n\t\t\t\t\t\tif (newGetter) descriptor.get = newGetter\n\t\t\t\t\t}\n\t\t\t\t\tif ('setter' in description) {\n\t\t\t\t\t\tconst newSetter = description.setter?.(descriptor.set, propertyKey)\n\t\t\t\t\t\tif (newSetter) descriptor.set = newSetter\n\t\t\t\t\t}\n\t\t\t\t\treturn descriptor\n\t\t\t\t} else if (typeof descriptor.value === 'function') {\n\t\t\t\t\tif (!('method' in description)) throw new Error('Decorator cannot be applied to a method')\n\t\t\t\t\tconst newMethod = description.method?.(descriptor.value, propertyKey)\n\t\t\t\t\tif (newMethod) descriptor.value = newMethod\n\t\t\t\t\treturn descriptor\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!('default' in description))\n\t\t\tthrow new Error('Decorator do not have a default implementation')\n\t\treturn description.default.call(this, target, propertyKey, descriptor, ...args)\n\t}\n}\n\nexport function modernDecorator<T = any>(description: DecoratorDescription<T>): any {\n\treturn function (target: any, context?: DecoratorContext, ...args: any[]) {\n\t\tif (!context?.kind || typeof context.kind !== 'string') {\n\t\t\tif (!('default' in description))\n\t\t\t\tthrow new Error('Decorator do not have a default implementation')\n\t\t\treturn description.default.call(this, target, context, ...args)\n\t\t}\n\t\tswitch (context.kind) {\n\t\t\tcase 'class':\n\t\t\t\tif (!('class' in description)) throw new Error('Decorator cannot be applied to a class')\n\t\t\t\treturn description.class?.(target)\n\t\t\tcase 'field':\n\t\t\t\tthrow new Error('Decorator cannot be applied to a field')\n\t\t\tcase 'getter':\n\t\t\t\tif (!('getter' in description)) throw new Error('Decorator cannot be applied to a getter')\n\t\t\t\treturn description.getter?.(target, context.name)\n\t\t\tcase 'setter':\n\t\t\t\tif (!('setter' in description)) throw new Error('Decorator cannot be applied to a setter')\n\t\t\t\treturn description.setter?.(target, context.name)\n\t\t\tcase 'method':\n\t\t\t\tif (!('method' in description)) throw new Error('Decorator cannot be applied to a method')\n\t\t\t\treturn description.method?.(target, context.name)\n\t\t\tcase 'accessor': {\n\t\t\t\tif (!('getter' in description || 'setter' in description))\n\t\t\t\t\tthrow new Error('Decorator cannot be applied to a getter or setter')\n\t\t\t\tconst rv: Partial<ClassAccessorDecoratorResult<any, any>> = {}\n\t\t\t\tif ('getter' in description) {\n\t\t\t\t\tconst newGetter = description.getter?.(target.get, context.name)\n\t\t\t\t\tif (newGetter) rv.get = newGetter\n\t\t\t\t}\n\t\t\t\tif ('setter' in description) {\n\t\t\t\t\tconst newSetter = description.setter?.(target.set, context.name)\n\t\t\t\t\tif (newSetter) rv.set = newSetter\n\t\t\t\t}\n\t\t\t\treturn rv\n\t\t\t}\n\t\t\t//return description.accessor?.(target, context.name, target)\n\t\t}\n\t}\n}\n\n/**\n * Detects if the decorator is being called in modern (Modern) or legacy (Legacy) mode\n * based on the arguments passed to the decorator function\n */\nfunction detectDecoratorMode(\n\t_target: any,\n\tcontextOrKey?: any,\n\t_descriptor?: any\n): 'modern' | 'legacy' {\n\t// Modern decorators have a context object as the second parameter\n\t// Legacy decorators have a string/symbol key as the second parameter\n\tif (\n\t\ttypeof contextOrKey === 'object' &&\n\t\tcontextOrKey !== null &&\n\t\ttypeof contextOrKey.kind === 'string'\n\t) {\n\t\treturn 'modern'\n\t}\n\treturn 'legacy'\n}\n\nexport const decorator: DecoratorFactory<any> = (description: DecoratorDescription<any>) => {\n\treturn ((target: any, contextOrKey?: any, ...args: any[]) => {\n\t\tconst mode = detectDecoratorMode(target, contextOrKey, args[0])\n\t\treturn mode === 'modern'\n\t\t\t? modernDecorator(description)(target, contextOrKey, ...args)\n\t\t\t: legacyDecorator(description)(target, contextOrKey, ...args)\n\t}) as any\n}\n\nexport type GenericClassDecorator<T> = LegacyClassDecorator<new (...args: any[]) => T> &\n\tModernClassDecorator<new (...args: any[]) => T>\n"],"names":[],"mappings":";;AAIM,SAAU,GAAG,CAAmC,GAAG,IAAO,EAAA;IAC/D,IAAI,CAAC,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,EAAE;IAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAsB,EAAE;AAEpC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;AACnC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAoB;AAC1D,QAAA,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;IACnB;AAEA,IAAA,OAAO,MAAM;AACd;AAEA,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAW;IAC5C,MAAM;IACN,KAAK;IACL,IAAI;IACJ,QAAQ;IACR,GAAG;IACH,GAAG;IACH,OAAO;IACP,OAAO;IACP,OAAO;IACP,KAAK;IACL,SAAS;IACT,cAAc;IACd,WAAW;IACX,UAAU;IACV,QAAQ;IACR,SAAS;IACT,OAAO;IACP,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;AACO,CAAA,CAAC;AACV,SAAU,aAAa,CAAC,EAAY,EAAA;IACzC,OAAO,EAAE,KAAK,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAChF;AAEM,SAAU,OAAO,CAAqB,GAAM,EAAE,IAAY,EAAA;AAC/D,IAAA,OAAO,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE;AACnC,QAAA,IAAI,EAAE;AACL,YAAA,KAAK,EAAE,IAAI;AACX,SAAA;AACD,KAAA,CAAC;AACH;;ACnDA;AACA;AAIM,MAAO,cAAe,SAAQ,KAAK,CAAA;AACxC,IAAA,WAAA,CAAY,OAAe,EAAA;QAC1B,KAAK,CAAC,OAAO,CAAC;AACd,QAAA,IAAI,CAAC,IAAI,GAAG,oBAAoB;IACjC;AACA;AAiFK,SAAU,eAAe,CAAU,WAAoC,EAAA;IAC5E,OAAO,UACN,MAAW,EACX,WAAyB,EACzB,UAA+B,EAC/B,GAAG,IAAW,EAAA;AAEd,QAAA,IAAI,WAAW,KAAK,SAAS,EAAE;AAC9B,YAAA,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE;AAC1B,gBAAA,IAAI,EAAE,OAAO,IAAI,WAAW,CAAC;AAAE,oBAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;AACxF,gBAAA,OAAO,WAAW,CAAC,KAAK,GAAG,MAAM,CAAC;YACnC;QACD;AAAO,aAAA,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,WAAW,CAAC,EAAE;AAC3F,YAAA,IAAI,CAAC,UAAU;AAAE,gBAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;iBACrE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,cAAc,IAAI,UAAU,EAAE;gBACxE,IAAI,KAAK,IAAI,UAAU,IAAI,KAAK,IAAI,UAAU,EAAE;oBAC/C,IAAI,EAAE,QAAQ,IAAI,WAAW,IAAI,QAAQ,IAAI,WAAW,CAAC;AACxD,wBAAA,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC;AACrE,oBAAA,IAAI,QAAQ,IAAI,WAAW,EAAE;AAC5B,wBAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC;AACnE,wBAAA,IAAI,SAAS;AAAE,4BAAA,UAAU,CAAC,GAAG,GAAG,SAAS;oBAC1C;AACA,oBAAA,IAAI,QAAQ,IAAI,WAAW,EAAE;AAC5B,wBAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC;AACnE,wBAAA,IAAI,SAAS;AAAE,4BAAA,UAAU,CAAC,GAAG,GAAG,SAAS;oBAC1C;AACA,oBAAA,OAAO,UAAU;gBAClB;AAAO,qBAAA,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE;AAClD,oBAAA,IAAI,EAAE,QAAQ,IAAI,WAAW,CAAC;AAAE,wBAAA,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC;AAC1F,oBAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC;AACrE,oBAAA,IAAI,SAAS;AAAE,wBAAA,UAAU,CAAC,KAAK,GAAG,SAAS;AAC3C,oBAAA,OAAO,UAAU;gBAClB;YACD;QACD;AACA,QAAA,IAAI,EAAE,SAAS,IAAI,WAAW,CAAC;AAC9B,YAAA,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC;AAClE,QAAA,OAAO,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;AAChF,IAAA,CAAC;AACF;AAEM,SAAU,eAAe,CAAU,WAAoC,EAAA;AAC5E,IAAA,OAAO,UAAU,MAAW,EAAE,OAA0B,EAAE,GAAG,IAAW,EAAA;AACvE,QAAA,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE;AACvD,YAAA,IAAI,EAAE,SAAS,IAAI,WAAW,CAAC;AAC9B,gBAAA,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC;AAClE,YAAA,OAAO,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;QAChE;AACA,QAAA,QAAQ,OAAO,CAAC,IAAI;AACnB,YAAA,KAAK,OAAO;AACX,gBAAA,IAAI,EAAE,OAAO,IAAI,WAAW,CAAC;AAAE,oBAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;AACxF,gBAAA,OAAO,WAAW,CAAC,KAAK,GAAG,MAAM,CAAC;AACnC,YAAA,KAAK,OAAO;AACX,gBAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;AAC1D,YAAA,KAAK,QAAQ;AACZ,gBAAA,IAAI,EAAE,QAAQ,IAAI,WAAW,CAAC;AAAE,oBAAA,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC;gBAC1F,OAAO,WAAW,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC;AAClD,YAAA,KAAK,QAAQ;AACZ,gBAAA,IAAI,EAAE,QAAQ,IAAI,WAAW,CAAC;AAAE,oBAAA,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC;gBAC1F,OAAO,WAAW,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC;AAClD,YAAA,KAAK,QAAQ;AACZ,gBAAA,IAAI,EAAE,QAAQ,IAAI,WAAW,CAAC;AAAE,oBAAA,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC;gBAC1F,OAAO,WAAW,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC;YAClD,KAAK,UAAU,EAAE;gBAChB,IAAI,EAAE,QAAQ,IAAI,WAAW,IAAI,QAAQ,IAAI,WAAW,CAAC;AACxD,oBAAA,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC;gBACrE,MAAM,EAAE,GAAoD,EAAE;AAC9D,gBAAA,IAAI,QAAQ,IAAI,WAAW,EAAE;AAC5B,oBAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC;AAChE,oBAAA,IAAI,SAAS;AAAE,wBAAA,EAAE,CAAC,GAAG,GAAG,SAAS;gBAClC;AACA,gBAAA,IAAI,QAAQ,IAAI,WAAW,EAAE;AAC5B,oBAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC;AAChE,oBAAA,IAAI,SAAS;AAAE,wBAAA,EAAE,CAAC,GAAG,GAAG,SAAS;gBAClC;AACA,gBAAA,OAAO,EAAE;YACV;;;AAGF,IAAA,CAAC;AACF;AAEA;;;AAGG;AACH,SAAS,mBAAmB,CAC3B,OAAY,EACZ,YAAkB,EAClB,WAAiB,EAAA;;;IAIjB,IACC,OAAO,YAAY,KAAK,QAAQ;AAChC,QAAA,YAAY,KAAK,IAAI;AACrB,QAAA,OAAO,YAAY,CAAC,IAAI,KAAK,QAAQ,EACpC;AACD,QAAA,OAAO,QAAQ;IAChB;AACA,IAAA,OAAO,QAAQ;AAChB;AAEO,MAAM,SAAS,GAA0B,CAAC,WAAsC,KAAI;IAC1F,QAAQ,CAAC,MAAW,EAAE,YAAkB,EAAE,GAAG,IAAW,KAAI;AAC3D,QAAA,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/D,OAAO,IAAI,KAAK;AACf,cAAE,eAAe,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI;AAC5D,cAAE,eAAe,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;AAC/D,IAAA,CAAC;AACF;;;;;;;;;;"}