sibujs 1.5.0 → 2.1.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 (208) hide show
  1. package/dist/browser.cjs +332 -121
  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 +1049 -344
  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-BMPL52BF.js → chunk-3DZP6OIT.js} +118 -66
  11. package/dist/chunk-3JHCYHWN.js +125 -0
  12. package/dist/{chunk-CZUGLNJS.js → chunk-45YP72ZQ.js} +3 -3
  13. package/dist/{chunk-JCDUJN2F.js → chunk-AMK2TYNW.js} +490 -153
  14. package/dist/{chunk-NHUC2QWH.js → chunk-CWBVQML6.js} +1 -1
  15. package/dist/{chunk-XHK6BDAJ.js → chunk-DRUZZAK4.js} +25 -8
  16. package/dist/{chunk-RJ46C3CS.js → chunk-GWWURC5M.js} +71 -20
  17. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  18. package/dist/{chunk-2BYQDGN3.js → chunk-KGYT6UO6.js} +234 -63
  19. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  20. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  21. package/dist/{chunk-XUEEGU5O.js → chunk-NASX6ST2.js} +16 -4
  22. package/dist/{chunk-VQDZK23A.js → chunk-O6EFQ3KT.js} +181 -66
  23. package/dist/{chunk-BGN5ZMP4.js → chunk-OJ3P4ECI.js} +14 -2
  24. package/dist/chunk-ON5MMR2J.js +1327 -0
  25. package/dist/{chunk-SFKNRVCU.js → chunk-P2HSJDDN.js} +135 -79
  26. package/dist/chunk-QO3WC6FS.js +384 -0
  27. package/dist/{chunk-WZSPOOER.js → chunk-RDTDJCAB.js} +8 -5
  28. package/dist/{chunk-7GRNSCFT.js → chunk-TH2ILCYW.js} +312 -185
  29. package/dist/chunk-UCS6AMJ7.js +79 -0
  30. package/dist/{chunk-VAPYJN4X.js → chunk-V6C4FADE.js} +93 -23
  31. package/dist/{chunk-OUZZEE4S.js → chunk-WANSMF2L.js} +17 -11
  32. package/dist/{chunk-23VV7YD3.js → chunk-WIPZPFBQ.js} +25 -30
  33. package/dist/chunk-WZA53FXU.js +149 -0
  34. package/dist/{chunk-BGTHZHJ5.js → chunk-ZAQSMOED.js} +188 -44
  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 +536 -151
  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 +613 -266
  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 +602 -197
  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 +3500 -1608
  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 +1055 -344
  54. package/dist/index.d.cts +85 -8
  55. package/dist/index.d.ts +85 -8
  56. package/dist/index.js +32 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-2TOlQ7oa.d.cts} +25 -3
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-DnIpHQQz.d.ts} +25 -3
  59. package/dist/motion.cjs +122 -63
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +450 -110
  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 +373 -149
  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 +729 -301
  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 +736 -274
  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 +303 -76
  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 +589 -178
  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 +1103 -146
  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-K4G4ZQNR.js +0 -286
  136. package/dist/chunk-K5ZUMYVS.js +0 -89
  137. package/dist/chunk-KQPDEVVS.js +0 -398
  138. package/dist/chunk-L6JRBDNS.js +0 -60
  139. package/dist/chunk-LA6KQEDU.js +0 -712
  140. package/dist/chunk-MB6QFH3I.js +0 -2776
  141. package/dist/chunk-MDVXJWFN.js +0 -304
  142. package/dist/chunk-MEZVEBPN.js +0 -2008
  143. package/dist/chunk-MK4ERFYL.js +0 -2249
  144. package/dist/chunk-MLKGABMK.js +0 -9
  145. package/dist/chunk-MQ5GOYPH.js +0 -2249
  146. package/dist/chunk-MYRV7VDM.js +0 -742
  147. package/dist/chunk-N6IZB6KJ.js +0 -567
  148. package/dist/chunk-NEKUBFPT.js +0 -60
  149. package/dist/chunk-NMRUZALC.js +0 -1097
  150. package/dist/chunk-NYVAC6P5.js +0 -37
  151. package/dist/chunk-NZIIMDWI.js +0 -84
  152. package/dist/chunk-OF7UZIVB.js +0 -725
  153. package/dist/chunk-P3XWXJZU.js +0 -282
  154. package/dist/chunk-P6W3STU4.js +0 -2249
  155. package/dist/chunk-PBHF5WKN.js +0 -616
  156. package/dist/chunk-PDZQY43A.js +0 -616
  157. package/dist/chunk-PTQJDMRT.js +0 -146
  158. package/dist/chunk-PZEGYCF5.js +0 -61
  159. package/dist/chunk-QBMDLBU2.js +0 -975
  160. package/dist/chunk-QWZG56ET.js +0 -2744
  161. package/dist/chunk-RQGQSLQK.js +0 -725
  162. package/dist/chunk-SDLZDHKP.js +0 -107
  163. package/dist/chunk-TDGZL5CU.js +0 -365
  164. package/dist/chunk-TNQWPPE6.js +0 -37
  165. package/dist/chunk-TSOKIX5Z.js +0 -654
  166. package/dist/chunk-UHNL42EF.js +0 -2730
  167. package/dist/chunk-UNXCEF6S.js +0 -21
  168. package/dist/chunk-V2XTI523.js +0 -347
  169. package/dist/chunk-VAU366PN.js +0 -2241
  170. package/dist/chunk-VMVDTCXB.js +0 -712
  171. package/dist/chunk-VQNQZCWJ.js +0 -61
  172. package/dist/chunk-VRW3FULF.js +0 -725
  173. package/dist/chunk-WADYRCO2.js +0 -304
  174. package/dist/chunk-WILQZRO4.js +0 -282
  175. package/dist/chunk-WR5D4EGH.js +0 -26
  176. package/dist/chunk-WUHJISPP.js +0 -298
  177. package/dist/chunk-XYU6TZOW.js +0 -182
  178. package/dist/chunk-Y6GP4QGG.js +0 -276
  179. package/dist/chunk-YECR7UIA.js +0 -347
  180. package/dist/chunk-YUTWTI4B.js +0 -654
  181. package/dist/chunk-Z65KYU7I.js +0 -26
  182. package/dist/chunk-Z6POF5YC.js +0 -975
  183. package/dist/chunk-ZBJP6WFL.js +0 -482
  184. package/dist/chunk-ZD6OAMTH.js +0 -277
  185. package/dist/chunk-ZWKZCBO6.js +0 -317
  186. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  187. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  188. package/dist/contracts-DOrhwbke.d.cts +0 -245
  189. package/dist/contracts-DOrhwbke.d.ts +0 -245
  190. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  191. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  192. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  193. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  194. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  195. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  196. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  197. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  198. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  199. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  200. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  201. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  202. package/dist/ssr-3RXHP5ES.js +0 -38
  203. package/dist/ssr-6GIMY5MX.js +0 -38
  204. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  205. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  206. package/dist/ssr-WKUPVSSK.js +0 -36
  207. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  208. 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;
@@ -582,15 +682,28 @@ function escapeAttr(str) {
582
682
 
583
683
  // src/reactivity/track.ts
584
684
  var _isDev3 = isDev();
585
- var subscriberStack = new Array(32);
586
- var stackCapacity = 32;
685
+ var STACK_INITIAL = 32;
686
+ var STACK_SHRINK_THRESHOLD = 128;
687
+ var subscriberStack = new Array(STACK_INITIAL);
688
+ var stackCapacity = STACK_INITIAL;
587
689
  var stackTop = -1;
588
690
  var currentSubscriber = null;
589
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
590
691
  var SUBS = "__s";
692
+ function syncFastPath(signal2, subs) {
693
+ const size = subs.size;
694
+ if (size === 0) {
695
+ signal2.__f = void 0;
696
+ delete signal2[SUBS];
697
+ } else if (size === 1) {
698
+ signal2.__f = subs.values().next().value;
699
+ } else {
700
+ signal2.__f = void 0;
701
+ }
702
+ }
591
703
  var notifyDepth = 0;
592
704
  var pendingQueue = [];
593
705
  var pendingSet = /* @__PURE__ */ new Set();
706
+ var propagateStack = [];
594
707
  function safeInvoke(sub2) {
595
708
  try {
596
709
  sub2();
@@ -613,37 +726,49 @@ function track(effectFn, subscriber) {
613
726
  } finally {
614
727
  stackTop--;
615
728
  currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
729
+ if (stackTop < 0 && stackCapacity > STACK_SHRINK_THRESHOLD) {
730
+ stackCapacity = Math.max(STACK_INITIAL, stackCapacity >>> 1);
731
+ subscriberStack.length = stackCapacity;
732
+ }
616
733
  }
617
734
  return () => cleanup(subscriber);
618
735
  }
619
736
  function recordDependency(signal2) {
620
737
  if (!currentSubscriber) return;
621
738
  const sub2 = currentSubscriber;
622
- if (sub2._dep === signal2) return;
739
+ const epoch = sub2._epoch;
740
+ if (sub2._dep === signal2) {
741
+ sub2._depEpoch = epoch;
742
+ return;
743
+ }
623
744
  const deps = sub2._deps;
624
745
  if (deps) {
625
- if (deps.has(signal2)) return;
626
- deps.add(signal2);
746
+ deps.set(signal2, epoch);
627
747
  } else if (sub2._dep !== void 0) {
628
- const set = /* @__PURE__ */ new Set();
629
- set.add(sub2._dep);
630
- set.add(signal2);
631
- sub2._deps = set;
748
+ const map2 = /* @__PURE__ */ new Map();
749
+ map2.set(sub2._dep, sub2._depEpoch);
750
+ map2.set(signal2, epoch);
751
+ sub2._deps = map2;
632
752
  sub2._dep = void 0;
753
+ sub2._depEpoch = void 0;
633
754
  } else {
634
755
  sub2._dep = signal2;
756
+ sub2._depEpoch = epoch;
635
757
  }
636
- let subs = signal2[SUBS];
758
+ const sig = signal2;
759
+ let subs = sig[SUBS];
637
760
  if (!subs) {
638
761
  subs = /* @__PURE__ */ new Set();
639
- signalSubscribers.set(signal2, subs);
640
- signal2[SUBS] = subs;
762
+ sig[SUBS] = subs;
641
763
  }
764
+ const prevSize = subs.size;
642
765
  subs.add(currentSubscriber);
643
- if (subs.size === 1) {
644
- signal2.__f = currentSubscriber;
645
- } else if (signal2.__f !== void 0) {
646
- signal2.__f = void 0;
766
+ if (subs.size !== prevSize) {
767
+ if (subs.size === 1) {
768
+ sig.__f = currentSubscriber;
769
+ } else if (sig.__f !== void 0) {
770
+ sig.__f = void 0;
771
+ }
647
772
  }
648
773
  }
649
774
  function queueSignalNotification(signal2) {
@@ -658,66 +783,102 @@ function queueSignalNotification(signal2) {
658
783
  }
659
784
  }
660
785
  }
661
- var MAX_DRAIN_ITERATIONS = 1e3;
786
+ var maxSubscriberRepeats = 50;
787
+ var maxDrainIterations = 1e6;
788
+ var drainEpoch = 0;
789
+ function tickRepeat(sub2) {
790
+ const s2 = sub2;
791
+ if (s2._runEpoch !== drainEpoch) {
792
+ s2._runEpoch = drainEpoch;
793
+ s2._runs = 1;
794
+ return false;
795
+ }
796
+ return ++s2._runs > maxSubscriberRepeats;
797
+ }
798
+ function cycleError(sub2) {
799
+ if (typeof console !== "undefined") {
800
+ const name = sub2.__name ?? "<unnamed>";
801
+ console.error(
802
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
803
+ );
804
+ }
805
+ }
806
+ function absoluteDrainError() {
807
+ if (typeof console !== "undefined") {
808
+ console.error(
809
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
810
+ );
811
+ }
812
+ }
813
+ function drainQueue() {
814
+ let i2 = 0;
815
+ while (i2 < pendingQueue.length) {
816
+ if (i2 >= maxDrainIterations) {
817
+ absoluteDrainError();
818
+ break;
819
+ }
820
+ const sub2 = pendingQueue[i2++];
821
+ if (tickRepeat(sub2)) {
822
+ cycleError(sub2);
823
+ break;
824
+ }
825
+ pendingSet.delete(sub2);
826
+ safeInvoke(sub2);
827
+ }
828
+ }
662
829
  function drainNotificationQueue() {
663
830
  if (notifyDepth > 0) return;
664
831
  notifyDepth++;
832
+ drainEpoch++;
665
833
  try {
666
- let i2 = 0;
667
- while (i2 < pendingQueue.length) {
668
- if (i2 >= MAX_DRAIN_ITERATIONS) {
669
- if (typeof console !== "undefined") {
670
- 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.`
672
- );
673
- }
674
- break;
675
- }
676
- safeInvoke(pendingQueue[i2]);
677
- i2++;
678
- }
834
+ drainQueue();
679
835
  } finally {
680
- pendingQueue.length = 0;
681
- pendingSet.clear();
682
836
  notifyDepth--;
837
+ if (notifyDepth === 0) {
838
+ pendingQueue.length = 0;
839
+ pendingSet.clear();
840
+ }
683
841
  }
684
842
  }
685
843
  function propagateDirty(sub2) {
686
844
  sub2();
687
- let sig = sub2._sig;
688
- while (sig) {
845
+ const rootSig = sub2._sig;
846
+ if (!rootSig) return;
847
+ const stack = propagateStack;
848
+ const baseLen = stack.length;
849
+ stack.push(rootSig);
850
+ while (stack.length > baseLen) {
851
+ const sig = stack.pop();
689
852
  const first = sig.__f;
690
853
  if (first) {
691
854
  if (first._c) {
692
855
  const nSig = first._sig;
693
- nSig._d = true;
694
- sig = nSig;
695
- continue;
696
- }
697
- if (!pendingSet.has(first)) {
856
+ if (!nSig._d) {
857
+ nSig._d = true;
858
+ stack.push(nSig);
859
+ }
860
+ } else if (!pendingSet.has(first)) {
698
861
  pendingSet.add(first);
699
862
  pendingQueue.push(first);
700
863
  }
701
- break;
864
+ continue;
702
865
  }
703
866
  const subs = sig[SUBS];
704
- if (!subs) break;
705
- let nextSig;
867
+ if (!subs) continue;
706
868
  for (const s2 of subs) {
707
869
  if (s2._c) {
708
- s2();
709
870
  const nSig = s2._sig;
710
- if (nSig && !nextSig) {
711
- nextSig = nSig;
712
- } else if (nSig) {
713
- propagateDirty(s2);
871
+ if (nSig && !nSig._d) {
872
+ nSig._d = true;
873
+ stack.push(nSig);
874
+ } else if (!nSig) {
875
+ s2();
714
876
  }
715
877
  } else if (!pendingSet.has(s2)) {
716
878
  pendingSet.add(s2);
717
879
  pendingQueue.push(s2);
718
880
  }
719
881
  }
720
- sig = nextSig;
721
882
  }
722
883
  }
723
884
  function notifySubscribers(signal2) {
@@ -733,21 +894,22 @@ function notifySubscribers(signal2) {
733
894
  return;
734
895
  }
735
896
  notifyDepth++;
897
+ drainEpoch++;
736
898
  try {
737
899
  if (first._c) {
738
900
  propagateDirty(first);
901
+ } else if (tickRepeat(first)) {
902
+ cycleError(first);
739
903
  } else {
740
904
  safeInvoke(first);
741
905
  }
742
- let i2 = 0;
743
- while (i2 < pendingQueue.length) {
744
- safeInvoke(pendingQueue[i2]);
745
- i2++;
746
- }
906
+ drainQueue();
747
907
  } finally {
748
- pendingQueue.length = 0;
749
- pendingSet.clear();
750
908
  notifyDepth--;
909
+ if (notifyDepth === 0) {
910
+ pendingQueue.length = 0;
911
+ pendingSet.clear();
912
+ }
751
913
  }
752
914
  return;
753
915
  }
@@ -765,68 +927,50 @@ function notifySubscribers(signal2) {
765
927
  return;
766
928
  }
767
929
  notifyDepth++;
930
+ drainEpoch++;
768
931
  try {
769
- let directCount = 0;
770
932
  for (const sub2 of subs) {
771
- pendingQueue[directCount++] = sub2;
772
- }
773
- for (let i3 = 0; i3 < directCount; i3++) {
774
- if (pendingQueue[i3]._c) {
775
- propagateDirty(pendingQueue[i3]);
776
- }
777
- }
778
- for (let i3 = 0; i3 < directCount; i3++) {
779
- if (!pendingQueue[i3]._c) {
780
- if (!pendingSet.has(pendingQueue[i3])) {
781
- safeInvoke(pendingQueue[i3]);
782
- }
933
+ if (sub2._c) {
934
+ propagateDirty(sub2);
935
+ } else if (!pendingSet.has(sub2)) {
936
+ pendingSet.add(sub2);
937
+ pendingQueue.push(sub2);
783
938
  }
784
939
  }
785
- let i2 = directCount;
786
- while (i2 < pendingQueue.length) {
787
- safeInvoke(pendingQueue[i2]);
788
- i2++;
789
- }
940
+ drainQueue();
790
941
  } finally {
791
- pendingQueue.length = 0;
792
- pendingSet.clear();
793
942
  notifyDepth--;
943
+ if (notifyDepth === 0) {
944
+ pendingQueue.length = 0;
945
+ pendingSet.clear();
946
+ }
794
947
  }
795
948
  }
796
949
  function cleanup(subscriber) {
797
950
  const sub2 = subscriber;
798
951
  const singleDep = sub2._dep;
799
952
  if (singleDep !== void 0) {
800
- const subs = singleDep[SUBS];
801
- if (subs) {
802
- subs.delete(subscriber);
803
- if (singleDep.__f === subscriber) {
804
- singleDep.__f = void 0;
805
- }
953
+ const sig = singleDep;
954
+ const subs = sig[SUBS];
955
+ if (subs?.delete(subscriber)) {
956
+ syncFastPath(sig, subs);
806
957
  }
807
958
  sub2._dep = void 0;
959
+ sub2._depEpoch = void 0;
808
960
  return;
809
961
  }
810
962
  const deps = sub2._deps;
811
963
  if (!deps || deps.size === 0) return;
812
- for (const signal2 of deps) {
813
- const subs = signal2[SUBS];
814
- if (subs) {
815
- subs.delete(subscriber);
816
- if (signal2.__f === subscriber) {
817
- signal2.__f = void 0;
818
- }
964
+ for (const signal2 of deps.keys()) {
965
+ const sig = signal2;
966
+ const subs = sig[SUBS];
967
+ if (subs?.delete(subscriber)) {
968
+ syncFastPath(sig, subs);
819
969
  }
820
970
  }
821
971
  deps.clear();
822
972
  }
823
973
 
824
- // src/core/ssr-context.ts
825
- var ssrMode = false;
826
- function isSSR() {
827
- return ssrMode;
828
- }
829
-
830
974
  // src/core/signals/effect.ts
831
975
  var _g = globalThis;
832
976
  function effect(effectFn, options) {
@@ -834,26 +978,114 @@ function effect(effectFn, options) {
834
978
  if (isSSR()) return () => {
835
979
  };
836
980
  const onError = options?.onError;
981
+ let userCleanups = [];
982
+ const onCleanup = (fn) => {
983
+ userCleanups.push(fn);
984
+ };
985
+ const runUserCleanups = () => {
986
+ if (userCleanups.length === 0) return;
987
+ const list = userCleanups;
988
+ userCleanups = [];
989
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
990
+ try {
991
+ list[i2]();
992
+ } catch (err) {
993
+ if (typeof console !== "undefined") {
994
+ console.warn("[SibuJS effect] onCleanup threw:", err);
995
+ }
996
+ }
997
+ }
998
+ };
999
+ const invokeBody = () => effectFn(onCleanup);
837
1000
  const wrappedFn = onError ? () => {
838
1001
  try {
839
- effectFn();
1002
+ invokeBody();
840
1003
  } catch (err) {
841
1004
  onError(err);
842
1005
  }
843
- } : effectFn;
1006
+ } : invokeBody;
844
1007
  let cleanupHandle = () => {
845
1008
  };
1009
+ let running = false;
1010
+ let rerunPending = false;
1011
+ const MAX_RERUNS = 100;
846
1012
  const subscriber = () => {
847
- cleanupHandle();
848
- cleanupHandle = track(wrappedFn, subscriber);
1013
+ if (running) {
1014
+ rerunPending = true;
1015
+ return;
1016
+ }
1017
+ running = true;
1018
+ try {
1019
+ let reruns = 0;
1020
+ do {
1021
+ rerunPending = false;
1022
+ runUserCleanups();
1023
+ cleanupHandle();
1024
+ cleanupHandle = track(wrappedFn, subscriber);
1025
+ if (++reruns > MAX_RERUNS) {
1026
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1027
+ console.error(
1028
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
1029
+ );
1030
+ }
1031
+ rerunPending = false;
1032
+ break;
1033
+ }
1034
+ } while (rerunPending);
1035
+ } finally {
1036
+ running = false;
1037
+ rerunPending = false;
1038
+ }
849
1039
  };
850
- cleanupHandle = track(wrappedFn, subscriber);
1040
+ running = true;
1041
+ try {
1042
+ let reruns = 0;
1043
+ do {
1044
+ rerunPending = false;
1045
+ runUserCleanups();
1046
+ cleanupHandle();
1047
+ cleanupHandle = track(wrappedFn, subscriber);
1048
+ if (++reruns > MAX_RERUNS) {
1049
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1050
+ console.error(
1051
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times on initial run \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
1052
+ );
1053
+ }
1054
+ rerunPending = false;
1055
+ break;
1056
+ }
1057
+ } while (rerunPending);
1058
+ } finally {
1059
+ running = false;
1060
+ rerunPending = false;
1061
+ }
851
1062
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
852
1063
  if (hook) hook.emit("effect:create", { effectFn });
1064
+ let disposed = false;
853
1065
  return () => {
1066
+ if (disposed) return;
1067
+ disposed = true;
854
1068
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
855
- if (h) h.emit("effect:destroy", { effectFn });
856
- cleanupHandle();
1069
+ if (h) {
1070
+ try {
1071
+ h.emit("effect:destroy", { effectFn });
1072
+ } catch {
1073
+ }
1074
+ }
1075
+ try {
1076
+ runUserCleanups();
1077
+ } catch (err) {
1078
+ if (typeof console !== "undefined") {
1079
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1080
+ }
1081
+ }
1082
+ try {
1083
+ cleanupHandle();
1084
+ } catch (err) {
1085
+ if (typeof console !== "undefined") {
1086
+ console.warn("[SibuJS effect] dispose threw:", err);
1087
+ }
1088
+ }
857
1089
  };
858
1090
  }
859
1091
 
@@ -1025,10 +1257,13 @@ function enqueueBatchedSignal(signal2) {
1025
1257
  return true;
1026
1258
  }
1027
1259
  function flushBatch() {
1028
- for (const signal2 of pendingSignals) {
1029
- queueSignalNotification(signal2);
1260
+ try {
1261
+ for (const signal2 of pendingSignals) {
1262
+ queueSignalNotification(signal2);
1263
+ }
1264
+ } finally {
1265
+ pendingSignals.clear();
1030
1266
  }
1031
- pendingSignals.clear();
1032
1267
  drainNotificationQueue();
1033
1268
  }
1034
1269
 
@@ -1075,24 +1310,42 @@ function createISR(options) {
1075
1310
  const { revalidateAfter, fetcher, initialData } = options;
1076
1311
  const [data2, setData] = signal(initialData);
1077
1312
  const [timestamp, setTimestamp] = signal(initialData !== void 0 ? Date.now() : 0);
1313
+ const controller = new AbortController();
1314
+ let inFlight = false;
1315
+ let disposed = false;
1078
1316
  const isStale = () => {
1079
1317
  const ts = timestamp();
1080
1318
  if (ts === 0) return true;
1081
1319
  return Date.now() - ts >= revalidateAfter;
1082
1320
  };
1083
1321
  const revalidate = async () => {
1084
- const result = await fetcher();
1085
- setData(result);
1086
- setTimestamp(Date.now());
1322
+ if (disposed || inFlight) return;
1323
+ if (controller.signal.aborted) return;
1324
+ inFlight = true;
1325
+ try {
1326
+ const result = await fetcher({ signal: controller.signal });
1327
+ if (disposed || controller.signal.aborted) return;
1328
+ setData(result);
1329
+ setTimestamp(Date.now());
1330
+ } finally {
1331
+ inFlight = false;
1332
+ }
1087
1333
  };
1088
1334
  if (initialData === void 0) {
1089
- revalidate();
1335
+ revalidate().catch((err) => {
1336
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
1337
+ });
1090
1338
  }
1091
1339
  const intervalId = setInterval(() => {
1092
- revalidate();
1340
+ revalidate().catch((err) => {
1341
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] revalidate failed", err);
1342
+ });
1093
1343
  }, revalidateAfter);
1094
1344
  const dispose = () => {
1345
+ if (disposed) return;
1346
+ disposed = true;
1095
1347
  clearInterval(intervalId);
1348
+ controller.abort();
1096
1349
  };
1097
1350
  return { data: data2, isStale, revalidate, dispose };
1098
1351
  }
@@ -1200,6 +1453,9 @@ function createMiddlewareChain() {
1200
1453
 
1201
1454
  // src/reactivity/bindAttribute.ts
1202
1455
  var _isDev5 = isDev();
1456
+ function setProp(el, key, val) {
1457
+ el[key] = val;
1458
+ }
1203
1459
  function isEventHandlerAttr3(name) {
1204
1460
  if (name.length < 3) return false;
1205
1461
  const lower = name.toLowerCase();
@@ -1225,7 +1481,7 @@ function bindAttribute(el, attr, getter) {
1225
1481
  }
1226
1482
  if (typeof value === "boolean") {
1227
1483
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1228
- el[attr] = value;
1484
+ setProp(el, attr, value);
1229
1485
  } else if (value) {
1230
1486
  el.setAttribute(attr, "");
1231
1487
  } else {
@@ -1235,7 +1491,7 @@ function bindAttribute(el, attr, getter) {
1235
1491
  }
1236
1492
  const str = String(value);
1237
1493
  if ((attr === "value" || attr === "checked") && attr in el) {
1238
- el[attr] = attr === "checked" ? Boolean(value) : str;
1494
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1239
1495
  } else {
1240
1496
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1241
1497
  }
@@ -1272,24 +1528,29 @@ function bindChildNode(placeholder, getter) {
1272
1528
  let newNodes;
1273
1529
  if (Array.isArray(result)) {
1274
1530
  newNodes = [];
1531
+ const seen = /* @__PURE__ */ new Set();
1275
1532
  for (let i2 = 0; i2 < result.length; i2++) {
1276
1533
  const item = result[i2];
1277
1534
  if (item == null || typeof item === "boolean") continue;
1278
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1535
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1536
+ if (seen.has(node)) {
1537
+ if (_isDev6)
1538
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1539
+ continue;
1540
+ }
1541
+ seen.add(node);
1542
+ newNodes.push(node);
1279
1543
  }
1280
1544
  } else {
1281
1545
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1282
1546
  newNodes = [node];
1283
1547
  }
1284
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1285
- if (reused) {
1548
+ let reused;
1549
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1550
+ const lastSet = new Set(lastNodes);
1551
+ reused = /* @__PURE__ */ new Set();
1286
1552
  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
- }
1553
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1293
1554
  }
1294
1555
  }
1295
1556
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1329,6 +1590,30 @@ function registerDisposer(node, teardown) {
1329
1590
 
1330
1591
  // src/core/rendering/tagFactory.ts
1331
1592
  var SVG_NS = "http://www.w3.org/2000/svg";
1593
+ var _isDev8 = isDev();
1594
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1595
+ function validateTagName(tag) {
1596
+ const lower = tag.toLowerCase();
1597
+ if (BLOCKED_TAGS.has(lower)) {
1598
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1599
+ }
1600
+ }
1601
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1602
+ "config",
1603
+ "location",
1604
+ "history",
1605
+ "document",
1606
+ "window",
1607
+ "navigator",
1608
+ "name",
1609
+ "top",
1610
+ "parent",
1611
+ "self",
1612
+ "frames"
1613
+ ]);
1614
+ function setProp2(el, key, val) {
1615
+ el[key] = val;
1616
+ }
1332
1617
  var kebabCache = /* @__PURE__ */ new Map();
1333
1618
  function toKebab(prop) {
1334
1619
  let cached = kebabCache.get(prop);
@@ -1453,79 +1738,103 @@ function appendChildren(el, nodes) {
1453
1738
  }
1454
1739
  }
1455
1740
  }
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);
1741
+ var tagFactory = (tag, ns) => {
1742
+ return (first, second) => {
1743
+ validateTagName(tag);
1744
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1745
+ if (first === void 0) return el;
1746
+ if (typeof first === "string") {
1747
+ if (second !== void 0) {
1748
+ el.setAttribute("class", first);
1749
+ appendChildren(el, second);
1750
+ return el;
1751
+ }
1752
+ el.textContent = first;
1463
1753
  return el;
1464
1754
  }
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, "");
1755
+ if (typeof first === "number") {
1756
+ el.textContent = String(first);
1757
+ return el;
1758
+ }
1759
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1760
+ appendChildren(el, first);
1761
+ return el;
1762
+ }
1763
+ const props = first;
1764
+ const pClass = props.class;
1765
+ if (pClass != null) applyClass(el, pClass);
1766
+ const pId = props.id;
1767
+ if (pId != null) {
1768
+ if (_isDev8 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1769
+ devWarn(
1770
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1771
+ );
1772
+ }
1773
+ el.id = pId;
1774
+ }
1775
+ const pNodes = second !== void 0 ? second : props.nodes;
1776
+ if (pNodes != null) appendChildren(el, pNodes);
1777
+ const pOn = props.on;
1778
+ if (pOn) {
1779
+ for (const ev in pOn) {
1780
+ const handler = pOn[ev];
1781
+ if (typeof handler === "function") {
1782
+ el.addEventListener(ev, handler);
1783
+ } else if (_isDev8) {
1784
+ devWarn(
1785
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1786
+ );
1787
+ }
1788
+ }
1789
+ }
1790
+ const pStyle = props.style;
1791
+ if (pStyle != null) applyStyle(el, pStyle);
1792
+ const pRef = props.ref;
1793
+ if (pRef) pRef.current = el;
1794
+ for (const key in props) {
1795
+ switch (key) {
1796
+ case "class":
1797
+ case "id":
1798
+ case "nodes":
1799
+ case "on":
1800
+ case "style":
1801
+ case "ref":
1802
+ case "onElement":
1803
+ continue;
1804
+ // already handled above / below
1805
+ default: {
1806
+ const value = props[key];
1807
+ if (value == null) continue;
1808
+ const lkey = key.toLowerCase();
1809
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1810
+ if (typeof value === "function") {
1811
+ registerDisposer(el, bindAttribute(el, key, value));
1812
+ } else if (typeof value === "boolean") {
1813
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1814
+ setProp2(el, key, value);
1815
+ } else if (value) {
1816
+ el.setAttribute(key, "");
1817
+ } else {
1818
+ el.removeAttribute(key);
1819
+ }
1515
1820
  } else {
1516
- el.removeAttribute(key);
1821
+ const str = String(value);
1822
+ if (lkey === "srcset") {
1823
+ el.setAttribute(key, sanitizeSrcset(str));
1824
+ } else if (isUrlAttribute(lkey)) {
1825
+ el.setAttribute(key, sanitizeUrl(str));
1826
+ } else {
1827
+ el.setAttribute(key, str);
1828
+ }
1517
1829
  }
1518
- } else {
1519
- const str = String(value);
1520
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1521
1830
  }
1522
1831
  }
1523
1832
  }
1524
- }
1525
- if (props.onElement && typeof props.onElement === "function") {
1526
- props.onElement(el);
1527
- }
1528
- return el;
1833
+ if (props.onElement && typeof props.onElement === "function") {
1834
+ props.onElement(el);
1835
+ }
1836
+ return el;
1837
+ };
1529
1838
  };
1530
1839
 
1531
1840
  // src/core/rendering/html.ts
@@ -1679,31 +1988,38 @@ function createMicroApp(config) {
1679
1988
  }
1680
1989
  let mounted = false;
1681
1990
  function mount(component) {
1682
- if (root instanceof ShadowRoot) {
1683
- root.innerHTML = "";
1684
- } else {
1685
- while (root.firstChild) {
1686
- root.removeChild(root.firstChild);
1687
- }
1688
- }
1991
+ root.replaceChildren();
1689
1992
  const el = component();
1690
1993
  root.appendChild(el);
1691
1994
  mounted = true;
1692
1995
  }
1693
1996
  function unmount() {
1694
1997
  if (!mounted) return;
1695
- if (root instanceof ShadowRoot) {
1696
- root.innerHTML = "";
1697
- } else {
1698
- while (root.firstChild) {
1699
- root.removeChild(root.firstChild);
1700
- }
1701
- }
1998
+ root.replaceChildren();
1702
1999
  mounted = false;
1703
2000
  }
1704
2001
  return { mount, unmount, element: host };
1705
2002
  }
1706
- function loadRemoteModule(url) {
2003
+ function loadRemoteModule(url, optionsOrAllowedOrigins = []) {
2004
+ const opts = Array.isArray(optionsOrAllowedOrigins) ? { allowedOrigins: optionsOrAllowedOrigins } : optionsOrAllowedOrigins;
2005
+ const allowedOrigins = opts.allowedOrigins ?? [];
2006
+ if (allowedOrigins.length > 0) {
2007
+ let parsed;
2008
+ try {
2009
+ parsed = new URL(url, typeof location !== "undefined" ? location.href : void 0);
2010
+ } catch {
2011
+ return Promise.reject(new Error(`loadRemoteModule: invalid URL "${url}"`));
2012
+ }
2013
+ if (!allowedOrigins.includes(parsed.origin)) {
2014
+ return Promise.reject(new Error(`loadRemoteModule: origin "${parsed.origin}" is not in the allowlist`));
2015
+ }
2016
+ } else if (!opts.unsafelyAllowAnyOrigin) {
2017
+ return Promise.reject(
2018
+ new Error(
2019
+ `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).`
2020
+ )
2021
+ );
2022
+ }
1707
2023
  const cached = moduleCache.get(url);
1708
2024
  if (cached) return cached;
1709
2025
  const promise = import(
@@ -1793,21 +2109,49 @@ function serviceWorker(scriptUrl, options) {
1793
2109
  const [isReady, setIsReady] = signal(false);
1794
2110
  const [isUpdateAvailable, setIsUpdateAvailable] = signal(false);
1795
2111
  const [error, setError] = signal(null);
2112
+ let disposed = false;
2113
+ let updateFoundHandler = null;
2114
+ let stateChangeHandler = null;
2115
+ let trackedWorker = null;
2116
+ let trackedReg = null;
2117
+ function detachListeners() {
2118
+ if (trackedReg && updateFoundHandler) {
2119
+ trackedReg.removeEventListener("updatefound", updateFoundHandler);
2120
+ }
2121
+ if (trackedWorker && stateChangeHandler) {
2122
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
2123
+ }
2124
+ updateFoundHandler = null;
2125
+ stateChangeHandler = null;
2126
+ trackedWorker = null;
2127
+ trackedReg = null;
2128
+ }
1796
2129
  if ("serviceWorker" in navigator) {
1797
2130
  navigator.serviceWorker.register(scriptUrl, options).then((reg) => {
2131
+ if (disposed) return;
1798
2132
  setRegistration(reg);
1799
2133
  setIsReady(true);
1800
- reg.addEventListener("updatefound", () => {
2134
+ trackedReg = reg;
2135
+ updateFoundHandler = () => {
2136
+ if (disposed) return;
1801
2137
  const newWorker = reg.installing;
1802
2138
  if (newWorker) {
1803
- newWorker.addEventListener("statechange", () => {
2139
+ if (trackedWorker && stateChangeHandler) {
2140
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
2141
+ }
2142
+ trackedWorker = newWorker;
2143
+ stateChangeHandler = () => {
2144
+ if (disposed) return;
1804
2145
  if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
1805
2146
  setIsUpdateAvailable(true);
1806
2147
  }
1807
- });
2148
+ };
2149
+ newWorker.addEventListener("statechange", stateChangeHandler);
1808
2150
  }
1809
- });
2151
+ };
2152
+ reg.addEventListener("updatefound", updateFoundHandler);
1810
2153
  }).catch((err) => {
2154
+ if (disposed) return;
1811
2155
  setError(err instanceof Error ? err : new Error(String(err)));
1812
2156
  });
1813
2157
  }
@@ -1818,6 +2162,8 @@ function serviceWorker(scriptUrl, options) {
1818
2162
  }
1819
2163
  }
1820
2164
  async function unregister() {
2165
+ disposed = true;
2166
+ detachListeners();
1821
2167
  const reg = registration();
1822
2168
  if (reg) {
1823
2169
  const result = await reg.unregister();
@@ -1838,24 +2184,37 @@ function worker(workerFn2) {
1838
2184
  const [error, setError] = signal(null);
1839
2185
  const [loading, setLoading] = signal(false);
1840
2186
  let worker2 = null;
2187
+ let blobUrl = null;
2188
+ const revokeBlobUrl = () => {
2189
+ if (blobUrl) {
2190
+ URL.revokeObjectURL(blobUrl);
2191
+ blobUrl = null;
2192
+ }
2193
+ };
1841
2194
  try {
1842
2195
  if (typeof Worker === "undefined") {
1843
2196
  throw new Error("Web Workers are not supported in this environment");
1844
2197
  }
1845
2198
  const fnBody = workerFn2.toString();
1846
2199
  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) => {
2200
+ blobUrl = URL.createObjectURL(blob);
2201
+ worker2 = new Worker(blobUrl);
2202
+ worker2.addEventListener("message", (e) => {
2203
+ revokeBlobUrl();
1851
2204
  setResult(e.data);
1852
2205
  setLoading(false);
1853
- };
1854
- worker2.onerror = (e) => {
2206
+ });
2207
+ worker2.addEventListener("error", (e) => {
2208
+ revokeBlobUrl();
1855
2209
  setError(new Error(e.message || "Worker error"));
1856
2210
  setLoading(false);
1857
- };
2211
+ if (worker2) {
2212
+ worker2.terminate();
2213
+ worker2 = null;
2214
+ }
2215
+ });
1858
2216
  } catch (err) {
2217
+ revokeBlobUrl();
1859
2218
  setError(err instanceof Error ? err : new Error(String(err)));
1860
2219
  }
1861
2220
  function post(data2) {
@@ -1869,6 +2228,7 @@ function worker(workerFn2) {
1869
2228
  if (!worker2) return;
1870
2229
  worker2.terminate();
1871
2230
  worker2 = null;
2231
+ revokeBlobUrl();
1872
2232
  setLoading(false);
1873
2233
  }
1874
2234
  return { post, result, error, loading, terminate };
@@ -1876,6 +2236,14 @@ function worker(workerFn2) {
1876
2236
  function workerFn(fn) {
1877
2237
  const [loading, setLoading] = signal(false);
1878
2238
  let worker2 = null;
2239
+ let blobUrl = null;
2240
+ const revokeBlobUrl = () => {
2241
+ if (blobUrl) {
2242
+ URL.revokeObjectURL(blobUrl);
2243
+ blobUrl = null;
2244
+ }
2245
+ };
2246
+ const queue = [];
1879
2247
  try {
1880
2248
  if (typeof Worker === "undefined") {
1881
2249
  throw new Error("Web Workers are not supported in this environment");
@@ -1891,10 +2259,26 @@ function workerFn(fn) {
1891
2259
  ],
1892
2260
  { type: "application/javascript" }
1893
2261
  );
1894
- const url = URL.createObjectURL(blob);
1895
- worker2 = new Worker(url);
1896
- URL.revokeObjectURL(url);
2262
+ blobUrl = URL.createObjectURL(blob);
2263
+ worker2 = new Worker(blobUrl);
2264
+ worker2.addEventListener("message", (e) => {
2265
+ revokeBlobUrl();
2266
+ const head2 = queue.shift();
2267
+ if (queue.length === 0) setLoading(false);
2268
+ if (head2) head2.resolve(e.data);
2269
+ });
2270
+ worker2.addEventListener("error", (e) => {
2271
+ revokeBlobUrl();
2272
+ const err = new Error(e.message || "Worker error");
2273
+ while (queue.length > 0) queue.shift().reject(err);
2274
+ setLoading(false);
2275
+ if (worker2) {
2276
+ worker2.terminate();
2277
+ worker2 = null;
2278
+ }
2279
+ });
1897
2280
  } catch {
2281
+ revokeBlobUrl();
1898
2282
  }
1899
2283
  function run(...args) {
1900
2284
  return new Promise((resolve, reject) => {
@@ -1903,14 +2287,7 @@ function workerFn(fn) {
1903
2287
  return;
1904
2288
  }
1905
2289
  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
- };
2290
+ queue.push({ resolve, reject });
1914
2291
  worker2.postMessage(args);
1915
2292
  });
1916
2293
  }
@@ -1918,6 +2295,9 @@ function workerFn(fn) {
1918
2295
  if (!worker2) return;
1919
2296
  worker2.terminate();
1920
2297
  worker2 = null;
2298
+ const err = new Error("Worker terminated");
2299
+ while (queue.length > 0) queue.shift().reject(err);
2300
+ revokeBlobUrl();
1921
2301
  setLoading(false);
1922
2302
  }
1923
2303
  return { run, loading, terminate };
@@ -1925,20 +2305,63 @@ function workerFn(fn) {
1925
2305
  function createWorkerPool(workerFn2, poolSize) {
1926
2306
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
1927
2307
  const workers = [];
2308
+ const queues = [];
2309
+ const inflight = [];
1928
2310
  let currentIndex = 0;
1929
2311
  let alive = true;
2312
+ let blobUrl = null;
2313
+ let firedOnce = false;
2314
+ const revokeBlobUrl = () => {
2315
+ if (blobUrl) {
2316
+ URL.revokeObjectURL(blobUrl);
2317
+ blobUrl = null;
2318
+ }
2319
+ };
2320
+ function dispatchNext(idx) {
2321
+ if (!alive || inflight[idx] || queues[idx].length === 0) return;
2322
+ const w = workers[idx];
2323
+ const slot2 = queues[idx].shift();
2324
+ const onMsg = (e) => {
2325
+ if (!firedOnce) {
2326
+ firedOnce = true;
2327
+ revokeBlobUrl();
2328
+ }
2329
+ w.removeEventListener("message", onMsg);
2330
+ w.removeEventListener("error", onErr);
2331
+ inflight[idx] = null;
2332
+ slot2.resolve(e.data);
2333
+ dispatchNext(idx);
2334
+ };
2335
+ const onErr = (e) => {
2336
+ if (!firedOnce) {
2337
+ firedOnce = true;
2338
+ revokeBlobUrl();
2339
+ }
2340
+ w.removeEventListener("message", onMsg);
2341
+ w.removeEventListener("error", onErr);
2342
+ inflight[idx] = null;
2343
+ slot2.reject(new Error(e.message || "Worker error"));
2344
+ dispatchNext(idx);
2345
+ };
2346
+ inflight[idx] = { ...slot2, onMsg, onErr };
2347
+ w.addEventListener("message", onMsg);
2348
+ w.addEventListener("error", onErr);
2349
+ w.postMessage(slot2.data);
2350
+ }
1930
2351
  try {
1931
2352
  if (typeof Worker === "undefined") {
1932
2353
  throw new Error("Web Workers are not supported in this environment");
1933
2354
  }
1934
2355
  const fnBody = workerFn2.toString();
1935
2356
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
1936
- const url = URL.createObjectURL(blob);
2357
+ blobUrl = URL.createObjectURL(blob);
1937
2358
  for (let i2 = 0; i2 < size; i2++) {
1938
- workers.push(new Worker(url));
2359
+ workers.push(new Worker(blobUrl));
2360
+ queues.push([]);
2361
+ inflight.push(null);
1939
2362
  }
1940
- URL.revokeObjectURL(url);
1941
2363
  } catch {
2364
+ revokeBlobUrl();
1942
2365
  }
1943
2366
  function execute(data2) {
1944
2367
  return new Promise((resolve, reject) => {
@@ -1946,23 +2369,25 @@ function createWorkerPool(workerFn2, poolSize) {
1946
2369
  reject(new Error("Worker pool is not available"));
1947
2370
  return;
1948
2371
  }
1949
- const worker2 = workers[currentIndex % workers.length];
2372
+ const idx = currentIndex % workers.length;
1950
2373
  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);
2374
+ queues[idx].push({ data: data2, resolve, reject });
2375
+ dispatchNext(idx);
1958
2376
  });
1959
2377
  }
1960
2378
  function terminate() {
1961
2379
  alive = false;
1962
- for (const w of workers) {
1963
- w.terminate();
2380
+ for (const w of workers) w.terminate();
2381
+ const err = new Error("Worker pool terminated");
2382
+ for (let i2 = 0; i2 < queues.length; i2++) {
2383
+ const inf = inflight[i2];
2384
+ if (inf) inf.reject(err);
2385
+ for (const s2 of queues[i2]) s2.reject(err);
2386
+ queues[i2] = [];
2387
+ inflight[i2] = null;
1964
2388
  }
1965
2389
  workers.length = 0;
2390
+ revokeBlobUrl();
1966
2391
  }
1967
2392
  return { execute, terminate };
1968
2393
  }
@@ -1998,7 +2423,28 @@ function wasm(source2, config = {}) {
1998
2423
  };
1999
2424
  }
2000
2425
  async function loadWasmModule(source2, imports, cacheKey) {
2001
- const key = cacheKey || (typeof source2 === "string" ? source2 : void 0);
2426
+ const isOptionsBag = !!(imports && ("allowedOrigins" in imports || "unsafelyAllowAnyOrigin" in imports));
2427
+ const opts = isOptionsBag ? imports : { imports, cacheKey };
2428
+ const wasmImports = opts.imports;
2429
+ const key = opts.cacheKey || (typeof source2 === "string" ? source2 : void 0);
2430
+ if (typeof source2 === "string") {
2431
+ const allowed = opts.allowedOrigins ?? [];
2432
+ if (allowed.length > 0) {
2433
+ let parsed;
2434
+ try {
2435
+ parsed = new URL(source2, typeof location !== "undefined" ? location.href : void 0);
2436
+ } catch {
2437
+ throw new Error(`loadWasmModule: invalid URL "${source2}"`);
2438
+ }
2439
+ if (!allowed.includes(parsed.origin)) {
2440
+ throw new Error(`loadWasmModule: origin "${parsed.origin}" is not in the allowlist`);
2441
+ }
2442
+ } else if (!opts.unsafelyAllowAnyOrigin) {
2443
+ throw new Error(
2444
+ `loadWasmModule: refused to fetch "${source2}" with no allowedOrigins. Pass { allowedOrigins: [...] } to restrict the origin, or { unsafelyAllowAnyOrigin: true } to opt in (CWE-829).`
2445
+ );
2446
+ }
2447
+ }
2002
2448
  if (key) {
2003
2449
  const cachedInstance = instanceCache.get(key);
2004
2450
  if (cachedInstance) {
@@ -2014,7 +2460,7 @@ async function loadWasmModule(source2, imports, cacheKey) {
2014
2460
  if (typeof source2 === "string") {
2015
2461
  if (typeof WebAssembly.instantiateStreaming === "function") {
2016
2462
  const response2 = fetch(source2);
2017
- const result = await WebAssembly.instantiateStreaming(response2, imports || {});
2463
+ const result = await WebAssembly.instantiateStreaming(response2, wasmImports || {});
2018
2464
  if (key) {
2019
2465
  moduleCache2.set(key, result.module);
2020
2466
  instanceCache.set(key, result.instance);
@@ -2031,12 +2477,28 @@ async function loadWasmModule(source2, imports, cacheKey) {
2031
2477
  module2 = await WebAssembly.compile(bytes);
2032
2478
  if (key) moduleCache2.set(key, module2);
2033
2479
  }
2034
- const instance = await WebAssembly.instantiate(module2, imports || {});
2480
+ const instance = await WebAssembly.instantiate(module2, wasmImports || {});
2035
2481
  if (key) instanceCache.set(key, instance);
2036
2482
  return instance;
2037
2483
  }
2038
- async function preloadWasm(url) {
2484
+ async function preloadWasm(url, options = {}) {
2039
2485
  if (moduleCache2.has(url)) return;
2486
+ const allowed = options.allowedOrigins ?? [];
2487
+ if (allowed.length > 0) {
2488
+ let parsed;
2489
+ try {
2490
+ parsed = new URL(url, typeof location !== "undefined" ? location.href : void 0);
2491
+ } catch {
2492
+ throw new Error(`preloadWasm: invalid URL "${url}"`);
2493
+ }
2494
+ if (!allowed.includes(parsed.origin)) {
2495
+ throw new Error(`preloadWasm: origin "${parsed.origin}" is not in the allowlist`);
2496
+ }
2497
+ } else if (!options.unsafelyAllowAnyOrigin) {
2498
+ throw new Error(
2499
+ `preloadWasm: refused to fetch "${url}" with no allowedOrigins. Pass { allowedOrigins: [...] } or { unsafelyAllowAnyOrigin: true } (CWE-829).`
2500
+ );
2501
+ }
2040
2502
  let module2;
2041
2503
  if (typeof WebAssembly.compileStreaming === "function") {
2042
2504
  module2 = await WebAssembly.compileStreaming(fetch(url));