mutts 1.0.5 → 1.0.7

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 (114) hide show
  1. package/README.md +2 -1
  2. package/dist/browser.d.ts +2 -0
  3. package/dist/browser.esm.js +70 -0
  4. package/dist/browser.esm.js.map +1 -0
  5. package/dist/browser.js +161 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/chunks/{index-Cvxdw6Ax.js → index-BFYK02LG.js} +5377 -4059
  8. package/dist/chunks/index-BFYK02LG.js.map +1 -0
  9. package/dist/chunks/{index-qiWwozOc.esm.js → index-CNR6QRUl.esm.js} +5247 -3963
  10. package/dist/chunks/index-CNR6QRUl.esm.js.map +1 -0
  11. package/dist/mutts.umd.js +1 -1
  12. package/dist/mutts.umd.js.map +1 -1
  13. package/dist/mutts.umd.min.js +1 -1
  14. package/dist/mutts.umd.min.js.map +1 -1
  15. package/dist/node.d.ts +2 -0
  16. package/dist/node.esm.js +45 -0
  17. package/dist/node.esm.js.map +1 -0
  18. package/dist/node.js +136 -0
  19. package/dist/node.js.map +1 -0
  20. package/docs/ai/api-reference.md +0 -2
  21. package/docs/ai/manual.md +14 -95
  22. package/docs/reactive/advanced.md +7 -111
  23. package/docs/reactive/collections.md +0 -125
  24. package/docs/reactive/core.md +27 -24
  25. package/docs/reactive/debugging.md +168 -0
  26. package/docs/reactive/project.md +1 -1
  27. package/docs/reactive/scan.md +78 -0
  28. package/docs/reactive.md +8 -6
  29. package/docs/std-decorators.md +1 -0
  30. package/docs/zone.md +88 -0
  31. package/package.json +47 -65
  32. package/src/async/browser.ts +87 -0
  33. package/src/async/index.ts +8 -0
  34. package/src/async/node.ts +46 -0
  35. package/src/decorator.ts +15 -9
  36. package/src/destroyable.ts +4 -4
  37. package/src/index.ts +54 -0
  38. package/src/indexable.ts +42 -0
  39. package/src/mixins.ts +2 -2
  40. package/src/reactive/array.ts +149 -141
  41. package/src/reactive/buffer.ts +168 -0
  42. package/src/reactive/change.ts +3 -3
  43. package/src/reactive/debug.ts +1 -1
  44. package/src/reactive/deep-touch.ts +1 -1
  45. package/src/reactive/deep-watch.ts +1 -1
  46. package/src/reactive/effect-context.ts +15 -91
  47. package/src/reactive/effects.ts +138 -170
  48. package/src/reactive/index.ts +10 -13
  49. package/src/reactive/interface.ts +20 -33
  50. package/src/reactive/map.ts +48 -61
  51. package/src/reactive/memoize.ts +87 -31
  52. package/src/reactive/project.ts +43 -22
  53. package/src/reactive/proxy.ts +18 -43
  54. package/src/reactive/record.ts +3 -3
  55. package/src/reactive/register.ts +5 -7
  56. package/src/reactive/registry.ts +59 -0
  57. package/src/reactive/set.ts +42 -56
  58. package/src/reactive/tracking.ts +5 -62
  59. package/src/reactive/types.ts +79 -19
  60. package/src/std-decorators.ts +9 -9
  61. package/src/utils.ts +203 -19
  62. package/src/zone.ts +127 -0
  63. package/dist/chunks/_tslib-BgjropY9.js +0 -81
  64. package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
  65. package/dist/chunks/_tslib-Mzh1rNsX.esm.js +0 -75
  66. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
  67. package/dist/chunks/decorator-DLvrD0UF.js +0 -265
  68. package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
  69. package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
  70. package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
  71. package/dist/chunks/index-Cvxdw6Ax.js.map +0 -1
  72. package/dist/chunks/index-qiWwozOc.esm.js.map +0 -1
  73. package/dist/decorator.d.ts +0 -107
  74. package/dist/decorator.esm.js +0 -2
  75. package/dist/decorator.esm.js.map +0 -1
  76. package/dist/decorator.js +0 -11
  77. package/dist/decorator.js.map +0 -1
  78. package/dist/destroyable.d.ts +0 -90
  79. package/dist/destroyable.esm.js +0 -109
  80. package/dist/destroyable.esm.js.map +0 -1
  81. package/dist/destroyable.js +0 -116
  82. package/dist/destroyable.js.map +0 -1
  83. package/dist/eventful.d.ts +0 -20
  84. package/dist/eventful.esm.js +0 -66
  85. package/dist/eventful.esm.js.map +0 -1
  86. package/dist/eventful.js +0 -68
  87. package/dist/eventful.js.map +0 -1
  88. package/dist/index.d.ts +0 -19
  89. package/dist/index.esm.js +0 -8
  90. package/dist/index.esm.js.map +0 -1
  91. package/dist/index.js +0 -95
  92. package/dist/index.js.map +0 -1
  93. package/dist/indexable.d.ts +0 -243
  94. package/dist/indexable.esm.js +0 -285
  95. package/dist/indexable.esm.js.map +0 -1
  96. package/dist/indexable.js +0 -291
  97. package/dist/indexable.js.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -21
  99. package/dist/promiseChain.esm.js +0 -78
  100. package/dist/promiseChain.esm.js.map +0 -1
  101. package/dist/promiseChain.js +0 -80
  102. package/dist/promiseChain.js.map +0 -1
  103. package/dist/reactive.d.ts +0 -885
  104. package/dist/reactive.esm.js +0 -5
  105. package/dist/reactive.esm.js.map +0 -1
  106. package/dist/reactive.js +0 -59
  107. package/dist/reactive.js.map +0 -1
  108. package/dist/std-decorators.d.ts +0 -52
  109. package/dist/std-decorators.esm.js +0 -196
  110. package/dist/std-decorators.esm.js.map +0 -1
  111. package/dist/std-decorators.js +0 -204
  112. package/dist/std-decorators.js.map +0 -1
  113. package/src/reactive/mapped.ts +0 -129
  114. package/src/reactive/zone.ts +0 -208
@@ -1,129 +0,0 @@
1
- import { Indexable } from '../indexable'
2
- import { native, ReactiveBaseArray } from './array'
3
- import { touched, touched1 } from './change'
4
- import { effect, untracked } from './effects'
5
- import { cleanedBy } from './interface'
6
- import { reactive } from './proxy'
7
- import { dependant } from './tracking'
8
- import { prototypeForwarding, type ScopedCallback } from './types'
9
-
10
- // TODO: Lazy reactivity ?
11
- export class ReadOnlyError extends Error { }
12
- /**
13
- * Reactive wrapper around JavaScript's Array class with full array method support
14
- * Tracks length changes, individual index operations, and collection-wide operations
15
- */
16
- class ReactiveReadOnlyArrayClass extends Indexable(ReactiveBaseArray, {
17
- get(i: number): any {
18
- dependant(this, i)
19
- return reactive(this[native][i])
20
- },
21
- set(i: number, _value: any) {
22
- throw new ReadOnlyError(`Setting index ${i} on a read-only array`)
23
- },
24
- getLength() {
25
- dependant(this, 'length')
26
- return this[native].length
27
- },
28
- setLength(value: number) {
29
- throw new ReadOnlyError(`Setting length to ${value} on a read-only array`)
30
- },
31
- }) {
32
- constructor(original: any[]) {
33
- super()
34
- Object.defineProperties(this, {
35
- // We have to make it double, as [native] must be `unique symbol` - impossible through import
36
- [native]: { value: original },
37
- [prototypeForwarding]: { value: original },
38
- })
39
- }
40
-
41
- push(..._items: any[]) {
42
- throw new ReadOnlyError(`Pushing items to a read-only array`)
43
- }
44
-
45
- pop() {
46
- throw new ReadOnlyError(`Popping from a read-only array`)
47
- }
48
-
49
- shift() {
50
- throw new ReadOnlyError(`Shifting from a read-only array`)
51
- }
52
-
53
- unshift(..._items: any[]) {
54
- throw new ReadOnlyError(`Unshifting items to a read-only array`)
55
- }
56
-
57
- splice(_start: number, _deleteCount?: number, ..._items: any[]) {
58
- throw new ReadOnlyError(`Splice from a read-only array`)
59
- }
60
-
61
- reverse() {
62
- throw new ReadOnlyError(`Reversing a read-only array`)
63
- }
64
-
65
- sort(_compareFn?: (a: any, b: any) => number) {
66
- throw new ReadOnlyError(`Sorting a read-only array`)
67
- }
68
-
69
- fill(_value: any, _start?: number, _end?: number) {
70
- throw new ReadOnlyError(`Filling a read-only array`)
71
- }
72
-
73
- copyWithin(_target: number, _start: number, _end?: number) {
74
- throw new ReadOnlyError(`Copying within a read-only array`)
75
- }
76
- }
77
-
78
- export const ReactiveReadOnlyArray = reactive(ReactiveReadOnlyArrayClass)
79
- export type ReactiveReadOnlyArray<T> = readonly T[]
80
- export function mapped<T, U>(
81
- inputs: readonly T[],
82
- compute: (input: T, index: number, output: U[]) => U,
83
- resize?: (newLength: number, oldLength: number) => void
84
- ): readonly U[] {
85
- const result: U[] = []
86
- const resultReactive = new ReactiveReadOnlyArray(result)
87
- const cleanups: ScopedCallback[] = []
88
- function input(index: number) {
89
- return effect(function computedIndexedMapInputEffect() {
90
- result[index] = compute(inputs[index], index, resultReactive)
91
- touched1(resultReactive, { type: 'set', prop: index }, index)
92
- })
93
- }
94
- const cleanupLength = effect(function computedMapLengthEffect({ ascend }) {
95
- const length = inputs.length
96
- const resultLength = untracked(() => result.length)
97
- resize?.(length, resultLength)
98
- touched1(resultReactive, { type: 'set', prop: 'length' }, 'length')
99
- if (length < resultLength) {
100
- const toCleanup = cleanups.splice(length)
101
- for (const cleanup of toCleanup) cleanup()
102
- result.length = length
103
- } else if (length > resultLength)
104
- // the input effects will be registered as the call's children, so they will remain not cleaned with this effect on length
105
- ascend(function computedMapNewElements() {
106
- for (let i = resultLength; i < length; i++) cleanups.push(input(i))
107
- })
108
- })
109
- return cleanedBy(resultReactive, () => {
110
- for (const cleanup of cleanups) cleanup()
111
- cleanups.length = 0
112
- cleanupLength()
113
- })
114
- }
115
-
116
- export function reduced<T, U, R extends object = any>(
117
- inputs: readonly T[],
118
- compute: (input: T, factor: R) => readonly U[]
119
- ): readonly U[] {
120
- const result: U[] = []
121
- const resultReactive = new ReactiveReadOnlyArray(result)
122
- const cleanupFactor = effect(function computedReducedFactorEffect() {
123
- const factor: R = {} as R
124
- result.length = 0
125
- for (const input of inputs) result.push(...compute(input, factor))
126
- touched(resultReactive, { type: 'invalidate', prop: 'reduced' })
127
- })
128
- return cleanedBy(resultReactive, cleanupFactor)
129
- }
@@ -1,208 +0,0 @@
1
- /**
2
- * Zone-like async context preservation for reactive effects
3
- *
4
- * Automatically preserves effect context across async boundaries:
5
- * - Promise methods: .then(), .catch(), .finally()
6
- * - Timers: setTimeout(), setInterval()
7
- * - Animation: requestAnimationFrame() (if available) - runs in untracked context
8
- * - Microtasks: queueMicrotask() (if available)
9
- *
10
- * **IMPORTANT:** This module is opt-in via `reactiveOptions.asyncMode` (truthy = enabled, false = disabled).
11
- * By default, async zone is ENABLED with 'cancel' mode.
12
- *
13
- * When disabled (asyncMode = false), use `tracked()` manually in async callbacks.
14
- * When enabled (asyncMode = 'cancel' | 'queue' | 'ignore'), async entry points are wrapped ONCE.
15
- */
16
-
17
- import { captureEffectStack, withEffectStack } from './effect-context'
18
- import { options, type ScopedCallback } from './types'
19
-
20
- let zoneHooked = false
21
-
22
- // Store original Promise methods at module load time (before any wrapping)
23
- // This ensures we always have the true originals, even if wrapping happens multiple times
24
- const originalPromiseThen =
25
- Object.getOwnPropertyDescriptor(Promise.prototype, 'then')?.value || Promise.prototype.then
26
- const originalPromiseCatch =
27
- Object.getOwnPropertyDescriptor(Promise.prototype, 'catch')?.value || Promise.prototype.catch
28
- const originalPromiseFinally =
29
- Object.getOwnPropertyDescriptor(Promise.prototype, 'finally')?.value || Promise.prototype.finally
30
-
31
- // Store original timer functions at module load time
32
- const originalSetTimeout = globalThis.setTimeout
33
- const originalSetInterval = globalThis.setInterval
34
- const originalRequestAnimationFrame =
35
- typeof globalThis.requestAnimationFrame !== 'undefined'
36
- ? globalThis.requestAnimationFrame
37
- : undefined
38
- const originalQueueMicrotask =
39
- typeof globalThis.queueMicrotask !== 'undefined' ? globalThis.queueMicrotask : undefined
40
-
41
- // Store batch function to avoid circular dependency
42
- let batchFn: ((cb: () => any, type: 'immediate') => any) | undefined
43
-
44
- /**
45
- * Check the asyncMode option and hook Promise.prototype once if enabled
46
- * Called lazily on first effect creation
47
- * asyncMode being truthy enables async zone, false disables it
48
- *
49
- * @param batch - Optional batch function injection from effects.ts to avoid circular dependency
50
- */
51
- export function ensureZoneHooked(batch?: (cb: () => any, type: 'immediate') => any) {
52
- if (batch) batchFn = batch
53
- if (zoneHooked || !options.asyncMode) return
54
- hookZone()
55
- zoneHooked = true
56
- }
57
-
58
- /**
59
- * Hook Promise.prototype methods to preserve effect context
60
- */
61
- function hookZone() {
62
- // biome-ignore lint/suspicious/noThenProperty: Intentional wrapping for zone functionality
63
- Promise.prototype.then = function <T, R1, R2>(
64
- this: Promise<T>,
65
- onFulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null,
66
- onRejected?: ((reason: any) => R2 | PromiseLike<R2>) | null
67
- ): Promise<R1 | R2> {
68
- const capturedStack = captureEffectStack()
69
- return originalPromiseThen.call(
70
- this,
71
- wrapCallback(onFulfilled, capturedStack),
72
- wrapCallback(onRejected, capturedStack)
73
- )
74
- }
75
-
76
- Promise.prototype.catch = function <T>(
77
- this: Promise<T>,
78
- onRejected?: ((reason: any) => T | PromiseLike<T>) | null
79
- ): Promise<T> {
80
- const capturedStack = captureEffectStack()
81
- return originalPromiseCatch.call(this, wrapCallback(onRejected, capturedStack))
82
- }
83
-
84
- Promise.prototype.finally = function <T>(
85
- this: Promise<T>,
86
- onFinally?: (() => void) | null
87
- ): Promise<T> {
88
- const capturedStack = captureEffectStack()
89
- return originalPromiseFinally.call(this, wrapCallback(onFinally, capturedStack))
90
- }
91
-
92
- // Hook setTimeout - preserve original function properties for Node.js compatibility
93
- const wrappedSetTimeout = (<TArgs extends any[]>(
94
- callback: (...args: TArgs) => void,
95
- delay?: number,
96
- ...args: TArgs
97
- ): ReturnType<typeof originalSetTimeout> => {
98
- const capturedStack = options.zones.setTimeout ? captureEffectStack() : undefined
99
- return originalSetTimeout.apply(globalThis, [
100
- wrapCallback(callback, capturedStack) as (...args: any[]) => void,
101
- delay,
102
- ...args,
103
- ] as any)
104
- }) as typeof originalSetTimeout
105
- Object.assign(wrappedSetTimeout, originalSetTimeout)
106
- globalThis.setTimeout = wrappedSetTimeout
107
-
108
- // Hook setInterval - preserve original function properties for Node.js compatibility
109
- const wrappedSetInterval = (<TArgs extends any[]>(
110
- callback: (...args: TArgs) => void,
111
- delay?: number,
112
- ...args: TArgs
113
- ): ReturnType<typeof originalSetInterval> => {
114
- const capturedStack = options.zones.setInterval ? captureEffectStack() : undefined
115
- return originalSetInterval.apply(globalThis, [
116
- wrapCallback(callback, capturedStack) as (...args: any[]) => void,
117
- delay,
118
- ...args,
119
- ] as any)
120
- }) as typeof originalSetInterval
121
- Object.assign(wrappedSetInterval, originalSetInterval)
122
- globalThis.setInterval = wrappedSetInterval
123
-
124
- // Hook requestAnimationFrame if available
125
- if (originalRequestAnimationFrame) {
126
- globalThis.requestAnimationFrame = ((
127
- callback: FrameRequestCallback
128
- ): ReturnType<typeof originalRequestAnimationFrame> => {
129
- const capturedStack = options.zones.requestAnimationFrame ? captureEffectStack() : undefined
130
- return originalRequestAnimationFrame.call(
131
- globalThis,
132
- wrapCallback(callback as any, capturedStack) as FrameRequestCallback
133
- )
134
- }) as typeof originalRequestAnimationFrame
135
- }
136
-
137
- // Hook queueMicrotask if available
138
- if (originalQueueMicrotask) {
139
- globalThis.queueMicrotask = ((callback: () => void): void => {
140
- const capturedStack = options.zones.queueMicrotask ? captureEffectStack() : undefined
141
- originalQueueMicrotask.call(globalThis, wrapCallback(callback, capturedStack) as () => void)
142
- }) as typeof originalQueueMicrotask
143
- }
144
- }
145
-
146
- /**
147
- * Wraps a callback to restore effect context and ensure batching
148
- */
149
- function wrapCallback<T extends (...args: any[]) => any>(
150
- callback: T | null | undefined,
151
- capturedStack: (ScopedCallback | undefined)[] | undefined
152
- ): T | undefined {
153
- if (!callback) return undefined
154
-
155
- // If no stack to restore and no batch function, direct call (optimization)
156
- if ((!capturedStack || !capturedStack.length) && !batchFn) {
157
- return callback
158
- }
159
-
160
- return ((...args: any[]) => {
161
- const execute = () => {
162
- if (capturedStack?.length) {
163
- return withEffectStack(capturedStack, () => callback(...args))
164
- }
165
- return callback(...args)
166
- }
167
-
168
- if (batchFn) {
169
- return batchFn(execute, 'immediate')
170
- }
171
- return execute()
172
- }) as T
173
- }
174
-
175
- /**
176
- * Manually enable/disable the zone (for testing)
177
- */
178
- export function setZoneEnabled(enabled: boolean) {
179
- if (enabled && !zoneHooked) {
180
- hookZone()
181
- zoneHooked = true
182
- } else if (!enabled && zoneHooked) {
183
- // Restore original Promise methods
184
- // biome-ignore lint/suspicious/noThenProperty: Restoring original methods
185
- Promise.prototype.then = originalPromiseThen
186
- Promise.prototype.catch = originalPromiseCatch
187
- Promise.prototype.finally = originalPromiseFinally
188
-
189
- // Restore original timer functions
190
- globalThis.setTimeout = originalSetTimeout
191
- globalThis.setInterval = originalSetInterval
192
- if (originalRequestAnimationFrame) {
193
- globalThis.requestAnimationFrame = originalRequestAnimationFrame
194
- }
195
- if (originalQueueMicrotask) {
196
- globalThis.queueMicrotask = originalQueueMicrotask
197
- }
198
-
199
- zoneHooked = false
200
- }
201
- }
202
-
203
- /**
204
- * Check if zone is currently hooked
205
- */
206
- export function isZoneEnabled(): boolean {
207
- return zoneHooked
208
- }