sibujs 1.5.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 (207) hide show
  1. package/dist/browser.cjs +238 -69
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +916 -292
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/chunk-3JHCYHWN.js +125 -0
  11. package/dist/{chunk-VAPYJN4X.js → chunk-3LR7GLWQ.js} +93 -23
  12. package/dist/{chunk-RJ46C3CS.js → chunk-3NSGB5JN.js} +71 -20
  13. package/dist/{chunk-XUEEGU5O.js → chunk-52YJLLRO.js} +16 -4
  14. package/dist/{chunk-XHK6BDAJ.js → chunk-54EDRCEF.js} +25 -8
  15. package/dist/chunk-7JDB7I65.js +1327 -0
  16. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  17. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  18. package/dist/{chunk-BGN5ZMP4.js → chunk-GTBNNBJ6.js} +14 -2
  19. package/dist/chunk-HB24TBAF.js +121 -0
  20. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  21. package/dist/{chunk-BGTHZHJ5.js → chunk-JA6667UN.js} +188 -44
  22. package/dist/{chunk-7GRNSCFT.js → chunk-JXMMDLBY.js} +306 -183
  23. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  24. package/dist/{chunk-SFKNRVCU.js → chunk-KLRMB5ZS.js} +135 -79
  25. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  26. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  27. package/dist/{chunk-BMPL52BF.js → chunk-MIUAXB7K.js} +118 -66
  28. package/dist/{chunk-JCDUJN2F.js → chunk-ND2664SF.js} +486 -153
  29. package/dist/{chunk-VQDZK23A.js → chunk-O2MNQFLP.js} +181 -66
  30. package/dist/{chunk-NHUC2QWH.js → chunk-R73P76YZ.js} +1 -1
  31. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  32. package/dist/chunk-UCS6AMJ7.js +79 -0
  33. package/dist/{chunk-K4G4ZQNR.js → chunk-VLPPXTYG.js} +84 -38
  34. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +410 -99
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +513 -223
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +475 -144
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3355 -1541
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +920 -292
  54. package/dist/index.d.cts +71 -8
  55. package/dist/index.d.ts +71 -8
  56. package/dist/index.js +28 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  59. package/dist/motion.cjs +77 -34
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +335 -69
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +279 -108
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +635 -260
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +642 -222
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +252 -63
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +463 -137
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +977 -94
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K5ZUMYVS.js +0 -89
  136. package/dist/chunk-KQPDEVVS.js +0 -398
  137. package/dist/chunk-L6JRBDNS.js +0 -60
  138. package/dist/chunk-LA6KQEDU.js +0 -712
  139. package/dist/chunk-MB6QFH3I.js +0 -2776
  140. package/dist/chunk-MDVXJWFN.js +0 -304
  141. package/dist/chunk-MEZVEBPN.js +0 -2008
  142. package/dist/chunk-MK4ERFYL.js +0 -2249
  143. package/dist/chunk-MLKGABMK.js +0 -9
  144. package/dist/chunk-MQ5GOYPH.js +0 -2249
  145. package/dist/chunk-MYRV7VDM.js +0 -742
  146. package/dist/chunk-N6IZB6KJ.js +0 -567
  147. package/dist/chunk-NEKUBFPT.js +0 -60
  148. package/dist/chunk-NMRUZALC.js +0 -1097
  149. package/dist/chunk-NYVAC6P5.js +0 -37
  150. package/dist/chunk-NZIIMDWI.js +0 -84
  151. package/dist/chunk-OF7UZIVB.js +0 -725
  152. package/dist/chunk-P3XWXJZU.js +0 -282
  153. package/dist/chunk-P6W3STU4.js +0 -2249
  154. package/dist/chunk-PBHF5WKN.js +0 -616
  155. package/dist/chunk-PDZQY43A.js +0 -616
  156. package/dist/chunk-PTQJDMRT.js +0 -146
  157. package/dist/chunk-PZEGYCF5.js +0 -61
  158. package/dist/chunk-QBMDLBU2.js +0 -975
  159. package/dist/chunk-QWZG56ET.js +0 -2744
  160. package/dist/chunk-RQGQSLQK.js +0 -725
  161. package/dist/chunk-SDLZDHKP.js +0 -107
  162. package/dist/chunk-TDGZL5CU.js +0 -365
  163. package/dist/chunk-TNQWPPE6.js +0 -37
  164. package/dist/chunk-TSOKIX5Z.js +0 -654
  165. package/dist/chunk-UHNL42EF.js +0 -2730
  166. package/dist/chunk-UNXCEF6S.js +0 -21
  167. package/dist/chunk-V2XTI523.js +0 -347
  168. package/dist/chunk-VAU366PN.js +0 -2241
  169. package/dist/chunk-VMVDTCXB.js +0 -712
  170. package/dist/chunk-VQNQZCWJ.js +0 -61
  171. package/dist/chunk-VRW3FULF.js +0 -725
  172. package/dist/chunk-WADYRCO2.js +0 -304
  173. package/dist/chunk-WILQZRO4.js +0 -282
  174. package/dist/chunk-WR5D4EGH.js +0 -26
  175. package/dist/chunk-WUHJISPP.js +0 -298
  176. package/dist/chunk-XYU6TZOW.js +0 -182
  177. package/dist/chunk-Y6GP4QGG.js +0 -276
  178. package/dist/chunk-YECR7UIA.js +0 -347
  179. package/dist/chunk-YUTWTI4B.js +0 -654
  180. package/dist/chunk-Z65KYU7I.js +0 -26
  181. package/dist/chunk-Z6POF5YC.js +0 -975
  182. package/dist/chunk-ZBJP6WFL.js +0 -482
  183. package/dist/chunk-ZD6OAMTH.js +0 -277
  184. package/dist/chunk-ZWKZCBO6.js +0 -317
  185. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  186. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  187. package/dist/contracts-DOrhwbke.d.cts +0 -245
  188. package/dist/contracts-DOrhwbke.d.ts +0 -245
  189. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  190. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  191. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  192. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  193. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  194. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  195. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  196. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  197. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  198. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  199. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  200. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  201. package/dist/ssr-3RXHP5ES.js +0 -38
  202. package/dist/ssr-6GIMY5MX.js +0 -38
  203. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  204. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  205. package/dist/ssr-WKUPVSSK.js +0 -36
  206. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  207. 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,17 +757,17 @@ function queueSignalNotification(signal2) {
658
757
  }
659
758
  }
660
759
  }
661
- var MAX_DRAIN_ITERATIONS = 1e3;
760
+ var maxDrainIterations = 1e5;
662
761
  function drainNotificationQueue() {
663
762
  if (notifyDepth > 0) return;
664
763
  notifyDepth++;
665
764
  try {
666
765
  let i2 = 0;
667
766
  while (i2 < pendingQueue.length) {
668
- if (i2 >= MAX_DRAIN_ITERATIONS) {
767
+ if (i2 >= maxDrainIterations) {
669
768
  if (typeof console !== "undefined") {
670
769
  console.error(
671
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
770
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
672
771
  );
673
772
  }
674
773
  break;
@@ -677,47 +776,52 @@ function drainNotificationQueue() {
677
776
  i2++;
678
777
  }
679
778
  } finally {
680
- pendingQueue.length = 0;
681
- pendingSet.clear();
682
779
  notifyDepth--;
780
+ if (notifyDepth === 0) {
781
+ pendingQueue.length = 0;
782
+ pendingSet.clear();
783
+ }
683
784
  }
684
785
  }
685
786
  function propagateDirty(sub2) {
686
787
  sub2();
687
- let sig = sub2._sig;
688
- 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();
689
795
  const first = sig.__f;
690
796
  if (first) {
691
797
  if (first._c) {
692
798
  const nSig = first._sig;
693
- nSig._d = true;
694
- sig = nSig;
695
- continue;
696
- }
697
- if (!pendingSet.has(first)) {
799
+ if (!nSig._d) {
800
+ nSig._d = true;
801
+ stack.push(nSig);
802
+ }
803
+ } else if (!pendingSet.has(first)) {
698
804
  pendingSet.add(first);
699
805
  pendingQueue.push(first);
700
806
  }
701
- break;
807
+ continue;
702
808
  }
703
809
  const subs = sig[SUBS];
704
- if (!subs) break;
705
- let nextSig;
810
+ if (!subs) continue;
706
811
  for (const s2 of subs) {
707
812
  if (s2._c) {
708
- s2();
709
813
  const nSig = s2._sig;
710
- if (nSig && !nextSig) {
711
- nextSig = nSig;
712
- } else if (nSig) {
713
- propagateDirty(s2);
814
+ if (nSig && !nSig._d) {
815
+ nSig._d = true;
816
+ stack.push(nSig);
817
+ } else if (!nSig) {
818
+ s2();
714
819
  }
715
820
  } else if (!pendingSet.has(s2)) {
716
821
  pendingSet.add(s2);
717
822
  pendingQueue.push(s2);
718
823
  }
719
824
  }
720
- sig = nextSig;
721
825
  }
722
826
  }
723
827
  function notifySubscribers(signal2) {
@@ -741,13 +845,23 @@ function notifySubscribers(signal2) {
741
845
  }
742
846
  let i2 = 0;
743
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
+ }
744
856
  safeInvoke(pendingQueue[i2]);
745
857
  i2++;
746
858
  }
747
859
  } finally {
748
- pendingQueue.length = 0;
749
- pendingSet.clear();
750
860
  notifyDepth--;
861
+ if (notifyDepth === 0) {
862
+ pendingQueue.length = 0;
863
+ pendingSet.clear();
864
+ }
751
865
  }
752
866
  return;
753
867
  }
@@ -767,30 +881,48 @@ function notifySubscribers(signal2) {
767
881
  notifyDepth++;
768
882
  try {
769
883
  let directCount = 0;
884
+ let hasComputedSub = false;
770
885
  for (const sub2 of subs) {
886
+ if (sub2._c) hasComputedSub = true;
771
887
  pendingQueue[directCount++] = sub2;
772
888
  }
773
- for (let i3 = 0; i3 < directCount; i3++) {
774
- if (pendingQueue[i3]._c) {
775
- propagateDirty(pendingQueue[i3]);
889
+ if (!hasComputedSub) {
890
+ for (let i3 = 0; i3 < directCount; i3++) {
891
+ safeInvoke(pendingQueue[i3]);
776
892
  }
777
- }
778
- for (let i3 = 0; i3 < directCount; i3++) {
779
- if (!pendingQueue[i3]._c) {
780
- if (!pendingSet.has(pendingQueue[i3])) {
781
- 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);
782
904
  }
783
905
  }
784
906
  }
785
907
  let i2 = directCount;
786
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
+ }
787
917
  safeInvoke(pendingQueue[i2]);
788
918
  i2++;
789
919
  }
790
920
  } finally {
791
- pendingQueue.length = 0;
792
- pendingSet.clear();
793
921
  notifyDepth--;
922
+ if (notifyDepth === 0) {
923
+ pendingQueue.length = 0;
924
+ pendingSet.clear();
925
+ }
794
926
  }
795
927
  }
796
928
  function cleanup(subscriber) {
@@ -801,7 +933,9 @@ function cleanup(subscriber) {
801
933
  if (subs) {
802
934
  subs.delete(subscriber);
803
935
  if (singleDep.__f === subscriber) {
804
- 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;
805
939
  }
806
940
  }
807
941
  sub2._dep = void 0;
@@ -814,19 +948,15 @@ function cleanup(subscriber) {
814
948
  if (subs) {
815
949
  subs.delete(subscriber);
816
950
  if (signal2.__f === subscriber) {
817
- 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;
818
954
  }
819
955
  }
820
956
  }
821
957
  deps.clear();
822
958
  }
823
959
 
824
- // src/core/ssr-context.ts
825
- var ssrMode = false;
826
- function isSSR() {
827
- return ssrMode;
828
- }
829
-
830
960
  // src/core/signals/effect.ts
831
961
  var _g = globalThis;
832
962
  function effect(effectFn, options) {
@@ -834,26 +964,86 @@ function effect(effectFn, options) {
834
964
  if (isSSR()) return () => {
835
965
  };
836
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);
837
986
  const wrappedFn = onError ? () => {
838
987
  try {
839
- effectFn();
988
+ invokeBody();
840
989
  } catch (err) {
841
990
  onError(err);
842
991
  }
843
- } : effectFn;
992
+ } : invokeBody;
844
993
  let cleanupHandle = () => {
845
994
  };
995
+ let running = false;
846
996
  const subscriber = () => {
847
- cleanupHandle();
848
- 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
+ }
849
1013
  };
850
- cleanupHandle = track(wrappedFn, subscriber);
1014
+ running = true;
1015
+ try {
1016
+ cleanupHandle = track(wrappedFn, subscriber);
1017
+ } finally {
1018
+ running = false;
1019
+ }
851
1020
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
852
1021
  if (hook) hook.emit("effect:create", { effectFn });
1022
+ let disposed = false;
853
1023
  return () => {
1024
+ if (disposed) return;
1025
+ disposed = true;
854
1026
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
855
- if (h) h.emit("effect:destroy", { effectFn });
856
- 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
+ }
857
1047
  };
858
1048
  }
859
1049
 
@@ -1025,10 +1215,13 @@ function enqueueBatchedSignal(signal2) {
1025
1215
  return true;
1026
1216
  }
1027
1217
  function flushBatch() {
1028
- for (const signal2 of pendingSignals) {
1029
- queueSignalNotification(signal2);
1218
+ try {
1219
+ for (const signal2 of pendingSignals) {
1220
+ queueSignalNotification(signal2);
1221
+ }
1222
+ } finally {
1223
+ pendingSignals.clear();
1030
1224
  }
1031
- pendingSignals.clear();
1032
1225
  drainNotificationQueue();
1033
1226
  }
1034
1227
 
@@ -1075,24 +1268,42 @@ function createISR(options) {
1075
1268
  const { revalidateAfter, fetcher, initialData } = options;
1076
1269
  const [data2, setData] = signal(initialData);
1077
1270
  const [timestamp, setTimestamp] = signal(initialData !== void 0 ? Date.now() : 0);
1271
+ const controller = new AbortController();
1272
+ let inFlight = false;
1273
+ let disposed = false;
1078
1274
  const isStale = () => {
1079
1275
  const ts = timestamp();
1080
1276
  if (ts === 0) return true;
1081
1277
  return Date.now() - ts >= revalidateAfter;
1082
1278
  };
1083
1279
  const revalidate = async () => {
1084
- const result = await fetcher();
1085
- setData(result);
1086
- 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
+ }
1087
1291
  };
1088
1292
  if (initialData === void 0) {
1089
- revalidate();
1293
+ revalidate().catch((err) => {
1294
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
1295
+ });
1090
1296
  }
1091
1297
  const intervalId = setInterval(() => {
1092
- revalidate();
1298
+ revalidate().catch((err) => {
1299
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] revalidate failed", err);
1300
+ });
1093
1301
  }, revalidateAfter);
1094
1302
  const dispose = () => {
1303
+ if (disposed) return;
1304
+ disposed = true;
1095
1305
  clearInterval(intervalId);
1306
+ controller.abort();
1096
1307
  };
1097
1308
  return { data: data2, isStale, revalidate, dispose };
1098
1309
  }
@@ -1200,6 +1411,9 @@ function createMiddlewareChain() {
1200
1411
 
1201
1412
  // src/reactivity/bindAttribute.ts
1202
1413
  var _isDev5 = isDev();
1414
+ function setProp(el, key, val) {
1415
+ el[key] = val;
1416
+ }
1203
1417
  function isEventHandlerAttr3(name) {
1204
1418
  if (name.length < 3) return false;
1205
1419
  const lower = name.toLowerCase();
@@ -1225,7 +1439,7 @@ function bindAttribute(el, attr, getter) {
1225
1439
  }
1226
1440
  if (typeof value === "boolean") {
1227
1441
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1228
- el[attr] = value;
1442
+ setProp(el, attr, value);
1229
1443
  } else if (value) {
1230
1444
  el.setAttribute(attr, "");
1231
1445
  } else {
@@ -1235,7 +1449,7 @@ function bindAttribute(el, attr, getter) {
1235
1449
  }
1236
1450
  const str = String(value);
1237
1451
  if ((attr === "value" || attr === "checked") && attr in el) {
1238
- el[attr] = attr === "checked" ? Boolean(value) : str;
1452
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1239
1453
  } else {
1240
1454
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1241
1455
  }
@@ -1272,24 +1486,29 @@ function bindChildNode(placeholder, getter) {
1272
1486
  let newNodes;
1273
1487
  if (Array.isArray(result)) {
1274
1488
  newNodes = [];
1489
+ const seen = /* @__PURE__ */ new Set();
1275
1490
  for (let i2 = 0; i2 < result.length; i2++) {
1276
1491
  const item = result[i2];
1277
1492
  if (item == null || typeof item === "boolean") continue;
1278
- 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);
1279
1501
  }
1280
1502
  } else {
1281
1503
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1282
1504
  newNodes = [node];
1283
1505
  }
1284
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1285
- if (reused) {
1506
+ let reused;
1507
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1508
+ const lastSet = new Set(lastNodes);
1509
+ reused = /* @__PURE__ */ new Set();
1286
1510
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1287
- for (let j = 0; j < lastNodes.length; j++) {
1288
- if (newNodes[i2] === lastNodes[j]) {
1289
- reused.add(newNodes[i2]);
1290
- break;
1291
- }
1292
- }
1511
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1293
1512
  }
1294
1513
  }
1295
1514
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1329,6 +1548,30 @@ function registerDisposer(node, teardown) {
1329
1548
 
1330
1549
  // src/core/rendering/tagFactory.ts
1331
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
+ }
1332
1575
  var kebabCache = /* @__PURE__ */ new Map();
1333
1576
  function toKebab(prop) {
1334
1577
  let cached = kebabCache.get(prop);
@@ -1453,79 +1696,103 @@ function appendChildren(el, nodes) {
1453
1696
  }
1454
1697
  }
1455
1698
  }
1456
- var tagFactory = (tag, ns) => (first, second) => {
1457
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1458
- if (first === void 0) return el;
1459
- if (typeof first === "string") {
1460
- if (second !== void 0) {
1461
- el.setAttribute("class", first);
1462
- 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;
1463
1711
  return el;
1464
1712
  }
1465
- el.textContent = first;
1466
- return el;
1467
- }
1468
- if (typeof first === "number") {
1469
- el.textContent = String(first);
1470
- return el;
1471
- }
1472
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1473
- appendChildren(el, first);
1474
- return el;
1475
- }
1476
- const props = first;
1477
- const pClass = props.class;
1478
- if (pClass != null) applyClass(el, pClass);
1479
- const pId = props.id;
1480
- if (pId != null) el.id = pId;
1481
- const pNodes = second !== void 0 ? second : props.nodes;
1482
- if (pNodes != null) appendChildren(el, pNodes);
1483
- const pOn = props.on;
1484
- if (pOn) {
1485
- for (const ev in pOn) {
1486
- el.addEventListener(ev, pOn[ev]);
1487
- }
1488
- }
1489
- const pStyle = props.style;
1490
- if (pStyle != null) applyStyle(el, pStyle);
1491
- const pRef = props.ref;
1492
- if (pRef) pRef.current = el;
1493
- for (const key in props) {
1494
- switch (key) {
1495
- case "class":
1496
- case "id":
1497
- case "nodes":
1498
- case "on":
1499
- case "style":
1500
- case "ref":
1501
- case "onElement":
1502
- continue;
1503
- // already handled above / below
1504
- default: {
1505
- const value = props[key];
1506
- if (value == null) continue;
1507
- if (key[0] === "o" && key[1] === "n") continue;
1508
- if (typeof value === "function") {
1509
- registerDisposer(el, bindAttribute(el, key, value));
1510
- } else if (typeof value === "boolean") {
1511
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1512
- el[key] = value;
1513
- } else if (value) {
1514
- 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
+ }
1515
1778
  } else {
1516
- 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
+ }
1517
1787
  }
1518
- } else {
1519
- const str = String(value);
1520
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1521
1788
  }
1522
1789
  }
1523
1790
  }
1524
- }
1525
- if (props.onElement && typeof props.onElement === "function") {
1526
- props.onElement(el);
1527
- }
1528
- return el;
1791
+ if (props.onElement && typeof props.onElement === "function") {
1792
+ props.onElement(el);
1793
+ }
1794
+ return el;
1795
+ };
1529
1796
  };
1530
1797
 
1531
1798
  // src/core/rendering/html.ts
@@ -1679,31 +1946,38 @@ function createMicroApp(config) {
1679
1946
  }
1680
1947
  let mounted = false;
1681
1948
  function mount(component) {
1682
- if (root instanceof ShadowRoot) {
1683
- root.innerHTML = "";
1684
- } else {
1685
- while (root.firstChild) {
1686
- root.removeChild(root.firstChild);
1687
- }
1688
- }
1949
+ root.replaceChildren();
1689
1950
  const el = component();
1690
1951
  root.appendChild(el);
1691
1952
  mounted = true;
1692
1953
  }
1693
1954
  function unmount() {
1694
1955
  if (!mounted) return;
1695
- if (root instanceof ShadowRoot) {
1696
- root.innerHTML = "";
1697
- } else {
1698
- while (root.firstChild) {
1699
- root.removeChild(root.firstChild);
1700
- }
1701
- }
1956
+ root.replaceChildren();
1702
1957
  mounted = false;
1703
1958
  }
1704
1959
  return { mount, unmount, element: host };
1705
1960
  }
1706
- 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
+ }
1707
1981
  const cached = moduleCache.get(url);
1708
1982
  if (cached) return cached;
1709
1983
  const promise = import(
@@ -1793,21 +2067,49 @@ function serviceWorker(scriptUrl, options) {
1793
2067
  const [isReady, setIsReady] = signal(false);
1794
2068
  const [isUpdateAvailable, setIsUpdateAvailable] = signal(false);
1795
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
+ }
1796
2087
  if ("serviceWorker" in navigator) {
1797
2088
  navigator.serviceWorker.register(scriptUrl, options).then((reg) => {
2089
+ if (disposed) return;
1798
2090
  setRegistration(reg);
1799
2091
  setIsReady(true);
1800
- reg.addEventListener("updatefound", () => {
2092
+ trackedReg = reg;
2093
+ updateFoundHandler = () => {
2094
+ if (disposed) return;
1801
2095
  const newWorker = reg.installing;
1802
2096
  if (newWorker) {
1803
- newWorker.addEventListener("statechange", () => {
2097
+ if (trackedWorker && stateChangeHandler) {
2098
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
2099
+ }
2100
+ trackedWorker = newWorker;
2101
+ stateChangeHandler = () => {
2102
+ if (disposed) return;
1804
2103
  if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
1805
2104
  setIsUpdateAvailable(true);
1806
2105
  }
1807
- });
2106
+ };
2107
+ newWorker.addEventListener("statechange", stateChangeHandler);
1808
2108
  }
1809
- });
2109
+ };
2110
+ reg.addEventListener("updatefound", updateFoundHandler);
1810
2111
  }).catch((err) => {
2112
+ if (disposed) return;
1811
2113
  setError(err instanceof Error ? err : new Error(String(err)));
1812
2114
  });
1813
2115
  }
@@ -1818,6 +2120,8 @@ function serviceWorker(scriptUrl, options) {
1818
2120
  }
1819
2121
  }
1820
2122
  async function unregister() {
2123
+ disposed = true;
2124
+ detachListeners();
1821
2125
  const reg = registration();
1822
2126
  if (reg) {
1823
2127
  const result = await reg.unregister();
@@ -1838,24 +2142,37 @@ function worker(workerFn2) {
1838
2142
  const [error, setError] = signal(null);
1839
2143
  const [loading, setLoading] = signal(false);
1840
2144
  let worker2 = null;
2145
+ let blobUrl = null;
2146
+ const revokeBlobUrl = () => {
2147
+ if (blobUrl) {
2148
+ URL.revokeObjectURL(blobUrl);
2149
+ blobUrl = null;
2150
+ }
2151
+ };
1841
2152
  try {
1842
2153
  if (typeof Worker === "undefined") {
1843
2154
  throw new Error("Web Workers are not supported in this environment");
1844
2155
  }
1845
2156
  const fnBody = workerFn2.toString();
1846
2157
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
1847
- const url = URL.createObjectURL(blob);
1848
- worker2 = new Worker(url);
1849
- URL.revokeObjectURL(url);
1850
- worker2.onmessage = (e) => {
2158
+ blobUrl = URL.createObjectURL(blob);
2159
+ worker2 = new Worker(blobUrl);
2160
+ worker2.addEventListener("message", (e) => {
2161
+ revokeBlobUrl();
1851
2162
  setResult(e.data);
1852
2163
  setLoading(false);
1853
- };
1854
- worker2.onerror = (e) => {
2164
+ });
2165
+ worker2.addEventListener("error", (e) => {
2166
+ revokeBlobUrl();
1855
2167
  setError(new Error(e.message || "Worker error"));
1856
2168
  setLoading(false);
1857
- };
2169
+ if (worker2) {
2170
+ worker2.terminate();
2171
+ worker2 = null;
2172
+ }
2173
+ });
1858
2174
  } catch (err) {
2175
+ revokeBlobUrl();
1859
2176
  setError(err instanceof Error ? err : new Error(String(err)));
1860
2177
  }
1861
2178
  function post(data2) {
@@ -1869,6 +2186,7 @@ function worker(workerFn2) {
1869
2186
  if (!worker2) return;
1870
2187
  worker2.terminate();
1871
2188
  worker2 = null;
2189
+ revokeBlobUrl();
1872
2190
  setLoading(false);
1873
2191
  }
1874
2192
  return { post, result, error, loading, terminate };
@@ -1876,6 +2194,14 @@ function worker(workerFn2) {
1876
2194
  function workerFn(fn) {
1877
2195
  const [loading, setLoading] = signal(false);
1878
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 = [];
1879
2205
  try {
1880
2206
  if (typeof Worker === "undefined") {
1881
2207
  throw new Error("Web Workers are not supported in this environment");
@@ -1891,10 +2217,26 @@ function workerFn(fn) {
1891
2217
  ],
1892
2218
  { type: "application/javascript" }
1893
2219
  );
1894
- const url = URL.createObjectURL(blob);
1895
- worker2 = new Worker(url);
1896
- 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
+ });
1897
2238
  } catch {
2239
+ revokeBlobUrl();
1898
2240
  }
1899
2241
  function run(...args) {
1900
2242
  return new Promise((resolve, reject) => {
@@ -1903,14 +2245,7 @@ function workerFn(fn) {
1903
2245
  return;
1904
2246
  }
1905
2247
  setLoading(true);
1906
- worker2.onmessage = (e) => {
1907
- setLoading(false);
1908
- resolve(e.data);
1909
- };
1910
- worker2.onerror = (e) => {
1911
- setLoading(false);
1912
- reject(new Error(e.message || "Worker error"));
1913
- };
2248
+ queue.push({ resolve, reject });
1914
2249
  worker2.postMessage(args);
1915
2250
  });
1916
2251
  }
@@ -1918,6 +2253,9 @@ function workerFn(fn) {
1918
2253
  if (!worker2) return;
1919
2254
  worker2.terminate();
1920
2255
  worker2 = null;
2256
+ const err = new Error("Worker terminated");
2257
+ while (queue.length > 0) queue.shift().reject(err);
2258
+ revokeBlobUrl();
1921
2259
  setLoading(false);
1922
2260
  }
1923
2261
  return { run, loading, terminate };
@@ -1925,20 +2263,63 @@ function workerFn(fn) {
1925
2263
  function createWorkerPool(workerFn2, poolSize) {
1926
2264
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
1927
2265
  const workers = [];
2266
+ const queues = [];
2267
+ const inflight = [];
1928
2268
  let currentIndex = 0;
1929
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
+ }
1930
2309
  try {
1931
2310
  if (typeof Worker === "undefined") {
1932
2311
  throw new Error("Web Workers are not supported in this environment");
1933
2312
  }
1934
2313
  const fnBody = workerFn2.toString();
1935
2314
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
1936
- const url = URL.createObjectURL(blob);
2315
+ blobUrl = URL.createObjectURL(blob);
1937
2316
  for (let i2 = 0; i2 < size; i2++) {
1938
- workers.push(new Worker(url));
2317
+ workers.push(new Worker(blobUrl));
2318
+ queues.push([]);
2319
+ inflight.push(null);
1939
2320
  }
1940
- URL.revokeObjectURL(url);
1941
2321
  } catch {
2322
+ revokeBlobUrl();
1942
2323
  }
1943
2324
  function execute(data2) {
1944
2325
  return new Promise((resolve, reject) => {
@@ -1946,23 +2327,25 @@ function createWorkerPool(workerFn2, poolSize) {
1946
2327
  reject(new Error("Worker pool is not available"));
1947
2328
  return;
1948
2329
  }
1949
- const worker2 = workers[currentIndex % workers.length];
2330
+ const idx = currentIndex % workers.length;
1950
2331
  currentIndex++;
1951
- worker2.onmessage = (e) => {
1952
- resolve(e.data);
1953
- };
1954
- worker2.onerror = (e) => {
1955
- reject(new Error(e.message || "Worker error"));
1956
- };
1957
- worker2.postMessage(data2);
2332
+ queues[idx].push({ data: data2, resolve, reject });
2333
+ dispatchNext(idx);
1958
2334
  });
1959
2335
  }
1960
2336
  function terminate() {
1961
2337
  alive = false;
1962
- for (const w of workers) {
1963
- 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;
1964
2346
  }
1965
2347
  workers.length = 0;
2348
+ revokeBlobUrl();
1966
2349
  }
1967
2350
  return { execute, terminate };
1968
2351
  }
@@ -1998,7 +2381,28 @@ function wasm(source2, config = {}) {
1998
2381
  };
1999
2382
  }
2000
2383
  async function loadWasmModule(source2, imports, cacheKey) {
2001
- 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
+ }
2002
2406
  if (key) {
2003
2407
  const cachedInstance = instanceCache.get(key);
2004
2408
  if (cachedInstance) {
@@ -2014,7 +2418,7 @@ async function loadWasmModule(source2, imports, cacheKey) {
2014
2418
  if (typeof source2 === "string") {
2015
2419
  if (typeof WebAssembly.instantiateStreaming === "function") {
2016
2420
  const response2 = fetch(source2);
2017
- const result = await WebAssembly.instantiateStreaming(response2, imports || {});
2421
+ const result = await WebAssembly.instantiateStreaming(response2, wasmImports || {});
2018
2422
  if (key) {
2019
2423
  moduleCache2.set(key, result.module);
2020
2424
  instanceCache.set(key, result.instance);
@@ -2031,12 +2435,28 @@ async function loadWasmModule(source2, imports, cacheKey) {
2031
2435
  module2 = await WebAssembly.compile(bytes);
2032
2436
  if (key) moduleCache2.set(key, module2);
2033
2437
  }
2034
- const instance = await WebAssembly.instantiate(module2, imports || {});
2438
+ const instance = await WebAssembly.instantiate(module2, wasmImports || {});
2035
2439
  if (key) instanceCache.set(key, instance);
2036
2440
  return instance;
2037
2441
  }
2038
- async function preloadWasm(url) {
2442
+ async function preloadWasm(url, options = {}) {
2039
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
+ }
2040
2460
  let module2;
2041
2461
  if (typeof WebAssembly.compileStreaming === "function") {
2042
2462
  module2 = await WebAssembly.compileStreaming(fetch(url));