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