mutts 1.0.7 → 1.0.9

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 (191) hide show
  1. package/README.md +72 -17
  2. package/dist/browser.cjs +3111 -0
  3. package/dist/browser.cjs.map +1 -0
  4. package/dist/browser.d.ts +1391 -1
  5. package/dist/browser.esm.js +264 -35
  6. package/dist/browser.esm.js.map +1 -1
  7. package/dist/chunks/async-browser-lvzLOCgk.cjs +297 -0
  8. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
  9. package/dist/chunks/async-core-CRLKP3l-.cjs +29 -0
  10. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  11. package/dist/chunks/async-node-C3DeIb0y.cjs +96 -0
  12. package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
  13. package/dist/chunks/index-2vea86wD.esm.js +3011 -0
  14. package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
  15. package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
  16. package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
  17. package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
  18. package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
  19. package/dist/debug/debug.d.ts +122 -0
  20. package/dist/debug/debug.d.ts.map +1 -0
  21. package/dist/debug/index.d.ts +4 -0
  22. package/dist/debug/index.d.ts.map +1 -0
  23. package/dist/debug/lineage-panel.d.ts +5 -0
  24. package/dist/debug/lineage-panel.d.ts.map +1 -0
  25. package/dist/debug/lineage.d.ts +79 -0
  26. package/dist/debug/lineage.d.ts.map +1 -0
  27. package/dist/debug.cjs +35 -0
  28. package/dist/debug.cjs.map +1 -0
  29. package/dist/debug.d.ts +209 -0
  30. package/dist/debug.esm.js +2 -0
  31. package/dist/debug.esm.js.map +1 -0
  32. package/dist/devtools/devtool/devtools.d.ts +1 -0
  33. package/dist/devtools/devtool/devtools.d.ts.map +1 -0
  34. package/dist/devtools/devtool/panel.d.ts +2 -0
  35. package/dist/devtools/devtool/panel.d.ts.map +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/mutts.umd.js +1 -1
  38. package/dist/mutts.umd.js.map +1 -1
  39. package/dist/mutts.umd.min.js +1 -1
  40. package/dist/mutts.umd.min.js.map +1 -1
  41. package/dist/node.cjs +107 -0
  42. package/dist/node.cjs.map +1 -0
  43. package/dist/node.d.ts +2 -2
  44. package/dist/node.esm.js +86 -33
  45. package/dist/node.esm.js.map +1 -1
  46. package/dist/src/async/browser.d.ts +2 -0
  47. package/dist/src/async/browser.d.ts.map +1 -0
  48. package/dist/src/async/index.d.ts +19 -0
  49. package/dist/src/async/index.d.ts.map +1 -0
  50. package/dist/src/async/node.d.ts +2 -0
  51. package/dist/src/async/node.d.ts.map +1 -0
  52. package/dist/src/decorator.d.ts +106 -0
  53. package/dist/src/decorator.d.ts.map +1 -0
  54. package/dist/src/destroyable.d.ts +87 -0
  55. package/dist/src/destroyable.d.ts.map +1 -0
  56. package/dist/src/entry-browser.d.ts +3 -0
  57. package/dist/src/entry-browser.d.ts.map +1 -0
  58. package/dist/src/entry-node.d.ts +3 -0
  59. package/dist/src/entry-node.d.ts.map +1 -0
  60. package/dist/src/eventful.d.ts +20 -0
  61. package/dist/src/eventful.d.ts.map +1 -0
  62. package/dist/src/flavored.d.ts +33 -0
  63. package/dist/src/flavored.d.ts.map +1 -0
  64. package/dist/src/index.d.ts +14 -0
  65. package/dist/src/index.d.ts.map +1 -0
  66. package/dist/src/indexable.d.ts +243 -0
  67. package/dist/src/indexable.d.ts.map +1 -0
  68. package/dist/src/introspection.d.ts +27 -0
  69. package/dist/src/introspection.d.ts.map +1 -0
  70. package/dist/src/iterableWeak.d.ts +53 -0
  71. package/dist/src/iterableWeak.d.ts.map +1 -0
  72. package/dist/src/mixins.d.ts +25 -0
  73. package/dist/src/mixins.d.ts.map +1 -0
  74. package/dist/src/promiseChain.d.ts +20 -0
  75. package/dist/src/promiseChain.d.ts.map +1 -0
  76. package/dist/src/reactive/array.d.ts +48 -0
  77. package/dist/src/reactive/array.d.ts.map +1 -0
  78. package/dist/src/reactive/buffer.d.ts +120 -0
  79. package/dist/src/reactive/buffer.d.ts.map +1 -0
  80. package/dist/src/reactive/change.d.ts +29 -0
  81. package/dist/src/reactive/change.d.ts.map +1 -0
  82. package/dist/src/reactive/deep-touch.d.ts +28 -0
  83. package/dist/src/reactive/deep-touch.d.ts.map +1 -0
  84. package/dist/src/reactive/deep-watch-state.d.ts +25 -0
  85. package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
  86. package/dist/src/reactive/deep-watch.d.ts +20 -0
  87. package/dist/src/reactive/deep-watch.d.ts.map +1 -0
  88. package/dist/src/reactive/describe.d.ts +12 -0
  89. package/dist/src/reactive/describe.d.ts.map +1 -0
  90. package/dist/src/reactive/effect-context.d.ts +34 -0
  91. package/dist/src/reactive/effect-context.d.ts.map +1 -0
  92. package/dist/src/reactive/effects.d.ts +164 -0
  93. package/dist/src/reactive/effects.d.ts.map +1 -0
  94. package/dist/src/reactive/index.d.ts +19 -0
  95. package/dist/src/reactive/index.d.ts.map +1 -0
  96. package/dist/src/reactive/map.d.ts +28 -0
  97. package/dist/src/reactive/map.d.ts.map +1 -0
  98. package/dist/src/reactive/memoize.d.ts +28 -0
  99. package/dist/src/reactive/memoize.d.ts.map +1 -0
  100. package/dist/src/reactive/non-reactive-state.d.ts +9 -0
  101. package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
  102. package/dist/src/reactive/non-reactive.d.ts +11 -0
  103. package/dist/src/reactive/non-reactive.d.ts.map +1 -0
  104. package/dist/src/reactive/project.d.ts +40 -0
  105. package/dist/src/reactive/project.d.ts.map +1 -0
  106. package/dist/src/reactive/proxy-state.d.ts +8 -0
  107. package/dist/src/reactive/proxy-state.d.ts.map +1 -0
  108. package/dist/src/reactive/proxy.d.ts +23 -0
  109. package/dist/src/reactive/proxy.d.ts.map +1 -0
  110. package/dist/src/reactive/record.d.ts +115 -0
  111. package/dist/src/reactive/record.d.ts.map +1 -0
  112. package/dist/src/reactive/register.d.ts +125 -0
  113. package/dist/src/reactive/register.d.ts.map +1 -0
  114. package/dist/src/reactive/registry.d.ts +21 -0
  115. package/dist/src/reactive/registry.d.ts.map +1 -0
  116. package/dist/src/reactive/set.d.ts +26 -0
  117. package/dist/src/reactive/set.d.ts.map +1 -0
  118. package/dist/src/reactive/tracking.d.ts +7 -0
  119. package/dist/src/reactive/tracking.d.ts.map +1 -0
  120. package/dist/src/reactive/types.d.ts +424 -0
  121. package/dist/src/reactive/types.d.ts.map +1 -0
  122. package/dist/src/reactive/watch.d.ts +48 -0
  123. package/dist/src/reactive/watch.d.ts.map +1 -0
  124. package/dist/src/std-decorators.d.ts +45 -0
  125. package/dist/src/std-decorators.d.ts.map +1 -0
  126. package/dist/src/utils.d.ts +49 -0
  127. package/dist/src/utils.d.ts.map +1 -0
  128. package/dist/src/zone.d.ts +40 -0
  129. package/dist/src/zone.d.ts.map +1 -0
  130. package/dist/types-DaHFfhlN.d.ts +414 -0
  131. package/docs/ai/api-reference.md +1 -1
  132. package/docs/ai/manual.md +443 -17
  133. package/docs/events.md +57 -2
  134. package/docs/flavored.md +210 -0
  135. package/docs/reactive/advanced.md +2 -3
  136. package/docs/reactive/attend.md +143 -0
  137. package/docs/reactive/collections.md +55 -0
  138. package/docs/reactive/core.md +120 -4
  139. package/docs/reactive/debugging.md +8 -10
  140. package/docs/reactive/describe.md +85 -0
  141. package/docs/reactive/error-handling.md +121 -0
  142. package/docs/reactive/project.md +1 -1
  143. package/docs/reactive/scan.md +215 -0
  144. package/docs/reactive.md +2 -0
  145. package/docs/std-decorators.md +171 -52
  146. package/docs/zone.md +9 -6
  147. package/package.json +44 -27
  148. package/src/async/browser.ts +277 -41
  149. package/src/async/index.ts +23 -4
  150. package/src/async/node.ts +78 -32
  151. package/src/entry-browser.ts +5 -0
  152. package/src/entry-node.ts +5 -0
  153. package/src/eventful.ts +82 -49
  154. package/src/flavored.ts +106 -0
  155. package/src/index.d.ts +12 -9
  156. package/src/index.ts +2 -1
  157. package/src/introspection.ts +1 -1
  158. package/src/reactive/array.ts +160 -80
  159. package/src/reactive/buffer.ts +187 -27
  160. package/src/reactive/change.ts +16 -23
  161. package/src/reactive/deep-touch.ts +19 -14
  162. package/src/reactive/deep-watch-state.ts +3 -3
  163. package/src/reactive/deep-watch.ts +7 -4
  164. package/src/reactive/describe.ts +39 -0
  165. package/src/reactive/effect-context.ts +71 -6
  166. package/src/reactive/effects.ts +445 -393
  167. package/src/reactive/index.ts +12 -14
  168. package/src/reactive/map.ts +8 -24
  169. package/src/reactive/memoize.ts +26 -3
  170. package/src/reactive/project.md +107 -0
  171. package/src/reactive/project.ts +48 -71
  172. package/src/reactive/proxy.ts +21 -3
  173. package/src/reactive/record.ts +30 -45
  174. package/src/reactive/register.ts +138 -19
  175. package/src/reactive/registry.ts +22 -9
  176. package/src/reactive/set.ts +7 -20
  177. package/src/reactive/tracking.ts +9 -12
  178. package/src/reactive/types.ts +104 -24
  179. package/src/reactive/{interface.ts → watch.ts} +29 -59
  180. package/src/std-decorators.ts +56 -17
  181. package/src/utils.ts +3 -4
  182. package/src/zone.ts +43 -28
  183. package/dist/browser.js +0 -161
  184. package/dist/browser.js.map +0 -1
  185. package/dist/chunks/index-BFYK02LG.js +0 -6378
  186. package/dist/chunks/index-BFYK02LG.js.map +0 -1
  187. package/dist/chunks/index-CNR6QRUl.esm.js +0 -6291
  188. package/dist/chunks/index-CNR6QRUl.esm.js.map +0 -1
  189. package/dist/node.js +0 -136
  190. package/dist/node.js.map +0 -1
  191. package/src/reactive/debug.ts +0 -517
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
3
+ *
4
+ * Each property defined in `flavors` returns a new flavored function that transforms
5
+ * how the original function is called. This enables a fluent API where properties
6
+ * create specialized variants of the base function.
7
+ *
8
+ * @param fn - The base function to flavor
9
+ * @param flavors - Object defining the flavor properties (getters or methods)
10
+ * @returns A proxy of the function with the flavor properties attached
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * function greet(name: string, options?: { loud?: boolean }) {
15
+ * const greeting = `Hello, ${name}!`
16
+ * return options?.loud ? greeting.toUpperCase() : greeting
17
+ * }
18
+ *
19
+ * const flavoredGreet = flavored(greet, {
20
+ * get loud() {
21
+ * return createFlavor(this, (name, opts) => [name, { ...opts, loud: true }])
22
+ * }
23
+ * })
24
+ *
25
+ * flavoredGreet('World') // "Hello, World!"
26
+ * flavoredGreet.loud('World') // "HELLO, WORLD!"
27
+ * ```
28
+ */
29
+ import { named } from './utils'
30
+
31
+ /**
32
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
33
+ */
34
+ export function flavored<T extends (...args: any[]) => any, F extends Record<string, any>>(
35
+ fn: T,
36
+ flavors: F
37
+ ): T & F {
38
+ // Store flavors for recursive flavoring
39
+ ;(fn as any).flavors = flavors
40
+
41
+ return new Proxy(fn, {
42
+ get(target, prop, receiver) {
43
+ if (prop in flavors) {
44
+ return Reflect.get(flavors, prop, receiver)
45
+ }
46
+ return (target as any)[prop]
47
+ },
48
+ }) as T & F
49
+ }
50
+
51
+ /**
52
+ * Creates a new flavored function that transforms arguments before calling the base.
53
+ *
54
+ * @param fn - The base flavored function
55
+ * @param transform - Function that receives the original arguments and returns transformed arguments
56
+ * @returns A new flavored function with the transformation applied
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const loudGreet = createFlavor(greet, (name, opts) => [name, { ...opts, loud: true }])
61
+ * ```
62
+ */
63
+ export function createFlavor<T extends (...args: any[]) => any>(
64
+ fn: T,
65
+ transform: (...args: Parameters<T>) => Parameters<T>,
66
+ name?: string
67
+ ): T {
68
+ const fct = function flavorWrapper(this: any, ...args: Parameters<T>) {
69
+ return fn.apply(this, transform(...args))
70
+ }
71
+ if (name) named(name, fct)
72
+
73
+ return flavored(fct as any as T, (fn as any).flavors || {})
74
+ }
75
+
76
+ /**
77
+ * Creates a new flavored function that merges options objects.
78
+ * Useful when the last argument is an options object that should be merged.
79
+ *
80
+ * @param fn - The base flavored function
81
+ * @param defaultOptions - Options to merge with the provided options
82
+ * @returns A new flavored function with merged options
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const opaqueEffect = flavorOptions(effect, { opaque: true })
87
+ * const namedEffect = flavorOptions(effect, { name: 'myEffect' })
88
+ * ```
89
+ */
90
+ export function flavorOptions<T extends (...args: any[]) => any>(
91
+ fn: T,
92
+ defaultOptions: Record<string, any>,
93
+ name?: string
94
+ ): T {
95
+ const fct = function flavorOptionsWrapper(this: any, ...args: any[]) {
96
+ const lastArg = args[args.length - 1]
97
+ const mergedArgs =
98
+ lastArg !== undefined && typeof lastArg === 'object'
99
+ ? [...args.slice(0, -1), { ...defaultOptions, ...lastArg }]
100
+ : [...args, defaultOptions]
101
+ return fn.apply(this, mergedArgs)
102
+ }
103
+ if (name) named(name, fct)
104
+
105
+ return flavored(fct as any as T, (fn as any).flavors || {})
106
+ }
package/src/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
- // Augment Array.isArray to properly handle readonly arrays in type narrowing
2
- interface ArrayConstructor {
3
- /**
4
- * Determines whether an object is an array.
5
- * @param arg Any value to test.
6
- * @returns True if the value is an array (mutable or readonly), false otherwise.
7
- */
8
- isArray(arg: any): arg is any[] | readonly any[]
9
- }
1
+ export * from './decorator'
2
+ export * from './destroyable'
3
+ export * from './eventful'
4
+ export * from './indexable'
5
+ export * from './iterableWeak'
6
+ export * from './mixins'
7
+ export * from './promiseChain'
8
+ export * from './reactive'
9
+ export * from './std-decorators'
10
+ export * from './utils'
11
+ export * from './zone'
12
+ export * from './async'
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
+ export * from './async'
1
2
  export * from './decorator'
2
3
  export * from './destroyable'
3
4
  export * from './eventful'
5
+ export * from './flavored'
4
6
  export * from './indexable'
5
7
  export * from './iterableWeak'
6
8
  export * from './mixins'
@@ -30,7 +32,6 @@ if (globalScope) {
30
32
  let source = 'mutts/index'
31
33
  const viteEval = eval
32
34
  try {
33
- // @ts-ignore
34
35
  if (typeof __filename !== 'undefined') source = __filename
35
36
  else {
36
37
  // Using eval to avoid SyntaxError in CJS environments where import.meta is not allowed
@@ -14,7 +14,7 @@ import {
14
14
  type MutationRecord,
15
15
  type ObjectNode,
16
16
  type ReactivityGraph,
17
- } from './reactive/debug'
17
+ } from '../debug/debug'
18
18
  import { options, type ReactiveDebugInfo, ReactiveErrorCode } from './reactive/types'
19
19
 
20
20
  export {
@@ -25,29 +25,18 @@ export abstract class Indexer extends Array {
25
25
  dependant(this, i)
26
26
  return reactive(this[i])
27
27
  }
28
+ // Returns undefined intentionally: signals the proxy handler that notifications
29
+ // were already dispatched via touched(), preventing double notification
28
30
  set(i: number, value: any) {
29
31
  const added = i >= this.length
30
32
  this[i] = value
31
33
  touched(this, { type: 'set', prop: i }, index(i, { length: added }))
32
34
  }
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
35
  }
46
36
  const indexLess = { get: FoolProof.get, set: FoolProof.set }
47
37
  Object.assign(FoolProof, {
48
38
  get(obj: any, prop: any, receiver: any) {
49
39
  if (obj instanceof Array && typeof prop === 'string') {
50
- if (prop === 'length') return Indexer.prototype.getLength.call(obj)
51
40
  const index = parseInt(prop)
52
41
  if (!Number.isNaN(index)) return Indexer.prototype.get.call(obj, index)
53
42
  }
@@ -55,7 +44,6 @@ Object.assign(FoolProof, {
55
44
  },
56
45
  set(obj: any, prop: any, value: any, receiver: any) {
57
46
  if (obj instanceof Array && typeof prop === 'string') {
58
- if (prop === 'length') return Indexer.prototype.setLength.call(obj, value)
59
47
  const index = parseInt(prop)
60
48
  if (!Number.isNaN(index)) return Indexer.prototype.set.call(obj, index, value)
61
49
  }
@@ -67,13 +55,24 @@ export abstract class ReactiveArray extends Array {
67
55
  toJSON() {
68
56
  return this
69
57
  }
70
- get [Symbol.toStringTag]() {
71
- return 'ReactiveArray'
58
+ get length(): number {
59
+ dependant(this, 'length')
60
+ return this.length
61
+ }
62
+ set length(value: number) {
63
+ const oldLength = this.length
64
+ if (oldLength === value) return
65
+ try {
66
+ this.length = value
67
+ } finally {
68
+ touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }))
69
+ }
72
70
  }
73
71
  // Safe array access with negative indices
74
72
  at(index: number): any {
75
73
  const actualIndex = index < 0 ? this.length + index : index
76
74
  dependant(this, actualIndex)
75
+ if (index < 0) dependant(this, 'length')
77
76
  if (actualIndex < 0 || actualIndex >= this.length) return undefined
78
77
  return reactive(this[actualIndex])
79
78
  }
@@ -102,17 +101,17 @@ export abstract class ReactiveArray extends Array {
102
101
  }
103
102
 
104
103
  // Iterator methods with reactivity tracking
105
- entries() {
104
+ entries(): any {
106
105
  dependant(this)
107
106
  return makeReactiveEntriesIterator(this.entries())
108
107
  }
109
108
 
110
- keys() {
109
+ keys(): any {
111
110
  dependant(this, 'length')
112
111
  return this.keys()
113
112
  }
114
113
 
115
- values() {
114
+ values(): any {
116
115
  dependant(this)
117
116
  return makeReactiveIterator(this.values())
118
117
  }
@@ -136,36 +135,57 @@ export abstract class ReactiveArray extends Array {
136
135
  }
137
136
 
138
137
  indexOf(searchElement: any, fromIndex?: number): number {
139
- dependant(this)
138
+ const length = this.length
139
+ let i = fromIndex === undefined ? 0 : fromIndex
140
+ if (i < 0) i = Math.max(length + i, 0)
141
+
140
142
  const unwrappedSearch = unwrap(searchElement)
141
- // Check both wrapped and unwrapped versions since array may contain either
142
- const index = this.indexOf(unwrappedSearch, fromIndex)
143
- if (index !== -1) return index
144
- // If not found with unwrapped, try with wrapped (in case array contains wrapped version)
145
- return this.indexOf(searchElement, fromIndex)
143
+
144
+ for (; i < length; i++) {
145
+ dependant(this, i)
146
+ const item = this[i]
147
+ if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
148
+ return i
149
+ }
150
+ }
151
+
152
+ dependant(this, 'length')
153
+ return -1
146
154
  }
147
155
 
148
156
  lastIndexOf(searchElement: any, fromIndex?: number): number {
149
- dependant(this)
157
+ const length = this.length
158
+ let i = fromIndex === undefined ? length - 1 : fromIndex
159
+ if (i >= length) i = length - 1
160
+ if (i < 0) i = Math.max(length + i, -1) // -1 ensures loop condition i >= 0 works correctly
161
+
150
162
  const unwrappedSearch = unwrap(searchElement)
151
- // Check both wrapped and unwrapped versions since array may contain either
152
- const index = this.lastIndexOf(unwrappedSearch, fromIndex)
153
- if (index !== -1) return index
154
- // If not found with unwrapped, try with wrapped (in case array contains wrapped version)
155
- return this.lastIndexOf(searchElement, fromIndex)
163
+
164
+ for (; i >= 0; i--) {
165
+ dependant(this, i)
166
+ const item = this[i]
167
+ if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
168
+ return i
169
+ }
170
+ }
171
+
172
+ // If we scanned the whole relevant part and didn't find it, we depend on length
173
+ // (because adding elements might shift indices or add the element)
174
+ // Actually for lastIndexOf, if we start from end, length dependency is implicit in the start index calculation?
175
+ // But if we return -1, it means we didn't find it.
176
+ // If we push an element, should lastIndexOf update?
177
+ // Yes, if the new element is the one we are looking for.
178
+ dependant(this, 'length')
179
+ return -1
156
180
  }
157
181
 
158
182
  includes(searchElement: any, fromIndex?: number): boolean {
159
- dependant(this)
160
- const unwrappedSearch = unwrap(searchElement)
161
- // Check both wrapped and unwrapped versions since array may contain either
162
- return this.includes(unwrappedSearch, fromIndex) || this.includes(searchElement, fromIndex)
183
+ return this.indexOf(searchElement, fromIndex) !== -1
163
184
  }
164
185
 
165
186
  find(predicate: (this: any, value: any, index: number, obj: any[]) => boolean, thisArg?: any): any
166
187
  find(searchElement: any, fromIndex?: number): any
167
188
  find(predicateOrElement: any, thisArg?: any): any {
168
- dependant(this)
169
189
  if (typeof predicateOrElement === 'function') {
170
190
  const predicate = predicateOrElement as (
171
191
  this: any,
@@ -173,12 +193,18 @@ export abstract class ReactiveArray extends Array {
173
193
  index: number,
174
194
  obj: any[]
175
195
  ) => boolean
176
- return reactive(
177
- this.find(
178
- (value, index, array) => predicate.call(thisArg, reactive(value), index, array),
179
- thisArg
180
- )
181
- )
196
+ const length = this.length
197
+
198
+ for (let i = 0; i < length; i++) {
199
+ dependant(this, i)
200
+ const val = reactive(this[i])
201
+ if (predicate.call(thisArg, val, i, this)) {
202
+ return val
203
+ }
204
+ }
205
+
206
+ dependant(this, 'length')
207
+ return undefined
182
208
  }
183
209
  const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
184
210
  const index = this.indexOf(predicateOrElement, fromIndex)
@@ -192,7 +218,6 @@ export abstract class ReactiveArray extends Array {
192
218
  ): number
193
219
  findIndex(searchElement: any, fromIndex?: number): number
194
220
  findIndex(predicateOrElement: any, thisArg?: any): number {
195
- dependant(this)
196
221
  if (typeof predicateOrElement === 'function') {
197
222
  const predicate = predicateOrElement as (
198
223
  this: any,
@@ -200,10 +225,18 @@ export abstract class ReactiveArray extends Array {
200
225
  index: number,
201
226
  obj: any[]
202
227
  ) => boolean
203
- return this.findIndex(
204
- (value, index, array) => predicate.call(thisArg, reactive(value), index, array),
205
- thisArg
206
- )
228
+ const length = this.length
229
+
230
+ for (let i = 0; i < length; i++) {
231
+ dependant(this, i)
232
+ const val = reactive(this[i])
233
+ if (predicate.call(thisArg, val, i, this)) {
234
+ return i
235
+ }
236
+ }
237
+
238
+ dependant(this, 'length')
239
+ return -1
207
240
  }
208
241
  const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
209
242
  return this.indexOf(predicateOrElement, fromIndex)
@@ -291,7 +324,6 @@ export abstract class ReactiveArray extends Array {
291
324
  })
292
325
  }
293
326
 
294
- // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some),
295
327
  // no need to make it dependant on indexes after the found one
296
328
  every<S>(
297
329
  predicate: (value: any, index: number, array: any[]) => value is S,
@@ -299,21 +331,34 @@ export abstract class ReactiveArray extends Array {
299
331
  ): this is S[]
300
332
  every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean
301
333
  every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
302
- dependant(this)
303
- return this.every(
304
- (value, index, array) => callbackfn.call(thisArg, reactive(value), index, array),
305
- thisArg
306
- )
334
+ const length = this.length
335
+
336
+ for (let i = 0; i < length; i++) {
337
+ dependant(this, i)
338
+ if (!callbackfn.call(thisArg, reactive(this[i]), i, this)) {
339
+ return false
340
+ }
341
+ }
342
+
343
+ dependant(this, 'length')
344
+ return true
307
345
  }
346
+
308
347
  some(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean {
309
- dependant(this)
310
- return this.some(
311
- (value, index, array) => callbackfn.call(thisArg, reactive(value), index, array),
312
- thisArg
313
- )
348
+ const length = this.length
349
+
350
+ for (let i = 0; i < length; i++) {
351
+ dependant(this, i)
352
+ if (callbackfn.call(thisArg, reactive(this[i]), i, this)) {
353
+ return true
354
+ }
355
+ }
356
+
357
+ dependant(this, 'length')
358
+ return false
314
359
  }
315
360
  // Side-effectful
316
- push(...items: any[]) {
361
+ push(...items: any[]): number {
317
362
  const oldLength = this.length
318
363
  try {
319
364
  return this.push(...items)
@@ -326,7 +371,7 @@ export abstract class ReactiveArray extends Array {
326
371
  }
327
372
  }
328
373
 
329
- pop() {
374
+ pop(): any {
330
375
  if (this.length === 0) return undefined
331
376
  try {
332
377
  return reactive(this.pop())
@@ -335,7 +380,7 @@ export abstract class ReactiveArray extends Array {
335
380
  }
336
381
  }
337
382
 
338
- shift() {
383
+ shift(): any {
339
384
  if (this.length === 0) return undefined
340
385
  try {
341
386
  return reactive(this.shift())
@@ -344,7 +389,7 @@ export abstract class ReactiveArray extends Array {
344
389
  }
345
390
  }
346
391
 
347
- unshift(...items: any[]) {
392
+ unshift(...items: any[]): number {
348
393
  try {
349
394
  return this.unshift(...items)
350
395
  } finally {
@@ -356,9 +401,22 @@ export abstract class ReactiveArray extends Array {
356
401
  }
357
402
  }
358
403
 
359
- splice(start: number, deleteCount?: number, ...items: any[]) {
404
+ splice(start: number, deleteCount?: number, ...items: any[]): any[] {
360
405
  const oldLength = this.length
361
- if (deleteCount === undefined) deleteCount = oldLength - start
406
+
407
+ // Normalize start index
408
+ let actualStart = start
409
+ if (actualStart < 0) actualStart = Math.max(oldLength + actualStart, 0)
410
+ else actualStart = Math.min(actualStart, oldLength)
411
+
412
+ // Normalize deleteCount
413
+ let actualDeleteCount = deleteCount
414
+ if (actualDeleteCount === undefined) {
415
+ actualDeleteCount = oldLength - actualStart
416
+ } else {
417
+ actualDeleteCount = Math.max(0, Math.min(actualDeleteCount, oldLength - actualStart))
418
+ }
419
+
362
420
  try {
363
421
  if (deleteCount === undefined) return reactive(this.splice(start))
364
422
  return reactive(this.splice(start, deleteCount, ...items))
@@ -366,17 +424,16 @@ export abstract class ReactiveArray extends Array {
366
424
  touched(
367
425
  this,
368
426
  { type: 'bunch', method: 'splice' },
369
- // TODO: edge cases
370
- deleteCount === items.length
371
- ? range(start, start + deleteCount)
372
- : range(start, oldLength + Math.max(items.length - deleteCount, 0), {
427
+ actualDeleteCount === items.length
428
+ ? range(actualStart, actualStart + actualDeleteCount - 1)
429
+ : range(actualStart, oldLength + Math.max(items.length - actualDeleteCount, 0), {
373
430
  length: true,
374
431
  })
375
432
  )
376
433
  }
377
434
  }
378
435
 
379
- reverse() {
436
+ reverse(): any[] {
380
437
  try {
381
438
  return this.reverse()
382
439
  } finally {
@@ -384,7 +441,7 @@ export abstract class ReactiveArray extends Array {
384
441
  }
385
442
  }
386
443
 
387
- sort(compareFn?: (a: any, b: any) => number) {
444
+ sort(compareFn?: (a: any, b: any) => number): this {
388
445
  compareFn = compareFn || ((a, b) => a.toString().localeCompare(b.toString()))
389
446
  try {
390
447
  return this.sort((a, b) => compareFn(reactive(a), reactive(b))) as any
@@ -393,28 +450,51 @@ export abstract class ReactiveArray extends Array {
393
450
  }
394
451
  }
395
452
 
396
- fill(value: any, start?: number, end?: number) {
453
+ fill(value: any, start?: number, end?: number): this {
454
+ const len = this.length
455
+ let k = start === undefined ? 0 : start
456
+ if (k < 0) k = Math.max(len + k, 0)
457
+ else k = Math.min(k, len)
458
+
459
+ let final = end === undefined ? len : end
460
+ if (final < 0) final = Math.max(len + final, 0)
461
+ else final = Math.min(final, len)
462
+
397
463
  try {
398
464
  if (start === undefined) return this.fill(value) as any
399
465
  if (end === undefined) return this.fill(value, start) as any
400
466
  return this.fill(value, start, end) as any
401
467
  } finally {
402
- touched(this, { type: 'bunch', method: 'fill' }, range(0, this.length - 1))
468
+ if (final > k) {
469
+ touched(this, { type: 'bunch', method: 'fill' }, range(k, final - 1))
470
+ }
403
471
  }
404
472
  }
405
473
 
406
- copyWithin(target: number, start: number, end?: number) {
474
+ copyWithin(target: number, start: number, end?: number): this {
407
475
  try {
408
476
  if (end === undefined) return this.copyWithin(target, start) as any
409
477
  return this.copyWithin(target, start, end) as any
410
478
  } finally {
411
- touched(
412
- this,
413
- { type: 'bunch', method: 'copyWithin' },
414
- // TODO: calculate the range properly
415
- range(0, this.length - 1)
416
- )
479
+ const len = this.length
480
+
481
+ let to = target
482
+ if (to < 0) to = Math.max(len + to, 0)
483
+ else if (to >= len) to = len
484
+
485
+ let from = start
486
+ if (from < 0) from = Math.max(len + from, 0)
487
+ else if (from >= len) from = len
488
+
489
+ let final = end === undefined ? len : end
490
+ if (final < 0) final = Math.max(len + final, 0)
491
+ else if (final >= len) final = len
492
+
493
+ const count = Math.min(final - from, len - to)
494
+
495
+ if (count > 0) {
496
+ touched(this, { type: 'bunch', method: 'copyWithin' }, range(to, to + count - 1))
497
+ }
417
498
  }
418
- // Touch all affected indices with a single allProps call
419
499
  }
420
500
  }