@pyreon/reactivity 0.1.0

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.
@@ -0,0 +1,725 @@
1
+ function batch(fn) {
2
+ batchDepth++;
3
+ try {
4
+ fn();
5
+ } finally {
6
+ batchDepth--;
7
+ if (batchDepth === 0) {
8
+ const flush = pendingNotifications;
9
+ pendingNotifications = /* @__PURE__ */new Set();
10
+ for (const notify of flush) notify();
11
+ }
12
+ }
13
+ }
14
+ function isBatching() {
15
+ return batchDepth > 0;
16
+ }
17
+ function enqueuePendingNotification(notify) {
18
+ pendingNotifications.add(notify);
19
+ }
20
+ /**
21
+ * Returns a Promise that resolves after all currently-pending microtasks have flushed.
22
+ * Useful when you need to read the DOM after a batch of signal updates has settled.
23
+ *
24
+ * @example
25
+ * count.set(1); count.set(2)
26
+ * await nextTick()
27
+ * // DOM is now up-to-date
28
+ */
29
+ function nextTick() {
30
+ return new Promise(resolve => queueMicrotask(resolve));
31
+ }
32
+
33
+ //#endregion
34
+ //#region src/cell.ts
35
+ /**
36
+ * Lightweight reactive cell — class-based alternative to signal().
37
+ *
38
+ * - 1 object allocation vs signal()'s 6 closures
39
+ * - Same API surface: peek(), set(), update(), subscribe(), listen()
40
+ * - NOT callable as a getter (no effect tracking) — use for fixed subscriptions
41
+ * - Methods on prototype, shared across all instances
42
+ * - Single-listener fast path: no Set allocated when ≤1 subscriber
43
+ *
44
+ * Use when you need reactive state but don't need automatic effect dependency tracking.
45
+ * Ideal for list item labels in keyed reconcilers where subscribe() is used directly.
46
+ */
47
+
48
+ function cell(value) {
49
+ return new Cell(value);
50
+ }
51
+
52
+ //#endregion
53
+ //#region src/scope.ts
54
+
55
+ function getCurrentScope() {
56
+ return _currentScope;
57
+ }
58
+ function setCurrentScope(scope) {
59
+ _currentScope = scope;
60
+ }
61
+ /** Create a new EffectScope. */
62
+ function effectScope() {
63
+ return new EffectScope();
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/tracking.ts
68
+
69
+ function setDepsCollector(collector) {
70
+ _depsCollector = collector;
71
+ }
72
+ /**
73
+ * Register the active effect as a subscriber of the given reactive source.
74
+ * The subscriber Set is created lazily on the host — sources read only outside
75
+ * effects never allocate a Set.
76
+ */
77
+ function trackSubscriber(host) {
78
+ if (activeEffect) {
79
+ if (!host._s) host._s = /* @__PURE__ */new Set();
80
+ const subscribers = host._s;
81
+ subscribers.add(activeEffect);
82
+ if (_depsCollector) _depsCollector.push(subscribers);else {
83
+ let deps = effectDeps.get(activeEffect);
84
+ if (!deps) {
85
+ deps = /* @__PURE__ */new Set();
86
+ effectDeps.set(activeEffect, deps);
87
+ }
88
+ deps.add(subscribers);
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Remove an effect from every subscriber set it was registered in,
94
+ * then clear its dep record. Call this before each re-run and on dispose.
95
+ */
96
+ function cleanupEffect(fn) {
97
+ const deps = effectDeps.get(fn);
98
+ if (deps) {
99
+ for (const sub of deps) sub.delete(fn);
100
+ deps.clear();
101
+ }
102
+ }
103
+ function notifySubscribers(subscribers) {
104
+ if (subscribers.size === 0) return;
105
+ if (subscribers.size === 1) {
106
+ const sub = subscribers.values().next().value;
107
+ if (isBatching()) enqueuePendingNotification(sub);else sub();
108
+ return;
109
+ }
110
+ if (isBatching()) for (const sub of subscribers) enqueuePendingNotification(sub);else for (const sub of [...subscribers]) sub();
111
+ }
112
+ function withTracking(fn, compute) {
113
+ const prev = activeEffect;
114
+ activeEffect = fn;
115
+ try {
116
+ return compute();
117
+ } finally {
118
+ activeEffect = prev;
119
+ }
120
+ }
121
+ function runUntracked(fn) {
122
+ const prev = activeEffect;
123
+ activeEffect = null;
124
+ try {
125
+ return fn();
126
+ } finally {
127
+ activeEffect = prev;
128
+ }
129
+ }
130
+
131
+ //#endregion
132
+ //#region src/computed.ts
133
+ function computed(fn, options) {
134
+ let value;
135
+ let dirty = true;
136
+ let initialized = false;
137
+ let disposed = false;
138
+ const customEquals = options?.equals;
139
+ const host = {
140
+ _s: null
141
+ };
142
+ const recompute = () => {
143
+ if (disposed) return;
144
+ cleanupEffect(recompute);
145
+ if (customEquals) {
146
+ const next = withTracking(recompute, fn);
147
+ if (initialized && customEquals(value, next)) return;
148
+ value = next;
149
+ dirty = false;
150
+ initialized = true;
151
+ if (host._s) notifySubscribers(host._s);
152
+ } else {
153
+ dirty = true;
154
+ if (host._s) notifySubscribers(host._s);
155
+ }
156
+ };
157
+ const read = () => {
158
+ trackSubscriber(host);
159
+ if (dirty) {
160
+ value = withTracking(recompute, fn);
161
+ dirty = false;
162
+ initialized = true;
163
+ }
164
+ return value;
165
+ };
166
+ read.dispose = () => {
167
+ disposed = true;
168
+ cleanupEffect(recompute);
169
+ };
170
+ getCurrentScope()?.add({
171
+ dispose: read.dispose
172
+ });
173
+ return read;
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/effect.ts
178
+
179
+ function setErrorHandler(fn) {
180
+ _errorHandler = fn;
181
+ }
182
+ function effect(fn) {
183
+ const scope = getCurrentScope();
184
+ let disposed = false;
185
+ let isFirstRun = true;
186
+ let cleanup;
187
+ const runCleanup = () => {
188
+ if (typeof cleanup === "function") {
189
+ try {
190
+ cleanup();
191
+ } catch (err) {
192
+ _errorHandler(err);
193
+ }
194
+ cleanup = void 0;
195
+ }
196
+ };
197
+ const run = () => {
198
+ if (disposed) return;
199
+ runCleanup();
200
+ cleanupEffect(run);
201
+ try {
202
+ cleanup = withTracking(run, fn) || void 0;
203
+ } catch (err) {
204
+ _errorHandler(err);
205
+ }
206
+ if (!isFirstRun) scope?.notifyEffectRan();
207
+ isFirstRun = false;
208
+ };
209
+ run();
210
+ const e = {
211
+ dispose() {
212
+ runCleanup();
213
+ disposed = true;
214
+ cleanupEffect(run);
215
+ }
216
+ };
217
+ getCurrentScope()?.add(e);
218
+ return e;
219
+ }
220
+ /**
221
+ * Lightweight effect for DOM render bindings.
222
+ *
223
+ * Differences from `effect()`:
224
+ * - No EffectScope registration (caller owns the dispose lifecycle)
225
+ * - No error handler (errors propagate naturally)
226
+ * - No onUpdate notification
227
+ * - Deps stored in a local array instead of the global WeakMap — faster
228
+ * creation and disposal (~200ns saved per effect vs WeakMap path)
229
+ *
230
+ * Returns a dispose function (not an Effect object — saves 1 allocation).
231
+ */
232
+ /**
233
+ * Static-dep binding — compiler helper for template expressions.
234
+ *
235
+ * Like renderEffect but assumes dependencies never change (true for all
236
+ * compiler-emitted template bindings like `_tpl()` text/attribute updates).
237
+ *
238
+ * Tracks dependencies only on the first run. Re-runs skip cleanup, re-tracking,
239
+ * and tracking context save/restore entirely — just calls `fn()` directly.
240
+ *
241
+ * Per re-run savings vs renderEffect:
242
+ * - No deps iteration + Set.delete (cleanup)
243
+ * - No setDepsCollector + withTracking (re-registration)
244
+ * - Signal reads hit `if (activeEffect)` null check → instant return
245
+ */
246
+ function _bind(fn) {
247
+ const deps = [];
248
+ let disposed = false;
249
+ const run = () => {
250
+ if (disposed) return;
251
+ fn();
252
+ };
253
+ setDepsCollector(deps);
254
+ withTracking(run, fn);
255
+ setDepsCollector(null);
256
+ const dispose = () => {
257
+ if (disposed) return;
258
+ disposed = true;
259
+ for (const s of deps) s.delete(run);
260
+ deps.length = 0;
261
+ };
262
+ getCurrentScope()?.add({
263
+ dispose
264
+ });
265
+ return dispose;
266
+ }
267
+ function renderEffect(fn) {
268
+ const deps = [];
269
+ let disposed = false;
270
+ const run = () => {
271
+ if (disposed) return;
272
+ for (const s of deps) s.delete(run);
273
+ deps.length = 0;
274
+ setDepsCollector(deps);
275
+ withTracking(run, fn);
276
+ setDepsCollector(null);
277
+ };
278
+ run();
279
+ const dispose = () => {
280
+ if (disposed) return;
281
+ disposed = true;
282
+ for (const s of deps) s.delete(run);
283
+ deps.length = 0;
284
+ };
285
+ getCurrentScope()?.add({
286
+ dispose
287
+ });
288
+ return dispose;
289
+ }
290
+
291
+ //#endregion
292
+ //#region src/createSelector.ts
293
+ /**
294
+ * Create an equality selector — returns a reactive predicate that is true
295
+ * only for the currently selected value.
296
+ *
297
+ * Unlike a plain `() => source() === value`, this only triggers the TWO
298
+ * affected subscribers (deselected + newly selected) instead of ALL
299
+ * subscribers, making selection O(1) regardless of list size.
300
+ *
301
+ * @example
302
+ * const isSelected = createSelector(selectedId)
303
+ * // In each row:
304
+ * class: () => (isSelected(row.id) ? "selected" : "")
305
+ */
306
+ function createSelector(source) {
307
+ const subs = /* @__PURE__ */new Map();
308
+ let current;
309
+ let initialized = false;
310
+ effect(() => {
311
+ const next = source();
312
+ if (!initialized) {
313
+ initialized = true;
314
+ current = next;
315
+ return;
316
+ }
317
+ if (Object.is(next, current)) return;
318
+ const old = current;
319
+ current = next;
320
+ const oldBucket = subs.get(old);
321
+ const newBucket = subs.get(next);
322
+ if (oldBucket) for (const fn of [...oldBucket]) fn();
323
+ if (newBucket) for (const fn of [...newBucket]) fn();
324
+ });
325
+ const hosts = /* @__PURE__ */new Map();
326
+ return value => {
327
+ let host = hosts.get(value);
328
+ if (!host) {
329
+ let bucket = subs.get(value);
330
+ if (!bucket) {
331
+ bucket = /* @__PURE__ */new Set();
332
+ subs.set(value, bucket);
333
+ }
334
+ host = {
335
+ _s: bucket
336
+ };
337
+ hosts.set(value, host);
338
+ }
339
+ trackSubscriber(host);
340
+ return Object.is(current, value);
341
+ };
342
+ }
343
+
344
+ //#endregion
345
+ //#region src/debug.ts
346
+
347
+ /**
348
+ * Register a listener that fires on every signal write.
349
+ * Returns a dispose function.
350
+ *
351
+ * @example
352
+ * const dispose = onSignalUpdate(e => {
353
+ * console.log(`${e.name ?? 'anonymous'}: ${e.prev} → ${e.next}`)
354
+ * })
355
+ */
356
+ function onSignalUpdate(listener) {
357
+ if (!_traceListeners) _traceListeners = [];
358
+ _traceListeners.push(listener);
359
+ return () => {
360
+ if (!_traceListeners) return;
361
+ _traceListeners = _traceListeners.filter(l => l !== listener);
362
+ if (_traceListeners.length === 0) _traceListeners = null;
363
+ };
364
+ }
365
+ /** @internal — called from signal.set() when tracing is active */
366
+ function _notifyTraceListeners(sig, prev, next) {
367
+ if (!_traceListeners) return;
368
+ const event = {
369
+ signal: sig,
370
+ name: sig.label,
371
+ prev,
372
+ next,
373
+ stack: (/* @__PURE__ */new Error()).stack ?? "",
374
+ timestamp: performance.now()
375
+ };
376
+ for (const l of _traceListeners) l(event);
377
+ }
378
+ /** Check if any trace listeners are active (fast path for signal.set) */
379
+ function isTracing() {
380
+ return _traceListeners !== null;
381
+ }
382
+ /**
383
+ * Trace the next signal update. Logs which signals fire and what changed.
384
+ * Call before triggering a state change to see what updates and why.
385
+ *
386
+ * @example
387
+ * why()
388
+ * count.set(5)
389
+ * // Console: [pyreon:why] "count": 3 → 5 (2 subscribers)
390
+ */
391
+ function why() {
392
+ if (_whyActive) return;
393
+ _whyActive = true;
394
+ _whyLog = [];
395
+ const dispose = onSignalUpdate(e => {
396
+ const _subCount = e.signal._s?.size ?? 0;
397
+ const _name = e.name ? `"${e.name}"` : "(anonymous signal)";
398
+ console.log(`[pyreon:why] ${_name}: ${JSON.stringify(e.prev)} → ${JSON.stringify(e.next)} (${_subCount} subscriber${_subCount === 1 ? "" : "s"})`);
399
+ _whyLog.push({
400
+ name: e.name,
401
+ prev: e.prev,
402
+ next: e.next
403
+ });
404
+ });
405
+ queueMicrotask(() => {
406
+ dispose();
407
+ if (_whyLog.length === 0) console.log("[pyreon:why] No signal updates detected");
408
+ _whyActive = false;
409
+ _whyLog = [];
410
+ });
411
+ }
412
+ /**
413
+ * Print a signal's current state to the console in a readable format.
414
+ *
415
+ * @example
416
+ * const count = signal(42, { name: "count" })
417
+ * inspectSignal(count)
418
+ * // Console:
419
+ * // 🔍 Signal "count"
420
+ * // value: 42
421
+ * // subscribers: 3
422
+ */
423
+ function inspectSignal(sig) {
424
+ const info = sig.debug();
425
+ console.group(`🔍 Signal ${info.name ? `"${info.name}"` : "(anonymous)"}`);
426
+ console.log("value:", info.value);
427
+ console.log("subscribers:", info.subscriberCount);
428
+ console.groupEnd();
429
+ return info;
430
+ }
431
+
432
+ //#endregion
433
+ //#region src/signal.ts
434
+ function _peek() {
435
+ return this._v;
436
+ }
437
+ function _set(newValue) {
438
+ if (Object.is(this._v, newValue)) return;
439
+ const prev = this._v;
440
+ this._v = newValue;
441
+ if (isTracing()) _notifyTraceListeners(this, prev, newValue);
442
+ if (this._s) notifySubscribers(this._s);
443
+ }
444
+ function _update(fn) {
445
+ _set.call(this, fn(this._v));
446
+ }
447
+ function _subscribe(listener) {
448
+ if (!this._s) this._s = /* @__PURE__ */new Set();
449
+ this._s.add(listener);
450
+ return () => this._s?.delete(listener);
451
+ }
452
+ function _debug() {
453
+ return {
454
+ name: this._n,
455
+ value: this._v,
456
+ subscriberCount: this._s?.size ?? 0
457
+ };
458
+ }
459
+ /**
460
+ * Create a reactive signal.
461
+ *
462
+ * Only 1 closure is allocated (the read function). State is stored as
463
+ * properties on the function object (_v, _s) and methods (peek, set,
464
+ * update, subscribe) are shared across all signals — not per-signal closures.
465
+ */
466
+ function signal(initialValue, options) {
467
+ const read = () => {
468
+ trackSubscriber(read);
469
+ return read._v;
470
+ };
471
+ read._v = initialValue;
472
+ read._s = null;
473
+ read._n = options?.name;
474
+ read.peek = _peek;
475
+ read.set = _set;
476
+ read.update = _update;
477
+ read.subscribe = _subscribe;
478
+ read.debug = _debug;
479
+ Object.defineProperty(read, "label", _labelDescriptor);
480
+ return read;
481
+ }
482
+
483
+ //#endregion
484
+ //#region src/store.ts
485
+ /**
486
+ * createStore — deep reactive Proxy store.
487
+ *
488
+ * Wraps a plain object/array in a Proxy that creates a fine-grained signal for
489
+ * every property. Direct mutations (`store.count++`, `store.items[0].label = "x"`)
490
+ * trigger only the signals for the mutated properties — not the whole tree.
491
+ *
492
+ * @example
493
+ * const state = createStore({ count: 0, items: [{ id: 1, text: "hello" }] })
494
+ *
495
+ * effect(() => console.log(state.count)) // tracks state.count only
496
+ * state.count++ // only the count effect re-runs
497
+ * state.items[0].text = "world" // only text-tracking effects re-run
498
+ */
499
+
500
+ /** Returns true if the value is a createStore proxy. */
501
+ function isStore(value) {
502
+ return value !== null && typeof value === "object" && value[IS_STORE] === true;
503
+ }
504
+ /**
505
+ * Create a deep reactive store from a plain object or array.
506
+ * Returns a proxy — mutations to the proxy trigger fine-grained reactive updates.
507
+ */
508
+ function createStore(initial) {
509
+ return wrap(initial);
510
+ }
511
+ function wrap(raw) {
512
+ const cached = proxyCache.get(raw);
513
+ if (cached) return cached;
514
+ const propSignals = /* @__PURE__ */new Map();
515
+ const isArray = Array.isArray(raw);
516
+ const lengthSig = isArray ? signal(raw.length) : null;
517
+ function getOrCreateSignal(key) {
518
+ if (!propSignals.has(key)) propSignals.set(key, signal(raw[key]));
519
+ return propSignals.get(key);
520
+ }
521
+ const proxy = new Proxy(raw, {
522
+ get(target, key) {
523
+ if (key === IS_STORE) return true;
524
+ if (typeof key === "symbol") return target[key];
525
+ if (isArray && key === "length") return lengthSig?.();
526
+ if (!Object.hasOwn(target, key)) return target[key];
527
+ const value = getOrCreateSignal(key)();
528
+ if (value !== null && typeof value === "object") return wrap(value);
529
+ return value;
530
+ },
531
+ set(target, key, value) {
532
+ if (typeof key === "symbol") {
533
+ target[key] = value;
534
+ return true;
535
+ }
536
+ const prevLength = isArray ? target.length : 0;
537
+ target[key] = value;
538
+ if (isArray && key === "length") {
539
+ lengthSig?.set(value);
540
+ return true;
541
+ }
542
+ if (propSignals.has(key)) propSignals.get(key)?.set(value);else propSignals.set(key, signal(value));
543
+ if (isArray && target.length !== prevLength) lengthSig?.set(target.length);
544
+ return true;
545
+ },
546
+ deleteProperty(target, key) {
547
+ delete target[key];
548
+ if (typeof key !== "symbol" && propSignals.has(key)) {
549
+ propSignals.get(key)?.set(void 0);
550
+ propSignals.delete(key);
551
+ }
552
+ if (isArray) lengthSig?.set(target.length);
553
+ return true;
554
+ },
555
+ has(target, key) {
556
+ return Reflect.has(target, key);
557
+ },
558
+ ownKeys(target) {
559
+ return Reflect.ownKeys(target);
560
+ },
561
+ getOwnPropertyDescriptor(target, key) {
562
+ return Reflect.getOwnPropertyDescriptor(target, key);
563
+ }
564
+ });
565
+ proxyCache.set(raw, proxy);
566
+ return proxy;
567
+ }
568
+
569
+ //#endregion
570
+ //#region src/reconcile.ts
571
+ /**
572
+ * reconcile — surgically diff new state into an existing createStore proxy.
573
+ *
574
+ * Instead of replacing the store root (which would trigger all downstream effects),
575
+ * reconcile walks both the new value and the store in parallel and only calls
576
+ * `.set()` on signals whose value actually changed.
577
+ *
578
+ * Ideal for applying API responses to a long-lived store:
579
+ *
580
+ * @example
581
+ * const state = createStore({ user: { name: "Alice", age: 30 }, items: [] })
582
+ *
583
+ * // API response arrives:
584
+ * reconcile({ user: { name: "Alice", age: 31 }, items: [{ id: 1 }] }, state)
585
+ * // → only state.user.age signal fires (name unchanged)
586
+ * // → state.items[0] is newly created
587
+ *
588
+ * Arrays are reconciled by index — elements at the same index are recursively
589
+ * diffed rather than replaced wholesale. Excess old elements are removed.
590
+ */
591
+ function reconcile(source, target) {
592
+ _reconcileInner(source, target, /* @__PURE__ */new WeakSet());
593
+ }
594
+ function _reconcileInner(source, target, seen) {
595
+ if (seen.has(source)) return;
596
+ seen.add(source);
597
+ if (Array.isArray(source) && Array.isArray(target)) _reconcileArray(source, target, seen);else _reconcileObject(source, target, seen);
598
+ }
599
+ function _reconcileArray(source, target, seen) {
600
+ const targetLen = target.length;
601
+ const sourceLen = source.length;
602
+ for (let i = 0; i < sourceLen; i++) {
603
+ const sv = source[i];
604
+ const tv = target[i];
605
+ if (i < targetLen && sv !== null && typeof sv === "object" && tv !== null && typeof tv === "object") _reconcileInner(sv, tv, seen);else target[i] = sv;
606
+ }
607
+ if (targetLen > sourceLen) target.length = sourceLen;
608
+ }
609
+ function _reconcileObject(source, target, seen) {
610
+ const sourceKeys = Object.keys(source);
611
+ const targetKeys = new Set(Object.keys(target));
612
+ for (const key of sourceKeys) {
613
+ const sv = source[key];
614
+ const tv = target[key];
615
+ if (sv !== null && typeof sv === "object" && tv !== null && typeof tv === "object") {
616
+ if (isStore(tv)) _reconcileInner(sv, tv, seen);else target[key] = sv;
617
+ } else target[key] = sv;
618
+ targetKeys.delete(key);
619
+ }
620
+ for (const key of targetKeys) delete target[key];
621
+ }
622
+
623
+ //#endregion
624
+ //#region src/resource.ts
625
+ /**
626
+ * Async data primitive. Fetches data reactively whenever `source()` changes.
627
+ *
628
+ * @example
629
+ * const userId = signal(1)
630
+ * const user = createResource(userId, (id) => fetchUser(id))
631
+ * // user.data() — the fetched user (undefined while loading)
632
+ * // user.loading() — true while in flight
633
+ * // user.error() — last error
634
+ */
635
+ function createResource(source, fetcher) {
636
+ const data = signal(void 0);
637
+ const loading = signal(false);
638
+ const error = signal(void 0);
639
+ let requestId = 0;
640
+ const doFetch = param => {
641
+ const id = ++requestId;
642
+ loading.set(true);
643
+ error.set(void 0);
644
+ fetcher(param).then(result => {
645
+ if (id !== requestId) return;
646
+ data.set(result);
647
+ loading.set(false);
648
+ }).catch(err => {
649
+ if (id !== requestId) return;
650
+ error.set(err);
651
+ loading.set(false);
652
+ });
653
+ };
654
+ effect(() => {
655
+ const param = source();
656
+ runUntracked(() => doFetch(param));
657
+ });
658
+ return {
659
+ data,
660
+ loading,
661
+ error,
662
+ refetch() {
663
+ runUntracked(() => doFetch(source()));
664
+ }
665
+ };
666
+ }
667
+
668
+ //#endregion
669
+ //#region src/watch.ts
670
+ /**
671
+ * Watch a reactive source and run a callback whenever it changes.
672
+ *
673
+ * Returns a stop function that disposes the watcher.
674
+ *
675
+ * The callback receives (newValue, oldValue). On the first call (when
676
+ * `immediate` is true) oldValue is `undefined`.
677
+ *
678
+ * The callback may return a cleanup function that is called before each
679
+ * re-run and on stop — useful for cancelling async work.
680
+ *
681
+ * @example
682
+ * const stop = watch(
683
+ * () => userId(),
684
+ * async (id, prev) => {
685
+ * const data = await fetch(`/api/user/${id}`)
686
+ * setUser(await data.json())
687
+ * },
688
+ * )
689
+ * // Later: stop()
690
+ */
691
+ function watch(source, callback, opts = {}) {
692
+ let oldVal;
693
+ let isFirst = true;
694
+ let cleanupFn;
695
+ const e = effect(() => {
696
+ const newVal = source();
697
+ if (isFirst) {
698
+ isFirst = false;
699
+ oldVal = newVal;
700
+ if (opts.immediate) {
701
+ const result = callback(newVal, void 0);
702
+ if (typeof result === "function") cleanupFn = result;
703
+ }
704
+ return;
705
+ }
706
+ if (cleanupFn) {
707
+ cleanupFn();
708
+ cleanupFn = void 0;
709
+ }
710
+ const result = callback(newVal, oldVal);
711
+ if (typeof result === "function") cleanupFn = result;
712
+ oldVal = newVal;
713
+ });
714
+ return () => {
715
+ e.dispose();
716
+ if (cleanupFn) {
717
+ cleanupFn();
718
+ cleanupFn = void 0;
719
+ }
720
+ };
721
+ }
722
+
723
+ //#endregion
724
+ export { Cell, EffectScope, _bind, batch, cell, computed, createResource, createSelector, createStore, effect, effectScope, getCurrentScope, inspectSignal, isStore, nextTick, onSignalUpdate, reconcile, renderEffect, runUntracked, setCurrentScope, setErrorHandler, signal, watch, why };
725
+ //# sourceMappingURL=index.d.ts.map