@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.
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
@@ -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
- }