@pyreon/reactivity 0.24.5 → 0.24.6
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/package.json +1 -4
- package/src/batch.ts +0 -196
- package/src/cell.ts +0 -72
- package/src/computed.ts +0 -313
- package/src/createSelector.ts +0 -109
- package/src/debug.ts +0 -134
- package/src/effect.ts +0 -467
- package/src/env.d.ts +0 -6
- package/src/index.ts +0 -60
- package/src/lpih.ts +0 -227
- package/src/manifest.ts +0 -660
- package/src/reactive-devtools.ts +0 -494
- package/src/reactive-trace.ts +0 -142
- package/src/reconcile.ts +0 -118
- package/src/resource.ts +0 -84
- package/src/scope.ts +0 -123
- package/src/signal.ts +0 -261
- package/src/store.ts +0 -250
- package/src/tests/batch.test.ts +0 -751
- package/src/tests/bind.test.ts +0 -84
- package/src/tests/branches.test.ts +0 -343
- package/src/tests/cell.test.ts +0 -159
- package/src/tests/computed.test.ts +0 -436
- package/src/tests/coverage-hardening.test.ts +0 -471
- package/src/tests/createSelector.test.ts +0 -291
- package/src/tests/debug.test.ts +0 -196
- package/src/tests/effect.test.ts +0 -464
- package/src/tests/fanout-repro.test.ts +0 -179
- package/src/tests/lpih-source-location.test.ts +0 -277
- package/src/tests/lpih.test.ts +0 -351
- package/src/tests/manifest-snapshot.test.ts +0 -96
- package/src/tests/reactive-devtools-treeshake.test.ts +0 -48
- package/src/tests/reactive-devtools.test.ts +0 -296
- package/src/tests/reactive-trace.test.ts +0 -102
- package/src/tests/reconcile-security.test.ts +0 -45
- package/src/tests/resource.test.ts +0 -326
- package/src/tests/scope.test.ts +0 -231
- package/src/tests/signal.test.ts +0 -368
- package/src/tests/store.test.ts +0 -286
- package/src/tests/tracking.test.ts +0 -158
- package/src/tests/vue-parity.test.ts +0 -191
- package/src/tests/watch.test.ts +0 -246
- package/src/tracking.ts +0 -139
- package/src/watch.ts +0 -68
package/src/store.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createStore — deep reactive Proxy store.
|
|
3
|
-
*
|
|
4
|
-
* Wraps a plain object/array in a Proxy that creates a fine-grained signal for
|
|
5
|
-
* every property. Direct mutations (`store.count++`, `store.items[0].label = "x"`)
|
|
6
|
-
* trigger only the signals for the mutated properties — not the whole tree.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const state = createStore({ count: 0, items: [{ id: 1, text: "hello" }] })
|
|
10
|
-
*
|
|
11
|
-
* effect(() => console.log(state.count)) // tracks state.count only
|
|
12
|
-
* state.count++ // only the count effect re-runs
|
|
13
|
-
* state.items[0].text = "world" // only text-tracking effects re-run
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { type Signal, signal } from './signal'
|
|
17
|
-
|
|
18
|
-
// WeakMap: raw object → its reactive proxy (ensures each raw object gets one proxy)
|
|
19
|
-
const proxyCache = new WeakMap<object, object>()
|
|
20
|
-
// Separate cache for shallow proxies — same raw can produce different proxies
|
|
21
|
-
// depending on shallow vs deep mode, so we can't share proxyCache.
|
|
22
|
-
const shallowProxyCache = new WeakMap<object, object>()
|
|
23
|
-
|
|
24
|
-
const IS_STORE = Symbol('pyreon.store')
|
|
25
|
-
const IS_RAW = Symbol('pyreon.raw')
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Mark an object as RAW — `createStore` and `shallowReactive` will return it
|
|
29
|
-
* unwrapped. Useful when storing class instances, third-party objects, or
|
|
30
|
-
* other shapes that shouldn't be deeply proxied (Vue 3 parity).
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* const cm = markRaw(new CodeMirrorView(...))
|
|
34
|
-
* const store = createStore({ editor: cm })
|
|
35
|
-
* store.editor === cm // true (not wrapped)
|
|
36
|
-
*
|
|
37
|
-
* Note: marking is one-way — there's no `unmarkRaw`. Mark BEFORE the object
|
|
38
|
-
* enters a store; marking after wrap doesn't unwrap an existing proxy.
|
|
39
|
-
*/
|
|
40
|
-
export function markRaw<T extends object>(value: T): T {
|
|
41
|
-
Object.defineProperty(value, IS_RAW, {
|
|
42
|
-
value: true,
|
|
43
|
-
enumerable: false,
|
|
44
|
-
configurable: true,
|
|
45
|
-
writable: false,
|
|
46
|
-
})
|
|
47
|
-
return value
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Returns true if the value was marked with `markRaw()`. */
|
|
51
|
-
function isMarkedRaw(value: object): boolean {
|
|
52
|
-
return (value as Record<symbol, unknown>)[IS_RAW] === true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Built-in object types that have internal slots and fail the Proxy
|
|
56
|
-
// internal-slot check on every method call (`Map.prototype.set` called on a
|
|
57
|
-
// Proxy → `TypeError: Method ... called on incompatible receiver`). Returning
|
|
58
|
-
// the raw instance keeps these usable but at the cost of fine-grained
|
|
59
|
-
// reactivity for their contents — write replace-the-whole-Map style if you
|
|
60
|
-
// need reactivity (`store.users = new Map(store.users)`). A future PR can
|
|
61
|
-
// add Vue-style collection-aware wrapping for Map/Set if demand emerges.
|
|
62
|
-
function isBuiltinNonProxiable(obj: object): boolean {
|
|
63
|
-
return (
|
|
64
|
-
obj instanceof Map ||
|
|
65
|
-
obj instanceof Set ||
|
|
66
|
-
obj instanceof WeakMap ||
|
|
67
|
-
obj instanceof WeakSet ||
|
|
68
|
-
obj instanceof Date ||
|
|
69
|
-
obj instanceof RegExp ||
|
|
70
|
-
obj instanceof Promise ||
|
|
71
|
-
obj instanceof Error
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Returns true if the value is a createStore proxy. */
|
|
76
|
-
export function isStore(value: unknown): boolean {
|
|
77
|
-
return (
|
|
78
|
-
value !== null &&
|
|
79
|
-
typeof value === 'object' &&
|
|
80
|
-
(value as Record<symbol, unknown>)[IS_STORE] === true
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Create a deep reactive store from a plain object or array.
|
|
86
|
-
* Returns a proxy — mutations to the proxy trigger fine-grained reactive updates.
|
|
87
|
-
*/
|
|
88
|
-
export function createStore<T extends object>(initial: T): T {
|
|
89
|
-
return wrap(initial, false) as T
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Create a SHALLOW reactive store — only top-level mutations trigger updates.
|
|
94
|
-
* Nested objects are NOT auto-wrapped; reading a nested object returns the
|
|
95
|
-
* raw reference. Use when:
|
|
96
|
-
* - the nested objects are immutable (frozen API responses)
|
|
97
|
-
* - you want explicit control over which subtrees are reactive
|
|
98
|
-
* - you need to store class instances or third-party objects without
|
|
99
|
-
* paying the deep-proxy overhead
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* const store = shallowReactive({ user: { name: 'Alice' }, count: 0 })
|
|
103
|
-
* effect(() => console.log(store.user)) // tracks store.user reference
|
|
104
|
-
* effect(() => console.log(store.count)) // tracks store.count
|
|
105
|
-
* store.user.name = 'Bob' // does NOT trigger any effect
|
|
106
|
-
* store.count = 5 // triggers the count effect
|
|
107
|
-
* store.user = { name: 'Bob' } // triggers the user effect
|
|
108
|
-
*/
|
|
109
|
-
export function shallowReactive<T extends object>(initial: T): T {
|
|
110
|
-
return wrap(initial, true) as T
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function wrap(raw: object, shallow: boolean): object {
|
|
114
|
-
// Built-ins with internal slots (Map, Set, Date, …) can't be proxied: their
|
|
115
|
-
// methods fail the receiver check when called on the proxy. Return raw.
|
|
116
|
-
if (isBuiltinNonProxiable(raw)) return raw
|
|
117
|
-
// Vue parity — `markRaw()` opts an object out of proxying entirely. Useful
|
|
118
|
-
// for class instances, third-party objects, or any shape the consumer
|
|
119
|
-
// wants to keep unwrapped.
|
|
120
|
-
if (isMarkedRaw(raw)) return raw
|
|
121
|
-
|
|
122
|
-
const cache = shallow ? shallowProxyCache : proxyCache
|
|
123
|
-
const cached = cache.get(raw)
|
|
124
|
-
if (cached) return cached
|
|
125
|
-
|
|
126
|
-
// Per-property signals. Lazily created on first access.
|
|
127
|
-
const propSignals = new Map<PropertyKey, Signal<unknown>>()
|
|
128
|
-
// For arrays: track length changes separately (push/pop/splice affect length)
|
|
129
|
-
const isArray = Array.isArray(raw)
|
|
130
|
-
const lengthSig = isArray ? signal((raw as unknown[]).length) : null
|
|
131
|
-
|
|
132
|
-
function getOrCreateSignal(key: PropertyKey): Signal<unknown> {
|
|
133
|
-
if (!propSignals.has(key)) {
|
|
134
|
-
propSignals.set(key, signal((raw as Record<PropertyKey, unknown>)[key]))
|
|
135
|
-
}
|
|
136
|
-
return propSignals.get(key) as Signal<unknown>
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const proxy = new Proxy(raw, {
|
|
140
|
-
get(target, key) {
|
|
141
|
-
// Pass through the identity marker and non-string/number keys (symbols, etc.)
|
|
142
|
-
if (key === IS_STORE) return true
|
|
143
|
-
if (typeof key === 'symbol') return (target as Record<symbol, unknown>)[key]
|
|
144
|
-
|
|
145
|
-
// Array length — tracked via dedicated signal for push/pop/splice reactivity
|
|
146
|
-
if (isArray && key === 'length') return lengthSig?.()
|
|
147
|
-
|
|
148
|
-
// Non-own properties without a tracked signal: prototype methods
|
|
149
|
-
// (forEach, map, push, …) returned untracked so array methods work.
|
|
150
|
-
// BUT if a signal already exists for this key, the property was tracked
|
|
151
|
-
// before — most likely the property is currently absent because of a
|
|
152
|
-
// `delete` operation. Continue tracking via the existing signal so that
|
|
153
|
-
// a subsequent reassign (`state.b = 99`) re-runs effects that read the
|
|
154
|
-
// key during its absent window. Without this branch, the `delete` →
|
|
155
|
-
// notify-undefined → effect-re-runs-and-reads-`undefined`-via-this-fast-
|
|
156
|
-
// path → effect-loses-subscription chain breaks reactivity for any
|
|
157
|
-
// delete-then-reassign cycle.
|
|
158
|
-
if (!Object.hasOwn(target, key)) {
|
|
159
|
-
if (propSignals.has(key)) return propSignals.get(key)?.()
|
|
160
|
-
return (target as Record<PropertyKey, unknown>)[key]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Track via per-property signal
|
|
164
|
-
const value = getOrCreateSignal(key)()
|
|
165
|
-
|
|
166
|
-
// Deep reactivity: wrap nested objects/arrays transparently. Shallow
|
|
167
|
-
// mode skips this — nested objects are returned raw so consumers can
|
|
168
|
-
// mutate them outside the proxy without triggering effects.
|
|
169
|
-
if (!shallow && value !== null && typeof value === 'object') {
|
|
170
|
-
return wrap(value as object, false)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return value
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
set(target, key, value) {
|
|
177
|
-
if (typeof key === 'symbol') {
|
|
178
|
-
;(target as Record<symbol, unknown>)[key] = value
|
|
179
|
-
return true
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Defense-in-depth against prototype pollution: a bare
|
|
183
|
-
// `target.__proto__ = obj` here would mutate the store object's
|
|
184
|
-
// prototype rather than set a property. `reconcile()` already
|
|
185
|
-
// filters these, but the proxy is the public write surface
|
|
186
|
-
// (`store.__proto__ = …`, spread of untrusted data) so guard here
|
|
187
|
-
// too. Silently ignore — assigning these through a store is never
|
|
188
|
-
// a legitimate operation.
|
|
189
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
190
|
-
return true
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const prevLength = isArray ? (target as unknown[]).length : 0
|
|
194
|
-
;(target as Record<PropertyKey, unknown>)[key] = value
|
|
195
|
-
|
|
196
|
-
// Array length set directly (e.g. arr.length = 0)
|
|
197
|
-
if (isArray && key === 'length') {
|
|
198
|
-
lengthSig?.set(value as number)
|
|
199
|
-
return true
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Update or create signal for this property
|
|
203
|
-
if (propSignals.has(key)) {
|
|
204
|
-
propSignals.get(key)?.set(value)
|
|
205
|
-
} else {
|
|
206
|
-
propSignals.set(key, signal(value))
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// If array length changed (e.g. via push/splice index assignment), update it
|
|
210
|
-
if (isArray && (target as unknown[]).length !== prevLength) {
|
|
211
|
-
lengthSig?.set((target as unknown[]).length)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return true
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
deleteProperty(target, key) {
|
|
218
|
-
delete (target as Record<PropertyKey, unknown>)[key]
|
|
219
|
-
// Notify subscribers that the property is now undefined, but KEEP the
|
|
220
|
-
// signal in `propSignals`. If we delete the entry, a later `set` on the
|
|
221
|
-
// same key creates a fresh signal — but every effect that previously
|
|
222
|
-
// read this key tracked the old (dropped) signal and never re-runs on
|
|
223
|
-
// the reassign. Keeping the entry preserves signal identity across
|
|
224
|
-
// delete-then-reassign cycles. The trade-off is that long-lived stores
|
|
225
|
-
// with high churn on transient keys retain those signal entries; for
|
|
226
|
-
// workloads where that's a real leak, reassign to undefined instead of
|
|
227
|
-
// delete.
|
|
228
|
-
if (typeof key !== 'symbol' && propSignals.has(key)) {
|
|
229
|
-
propSignals.get(key)?.set(undefined)
|
|
230
|
-
}
|
|
231
|
-
if (isArray) lengthSig?.set((target as unknown[]).length)
|
|
232
|
-
return true
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
has(target, key) {
|
|
236
|
-
return Reflect.has(target, key)
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
ownKeys(target) {
|
|
240
|
-
return Reflect.ownKeys(target)
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
getOwnPropertyDescriptor(target, key) {
|
|
244
|
-
return Reflect.getOwnPropertyDescriptor(target, key)
|
|
245
|
-
},
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
cache.set(raw, proxy)
|
|
249
|
-
return proxy
|
|
250
|
-
}
|