mutts 1.0.2 → 1.0.4

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 (104) hide show
  1. package/README.md +14 -6
  2. package/dist/chunks/{_tslib-C-cuVLvZ.js → _tslib-BgjropY9.js} +9 -1
  3. package/dist/chunks/_tslib-BgjropY9.js.map +1 -0
  4. package/dist/chunks/{_tslib-CMEnd0VE.esm.js → _tslib-Mzh1rNsX.esm.js} +9 -2
  5. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +1 -0
  6. package/dist/chunks/{decorator-D4DU97Zg.js → decorator-DLvrD0UF.js} +42 -19
  7. package/dist/chunks/decorator-DLvrD0UF.js.map +1 -0
  8. package/dist/chunks/{decorator-GnHw1Az7.esm.js → decorator-DqiszP7i.esm.js} +42 -19
  9. package/dist/chunks/decorator-DqiszP7i.esm.js.map +1 -0
  10. package/dist/chunks/index-79Kk8D6e.esm.js +4857 -0
  11. package/dist/chunks/index-79Kk8D6e.esm.js.map +1 -0
  12. package/dist/chunks/index-GRBSx0mB.js +4908 -0
  13. package/dist/chunks/index-GRBSx0mB.js.map +1 -0
  14. package/dist/decorator.esm.js +1 -1
  15. package/dist/decorator.js +1 -1
  16. package/dist/destroyable.d.ts +1 -1
  17. package/dist/destroyable.esm.js +1 -1
  18. package/dist/destroyable.esm.js.map +1 -1
  19. package/dist/destroyable.js +1 -1
  20. package/dist/destroyable.js.map +1 -1
  21. package/dist/devtools/devtools.html +9 -0
  22. package/dist/devtools/devtools.js +5 -0
  23. package/dist/devtools/devtools.js.map +1 -0
  24. package/dist/devtools/manifest.json +8 -0
  25. package/dist/devtools/panel.css +72 -0
  26. package/dist/devtools/panel.html +31 -0
  27. package/dist/devtools/panel.js +13048 -0
  28. package/dist/devtools/panel.js.map +1 -0
  29. package/dist/eventful.esm.js +1 -1
  30. package/dist/eventful.js +1 -1
  31. package/dist/index.d.ts +18 -63
  32. package/dist/index.esm.js +4 -4
  33. package/dist/index.js +37 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexable.d.ts +187 -1
  36. package/dist/indexable.esm.js +197 -3
  37. package/dist/indexable.esm.js.map +1 -1
  38. package/dist/indexable.js +198 -2
  39. package/dist/indexable.js.map +1 -1
  40. package/dist/mutts.umd.js +1 -1
  41. package/dist/mutts.umd.js.map +1 -1
  42. package/dist/mutts.umd.min.js +1 -1
  43. package/dist/mutts.umd.min.js.map +1 -1
  44. package/dist/promiseChain.esm.js.map +1 -1
  45. package/dist/promiseChain.js.map +1 -1
  46. package/dist/reactive.d.ts +602 -97
  47. package/dist/reactive.esm.js +3 -3
  48. package/dist/reactive.js +32 -10
  49. package/dist/reactive.js.map +1 -1
  50. package/dist/std-decorators.esm.js +1 -1
  51. package/dist/std-decorators.js +1 -1
  52. package/docs/ai/api-reference.md +133 -0
  53. package/docs/ai/manual.md +105 -0
  54. package/docs/iterableWeak.md +646 -0
  55. package/docs/reactive/advanced.md +1280 -0
  56. package/docs/reactive/collections.md +767 -0
  57. package/docs/reactive/core.md +973 -0
  58. package/docs/reactive.md +21 -9545
  59. package/package.json +18 -5
  60. package/src/decorator.ts +266 -0
  61. package/src/destroyable.ts +199 -0
  62. package/src/eventful.ts +77 -0
  63. package/src/index.d.ts +9 -0
  64. package/src/index.ts +9 -0
  65. package/src/indexable.ts +484 -0
  66. package/src/introspection.ts +59 -0
  67. package/src/iterableWeak.ts +233 -0
  68. package/src/mixins.ts +123 -0
  69. package/src/promiseChain.ts +110 -0
  70. package/src/reactive/array.ts +414 -0
  71. package/src/reactive/change.ts +134 -0
  72. package/src/reactive/debug.ts +517 -0
  73. package/src/reactive/deep-touch.ts +268 -0
  74. package/src/reactive/deep-watch-state.ts +82 -0
  75. package/src/reactive/deep-watch.ts +168 -0
  76. package/src/reactive/effect-context.ts +94 -0
  77. package/src/reactive/effects.ts +1345 -0
  78. package/src/reactive/index.ts +76 -0
  79. package/src/reactive/interface.ts +223 -0
  80. package/src/reactive/map.ts +171 -0
  81. package/src/reactive/mapped.ts +130 -0
  82. package/src/reactive/memoize.ts +107 -0
  83. package/src/reactive/non-reactive-state.ts +49 -0
  84. package/src/reactive/non-reactive.ts +43 -0
  85. package/src/reactive/project.project.md +93 -0
  86. package/src/reactive/project.ts +335 -0
  87. package/src/reactive/proxy-state.ts +27 -0
  88. package/src/reactive/proxy.ts +289 -0
  89. package/src/reactive/record.ts +196 -0
  90. package/src/reactive/register.ts +421 -0
  91. package/src/reactive/set.ts +144 -0
  92. package/src/reactive/tracking.ts +101 -0
  93. package/src/reactive/types.ts +358 -0
  94. package/src/reactive/zone.ts +208 -0
  95. package/src/std-decorators.ts +217 -0
  96. package/src/utils.ts +117 -0
  97. package/dist/chunks/_tslib-C-cuVLvZ.js.map +0 -1
  98. package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +0 -1
  99. package/dist/chunks/decorator-D4DU97Zg.js.map +0 -1
  100. package/dist/chunks/decorator-GnHw1Az7.esm.js.map +0 -1
  101. package/dist/chunks/index-DBScoeCX.esm.js +0 -1960
  102. package/dist/chunks/index-DBScoeCX.esm.js.map +0 -1
  103. package/dist/chunks/index-DOTmXL89.js +0 -1983
  104. package/dist/chunks/index-DOTmXL89.js.map +0 -1
@@ -0,0 +1,421 @@
1
+ import { ArrayReadForward, forwardArray, getAt, Indexable, setAt } from '../indexable'
2
+ import { effect } from './effects'
3
+ import { unreactive } from './interface'
4
+ import { reactive } from './proxy'
5
+ import { type DependencyFunction, prototypeForwarding, type ScopedCallback } from './types'
6
+
7
+ // TODO: use register in a real-world crud situation, have "events" for add, delete, update
8
+
9
+ type KeyFunction<T, K extends PropertyKey> = (item: T) => K
10
+
11
+ // Helper to work around TypeScript limitation: base class expressions cannot reference class type parameters
12
+ function getRegisterBase<T>() {
13
+ class RegisterBase extends Indexable(ArrayReadForward, {
14
+ get(this: any, index: number) {
15
+ return this[getAt](index)
16
+ },
17
+ set(this: any, index: number, value: T) {
18
+ this[setAt](index, value)
19
+ },
20
+ getLength(this: any) {
21
+ return this.length
22
+ },
23
+ setLength(this: any, value: number) {
24
+ this.length = value
25
+ },
26
+ }) {
27
+ toArray(): T[] {
28
+ return Array.from(this)
29
+ }
30
+ }
31
+ return RegisterBase as new () => ArrayReadForward<T> & {
32
+ [x: number]: T
33
+ toArray(): T[]
34
+ }
35
+ }
36
+ interface RegisterInstance<T> extends ArrayReadForward<T> {
37
+ [index: number]: T
38
+ }
39
+
40
+ @unreactive
41
+ class RegisterClass<T, K extends PropertyKey = PropertyKey>
42
+ extends getRegisterBase<any>()
43
+ implements RegisterInstance<T>
44
+ {
45
+ protected get [forwardArray](): readonly T[] {
46
+ return this.toArray()
47
+ }
48
+ readonly #keyFn: KeyFunction<T, K>
49
+ readonly #keys: K[]
50
+ readonly #values: Map<K, T>
51
+ readonly #usage = new Map<K, number>()
52
+ readonly #valueInfo = new Map<T, { key: K; stop?: ScopedCallback }>()
53
+ readonly #keyEffects = new Set<ScopedCallback>()
54
+ readonly #ascend: DependencyFunction
55
+
56
+ constructor(keyFn: KeyFunction<T, K>, initial?: Iterable<T>) {
57
+ super()
58
+ /* Moved below initialization */
59
+ let ascendGet: DependencyFunction | undefined
60
+ effect(({ ascend }) => {
61
+ ascendGet = ascend
62
+ })
63
+ this.#ascend = ascendGet!
64
+ if (typeof keyFn !== 'function') throw new Error('Register requires a key function')
65
+ this.#keyFn = keyFn
66
+ this.#keys = reactive([] as K[])
67
+ this.#values = reactive(new Map<K, T>())
68
+ Object.defineProperties(this, {
69
+ [prototypeForwarding]: { value: this.#keys },
70
+ })
71
+ if (initial) this.push(...initial)
72
+ }
73
+
74
+ private ensureKey(value: T): K {
75
+ let info = this.#valueInfo.get(value)
76
+ if (info) return info.key
77
+ info = { key: undefined as unknown as K }
78
+ this.#valueInfo.set(value, info)
79
+ this.#ascend(() => {
80
+ const stop = effect(({ reaction }) => {
81
+ const nextKey = this.#keyFn(value)
82
+ this.assertValidKey(nextKey)
83
+ const previousKey = info!.key
84
+ if (reaction && previousKey !== undefined && !Object.is(nextKey, previousKey))
85
+ this.#rekeyValue(value, previousKey, nextKey)
86
+ info!.key = nextKey
87
+ })
88
+ info!.stop = stop
89
+ this.#keyEffects.add(stop)
90
+ })
91
+ if (info.key === undefined) throw new Error('Register key function must return a property key')
92
+ return info.key
93
+ }
94
+
95
+ private assertValidKey(key: unknown): asserts key is K {
96
+ const type = typeof key
97
+ if (type !== 'string' && type !== 'number' && type !== 'symbol')
98
+ throw new Error('Register key function must return a property key')
99
+ }
100
+
101
+ private setKeyValue(key: K, value: T) {
102
+ const existing = this.#values.get(key)
103
+ if (existing !== undefined && existing !== value) this.cleanupValue(existing)
104
+ this.#values.set(key, value)
105
+ }
106
+
107
+ private cleanupValue(value: T) {
108
+ const info = this.#valueInfo.get(value)
109
+ if (!info) return
110
+ const stop = info.stop
111
+ if (stop) {
112
+ info.stop = undefined
113
+ this.#keyEffects.delete(stop)
114
+ stop()
115
+ }
116
+ this.#valueInfo.delete(value)
117
+ }
118
+
119
+ private disposeKeyEffects() {
120
+ for (const value of Array.from(this.#valueInfo.keys())) this.cleanupValue(value)
121
+ this.#keyEffects.clear()
122
+ }
123
+
124
+ #rekeyValue(value: T, oldKey: K, newKey: K) {
125
+ if (Object.is(oldKey, newKey)) return
126
+ const existingValue = this.#values.get(newKey)
127
+ if (existingValue !== undefined && existingValue !== value)
128
+ throw new Error(`Register key collision for key ${String(newKey)}`)
129
+ const count = this.#usage.get(oldKey)
130
+ if (!count) return
131
+ const existingCount = this.#usage.get(newKey) ?? 0
132
+ this.setKeyValue(newKey, value)
133
+ for (let i = 0; i < this.#keys.length; i++)
134
+ if (Object.is(this.#keys[i], oldKey)) this.#keys[i] = newKey
135
+ this.#usage.set(newKey, existingCount + count)
136
+ this.#usage.delete(oldKey)
137
+ this.#values.delete(oldKey)
138
+ const updatedInfo = this.#valueInfo.get(value)
139
+ if (updatedInfo) updatedInfo.key = newKey
140
+ }
141
+
142
+ private incrementUsage(key: K) {
143
+ const count = this.#usage.get(key) ?? 0
144
+ this.#usage.set(key, count + 1)
145
+ }
146
+
147
+ private decrementUsage(key: K) {
148
+ const count = this.#usage.get(key)
149
+ if (!count) return
150
+ if (count <= 1) {
151
+ const value = this.#values.get(key)
152
+ this.#usage.delete(key)
153
+ this.#values.delete(key)
154
+ if (value !== undefined) this.cleanupValue(value)
155
+ } else {
156
+ this.#usage.set(key, count - 1)
157
+ }
158
+ }
159
+
160
+ private normalizeIndex(index: number, allowEnd = false): number {
161
+ const length = this.length
162
+ let resolved = index
163
+ if (resolved < 0) resolved = Math.max(length + resolved, 0)
164
+ if (resolved > length) {
165
+ if (allowEnd) resolved = length
166
+ else throw new RangeError('Index out of bounds')
167
+ }
168
+ if (!allowEnd && resolved === length) throw new RangeError('Index out of bounds')
169
+ return resolved
170
+ }
171
+
172
+ private assignAt(index: number, key: K, value: T) {
173
+ const oldKey = this.#keys[index]
174
+ if (oldKey !== undefined && Object.is(oldKey, key)) {
175
+ this.setKeyValue(key, value)
176
+ return
177
+ }
178
+ if (oldKey !== undefined) this.decrementUsage(oldKey as K)
179
+ this.#keys[index] = key
180
+ this.incrementUsage(key)
181
+ this.setKeyValue(key, value)
182
+ }
183
+
184
+ private insertKeyValue(index: number, key: K, value: T) {
185
+ this.#keys.splice(index, 0, key)
186
+ this.incrementUsage(key)
187
+ this.setKeyValue(key, value)
188
+ }
189
+
190
+ private rebuildFrom(values: T[]) {
191
+ this.disposeKeyEffects()
192
+ this.#keys.splice(0, this.#keys.length)
193
+ this.#usage.clear()
194
+ this.#values.clear()
195
+ for (const value of values) {
196
+ const key = this.ensureKey(value)
197
+ this.#keys.push(key)
198
+ this.incrementUsage(key)
199
+ this.#values.set(key, value)
200
+ }
201
+ }
202
+
203
+ get length(): number {
204
+ return this.#keys.length
205
+ }
206
+
207
+ [getAt](index: number): T | undefined {
208
+ const key = this.#keys[index]
209
+ return key === undefined ? undefined : this.#values.get(key)
210
+ }
211
+
212
+ [setAt](index: number, value: T): void {
213
+ const key = this.ensureKey(value)
214
+ if (index === this.length) {
215
+ this.insertKeyValue(index, key, value)
216
+ return
217
+ }
218
+ const normalized = this.normalizeIndex(index)
219
+ this.assignAt(normalized, key, value)
220
+ }
221
+
222
+ push(...items: T[]): number {
223
+ for (const item of items) {
224
+ const key = this.ensureKey(item)
225
+ this.insertKeyValue(this.length, key, item)
226
+ }
227
+ return this.length
228
+ }
229
+
230
+ pop(): T | undefined {
231
+ if (!this.length) return undefined
232
+ return this.removeAt(this.length - 1)
233
+ }
234
+
235
+ shift(): T | undefined {
236
+ if (!this.length) return undefined
237
+ return this.removeAt(0)
238
+ }
239
+
240
+ unshift(...items: T[]): number {
241
+ let index = 0
242
+ for (const item of items) {
243
+ const key = this.ensureKey(item)
244
+ this.insertKeyValue(index++, key, item)
245
+ }
246
+ return this.length
247
+ }
248
+
249
+ splice(start: number, deleteCount?: number, ...items: T[]): T[] {
250
+ const normalizedStart = this.normalizeIndex(start, true)
251
+ const maxDeletions = this.length - normalizedStart
252
+ const actualDelete = Math.min(
253
+ deleteCount === undefined ? maxDeletions : Math.max(deleteCount, 0),
254
+ maxDeletions
255
+ )
256
+ const keysToInsert: K[] = []
257
+ for (const item of items) keysToInsert.push(this.ensureKey(item))
258
+ const removedKeys = this.#keys.splice(normalizedStart, actualDelete, ...keysToInsert)
259
+ const removedValues: T[] = []
260
+ for (const key of removedKeys) {
261
+ if (key === undefined) continue
262
+ const value = this.#values.get(key as K)
263
+ this.decrementUsage(key as K)
264
+ removedValues.push(value as T)
265
+ }
266
+ for (let i = 0; i < keysToInsert.length; i++) {
267
+ const key = keysToInsert[i]
268
+ const value = items[i]
269
+ this.incrementUsage(key)
270
+ this.setKeyValue(key, value)
271
+ }
272
+ return removedValues
273
+ }
274
+
275
+ clear(): void {
276
+ this.#keys.length = 0
277
+ this.#usage.clear()
278
+ this.#values.clear()
279
+ this.disposeKeyEffects()
280
+ }
281
+
282
+ get(key: K): T | undefined {
283
+ return this.#values.get(key)
284
+ }
285
+ set(key: K, value: T): void {
286
+ if (this.#values.has(key)) this.setKeyValue(key, value)
287
+ }
288
+
289
+ remove(key: K) {
290
+ let index: number = this.indexOfKey(key)
291
+ while (index !== -1) {
292
+ this.removeAt(index)
293
+ index = this.indexOfKey(key)
294
+ }
295
+ }
296
+
297
+ public removeAt(index: number): T | undefined {
298
+ const [key] = this.#keys.splice(index, 1)
299
+ if (key === undefined) return undefined
300
+ const value = this.#values.get(key as K)
301
+ this.decrementUsage(key as K)
302
+ return value
303
+ }
304
+
305
+ /**
306
+ * Keep only the items for which the predicate returns true.
307
+ * Items for which the predicate returns false are removed.
308
+ *
309
+ * The predicate is evaluated once per distinct key; duplicate keys
310
+ * will follow the same keep/remove decision.
311
+ */
312
+ public keep(predicate: (value: T) => boolean): void {
313
+ const decisions = new Map<K, boolean>()
314
+ for (const [index, key] of this.#keys.entries()) {
315
+ if (decisions.has(key)) {
316
+ if (!decisions.get(key)) this.removeAt(index)
317
+ continue
318
+ }
319
+ const value = this.#values.get(key)
320
+ const shouldKeep = predicate(value as T)
321
+ decisions.set(key, shouldKeep)
322
+ if (!shouldKeep) this.removeAt(index)
323
+ }
324
+ }
325
+
326
+ hasKey(key: K): boolean {
327
+ return this.#usage.has(key)
328
+ }
329
+
330
+ indexOfKey(key: K): number {
331
+ return this.#keys.indexOf(key)
332
+ }
333
+
334
+ mapKeys(): IterableIterator<K> {
335
+ return this.#values.keys()
336
+ }
337
+
338
+ update(...values: T[]): void {
339
+ for (const value of values) {
340
+ const key = this.ensureKey(value)
341
+ if (this.#values.has(key)) this.setKeyValue(key, value)
342
+ }
343
+ }
344
+
345
+ upsert(insert: (value: T) => void, ...values: T[]): void {
346
+ for (const value of values) {
347
+ const key = this.ensureKey(value)
348
+ if (this.#values.has(key)) this.setKeyValue(key, value)
349
+ else insert(value)
350
+ }
351
+ }
352
+
353
+ entries(): IterableIterator<[number, T]> {
354
+ const self = this
355
+ function* iterator(): IterableIterator<[number, T]> {
356
+ for (let i = 0; i < self.#keys.length; i++) {
357
+ const val = self.#values.get(self.#keys[i])
358
+ if (val !== undefined) yield [i, val]
359
+ }
360
+ }
361
+ return iterator()
362
+ }
363
+
364
+ [Symbol.iterator](): IterableIterator<T> {
365
+ const self = this
366
+ function* iterator(): IterableIterator<T> {
367
+ for (const key of self.#keys) {
368
+ const value = self.#values.get(key)
369
+ if (value !== undefined) yield value
370
+ }
371
+ }
372
+ return iterator()
373
+ }
374
+
375
+ toString(): string {
376
+ return `[Register length=${this.length}]`
377
+ }
378
+
379
+ at(index: number): T | undefined {
380
+ const resolved = index < 0 ? this.length + index : index
381
+ if (resolved < 0 || resolved >= this.length) return undefined
382
+ return this[getAt](resolved)
383
+ }
384
+ reverse(): this {
385
+ this.#keys.reverse()
386
+ return this
387
+ }
388
+ sort(compareFn?: ((a: T, b: T) => number) | undefined): this {
389
+ const fwdCompareFn = compareFn
390
+ ? (a: K, b: K) => compareFn(this.#values.get(a) as T, this.#values.get(b) as T)
391
+ : undefined
392
+ this.#keys.sort(fwdCompareFn)
393
+ return this
394
+ }
395
+ fill(value: T, start = 0, end = this.length): this {
396
+ const values = this.toArray()
397
+ values.fill(value, start, end)
398
+ this.rebuildFrom(values)
399
+ return this
400
+ }
401
+
402
+ copyWithin(target: number, start: number, end?: number): this {
403
+ const values = this.toArray()
404
+ values.copyWithin(target, start, end)
405
+ this.rebuildFrom(values)
406
+ return this
407
+ }
408
+ }
409
+
410
+ export type Register<T, K extends PropertyKey = PropertyKey> = RegisterClass<T, K> & T[]
411
+ export const Register = RegisterClass as new <T, K extends PropertyKey = PropertyKey>(
412
+ keyFn: KeyFunction<T, K>,
413
+ initial?: Iterable<T>
414
+ ) => Register<T, K>
415
+
416
+ export function register<T, K extends PropertyKey = PropertyKey>(
417
+ keyFn: KeyFunction<T, K>,
418
+ initial?: Iterable<T>
419
+ ): Register<T, K> {
420
+ return new RegisterClass(keyFn, initial) as Register<T, K>
421
+ }
@@ -0,0 +1,144 @@
1
+ import { touched, touched1 } from './change'
2
+ import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
3
+ import { reactive } from './proxy'
4
+ import { dependant } from './tracking'
5
+ import { prototypeForwarding } from './types'
6
+
7
+ const native = Symbol('native')
8
+
9
+ /**
10
+ * Reactive wrapper around JavaScript's WeakSet class
11
+ * Only tracks individual value operations, no size tracking (WeakSet limitation)
12
+ */
13
+ export class ReactiveWeakSet<T extends object> {
14
+ declare readonly [native]: WeakSet<T>
15
+ declare readonly content: symbol
16
+
17
+ constructor(original: WeakSet<T>) {
18
+ Object.defineProperties(this, {
19
+ [native]: { value: original },
20
+ [prototypeForwarding]: { value: original },
21
+ content: { value: Symbol('content') },
22
+ [Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
23
+ })
24
+ }
25
+
26
+ add(value: T): this {
27
+ const had = this[native].has(value)
28
+ this[native].add(value)
29
+ if (!had) {
30
+ // touch the specific value and the collection view
31
+ touched1(this.content, { type: 'add', prop: value }, value)
32
+ // no size/allProps for WeakSet
33
+ }
34
+ return this
35
+ }
36
+
37
+ delete(value: T): boolean {
38
+ const had = this[native].has(value)
39
+ const res = this[native].delete(value)
40
+ if (had) touched1(this.content, { type: 'del', prop: value }, value)
41
+ return res
42
+ }
43
+
44
+ has(value: T): boolean {
45
+ dependant(this.content, value)
46
+ return this[native].has(value)
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Reactive wrapper around JavaScript's Set class
52
+ * Tracks size changes, individual value operations, and collection-wide operations
53
+ */
54
+ export class ReactiveSet<T> {
55
+ declare readonly [native]: Set<T>
56
+ declare readonly content: symbol
57
+ constructor(original: Set<T>) {
58
+ Object.defineProperties(this, {
59
+ [native]: { value: original },
60
+ [prototypeForwarding]: { value: original },
61
+ content: { value: Symbol('content') },
62
+ [Symbol.toStringTag]: { value: 'ReactiveSet' },
63
+ })
64
+ }
65
+
66
+ get size(): number {
67
+ // size depends on the wrapper instance, like Map counterpart
68
+ dependant(this, 'size')
69
+ return this[native].size
70
+ }
71
+
72
+ add(value: T): this {
73
+ const had = this[native].has(value)
74
+ const reactiveValue = reactive(value)
75
+ this[native].add(reactiveValue)
76
+ if (!had) {
77
+ const evolution = { type: 'add', prop: reactiveValue } as const
78
+ // touch for value-specific and aggregate dependencies
79
+ touched1(this.content, evolution, reactiveValue)
80
+ touched1(this, evolution, 'size')
81
+ }
82
+ return this
83
+ }
84
+
85
+ clear(): void {
86
+ const hadEntries = this[native].size > 0
87
+ this[native].clear()
88
+ if (hadEntries) {
89
+ const evolution = { type: 'bunch', method: 'clear' } as const
90
+ touched1(this, evolution, 'size')
91
+ touched(this.content, evolution)
92
+ }
93
+ }
94
+
95
+ delete(value: T): boolean {
96
+ const had = this[native].has(value)
97
+ const res = this[native].delete(value)
98
+ if (had) {
99
+ const evolution = { type: 'del', prop: value } as const
100
+ touched1(this.content, evolution, value)
101
+ touched1(this, evolution, 'size')
102
+ }
103
+ return res
104
+ }
105
+
106
+ has(value: T): boolean {
107
+ dependant(this.content, value)
108
+ return this[native].has(value)
109
+ }
110
+
111
+ entries(): Generator<[T, T]> {
112
+ dependant(this.content)
113
+ return makeReactiveEntriesIterator(this[native].entries())
114
+ }
115
+
116
+ forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
117
+ dependant(this.content)
118
+ this[native].forEach(callbackfn, thisArg)
119
+ }
120
+
121
+ keys(): Generator<T> {
122
+ dependant(this.content)
123
+ return makeReactiveIterator(this[native].keys())
124
+ }
125
+
126
+ values(): Generator<T> {
127
+ dependant(this.content)
128
+ return makeReactiveIterator(this[native].values())
129
+ }
130
+
131
+ [Symbol.iterator](): Iterator<T> {
132
+ dependant(this.content)
133
+ const nativeIterator = this[native][Symbol.iterator]()
134
+ return {
135
+ next() {
136
+ const result = nativeIterator.next()
137
+ if (result.done) {
138
+ return result
139
+ }
140
+ return { value: reactive(result.value), done: false }
141
+ },
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,101 @@
1
+ import { getActiveEffect } from './effect-context'
2
+ import { unwrap } from './proxy-state'
3
+ import { allProps, rootFunction, type ScopedCallback } from './types'
4
+
5
+ // Track which effects are watching which reactive objects for cleanup
6
+ export const effectToReactiveObjects = new WeakMap<ScopedCallback, Set<object>>()
7
+
8
+ // Track effects per reactive object and property
9
+ export const watchers = new WeakMap<object, Map<any, Set<ScopedCallback>>>()
10
+
11
+ // runEffect -> set<stop>
12
+ export const effectChildren = new WeakMap<ScopedCallback, Set<ScopedCallback>>()
13
+
14
+ // Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
15
+ export const effectParent = new WeakMap<ScopedCallback, ScopedCallback | undefined>()
16
+
17
+ /**
18
+ * Marks a function with its root function for effect tracking
19
+ * @param fn - The function to mark
20
+ * @param root - The root function
21
+ * @returns The marked function
22
+ */
23
+ export function markWithRoot<T extends Function>(fn: T, root: Function): T {
24
+ // Mark fn with the new root
25
+ return Object.defineProperty(fn, rootFunction, {
26
+ value: getRoot(root),
27
+ writable: false,
28
+ })
29
+ }
30
+
31
+ /**
32
+ * Gets the root function of a function for effect tracking
33
+ * @param fn - The function to get the root of
34
+ * @returns The root function
35
+ */
36
+ export function getRoot<T extends Function | undefined>(fn: T): T {
37
+ return (fn as any)?.[rootFunction] || fn
38
+ }
39
+
40
+ // Flag to disable dependency tracking for the current active effect (not globally)
41
+ const trackingDisabledEffects = new WeakSet<ScopedCallback>()
42
+ let globalTrackingDisabled = false
43
+
44
+ export function getTrackingDisabled(): boolean {
45
+ const active = getActiveEffect()
46
+ if (!active) return globalTrackingDisabled
47
+ return trackingDisabledEffects.has(getRoot(active))
48
+ }
49
+
50
+ export function setTrackingDisabled(value: boolean): void {
51
+ const active = getActiveEffect()
52
+ if (!active) {
53
+ globalTrackingDisabled = value
54
+ return
55
+ }
56
+ const root = getRoot(active)
57
+ if (value) trackingDisabledEffects.add(root)
58
+ else trackingDisabledEffects.delete(root)
59
+ }
60
+
61
+ /**
62
+ * Marks a property as a dependency of the current effect
63
+ * @param obj - The object containing the property
64
+ * @param prop - The property name (defaults to allProps)
65
+ */
66
+ export function dependant(obj: any, prop: any = allProps) {
67
+ obj = unwrap(obj)
68
+ const currentActiveEffect = getActiveEffect()
69
+
70
+ // Early return if no active effect, tracking disabled, or invalid prop
71
+ if (
72
+ !currentActiveEffect ||
73
+ getTrackingDisabled() ||
74
+ (typeof prop === 'symbol' && prop !== allProps)
75
+ )
76
+ return
77
+
78
+ registerDependency(obj, prop, currentActiveEffect)
79
+ }
80
+
81
+ function registerDependency(obj: any, prop: any, currentActiveEffect: ScopedCallback) {
82
+ let objectWatchers = watchers.get(obj)
83
+ if (!objectWatchers) {
84
+ objectWatchers = new Map<PropertyKey, Set<ScopedCallback>>()
85
+ watchers.set(obj, objectWatchers)
86
+ }
87
+ let deps = objectWatchers.get(prop)
88
+ if (!deps) {
89
+ deps = new Set<ScopedCallback>()
90
+ objectWatchers.set(prop, deps)
91
+ }
92
+ deps.add(currentActiveEffect)
93
+
94
+ // Track which reactive objects this effect is watching
95
+ const effectObjects = effectToReactiveObjects.get(currentActiveEffect)
96
+ if (effectObjects) {
97
+ effectObjects.add(obj)
98
+ } else {
99
+ effectToReactiveObjects.set(currentActiveEffect, new Set([obj]))
100
+ }
101
+ }