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/devtools.cjs CHANGED
@@ -45,7 +45,7 @@ __export(devtools_exports, {
45
45
  getDependencies: () => getDependencies,
46
46
  getPerformanceReport: () => getPerformanceReport,
47
47
  getSignalName: () => getSignalName,
48
- getSubscriberCount: () => getSubscriberCount,
48
+ getSubscriberCount: () => getSubscriberCount2,
49
49
  hmrState: () => hmrState,
50
50
  initDevTools: () => initDevTools,
51
51
  inspectSignal: () => inspectSignal,
@@ -184,11 +184,88 @@ function devWarn(message) {
184
184
 
185
185
  // src/reactivity/track.ts
186
186
  var _isDev2 = isDev();
187
- var subscriberStack = new Array(32);
188
- var stackCapacity = 32;
189
- var stackTop = -1;
187
+ var POOL_MAX = 4096;
188
+ var nodePool = [];
189
+ function createNode() {
190
+ return {
191
+ sig: null,
192
+ sub: null,
193
+ epoch: 0,
194
+ sigPrev: null,
195
+ sigNext: null,
196
+ subPrev: null,
197
+ subNext: null,
198
+ prevActive: null
199
+ };
200
+ }
201
+ function allocNode(sig, sub, epoch) {
202
+ const n = nodePool.pop();
203
+ if (n) {
204
+ n.sig = sig;
205
+ n.sub = sub;
206
+ n.epoch = epoch;
207
+ return n;
208
+ }
209
+ const fresh = createNode();
210
+ fresh.sig = sig;
211
+ fresh.sub = sub;
212
+ fresh.epoch = epoch;
213
+ return fresh;
214
+ }
215
+ function freeNode(node) {
216
+ node.sig = null;
217
+ node.sub = null;
218
+ node.sigPrev = null;
219
+ node.sigNext = null;
220
+ node.subPrev = null;
221
+ node.subNext = null;
222
+ node.prevActive = null;
223
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
224
+ }
225
+ function linkSignal(sig, node) {
226
+ const oldHead = sig.subsHead ?? null;
227
+ node.sigPrev = null;
228
+ node.sigNext = oldHead;
229
+ if (oldHead) oldHead.sigPrev = node;
230
+ else sig.subsTail = node;
231
+ sig.subsHead = node;
232
+ sig.__sc = (sig.__sc ?? 0) + 1;
233
+ }
234
+ function unlinkSignal(node) {
235
+ const sig = node.sig;
236
+ if (!sig) return;
237
+ const prev = node.sigPrev;
238
+ const next = node.sigNext;
239
+ if (prev) prev.sigNext = next;
240
+ else sig.subsHead = next;
241
+ if (next) next.sigPrev = prev;
242
+ else sig.subsTail = prev;
243
+ sig.__sc = (sig.__sc ?? 1) - 1;
244
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
245
+ if (sig.__sc === 0) {
246
+ sig.subsHead = null;
247
+ sig.subsTail = null;
248
+ }
249
+ }
250
+ function linkSub(sub, node) {
251
+ const oldTail = sub.depsTail ?? null;
252
+ node.subPrev = oldTail;
253
+ node.subNext = null;
254
+ if (oldTail) oldTail.subNext = node;
255
+ else sub.depsHead = node;
256
+ sub.depsTail = node;
257
+ }
258
+ function unlinkSub(node) {
259
+ const sub = node.sub;
260
+ if (!sub) return;
261
+ const prev = node.subPrev;
262
+ const next = node.subNext;
263
+ if (prev) prev.subNext = next;
264
+ else sub.depsHead = next;
265
+ if (next) next.subPrev = prev;
266
+ else sub.depsTail = prev;
267
+ }
190
268
  var currentSubscriber = null;
191
- var SUBS = "__s";
192
269
  var notifyDepth = 0;
193
270
  var pendingQueue = [];
194
271
  var pendingSet = /* @__PURE__ */ new Set();
@@ -200,54 +277,111 @@ function safeInvoke(sub) {
200
277
  if (_isDev2) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
201
278
  }
202
279
  }
203
- function track(effectFn, subscriber) {
204
- if (!subscriber) subscriber = effectFn;
205
- cleanup(subscriber);
206
- ++stackTop;
207
- if (stackTop >= stackCapacity) {
208
- stackCapacity *= 2;
209
- subscriberStack.length = stackCapacity;
210
- }
211
- subscriberStack[stackTop] = subscriber;
280
+ var subscriberEpochCounter = 0;
281
+ function retrack(effectFn, subscriber) {
282
+ const prev = currentSubscriber;
212
283
  currentSubscriber = subscriber;
284
+ const sub = subscriber;
285
+ const epoch = ++subscriberEpochCounter;
286
+ sub._epoch = epoch;
287
+ sub._structDirty = false;
288
+ for (let n = sub.depsHead ?? null; n !== null; n = n.subNext) {
289
+ const sig = n.sig;
290
+ n.prevActive = sig.__activeNode ?? null;
291
+ sig.__activeNode = n;
292
+ }
213
293
  try {
214
294
  effectFn();
215
295
  } finally {
216
- stackTop--;
217
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
296
+ currentSubscriber = prev;
297
+ let node = sub.depsHead ?? null;
298
+ while (node !== null) {
299
+ const next = node.subNext;
300
+ const sig = node.sig;
301
+ sig.__activeNode = node.prevActive;
302
+ node.prevActive = null;
303
+ if (node.epoch !== epoch) {
304
+ unlinkSub(node);
305
+ unlinkSignal(node);
306
+ freeNode(node);
307
+ }
308
+ node = next;
309
+ }
218
310
  }
219
- return () => cleanup(subscriber);
220
311
  }
221
312
  function recordDependency(signal2) {
222
313
  if (!currentSubscriber) return;
223
314
  const sub = currentSubscriber;
224
- if (sub._dep === signal2) return;
225
- const deps = sub._deps;
226
- if (deps) {
227
- if (deps.has(signal2)) return;
228
- deps.add(signal2);
229
- } else if (sub._dep !== void 0) {
230
- const set = /* @__PURE__ */ new Set();
231
- set.add(sub._dep);
232
- set.add(signal2);
233
- sub._deps = set;
234
- sub._dep = void 0;
235
- } else {
236
- sub._dep = signal2;
237
- }
238
- let subs = signal2[SUBS];
239
- if (!subs) {
240
- subs = /* @__PURE__ */ new Set();
241
- signal2[SUBS] = subs;
315
+ const sig = signal2;
316
+ const epoch = sub._epoch ?? 0;
317
+ const active = sig.__activeNode ?? null;
318
+ if (active !== null && active.sub === sub) {
319
+ active.epoch = epoch;
320
+ return;
242
321
  }
243
- subs.add(currentSubscriber);
244
- if (subs.size === 1) {
245
- signal2.__f = currentSubscriber;
246
- } else if (signal2.__f !== void 0) {
247
- signal2.__f = void 0;
322
+ const node = allocNode(signal2, sub, epoch);
323
+ node.prevActive = active;
324
+ sig.__activeNode = node;
325
+ linkSub(sub, node);
326
+ linkSignal(sig, node);
327
+ sub._structDirty = true;
328
+ }
329
+ function cleanup(subscriber) {
330
+ const sub = subscriber;
331
+ let node = sub.depsHead ?? null;
332
+ sub.depsHead = null;
333
+ sub.depsTail = null;
334
+ while (node) {
335
+ const next = node.subNext;
336
+ unlinkSignal(node);
337
+ freeNode(node);
338
+ node = next;
339
+ }
340
+ }
341
+ var maxSubscriberRepeats = 50;
342
+ var maxDrainIterations = 1e6;
343
+ var drainEpoch = 0;
344
+ function tickRepeat(sub) {
345
+ const s = sub;
346
+ if (s._runEpoch !== drainEpoch) {
347
+ s._runEpoch = drainEpoch;
348
+ s._runs = 1;
349
+ return false;
350
+ }
351
+ s._runs = (s._runs ?? 0) + 1;
352
+ return s._runs > maxSubscriberRepeats;
353
+ }
354
+ function cycleError(sub) {
355
+ if (typeof console !== "undefined") {
356
+ const name = sub.__name ?? "<unnamed>";
357
+ console.error(
358
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
359
+ );
360
+ }
361
+ }
362
+ function absoluteDrainError() {
363
+ if (typeof console !== "undefined") {
364
+ console.error(
365
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
366
+ );
367
+ }
368
+ }
369
+ function drainQueue() {
370
+ let i = 0;
371
+ while (i < pendingQueue.length) {
372
+ if (i >= maxDrainIterations) {
373
+ absoluteDrainError();
374
+ break;
375
+ }
376
+ const sub = pendingQueue[i++];
377
+ if (tickRepeat(sub)) {
378
+ cycleError(sub);
379
+ break;
380
+ }
381
+ pendingSet.delete(sub);
382
+ safeInvoke(sub);
248
383
  }
249
384
  }
250
- var maxDrainIterations = 1e5;
251
385
  function propagateDirty(sub) {
252
386
  sub();
253
387
  const rootSig = sub._sig;
@@ -257,131 +391,66 @@ function propagateDirty(sub) {
257
391
  stack.push(rootSig);
258
392
  while (stack.length > baseLen) {
259
393
  const sig = stack.pop();
260
- const first = sig.__f;
261
- if (first) {
262
- if (first._c) {
263
- const nSig = first._sig;
264
- if (!nSig._d) {
265
- nSig._d = true;
266
- stack.push(nSig);
267
- }
268
- } else if (!pendingSet.has(first)) {
269
- pendingSet.add(first);
270
- pendingQueue.push(first);
271
- }
272
- continue;
273
- }
274
- const subs = sig[SUBS];
275
- if (!subs) continue;
276
- for (const s of subs) {
277
- if (s._c) {
278
- const nSig = s._sig;
279
- if (nSig && !nSig._d) {
280
- nSig._d = true;
281
- stack.push(nSig);
282
- } else if (!nSig) {
283
- s();
394
+ let node = sig.subsHead ?? null;
395
+ while (node) {
396
+ const s = node.sub;
397
+ if (s) {
398
+ if (s._c) {
399
+ const nSig = s._sig;
400
+ if (nSig) {
401
+ if (!nSig._d) {
402
+ nSig._d = true;
403
+ stack.push(nSig);
404
+ }
405
+ } else {
406
+ s();
407
+ }
408
+ } else if (!pendingSet.has(s)) {
409
+ pendingSet.add(s);
410
+ pendingQueue.push(s);
284
411
  }
285
- } else if (!pendingSet.has(s)) {
286
- pendingSet.add(s);
287
- pendingQueue.push(s);
288
412
  }
413
+ node = node.sigNext;
289
414
  }
290
415
  }
291
416
  }
292
417
  function notifySubscribers(signal2) {
293
- const first = signal2.__f;
294
- if (first) {
295
- if (notifyDepth > 0) {
296
- if (first._c) {
297
- propagateDirty(first);
298
- } else if (!pendingSet.has(first)) {
299
- pendingSet.add(first);
300
- pendingQueue.push(first);
301
- }
302
- return;
303
- }
304
- notifyDepth++;
305
- try {
306
- if (first._c) {
307
- propagateDirty(first);
308
- } else {
309
- safeInvoke(first);
310
- }
311
- let i = 0;
312
- while (i < pendingQueue.length) {
313
- if (i >= maxDrainIterations) {
314
- if (typeof console !== "undefined") {
315
- console.error(
316
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
317
- );
318
- }
319
- break;
320
- }
321
- safeInvoke(pendingQueue[i]);
322
- i++;
323
- }
324
- } finally {
325
- notifyDepth--;
326
- if (notifyDepth === 0) {
327
- pendingQueue.length = 0;
328
- pendingSet.clear();
329
- }
330
- }
331
- return;
332
- }
333
- const subs = signal2[SUBS];
334
- if (!subs || subs.size === 0) return;
418
+ const sig = signal2;
419
+ const head = sig.subsHead;
420
+ if (!head) return;
335
421
  if (notifyDepth > 0) {
336
- for (const sub of subs) {
337
- if (sub._c) {
338
- propagateDirty(sub);
339
- } else if (!pendingSet.has(sub)) {
340
- pendingSet.add(sub);
341
- pendingQueue.push(sub);
422
+ let node = head;
423
+ while (node) {
424
+ const s = node.sub;
425
+ if (s) {
426
+ if (s._c) {
427
+ propagateDirty(s);
428
+ } else if (!pendingSet.has(s)) {
429
+ pendingSet.add(s);
430
+ pendingQueue.push(s);
431
+ }
342
432
  }
433
+ node = node.sigNext;
343
434
  }
344
435
  return;
345
436
  }
346
437
  notifyDepth++;
438
+ drainEpoch++;
347
439
  try {
348
- let directCount = 0;
349
- let hasComputedSub = false;
350
- for (const sub of subs) {
351
- if (sub._c) hasComputedSub = true;
352
- pendingQueue[directCount++] = sub;
353
- }
354
- if (!hasComputedSub) {
355
- for (let i2 = 0; i2 < directCount; i2++) {
356
- safeInvoke(pendingQueue[i2]);
357
- }
358
- } else {
359
- for (let i2 = 0; i2 < directCount; i2++) {
360
- if (pendingQueue[i2]._c) {
361
- propagateDirty(pendingQueue[i2]);
362
- }
363
- }
364
- for (let i2 = 0; i2 < directCount; i2++) {
365
- const sub = pendingQueue[i2];
366
- if (!sub._c && !pendingSet.has(sub)) {
367
- pendingSet.add(sub);
368
- safeInvoke(sub);
369
- }
370
- }
371
- }
372
- let i = directCount;
373
- while (i < pendingQueue.length) {
374
- if (i - directCount >= maxDrainIterations) {
375
- if (typeof console !== "undefined") {
376
- console.error(
377
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
378
- );
440
+ let node = head;
441
+ while (node) {
442
+ const s = node.sub;
443
+ if (s) {
444
+ if (s._c) {
445
+ propagateDirty(s);
446
+ } else if (!pendingSet.has(s)) {
447
+ pendingSet.add(s);
448
+ pendingQueue.push(s);
379
449
  }
380
- break;
381
450
  }
382
- safeInvoke(pendingQueue[i]);
383
- i++;
451
+ node = node.sigNext;
384
452
  }
453
+ drainQueue();
385
454
  } finally {
386
455
  notifyDepth--;
387
456
  if (notifyDepth === 0) {
@@ -390,36 +459,26 @@ function notifySubscribers(signal2) {
390
459
  }
391
460
  }
392
461
  }
393
- function cleanup(subscriber) {
462
+ function getSubscriberCount(signal2) {
463
+ return signal2.__sc ?? 0;
464
+ }
465
+ function getSubscriberDeps(subscriber) {
394
466
  const sub = subscriber;
395
- const singleDep = sub._dep;
396
- if (singleDep !== void 0) {
397
- const subs = singleDep[SUBS];
398
- if (subs) {
399
- subs.delete(subscriber);
400
- if (singleDep.__f === subscriber) {
401
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
402
- } else if (subs.size === 1 && singleDep.__f === void 0) {
403
- singleDep.__f = subs.values().next().value;
404
- }
405
- }
406
- sub._dep = void 0;
407
- return;
467
+ const out = [];
468
+ let node = sub.depsHead ?? null;
469
+ while (node) {
470
+ if (node.sig) out.push(node.sig);
471
+ node = node.subNext;
408
472
  }
409
- const deps = sub._deps;
410
- if (!deps || deps.size === 0) return;
411
- for (const signal2 of deps) {
412
- const subs = signal2[SUBS];
413
- if (subs) {
414
- subs.delete(subscriber);
415
- if (signal2.__f === subscriber) {
416
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
417
- } else if (subs.size === 1 && signal2.__f === void 0) {
418
- signal2.__f = subs.values().next().value;
419
- }
420
- }
473
+ return out;
474
+ }
475
+ function forEachSubscriber(signal2, visit) {
476
+ let node = signal2.subsHead ?? null;
477
+ while (node) {
478
+ const s = node.sub;
479
+ if (s) visit(s);
480
+ node = node.sigNext;
421
481
  }
422
- deps.clear();
423
482
  }
424
483
 
425
484
  // src/reactivity/batch.ts
@@ -435,32 +494,64 @@ function enqueueBatchedSignal(signal2) {
435
494
  var _g = globalThis;
436
495
  var _isDev3 = isDev();
437
496
  function signal(initial, options) {
438
- const state = { value: initial };
497
+ const state = {
498
+ value: initial,
499
+ __v: 0,
500
+ __sc: 0,
501
+ subsHead: null,
502
+ subsTail: null,
503
+ __activeNode: null,
504
+ __name: void 0
505
+ };
439
506
  const debugName = _isDev3 ? options?.name : void 0;
440
507
  const equalsFn = options?.equals;
441
- if (debugName) {
442
- state.__name = debugName;
443
- }
508
+ if (debugName) state.__name = debugName;
444
509
  function get() {
445
510
  recordDependency(state);
446
511
  return state.value;
447
512
  }
448
513
  get.__signal = state;
449
514
  if (debugName) get.__name = debugName;
450
- function set(next) {
451
- const newValue = typeof next === "function" ? next(state.value) : next;
452
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
453
- if (_isDev3) {
454
- const oldValue = state.value;
515
+ let set;
516
+ if (equalsFn) {
517
+ set = (next) => {
518
+ const prev = state.value;
519
+ const newValue = typeof next === "function" ? next(prev) : next;
520
+ if (equalsFn(prev, newValue)) return;
521
+ state.value = newValue;
522
+ state.__v++;
523
+ if (_isDev3) {
524
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
525
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
526
+ }
527
+ if (!enqueueBatchedSignal(state)) {
528
+ notifySubscribers(state);
529
+ }
530
+ };
531
+ } else if (_isDev3) {
532
+ set = (next) => {
533
+ const prev = state.value;
534
+ const newValue = typeof next === "function" ? next(prev) : next;
535
+ if (Object.is(newValue, prev)) return;
455
536
  state.value = newValue;
537
+ state.__v++;
456
538
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
457
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
458
- } else {
539
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
540
+ if (!enqueueBatchedSignal(state)) {
541
+ notifySubscribers(state);
542
+ }
543
+ };
544
+ } else {
545
+ set = (next) => {
546
+ const prev = state.value;
547
+ const newValue = typeof next === "function" ? next(prev) : next;
548
+ if (Object.is(newValue, prev)) return;
459
549
  state.value = newValue;
460
- }
461
- if (!enqueueBatchedSignal(state)) {
462
- notifySubscribers(state);
463
- }
550
+ state.__v++;
551
+ if (!enqueueBatchedSignal(state)) {
552
+ notifySubscribers(state);
553
+ }
554
+ };
464
555
  }
465
556
  if (_isDev3) {
466
557
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -669,13 +760,14 @@ function initDevTools(config) {
669
760
  } catch {
670
761
  value = "<error>";
671
762
  }
672
- const subs = node.ref?.__s;
673
763
  result.push({
674
764
  id: node.id,
675
765
  name: node.name,
676
766
  type: node.type,
677
767
  value,
678
- subscriberCount: subs instanceof Set ? subs.size : 0
768
+ // __sc is the O(1) subscriber count maintained by the reactivity
769
+ // core (track.ts) on every linked-list splice.
770
+ subscriberCount: node.ref?.__sc ?? 0
679
771
  });
680
772
  }
681
773
  return result;
@@ -816,14 +908,13 @@ function initDevTools(config) {
816
908
  }
817
909
  const fullVal = val;
818
910
  const shortVal = val.length > 80 ? `${val.substring(0, 80)}...` : val;
819
- const subs = node.ref?.__s;
820
911
  sArr.push({
821
912
  id: node.id,
822
913
  n: node.name,
823
914
  tp: node.type,
824
915
  v: shortVal,
825
916
  fv: fullVal,
826
- sc: subs instanceof Set ? subs.size : 0
917
+ sc: node.ref?.__sc ?? 0
827
918
  });
828
919
  }
829
920
  function walkElement(el, depth) {
@@ -1463,92 +1554,122 @@ function isSSR() {
1463
1554
 
1464
1555
  // src/core/signals/effect.ts
1465
1556
  var _g2 = globalThis;
1557
+ var MAX_RERUNS = 100;
1558
+ function flushUserCleanups(ctx) {
1559
+ const list = ctx.userCleanups;
1560
+ if (list.length === 0) return;
1561
+ ctx.userCleanups = [];
1562
+ for (let i = list.length - 1; i >= 0; i--) {
1563
+ try {
1564
+ list[i]();
1565
+ } catch (err) {
1566
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
1567
+ }
1568
+ }
1569
+ }
1570
+ function drainReruns(ctx) {
1571
+ let reruns = 1;
1572
+ do {
1573
+ ctx.rerunPending = false;
1574
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1575
+ retrack(ctx.bodyFn, ctx.subscriber);
1576
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
1577
+ if (ctx.rerunPending) {
1578
+ ctx.rerunPending = false;
1579
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1580
+ console.error(
1581
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
1582
+ );
1583
+ }
1584
+ }
1585
+ }
1586
+ function disposeEffect(ctx) {
1587
+ if (ctx.disposed) return;
1588
+ ctx.disposed = true;
1589
+ const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1590
+ if (h) {
1591
+ try {
1592
+ h.emit("effect:destroy", { effectFn: ctx.fn });
1593
+ } catch {
1594
+ }
1595
+ }
1596
+ try {
1597
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1598
+ } catch (err) {
1599
+ if (typeof console !== "undefined") {
1600
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1601
+ }
1602
+ }
1603
+ try {
1604
+ cleanup(ctx.subscriber);
1605
+ } catch (err) {
1606
+ if (typeof console !== "undefined") {
1607
+ console.warn("[SibuJS effect] dispose threw:", err);
1608
+ }
1609
+ }
1610
+ }
1466
1611
  function effect(effectFn, options) {
1467
1612
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1468
1613
  if (isSSR()) return () => {
1469
1614
  };
1470
- const onError = options?.onError;
1471
- let userCleanups = [];
1472
- const onCleanup = (fn) => {
1473
- userCleanups.push(fn);
1615
+ const ctx = {
1616
+ fn: effectFn,
1617
+ onError: options?.onError,
1618
+ userCleanups: [],
1619
+ running: false,
1620
+ rerunPending: false,
1621
+ disposed: false,
1622
+ onCleanup: null,
1623
+ subscriber: null,
1624
+ bodyFn: null
1474
1625
  };
1475
- const runUserCleanups = () => {
1476
- if (userCleanups.length === 0) return;
1477
- const list = userCleanups;
1478
- userCleanups = [];
1479
- for (let i = list.length - 1; i >= 0; i--) {
1480
- try {
1481
- list[i]();
1482
- } catch (err) {
1483
- if (typeof console !== "undefined") {
1484
- console.warn("[SibuJS effect] onCleanup threw:", err);
1485
- }
1486
- }
1487
- }
1626
+ ctx.onCleanup = (fn) => {
1627
+ ctx.userCleanups.push(fn);
1488
1628
  };
1489
- const invokeBody = () => effectFn(onCleanup);
1490
- const wrappedFn = onError ? () => {
1629
+ const onErrorCaptured = ctx.onError;
1630
+ ctx.bodyFn = onErrorCaptured ? () => {
1491
1631
  try {
1492
- invokeBody();
1632
+ ctx.fn(ctx.onCleanup);
1493
1633
  } catch (err) {
1494
- onError(err);
1634
+ onErrorCaptured(err);
1495
1635
  }
1496
- } : invokeBody;
1497
- let cleanupHandle = () => {
1636
+ } : () => {
1637
+ ctx.fn(ctx.onCleanup);
1498
1638
  };
1499
- let running = false;
1500
- const subscriber = () => {
1501
- if (running) {
1502
- if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1503
- console.warn(
1504
- "[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."
1505
- );
1506
- }
1639
+ const sub = (() => {
1640
+ if (ctx.running) {
1641
+ ctx.rerunPending = true;
1507
1642
  return;
1508
1643
  }
1509
- running = true;
1644
+ ctx.running = true;
1510
1645
  try {
1511
- runUserCleanups();
1512
- cleanupHandle();
1513
- cleanupHandle = track(wrappedFn, subscriber);
1646
+ ctx.rerunPending = false;
1647
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1648
+ retrack(ctx.bodyFn, sub);
1649
+ if (ctx.rerunPending) drainReruns(ctx);
1514
1650
  } finally {
1515
- running = false;
1651
+ ctx.running = false;
1652
+ ctx.rerunPending = false;
1516
1653
  }
1517
- };
1518
- running = true;
1654
+ });
1655
+ sub.depsHead = null;
1656
+ sub.depsTail = null;
1657
+ sub._epoch = 0;
1658
+ sub._structDirty = false;
1659
+ sub._runEpoch = 0;
1660
+ sub._runs = 0;
1661
+ ctx.subscriber = sub;
1662
+ ctx.running = true;
1519
1663
  try {
1520
- cleanupHandle = track(wrappedFn, subscriber);
1664
+ retrack(ctx.bodyFn, ctx.subscriber);
1665
+ if (ctx.rerunPending) drainReruns(ctx);
1521
1666
  } finally {
1522
- running = false;
1667
+ ctx.running = false;
1668
+ ctx.rerunPending = false;
1523
1669
  }
1524
1670
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1525
1671
  if (hook) hook.emit("effect:create", { effectFn });
1526
- let disposed = false;
1527
- return () => {
1528
- if (disposed) return;
1529
- disposed = true;
1530
- const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1531
- if (h) {
1532
- try {
1533
- h.emit("effect:destroy", { effectFn });
1534
- } catch {
1535
- }
1536
- }
1537
- try {
1538
- runUserCleanups();
1539
- } catch (err) {
1540
- if (typeof console !== "undefined") {
1541
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1542
- }
1543
- }
1544
- try {
1545
- cleanupHandle();
1546
- } catch (err) {
1547
- if (typeof console !== "undefined") {
1548
- console.warn("[SibuJS effect] dispose threw:", err);
1549
- }
1550
- }
1551
- };
1672
+ return () => disposeEffect(ctx);
1552
1673
  }
1553
1674
 
1554
1675
  // src/devtools/debugValue.ts
@@ -1651,28 +1772,24 @@ function createDevtoolsOverlay(options) {
1651
1772
  }
1652
1773
 
1653
1774
  // src/devtools/introspect.ts
1654
- var SUBS2 = "__s";
1655
1775
  function getSignalName(getter) {
1656
1776
  return getter.__name;
1657
1777
  }
1658
- function getSubscriberCount(getter) {
1778
+ function getSubscriberCount2(getter) {
1659
1779
  const signal2 = getter.__signal;
1660
1780
  if (!signal2) return 0;
1661
- const subs = signal2[SUBS2];
1662
- return subs ? subs.size : 0;
1781
+ return getSubscriberCount(signal2);
1663
1782
  }
1664
1783
  function getDependencies(subscriberFn) {
1665
- const deps = subscriberFn._deps;
1666
- return deps ? Array.from(deps) : [];
1784
+ return getSubscriberDeps(subscriberFn);
1667
1785
  }
1668
1786
  function inspectSignal(getter) {
1669
1787
  const signal2 = getter.__signal;
1670
1788
  if (!signal2) return null;
1671
- const subs = signal2[SUBS2];
1672
1789
  return {
1673
1790
  name: getter.__name,
1674
1791
  signal: signal2,
1675
- subscriberCount: subs ? subs.size : 0
1792
+ subscriberCount: getSubscriberCount(signal2)
1676
1793
  };
1677
1794
  }
1678
1795
  function walkDependencyGraph(getter, maxDepth = 10, visited = /* @__PURE__ */ new WeakSet()) {
@@ -1681,24 +1798,21 @@ function walkDependencyGraph(getter, maxDepth = 10, visited = /* @__PURE__ */ ne
1681
1798
  return { name: getSignalName(getter), subscribers: 0, downstream: [] };
1682
1799
  }
1683
1800
  visited.add(signal2);
1684
- const subs = signal2[SUBS2];
1685
1801
  const downstream = [];
1686
- if (subs) {
1687
- for (const sub of subs) {
1688
- const subSig = sub._sig;
1689
- if (subSig && !visited.has(subSig)) {
1690
- const subName = subSig.__name;
1691
- const fakeGetter = (() => void 0);
1692
- const tag = fakeGetter;
1693
- tag.__signal = subSig;
1694
- if (subName !== void 0) tag.__name = subName;
1695
- downstream.push(walkDependencyGraph(fakeGetter, maxDepth - 1, visited));
1696
- }
1802
+ forEachSubscriber(signal2, (sub) => {
1803
+ const subSig = sub._sig;
1804
+ if (subSig && !visited.has(subSig)) {
1805
+ const subName = subSig.__name;
1806
+ const fakeGetter = (() => void 0);
1807
+ const tag = fakeGetter;
1808
+ tag.__signal = subSig;
1809
+ if (subName !== void 0) tag.__name = subName;
1810
+ downstream.push(walkDependencyGraph(fakeGetter, maxDepth - 1, visited));
1697
1811
  }
1698
- }
1812
+ });
1699
1813
  return {
1700
1814
  name: getSignalName(getter),
1701
- subscribers: subs ? subs.size : 0,
1815
+ subscribers: getSubscriberCount(signal2),
1702
1816
  downstream
1703
1817
  };
1704
1818
  }