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.
- package/README.md +58 -33
- package/dist/browser.cjs +1371 -1206
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -1392
- package/dist/browser.dev.cjs +110 -0
- package/dist/browser.dev.cjs.map +1 -0
- package/dist/browser.dev.d.ts +2 -0
- package/dist/browser.dev.esm.js +5 -0
- package/dist/browser.dev.esm.js.map +1 -0
- package/dist/browser.esm.js +6 -87
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-BU_IfxYD.cjs} +4 -85
- package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
- package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
- package/dist/chunks/{index-2vea86wD.esm.js → index-CaaQQlPJ.esm.js} +1116 -953
- package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
- package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
- package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
- package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Dtg-bJ3T.cjs} +2285 -2983
- package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
- package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-r7lARftl.esm.js} +2273 -2948
- package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
- package/dist/debug.cjs +973 -28
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +36 -37
- package/dist/debug.esm.js +949 -1
- package/dist/debug.esm.js.map +1 -1
- package/dist/devtools/manifest.json +1 -1
- package/dist/devtools/panel.html +1 -1
- package/dist/devtools/panel.js +107 -94
- package/dist/devtools/panel.js.map +1 -1
- package/dist/index.d.ts +1447 -1
- package/dist/mutts.umd.js +7167 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +62 -60
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +110 -0
- package/dist/node.dev.cjs.map +1 -0
- package/dist/node.dev.d.ts +2 -0
- package/dist/node.dev.esm.js +6 -0
- package/dist/node.dev.esm.js.map +1 -0
- package/dist/node.esm.js +4 -97
- package/dist/node.esm.js.map +1 -1
- package/dist/{types-DaHFfhlN.d.ts → types-W5vD6m2n.d.ts} +99 -44
- package/docs/ai/api-reference.md +11 -18
- package/docs/ai/manual.md +308 -230
- package/docs/reactive/advanced.md +312 -2
- package/docs/reactive/attend.md +1 -2
- package/docs/reactive/collections.md +21 -39
- package/docs/reactive/core.md +7 -37
- package/docs/reactive/debugging.md +117 -10
- package/docs/reactive/error-handling.md +10 -10
- package/docs/reactive/resource.md +125 -0
- package/docs/reactive/scan.md +32 -1
- package/docs/reactive.md +3 -2
- package/docs/utils.md +70 -0
- package/package.json +59 -30
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
- package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
- package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
- package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
- package/dist/debug/debug.d.ts +0 -122
- package/dist/debug/debug.d.ts.map +0 -1
- package/dist/debug/index.d.ts +0 -4
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/lineage-panel.d.ts +0 -5
- package/dist/debug/lineage-panel.d.ts.map +0 -1
- package/dist/debug/lineage.d.ts +0 -79
- package/dist/debug/lineage.d.ts.map +0 -1
- package/dist/src/async/browser.d.ts +0 -2
- package/dist/src/async/browser.d.ts.map +0 -1
- package/dist/src/async/index.d.ts +0 -19
- package/dist/src/async/index.d.ts.map +0 -1
- package/dist/src/async/node.d.ts +0 -2
- package/dist/src/async/node.d.ts.map +0 -1
- package/dist/src/decorator.d.ts +0 -106
- package/dist/src/decorator.d.ts.map +0 -1
- package/dist/src/destroyable.d.ts +0 -87
- package/dist/src/destroyable.d.ts.map +0 -1
- package/dist/src/entry-browser.d.ts +0 -3
- package/dist/src/entry-browser.d.ts.map +0 -1
- package/dist/src/entry-node.d.ts +0 -3
- package/dist/src/entry-node.d.ts.map +0 -1
- package/dist/src/eventful.d.ts +0 -20
- package/dist/src/eventful.d.ts.map +0 -1
- package/dist/src/flavored.d.ts +0 -33
- package/dist/src/flavored.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/indexable.d.ts +0 -243
- package/dist/src/indexable.d.ts.map +0 -1
- package/dist/src/introspection.d.ts +0 -27
- package/dist/src/introspection.d.ts.map +0 -1
- package/dist/src/iterableWeak.d.ts +0 -53
- package/dist/src/iterableWeak.d.ts.map +0 -1
- package/dist/src/mixins.d.ts +0 -25
- package/dist/src/mixins.d.ts.map +0 -1
- package/dist/src/promiseChain.d.ts +0 -20
- package/dist/src/promiseChain.d.ts.map +0 -1
- package/dist/src/reactive/array.d.ts +0 -48
- package/dist/src/reactive/array.d.ts.map +0 -1
- package/dist/src/reactive/buffer.d.ts +0 -120
- package/dist/src/reactive/buffer.d.ts.map +0 -1
- package/dist/src/reactive/change.d.ts +0 -29
- package/dist/src/reactive/change.d.ts.map +0 -1
- package/dist/src/reactive/deep-touch.d.ts +0 -28
- package/dist/src/reactive/deep-touch.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch-state.d.ts +0 -25
- package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch.d.ts +0 -20
- package/dist/src/reactive/deep-watch.d.ts.map +0 -1
- package/dist/src/reactive/describe.d.ts +0 -12
- package/dist/src/reactive/describe.d.ts.map +0 -1
- package/dist/src/reactive/effect-context.d.ts +0 -34
- package/dist/src/reactive/effect-context.d.ts.map +0 -1
- package/dist/src/reactive/effects.d.ts +0 -164
- package/dist/src/reactive/effects.d.ts.map +0 -1
- package/dist/src/reactive/index.d.ts +0 -19
- package/dist/src/reactive/index.d.ts.map +0 -1
- package/dist/src/reactive/map.d.ts +0 -28
- package/dist/src/reactive/map.d.ts.map +0 -1
- package/dist/src/reactive/memoize.d.ts +0 -28
- package/dist/src/reactive/memoize.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive-state.d.ts +0 -9
- package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive.d.ts +0 -11
- package/dist/src/reactive/non-reactive.d.ts.map +0 -1
- package/dist/src/reactive/project.d.ts +0 -40
- package/dist/src/reactive/project.d.ts.map +0 -1
- package/dist/src/reactive/proxy-state.d.ts +0 -8
- package/dist/src/reactive/proxy-state.d.ts.map +0 -1
- package/dist/src/reactive/proxy.d.ts +0 -23
- package/dist/src/reactive/proxy.d.ts.map +0 -1
- package/dist/src/reactive/record.d.ts +0 -115
- package/dist/src/reactive/record.d.ts.map +0 -1
- package/dist/src/reactive/register.d.ts +0 -125
- package/dist/src/reactive/register.d.ts.map +0 -1
- package/dist/src/reactive/registry.d.ts +0 -21
- package/dist/src/reactive/registry.d.ts.map +0 -1
- package/dist/src/reactive/set.d.ts +0 -26
- package/dist/src/reactive/set.d.ts.map +0 -1
- package/dist/src/reactive/tracking.d.ts +0 -7
- package/dist/src/reactive/tracking.d.ts.map +0 -1
- package/dist/src/reactive/types.d.ts +0 -424
- package/dist/src/reactive/types.d.ts.map +0 -1
- package/dist/src/reactive/watch.d.ts +0 -48
- package/dist/src/reactive/watch.d.ts.map +0 -1
- package/dist/src/std-decorators.d.ts +0 -45
- package/dist/src/std-decorators.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -49
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/zone.d.ts +0 -40
- package/dist/src/zone.d.ts.map +0 -1
- package/docs/reactive/describe.md +0 -85
- package/docs/reactive/project.md +0 -93
- package/src/async/browser.ts +0 -323
- package/src/async/index.ts +0 -27
- package/src/async/node.ts +0 -92
- package/src/decorator.ts +0 -272
- package/src/destroyable.ts +0 -199
- package/src/entry-browser.ts +0 -5
- package/src/entry-node.ts +0 -5
- package/src/eventful.ts +0 -110
- package/src/flavored.ts +0 -106
- package/src/index.d.ts +0 -12
- package/src/index.ts +0 -64
- package/src/indexable.ts +0 -526
- package/src/introspection.ts +0 -59
- package/src/iterableWeak.ts +0 -233
- package/src/mixins.ts +0 -123
- package/src/promiseChain.ts +0 -110
- package/src/reactive/array.ts +0 -500
- package/src/reactive/buffer.ts +0 -328
- package/src/reactive/change.ts +0 -131
- package/src/reactive/deep-touch.ts +0 -273
- package/src/reactive/deep-watch-state.ts +0 -82
- package/src/reactive/deep-watch.ts +0 -171
- package/src/reactive/describe.ts +0 -39
- package/src/reactive/effect-context.ts +0 -83
- package/src/reactive/effects.ts +0 -1434
- package/src/reactive/index.ts +0 -72
- package/src/reactive/map.ts +0 -142
- package/src/reactive/memoize.ts +0 -186
- package/src/reactive/non-reactive-state.ts +0 -49
- package/src/reactive/non-reactive.ts +0 -43
- package/src/reactive/project.md +0 -107
- package/src/reactive/project.ts +0 -430
- package/src/reactive/proxy-state.ts +0 -27
- package/src/reactive/proxy.ts +0 -282
- package/src/reactive/record.ts +0 -181
- package/src/reactive/register.ts +0 -538
- package/src/reactive/registry.ts +0 -72
- package/src/reactive/set.ts +0 -117
- package/src/reactive/tracking.ts +0 -41
- package/src/reactive/types.ts +0 -520
- package/src/reactive/watch.ts +0 -180
- package/src/std-decorators.ts +0 -256
- package/src/utils.ts +0 -300
- package/src/zone.ts +0 -142
package/src/reactive/register.ts
DELETED
|
@@ -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
|
-
}
|
package/src/reactive/registry.ts
DELETED
|
@@ -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
|
-
}
|