mutts 1.0.8 → 1.0.10

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 (195) hide show
  1. package/README.md +77 -35
  2. package/dist/browser.cjs +2144 -5355
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1655
  5. package/dist/browser.dev.cjs +110 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +20 -107
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-BU_IfxYD.cjs} +19 -107
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  15. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  16. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-3PrbVAbB.cjs} +7 -12
  17. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  18. package/dist/chunks/index-CaaQQlPJ.esm.js +3174 -0
  19. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  20. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  21. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  22. package/dist/chunks/proxy-Dtg-bJ3T.cjs +3763 -0
  23. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  24. package/dist/chunks/proxy-r7lARftl.esm.js +3701 -0
  25. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  26. package/dist/debug.cjs +980 -0
  27. package/dist/debug.cjs.map +1 -0
  28. package/dist/debug.d.ts +208 -0
  29. package/dist/debug.esm.js +950 -0
  30. package/dist/debug.esm.js.map +1 -0
  31. package/dist/devtools/manifest.json +1 -1
  32. package/dist/devtools/panel.html +1 -1
  33. package/dist/devtools/panel.js +107 -94
  34. package/dist/devtools/panel.js.map +1 -1
  35. package/dist/index.d.ts +1447 -13
  36. package/dist/mutts.umd.js +7167 -1
  37. package/dist/mutts.umd.js.map +1 -1
  38. package/dist/mutts.umd.min.js +1 -1
  39. package/dist/mutts.umd.min.js.map +1 -1
  40. package/dist/node.cjs +70 -66
  41. package/dist/node.cjs.map +1 -1
  42. package/dist/node.d.ts +2 -1
  43. package/dist/node.dev.cjs +110 -0
  44. package/dist/node.dev.cjs.map +1 -0
  45. package/dist/node.dev.d.ts +2 -0
  46. package/dist/node.dev.esm.js +6 -0
  47. package/dist/node.dev.esm.js.map +1 -0
  48. package/dist/node.esm.js +4 -103
  49. package/dist/node.esm.js.map +1 -1
  50. package/dist/{reactive/types.d.ts → types-W5vD6m2n.d.ts} +186 -93
  51. package/docs/ai/api-reference.md +12 -19
  52. package/docs/ai/manual.md +521 -17
  53. package/docs/events.md +57 -2
  54. package/docs/flavored.md +210 -0
  55. package/docs/reactive/advanced.md +314 -5
  56. package/docs/reactive/attend.md +142 -0
  57. package/docs/reactive/collections.md +76 -39
  58. package/docs/reactive/core.md +100 -14
  59. package/docs/reactive/debugging.md +123 -18
  60. package/docs/reactive/error-handling.md +121 -0
  61. package/docs/reactive/resource.md +125 -0
  62. package/docs/reactive/scan.md +247 -1
  63. package/docs/reactive.md +4 -1
  64. package/docs/std-decorators.md +102 -51
  65. package/docs/utils.md +70 -0
  66. package/docs/zone.md +3 -7
  67. package/package.json +64 -30
  68. package/dist/async/browser.d.ts +0 -2
  69. package/dist/async/browser.d.ts.map +0 -1
  70. package/dist/async/index.d.ts +0 -18
  71. package/dist/async/index.d.ts.map +0 -1
  72. package/dist/async/node.d.ts +0 -2
  73. package/dist/async/node.d.ts.map +0 -1
  74. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  75. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  76. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  77. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  78. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  79. package/dist/decorator.d.ts +0 -106
  80. package/dist/decorator.d.ts.map +0 -1
  81. package/dist/destroyable.d.ts +0 -87
  82. package/dist/destroyable.d.ts.map +0 -1
  83. package/dist/entry-browser.d.ts +0 -3
  84. package/dist/entry-browser.d.ts.map +0 -1
  85. package/dist/entry-node.d.ts +0 -3
  86. package/dist/entry-node.d.ts.map +0 -1
  87. package/dist/eventful.d.ts +0 -18
  88. package/dist/eventful.d.ts.map +0 -1
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/indexable.d.ts +0 -243
  91. package/dist/indexable.d.ts.map +0 -1
  92. package/dist/introspection.d.ts +0 -27
  93. package/dist/introspection.d.ts.map +0 -1
  94. package/dist/iterableWeak.d.ts +0 -53
  95. package/dist/iterableWeak.d.ts.map +0 -1
  96. package/dist/mixins.d.ts +0 -25
  97. package/dist/mixins.d.ts.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -20
  99. package/dist/promiseChain.d.ts.map +0 -1
  100. package/dist/reactive/array.d.ts +0 -49
  101. package/dist/reactive/array.d.ts.map +0 -1
  102. package/dist/reactive/buffer.d.ts +0 -44
  103. package/dist/reactive/buffer.d.ts.map +0 -1
  104. package/dist/reactive/change.d.ts +0 -29
  105. package/dist/reactive/change.d.ts.map +0 -1
  106. package/dist/reactive/debug.d.ts +0 -111
  107. package/dist/reactive/debug.d.ts.map +0 -1
  108. package/dist/reactive/deep-touch.d.ts +0 -28
  109. package/dist/reactive/deep-touch.d.ts.map +0 -1
  110. package/dist/reactive/deep-watch-state.d.ts +0 -25
  111. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  112. package/dist/reactive/deep-watch.d.ts +0 -19
  113. package/dist/reactive/deep-watch.d.ts.map +0 -1
  114. package/dist/reactive/effect-context.d.ts +0 -7
  115. package/dist/reactive/effect-context.d.ts.map +0 -1
  116. package/dist/reactive/effects.d.ts +0 -151
  117. package/dist/reactive/effects.d.ts.map +0 -1
  118. package/dist/reactive/index.d.ts +0 -20
  119. package/dist/reactive/index.d.ts.map +0 -1
  120. package/dist/reactive/interface.d.ts +0 -64
  121. package/dist/reactive/interface.d.ts.map +0 -1
  122. package/dist/reactive/map.d.ts +0 -30
  123. package/dist/reactive/map.d.ts.map +0 -1
  124. package/dist/reactive/memoize.d.ts +0 -5
  125. package/dist/reactive/memoize.d.ts.map +0 -1
  126. package/dist/reactive/non-reactive-state.d.ts +0 -9
  127. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  128. package/dist/reactive/non-reactive.d.ts +0 -11
  129. package/dist/reactive/non-reactive.d.ts.map +0 -1
  130. package/dist/reactive/project.d.ts +0 -41
  131. package/dist/reactive/project.d.ts.map +0 -1
  132. package/dist/reactive/proxy-state.d.ts +0 -8
  133. package/dist/reactive/proxy-state.d.ts.map +0 -1
  134. package/dist/reactive/proxy.d.ts +0 -23
  135. package/dist/reactive/proxy.d.ts.map +0 -1
  136. package/dist/reactive/record.d.ts +0 -116
  137. package/dist/reactive/record.d.ts.map +0 -1
  138. package/dist/reactive/register.d.ts +0 -64
  139. package/dist/reactive/register.d.ts.map +0 -1
  140. package/dist/reactive/registry.d.ts +0 -20
  141. package/dist/reactive/registry.d.ts.map +0 -1
  142. package/dist/reactive/set.d.ts +0 -28
  143. package/dist/reactive/set.d.ts.map +0 -1
  144. package/dist/reactive/tracking.d.ts +0 -7
  145. package/dist/reactive/tracking.d.ts.map +0 -1
  146. package/dist/reactive/types.d.ts.map +0 -1
  147. package/dist/std-decorators.d.ts +0 -50
  148. package/dist/std-decorators.d.ts.map +0 -1
  149. package/dist/utils.d.ts +0 -49
  150. package/dist/utils.d.ts.map +0 -1
  151. package/dist/zone.d.ts +0 -40
  152. package/dist/zone.d.ts.map +0 -1
  153. package/docs/reactive/project.md +0 -93
  154. package/src/async/browser.ts +0 -319
  155. package/src/async/index.ts +0 -23
  156. package/src/async/node.ts +0 -104
  157. package/src/decorator.ts +0 -272
  158. package/src/destroyable.ts +0 -199
  159. package/src/entry-browser.ts +0 -5
  160. package/src/entry-node.ts +0 -5
  161. package/src/eventful.ts +0 -77
  162. package/src/index.d.ts +0 -12
  163. package/src/index.ts +0 -64
  164. package/src/indexable.ts +0 -526
  165. package/src/introspection.ts +0 -59
  166. package/src/iterableWeak.ts +0 -233
  167. package/src/mixins.ts +0 -123
  168. package/src/promiseChain.ts +0 -110
  169. package/src/reactive/array.ts +0 -507
  170. package/src/reactive/buffer.ts +0 -168
  171. package/src/reactive/change.ts +0 -138
  172. package/src/reactive/debug.ts +0 -517
  173. package/src/reactive/deep-touch.ts +0 -268
  174. package/src/reactive/deep-watch-state.ts +0 -82
  175. package/src/reactive/deep-watch.ts +0 -168
  176. package/src/reactive/effect-context.ts +0 -18
  177. package/src/reactive/effects.ts +0 -1382
  178. package/src/reactive/index.ts +0 -75
  179. package/src/reactive/interface.ts +0 -210
  180. package/src/reactive/map.ts +0 -158
  181. package/src/reactive/memoize.ts +0 -163
  182. package/src/reactive/non-reactive-state.ts +0 -49
  183. package/src/reactive/non-reactive.ts +0 -43
  184. package/src/reactive/project.ts +0 -453
  185. package/src/reactive/proxy-state.ts +0 -27
  186. package/src/reactive/proxy.ts +0 -264
  187. package/src/reactive/record.ts +0 -196
  188. package/src/reactive/register.ts +0 -419
  189. package/src/reactive/registry.ts +0 -59
  190. package/src/reactive/set.ts +0 -130
  191. package/src/reactive/tracking.ts +0 -44
  192. package/src/reactive/types.ts +0 -440
  193. package/src/std-decorators.ts +0 -217
  194. package/src/utils.ts +0 -301
  195. package/src/zone.ts +0 -138
@@ -1,507 +0,0 @@
1
- import { FoolProof } from '../utils'
2
- import { touched } from './change'
3
- import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
4
- import { reactive } from './proxy'
5
- import { unwrap } from './proxy-state'
6
- import { dependant } from './tracking'
7
-
8
- function* index(i: number, { length = true } = {}): IterableIterator<number | 'length'> {
9
- if (length) yield 'length'
10
- yield i
11
- }
12
-
13
- function* range(
14
- a: number,
15
- b: number,
16
- { length = false } = {}
17
- ): IterableIterator<number | 'length'> {
18
- const start = Math.min(a, b)
19
- const end = Math.max(a, b)
20
- if (length) yield 'length'
21
- for (let i = start; i <= end; i++) yield i
22
- }
23
- export abstract class Indexer extends Array {
24
- get(i: number): any {
25
- dependant(this, i)
26
- return reactive(this[i])
27
- }
28
- set(i: number, value: any) {
29
- const added = i >= this.length
30
- this[i] = value
31
- touched(this, { type: 'set', prop: i }, index(i, { length: added }))
32
- }
33
- getLength() {
34
- dependant(this, 'length')
35
- return this.length
36
- }
37
- setLength(value: number) {
38
- const oldLength = this.length
39
- try {
40
- this.length = value
41
- } finally {
42
- touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }))
43
- }
44
- }
45
- }
46
- const indexLess = { get: FoolProof.get, set: FoolProof.set }
47
- Object.assign(FoolProof, {
48
- get(obj: any, prop: any, receiver: any) {
49
- if (obj instanceof Array && typeof prop === 'string') {
50
- if (prop === 'length') return Indexer.prototype.getLength.call(obj)
51
- const index = parseInt(prop)
52
- if (!Number.isNaN(index)) return Indexer.prototype.get.call(obj, index)
53
- }
54
- return indexLess.get(obj, prop, receiver)
55
- },
56
- set(obj: any, prop: any, value: any, receiver: any) {
57
- if (obj instanceof Array && typeof prop === 'string') {
58
- if (prop === 'length') return Indexer.prototype.setLength.call(obj, value)
59
- const index = parseInt(prop)
60
- if (!Number.isNaN(index)) return Indexer.prototype.set.call(obj, index, value)
61
- }
62
- return indexLess.set(obj, prop, value, receiver)
63
- },
64
- })
65
-
66
- export abstract class ReactiveArray extends Array {
67
- toJSON() {
68
- return this
69
- }
70
- get [Symbol.toStringTag]() {
71
- return 'ReactiveArray'
72
- }
73
- // Safe array access with negative indices
74
- at(index: number): any {
75
- const actualIndex = index < 0 ? this.length + index : index
76
- dependant(this, actualIndex)
77
- if (index < 0) dependant(this, 'length')
78
- if (actualIndex < 0 || actualIndex >= this.length) return undefined
79
- return reactive(this[actualIndex])
80
- }
81
-
82
- // Immutable versions of mutator methods
83
- toReversed(): any[] {
84
- dependant(this)
85
- return reactive(this.toReversed())
86
- }
87
-
88
- toSorted(compareFn?: (a: any, b: any) => number): any[] {
89
- dependant(this)
90
- return reactive(this.toSorted(compareFn))
91
- }
92
-
93
- toSpliced(start: number, deleteCount?: number, ...items: any[]): any[] {
94
- dependant(this)
95
- return deleteCount === undefined
96
- ? this.toSpliced(start)
97
- : this.toSpliced(start, deleteCount, ...items)
98
- }
99
-
100
- with(index: number, value: any): any[] {
101
- dependant(this)
102
- return reactive(this.with(index, value))
103
- }
104
-
105
- // Iterator methods with reactivity tracking
106
- entries() {
107
- dependant(this)
108
- return makeReactiveEntriesIterator(this.entries())
109
- }
110
-
111
- keys() {
112
- dependant(this, 'length')
113
- return this.keys()
114
- }
115
-
116
- values() {
117
- dependant(this)
118
- return makeReactiveIterator(this.values())
119
- }
120
-
121
- [Symbol.iterator](): ArrayIterator<any> {
122
- dependant(this)
123
- const nativeIterator = this[Symbol.iterator]()
124
- return {
125
- next() {
126
- const result = nativeIterator.next()
127
- if (result.done) {
128
- return result
129
- }
130
- return { value: reactive(result.value), done: false }
131
- },
132
- [Symbol.iterator]() {
133
- return this
134
- },
135
- [Symbol.dispose]() {},
136
- } as any
137
- }
138
-
139
- indexOf(searchElement: any, fromIndex?: number): number {
140
- const length = this.length
141
- let i = fromIndex === undefined ? 0 : fromIndex
142
- if (i < 0) i = Math.max(length + i, 0)
143
-
144
- const unwrappedSearch = unwrap(searchElement)
145
-
146
- for (; i < length; i++) {
147
- dependant(this, i)
148
- const item = this[i]
149
- if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
150
- return i
151
- }
152
- }
153
-
154
- dependant(this, 'length')
155
- return -1
156
- }
157
-
158
- lastIndexOf(searchElement: any, fromIndex?: number): number {
159
- const length = this.length
160
- let i = fromIndex === undefined ? length - 1 : fromIndex
161
- if (i >= length) i = length - 1
162
- if (i < 0) i = Math.max(length + i, -1) // -1 ensures loop condition i >= 0 works correctly
163
-
164
- const unwrappedSearch = unwrap(searchElement)
165
-
166
- for (; i >= 0; i--) {
167
- dependant(this, i)
168
- const item = this[i]
169
- if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
170
- return i
171
- }
172
- }
173
-
174
- // If we scanned the whole relevant part and didn't find it, we depend on length
175
- // (because adding elements might shift indices or add the element)
176
- // Actually for lastIndexOf, if we start from end, length dependency is implicit in the start index calculation?
177
- // But if we return -1, it means we didn't find it.
178
- // If we push an element, should lastIndexOf update?
179
- // Yes, if the new element is the one we are looking for.
180
- dependant(this, 'length')
181
- return -1
182
- }
183
-
184
- includes(searchElement: any, fromIndex?: number): boolean {
185
- return this.indexOf(searchElement, fromIndex) !== -1
186
- }
187
-
188
- find(predicate: (this: any, value: any, index: number, obj: any[]) => boolean, thisArg?: any): any
189
- find(searchElement: any, fromIndex?: number): any
190
- find(predicateOrElement: any, thisArg?: any): any {
191
- if (typeof predicateOrElement === 'function') {
192
- const predicate = predicateOrElement as (
193
- this: any,
194
- value: any,
195
- index: number,
196
- obj: any[]
197
- ) => boolean
198
- const length = this.length
199
-
200
- for (let i = 0; i < length; i++) {
201
- dependant(this, i)
202
- const val = reactive(this[i])
203
- if (predicate.call(thisArg, val, i, this)) {
204
- return val
205
- }
206
- }
207
-
208
- dependant(this, 'length')
209
- return undefined
210
- }
211
- const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
212
- const index = this.indexOf(predicateOrElement, fromIndex)
213
- if (index === -1) return undefined
214
- return reactive(this[index])
215
- }
216
-
217
- findIndex(
218
- predicate: (this: any, value: any, index: number, obj: any[]) => boolean,
219
- thisArg?: any
220
- ): number
221
- findIndex(searchElement: any, fromIndex?: number): number
222
- findIndex(predicateOrElement: any, thisArg?: any): number {
223
- if (typeof predicateOrElement === 'function') {
224
- const predicate = predicateOrElement as (
225
- this: any,
226
- value: any,
227
- index: number,
228
- obj: any[]
229
- ) => boolean
230
- const length = this.length
231
-
232
- for (let i = 0; i < length; i++) {
233
- dependant(this, i)
234
- const val = reactive(this[i])
235
- if (predicate.call(thisArg, val, i, this)) {
236
- return i
237
- }
238
- }
239
-
240
- dependant(this, 'length')
241
- return -1
242
- }
243
- const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
244
- return this.indexOf(predicateOrElement, fromIndex)
245
- }
246
-
247
- flat(depth?: number): any[] {
248
- dependant(this)
249
- return reactive(depth === undefined ? this.flat() : this.flat(depth))
250
- }
251
-
252
- flatMap(
253
- callbackfn: (this: any, value: any, index: number, array: any[]) => any[],
254
- thisArg?: any
255
- ): any[] {
256
- dependant(this)
257
- return reactive(
258
- this.flatMap(
259
- (item, index, array) => callbackfn.call(thisArg, reactive(item), index, array),
260
- thisArg
261
- )
262
- )
263
- }
264
-
265
- filter(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): any[] {
266
- dependant(this)
267
- return reactive(
268
- this.filter((item, index, array) => callbackfn(reactive(item), index, array), thisArg)
269
- )
270
- }
271
-
272
- map(callbackfn: (value: any, index: number, array: any[]) => any, thisArg?: any): any[] {
273
- dependant(this)
274
- return reactive(
275
- this.map((item, index, array) => callbackfn(reactive(item), index, array), thisArg)
276
- )
277
- }
278
-
279
- reduce(
280
- callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any,
281
- initialValue?: any
282
- ): any {
283
- dependant(this)
284
- const result =
285
- initialValue === undefined
286
- ? this.reduce(callbackfn as any)
287
- : this.reduce(callbackfn as any, initialValue)
288
- return reactive(result)
289
- }
290
-
291
- reduceRight(
292
- callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any,
293
- initialValue?: any
294
- ): any {
295
- dependant(this)
296
- const result =
297
- initialValue !== undefined
298
- ? this.reduceRight(callbackfn as any, initialValue)
299
- : (this as any).reduceRight(callbackfn as any)
300
- return reactive(result)
301
- }
302
-
303
- slice(start?: number, end?: number): any[] {
304
- for (const i of range(start || 0, end || this.length - 1)) dependant(this, i)
305
- return start === undefined
306
- ? this.slice()
307
- : end === undefined
308
- ? this.slice(start)
309
- : this.slice(start, end)
310
- }
311
-
312
- concat(...items: any[]): any[] {
313
- dependant(this)
314
- return reactive(this.concat(...items))
315
- }
316
-
317
- join(separator?: string): string {
318
- dependant(this)
319
- return this.join(separator as any)
320
- }
321
-
322
- forEach(callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any): void {
323
- dependant(this)
324
- this.forEach((value, index, array) => {
325
- callbackfn.call(thisArg, reactive(value), index, array)
326
- })
327
- }
328
-
329
- // no need to make it dependant on indexes after the found one
330
- every<S>(
331
- predicate: (value: any, index: number, array: any[]) => value is S,
332
- thisArg?: any
333
- ): this is S[]
334
- every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean
335
- every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
336
- const length = this.length
337
-
338
- for (let i = 0; i < length; i++) {
339
- dependant(this, i)
340
- if (!callbackfn.call(thisArg, reactive(this[i]), i, this)) {
341
- return false
342
- }
343
- }
344
-
345
- dependant(this, 'length')
346
- return true
347
- }
348
-
349
- some(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
350
- const length = this.length
351
-
352
- for (let i = 0; i < length; i++) {
353
- dependant(this, i)
354
- if (callbackfn.call(thisArg, reactive(this[i]), i, this)) {
355
- return true
356
- }
357
- }
358
-
359
- dependant(this, 'length')
360
- return false
361
- }
362
- // Side-effectful
363
- push(...items: any[]) {
364
- const oldLength = this.length
365
- try {
366
- return this.push(...items)
367
- } finally {
368
- touched(
369
- this,
370
- { type: 'bunch', method: 'push' },
371
- range(oldLength, oldLength + items.length - 1, { length: true })
372
- )
373
- }
374
- }
375
-
376
- pop() {
377
- if (this.length === 0) return undefined
378
- try {
379
- return reactive(this.pop())
380
- } finally {
381
- touched(this, { type: 'bunch', method: 'pop' }, index(this.length))
382
- }
383
- }
384
-
385
- shift() {
386
- if (this.length === 0) return undefined
387
- try {
388
- return reactive(this.shift())
389
- } finally {
390
- touched(this, { type: 'bunch', method: 'shift' }, range(0, this.length + 1, { length: true }))
391
- }
392
- }
393
-
394
- unshift(...items: any[]) {
395
- try {
396
- return this.unshift(...items)
397
- } finally {
398
- touched(
399
- this,
400
- { type: 'bunch', method: 'unshift' },
401
- range(0, this.length - items.length, { length: true })
402
- )
403
- }
404
- }
405
-
406
- splice(start: number, deleteCount?: number, ...items: any[]) {
407
- const oldLength = this.length
408
-
409
- // Normalize start index
410
- let actualStart = start
411
- if (actualStart < 0) actualStart = Math.max(oldLength + actualStart, 0)
412
- else actualStart = Math.min(actualStart, oldLength)
413
-
414
- // Normalize deleteCount
415
- let actualDeleteCount = deleteCount
416
- if (actualDeleteCount === undefined) {
417
- actualDeleteCount = oldLength - actualStart
418
- } else {
419
- actualDeleteCount = Math.max(0, Math.min(actualDeleteCount, oldLength - actualStart))
420
- }
421
-
422
- try {
423
- if (deleteCount === undefined) return reactive(this.splice(start))
424
- return reactive(this.splice(start, deleteCount, ...items))
425
- } finally {
426
- touched(
427
- this,
428
- { type: 'bunch', method: 'splice' },
429
- actualDeleteCount === items.length
430
- ? range(actualStart, actualStart + actualDeleteCount - 1)
431
- : range(actualStart, oldLength + Math.max(items.length - actualDeleteCount, 0), {
432
- length: true,
433
- })
434
- )
435
- }
436
- }
437
-
438
- reverse() {
439
- try {
440
- return this.reverse()
441
- } finally {
442
- touched(this, { type: 'bunch', method: 'reverse' }, range(0, this.length - 1))
443
- }
444
- }
445
-
446
- sort(compareFn?: (a: any, b: any) => number) {
447
- compareFn = compareFn || ((a, b) => a.toString().localeCompare(b.toString()))
448
- try {
449
- return this.sort((a, b) => compareFn(reactive(a), reactive(b))) as any
450
- } finally {
451
- touched(this, { type: 'bunch', method: 'sort' }, range(0, this.length - 1))
452
- }
453
- }
454
-
455
- fill(value: any, start?: number, end?: number) {
456
- const len = this.length
457
- let k = start === undefined ? 0 : start
458
- if (k < 0) k = Math.max(len + k, 0)
459
- else k = Math.min(k, len)
460
-
461
- let final = end === undefined ? len : end
462
- if (final < 0) final = Math.max(len + final, 0)
463
- else final = Math.min(final, len)
464
-
465
- try {
466
- if (start === undefined) return this.fill(value) as any
467
- if (end === undefined) return this.fill(value, start) as any
468
- return this.fill(value, start, end) as any
469
- } finally {
470
- if (final > k) {
471
- touched(this, { type: 'bunch', method: 'fill' }, range(k, final - 1))
472
- }
473
- }
474
- }
475
-
476
- copyWithin(target: number, start: number, end?: number) {
477
- try {
478
- if (end === undefined) return this.copyWithin(target, start) as any
479
- return this.copyWithin(target, start, end) as any
480
- } finally {
481
- const len = this.length
482
-
483
- let to = target
484
- if (to < 0) to = Math.max(len + to, 0)
485
- else if (to >= len) to = len
486
-
487
- let from = start
488
- if (from < 0) from = Math.max(len + from, 0)
489
- else if (from >= len) from = len
490
-
491
- let final = end === undefined ? len : end
492
- if (final < 0) final = Math.max(len + final, 0)
493
- else if (final >= len) final = len
494
-
495
- const count = Math.min(final - from, len - to)
496
-
497
- if (count > 0) {
498
- touched(
499
- this,
500
- { type: 'bunch', method: 'copyWithin' },
501
- range(to, to + count - 1)
502
- )
503
- }
504
-
505
- }
506
- }
507
- }
@@ -1,168 +0,0 @@
1
- import { FoolProof } from '../utils'
2
- import { effect, untracked } from './effects'
3
- import { cleanedBy, cleanup } from './interface'
4
- import { memoize } from './memoize'
5
- import { reactive } from './proxy'
6
- import type { ScopedCallback } from './types'
7
-
8
- /**
9
- * Result of a reactive scan, which is a reactive array of accumulated values
10
- * with an attached cleanup function.
11
- */
12
- export type ScanResult<Output> = readonly Output[] & { [cleanup]: ScopedCallback }
13
-
14
- /**
15
- * Perform a reactive scan over an array of items.
16
- *
17
- * This implementation is highly optimized for performance and fine-grained reactivity:
18
- * - **Incremental Updates**: Changes to an item only trigger re-computation from that
19
- * point onwards in the result chain.
20
- * - **Move Optimization**: If items are moved within the array, their accumulated
21
- * values are reused as long as their predecessor remains the same.
22
- * - **Duplicate Support**: Correctly handles multiple occurrences of the same object
23
- * instance using an internal occurrence tracking mechanism.
24
- * - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
25
- * automatically cleared when source items are garbage collected.
26
- *
27
- * @example
28
- * ```typescript
29
- * const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
30
- * const sum = scan(source, (acc, item) => acc + item.val, 0)
31
- *
32
- * expect([...sum]).toEqual([1, 3, 6])
33
- *
34
- * // Modifying an item only re-computes subsequent sums
35
- * source[1].val = 10
36
- * expect([...sum]).toEqual([1, 11, 14])
37
- * ```
38
- *
39
- * @param source The source array of objects (will be made reactive)
40
- * @param callback The accumulator function called with (accumulator, currentItem)
41
- * @param initialValue The starting value for the accumulation
42
- * @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
43
- */
44
- export function scan<Input extends object, Output>(
45
- source: readonly Input[],
46
- callback: (acc: Output, val: Input) => Output,
47
- initialValue: Output
48
- ): ScanResult<Output> {
49
- const observedSource = reactive(source)
50
- const result = reactive([] as Output[])
51
-
52
- // Track effects for each index to dispose them when the array shrinks
53
- const indexEffects = new Map<number, ScopedCallback>()
54
- // Mapping from index to its current intermediate object
55
- const indexToIntermediate = reactive([] as Intermediate[])
56
- const intermediaries = new WeakMap<Input, Intermediate[]>()
57
-
58
- class Intermediate {
59
- public prev: Intermediate | undefined
60
- constructor(public val: Input, prev: Intermediate | undefined) {
61
- this.prev = prev
62
- }
63
-
64
- @memoize
65
- get acc(): Output {
66
- const prevAcc = this.prev ? this.prev.acc : initialValue
67
- return callback(prevAcc, this.val)
68
- }
69
- }
70
-
71
- function disposeIndex(index: number) {
72
- const stop = indexEffects.get(index)
73
- if (stop) {
74
- stop()
75
- indexEffects.delete(index)
76
- untracked(() => {
77
- Reflect.deleteProperty(indexToIntermediate as any, index)
78
- Reflect.deleteProperty(result as any, index)
79
- })
80
- }
81
- }
82
-
83
- const mainEffect = effect(function scanMainEffect({ ascend }) {
84
- const length = observedSource.length
85
- const occurrenceCount = new Map<Input, number>()
86
- let prev: Intermediate | undefined = undefined
87
-
88
- for (let i = 0; i < length; i++) {
89
- const val = FoolProof.get(observedSource as any, i, observedSource) as Input
90
-
91
- if (!(val && (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
92
- throw new Error('scan: items must be objects (WeakKey) for intermediate caching')
93
- }
94
-
95
- const count = occurrenceCount.get(val) ?? 0
96
- occurrenceCount.set(val, count + 1)
97
-
98
- let list = intermediaries.get(val)
99
- if (!list) {
100
- list = []
101
- intermediaries.set(val, list)
102
- }
103
-
104
- let intermediate = list[count]
105
- if (!intermediate) {
106
- intermediate = reactive(new Intermediate(val, prev))
107
- list[count] = intermediate
108
- } else {
109
- // Update the link.
110
- if (untracked(() => intermediate.prev) !== prev) {
111
- intermediate.prev = prev
112
- }
113
- }
114
-
115
- // Update the reactive mapping for this index
116
- if (indexToIntermediate[i] !== intermediate) {
117
- indexToIntermediate[i] = intermediate
118
- }
119
-
120
- // If we don't have an effect for this index yet, create one
121
- if (!indexEffects.has(i)) {
122
- ascend(() => {
123
- const index = i
124
- const stop = effect(function scanIndexSyncEffect() {
125
- const inter = indexToIntermediate[index]
126
- if (inter) {
127
- const accValue = inter.acc
128
- untracked(() => {
129
- result[index] = accValue
130
- })
131
- }
132
- })
133
- indexEffects.set(index, stop)
134
- })
135
- }
136
-
137
- prev = intermediate
138
- }
139
-
140
- // Cleanup trailing indices
141
- for (const index of Array.from(indexEffects.keys())) {
142
- if (index >= length) disposeIndex(index)
143
- }
144
-
145
- // Ensure result length matches source length
146
- untracked(() => {
147
- if (result.length !== length) {
148
- FoolProof.set(result as any, 'length', length, result)
149
- }
150
- })
151
- })
152
-
153
- return cleanedBy(result, () => {
154
- mainEffect()
155
- for (const stop of indexEffects.values()) stop()
156
- indexEffects.clear()
157
- }) as ScanResult<Output>
158
- }
159
-
160
- export function resolve<Output>(cb: () => Output[]): Output[] & { [cleanup]: ScopedCallback } {
161
- const result = reactive([] as Output[])
162
- return cleanedBy(result, effect(() => {
163
- const source = cb()
164
- if (result.length !== source.length) result.length = source.length
165
- for (let i = 0; i < source.length; i++)
166
- if (result[i] !== source[i]) result[i] = source[i]
167
- }))
168
- }