sibujs 2.0.0 → 2.2.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.
Files changed (62) hide show
  1. package/dist/browser.cjs +369 -276
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +411 -300
  4. package/dist/build.js +10 -10
  5. package/dist/cdn.global.js +8 -8
  6. package/dist/{chunk-JA6667UN.js → chunk-2JQUV4Y3.js} +4 -4
  7. package/dist/{chunk-3NSGB5JN.js → chunk-2KM2724A.js} +2 -2
  8. package/dist/{chunk-52YJLLRO.js → chunk-4YTVESDX.js} +1 -1
  9. package/dist/chunk-5WD7BYTZ.js +152 -0
  10. package/dist/{chunk-CC65Y57T.js → chunk-6QZO7MMG.js} +48 -16
  11. package/dist/{chunk-54EDRCEF.js → chunk-DF3GTP4Q.js} +7 -2
  12. package/dist/{chunk-ND2664SF.js → chunk-J63GPPCJ.js} +13 -9
  13. package/dist/{chunk-O2MNQFLP.js → chunk-KH4OE6WY.js} +5 -5
  14. package/dist/{chunk-3LR7GLWQ.js → chunk-KZA7ANXP.js} +3 -3
  15. package/dist/chunk-L4DAT4WU.js +400 -0
  16. package/dist/{chunk-WOMYAHHI.js → chunk-L52H775O.js} +4 -4
  17. package/dist/{chunk-ITX6OO3F.js → chunk-NEWH4O5U.js} +1 -1
  18. package/dist/{chunk-7JDB7I65.js → chunk-RJIRT46U.js} +4 -4
  19. package/dist/{chunk-KLRMB5ZS.js → chunk-STFTTMO2.js} +2 -2
  20. package/dist/{chunk-DFPFITST.js → chunk-UKMXT5T6.js} +1 -1
  21. package/dist/{chunk-SAHNHTFC.js → chunk-V65KTDZW.js} +3 -3
  22. package/dist/{chunk-R73P76YZ.js → chunk-VSNLICTS.js} +1 -1
  23. package/dist/{chunk-MIUAXB7K.js → chunk-XDKP4T7G.js} +2 -2
  24. package/dist/{chunk-JXMMDLBY.js → chunk-XVYB3J6C.js} +27 -29
  25. package/dist/{chunk-GTBNNBJ6.js → chunk-YMOIAHWA.js} +1 -1
  26. package/dist/data.cjs +382 -274
  27. package/dist/data.js +6 -6
  28. package/dist/devtools.cjs +398 -284
  29. package/dist/devtools.d.cts +1 -1
  30. package/dist/devtools.d.ts +1 -1
  31. package/dist/devtools.js +4 -4
  32. package/dist/ecosystem.cjs +382 -274
  33. package/dist/ecosystem.js +7 -7
  34. package/dist/extras.cjs +421 -299
  35. package/dist/extras.d.cts +1 -1
  36. package/dist/extras.d.ts +1 -1
  37. package/dist/extras.js +19 -19
  38. package/dist/index.cjs +413 -300
  39. package/dist/index.d.cts +16 -11
  40. package/dist/index.d.ts +16 -11
  41. package/dist/index.js +14 -10
  42. package/dist/{introspect-cY2pg9pW.d.ts → introspect-BZWKvQUZ.d.ts} +2 -1
  43. package/dist/{introspect-BWNjNw64.d.cts → introspect-DsJlDD2T.d.cts} +2 -1
  44. package/dist/motion.cjs +189 -149
  45. package/dist/motion.js +3 -3
  46. package/dist/patterns.cjs +382 -274
  47. package/dist/patterns.js +5 -5
  48. package/dist/performance.cjs +360 -260
  49. package/dist/performance.js +4 -4
  50. package/dist/plugins.cjs +376 -257
  51. package/dist/plugins.js +6 -6
  52. package/dist/ssr.cjs +383 -271
  53. package/dist/ssr.js +7 -7
  54. package/dist/testing.cjs +168 -109
  55. package/dist/testing.js +2 -2
  56. package/dist/ui.cjs +373 -258
  57. package/dist/ui.js +6 -6
  58. package/dist/widgets.cjs +382 -274
  59. package/dist/widgets.js +6 -6
  60. package/package.json +1 -1
  61. package/dist/chunk-HB24TBAF.js +0 -121
  62. package/dist/chunk-VLPPXTYG.js +0 -332
package/dist/extras.cjs CHANGED
@@ -145,7 +145,7 @@ __export(extras_exports, {
145
145
  getPerformanceReport: () => getPerformanceReport,
146
146
  getQueryData: () => getQueryData,
147
147
  getSignalName: () => getSignalName,
148
- getSubscriberCount: () => getSubscriberCount,
148
+ getSubscriberCount: () => getSubscriberCount2,
149
149
  globalStore: () => globalStore,
150
150
  healthCheck: () => healthCheck,
151
151
  hmrState: () => hmrState,
@@ -332,11 +332,88 @@ function devWarn(message) {
332
332
 
333
333
  // src/reactivity/track.ts
334
334
  var _isDev2 = isDev();
335
- var subscriberStack = new Array(32);
336
- var stackCapacity = 32;
337
- var stackTop = -1;
335
+ var POOL_MAX = 4096;
336
+ var nodePool = [];
337
+ function createNode() {
338
+ return {
339
+ sig: null,
340
+ sub: null,
341
+ epoch: 0,
342
+ sigPrev: null,
343
+ sigNext: null,
344
+ subPrev: null,
345
+ subNext: null,
346
+ prevActive: null
347
+ };
348
+ }
349
+ function allocNode(sig, sub2, epoch) {
350
+ const n = nodePool.pop();
351
+ if (n) {
352
+ n.sig = sig;
353
+ n.sub = sub2;
354
+ n.epoch = epoch;
355
+ return n;
356
+ }
357
+ const fresh = createNode();
358
+ fresh.sig = sig;
359
+ fresh.sub = sub2;
360
+ fresh.epoch = epoch;
361
+ return fresh;
362
+ }
363
+ function freeNode(node) {
364
+ node.sig = null;
365
+ node.sub = null;
366
+ node.sigPrev = null;
367
+ node.sigNext = null;
368
+ node.subPrev = null;
369
+ node.subNext = null;
370
+ node.prevActive = null;
371
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
372
+ }
373
+ function linkSignal(sig, node) {
374
+ const oldHead = sig.subsHead ?? null;
375
+ node.sigPrev = null;
376
+ node.sigNext = oldHead;
377
+ if (oldHead) oldHead.sigPrev = node;
378
+ else sig.subsTail = node;
379
+ sig.subsHead = node;
380
+ sig.__sc = (sig.__sc ?? 0) + 1;
381
+ }
382
+ function unlinkSignal(node) {
383
+ const sig = node.sig;
384
+ if (!sig) return;
385
+ const prev = node.sigPrev;
386
+ const next = node.sigNext;
387
+ if (prev) prev.sigNext = next;
388
+ else sig.subsHead = next;
389
+ if (next) next.sigPrev = prev;
390
+ else sig.subsTail = prev;
391
+ sig.__sc = (sig.__sc ?? 1) - 1;
392
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
393
+ if (sig.__sc === 0) {
394
+ sig.subsHead = null;
395
+ sig.subsTail = null;
396
+ }
397
+ }
398
+ function linkSub(sub2, node) {
399
+ const oldTail = sub2.depsTail ?? null;
400
+ node.subPrev = oldTail;
401
+ node.subNext = null;
402
+ if (oldTail) oldTail.subNext = node;
403
+ else sub2.depsHead = node;
404
+ sub2.depsTail = node;
405
+ }
406
+ function unlinkSub(node) {
407
+ const sub2 = node.sub;
408
+ if (!sub2) return;
409
+ const prev = node.subPrev;
410
+ const next = node.subNext;
411
+ if (prev) prev.subNext = next;
412
+ else sub2.depsHead = next;
413
+ if (next) next.subPrev = prev;
414
+ else sub2.depsTail = prev;
415
+ }
338
416
  var currentSubscriber = null;
339
- var SUBS = "__s";
340
417
  var notifyDepth = 0;
341
418
  var pendingQueue = [];
342
419
  var pendingSet = /* @__PURE__ */ new Set();
@@ -349,92 +426,136 @@ function safeInvoke(sub2) {
349
426
  }
350
427
  }
351
428
  var trackingSuspended = false;
429
+ var subscriberEpochCounter = 0;
352
430
  function retrack(effectFn, subscriber) {
353
431
  const prev = currentSubscriber;
354
432
  currentSubscriber = subscriber;
433
+ const sub2 = subscriber;
434
+ const epoch = ++subscriberEpochCounter;
435
+ sub2._epoch = epoch;
436
+ sub2._structDirty = false;
437
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
438
+ const sig = n.sig;
439
+ n.prevActive = sig.__activeNode ?? null;
440
+ sig.__activeNode = n;
441
+ }
355
442
  try {
356
443
  effectFn();
357
444
  } finally {
358
445
  currentSubscriber = prev;
446
+ let node = sub2.depsHead ?? null;
447
+ while (node !== null) {
448
+ const next = node.subNext;
449
+ const sig = node.sig;
450
+ sig.__activeNode = node.prevActive;
451
+ node.prevActive = null;
452
+ if (node.epoch !== epoch) {
453
+ unlinkSub(node);
454
+ unlinkSignal(node);
455
+ freeNode(node);
456
+ }
457
+ node = next;
458
+ }
359
459
  }
360
460
  }
361
461
  function track(effectFn, subscriber) {
362
462
  if (!subscriber) subscriber = effectFn;
363
463
  cleanup(subscriber);
364
- ++stackTop;
365
- if (stackTop >= stackCapacity) {
366
- stackCapacity *= 2;
367
- subscriberStack.length = stackCapacity;
368
- }
369
- subscriberStack[stackTop] = subscriber;
464
+ const prev = currentSubscriber;
370
465
  currentSubscriber = subscriber;
371
466
  try {
372
467
  effectFn();
373
468
  } finally {
374
- stackTop--;
375
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
469
+ currentSubscriber = prev;
470
+ const sub3 = subscriber;
471
+ for (let n = sub3.depsHead ?? null; n !== null; n = n.subNext) {
472
+ const sig = n.sig;
473
+ sig.__activeNode = n.prevActive;
474
+ n.prevActive = null;
475
+ }
376
476
  }
377
- return () => cleanup(subscriber);
477
+ const sub2 = subscriber;
478
+ return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
378
479
  }
379
480
  function recordDependency(signal2) {
380
481
  if (!currentSubscriber) return;
381
482
  const sub2 = currentSubscriber;
382
- if (sub2._dep === signal2) return;
383
- const deps = sub2._deps;
384
- if (deps) {
385
- if (deps.has(signal2)) return;
386
- deps.add(signal2);
387
- } else if (sub2._dep !== void 0) {
388
- const set = /* @__PURE__ */ new Set();
389
- set.add(sub2._dep);
390
- set.add(signal2);
391
- sub2._deps = set;
392
- sub2._dep = void 0;
393
- } else {
394
- sub2._dep = signal2;
483
+ const sig = signal2;
484
+ const epoch = sub2._epoch ?? 0;
485
+ const active = sig.__activeNode ?? null;
486
+ if (active !== null && active.sub === sub2) {
487
+ active.epoch = epoch;
488
+ return;
395
489
  }
396
- let subs = signal2[SUBS];
397
- if (!subs) {
398
- subs = /* @__PURE__ */ new Set();
399
- signal2[SUBS] = subs;
490
+ const node = allocNode(signal2, sub2, epoch);
491
+ node.prevActive = active;
492
+ sig.__activeNode = node;
493
+ linkSub(sub2, node);
494
+ linkSignal(sig, node);
495
+ sub2._structDirty = true;
496
+ }
497
+ function cleanup(subscriber) {
498
+ const sub2 = subscriber;
499
+ let node = sub2.depsHead ?? null;
500
+ sub2.depsHead = null;
501
+ sub2.depsTail = null;
502
+ while (node) {
503
+ const next = node.subNext;
504
+ unlinkSignal(node);
505
+ freeNode(node);
506
+ node = next;
507
+ }
508
+ }
509
+ var maxSubscriberRepeats = 50;
510
+ var maxDrainIterations = 1e6;
511
+ var drainEpoch = 0;
512
+ function tickRepeat(sub2) {
513
+ const s2 = sub2;
514
+ if (s2._runEpoch !== drainEpoch) {
515
+ s2._runEpoch = drainEpoch;
516
+ s2._runs = 1;
517
+ return false;
400
518
  }
401
- subs.add(currentSubscriber);
402
- if (subs.size === 1) {
403
- signal2.__f = currentSubscriber;
404
- } else if (signal2.__f !== void 0) {
405
- signal2.__f = void 0;
519
+ s2._runs = (s2._runs ?? 0) + 1;
520
+ return s2._runs > maxSubscriberRepeats;
521
+ }
522
+ function cycleError(sub2) {
523
+ if (typeof console !== "undefined") {
524
+ const name = sub2.__name ?? "<unnamed>";
525
+ console.error(
526
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
527
+ );
406
528
  }
407
529
  }
408
- function queueSignalNotification(signal2) {
409
- const subs = signal2[SUBS];
410
- if (!subs) return;
411
- for (const sub2 of subs) {
412
- if (sub2._c) {
413
- propagateDirty(sub2);
414
- } else if (!pendingSet.has(sub2)) {
415
- pendingSet.add(sub2);
416
- pendingQueue.push(sub2);
530
+ function absoluteDrainError() {
531
+ if (typeof console !== "undefined") {
532
+ console.error(
533
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
534
+ );
535
+ }
536
+ }
537
+ function drainQueue() {
538
+ let i2 = 0;
539
+ while (i2 < pendingQueue.length) {
540
+ if (i2 >= maxDrainIterations) {
541
+ absoluteDrainError();
542
+ break;
543
+ }
544
+ const sub2 = pendingQueue[i2++];
545
+ if (tickRepeat(sub2)) {
546
+ cycleError(sub2);
547
+ break;
417
548
  }
549
+ pendingSet.delete(sub2);
550
+ safeInvoke(sub2);
418
551
  }
419
552
  }
420
- var maxDrainIterations = 1e5;
421
553
  function drainNotificationQueue() {
422
554
  if (notifyDepth > 0) return;
423
555
  notifyDepth++;
556
+ drainEpoch++;
424
557
  try {
425
- let i2 = 0;
426
- while (i2 < pendingQueue.length) {
427
- if (i2 >= maxDrainIterations) {
428
- if (typeof console !== "undefined") {
429
- console.error(
430
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
431
- );
432
- }
433
- break;
434
- }
435
- safeInvoke(pendingQueue[i2]);
436
- i2++;
437
- }
558
+ drainQueue();
438
559
  } finally {
439
560
  notifyDepth--;
440
561
  if (notifyDepth === 0) {
@@ -452,131 +573,82 @@ function propagateDirty(sub2) {
452
573
  stack.push(rootSig);
453
574
  while (stack.length > baseLen) {
454
575
  const sig = stack.pop();
455
- const first = sig.__f;
456
- if (first) {
457
- if (first._c) {
458
- const nSig = first._sig;
459
- if (!nSig._d) {
460
- nSig._d = true;
461
- stack.push(nSig);
576
+ let node = sig.subsHead ?? null;
577
+ while (node) {
578
+ const s2 = node.sub;
579
+ if (s2) {
580
+ if (s2._c) {
581
+ const nSig = s2._sig;
582
+ if (nSig) {
583
+ if (!nSig._d) {
584
+ nSig._d = true;
585
+ stack.push(nSig);
586
+ }
587
+ } else {
588
+ s2();
589
+ }
590
+ } else if (!pendingSet.has(s2)) {
591
+ pendingSet.add(s2);
592
+ pendingQueue.push(s2);
462
593
  }
463
- } else if (!pendingSet.has(first)) {
464
- pendingSet.add(first);
465
- pendingQueue.push(first);
466
594
  }
467
- continue;
595
+ node = node.sigNext;
468
596
  }
469
- const subs = sig[SUBS];
470
- if (!subs) continue;
471
- for (const s2 of subs) {
597
+ }
598
+ }
599
+ function queueSignalNotification(signal2) {
600
+ const sig = signal2;
601
+ let node = sig.subsHead ?? null;
602
+ while (node) {
603
+ const s2 = node.sub;
604
+ if (s2) {
472
605
  if (s2._c) {
473
- const nSig = s2._sig;
474
- if (nSig && !nSig._d) {
475
- nSig._d = true;
476
- stack.push(nSig);
477
- } else if (!nSig) {
478
- s2();
479
- }
606
+ propagateDirty(s2);
480
607
  } else if (!pendingSet.has(s2)) {
481
608
  pendingSet.add(s2);
482
609
  pendingQueue.push(s2);
483
610
  }
484
611
  }
612
+ node = node.sigNext;
485
613
  }
486
614
  }
487
615
  function notifySubscribers(signal2) {
488
- const first = signal2.__f;
489
- if (first) {
490
- if (notifyDepth > 0) {
491
- if (first._c) {
492
- propagateDirty(first);
493
- } else if (!pendingSet.has(first)) {
494
- pendingSet.add(first);
495
- pendingQueue.push(first);
496
- }
497
- return;
498
- }
499
- notifyDepth++;
500
- try {
501
- if (first._c) {
502
- propagateDirty(first);
503
- } else {
504
- safeInvoke(first);
505
- }
506
- let i2 = 0;
507
- while (i2 < pendingQueue.length) {
508
- if (i2 >= maxDrainIterations) {
509
- if (typeof console !== "undefined") {
510
- console.error(
511
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
512
- );
513
- }
514
- break;
515
- }
516
- safeInvoke(pendingQueue[i2]);
517
- i2++;
518
- }
519
- } finally {
520
- notifyDepth--;
521
- if (notifyDepth === 0) {
522
- pendingQueue.length = 0;
523
- pendingSet.clear();
524
- }
525
- }
526
- return;
527
- }
528
- const subs = signal2[SUBS];
529
- if (!subs || subs.size === 0) return;
616
+ const sig = signal2;
617
+ const head2 = sig.subsHead;
618
+ if (!head2) return;
530
619
  if (notifyDepth > 0) {
531
- for (const sub2 of subs) {
532
- if (sub2._c) {
533
- propagateDirty(sub2);
534
- } else if (!pendingSet.has(sub2)) {
535
- pendingSet.add(sub2);
536
- pendingQueue.push(sub2);
620
+ let node = head2;
621
+ while (node) {
622
+ const s2 = node.sub;
623
+ if (s2) {
624
+ if (s2._c) {
625
+ propagateDirty(s2);
626
+ } else if (!pendingSet.has(s2)) {
627
+ pendingSet.add(s2);
628
+ pendingQueue.push(s2);
629
+ }
537
630
  }
631
+ node = node.sigNext;
538
632
  }
539
633
  return;
540
634
  }
541
635
  notifyDepth++;
636
+ drainEpoch++;
542
637
  try {
543
- let directCount = 0;
544
- let hasComputedSub = false;
545
- for (const sub2 of subs) {
546
- if (sub2._c) hasComputedSub = true;
547
- pendingQueue[directCount++] = sub2;
548
- }
549
- if (!hasComputedSub) {
550
- for (let i3 = 0; i3 < directCount; i3++) {
551
- safeInvoke(pendingQueue[i3]);
552
- }
553
- } else {
554
- for (let i3 = 0; i3 < directCount; i3++) {
555
- if (pendingQueue[i3]._c) {
556
- propagateDirty(pendingQueue[i3]);
557
- }
558
- }
559
- for (let i3 = 0; i3 < directCount; i3++) {
560
- const sub2 = pendingQueue[i3];
561
- if (!sub2._c && !pendingSet.has(sub2)) {
562
- pendingSet.add(sub2);
563
- safeInvoke(sub2);
564
- }
565
- }
566
- }
567
- let i2 = directCount;
568
- while (i2 < pendingQueue.length) {
569
- if (i2 - directCount >= maxDrainIterations) {
570
- if (typeof console !== "undefined") {
571
- console.error(
572
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
573
- );
638
+ let node = head2;
639
+ while (node) {
640
+ const s2 = node.sub;
641
+ if (s2) {
642
+ if (s2._c) {
643
+ propagateDirty(s2);
644
+ } else if (!pendingSet.has(s2)) {
645
+ pendingSet.add(s2);
646
+ pendingQueue.push(s2);
574
647
  }
575
- break;
576
648
  }
577
- safeInvoke(pendingQueue[i2]);
578
- i2++;
649
+ node = node.sigNext;
579
650
  }
651
+ drainQueue();
580
652
  } finally {
581
653
  notifyDepth--;
582
654
  if (notifyDepth === 0) {
@@ -585,36 +657,26 @@ function notifySubscribers(signal2) {
585
657
  }
586
658
  }
587
659
  }
588
- function cleanup(subscriber) {
660
+ function getSubscriberCount(signal2) {
661
+ return signal2.__sc ?? 0;
662
+ }
663
+ function getSubscriberDeps(subscriber) {
589
664
  const sub2 = subscriber;
590
- const singleDep = sub2._dep;
591
- if (singleDep !== void 0) {
592
- const subs = singleDep[SUBS];
593
- if (subs) {
594
- subs.delete(subscriber);
595
- if (singleDep.__f === subscriber) {
596
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
597
- } else if (subs.size === 1 && singleDep.__f === void 0) {
598
- singleDep.__f = subs.values().next().value;
599
- }
600
- }
601
- sub2._dep = void 0;
602
- return;
665
+ const out = [];
666
+ let node = sub2.depsHead ?? null;
667
+ while (node) {
668
+ if (node.sig) out.push(node.sig);
669
+ node = node.subNext;
603
670
  }
604
- const deps = sub2._deps;
605
- if (!deps || deps.size === 0) return;
606
- for (const signal2 of deps) {
607
- const subs = signal2[SUBS];
608
- if (subs) {
609
- subs.delete(subscriber);
610
- if (signal2.__f === subscriber) {
611
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
612
- } else if (subs.size === 1 && signal2.__f === void 0) {
613
- signal2.__f = subs.values().next().value;
614
- }
615
- }
671
+ return out;
672
+ }
673
+ function forEachSubscriber(signal2, visit) {
674
+ let node = signal2.subsHead ?? null;
675
+ while (node) {
676
+ const s2 = node.sub;
677
+ if (s2) visit(s2);
678
+ node = node.sigNext;
616
679
  }
617
- deps.clear();
618
680
  }
619
681
 
620
682
  // src/core/signals/derived.ts
@@ -625,6 +687,7 @@ function derived(getter, options) {
625
687
  const cs = {};
626
688
  cs._d = false;
627
689
  cs._g = getter;
690
+ cs.__v = 0;
628
691
  const markDirty = () => {
629
692
  if (cs._d) return;
630
693
  cs._d = true;
@@ -654,11 +717,14 @@ function derived(getter, options) {
654
717
  evaluating = true;
655
718
  let threw = true;
656
719
  try {
720
+ const prev = cs._v;
657
721
  retrack(() => {
658
- cs._v = getter();
722
+ const next = getter();
723
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
659
724
  cs._d = false;
660
725
  threw = false;
661
726
  }, markDirty);
727
+ if (!Object.is(prev, cs._v)) cs.__v++;
662
728
  } finally {
663
729
  evaluating = false;
664
730
  if (threw) cs._d = true;
@@ -678,6 +744,7 @@ function derived(getter, options) {
678
744
  cs._d = false;
679
745
  threw = false;
680
746
  }, markDirty);
747
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
681
748
  } finally {
682
749
  evaluating = false;
683
750
  if (threw) cs._d = true;
@@ -724,92 +791,122 @@ function isSSR() {
724
791
 
725
792
  // src/core/signals/effect.ts
726
793
  var _g = globalThis;
794
+ var MAX_RERUNS = 100;
795
+ function flushUserCleanups(ctx) {
796
+ const list = ctx.userCleanups;
797
+ if (list.length === 0) return;
798
+ ctx.userCleanups = [];
799
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
800
+ try {
801
+ list[i2]();
802
+ } catch (err) {
803
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
804
+ }
805
+ }
806
+ }
807
+ function drainReruns(ctx) {
808
+ let reruns = 1;
809
+ do {
810
+ ctx.rerunPending = false;
811
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
812
+ retrack(ctx.bodyFn, ctx.subscriber);
813
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
814
+ if (ctx.rerunPending) {
815
+ ctx.rerunPending = false;
816
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
817
+ console.error(
818
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
819
+ );
820
+ }
821
+ }
822
+ }
823
+ function disposeEffect(ctx) {
824
+ if (ctx.disposed) return;
825
+ ctx.disposed = true;
826
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
827
+ if (h) {
828
+ try {
829
+ h.emit("effect:destroy", { effectFn: ctx.fn });
830
+ } catch {
831
+ }
832
+ }
833
+ try {
834
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
835
+ } catch (err) {
836
+ if (typeof console !== "undefined") {
837
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
838
+ }
839
+ }
840
+ try {
841
+ cleanup(ctx.subscriber);
842
+ } catch (err) {
843
+ if (typeof console !== "undefined") {
844
+ console.warn("[SibuJS effect] dispose threw:", err);
845
+ }
846
+ }
847
+ }
727
848
  function effect(effectFn, options) {
728
849
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
729
850
  if (isSSR()) return () => {
730
851
  };
731
- const onError = options?.onError;
732
- let userCleanups = [];
733
- const onCleanup = (fn) => {
734
- userCleanups.push(fn);
735
- };
736
- const runUserCleanups = () => {
737
- if (userCleanups.length === 0) return;
738
- const list = userCleanups;
739
- userCleanups = [];
740
- for (let i2 = list.length - 1; i2 >= 0; i2--) {
741
- try {
742
- list[i2]();
743
- } catch (err) {
744
- if (typeof console !== "undefined") {
745
- console.warn("[SibuJS effect] onCleanup threw:", err);
746
- }
747
- }
748
- }
749
- };
750
- const invokeBody = () => effectFn(onCleanup);
751
- const wrappedFn = onError ? () => {
852
+ const ctx = {
853
+ fn: effectFn,
854
+ onError: options?.onError,
855
+ userCleanups: [],
856
+ running: false,
857
+ rerunPending: false,
858
+ disposed: false,
859
+ onCleanup: null,
860
+ subscriber: null,
861
+ bodyFn: null
862
+ };
863
+ ctx.onCleanup = (fn) => {
864
+ ctx.userCleanups.push(fn);
865
+ };
866
+ const onErrorCaptured = ctx.onError;
867
+ ctx.bodyFn = onErrorCaptured ? () => {
752
868
  try {
753
- invokeBody();
869
+ ctx.fn(ctx.onCleanup);
754
870
  } catch (err) {
755
- onError(err);
871
+ onErrorCaptured(err);
756
872
  }
757
- } : invokeBody;
758
- let cleanupHandle = () => {
873
+ } : () => {
874
+ ctx.fn(ctx.onCleanup);
759
875
  };
760
- let running = false;
761
- const subscriber = () => {
762
- if (running) {
763
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
764
- console.warn(
765
- "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
766
- );
767
- }
876
+ const sub2 = (() => {
877
+ if (ctx.running) {
878
+ ctx.rerunPending = true;
768
879
  return;
769
880
  }
770
- running = true;
881
+ ctx.running = true;
771
882
  try {
772
- runUserCleanups();
773
- cleanupHandle();
774
- cleanupHandle = track(wrappedFn, subscriber);
883
+ ctx.rerunPending = false;
884
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
885
+ retrack(ctx.bodyFn, sub2);
886
+ if (ctx.rerunPending) drainReruns(ctx);
775
887
  } finally {
776
- running = false;
888
+ ctx.running = false;
889
+ ctx.rerunPending = false;
777
890
  }
778
- };
779
- running = true;
891
+ });
892
+ sub2.depsHead = null;
893
+ sub2.depsTail = null;
894
+ sub2._epoch = 0;
895
+ sub2._structDirty = false;
896
+ sub2._runEpoch = 0;
897
+ sub2._runs = 0;
898
+ ctx.subscriber = sub2;
899
+ ctx.running = true;
780
900
  try {
781
- cleanupHandle = track(wrappedFn, subscriber);
901
+ retrack(ctx.bodyFn, ctx.subscriber);
902
+ if (ctx.rerunPending) drainReruns(ctx);
782
903
  } finally {
783
- running = false;
904
+ ctx.running = false;
905
+ ctx.rerunPending = false;
784
906
  }
785
907
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
786
908
  if (hook) hook.emit("effect:create", { effectFn });
787
- let disposed = false;
788
- return () => {
789
- if (disposed) return;
790
- disposed = true;
791
- const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
792
- if (h) {
793
- try {
794
- h.emit("effect:destroy", { effectFn });
795
- } catch {
796
- }
797
- }
798
- try {
799
- runUserCleanups();
800
- } catch (err) {
801
- if (typeof console !== "undefined") {
802
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
803
- }
804
- }
805
- try {
806
- cleanupHandle();
807
- } catch (err) {
808
- if (typeof console !== "undefined") {
809
- console.warn("[SibuJS effect] dispose threw:", err);
810
- }
811
- }
812
- };
909
+ return () => disposeEffect(ctx);
813
910
  }
814
911
 
815
912
  // src/reactivity/batch.ts
@@ -846,32 +943,64 @@ function flushBatch() {
846
943
  var _g2 = globalThis;
847
944
  var _isDev3 = isDev();
848
945
  function signal(initial, options) {
849
- const state = { value: initial };
946
+ const state = {
947
+ value: initial,
948
+ __v: 0,
949
+ __sc: 0,
950
+ subsHead: null,
951
+ subsTail: null,
952
+ __activeNode: null,
953
+ __name: void 0
954
+ };
850
955
  const debugName = _isDev3 ? options?.name : void 0;
851
956
  const equalsFn = options?.equals;
852
- if (debugName) {
853
- state.__name = debugName;
854
- }
957
+ if (debugName) state.__name = debugName;
855
958
  function get() {
856
959
  recordDependency(state);
857
960
  return state.value;
858
961
  }
859
962
  get.__signal = state;
860
963
  if (debugName) get.__name = debugName;
861
- function set(next) {
862
- const newValue = typeof next === "function" ? next(state.value) : next;
863
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
864
- if (_isDev3) {
865
- const oldValue = state.value;
964
+ let set;
965
+ if (equalsFn) {
966
+ set = (next) => {
967
+ const prev = state.value;
968
+ const newValue = typeof next === "function" ? next(prev) : next;
969
+ if (equalsFn(prev, newValue)) return;
970
+ state.value = newValue;
971
+ state.__v++;
972
+ if (_isDev3) {
973
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
974
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
975
+ }
976
+ if (!enqueueBatchedSignal(state)) {
977
+ notifySubscribers(state);
978
+ }
979
+ };
980
+ } else if (_isDev3) {
981
+ set = (next) => {
982
+ const prev = state.value;
983
+ const newValue = typeof next === "function" ? next(prev) : next;
984
+ if (Object.is(newValue, prev)) return;
866
985
  state.value = newValue;
986
+ state.__v++;
867
987
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
868
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
869
- } else {
988
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
989
+ if (!enqueueBatchedSignal(state)) {
990
+ notifySubscribers(state);
991
+ }
992
+ };
993
+ } else {
994
+ set = (next) => {
995
+ const prev = state.value;
996
+ const newValue = typeof next === "function" ? next(prev) : next;
997
+ if (Object.is(newValue, prev)) return;
870
998
  state.value = newValue;
871
- }
872
- if (!enqueueBatchedSignal(state)) {
873
- notifySubscribers(state);
874
- }
999
+ state.__v++;
1000
+ if (!enqueueBatchedSignal(state)) {
1001
+ notifySubscribers(state);
1002
+ }
1003
+ };
875
1004
  }
876
1005
  if (_isDev3) {
877
1006
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -9178,13 +9307,14 @@ function initDevTools(config) {
9178
9307
  } catch {
9179
9308
  value = "<error>";
9180
9309
  }
9181
- const subs = node.ref?.__s;
9182
9310
  result.push({
9183
9311
  id: node.id,
9184
9312
  name: node.name,
9185
9313
  type: node.type,
9186
9314
  value,
9187
- subscriberCount: subs instanceof Set ? subs.size : 0
9315
+ // __sc is the O(1) subscriber count maintained by the reactivity
9316
+ // core (track.ts) on every linked-list splice.
9317
+ subscriberCount: node.ref?.__sc ?? 0
9188
9318
  });
9189
9319
  }
9190
9320
  return result;
@@ -9325,14 +9455,13 @@ function initDevTools(config) {
9325
9455
  }
9326
9456
  const fullVal = val;
9327
9457
  const shortVal = val.length > 80 ? `${val.substring(0, 80)}...` : val;
9328
- const subs = node.ref?.__s;
9329
9458
  sArr.push({
9330
9459
  id: node.id,
9331
9460
  n: node.name,
9332
9461
  tp: node.type,
9333
9462
  v: shortVal,
9334
9463
  fv: fullVal,
9335
- sc: subs instanceof Set ? subs.size : 0
9464
+ sc: node.ref?.__sc ?? 0
9336
9465
  });
9337
9466
  }
9338
9467
  function walkElement(el, depth) {
@@ -9959,28 +10088,24 @@ function startMeasure(profiler) {
9959
10088
  }
9960
10089
 
9961
10090
  // src/devtools/introspect.ts
9962
- var SUBS2 = "__s";
9963
10091
  function getSignalName(getter) {
9964
10092
  return getter.__name;
9965
10093
  }
9966
- function getSubscriberCount(getter) {
10094
+ function getSubscriberCount2(getter) {
9967
10095
  const signal2 = getter.__signal;
9968
10096
  if (!signal2) return 0;
9969
- const subs = signal2[SUBS2];
9970
- return subs ? subs.size : 0;
10097
+ return getSubscriberCount(signal2);
9971
10098
  }
9972
10099
  function getDependencies(subscriberFn) {
9973
- const deps = subscriberFn._deps;
9974
- return deps ? Array.from(deps) : [];
10100
+ return getSubscriberDeps(subscriberFn);
9975
10101
  }
9976
10102
  function inspectSignal(getter) {
9977
10103
  const signal2 = getter.__signal;
9978
10104
  if (!signal2) return null;
9979
- const subs = signal2[SUBS2];
9980
10105
  return {
9981
10106
  name: getter.__name,
9982
10107
  signal: signal2,
9983
- subscriberCount: subs ? subs.size : 0
10108
+ subscriberCount: getSubscriberCount(signal2)
9984
10109
  };
9985
10110
  }
9986
10111
  function walkDependencyGraph(getter, maxDepth = 10, visited = /* @__PURE__ */ new WeakSet()) {
@@ -9989,24 +10114,21 @@ function walkDependencyGraph(getter, maxDepth = 10, visited = /* @__PURE__ */ ne
9989
10114
  return { name: getSignalName(getter), subscribers: 0, downstream: [] };
9990
10115
  }
9991
10116
  visited.add(signal2);
9992
- const subs = signal2[SUBS2];
9993
10117
  const downstream = [];
9994
- if (subs) {
9995
- for (const sub2 of subs) {
9996
- const subSig = sub2._sig;
9997
- if (subSig && !visited.has(subSig)) {
9998
- const subName = subSig.__name;
9999
- const fakeGetter = (() => void 0);
10000
- const tag = fakeGetter;
10001
- tag.__signal = subSig;
10002
- if (subName !== void 0) tag.__name = subName;
10003
- downstream.push(walkDependencyGraph(fakeGetter, maxDepth - 1, visited));
10004
- }
10118
+ forEachSubscriber(signal2, (sub2) => {
10119
+ const subSig = sub2._sig;
10120
+ if (subSig && !visited.has(subSig)) {
10121
+ const subName = subSig.__name;
10122
+ const fakeGetter = (() => void 0);
10123
+ const tag = fakeGetter;
10124
+ tag.__signal = subSig;
10125
+ if (subName !== void 0) tag.__name = subName;
10126
+ downstream.push(walkDependencyGraph(fakeGetter, maxDepth - 1, visited));
10005
10127
  }
10006
- }
10128
+ });
10007
10129
  return {
10008
10130
  name: getSignalName(getter),
10009
- subscribers: subs ? subs.size : 0,
10131
+ subscribers: getSubscriberCount(signal2),
10010
10132
  downstream
10011
10133
  };
10012
10134
  }