sibujs 4.4.0 → 4.6.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 (112) hide show
  1. package/README.md +11 -0
  2. package/dist/browser.cjs +339 -130
  3. package/dist/browser.d.cts +61 -16
  4. package/dist/browser.d.ts +61 -16
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +273 -82
  7. package/dist/build.js +15 -16
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +11 -11
  10. package/dist/cdn.full.global.js +10 -10
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  13. package/dist/chunk-3ISI6ACU.js +44 -0
  14. package/dist/{chunk-VCTAEPSB.js → chunk-47M47FOM.js} +11 -3
  15. package/dist/chunk-5HZXGZ6T.js +24 -0
  16. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  17. package/dist/{chunk-3BTTCZ5J.js → chunk-5Q4R7HCL.js} +42 -4
  18. package/dist/{chunk-ADM46X22.js → chunk-7GQCWFOE.js} +157 -61
  19. package/dist/{chunk-4Z3SPQ67.js → chunk-7NCARGJW.js} +16 -8
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/{chunk-LQFGQNMV.js → chunk-AIF3Z2T7.js} +302 -39
  22. package/dist/{chunk-UCALUKB5.js → chunk-BAAG6ZTI.js} +98 -29
  23. package/dist/{chunk-NVNJH22U.js → chunk-D33YTSX5.js} +2 -2
  24. package/dist/{chunk-M2F7TZHH.js → chunk-DKGBBKOF.js} +136 -80
  25. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  26. package/dist/{chunk-HFCOH2GN.js → chunk-HYXDKS4N.js} +153 -110
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-36S2YPP4.js → chunk-JSXPZCET.js} +1 -1
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-HQSEH5F6.js → chunk-ORMZXBKQ.js} +108 -45
  31. package/dist/{chunk-LYVUX7NT.js → chunk-OXUY2A6L.js} +287 -130
  32. package/dist/{chunk-6GSIXTWX.js → chunk-PK6FK2G2.js} +187 -66
  33. package/dist/{chunk-W2EQ7X2L.js → chunk-QE4TTDU3.js} +47 -9
  34. package/dist/{chunk-XLO7SLIX.js → chunk-SXXVZMKZ.js} +262 -70
  35. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  36. package/dist/{chunk-RR7M3FHM.js → chunk-UZQ6ALFS.js} +3 -3
  37. package/dist/{chunk-PNIRUQ4C.js → chunk-VVWPJ543.js} +6 -8
  38. package/dist/{chunk-7GIHSAWB.js → chunk-XC4MEKGA.js} +3 -3
  39. package/dist/{chunk-SKPTERHP.js → chunk-XRRZKZYX.js} +55 -31
  40. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  41. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  42. package/dist/{customElement-CNZxEB9G.d.ts → customElement-MmInOW1U.d.cts} +59 -10
  43. package/dist/{customElement-CNZxEB9G.d.cts → customElement-MmInOW1U.d.ts} +59 -10
  44. package/dist/data.cjs +293 -125
  45. package/dist/data.d.cts +47 -9
  46. package/dist/data.d.ts +47 -9
  47. package/dist/data.js +12 -9
  48. package/dist/devtools.cjs +121 -58
  49. package/dist/devtools.js +7 -8
  50. package/dist/dispose-GEIG2KOF.js +28 -0
  51. package/dist/ecosystem.cjs +391 -112
  52. package/dist/ecosystem.d.cts +31 -7
  53. package/dist/ecosystem.d.ts +31 -7
  54. package/dist/ecosystem.js +12 -12
  55. package/dist/extras.cjs +2250 -879
  56. package/dist/extras.d.cts +10 -9
  57. package/dist/extras.d.ts +10 -9
  58. package/dist/extras.js +39 -28
  59. package/dist/index.cjs +273 -82
  60. package/dist/index.d.cts +44 -154
  61. package/dist/index.d.ts +44 -154
  62. package/dist/index.js +24 -27
  63. package/dist/motion.cjs +137 -42
  64. package/dist/motion.js +5 -5
  65. package/dist/patterns.cjs +397 -48
  66. package/dist/patterns.d.cts +32 -9
  67. package/dist/patterns.d.ts +32 -9
  68. package/dist/patterns.js +8 -8
  69. package/dist/performance.cjs +338 -217
  70. package/dist/performance.d.cts +2 -2
  71. package/dist/performance.d.ts +2 -2
  72. package/dist/performance.js +8 -9
  73. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  74. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  75. package/dist/plugins.cjs +587 -203
  76. package/dist/plugins.d.cts +3 -3
  77. package/dist/plugins.d.ts +3 -3
  78. package/dist/plugins.js +35 -23
  79. package/dist/signal-EotCj4hS.d.cts +110 -0
  80. package/dist/signal-EotCj4hS.d.ts +110 -0
  81. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  82. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  83. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  84. package/dist/ssr.cjs +214 -73
  85. package/dist/ssr.d.cts +9 -3
  86. package/dist/ssr.d.ts +9 -3
  87. package/dist/ssr.js +11 -12
  88. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  89. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  90. package/dist/tagFactory-DFstCLQV.d.cts +117 -0
  91. package/dist/tagFactory-DkaNVUNV.d.ts +117 -0
  92. package/dist/testing.cjs +2481 -2153
  93. package/dist/testing.d.cts +56 -5
  94. package/dist/testing.d.ts +56 -5
  95. package/dist/testing.js +580 -307
  96. package/dist/ui.cjs +798 -328
  97. package/dist/ui.d.cts +40 -7
  98. package/dist/ui.d.ts +40 -7
  99. package/dist/ui.js +151 -56
  100. package/dist/widgets.cjs +319 -285
  101. package/dist/widgets.js +9 -10
  102. package/package.json +2 -2
  103. package/dist/chunk-2WLZ6757.js +0 -149
  104. package/dist/chunk-7NBDXVHS.js +0 -60
  105. package/dist/chunk-CCSJMTRN.js +0 -15
  106. package/dist/chunk-QKRPLZ2V.js +0 -108
  107. package/dist/chunk-WWV3SJ3L.js +0 -131
  108. package/dist/dispose-46BOMMQJ.js +0 -19
  109. package/dist/plugin-D30wlGW5.d.cts +0 -71
  110. package/dist/plugin-D30wlGW5.d.ts +0 -71
  111. package/dist/tagFactory-Bzupt4Pj.d.cts +0 -55
  112. package/dist/tagFactory-Bzupt4Pj.d.ts +0 -55
package/dist/plugins.cjs CHANGED
@@ -382,12 +382,17 @@ var init_sanitize = __esm({
382
382
  var dispose_exports = {};
383
383
  __export(dispose_exports, {
384
384
  MAX_DRAIN_TEARDOWNS: () => MAX_DRAIN_TEARDOWNS,
385
+ beginDisposerCapture: () => beginDisposerCapture,
385
386
  checkLeaks: () => checkLeaks,
387
+ currentDisposerCapture: () => currentDisposerCapture,
386
388
  dispose: () => dispose,
389
+ disposeNodeOwn: () => disposeNodeOwn,
390
+ endDisposerCapture: () => endDisposerCapture,
387
391
  registerDisposer: () => registerDisposer,
388
392
  replaceChildrenSafely: () => replaceChildrenSafely,
389
393
  reportDrainRunaway: () => reportDrainRunaway,
390
- unregisterDisposer: () => unregisterDisposer
394
+ unregisterDisposer: () => unregisterDisposer,
395
+ withDisposerRollback: () => withDisposerRollback
391
396
  });
392
397
  function reportDrainRunaway(label2, executed, remaining) {
393
398
  if (typeof console === "undefined") return;
@@ -396,6 +401,22 @@ function reportDrainRunaway(label2, executed, remaining) {
396
401
  { executed, remaining }
397
402
  );
398
403
  }
404
+ function resolveCapture(capture) {
405
+ let frame = capture;
406
+ while (frame?.closed) frame = frame.forwardTo;
407
+ return frame ?? null;
408
+ }
409
+ function currentDisposerCapture() {
410
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
411
+ }
412
+ function beginDisposerCapture(capture) {
413
+ if (capture === null && registrationCaptures.length === 0) return false;
414
+ registrationCaptures.push(resolveCapture(capture));
415
+ return true;
416
+ }
417
+ function endDisposerCapture() {
418
+ registrationCaptures.pop();
419
+ }
399
420
  function registerDisposer(node, teardown) {
400
421
  let disposers = elementDisposers.get(node);
401
422
  if (!disposers) {
@@ -404,15 +425,61 @@ function registerDisposer(node, teardown) {
404
425
  }
405
426
  disposers.push(teardown);
406
427
  if (DEV) activeBindingCount++;
428
+ const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
429
+ if (capture) capture.entries.push([node, teardown]);
430
+ }
431
+ function withDisposerRollback(build) {
432
+ const frame = { entries: [], closed: false };
433
+ const captured = frame.entries;
434
+ registrationCaptures.push(frame);
435
+ let result;
436
+ try {
437
+ result = build();
438
+ } catch (err) {
439
+ let executed = 0;
440
+ try {
441
+ while (captured.length > 0) {
442
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
443
+ reportDrainRunaway("rollback", executed, captured.length);
444
+ break;
445
+ }
446
+ const [node, teardown] = captured.pop();
447
+ if (!unregisterDisposer(node, teardown)) continue;
448
+ executed++;
449
+ try {
450
+ teardown();
451
+ } catch (cleanupErr) {
452
+ reportError(cleanupErr, { phase: "cleanup", name: "disposer" });
453
+ }
454
+ }
455
+ } finally {
456
+ frame.closed = true;
457
+ captured.length = 0;
458
+ registrationCaptures.pop();
459
+ }
460
+ throw err;
461
+ }
462
+ frame.closed = true;
463
+ registrationCaptures.pop();
464
+ const parent = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
465
+ if (parent) {
466
+ for (const entry of captured) {
467
+ if (elementDisposers.get(entry[0])?.includes(entry[1])) parent.entries.push(entry);
468
+ }
469
+ frame.forwardTo = parent;
470
+ }
471
+ captured.length = 0;
472
+ return result;
407
473
  }
408
474
  function unregisterDisposer(node, teardown) {
409
475
  const disposers = elementDisposers.get(node);
410
- if (!disposers) return;
476
+ if (!disposers) return false;
411
477
  const index = disposers.indexOf(teardown);
412
- if (index === -1) return;
478
+ if (index === -1) return false;
413
479
  disposers.splice(index, 1);
414
480
  if (DEV) activeBindingCount--;
415
481
  if (disposers.length === 0) elementDisposers.delete(node);
482
+ return true;
416
483
  }
417
484
  function dispose(node) {
418
485
  const stack = [node];
@@ -426,33 +493,34 @@ function dispose(node) {
426
493
  }
427
494
  }
428
495
  for (let i2 = order.length - 1; i2 >= 0; i2--) {
429
- const current = order[i2];
430
- if (elementDisposers.has(current)) {
431
- let executed = 0;
432
- let runaway = false;
433
- while (!runaway) {
434
- const pending = elementDisposers.get(current);
435
- if (!pending || pending.length === 0) break;
436
- const snapshot = pending.slice();
437
- elementDisposers.delete(current);
438
- if (DEV) activeBindingCount -= snapshot.length;
439
- for (let i3 = 0; i3 < snapshot.length; i3++) {
440
- if (executed >= MAX_DRAIN_TEARDOWNS) {
441
- const rest = snapshot.slice(i3);
442
- const added = elementDisposers.get(current);
443
- elementDisposers.set(current, added ? rest.concat(added) : rest);
444
- if (DEV) activeBindingCount += rest.length;
445
- reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
446
- runaway = true;
447
- break;
448
- }
449
- executed++;
450
- try {
451
- snapshot[i3]();
452
- } catch (err) {
453
- reportError(err, { phase: "cleanup", name: "disposer" });
454
- }
455
- }
496
+ disposeNodeOwn(order[i2]);
497
+ }
498
+ }
499
+ function disposeNodeOwn(node) {
500
+ if (!elementDisposers.has(node)) return;
501
+ let executed = 0;
502
+ let runaway = false;
503
+ while (!runaway) {
504
+ const pending = elementDisposers.get(node);
505
+ if (!pending || pending.length === 0) break;
506
+ const snapshot = pending.slice();
507
+ elementDisposers.delete(node);
508
+ if (DEV) activeBindingCount -= snapshot.length;
509
+ for (let i2 = 0; i2 < snapshot.length; i2++) {
510
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
511
+ const rest = snapshot.slice(i2);
512
+ const added = elementDisposers.get(node);
513
+ elementDisposers.set(node, added ? rest.concat(added) : rest);
514
+ if (DEV) activeBindingCount += rest.length;
515
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
516
+ runaway = true;
517
+ break;
518
+ }
519
+ executed++;
520
+ try {
521
+ snapshot[i2]();
522
+ } catch (err) {
523
+ reportError(err, { phase: "cleanup", name: "disposer" });
456
524
  }
457
525
  }
458
526
  }
@@ -477,7 +545,7 @@ function checkLeaks(warnThreshold = 0) {
477
545
  }
478
546
  return activeBindingCount;
479
547
  }
480
- var elementDisposers, MAX_DRAIN_TEARDOWNS, activeBindingCount;
548
+ var elementDisposers, MAX_DRAIN_TEARDOWNS, activeBindingCount, registrationCaptures;
481
549
  var init_dispose = __esm({
482
550
  "src/core/rendering/dispose.ts"() {
483
551
  "use strict";
@@ -486,6 +554,7 @@ var init_dispose = __esm({
486
554
  elementDisposers = /* @__PURE__ */ new WeakMap();
487
555
  MAX_DRAIN_TEARDOWNS = 1e4;
488
556
  activeBindingCount = 0;
557
+ registrationCaptures = [];
489
558
  }
490
559
  });
491
560
 
@@ -985,7 +1054,7 @@ function renderToDocument(component, options = {}) {
985
1054
  <head>
986
1055
  <meta charset="UTF-8" />
987
1056
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
988
- ${options.title ? `<title>${escapeHtml(options.title)}</title>` : ""}
1057
+ ${options.title != null ? `<title>${escapeHtml(options.title)}</title>` : ""}
989
1058
  ${metaTags}
990
1059
  ${linkTags}
991
1060
  ${options.headExtra || ""}
@@ -1159,16 +1228,32 @@ function ssrSuspense(props) {
1159
1228
  const timeoutPromise = new Promise((_, reject) => {
1160
1229
  timer = setTimeout(() => reject(new Error(`[SibuJS SSR] ssrSuspense timed out after ${timeoutMs}ms`)), timeoutMs);
1161
1230
  });
1162
- const raced = Promise.race([props.content(), timeoutPromise]);
1231
+ let contentPromise;
1232
+ try {
1233
+ contentPromise = Promise.resolve(props.content());
1234
+ } catch (err) {
1235
+ contentPromise = Promise.reject(err);
1236
+ }
1237
+ const toFallback = (err) => {
1238
+ if (DEV) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
1239
+ return { id, html: fallbackHtml };
1240
+ };
1241
+ const clearTimer = () => {
1242
+ if (timer !== void 0) clearTimeout(timer);
1243
+ };
1244
+ const raced = Promise.race([contentPromise, timeoutPromise]);
1163
1245
  const promise = raced.then(
1164
1246
  (resolvedEl) => {
1165
- if (timer) clearTimeout(timer);
1166
- return { id, html: renderToString(resolvedEl) };
1247
+ clearTimer();
1248
+ try {
1249
+ return { id, html: renderToString(resolvedEl) };
1250
+ } catch (err) {
1251
+ return toFallback(err);
1252
+ }
1167
1253
  },
1168
1254
  (err) => {
1169
- if (timer) clearTimeout(timer);
1170
- if (DEV) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
1171
- return { id, html: fallbackHtml };
1255
+ clearTimer();
1256
+ return toFallback(err);
1172
1257
  }
1173
1258
  );
1174
1259
  promise.catch(noop);
@@ -1265,7 +1350,9 @@ var init_ssr = __esm({
1265
1350
  var plugins_exports = {};
1266
1351
  __export(plugins_exports, {
1267
1352
  KeepAliveRoute: () => KeepAliveRoute,
1353
+ MigrationStorageError: () => MigrationStorageError,
1268
1354
  Outlet: () => Outlet,
1355
+ PluginInstallCancelledError: () => PluginInstallCancelledError,
1269
1356
  Route: () => Route,
1270
1357
  RouterLink: () => RouterLink,
1271
1358
  SibuRouter: () => SibuRouter,
@@ -1309,6 +1396,7 @@ __export(plugins_exports, {
1309
1396
  healthCheck: () => healthCheck,
1310
1397
  hydrateRouter: () => hydrateRouter,
1311
1398
  inject: () => inject,
1399
+ isPluginInstallCancelledError: () => isPluginInstallCancelledError,
1312
1400
  lazy: () => lazy,
1313
1401
  lazyModule: () => lazyModule,
1314
1402
  navigate: () => navigate,
@@ -1402,6 +1490,14 @@ function setSafeAttribute(el, name, value, options = {}) {
1402
1490
  const html2 = isHtmlElement(el);
1403
1491
  const idlName = html2 ? name.toLowerCase() : name;
1404
1492
  if (typeof value === "boolean") {
1493
+ if (name.length > 5 && name.slice(0, 5).toLowerCase() === "aria-") {
1494
+ const token = value ? "true" : "false";
1495
+ if ((ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name)) !== token) {
1496
+ if (ns) el.setAttributeNS(ns, name, token);
1497
+ else el.setAttribute(name, token);
1498
+ }
1499
+ return true;
1500
+ }
1405
1501
  if (html2 && BOOLEAN_IDL_ATTRS.has(idlName) && idlName in el) {
1406
1502
  setProp(el, idlName, value);
1407
1503
  } else if (value) {
@@ -1432,6 +1528,7 @@ init_dev();
1432
1528
 
1433
1529
  // src/reactivity/track-core.ts
1434
1530
  init_errors();
1531
+ init_dispose();
1435
1532
  var POOL_MAX = 4096;
1436
1533
  var nodePool = [];
1437
1534
  function createNode() {
@@ -1578,8 +1675,18 @@ function untracked(fn) {
1578
1675
  var subscriberEpochCounter = 0;
1579
1676
  function retrack(effectFn, subscriber) {
1580
1677
  const prev = currentSubscriber;
1678
+ let savedDepth = 0;
1679
+ let savedSuspendSub = null;
1680
+ if (trackingSuspended) {
1681
+ savedDepth = suspendDepth;
1682
+ savedSuspendSub = suspendSavedSub;
1683
+ suspendDepth = 0;
1684
+ suspendSavedSub = null;
1685
+ trackingSuspended = false;
1686
+ }
1581
1687
  currentSubscriber = subscriber;
1582
1688
  const sub2 = subscriber;
1689
+ if (sub2._cap === void 0) sub2._cap = currentDisposerCapture();
1583
1690
  const epoch = ++subscriberEpochCounter;
1584
1691
  sub2._epoch = epoch;
1585
1692
  sub2._structDirty = false;
@@ -1592,6 +1699,11 @@ function retrack(effectFn, subscriber) {
1592
1699
  effectFn();
1593
1700
  } finally {
1594
1701
  currentSubscriber = prev;
1702
+ if (savedDepth !== 0) {
1703
+ suspendDepth = savedDepth;
1704
+ suspendSavedSub = savedSuspendSub;
1705
+ trackingSuspended = true;
1706
+ }
1595
1707
  let node = sub2.depsHead ?? null;
1596
1708
  while (node !== null) {
1597
1709
  const next = node.subNext;
@@ -1611,11 +1723,25 @@ function track(effectFn, subscriber) {
1611
1723
  if (!subscriber) return reactiveBinding(effectFn);
1612
1724
  cleanup(subscriber);
1613
1725
  const prev = currentSubscriber;
1726
+ let savedDepth = 0;
1727
+ let savedSuspendSub = null;
1728
+ if (trackingSuspended) {
1729
+ savedDepth = suspendDepth;
1730
+ savedSuspendSub = suspendSavedSub;
1731
+ suspendDepth = 0;
1732
+ suspendSavedSub = null;
1733
+ trackingSuspended = false;
1734
+ }
1614
1735
  currentSubscriber = subscriber;
1615
1736
  try {
1616
1737
  effectFn();
1617
1738
  } finally {
1618
1739
  currentSubscriber = prev;
1740
+ if (savedDepth !== 0) {
1741
+ suspendDepth = savedDepth;
1742
+ suspendSavedSub = savedSuspendSub;
1743
+ trackingSuspended = true;
1744
+ }
1619
1745
  const sub3 = subscriber;
1620
1746
  for (let n = sub3.depsHead ?? null; n !== null; n = n.subNext) {
1621
1747
  const sig = n.sig;
@@ -1752,7 +1878,12 @@ function drainQueue() {
1752
1878
  quarantined.add(sub2);
1753
1879
  continue;
1754
1880
  }
1755
- safeInvoke(sub2);
1881
+ const pushed = beginDisposerCapture(sub2._cap ?? null);
1882
+ try {
1883
+ safeInvoke(sub2);
1884
+ } finally {
1885
+ if (pushed) endDisposerCapture();
1886
+ }
1756
1887
  }
1757
1888
  }
1758
1889
  function drainNotificationQueue() {
@@ -1786,8 +1917,9 @@ function propagateDirty(sub2) {
1786
1917
  if (s2._c) {
1787
1918
  const nSig = s2._sig;
1788
1919
  if (nSig) {
1789
- if (!nSig._d) {
1920
+ if (!nSig._d || nSig._f === true) {
1790
1921
  nSig._d = true;
1922
+ nSig._f = false;
1791
1923
  stack.push(nSig);
1792
1924
  }
1793
1925
  } else {
@@ -1887,7 +2019,7 @@ function forEachSubscriber(signal2, visit) {
1887
2019
  }
1888
2020
 
1889
2021
  // src/reactivity/track.ts
1890
- var _runtimeVersion = true ? "4.4.0" : "dev";
2022
+ var _runtimeVersion = true ? "4.6.0" : "dev";
1891
2023
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1892
2024
  function resolveReactiveApi() {
1893
2025
  const g2 = globalThis;
@@ -2860,6 +2992,15 @@ function effect(effectFn, options) {
2860
2992
 
2861
2993
  // src/plugins/router.ts
2862
2994
  init_sanitize();
2995
+ function splitRouteUrl(url) {
2996
+ const hashIndex = url.indexOf("#");
2997
+ const beforeHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
2998
+ const hash = hashIndex === -1 ? "" : url.slice(hashIndex + 1);
2999
+ const queryIndex = beforeHash.indexOf("?");
3000
+ const path2 = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
3001
+ const query = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
3002
+ return { path: path2, query, hash };
3003
+ }
2863
3004
  function classifyNavigationTarget(path2) {
2864
3005
  if (path2 === "") return "internal";
2865
3006
  const normalized = stripControlChars(path2).replace(/\\/g, "/");
@@ -3578,8 +3719,7 @@ var _SibuRouter = class _SibuRouter {
3578
3719
  return (path2 || "/") + window.location.search + window.location.hash;
3579
3720
  }
3580
3721
  createRouteContext(fullPath) {
3581
- const [pathWithQuery, hash = ""] = fullPath.split("#");
3582
- const [path2, queryString = ""] = pathWithQuery.split("?");
3722
+ const { path: path2, query: queryString, hash } = splitRouteUrl(fullPath);
3583
3723
  const query = Object.fromEntries(new URLSearchParams(queryString));
3584
3724
  const match = this.matcher.match(path2 || "/");
3585
3725
  const params = match?.params || {};
@@ -4326,8 +4466,7 @@ function RouterLink(props, children) {
4326
4466
  const rawHref = _routerRef.current["resolvePath"](to);
4327
4467
  const kind = classifyNavigationTarget(rawHref);
4328
4468
  const href = kind === "unsafe" ? "#" : rawHref;
4329
- const [hrefBeforeHash, hrefHash = ""] = href.split("#");
4330
- const [hrefPathRaw, hrefQueryRaw = ""] = hrefBeforeHash.split("?");
4469
+ const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
4331
4470
  const targetPath = normalizePathname(hrefPathRaw);
4332
4471
  const targetQuery = normalizeQuery(hrefQueryRaw);
4333
4472
  const link2 = document.createElement("a");
@@ -4968,37 +5107,152 @@ function escapeAttrLocal(str) {
4968
5107
  }
4969
5108
 
4970
5109
  // src/plugins/plugin.ts
5110
+ init_dev();
5111
+ init_errors();
5112
+
5113
+ // src/utils/adoptThenable.ts
5114
+ function adoptThenable(value) {
5115
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
5116
+ let then;
5117
+ try {
5118
+ then = value.then;
5119
+ } catch (error) {
5120
+ return Promise.reject(error);
5121
+ }
5122
+ if (typeof then !== "function") return null;
5123
+ return new Promise((resolve, reject) => {
5124
+ queueMicrotask(() => {
5125
+ try {
5126
+ Reflect.apply(then, value, [resolve, reject]);
5127
+ } catch (error) {
5128
+ reject(error);
5129
+ }
5130
+ });
5131
+ });
5132
+ }
5133
+
5134
+ // src/plugins/plugin.ts
5135
+ var CANCELLED_BRAND = /* @__PURE__ */ Symbol.for("sibujs.plugins.installCancelled.v1");
5136
+ var _a;
5137
+ var PluginInstallCancelledError = class extends Error {
5138
+ constructor(pluginName) {
5139
+ super(`[Plugin] Installation of "${pluginName}" was cancelled before it was committed (registry reset).`);
5140
+ /**
5141
+ * Brand, not identity: the default registry is shared across duplicate copies
5142
+ * of this module, so the error a caller receives may come from another copy's
5143
+ * constructor. `instanceof` is taught to recognise the brand instead.
5144
+ */
5145
+ this[_a] = true;
5146
+ this.name = "PluginInstallCancelledError";
5147
+ this.pluginName = pluginName;
5148
+ }
5149
+ static [(_a = CANCELLED_BRAND, Symbol.hasInstance)](value) {
5150
+ return isPluginInstallCancelledError(value);
5151
+ }
5152
+ };
5153
+ function isPluginInstallCancelledError(value) {
5154
+ return typeof value === "object" && value !== null && value[CANCELLED_BRAND] === true;
5155
+ }
4971
5156
  function createPluginRegistry() {
4972
5157
  const installedPlugins = /* @__PURE__ */ new Set();
4973
5158
  const hooks = { init: [], mount: [], unmount: [], error: [] };
4974
5159
  const provided = /* @__PURE__ */ new Map();
5160
+ const installStack = /* @__PURE__ */ new Set();
5161
+ const pendingInstalls = /* @__PURE__ */ new Map();
5162
+ let generation = 0;
4975
5163
  const registry = {
4976
5164
  installedPlugins,
4977
5165
  hooks,
4978
5166
  provided,
5167
+ /**
5168
+ * Install a plugin as a transaction.
5169
+ *
5170
+ * Hooks and providers registered through `ctx` are staged and committed
5171
+ * only if `install()` returns; a throwing install leaves the registry
5172
+ * exactly as it found it (and can simply be retried). The plugin is marked
5173
+ * installed at commit, before its init hooks run.
5174
+ *
5175
+ * Each `plugin()` call is its own transaction: a dependency installed by a
5176
+ * nested `plugin()` call commits on its own and stays installed even if the
5177
+ * outer installation later fails.
5178
+ */
4979
5179
  plugin(p2, options) {
4980
5180
  if (installedPlugins.has(p2.name)) {
4981
5181
  console.warn(`[Plugin] "${p2.name}" is already installed.`);
4982
5182
  return;
4983
5183
  }
5184
+ if (installStack.has(p2.name)) {
5185
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (recursive installation).`);
5186
+ }
5187
+ if (pendingInstalls.has(p2.name)) {
5188
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (asynchronous installation in flight).`);
5189
+ }
5190
+ const staged = { init: [], mount: [], unmount: [], error: [] };
5191
+ const stagedProvided = /* @__PURE__ */ new Map();
5192
+ let committed = false;
5193
+ const myGeneration = generation;
5194
+ const stale = () => {
5195
+ if (myGeneration === generation) return false;
5196
+ if (DEV) devWarn(`plugin: "${p2.name}" registered after the registry was reset; the registration was ignored.`);
5197
+ return true;
5198
+ };
5199
+ const target = () => stale() ? null : committed ? hooks : staged;
4984
5200
  const ctx = {
4985
- onInit: (cb) => hooks.init.push(cb),
4986
- onMount: (cb) => hooks.mount.push(cb),
4987
- onUnmount: (cb) => hooks.unmount.push(cb),
4988
- onError: (cb) => hooks.error.push(cb),
4989
- provide: (key, value) => provided.set(key, value)
5201
+ onInit: (cb) => void target()?.init.push(cb),
5202
+ onMount: (cb) => void target()?.mount.push(cb),
5203
+ onUnmount: (cb) => void target()?.unmount.push(cb),
5204
+ onError: (cb) => void target()?.error.push(cb),
5205
+ provide: (key, value) => {
5206
+ if (stale()) return;
5207
+ (committed ? provided : stagedProvided).set(key, value);
5208
+ }
4990
5209
  };
4991
- const initHooksBefore = hooks.init.length;
4992
- p2.install(ctx, options);
4993
- installedPlugins.add(p2.name);
4994
- const justAdded = hooks.init.slice(initHooksBefore);
4995
- for (const cb of justAdded) {
4996
- try {
4997
- cb();
4998
- } catch (e) {
4999
- console.error(`[Plugin] "${p2.name}" init error:`, e);
5210
+ const commit = () => {
5211
+ if (myGeneration !== generation) return false;
5212
+ hooks.init.push(...staged.init);
5213
+ hooks.mount.push(...staged.mount);
5214
+ hooks.unmount.push(...staged.unmount);
5215
+ hooks.error.push(...staged.error);
5216
+ for (const [key, value] of stagedProvided) provided.set(key, value);
5217
+ installedPlugins.add(p2.name);
5218
+ committed = true;
5219
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
5220
+ for (const cb of staged.init.slice()) {
5221
+ try {
5222
+ cb();
5223
+ } catch (e) {
5224
+ console.error(`[Plugin] "${p2.name}" init error:`, e);
5225
+ }
5000
5226
  }
5227
+ return true;
5228
+ };
5229
+ installStack.add(p2.name);
5230
+ let pending;
5231
+ try {
5232
+ pending = adoptThenable(p2.install(ctx, options));
5233
+ } finally {
5234
+ installStack.delete(p2.name);
5001
5235
  }
5236
+ if (!pending) {
5237
+ if (!commit()) throw new PluginInstallCancelledError(p2.name);
5238
+ return;
5239
+ }
5240
+ if (myGeneration === generation) pendingInstalls.set(p2.name, myGeneration);
5241
+ const settled = pending.then(
5242
+ () => {
5243
+ if (commit()) return;
5244
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
5245
+ throw new PluginInstallCancelledError(p2.name);
5246
+ },
5247
+ (err) => {
5248
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
5249
+ reportError(err, { phase: "async", name: `plugin(${p2.name})` });
5250
+ throw err;
5251
+ }
5252
+ );
5253
+ settled.catch(() => {
5254
+ });
5255
+ return settled;
5002
5256
  },
5003
5257
  inject(key, defaultValue) {
5004
5258
  if (provided.has(key)) return provided.get(key);
@@ -5036,6 +5290,8 @@ function createPluginRegistry() {
5036
5290
  }
5037
5291
  },
5038
5292
  reset() {
5293
+ generation++;
5294
+ pendingInstalls.clear();
5039
5295
  installedPlugins.clear();
5040
5296
  hooks.init.length = 0;
5041
5297
  hooks.mount.length = 0;
@@ -5055,7 +5311,7 @@ function createPlugin(name, install) {
5055
5311
  }
5056
5312
  function plugin(plugin2, options) {
5057
5313
  _defaults.touched = true;
5058
- _defaults.registry.plugin(plugin2, options);
5314
+ return _defaults.registry.plugin(plugin2, options);
5059
5315
  }
5060
5316
  function inject(key, defaultValue) {
5061
5317
  return _defaults.registry.inject(key, defaultValue);
@@ -5086,35 +5342,44 @@ function setDefaultPluginRegistry(registry) {
5086
5342
  // src/plugins/modular.ts
5087
5343
  function createModuleRegistry() {
5088
5344
  const modules = /* @__PURE__ */ new Map();
5089
- function resolveInternal(name, stack) {
5345
+ const resolving = [];
5346
+ function resolveInternal(name) {
5090
5347
  const entry = modules.get(name);
5091
5348
  if (!entry) {
5092
5349
  throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
5093
5350
  }
5094
- if (entry.loaded) {
5351
+ if (entry.state === "loaded") {
5095
5352
  return entry.value;
5096
5353
  }
5097
- if (stack.has(name)) {
5098
- const cycle = [...stack, name].join(" -> ");
5354
+ if (entry.state === "resolving") {
5355
+ const cycle = [...resolving.slice(resolving.indexOf(name)), name].join(" -> ");
5099
5356
  throw new Error(`[ModuleRegistry] Circular dependency detected: ${cycle}`);
5100
5357
  }
5101
- stack.add(name);
5102
- for (const dep of entry.deps) {
5103
- resolveInternal(dep, stack);
5358
+ entry.state = "resolving";
5359
+ resolving.push(name);
5360
+ try {
5361
+ for (const dep of entry.deps) {
5362
+ resolveInternal(dep);
5363
+ }
5364
+ entry.value = entry.factory();
5365
+ entry.state = "loaded";
5366
+ return entry.value;
5367
+ } catch (err) {
5368
+ entry.state = "unloaded";
5369
+ entry.value = void 0;
5370
+ throw err;
5371
+ } finally {
5372
+ resolving.pop();
5104
5373
  }
5105
- stack.delete(name);
5106
- entry.value = entry.factory();
5107
- entry.loaded = true;
5108
- return entry.value;
5109
5374
  }
5110
5375
  return {
5111
5376
  /** Register a module with its factory function and optional dependencies */
5112
5377
  register(name, factory, deps = []) {
5113
- modules.set(name, { factory, deps, loaded: false });
5378
+ modules.set(name, { factory, deps, state: "unloaded" });
5114
5379
  },
5115
5380
  /** Resolve a module, loading its dependencies first */
5116
5381
  resolve(name) {
5117
- return resolveInternal(name, /* @__PURE__ */ new Set());
5382
+ return resolveInternal(name);
5118
5383
  },
5119
5384
  /** Check if a module is registered */
5120
5385
  has(name) {
@@ -5149,7 +5414,7 @@ function createModuleRegistry() {
5149
5414
  /** Reset all loaded modules back to unloaded state (useful for testing) */
5150
5415
  reset() {
5151
5416
  for (const entry of modules.values()) {
5152
- entry.loaded = false;
5417
+ entry.state = "unloaded";
5153
5418
  entry.value = void 0;
5154
5419
  }
5155
5420
  }
@@ -5177,46 +5442,83 @@ function createBundle(modules) {
5177
5442
  function lazyModule(loader) {
5178
5443
  let cached;
5179
5444
  let loadedFlag = false;
5180
- const handle = {
5445
+ let inFlight = null;
5446
+ return {
5181
5447
  get loaded() {
5182
5448
  return loadedFlag;
5183
5449
  },
5184
- async get() {
5185
- if (loadedFlag) return cached;
5186
- cached = await loader();
5187
- loadedFlag = true;
5188
- return cached;
5450
+ get() {
5451
+ if (loadedFlag) return Promise.resolve(cached);
5452
+ if (inFlight) return inFlight;
5453
+ let started;
5454
+ try {
5455
+ started = Promise.resolve(loader());
5456
+ } catch (err) {
5457
+ started = Promise.reject(err);
5458
+ }
5459
+ const attempt = started.then(
5460
+ (value) => {
5461
+ cached = value;
5462
+ loadedFlag = true;
5463
+ if (inFlight === attempt) inFlight = null;
5464
+ return value;
5465
+ },
5466
+ (err) => {
5467
+ if (inFlight === attempt) inFlight = null;
5468
+ throw err;
5469
+ }
5470
+ );
5471
+ inFlight = attempt;
5472
+ return attempt;
5189
5473
  }
5190
5474
  };
5191
- return handle;
5192
5475
  }
5476
+ var MODULE_ENTRY_POINTS = {
5477
+ main: "./index.ts",
5478
+ data: "./data.ts",
5479
+ browser: "./browser.ts",
5480
+ patterns: "./patterns.ts",
5481
+ motion: "./motion.ts",
5482
+ ui: "./ui.ts",
5483
+ widgets: "./widgets.ts",
5484
+ ssr: "./ssr.ts",
5485
+ devtools: "./devtools.ts",
5486
+ performance: "./performance.ts",
5487
+ ecosystem: "./ecosystem.ts",
5488
+ plugins: "./plugins.ts",
5489
+ build: "./build.ts",
5490
+ testing: "./testing.ts",
5491
+ extras: "./extras.ts"
5492
+ };
5493
+ var CDN_ENTRY_POINTS = {
5494
+ cdn: "./dist/cdn.global.js",
5495
+ "cdn-dev": "./dist/cdn.dev.global.js",
5496
+ "cdn-full": "./dist/cdn.full.global.js",
5497
+ "cdn-full-dev": "./dist/cdn.full.dev.global.js"
5498
+ };
5193
5499
  var packageInfo = {
5194
- name: "sibu",
5195
- version: "1.0.0",
5196
- entryPoints: {
5197
- main: "./index.ts",
5198
- core: "./src/core/html.ts",
5199
- hooks: "./src/core/signals/signal.ts",
5200
- router: "./src/plugins/router.ts",
5201
- i18n: "./src/plugins/i18n.ts",
5202
- testing: "./src/testing/index.ts",
5203
- ssr: "./src/core/ssr.ts"
5204
- },
5500
+ name: "sibujs",
5501
+ version: true ? "4.6.0" : "dev",
5502
+ entryPoints: MODULE_ENTRY_POINTS,
5205
5503
  /**
5206
- * Generate a package.json `exports` map for Node.js subpath exports.
5207
- * Maps each entry point to its import, require, and types paths.
5504
+ * Generate the package.json `exports` map. Module entries resolve to the
5505
+ * `.js` (ESM), `.cjs` and `.d.ts` files tsup emits into `dist/`; CDN entries
5506
+ * resolve to their prebuilt global script.
5208
5507
  */
5209
5508
  generateExportsMap() {
5210
5509
  const exportsMap = {};
5211
- for (const [name, sourcePath] of Object.entries(this.entryPoints)) {
5510
+ for (const [name, sourcePath] of Object.entries(MODULE_ENTRY_POINTS)) {
5212
5511
  const distPath = sourcePath.replace(/^\.\//, "./dist/").replace(/\.ts$/, "");
5213
5512
  const subpath = name === "main" ? "." : `./${name}`;
5214
5513
  exportsMap[subpath] = {
5215
- import: `${distPath}.mjs`,
5216
- require: `${distPath}.cjs`,
5217
- types: `${distPath}.d.ts`
5514
+ types: `${distPath}.d.ts`,
5515
+ import: `${distPath}.js`,
5516
+ require: `${distPath}.cjs`
5218
5517
  };
5219
5518
  }
5519
+ for (const [name, file] of Object.entries(CDN_ENTRY_POINTS)) {
5520
+ exportsMap[`./${name}`] = { default: file };
5521
+ }
5220
5522
  return exportsMap;
5221
5523
  }
5222
5524
  };
@@ -5341,29 +5643,26 @@ var env = {
5341
5643
  };
5342
5644
 
5343
5645
  // src/plugins/versioning.ts
5344
- var VERSION = "1.0.0";
5646
+ var VERSION = true ? "4.6.0" : "dev";
5647
+ var NUM = "(?:0|[1-9]\\d*)";
5648
+ var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
5649
+ var BUILD_ID = "[0-9a-zA-Z-]+";
5650
+ var SEMVER_PATTERN = new RegExp(
5651
+ `^(${NUM})(?:\\.(${NUM})(?:\\.(${NUM})(?:-(${PRE_ID}(?:\\.${PRE_ID})*))?(?:\\+(${BUILD_ID}(?:\\.${BUILD_ID})*))?)?)?$`
5652
+ );
5345
5653
  function parseSemVer(version) {
5346
- const trimmed = version.trim().replace(/^v/i, "");
5347
- const prereleaseIndex = trimmed.indexOf("-");
5348
- let main2;
5349
- let prerelease;
5350
- if (prereleaseIndex !== -1) {
5351
- main2 = trimmed.slice(0, prereleaseIndex);
5352
- prerelease = trimmed.slice(prereleaseIndex + 1);
5353
- } else {
5354
- main2 = trimmed;
5355
- }
5356
- const parts = main2.split(".");
5357
- const major = parseInt(parts[0], 10);
5358
- const minor = parseInt(parts[1] || "0", 10);
5359
- const patch = parseInt(parts[2] || "0", 10);
5360
- if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
5654
+ const trimmed = String(version).trim().replace(/^v/i, "");
5655
+ const match = SEMVER_PATTERN.exec(trimmed);
5656
+ if (!match) {
5361
5657
  throw new Error(`[Versioning] Invalid semver string: "${version}"`);
5362
5658
  }
5363
- const result = { major, minor, patch };
5364
- if (prerelease !== void 0) {
5365
- result.prerelease = prerelease;
5366
- }
5659
+ const result = {
5660
+ major: Number(match[1]),
5661
+ minor: match[2] === void 0 ? 0 : Number(match[2]),
5662
+ patch: match[3] === void 0 ? 0 : Number(match[3])
5663
+ };
5664
+ if (match[4] !== void 0) result.prerelease = match[4];
5665
+ if (match[5] !== void 0) result.build = match[5];
5367
5666
  return result;
5368
5667
  }
5369
5668
  function compareSemVer(a2, b2) {
@@ -5446,82 +5745,154 @@ function satisfies(version, range) {
5446
5745
  const target = parseSemVer(exactStr);
5447
5746
  return compareSemVer(v, target) === 0;
5448
5747
  }
5748
+ var MigrationStorageError = class extends Error {
5749
+ constructor(version, cause) {
5750
+ const reason = cause instanceof Error ? cause.message : String(cause);
5751
+ super(`[Versioning] Migration ${version} completed, but recording it in storage failed: ${reason}`);
5752
+ this.name = "MigrationStorageError";
5753
+ this.version = version;
5754
+ this.cause = cause;
5755
+ }
5756
+ };
5757
+ var _migrationQueues = globalSingleton(
5758
+ /* @__PURE__ */ Symbol.for("sibujs.migrationQueues.v1"),
5759
+ () => /* @__PURE__ */ new WeakMap()
5760
+ );
5761
+ var NO_STORAGE = {};
5449
5762
  function createMigrationRunner(config) {
5450
5763
  const storageKey = config.storageKey || "__sibu_migration_version__";
5451
5764
  const sortedMigrations = [...config.migrations].sort((a2, b2) => compareSemVer(a2.version, b2.version));
5452
5765
  function getStorage() {
5766
+ if (config.storage) return config.storage;
5453
5767
  try {
5454
5768
  return typeof localStorage !== "undefined" ? localStorage : null;
5455
5769
  } catch {
5456
5770
  return null;
5457
5771
  }
5458
5772
  }
5773
+ function getAppliedVersion() {
5774
+ const storage = getStorage();
5775
+ if (!storage) return null;
5776
+ return storage.getItem(storageKey);
5777
+ }
5778
+ function getPending() {
5779
+ const appliedVersion = getAppliedVersion();
5780
+ if (!appliedVersion) {
5781
+ return sortedMigrations.filter((m) => compareSemVer(m.version, config.currentVersion) <= 0);
5782
+ }
5783
+ return sortedMigrations.filter(
5784
+ (m) => compareSemVer(m.version, appliedVersion) > 0 && compareSemVer(m.version, config.currentVersion) <= 0
5785
+ );
5786
+ }
5787
+ function writeApplied(version, migrationVersion) {
5788
+ const storage = getStorage();
5789
+ if (!storage) return;
5790
+ try {
5791
+ if (version === null) storage.removeItem(storageKey);
5792
+ else storage.setItem(storageKey, version);
5793
+ } catch (e) {
5794
+ throw new MigrationStorageError(migrationVersion, e);
5795
+ }
5796
+ }
5797
+ function serialize(operation) {
5798
+ const identity = getStorage() ?? NO_STORAGE;
5799
+ let queues = _migrationQueues.get(identity);
5800
+ if (!queues) {
5801
+ queues = /* @__PURE__ */ new Map();
5802
+ _migrationQueues.set(identity, queues);
5803
+ }
5804
+ const run = (queues.get(storageKey) ?? Promise.resolve()).then(operation, operation);
5805
+ const tail = run.then(
5806
+ () => void 0,
5807
+ () => void 0
5808
+ );
5809
+ queues.set(storageKey, tail);
5810
+ const settled = queues;
5811
+ tail.then(() => {
5812
+ if (settled.get(storageKey) === tail) settled.delete(storageKey);
5813
+ });
5814
+ return run;
5815
+ }
5459
5816
  return {
5460
5817
  /** Get the last applied migration version from storage */
5461
- getAppliedVersion() {
5462
- const storage = getStorage();
5463
- if (!storage) return null;
5464
- return storage.getItem(storageKey);
5465
- },
5818
+ getAppliedVersion,
5466
5819
  /** Get pending migrations that haven't been applied */
5467
- getPending() {
5468
- const appliedVersion = this.getAppliedVersion();
5469
- if (!appliedVersion) {
5470
- return sortedMigrations.filter((m) => compareSemVer(m.version, config.currentVersion) <= 0);
5471
- }
5472
- return sortedMigrations.filter(
5473
- (m) => compareSemVer(m.version, appliedVersion) > 0 && compareSemVer(m.version, config.currentVersion) <= 0
5474
- );
5475
- },
5476
- /** Run all pending migrations in order */
5477
- async migrate() {
5478
- const pending = this.getPending();
5479
- const applied = [];
5480
- const errors = [];
5481
- for (const migration of pending) {
5820
+ getPending,
5821
+ /**
5822
+ * Run all pending migrations in order. Concurrent calls (and calls racing
5823
+ * `rollback()`) run one after another, each recomputing what is pending —
5824
+ * on this runner and on every other runner in this realm sharing its
5825
+ * storage and storage key.
5826
+ */
5827
+ migrate() {
5828
+ return serialize(async () => {
5829
+ const applied = [];
5830
+ const errors = [];
5831
+ let pending;
5482
5832
  try {
5483
- await migration.up();
5484
- applied.push(migration.version);
5485
- const storage = getStorage();
5486
- if (storage) {
5487
- storage.setItem(storageKey, migration.version);
5488
- }
5833
+ pending = getPending();
5489
5834
  } catch (e) {
5490
5835
  errors.push({
5491
- version: migration.version,
5836
+ version: getAppliedVersion() ?? "",
5492
5837
  error: e instanceof Error ? e : new Error(String(e))
5493
5838
  });
5494
- break;
5839
+ return { applied, errors };
5495
5840
  }
5496
- }
5497
- return { applied, errors };
5841
+ for (const migration of pending) {
5842
+ try {
5843
+ await migration.up();
5844
+ } catch (e) {
5845
+ errors.push({
5846
+ version: migration.version,
5847
+ error: e instanceof Error ? e : new Error(String(e))
5848
+ });
5849
+ break;
5850
+ }
5851
+ applied.push(migration.version);
5852
+ try {
5853
+ writeApplied(migration.version, migration.version);
5854
+ } catch (e) {
5855
+ errors.push({ version: migration.version, error: e });
5856
+ break;
5857
+ }
5858
+ }
5859
+ return { applied, errors };
5860
+ });
5498
5861
  },
5499
- /** Rollback to a specific version */
5500
- async rollback(targetVersion) {
5501
- const appliedVersion = this.getAppliedVersion();
5502
- const rolledBack = [];
5503
- if (!appliedVersion) {
5504
- return { rolledBack };
5505
- }
5506
- const toRollback = sortedMigrations.filter((m) => compareSemVer(m.version, targetVersion) > 0 && compareSemVer(m.version, appliedVersion) <= 0).reverse();
5507
- for (const migration of toRollback) {
5508
- if (!migration.down) {
5509
- throw new Error(
5510
- `[Versioning] Migration ${migration.version} ("${migration.description}") does not have a down() function and cannot be rolled back.`
5511
- );
5862
+ /**
5863
+ * Rollback to a specific version.
5864
+ *
5865
+ * The applied version is checkpointed after EVERY successful `down()`, so a
5866
+ * rollback that fails part-way leaves storage describing what is actually
5867
+ * still applied, and a retry does not repeat completed `down()` steps.
5868
+ * Throws the failing `down()`'s error, a missing-`down()` error, or a
5869
+ * {@link MigrationStorageError} if a checkpoint cannot be written.
5870
+ */
5871
+ rollback(targetVersion) {
5872
+ return serialize(async () => {
5873
+ const appliedVersion = getAppliedVersion();
5874
+ const rolledBack = [];
5875
+ if (!appliedVersion) {
5876
+ return { rolledBack };
5512
5877
  }
5513
- await migration.down();
5514
- rolledBack.push(migration.version);
5515
- }
5516
- const storage = getStorage();
5517
- if (storage) {
5518
- if (targetVersion === "0.0.0") {
5519
- storage.removeItem(storageKey);
5520
- } else {
5521
- storage.setItem(storageKey, targetVersion);
5878
+ const toRollback = sortedMigrations.filter((m) => compareSemVer(m.version, targetVersion) > 0 && compareSemVer(m.version, appliedVersion) <= 0).reverse();
5879
+ for (const migration of toRollback) {
5880
+ if (!migration.down) {
5881
+ throw new Error(
5882
+ `[Versioning] Migration ${migration.version} ("${migration.description}") does not have a down() function and cannot be rolled back.`
5883
+ );
5884
+ }
5522
5885
  }
5523
- }
5524
- return { rolledBack };
5886
+ for (const migration of toRollback) {
5887
+ await migration.down();
5888
+ rolledBack.push(migration.version);
5889
+ const index = sortedMigrations.indexOf(migration);
5890
+ const previous = index > 0 ? sortedMigrations[index - 1].version : null;
5891
+ writeApplied(previous, migration.version);
5892
+ }
5893
+ writeApplied(targetVersion === "0.0.0" ? null : targetVersion, targetVersion);
5894
+ return { rolledBack };
5895
+ });
5525
5896
  }
5526
5897
  };
5527
5898
  }
@@ -5552,6 +5923,7 @@ function extractRangeTarget(range) {
5552
5923
  }
5553
5924
 
5554
5925
  // src/plugins/startup.ts
5926
+ init_errors();
5555
5927
  init_ssr();
5556
5928
  function preloadCritical(resources) {
5557
5929
  if (typeof document === "undefined") return;
@@ -5572,28 +5944,43 @@ function preloadCritical(resources) {
5572
5944
  document.head.appendChild(link2);
5573
5945
  }
5574
5946
  }
5575
- function prerenderRoutes(routes, options) {
5576
- const maxCacheSize = options?.maxCacheSize ?? 50;
5577
- const cacheTTL = options?.cacheTTL ?? 0;
5578
- const cache = /* @__PURE__ */ new Map();
5579
- function isValid(entry) {
5580
- if (cacheTTL === 0) return true;
5581
- return Date.now() - entry.timestamp < cacheTTL;
5947
+ function assertCacheSize(name, value) {
5948
+ if (!Number.isInteger(value) || value < 0) {
5949
+ throw new RangeError(`[Startup] ${name} must be a non-negative integer, got ${value}`);
5582
5950
  }
5583
- function evictOldest() {
5584
- if (cache.size < maxCacheSize) return;
5951
+ }
5952
+ function makeRoomFor(cache, key, maxSize, isValid) {
5953
+ if (maxSize === 0) return false;
5954
+ if (cache.has(key)) return true;
5955
+ if (cache.size < maxSize) return true;
5956
+ for (const [k, entry] of cache) {
5957
+ if (!isValid(entry)) cache.delete(k);
5958
+ }
5959
+ while (cache.size >= maxSize) {
5585
5960
  let oldestKey = null;
5586
5961
  let oldestTime = Infinity;
5587
- for (const [key, entry] of cache) {
5962
+ for (const [k, entry] of cache) {
5588
5963
  if (entry.timestamp < oldestTime) {
5589
5964
  oldestTime = entry.timestamp;
5590
- oldestKey = key;
5965
+ oldestKey = k;
5591
5966
  }
5592
5967
  }
5593
- if (oldestKey) cache.delete(oldestKey);
5968
+ if (oldestKey === null) break;
5969
+ cache.delete(oldestKey);
5970
+ }
5971
+ return true;
5972
+ }
5973
+ function prerenderRoutes(routes, options) {
5974
+ const maxCacheSize = options?.maxCacheSize ?? 50;
5975
+ const cacheTTL = options?.cacheTTL ?? 0;
5976
+ assertCacheSize("maxCacheSize", maxCacheSize);
5977
+ const cache = /* @__PURE__ */ new Map();
5978
+ function isValid(entry) {
5979
+ if (cacheTTL === 0) return true;
5980
+ return Date.now() - entry.timestamp < cacheTTL;
5594
5981
  }
5595
5982
  for (const route2 of routes) {
5596
- evictOldest();
5983
+ if (!makeRoomFor(cache, route2.path, maxCacheSize, isValid)) break;
5597
5984
  const html2 = renderToString(route2.component());
5598
5985
  cache.set(route2.path, { html: html2, timestamp: Date.now() });
5599
5986
  }
@@ -5645,6 +6032,7 @@ function prerenderRoutes(routes, options) {
5645
6032
  function createSSRCache(config) {
5646
6033
  const maxSize = config?.maxSize ?? 100;
5647
6034
  const defaultTTL = config?.defaultTTL ?? 6e4;
6035
+ assertCacheSize("maxSize", maxSize);
5648
6036
  const cache = /* @__PURE__ */ new Map();
5649
6037
  let hits = 0;
5650
6038
  let misses = 0;
@@ -5652,18 +6040,6 @@ function createSSRCache(config) {
5652
6040
  if (entry.ttl === 0) return true;
5653
6041
  return Date.now() - entry.timestamp < entry.ttl;
5654
6042
  }
5655
- function evictOldest() {
5656
- if (cache.size < maxSize) return;
5657
- let oldestKey = null;
5658
- let oldestTime = Infinity;
5659
- for (const [key, entry] of cache) {
5660
- if (entry.timestamp < oldestTime) {
5661
- oldestTime = entry.timestamp;
5662
- oldestKey = key;
5663
- }
5664
- }
5665
- if (oldestKey) cache.delete(oldestKey);
5666
- }
5667
6043
  return {
5668
6044
  /** Get cached HTML for a key, or undefined if not cached or expired */
5669
6045
  get(key) {
@@ -5682,7 +6058,7 @@ function createSSRCache(config) {
5682
6058
  },
5683
6059
  /** Cache an HTML string with an optional TTL override */
5684
6060
  set(key, html2, ttl) {
5685
- evictOldest();
6061
+ if (!makeRoomFor(cache, key, maxSize, isValid)) return;
5686
6062
  cache.set(key, {
5687
6063
  html: html2,
5688
6064
  timestamp: Date.now(),
@@ -5715,25 +6091,30 @@ function createSSRCache(config) {
5715
6091
  }
5716
6092
  };
5717
6093
  }
6094
+ var DEFER_IDLE_TIMEOUT_MS = 2e3;
5718
6095
  function deferNonCritical(tasks) {
5719
6096
  if (tasks.length === 0) return;
5720
- const schedule = typeof requestIdleCallback !== "undefined" ? requestIdleCallback : (cb) => setTimeout(cb, 1);
6097
+ const schedule = typeof requestIdleCallback !== "undefined" ? (cb) => {
6098
+ requestIdleCallback(cb, { timeout: DEFER_IDLE_TIMEOUT_MS });
6099
+ } : (cb) => {
6100
+ setTimeout(cb, 1);
6101
+ };
5721
6102
  const queue = [...tasks];
5722
6103
  let index = 0;
5723
6104
  function processNext(deadline) {
6105
+ let ranOne = false;
5724
6106
  while (index < queue.length) {
5725
- if (deadline && deadline.timeRemaining() < 1) {
6107
+ if (ranOne && deadline && deadline.timeRemaining() < 1) {
5726
6108
  schedule(processNext);
5727
6109
  return;
5728
6110
  }
5729
6111
  try {
5730
6112
  queue[index]();
5731
6113
  } catch (e) {
5732
- if (typeof console !== "undefined") {
5733
- console.error("[Startup] Deferred task failed:", e);
5734
- }
6114
+ reportError(e, { phase: "scheduler", name: "deferNonCritical" });
5735
6115
  }
5736
6116
  index++;
6117
+ ranOne = true;
5737
6118
  }
5738
6119
  }
5739
6120
  schedule(processNext);
@@ -5785,7 +6166,9 @@ function createBootSequence() {
5785
6166
  // Annotate the CommonJS export names for ESM import in node:
5786
6167
  0 && (module.exports = {
5787
6168
  KeepAliveRoute,
6169
+ MigrationStorageError,
5788
6170
  Outlet,
6171
+ PluginInstallCancelledError,
5789
6172
  Route,
5790
6173
  RouterLink,
5791
6174
  SibuRouter,
@@ -5829,6 +6212,7 @@ function createBootSequence() {
5829
6212
  healthCheck,
5830
6213
  hydrateRouter,
5831
6214
  inject,
6215
+ isPluginInstallCancelledError,
5832
6216
  lazy,
5833
6217
  lazyModule,
5834
6218
  navigate,