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/patterns.cjs CHANGED
@@ -61,11 +61,88 @@ function devWarn(message) {
61
61
 
62
62
  // src/reactivity/track.ts
63
63
  var _isDev2 = isDev();
64
- var subscriberStack = new Array(32);
65
- var stackCapacity = 32;
66
- var stackTop = -1;
64
+ var POOL_MAX = 4096;
65
+ var nodePool = [];
66
+ function createNode() {
67
+ return {
68
+ sig: null,
69
+ sub: null,
70
+ epoch: 0,
71
+ sigPrev: null,
72
+ sigNext: null,
73
+ subPrev: null,
74
+ subNext: null,
75
+ prevActive: null
76
+ };
77
+ }
78
+ function allocNode(sig, sub, epoch) {
79
+ const n = nodePool.pop();
80
+ if (n) {
81
+ n.sig = sig;
82
+ n.sub = sub;
83
+ n.epoch = epoch;
84
+ return n;
85
+ }
86
+ const fresh = createNode();
87
+ fresh.sig = sig;
88
+ fresh.sub = sub;
89
+ fresh.epoch = epoch;
90
+ return fresh;
91
+ }
92
+ function freeNode(node) {
93
+ node.sig = null;
94
+ node.sub = null;
95
+ node.sigPrev = null;
96
+ node.sigNext = null;
97
+ node.subPrev = null;
98
+ node.subNext = null;
99
+ node.prevActive = null;
100
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
101
+ }
102
+ function linkSignal(sig, node) {
103
+ const oldHead = sig.subsHead ?? null;
104
+ node.sigPrev = null;
105
+ node.sigNext = oldHead;
106
+ if (oldHead) oldHead.sigPrev = node;
107
+ else sig.subsTail = node;
108
+ sig.subsHead = node;
109
+ sig.__sc = (sig.__sc ?? 0) + 1;
110
+ }
111
+ function unlinkSignal(node) {
112
+ const sig = node.sig;
113
+ if (!sig) return;
114
+ const prev = node.sigPrev;
115
+ const next = node.sigNext;
116
+ if (prev) prev.sigNext = next;
117
+ else sig.subsHead = next;
118
+ if (next) next.sigPrev = prev;
119
+ else sig.subsTail = prev;
120
+ sig.__sc = (sig.__sc ?? 1) - 1;
121
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
122
+ if (sig.__sc === 0) {
123
+ sig.subsHead = null;
124
+ sig.subsTail = null;
125
+ }
126
+ }
127
+ function linkSub(sub, node) {
128
+ const oldTail = sub.depsTail ?? null;
129
+ node.subPrev = oldTail;
130
+ node.subNext = null;
131
+ if (oldTail) oldTail.subNext = node;
132
+ else sub.depsHead = node;
133
+ sub.depsTail = node;
134
+ }
135
+ function unlinkSub(node) {
136
+ const sub = node.sub;
137
+ if (!sub) return;
138
+ const prev = node.subPrev;
139
+ const next = node.subNext;
140
+ if (prev) prev.subNext = next;
141
+ else sub.depsHead = next;
142
+ if (next) next.subPrev = prev;
143
+ else sub.depsTail = prev;
144
+ }
67
145
  var currentSubscriber = null;
68
- var SUBS = "__s";
69
146
  var notifyDepth = 0;
70
147
  var pendingQueue = [];
71
148
  var pendingSet = /* @__PURE__ */ new Set();
@@ -78,92 +155,136 @@ function safeInvoke(sub) {
78
155
  }
79
156
  }
80
157
  var trackingSuspended = false;
158
+ var subscriberEpochCounter = 0;
81
159
  function retrack(effectFn, subscriber) {
82
160
  const prev = currentSubscriber;
83
161
  currentSubscriber = subscriber;
162
+ const sub = subscriber;
163
+ const epoch = ++subscriberEpochCounter;
164
+ sub._epoch = epoch;
165
+ sub._structDirty = false;
166
+ for (let n = sub.depsHead ?? null; n !== null; n = n.subNext) {
167
+ const sig = n.sig;
168
+ n.prevActive = sig.__activeNode ?? null;
169
+ sig.__activeNode = n;
170
+ }
84
171
  try {
85
172
  effectFn();
86
173
  } finally {
87
174
  currentSubscriber = prev;
175
+ let node = sub.depsHead ?? null;
176
+ while (node !== null) {
177
+ const next = node.subNext;
178
+ const sig = node.sig;
179
+ sig.__activeNode = node.prevActive;
180
+ node.prevActive = null;
181
+ if (node.epoch !== epoch) {
182
+ unlinkSub(node);
183
+ unlinkSignal(node);
184
+ freeNode(node);
185
+ }
186
+ node = next;
187
+ }
88
188
  }
89
189
  }
90
190
  function track(effectFn, subscriber) {
91
191
  if (!subscriber) subscriber = effectFn;
92
192
  cleanup(subscriber);
93
- ++stackTop;
94
- if (stackTop >= stackCapacity) {
95
- stackCapacity *= 2;
96
- subscriberStack.length = stackCapacity;
97
- }
98
- subscriberStack[stackTop] = subscriber;
193
+ const prev = currentSubscriber;
99
194
  currentSubscriber = subscriber;
100
195
  try {
101
196
  effectFn();
102
197
  } finally {
103
- stackTop--;
104
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
198
+ currentSubscriber = prev;
199
+ const sub2 = subscriber;
200
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
201
+ const sig = n.sig;
202
+ sig.__activeNode = n.prevActive;
203
+ n.prevActive = null;
204
+ }
105
205
  }
106
- return () => cleanup(subscriber);
206
+ const sub = subscriber;
207
+ return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
107
208
  }
108
209
  function recordDependency(signal2) {
109
210
  if (!currentSubscriber) return;
110
211
  const sub = currentSubscriber;
111
- if (sub._dep === signal2) return;
112
- const deps = sub._deps;
113
- if (deps) {
114
- if (deps.has(signal2)) return;
115
- deps.add(signal2);
116
- } else if (sub._dep !== void 0) {
117
- const set = /* @__PURE__ */ new Set();
118
- set.add(sub._dep);
119
- set.add(signal2);
120
- sub._deps = set;
121
- sub._dep = void 0;
122
- } else {
123
- sub._dep = signal2;
212
+ const sig = signal2;
213
+ const epoch = sub._epoch ?? 0;
214
+ const active = sig.__activeNode ?? null;
215
+ if (active !== null && active.sub === sub) {
216
+ active.epoch = epoch;
217
+ return;
124
218
  }
125
- let subs = signal2[SUBS];
126
- if (!subs) {
127
- subs = /* @__PURE__ */ new Set();
128
- signal2[SUBS] = subs;
219
+ const node = allocNode(signal2, sub, epoch);
220
+ node.prevActive = active;
221
+ sig.__activeNode = node;
222
+ linkSub(sub, node);
223
+ linkSignal(sig, node);
224
+ sub._structDirty = true;
225
+ }
226
+ function cleanup(subscriber) {
227
+ const sub = subscriber;
228
+ let node = sub.depsHead ?? null;
229
+ sub.depsHead = null;
230
+ sub.depsTail = null;
231
+ while (node) {
232
+ const next = node.subNext;
233
+ unlinkSignal(node);
234
+ freeNode(node);
235
+ node = next;
129
236
  }
130
- subs.add(currentSubscriber);
131
- if (subs.size === 1) {
132
- signal2.__f = currentSubscriber;
133
- } else if (signal2.__f !== void 0) {
134
- signal2.__f = void 0;
237
+ }
238
+ var maxSubscriberRepeats = 50;
239
+ var maxDrainIterations = 1e6;
240
+ var drainEpoch = 0;
241
+ function tickRepeat(sub) {
242
+ const s = sub;
243
+ if (s._runEpoch !== drainEpoch) {
244
+ s._runEpoch = drainEpoch;
245
+ s._runs = 1;
246
+ return false;
135
247
  }
248
+ s._runs = (s._runs ?? 0) + 1;
249
+ return s._runs > maxSubscriberRepeats;
136
250
  }
137
- function queueSignalNotification(signal2) {
138
- const subs = signal2[SUBS];
139
- if (!subs) return;
140
- for (const sub of subs) {
141
- if (sub._c) {
142
- propagateDirty(sub);
143
- } else if (!pendingSet.has(sub)) {
144
- pendingSet.add(sub);
145
- pendingQueue.push(sub);
251
+ function cycleError(sub) {
252
+ if (typeof console !== "undefined") {
253
+ const name = sub.__name ?? "<unnamed>";
254
+ console.error(
255
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
256
+ );
257
+ }
258
+ }
259
+ function absoluteDrainError() {
260
+ if (typeof console !== "undefined") {
261
+ console.error(
262
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
263
+ );
264
+ }
265
+ }
266
+ function drainQueue() {
267
+ let i = 0;
268
+ while (i < pendingQueue.length) {
269
+ if (i >= maxDrainIterations) {
270
+ absoluteDrainError();
271
+ break;
272
+ }
273
+ const sub = pendingQueue[i++];
274
+ if (tickRepeat(sub)) {
275
+ cycleError(sub);
276
+ break;
146
277
  }
278
+ pendingSet.delete(sub);
279
+ safeInvoke(sub);
147
280
  }
148
281
  }
149
- var maxDrainIterations = 1e5;
150
282
  function drainNotificationQueue() {
151
283
  if (notifyDepth > 0) return;
152
284
  notifyDepth++;
285
+ drainEpoch++;
153
286
  try {
154
- let i = 0;
155
- while (i < pendingQueue.length) {
156
- if (i >= maxDrainIterations) {
157
- if (typeof console !== "undefined") {
158
- console.error(
159
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
160
- );
161
- }
162
- break;
163
- }
164
- safeInvoke(pendingQueue[i]);
165
- i++;
166
- }
287
+ drainQueue();
167
288
  } finally {
168
289
  notifyDepth--;
169
290
  if (notifyDepth === 0) {
@@ -181,131 +302,82 @@ function propagateDirty(sub) {
181
302
  stack.push(rootSig);
182
303
  while (stack.length > baseLen) {
183
304
  const sig = stack.pop();
184
- const first = sig.__f;
185
- if (first) {
186
- if (first._c) {
187
- const nSig = first._sig;
188
- if (!nSig._d) {
189
- nSig._d = true;
190
- stack.push(nSig);
305
+ let node = sig.subsHead ?? null;
306
+ while (node) {
307
+ const s = node.sub;
308
+ if (s) {
309
+ if (s._c) {
310
+ const nSig = s._sig;
311
+ if (nSig) {
312
+ if (!nSig._d) {
313
+ nSig._d = true;
314
+ stack.push(nSig);
315
+ }
316
+ } else {
317
+ s();
318
+ }
319
+ } else if (!pendingSet.has(s)) {
320
+ pendingSet.add(s);
321
+ pendingQueue.push(s);
191
322
  }
192
- } else if (!pendingSet.has(first)) {
193
- pendingSet.add(first);
194
- pendingQueue.push(first);
195
323
  }
196
- continue;
324
+ node = node.sigNext;
197
325
  }
198
- const subs = sig[SUBS];
199
- if (!subs) continue;
200
- for (const s of subs) {
326
+ }
327
+ }
328
+ function queueSignalNotification(signal2) {
329
+ const sig = signal2;
330
+ let node = sig.subsHead ?? null;
331
+ while (node) {
332
+ const s = node.sub;
333
+ if (s) {
201
334
  if (s._c) {
202
- const nSig = s._sig;
203
- if (nSig && !nSig._d) {
204
- nSig._d = true;
205
- stack.push(nSig);
206
- } else if (!nSig) {
207
- s();
208
- }
335
+ propagateDirty(s);
209
336
  } else if (!pendingSet.has(s)) {
210
337
  pendingSet.add(s);
211
338
  pendingQueue.push(s);
212
339
  }
213
340
  }
341
+ node = node.sigNext;
214
342
  }
215
343
  }
216
344
  function notifySubscribers(signal2) {
217
- const first = signal2.__f;
218
- if (first) {
219
- if (notifyDepth > 0) {
220
- if (first._c) {
221
- propagateDirty(first);
222
- } else if (!pendingSet.has(first)) {
223
- pendingSet.add(first);
224
- pendingQueue.push(first);
225
- }
226
- return;
227
- }
228
- notifyDepth++;
229
- try {
230
- if (first._c) {
231
- propagateDirty(first);
232
- } else {
233
- safeInvoke(first);
234
- }
235
- let i = 0;
236
- while (i < pendingQueue.length) {
237
- if (i >= maxDrainIterations) {
238
- if (typeof console !== "undefined") {
239
- console.error(
240
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
241
- );
242
- }
243
- break;
244
- }
245
- safeInvoke(pendingQueue[i]);
246
- i++;
247
- }
248
- } finally {
249
- notifyDepth--;
250
- if (notifyDepth === 0) {
251
- pendingQueue.length = 0;
252
- pendingSet.clear();
253
- }
254
- }
255
- return;
256
- }
257
- const subs = signal2[SUBS];
258
- if (!subs || subs.size === 0) return;
345
+ const sig = signal2;
346
+ const head = sig.subsHead;
347
+ if (!head) return;
259
348
  if (notifyDepth > 0) {
260
- for (const sub of subs) {
261
- if (sub._c) {
262
- propagateDirty(sub);
263
- } else if (!pendingSet.has(sub)) {
264
- pendingSet.add(sub);
265
- pendingQueue.push(sub);
349
+ let node = head;
350
+ while (node) {
351
+ const s = node.sub;
352
+ if (s) {
353
+ if (s._c) {
354
+ propagateDirty(s);
355
+ } else if (!pendingSet.has(s)) {
356
+ pendingSet.add(s);
357
+ pendingQueue.push(s);
358
+ }
266
359
  }
360
+ node = node.sigNext;
267
361
  }
268
362
  return;
269
363
  }
270
364
  notifyDepth++;
365
+ drainEpoch++;
271
366
  try {
272
- let directCount = 0;
273
- let hasComputedSub = false;
274
- for (const sub of subs) {
275
- if (sub._c) hasComputedSub = true;
276
- pendingQueue[directCount++] = sub;
277
- }
278
- if (!hasComputedSub) {
279
- for (let i2 = 0; i2 < directCount; i2++) {
280
- safeInvoke(pendingQueue[i2]);
281
- }
282
- } else {
283
- for (let i2 = 0; i2 < directCount; i2++) {
284
- if (pendingQueue[i2]._c) {
285
- propagateDirty(pendingQueue[i2]);
286
- }
287
- }
288
- for (let i2 = 0; i2 < directCount; i2++) {
289
- const sub = pendingQueue[i2];
290
- if (!sub._c && !pendingSet.has(sub)) {
291
- pendingSet.add(sub);
292
- safeInvoke(sub);
367
+ let node = head;
368
+ while (node) {
369
+ const s = node.sub;
370
+ if (s) {
371
+ if (s._c) {
372
+ propagateDirty(s);
373
+ } else if (!pendingSet.has(s)) {
374
+ pendingSet.add(s);
375
+ pendingQueue.push(s);
293
376
  }
294
377
  }
378
+ node = node.sigNext;
295
379
  }
296
- let i = directCount;
297
- while (i < pendingQueue.length) {
298
- if (i - directCount >= maxDrainIterations) {
299
- if (typeof console !== "undefined") {
300
- console.error(
301
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
302
- );
303
- }
304
- break;
305
- }
306
- safeInvoke(pendingQueue[i]);
307
- i++;
308
- }
380
+ drainQueue();
309
381
  } finally {
310
382
  notifyDepth--;
311
383
  if (notifyDepth === 0) {
@@ -314,37 +386,6 @@ function notifySubscribers(signal2) {
314
386
  }
315
387
  }
316
388
  }
317
- function cleanup(subscriber) {
318
- const sub = subscriber;
319
- const singleDep = sub._dep;
320
- if (singleDep !== void 0) {
321
- const subs = singleDep[SUBS];
322
- if (subs) {
323
- subs.delete(subscriber);
324
- if (singleDep.__f === subscriber) {
325
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
326
- } else if (subs.size === 1 && singleDep.__f === void 0) {
327
- singleDep.__f = subs.values().next().value;
328
- }
329
- }
330
- sub._dep = void 0;
331
- return;
332
- }
333
- const deps = sub._deps;
334
- if (!deps || deps.size === 0) return;
335
- for (const signal2 of deps) {
336
- const subs = signal2[SUBS];
337
- if (subs) {
338
- subs.delete(subscriber);
339
- if (signal2.__f === subscriber) {
340
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
341
- } else if (subs.size === 1 && signal2.__f === void 0) {
342
- signal2.__f = subs.values().next().value;
343
- }
344
- }
345
- }
346
- deps.clear();
347
- }
348
389
 
349
390
  // src/reactivity/batch.ts
350
391
  var batchDepth = 0;
@@ -380,32 +421,64 @@ function flushBatch() {
380
421
  var _g = globalThis;
381
422
  var _isDev3 = isDev();
382
423
  function signal(initial, options) {
383
- const state = { value: initial };
424
+ const state = {
425
+ value: initial,
426
+ __v: 0,
427
+ __sc: 0,
428
+ subsHead: null,
429
+ subsTail: null,
430
+ __activeNode: null,
431
+ __name: void 0
432
+ };
384
433
  const debugName = _isDev3 ? options?.name : void 0;
385
434
  const equalsFn = options?.equals;
386
- if (debugName) {
387
- state.__name = debugName;
388
- }
435
+ if (debugName) state.__name = debugName;
389
436
  function get() {
390
437
  recordDependency(state);
391
438
  return state.value;
392
439
  }
393
440
  get.__signal = state;
394
441
  if (debugName) get.__name = debugName;
395
- function set(next) {
396
- const newValue = typeof next === "function" ? next(state.value) : next;
397
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
398
- if (_isDev3) {
399
- const oldValue = state.value;
442
+ let set;
443
+ if (equalsFn) {
444
+ set = (next) => {
445
+ const prev = state.value;
446
+ const newValue = typeof next === "function" ? next(prev) : next;
447
+ if (equalsFn(prev, newValue)) return;
448
+ state.value = newValue;
449
+ state.__v++;
450
+ if (_isDev3) {
451
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
452
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
453
+ }
454
+ if (!enqueueBatchedSignal(state)) {
455
+ notifySubscribers(state);
456
+ }
457
+ };
458
+ } else if (_isDev3) {
459
+ set = (next) => {
460
+ const prev = state.value;
461
+ const newValue = typeof next === "function" ? next(prev) : next;
462
+ if (Object.is(newValue, prev)) return;
400
463
  state.value = newValue;
464
+ state.__v++;
401
465
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
402
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
403
- } else {
466
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
467
+ if (!enqueueBatchedSignal(state)) {
468
+ notifySubscribers(state);
469
+ }
470
+ };
471
+ } else {
472
+ set = (next) => {
473
+ const prev = state.value;
474
+ const newValue = typeof next === "function" ? next(prev) : next;
475
+ if (Object.is(newValue, prev)) return;
404
476
  state.value = newValue;
405
- }
406
- if (!enqueueBatchedSignal(state)) {
407
- notifySubscribers(state);
408
- }
477
+ state.__v++;
478
+ if (!enqueueBatchedSignal(state)) {
479
+ notifySubscribers(state);
480
+ }
481
+ };
409
482
  }
410
483
  if (_isDev3) {
411
484
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -503,92 +576,122 @@ function isSSR() {
503
576
 
504
577
  // src/core/signals/effect.ts
505
578
  var _g2 = globalThis;
579
+ var MAX_RERUNS = 100;
580
+ function flushUserCleanups(ctx) {
581
+ const list = ctx.userCleanups;
582
+ if (list.length === 0) return;
583
+ ctx.userCleanups = [];
584
+ for (let i = list.length - 1; i >= 0; i--) {
585
+ try {
586
+ list[i]();
587
+ } catch (err) {
588
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
589
+ }
590
+ }
591
+ }
592
+ function drainReruns(ctx) {
593
+ let reruns = 1;
594
+ do {
595
+ ctx.rerunPending = false;
596
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
597
+ retrack(ctx.bodyFn, ctx.subscriber);
598
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
599
+ if (ctx.rerunPending) {
600
+ ctx.rerunPending = false;
601
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
602
+ console.error(
603
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
604
+ );
605
+ }
606
+ }
607
+ }
608
+ function disposeEffect(ctx) {
609
+ if (ctx.disposed) return;
610
+ ctx.disposed = true;
611
+ const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
612
+ if (h) {
613
+ try {
614
+ h.emit("effect:destroy", { effectFn: ctx.fn });
615
+ } catch {
616
+ }
617
+ }
618
+ try {
619
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
620
+ } catch (err) {
621
+ if (typeof console !== "undefined") {
622
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
623
+ }
624
+ }
625
+ try {
626
+ cleanup(ctx.subscriber);
627
+ } catch (err) {
628
+ if (typeof console !== "undefined") {
629
+ console.warn("[SibuJS effect] dispose threw:", err);
630
+ }
631
+ }
632
+ }
506
633
  function effect(effectFn, options) {
507
634
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
508
635
  if (isSSR()) return () => {
509
636
  };
510
- const onError = options?.onError;
511
- let userCleanups = [];
512
- const onCleanup = (fn) => {
513
- userCleanups.push(fn);
637
+ const ctx = {
638
+ fn: effectFn,
639
+ onError: options?.onError,
640
+ userCleanups: [],
641
+ running: false,
642
+ rerunPending: false,
643
+ disposed: false,
644
+ onCleanup: null,
645
+ subscriber: null,
646
+ bodyFn: null
514
647
  };
515
- const runUserCleanups = () => {
516
- if (userCleanups.length === 0) return;
517
- const list = userCleanups;
518
- userCleanups = [];
519
- for (let i = list.length - 1; i >= 0; i--) {
520
- try {
521
- list[i]();
522
- } catch (err) {
523
- if (typeof console !== "undefined") {
524
- console.warn("[SibuJS effect] onCleanup threw:", err);
525
- }
526
- }
527
- }
648
+ ctx.onCleanup = (fn) => {
649
+ ctx.userCleanups.push(fn);
528
650
  };
529
- const invokeBody = () => effectFn(onCleanup);
530
- const wrappedFn = onError ? () => {
651
+ const onErrorCaptured = ctx.onError;
652
+ ctx.bodyFn = onErrorCaptured ? () => {
531
653
  try {
532
- invokeBody();
654
+ ctx.fn(ctx.onCleanup);
533
655
  } catch (err) {
534
- onError(err);
656
+ onErrorCaptured(err);
535
657
  }
536
- } : invokeBody;
537
- let cleanupHandle = () => {
658
+ } : () => {
659
+ ctx.fn(ctx.onCleanup);
538
660
  };
539
- let running = false;
540
- const subscriber = () => {
541
- if (running) {
542
- if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
543
- console.warn(
544
- "[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."
545
- );
546
- }
661
+ const sub = (() => {
662
+ if (ctx.running) {
663
+ ctx.rerunPending = true;
547
664
  return;
548
665
  }
549
- running = true;
666
+ ctx.running = true;
550
667
  try {
551
- runUserCleanups();
552
- cleanupHandle();
553
- cleanupHandle = track(wrappedFn, subscriber);
668
+ ctx.rerunPending = false;
669
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
670
+ retrack(ctx.bodyFn, sub);
671
+ if (ctx.rerunPending) drainReruns(ctx);
554
672
  } finally {
555
- running = false;
673
+ ctx.running = false;
674
+ ctx.rerunPending = false;
556
675
  }
557
- };
558
- running = true;
676
+ });
677
+ sub.depsHead = null;
678
+ sub.depsTail = null;
679
+ sub._epoch = 0;
680
+ sub._structDirty = false;
681
+ sub._runEpoch = 0;
682
+ sub._runs = 0;
683
+ ctx.subscriber = sub;
684
+ ctx.running = true;
559
685
  try {
560
- cleanupHandle = track(wrappedFn, subscriber);
686
+ retrack(ctx.bodyFn, ctx.subscriber);
687
+ if (ctx.rerunPending) drainReruns(ctx);
561
688
  } finally {
562
- running = false;
689
+ ctx.running = false;
690
+ ctx.rerunPending = false;
563
691
  }
564
692
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
565
693
  if (hook) hook.emit("effect:create", { effectFn });
566
- let disposed = false;
567
- return () => {
568
- if (disposed) return;
569
- disposed = true;
570
- const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
571
- if (h) {
572
- try {
573
- h.emit("effect:destroy", { effectFn });
574
- } catch {
575
- }
576
- }
577
- try {
578
- runUserCleanups();
579
- } catch (err) {
580
- if (typeof console !== "undefined") {
581
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
582
- }
583
- }
584
- try {
585
- cleanupHandle();
586
- } catch (err) {
587
- if (typeof console !== "undefined") {
588
- console.warn("[SibuJS effect] dispose threw:", err);
589
- }
590
- }
591
- };
694
+ return () => disposeEffect(ctx);
592
695
  }
593
696
 
594
697
  // src/patterns/persist.ts
@@ -821,6 +924,7 @@ function derived(getter, options) {
821
924
  const cs = {};
822
925
  cs._d = false;
823
926
  cs._g = getter;
927
+ cs.__v = 0;
824
928
  const markDirty = () => {
825
929
  if (cs._d) return;
826
930
  cs._d = true;
@@ -850,11 +954,14 @@ function derived(getter, options) {
850
954
  evaluating = true;
851
955
  let threw = true;
852
956
  try {
957
+ const prev = cs._v;
853
958
  retrack(() => {
854
- cs._v = getter();
959
+ const next = getter();
960
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
855
961
  cs._d = false;
856
962
  threw = false;
857
963
  }, markDirty);
964
+ if (!Object.is(prev, cs._v)) cs.__v++;
858
965
  } finally {
859
966
  evaluating = false;
860
967
  if (threw) cs._d = true;
@@ -874,6 +981,7 @@ function derived(getter, options) {
874
981
  cs._d = false;
875
982
  threw = false;
876
983
  }, markDirty);
984
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
877
985
  } finally {
878
986
  evaluating = false;
879
987
  if (threw) cs._d = true;