@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/effect.ts DELETED
@@ -1,467 +0,0 @@
1
- import { _captureCallerLocation, _rdRecordFire, _rdRegister } from './reactive-devtools'
2
- import { getCurrentScope } from './scope'
3
- import { _restoreActiveEffect, _setActiveEffect, setDepsCollector, withTracking } from './tracking'
4
-
5
- // Dev-time counter sink — see packages/internals/perf-harness for contract.
6
- const _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }
7
-
8
- export interface Effect {
9
- dispose(): void
10
- }
11
-
12
- export interface EffectOptions {
13
- /**
14
- * @internal — source location injected by `@pyreon/vite-plugin` at build
15
- * time. When present, the runtime skips the `new Error().stack` capture
16
- * in `_rdRegister` — saves ~2.2µs per effect creation when devtools is
17
- * active. Plain user code should NOT set this; the field is opaque
18
- * (no public type) so it's not part of the public API surface.
19
- *
20
- * Shape: `{ file: string; line: number; col: number }` matching
21
- * `@pyreon/reactivity`'s `SourceLocation`.
22
- */
23
- __sourceLocation?: { file: string; line: number; col: number }
24
- }
25
-
26
- // ─── Effect-scoped snapshot capture (DI from `@pyreon/core`) ─────────────────
27
- //
28
- // Effects re-run reactively in response to signal changes. When that re-run
29
- // happens AFTER the synchronous mount that set the effect up, the surrounding
30
- // context stack (from `@pyreon/core`'s provide() calls) may have been
31
- // destructively truncated by `mountReactive`'s `restoreContextStack` cleanup.
32
- // Without restoring the captured context, signal-driven re-runs of `_bind` /
33
- // `renderEffect` / `effect` see a half-empty stack and `useContext()` falls
34
- // back to the default value — silently breaking provider-backed APIs like
35
- // `useMode()`, `useTheme()`, `useRouter()`, etc. on every reactive update.
36
- //
37
- // `@pyreon/reactivity` is below `@pyreon/core` in the dep order, so it can't
38
- // import `captureContextStack` / `restoreContextStack` directly. Core
39
- // registers its capture+restore pair via `setSnapshotCapture` at module load.
40
- // When unset (raw reactivity-only consumers), effects skip context handling
41
- // — same behavior as before this hook existed.
42
- export interface ReactiveSnapshotCapture {
43
- capture: () => unknown
44
- /** Run `fn` with the previously-captured snapshot active. */
45
- restore: <T>(snap: unknown, fn: () => T) => T
46
- }
47
-
48
- let _snapshotCapture: ReactiveSnapshotCapture | null = null
49
-
50
- /**
51
- * Register a capture/restore pair so reactivity-layer effects (`_bind`,
52
- * `renderEffect`, `effect`) can preserve external context (e.g. the core
53
- * provide/useContext stack) across signal-driven re-runs. Called by
54
- * `@pyreon/core`'s context module at import time. Idempotent — calling again
55
- * replaces the previously registered hook.
56
- */
57
- export function setSnapshotCapture(hook: ReactiveSnapshotCapture | null): void {
58
- _snapshotCapture = hook
59
- }
60
-
61
- // ─── onCleanup ───────────────────────────────────────────────────────────────
62
- // Thread-local collector for cleanup functions registered via onCleanup()
63
- // during effect execution. Pushed/popped around the user callback in effect().
64
- let _cleanupCollector: (() => void)[] | null = null
65
-
66
- /**
67
- * Register a cleanup function inside an effect. The cleanup runs:
68
- * - Before the effect re-runs (when dependencies change)
69
- * - When the effect is disposed
70
- *
71
- * Can be called multiple times — all cleanups run in registration order.
72
- * Must be called synchronously during effect setup (like onMount/onUnmount).
73
- *
74
- * @example
75
- * effect(() => {
76
- * const controller = new AbortController()
77
- * onCleanup(() => controller.abort())
78
- * fetch(`/api/user/${userId()}`, { signal: controller.signal })
79
- * .then(r => r.json())
80
- * .then(data => user.set(data))
81
- * })
82
- */
83
- export function onCleanup(fn: () => void): void {
84
- if (_cleanupCollector) {
85
- _cleanupCollector.push(fn)
86
- }
87
- }
88
-
89
- // Thread-local collector for nested effects — captures effect() calls made
90
- // inside another effect's fn() body so the parent can dispose them on
91
- // re-run / disposal. Without this, inner effects leak across outer
92
- // lifecycle boundaries (caught by cleanup-nested.test.ts).
93
- let _innerEffectCollector: Effect[] | null = null
94
-
95
- // Global error handler — called for unhandled errors thrown inside effects.
96
- // Defaults to console.error so silent failures are never swallowed.
97
- //
98
- // Two-layer model:
99
- // 1. The user-overridable single handler set via `setErrorHandler` (legacy
100
- // direct API).
101
- // 2. A globalThis bridge `__pyreon_report_error__` that `@pyreon/core`
102
- // installs in `registerErrorHandler` to forward effect errors into the
103
- // same telemetry pipeline as component / mount / render errors.
104
- // Pre-fix the two surfaces were disconnected — Sentry/Datadog wiring via
105
- // core's `registerErrorHandler` silently missed effect-thrown errors.
106
- // Globalthis-based to avoid an upward import (core depends on
107
- // reactivity, not the reverse). Same shape as the perf-harness counter
108
- // sink — zero cost when no consumer is installed.
109
- //
110
- // Both surfaces fire on every effect error. The legacy handler stays for
111
- // backward compat; new consumers should prefer `@pyreon/core`'s
112
- // `registerErrorHandler`.
113
-
114
- interface PyreonErrorBridge {
115
- __pyreon_report_error__?: (err: unknown, phase: 'effect') => void
116
- }
117
- const _errorBridge = globalThis as PyreonErrorBridge
118
-
119
- function _defaultErrorHandler(err: unknown): void {
120
- // Last-resort unhandled-effect-error reporter — MUST fire in
121
- // production (silently swallowing uncaught effect errors is a
122
- // serious bug; React/Vue/Solid all log uncaught errors in prod).
123
- // Deliberately not __DEV__-gated.
124
- // pyreon-lint-disable-next-line pyreon/dev-guard-warnings
125
- console.error('[pyreon] Unhandled effect error:', err)
126
- }
127
-
128
- let _userErrorHandler: ((err: unknown) => void) | undefined
129
-
130
- export const _errorHandler: (err: unknown) => void = (err) => {
131
- // 1. User-set or default direct handler.
132
- ;(_userErrorHandler ?? _defaultErrorHandler)(err)
133
- // 2. Global telemetry bridge (installed by @pyreon/core's
134
- // registerErrorHandler). Forwards effect errors into reportError so
135
- // Sentry/Datadog wiring captures them alongside component errors.
136
- _errorBridge.__pyreon_report_error__?.(err, 'effect')
137
- }
138
-
139
- export function setErrorHandler(fn: (err: unknown) => void): void {
140
- _userErrorHandler = fn
141
- }
142
-
143
- /** Remove an effect from all dependency subscriber sets (local deps array). */
144
- function cleanupLocalDeps(deps: Set<() => void>[], fn: () => void): void {
145
- if (deps.length === 1) {
146
- ;(deps[0] as Set<() => void>).delete(fn)
147
- deps.length = 0
148
- } else if (deps.length > 1) {
149
- for (let i = 0; i < deps.length; i++) (deps[i] as Set<() => void>).delete(fn)
150
- deps.length = 0
151
- }
152
- }
153
-
154
- export function effect(
155
- fn: () => (() => void) | void,
156
- options?: EffectOptions,
157
- ): Effect {
158
- // Dev-mode warning for async effect callbacks (audit bug #1). The
159
- // tracking context is the synchronous frame around `fn()`'s top half;
160
- // anything after the first `await` runs detached, so signal reads on
161
- // the back side aren't tracked and the effect won't re-run when those
162
- // signals change. The fix at the call site is either to read all
163
- // tracked signals BEFORE the first await, or split the work into two
164
- // effects (or use `watch` for async-in-callback). Surfacing the warn
165
- // at registration is the cheapest catch we can offer: an
166
- // `AsyncFunction.prototype.constructor.name === 'AsyncFunction'`
167
- // check is true at function-definition time without invoking anything.
168
- if (process.env.NODE_ENV !== 'production') {
169
- if (fn.constructor && fn.constructor.name === 'AsyncFunction') {
170
- // oxlint-disable-next-line no-console
171
- console.warn(
172
- '[pyreon] effect() received an async function. Signal reads after the first `await` are NOT tracked — only the synchronous prefix is. ' +
173
- 'Read every tracked signal BEFORE any await, or split into separate effects, or use `watch(source, asyncCb)` for async-in-callback patterns.',
174
- )
175
- }
176
- }
177
-
178
- // Capture the scope at creation time — remains correct during future re-runs
179
- // even after setCurrentScope(null) has been called post-setup.
180
- const scope = getCurrentScope()
181
- // Capture the external (core-context) snapshot at SETUP time. Reactive
182
- // re-runs restore it before invoking fn, so provider lookups stay correct
183
- // even when the global context stack has been destructively truncated by
184
- // mountReactive's restoreContextStack cleanup. See `_bind` for the full
185
- // rationale.
186
- const snapshot = _snapshotCapture ? _snapshotCapture.capture() : null
187
- let disposed = false
188
- let isFirstRun = true
189
- let cleanup: (() => void) | undefined
190
- // Local deps array — avoids WeakMap overhead (like renderEffect)
191
- const deps: Set<() => void>[] = []
192
-
193
- let cleanups: (() => void)[] | undefined
194
- // Inner effects created during this effect's fn() body. Disposed on
195
- // outer re-run (before the next fn()) and on outer dispose(). Without
196
- // this, nested effects leak across outer lifecycle boundaries.
197
- let innerEffects: Effect[] | null = null
198
-
199
- const runCleanup = () => {
200
- if (innerEffects) {
201
- for (const ie of innerEffects) {
202
- try {
203
- ie.dispose()
204
- } catch (err) {
205
- _errorHandler(err)
206
- }
207
- }
208
- innerEffects = null
209
- }
210
- if (cleanups) {
211
- for (const c of cleanups) {
212
- try {
213
- c()
214
- } catch (err) {
215
- _errorHandler(err)
216
- }
217
- }
218
- cleanups = undefined
219
- }
220
- if (typeof cleanup === 'function') {
221
- try {
222
- cleanup()
223
- } catch (err) {
224
- _errorHandler(err)
225
- }
226
- cleanup = undefined
227
- }
228
- }
229
-
230
- const run = () => {
231
- if (disposed) return
232
- if (process.env.NODE_ENV !== 'production') {
233
- _countSink.__pyreon_count__?.('reactivity.effectRun')
234
- _rdRecordFire(run)
235
- }
236
- // Run previous cleanup before re-running
237
- runCleanup()
238
- // Start a new inner-effect collection window. Effects created during
239
- // fn() will push themselves into this array and be disposed on the
240
- // next re-run or on dispose.
241
- const outerCollector = _innerEffectCollector
242
- const myInners: Effect[] = []
243
- _innerEffectCollector = myInners
244
- try {
245
- cleanupLocalDeps(deps, run)
246
- setDepsCollector(deps)
247
- // Collect onCleanup() registrations during execution
248
- const collected: (() => void)[] = []
249
- _cleanupCollector = collected
250
- // First run executes inside the synchronous mount where the context
251
- // stack is still intact — call fn directly to avoid pushing the
252
- // captured snapshot a redundant second time. Subsequent re-runs
253
- // happen AFTER mountReactive's cleanup has truncated the stack, so
254
- // they need the snapshot restored to find provider frames.
255
- const fnToRun =
256
- isFirstRun || snapshot === null || _snapshotCapture === null
257
- ? fn
258
- : () => (_snapshotCapture as ReactiveSnapshotCapture).restore(snapshot, fn)
259
- cleanup = withTracking(run, fnToRun) || undefined
260
- _cleanupCollector = null
261
- if (collected.length > 0) cleanups = collected
262
- setDepsCollector(null)
263
- } catch (err) {
264
- _cleanupCollector = null
265
- setDepsCollector(null)
266
- _errorHandler(err)
267
- } finally {
268
- _innerEffectCollector = outerCollector
269
- }
270
- if (myInners.length > 0) innerEffects = myInners
271
- // Notify scope after each reactive re-run (not the initial synchronous run)
272
- // so onUpdate hooks fire after the DOM has settled.
273
- if (!isFirstRun) scope?.notifyEffectRan()
274
- isFirstRun = false
275
- }
276
-
277
- if (process.env.NODE_ENV !== 'production')
278
- // skipFrames=1: skip the `effect()` / `renderEffect()` frame, capture the user's call site.
279
- // Prefer build-time-injected location over the ~2.2µs stack-capture
280
- // fallback. @pyreon/vite-plugin's `injectSignalNames` rewrites
281
- // `effect(() => …)` to `effect(() => …, { __sourceLocation: {…} })`.
282
- _rdRegister(
283
- run,
284
- 'effect',
285
- null,
286
- run,
287
- undefined,
288
- options?.__sourceLocation ?? _captureCallerLocation(1),
289
- )
290
-
291
- run()
292
-
293
- const e: Effect = {
294
- dispose() {
295
- runCleanup()
296
- disposed = true
297
- cleanupLocalDeps(deps, run)
298
- },
299
- }
300
-
301
- // If we're inside another effect's run, register with it so the outer
302
- // disposes this inner automatically.
303
- if (_innerEffectCollector !== null) {
304
- _innerEffectCollector.push(e)
305
- } else {
306
- // Otherwise auto-register with the active EffectScope (if any)
307
- getCurrentScope()?.add(e)
308
- }
309
-
310
- return e
311
- }
312
-
313
- /**
314
- * Lightweight effect for DOM render bindings.
315
- *
316
- * Differences from `effect()`:
317
- * - No EffectScope registration (caller owns the dispose lifecycle)
318
- * - No error handler (errors propagate naturally)
319
- * - No onUpdate notification
320
- * - Deps stored in a local array instead of the global WeakMap — faster
321
- * creation and disposal (~200ns saved per effect vs WeakMap path)
322
- *
323
- * Returns a dispose function (not an Effect object — saves 1 allocation).
324
- */
325
- /**
326
- * Static-dep binding — compiler helper for template expressions.
327
- *
328
- * Like renderEffect but assumes dependencies never change (true for all
329
- * compiler-emitted template bindings like `_tpl()` text/attribute updates).
330
- *
331
- * Tracks dependencies only on the first run. Re-runs skip cleanup, re-tracking,
332
- * and tracking context save/restore entirely — just calls `fn()` directly.
333
- *
334
- * Per re-run savings vs renderEffect:
335
- * - No deps iteration + Set.delete (cleanup)
336
- * - No setDepsCollector + withTracking (re-registration)
337
- * - Signal reads hit `if (activeEffect)` null check → instant return
338
- */
339
- export function _bind(fn: () => void): () => void {
340
- const deps: Set<() => void>[] = []
341
- let disposed = false
342
-
343
- // Capture external (core-context) snapshot at SETUP time. Re-runs restore
344
- // it before invoking fn, so signal-driven re-runs see the same provider
345
- // chain that was active when the binding was first set up — even if the
346
- // global context stack has been destructively truncated by mountReactive's
347
- // restoreContextStack cleanup in the meantime.
348
- const snapshot = _snapshotCapture ? _snapshotCapture.capture() : null
349
-
350
- const run = () => {
351
- if (disposed) return
352
- if (snapshot !== null && _snapshotCapture) {
353
- _snapshotCapture.restore(snapshot, fn)
354
- } else {
355
- fn()
356
- }
357
- }
358
-
359
- // First run: track deps so we know what to unsubscribe on dispose. We
360
- // intentionally call `fn` directly (not `run`) here — the synchronous
361
- // mount stack is already intact at this point, so restoring the captured
362
- // snapshot would just push the same frames again redundantly.
363
- setDepsCollector(deps)
364
- withTracking(run, fn)
365
- setDepsCollector(null)
366
-
367
- const dispose = () => {
368
- if (disposed) return
369
- disposed = true
370
- for (const s of deps) s.delete(run)
371
- deps.length = 0
372
- }
373
-
374
- // Auto-register with scope so template bindings are disposed during teardown
375
- getCurrentScope()?.add({ dispose })
376
-
377
- return dispose
378
- }
379
-
380
- /** Full re-track path for renderEffect: cleanup old deps, evaluate with tracking. */
381
- function renderEffectFullTrack(deps: Set<() => void>[], run: () => void, fn: () => void): void {
382
- if (deps.length === 1) {
383
- ;(deps[0] as Set<() => void>).delete(run)
384
- deps.length = 0
385
- } else if (deps.length > 1) {
386
- for (const s of deps) s.delete(run)
387
- deps.length = 0
388
- }
389
- setDepsCollector(deps)
390
- _setActiveEffect(run)
391
- try {
392
- fn()
393
- } finally {
394
- _restoreActiveEffect()
395
- setDepsCollector(null)
396
- }
397
- }
398
-
399
- export function renderEffect(fn: () => void): () => void {
400
- // Same dev warning as `effect()` — signal reads after the first
401
- // await aren't tracked. See effect()'s docstring for full reasoning.
402
- if (process.env.NODE_ENV !== 'production') {
403
- if (fn.constructor && fn.constructor.name === 'AsyncFunction') {
404
- // oxlint-disable-next-line no-console
405
- console.warn(
406
- '[pyreon] renderEffect() received an async function. Signal reads after the first `await` are NOT tracked — only the synchronous prefix is. ' +
407
- 'Read every tracked signal BEFORE any await, or split into separate effects, or use `watch(source, asyncCb)` for async-in-callback patterns.',
408
- )
409
- }
410
- }
411
-
412
- const deps: Set<() => void>[] = []
413
- let disposed = false
414
- let isFirstRun = true
415
-
416
- // Same rationale as `_bind`: capture the external context snapshot at
417
- // SETUP and restore it on signal-driven re-runs so provider lookups stay
418
- // correct even after `mountReactive`'s cleanup truncates the global stack.
419
- const snapshot = _snapshotCapture ? _snapshotCapture.capture() : null
420
-
421
- const trackedFn =
422
- snapshot !== null && _snapshotCapture
423
- ? () => (_snapshotCapture as ReactiveSnapshotCapture).restore(snapshot, fn)
424
- : fn
425
-
426
- const run = () => {
427
- if (disposed) return
428
- if (isFirstRun) {
429
- isFirstRun = false
430
- setDepsCollector(deps)
431
- _setActiveEffect(run)
432
- try {
433
- // First run: stack is still intact (we're inside the synchronous
434
- // mount), so call fn directly to avoid pushing the snapshot frames
435
- // a second time.
436
- fn()
437
- } finally {
438
- _restoreActiveEffect()
439
- setDepsCollector(null)
440
- }
441
- } else {
442
- renderEffectFullTrack(deps, run, trackedFn)
443
- }
444
- }
445
-
446
- if (process.env.NODE_ENV !== 'production')
447
- // skipFrames=1: skip the `effect()` / `renderEffect()` frame, capture the user's call site.
448
- _rdRegister(run, 'effect', null, run, undefined, _captureCallerLocation(1))
449
-
450
- run()
451
-
452
- const dispose = () => {
453
- if (disposed) return
454
- disposed = true
455
- if (deps.length === 1) {
456
- ;(deps[0] as Set<() => void>).delete(run)
457
- } else {
458
- for (const s of deps) s.delete(run)
459
- }
460
- deps.length = 0
461
- }
462
-
463
- // Auto-register with scope so render effects are disposed during teardown
464
- getCurrentScope()?.add({ dispose })
465
-
466
- return dispose
467
- }
package/src/env.d.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Minimal process type — just enough for `process.env.NODE_ENV` checks.
3
- * Avoids requiring @types/node in consumers that import pyreon source
4
- * via the `"bun"` export condition.
5
- */
6
- declare var process: { env: { NODE_ENV?: string } }
package/src/index.ts DELETED
@@ -1,60 +0,0 @@
1
- // @pyreon/reactivity — signals-based reactive primitives
2
-
3
- export { batch, nextTick } from './batch'
4
- export { Cell, cell } from './cell'
5
- export { type Computed, type ComputedOptions, computed } from './computed'
6
- export { createSelector } from './createSelector'
7
- export { inspectSignal, onSignalUpdate, why } from './debug'
8
- export type {
9
- FireSummary,
10
- ReactiveEdge,
11
- ReactiveFire,
12
- ReactiveGraph,
13
- ReactiveNode,
14
- ReactiveNodeKind,
15
- SourceLocation,
16
- } from './reactive-devtools'
17
- export {
18
- activateReactiveDevtools,
19
- deactivateReactiveDevtools,
20
- getFireSummaries,
21
- getReactiveFires,
22
- getReactiveGraph,
23
- isReactiveDevtoolsActive,
24
- } from './reactive-devtools'
25
- // `writeLpihCache` + `startLpihPolling` ship at the `@pyreon/reactivity/lpih`
26
- // subpath. They depend on `node:fs/promises` (Node-only) and are dev-mode
27
- // integration utilities — separating them keeps the core main-entry bundle
28
- // smaller AND clarifies that LPIH writes are an opt-in side-channel, not a
29
- // core reactivity primitive. See `./lpih.ts` and `docs/docs/lpih.md`.
30
- export type { ReactiveTraceEntry } from './reactive-trace'
31
- export { clearReactiveTrace, getReactiveTrace } from './reactive-trace'
32
- export {
33
- _bind,
34
- type Effect,
35
- effect,
36
- onCleanup,
37
- type ReactiveSnapshotCapture,
38
- renderEffect,
39
- setErrorHandler,
40
- setSnapshotCapture,
41
- } from './effect'
42
- export { reconcile } from './reconcile'
43
- export { createResource, type Resource } from './resource'
44
- export {
45
- EffectScope,
46
- effectScope,
47
- getCurrentScope,
48
- onScopeDispose,
49
- setCurrentScope,
50
- } from './scope'
51
- export {
52
- type ReadonlySignal,
53
- type Signal,
54
- type SignalDebugInfo,
55
- type SignalOptions,
56
- signal,
57
- } from './signal'
58
- export { createStore, isStore, markRaw, shallowReactive } from './store'
59
- export { runUntracked, runUntracked as untrack } from './tracking'
60
- export { type WatchOptions, watch } from './watch'