@solidjs/web 2.0.0-beta.26 → 2.0.0-beta.27

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 (39) hide show
  1. package/dist/dev.cjs +3 -1
  2. package/dist/dev.js +3 -2
  3. package/dist/server.cjs +41 -78
  4. package/dist/server.js +42 -80
  5. package/dist/web.cjs +3 -1
  6. package/dist/web.js +3 -2
  7. package/frames/dist/client.cjs +69 -21
  8. package/frames/dist/client.dev.cjs +69 -21
  9. package/frames/dist/client.dev.js +70 -22
  10. package/frames/dist/client.js +70 -22
  11. package/frames/dist/server.cjs +33 -45
  12. package/frames/dist/server.js +33 -45
  13. package/package.json +4 -4
  14. package/serialization/dist/serialization.cjs +6 -3
  15. package/serialization/dist/serialization.js +6 -3
  16. package/serialization/types/index.d.ts +7 -1
  17. package/serialization/types-cjs/index.d.cts +7 -1
  18. package/server-functions/dist/client.cjs +15 -2
  19. package/server-functions/dist/client.js +13 -3
  20. package/server-functions/dist/server.cjs +161 -5
  21. package/server-functions/dist/server.js +155 -6
  22. package/types/client.d.ts +3 -3
  23. package/types/frames/serializer.d.ts +7 -1
  24. package/types/jsx.d.ts +1 -1
  25. package/types/response.d.ts +10 -0
  26. package/types/serializer.d.ts +7 -1
  27. package/types/server-functions/client.d.ts +3 -0
  28. package/types/server-functions/flash.d.ts +38 -0
  29. package/types/server-functions/server.d.ts +79 -4
  30. package/types/server-functions/shared.d.ts +35 -0
  31. package/types-cjs/client.d.cts +3 -3
  32. package/types-cjs/frames/serializer.d.cts +7 -1
  33. package/types-cjs/jsx.d.cts +1 -1
  34. package/types-cjs/response.d.cts +10 -0
  35. package/types-cjs/serializer.d.cts +7 -1
  36. package/types-cjs/server-functions/client.d.cts +3 -0
  37. package/types-cjs/server-functions/flash.d.cts +38 -0
  38. package/types-cjs/server-functions/server.d.cts +79 -4
  39. package/types-cjs/server-functions/shared.d.cts +35 -0
@@ -10,6 +10,7 @@ function isResponseEnvelope(value) {
10
10
  }
11
11
 
12
12
  seroval.Feature.AggregateError | seroval.Feature.BigIntTypedArray;
13
+ const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : seroval.Feature.ErrorPrototypeStack;
13
14
  const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
14
15
  web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
15
16
  web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin]);
@@ -35,11 +36,13 @@ function serializeJSON(value, {
35
36
  onError,
36
37
  ...codecOptions
37
38
  }) {
39
+ const resolved = resolveCodecOptions(codecOptions);
38
40
  return seroval.toCrossJSONStream(value, {
39
41
  onParse,
40
42
  onDone,
41
43
  onError,
42
- ...resolveCodecOptions(codecOptions)
44
+ ...resolved,
45
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
43
46
  });
44
47
  }
45
48
  function createJSONDeserializer(options) {
@@ -117,6 +120,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
117
120
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
118
121
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
119
122
  const FILE_FORM_KEY = "__server_function_file__";
123
+ const FLASH_COOKIE = "flash";
124
+ const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
125
+ function hasFlashCookie(cookieHeader) {
126
+ return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
127
+ }
128
+ function matchFlashCookie(cookieHeader) {
129
+ const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
130
+ return match ? match[1] : undefined;
131
+ }
132
+ function clearFlashCookie() {
133
+ return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
134
+ }
120
135
  const BodyFormat = {
121
136
  Serialized: "0",
122
137
  String: "1",
@@ -329,11 +344,61 @@ async function decodeResponse(response, codecOptions) {
329
344
  return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
330
345
  }
331
346
 
347
+ function encodeInputValue(value) {
348
+ if (value instanceof FormData) return {
349
+ $f: [...value.entries()].filter(([, v]) => typeof v === "string")
350
+ };
351
+ if (value instanceof URLSearchParams) return {
352
+ $u: [...value.entries()]
353
+ };
354
+ return value;
355
+ }
356
+ function decodeInputValue(value) {
357
+ if (value && typeof value === "object") {
358
+ if (Array.isArray(value.$f)) {
359
+ const form = new FormData();
360
+ for (const [k, v] of value.$f) form.append(k, v);
361
+ return form;
362
+ }
363
+ if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
364
+ }
365
+ return value;
366
+ }
367
+ function encodeFlashCookie(url, result, input, thrown) {
368
+ const isError = result instanceof Error;
369
+ const payload = {
370
+ url,
371
+ result: isError ? result.message : result,
372
+ error: isError,
373
+ thrown: !!thrown,
374
+ input: input.map(encodeInputValue)
375
+ };
376
+ return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
377
+ }
378
+ function decodeFlashCookie(cookieHeader) {
379
+ const match = matchFlashCookie(cookieHeader);
380
+ if (!match) return;
381
+ try {
382
+ const payload = JSON.parse(decodeURIComponent(match));
383
+ if (!payload || !payload.result) return;
384
+ const result = payload.error ? new Error(payload.result) : payload.result;
385
+ return {
386
+ input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
387
+ url: payload.url,
388
+ result: payload.thrown ? undefined : result,
389
+ error: payload.thrown ? result : undefined
390
+ };
391
+ } catch (error) {
392
+ console.error(error);
393
+ }
394
+ }
395
+
332
396
  const config = {
333
397
  provideEvent: undefined,
334
398
  collectFlightData: undefined,
335
399
  transformResult: undefined,
336
400
  transformDirectResult: undefined,
401
+ handleNoJS: undefined,
337
402
  endpoint: "/_server"
338
403
  };
339
404
  function configureServerFunctionsServer({
@@ -341,6 +406,7 @@ function configureServerFunctionsServer({
341
406
  collectFlightData,
342
407
  transformResult,
343
408
  transformDirectResult,
409
+ handleNoJS,
344
410
  endpoint,
345
411
  codec
346
412
  } = {}) {
@@ -348,6 +414,7 @@ function configureServerFunctionsServer({
348
414
  if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
349
415
  if (transformResult !== undefined) config.transformResult = transformResult;
350
416
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
417
+ if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
351
418
  if (endpoint !== undefined) config.endpoint = endpoint;
352
419
  if (codec !== undefined) configureServerFunctionsCodec(codec);
353
420
  }
@@ -473,6 +540,87 @@ async function foldFlightData(hook, event, headers, outcome) {
473
540
  data
474
541
  };
475
542
  }
543
+ function parseSetCookie(setCookie) {
544
+ const [pair, ...attributes] = setCookie.split(";");
545
+ const eq = pair.indexOf("=");
546
+ if (eq < 0) return undefined;
547
+ const parsed = {
548
+ name: pair.slice(0, eq).trim(),
549
+ value: pair.slice(eq + 1).trim()
550
+ };
551
+ for (const attribute of attributes) {
552
+ const attrEq = attribute.indexOf("=");
553
+ const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
554
+ const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
555
+ if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
556
+ }
557
+ return parsed;
558
+ }
559
+ function foldSetCookies(headers, setCookies) {
560
+ const folded = new Headers(headers);
561
+ if (!setCookies.length) return folded;
562
+ const cookies = {};
563
+ for (const pair of folded.get("cookie")?.split(";") ?? []) {
564
+ const eq = pair.indexOf("=");
565
+ if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
566
+ }
567
+ for (const setCookie of setCookies) {
568
+ const parsed = parseSetCookie(setCookie);
569
+ if (!parsed) continue;
570
+ if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
571
+ delete cookies[parsed.name];
572
+ } else {
573
+ cookies[parsed.name] = parsed.value;
574
+ }
575
+ }
576
+ folded.delete("cookie");
577
+ const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
578
+ if (serialized) folded.set("cookie", serialized);
579
+ return folded;
580
+ }
581
+ const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
582
+ function createNoJSHandler({
583
+ base = ""
584
+ } = {}) {
585
+ return function handleNoJS(result, request, args, thrown) {
586
+ const url = new URL(request.url);
587
+ let back = new URL(base || "/", url.origin).toString();
588
+ try {
589
+ const referer = request.headers.get("referer");
590
+ if (referer) back = new URL(referer).toString();
591
+ } catch {}
592
+ let status = 303;
593
+ let headers;
594
+ if (result instanceof Response) {
595
+ headers = new Headers(result.headers);
596
+ if (result.headers.has("Location")) {
597
+ headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
598
+ if (validRedirectStatuses.has(result.status)) status = result.status;
599
+ } else {
600
+ headers.set("Location", back);
601
+ }
602
+ headers.delete("Content-Type");
603
+ headers.delete("Content-Length");
604
+ } else {
605
+ headers = new Headers({
606
+ Location: back
607
+ });
608
+ }
609
+ if (result && !(result instanceof Response)) {
610
+ headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
611
+ }
612
+ return new Response(null, {
613
+ status,
614
+ headers
615
+ });
616
+ };
617
+ }
618
+ let defaultNoJSHandler;
619
+ function isFormPost(request) {
620
+ if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
621
+ const type = request.headers.get("content-type") || "";
622
+ return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
623
+ }
476
624
  function serializedResponse(value, headers, codec) {
477
625
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
478
626
  headers.set("Content-Type", "text/plain");
@@ -530,6 +678,7 @@ async function handleServerFunctionRequest(request, options = {}) {
530
678
  const provide = options.provideEvent || provideEvent;
531
679
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
532
680
  const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
681
+ const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
533
682
  const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
534
683
  const parsed = await parseArguments(request, url, instance, codec);
535
684
  const headers = new Headers();
@@ -553,7 +702,7 @@ async function handleServerFunctionRequest(request, options = {}) {
553
702
  response,
554
703
  value
555
704
  } = result;
556
- if (!instance && !options.handleNoJS && response && response.body) {
705
+ if (!instance && !handleNoJS && response && response.body) {
557
706
  return response;
558
707
  }
559
708
  if (response && response.headers) {
@@ -589,7 +738,7 @@ async function handleServerFunctionRequest(request, options = {}) {
589
738
  });
590
739
  }
591
740
  if (!instance) {
592
- if (options.handleNoJS) return options.handleNoJS(result, request, parsed);
741
+ if (handleNoJS) return handleNoJS(result, request, parsed);
593
742
  if (result instanceof Response) return result;
594
743
  return encodeResult(result, headers, 200, codec);
595
744
  }
@@ -641,13 +790,13 @@ async function handleServerFunctionRequest(request, options = {}) {
641
790
  }
642
791
  headers.set(ERROR_HEADER, "true");
643
792
  if (!instance) {
644
- if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
793
+ if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
645
794
  if (x instanceof Response) return x;
646
795
  }
647
796
  return encodeResult(x, headers, status, codec);
648
797
  }
649
798
  if (!instance) {
650
- if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
799
+ if (handleNoJS) return handleNoJS(x, request, parsed, true);
651
800
  const message = x instanceof Error ? x.message : String(x);
652
801
  return new Response(process.env.NODE_ENV === "development" ? message : null, {
653
802
  status: 500
@@ -660,19 +809,26 @@ async function handleServerFunctionRequest(request, options = {}) {
660
809
  }
661
810
 
662
811
  exports.ERROR_HEADER = ERROR_HEADER;
812
+ exports.FLASH_COOKIE = FLASH_COOKIE;
663
813
  exports.FUNCTION_HEADER = FUNCTION_HEADER;
664
814
  exports.GET = GET;
665
815
  exports.INSTANCE_HEADER = INSTANCE_HEADER;
666
816
  exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
817
+ exports.clearFlashCookie = clearFlashCookie;
667
818
  exports.configureServerFunctionsServer = configureServerFunctionsServer;
819
+ exports.createNoJSHandler = createNoJSHandler;
668
820
  exports.createServerReference = createServerReference;
669
821
  exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
822
+ exports.decodeFlashCookie = decodeFlashCookie;
670
823
  exports.decodeResponse = decodeResponse;
671
824
  exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
825
+ exports.encodeFlashCookie = encodeFlashCookie;
826
+ exports.foldSetCookies = foldSetCookies;
672
827
  exports.getServerFunction = getServerFunction;
673
828
  exports.getServerFunctionMeta = getServerFunctionMeta;
674
829
  exports.getServerFunctionMetadata = getServerFunctionMetadata;
675
830
  exports.handleServerFunctionRequest = handleServerFunctionRequest;
831
+ exports.hasFlashCookie = hasFlashCookie;
676
832
  exports.isServerFunction = isServerFunction;
677
833
  exports.registerServerFunction = registerServerFunction;
678
834
  exports.registerServerReference = registerServerReference;
@@ -8,6 +8,7 @@ function isResponseEnvelope(value) {
8
8
  }
9
9
 
10
10
  Feature.AggregateError | Feature.BigIntTypedArray;
11
+ const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
11
12
  const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
12
13
  CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
13
14
  FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
@@ -33,11 +34,13 @@ function serializeJSON(value, {
33
34
  onError,
34
35
  ...codecOptions
35
36
  }) {
37
+ const resolved = resolveCodecOptions(codecOptions);
36
38
  return toCrossJSONStream(value, {
37
39
  onParse,
38
40
  onDone,
39
41
  onError,
40
- ...resolveCodecOptions(codecOptions)
42
+ ...resolved,
43
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
41
44
  });
42
45
  }
43
46
  function createJSONDeserializer(options) {
@@ -115,6 +118,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
115
118
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
116
119
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
117
120
  const FILE_FORM_KEY = "__server_function_file__";
121
+ const FLASH_COOKIE = "flash";
122
+ const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
123
+ function hasFlashCookie(cookieHeader) {
124
+ return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
125
+ }
126
+ function matchFlashCookie(cookieHeader) {
127
+ const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
128
+ return match ? match[1] : undefined;
129
+ }
130
+ function clearFlashCookie() {
131
+ return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
132
+ }
118
133
  const BodyFormat = {
119
134
  Serialized: "0",
120
135
  String: "1",
@@ -327,11 +342,61 @@ async function decodeResponse(response, codecOptions) {
327
342
  return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
328
343
  }
329
344
 
345
+ function encodeInputValue(value) {
346
+ if (value instanceof FormData) return {
347
+ $f: [...value.entries()].filter(([, v]) => typeof v === "string")
348
+ };
349
+ if (value instanceof URLSearchParams) return {
350
+ $u: [...value.entries()]
351
+ };
352
+ return value;
353
+ }
354
+ function decodeInputValue(value) {
355
+ if (value && typeof value === "object") {
356
+ if (Array.isArray(value.$f)) {
357
+ const form = new FormData();
358
+ for (const [k, v] of value.$f) form.append(k, v);
359
+ return form;
360
+ }
361
+ if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
362
+ }
363
+ return value;
364
+ }
365
+ function encodeFlashCookie(url, result, input, thrown) {
366
+ const isError = result instanceof Error;
367
+ const payload = {
368
+ url,
369
+ result: isError ? result.message : result,
370
+ error: isError,
371
+ thrown: !!thrown,
372
+ input: input.map(encodeInputValue)
373
+ };
374
+ return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
375
+ }
376
+ function decodeFlashCookie(cookieHeader) {
377
+ const match = matchFlashCookie(cookieHeader);
378
+ if (!match) return;
379
+ try {
380
+ const payload = JSON.parse(decodeURIComponent(match));
381
+ if (!payload || !payload.result) return;
382
+ const result = payload.error ? new Error(payload.result) : payload.result;
383
+ return {
384
+ input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
385
+ url: payload.url,
386
+ result: payload.thrown ? undefined : result,
387
+ error: payload.thrown ? result : undefined
388
+ };
389
+ } catch (error) {
390
+ console.error(error);
391
+ }
392
+ }
393
+
330
394
  const config = {
331
395
  provideEvent: undefined,
332
396
  collectFlightData: undefined,
333
397
  transformResult: undefined,
334
398
  transformDirectResult: undefined,
399
+ handleNoJS: undefined,
335
400
  endpoint: "/_server"
336
401
  };
337
402
  function configureServerFunctionsServer({
@@ -339,6 +404,7 @@ function configureServerFunctionsServer({
339
404
  collectFlightData,
340
405
  transformResult,
341
406
  transformDirectResult,
407
+ handleNoJS,
342
408
  endpoint,
343
409
  codec
344
410
  } = {}) {
@@ -346,6 +412,7 @@ function configureServerFunctionsServer({
346
412
  if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
347
413
  if (transformResult !== undefined) config.transformResult = transformResult;
348
414
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
415
+ if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
349
416
  if (endpoint !== undefined) config.endpoint = endpoint;
350
417
  if (codec !== undefined) configureServerFunctionsCodec(codec);
351
418
  }
@@ -471,6 +538,87 @@ async function foldFlightData(hook, event, headers, outcome) {
471
538
  data
472
539
  };
473
540
  }
541
+ function parseSetCookie(setCookie) {
542
+ const [pair, ...attributes] = setCookie.split(";");
543
+ const eq = pair.indexOf("=");
544
+ if (eq < 0) return undefined;
545
+ const parsed = {
546
+ name: pair.slice(0, eq).trim(),
547
+ value: pair.slice(eq + 1).trim()
548
+ };
549
+ for (const attribute of attributes) {
550
+ const attrEq = attribute.indexOf("=");
551
+ const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
552
+ const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
553
+ if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
554
+ }
555
+ return parsed;
556
+ }
557
+ function foldSetCookies(headers, setCookies) {
558
+ const folded = new Headers(headers);
559
+ if (!setCookies.length) return folded;
560
+ const cookies = {};
561
+ for (const pair of folded.get("cookie")?.split(";") ?? []) {
562
+ const eq = pair.indexOf("=");
563
+ if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
564
+ }
565
+ for (const setCookie of setCookies) {
566
+ const parsed = parseSetCookie(setCookie);
567
+ if (!parsed) continue;
568
+ if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
569
+ delete cookies[parsed.name];
570
+ } else {
571
+ cookies[parsed.name] = parsed.value;
572
+ }
573
+ }
574
+ folded.delete("cookie");
575
+ const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
576
+ if (serialized) folded.set("cookie", serialized);
577
+ return folded;
578
+ }
579
+ const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
580
+ function createNoJSHandler({
581
+ base = ""
582
+ } = {}) {
583
+ return function handleNoJS(result, request, args, thrown) {
584
+ const url = new URL(request.url);
585
+ let back = new URL(base || "/", url.origin).toString();
586
+ try {
587
+ const referer = request.headers.get("referer");
588
+ if (referer) back = new URL(referer).toString();
589
+ } catch {}
590
+ let status = 303;
591
+ let headers;
592
+ if (result instanceof Response) {
593
+ headers = new Headers(result.headers);
594
+ if (result.headers.has("Location")) {
595
+ headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
596
+ if (validRedirectStatuses.has(result.status)) status = result.status;
597
+ } else {
598
+ headers.set("Location", back);
599
+ }
600
+ headers.delete("Content-Type");
601
+ headers.delete("Content-Length");
602
+ } else {
603
+ headers = new Headers({
604
+ Location: back
605
+ });
606
+ }
607
+ if (result && !(result instanceof Response)) {
608
+ headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
609
+ }
610
+ return new Response(null, {
611
+ status,
612
+ headers
613
+ });
614
+ };
615
+ }
616
+ let defaultNoJSHandler;
617
+ function isFormPost(request) {
618
+ if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
619
+ const type = request.headers.get("content-type") || "";
620
+ return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
621
+ }
474
622
  function serializedResponse(value, headers, codec) {
475
623
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
476
624
  headers.set("Content-Type", "text/plain");
@@ -528,6 +676,7 @@ async function handleServerFunctionRequest(request, options = {}) {
528
676
  const provide = options.provideEvent || provideEvent;
529
677
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
530
678
  const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
679
+ const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
531
680
  const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
532
681
  const parsed = await parseArguments(request, url, instance, codec);
533
682
  const headers = new Headers();
@@ -551,7 +700,7 @@ async function handleServerFunctionRequest(request, options = {}) {
551
700
  response,
552
701
  value
553
702
  } = result;
554
- if (!instance && !options.handleNoJS && response && response.body) {
703
+ if (!instance && !handleNoJS && response && response.body) {
555
704
  return response;
556
705
  }
557
706
  if (response && response.headers) {
@@ -587,7 +736,7 @@ async function handleServerFunctionRequest(request, options = {}) {
587
736
  });
588
737
  }
589
738
  if (!instance) {
590
- if (options.handleNoJS) return options.handleNoJS(result, request, parsed);
739
+ if (handleNoJS) return handleNoJS(result, request, parsed);
591
740
  if (result instanceof Response) return result;
592
741
  return encodeResult(result, headers, 200, codec);
593
742
  }
@@ -639,13 +788,13 @@ async function handleServerFunctionRequest(request, options = {}) {
639
788
  }
640
789
  headers.set(ERROR_HEADER, "true");
641
790
  if (!instance) {
642
- if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
791
+ if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
643
792
  if (x instanceof Response) return x;
644
793
  }
645
794
  return encodeResult(x, headers, status, codec);
646
795
  }
647
796
  if (!instance) {
648
- if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
797
+ if (handleNoJS) return handleNoJS(x, request, parsed, true);
649
798
  const message = x instanceof Error ? x.message : String(x);
650
799
  return new Response(process.env.NODE_ENV === "development" ? message : null, {
651
800
  status: 500
@@ -657,4 +806,4 @@ async function handleServerFunctionRequest(request, options = {}) {
657
806
  }
658
807
  }
659
808
 
660
- export { ERROR_HEADER, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, configureServerFunctionsServer, createServerReference, decodeErrorHeaderValue, decodeResponse, encodeErrorHeaderValue, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
809
+ export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
package/types/client.d.ts CHANGED
@@ -109,9 +109,9 @@ export function style(
109
109
  export function getOwner(): unknown;
110
110
  export function mergeProps(...sources: unknown[]): unknown;
111
111
  export function dynamicProperty(props: unknown, key: string): unknown;
112
- export function applyRef(
113
- r: ((element: Element) => void) | ((element: Element) => void)[],
114
- element: Element
112
+ export function applyRef<T extends Element = Element>(
113
+ r: ((element: NoInfer<T>) => void) | ((element: NoInfer<T>) => void)[],
114
+ element: T
115
115
  ): void;
116
116
  export function ref(
117
117
  fn: () => ((element: Element) => void) | ((element: Element) => void)[],
@@ -37,7 +37,9 @@ export interface WebSerializerOptions {
37
37
  scopeId?: string;
38
38
  /**
39
39
  * Seroval feature bitflags to exclude from output. Defaults to disabling
40
- * post-ES2017 features (AggregateError, BigInt typed arrays).
40
+ * post-ES2017 features (AggregateError, BigInt typed arrays). Outside
41
+ * development, `Error.prototype.stack` is additionally stripped on top of
42
+ * any override — serialized stacks leak server paths to the client.
41
43
  */
42
44
  disabledFeatures?: number;
43
45
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
@@ -101,6 +103,10 @@ export interface JSONCodecOptions {
101
103
  /**
102
104
  * Seroval feature bitflags to exclude. Defaults to disabling `RegExp`
103
105
  * (payloads may come from an untrusted peer). Must match on both peers.
106
+ * Outside development, the encoding side additionally strips
107
+ * `Error.prototype.stack` on top of any override — serialized stacks leak
108
+ * server paths to the client. Decoding stays permissive, so payloads from
109
+ * a development peer still round-trip.
104
110
  */
105
111
  disabledFeatures?: number;
106
112
  /** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
package/types/jsx.d.ts CHANGED
@@ -240,7 +240,7 @@ export namespace JSX {
240
240
  }
241
241
 
242
242
  type RefCallback<T> = (el: T) => void;
243
- type Ref<T> = T | RefCallback<T> | (RefCallback<T> | Ref<T>)[];
243
+ type Ref<T> = T | RefCallback<T> | Ref<T>[];
244
244
 
245
245
  interface IntrinsicAttributes {
246
246
  ref?: Ref<unknown> | undefined;
@@ -49,6 +49,16 @@ export interface Href {
49
49
  */
50
50
  export function isHref(value: unknown): value is Href;
51
51
 
52
+ /**
53
+ * Response header naming the cache keys a mutation invalidated
54
+ * (`"X-Revalidate"`), comma separated. The response helpers below set it
55
+ * from their `revalidate` option; the client transport treats its presence
56
+ * as control flow, and integrations read it to invalidate their own cache.
57
+ * Core never inspects the keys, so how they are matched (prefixes, exact
58
+ * names, namespaces) is the integration's business.
59
+ */
60
+ export const REVALIDATE_HEADER: string;
61
+
52
62
  /** `ResponseInit` accepted by the response helpers, plus `revalidate`. */
53
63
  export interface ResponseHelperInit extends ResponseInit {
54
64
  /**
@@ -37,7 +37,9 @@ export interface WebSerializerOptions {
37
37
  scopeId?: string;
38
38
  /**
39
39
  * Seroval feature bitflags to exclude from output. Defaults to disabling
40
- * post-ES2017 features (AggregateError, BigInt typed arrays).
40
+ * post-ES2017 features (AggregateError, BigInt typed arrays). Outside
41
+ * development, `Error.prototype.stack` is additionally stripped on top of
42
+ * any override — serialized stacks leak server paths to the client.
41
43
  */
42
44
  disabledFeatures?: number;
43
45
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
@@ -101,6 +103,10 @@ export interface JSONCodecOptions {
101
103
  /**
102
104
  * Seroval feature bitflags to exclude. Defaults to disabling `RegExp`
103
105
  * (payloads may come from an untrusted peer). Must match on both peers.
106
+ * Outside development, the encoding side additionally strips
107
+ * `Error.prototype.stack` on top of any override — serialized stacks leak
108
+ * server paths to the client. Decoding stays permissive, so payloads from
109
+ * a development peer still round-trip.
104
110
  */
105
111
  disabledFeatures?: number;
106
112
  /** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
@@ -3,13 +3,16 @@ import { ServerFunction, ServerFunctionMetadata } from "./shared.js";
3
3
 
4
4
  export {
5
5
  ERROR_HEADER,
6
+ FLASH_COOKIE,
6
7
  FUNCTION_HEADER,
7
8
  INSTANCE_HEADER,
8
9
  SINGLE_FLIGHT_HEADER,
10
+ clearFlashCookie,
9
11
  decodeErrorHeaderValue,
10
12
  decodeResponse,
11
13
  encodeErrorHeaderValue,
12
14
  getServerFunctionMetadata,
15
+ hasFlashCookie,
13
16
  isServerFunction,
14
17
  subscribeFlightData,
15
18
  withMeta
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The outcome of a call made without the client runtime, as it rides the
3
+ * flash cookie: what was submitted, where, and what came back. `result` and
4
+ * `error` are mutually exclusive — a thrown outcome fills `error`, a
5
+ * returned one fills `result` — mirroring the split a scripted call sees.
6
+ */
7
+ export interface FlashSubmission {
8
+ /** The arguments the call was made with (files are dropped). */
9
+ input: any[];
10
+ /** The call's url: pathname + search of the server function request. */
11
+ url: string;
12
+ /** The returned value, when the call returned. */
13
+ result?: any;
14
+ /** The thrown value, when the call threw. */
15
+ error?: any;
16
+ }
17
+
18
+ /**
19
+ * Encodes the outcome of a no-JS call as a `Set-Cookie` value, for the
20
+ * handler to send with its redirect. `url` identifies which submission the
21
+ * outcome belongs to; pass `thrown` when the call threw rather than
22
+ * returned.
23
+ *
24
+ * The payload is JSON inside the cookie: `FormData` and `URLSearchParams`
25
+ * arguments are captured as entry pairs and revived on decode, and `File`
26
+ * entries are dropped (they cannot ride a cookie). Keep in mind the 4 KB
27
+ * cookie budget — outcomes larger than that will not survive the round
28
+ * trip.
29
+ */
30
+ export function encodeFlashCookie(url: string, result: any, input: any[], thrown?: boolean): string;
31
+
32
+ /**
33
+ * Decodes the flash cookie out of a request's `Cookie` header, for the
34
+ * render that follows the redirect. Returns undefined when the cookie is
35
+ * absent or unreadable — a malformed cookie never takes down the render,
36
+ * and `clearFlashCookie` should be appended regardless.
37
+ */
38
+ export function decodeFlashCookie(cookieHeader: string | null): FlashSubmission | undefined;