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