@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.5

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 (57) hide show
  1. package/dist/dev.cjs +52 -7
  2. package/dist/dev.js +51 -8
  3. package/dist/server.cjs +162 -35
  4. package/dist/server.js +161 -36
  5. package/dist/web.cjs +32 -7
  6. package/dist/web.js +31 -8
  7. package/frames/dist/client.cjs +41 -8
  8. package/frames/dist/client.dev.cjs +41 -8
  9. package/frames/dist/client.dev.js +41 -8
  10. package/frames/dist/client.js +41 -8
  11. package/frames/dist/server.cjs +432 -44
  12. package/frames/dist/server.js +432 -44
  13. package/package.json +2 -2
  14. package/serialization/dist/decode.cjs +4 -2
  15. package/serialization/dist/decode.js +4 -2
  16. package/serialization/dist/serialization.cjs +12 -8
  17. package/serialization/dist/serialization.js +12 -8
  18. package/serialization/types/index.d.ts +7 -0
  19. package/serialization/types/serializer-decode.d.ts +14 -1
  20. package/serialization/types/serializer.d.ts +7 -0
  21. package/serialization/types-cjs/index.d.cts +7 -0
  22. package/serialization/types-cjs/serializer-decode.d.cts +14 -1
  23. package/serialization/types-cjs/serializer.d.cts +7 -0
  24. package/server-functions/dist/client.cjs +150 -33
  25. package/server-functions/dist/client.js +147 -34
  26. package/server-functions/dist/server.cjs +731 -118
  27. package/server-functions/dist/server.dev.cjs +751 -118
  28. package/server-functions/dist/server.dev.js +747 -119
  29. package/server-functions/dist/server.js +727 -119
  30. package/types/cookies.d.ts +6 -14
  31. package/types/frames/frame-client.d.ts +4 -0
  32. package/types/frames/serializer-decode.d.ts +14 -1
  33. package/types/frames/serializer.d.ts +7 -0
  34. package/types/jsx.d.ts +11 -16
  35. package/types/response.d.ts +11 -0
  36. package/types/serializer-decode.d.ts +14 -1
  37. package/types/serializer.d.ts +7 -0
  38. package/types/server-functions/client.d.ts +22 -2
  39. package/types/server-functions/flash.d.ts +9 -0
  40. package/types/server-functions/server.d.ts +58 -9
  41. package/types/server-functions/shared.d.ts +77 -14
  42. package/types/server-mock.d.ts +17 -2
  43. package/types/server.d.ts +16 -2
  44. package/types-cjs/cookies.d.cts +6 -14
  45. package/types-cjs/frames/frame-client.d.cts +4 -0
  46. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  47. package/types-cjs/frames/serializer.d.cts +7 -0
  48. package/types-cjs/jsx.d.cts +11 -16
  49. package/types-cjs/response.d.cts +11 -0
  50. package/types-cjs/serializer-decode.d.cts +14 -1
  51. package/types-cjs/serializer.d.cts +7 -0
  52. package/types-cjs/server-functions/client.d.cts +22 -2
  53. package/types-cjs/server-functions/flash.d.cts +9 -0
  54. package/types-cjs/server-functions/server.d.cts +58 -9
  55. package/types-cjs/server-functions/shared.d.cts +77 -14
  56. package/types-cjs/server-mock.d.cts +17 -2
  57. package/types-cjs/server.d.cts +16 -2
package/dist/server.js CHANGED
@@ -150,13 +150,13 @@ function resolveSerializerPlugins(customPlugins) {
150
150
  Feature.RegExp;
151
151
 
152
152
  const DEFAULT_DISABLED_FEATURES = Feature.AggregateError | Feature.BigIntTypedArray;
153
- const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
153
+ const serializeOnlyDisabledFeatures = serializeErrorStacks => (serializeErrorStacks === undefined ? process.env.NODE_ENV === "development" : serializeErrorStacks) ? 0 : Feature.ErrorPrototypeStack;
154
154
  const HYDRATION_GLOBAL = "_$HY.r";
155
155
  function createSerializer(options) {
156
156
  return new Serializer({
157
157
  ...options,
158
158
  plugins: resolveSerializerPlugins(options.plugins),
159
- disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures()
159
+ disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures(options.serializeErrorStacks)
160
160
  });
161
161
  }
162
162
  function createHydrationSerializer({
@@ -213,6 +213,7 @@ function isSafeError(value) {
213
213
  return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
214
214
  }
215
215
  const REVALIDATE_HEADER = "X-Revalidate";
216
+ const RESPONSE_HEADER_VALUE_LIMIT = 4096;
216
217
  function initWithRevalidate(init = {}) {
217
218
  const resolved = typeof init === "number" ? {
218
219
  status: init
@@ -233,7 +234,13 @@ function initWithRevalidate(init = {}) {
233
234
  } else {
234
235
  headers = new Headers(responseInit.headers);
235
236
  }
236
- revalidate !== undefined && headers.set(REVALIDATE_HEADER, revalidate.toString());
237
+ if (revalidate !== undefined) {
238
+ const keys = revalidate.toString();
239
+ if (keys.length > RESPONSE_HEADER_VALUE_LIMIT) {
240
+ throw new TypeError(`revalidate names ${keys.length} characters of cache keys; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the ${REVALIDATE_HEADER} header would overflow ` + `receivers (an 8 KiB proxy buffer holds the whole header block) and the response ` + `dies at the socket after the mutation committed. Split the invalidation across ` + `calls or use coarser keys — a dropped key would be a silently stale cache, so ` + `the runtime refuses rather than trims.`);
241
+ }
242
+ headers.set(REVALIDATE_HEADER, keys);
243
+ }
237
244
  return {
238
245
  responseInit,
239
246
  headers
@@ -251,7 +258,12 @@ function redirect(url, init = 302) {
251
258
  responseInit.status = 302;
252
259
  }
253
260
  const target = typeof url === "string" ? url : url[HREF];
254
- headers.set("Location", typeof target === "string" ? target : String(url));
261
+ const location = typeof target === "string" ? target : String(url);
262
+ const encoded = location.replace(/[^\x00-\x7f]/gu, encodeURIComponent);
263
+ if (encoded.length > RESPONSE_HEADER_VALUE_LIMIT) {
264
+ throw new TypeError(`redirect() target is ${encoded.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the Location header (and the scripted transport's ` + `redirect header) would overflow receivers and the response dies at the socket. ` + `A target this size is usually unbounded input echoed into the url — carry the ` + `state server-side instead. Refused rather than trimmed: a cut target is a ` + `different address.`);
265
+ }
266
+ headers.set("Location", encoded);
255
267
  return new Response(null, {
256
268
  ...responseInit,
257
269
  headers
@@ -267,11 +279,18 @@ function reload(init = {}) {
267
279
  headers
268
280
  });
269
281
  }
282
+ const NULL_BODY_STATUSES = new Set([204, 205, 304]);
270
283
  function respond(value, init = {}) {
271
284
  const {
272
285
  responseInit,
273
286
  headers
274
287
  } = initWithRevalidate(init);
288
+ if (NULL_BODY_STATUSES.has(responseInit.status)) {
289
+ return new ResponseEnvelope(new Response(null, {
290
+ ...responseInit,
291
+ headers
292
+ }), value);
293
+ }
275
294
  headers.set("Content-Type", "application/json");
276
295
  return new ResponseEnvelope(new Response(JSON.stringify(value), {
277
296
  ...responseInit,
@@ -322,6 +341,7 @@ function serializeCookie(name, value, options = {}) {
322
341
  if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
323
342
  if (options.httpOnly) cookie += "; HttpOnly";
324
343
  if (options.secure) cookie += "; Secure";
344
+ if (options.partitioned) cookie += "; Partitioned";
325
345
  if (options.sameSite) {
326
346
  const sameSite = options.sameSite.toLowerCase();
327
347
  cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
@@ -339,6 +359,7 @@ function clearFlashCookie() {
339
359
 
340
360
  const ERROR_HEADER = "X-Server-Function-Error";
341
361
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
362
+ const REDIRECT_HEADER = "X-Server-Function-Redirect";
342
363
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
343
364
 
344
365
  const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
@@ -377,7 +398,8 @@ function resourceIdentity(tag, props) {
377
398
  let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
378
399
  for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
379
400
  const q = RESOURCE_QUALIFIERS[i];
380
- if (props[q] != null) id += ":" + q + "=" + props[q];
401
+ const value = props[q];
402
+ if (value != null) id += ":" + q + "=" + (value === true ? "" : value);
381
403
  }
382
404
  return id;
383
405
  }
@@ -439,6 +461,7 @@ function resolveAssets(moduleUrl, manifest) {
439
461
  if (!entry) return null;
440
462
  const css = [];
441
463
  const js = [];
464
+ let preloads;
442
465
  const visited = new Set();
443
466
  const walk = key => {
444
467
  if (visited.has(key)) return;
@@ -447,13 +470,26 @@ function resolveAssets(moduleUrl, manifest) {
447
470
  if (!e) return;
448
471
  js.push(joinAssetPath(base, e.file));
449
472
  if (e.css) for (let i = 0; i < e.css.length; i++) css.push(joinAssetPath(base, e.css[i]));
473
+ if (e.preloads) {
474
+ for (let i = 0; i < e.preloads.length; i++) {
475
+ const link = e.preloads[i];
476
+ if (!link || typeof link.href !== "string" || !link.href) continue;
477
+ if (!preloads) preloads = [];
478
+ preloads.push({
479
+ ...link,
480
+ href: joinAssetPath(base, link.href)
481
+ });
482
+ }
483
+ }
450
484
  if (e.imports) for (let i = 0; i < e.imports.length; i++) walk(e.imports[i]);
451
485
  };
452
486
  walk(moduleUrl);
453
- return {
487
+ const assets = {
454
488
  js,
455
489
  css
456
490
  };
491
+ if (preloads) assets.preloads = preloads;
492
+ return assets;
457
493
  }
458
494
  function registerEntryAssets(manifest) {
459
495
  if (!manifest || typeof manifest === "function" || typeof manifest.resolve === "function") return;
@@ -463,6 +499,7 @@ function registerEntryAssets(manifest) {
463
499
  if (manifest[key].isEntry) {
464
500
  const assets = resolveAssets(key, manifest);
465
501
  if (assets) {
502
+ if (assets.preloads) for (let i = 0; i < assets.preloads.length; i++) ctx.registerAsset("preload", assets.preloads[i]);
466
503
  for (let i = 0; i < assets.css.length; i++) ctx.registerAsset("style", assets.css[i]);
467
504
  for (let i = 1; i < assets.js.length; i++) ctx.registerAsset("module", assets.js[i]);
468
505
  }
@@ -481,6 +518,7 @@ function createAssetTracking() {
481
518
  boundaryStyles,
482
519
  emittedAssets,
483
520
  inlineStyles,
521
+ preloadLinks: null,
484
522
  registerInlineStyle(desc) {
485
523
  let entry = inlineStyles.get(desc.id);
486
524
  if (!entry) {
@@ -572,6 +610,56 @@ function isCssUrl(url) {
572
610
  const q = url.search(/[?#]/);
573
611
  return (q === -1 ? url : url.slice(0, q)).endsWith(".css");
574
612
  }
613
+ const PRELOAD_LINK_ATTRIBUTES = ["type", "crossorigin", "integrity", "referrerpolicy", "fetchpriority", "media"];
614
+ function registerPreloadLink(tracking, headRegistry, link, nonce) {
615
+ if (!link || typeof link !== "object" || typeof link.href !== "string" || !link.href) {
616
+ return null;
617
+ }
618
+ if (typeof link.as !== "string") {
619
+ return null;
620
+ }
621
+ const as = asciiLowerCase(link.as);
622
+ let destination = null;
623
+ switch (as) {
624
+ case "script":
625
+ case "style":
626
+ destination = as;
627
+ break;
628
+ case "fetch":
629
+ case "font":
630
+ case "image":
631
+ case "track":
632
+ break;
633
+ default:
634
+ return null;
635
+ }
636
+ const props = {
637
+ rel: "preload",
638
+ href: link.href,
639
+ as
640
+ };
641
+ for (let i = 0; i < PRELOAD_LINK_ATTRIBUTES.length; i++) {
642
+ const name = PRELOAD_LINK_ATTRIBUTES[i];
643
+ const value = link[name];
644
+ if (value == null || value === false) continue;
645
+ props[name] = value === true ? "" : String(value);
646
+ }
647
+ const identity = resourceIdentity("link", props);
648
+ if (headRegistry.resources.has(identity)) return null;
649
+ const attrs = headAttrRecord(props);
650
+ const nonceValue = destination && nonce && nonce[destination];
651
+ if (typeof nonceValue === "string" && nonceValue) attrs.nonce = nonceValue;
652
+ const entry = {
653
+ href: props.href,
654
+ attrs,
655
+ attrHtml: renderHeadAttrHtml(props) + nonceAttr(nonce, destination)
656
+ };
657
+ headRegistry.resources.add(identity);
658
+ let links = tracking.preloadLinks;
659
+ if (!links) tracking.preloadLinks = links = [];
660
+ links.push(entry);
661
+ return entry;
662
+ }
575
663
  function createHeadRegistry() {
576
664
  return {
577
665
  pending: [],
@@ -1048,6 +1136,10 @@ function renderToString(code, options = {}) {
1048
1136
  serializer.write(id, p);
1049
1137
  },
1050
1138
  registerAsset(type, value) {
1139
+ if (type === "preload") {
1140
+ registerPreloadLink(tracking, headRegistry, value, nonce);
1141
+ return;
1142
+ }
1051
1143
  if (type === "inline-style") {
1052
1144
  tracking.registerInlineStyle(value);
1053
1145
  return;
@@ -1075,7 +1167,7 @@ function renderToString(code, options = {}) {
1075
1167
  sharedConfig.context.noHydrate = true;
1076
1168
  serializer.close();
1077
1169
  const head = renderShellHead(headRegistry, nonce, null, noScripts);
1078
- return assembleDocument(resolveSSRSelectValues(html), tracking.emittedAssets, tracking.inlineStyles, scripts.length ? scripts : "", nonce, head, onHead);
1170
+ return assembleDocument(resolveSSRSelectValues(html), tracking.emittedAssets, tracking.preloadLinks, tracking.inlineStyles, scripts.length ? scripts : "", nonce, head, onHead);
1079
1171
  }
1080
1172
  function renderToStream(code, options = {}) {
1081
1173
  let {
@@ -1229,6 +1321,8 @@ function renderToStream(code, options = {}) {
1229
1321
  asset(type, value) {
1230
1322
  if (type === "module") {
1231
1323
  buffer.write(`<link rel="modulepreload" href="${value}"${nonceAttr(nonce, "script")}>`);
1324
+ } else if (type === "preload") {
1325
+ buffer.write(`<link${value.attrHtml}>`);
1232
1326
  } else if (type === "inline-style") {
1233
1327
  buffer.write(renderInlineStyle(value, nonce));
1234
1328
  } else if (type === "head-tag") {
@@ -1236,7 +1330,7 @@ function renderToStream(code, options = {}) {
1236
1330
  }
1237
1331
  },
1238
1332
  shell(shellHtml, meta) {
1239
- buffer.write(assembleDocument(shellHtml, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce, meta.head, onHead));
1333
+ buffer.write(assembleDocument(shellHtml, meta.preloads, meta.preloadLinks, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce, meta.head, onHead));
1240
1334
  },
1241
1335
  ...options.sink
1242
1336
  };
@@ -1269,6 +1363,29 @@ function renderToStream(code, options = {}) {
1269
1363
  }
1270
1364
  };
1271
1365
  const registry = new Map();
1366
+ const pendingSerialized = new Map();
1367
+ const trackSerialized = (id, p) => {
1368
+ let settle;
1369
+ const raced = Promise.race([p, new Promise(r => settle = r)]);
1370
+ pendingSerialized.set(id, settle);
1371
+ const drop = () => pendingSerialized.delete(id);
1372
+ p.then(drop, drop);
1373
+ return raced;
1374
+ };
1375
+ const abandonSubtree = key => {
1376
+ for (const [k, entry] of registry) {
1377
+ if (k.length > key.length && k.startsWith(key)) {
1378
+ registry.delete(k);
1379
+ entry.resolve();
1380
+ }
1381
+ }
1382
+ for (const [id, settle] of pendingSerialized) {
1383
+ if (id.startsWith(key)) {
1384
+ pendingSerialized.delete(id);
1385
+ settle();
1386
+ }
1387
+ }
1388
+ };
1272
1389
  const writeTasks = () => {
1273
1390
  if (tasks.length && !completed && firstFlushed) {
1274
1391
  buffer.write(`<script${nonceAttr(nonce, "script")}>${tasks}</script>`);
@@ -1339,6 +1456,11 @@ function renderToStream(code, options = {}) {
1339
1456
  }, nonce, tags);
1340
1457
  },
1341
1458
  registerAsset(type, value) {
1459
+ if (type === "preload") {
1460
+ const entry = registerPreloadLink(tracking, headRegistry, value, nonce);
1461
+ if (entry && firstFlushed) sink.asset("preload", entry);
1462
+ return;
1463
+ }
1342
1464
  if (type === "inline-style") {
1343
1465
  const entry = tracking.registerInlineStyle(value);
1344
1466
  if (firstFlushed && !tracking.currentBoundaryId && !entry.emitted) {
@@ -1383,13 +1505,14 @@ function renderToStream(code, options = {}) {
1383
1505
  },
1384
1506
  serialize(id, p, deferStream) {
1385
1507
  if (sharedConfig.context.noHydrate) return;
1386
- if (!firstFlushed && p && typeof p === "object" && "then" in p) {
1387
- if (deferStream) {
1508
+ if (p && typeof p === "object" && typeof p.then === "function") {
1509
+ if (!firstFlushed && deferStream) {
1388
1510
  blockingPromises.add(p);
1389
1511
  p.then(d => serializer.write(id, d)).catch(e => serializer.write(id, e));
1390
1512
  return;
1391
1513
  }
1392
- if (canBatchStubs && !shellCompleted) {
1514
+ p = trackSerialized(id, p);
1515
+ if (!firstFlushed && canBatchStubs && !shellCompleted) {
1393
1516
  (stubBatch ||= new Map()).set(id, p);
1394
1517
  return;
1395
1518
  }
@@ -1434,6 +1557,7 @@ function renderToStream(code, options = {}) {
1434
1557
  if (registry.has(key)) {
1435
1558
  const item = registry.get(key);
1436
1559
  registry.delete(key);
1560
+ if (error) abandonSubtree(key);
1437
1561
  if (item.children) {
1438
1562
  for (const k in item.children) {
1439
1563
  value = replacePlaceholder(value, k, item.children[k]);
@@ -1558,6 +1682,7 @@ function renderToStream(code, options = {}) {
1558
1682
  const head = renderShellHead(headRegistry, nonce, k => registry.has(k), noScripts);
1559
1683
  sink.shell(resolveSSRSelectValues(html), {
1560
1684
  preloads: tracking.emittedAssets,
1685
+ preloadLinks: tracking.preloadLinks,
1561
1686
  inlineStyles: tracking.inlineStyles,
1562
1687
  tasks,
1563
1688
  head
@@ -2704,12 +2829,12 @@ function allSettled(promises) {
2704
2829
  return;
2705
2830
  });
2706
2831
  }
2707
- function assembleDocument(html, emittedAssets, inlineStyles, scripts, nonce, headTags, onHead) {
2832
+ function assembleDocument(html, emittedAssets, preloadLinks, inlineStyles, scripts, nonce, headTags, onHead) {
2708
2833
  const scriptTag = scripts ? `<script${nonceAttr(nonce, "script")}>${scripts}</script>` : "";
2709
2834
  const title = headTags ? headTags.title : null;
2710
2835
  let headTagsHtml = headTags ? headTags.html : "";
2711
2836
  const headPrelude = headTags ? headTags.prelude : "";
2712
- if (!onHead && title == null && !headTagsHtml && !headPrelude && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
2837
+ if (!onHead && title == null && !headTagsHtml && !headPrelude && !(emittedAssets && emittedAssets.size) && !preloadLinks && !(inlineStyles && inlineStyles.size)) {
2713
2838
  if (!scriptTag) return html;
2714
2839
  const xs = html.indexOf("<!--xs-->");
2715
2840
  return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
@@ -2728,7 +2853,7 @@ function assembleDocument(html, emittedAssets, inlineStyles, scripts, nonce, hea
2728
2853
  if (title != null) {
2729
2854
  titleHtml = headTags.noScripts ? `<title data-dh="title">${escape(title)}</title>` : `<script${nonceAttr(nonce, "script")}>(function(x,t){(t=document.querySelector("title"))?(t.hasAttribute("data-dh")||t.setAttribute("data-dhf",t.textContent),t.textContent=x):(document.title=x,t=document.querySelector("title"));t&&t.setAttribute("data-dh","title")})(${JSON.stringify(title).replace(/</g, "\\u003C")})</script>`;
2730
2855
  }
2731
- onHead(headPrelude + headTagsHtml + titleHtml + renderHeadAssets(emittedAssets, inlineStyles, nonce));
2856
+ onHead(headPrelude + headTagsHtml + titleHtml + renderHeadAssets(emittedAssets, preloadLinks, inlineStyles, nonce));
2732
2857
  }
2733
2858
  if (!scriptTag) return html;
2734
2859
  const xs = html.indexOf("<!--xs-->");
@@ -2748,13 +2873,13 @@ function assembleDocument(html, emittedAssets, inlineStyles, scripts, nonce, hea
2748
2873
  headTagsHtml = `<title data-dh="title">${winner}</title>` + headTagsHtml;
2749
2874
  }
2750
2875
  }
2751
- const head = headTagsHtml + renderHeadAssets(emittedAssets, inlineStyles, nonce);
2876
+ const head = headTagsHtml + renderHeadAssets(emittedAssets, preloadLinks, inlineStyles, nonce);
2752
2877
  if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
2753
2878
  const xsIdx = html.indexOf("<!--xs-->");
2754
2879
  if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
2755
2880
  return xsIdx < headIdx ? html.slice(0, xsIdx) + scriptTag + html.slice(xsIdx, headIdx) + head + html.slice(headIdx) : html.slice(0, headIdx) + head + html.slice(headIdx, xsIdx) + scriptTag + html.slice(xsIdx);
2756
2881
  }
2757
- function renderHeadAssets(emittedAssets, inlineStyles, nonce) {
2882
+ function renderHeadAssets(emittedAssets, preloadLinks, inlineStyles, nonce) {
2758
2883
  let head = "";
2759
2884
  const styleAttr = nonceAttr(nonce, "style");
2760
2885
  const scriptAttr = nonceAttr(nonce, "script");
@@ -2763,6 +2888,9 @@ function renderHeadAssets(emittedAssets, inlineStyles, nonce) {
2763
2888
  head += isCssUrl(url) ? `<link rel="stylesheet" href="${url}"${styleAttr}>` : `<link rel="modulepreload" href="${url}"${scriptAttr}>`;
2764
2889
  }
2765
2890
  }
2891
+ if (preloadLinks) {
2892
+ for (const entry of preloadLinks) head += `<link${entry.attrHtml}>`;
2893
+ }
2766
2894
  if (inlineStyles && inlineStyles.size) {
2767
2895
  for (const entry of inlineStyles.values()) {
2768
2896
  if (entry.emitted) continue;
@@ -3027,7 +3155,7 @@ function copyInitHeaders(init) {
3027
3155
  for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
3028
3156
  return headers;
3029
3157
  }
3030
- const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
3158
+ const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location"].map(header => header.toLowerCase()));
3031
3159
  function fillsStubGap(key, headers, response) {
3032
3160
  if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
3033
3161
  if (response.body === null && (key === "content-type" || key === "content-length")) return false;
@@ -3043,24 +3171,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
3043
3171
  if (fillsStubGap(key, response.headers, response)) hasGaps = true;
3044
3172
  });
3045
3173
  if (!cookies.length && !hasGaps) return response;
3046
- try {
3047
- for (const cookie of cookies) response.headers.append("Set-Cookie", cookie);
3048
- stub.headers.forEach((value, key) => {
3049
- if (fillsStubGap(key, response.headers, response)) response.headers.set(key, value);
3050
- });
3051
- return response;
3052
- } catch {
3053
- const headers = copyInitHeaders(response.headers);
3054
- for (const cookie of cookies) headers.append("Set-Cookie", cookie);
3055
- stub.headers.forEach((value, key) => {
3056
- if (fillsStubGap(key, headers, response)) headers.set(key, value);
3057
- });
3058
- return new Response(response.body, {
3059
- status: response.status,
3060
- statusText: response.statusText,
3061
- headers
3062
- });
3063
- }
3174
+ const headers = copyInitHeaders(response.headers);
3175
+ for (const cookie of cookies) headers.append("Set-Cookie", cookie);
3176
+ stub.headers.forEach((value, key) => {
3177
+ if (fillsStubGap(key, headers, response)) headers.set(key, value);
3178
+ });
3179
+ return new Response(response.body, {
3180
+ status: response.status,
3181
+ statusText: response.statusText,
3182
+ headers
3183
+ });
3064
3184
  }
3065
3185
  function deriveHead(stub, responseInit = {}) {
3066
3186
  const headers = mergeStubHeaders(copyInitHeaders(responseInit.headers), stub);
@@ -3226,6 +3346,11 @@ function registerClientOnlyPreload(moduleUrl) {
3226
3346
  const css = assets.css[i];
3227
3347
  if (typeof css === "string") registerAsset("style", css);else registerAsset("inline-style", css);
3228
3348
  }
3349
+ if (assets.preloads) {
3350
+ for (let i = 0; i < assets.preloads.length; i++) {
3351
+ registerAsset("preload", assets.preloads[i]);
3352
+ }
3353
+ }
3229
3354
  for (let i = 0; i < assets.js.length; i++) registerAsset("module", assets.js[i]);
3230
3355
  };
3231
3356
  const assets = resolve(moduleUrl);
@@ -3329,4 +3454,4 @@ function httpHeader(name, value, options) {
3329
3454
  }
3330
3455
  }
3331
3456
 
3332
- export { CLAIMS_DOCUMENT, CLAIMS_STREAM, CLAIM_PROP, ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, Namespaces, Portal, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SAFE_ERROR, SVGElements, VoidElements, notSup as acquireAsset, notSup as addEvent, applyRef, notSup as assign, claimElement, claimElementTree, notSup as className, clearFlashCookie, clientOnly, commitEventResponse, commitResponseStub, composeMiddleware, createLiveHoles, createRequestEvent, createResponseStub, createSSRResponse, notSup as delegateEvents, dynamic, notSup as dynamicProperty, effect, escape, generateHydrationScript, notSup as getDelegatedRoot, getExpectedRedirectStatus, getHydrationKey, notSup as getNextElement, notSup as getNextMarker, notSup as getNextMatch, getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, hasFlashCookie, httpHeader, httpStatus, notSup as hydrate, notSup as insert, isDev, isHref, isResponseEnvelope, isSafeError, isServer, isServerFunction, markSafeError, memo, parseCookieHeader, redirect, notSup as ref, notSup as registerDelegatedContainer, notSup as registerDelegatedRoot, registerElementClaim, reload, notSup as render, renderToStream, renderToString, resolveSSRNode, resolveSSRSelectValues, respond, notSup as runHydrationEvents, scriptNonce, serializeCookie, notSup as setAttribute, notSup as setAttributeNS, notSup as setProperty, notSup as setStyleProperty, notSup as spread, ssr, ssrAttribute, ssrClaim, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrSelectValues, ssrStyle, ssrStyleProperty, notSup as style, styleNonce, notSup as template, notSup as unregisterDelegatedContainer, notSup as unregisterDelegatedRoot, useHead };
3457
+ export { CLAIMS_DOCUMENT, CLAIMS_STREAM, CLAIM_PROP, ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, NULL_BODY_STATUSES, Namespaces, Portal, RESPONSE_HEADER_VALUE_LIMIT, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SAFE_ERROR, SVGElements, VoidElements, notSup as acquireAsset, notSup as addEvent, applyRef, notSup as assign, claimElement, claimElementTree, notSup as className, clearFlashCookie, clientOnly, commitEventResponse, commitResponseStub, composeMiddleware, createLiveHoles, createRequestEvent, createResponseStub, createSSRResponse, notSup as delegateEvents, dynamic, notSup as dynamicProperty, effect, escape, generateHydrationScript, notSup as getDelegatedRoot, getExpectedRedirectStatus, getHydrationKey, notSup as getNextElement, notSup as getNextMarker, notSup as getNextMatch, getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, hasFlashCookie, httpHeader, httpStatus, notSup as hydrate, notSup as insert, isDev, isHref, isResponseEnvelope, isSafeError, isServer, isServerFunction, markSafeError, memo, parseCookieHeader, redirect, notSup as ref, notSup as registerDelegatedContainer, notSup as registerDelegatedRoot, registerElementClaim, reload, notSup as render, renderToStream, renderToString, resolveSSRNode, resolveSSRSelectValues, respond, notSup as runHydrationEvents, scriptNonce, serializeCookie, notSup as setAttribute, notSup as setAttributeNS, notSup as setProperty, notSup as setStyleProperty, notSup as spread, ssr, ssrAttribute, ssrClaim, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrSelectValues, ssrStyle, ssrStyleProperty, notSup as style, styleNonce, notSup as template, notSup as unregisterDelegatedContainer, notSup as unregisterDelegatedRoot, useHead };
package/dist/web.cjs CHANGED
@@ -215,7 +215,8 @@ function resourceIdentity(tag, props) {
215
215
  let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
216
216
  for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
217
217
  const q = RESOURCE_QUALIFIERS[i];
218
- if (props[q] != null) id += ":" + q + "=" + props[q];
218
+ const value = props[q];
219
+ if (value != null) id += ":" + q + "=" + (value === true ? "" : value);
219
220
  }
220
221
  return id;
221
222
  }
@@ -294,6 +295,7 @@ function serializeCookie(name, value, options = {}) {
294
295
  if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
295
296
  if (options.httpOnly) cookie += "; HttpOnly";
296
297
  if (options.secure) cookie += "; Secure";
298
+ if (options.partitioned) cookie += "; Partitioned";
297
299
  if (options.sameSite) {
298
300
  const sameSite = options.sameSite.toLowerCase();
299
301
  cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
@@ -580,16 +582,18 @@ function setStyleProperty(node, name, value) {
580
582
  }
581
583
  function spread(node, props = {}, skipChildren) {
582
584
  const prevProps = {};
583
- if (!skipChildren) insert(node, () => props.children);
585
+ const get = typeof props === "function" ? props : () => props;
586
+ if (!skipChildren) insert(node, () => get().children);
584
587
  effect(() => {
585
- const r = props.ref;
588
+ const r = get().ref;
586
589
  (typeof r === "function" || Array.isArray(r)) && ref(() => r, node);
587
590
  }, () => {});
588
591
  effect(() => {
592
+ const source = get();
589
593
  const newProps = {};
590
- for (const prop in props) {
594
+ for (const prop in source) {
591
595
  if (prop === "children" || prop === "ref") continue;
592
- newProps[prop] = props[prop];
596
+ newProps[prop] = source[prop];
593
597
  }
594
598
  return newProps;
595
599
  }, props => assign(node, props, true, prevProps, true));
@@ -2016,6 +2020,7 @@ function isSafeError(value) {
2016
2020
  return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
2017
2021
  }
2018
2022
  const REVALIDATE_HEADER = "X-Revalidate";
2023
+ const RESPONSE_HEADER_VALUE_LIMIT = 4096;
2019
2024
  function initWithRevalidate(init = {}) {
2020
2025
  const resolved = typeof init === "number" ? {
2021
2026
  status: init
@@ -2036,7 +2041,13 @@ function initWithRevalidate(init = {}) {
2036
2041
  } else {
2037
2042
  headers = new Headers(responseInit.headers);
2038
2043
  }
2039
- revalidate !== undefined && headers.set(REVALIDATE_HEADER, revalidate.toString());
2044
+ if (revalidate !== undefined) {
2045
+ const keys = revalidate.toString();
2046
+ if (keys.length > RESPONSE_HEADER_VALUE_LIMIT) {
2047
+ throw new TypeError(`revalidate names ${keys.length} characters of cache keys; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the ${REVALIDATE_HEADER} header would overflow ` + `receivers (an 8 KiB proxy buffer holds the whole header block) and the response ` + `dies at the socket after the mutation committed. Split the invalidation across ` + `calls or use coarser keys — a dropped key would be a silently stale cache, so ` + `the runtime refuses rather than trims.`);
2048
+ }
2049
+ headers.set(REVALIDATE_HEADER, keys);
2050
+ }
2040
2051
  return {
2041
2052
  responseInit,
2042
2053
  headers
@@ -2054,7 +2065,12 @@ function redirect(url, init = 302) {
2054
2065
  responseInit.status = 302;
2055
2066
  }
2056
2067
  const target = typeof url === "string" ? url : url[HREF];
2057
- headers.set("Location", typeof target === "string" ? target : String(url));
2068
+ const location = typeof target === "string" ? target : String(url);
2069
+ const encoded = location.replace(/[^\x00-\x7f]/gu, encodeURIComponent);
2070
+ if (encoded.length > RESPONSE_HEADER_VALUE_LIMIT) {
2071
+ throw new TypeError(`redirect() target is ${encoded.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the Location header (and the scripted transport's ` + `redirect header) would overflow receivers and the response dies at the socket. ` + `A target this size is usually unbounded input echoed into the url — carry the ` + `state server-side instead. Refused rather than trimmed: a cut target is a ` + `different address.`);
2072
+ }
2073
+ headers.set("Location", encoded);
2058
2074
  return new Response(null, {
2059
2075
  ...responseInit,
2060
2076
  headers
@@ -2070,11 +2086,18 @@ function reload(init = {}) {
2070
2086
  headers
2071
2087
  });
2072
2088
  }
2089
+ const NULL_BODY_STATUSES = new Set([204, 205, 304]);
2073
2090
  function respond(value, init = {}) {
2074
2091
  const {
2075
2092
  responseInit,
2076
2093
  headers
2077
2094
  } = initWithRevalidate(init);
2095
+ if (NULL_BODY_STATUSES.has(responseInit.status)) {
2096
+ return new ResponseEnvelope(new Response(null, {
2097
+ ...responseInit,
2098
+ headers
2099
+ }), value);
2100
+ }
2078
2101
  headers.set("Content-Type", "application/json");
2079
2102
  return new ResponseEnvelope(new Response(JSON.stringify(value), {
2080
2103
  ...responseInit,
@@ -2289,8 +2312,10 @@ exports.Dynamic = Dynamic;
2289
2312
  exports.HREF = HREF;
2290
2313
  exports.HydrationScript = HydrationScript;
2291
2314
  exports.MathMLElements = MathMLElements;
2315
+ exports.NULL_BODY_STATUSES = NULL_BODY_STATUSES;
2292
2316
  exports.Namespaces = Namespaces;
2293
2317
  exports.Portal = Portal;
2318
+ exports.RESPONSE_HEADER_VALUE_LIMIT = RESPONSE_HEADER_VALUE_LIMIT;
2294
2319
  exports.REVALIDATE_HEADER = REVALIDATE_HEADER;
2295
2320
  exports.RawTextElements = RawTextElements;
2296
2321
  exports.RequestContext = RequestContext;
package/dist/web.js CHANGED
@@ -214,7 +214,8 @@ function resourceIdentity(tag, props) {
214
214
  let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
215
215
  for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
216
216
  const q = RESOURCE_QUALIFIERS[i];
217
- if (props[q] != null) id += ":" + q + "=" + props[q];
217
+ const value = props[q];
218
+ if (value != null) id += ":" + q + "=" + (value === true ? "" : value);
218
219
  }
219
220
  return id;
220
221
  }
@@ -293,6 +294,7 @@ function serializeCookie(name, value, options = {}) {
293
294
  if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
294
295
  if (options.httpOnly) cookie += "; HttpOnly";
295
296
  if (options.secure) cookie += "; Secure";
297
+ if (options.partitioned) cookie += "; Partitioned";
296
298
  if (options.sameSite) {
297
299
  const sameSite = options.sameSite.toLowerCase();
298
300
  cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
@@ -579,16 +581,18 @@ function setStyleProperty(node, name, value) {
579
581
  }
580
582
  function spread(node, props = {}, skipChildren) {
581
583
  const prevProps = {};
582
- if (!skipChildren) insert(node, () => props.children);
584
+ const get = typeof props === "function" ? props : () => props;
585
+ if (!skipChildren) insert(node, () => get().children);
583
586
  effect(() => {
584
- const r = props.ref;
587
+ const r = get().ref;
585
588
  (typeof r === "function" || Array.isArray(r)) && ref(() => r, node);
586
589
  }, () => {});
587
590
  effect(() => {
591
+ const source = get();
588
592
  const newProps = {};
589
- for (const prop in props) {
593
+ for (const prop in source) {
590
594
  if (prop === "children" || prop === "ref") continue;
591
- newProps[prop] = props[prop];
595
+ newProps[prop] = source[prop];
592
596
  }
593
597
  return newProps;
594
598
  }, props => assign(node, props, true, prevProps, true));
@@ -2015,6 +2019,7 @@ function isSafeError(value) {
2015
2019
  return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
2016
2020
  }
2017
2021
  const REVALIDATE_HEADER = "X-Revalidate";
2022
+ const RESPONSE_HEADER_VALUE_LIMIT = 4096;
2018
2023
  function initWithRevalidate(init = {}) {
2019
2024
  const resolved = typeof init === "number" ? {
2020
2025
  status: init
@@ -2035,7 +2040,13 @@ function initWithRevalidate(init = {}) {
2035
2040
  } else {
2036
2041
  headers = new Headers(responseInit.headers);
2037
2042
  }
2038
- revalidate !== undefined && headers.set(REVALIDATE_HEADER, revalidate.toString());
2043
+ if (revalidate !== undefined) {
2044
+ const keys = revalidate.toString();
2045
+ if (keys.length > RESPONSE_HEADER_VALUE_LIMIT) {
2046
+ throw new TypeError(`revalidate names ${keys.length} characters of cache keys; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the ${REVALIDATE_HEADER} header would overflow ` + `receivers (an 8 KiB proxy buffer holds the whole header block) and the response ` + `dies at the socket after the mutation committed. Split the invalidation across ` + `calls or use coarser keys — a dropped key would be a silently stale cache, so ` + `the runtime refuses rather than trims.`);
2047
+ }
2048
+ headers.set(REVALIDATE_HEADER, keys);
2049
+ }
2039
2050
  return {
2040
2051
  responseInit,
2041
2052
  headers
@@ -2053,7 +2064,12 @@ function redirect(url, init = 302) {
2053
2064
  responseInit.status = 302;
2054
2065
  }
2055
2066
  const target = typeof url === "string" ? url : url[HREF];
2056
- headers.set("Location", typeof target === "string" ? target : String(url));
2067
+ const location = typeof target === "string" ? target : String(url);
2068
+ const encoded = location.replace(/[^\x00-\x7f]/gu, encodeURIComponent);
2069
+ if (encoded.length > RESPONSE_HEADER_VALUE_LIMIT) {
2070
+ throw new TypeError(`redirect() target is ${encoded.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} the Location header (and the scripted transport's ` + `redirect header) would overflow receivers and the response dies at the socket. ` + `A target this size is usually unbounded input echoed into the url — carry the ` + `state server-side instead. Refused rather than trimmed: a cut target is a ` + `different address.`);
2071
+ }
2072
+ headers.set("Location", encoded);
2057
2073
  return new Response(null, {
2058
2074
  ...responseInit,
2059
2075
  headers
@@ -2069,11 +2085,18 @@ function reload(init = {}) {
2069
2085
  headers
2070
2086
  });
2071
2087
  }
2088
+ const NULL_BODY_STATUSES = new Set([204, 205, 304]);
2072
2089
  function respond(value, init = {}) {
2073
2090
  const {
2074
2091
  responseInit,
2075
2092
  headers
2076
2093
  } = initWithRevalidate(init);
2094
+ if (NULL_BODY_STATUSES.has(responseInit.status)) {
2095
+ return new ResponseEnvelope(new Response(null, {
2096
+ ...responseInit,
2097
+ headers
2098
+ }), value);
2099
+ }
2077
2100
  headers.set("Content-Type", "application/json");
2078
2101
  return new ResponseEnvelope(new Response(JSON.stringify(value), {
2079
2102
  ...responseInit,
@@ -2224,4 +2247,4 @@ _moduleUrl) {
2224
2247
  function httpStatus(_code, _text) {}
2225
2248
  function httpHeader(_name, _value, _options) {}
2226
2249
 
2227
- export { ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, Namespaces, Portal, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SAFE_ERROR, SVGElements, VoidElements, acquireAsset, addEvent, applyRef, assign, claimElement, claimElementTree, className, clearFlashCookie, clientOnly, commitEventResponse, composeMiddleware, createRequestEvent, createResponseStub, createSSRResponse, delegateEvents, driveList, dynamic, dynamicProperty, effect, escape, generateHydrationScript, getDelegatedRoot, getExpectedRedirectStatus, getFirstChild, getHydrationKey, getNextElement, getNextMarker, getNextMatch, getNextSibling, getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, hasFlashCookie, httpHeader, httpStatus, hydrate, insert, installHydrationRuntime, installListDriver, isDev, isHref, isResponseEnvelope, isSafeError, isServer, isServerFunction, listDriver, markSafeError, memo, parseCookieHeader, patchDriver, redirect, ref, registerDelegatedContainer, registerDelegatedRoot, registerElementClaim, reload, render, renderToStream, renderToString, resolveSSRNode, respond, rowProof, runHydrationEvents, scope, serializeCookie, setAttribute, setAttributeNS, setProperty, setStyleProperty, spread, ssr, ssrAttribute, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrStyle, ssrStyleProperty, style, template, unregisterDelegatedContainer, unregisterDelegatedRoot, useHead, waitAsset, warmAsset };
2250
+ export { ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, NULL_BODY_STATUSES, Namespaces, Portal, RESPONSE_HEADER_VALUE_LIMIT, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SAFE_ERROR, SVGElements, VoidElements, acquireAsset, addEvent, applyRef, assign, claimElement, claimElementTree, className, clearFlashCookie, clientOnly, commitEventResponse, composeMiddleware, createRequestEvent, createResponseStub, createSSRResponse, delegateEvents, driveList, dynamic, dynamicProperty, effect, escape, generateHydrationScript, getDelegatedRoot, getExpectedRedirectStatus, getFirstChild, getHydrationKey, getNextElement, getNextMarker, getNextMatch, getNextSibling, getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, hasFlashCookie, httpHeader, httpStatus, hydrate, insert, installHydrationRuntime, installListDriver, isDev, isHref, isResponseEnvelope, isSafeError, isServer, isServerFunction, listDriver, markSafeError, memo, parseCookieHeader, patchDriver, redirect, ref, registerDelegatedContainer, registerDelegatedRoot, registerElementClaim, reload, render, renderToStream, renderToString, resolveSSRNode, respond, rowProof, runHydrationEvents, scope, serializeCookie, setAttribute, setAttributeNS, setProperty, setStyleProperty, spread, ssr, ssrAttribute, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrStyle, ssrStyleProperty, style, template, unregisterDelegatedContainer, unregisterDelegatedRoot, useHead, waitAsset, warmAsset };