@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.
- 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/createSelector.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { effect } from './effect'
|
|
2
|
-
import { trackSubscriber } from './tracking'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Notify a subscriber bucket without snapshot allocation.
|
|
6
|
-
* Caps iteration at the original size to avoid infinite loops from
|
|
7
|
-
* re-inserted entries (same pattern as notifySubscribers in tracking.ts).
|
|
8
|
-
*/
|
|
9
|
-
function notifyBucket(bucket: Set<() => void>): void {
|
|
10
|
-
if (bucket.size === 0) return
|
|
11
|
-
if (bucket.size === 1) {
|
|
12
|
-
;(bucket.values().next().value as () => void)()
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
const originalSize = bucket.size
|
|
16
|
-
let i = 0
|
|
17
|
-
for (const fn of bucket) {
|
|
18
|
-
if (i >= originalSize) break
|
|
19
|
-
fn()
|
|
20
|
-
i++
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Selector predicate with a `dispose()` method to release internal state. */
|
|
25
|
-
export interface Selector<T> {
|
|
26
|
-
(value: T): boolean
|
|
27
|
-
/**
|
|
28
|
-
* Stop the source-tracking effect AND clear the per-value subscriber/host
|
|
29
|
-
* Maps. After dispose, calls to the selector return the last-known result
|
|
30
|
-
* but no longer track. Required for selectors over dynamic value spaces
|
|
31
|
-
* (UUIDs, ephemeral IDs) created outside an `EffectScope` — without it,
|
|
32
|
-
* each unique queried value adds a permanent entry to the internal Maps,
|
|
33
|
-
* leaking memory for the lifetime of the program. Idempotent.
|
|
34
|
-
*/
|
|
35
|
-
dispose(): void
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Create an equality selector — returns a reactive predicate that is true
|
|
40
|
-
* only for the currently selected value.
|
|
41
|
-
*
|
|
42
|
-
* Unlike a plain `() => source() === value`, this only triggers the TWO
|
|
43
|
-
* affected subscribers (deselected + newly selected) instead of ALL
|
|
44
|
-
* subscribers, making selection O(1) regardless of list size.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* const isSelected = createSelector(selectedId)
|
|
48
|
-
* // In each row:
|
|
49
|
-
* class: () => (isSelected(row.id) ? "selected" : "")
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* // Dynamic value spaces — call dispose() to release the per-value cache:
|
|
53
|
-
* const isCurrentTab = createSelector(() => currentTabId())
|
|
54
|
-
* onUnmount(() => isCurrentTab.dispose())
|
|
55
|
-
*/
|
|
56
|
-
export function createSelector<T>(source: () => T): Selector<T> {
|
|
57
|
-
const subs = new Map<T, Set<() => void>>()
|
|
58
|
-
let current: T
|
|
59
|
-
let initialized = false
|
|
60
|
-
let disposed = false
|
|
61
|
-
|
|
62
|
-
const sourceEffect = effect(() => {
|
|
63
|
-
const next = source()
|
|
64
|
-
if (!initialized) {
|
|
65
|
-
initialized = true
|
|
66
|
-
current = next
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
if (Object.is(next, current)) return
|
|
70
|
-
const old = current
|
|
71
|
-
current = next
|
|
72
|
-
// Only notify the two affected buckets — O(1) regardless of list size.
|
|
73
|
-
// Iteration-capped loop avoids [...bucket] snapshot allocation.
|
|
74
|
-
const oldBucket = subs.get(old)
|
|
75
|
-
const newBucket = subs.get(next)
|
|
76
|
-
if (oldBucket) notifyBucket(oldBucket)
|
|
77
|
-
if (newBucket) notifyBucket(newBucket)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Reusable hosts per value — avoids allocating a closure per trackSubscriber call
|
|
81
|
-
const hosts = new Map<T, { _s: Set<() => void> | null }>()
|
|
82
|
-
|
|
83
|
-
const selector = ((value: T): boolean => {
|
|
84
|
-
if (!disposed) {
|
|
85
|
-
let host = hosts.get(value)
|
|
86
|
-
if (!host) {
|
|
87
|
-
let bucket = subs.get(value)
|
|
88
|
-
if (!bucket) {
|
|
89
|
-
bucket = new Set()
|
|
90
|
-
subs.set(value, bucket)
|
|
91
|
-
}
|
|
92
|
-
host = { _s: bucket }
|
|
93
|
-
hosts.set(value, host)
|
|
94
|
-
}
|
|
95
|
-
trackSubscriber(host)
|
|
96
|
-
}
|
|
97
|
-
return Object.is(current, value)
|
|
98
|
-
}) as Selector<T>
|
|
99
|
-
|
|
100
|
-
selector.dispose = (): void => {
|
|
101
|
-
if (disposed) return
|
|
102
|
-
disposed = true
|
|
103
|
-
sourceEffect.dispose()
|
|
104
|
-
subs.clear()
|
|
105
|
-
hosts.clear()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return selector
|
|
109
|
-
}
|
package/src/debug.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pyreon/reactivity debug utilities.
|
|
3
|
-
*
|
|
4
|
-
* Development-only tools for tracing signal updates, inspecting reactive
|
|
5
|
-
* graphs, and understanding why DOM nodes re-render.
|
|
6
|
-
*
|
|
7
|
-
* All utilities are tree-shakeable — they compile away in production builds
|
|
8
|
-
* when unused.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Signal, SignalDebugInfo } from './signal'
|
|
12
|
-
|
|
13
|
-
// ─── Signal update tracing ───────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
interface SignalUpdateEvent {
|
|
16
|
-
/** The signal that changed */
|
|
17
|
-
signal: Signal<unknown>
|
|
18
|
-
/** Signal name (from options or label) */
|
|
19
|
-
name: string | undefined
|
|
20
|
-
/** Previous value */
|
|
21
|
-
prev: unknown
|
|
22
|
-
/** New value */
|
|
23
|
-
next: unknown
|
|
24
|
-
/** Stack trace at the point of the .set() / .update() call */
|
|
25
|
-
stack: string
|
|
26
|
-
/** Timestamp */
|
|
27
|
-
timestamp: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type SignalUpdateListener = (event: SignalUpdateEvent) => void
|
|
31
|
-
|
|
32
|
-
let _traceListeners: SignalUpdateListener[] | null = null
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Register a listener that fires on every signal write.
|
|
36
|
-
* Returns a dispose function.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* const dispose = onSignalUpdate(e => {
|
|
40
|
-
* console.log(`${e.name ?? 'anonymous'}: ${e.prev} → ${e.next}`)
|
|
41
|
-
* })
|
|
42
|
-
*/
|
|
43
|
-
export function onSignalUpdate(listener: SignalUpdateListener): () => void {
|
|
44
|
-
if (!_traceListeners) _traceListeners = []
|
|
45
|
-
_traceListeners.push(listener)
|
|
46
|
-
return () => {
|
|
47
|
-
if (!_traceListeners) return
|
|
48
|
-
_traceListeners = _traceListeners.filter((l) => l !== listener)
|
|
49
|
-
if (_traceListeners.length === 0) _traceListeners = null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** @internal — called from signal.set() when tracing is active */
|
|
54
|
-
export function _notifyTraceListeners(sig: Signal<unknown>, prev: unknown, next: unknown): void {
|
|
55
|
-
if (!_traceListeners) return
|
|
56
|
-
const event: SignalUpdateEvent = {
|
|
57
|
-
signal: sig,
|
|
58
|
-
name: sig.label,
|
|
59
|
-
prev,
|
|
60
|
-
next,
|
|
61
|
-
stack: new Error().stack ?? '',
|
|
62
|
-
timestamp: performance.now(),
|
|
63
|
-
}
|
|
64
|
-
for (const l of _traceListeners) l(event)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Check if any trace listeners are active (fast path for signal.set) */
|
|
68
|
-
export function isTracing(): boolean {
|
|
69
|
-
return _traceListeners !== null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ─── why() — trace which signal caused a re-run ──────────────────────────────
|
|
73
|
-
|
|
74
|
-
let _whyActive = false
|
|
75
|
-
let _whyLog: { name: string | undefined; prev: unknown; next: unknown }[] = []
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Trace the next signal update. Logs which signals fire and what changed.
|
|
79
|
-
* Call before triggering a state change to see what updates and why.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* why()
|
|
83
|
-
* count.set(5)
|
|
84
|
-
* // Console: [pyreon:why] "count": 3 → 5 (2 subscribers)
|
|
85
|
-
*/
|
|
86
|
-
export function why(): void {
|
|
87
|
-
if (_whyActive) return
|
|
88
|
-
_whyActive = true
|
|
89
|
-
_whyLog = []
|
|
90
|
-
|
|
91
|
-
const dispose = onSignalUpdate((e) => {
|
|
92
|
-
const _subCount = (e.signal as unknown as { _s: Set<unknown> | null })._s?.size ?? 0
|
|
93
|
-
const _name = e.name ? `"${e.name}"` : '(anonymous signal)'
|
|
94
|
-
|
|
95
|
-
console.log(
|
|
96
|
-
`[pyreon:why] ${_name}: ${JSON.stringify(e.prev)} → ${JSON.stringify(e.next)} (${_subCount} subscriber${_subCount === 1 ? '' : 's'})`,
|
|
97
|
-
)
|
|
98
|
-
_whyLog.push({ name: e.name, prev: e.prev, next: e.next })
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Auto-dispose after the current microtask (captures the synchronous batch)
|
|
102
|
-
queueMicrotask(() => {
|
|
103
|
-
dispose()
|
|
104
|
-
if (_whyLog.length === 0) {
|
|
105
|
-
console.log('[pyreon:why] No signal updates detected')
|
|
106
|
-
}
|
|
107
|
-
_whyActive = false
|
|
108
|
-
_whyLog = []
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ─── inspectSignal — rich console output ─────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Print a signal's current state to the console in a readable format.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* const count = signal(42, { name: "count" })
|
|
119
|
-
* inspectSignal(count)
|
|
120
|
-
* // Console:
|
|
121
|
-
* // 🔍 Signal "count"
|
|
122
|
-
* // value: 42
|
|
123
|
-
* // subscribers: 3
|
|
124
|
-
*/
|
|
125
|
-
export function inspectSignal<T>(sig: Signal<T>): SignalDebugInfo<T> {
|
|
126
|
-
const info = sig.debug()
|
|
127
|
-
|
|
128
|
-
console.group(`🔍 Signal ${info.name ? `"${info.name}"` : '(anonymous)'}`)
|
|
129
|
-
console.log('value:', info.value)
|
|
130
|
-
console.log('subscribers:', info.subscriberCount)
|
|
131
|
-
console.groupEnd()
|
|
132
|
-
|
|
133
|
-
return info
|
|
134
|
-
}
|