mutts 1.0.9 → 1.0.11

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 (206) hide show
  1. package/README.md +60 -35
  2. package/dist/browser.cjs +1333 -1760
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  5. package/dist/browser.dev.cjs +114 -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 +18 -97
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
  13. package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
  14. package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
  15. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  16. package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
  17. package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
  18. package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
  19. package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
  21. package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
  23. package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
  24. package/dist/debug.cjs +987 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +37 -38
  27. package/dist/debug.esm.js +963 -1
  28. package/dist/debug.esm.js.map +1 -1
  29. package/dist/devtools/manifest.json +1 -1
  30. package/dist/devtools/panel.html +1 -1
  31. package/dist/devtools/panel.js +107 -94
  32. package/dist/devtools/panel.js.map +1 -1
  33. package/dist/index.d.ts +1322 -1
  34. package/dist/mutts.umd.js +6647 -1
  35. package/dist/mutts.umd.js.map +1 -1
  36. package/dist/mutts.umd.min.js +1 -1
  37. package/dist/mutts.umd.min.js.map +1 -1
  38. package/dist/node.cjs +69 -63
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +114 -0
  42. package/dist/node.dev.cjs.map +1 -0
  43. package/dist/node.dev.d.ts +2 -0
  44. package/dist/node.dev.esm.js +6 -0
  45. package/dist/node.dev.esm.js.map +1 -0
  46. package/dist/node.esm.js +4 -97
  47. package/dist/node.esm.js.map +1 -1
  48. package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
  49. package/docs/ai/api-reference.md +11 -32
  50. package/docs/ai/manual.md +297 -239
  51. package/docs/reactive/advanced.md +318 -9
  52. package/docs/reactive/attend.md +2 -4
  53. package/docs/reactive/collections.md +22 -187
  54. package/docs/reactive/core.md +223 -131
  55. package/docs/reactive/debugging.md +119 -12
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive.md +3 -4
  59. package/docs/utils.md +70 -0
  60. package/docs/zone.md +1 -1
  61. package/package.json +76 -38
  62. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
  63. package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
  64. package/dist/chunks/index-2vea86wD.esm.js +0 -3011
  65. package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
  66. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  67. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  68. package/dist/debug/debug.d.ts +0 -122
  69. package/dist/debug/debug.d.ts.map +0 -1
  70. package/dist/debug/index.d.ts +0 -4
  71. package/dist/debug/index.d.ts.map +0 -1
  72. package/dist/debug/lineage-panel.d.ts +0 -5
  73. package/dist/debug/lineage-panel.d.ts.map +0 -1
  74. package/dist/debug/lineage.d.ts +0 -79
  75. package/dist/debug/lineage.d.ts.map +0 -1
  76. package/dist/src/async/browser.d.ts +0 -2
  77. package/dist/src/async/browser.d.ts.map +0 -1
  78. package/dist/src/async/index.d.ts +0 -19
  79. package/dist/src/async/index.d.ts.map +0 -1
  80. package/dist/src/async/node.d.ts +0 -2
  81. package/dist/src/async/node.d.ts.map +0 -1
  82. package/dist/src/decorator.d.ts +0 -106
  83. package/dist/src/decorator.d.ts.map +0 -1
  84. package/dist/src/destroyable.d.ts +0 -87
  85. package/dist/src/destroyable.d.ts.map +0 -1
  86. package/dist/src/entry-browser.d.ts +0 -3
  87. package/dist/src/entry-browser.d.ts.map +0 -1
  88. package/dist/src/entry-node.d.ts +0 -3
  89. package/dist/src/entry-node.d.ts.map +0 -1
  90. package/dist/src/eventful.d.ts +0 -20
  91. package/dist/src/eventful.d.ts.map +0 -1
  92. package/dist/src/flavored.d.ts +0 -33
  93. package/dist/src/flavored.d.ts.map +0 -1
  94. package/dist/src/index.d.ts +0 -14
  95. package/dist/src/index.d.ts.map +0 -1
  96. package/dist/src/indexable.d.ts +0 -243
  97. package/dist/src/indexable.d.ts.map +0 -1
  98. package/dist/src/introspection.d.ts +0 -27
  99. package/dist/src/introspection.d.ts.map +0 -1
  100. package/dist/src/iterableWeak.d.ts +0 -53
  101. package/dist/src/iterableWeak.d.ts.map +0 -1
  102. package/dist/src/mixins.d.ts +0 -25
  103. package/dist/src/mixins.d.ts.map +0 -1
  104. package/dist/src/promiseChain.d.ts +0 -20
  105. package/dist/src/promiseChain.d.ts.map +0 -1
  106. package/dist/src/reactive/array.d.ts +0 -48
  107. package/dist/src/reactive/array.d.ts.map +0 -1
  108. package/dist/src/reactive/buffer.d.ts +0 -120
  109. package/dist/src/reactive/buffer.d.ts.map +0 -1
  110. package/dist/src/reactive/change.d.ts +0 -29
  111. package/dist/src/reactive/change.d.ts.map +0 -1
  112. package/dist/src/reactive/deep-touch.d.ts +0 -28
  113. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  114. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  115. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  116. package/dist/src/reactive/deep-watch.d.ts +0 -20
  117. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  118. package/dist/src/reactive/describe.d.ts +0 -12
  119. package/dist/src/reactive/describe.d.ts.map +0 -1
  120. package/dist/src/reactive/effect-context.d.ts +0 -34
  121. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  122. package/dist/src/reactive/effects.d.ts +0 -164
  123. package/dist/src/reactive/effects.d.ts.map +0 -1
  124. package/dist/src/reactive/index.d.ts +0 -19
  125. package/dist/src/reactive/index.d.ts.map +0 -1
  126. package/dist/src/reactive/map.d.ts +0 -28
  127. package/dist/src/reactive/map.d.ts.map +0 -1
  128. package/dist/src/reactive/memoize.d.ts +0 -28
  129. package/dist/src/reactive/memoize.d.ts.map +0 -1
  130. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  131. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  132. package/dist/src/reactive/non-reactive.d.ts +0 -11
  133. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  134. package/dist/src/reactive/project.d.ts +0 -40
  135. package/dist/src/reactive/project.d.ts.map +0 -1
  136. package/dist/src/reactive/proxy-state.d.ts +0 -8
  137. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  138. package/dist/src/reactive/proxy.d.ts +0 -23
  139. package/dist/src/reactive/proxy.d.ts.map +0 -1
  140. package/dist/src/reactive/record.d.ts +0 -115
  141. package/dist/src/reactive/record.d.ts.map +0 -1
  142. package/dist/src/reactive/register.d.ts +0 -125
  143. package/dist/src/reactive/register.d.ts.map +0 -1
  144. package/dist/src/reactive/registry.d.ts +0 -21
  145. package/dist/src/reactive/registry.d.ts.map +0 -1
  146. package/dist/src/reactive/set.d.ts +0 -26
  147. package/dist/src/reactive/set.d.ts.map +0 -1
  148. package/dist/src/reactive/tracking.d.ts +0 -7
  149. package/dist/src/reactive/tracking.d.ts.map +0 -1
  150. package/dist/src/reactive/types.d.ts +0 -424
  151. package/dist/src/reactive/types.d.ts.map +0 -1
  152. package/dist/src/reactive/watch.d.ts +0 -48
  153. package/dist/src/reactive/watch.d.ts.map +0 -1
  154. package/dist/src/std-decorators.d.ts +0 -45
  155. package/dist/src/std-decorators.d.ts.map +0 -1
  156. package/dist/src/utils.d.ts +0 -49
  157. package/dist/src/utils.d.ts.map +0 -1
  158. package/dist/src/zone.d.ts +0 -40
  159. package/dist/src/zone.d.ts.map +0 -1
  160. package/docs/reactive/describe.md +0 -85
  161. package/docs/reactive/project.md +0 -93
  162. package/docs/reactive/scan.md +0 -293
  163. package/src/async/browser.ts +0 -323
  164. package/src/async/index.ts +0 -27
  165. package/src/async/node.ts +0 -92
  166. package/src/decorator.ts +0 -272
  167. package/src/destroyable.ts +0 -199
  168. package/src/entry-browser.ts +0 -5
  169. package/src/entry-node.ts +0 -5
  170. package/src/eventful.ts +0 -110
  171. package/src/flavored.ts +0 -106
  172. package/src/index.d.ts +0 -12
  173. package/src/index.ts +0 -64
  174. package/src/indexable.ts +0 -526
  175. package/src/introspection.ts +0 -59
  176. package/src/iterableWeak.ts +0 -233
  177. package/src/mixins.ts +0 -123
  178. package/src/promiseChain.ts +0 -110
  179. package/src/reactive/array.ts +0 -500
  180. package/src/reactive/buffer.ts +0 -328
  181. package/src/reactive/change.ts +0 -131
  182. package/src/reactive/deep-touch.ts +0 -273
  183. package/src/reactive/deep-watch-state.ts +0 -82
  184. package/src/reactive/deep-watch.ts +0 -171
  185. package/src/reactive/describe.ts +0 -39
  186. package/src/reactive/effect-context.ts +0 -83
  187. package/src/reactive/effects.ts +0 -1434
  188. package/src/reactive/index.ts +0 -72
  189. package/src/reactive/map.ts +0 -142
  190. package/src/reactive/memoize.ts +0 -186
  191. package/src/reactive/non-reactive-state.ts +0 -49
  192. package/src/reactive/non-reactive.ts +0 -43
  193. package/src/reactive/project.md +0 -107
  194. package/src/reactive/project.ts +0 -430
  195. package/src/reactive/proxy-state.ts +0 -27
  196. package/src/reactive/proxy.ts +0 -282
  197. package/src/reactive/record.ts +0 -181
  198. package/src/reactive/register.ts +0 -538
  199. package/src/reactive/registry.ts +0 -72
  200. package/src/reactive/set.ts +0 -117
  201. package/src/reactive/tracking.ts +0 -41
  202. package/src/reactive/types.ts +0 -520
  203. package/src/reactive/watch.ts +0 -180
  204. package/src/std-decorators.ts +0 -256
  205. package/src/utils.ts +0 -300
  206. package/src/zone.ts +0 -142
@@ -1,538 +0,0 @@
1
- import { Eventful } from '../eventful'
2
- import { ArrayReadForward, forwardArray, getAt, Indexable, setAt } from '../indexable'
3
- import type { FunctionWrapper } from '../zone'
4
- import { effect } from './effects'
5
- import { reactive } from './proxy'
6
- import type { EffectCleanup } from './types'
7
- import { unreactive } from './watch'
8
-
9
- type KeyFunction<T, K extends PropertyKey> = (item: T) => K
10
-
11
- /**
12
- * Events emitted by the Register for CRUD operations
13
- */
14
- export interface RegisterEvents<T, K extends PropertyKey> {
15
- /**
16
- * Emitted when a new item is added to the register
17
- */
18
- add: (item: T, key: K, index: number) => void
19
- /**
20
- * Emitted when an item is removed from the register
21
- */
22
- delete: (item: T, key: K, index: number) => void
23
- /**
24
- * Emitted when an item's value is updated (same key, new value)
25
- */
26
- update: (oldItem: T, newItem: T, key: K, index: number) => void
27
- /**
28
- * Emitted when an item's key changes (rekey operation)
29
- */
30
- rekey: (item: T, oldKey: K, newKey: K, index: number) => void
31
- /**
32
- * Index signature for EventsBase compatibility
33
- */
34
- [key: string]: (...args: any[]) => void
35
- }
36
-
37
- // Helper to work around TypeScript limitation: base class expressions cannot reference class type parameters
38
- function getRegisterBase<T>() {
39
- class RegisterBase extends Indexable(ArrayReadForward, {
40
- get(this: any, index: number) {
41
- return this[getAt](index)
42
- },
43
- set(this: any, index: number, value: T) {
44
- this[setAt](index, value)
45
- },
46
- getLength(this: any) {
47
- return this.length
48
- },
49
- setLength(this: any, value: number) {
50
- this.length = value
51
- },
52
- }) {
53
- toArray(): T[] {
54
- return Array.from(this)
55
- }
56
- }
57
- return RegisterBase as new () => ArrayReadForward<T> & {
58
- [x: number]: T
59
- toArray(): T[]
60
- }
61
- }
62
- interface RegisterInstance<T> extends ArrayReadForward<T> {
63
- [index: number]: T
64
- }
65
- @unreactive
66
- class RegisterClass<T, K extends PropertyKey = PropertyKey>
67
- extends getRegisterBase<any>()
68
- implements RegisterInstance<T>
69
- {
70
- protected get [forwardArray](): readonly T[] {
71
- return this.toArray()
72
- }
73
- readonly #keyFn: KeyFunction<T, K>
74
- readonly #keys: K[]
75
- readonly #values: Map<K, T>
76
- readonly #usage = new Map<K, number>()
77
- readonly #valueInfo = new Map<T, { key: K; stop?: EffectCleanup }>()
78
- readonly #keyEffects = new Set<EffectCleanup>()
79
- readonly #ascend: FunctionWrapper
80
- readonly #events = new Eventful<RegisterEvents<T, K>>()
81
-
82
- /**
83
- * Register event listeners for CRUD operations
84
- */
85
- on(events: Partial<RegisterEvents<T, K>>): void
86
- on<EventType extends keyof RegisterEvents<T, K>>(
87
- event: EventType,
88
- cb: RegisterEvents<T, K>[EventType]
89
- ): () => void
90
- on<EventType extends keyof RegisterEvents<T, K>>(
91
- eventOrEvents: EventType | Partial<RegisterEvents<T, K>>,
92
- cb?: RegisterEvents<T, K>[EventType]
93
- ): () => void {
94
- // @ts-expect-error Delegate to Eventful
95
- return this.#events.on(eventOrEvents, cb)
96
- }
97
-
98
- /**
99
- * Remove event listeners
100
- */
101
- off(events: Partial<RegisterEvents<T, K>>): void
102
- off<EventType extends keyof RegisterEvents<T, K>>(
103
- event: EventType,
104
- cb?: RegisterEvents<T, K>[EventType]
105
- ): void
106
- off<EventType extends keyof RegisterEvents<T, K>>(
107
- eventOrEvents: EventType | Partial<RegisterEvents<T, K>>,
108
- cb?: RegisterEvents<T, K>[EventType]
109
- ): void {
110
- // @ts-expect-error Delegate to Eventful
111
- this.#events.off(eventOrEvents, cb)
112
- }
113
-
114
- /**
115
- * Register a global hook that receives all events
116
- */
117
- hook(
118
- cb: <EventType extends keyof RegisterEvents<T, K>>(
119
- event: EventType,
120
- ...args: Parameters<RegisterEvents<T, K>[EventType]>
121
- ) => void
122
- ): () => void {
123
- return this.#events.hook(cb)
124
- }
125
-
126
- constructor(keyFn: KeyFunction<T, K>, initial?: Iterable<T>) {
127
- super()
128
- /* Moved below initialization */
129
- let ascendGet: FunctionWrapper | undefined
130
- effect(({ ascend }) => {
131
- ascendGet = ascend
132
- })
133
- this.#ascend = ascendGet!
134
- if (typeof keyFn !== 'function') throw new Error('Register requires a key function')
135
- this.#keyFn = keyFn
136
- this.#keys = reactive([] as K[])
137
- this.#values = reactive(new Map<K, T>())
138
- if (initial) this.push(...initial)
139
- }
140
-
141
- private ensureKey(value: T): K {
142
- let info = this.#valueInfo.get(value)
143
- if (info) return info.key
144
- info = { key: undefined as unknown as K }
145
- this.#valueInfo.set(value, info)
146
- this.#ascend(() => {
147
- const stop = effect(({ reaction }) => {
148
- const nextKey = this.#keyFn(value)
149
- this.assertValidKey(nextKey)
150
- const previousKey = info!.key
151
- if (reaction && previousKey !== undefined && !Object.is(nextKey, previousKey))
152
- this.#rekeyValue(value, previousKey, nextKey)
153
- info!.key = nextKey
154
- })
155
- info!.stop = stop
156
- this.#keyEffects.add(stop as EffectCleanup)
157
- })
158
- if (info.key === undefined) throw new Error('Register key function must return a property key')
159
- return info.key
160
- }
161
-
162
- private assertValidKey(key: unknown): asserts key is K {
163
- const type = typeof key
164
- if (type !== 'string' && type !== 'number' && type !== 'symbol')
165
- throw new Error('Register key function must return a property key')
166
- }
167
-
168
- private setKeyValue(key: K, value: T, index?: number) {
169
- const existing = this.#values.get(key)
170
- if (existing !== undefined && existing !== value) {
171
- this.cleanupValue(existing)
172
- this.#values.set(key, value)
173
- if (index !== undefined) {
174
- this.#events.emit('update', existing, value, key, index)
175
- }
176
- } else {
177
- this.#values.set(key, value)
178
- }
179
- }
180
-
181
- private cleanupValue(value: T) {
182
- const info = this.#valueInfo.get(value)
183
- if (!info) return
184
- const stop = info.stop
185
- if (stop) {
186
- info.stop = undefined
187
- this.#keyEffects.delete(stop)
188
- stop()
189
- }
190
- this.#valueInfo.delete(value)
191
- }
192
-
193
- private disposeKeyEffects() {
194
- for (const value of Array.from(this.#valueInfo.keys())) this.cleanupValue(value)
195
- this.#keyEffects.clear()
196
- }
197
-
198
- #rekeyValue(value: T, oldKey: K, newKey: K) {
199
- if (Object.is(oldKey, newKey)) return
200
- const existingValue = this.#values.get(newKey)
201
- if (existingValue !== undefined && existingValue !== value)
202
- throw new Error(`Register key collision for key ${String(newKey)}`)
203
- const count = this.#usage.get(oldKey)
204
- if (!count) return
205
- const existingCount = this.#usage.get(newKey) ?? 0
206
- this.setKeyValue(newKey, value)
207
- let index = -1
208
- for (let i = 0; i < this.#keys.length; i++)
209
- if (Object.is(this.#keys[i], oldKey)) {
210
- this.#keys[i] = newKey
211
- if (index === -1) index = i
212
- }
213
- this.#usage.set(newKey, existingCount + count)
214
- this.#usage.delete(oldKey)
215
- this.#values.delete(oldKey)
216
- const updatedInfo = this.#valueInfo.get(value)
217
- if (updatedInfo) updatedInfo.key = newKey
218
- if (index !== -1) {
219
- this.#events.emit('rekey', value, oldKey, newKey, index)
220
- }
221
- }
222
-
223
- private incrementUsage(key: K) {
224
- const count = this.#usage.get(key) ?? 0
225
- this.#usage.set(key, count + 1)
226
- }
227
-
228
- private decrementUsage(key: K, index?: number) {
229
- const count = this.#usage.get(key)
230
- if (!count) return
231
- if (count <= 1) {
232
- const value = this.#values.get(key)
233
- this.#usage.delete(key)
234
- this.#values.delete(key)
235
- if (value !== undefined) {
236
- this.cleanupValue(value)
237
- const idx = index ?? this.#keys.indexOf(key)
238
- this.#events.emit('delete', value, key, idx)
239
- }
240
- } else {
241
- this.#usage.set(key, count - 1)
242
- }
243
- }
244
-
245
- private normalizeIndex(index: number, allowEnd = false): number {
246
- const length = this.length
247
- let resolved = index
248
- if (resolved < 0) resolved = Math.max(length + resolved, 0)
249
- if (resolved > length) {
250
- if (allowEnd) resolved = length
251
- else throw new RangeError('Index out of bounds')
252
- }
253
- if (!allowEnd && resolved === length) throw new RangeError('Index out of bounds')
254
- return resolved
255
- }
256
-
257
- private assignAt(index: number, key: K, value: T) {
258
- const oldKey = this.#keys[index]
259
- if (oldKey !== undefined && Object.is(oldKey, key)) {
260
- const oldValue = this.#values.get(key)
261
- this.setKeyValue(key, value)
262
- if (oldValue !== undefined && oldValue !== value) {
263
- this.#events.emit('update', oldValue, value, key, index)
264
- }
265
- return
266
- }
267
- if (oldKey !== undefined) this.decrementUsage(oldKey as K)
268
- this.#keys[index] = key
269
- this.incrementUsage(key)
270
- this.setKeyValue(key, value)
271
- this.#events.emit('add', value, key, index)
272
- }
273
-
274
- private insertKeyValue(index: number, key: K, value: T) {
275
- this.#keys.splice(index, 0, key)
276
- this.incrementUsage(key)
277
- this.setKeyValue(key, value)
278
- this.#events.emit('add', value, key, index)
279
- }
280
-
281
- private rebuildFrom(values: T[]) {
282
- this.disposeKeyEffects()
283
- this.#keys.splice(0, this.#keys.length)
284
- this.#usage.clear()
285
- this.#values.clear()
286
- for (const value of values) {
287
- const key = this.ensureKey(value)
288
- this.#keys.push(key)
289
- this.incrementUsage(key)
290
- this.#values.set(key, value)
291
- }
292
- }
293
-
294
- get length(): number {
295
- return this.#keys.length
296
- }
297
-
298
- [getAt](index: number): T | undefined {
299
- const key = this.#keys[index]
300
- return key === undefined ? undefined : this.#values.get(key)
301
- }
302
-
303
- [setAt](index: number, value: T): void {
304
- const key = this.ensureKey(value)
305
- if (index === this.length) {
306
- this.insertKeyValue(index, key, value)
307
- return
308
- }
309
- const normalized = this.normalizeIndex(index)
310
- this.assignAt(normalized, key, value)
311
- }
312
-
313
- push(...items: T[]): number {
314
- for (const item of items) {
315
- const key = this.ensureKey(item)
316
- this.insertKeyValue(this.length, key, item)
317
- }
318
- return this.length
319
- }
320
-
321
- pop(): T | undefined {
322
- if (!this.length) return undefined
323
- return this.removeAt(this.length - 1)
324
- }
325
-
326
- shift(): T | undefined {
327
- if (!this.length) return undefined
328
- return this.removeAt(0)
329
- }
330
-
331
- unshift(...items: T[]): number {
332
- let index = 0
333
- for (const item of items) {
334
- const key = this.ensureKey(item)
335
- this.insertKeyValue(index++, key, item)
336
- }
337
- return this.length
338
- }
339
-
340
- splice(start: number, deleteCount?: number, ...items: T[]): T[] {
341
- const normalizedStart = this.normalizeIndex(start, true)
342
- const maxDeletions = this.length - normalizedStart
343
- const actualDelete = Math.min(
344
- deleteCount === undefined ? maxDeletions : Math.max(deleteCount, 0),
345
- maxDeletions
346
- )
347
- const keysToInsert: K[] = []
348
- for (const item of items) keysToInsert.push(this.ensureKey(item))
349
- const removedKeys = this.#keys.splice(normalizedStart, actualDelete, ...keysToInsert)
350
- const removedValues: T[] = []
351
- for (let i = 0; i < removedKeys.length; i++) {
352
- const key = removedKeys[i]
353
- if (key === undefined) continue
354
- const value = this.#values.get(key as K)
355
- this.decrementUsage(key as K, normalizedStart + i)
356
- removedValues.push(value as T)
357
- }
358
- for (let i = 0; i < keysToInsert.length; i++) {
359
- const key = keysToInsert[i]
360
- const value = items[i]
361
- this.incrementUsage(key)
362
- this.setKeyValue(key, value)
363
- }
364
- return removedValues
365
- }
366
-
367
- clear(): void {
368
- this.#keys.length = 0
369
- this.#usage.clear()
370
- this.#values.clear()
371
- this.disposeKeyEffects()
372
- }
373
-
374
- get(key: K): T | undefined {
375
- return this.#values.get(key)
376
- }
377
- set(key: K, value: T): void {
378
- if (this.#values.has(key)) this.setKeyValue(key, value)
379
- }
380
-
381
- remove(key: K) {
382
- let index: number = this.indexOfKey(key)
383
- while (index !== -1) {
384
- this.removeAt(index)
385
- index = this.indexOfKey(key)
386
- }
387
- }
388
-
389
- public removeAt(index: number): T | undefined {
390
- const [key] = this.#keys.splice(index, 1)
391
- if (key === undefined) return undefined
392
- const value = this.#values.get(key as K)
393
- this.decrementUsage(key as K, index)
394
- return value
395
- }
396
-
397
- /**
398
- * Keep only the items for which the predicate returns true.
399
- * Items for which the predicate returns false are removed.
400
- *
401
- * The predicate is evaluated once per distinct key; duplicate keys
402
- * will follow the same keep/remove decision.
403
- */
404
- public keep(predicate: (value: T) => boolean): void {
405
- const decisions = new Map<K, boolean>()
406
- for (const [index, key] of this.#keys.entries()) {
407
- if (decisions.has(key)) {
408
- if (!decisions.get(key)) this.removeAt(index)
409
- continue
410
- }
411
- const value = this.#values.get(key)
412
- const shouldKeep = predicate(value as T)
413
- decisions.set(key, shouldKeep)
414
- if (!shouldKeep) this.removeAt(index)
415
- }
416
- }
417
-
418
- hasKey(key: K): boolean {
419
- return this.#usage.has(key)
420
- }
421
-
422
- indexOfKey(key: K): number {
423
- return this.#keys.indexOf(key)
424
- }
425
-
426
- mapKeys(): IterableIterator<K> {
427
- return this.#values.keys()
428
- }
429
-
430
- update(...values: T[]): void {
431
- for (const value of values) {
432
- const key = this.ensureKey(value)
433
- if (this.#values.has(key)) {
434
- const index = this.#keys.indexOf(key)
435
- this.setKeyValue(key, value, index)
436
- }
437
- }
438
- }
439
-
440
- upsert(insert: (value: T) => void, ...values: T[]): void {
441
- for (const value of values) {
442
- const key = this.ensureKey(value)
443
- if (this.#values.has(key)) this.setKeyValue(key, value)
444
- else insert(value)
445
- }
446
- }
447
-
448
- entries(): IterableIterator<[number, T]> {
449
- const self = this
450
- function* iterator(): IterableIterator<[number, T]> {
451
- for (let i = 0; i < self.#keys.length; i++) {
452
- const val = self.#values.get(self.#keys[i])
453
- if (val !== undefined) yield [i, val]
454
- }
455
- }
456
- return iterator()
457
- }
458
-
459
- [Symbol.iterator](): IterableIterator<T> {
460
- const self = this
461
- function* iterator(): IterableIterator<T> {
462
- for (const key of self.#keys) {
463
- const value = self.#values.get(key)
464
- if (value !== undefined) yield value
465
- }
466
- }
467
- return iterator()
468
- }
469
-
470
- toString(): string {
471
- return `[Register length=${this.length}]`
472
- }
473
-
474
- at(index: number): T | undefined {
475
- const resolved = index < 0 ? this.length + index : index
476
- if (resolved < 0 || resolved >= this.length) return undefined
477
- return this[getAt](resolved)
478
- }
479
- reverse(): this {
480
- this.#keys.reverse()
481
- return this
482
- }
483
- sort(compareFn?: ((a: T, b: T) => number) | undefined): this {
484
- const fwdCompareFn = compareFn
485
- ? (a: K, b: K) => compareFn(this.#values.get(a) as T, this.#values.get(b) as T)
486
- : undefined
487
- this.#keys.sort(fwdCompareFn)
488
- return this
489
- }
490
- fill(value: T, start = 0, end = this.length): this {
491
- const values = this.toArray()
492
- values.fill(value, start, end)
493
- this.rebuildFrom(values)
494
- return this
495
- }
496
-
497
- copyWithin(target: number, start: number, end?: number): this {
498
- const values = this.toArray()
499
- values.copyWithin(target, start, end)
500
- this.rebuildFrom(values)
501
- return this
502
- }
503
- }
504
-
505
- export type Register<T, K extends PropertyKey = PropertyKey> = RegisterClass<T, K> & T[]
506
- export const Register = RegisterClass as new <T, K extends PropertyKey = PropertyKey>(
507
- keyFn: KeyFunction<T, K>,
508
- initial?: Iterable<T>
509
- ) => Register<T, K>
510
-
511
- /**
512
- * Creates a reactive Register - an ordered, array-like collection with stable key-based identity.
513
- *
514
- * Register combines array semantics (indexable access, ordering, iteration) with Map-like
515
- * key-based lookups. Items with the same key share the same value instance, making it ideal
516
- * for UI lists keyed by IDs or when you need to preserve identity across reorders.
517
- *
518
- * @param keyFn - Function that extracts the key from each item
519
- * @param initial - Optional initial items to populate the register
520
- * @returns A reactive Register instance
521
- *
522
- * @example
523
- * ```typescript
524
- * const users = register(
525
- * (user: User) => user.id,
526
- * [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
527
- * )
528
- *
529
- * users.push({ id: 3, name: 'Charlie' })
530
- * const bob = users.get(2) // Get by key
531
- * ```
532
- */
533
- export function register<T, K extends PropertyKey = PropertyKey>(
534
- keyFn: KeyFunction<T, K>,
535
- initial?: Iterable<T>
536
- ): Register<T, K> {
537
- return new RegisterClass(keyFn, initial) as Register<T, K>
538
- }
@@ -1,72 +0,0 @@
1
- import { type EffectCleanup, type EffectNode, type EffectTrigger, rootFunction } from './types'
2
-
3
- // Track which effects are watching which reactive objects for cleanup
4
- export let effectToReactiveObjects = new WeakMap<EffectTrigger, Set<object>>()
5
-
6
- // Track effects per reactive object and property
7
- export let watchers = new WeakMap<object, Map<any, Set<EffectTrigger>>>()
8
-
9
- // Track effect metadata and relationships
10
- export let effectNodes = new WeakMap<EffectTrigger, EffectNode>()
11
-
12
- export function getEffectNode(effect: EffectTrigger): EffectNode {
13
- let node = effectNodes.get(effect)
14
- if (!node) {
15
- node = {}
16
- effectNodes.set(effect, node)
17
- }
18
- return node
19
- }
20
-
21
- // Track reverse mapping to ensure unicity: One Root -> One Function
22
- let reverseRoots = new WeakMap<any, WeakRef<Function>>()
23
-
24
- export function resetRegistry() {
25
- effectToReactiveObjects = new WeakMap()
26
- watchers = new WeakMap()
27
- effectNodes = new WeakMap()
28
- reverseRoots = new WeakMap()
29
- }
30
-
31
- /**
32
- * Marks a function with its root function for effect tracking
33
- * Enforces strict unicity: A root function can only identify ONE function.
34
- * @param fn - The function to mark
35
- * @param root - The root function
36
- * @returns The marked function
37
- */
38
- export function markWithRoot<T extends Function>(fn: T, root: any): T {
39
- // Check for collision
40
- const existingRef = reverseRoots.get(root)
41
- const existing = existingRef?.deref()
42
-
43
- if (existing && existing !== fn) {
44
- const rootName = root.name || 'anonymous'
45
- const existingName = existing.name || 'anonymous'
46
- const fnName = fn.name || 'anonymous'
47
- throw new Error(
48
- `[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
49
- `Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`
50
- )
51
- }
52
-
53
- // Always update the map so subsequent checks find this one
54
- // (Last writer wins for the check)
55
- reverseRoots.set(root, new WeakRef(fn))
56
-
57
- // Mark fn with the new root
58
- return Object.defineProperty(fn, rootFunction, {
59
- value: getRoot(root),
60
- writable: false,
61
- })
62
- }
63
-
64
- /**
65
- * Gets the root function of a function for effect tracking
66
- * @param fn - The function to get the root of
67
- * @returns The root function
68
- */
69
- export function getRoot<T extends Function | undefined>(fn: T): T {
70
- while (fn && rootFunction in fn) fn = fn[rootFunction] as T
71
- return fn
72
- }