mutts 1.0.9 → 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 (204) hide show
  1. package/README.md +58 -33
  2. package/dist/browser.cjs +1371 -1206
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  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 +6 -87
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-BU_IfxYD.cjs} +4 -85
  13. package/dist/chunks/async-browser-BU_IfxYD.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-2vea86wD.esm.js → index-CaaQQlPJ.esm.js} +1116 -953
  17. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  18. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  19. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Dtg-bJ3T.cjs} +2285 -2983
  21. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-r7lARftl.esm.js} +2273 -2948
  23. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  24. package/dist/debug.cjs +973 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +36 -37
  27. package/dist/debug.esm.js +949 -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 +1447 -1
  34. package/dist/mutts.umd.js +7167 -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 +62 -60
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +110 -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-W5vD6m2n.d.ts} +99 -44
  49. package/docs/ai/api-reference.md +11 -18
  50. package/docs/ai/manual.md +308 -230
  51. package/docs/reactive/advanced.md +312 -2
  52. package/docs/reactive/attend.md +1 -2
  53. package/docs/reactive/collections.md +21 -39
  54. package/docs/reactive/core.md +7 -37
  55. package/docs/reactive/debugging.md +117 -10
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive/scan.md +32 -1
  59. package/docs/reactive.md +3 -2
  60. package/docs/utils.md +70 -0
  61. package/package.json +59 -30
  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.map +0 -1
  65. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  66. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  67. package/dist/debug/debug.d.ts +0 -122
  68. package/dist/debug/debug.d.ts.map +0 -1
  69. package/dist/debug/index.d.ts +0 -4
  70. package/dist/debug/index.d.ts.map +0 -1
  71. package/dist/debug/lineage-panel.d.ts +0 -5
  72. package/dist/debug/lineage-panel.d.ts.map +0 -1
  73. package/dist/debug/lineage.d.ts +0 -79
  74. package/dist/debug/lineage.d.ts.map +0 -1
  75. package/dist/src/async/browser.d.ts +0 -2
  76. package/dist/src/async/browser.d.ts.map +0 -1
  77. package/dist/src/async/index.d.ts +0 -19
  78. package/dist/src/async/index.d.ts.map +0 -1
  79. package/dist/src/async/node.d.ts +0 -2
  80. package/dist/src/async/node.d.ts.map +0 -1
  81. package/dist/src/decorator.d.ts +0 -106
  82. package/dist/src/decorator.d.ts.map +0 -1
  83. package/dist/src/destroyable.d.ts +0 -87
  84. package/dist/src/destroyable.d.ts.map +0 -1
  85. package/dist/src/entry-browser.d.ts +0 -3
  86. package/dist/src/entry-browser.d.ts.map +0 -1
  87. package/dist/src/entry-node.d.ts +0 -3
  88. package/dist/src/entry-node.d.ts.map +0 -1
  89. package/dist/src/eventful.d.ts +0 -20
  90. package/dist/src/eventful.d.ts.map +0 -1
  91. package/dist/src/flavored.d.ts +0 -33
  92. package/dist/src/flavored.d.ts.map +0 -1
  93. package/dist/src/index.d.ts +0 -14
  94. package/dist/src/index.d.ts.map +0 -1
  95. package/dist/src/indexable.d.ts +0 -243
  96. package/dist/src/indexable.d.ts.map +0 -1
  97. package/dist/src/introspection.d.ts +0 -27
  98. package/dist/src/introspection.d.ts.map +0 -1
  99. package/dist/src/iterableWeak.d.ts +0 -53
  100. package/dist/src/iterableWeak.d.ts.map +0 -1
  101. package/dist/src/mixins.d.ts +0 -25
  102. package/dist/src/mixins.d.ts.map +0 -1
  103. package/dist/src/promiseChain.d.ts +0 -20
  104. package/dist/src/promiseChain.d.ts.map +0 -1
  105. package/dist/src/reactive/array.d.ts +0 -48
  106. package/dist/src/reactive/array.d.ts.map +0 -1
  107. package/dist/src/reactive/buffer.d.ts +0 -120
  108. package/dist/src/reactive/buffer.d.ts.map +0 -1
  109. package/dist/src/reactive/change.d.ts +0 -29
  110. package/dist/src/reactive/change.d.ts.map +0 -1
  111. package/dist/src/reactive/deep-touch.d.ts +0 -28
  112. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  113. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  114. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  115. package/dist/src/reactive/deep-watch.d.ts +0 -20
  116. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  117. package/dist/src/reactive/describe.d.ts +0 -12
  118. package/dist/src/reactive/describe.d.ts.map +0 -1
  119. package/dist/src/reactive/effect-context.d.ts +0 -34
  120. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  121. package/dist/src/reactive/effects.d.ts +0 -164
  122. package/dist/src/reactive/effects.d.ts.map +0 -1
  123. package/dist/src/reactive/index.d.ts +0 -19
  124. package/dist/src/reactive/index.d.ts.map +0 -1
  125. package/dist/src/reactive/map.d.ts +0 -28
  126. package/dist/src/reactive/map.d.ts.map +0 -1
  127. package/dist/src/reactive/memoize.d.ts +0 -28
  128. package/dist/src/reactive/memoize.d.ts.map +0 -1
  129. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  130. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  131. package/dist/src/reactive/non-reactive.d.ts +0 -11
  132. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  133. package/dist/src/reactive/project.d.ts +0 -40
  134. package/dist/src/reactive/project.d.ts.map +0 -1
  135. package/dist/src/reactive/proxy-state.d.ts +0 -8
  136. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  137. package/dist/src/reactive/proxy.d.ts +0 -23
  138. package/dist/src/reactive/proxy.d.ts.map +0 -1
  139. package/dist/src/reactive/record.d.ts +0 -115
  140. package/dist/src/reactive/record.d.ts.map +0 -1
  141. package/dist/src/reactive/register.d.ts +0 -125
  142. package/dist/src/reactive/register.d.ts.map +0 -1
  143. package/dist/src/reactive/registry.d.ts +0 -21
  144. package/dist/src/reactive/registry.d.ts.map +0 -1
  145. package/dist/src/reactive/set.d.ts +0 -26
  146. package/dist/src/reactive/set.d.ts.map +0 -1
  147. package/dist/src/reactive/tracking.d.ts +0 -7
  148. package/dist/src/reactive/tracking.d.ts.map +0 -1
  149. package/dist/src/reactive/types.d.ts +0 -424
  150. package/dist/src/reactive/types.d.ts.map +0 -1
  151. package/dist/src/reactive/watch.d.ts +0 -48
  152. package/dist/src/reactive/watch.d.ts.map +0 -1
  153. package/dist/src/std-decorators.d.ts +0 -45
  154. package/dist/src/std-decorators.d.ts.map +0 -1
  155. package/dist/src/utils.d.ts +0 -49
  156. package/dist/src/utils.d.ts.map +0 -1
  157. package/dist/src/zone.d.ts +0 -40
  158. package/dist/src/zone.d.ts.map +0 -1
  159. package/docs/reactive/describe.md +0 -85
  160. package/docs/reactive/project.md +0 -93
  161. package/src/async/browser.ts +0 -323
  162. package/src/async/index.ts +0 -27
  163. package/src/async/node.ts +0 -92
  164. package/src/decorator.ts +0 -272
  165. package/src/destroyable.ts +0 -199
  166. package/src/entry-browser.ts +0 -5
  167. package/src/entry-node.ts +0 -5
  168. package/src/eventful.ts +0 -110
  169. package/src/flavored.ts +0 -106
  170. package/src/index.d.ts +0 -12
  171. package/src/index.ts +0 -64
  172. package/src/indexable.ts +0 -526
  173. package/src/introspection.ts +0 -59
  174. package/src/iterableWeak.ts +0 -233
  175. package/src/mixins.ts +0 -123
  176. package/src/promiseChain.ts +0 -110
  177. package/src/reactive/array.ts +0 -500
  178. package/src/reactive/buffer.ts +0 -328
  179. package/src/reactive/change.ts +0 -131
  180. package/src/reactive/deep-touch.ts +0 -273
  181. package/src/reactive/deep-watch-state.ts +0 -82
  182. package/src/reactive/deep-watch.ts +0 -171
  183. package/src/reactive/describe.ts +0 -39
  184. package/src/reactive/effect-context.ts +0 -83
  185. package/src/reactive/effects.ts +0 -1434
  186. package/src/reactive/index.ts +0 -72
  187. package/src/reactive/map.ts +0 -142
  188. package/src/reactive/memoize.ts +0 -186
  189. package/src/reactive/non-reactive-state.ts +0 -49
  190. package/src/reactive/non-reactive.ts +0 -43
  191. package/src/reactive/project.md +0 -107
  192. package/src/reactive/project.ts +0 -430
  193. package/src/reactive/proxy-state.ts +0 -27
  194. package/src/reactive/proxy.ts +0 -282
  195. package/src/reactive/record.ts +0 -181
  196. package/src/reactive/register.ts +0 -538
  197. package/src/reactive/registry.ts +0 -72
  198. package/src/reactive/set.ts +0 -117
  199. package/src/reactive/tracking.ts +0 -41
  200. package/src/reactive/types.ts +0 -520
  201. package/src/reactive/watch.ts +0 -180
  202. package/src/std-decorators.ts +0 -256
  203. package/src/utils.ts +0 -300
  204. 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
- }