sibujs 1.4.0 → 2.0.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 (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/ssr.cjs CHANGED
@@ -70,33 +70,105 @@ function isDev() {
70
70
  var _isDev = isDev();
71
71
  function devAssert(condition, message) {
72
72
  if (_isDev && !condition) {
73
- throw new Error(`[Sibu] ${message}`);
73
+ throw new Error(`[SibuJS] ${message}`);
74
74
  }
75
75
  }
76
76
  function devWarn(message) {
77
77
  if (_isDev) {
78
- console.warn(`[Sibu] ${message}`);
78
+ console.warn(`[SibuJS] ${message}`);
79
79
  }
80
80
  }
81
81
 
82
+ // src/core/ssr-context.ts
83
+ var als = null;
84
+ try {
85
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
86
+ const req = Function("return typeof require==='function'?require:null")();
87
+ if (req) {
88
+ const mod = req("node:async_hooks");
89
+ als = new mod.AsyncLocalStorage();
90
+ }
91
+ }
92
+ } catch {
93
+ als = null;
94
+ }
95
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
96
+ function getSSRStore() {
97
+ if (als) {
98
+ const s2 = als.getStore();
99
+ if (s2) return s2;
100
+ }
101
+ return fallbackStore;
102
+ }
103
+ function isSSR() {
104
+ return getSSRStore().ssr;
105
+ }
106
+
82
107
  // src/utils/sanitize.ts
108
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
83
109
  function sanitizeUrl(url) {
84
110
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
85
111
  if (!trimmed) return "";
86
112
  const lower = trimmed.toLowerCase();
87
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
88
- return "";
113
+ let schemeEnd = -1;
114
+ for (let i2 = 0; i2 < lower.length; i2++) {
115
+ const ch = lower.charCodeAt(i2);
116
+ if (ch === 58) {
117
+ schemeEnd = i2;
118
+ break;
119
+ }
120
+ if (ch === 47 || ch === 63 || ch === 35) break;
89
121
  }
122
+ if (schemeEnd === -1) return trimmed;
123
+ const scheme = lower.slice(0, schemeEnd + 1);
124
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
125
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
90
126
  return trimmed;
91
127
  }
128
+ function sanitizeSrcset(value) {
129
+ const parts = value.split(",");
130
+ const out = [];
131
+ for (let i2 = 0; i2 < parts.length; i2++) {
132
+ const part = parts[i2].trim();
133
+ if (!part) continue;
134
+ const m = part.match(/^(\S+)(\s+.+)?$/);
135
+ if (!m) continue;
136
+ const safe = sanitizeUrl(m[1]);
137
+ if (!safe) continue;
138
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
139
+ }
140
+ return out.join(", ");
141
+ }
92
142
  function sanitizeCSSValue(value) {
93
- const lower = value.toLowerCase().replace(/\s+/g, "");
94
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
143
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
144
+ const code2 = Number.parseInt(hex, 16);
145
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
146
+ try {
147
+ return String.fromCodePoint(code2);
148
+ } catch {
149
+ return "";
150
+ }
151
+ });
152
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
153
+ if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
95
154
  return "";
96
155
  }
97
156
  return value;
98
157
  }
99
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
158
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
159
+ "href",
160
+ "xlink:href",
161
+ "src",
162
+ "action",
163
+ "formaction",
164
+ "formtarget",
165
+ "cite",
166
+ "poster",
167
+ "background",
168
+ "srcset",
169
+ "ping",
170
+ "data"
171
+ ]);
100
172
  function isUrlAttribute(attr) {
101
173
  return URL_ATTRIBUTES.has(attr);
102
174
  }
@@ -219,7 +291,7 @@ function hydrate(component, container, options = {}) {
219
291
  options.onMismatch(first);
220
292
  } else if (_isDev2) {
221
293
  console.warn(
222
- `[Sibu hydration] ${first.message}
294
+ `[SibuJS hydration] ${first.message}
223
295
  at ${first.path}
224
296
  server: ${first.serverValue}
225
297
  client: ${first.clientValue}`
@@ -227,17 +299,9 @@ function hydrate(component, container, options = {}) {
227
299
  }
228
300
  }
229
301
  }
230
- hydrateNode(container.firstElementChild, clientTree);
302
+ container.replaceChildren(clientTree);
231
303
  container.setAttribute("data-sibu-hydrated", "true");
232
304
  }
233
- function hydrateNode(serverNode, clientNode) {
234
- if (!serverNode) return;
235
- const serverChildren = Array.from(serverNode.children);
236
- const clientChildren = Array.from(clientNode.children);
237
- for (let i2 = 0; i2 < Math.min(serverChildren.length, clientChildren.length); i2++) {
238
- hydrateNode(serverChildren[i2], clientChildren[i2]);
239
- }
240
- }
241
305
  function collectMismatches(serverNode, clientNode, path2, out, max = 5) {
242
306
  if (out.length >= max) return;
243
307
  const nodePath = path2 || clientNode?.tagName?.toLowerCase() || "(root)";
@@ -467,12 +531,16 @@ function renderToReadableStream(element) {
467
531
  controller.enqueue(value);
468
532
  }
469
533
  },
470
- cancel() {
471
- generator.return(void 0);
534
+ async cancel() {
535
+ await generator.return(void 0);
472
536
  }
473
537
  });
474
538
  }
539
+ var SAFE_ID = /^[A-Za-z0-9_-]+$/;
475
540
  function island(id, component) {
541
+ if (!SAFE_ID.test(id)) {
542
+ throw new Error(`[SibuJS SSR] island: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`);
543
+ }
476
544
  const el = component();
477
545
  el.setAttribute("data-sibu-island", id);
478
546
  return el;
@@ -485,8 +553,9 @@ function hydrateIslands(container, islands) {
485
553
  const factory = islands[id];
486
554
  if (typeof factory !== "function") continue;
487
555
  const clientTree = factory();
488
- hydrateNode(marker2, clientTree);
489
- marker2.setAttribute("data-sibu-hydrated", "true");
556
+ clientTree.setAttribute("data-sibu-island", id);
557
+ clientTree.setAttribute("data-sibu-hydrated", "true");
558
+ marker2.replaceWith(clientTree);
490
559
  }
491
560
  container.setAttribute("data-sibu-hydrated", "partial");
492
561
  }
@@ -503,8 +572,9 @@ function hydrateProgressively(container, islands, options) {
503
572
  for (const entry of entries) {
504
573
  if (entry.isIntersecting) {
505
574
  const clientTree = factory();
506
- hydrateNode(marker2, clientTree);
507
- marker2.setAttribute("data-sibu-hydrated", "true");
575
+ clientTree.setAttribute("data-sibu-island", id);
576
+ clientTree.setAttribute("data-sibu-hydrated", "true");
577
+ marker2.replaceWith(clientTree);
508
578
  observer.disconnect();
509
579
  break;
510
580
  }
@@ -520,20 +590,37 @@ function hydrateProgressively(container, islands, options) {
520
590
  for (const cleanup2 of cleanups) cleanup2();
521
591
  };
522
592
  }
523
- var suspenseIdCounter = 0;
524
593
  function resetSSRState() {
525
- suspenseIdCounter = 0;
594
+ getSSRStore().suspenseIdCounter = 0;
595
+ }
596
+ function noop() {
526
597
  }
527
598
  function ssrSuspense(props) {
528
- const id = `sibu-sus-${suspenseIdCounter++}`;
599
+ const store = getSSRStore();
600
+ const id = `sibu-sus-${store.suspenseIdCounter++}`;
601
+ const timeoutMs = props.timeoutMs ?? 3e4;
529
602
  const fallbackEl = props.fallback();
530
603
  const wrapper = document.createElement("div");
531
604
  wrapper.setAttribute("data-sibu-suspense-id", id);
532
605
  wrapper.appendChild(fallbackEl);
533
- const promise = props.content().then((resolvedEl) => ({
534
- id,
535
- html: renderToString(resolvedEl)
536
- }));
606
+ const fallbackHtml = renderToString(fallbackEl);
607
+ let timer;
608
+ const timeoutPromise = new Promise((_, reject) => {
609
+ timer = setTimeout(() => reject(new Error(`[SibuJS SSR] ssrSuspense timed out after ${timeoutMs}ms`)), timeoutMs);
610
+ });
611
+ const raced = Promise.race([props.content(), timeoutPromise]);
612
+ const promise = raced.then(
613
+ (resolvedEl) => {
614
+ if (timer) clearTimeout(timer);
615
+ return { id, html: renderToString(resolvedEl) };
616
+ },
617
+ (err) => {
618
+ if (timer) clearTimeout(timer);
619
+ if (_isDev2) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
620
+ return { id, html: fallbackHtml };
621
+ }
622
+ );
623
+ promise.catch(noop);
537
624
  return { element: wrapper, promise };
538
625
  }
539
626
  var SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
@@ -561,14 +648,27 @@ var SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
561
648
  function escapeScriptJson(json) {
562
649
  return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
563
650
  }
564
- function serializeState(state, nonce) {
565
- const json = escapeScriptJson(JSON.stringify(state));
651
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
652
+ function serializeState(state, nonce, options) {
653
+ const rawJson = JSON.stringify(state);
654
+ const maxBytes = options?.maxBytes ?? DEFAULT_MAX_SSR_BYTES;
655
+ const byteLen = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(rawJson).byteLength : Buffer.byteLength(rawJson, "utf8");
656
+ if (byteLen > maxBytes) {
657
+ throw new Error(`[SibuJS SSR] serializeState: payload (${byteLen} bytes) exceeds maxBytes (${maxBytes})`);
658
+ }
659
+ const json = escapeScriptJson(rawJson);
566
660
  const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
567
661
  return `<script${nonceAttr}>window.${SSR_DATA_ATTR}=${json}</script>`;
568
662
  }
569
663
  function deserializeState(validate) {
570
664
  if (typeof window === "undefined") return void 0;
571
- const raw = window[SSR_DATA_ATTR];
665
+ if (_isDev2 && !validate) {
666
+ console.warn(
667
+ "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
668
+ );
669
+ }
670
+ const w = window;
671
+ const raw = w[SSR_DATA_ATTR];
572
672
  if (raw === void 0) return void 0;
573
673
  if (validate && !validate(raw)) return void 0;
574
674
  return raw;
@@ -586,11 +686,11 @@ var subscriberStack = new Array(32);
586
686
  var stackCapacity = 32;
587
687
  var stackTop = -1;
588
688
  var currentSubscriber = null;
589
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
590
689
  var SUBS = "__s";
591
690
  var notifyDepth = 0;
592
691
  var pendingQueue = [];
593
692
  var pendingSet = /* @__PURE__ */ new Set();
693
+ var propagateStack = [];
594
694
  function safeInvoke(sub2) {
595
695
  try {
596
696
  sub2();
@@ -636,7 +736,6 @@ function recordDependency(signal2) {
636
736
  let subs = signal2[SUBS];
637
737
  if (!subs) {
638
738
  subs = /* @__PURE__ */ new Set();
639
- signalSubscribers.set(signal2, subs);
640
739
  signal2[SUBS] = subs;
641
740
  }
642
741
  subs.add(currentSubscriber);
@@ -658,57 +757,71 @@ function queueSignalNotification(signal2) {
658
757
  }
659
758
  }
660
759
  }
760
+ var maxDrainIterations = 1e5;
661
761
  function drainNotificationQueue() {
662
762
  if (notifyDepth > 0) return;
663
763
  notifyDepth++;
664
764
  try {
665
765
  let i2 = 0;
666
766
  while (i2 < pendingQueue.length) {
767
+ if (i2 >= maxDrainIterations) {
768
+ if (typeof console !== "undefined") {
769
+ console.error(
770
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
771
+ );
772
+ }
773
+ break;
774
+ }
667
775
  safeInvoke(pendingQueue[i2]);
668
776
  i2++;
669
777
  }
670
778
  } finally {
671
- pendingQueue.length = 0;
672
- pendingSet.clear();
673
779
  notifyDepth--;
780
+ if (notifyDepth === 0) {
781
+ pendingQueue.length = 0;
782
+ pendingSet.clear();
783
+ }
674
784
  }
675
785
  }
676
786
  function propagateDirty(sub2) {
677
787
  sub2();
678
- let sig = sub2._sig;
679
- while (sig) {
788
+ const rootSig = sub2._sig;
789
+ if (!rootSig) return;
790
+ const stack = propagateStack;
791
+ const baseLen = stack.length;
792
+ stack.push(rootSig);
793
+ while (stack.length > baseLen) {
794
+ const sig = stack.pop();
680
795
  const first = sig.__f;
681
796
  if (first) {
682
797
  if (first._c) {
683
798
  const nSig = first._sig;
684
- nSig._d = true;
685
- sig = nSig;
686
- continue;
687
- }
688
- if (!pendingSet.has(first)) {
799
+ if (!nSig._d) {
800
+ nSig._d = true;
801
+ stack.push(nSig);
802
+ }
803
+ } else if (!pendingSet.has(first)) {
689
804
  pendingSet.add(first);
690
805
  pendingQueue.push(first);
691
806
  }
692
- break;
807
+ continue;
693
808
  }
694
809
  const subs = sig[SUBS];
695
- if (!subs) break;
696
- let nextSig;
810
+ if (!subs) continue;
697
811
  for (const s2 of subs) {
698
812
  if (s2._c) {
699
- s2();
700
813
  const nSig = s2._sig;
701
- if (nSig && !nextSig) {
702
- nextSig = nSig;
703
- } else if (nSig) {
704
- propagateDirty(s2);
814
+ if (nSig && !nSig._d) {
815
+ nSig._d = true;
816
+ stack.push(nSig);
817
+ } else if (!nSig) {
818
+ s2();
705
819
  }
706
820
  } else if (!pendingSet.has(s2)) {
707
821
  pendingSet.add(s2);
708
822
  pendingQueue.push(s2);
709
823
  }
710
824
  }
711
- sig = nextSig;
712
825
  }
713
826
  }
714
827
  function notifySubscribers(signal2) {
@@ -732,13 +845,23 @@ function notifySubscribers(signal2) {
732
845
  }
733
846
  let i2 = 0;
734
847
  while (i2 < pendingQueue.length) {
848
+ if (i2 >= maxDrainIterations) {
849
+ if (typeof console !== "undefined") {
850
+ console.error(
851
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
852
+ );
853
+ }
854
+ break;
855
+ }
735
856
  safeInvoke(pendingQueue[i2]);
736
857
  i2++;
737
858
  }
738
859
  } finally {
739
- pendingQueue.length = 0;
740
- pendingSet.clear();
741
860
  notifyDepth--;
861
+ if (notifyDepth === 0) {
862
+ pendingQueue.length = 0;
863
+ pendingSet.clear();
864
+ }
742
865
  }
743
866
  return;
744
867
  }
@@ -758,30 +881,48 @@ function notifySubscribers(signal2) {
758
881
  notifyDepth++;
759
882
  try {
760
883
  let directCount = 0;
884
+ let hasComputedSub = false;
761
885
  for (const sub2 of subs) {
886
+ if (sub2._c) hasComputedSub = true;
762
887
  pendingQueue[directCount++] = sub2;
763
888
  }
764
- for (let i3 = 0; i3 < directCount; i3++) {
765
- if (pendingQueue[i3]._c) {
766
- propagateDirty(pendingQueue[i3]);
889
+ if (!hasComputedSub) {
890
+ for (let i3 = 0; i3 < directCount; i3++) {
891
+ safeInvoke(pendingQueue[i3]);
767
892
  }
768
- }
769
- for (let i3 = 0; i3 < directCount; i3++) {
770
- if (!pendingQueue[i3]._c) {
771
- if (!pendingSet.has(pendingQueue[i3])) {
772
- safeInvoke(pendingQueue[i3]);
893
+ } else {
894
+ for (let i3 = 0; i3 < directCount; i3++) {
895
+ if (pendingQueue[i3]._c) {
896
+ propagateDirty(pendingQueue[i3]);
897
+ }
898
+ }
899
+ for (let i3 = 0; i3 < directCount; i3++) {
900
+ const sub2 = pendingQueue[i3];
901
+ if (!sub2._c && !pendingSet.has(sub2)) {
902
+ pendingSet.add(sub2);
903
+ safeInvoke(sub2);
773
904
  }
774
905
  }
775
906
  }
776
907
  let i2 = directCount;
777
908
  while (i2 < pendingQueue.length) {
909
+ if (i2 - directCount >= maxDrainIterations) {
910
+ if (typeof console !== "undefined") {
911
+ console.error(
912
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
913
+ );
914
+ }
915
+ break;
916
+ }
778
917
  safeInvoke(pendingQueue[i2]);
779
918
  i2++;
780
919
  }
781
920
  } finally {
782
- pendingQueue.length = 0;
783
- pendingSet.clear();
784
921
  notifyDepth--;
922
+ if (notifyDepth === 0) {
923
+ pendingQueue.length = 0;
924
+ pendingSet.clear();
925
+ }
785
926
  }
786
927
  }
787
928
  function cleanup(subscriber) {
@@ -792,7 +933,9 @@ function cleanup(subscriber) {
792
933
  if (subs) {
793
934
  subs.delete(subscriber);
794
935
  if (singleDep.__f === subscriber) {
795
- singleDep.__f = void 0;
936
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
937
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
938
+ singleDep.__f = subs.values().next().value;
796
939
  }
797
940
  }
798
941
  sub2._dep = void 0;
@@ -805,19 +948,15 @@ function cleanup(subscriber) {
805
948
  if (subs) {
806
949
  subs.delete(subscriber);
807
950
  if (signal2.__f === subscriber) {
808
- signal2.__f = void 0;
951
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
952
+ } else if (subs.size === 1 && signal2.__f === void 0) {
953
+ signal2.__f = subs.values().next().value;
809
954
  }
810
955
  }
811
956
  }
812
957
  deps.clear();
813
958
  }
814
959
 
815
- // src/core/ssr-context.ts
816
- var ssrMode = false;
817
- function isSSR() {
818
- return ssrMode;
819
- }
820
-
821
960
  // src/core/signals/effect.ts
822
961
  var _g = globalThis;
823
962
  function effect(effectFn, options) {
@@ -825,26 +964,86 @@ function effect(effectFn, options) {
825
964
  if (isSSR()) return () => {
826
965
  };
827
966
  const onError = options?.onError;
967
+ let userCleanups = [];
968
+ const onCleanup = (fn) => {
969
+ userCleanups.push(fn);
970
+ };
971
+ const runUserCleanups = () => {
972
+ if (userCleanups.length === 0) return;
973
+ const list = userCleanups;
974
+ userCleanups = [];
975
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
976
+ try {
977
+ list[i2]();
978
+ } catch (err) {
979
+ if (typeof console !== "undefined") {
980
+ console.warn("[SibuJS effect] onCleanup threw:", err);
981
+ }
982
+ }
983
+ }
984
+ };
985
+ const invokeBody = () => effectFn(onCleanup);
828
986
  const wrappedFn = onError ? () => {
829
987
  try {
830
- effectFn();
988
+ invokeBody();
831
989
  } catch (err) {
832
990
  onError(err);
833
991
  }
834
- } : effectFn;
992
+ } : invokeBody;
835
993
  let cleanupHandle = () => {
836
994
  };
995
+ let running = false;
837
996
  const subscriber = () => {
838
- cleanupHandle();
839
- cleanupHandle = track(wrappedFn, subscriber);
997
+ if (running) {
998
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
999
+ console.warn(
1000
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
1001
+ );
1002
+ }
1003
+ return;
1004
+ }
1005
+ running = true;
1006
+ try {
1007
+ runUserCleanups();
1008
+ cleanupHandle();
1009
+ cleanupHandle = track(wrappedFn, subscriber);
1010
+ } finally {
1011
+ running = false;
1012
+ }
840
1013
  };
841
- cleanupHandle = track(wrappedFn, subscriber);
1014
+ running = true;
1015
+ try {
1016
+ cleanupHandle = track(wrappedFn, subscriber);
1017
+ } finally {
1018
+ running = false;
1019
+ }
842
1020
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
843
1021
  if (hook) hook.emit("effect:create", { effectFn });
1022
+ let disposed = false;
844
1023
  return () => {
1024
+ if (disposed) return;
1025
+ disposed = true;
845
1026
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
846
- if (h) h.emit("effect:destroy", { effectFn });
847
- cleanupHandle();
1027
+ if (h) {
1028
+ try {
1029
+ h.emit("effect:destroy", { effectFn });
1030
+ } catch {
1031
+ }
1032
+ }
1033
+ try {
1034
+ runUserCleanups();
1035
+ } catch (err) {
1036
+ if (typeof console !== "undefined") {
1037
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1038
+ }
1039
+ }
1040
+ try {
1041
+ cleanupHandle();
1042
+ } catch (err) {
1043
+ if (typeof console !== "undefined") {
1044
+ console.warn("[SibuJS effect] dispose threw:", err);
1045
+ }
1046
+ }
848
1047
  };
849
1048
  }
850
1049
 
@@ -1016,10 +1215,13 @@ function enqueueBatchedSignal(signal2) {
1016
1215
  return true;
1017
1216
  }
1018
1217
  function flushBatch() {
1019
- for (const signal2 of pendingSignals) {
1020
- queueSignalNotification(signal2);
1218
+ try {
1219
+ for (const signal2 of pendingSignals) {
1220
+ queueSignalNotification(signal2);
1221
+ }
1222
+ } finally {
1223
+ pendingSignals.clear();
1021
1224
  }
1022
- pendingSignals.clear();
1023
1225
  drainNotificationQueue();
1024
1226
  }
1025
1227
 
@@ -1066,24 +1268,42 @@ function createISR(options) {
1066
1268
  const { revalidateAfter, fetcher, initialData } = options;
1067
1269
  const [data2, setData] = signal(initialData);
1068
1270
  const [timestamp, setTimestamp] = signal(initialData !== void 0 ? Date.now() : 0);
1271
+ const controller = new AbortController();
1272
+ let inFlight = false;
1273
+ let disposed = false;
1069
1274
  const isStale = () => {
1070
1275
  const ts = timestamp();
1071
1276
  if (ts === 0) return true;
1072
1277
  return Date.now() - ts >= revalidateAfter;
1073
1278
  };
1074
1279
  const revalidate = async () => {
1075
- const result = await fetcher();
1076
- setData(result);
1077
- setTimestamp(Date.now());
1280
+ if (disposed || inFlight) return;
1281
+ if (controller.signal.aborted) return;
1282
+ inFlight = true;
1283
+ try {
1284
+ const result = await fetcher({ signal: controller.signal });
1285
+ if (disposed || controller.signal.aborted) return;
1286
+ setData(result);
1287
+ setTimestamp(Date.now());
1288
+ } finally {
1289
+ inFlight = false;
1290
+ }
1078
1291
  };
1079
1292
  if (initialData === void 0) {
1080
- revalidate();
1293
+ revalidate().catch((err) => {
1294
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
1295
+ });
1081
1296
  }
1082
1297
  const intervalId = setInterval(() => {
1083
- revalidate();
1298
+ revalidate().catch((err) => {
1299
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] revalidate failed", err);
1300
+ });
1084
1301
  }, revalidateAfter);
1085
1302
  const dispose = () => {
1303
+ if (disposed) return;
1304
+ disposed = true;
1086
1305
  clearInterval(intervalId);
1306
+ controller.abort();
1087
1307
  };
1088
1308
  return { data: data2, isStale, revalidate, dispose };
1089
1309
  }
@@ -1191,6 +1411,9 @@ function createMiddlewareChain() {
1191
1411
 
1192
1412
  // src/reactivity/bindAttribute.ts
1193
1413
  var _isDev5 = isDev();
1414
+ function setProp(el, key, val) {
1415
+ el[key] = val;
1416
+ }
1194
1417
  function isEventHandlerAttr3(name) {
1195
1418
  if (name.length < 3) return false;
1196
1419
  const lower = name.toLowerCase();
@@ -1216,7 +1439,7 @@ function bindAttribute(el, attr, getter) {
1216
1439
  }
1217
1440
  if (typeof value === "boolean") {
1218
1441
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1219
- el[attr] = value;
1442
+ setProp(el, attr, value);
1220
1443
  } else if (value) {
1221
1444
  el.setAttribute(attr, "");
1222
1445
  } else {
@@ -1226,7 +1449,7 @@ function bindAttribute(el, attr, getter) {
1226
1449
  }
1227
1450
  const str = String(value);
1228
1451
  if ((attr === "value" || attr === "checked") && attr in el) {
1229
- el[attr] = attr === "checked" ? Boolean(value) : str;
1452
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1230
1453
  } else {
1231
1454
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1232
1455
  }
@@ -1263,24 +1486,29 @@ function bindChildNode(placeholder, getter) {
1263
1486
  let newNodes;
1264
1487
  if (Array.isArray(result)) {
1265
1488
  newNodes = [];
1489
+ const seen = /* @__PURE__ */ new Set();
1266
1490
  for (let i2 = 0; i2 < result.length; i2++) {
1267
1491
  const item = result[i2];
1268
1492
  if (item == null || typeof item === "boolean") continue;
1269
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1493
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1494
+ if (seen.has(node)) {
1495
+ if (_isDev6)
1496
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1497
+ continue;
1498
+ }
1499
+ seen.add(node);
1500
+ newNodes.push(node);
1270
1501
  }
1271
1502
  } else {
1272
1503
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1273
1504
  newNodes = [node];
1274
1505
  }
1275
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1276
- if (reused) {
1506
+ let reused;
1507
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1508
+ const lastSet = new Set(lastNodes);
1509
+ reused = /* @__PURE__ */ new Set();
1277
1510
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1278
- for (let j = 0; j < lastNodes.length; j++) {
1279
- if (newNodes[i2] === lastNodes[j]) {
1280
- reused.add(newNodes[i2]);
1281
- break;
1282
- }
1283
- }
1511
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1284
1512
  }
1285
1513
  }
1286
1514
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1320,6 +1548,30 @@ function registerDisposer(node, teardown) {
1320
1548
 
1321
1549
  // src/core/rendering/tagFactory.ts
1322
1550
  var SVG_NS = "http://www.w3.org/2000/svg";
1551
+ var _isDev8 = isDev();
1552
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1553
+ function validateTagName(tag) {
1554
+ const lower = tag.toLowerCase();
1555
+ if (BLOCKED_TAGS.has(lower)) {
1556
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1557
+ }
1558
+ }
1559
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1560
+ "config",
1561
+ "location",
1562
+ "history",
1563
+ "document",
1564
+ "window",
1565
+ "navigator",
1566
+ "name",
1567
+ "top",
1568
+ "parent",
1569
+ "self",
1570
+ "frames"
1571
+ ]);
1572
+ function setProp2(el, key, val) {
1573
+ el[key] = val;
1574
+ }
1323
1575
  var kebabCache = /* @__PURE__ */ new Map();
1324
1576
  function toKebab(prop) {
1325
1577
  let cached = kebabCache.get(prop);
@@ -1444,79 +1696,103 @@ function appendChildren(el, nodes) {
1444
1696
  }
1445
1697
  }
1446
1698
  }
1447
- var tagFactory = (tag, ns) => (first, second) => {
1448
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1449
- if (first === void 0) return el;
1450
- if (typeof first === "string") {
1451
- if (second !== void 0) {
1452
- el.setAttribute("class", first);
1453
- appendChildren(el, second);
1699
+ var tagFactory = (tag, ns) => {
1700
+ return (first, second) => {
1701
+ validateTagName(tag);
1702
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1703
+ if (first === void 0) return el;
1704
+ if (typeof first === "string") {
1705
+ if (second !== void 0) {
1706
+ el.setAttribute("class", first);
1707
+ appendChildren(el, second);
1708
+ return el;
1709
+ }
1710
+ el.textContent = first;
1454
1711
  return el;
1455
1712
  }
1456
- el.textContent = first;
1457
- return el;
1458
- }
1459
- if (typeof first === "number") {
1460
- el.textContent = String(first);
1461
- return el;
1462
- }
1463
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1464
- appendChildren(el, first);
1465
- return el;
1466
- }
1467
- const props = first;
1468
- const pClass = props.class;
1469
- if (pClass != null) applyClass(el, pClass);
1470
- const pId = props.id;
1471
- if (pId != null) el.id = pId;
1472
- const pNodes = second !== void 0 ? second : props.nodes;
1473
- if (pNodes != null) appendChildren(el, pNodes);
1474
- const pOn = props.on;
1475
- if (pOn) {
1476
- for (const ev in pOn) {
1477
- el.addEventListener(ev, pOn[ev]);
1478
- }
1479
- }
1480
- const pStyle = props.style;
1481
- if (pStyle != null) applyStyle(el, pStyle);
1482
- const pRef = props.ref;
1483
- if (pRef) pRef.current = el;
1484
- for (const key in props) {
1485
- switch (key) {
1486
- case "class":
1487
- case "id":
1488
- case "nodes":
1489
- case "on":
1490
- case "style":
1491
- case "ref":
1492
- case "onElement":
1493
- continue;
1494
- // already handled above / below
1495
- default: {
1496
- const value = props[key];
1497
- if (value == null) continue;
1498
- if (key[0] === "o" && key[1] === "n") continue;
1499
- if (typeof value === "function") {
1500
- registerDisposer(el, bindAttribute(el, key, value));
1501
- } else if (typeof value === "boolean") {
1502
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1503
- el[key] = value;
1504
- } else if (value) {
1505
- el.setAttribute(key, "");
1713
+ if (typeof first === "number") {
1714
+ el.textContent = String(first);
1715
+ return el;
1716
+ }
1717
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1718
+ appendChildren(el, first);
1719
+ return el;
1720
+ }
1721
+ const props = first;
1722
+ const pClass = props.class;
1723
+ if (pClass != null) applyClass(el, pClass);
1724
+ const pId = props.id;
1725
+ if (pId != null) {
1726
+ if (_isDev8 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1727
+ devWarn(
1728
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1729
+ );
1730
+ }
1731
+ el.id = pId;
1732
+ }
1733
+ const pNodes = second !== void 0 ? second : props.nodes;
1734
+ if (pNodes != null) appendChildren(el, pNodes);
1735
+ const pOn = props.on;
1736
+ if (pOn) {
1737
+ for (const ev in pOn) {
1738
+ const handler = pOn[ev];
1739
+ if (typeof handler === "function") {
1740
+ el.addEventListener(ev, handler);
1741
+ } else if (_isDev8) {
1742
+ devWarn(
1743
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1744
+ );
1745
+ }
1746
+ }
1747
+ }
1748
+ const pStyle = props.style;
1749
+ if (pStyle != null) applyStyle(el, pStyle);
1750
+ const pRef = props.ref;
1751
+ if (pRef) pRef.current = el;
1752
+ for (const key in props) {
1753
+ switch (key) {
1754
+ case "class":
1755
+ case "id":
1756
+ case "nodes":
1757
+ case "on":
1758
+ case "style":
1759
+ case "ref":
1760
+ case "onElement":
1761
+ continue;
1762
+ // already handled above / below
1763
+ default: {
1764
+ const value = props[key];
1765
+ if (value == null) continue;
1766
+ const lkey = key.toLowerCase();
1767
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1768
+ if (typeof value === "function") {
1769
+ registerDisposer(el, bindAttribute(el, key, value));
1770
+ } else if (typeof value === "boolean") {
1771
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1772
+ setProp2(el, key, value);
1773
+ } else if (value) {
1774
+ el.setAttribute(key, "");
1775
+ } else {
1776
+ el.removeAttribute(key);
1777
+ }
1506
1778
  } else {
1507
- el.removeAttribute(key);
1779
+ const str = String(value);
1780
+ if (lkey === "srcset") {
1781
+ el.setAttribute(key, sanitizeSrcset(str));
1782
+ } else if (isUrlAttribute(lkey)) {
1783
+ el.setAttribute(key, sanitizeUrl(str));
1784
+ } else {
1785
+ el.setAttribute(key, str);
1786
+ }
1508
1787
  }
1509
- } else {
1510
- const str = String(value);
1511
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1512
1788
  }
1513
1789
  }
1514
1790
  }
1515
- }
1516
- if (props.onElement && typeof props.onElement === "function") {
1517
- props.onElement(el);
1518
- }
1519
- return el;
1791
+ if (props.onElement && typeof props.onElement === "function") {
1792
+ props.onElement(el);
1793
+ }
1794
+ return el;
1795
+ };
1520
1796
  };
1521
1797
 
1522
1798
  // src/core/rendering/html.ts
@@ -1670,31 +1946,38 @@ function createMicroApp(config) {
1670
1946
  }
1671
1947
  let mounted = false;
1672
1948
  function mount(component) {
1673
- if (root instanceof ShadowRoot) {
1674
- root.innerHTML = "";
1675
- } else {
1676
- while (root.firstChild) {
1677
- root.removeChild(root.firstChild);
1678
- }
1679
- }
1949
+ root.replaceChildren();
1680
1950
  const el = component();
1681
1951
  root.appendChild(el);
1682
1952
  mounted = true;
1683
1953
  }
1684
1954
  function unmount() {
1685
1955
  if (!mounted) return;
1686
- if (root instanceof ShadowRoot) {
1687
- root.innerHTML = "";
1688
- } else {
1689
- while (root.firstChild) {
1690
- root.removeChild(root.firstChild);
1691
- }
1692
- }
1956
+ root.replaceChildren();
1693
1957
  mounted = false;
1694
1958
  }
1695
1959
  return { mount, unmount, element: host };
1696
1960
  }
1697
- function loadRemoteModule(url) {
1961
+ function loadRemoteModule(url, optionsOrAllowedOrigins = []) {
1962
+ const opts = Array.isArray(optionsOrAllowedOrigins) ? { allowedOrigins: optionsOrAllowedOrigins } : optionsOrAllowedOrigins;
1963
+ const allowedOrigins = opts.allowedOrigins ?? [];
1964
+ if (allowedOrigins.length > 0) {
1965
+ let parsed;
1966
+ try {
1967
+ parsed = new URL(url, typeof location !== "undefined" ? location.href : void 0);
1968
+ } catch {
1969
+ return Promise.reject(new Error(`loadRemoteModule: invalid URL "${url}"`));
1970
+ }
1971
+ if (!allowedOrigins.includes(parsed.origin)) {
1972
+ return Promise.reject(new Error(`loadRemoteModule: origin "${parsed.origin}" is not in the allowlist`));
1973
+ }
1974
+ } else if (!opts.unsafelyAllowAnyOrigin) {
1975
+ return Promise.reject(
1976
+ new Error(
1977
+ `loadRemoteModule: refused to import "${url}" with no allowedOrigins. Pass { allowedOrigins: [...] } to restrict the origin, or { unsafelyAllowAnyOrigin: true } to opt in to unrestricted imports (CWE-829).`
1978
+ )
1979
+ );
1980
+ }
1698
1981
  const cached = moduleCache.get(url);
1699
1982
  if (cached) return cached;
1700
1983
  const promise = import(
@@ -1784,21 +2067,49 @@ function serviceWorker(scriptUrl, options) {
1784
2067
  const [isReady, setIsReady] = signal(false);
1785
2068
  const [isUpdateAvailable, setIsUpdateAvailable] = signal(false);
1786
2069
  const [error, setError] = signal(null);
2070
+ let disposed = false;
2071
+ let updateFoundHandler = null;
2072
+ let stateChangeHandler = null;
2073
+ let trackedWorker = null;
2074
+ let trackedReg = null;
2075
+ function detachListeners() {
2076
+ if (trackedReg && updateFoundHandler) {
2077
+ trackedReg.removeEventListener("updatefound", updateFoundHandler);
2078
+ }
2079
+ if (trackedWorker && stateChangeHandler) {
2080
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
2081
+ }
2082
+ updateFoundHandler = null;
2083
+ stateChangeHandler = null;
2084
+ trackedWorker = null;
2085
+ trackedReg = null;
2086
+ }
1787
2087
  if ("serviceWorker" in navigator) {
1788
2088
  navigator.serviceWorker.register(scriptUrl, options).then((reg) => {
2089
+ if (disposed) return;
1789
2090
  setRegistration(reg);
1790
2091
  setIsReady(true);
1791
- reg.addEventListener("updatefound", () => {
2092
+ trackedReg = reg;
2093
+ updateFoundHandler = () => {
2094
+ if (disposed) return;
1792
2095
  const newWorker = reg.installing;
1793
2096
  if (newWorker) {
1794
- newWorker.addEventListener("statechange", () => {
2097
+ if (trackedWorker && stateChangeHandler) {
2098
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
2099
+ }
2100
+ trackedWorker = newWorker;
2101
+ stateChangeHandler = () => {
2102
+ if (disposed) return;
1795
2103
  if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
1796
2104
  setIsUpdateAvailable(true);
1797
2105
  }
1798
- });
2106
+ };
2107
+ newWorker.addEventListener("statechange", stateChangeHandler);
1799
2108
  }
1800
- });
2109
+ };
2110
+ reg.addEventListener("updatefound", updateFoundHandler);
1801
2111
  }).catch((err) => {
2112
+ if (disposed) return;
1802
2113
  setError(err instanceof Error ? err : new Error(String(err)));
1803
2114
  });
1804
2115
  }
@@ -1809,6 +2120,8 @@ function serviceWorker(scriptUrl, options) {
1809
2120
  }
1810
2121
  }
1811
2122
  async function unregister() {
2123
+ disposed = true;
2124
+ detachListeners();
1812
2125
  const reg = registration();
1813
2126
  if (reg) {
1814
2127
  const result = await reg.unregister();
@@ -1829,24 +2142,37 @@ function worker(workerFn2) {
1829
2142
  const [error, setError] = signal(null);
1830
2143
  const [loading, setLoading] = signal(false);
1831
2144
  let worker2 = null;
2145
+ let blobUrl = null;
2146
+ const revokeBlobUrl = () => {
2147
+ if (blobUrl) {
2148
+ URL.revokeObjectURL(blobUrl);
2149
+ blobUrl = null;
2150
+ }
2151
+ };
1832
2152
  try {
1833
2153
  if (typeof Worker === "undefined") {
1834
2154
  throw new Error("Web Workers are not supported in this environment");
1835
2155
  }
1836
2156
  const fnBody = workerFn2.toString();
1837
2157
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
1838
- const url = URL.createObjectURL(blob);
1839
- worker2 = new Worker(url);
1840
- URL.revokeObjectURL(url);
1841
- worker2.onmessage = (e) => {
2158
+ blobUrl = URL.createObjectURL(blob);
2159
+ worker2 = new Worker(blobUrl);
2160
+ worker2.addEventListener("message", (e) => {
2161
+ revokeBlobUrl();
1842
2162
  setResult(e.data);
1843
2163
  setLoading(false);
1844
- };
1845
- worker2.onerror = (e) => {
2164
+ });
2165
+ worker2.addEventListener("error", (e) => {
2166
+ revokeBlobUrl();
1846
2167
  setError(new Error(e.message || "Worker error"));
1847
2168
  setLoading(false);
1848
- };
2169
+ if (worker2) {
2170
+ worker2.terminate();
2171
+ worker2 = null;
2172
+ }
2173
+ });
1849
2174
  } catch (err) {
2175
+ revokeBlobUrl();
1850
2176
  setError(err instanceof Error ? err : new Error(String(err)));
1851
2177
  }
1852
2178
  function post(data2) {
@@ -1860,6 +2186,7 @@ function worker(workerFn2) {
1860
2186
  if (!worker2) return;
1861
2187
  worker2.terminate();
1862
2188
  worker2 = null;
2189
+ revokeBlobUrl();
1863
2190
  setLoading(false);
1864
2191
  }
1865
2192
  return { post, result, error, loading, terminate };
@@ -1867,6 +2194,14 @@ function worker(workerFn2) {
1867
2194
  function workerFn(fn) {
1868
2195
  const [loading, setLoading] = signal(false);
1869
2196
  let worker2 = null;
2197
+ let blobUrl = null;
2198
+ const revokeBlobUrl = () => {
2199
+ if (blobUrl) {
2200
+ URL.revokeObjectURL(blobUrl);
2201
+ blobUrl = null;
2202
+ }
2203
+ };
2204
+ const queue = [];
1870
2205
  try {
1871
2206
  if (typeof Worker === "undefined") {
1872
2207
  throw new Error("Web Workers are not supported in this environment");
@@ -1882,10 +2217,26 @@ function workerFn(fn) {
1882
2217
  ],
1883
2218
  { type: "application/javascript" }
1884
2219
  );
1885
- const url = URL.createObjectURL(blob);
1886
- worker2 = new Worker(url);
1887
- URL.revokeObjectURL(url);
2220
+ blobUrl = URL.createObjectURL(blob);
2221
+ worker2 = new Worker(blobUrl);
2222
+ worker2.addEventListener("message", (e) => {
2223
+ revokeBlobUrl();
2224
+ const head2 = queue.shift();
2225
+ if (queue.length === 0) setLoading(false);
2226
+ if (head2) head2.resolve(e.data);
2227
+ });
2228
+ worker2.addEventListener("error", (e) => {
2229
+ revokeBlobUrl();
2230
+ const err = new Error(e.message || "Worker error");
2231
+ while (queue.length > 0) queue.shift().reject(err);
2232
+ setLoading(false);
2233
+ if (worker2) {
2234
+ worker2.terminate();
2235
+ worker2 = null;
2236
+ }
2237
+ });
1888
2238
  } catch {
2239
+ revokeBlobUrl();
1889
2240
  }
1890
2241
  function run(...args) {
1891
2242
  return new Promise((resolve, reject) => {
@@ -1894,14 +2245,7 @@ function workerFn(fn) {
1894
2245
  return;
1895
2246
  }
1896
2247
  setLoading(true);
1897
- worker2.onmessage = (e) => {
1898
- setLoading(false);
1899
- resolve(e.data);
1900
- };
1901
- worker2.onerror = (e) => {
1902
- setLoading(false);
1903
- reject(new Error(e.message || "Worker error"));
1904
- };
2248
+ queue.push({ resolve, reject });
1905
2249
  worker2.postMessage(args);
1906
2250
  });
1907
2251
  }
@@ -1909,6 +2253,9 @@ function workerFn(fn) {
1909
2253
  if (!worker2) return;
1910
2254
  worker2.terminate();
1911
2255
  worker2 = null;
2256
+ const err = new Error("Worker terminated");
2257
+ while (queue.length > 0) queue.shift().reject(err);
2258
+ revokeBlobUrl();
1912
2259
  setLoading(false);
1913
2260
  }
1914
2261
  return { run, loading, terminate };
@@ -1916,20 +2263,63 @@ function workerFn(fn) {
1916
2263
  function createWorkerPool(workerFn2, poolSize) {
1917
2264
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
1918
2265
  const workers = [];
2266
+ const queues = [];
2267
+ const inflight = [];
1919
2268
  let currentIndex = 0;
1920
2269
  let alive = true;
2270
+ let blobUrl = null;
2271
+ let firedOnce = false;
2272
+ const revokeBlobUrl = () => {
2273
+ if (blobUrl) {
2274
+ URL.revokeObjectURL(blobUrl);
2275
+ blobUrl = null;
2276
+ }
2277
+ };
2278
+ function dispatchNext(idx) {
2279
+ if (!alive || inflight[idx] || queues[idx].length === 0) return;
2280
+ const w = workers[idx];
2281
+ const slot2 = queues[idx].shift();
2282
+ const onMsg = (e) => {
2283
+ if (!firedOnce) {
2284
+ firedOnce = true;
2285
+ revokeBlobUrl();
2286
+ }
2287
+ w.removeEventListener("message", onMsg);
2288
+ w.removeEventListener("error", onErr);
2289
+ inflight[idx] = null;
2290
+ slot2.resolve(e.data);
2291
+ dispatchNext(idx);
2292
+ };
2293
+ const onErr = (e) => {
2294
+ if (!firedOnce) {
2295
+ firedOnce = true;
2296
+ revokeBlobUrl();
2297
+ }
2298
+ w.removeEventListener("message", onMsg);
2299
+ w.removeEventListener("error", onErr);
2300
+ inflight[idx] = null;
2301
+ slot2.reject(new Error(e.message || "Worker error"));
2302
+ dispatchNext(idx);
2303
+ };
2304
+ inflight[idx] = { ...slot2, onMsg, onErr };
2305
+ w.addEventListener("message", onMsg);
2306
+ w.addEventListener("error", onErr);
2307
+ w.postMessage(slot2.data);
2308
+ }
1921
2309
  try {
1922
2310
  if (typeof Worker === "undefined") {
1923
2311
  throw new Error("Web Workers are not supported in this environment");
1924
2312
  }
1925
2313
  const fnBody = workerFn2.toString();
1926
2314
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
1927
- const url = URL.createObjectURL(blob);
2315
+ blobUrl = URL.createObjectURL(blob);
1928
2316
  for (let i2 = 0; i2 < size; i2++) {
1929
- workers.push(new Worker(url));
2317
+ workers.push(new Worker(blobUrl));
2318
+ queues.push([]);
2319
+ inflight.push(null);
1930
2320
  }
1931
- URL.revokeObjectURL(url);
1932
2321
  } catch {
2322
+ revokeBlobUrl();
1933
2323
  }
1934
2324
  function execute(data2) {
1935
2325
  return new Promise((resolve, reject) => {
@@ -1937,23 +2327,25 @@ function createWorkerPool(workerFn2, poolSize) {
1937
2327
  reject(new Error("Worker pool is not available"));
1938
2328
  return;
1939
2329
  }
1940
- const worker2 = workers[currentIndex % workers.length];
2330
+ const idx = currentIndex % workers.length;
1941
2331
  currentIndex++;
1942
- worker2.onmessage = (e) => {
1943
- resolve(e.data);
1944
- };
1945
- worker2.onerror = (e) => {
1946
- reject(new Error(e.message || "Worker error"));
1947
- };
1948
- worker2.postMessage(data2);
2332
+ queues[idx].push({ data: data2, resolve, reject });
2333
+ dispatchNext(idx);
1949
2334
  });
1950
2335
  }
1951
2336
  function terminate() {
1952
2337
  alive = false;
1953
- for (const w of workers) {
1954
- w.terminate();
2338
+ for (const w of workers) w.terminate();
2339
+ const err = new Error("Worker pool terminated");
2340
+ for (let i2 = 0; i2 < queues.length; i2++) {
2341
+ const inf = inflight[i2];
2342
+ if (inf) inf.reject(err);
2343
+ for (const s2 of queues[i2]) s2.reject(err);
2344
+ queues[i2] = [];
2345
+ inflight[i2] = null;
1955
2346
  }
1956
2347
  workers.length = 0;
2348
+ revokeBlobUrl();
1957
2349
  }
1958
2350
  return { execute, terminate };
1959
2351
  }
@@ -1989,7 +2381,28 @@ function wasm(source2, config = {}) {
1989
2381
  };
1990
2382
  }
1991
2383
  async function loadWasmModule(source2, imports, cacheKey) {
1992
- const key = cacheKey || (typeof source2 === "string" ? source2 : void 0);
2384
+ const isOptionsBag = !!(imports && ("allowedOrigins" in imports || "unsafelyAllowAnyOrigin" in imports));
2385
+ const opts = isOptionsBag ? imports : { imports, cacheKey };
2386
+ const wasmImports = opts.imports;
2387
+ const key = opts.cacheKey || (typeof source2 === "string" ? source2 : void 0);
2388
+ if (typeof source2 === "string") {
2389
+ const allowed = opts.allowedOrigins ?? [];
2390
+ if (allowed.length > 0) {
2391
+ let parsed;
2392
+ try {
2393
+ parsed = new URL(source2, typeof location !== "undefined" ? location.href : void 0);
2394
+ } catch {
2395
+ throw new Error(`loadWasmModule: invalid URL "${source2}"`);
2396
+ }
2397
+ if (!allowed.includes(parsed.origin)) {
2398
+ throw new Error(`loadWasmModule: origin "${parsed.origin}" is not in the allowlist`);
2399
+ }
2400
+ } else if (!opts.unsafelyAllowAnyOrigin) {
2401
+ throw new Error(
2402
+ `loadWasmModule: refused to fetch "${source2}" with no allowedOrigins. Pass { allowedOrigins: [...] } to restrict the origin, or { unsafelyAllowAnyOrigin: true } to opt in (CWE-829).`
2403
+ );
2404
+ }
2405
+ }
1993
2406
  if (key) {
1994
2407
  const cachedInstance = instanceCache.get(key);
1995
2408
  if (cachedInstance) {
@@ -2005,7 +2418,7 @@ async function loadWasmModule(source2, imports, cacheKey) {
2005
2418
  if (typeof source2 === "string") {
2006
2419
  if (typeof WebAssembly.instantiateStreaming === "function") {
2007
2420
  const response2 = fetch(source2);
2008
- const result = await WebAssembly.instantiateStreaming(response2, imports || {});
2421
+ const result = await WebAssembly.instantiateStreaming(response2, wasmImports || {});
2009
2422
  if (key) {
2010
2423
  moduleCache2.set(key, result.module);
2011
2424
  instanceCache.set(key, result.instance);
@@ -2022,12 +2435,28 @@ async function loadWasmModule(source2, imports, cacheKey) {
2022
2435
  module2 = await WebAssembly.compile(bytes);
2023
2436
  if (key) moduleCache2.set(key, module2);
2024
2437
  }
2025
- const instance = await WebAssembly.instantiate(module2, imports || {});
2438
+ const instance = await WebAssembly.instantiate(module2, wasmImports || {});
2026
2439
  if (key) instanceCache.set(key, instance);
2027
2440
  return instance;
2028
2441
  }
2029
- async function preloadWasm(url) {
2442
+ async function preloadWasm(url, options = {}) {
2030
2443
  if (moduleCache2.has(url)) return;
2444
+ const allowed = options.allowedOrigins ?? [];
2445
+ if (allowed.length > 0) {
2446
+ let parsed;
2447
+ try {
2448
+ parsed = new URL(url, typeof location !== "undefined" ? location.href : void 0);
2449
+ } catch {
2450
+ throw new Error(`preloadWasm: invalid URL "${url}"`);
2451
+ }
2452
+ if (!allowed.includes(parsed.origin)) {
2453
+ throw new Error(`preloadWasm: origin "${parsed.origin}" is not in the allowlist`);
2454
+ }
2455
+ } else if (!options.unsafelyAllowAnyOrigin) {
2456
+ throw new Error(
2457
+ `preloadWasm: refused to fetch "${url}" with no allowedOrigins. Pass { allowedOrigins: [...] } or { unsafelyAllowAnyOrigin: true } (CWE-829).`
2458
+ );
2459
+ }
2031
2460
  let module2;
2032
2461
  if (typeof WebAssembly.compileStreaming === "function") {
2033
2462
  module2 = await WebAssembly.compileStreaming(fetch(url));