@pyreon/reactivity 0.24.4 → 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.
Files changed (44) hide show
  1. package/package.json +1 -4
  2. package/src/batch.ts +0 -196
  3. package/src/cell.ts +0 -72
  4. package/src/computed.ts +0 -313
  5. package/src/createSelector.ts +0 -109
  6. package/src/debug.ts +0 -134
  7. package/src/effect.ts +0 -467
  8. package/src/env.d.ts +0 -6
  9. package/src/index.ts +0 -60
  10. package/src/lpih.ts +0 -227
  11. package/src/manifest.ts +0 -660
  12. package/src/reactive-devtools.ts +0 -494
  13. package/src/reactive-trace.ts +0 -142
  14. package/src/reconcile.ts +0 -118
  15. package/src/resource.ts +0 -84
  16. package/src/scope.ts +0 -123
  17. package/src/signal.ts +0 -261
  18. package/src/store.ts +0 -250
  19. package/src/tests/batch.test.ts +0 -751
  20. package/src/tests/bind.test.ts +0 -84
  21. package/src/tests/branches.test.ts +0 -343
  22. package/src/tests/cell.test.ts +0 -159
  23. package/src/tests/computed.test.ts +0 -436
  24. package/src/tests/coverage-hardening.test.ts +0 -471
  25. package/src/tests/createSelector.test.ts +0 -291
  26. package/src/tests/debug.test.ts +0 -196
  27. package/src/tests/effect.test.ts +0 -464
  28. package/src/tests/fanout-repro.test.ts +0 -179
  29. package/src/tests/lpih-source-location.test.ts +0 -277
  30. package/src/tests/lpih.test.ts +0 -351
  31. package/src/tests/manifest-snapshot.test.ts +0 -96
  32. package/src/tests/reactive-devtools-treeshake.test.ts +0 -48
  33. package/src/tests/reactive-devtools.test.ts +0 -296
  34. package/src/tests/reactive-trace.test.ts +0 -102
  35. package/src/tests/reconcile-security.test.ts +0 -45
  36. package/src/tests/resource.test.ts +0 -326
  37. package/src/tests/scope.test.ts +0 -231
  38. package/src/tests/signal.test.ts +0 -368
  39. package/src/tests/store.test.ts +0 -286
  40. package/src/tests/tracking.test.ts +0 -158
  41. package/src/tests/vue-parity.test.ts +0 -191
  42. package/src/tests/watch.test.ts +0 -246
  43. package/src/tracking.ts +0 -139
  44. 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
- }