sibujs 4.5.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 (109) hide show
  1. package/dist/browser.cjs +309 -129
  2. package/dist/browser.d.cts +46 -14
  3. package/dist/browser.d.ts +46 -14
  4. package/dist/browser.js +8 -6
  5. package/dist/build.cjs +184 -72
  6. package/dist/build.js +15 -16
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +11 -11
  9. package/dist/cdn.full.global.js +10 -10
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  12. package/dist/chunk-3ISI6ACU.js +44 -0
  13. package/dist/{chunk-ONOHFDLG.js → chunk-47M47FOM.js} +3 -3
  14. package/dist/chunk-5HZXGZ6T.js +24 -0
  15. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  16. package/dist/{chunk-UGRX3S57.js → chunk-5Q4R7HCL.js} +12 -3
  17. package/dist/{chunk-S373NSMK.js → chunk-7GQCWFOE.js} +150 -59
  18. package/dist/{chunk-FTIR4QW2.js → chunk-7NCARGJW.js} +6 -8
  19. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  20. package/dist/{chunk-VZKNK2V7.js → chunk-AIF3Z2T7.js} +296 -38
  21. package/dist/{chunk-4PMLNECI.js → chunk-BAAG6ZTI.js} +98 -29
  22. package/dist/{chunk-XZZOBQAY.js → chunk-D33YTSX5.js} +2 -2
  23. package/dist/{chunk-TBYTO6BS.js → chunk-DKGBBKOF.js} +136 -80
  24. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  25. package/dist/{chunk-GW3SCCZG.js → chunk-HYXDKS4N.js} +153 -110
  26. package/dist/chunk-J6FW5TV6.js +233 -0
  27. package/dist/{chunk-PCT43HW3.js → chunk-JSXPZCET.js} +1 -1
  28. package/dist/chunk-NYNYSPK7.js +318 -0
  29. package/dist/{chunk-RJE2BNI4.js → chunk-ORMZXBKQ.js} +108 -45
  30. package/dist/{chunk-OMJJM3KM.js → chunk-OXUY2A6L.js} +287 -130
  31. package/dist/{chunk-3JZ4L5TJ.js → chunk-PK6FK2G2.js} +181 -66
  32. package/dist/{chunk-RBTPLM32.js → chunk-QE4TTDU3.js} +2 -2
  33. package/dist/{chunk-2DCGACUU.js → chunk-SXXVZMKZ.js} +251 -66
  34. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  35. package/dist/{chunk-7LN645I6.js → chunk-UZQ6ALFS.js} +3 -3
  36. package/dist/{chunk-KEISJXBU.js → chunk-VVWPJ543.js} +6 -8
  37. package/dist/{chunk-RIXRAYIU.js → chunk-XC4MEKGA.js} +3 -3
  38. package/dist/{chunk-B3WHI2QA.js → chunk-XRRZKZYX.js} +55 -31
  39. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  40. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  41. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  42. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  43. package/dist/data.cjs +213 -117
  44. package/dist/data.d.cts +47 -9
  45. package/dist/data.d.ts +47 -9
  46. package/dist/data.js +12 -9
  47. package/dist/devtools.cjs +81 -57
  48. package/dist/devtools.js +7 -8
  49. package/dist/dispose-GEIG2KOF.js +28 -0
  50. package/dist/ecosystem.cjs +309 -104
  51. package/dist/ecosystem.d.cts +19 -3
  52. package/dist/ecosystem.d.ts +19 -3
  53. package/dist/ecosystem.js +12 -12
  54. package/dist/extras.cjs +2133 -863
  55. package/dist/extras.d.cts +10 -9
  56. package/dist/extras.d.ts +10 -9
  57. package/dist/extras.js +39 -28
  58. package/dist/index.cjs +184 -72
  59. package/dist/index.d.cts +22 -5
  60. package/dist/index.d.ts +22 -5
  61. package/dist/index.js +24 -27
  62. package/dist/motion.cjs +107 -41
  63. package/dist/motion.js +5 -5
  64. package/dist/patterns.cjs +317 -39
  65. package/dist/patterns.d.cts +27 -9
  66. package/dist/patterns.d.ts +27 -9
  67. package/dist/patterns.js +8 -8
  68. package/dist/performance.cjs +308 -216
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +8 -9
  72. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  73. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  74. package/dist/plugins.cjs +549 -202
  75. package/dist/plugins.d.cts +3 -3
  76. package/dist/plugins.d.ts +3 -3
  77. package/dist/plugins.js +35 -23
  78. package/dist/signal-EotCj4hS.d.cts +110 -0
  79. package/dist/signal-EotCj4hS.d.ts +110 -0
  80. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  81. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  82. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  83. package/dist/ssr.cjs +176 -72
  84. package/dist/ssr.d.cts +9 -3
  85. package/dist/ssr.d.ts +9 -3
  86. package/dist/ssr.js +11 -12
  87. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  88. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  89. package/dist/{tagFactory-DVoDpHye.d.ts → tagFactory-DFstCLQV.d.cts} +2 -100
  90. package/dist/{tagFactory-DVoDpHye.d.cts → tagFactory-DkaNVUNV.d.ts} +2 -100
  91. package/dist/testing.cjs +2484 -2185
  92. package/dist/testing.d.cts +56 -5
  93. package/dist/testing.d.ts +56 -5
  94. package/dist/testing.js +580 -307
  95. package/dist/ui.cjs +707 -317
  96. package/dist/ui.d.cts +40 -7
  97. package/dist/ui.d.ts +40 -7
  98. package/dist/ui.js +151 -56
  99. package/dist/widgets.cjs +246 -278
  100. package/dist/widgets.js +9 -10
  101. package/package.json +1 -1
  102. package/dist/chunk-2WLZ6757.js +0 -149
  103. package/dist/chunk-CCSJMTRN.js +0 -15
  104. package/dist/chunk-QKRPLZ2V.js +0 -108
  105. package/dist/chunk-VUF4ALSW.js +0 -60
  106. package/dist/chunk-WWV3SJ3L.js +0 -131
  107. package/dist/dispose-46BOMMQJ.js +0 -19
  108. package/dist/plugin-D30wlGW5.d.cts +0 -71
  109. package/dist/plugin-D30wlGW5.d.ts +0 -71
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,
@@ -1440,6 +1528,7 @@ init_dev();
1440
1528
 
1441
1529
  // src/reactivity/track-core.ts
1442
1530
  init_errors();
1531
+ init_dispose();
1443
1532
  var POOL_MAX = 4096;
1444
1533
  var nodePool = [];
1445
1534
  function createNode() {
@@ -1597,6 +1686,7 @@ function retrack(effectFn, subscriber) {
1597
1686
  }
1598
1687
  currentSubscriber = subscriber;
1599
1688
  const sub2 = subscriber;
1689
+ if (sub2._cap === void 0) sub2._cap = currentDisposerCapture();
1600
1690
  const epoch = ++subscriberEpochCounter;
1601
1691
  sub2._epoch = epoch;
1602
1692
  sub2._structDirty = false;
@@ -1788,7 +1878,12 @@ function drainQueue() {
1788
1878
  quarantined.add(sub2);
1789
1879
  continue;
1790
1880
  }
1791
- safeInvoke(sub2);
1881
+ const pushed = beginDisposerCapture(sub2._cap ?? null);
1882
+ try {
1883
+ safeInvoke(sub2);
1884
+ } finally {
1885
+ if (pushed) endDisposerCapture();
1886
+ }
1792
1887
  }
1793
1888
  }
1794
1889
  function drainNotificationQueue() {
@@ -1924,7 +2019,7 @@ function forEachSubscriber(signal2, visit) {
1924
2019
  }
1925
2020
 
1926
2021
  // src/reactivity/track.ts
1927
- var _runtimeVersion = true ? "4.5.0" : "dev";
2022
+ var _runtimeVersion = true ? "4.6.0" : "dev";
1928
2023
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1929
2024
  function resolveReactiveApi() {
1930
2025
  const g2 = globalThis;
@@ -2897,6 +2992,15 @@ function effect(effectFn, options) {
2897
2992
 
2898
2993
  // src/plugins/router.ts
2899
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
+ }
2900
3004
  function classifyNavigationTarget(path2) {
2901
3005
  if (path2 === "") return "internal";
2902
3006
  const normalized = stripControlChars(path2).replace(/\\/g, "/");
@@ -3615,8 +3719,7 @@ var _SibuRouter = class _SibuRouter {
3615
3719
  return (path2 || "/") + window.location.search + window.location.hash;
3616
3720
  }
3617
3721
  createRouteContext(fullPath) {
3618
- const [pathWithQuery, hash = ""] = fullPath.split("#");
3619
- const [path2, queryString = ""] = pathWithQuery.split("?");
3722
+ const { path: path2, query: queryString, hash } = splitRouteUrl(fullPath);
3620
3723
  const query = Object.fromEntries(new URLSearchParams(queryString));
3621
3724
  const match = this.matcher.match(path2 || "/");
3622
3725
  const params = match?.params || {};
@@ -4363,8 +4466,7 @@ function RouterLink(props, children) {
4363
4466
  const rawHref = _routerRef.current["resolvePath"](to);
4364
4467
  const kind = classifyNavigationTarget(rawHref);
4365
4468
  const href = kind === "unsafe" ? "#" : rawHref;
4366
- const [hrefBeforeHash, hrefHash = ""] = href.split("#");
4367
- const [hrefPathRaw, hrefQueryRaw = ""] = hrefBeforeHash.split("?");
4469
+ const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
4368
4470
  const targetPath = normalizePathname(hrefPathRaw);
4369
4471
  const targetQuery = normalizeQuery(hrefQueryRaw);
4370
4472
  const link2 = document.createElement("a");
@@ -5005,37 +5107,152 @@ function escapeAttrLocal(str) {
5005
5107
  }
5006
5108
 
5007
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
+ }
5008
5156
  function createPluginRegistry() {
5009
5157
  const installedPlugins = /* @__PURE__ */ new Set();
5010
5158
  const hooks = { init: [], mount: [], unmount: [], error: [] };
5011
5159
  const provided = /* @__PURE__ */ new Map();
5160
+ const installStack = /* @__PURE__ */ new Set();
5161
+ const pendingInstalls = /* @__PURE__ */ new Map();
5162
+ let generation = 0;
5012
5163
  const registry = {
5013
5164
  installedPlugins,
5014
5165
  hooks,
5015
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
+ */
5016
5179
  plugin(p2, options) {
5017
5180
  if (installedPlugins.has(p2.name)) {
5018
5181
  console.warn(`[Plugin] "${p2.name}" is already installed.`);
5019
5182
  return;
5020
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;
5021
5200
  const ctx = {
5022
- onInit: (cb) => hooks.init.push(cb),
5023
- onMount: (cb) => hooks.mount.push(cb),
5024
- onUnmount: (cb) => hooks.unmount.push(cb),
5025
- onError: (cb) => hooks.error.push(cb),
5026
- 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
+ }
5027
5209
  };
5028
- const initHooksBefore = hooks.init.length;
5029
- p2.install(ctx, options);
5030
- installedPlugins.add(p2.name);
5031
- const justAdded = hooks.init.slice(initHooksBefore);
5032
- for (const cb of justAdded) {
5033
- try {
5034
- cb();
5035
- } catch (e) {
5036
- 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
+ }
5037
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);
5235
+ }
5236
+ if (!pending) {
5237
+ if (!commit()) throw new PluginInstallCancelledError(p2.name);
5238
+ return;
5038
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;
5039
5256
  },
5040
5257
  inject(key, defaultValue) {
5041
5258
  if (provided.has(key)) return provided.get(key);
@@ -5073,6 +5290,8 @@ function createPluginRegistry() {
5073
5290
  }
5074
5291
  },
5075
5292
  reset() {
5293
+ generation++;
5294
+ pendingInstalls.clear();
5076
5295
  installedPlugins.clear();
5077
5296
  hooks.init.length = 0;
5078
5297
  hooks.mount.length = 0;
@@ -5092,7 +5311,7 @@ function createPlugin(name, install) {
5092
5311
  }
5093
5312
  function plugin(plugin2, options) {
5094
5313
  _defaults.touched = true;
5095
- _defaults.registry.plugin(plugin2, options);
5314
+ return _defaults.registry.plugin(plugin2, options);
5096
5315
  }
5097
5316
  function inject(key, defaultValue) {
5098
5317
  return _defaults.registry.inject(key, defaultValue);
@@ -5123,35 +5342,44 @@ function setDefaultPluginRegistry(registry) {
5123
5342
  // src/plugins/modular.ts
5124
5343
  function createModuleRegistry() {
5125
5344
  const modules = /* @__PURE__ */ new Map();
5126
- function resolveInternal(name, stack) {
5345
+ const resolving = [];
5346
+ function resolveInternal(name) {
5127
5347
  const entry = modules.get(name);
5128
5348
  if (!entry) {
5129
5349
  throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
5130
5350
  }
5131
- if (entry.loaded) {
5351
+ if (entry.state === "loaded") {
5132
5352
  return entry.value;
5133
5353
  }
5134
- if (stack.has(name)) {
5135
- const cycle = [...stack, name].join(" -> ");
5354
+ if (entry.state === "resolving") {
5355
+ const cycle = [...resolving.slice(resolving.indexOf(name)), name].join(" -> ");
5136
5356
  throw new Error(`[ModuleRegistry] Circular dependency detected: ${cycle}`);
5137
5357
  }
5138
- stack.add(name);
5139
- for (const dep of entry.deps) {
5140
- 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();
5141
5373
  }
5142
- stack.delete(name);
5143
- entry.value = entry.factory();
5144
- entry.loaded = true;
5145
- return entry.value;
5146
5374
  }
5147
5375
  return {
5148
5376
  /** Register a module with its factory function and optional dependencies */
5149
5377
  register(name, factory, deps = []) {
5150
- modules.set(name, { factory, deps, loaded: false });
5378
+ modules.set(name, { factory, deps, state: "unloaded" });
5151
5379
  },
5152
5380
  /** Resolve a module, loading its dependencies first */
5153
5381
  resolve(name) {
5154
- return resolveInternal(name, /* @__PURE__ */ new Set());
5382
+ return resolveInternal(name);
5155
5383
  },
5156
5384
  /** Check if a module is registered */
5157
5385
  has(name) {
@@ -5186,7 +5414,7 @@ function createModuleRegistry() {
5186
5414
  /** Reset all loaded modules back to unloaded state (useful for testing) */
5187
5415
  reset() {
5188
5416
  for (const entry of modules.values()) {
5189
- entry.loaded = false;
5417
+ entry.state = "unloaded";
5190
5418
  entry.value = void 0;
5191
5419
  }
5192
5420
  }
@@ -5214,46 +5442,83 @@ function createBundle(modules) {
5214
5442
  function lazyModule(loader) {
5215
5443
  let cached;
5216
5444
  let loadedFlag = false;
5217
- const handle = {
5445
+ let inFlight = null;
5446
+ return {
5218
5447
  get loaded() {
5219
5448
  return loadedFlag;
5220
5449
  },
5221
- async get() {
5222
- if (loadedFlag) return cached;
5223
- cached = await loader();
5224
- loadedFlag = true;
5225
- 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;
5226
5473
  }
5227
5474
  };
5228
- return handle;
5229
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
+ };
5230
5499
  var packageInfo = {
5231
- name: "sibu",
5232
- version: "1.0.0",
5233
- entryPoints: {
5234
- main: "./index.ts",
5235
- core: "./src/core/html.ts",
5236
- hooks: "./src/core/signals/signal.ts",
5237
- router: "./src/plugins/router.ts",
5238
- i18n: "./src/plugins/i18n.ts",
5239
- testing: "./src/testing/index.ts",
5240
- ssr: "./src/core/ssr.ts"
5241
- },
5500
+ name: "sibujs",
5501
+ version: true ? "4.6.0" : "dev",
5502
+ entryPoints: MODULE_ENTRY_POINTS,
5242
5503
  /**
5243
- * Generate a package.json `exports` map for Node.js subpath exports.
5244
- * 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.
5245
5507
  */
5246
5508
  generateExportsMap() {
5247
5509
  const exportsMap = {};
5248
- for (const [name, sourcePath] of Object.entries(this.entryPoints)) {
5510
+ for (const [name, sourcePath] of Object.entries(MODULE_ENTRY_POINTS)) {
5249
5511
  const distPath = sourcePath.replace(/^\.\//, "./dist/").replace(/\.ts$/, "");
5250
5512
  const subpath = name === "main" ? "." : `./${name}`;
5251
5513
  exportsMap[subpath] = {
5252
- import: `${distPath}.mjs`,
5253
- require: `${distPath}.cjs`,
5254
- types: `${distPath}.d.ts`
5514
+ types: `${distPath}.d.ts`,
5515
+ import: `${distPath}.js`,
5516
+ require: `${distPath}.cjs`
5255
5517
  };
5256
5518
  }
5519
+ for (const [name, file] of Object.entries(CDN_ENTRY_POINTS)) {
5520
+ exportsMap[`./${name}`] = { default: file };
5521
+ }
5257
5522
  return exportsMap;
5258
5523
  }
5259
5524
  };
@@ -5378,29 +5643,26 @@ var env = {
5378
5643
  };
5379
5644
 
5380
5645
  // src/plugins/versioning.ts
5381
- 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
+ );
5382
5653
  function parseSemVer(version) {
5383
- const trimmed = version.trim().replace(/^v/i, "");
5384
- const prereleaseIndex = trimmed.indexOf("-");
5385
- let main2;
5386
- let prerelease;
5387
- if (prereleaseIndex !== -1) {
5388
- main2 = trimmed.slice(0, prereleaseIndex);
5389
- prerelease = trimmed.slice(prereleaseIndex + 1);
5390
- } else {
5391
- main2 = trimmed;
5392
- }
5393
- const parts = main2.split(".");
5394
- const major = parseInt(parts[0], 10);
5395
- const minor = parseInt(parts[1] || "0", 10);
5396
- const patch = parseInt(parts[2] || "0", 10);
5397
- 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) {
5398
5657
  throw new Error(`[Versioning] Invalid semver string: "${version}"`);
5399
5658
  }
5400
- const result = { major, minor, patch };
5401
- if (prerelease !== void 0) {
5402
- result.prerelease = prerelease;
5403
- }
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];
5404
5666
  return result;
5405
5667
  }
5406
5668
  function compareSemVer(a2, b2) {
@@ -5483,82 +5745,154 @@ function satisfies(version, range) {
5483
5745
  const target = parseSemVer(exactStr);
5484
5746
  return compareSemVer(v, target) === 0;
5485
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 = {};
5486
5762
  function createMigrationRunner(config) {
5487
5763
  const storageKey = config.storageKey || "__sibu_migration_version__";
5488
5764
  const sortedMigrations = [...config.migrations].sort((a2, b2) => compareSemVer(a2.version, b2.version));
5489
5765
  function getStorage() {
5766
+ if (config.storage) return config.storage;
5490
5767
  try {
5491
5768
  return typeof localStorage !== "undefined" ? localStorage : null;
5492
5769
  } catch {
5493
5770
  return null;
5494
5771
  }
5495
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
+ }
5496
5816
  return {
5497
5817
  /** Get the last applied migration version from storage */
5498
- getAppliedVersion() {
5499
- const storage = getStorage();
5500
- if (!storage) return null;
5501
- return storage.getItem(storageKey);
5502
- },
5818
+ getAppliedVersion,
5503
5819
  /** Get pending migrations that haven't been applied */
5504
- getPending() {
5505
- const appliedVersion = this.getAppliedVersion();
5506
- if (!appliedVersion) {
5507
- return sortedMigrations.filter((m) => compareSemVer(m.version, config.currentVersion) <= 0);
5508
- }
5509
- return sortedMigrations.filter(
5510
- (m) => compareSemVer(m.version, appliedVersion) > 0 && compareSemVer(m.version, config.currentVersion) <= 0
5511
- );
5512
- },
5513
- /** Run all pending migrations in order */
5514
- async migrate() {
5515
- const pending = this.getPending();
5516
- const applied = [];
5517
- const errors = [];
5518
- 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;
5519
5832
  try {
5520
- await migration.up();
5521
- applied.push(migration.version);
5522
- const storage = getStorage();
5523
- if (storage) {
5524
- storage.setItem(storageKey, migration.version);
5525
- }
5833
+ pending = getPending();
5526
5834
  } catch (e) {
5527
5835
  errors.push({
5528
- version: migration.version,
5836
+ version: getAppliedVersion() ?? "",
5529
5837
  error: e instanceof Error ? e : new Error(String(e))
5530
5838
  });
5531
- break;
5839
+ return { applied, errors };
5532
5840
  }
5533
- }
5534
- 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
+ });
5535
5861
  },
5536
- /** Rollback to a specific version */
5537
- async rollback(targetVersion) {
5538
- const appliedVersion = this.getAppliedVersion();
5539
- const rolledBack = [];
5540
- if (!appliedVersion) {
5541
- return { rolledBack };
5542
- }
5543
- const toRollback = sortedMigrations.filter((m) => compareSemVer(m.version, targetVersion) > 0 && compareSemVer(m.version, appliedVersion) <= 0).reverse();
5544
- for (const migration of toRollback) {
5545
- if (!migration.down) {
5546
- throw new Error(
5547
- `[Versioning] Migration ${migration.version} ("${migration.description}") does not have a down() function and cannot be rolled back.`
5548
- );
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 };
5549
5877
  }
5550
- await migration.down();
5551
- rolledBack.push(migration.version);
5552
- }
5553
- const storage = getStorage();
5554
- if (storage) {
5555
- if (targetVersion === "0.0.0") {
5556
- storage.removeItem(storageKey);
5557
- } else {
5558
- 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
+ }
5559
5885
  }
5560
- }
5561
- 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
+ });
5562
5896
  }
5563
5897
  };
5564
5898
  }
@@ -5589,6 +5923,7 @@ function extractRangeTarget(range) {
5589
5923
  }
5590
5924
 
5591
5925
  // src/plugins/startup.ts
5926
+ init_errors();
5592
5927
  init_ssr();
5593
5928
  function preloadCritical(resources) {
5594
5929
  if (typeof document === "undefined") return;
@@ -5609,28 +5944,43 @@ function preloadCritical(resources) {
5609
5944
  document.head.appendChild(link2);
5610
5945
  }
5611
5946
  }
5612
- function prerenderRoutes(routes, options) {
5613
- const maxCacheSize = options?.maxCacheSize ?? 50;
5614
- const cacheTTL = options?.cacheTTL ?? 0;
5615
- const cache = /* @__PURE__ */ new Map();
5616
- function isValid(entry) {
5617
- if (cacheTTL === 0) return true;
5618
- 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}`);
5950
+ }
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);
5619
5958
  }
5620
- function evictOldest() {
5621
- if (cache.size < maxCacheSize) return;
5959
+ while (cache.size >= maxSize) {
5622
5960
  let oldestKey = null;
5623
5961
  let oldestTime = Infinity;
5624
- for (const [key, entry] of cache) {
5962
+ for (const [k, entry] of cache) {
5625
5963
  if (entry.timestamp < oldestTime) {
5626
5964
  oldestTime = entry.timestamp;
5627
- oldestKey = key;
5965
+ oldestKey = k;
5628
5966
  }
5629
5967
  }
5630
- 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;
5631
5981
  }
5632
5982
  for (const route2 of routes) {
5633
- evictOldest();
5983
+ if (!makeRoomFor(cache, route2.path, maxCacheSize, isValid)) break;
5634
5984
  const html2 = renderToString(route2.component());
5635
5985
  cache.set(route2.path, { html: html2, timestamp: Date.now() });
5636
5986
  }
@@ -5682,6 +6032,7 @@ function prerenderRoutes(routes, options) {
5682
6032
  function createSSRCache(config) {
5683
6033
  const maxSize = config?.maxSize ?? 100;
5684
6034
  const defaultTTL = config?.defaultTTL ?? 6e4;
6035
+ assertCacheSize("maxSize", maxSize);
5685
6036
  const cache = /* @__PURE__ */ new Map();
5686
6037
  let hits = 0;
5687
6038
  let misses = 0;
@@ -5689,18 +6040,6 @@ function createSSRCache(config) {
5689
6040
  if (entry.ttl === 0) return true;
5690
6041
  return Date.now() - entry.timestamp < entry.ttl;
5691
6042
  }
5692
- function evictOldest() {
5693
- if (cache.size < maxSize) return;
5694
- let oldestKey = null;
5695
- let oldestTime = Infinity;
5696
- for (const [key, entry] of cache) {
5697
- if (entry.timestamp < oldestTime) {
5698
- oldestTime = entry.timestamp;
5699
- oldestKey = key;
5700
- }
5701
- }
5702
- if (oldestKey) cache.delete(oldestKey);
5703
- }
5704
6043
  return {
5705
6044
  /** Get cached HTML for a key, or undefined if not cached or expired */
5706
6045
  get(key) {
@@ -5719,7 +6058,7 @@ function createSSRCache(config) {
5719
6058
  },
5720
6059
  /** Cache an HTML string with an optional TTL override */
5721
6060
  set(key, html2, ttl) {
5722
- evictOldest();
6061
+ if (!makeRoomFor(cache, key, maxSize, isValid)) return;
5723
6062
  cache.set(key, {
5724
6063
  html: html2,
5725
6064
  timestamp: Date.now(),
@@ -5752,25 +6091,30 @@ function createSSRCache(config) {
5752
6091
  }
5753
6092
  };
5754
6093
  }
6094
+ var DEFER_IDLE_TIMEOUT_MS = 2e3;
5755
6095
  function deferNonCritical(tasks) {
5756
6096
  if (tasks.length === 0) return;
5757
- 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
+ };
5758
6102
  const queue = [...tasks];
5759
6103
  let index = 0;
5760
6104
  function processNext(deadline) {
6105
+ let ranOne = false;
5761
6106
  while (index < queue.length) {
5762
- if (deadline && deadline.timeRemaining() < 1) {
6107
+ if (ranOne && deadline && deadline.timeRemaining() < 1) {
5763
6108
  schedule(processNext);
5764
6109
  return;
5765
6110
  }
5766
6111
  try {
5767
6112
  queue[index]();
5768
6113
  } catch (e) {
5769
- if (typeof console !== "undefined") {
5770
- console.error("[Startup] Deferred task failed:", e);
5771
- }
6114
+ reportError(e, { phase: "scheduler", name: "deferNonCritical" });
5772
6115
  }
5773
6116
  index++;
6117
+ ranOne = true;
5774
6118
  }
5775
6119
  }
5776
6120
  schedule(processNext);
@@ -5822,7 +6166,9 @@ function createBootSequence() {
5822
6166
  // Annotate the CommonJS export names for ESM import in node:
5823
6167
  0 && (module.exports = {
5824
6168
  KeepAliveRoute,
6169
+ MigrationStorageError,
5825
6170
  Outlet,
6171
+ PluginInstallCancelledError,
5826
6172
  Route,
5827
6173
  RouterLink,
5828
6174
  SibuRouter,
@@ -5866,6 +6212,7 @@ function createBootSequence() {
5866
6212
  healthCheck,
5867
6213
  hydrateRouter,
5868
6214
  inject,
6215
+ isPluginInstallCancelledError,
5869
6216
  lazy,
5870
6217
  lazyModule,
5871
6218
  navigate,