mutts 1.0.7 → 1.0.8

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 (132) hide show
  1. package/README.md +61 -23
  2. package/dist/async/browser.d.ts +2 -0
  3. package/dist/async/browser.d.ts.map +1 -0
  4. package/dist/async/index.d.ts +18 -0
  5. package/dist/async/index.d.ts.map +1 -0
  6. package/dist/async/node.d.ts +2 -0
  7. package/dist/async/node.d.ts.map +1 -0
  8. package/dist/{chunks/index-BFYK02LG.js → browser.cjs} +169 -60
  9. package/dist/browser.cjs.map +1 -0
  10. package/dist/browser.d.ts +1654 -1
  11. package/dist/browser.esm.js +260 -25
  12. package/dist/browser.esm.js.map +1 -1
  13. package/dist/chunks/async-browser-CA0jPWIi.cjs +304 -0
  14. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +1 -0
  15. package/dist/chunks/async-core-UqHzvJ-S.cjs +25 -0
  16. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +1 -0
  17. package/dist/chunks/async-node-BYHuGTni.cjs +103 -0
  18. package/dist/chunks/async-node-BYHuGTni.cjs.map +1 -0
  19. package/dist/chunks/{index-CNR6QRUl.esm.js → index-DhaOVusv.esm.js} +173 -53
  20. package/dist/chunks/index-DhaOVusv.esm.js.map +1 -0
  21. package/dist/decorator.d.ts +106 -0
  22. package/dist/decorator.d.ts.map +1 -0
  23. package/dist/destroyable.d.ts +87 -0
  24. package/dist/destroyable.d.ts.map +1 -0
  25. package/dist/devtools/devtool/devtools.d.ts +1 -0
  26. package/dist/devtools/devtool/devtools.d.ts.map +1 -0
  27. package/dist/devtools/devtool/panel.d.ts +2 -0
  28. package/dist/devtools/devtool/panel.d.ts.map +1 -0
  29. package/dist/entry-browser.d.ts +3 -0
  30. package/dist/entry-browser.d.ts.map +1 -0
  31. package/dist/entry-node.d.ts +3 -0
  32. package/dist/entry-node.d.ts.map +1 -0
  33. package/dist/eventful.d.ts +18 -0
  34. package/dist/eventful.d.ts.map +1 -0
  35. package/dist/index.d.ts +13 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/indexable.d.ts +243 -0
  38. package/dist/indexable.d.ts.map +1 -0
  39. package/dist/introspection.d.ts +27 -0
  40. package/dist/introspection.d.ts.map +1 -0
  41. package/dist/iterableWeak.d.ts +53 -0
  42. package/dist/iterableWeak.d.ts.map +1 -0
  43. package/dist/mixins.d.ts +25 -0
  44. package/dist/mixins.d.ts.map +1 -0
  45. package/dist/mutts.umd.js +1 -1
  46. package/dist/mutts.umd.js.map +1 -1
  47. package/dist/mutts.umd.min.js +1 -1
  48. package/dist/mutts.umd.min.js.map +1 -1
  49. package/dist/node.cjs +105 -0
  50. package/dist/node.cjs.map +1 -0
  51. package/dist/node.d.ts +1 -2
  52. package/dist/node.esm.js +91 -32
  53. package/dist/node.esm.js.map +1 -1
  54. package/dist/promiseChain.d.ts +20 -0
  55. package/dist/promiseChain.d.ts.map +1 -0
  56. package/dist/reactive/array.d.ts +49 -0
  57. package/dist/reactive/array.d.ts.map +1 -0
  58. package/dist/reactive/buffer.d.ts +44 -0
  59. package/dist/reactive/buffer.d.ts.map +1 -0
  60. package/dist/reactive/change.d.ts +29 -0
  61. package/dist/reactive/change.d.ts.map +1 -0
  62. package/dist/reactive/debug.d.ts +111 -0
  63. package/dist/reactive/debug.d.ts.map +1 -0
  64. package/dist/reactive/deep-touch.d.ts +28 -0
  65. package/dist/reactive/deep-touch.d.ts.map +1 -0
  66. package/dist/reactive/deep-watch-state.d.ts +25 -0
  67. package/dist/reactive/deep-watch-state.d.ts.map +1 -0
  68. package/dist/reactive/deep-watch.d.ts +19 -0
  69. package/dist/reactive/deep-watch.d.ts.map +1 -0
  70. package/dist/reactive/effect-context.d.ts +7 -0
  71. package/dist/reactive/effect-context.d.ts.map +1 -0
  72. package/dist/reactive/effects.d.ts +151 -0
  73. package/dist/reactive/effects.d.ts.map +1 -0
  74. package/dist/reactive/index.d.ts +20 -0
  75. package/dist/reactive/index.d.ts.map +1 -0
  76. package/dist/reactive/interface.d.ts +64 -0
  77. package/dist/reactive/interface.d.ts.map +1 -0
  78. package/dist/reactive/map.d.ts +30 -0
  79. package/dist/reactive/map.d.ts.map +1 -0
  80. package/dist/reactive/memoize.d.ts +5 -0
  81. package/dist/reactive/memoize.d.ts.map +1 -0
  82. package/dist/reactive/non-reactive-state.d.ts +9 -0
  83. package/dist/reactive/non-reactive-state.d.ts.map +1 -0
  84. package/dist/reactive/non-reactive.d.ts +11 -0
  85. package/dist/reactive/non-reactive.d.ts.map +1 -0
  86. package/dist/reactive/project.d.ts +41 -0
  87. package/dist/reactive/project.d.ts.map +1 -0
  88. package/dist/reactive/proxy-state.d.ts +8 -0
  89. package/dist/reactive/proxy-state.d.ts.map +1 -0
  90. package/dist/reactive/proxy.d.ts +23 -0
  91. package/dist/reactive/proxy.d.ts.map +1 -0
  92. package/dist/reactive/record.d.ts +116 -0
  93. package/dist/reactive/record.d.ts.map +1 -0
  94. package/dist/reactive/register.d.ts +64 -0
  95. package/dist/reactive/register.d.ts.map +1 -0
  96. package/dist/reactive/registry.d.ts +20 -0
  97. package/dist/reactive/registry.d.ts.map +1 -0
  98. package/dist/reactive/set.d.ts +28 -0
  99. package/dist/reactive/set.d.ts.map +1 -0
  100. package/dist/reactive/tracking.d.ts +7 -0
  101. package/dist/reactive/tracking.d.ts.map +1 -0
  102. package/dist/reactive/types.d.ts +376 -0
  103. package/dist/reactive/types.d.ts.map +1 -0
  104. package/dist/std-decorators.d.ts +50 -0
  105. package/dist/std-decorators.d.ts.map +1 -0
  106. package/dist/utils.d.ts +49 -0
  107. package/dist/utils.d.ts.map +1 -0
  108. package/dist/zone.d.ts +40 -0
  109. package/dist/zone.d.ts.map +1 -0
  110. package/docs/std-decorators.md +69 -1
  111. package/docs/zone.md +7 -0
  112. package/package.json +39 -27
  113. package/src/async/browser.ts +266 -34
  114. package/src/async/index.ts +17 -2
  115. package/src/async/node.ts +89 -31
  116. package/src/entry-browser.ts +5 -0
  117. package/src/entry-node.ts +5 -0
  118. package/src/index.d.ts +12 -9
  119. package/src/index.ts +1 -0
  120. package/src/reactive/array.ts +139 -52
  121. package/src/reactive/effect-context.ts +3 -3
  122. package/src/reactive/index.ts +2 -1
  123. package/src/reactive/map.ts +1 -1
  124. package/src/reactive/set.ts +1 -1
  125. package/src/utils.ts +1 -1
  126. package/src/zone.ts +19 -8
  127. package/dist/browser.js +0 -161
  128. package/dist/browser.js.map +0 -1
  129. package/dist/chunks/index-BFYK02LG.js.map +0 -1
  130. package/dist/chunks/index-CNR6QRUl.esm.js.map +0 -1
  131. package/dist/node.js +0 -136
  132. package/dist/node.js.map +0 -1
package/src/async/node.ts CHANGED
@@ -1,46 +1,104 @@
1
1
  import { createHook } from 'node:async_hooks'
2
2
  import { Hook, Restorer, asyncHooks } from '.'
3
3
 
4
+ // 1. Generic async_hooks implementation for Hooks
5
+ // This maintains support for 'asyncHooks.addHook' for generic use cases.
6
+
4
7
  const hooks = new Set<Hook>()
5
- const restorersPerAsyncId = new Map<number, Set<Restorer>>()
6
- const undoersPerAsyncId = new Map<number, Set<() => void>>()
7
-
8
- createHook({
9
- init(asyncId) {
10
- const restorers = new Set<Restorer>()
11
- for (const hook of hooks) {
12
- restorers.add(hook())
8
+ asyncHooks.addHook = function (hook: Hook) {
9
+ hooks.add(hook)
10
+ return () => {
11
+ hooks.delete(hook)
12
+ }
13
+ }
14
+
15
+ const contexts = new Map<number, Restorer[]>()
16
+ const activeUndoers = new Map<number, (() => void)[]>()
17
+
18
+ // Helper to capture current hooks state
19
+ function captureRestorers() {
20
+ if (hooks.size === 0) return []
21
+ const restorers: Restorer[] = []
22
+ for (const h of hooks) {
23
+ const r = h()
24
+ if (r) restorers.push(r)
25
+ }
26
+ return restorers
27
+ }
28
+
29
+ // Manual Wrap function to handle Promise callbacks
30
+ function wrap<Args extends any[], R>(fn: ((...args: Args) => R) | null | undefined) {
31
+ if (typeof fn !== 'function') return fn
32
+ const restorers = captureRestorers()
33
+ if (restorers.length === 0) return fn
34
+
35
+ return function (this: any, ...args: Args) {
36
+ const undoers: (() => void)[] = []
37
+ for (const restore of restorers) {
38
+ const u = restore()
39
+ if (u) undoers.push(u)
40
+ }
41
+ try {
42
+ return fn.apply(this, args)
43
+ } finally {
44
+ for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
13
45
  }
14
- restorersPerAsyncId.set(asyncId, restorers)
46
+ }
47
+ }
48
+
49
+ const hook = createHook({
50
+ init(asyncId, type, triggerId, resource) {
51
+ // Used for native resources like Timers
52
+ const restorers = captureRestorers()
53
+ if (restorers.length > 0) contexts.set(asyncId, restorers)
15
54
  },
16
55
  before(asyncId) {
17
- const restorers = restorersPerAsyncId.get(asyncId)
18
- if (restorers) {
19
- const undoers = new Set<() => void>()
20
- for (const restore of restorers) {
21
- undoers.add(restore())
22
- }
23
- undoersPerAsyncId.set(asyncId, undoers)
56
+ const restorers = contexts.get(asyncId)
57
+ if (!restorers) return
58
+ const undoers: (() => void)[] = []
59
+ for (const restore of restorers) {
60
+ const u = restore()
61
+ if (u) undoers.push(u)
24
62
  }
63
+ if (undoers.length > 0) activeUndoers.set(asyncId, undoers)
25
64
  },
26
65
  after(asyncId) {
27
- const undoers = undoersPerAsyncId.get(asyncId)
28
- if (undoers) {
29
- for (const undo of undoers) undo()
30
- undoersPerAsyncId.delete(asyncId)
31
- }
66
+ const undoers = activeUndoers.get(asyncId)
67
+ if (!undoers) return
68
+ for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
69
+ activeUndoers.delete(asyncId)
32
70
  },
33
71
  destroy(asyncId) {
34
- restorersPerAsyncId.delete(asyncId)
35
- undoersPerAsyncId.delete(asyncId)
36
- }
37
- }).enable()
72
+ contexts.delete(asyncId)
73
+ activeUndoers.delete(asyncId)
74
+ },
75
+ })
76
+ hook.enable()
38
77
 
39
- asyncHooks.addHook = function (hook: Hook) {
40
- hooks.add(hook)
41
- return () => {
42
- hooks.delete(hook)
43
- }
78
+ // 2. Shadow Promise Implementation
79
+ // Ensures V8 await resumptions are visible as .then callbacks, wrapping them to restore context.
80
+
81
+ const OriginalPromise = globalThis.Promise
82
+ const originalMethods = {
83
+ then: OriginalPromise.prototype.then,
84
+ catch: OriginalPromise.prototype.catch,
85
+ finally: OriginalPromise.prototype.finally,
86
+ resolve: OriginalPromise.resolve,
87
+ reject: OriginalPromise.reject,
88
+ all: OriginalPromise.all,
44
89
  }
45
90
 
46
- export * from '../index'
91
+
92
+
93
+ // Patch prototype
94
+ OriginalPromise.prototype.then = function(onFulfilled, onRejected) {
95
+ return originalMethods.then.call(this, wrap(onFulfilled), wrap(onRejected))
96
+ } as any
97
+ OriginalPromise.prototype.catch = function(onRejected) {
98
+ return originalMethods.catch.call(this, wrap(onRejected))
99
+ } as any
100
+ OriginalPromise.prototype.finally = function(onFinally) {
101
+ return originalMethods.finally.call(this, wrap(onFinally))
102
+ } as any
103
+
104
+
@@ -0,0 +1,5 @@
1
+ // Import environment-specific patches first (SIDE EFFECTS)
2
+ import './async/browser'
3
+
4
+ // Then export the library
5
+ export * from './index'
@@ -0,0 +1,5 @@
1
+ // Import environment-specific patches first (SIDE EFFECTS)
2
+ import './async/node'
3
+
4
+ // Then export the library
5
+ export * from './index'
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
@@ -9,6 +9,7 @@ export * from './reactive'
9
9
  export * from './std-decorators'
10
10
  export * from './utils'
11
11
  export * from './zone'
12
+ export * from './async'
12
13
 
13
14
  import pkg from '../package.json'
14
15
 
@@ -74,6 +74,7 @@ export abstract class ReactiveArray extends Array {
74
74
  at(index: number): any {
75
75
  const actualIndex = index < 0 ? this.length + index : index
76
76
  dependant(this, actualIndex)
77
+ if (index < 0) dependant(this, 'length')
77
78
  if (actualIndex < 0 || actualIndex >= this.length) return undefined
78
79
  return reactive(this[actualIndex])
79
80
  }
@@ -136,36 +137,57 @@ export abstract class ReactiveArray extends Array {
136
137
  }
137
138
 
138
139
  indexOf(searchElement: any, fromIndex?: number): number {
139
- dependant(this)
140
+ const length = this.length
141
+ let i = fromIndex === undefined ? 0 : fromIndex
142
+ if (i < 0) i = Math.max(length + i, 0)
143
+
140
144
  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)
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
146
156
  }
147
157
 
148
158
  lastIndexOf(searchElement: any, fromIndex?: number): number {
149
- dependant(this)
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
+
150
164
  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)
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
156
182
  }
157
183
 
158
184
  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)
185
+ return this.indexOf(searchElement, fromIndex) !== -1
163
186
  }
164
187
 
165
188
  find(predicate: (this: any, value: any, index: number, obj: any[]) => boolean, thisArg?: any): any
166
189
  find(searchElement: any, fromIndex?: number): any
167
190
  find(predicateOrElement: any, thisArg?: any): any {
168
- dependant(this)
169
191
  if (typeof predicateOrElement === 'function') {
170
192
  const predicate = predicateOrElement as (
171
193
  this: any,
@@ -173,12 +195,18 @@ export abstract class ReactiveArray extends Array {
173
195
  index: number,
174
196
  obj: any[]
175
197
  ) => boolean
176
- return reactive(
177
- this.find(
178
- (value, index, array) => predicate.call(thisArg, reactive(value), index, array),
179
- thisArg
180
- )
181
- )
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
182
210
  }
183
211
  const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
184
212
  const index = this.indexOf(predicateOrElement, fromIndex)
@@ -192,7 +220,6 @@ export abstract class ReactiveArray extends Array {
192
220
  ): number
193
221
  findIndex(searchElement: any, fromIndex?: number): number
194
222
  findIndex(predicateOrElement: any, thisArg?: any): number {
195
- dependant(this)
196
223
  if (typeof predicateOrElement === 'function') {
197
224
  const predicate = predicateOrElement as (
198
225
  this: any,
@@ -200,10 +227,18 @@ export abstract class ReactiveArray extends Array {
200
227
  index: number,
201
228
  obj: any[]
202
229
  ) => boolean
203
- return this.findIndex(
204
- (value, index, array) => predicate.call(thisArg, reactive(value), index, array),
205
- thisArg
206
- )
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
207
242
  }
208
243
  const fromIndex = typeof thisArg === 'number' ? thisArg : undefined
209
244
  return this.indexOf(predicateOrElement, fromIndex)
@@ -291,7 +326,6 @@ export abstract class ReactiveArray extends Array {
291
326
  })
292
327
  }
293
328
 
294
- // TODO: re-implement for fun dependencies? (eg - every only check the first ones until it find some),
295
329
  // no need to make it dependant on indexes after the found one
296
330
  every<S>(
297
331
  predicate: (value: any, index: number, array: any[]) => value is S,
@@ -299,18 +333,31 @@ export abstract class ReactiveArray extends Array {
299
333
  ): this is S[]
300
334
  every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean
301
335
  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
- )
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
307
347
  }
348
+
308
349
  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
- )
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
314
361
  }
315
362
  // Side-effectful
316
363
  push(...items: any[]) {
@@ -358,7 +405,20 @@ export abstract class ReactiveArray extends Array {
358
405
 
359
406
  splice(start: number, deleteCount?: number, ...items: any[]) {
360
407
  const oldLength = this.length
361
- if (deleteCount === undefined) deleteCount = oldLength - start
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
+
362
422
  try {
363
423
  if (deleteCount === undefined) return reactive(this.splice(start))
364
424
  return reactive(this.splice(start, deleteCount, ...items))
@@ -366,10 +426,9 @@ export abstract class ReactiveArray extends Array {
366
426
  touched(
367
427
  this,
368
428
  { 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), {
429
+ actualDeleteCount === items.length
430
+ ? range(actualStart, actualStart + actualDeleteCount - 1)
431
+ : range(actualStart, oldLength + Math.max(items.length - actualDeleteCount, 0), {
373
432
  length: true,
374
433
  })
375
434
  )
@@ -394,12 +453,23 @@ export abstract class ReactiveArray extends Array {
394
453
  }
395
454
 
396
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
+
397
465
  try {
398
466
  if (start === undefined) return this.fill(value) as any
399
467
  if (end === undefined) return this.fill(value, start) as any
400
468
  return this.fill(value, start, end) as any
401
469
  } finally {
402
- touched(this, { type: 'bunch', method: 'fill' }, range(0, this.length - 1))
470
+ if (final > k) {
471
+ touched(this, { type: 'bunch', method: 'fill' }, range(k, final - 1))
472
+ }
403
473
  }
404
474
  }
405
475
 
@@ -408,13 +478,30 @@ export abstract class ReactiveArray extends Array {
408
478
  if (end === undefined) return this.copyWithin(target, start) as any
409
479
  return this.copyWithin(target, start, end) as any
410
480
  } finally {
411
- touched(
412
- this,
413
- { type: 'bunch', method: 'copyWithin' },
414
- // TODO: calculate the range properly
415
- range(0, this.length - 1)
416
- )
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
+
417
505
  }
418
- // Touch all affected indices with a single allProps call
419
506
  }
420
507
  }
@@ -3,10 +3,10 @@ import { asyncZone, ZoneAggregator, ZoneHistory } from '../zone'
3
3
  import { getRoot } from './registry'
4
4
  import { type ScopedCallback } from './types'
5
5
 
6
- export const effectHistory = tag(new ZoneHistory<ScopedCallback>(), 'effectHistory')
7
- tag(effectHistory.present, 'effectHistory.present')
6
+ export const effectHistory = tag('effectHistory', new ZoneHistory<ScopedCallback>())
7
+ tag('effectHistory.present', effectHistory.present)
8
8
  asyncZone.add(effectHistory)
9
- export const effectAggregator = tag(new ZoneAggregator(effectHistory.present), 'effectAggregator')
9
+ export const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present))
10
10
 
11
11
  export function isRunning(effect: ScopedCallback): boolean {
12
12
  const root = getRoot(effect)
@@ -27,8 +27,9 @@ export { immutables, isNonReactive, registerNativeReactivity } from './non-react
27
27
  export { getActiveProjection, project } from './project'
28
28
  export { isReactive, ReactiveBase, reactive, unwrap } from './proxy'
29
29
  export { organize, organized } from './record'
30
- export { scan, type ScanResult } from './buffer'
30
+ export { scan, type ScanResult, resolve } from './buffer'
31
31
  export { Register, register } from './register'
32
+ export { getActiveEffect, effectAggregator } from './effect-context'
32
33
  export {
33
34
  type DependencyAccess,
34
35
  type Evolution,
@@ -113,7 +113,7 @@ export abstract class ReactiveMap<K, V> extends Map<K, V> {
113
113
  return this
114
114
  },
115
115
  [Symbol.dispose]() {},
116
- }
116
+ } as any // TODO: real iterator? (If easy)
117
117
  }
118
118
 
119
119
  // Implement Map methods with reactivity
@@ -125,6 +125,6 @@ export abstract class ReactiveSet<T> extends Set<T> {
125
125
  return this
126
126
  },
127
127
  [Symbol.dispose]() {},
128
- }
128
+ } as any //TODO? (something easy)
129
129
  }
130
130
  }
package/src/utils.ts CHANGED
@@ -275,7 +275,7 @@ export function contentRef(container: object) {
275
275
  return contentRefs.get(container)
276
276
  }
277
277
 
278
- export function tag<T extends object>(obj: T, name: string): T {
278
+ export function tag<T extends object>(name: string, obj: T): T {
279
279
  Object.defineProperties(obj, {
280
280
  [Symbol.toStringTag]: {
281
281
  value: name,
package/src/zone.ts CHANGED
@@ -1,23 +1,34 @@
1
1
  import { asyncHooks } from "./async"
2
2
  import { named, tag } from "./utils"
3
3
 
4
+ interface InternalZoneUse<T> {
5
+ enter(value?: T): unknown
6
+ leave(entered: unknown): void
7
+ }
8
+ function isu<T>(z: AZone<T> | InternalZoneUse<T>): InternalZoneUse<T> {
9
+ return z as InternalZoneUse<T>
10
+ }
4
11
  export abstract class AZone<T> {
5
12
  abstract active?: T
6
- enter(value?: T): unknown {
13
+ protected enter(value?: T): unknown {
7
14
  const prev = this.active
8
15
  this.active = value
9
16
  return prev
10
17
  }
11
- leave(entered: unknown): void {
18
+ protected leave(entered: unknown): void {
12
19
  this.active = entered as T | undefined
13
20
  }
14
21
  with<R>(value: T, fn: () => R): R {
15
22
  const entered = this.enter(value)
23
+ let res: R;
16
24
  try {
17
- return fn()
25
+ res = fn()
18
26
  } finally {
19
27
  this.leave(entered)
20
28
  }
29
+ // [HACK]: Sanitization
30
+ // See BROWSER_ASYNC_POLYFILL.md
31
+ return asyncHooks.sanitizePromise(res) as R
21
32
  }
22
33
  root<R>(fn: () => R): R {
23
34
  let prev = this.enter()
@@ -61,11 +72,11 @@ export class ZoneHistory<T> extends AZone<HistoryValue<T>> {
61
72
  enter(value?: T) {
62
73
  if(value && self.history.has(value)) throw new Error('ZoneHistory: re-entering historical zone')
63
74
  if(value !== undefined) self.history.add(value)
64
- return { added: value, entered: controlled.enter(value) }
75
+ return { added: value, entered: isu(controlled).enter(value) }
65
76
  },
66
77
  leave(entered: { added: T | undefined, entered: unknown }) {
67
78
  if(entered.added !== undefined) self.history.delete(entered.added)
68
- return controlled.leave(entered.entered)
79
+ return isu(controlled).leave(entered.entered)
69
80
  }
70
81
  })
71
82
  )
@@ -98,12 +109,12 @@ export class ZoneAggregator extends AZone<Map<AZone<unknown>, unknown>> {
98
109
  const entered = new Map<AZone<unknown>, unknown>()
99
110
  for (const z of this.#zones) {
100
111
  const v = value?.get(z)
101
- entered.set(z, z.enter(v))
112
+ entered.set(z, isu(z).enter(v))
102
113
  }
103
114
  return entered
104
115
  }
105
116
  leave(entered: Map<AZone<unknown>, unknown>): void {
106
- for (const z of this.#zones) z.leave(entered.get(z))
117
+ for (const z of this.#zones) isu(z).leave(entered.get(z))
107
118
  }
108
119
  add(z: AZone<unknown>) {
109
120
  this.#zones.add(z)
@@ -116,7 +127,7 @@ export class ZoneAggregator extends AZone<Map<AZone<unknown>, unknown>> {
116
127
  }
117
128
  }
118
129
 
119
- export const asyncZone = tag(new ZoneAggregator(), 'async')
130
+ export const asyncZone = tag('async', new ZoneAggregator())
120
131
  asyncHooks.addHook(() => {
121
132
  const zone = asyncZone.active
122
133
  return () => {