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

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 (61) hide show
  1. package/dist/dev.cjs +163 -36
  2. package/dist/dev.js +161 -37
  3. package/dist/server.cjs +178 -37
  4. package/dist/server.js +177 -38
  5. package/dist/web.cjs +143 -36
  6. package/dist/web.js +141 -37
  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 +497 -44
  12. package/frames/dist/server.js +497 -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 +164 -35
  25. package/server-functions/dist/client.js +161 -36
  26. package/server-functions/dist/server.cjs +899 -136
  27. package/server-functions/dist/server.dev.cjs +919 -136
  28. package/server-functions/dist/server.dev.js +915 -137
  29. package/server-functions/dist/server.js +895 -137
  30. package/types/client.d.ts +2 -1
  31. package/types/constants.d.ts +3 -1
  32. package/types/cookies.d.ts +6 -14
  33. package/types/frames/frame-client.d.ts +4 -0
  34. package/types/frames/serializer-decode.d.ts +14 -1
  35. package/types/frames/serializer.d.ts +7 -0
  36. package/types/jsx.d.ts +11 -16
  37. package/types/response.d.ts +11 -0
  38. package/types/serializer-decode.d.ts +14 -1
  39. package/types/serializer.d.ts +7 -0
  40. package/types/server-functions/client.d.ts +22 -2
  41. package/types/server-functions/flash.d.ts +9 -0
  42. package/types/server-functions/server.d.ts +131 -11
  43. package/types/server-functions/shared.d.ts +77 -14
  44. package/types/server-mock.d.ts +17 -2
  45. package/types/server.d.ts +16 -2
  46. package/types-cjs/client.d.cts +2 -1
  47. package/types-cjs/constants.d.cts +3 -1
  48. package/types-cjs/cookies.d.cts +6 -14
  49. package/types-cjs/frames/frame-client.d.cts +4 -0
  50. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  51. package/types-cjs/frames/serializer.d.cts +7 -0
  52. package/types-cjs/jsx.d.cts +11 -16
  53. package/types-cjs/response.d.cts +11 -0
  54. package/types-cjs/serializer-decode.d.cts +14 -1
  55. package/types-cjs/serializer.d.cts +7 -0
  56. package/types-cjs/server-functions/client.d.cts +22 -2
  57. package/types-cjs/server-functions/flash.d.cts +9 -0
  58. package/types-cjs/server-functions/server.d.cts +131 -11
  59. package/types-cjs/server-functions/shared.d.cts +77 -14
  60. package/types-cjs/server-mock.d.cts +17 -2
  61. package/types-cjs/server.d.cts +16 -2
@@ -1,5 +1,15 @@
1
1
  import { sharedConfig } from 'solid-js';
2
2
 
3
+ const COMPOSED_BODY_FRAMING = /*#__PURE__*/new Set(["content-length", "content-encoding", "transfer-encoding"]);
4
+ function isHttpNavigationTarget(target) {
5
+ try {
6
+ const protocol = new URL(target, "http://base.invalid").protocol;
7
+ return protocol === "http:" || protocol === "https:";
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
3
13
  const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
4
14
  function isResponseEnvelope(value) {
5
15
  return !!(value && typeof value === "object" && value[ENVELOPE]);
@@ -9,6 +19,8 @@ function isSafeError(value) {
9
19
  return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
10
20
  }
11
21
  const REVALIDATE_HEADER = "X-Revalidate";
22
+ const RESPONSE_HEADER_VALUE_LIMIT = 4096;
23
+ const NULL_BODY_STATUSES = new Set([204, 205, 304]);
12
24
 
13
25
  const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
14
26
  function getServerFunctionMetadata(fn) {
@@ -81,6 +93,7 @@ function decodeSafe(text) {
81
93
  }
82
94
  }
83
95
  function serializeCookie(name, value, options = {}) {
96
+ assertServableCookie(name, options);
84
97
  let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
85
98
  cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
86
99
  if (options.domain) cookie += `; Domain=${options.domain}`;
@@ -88,12 +101,32 @@ function serializeCookie(name, value, options = {}) {
88
101
  if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
89
102
  if (options.httpOnly) cookie += "; HttpOnly";
90
103
  if (options.secure) cookie += "; Secure";
104
+ if (options.partitioned) cookie += "; Partitioned";
91
105
  if (options.sameSite) {
92
106
  const sameSite = options.sameSite.toLowerCase();
93
107
  cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
94
108
  }
95
109
  return cookie;
96
110
  }
111
+ function assertServableCookie(name, options) {
112
+ const reject = reason => {
113
+ throw new Error(`serializeCookie: every browser silently rejects this cookie — ${reason}. ` + `It would never come back on a request, with no error anywhere.`);
114
+ };
115
+ const lower = name.toLowerCase();
116
+ if (lower.startsWith("__host-")) {
117
+ if (!options.secure) reject(`the __Host- prefix on \`${name}\` requires \`secure: true\``);
118
+ if (options.path !== undefined && options.path !== "/") reject(`the __Host- prefix on \`${name}\` requires \`Path=/\` (got \`${options.path}\`) — ` + `host-locking is the prefix's whole contract, so it cannot be path-scoped`);
119
+ if (options.domain) reject(`the __Host- prefix on \`${name}\` forbids \`Domain\` (got \`${options.domain}\`)`);
120
+ } else if (lower.startsWith("__secure-") && !options.secure) {
121
+ reject(`the __Secure- prefix on \`${name}\` requires \`secure: true\``);
122
+ }
123
+ if (options.sameSite && options.sameSite.toLowerCase() === "none" && !options.secure) {
124
+ reject("`SameSite=None` requires `secure: true`");
125
+ }
126
+ if (options.partitioned && !options.secure) {
127
+ reject("`Partitioned` requires `secure: true`");
128
+ }
129
+ }
97
130
  const FLASH_COOKIE = "flash";
98
131
  const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
99
132
  function hasFlashCookie(cookieHeader) {
@@ -112,8 +145,23 @@ function configureServerFunctionsCodec(codec) {
112
145
  function getServerFunctionsCodec() {
113
146
  return codecConfig.codec;
114
147
  }
115
- function subscribeFlightData(consumer) {
148
+ const UNNAMED_FLIGHT_SOURCE = "true";
149
+ const flightConfig = {
150
+ consumers: new Map()
151
+ };
152
+ function assertFlightSource(source) {
153
+ if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
154
+ throw new TypeError(`Invalid flight data source id "${source}": ids ride the ` + `${SINGLE_FLIGHT_HEADER} header as a comma-separated list, and "true" is ` + `reserved for the unnamed registration (the bare consumer/hook signatures).`);
155
+ }
156
+ }
157
+ function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
158
+ const named = typeof sourceOrConsumer === "string";
159
+ if (named) assertFlightSource(sourceOrConsumer);
160
+ const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
161
+ const consumer = named ? maybeConsumer : sourceOrConsumer;
162
+ flightConfig.consumers.set(source, consumer);
116
163
  return () => {
164
+ if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
117
165
  };
118
166
  }
119
167
  function serverFunctionAddress(endpoint, id) {
@@ -125,10 +173,18 @@ function parseServerFunctionAddress(pathname, endpoint) {
125
173
  if (!pathname.startsWith(mount)) return null;
126
174
  const rest = pathname.slice(mount.length);
127
175
  if (!rest.startsWith("/")) return null;
128
- const segment = rest.slice(1);
176
+ let segment = rest.slice(1);
177
+ let data = false;
178
+ if (segment.startsWith("data/")) {
179
+ segment = segment.slice(5);
180
+ data = true;
181
+ }
129
182
  if (!segment || segment.includes("/")) return null;
130
183
  try {
131
- return decodeURIComponent(segment);
184
+ return {
185
+ id: decodeURIComponent(segment),
186
+ data
187
+ };
132
188
  } catch {
133
189
  return null;
134
190
  }
@@ -160,6 +216,28 @@ function decodeErrorHeaderValue(value) {
160
216
  }
161
217
  const INSTANCE_HEADER = "X-Server-Function-Instance";
162
218
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
219
+ const UNKNOWN_HEADER = "X-Server-Function-Unknown";
220
+ const REDIRECT_HEADER = "X-Server-Function-Redirect";
221
+ function decodeRedirectHeaderValue(value) {
222
+ if (typeof value !== "string") return undefined;
223
+ const at = value.indexOf(" ");
224
+ if (at < 0) return undefined;
225
+ const status = Number(value.slice(0, at));
226
+ const url = value.slice(at + 1);
227
+ if (!Number.isInteger(status) || !url) return undefined;
228
+ if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
229
+ let parsed;
230
+ try {
231
+ parsed = new URL(url);
232
+ } catch {
233
+ return undefined;
234
+ }
235
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
236
+ return {
237
+ status,
238
+ url
239
+ };
240
+ }
163
241
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
164
242
  const FILE_FORM_KEY = "__server_function_file__";
165
243
  const BodyFormat = {
@@ -202,7 +280,11 @@ function isJSONSafe(value) {
202
280
  const proto = Object.getPrototypeOf(v);
203
281
  if (proto !== Object.prototype && proto !== null) return false;
204
282
  if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
205
- for (const k in v) stack.push(v[k]);
283
+ for (const k in v) {
284
+ const descriptor = Object.getOwnPropertyDescriptor(v, k);
285
+ if (descriptor === undefined || !("value" in descriptor)) return false;
286
+ stack.push(descriptor.value);
287
+ }
206
288
  }
207
289
  }
208
290
  return true;
@@ -311,18 +393,34 @@ function createChunk(data) {
311
393
  class ChunkReader {
312
394
  constructor(stream) {
313
395
  this.reader = stream.getReader();
314
- this.buffer = new Uint8Array(0);
396
+ this.store = new Uint8Array(0);
397
+ this.buffer = this.store;
315
398
  this.done = false;
316
399
  }
317
400
  async readChunk() {
318
401
  const chunk = await this.reader.read();
319
- if (!chunk.done) {
320
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
321
- newBuffer.set(this.buffer);
322
- newBuffer.set(chunk.value, this.buffer.length);
323
- this.buffer = newBuffer;
324
- } else {
402
+ if (chunk.done) {
325
403
  this.done = true;
404
+ return;
405
+ }
406
+ const incoming = chunk.value;
407
+ const store = this.store;
408
+ const start = this.buffer.byteOffset;
409
+ const end = start + this.buffer.length;
410
+ const needed = this.buffer.length + incoming.length;
411
+ if (end + incoming.length <= store.length) {
412
+ store.set(incoming, end);
413
+ this.buffer = store.subarray(start, end + incoming.length);
414
+ } else if (needed <= store.length) {
415
+ store.copyWithin(0, start, end);
416
+ store.set(incoming, this.buffer.length);
417
+ this.buffer = store.subarray(0, needed);
418
+ } else {
419
+ const grown = new Uint8Array(Math.max(needed, store.length * 2));
420
+ grown.set(this.buffer);
421
+ grown.set(incoming, this.buffer.length);
422
+ this.store = grown;
423
+ this.buffer = grown.subarray(0, needed);
326
424
  }
327
425
  }
328
426
  async next() {
@@ -364,6 +462,27 @@ class ChunkReader {
364
462
  }
365
463
  }
366
464
  }
465
+ const ERROR_TRAILER_PREFIX = "!";
466
+ function encodeErrorTrailer(error) {
467
+ const shaped = error instanceof Error ? error : new Error(String(error));
468
+ return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
469
+ name: shaped.name,
470
+ message: shaped.message
471
+ } : {
472
+ message: shaped.message
473
+ });
474
+ }
475
+ function errorFromTrailer(payload) {
476
+ let shape;
477
+ try {
478
+ shape = JSON.parse(payload.slice(1));
479
+ } catch {
480
+ shape = null;
481
+ }
482
+ const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
483
+ if (shape && typeof shape.name === "string") error.name = shape.name;
484
+ return error;
485
+ }
367
486
  async function deserializeStream(source, codecOptions) {
368
487
  if (!source.body) {
369
488
  throw new Error("missing body");
@@ -371,11 +490,17 @@ async function deserializeStream(source, codecOptions) {
371
490
  const reader = new ChunkReader(source.body);
372
491
  const result = await reader.next();
373
492
  if (!result.done) {
493
+ if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
494
+ throw errorFromTrailer(result.value);
495
+ }
374
496
  const {
375
497
  createJSONDeserializer
376
498
  } = await import('@solidjs/web/serialization/decode');
377
499
  const deserializeChunk = createJSONDeserializer(codecOptions);
378
500
  function interpretChunk(chunk) {
501
+ if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
502
+ throw errorFromTrailer(chunk);
503
+ }
379
504
  return deserializeChunk(JSON.parse(chunk));
380
505
  }
381
506
  reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
@@ -439,7 +564,8 @@ function copyInitHeaders(init) {
439
564
  for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
440
565
  return headers;
441
566
  }
442
- const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
567
+ const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location",
568
+ ...COMPOSED_BODY_FRAMING].map(header => header.toLowerCase()));
443
569
  function fillsStubGap(key, headers, response) {
444
570
  if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
445
571
  if (response.body === null && (key === "content-type" || key === "content-length")) return false;
@@ -455,24 +581,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
455
581
  if (fillsStubGap(key, response.headers, response)) hasGaps = true;
456
582
  });
457
583
  if (!cookies.length && !hasGaps) return response;
458
- try {
459
- for (const cookie of cookies) response.headers.append("Set-Cookie", cookie);
460
- stub.headers.forEach((value, key) => {
461
- if (fillsStubGap(key, response.headers, response)) response.headers.set(key, value);
462
- });
463
- return response;
464
- } catch {
465
- const headers = copyInitHeaders(response.headers);
466
- for (const cookie of cookies) headers.append("Set-Cookie", cookie);
467
- stub.headers.forEach((value, key) => {
468
- if (fillsStubGap(key, headers, response)) headers.set(key, value);
469
- });
470
- return new Response(response.body, {
471
- status: response.status,
472
- statusText: response.statusText,
473
- headers
474
- });
475
- }
584
+ const headers = copyInitHeaders(response.headers);
585
+ for (const cookie of cookies) headers.append("Set-Cookie", cookie);
586
+ stub.headers.forEach((value, key) => {
587
+ if (fillsStubGap(key, headers, response)) headers.set(key, value);
588
+ });
589
+ return new Response(response.body, {
590
+ status: response.status,
591
+ statusText: response.statusText,
592
+ headers
593
+ });
476
594
  }
477
595
 
478
596
  function encodeInputValue(value) {
@@ -504,11 +622,35 @@ function encodeFlashCookie(url, result, input, thrown) {
504
622
  thrown: !!thrown,
505
623
  input: input.map(encodeInputValue)
506
624
  };
625
+ if (fitsCookie(payload)) return flashCookie(payload);
626
+ payload.truncated = true;
627
+ payload.input = [];
628
+ if (!fitsCookie(payload)) {
629
+ if (typeof payload.result === "string") {
630
+ let prefix = payload.result;
631
+ while (prefix.length > 0 && !fitsCookie({
632
+ ...payload,
633
+ result: prefix
634
+ })) {
635
+ prefix = prefix.slice(0, prefix.length >> 1);
636
+ }
637
+ payload.result = prefix.length > 0 ? prefix : true;
638
+ } else {
639
+ payload.result = true;
640
+ }
641
+ }
642
+ return flashCookie(payload);
643
+ }
644
+ function flashCookie(payload) {
507
645
  return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
508
646
  secure: true,
509
647
  httpOnly: true
510
648
  });
511
649
  }
650
+ const COOKIE_PAIR_BUDGET = 4000;
651
+ function fitsCookie(payload) {
652
+ return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
653
+ }
512
654
  function decodeFlashCookie(cookieHeader) {
513
655
  const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
514
656
  if (!match) return;
@@ -516,12 +658,14 @@ function decodeFlashCookie(cookieHeader) {
516
658
  const payload = JSON.parse(match);
517
659
  if (!payload || !payload.result) return;
518
660
  const result = payload.error ? new Error(payload.result) : payload.result;
519
- return {
661
+ const submission = {
520
662
  input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
521
663
  url: payload.url,
522
664
  result: payload.thrown ? undefined : result,
523
665
  error: payload.thrown ? result : undefined
524
666
  };
667
+ if (payload.truncated) submission.truncated = true;
668
+ return submission;
525
669
  } catch (error) {
526
670
  console.error(error);
527
671
  }
@@ -536,7 +680,9 @@ const config = {
536
680
  transformDirectResult: undefined,
537
681
  handleNoJS: undefined,
538
682
  endpoint: "/_server",
539
- csrf: true
683
+ csrf: true,
684
+ bodySizeLimit: 1_048_576,
685
+ maxArguments: 1000
540
686
  };
541
687
  function configureServerFunctionsServer({
542
688
  provideEvent,
@@ -548,7 +694,9 @@ function configureServerFunctionsServer({
548
694
  handleNoJS,
549
695
  endpoint,
550
696
  csrf,
551
- codec
697
+ codec,
698
+ bodySizeLimit,
699
+ maxArguments
552
700
  } = {}) {
553
701
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
554
702
  if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
@@ -560,6 +708,16 @@ function configureServerFunctionsServer({
560
708
  if (endpoint !== undefined) config.endpoint = endpoint;
561
709
  if (csrf !== undefined) config.csrf = csrf;
562
710
  if (codec !== undefined) configureServerFunctionsCodec(codec);
711
+ if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
712
+ if (maxArguments !== undefined) config.maxArguments = maxArguments;
713
+ }
714
+ const flightSources = new Map();
715
+ function registerFlightDataSource(source, hook) {
716
+ assertFlightSource(source);
717
+ flightSources.set(source, hook);
718
+ return () => {
719
+ if (flightSources.get(source) === hook) flightSources.delete(source);
720
+ };
563
721
  }
564
722
  function provideEvent(event, fn) {
565
723
  if (config.provideEvent) return config.provideEvent(event, fn);
@@ -567,6 +725,52 @@ function provideEvent(event, fn) {
567
725
  if (ctx) return ctx.run(event, fn);
568
726
  throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
569
727
  }
728
+ function scopeDeferredResult(value, scope) {
729
+ if (value === null || typeof value !== "object" && typeof value !== "function") return value;
730
+ const promised = scope(() => nativePromise(value));
731
+ if (promised) {
732
+ return promised.then(result => scope(() => scopeDeferredResult(result, scope)));
733
+ }
734
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
735
+ let reader;
736
+ return new ReadableStream({
737
+ async pull(controller) {
738
+ try {
739
+ const step = await scope(() => {
740
+ if (!reader) reader = value.getReader();
741
+ return reader.read();
742
+ });
743
+ if (step.done) controller.close();else controller.enqueue(step.value);
744
+ } catch (error) {
745
+ controller.error(error);
746
+ }
747
+ },
748
+ cancel(reason) {
749
+ return scope(() => reader ? reader.cancel(reason) : value.cancel(reason));
750
+ }
751
+ }, {
752
+ highWaterMark: 0
753
+ });
754
+ }
755
+ const scopedIterator = symbol => ({
756
+ [symbol]() {
757
+ const iterator = scope(() => value[symbol]());
758
+ return new Proxy(iterator, {
759
+ get(target, property) {
760
+ const member = Reflect.get(target, property, target);
761
+ return typeof member === "function" && (property === "next" || property === "return" || property === "throw") ? (...args) => scope(() => member.apply(target, args)) : member;
762
+ }
763
+ });
764
+ }
765
+ });
766
+ if (typeof value[Symbol.asyncIterator] === "function") {
767
+ return scopedIterator(Symbol.asyncIterator);
768
+ }
769
+ if (typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
770
+ return scopedIterator(Symbol.iterator);
771
+ }
772
+ return value;
773
+ }
570
774
  const REGISTRATIONS = new Map();
571
775
  const METHODS = new Map();
572
776
  const INVOCATIONS = new WeakMap();
@@ -581,6 +785,7 @@ function provideRPC() {
581
785
  }
582
786
  function registerServerFunction(id, callback) {
583
787
  provideRPC();
788
+ if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
584
789
  REGISTRATIONS.set(id, callback);
585
790
  return callback;
586
791
  }
@@ -654,13 +859,17 @@ function createServerReference({
654
859
  const ogEvt = getRequestEvent();
655
860
  if (!ogEvt) throw new Error("Cannot call server function outside of a request");
656
861
  const evt = {
657
- ...ogEvt
862
+ ...ogEvt,
863
+ locals: {
864
+ ...ogEvt.locals
865
+ }
658
866
  };
659
867
  INVOCATIONS.set(evt, {
660
868
  id
661
869
  });
662
870
  evt.serverOnly = true;
663
- const result = provideEvent(evt, () => {
871
+ const scope = run => provideEvent(evt, run);
872
+ let result = provideEvent(evt, () => {
664
873
  const run = () => fn.apply(thisArg, args);
665
874
  return config.wrapInvocation ? config.wrapInvocation(run, {
666
875
  id,
@@ -669,19 +878,20 @@ function createServerReference({
669
878
  direct: true
670
879
  }) : run();
671
880
  });
881
+ result = scopeDeferredResult(result, scope);
672
882
  const transform = config.transformDirectResult;
673
883
  if (transform && result && typeof result.then === "function") {
674
- return result.then(value => transform(value, {
884
+ return result.then(value => scopeDeferredResult(transform(value, {
675
885
  id,
676
886
  args,
677
887
  event: evt
678
- }));
888
+ }), scope));
679
889
  }
680
- return transform ? transform(result, {
890
+ return transform ? scopeDeferredResult(transform(result, {
681
891
  id,
682
892
  args,
683
893
  event: evt
684
- }) : result;
894
+ }), scope) : result;
685
895
  }
686
896
  });
687
897
  return proxy;
@@ -725,18 +935,107 @@ function getServerFunctionInvocation() {
725
935
  function getEventServerFunctionInvocation(event) {
726
936
  return event && INVOCATIONS.get(event);
727
937
  }
728
- function resolveFunctionId(url) {
938
+ function resolveAddress(url) {
729
939
  return parseServerFunctionAddress(url.pathname, config.endpoint);
730
940
  }
731
- async function parseArguments(request, url, instance, codec) {
941
+ const DECODE_DEPTH_LIMIT = 64;
942
+ function assertDecodeDepth(value) {
943
+ let level = [value];
944
+ for (let depth = 0; level.length > 0; depth++) {
945
+ if (depth > DECODE_DEPTH_LIMIT) {
946
+ throw new TypeError("Server function arguments exceed the decode depth limit");
947
+ }
948
+ const next = [];
949
+ for (const node of level) {
950
+ if (node === null || typeof node !== "object") continue;
951
+ if (Array.isArray(node)) {
952
+ for (const child of node) next.push(child);
953
+ } else {
954
+ for (const key of Object.keys(node)) next.push(node[key]);
955
+ }
956
+ }
957
+ level = next;
958
+ }
959
+ }
960
+ const UNSAFE_ARGUMENT_KEYS = ["__proto__", "constructor", "prototype"];
961
+ function stripUnsafeArgumentKeys(value) {
962
+ const stack = [value];
963
+ const seen = new Set();
964
+ while (stack.length) {
965
+ const v = stack.pop();
966
+ if (v === null || typeof v !== "object" || seen.has(v)) continue;
967
+ seen.add(v);
968
+ for (const key of UNSAFE_ARGUMENT_KEYS) {
969
+ delete v[key];
970
+ }
971
+ for (const key of Object.keys(v)) stack.push(v[key]);
972
+ if (v instanceof Map) {
973
+ for (const [k, entry] of v) stack.push(k, entry);
974
+ } else if (v instanceof Set) {
975
+ for (const member of v) stack.push(member);
976
+ }
977
+ }
978
+ return value;
979
+ }
980
+ async function bufferBodyWithin(request, limit) {
981
+ const reader = request.body.getReader();
982
+ const signal = request.signal;
983
+ const chunks = [];
984
+ let total = 0;
985
+ const onAbort = () => {
986
+ reader.cancel(signal.reason).catch(() => {});
987
+ };
988
+ if (signal.aborted) onAbort();else signal.addEventListener("abort", onAbort, {
989
+ once: true
990
+ });
991
+ try {
992
+ for (;;) {
993
+ const {
994
+ done,
995
+ value
996
+ } = await reader.read();
997
+ if (signal.aborted) throw signal.reason;
998
+ if (done) break;
999
+ total += value.byteLength;
1000
+ if (total > limit) {
1001
+ reader.cancel().catch(() => {});
1002
+ return null;
1003
+ }
1004
+ chunks.push(value);
1005
+ }
1006
+ } catch (error) {
1007
+ reader.cancel(error).catch(() => {});
1008
+ throw error;
1009
+ } finally {
1010
+ signal.removeEventListener("abort", onAbort);
1011
+ reader.releaseLock();
1012
+ }
1013
+ const body = new Uint8Array(total);
1014
+ let offset = 0;
1015
+ for (const chunk of chunks) {
1016
+ body.set(chunk, offset);
1017
+ offset += chunk.byteLength;
1018
+ }
1019
+ return new Request(request, {
1020
+ body
1021
+ });
1022
+ }
1023
+ async function parseArguments(request, url, scripted, codec) {
732
1024
  const parsed = [];
733
1025
  const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
734
1026
  const args = url.searchParams.get("args");
735
- if (args && (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
736
- const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
1027
+ if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
1028
+ let result;
1029
+ if (args.startsWith(";0x")) {
1030
+ result = await deserializeString(args, codec);
1031
+ } else {
1032
+ result = JSON.parse(args);
1033
+ assertDecodeDepth(result);
1034
+ }
737
1035
  if (!Array.isArray(result)) {
738
1036
  throw new TypeError("Server function arguments must encode an array");
739
1037
  }
1038
+ stripUnsafeArgumentKeys(result);
740
1039
  for (const arg of result) {
741
1040
  parsed.push(arg);
742
1041
  }
@@ -746,18 +1045,37 @@ async function parseArguments(request, url, instance, codec) {
746
1045
  if (request.method === "POST" && request.body !== null) {
747
1046
  const decoded = await extractBody(request.clone(), codec);
748
1047
  if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
749
- return decoded;
1048
+ if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
1049
+ if (!Array.isArray(decoded)) {
1050
+ throw new TypeError("Server function arguments must encode an array");
1051
+ }
1052
+ return stripUnsafeArgumentKeys(decoded);
1053
+ }
1054
+ if (decoded === undefined) {
1055
+ if (bodyFormat === null && (await request.clone().arrayBuffer()).byteLength === 0) {
1056
+ return parsed;
1057
+ }
1058
+ throw new TypeError("Server function body carries no usable encoding");
750
1059
  }
751
1060
  parsed.push(decoded);
752
1061
  }
753
1062
  return parsed;
754
1063
  }
755
- async function foldFlightData(hook, event, headers, outcome, context = {}) {
1064
+ async function foldFlightData(hooks, event, headers, outcome, context = {}) {
756
1065
  if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
757
1066
  digestOutcome(event, outcome);
758
- const data = await hook(event, outcome);
759
- if (data === undefined) return outcome.value;
760
- headers.set(SINGLE_FLIGHT_HEADER, "true");
1067
+ const folded = [];
1068
+ for (const [source, hook] of hooks) {
1069
+ try {
1070
+ const slice = await hook(event, outcome);
1071
+ if (slice !== undefined) folded.push([source, slice]);
1072
+ } catch (error) {
1073
+ console.error(`Error collecting flight data for source "${source}"`, error);
1074
+ }
1075
+ }
1076
+ if (folded.length === 0) return outcome.value;
1077
+ const data = Object.fromEntries(folded);
1078
+ headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
761
1079
  if (context.transformFlightResult) {
762
1080
  const transformed = await context.transformFlightResult(event, {
763
1081
  value: outcome.value,
@@ -837,7 +1155,7 @@ function foldSetCookies(headers, setCookies) {
837
1155
  }
838
1156
  function mergeResponseHeaders(target, source) {
839
1157
  source.forEach((value, key) => {
840
- if (key !== "set-cookie") target.append(key, value);
1158
+ if (key !== "set-cookie" && !COMPOSED_BODY_FRAMING.has(key)) target.append(key, value);
841
1159
  });
842
1160
  if (source.getSetCookie) {
843
1161
  for (const cookie of source.getSetCookie()) target.append("Set-Cookie", cookie);
@@ -846,6 +1164,48 @@ function mergeResponseHeaders(target, source) {
846
1164
  }
847
1165
  }
848
1166
  const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
1167
+ function maskRedirect(headers, response, requestUrl) {
1168
+ const target = response.headers && response.headers.get("Location");
1169
+ if (target) {
1170
+ headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
1171
+ }
1172
+ headers.delete("Location");
1173
+ }
1174
+ const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
1175
+ function enforceComposedHeaderInvariants(response) {
1176
+ for (const name of BOUNDED_COMPOSED_HEADERS) {
1177
+ const value = response.headers.get(name);
1178
+ if (value === null) continue;
1179
+ if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
1180
+ return refuseComposedHeader(response, name, `${name} response header refused at ${value.length} characters`, DEV ? `The ${name} response header is ${value.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} it would overflow receivers (an 8 KiB proxy ` + `buffer holds the whole header block) and the response dies at the socket after ` + `the mutation committed. Refused rather than trimmed: a cut redirect target is a ` + `different address, a dropped revalidate key is a silently stale cache. The ` + `redirect()/reload() helpers enforce this bound with the full reasoning at the ` + `call site.` : null);
1181
+ }
1182
+ if (name === REVALIDATE_HEADER) continue;
1183
+ const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
1184
+ if (!isHttpNavigationTarget(target)) {
1185
+ return refuseComposedHeader(response, name, `${name} response header refused: non-http(s) navigation target`, DEV ? `The ${name} response header carries a navigation target with a non-http(s) ` + `scheme ("${target.slice(0, 64)}"). A javascript: target is same-origin script ` + `execution in any integration that navigates to it, so only http(s) and ` + `relative targets leave this transport. If the target came from request data ` + `(?next= and friends), validate it against your own origin before redirecting.` : null);
1186
+ }
1187
+ }
1188
+ return response;
1189
+ }
1190
+ function refuseComposedHeader(response, name, headerMessage, body) {
1191
+ if (response.body) {
1192
+ try {
1193
+ const cancelled = response.body.cancel();
1194
+ if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
1195
+ } catch {}
1196
+ }
1197
+ const headers = new Headers();
1198
+ headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
1199
+ return new Response(body, {
1200
+ status: 500,
1201
+ headers
1202
+ });
1203
+ }
1204
+ function warnScripted304(functionId) {
1205
+ if (DEV) {
1206
+ console.warn(`Server function "${functionId}" answered a scripted call with 304 Not Modified. ` + `The client transport sends no conditional headers, so nothing was asked to be ` + `revalidated: the call resolves to undefined, not "unchanged". For conditional ` + `reads, declare the function GET and set ETag/Cache-Control response headers - ` + `the browser owns that exchange and replays its cached answer on a 304.`);
1207
+ }
1208
+ }
849
1209
  function createNoJSHandler({
850
1210
  base = ""
851
1211
  } = {}) {
@@ -889,44 +1249,291 @@ function isFormPost(request) {
889
1249
  const type = request.headers.get("content-type") || "";
890
1250
  return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
891
1251
  }
892
- function serializeResponseStream(value, codecOptions, signal) {
893
- let closeIterator = null;
894
- let closed = false;
895
- let cancelSerialize = null;
896
- let onAbort = null;
897
- const teardown = () => {
898
- if (closed) return;
899
- closed = true;
900
- if (onAbort) signal.removeEventListener("abort", onAbort);
901
- if (cancelSerialize) cancelSerialize();
902
- if (closeIterator) closeIterator();
1252
+ function guardFailures(value, state) {
1253
+ if (!state) state = {
1254
+ seen: new WeakMap(),
1255
+ cyclic: new WeakSet()
903
1256
  };
904
- if (value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
1257
+ const entered = enterGuard(value, state);
1258
+ if (!(entered instanceof Frame)) return entered;
1259
+ const stack = [entered];
1260
+ let delivered = NOTHING;
1261
+ for (;;) {
1262
+ const top = stack[stack.length - 1];
1263
+ const items = top.items;
1264
+ let pushed = null;
1265
+ while (top.i < items.length) {
1266
+ const i = top.i;
1267
+ let original;
1268
+ if (top.kind === OBJECT) {
1269
+ const descriptor = top.descriptors[items[i]];
1270
+ if ("value" in descriptor) {
1271
+ original = descriptor.value;
1272
+ } else if (typeof descriptor.get === "function") {
1273
+ if (top.accessorRead !== i) {
1274
+ try {
1275
+ top.accessorValue = descriptor.get.call(top.value);
1276
+ } catch (error) {
1277
+ throw sanitizeServerError(error);
1278
+ }
1279
+ top.accessorRead = i;
1280
+ }
1281
+ original = top.accessorValue;
1282
+ } else {
1283
+ top.i++;
1284
+ continue;
1285
+ }
1286
+ } else {
1287
+ original = items[i];
1288
+ }
1289
+ let guarded;
1290
+ if (delivered !== NOTHING) {
1291
+ guarded = delivered;
1292
+ delivered = NOTHING;
1293
+ } else {
1294
+ guarded = enterGuard(original, state);
1295
+ if (guarded instanceof Frame) {
1296
+ pushed = guarded;
1297
+ break;
1298
+ }
1299
+ }
1300
+ if (top.kind === ARRAY) {
1301
+ if (guarded !== original) {
1302
+ top.next[i] = guarded;
1303
+ top.changed = true;
1304
+ }
1305
+ } else if (top.kind === MAP) {
1306
+ if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
1307
+ if (guarded !== original) top.changed = true;
1308
+ } else if (top.kind === SET) {
1309
+ top.next.add(guarded);
1310
+ if (guarded !== original) top.changed = true;
1311
+ } else if (guarded !== original || top.accessorRead === i) {
1312
+ Object.defineProperty(top.next, items[i], {
1313
+ value: guarded,
1314
+ writable: true,
1315
+ configurable: true,
1316
+ enumerable: top.descriptors[items[i]].enumerable
1317
+ });
1318
+ top.changed = true;
1319
+ }
1320
+ top.i++;
1321
+ }
1322
+ if (pushed !== null) {
1323
+ stack.push(pushed);
1324
+ continue;
1325
+ }
1326
+ stack.pop();
1327
+ const out = keepGuarded(top.value, top.next, top.changed, state);
1328
+ if (stack.length === 0) return out;
1329
+ delivered = out;
1330
+ }
1331
+ }
1332
+ const NOTHING = Symbol();
1333
+ const ARRAY = 0;
1334
+ const MAP = 1;
1335
+ const SET = 2;
1336
+ const OBJECT = 3;
1337
+ class Frame {
1338
+ constructor(kind, value, next, items, descriptors) {
1339
+ this.kind = kind;
1340
+ this.value = value;
1341
+ this.next = next;
1342
+ this.items = items;
1343
+ this.descriptors = descriptors;
1344
+ this.i = 0;
1345
+ this.changed = false;
1346
+ this.accessorRead = -1;
1347
+ this.accessorValue = undefined;
1348
+ this.pendingKey = undefined;
1349
+ }
1350
+ }
1351
+ function guardOperation(state, run) {
1352
+ return state.scope ? state.scope(run) : run();
1353
+ }
1354
+ function enterGuard(value, state) {
1355
+ if (value === null || typeof value !== "object") return value;
1356
+ if (state.seen.has(value)) {
1357
+ state.cyclic.add(value);
1358
+ return state.seen.get(value);
1359
+ }
1360
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
1361
+ let reader;
1362
+ const gate = state.gate;
1363
+ let finished = false;
1364
+ const close = () => {
1365
+ if (finished) return;
1366
+ finished = true;
1367
+ try {
1368
+ const cancelled = guardOperation(state, () => reader ? reader.cancel() : value.cancel());
1369
+ if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
1370
+ } catch {}
1371
+ };
1372
+ const guardedStream = new ReadableStream({
1373
+ async pull(controller) {
1374
+ try {
1375
+ if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
1376
+ if (finished) {
1377
+ controller.close();
1378
+ return;
1379
+ }
1380
+ if (!reader) reader = guardOperation(state, () => value.getReader());
1381
+ const {
1382
+ done,
1383
+ value: chunk
1384
+ } = await guardOperation(state, () => reader.read());
1385
+ done ? controller.close() : controller.enqueue(guardOperation(state, () => guardFailures(chunk, state)));
1386
+ } catch (error) {
1387
+ controller.error(guardOperation(state, () => sanitizeServerError(error)));
1388
+ }
1389
+ },
1390
+ cancel(reason) {
1391
+ finished = true;
1392
+ return guardOperation(state, () => reader ? reader.cancel(reason) : value.cancel(reason));
1393
+ }
1394
+ });
1395
+ if (gate) gate.onOpen(close);
1396
+ state.seen.set(value, guardedStream);
1397
+ return guardedStream;
1398
+ }
1399
+ if (typeof value.then === "function") {
1400
+ const guardedPromise = Promise.resolve(value).then(resolved => guardOperation(state, () => guardFailures(resolved, state)), error => {
1401
+ throw guardOperation(state, () => sanitizeServerError(error));
1402
+ });
1403
+ guardedPromise.catch(() => {});
1404
+ state.seen.set(value, guardedPromise);
1405
+ return guardedPromise;
1406
+ }
1407
+ if (typeof value[Symbol.asyncIterator] === "function") {
905
1408
  const source = value;
906
- value = {
1409
+ const gate = state.gate;
1410
+ const guardedIterable = {
907
1411
  [Symbol.asyncIterator]() {
908
- const it = source[Symbol.asyncIterator]();
1412
+ const iterator = guardOperation(state, () => source[Symbol.asyncIterator]());
909
1413
  let finished = false;
910
- closeIterator = () => {
1414
+ const close = () => {
911
1415
  if (finished) return;
912
1416
  finished = true;
913
1417
  try {
914
- const returned = it.return && it.return();
1418
+ const returned = iterator.return && guardOperation(state, () => iterator.return());
915
1419
  if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
916
1420
  } catch {}
917
1421
  };
918
- if (closed) closeIterator();
1422
+ if (gate) gate.onOpen(close);
1423
+ const step = () => finished ? Promise.resolve({
1424
+ done: true,
1425
+ value: undefined
1426
+ }) : guardOperation(state, () => iterator.next()).then(step => {
1427
+ if (step.done) {
1428
+ finished = true;
1429
+ return step;
1430
+ }
1431
+ return {
1432
+ done: false,
1433
+ value: guardOperation(state, () => guardFailures(step.value, state))
1434
+ };
1435
+ }, error => {
1436
+ throw guardOperation(state, () => sanitizeServerError(error));
1437
+ });
919
1438
  return {
920
- next: () => finished ? Promise.resolve({
921
- done: true,
922
- value: undefined
923
- }) : it.next()
1439
+ next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
1440
+ return: () => {
1441
+ close();
1442
+ return Promise.resolve({
1443
+ done: true,
1444
+ value: undefined
1445
+ });
1446
+ }
924
1447
  };
925
1448
  }
926
1449
  };
1450
+ state.seen.set(value, guardedIterable);
1451
+ return guardedIterable;
1452
+ }
1453
+ if (state.scope && typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
1454
+ const scopedIterable = scopeDeferredResult(value, state.scope);
1455
+ state.seen.set(value, scopedIterable);
1456
+ return scopedIterable;
1457
+ }
1458
+ if (Array.isArray(value)) {
1459
+ const next = value.slice();
1460
+ state.seen.set(value, next);
1461
+ return new Frame(ARRAY, value, next, value, null);
1462
+ }
1463
+ if (value instanceof Map) {
1464
+ const next = new Map();
1465
+ state.seen.set(value, next);
1466
+ const items = [];
1467
+ for (const entry of value) items.push(entry[0], entry[1]);
1468
+ return new Frame(MAP, value, next, items, null);
1469
+ }
1470
+ if (value instanceof Set) {
1471
+ const next = new Set();
1472
+ state.seen.set(value, next);
1473
+ return new Frame(SET, value, next, [...value], null);
927
1474
  }
1475
+ const prototype = Object.getPrototypeOf(value);
1476
+ if (prototype !== Object.prototype && prototype !== null) {
1477
+ state.seen.set(value, value);
1478
+ return value;
1479
+ }
1480
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1481
+ for (const key of Object.keys(descriptors)) {
1482
+ descriptors[key].configurable = true;
1483
+ if ("value" in descriptors[key]) descriptors[key].writable = true;
1484
+ }
1485
+ const next = Object.create(prototype, descriptors);
1486
+ state.seen.set(value, next);
1487
+ return new Frame(OBJECT, value, next, Object.keys(value), descriptors);
1488
+ }
1489
+ function keepGuarded(value, next, changed, state) {
1490
+ if (changed || state.cyclic.has(value)) return next;
1491
+ state.seen.set(value, value);
1492
+ return value;
1493
+ }
1494
+ function serializeResponseStream(value, codecOptions, signal, scope) {
1495
+ let closed = false;
1496
+ let streamController = null;
1497
+ let demandWaiters = null;
1498
+ const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
1499
+ const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
1500
+ const supplyDemand = () => {
1501
+ const resolvers = demandWaiters;
1502
+ demandWaiters = null;
1503
+ if (resolvers) for (const resolve of resolvers) resolve();
1504
+ };
1505
+ const sourceClosers = new Set();
1506
+ const gate = {
1507
+ wantsMore,
1508
+ awaitDemand,
1509
+ onOpen(close) {
1510
+ if (closed) close();else sourceClosers.add(close);
1511
+ }
1512
+ };
1513
+ const guardState = {
1514
+ seen: new WeakMap(),
1515
+ cyclic: new WeakSet(),
1516
+ gate,
1517
+ scope
1518
+ };
1519
+ value = guardOperation(guardState, () => guardFailures(value, guardState));
1520
+ let cancelSerialize = null;
1521
+ let onAbort = null;
1522
+ const finishSource = () => {
1523
+ for (const close of sourceClosers) close();
1524
+ sourceClosers.clear();
1525
+ supplyDemand();
1526
+ };
1527
+ const teardown = () => {
1528
+ if (closed) return;
1529
+ closed = true;
1530
+ if (onAbort) signal.removeEventListener("abort", onAbort);
1531
+ if (cancelSerialize) cancelSerialize();
1532
+ finishSource();
1533
+ };
928
1534
  return new ReadableStream({
929
1535
  async start(controller) {
1536
+ streamController = controller;
930
1537
  if (signal) {
931
1538
  if (signal.aborted) {
932
1539
  teardown();
@@ -962,29 +1569,54 @@ function serializeResponseStream(value, codecOptions, signal) {
962
1569
  if (closed) return;
963
1570
  closed = true;
964
1571
  if (onAbort) signal.removeEventListener("abort", onAbort);
1572
+ finishSource();
965
1573
  controller.close();
966
1574
  },
967
1575
  onError(error) {
968
1576
  if (closed) return;
969
1577
  closed = true;
970
1578
  if (onAbort) signal.removeEventListener("abort", onAbort);
971
- controller.error(error);
1579
+ finishSource();
1580
+ try {
1581
+ const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
1582
+ controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
1583
+ controller.close();
1584
+ } catch {
1585
+ try {
1586
+ controller.error(error);
1587
+ } catch {}
1588
+ }
972
1589
  }
973
1590
  });
974
1591
  },
1592
+ pull() {
1593
+ supplyDemand();
1594
+ },
975
1595
  cancel() {
976
1596
  teardown();
977
1597
  }
978
1598
  });
979
1599
  }
980
- function serializedResponse(value, headers, codec, signal) {
1600
+ function serializedResponse(value, headers, codec, signal, scope) {
981
1601
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
982
1602
  headers.set("Content-Type", "text/plain");
983
- return new Response(serializeResponseStream(value, codec, signal), {
1603
+ return new Response(serializeResponseStream(value, codec, signal, scope), {
984
1604
  headers
985
1605
  });
986
1606
  }
987
- function encodeResult(value, headers, status, codec, signal) {
1607
+ function encodeResult(value, headers, status, codec, signal, scope) {
1608
+ if (NULL_BODY_STATUSES.has(status)) {
1609
+ if (value === undefined || value === null) {
1610
+ headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
1611
+ return new Response(null, {
1612
+ status,
1613
+ headers
1614
+ });
1615
+ }
1616
+ const error = new Error(`Server function answered status ${status}, which forbids a response body, with a value. ` + `Return respond(undefined, { status: ${status} }) for a bodiless answer, or drop the ` + `status to send the value.`);
1617
+ headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
1618
+ return encodeResult(error, headers, 500, codec, signal, scope);
1619
+ }
988
1620
  const direct = getHeadersAndBody(value);
989
1621
  if (direct) {
990
1622
  for (const [key, val] of Object.entries(direct.headers || {})) {
@@ -1003,21 +1635,37 @@ function encodeResult(value, headers, status, codec, signal) {
1003
1635
  });
1004
1636
  }
1005
1637
  try {
1006
- if (isJSONSafe(value)) {
1638
+ const jsonSafe = scope ? scope(() => isJSONSafe(value)) : isJSONSafe(value);
1639
+ if (jsonSafe) {
1007
1640
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Json);
1008
1641
  headers.set("Content-Type", "application/json");
1009
- return new Response(JSON.stringify(value), {
1642
+ const body = scope ? scope(() => JSON.stringify(value)) : JSON.stringify(value);
1643
+ return new Response(body, {
1010
1644
  status,
1011
1645
  headers
1012
1646
  });
1013
1647
  }
1014
1648
  } catch {
1015
1649
  }
1016
- const response = serializedResponse(value, headers, codec, signal);
1017
- return status === 200 ? response : new Response(response.body, {
1018
- status,
1019
- headers
1020
- });
1650
+ try {
1651
+ const response = serializedResponse(value, headers, codec, signal, scope);
1652
+ return status === 200 ? response : new Response(response.body, {
1653
+ status,
1654
+ headers
1655
+ });
1656
+ } catch (error) {
1657
+ throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
1658
+ }
1659
+ }
1660
+ const ERROR_HEADER_VALUE_LIMIT = 1024;
1661
+ function boundedErrorHeaderValue(message) {
1662
+ let label = message.length > 256 ? message.slice(0, 256) : message;
1663
+ let encoded = encodeErrorHeaderValue(label);
1664
+ while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
1665
+ label = label.slice(0, Math.ceil(label.length / 2));
1666
+ encoded = encodeErrorHeaderValue(label);
1667
+ }
1668
+ return encoded;
1021
1669
  }
1022
1670
  const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
1023
1671
  let DEV = true === true;
@@ -1041,11 +1689,12 @@ function serverFunctionUrl(id, boundArgs) {
1041
1689
  return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
1042
1690
  }
1043
1691
  function parseServerFunctionUrl(url) {
1044
- return parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
1692
+ const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
1693
+ return parsed && parsed.id;
1045
1694
  }
1046
1695
  async function matchesOrigin(origin, request, matcher) {
1047
1696
  if (matcher === undefined) return origin === new URL(request.url).origin;
1048
- if (typeof matcher === "function") return !!(await matcher(origin, request));
1697
+ if (typeof matcher === "function") return (await matcher(origin, request)) === true;
1049
1698
  return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
1050
1699
  }
1051
1700
  async function allowsServerFunctionRequest(request, options) {
@@ -1097,14 +1746,37 @@ function forbiddenResponse() {
1097
1746
  }
1098
1747
  }));
1099
1748
  }
1749
+ function nativePromise(value) {
1750
+ if (value instanceof Promise) return value;
1751
+ try {
1752
+ if (Object.prototype.toString.call(value) === "[object Promise]") return Promise.prototype.then.call(value, value => value);
1753
+ } catch {}
1754
+ }
1100
1755
  async function handleServerFunctionRequest(request, options = {}) {
1101
- const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
1756
+ const codec = {
1757
+ ...(options.codec !== undefined ? options.codec : getServerFunctionsCodec())
1758
+ };
1759
+ codec.serializeErrorStacks ??= DEV;
1102
1760
  const url = new URL(request.url);
1103
1761
  const method = request.method;
1104
- const functionId = resolveFunctionId(url);
1762
+ const address = resolveAddress(url);
1763
+ const functionId = address && address.id;
1105
1764
  const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
1106
1765
  const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
1107
- const protectsRequest = csrf !== false && !declaredRead;
1766
+ const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
1767
+ let serverFunction;
1768
+ if (functionId) {
1769
+ try {
1770
+ serverFunction = getServerFunction(functionId);
1771
+ } catch {
1772
+ return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1773
+ status: 404,
1774
+ headers: {
1775
+ [UNKNOWN_HEADER]: "true"
1776
+ }
1777
+ }), method);
1778
+ }
1779
+ }
1108
1780
  if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
1109
1781
  return finalizeTransportResponse(forbiddenResponse(), method);
1110
1782
  }
@@ -1115,15 +1787,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1115
1787
  });
1116
1788
  return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1117
1789
  }
1118
- let serverFunction;
1119
- try {
1120
- serverFunction = getServerFunction(functionId);
1121
- } catch {
1122
- const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1123
- status: 404
1124
- });
1125
- return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1126
- }
1790
+ const scripted = address.data;
1127
1791
  if (method !== "POST" && !declaredRead) {
1128
1792
  const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
1129
1793
  status: 405,
@@ -1133,25 +1797,103 @@ async function handleServerFunctionRequest(request, options = {}) {
1133
1797
  });
1134
1798
  return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1135
1799
  }
1136
- const event = options.createEvent ? options.createEvent(request) : {
1137
- request,
1138
- locals: {}
1800
+ const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
1801
+ const argsEncoding = url.searchParams.get("args");
1802
+ if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
1803
+ const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
1804
+ status: 413
1805
+ });
1806
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1807
+ }
1808
+ if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
1809
+ const raw = request.headers.get("content-length");
1810
+ const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
1811
+ if (declared > bodySizeLimit) {
1812
+ const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
1813
+ status: 413
1814
+ });
1815
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1816
+ }
1817
+ if (!(declared > 0)) {
1818
+ let bounded;
1819
+ try {
1820
+ bounded = await bufferBodyWithin(request, bodySizeLimit);
1821
+ } catch {
1822
+ const response = new Response(DEV ? "Malformed server function arguments" : null, {
1823
+ status: 400
1824
+ });
1825
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1826
+ }
1827
+ if (bounded === null) {
1828
+ const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
1829
+ status: 413
1830
+ });
1831
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1832
+ }
1833
+ request = bounded;
1834
+ }
1835
+ }
1836
+ let event;
1837
+ try {
1838
+ event = options.createEvent ? options.createEvent(request) : {
1839
+ request,
1840
+ locals: {}
1841
+ };
1842
+ const promised = nativePromise(event);
1843
+ if (promised) event = await promised;
1844
+ } catch (error) {
1845
+ const safe = sanitizeServerError(error);
1846
+ const message = safe instanceof Error ? safe.message : String(safe);
1847
+ const headers = new Headers();
1848
+ headers.set(ERROR_HEADER, boundedErrorHeaderValue(message));
1849
+ const response = scripted ? encodeResult(safe, headers, 500, codec, request.signal) : new Response(DEV ? message : null, {
1850
+ status: 500
1851
+ });
1852
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1853
+ }
1854
+ const refuseCommitted = raw => {
1855
+ const response = commitEventResponse(raw, event);
1856
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1139
1857
  };
1140
1858
  const provide = options.provideEvent || provideEvent;
1859
+ const scope = run => provide(event, run);
1141
1860
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
1142
1861
  const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
1143
1862
  const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
1144
1863
  const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
1145
- const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
1146
- const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
1864
+ let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
1865
+ if (handleNoJS === undefined && !scripted && isFormPost(request)) {
1866
+ const fetchMode = request.headers.get("Sec-Fetch-Mode");
1867
+ if (fetchMode === null || fetchMode === "navigate") {
1868
+ handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
1869
+ } else {
1870
+ const response = new Response(DEV ? "The bare server-function address answers form navigations with the " + "no-JS redirect convention. Scripted callers use the data address " + `(…/data/${functionId}) or send the ${BODY_FORMAT_HEADER} tag.` : null, {
1871
+ status: 400
1872
+ });
1873
+ return refuseCommitted(response);
1874
+ }
1875
+ }
1876
+ const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
1877
+ const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
1878
+ const hook = source === "true" ? flightHook : flightSources.get(source);
1879
+ return hook ? [[source, hook]] : [];
1880
+ }) : [];
1881
+ const collectsFlight = flightHooks.length > 0;
1147
1882
  let parsed;
1148
1883
  try {
1149
- parsed = await parseArguments(request, url, instance, codec);
1884
+ parsed = await parseArguments(request, url, scripted, codec);
1150
1885
  } catch {
1151
1886
  const response = new Response(DEV ? "Malformed server function arguments" : null, {
1152
1887
  status: 400
1153
1888
  });
1154
- return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1889
+ return refuseCommitted(response);
1890
+ }
1891
+ const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
1892
+ if (parsed.length > maxArguments) {
1893
+ const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
1894
+ status: 400
1895
+ });
1896
+ return refuseCommitted(response);
1155
1897
  }
1156
1898
  const flightContext = {
1157
1899
  id: functionId,
@@ -1165,7 +1907,8 @@ async function handleServerFunctionRequest(request, options = {}) {
1165
1907
  const headers = new Headers();
1166
1908
  const dispatch = async () => {
1167
1909
  try {
1168
- let result = await provide(event, async () => {
1910
+ let invocations = 0;
1911
+ const invokeOnce = async () => {
1169
1912
  INVOCATIONS.set(event, {
1170
1913
  id: functionId
1171
1914
  });
@@ -1177,7 +1920,16 @@ async function handleServerFunctionRequest(request, options = {}) {
1177
1920
  request,
1178
1921
  direct: false
1179
1922
  }) : run();
1923
+ };
1924
+ let result = await provide(event, () => {
1925
+ if (++invocations > 1) {
1926
+ throw new Error("provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
1927
+ }
1928
+ return invokeOnce();
1180
1929
  });
1930
+ if (invocations !== 1) {
1931
+ throw new Error(invocations === 0 ? "provideEvent returned without invoking the server function callback: the call " + "would have answered as a void success without running the function. The hook " + "must call fn exactly once and return its result." : "provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
1932
+ }
1181
1933
  if (transformResult) {
1182
1934
  result = await transformResult(event, result, flightContext);
1183
1935
  }
@@ -1188,25 +1940,29 @@ async function handleServerFunctionRequest(request, options = {}) {
1188
1940
  response,
1189
1941
  value
1190
1942
  } = result;
1191
- if (!instance && !handleNoJS && response && response.body) {
1943
+ if (!scripted && !handleNoJS && response && response.body) {
1192
1944
  return response;
1193
1945
  }
1194
1946
  if (response && response.headers) {
1195
1947
  mergeResponseHeaders(headers, response.headers);
1196
1948
  }
1197
- if (response && response.status && (response.status < 300 || response.status >= 400)) {
1949
+ if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
1198
1950
  status = response.status;
1951
+ } else if (response && response.status) {
1952
+ maskRedirect(headers, response, request.url);
1199
1953
  }
1200
1954
  metadata = response;
1201
1955
  result = value;
1202
1956
  } else if (result instanceof Response) {
1203
1957
  if (result.headers && result.headers.has("X-Content-Raw")) return result;
1204
- if (instance) {
1958
+ if (scripted) {
1205
1959
  if (result.headers) {
1206
1960
  mergeResponseHeaders(headers, result.headers);
1207
1961
  }
1208
- if (result.status && (result.status < 300 || result.status >= 400)) {
1962
+ if (result.status && !validRedirectStatuses.has(result.status)) {
1209
1963
  status = result.status;
1964
+ } else if (result.status) {
1965
+ maskRedirect(headers, result, request.url);
1210
1966
  }
1211
1967
  metadata = result;
1212
1968
  if (result.body == null) {
@@ -1215,7 +1971,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1215
1971
  }
1216
1972
  }
1217
1973
  if (collectsFlight) {
1218
- result = await foldFlightData(flightHook, event, headers, {
1974
+ result = await foldFlightData(flightHooks, event, headers, {
1219
1975
  id: functionId,
1220
1976
  value: result,
1221
1977
  response: metadata,
@@ -1224,19 +1980,37 @@ async function handleServerFunctionRequest(request, options = {}) {
1224
1980
  }, flightContext);
1225
1981
  if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
1226
1982
  }
1227
- if (!instance) {
1228
- if (handleNoJS) return handleNoJS(result, request, parsed);
1983
+ if (!scripted) {
1984
+ if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
1229
1985
  if (result instanceof Response) return result;
1230
- return encodeResult(result, headers, 200, codec, request.signal);
1986
+ return encodeResult(result, headers, status, codec, request.signal, scope);
1231
1987
  }
1232
- return encodeResult(result, headers, status, codec, request.signal);
1988
+ if (status === 304) warnScripted304(functionId);
1989
+ return encodeResult(result, headers, status, codec, request.signal, scope);
1233
1990
  } catch (x) {
1991
+ const respondThrown = value => {
1992
+ const safe = sanitizeServerError(value);
1993
+ if (!scripted) {
1994
+ if (handleNoJS) return handleNoJS(safe, request, parsed, true);
1995
+ const message = safe instanceof Error ? safe.message : String(safe);
1996
+ return new Response(DEV ? message : null, {
1997
+ status: 500
1998
+ });
1999
+ }
2000
+ const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
2001
+ headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
2002
+ return encodeResult(safe, headers, 500, codec, request.signal, scope);
2003
+ };
1234
2004
  if (x instanceof Response || isResponseEnvelope(x)) {
1235
2005
  if (transformResult) {
1236
- x = await transformResult(event, x, {
1237
- ...flightContext,
1238
- thrown: true
1239
- });
2006
+ try {
2007
+ x = await transformResult(event, x, {
2008
+ ...flightContext,
2009
+ thrown: true
2010
+ });
2011
+ } catch (hookError) {
2012
+ return respondThrown(hookError);
2013
+ }
1240
2014
  }
1241
2015
  let status = 200;
1242
2016
  let metadata;
@@ -1248,8 +2022,10 @@ async function handleServerFunctionRequest(request, options = {}) {
1248
2022
  if (response && response.headers) {
1249
2023
  mergeResponseHeaders(headers, response.headers);
1250
2024
  }
1251
- if (response && response.status && (!instance || response.status < 300 || response.status >= 400)) {
2025
+ if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
1252
2026
  status = response.status;
2027
+ } else if (response && response.status) {
2028
+ maskRedirect(headers, response, request.url);
1253
2029
  }
1254
2030
  metadata = response;
1255
2031
  x = value;
@@ -1257,8 +2033,10 @@ async function handleServerFunctionRequest(request, options = {}) {
1257
2033
  if (x.headers) {
1258
2034
  mergeResponseHeaders(headers, x.headers);
1259
2035
  }
1260
- if (x.status && (!instance || x.status < 300 || x.status >= 400)) {
2036
+ if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
1261
2037
  status = x.status;
2038
+ } else if (x.status) {
2039
+ maskRedirect(headers, x, request.url);
1262
2040
  }
1263
2041
  metadata = x;
1264
2042
  if (x.body == null) {
@@ -1266,7 +2044,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1266
2044
  }
1267
2045
  }
1268
2046
  if (collectsFlight) {
1269
- x = await foldFlightData(flightHook, event, headers, {
2047
+ x = await foldFlightData(flightHooks, event, headers, {
1270
2048
  id: functionId,
1271
2049
  value: x,
1272
2050
  response: metadata,
@@ -1274,38 +2052,38 @@ async function handleServerFunctionRequest(request, options = {}) {
1274
2052
  thrown: true
1275
2053
  }, flightContext);
1276
2054
  if (x instanceof Response && x.headers.has("X-Content-Raw")) {
2055
+ x = ownResponse(x);
1277
2056
  x.headers.set(ERROR_HEADER, "true");
1278
2057
  return x;
1279
2058
  }
1280
2059
  }
1281
2060
  headers.set(ERROR_HEADER, "true");
1282
- if (!instance) {
2061
+ if (!scripted) {
1283
2062
  if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
1284
2063
  if (x instanceof Response) return x;
1285
2064
  }
1286
- return encodeResult(x, headers, status, codec, request.signal);
1287
- }
1288
- const safe = sanitizeServerError(x);
1289
- if (!instance) {
1290
- if (handleNoJS) return handleNoJS(safe, request, parsed, true);
1291
- const message = safe instanceof Error ? safe.message : String(safe);
1292
- return new Response(DEV ? message : null, {
1293
- status: 500
1294
- });
2065
+ if (scripted && status === 304) warnScripted304(functionId);
2066
+ return encodeResult(x, headers, status, codec, request.signal, scope);
1295
2067
  }
1296
- const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
1297
- headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
1298
- return encodeResult(safe, headers, 200, codec, request.signal);
2068
+ return respondThrown(x);
1299
2069
  }
1300
2070
  };
1301
- const response = commitEventResponse(await dispatch(), event);
2071
+ const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
1302
2072
  return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1303
2073
  }
2074
+ function ownResponse(response) {
2075
+ try {
2076
+ return new Response(response.body, response);
2077
+ } catch {
2078
+ return response;
2079
+ }
2080
+ }
1304
2081
  function finalizeTransportResponse(response, method) {
1305
2082
  const stripBody = method === "HEAD" && response.body !== null;
1306
- if (stripBody || !response.headers.has("Cache-Control")) {
2083
+ const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
2084
+ if (stripBody || defaultsCache) {
1307
2085
  try {
1308
- if (!response.headers.has("Cache-Control")) {
2086
+ if (defaultsCache) {
1309
2087
  response.headers.set("Cache-Control", "no-store");
1310
2088
  }
1311
2089
  if (!stripBody) return response;
@@ -1317,7 +2095,7 @@ function finalizeTransportResponse(response, method) {
1317
2095
  });
1318
2096
  } catch {
1319
2097
  const headers = new Headers(response.headers);
1320
- if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
2098
+ if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
1321
2099
  if (stripBody) response.body.cancel().catch(() => {});
1322
2100
  return new Response(stripBody ? null : response.body, {
1323
2101
  status: response.status,
@@ -1329,4 +2107,4 @@ function finalizeTransportResponse(response, method) {
1329
2107
  return response;
1330
2108
  }
1331
2109
 
1332
- export { ERROR_HEADER, FLASH_COOKIE, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, serverFunctionUrl, setServerFunctionsDev, subscribeFlightData, withMeta };
2110
+ export { ERROR_HEADER, FLASH_COOKIE, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, REDIRECT_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, UNKNOWN_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeRedirectHeaderValue, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, guardFailures, handleServerFunctionRequest, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerFlightDataSource, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, serverFunctionUrl, setServerFunctionsDev, subscribeFlightData, withMeta };