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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/dev.cjs +398 -17
  2. package/dist/dev.js +396 -19
  3. package/dist/server.cjs +166 -38
  4. package/dist/server.js +165 -39
  5. package/dist/web.cjs +369 -17
  6. package/dist/web.js +367 -19
  7. package/frames/dist/client.cjs +41 -8
  8. package/frames/dist/client.dev.cjs +41 -8
  9. package/frames/dist/client.dev.js +41 -8
  10. package/frames/dist/client.js +41 -8
  11. package/frames/dist/server.cjs +432 -44
  12. package/frames/dist/server.js +432 -44
  13. package/package.json +2 -2
  14. package/serialization/dist/decode.cjs +4 -2
  15. package/serialization/dist/decode.js +4 -2
  16. package/serialization/dist/serialization.cjs +12 -8
  17. package/serialization/dist/serialization.js +12 -8
  18. package/serialization/types/index.d.ts +7 -0
  19. package/serialization/types/serializer-decode.d.ts +14 -1
  20. package/serialization/types/serializer.d.ts +7 -0
  21. package/serialization/types-cjs/index.d.cts +7 -0
  22. package/serialization/types-cjs/serializer-decode.d.cts +14 -1
  23. package/serialization/types-cjs/serializer.d.cts +7 -0
  24. package/server-functions/dist/client.cjs +271 -47
  25. package/server-functions/dist/client.js +264 -47
  26. package/server-functions/dist/server.cjs +872 -131
  27. package/server-functions/dist/server.dev.cjs +892 -131
  28. package/server-functions/dist/server.dev.js +884 -131
  29. package/server-functions/dist/server.js +864 -131
  30. package/types/client.d.ts +4 -2
  31. package/types/cookies.d.ts +6 -14
  32. package/types/frames/frame-client.d.ts +4 -0
  33. package/types/frames/serializer-decode.d.ts +14 -1
  34. package/types/frames/serializer.d.ts +7 -0
  35. package/types/index.d.ts +1 -0
  36. package/types/jsx.d.ts +11 -16
  37. package/types/patch-driver.d.ts +3 -0
  38. package/types/response.d.ts +20 -1
  39. package/types/serializer-decode.d.ts +14 -1
  40. package/types/serializer.d.ts +7 -0
  41. package/types/server-functions/client.d.ts +58 -7
  42. package/types/server-functions/flash.d.ts +9 -0
  43. package/types/server-functions/registry.d.ts +38 -1
  44. package/types/server-functions/server.d.ts +66 -14
  45. package/types/server-functions/shared.d.ts +122 -16
  46. package/types/server-mock.d.ts +17 -2
  47. package/types/server.d.ts +16 -2
  48. package/types-cjs/client.d.cts +4 -2
  49. package/types-cjs/cookies.d.cts +6 -14
  50. package/types-cjs/frames/frame-client.d.cts +4 -0
  51. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  52. package/types-cjs/frames/serializer.d.cts +7 -0
  53. package/types-cjs/index.d.cts +1 -0
  54. package/types-cjs/jsx.d.cts +11 -16
  55. package/types-cjs/patch-driver.d.cts +3 -0
  56. package/types-cjs/response.d.cts +20 -1
  57. package/types-cjs/serializer-decode.d.cts +14 -1
  58. package/types-cjs/serializer.d.cts +7 -0
  59. package/types-cjs/server-functions/client.d.cts +58 -7
  60. package/types-cjs/server-functions/flash.d.cts +9 -0
  61. package/types-cjs/server-functions/registry.d.cts +38 -1
  62. package/types-cjs/server-functions/server.d.cts +66 -14
  63. package/types-cjs/server-functions/shared.d.cts +122 -16
  64. package/types-cjs/server-mock.d.cts +17 -2
  65. package/types-cjs/server.d.cts +16 -2
@@ -9,6 +9,8 @@ function isSafeError(value) {
9
9
  return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
10
10
  }
11
11
  const REVALIDATE_HEADER = "X-Revalidate";
12
+ const RESPONSE_HEADER_VALUE_LIMIT = 4096;
13
+ const NULL_BODY_STATUSES = new Set([204, 205, 304]);
12
14
 
13
15
  const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
14
16
  function getServerFunctionMetadata(fn) {
@@ -26,6 +28,32 @@ function withMeta(fn, meta) {
26
28
  Object.assign(metadata, meta);
27
29
  return fn;
28
30
  }
31
+ const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
32
+ const INVOKE_OPTION_REDIRECTS = {
33
+ headers: "Session-dynamic headers belong to the prepareRequest hook, declaration metadata to " + "withMeta(fn, meta), and data belongs in the arguments, where it is serialized, typed, " + "and part of the cache key.",
34
+ method: "The method is declaration-scoped: declare the function with GET(fn).",
35
+ body: "The arguments are the body: pass them in the args array.",
36
+ timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
37
+ };
38
+ function invoke(fn, options, ...args) {
39
+ const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
40
+ if (!channel) {
41
+ throw new Error(isServerFunction(fn) ? "invoke: this wrapper does not forward the invocation channel " + "(SERVER_FUNCTION_INVOKE). Wrappers that share calls (caches, channels) opt in " + "deliberately — a caller's signal cannot own a wire other callers share. Invoke " + "the underlying reference directly, or use the wrapper's own per-call idioms." : "invoke expects a server function reference (or a wrapper that forwards its " + "invocation channel). Per-call options apply at the transport; for a data " + "layer's calls, use its per-call options instead.");
42
+ }
43
+ if (options === null || typeof options !== "object") {
44
+ throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
45
+ }
46
+ const picked = {};
47
+ for (const key in options) {
48
+ if (key !== "signal" && key !== "keepalive" && key !== "priority") {
49
+ throw new Error(`\`${key}\` is not an invocation option. ` + (INVOKE_OPTION_REDIRECTS[key] || "Options here are strictly invocation-scoped (they vary between calls of the " + "same function): signal, keepalive, priority."));
50
+ }
51
+ }
52
+ if (options.signal !== undefined) picked.signal = options.signal;
53
+ if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
54
+ if (options.priority !== undefined) picked.priority = options.priority;
55
+ return channel(args, picked);
56
+ }
29
57
  const LIVE_SOURCE = Symbol.for("solid.LiveSource");
30
58
  const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
31
59
  function provideServerFunctionRPC(rpc) {
@@ -62,6 +90,7 @@ function serializeCookie(name, value, options = {}) {
62
90
  if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
63
91
  if (options.httpOnly) cookie += "; HttpOnly";
64
92
  if (options.secure) cookie += "; Secure";
93
+ if (options.partitioned) cookie += "; Partitioned";
65
94
  if (options.sameSite) {
66
95
  const sameSite = options.sameSite.toLowerCase();
67
96
  cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
@@ -86,11 +115,50 @@ function configureServerFunctionsCodec(codec) {
86
115
  function getServerFunctionsCodec() {
87
116
  return codecConfig.codec;
88
117
  }
89
- function subscribeFlightData(consumer) {
118
+ const UNNAMED_FLIGHT_SOURCE = "true";
119
+ const flightConfig = {
120
+ consumers: new Map()
121
+ };
122
+ function assertFlightSource(source) {
123
+ if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
124
+ 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).`);
125
+ }
126
+ }
127
+ function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
128
+ const named = typeof sourceOrConsumer === "string";
129
+ if (named) assertFlightSource(sourceOrConsumer);
130
+ const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
131
+ const consumer = named ? maybeConsumer : sourceOrConsumer;
132
+ flightConfig.consumers.set(source, consumer);
90
133
  return () => {
134
+ if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
91
135
  };
92
136
  }
93
- const FUNCTION_HEADER = "X-Server-Function-Id";
137
+ function serverFunctionAddress(endpoint, id) {
138
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
139
+ return `${mount}/${encodeURIComponent(id)}`;
140
+ }
141
+ function parseServerFunctionAddress(pathname, endpoint) {
142
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
143
+ if (!pathname.startsWith(mount)) return null;
144
+ const rest = pathname.slice(mount.length);
145
+ if (!rest.startsWith("/")) return null;
146
+ let segment = rest.slice(1);
147
+ let data = false;
148
+ if (segment.startsWith("data/")) {
149
+ segment = segment.slice(5);
150
+ data = true;
151
+ }
152
+ if (!segment || segment.includes("/")) return null;
153
+ try {
154
+ return {
155
+ id: decodeURIComponent(segment),
156
+ data
157
+ };
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
94
162
  const ERROR_HEADER = "X-Server-Function-Error";
95
163
  const ERROR_HEADER_MARKER = "=?1?";
96
164
  const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
@@ -118,6 +186,28 @@ function decodeErrorHeaderValue(value) {
118
186
  }
119
187
  const INSTANCE_HEADER = "X-Server-Function-Instance";
120
188
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
189
+ const UNKNOWN_HEADER = "X-Server-Function-Unknown";
190
+ const REDIRECT_HEADER = "X-Server-Function-Redirect";
191
+ function decodeRedirectHeaderValue(value) {
192
+ if (typeof value !== "string") return undefined;
193
+ const at = value.indexOf(" ");
194
+ if (at < 0) return undefined;
195
+ const status = Number(value.slice(0, at));
196
+ const url = value.slice(at + 1);
197
+ if (!Number.isInteger(status) || !url) return undefined;
198
+ if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
199
+ let parsed;
200
+ try {
201
+ parsed = new URL(url);
202
+ } catch {
203
+ return undefined;
204
+ }
205
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
206
+ return {
207
+ status,
208
+ url
209
+ };
210
+ }
121
211
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
122
212
  const FILE_FORM_KEY = "__server_function_file__";
123
213
  const BodyFormat = {
@@ -129,7 +219,8 @@ const BodyFormat = {
129
219
  File: "5",
130
220
  ArrayBuffer: "6",
131
221
  Uint8Array: "7",
132
- Json: "8"
222
+ Json: "8",
223
+ Void: "9"
133
224
  };
134
225
  const JSON_SAFE_DEPTH_LIMIT = 4096;
135
226
  const EXIT = {};
@@ -159,7 +250,11 @@ function isJSONSafe(value) {
159
250
  const proto = Object.getPrototypeOf(v);
160
251
  if (proto !== Object.prototype && proto !== null) return false;
161
252
  if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
162
- for (const k in v) stack.push(v[k]);
253
+ for (const k in v) {
254
+ const descriptor = Object.getOwnPropertyDescriptor(v, k);
255
+ if (descriptor === undefined || !("value" in descriptor)) return false;
256
+ stack.push(descriptor.value);
257
+ }
163
258
  }
164
259
  }
165
260
  return true;
@@ -268,18 +363,34 @@ function createChunk(data) {
268
363
  class ChunkReader {
269
364
  constructor(stream) {
270
365
  this.reader = stream.getReader();
271
- this.buffer = new Uint8Array(0);
366
+ this.store = new Uint8Array(0);
367
+ this.buffer = this.store;
272
368
  this.done = false;
273
369
  }
274
370
  async readChunk() {
275
371
  const chunk = await this.reader.read();
276
- if (!chunk.done) {
277
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
278
- newBuffer.set(this.buffer);
279
- newBuffer.set(chunk.value, this.buffer.length);
280
- this.buffer = newBuffer;
281
- } else {
372
+ if (chunk.done) {
282
373
  this.done = true;
374
+ return;
375
+ }
376
+ const incoming = chunk.value;
377
+ const store = this.store;
378
+ const start = this.buffer.byteOffset;
379
+ const end = start + this.buffer.length;
380
+ const needed = this.buffer.length + incoming.length;
381
+ if (end + incoming.length <= store.length) {
382
+ store.set(incoming, end);
383
+ this.buffer = store.subarray(start, end + incoming.length);
384
+ } else if (needed <= store.length) {
385
+ store.copyWithin(0, start, end);
386
+ store.set(incoming, this.buffer.length);
387
+ this.buffer = store.subarray(0, needed);
388
+ } else {
389
+ const grown = new Uint8Array(Math.max(needed, store.length * 2));
390
+ grown.set(this.buffer);
391
+ grown.set(incoming, this.buffer.length);
392
+ this.store = grown;
393
+ this.buffer = grown.subarray(0, needed);
283
394
  }
284
395
  }
285
396
  async next() {
@@ -321,6 +432,27 @@ class ChunkReader {
321
432
  }
322
433
  }
323
434
  }
435
+ const ERROR_TRAILER_PREFIX = "!";
436
+ function encodeErrorTrailer(error) {
437
+ const shaped = error instanceof Error ? error : new Error(String(error));
438
+ return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
439
+ name: shaped.name,
440
+ message: shaped.message
441
+ } : {
442
+ message: shaped.message
443
+ });
444
+ }
445
+ function errorFromTrailer(payload) {
446
+ let shape;
447
+ try {
448
+ shape = JSON.parse(payload.slice(1));
449
+ } catch {
450
+ shape = null;
451
+ }
452
+ const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
453
+ if (shape && typeof shape.name === "string") error.name = shape.name;
454
+ return error;
455
+ }
324
456
  async function deserializeStream(source, codecOptions) {
325
457
  if (!source.body) {
326
458
  throw new Error("missing body");
@@ -328,11 +460,17 @@ async function deserializeStream(source, codecOptions) {
328
460
  const reader = new ChunkReader(source.body);
329
461
  const result = await reader.next();
330
462
  if (!result.done) {
463
+ if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
464
+ throw errorFromTrailer(result.value);
465
+ }
331
466
  const {
332
467
  createJSONDeserializer
333
468
  } = await import('@solidjs/web/serialization/decode');
334
469
  const deserializeChunk = createJSONDeserializer(codecOptions);
335
470
  function interpretChunk(chunk) {
471
+ if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
472
+ throw errorFromTrailer(chunk);
473
+ }
336
474
  return deserializeChunk(JSON.parse(chunk));
337
475
  }
338
476
  reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
@@ -396,7 +534,7 @@ function copyInitHeaders(init) {
396
534
  for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
397
535
  return headers;
398
536
  }
399
- const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
537
+ const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location"].map(header => header.toLowerCase()));
400
538
  function fillsStubGap(key, headers, response) {
401
539
  if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
402
540
  if (response.body === null && (key === "content-type" || key === "content-length")) return false;
@@ -412,24 +550,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
412
550
  if (fillsStubGap(key, response.headers, response)) hasGaps = true;
413
551
  });
414
552
  if (!cookies.length && !hasGaps) return response;
415
- try {
416
- for (const cookie of cookies) response.headers.append("Set-Cookie", cookie);
417
- stub.headers.forEach((value, key) => {
418
- if (fillsStubGap(key, response.headers, response)) response.headers.set(key, value);
419
- });
420
- return response;
421
- } catch {
422
- const headers = copyInitHeaders(response.headers);
423
- for (const cookie of cookies) headers.append("Set-Cookie", cookie);
424
- stub.headers.forEach((value, key) => {
425
- if (fillsStubGap(key, headers, response)) headers.set(key, value);
426
- });
427
- return new Response(response.body, {
428
- status: response.status,
429
- statusText: response.statusText,
430
- headers
431
- });
432
- }
553
+ const headers = copyInitHeaders(response.headers);
554
+ for (const cookie of cookies) headers.append("Set-Cookie", cookie);
555
+ stub.headers.forEach((value, key) => {
556
+ if (fillsStubGap(key, headers, response)) headers.set(key, value);
557
+ });
558
+ return new Response(response.body, {
559
+ status: response.status,
560
+ statusText: response.statusText,
561
+ headers
562
+ });
433
563
  }
434
564
 
435
565
  function encodeInputValue(value) {
@@ -461,11 +591,35 @@ function encodeFlashCookie(url, result, input, thrown) {
461
591
  thrown: !!thrown,
462
592
  input: input.map(encodeInputValue)
463
593
  };
594
+ if (fitsCookie(payload)) return flashCookie(payload);
595
+ payload.truncated = true;
596
+ payload.input = [];
597
+ if (!fitsCookie(payload)) {
598
+ if (typeof payload.result === "string") {
599
+ let prefix = payload.result;
600
+ while (prefix.length > 0 && !fitsCookie({
601
+ ...payload,
602
+ result: prefix
603
+ })) {
604
+ prefix = prefix.slice(0, prefix.length >> 1);
605
+ }
606
+ payload.result = prefix.length > 0 ? prefix : true;
607
+ } else {
608
+ payload.result = true;
609
+ }
610
+ }
611
+ return flashCookie(payload);
612
+ }
613
+ function flashCookie(payload) {
464
614
  return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
465
615
  secure: true,
466
616
  httpOnly: true
467
617
  });
468
618
  }
619
+ const COOKIE_PAIR_BUDGET = 4000;
620
+ function fitsCookie(payload) {
621
+ return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
622
+ }
469
623
  function decodeFlashCookie(cookieHeader) {
470
624
  const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
471
625
  if (!match) return;
@@ -473,12 +627,14 @@ function decodeFlashCookie(cookieHeader) {
473
627
  const payload = JSON.parse(match);
474
628
  if (!payload || !payload.result) return;
475
629
  const result = payload.error ? new Error(payload.result) : payload.result;
476
- return {
630
+ const submission = {
477
631
  input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
478
632
  url: payload.url,
479
633
  result: payload.thrown ? undefined : result,
480
634
  error: payload.thrown ? result : undefined
481
635
  };
636
+ if (payload.truncated) submission.truncated = true;
637
+ return submission;
482
638
  } catch (error) {
483
639
  console.error(error);
484
640
  }
@@ -493,7 +649,9 @@ const config = {
493
649
  transformDirectResult: undefined,
494
650
  handleNoJS: undefined,
495
651
  endpoint: "/_server",
496
- csrf: true
652
+ csrf: true,
653
+ bodySizeLimit: 1_048_576,
654
+ maxArguments: 1000
497
655
  };
498
656
  function configureServerFunctionsServer({
499
657
  provideEvent,
@@ -505,7 +663,9 @@ function configureServerFunctionsServer({
505
663
  handleNoJS,
506
664
  endpoint,
507
665
  csrf,
508
- codec
666
+ codec,
667
+ bodySizeLimit,
668
+ maxArguments
509
669
  } = {}) {
510
670
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
511
671
  if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
@@ -517,6 +677,16 @@ function configureServerFunctionsServer({
517
677
  if (endpoint !== undefined) config.endpoint = endpoint;
518
678
  if (csrf !== undefined) config.csrf = csrf;
519
679
  if (codec !== undefined) configureServerFunctionsCodec(codec);
680
+ if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
681
+ if (maxArguments !== undefined) config.maxArguments = maxArguments;
682
+ }
683
+ const flightSources = new Map();
684
+ function registerFlightDataSource(source, hook) {
685
+ assertFlightSource(source);
686
+ flightSources.set(source, hook);
687
+ return () => {
688
+ if (flightSources.get(source) === hook) flightSources.delete(source);
689
+ };
520
690
  }
521
691
  function provideEvent(event, fn) {
522
692
  if (config.provideEvent) return config.provideEvent(event, fn);
@@ -538,6 +708,7 @@ function provideRPC() {
538
708
  }
539
709
  function registerServerFunction(id, callback) {
540
710
  provideRPC();
711
+ if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
541
712
  REGISTRATIONS.set(id, callback);
542
713
  return callback;
543
714
  }
@@ -549,6 +720,9 @@ function getServerFunction(id) {
549
720
  throw new Error("invalid server function: " + id);
550
721
  }
551
722
  function registerServerReference(id, fn, name) {
723
+ if (typeof fn !== "function") {
724
+ throw new Error(`Server function${name ? ` \`${name}\`` : ""} (${id}) is not a function: a module-level ` + `"use server" export must evaluate to a server function (got ${fn === null ? "null" : typeof fn}). Move non-function exports out of the directive module.`);
725
+ }
552
726
  registerServerFunction(id, fn);
553
727
  return {
554
728
  id,
@@ -556,6 +730,33 @@ function registerServerReference(id, fn, name) {
556
730
  name
557
731
  };
558
732
  }
733
+ function inProcessInvoker(call) {
734
+ return (args, options) => {
735
+ const signal = options && options.signal;
736
+ if (!signal) return call(...args);
737
+ if (signal.aborted) return Promise.reject(signal.reason);
738
+ return new Promise((resolve, reject) => {
739
+ const onAbort = () => reject(signal.reason);
740
+ signal.addEventListener("abort", onAbort, {
741
+ once: true
742
+ });
743
+ let result;
744
+ try {
745
+ result = call(...args);
746
+ } catch (error) {
747
+ signal.removeEventListener("abort", onAbort);
748
+ return reject(error);
749
+ }
750
+ Promise.resolve(result).then(value => {
751
+ signal.removeEventListener("abort", onAbort);
752
+ resolve(value);
753
+ }, error => {
754
+ signal.removeEventListener("abort", onAbort);
755
+ reject(error);
756
+ });
757
+ });
758
+ };
759
+ }
559
760
  function createServerReference({
560
761
  id,
561
762
  fn,
@@ -566,20 +767,25 @@ function createServerReference({
566
767
  const metadata = name === undefined ? {} : {
567
768
  name
568
769
  };
569
- return new Proxy(fn, {
770
+ const invokeChannel = inProcessInvoker((...args) => proxy(...args));
771
+ const proxy = new Proxy(fn, {
570
772
  get(target, prop) {
571
773
  if (prop === "id") return id;
572
774
  if (prop === "url") {
573
- return `${config.endpoint}?id=${encodeURIComponent(id)}`;
775
+ return serverFunctionAddress(config.endpoint, id);
574
776
  }
575
777
  if (prop === SERVER_FUNCTION_METADATA) return metadata;
778
+ if (prop === SERVER_FUNCTION_INVOKE) return invokeChannel;
576
779
  return target[prop];
577
780
  },
578
781
  apply(target, thisArg, args) {
579
782
  const ogEvt = getRequestEvent();
580
783
  if (!ogEvt) throw new Error("Cannot call server function outside of a request");
581
784
  const evt = {
582
- ...ogEvt
785
+ ...ogEvt,
786
+ locals: {
787
+ ...ogEvt.locals
788
+ }
583
789
  };
584
790
  INVOCATIONS.set(evt, {
585
791
  id
@@ -609,6 +815,7 @@ function createServerReference({
609
815
  }) : result;
610
816
  }
611
817
  });
818
+ return proxy;
612
819
  }
613
820
  function GET(fn) {
614
821
  if (!isServerFunction(fn) || typeof fn.id !== "string") {
@@ -635,6 +842,7 @@ function live(fn) {
635
842
  return result;
636
843
  };
637
844
  wrapped[SERVER_FUNCTION_METADATA] = metadata;
845
+ wrapped[SERVER_FUNCTION_INVOKE] = inProcessInvoker(wrapped);
638
846
  wrapped.id = fn.id;
639
847
  Object.defineProperty(wrapped, "url", {
640
848
  get: () => fn.url,
@@ -648,40 +856,107 @@ function getServerFunctionInvocation() {
648
856
  function getEventServerFunctionInvocation(event) {
649
857
  return event && INVOCATIONS.get(event);
650
858
  }
651
- function resolveFunctionId(request, url) {
652
- const reference = request.headers.get(FUNCTION_HEADER);
653
- if (reference) {
654
- return reference.split("#")[0];
859
+ function resolveAddress(url) {
860
+ return parseServerFunctionAddress(url.pathname, config.endpoint);
861
+ }
862
+ const DECODE_DEPTH_LIMIT = 64;
863
+ function assertDecodeDepth(value) {
864
+ let level = [value];
865
+ for (let depth = 0; level.length > 0; depth++) {
866
+ if (depth > DECODE_DEPTH_LIMIT) {
867
+ throw new TypeError("Server function arguments exceed the decode depth limit");
868
+ }
869
+ const next = [];
870
+ for (const node of level) {
871
+ if (node === null || typeof node !== "object") continue;
872
+ if (Array.isArray(node)) {
873
+ for (const child of node) next.push(child);
874
+ } else {
875
+ for (const key of Object.keys(node)) next.push(node[key]);
876
+ }
877
+ }
878
+ level = next;
655
879
  }
656
- return url.searchParams.get("id");
657
880
  }
658
- async function parseArguments(request, url, instance, codec) {
881
+ async function bufferBodyWithin(request, limit) {
882
+ const reader = request.clone().body.getReader();
883
+ const chunks = [];
884
+ let total = 0;
885
+ for (;;) {
886
+ const {
887
+ done,
888
+ value
889
+ } = await reader.read();
890
+ if (done) break;
891
+ total += value.byteLength;
892
+ if (total > limit) {
893
+ reader.cancel().catch(() => {});
894
+ return null;
895
+ }
896
+ chunks.push(value);
897
+ }
898
+ const body = new Uint8Array(total);
899
+ let offset = 0;
900
+ for (const chunk of chunks) {
901
+ body.set(chunk, offset);
902
+ offset += chunk.byteLength;
903
+ }
904
+ return new Request(request, {
905
+ body
906
+ });
907
+ }
908
+ async function parseArguments(request, url, scripted, codec) {
659
909
  const parsed = [];
660
910
  const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
661
- if (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized) {
662
- const args = url.searchParams.get("args");
663
- if (args) {
664
- const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
665
- for (const arg of result) {
666
- parsed.push(arg);
667
- }
911
+ const args = url.searchParams.get("args");
912
+ if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
913
+ let result;
914
+ if (args.startsWith(";0x")) {
915
+ result = await deserializeString(args, codec);
916
+ } else {
917
+ result = JSON.parse(args);
918
+ assertDecodeDepth(result);
919
+ }
920
+ if (!Array.isArray(result)) {
921
+ throw new TypeError("Server function arguments must encode an array");
668
922
  }
923
+ for (const arg of result) {
924
+ parsed.push(arg);
925
+ }
926
+ } else if (!args && url.search && (request.method === "GET" || request.method === "HEAD")) {
927
+ parsed.push(url.searchParams);
669
928
  }
670
929
  if (request.method === "POST" && request.body !== null) {
671
930
  const decoded = await extractBody(request.clone(), codec);
672
931
  if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
932
+ if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
933
+ if (!Array.isArray(decoded)) {
934
+ throw new TypeError("Server function arguments must encode an array");
935
+ }
673
936
  return decoded;
674
937
  }
938
+ if (decoded === undefined) {
939
+ throw new TypeError("Server function body carries no usable encoding");
940
+ }
675
941
  parsed.push(decoded);
676
942
  }
677
943
  return parsed;
678
944
  }
679
- async function foldFlightData(hook, event, headers, outcome, context = {}) {
945
+ async function foldFlightData(hooks, event, headers, outcome, context = {}) {
680
946
  if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
681
947
  digestOutcome(event, outcome);
682
- const data = await hook(event, outcome);
683
- if (data === undefined) return outcome.value;
684
- headers.set(SINGLE_FLIGHT_HEADER, "true");
948
+ const folded = [];
949
+ for (const [source, hook] of hooks) {
950
+ try {
951
+ const slice = await hook(event, outcome);
952
+ if (slice !== undefined) folded.push([source, slice]);
953
+ } catch (error) {
954
+ console.error(`Error collecting flight data for source "${source}"`, error);
955
+ }
956
+ }
957
+ if (folded.length === 0) return outcome.value;
958
+ const data = Object.fromEntries(folded);
959
+ headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
685
960
  if (context.transformFlightResult) {
686
961
  const transformed = await context.transformFlightResult(event, {
687
962
  value: outcome.value,
@@ -770,6 +1045,52 @@ function mergeResponseHeaders(target, source) {
770
1045
  }
771
1046
  }
772
1047
  const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
1048
+ function maskRedirect(headers, response, requestUrl) {
1049
+ const target = response.headers && response.headers.get("Location");
1050
+ if (target) {
1051
+ headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
1052
+ }
1053
+ headers.delete("Location");
1054
+ }
1055
+ const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
1056
+ function refusedTargetScheme(target) {
1057
+ const match = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(target);
1058
+ return match !== null && !/^https?:$/i.test(match[0]);
1059
+ }
1060
+ function enforceComposedHeaderInvariants(response) {
1061
+ for (const name of BOUNDED_COMPOSED_HEADERS) {
1062
+ const value = response.headers.get(name);
1063
+ if (value === null) continue;
1064
+ if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
1065
+ 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);
1066
+ }
1067
+ if (name === REVALIDATE_HEADER) continue;
1068
+ const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
1069
+ if (refusedTargetScheme(target)) {
1070
+ 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);
1071
+ }
1072
+ }
1073
+ return response;
1074
+ }
1075
+ function refuseComposedHeader(response, name, headerMessage, body) {
1076
+ if (response.body) {
1077
+ try {
1078
+ const cancelled = response.body.cancel();
1079
+ if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
1080
+ } catch {}
1081
+ }
1082
+ const headers = new Headers();
1083
+ headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
1084
+ return new Response(body, {
1085
+ status: 500,
1086
+ headers
1087
+ });
1088
+ }
1089
+ function warnScripted304(functionId) {
1090
+ if (DEV) {
1091
+ 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.`);
1092
+ }
1093
+ }
773
1094
  function createNoJSHandler({
774
1095
  base = ""
775
1096
  } = {}) {
@@ -813,44 +1134,279 @@ function isFormPost(request) {
813
1134
  const type = request.headers.get("content-type") || "";
814
1135
  return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
815
1136
  }
816
- function serializeResponseStream(value, codecOptions, signal) {
817
- let closeIterator = null;
818
- let closed = false;
819
- let cancelSerialize = null;
820
- let onAbort = null;
821
- const teardown = () => {
822
- if (closed) return;
823
- closed = true;
824
- if (onAbort) signal.removeEventListener("abort", onAbort);
825
- if (cancelSerialize) cancelSerialize();
826
- if (closeIterator) closeIterator();
1137
+ function guardFailures(value, state) {
1138
+ if (!state) state = {
1139
+ seen: new WeakMap(),
1140
+ cyclic: new WeakSet()
827
1141
  };
828
- if (value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
1142
+ const entered = enterGuard(value, state);
1143
+ if (!(entered instanceof Frame)) return entered;
1144
+ const stack = [entered];
1145
+ let delivered = NOTHING;
1146
+ for (;;) {
1147
+ const top = stack[stack.length - 1];
1148
+ const items = top.items;
1149
+ let pushed = null;
1150
+ while (top.i < items.length) {
1151
+ const i = top.i;
1152
+ let original;
1153
+ if (top.kind === OBJECT) {
1154
+ const descriptor = top.descriptors[items[i]];
1155
+ if ("value" in descriptor) {
1156
+ original = descriptor.value;
1157
+ } else if (typeof descriptor.get === "function") {
1158
+ if (top.accessorRead !== i) {
1159
+ try {
1160
+ top.accessorValue = descriptor.get.call(top.value);
1161
+ } catch (error) {
1162
+ throw sanitizeServerError(error);
1163
+ }
1164
+ top.accessorRead = i;
1165
+ }
1166
+ original = top.accessorValue;
1167
+ } else {
1168
+ top.i++;
1169
+ continue;
1170
+ }
1171
+ } else {
1172
+ original = items[i];
1173
+ }
1174
+ let guarded;
1175
+ if (delivered !== NOTHING) {
1176
+ guarded = delivered;
1177
+ delivered = NOTHING;
1178
+ } else {
1179
+ guarded = enterGuard(original, state);
1180
+ if (guarded instanceof Frame) {
1181
+ pushed = guarded;
1182
+ break;
1183
+ }
1184
+ }
1185
+ if (top.kind === ARRAY) {
1186
+ if (guarded !== original) {
1187
+ top.next[i] = guarded;
1188
+ top.changed = true;
1189
+ }
1190
+ } else if (top.kind === MAP) {
1191
+ if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
1192
+ if (guarded !== original) top.changed = true;
1193
+ } else if (top.kind === SET) {
1194
+ top.next.add(guarded);
1195
+ if (guarded !== original) top.changed = true;
1196
+ } else if (guarded !== original || top.accessorRead === i) {
1197
+ Object.defineProperty(top.next, items[i], top.accessorRead === i ? {
1198
+ enumerable: true,
1199
+ configurable: true,
1200
+ writable: true,
1201
+ value: guarded
1202
+ } : {
1203
+ ...top.descriptors[items[i]],
1204
+ value: guarded
1205
+ });
1206
+ top.changed = true;
1207
+ }
1208
+ top.i++;
1209
+ }
1210
+ if (pushed !== null) {
1211
+ stack.push(pushed);
1212
+ continue;
1213
+ }
1214
+ stack.pop();
1215
+ const out = keepGuarded(top.value, top.next, top.changed, state);
1216
+ if (stack.length === 0) return out;
1217
+ delivered = out;
1218
+ }
1219
+ }
1220
+ const NOTHING = Symbol();
1221
+ const ARRAY = 0;
1222
+ const MAP = 1;
1223
+ const SET = 2;
1224
+ const OBJECT = 3;
1225
+ class Frame {
1226
+ constructor(kind, value, next, items, descriptors) {
1227
+ this.kind = kind;
1228
+ this.value = value;
1229
+ this.next = next;
1230
+ this.items = items;
1231
+ this.descriptors = descriptors;
1232
+ this.i = 0;
1233
+ this.changed = false;
1234
+ this.accessorRead = -1;
1235
+ this.accessorValue = undefined;
1236
+ this.pendingKey = undefined;
1237
+ }
1238
+ }
1239
+ function enterGuard(value, state) {
1240
+ if (value === null || typeof value !== "object") return value;
1241
+ if (state.seen.has(value)) {
1242
+ state.cyclic.add(value);
1243
+ return state.seen.get(value);
1244
+ }
1245
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
1246
+ let reader;
1247
+ const gate = state.gate;
1248
+ let finished = false;
1249
+ const close = () => {
1250
+ if (finished) return;
1251
+ finished = true;
1252
+ try {
1253
+ const cancelled = reader ? reader.cancel() : value.cancel();
1254
+ if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
1255
+ } catch {}
1256
+ };
1257
+ const guardedStream = new ReadableStream({
1258
+ async pull(controller) {
1259
+ try {
1260
+ if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
1261
+ if (finished) {
1262
+ controller.close();
1263
+ return;
1264
+ }
1265
+ if (!reader) reader = value.getReader();
1266
+ const {
1267
+ done,
1268
+ value: chunk
1269
+ } = await reader.read();
1270
+ done ? controller.close() : controller.enqueue(guardFailures(chunk, state));
1271
+ } catch (error) {
1272
+ controller.error(sanitizeServerError(error));
1273
+ }
1274
+ },
1275
+ cancel(reason) {
1276
+ finished = true;
1277
+ return reader ? reader.cancel(reason) : value.cancel(reason);
1278
+ }
1279
+ });
1280
+ if (gate) gate.onOpen(close);
1281
+ state.seen.set(value, guardedStream);
1282
+ return guardedStream;
1283
+ }
1284
+ if (typeof value.then === "function") {
1285
+ const guardedPromise = Promise.resolve(value).then(resolved => guardFailures(resolved, state), error => {
1286
+ throw sanitizeServerError(error);
1287
+ });
1288
+ state.seen.set(value, guardedPromise);
1289
+ return guardedPromise;
1290
+ }
1291
+ if (typeof value[Symbol.asyncIterator] === "function") {
829
1292
  const source = value;
830
- value = {
1293
+ const gate = state.gate;
1294
+ const guardedIterable = {
831
1295
  [Symbol.asyncIterator]() {
832
- const it = source[Symbol.asyncIterator]();
1296
+ const iterator = source[Symbol.asyncIterator]();
833
1297
  let finished = false;
834
- closeIterator = () => {
1298
+ const close = () => {
835
1299
  if (finished) return;
836
1300
  finished = true;
837
1301
  try {
838
- const returned = it.return && it.return();
1302
+ const returned = iterator.return && iterator.return();
839
1303
  if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
840
1304
  } catch {}
841
1305
  };
842
- if (closed) closeIterator();
1306
+ if (gate) gate.onOpen(close);
1307
+ const step = () => finished ? Promise.resolve({
1308
+ done: true,
1309
+ value: undefined
1310
+ }) : iterator.next().then(step => {
1311
+ if (step.done) {
1312
+ finished = true;
1313
+ return step;
1314
+ }
1315
+ return {
1316
+ done: false,
1317
+ value: guardFailures(step.value, state)
1318
+ };
1319
+ }, error => {
1320
+ throw sanitizeServerError(error);
1321
+ });
843
1322
  return {
844
- next: () => finished ? Promise.resolve({
845
- done: true,
846
- value: undefined
847
- }) : it.next()
1323
+ next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
1324
+ return: () => {
1325
+ close();
1326
+ return Promise.resolve({
1327
+ done: true,
1328
+ value: undefined
1329
+ });
1330
+ }
848
1331
  };
849
1332
  }
850
1333
  };
1334
+ state.seen.set(value, guardedIterable);
1335
+ return guardedIterable;
1336
+ }
1337
+ if (Array.isArray(value)) {
1338
+ const next = value.slice();
1339
+ state.seen.set(value, next);
1340
+ return new Frame(ARRAY, value, next, value, null);
851
1341
  }
1342
+ if (value instanceof Map) {
1343
+ const next = new Map();
1344
+ state.seen.set(value, next);
1345
+ const items = [];
1346
+ for (const entry of value) items.push(entry[0], entry[1]);
1347
+ return new Frame(MAP, value, next, items, null);
1348
+ }
1349
+ if (value instanceof Set) {
1350
+ const next = new Set();
1351
+ state.seen.set(value, next);
1352
+ return new Frame(SET, value, next, [...value], null);
1353
+ }
1354
+ const prototype = Object.getPrototypeOf(value);
1355
+ if (prototype !== Object.prototype && prototype !== null) {
1356
+ state.seen.set(value, value);
1357
+ return value;
1358
+ }
1359
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1360
+ const next = Object.create(prototype, descriptors);
1361
+ state.seen.set(value, next);
1362
+ return new Frame(OBJECT, value, next, Object.keys(descriptors), descriptors);
1363
+ }
1364
+ function keepGuarded(value, next, changed, state) {
1365
+ if (changed || state.cyclic.has(value)) return next;
1366
+ state.seen.set(value, value);
1367
+ return value;
1368
+ }
1369
+ function serializeResponseStream(value, codecOptions, signal) {
1370
+ let closed = false;
1371
+ let streamController = null;
1372
+ let demandWaiters = null;
1373
+ const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
1374
+ const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
1375
+ const supplyDemand = () => {
1376
+ const resolvers = demandWaiters;
1377
+ demandWaiters = null;
1378
+ if (resolvers) for (const resolve of resolvers) resolve();
1379
+ };
1380
+ const sourceClosers = new Set();
1381
+ const gate = {
1382
+ wantsMore,
1383
+ awaitDemand,
1384
+ onOpen(close) {
1385
+ if (closed) close();else sourceClosers.add(close);
1386
+ }
1387
+ };
1388
+ value = guardFailures(value, {
1389
+ seen: new WeakMap(),
1390
+ cyclic: new WeakSet(),
1391
+ gate
1392
+ });
1393
+ let cancelSerialize = null;
1394
+ let onAbort = null;
1395
+ const finishSource = () => {
1396
+ for (const close of sourceClosers) close();
1397
+ sourceClosers.clear();
1398
+ supplyDemand();
1399
+ };
1400
+ const teardown = () => {
1401
+ if (closed) return;
1402
+ closed = true;
1403
+ if (onAbort) signal.removeEventListener("abort", onAbort);
1404
+ if (cancelSerialize) cancelSerialize();
1405
+ finishSource();
1406
+ };
852
1407
  return new ReadableStream({
853
1408
  async start(controller) {
1409
+ streamController = controller;
854
1410
  if (signal) {
855
1411
  if (signal.aborted) {
856
1412
  teardown();
@@ -886,16 +1442,29 @@ function serializeResponseStream(value, codecOptions, signal) {
886
1442
  if (closed) return;
887
1443
  closed = true;
888
1444
  if (onAbort) signal.removeEventListener("abort", onAbort);
1445
+ finishSource();
889
1446
  controller.close();
890
1447
  },
891
1448
  onError(error) {
892
1449
  if (closed) return;
893
1450
  closed = true;
894
1451
  if (onAbort) signal.removeEventListener("abort", onAbort);
895
- controller.error(error);
1452
+ finishSource();
1453
+ try {
1454
+ const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
1455
+ controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
1456
+ controller.close();
1457
+ } catch {
1458
+ try {
1459
+ controller.error(error);
1460
+ } catch {}
1461
+ }
896
1462
  }
897
1463
  });
898
1464
  },
1465
+ pull() {
1466
+ supplyDemand();
1467
+ },
899
1468
  cancel() {
900
1469
  teardown();
901
1470
  }
@@ -909,6 +1478,18 @@ function serializedResponse(value, headers, codec, signal) {
909
1478
  });
910
1479
  }
911
1480
  function encodeResult(value, headers, status, codec, signal) {
1481
+ if (NULL_BODY_STATUSES.has(status)) {
1482
+ if (value === undefined || value === null) {
1483
+ headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
1484
+ return new Response(null, {
1485
+ status,
1486
+ headers
1487
+ });
1488
+ }
1489
+ 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.`);
1490
+ headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
1491
+ return encodeResult(error, headers, 500, codec, signal);
1492
+ }
912
1493
  const direct = getHeadersAndBody(value);
913
1494
  if (direct) {
914
1495
  for (const [key, val] of Object.entries(direct.headers || {})) {
@@ -920,6 +1501,7 @@ function encodeResult(value, headers, status, codec, signal) {
920
1501
  });
921
1502
  }
922
1503
  if (value === undefined) {
1504
+ headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
923
1505
  return new Response(null, {
924
1506
  status,
925
1507
  headers
@@ -936,11 +1518,25 @@ function encodeResult(value, headers, status, codec, signal) {
936
1518
  }
937
1519
  } catch {
938
1520
  }
939
- const response = serializedResponse(value, headers, codec, signal);
940
- return status === 200 ? response : new Response(response.body, {
941
- status,
942
- headers
943
- });
1521
+ try {
1522
+ const response = serializedResponse(value, headers, codec, signal);
1523
+ return status === 200 ? response : new Response(response.body, {
1524
+ status,
1525
+ headers
1526
+ });
1527
+ } catch (error) {
1528
+ throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
1529
+ }
1530
+ }
1531
+ const ERROR_HEADER_VALUE_LIMIT = 1024;
1532
+ function boundedErrorHeaderValue(message) {
1533
+ let label = message.length > 256 ? message.slice(0, 256) : message;
1534
+ let encoded = encodeErrorHeaderValue(label);
1535
+ while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
1536
+ label = label.slice(0, Math.ceil(label.length / 2));
1537
+ encoded = encodeErrorHeaderValue(label);
1538
+ }
1539
+ return encoded;
944
1540
  }
945
1541
  const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
946
1542
  let DEV = false === true;
@@ -955,9 +1551,21 @@ function sanitizeServerError(value) {
955
1551
  function observeServerFunctionCalls() {
956
1552
  return () => {};
957
1553
  }
1554
+ function serverFunctionUrl(id, boundArgs) {
1555
+ const address = serverFunctionAddress(config.endpoint, id);
1556
+ if (!boundArgs || !boundArgs.length) return address;
1557
+ if (!isJSONSafe(boundArgs)) {
1558
+ throw new Error("Bound arguments in an action url must be JSON-safe: the server reads them the way it " + "reads a form post's, and that convention has no codec. Pass the value through the " + "function's body, or call the reference instead of rendering a url for it.");
1559
+ }
1560
+ return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
1561
+ }
1562
+ function parseServerFunctionUrl(url) {
1563
+ const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
1564
+ return parsed && parsed.id;
1565
+ }
958
1566
  async function matchesOrigin(origin, request, matcher) {
959
1567
  if (matcher === undefined) return origin === new URL(request.url).origin;
960
- if (typeof matcher === "function") return !!(await matcher(origin, request));
1568
+ if (typeof matcher === "function") return (await matcher(origin, request)) === true;
961
1569
  return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
962
1570
  }
963
1571
  async function allowsServerFunctionRequest(request, options) {
@@ -1012,49 +1620,121 @@ function forbiddenResponse() {
1012
1620
  async function handleServerFunctionRequest(request, options = {}) {
1013
1621
  const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
1014
1622
  const url = new URL(request.url);
1623
+ const method = request.method;
1624
+ const address = resolveAddress(url);
1625
+ const functionId = address && address.id;
1626
+ const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
1015
1627
  const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
1016
- const protectsRequest = csrf !== false;
1628
+ const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
1629
+ let serverFunction;
1630
+ if (functionId) {
1631
+ try {
1632
+ serverFunction = getServerFunction(functionId);
1633
+ } catch {
1634
+ return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1635
+ status: 404,
1636
+ headers: {
1637
+ [UNKNOWN_HEADER]: "true"
1638
+ }
1639
+ }), method);
1640
+ }
1641
+ }
1017
1642
  if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
1018
- return forbiddenResponse();
1643
+ return finalizeTransportResponse(forbiddenResponse(), method);
1019
1644
  }
1020
1645
  const instance = request.headers.get(INSTANCE_HEADER);
1021
- const functionId = resolveFunctionId(request, url);
1022
1646
  if (!functionId) {
1023
1647
  const response = new Response(DEV ? "Server function not found" : null, {
1024
1648
  status: 404
1025
1649
  });
1026
- return protectsRequest ? withCSRFVary(response) : response;
1650
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1027
1651
  }
1028
- let serverFunction;
1029
- try {
1030
- serverFunction = getServerFunction(functionId);
1031
- } catch {
1032
- const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1033
- status: 404
1034
- });
1035
- return protectsRequest ? withCSRFVary(response) : response;
1036
- }
1037
- if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
1652
+ const scripted = address.data;
1653
+ if (method !== "POST" && !declaredRead) {
1038
1654
  const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
1039
1655
  status: 405,
1040
1656
  headers: {
1041
- Allow: "POST"
1657
+ Allow: METHODS.get(functionId) === "GET" ? "POST, GET, HEAD" : "POST"
1042
1658
  }
1043
1659
  });
1044
- return protectsRequest ? withCSRFVary(response) : response;
1660
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1045
1661
  }
1046
- const event = options.createEvent ? options.createEvent(request) : {
1662
+ const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
1663
+ const argsEncoding = url.searchParams.get("args");
1664
+ if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
1665
+ const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
1666
+ status: 413
1667
+ });
1668
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1669
+ }
1670
+ if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
1671
+ const raw = request.headers.get("content-length");
1672
+ const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
1673
+ if (declared > bodySizeLimit) {
1674
+ const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
1675
+ status: 413
1676
+ });
1677
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1678
+ }
1679
+ if (!(declared > 0)) {
1680
+ const bounded = await bufferBodyWithin(request, bodySizeLimit);
1681
+ if (bounded === null) {
1682
+ const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
1683
+ status: 413
1684
+ });
1685
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1686
+ }
1687
+ request = bounded;
1688
+ }
1689
+ }
1690
+ let event = options.createEvent ? options.createEvent(request) : {
1047
1691
  request,
1048
1692
  locals: {}
1049
1693
  };
1694
+ if (typeof event?.then === "function") event = await event;
1695
+ const refuseCommitted = raw => {
1696
+ const response = commitEventResponse(raw, event);
1697
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1698
+ };
1050
1699
  const provide = options.provideEvent || provideEvent;
1051
1700
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
1052
1701
  const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
1053
1702
  const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
1054
1703
  const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
1055
- const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
1056
- const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
1057
- const parsed = await parseArguments(request, url, instance, codec);
1704
+ let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
1705
+ if (handleNoJS === undefined && !scripted && isFormPost(request)) {
1706
+ const fetchMode = request.headers.get("Sec-Fetch-Mode");
1707
+ if (fetchMode === null || fetchMode === "navigate") {
1708
+ handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
1709
+ } else {
1710
+ 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, {
1711
+ status: 400
1712
+ });
1713
+ return refuseCommitted(response);
1714
+ }
1715
+ }
1716
+ const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
1717
+ const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
1718
+ const hook = source === "true" ? flightHook : flightSources.get(source);
1719
+ return hook ? [[source, hook]] : [];
1720
+ }) : [];
1721
+ const collectsFlight = flightHooks.length > 0;
1722
+ let parsed;
1723
+ try {
1724
+ parsed = await parseArguments(request, url, scripted, codec);
1725
+ } catch {
1726
+ const response = new Response(DEV ? "Malformed server function arguments" : null, {
1727
+ status: 400
1728
+ });
1729
+ return refuseCommitted(response);
1730
+ }
1731
+ const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
1732
+ if (parsed.length > maxArguments) {
1733
+ const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
1734
+ status: 400
1735
+ });
1736
+ return refuseCommitted(response);
1737
+ }
1058
1738
  const flightContext = {
1059
1739
  id: functionId,
1060
1740
  args: parsed,
@@ -1090,25 +1770,29 @@ async function handleServerFunctionRequest(request, options = {}) {
1090
1770
  response,
1091
1771
  value
1092
1772
  } = result;
1093
- if (!instance && !handleNoJS && response && response.body) {
1773
+ if (!scripted && !handleNoJS && response && response.body) {
1094
1774
  return response;
1095
1775
  }
1096
1776
  if (response && response.headers) {
1097
1777
  mergeResponseHeaders(headers, response.headers);
1098
1778
  }
1099
- if (response && response.status && (response.status < 300 || response.status >= 400)) {
1779
+ if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
1100
1780
  status = response.status;
1781
+ } else if (response && response.status) {
1782
+ maskRedirect(headers, response, request.url);
1101
1783
  }
1102
1784
  metadata = response;
1103
1785
  result = value;
1104
1786
  } else if (result instanceof Response) {
1105
1787
  if (result.headers && result.headers.has("X-Content-Raw")) return result;
1106
- if (instance) {
1788
+ if (scripted) {
1107
1789
  if (result.headers) {
1108
1790
  mergeResponseHeaders(headers, result.headers);
1109
1791
  }
1110
- if (result.status && (result.status < 300 || result.status >= 400)) {
1792
+ if (result.status && !validRedirectStatuses.has(result.status)) {
1111
1793
  status = result.status;
1794
+ } else if (result.status) {
1795
+ maskRedirect(headers, result, request.url);
1112
1796
  }
1113
1797
  metadata = result;
1114
1798
  if (result.body == null) {
@@ -1117,7 +1801,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1117
1801
  }
1118
1802
  }
1119
1803
  if (collectsFlight) {
1120
- result = await foldFlightData(flightHook, event, headers, {
1804
+ result = await foldFlightData(flightHooks, event, headers, {
1121
1805
  id: functionId,
1122
1806
  value: result,
1123
1807
  response: metadata,
@@ -1126,19 +1810,37 @@ async function handleServerFunctionRequest(request, options = {}) {
1126
1810
  }, flightContext);
1127
1811
  if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
1128
1812
  }
1129
- if (!instance) {
1130
- if (handleNoJS) return handleNoJS(result, request, parsed);
1813
+ if (!scripted) {
1814
+ if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
1131
1815
  if (result instanceof Response) return result;
1132
- return encodeResult(result, headers, 200, codec, request.signal);
1816
+ return encodeResult(result, headers, status, codec, request.signal);
1133
1817
  }
1818
+ if (status === 304) warnScripted304(functionId);
1134
1819
  return encodeResult(result, headers, status, codec, request.signal);
1135
1820
  } catch (x) {
1821
+ const respondThrown = value => {
1822
+ const safe = sanitizeServerError(value);
1823
+ if (!scripted) {
1824
+ if (handleNoJS) return handleNoJS(safe, request, parsed, true);
1825
+ const message = safe instanceof Error ? safe.message : String(safe);
1826
+ return new Response(DEV ? message : null, {
1827
+ status: 500
1828
+ });
1829
+ }
1830
+ const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
1831
+ headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
1832
+ return encodeResult(safe, headers, 500, codec, request.signal);
1833
+ };
1136
1834
  if (x instanceof Response || isResponseEnvelope(x)) {
1137
1835
  if (transformResult) {
1138
- x = await transformResult(event, x, {
1139
- ...flightContext,
1140
- thrown: true
1141
- });
1836
+ try {
1837
+ x = await transformResult(event, x, {
1838
+ ...flightContext,
1839
+ thrown: true
1840
+ });
1841
+ } catch (hookError) {
1842
+ return respondThrown(hookError);
1843
+ }
1142
1844
  }
1143
1845
  let status = 200;
1144
1846
  let metadata;
@@ -1150,8 +1852,10 @@ async function handleServerFunctionRequest(request, options = {}) {
1150
1852
  if (response && response.headers) {
1151
1853
  mergeResponseHeaders(headers, response.headers);
1152
1854
  }
1153
- if (response && response.status && (!instance || response.status < 300 || response.status >= 400)) {
1855
+ if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
1154
1856
  status = response.status;
1857
+ } else if (response && response.status) {
1858
+ maskRedirect(headers, response, request.url);
1155
1859
  }
1156
1860
  metadata = response;
1157
1861
  x = value;
@@ -1159,8 +1863,10 @@ async function handleServerFunctionRequest(request, options = {}) {
1159
1863
  if (x.headers) {
1160
1864
  mergeResponseHeaders(headers, x.headers);
1161
1865
  }
1162
- if (x.status && (!instance || x.status < 300 || x.status >= 400)) {
1866
+ if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
1163
1867
  status = x.status;
1868
+ } else if (x.status) {
1869
+ maskRedirect(headers, x, request.url);
1164
1870
  }
1165
1871
  metadata = x;
1166
1872
  if (x.body == null) {
@@ -1168,7 +1874,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1168
1874
  }
1169
1875
  }
1170
1876
  if (collectsFlight) {
1171
- x = await foldFlightData(flightHook, event, headers, {
1877
+ x = await foldFlightData(flightHooks, event, headers, {
1172
1878
  id: functionId,
1173
1879
  value: x,
1174
1880
  response: metadata,
@@ -1176,32 +1882,59 @@ async function handleServerFunctionRequest(request, options = {}) {
1176
1882
  thrown: true
1177
1883
  }, flightContext);
1178
1884
  if (x instanceof Response && x.headers.has("X-Content-Raw")) {
1885
+ x = ownResponse(x);
1179
1886
  x.headers.set(ERROR_HEADER, "true");
1180
1887
  return x;
1181
1888
  }
1182
1889
  }
1183
1890
  headers.set(ERROR_HEADER, "true");
1184
- if (!instance) {
1891
+ if (!scripted) {
1185
1892
  if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
1186
1893
  if (x instanceof Response) return x;
1187
1894
  }
1895
+ if (scripted && status === 304) warnScripted304(functionId);
1188
1896
  return encodeResult(x, headers, status, codec, request.signal);
1189
1897
  }
1190
- const safe = sanitizeServerError(x);
1191
- if (!instance) {
1192
- if (handleNoJS) return handleNoJS(safe, request, parsed, true);
1193
- const message = safe instanceof Error ? safe.message : String(safe);
1194
- return new Response(DEV ? message : null, {
1195
- status: 500
1196
- });
1197
- }
1198
- const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
1199
- headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
1200
- return encodeResult(safe, headers, 200, codec, request.signal);
1898
+ return respondThrown(x);
1201
1899
  }
1202
1900
  };
1203
- const response = commitEventResponse(await dispatch(), event);
1204
- return protectsRequest ? withCSRFVary(response) : response;
1901
+ const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
1902
+ return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
1903
+ }
1904
+ function ownResponse(response) {
1905
+ try {
1906
+ return new Response(response.body, response);
1907
+ } catch {
1908
+ return response;
1909
+ }
1910
+ }
1911
+ function finalizeTransportResponse(response, method) {
1912
+ const stripBody = method === "HEAD" && response.body !== null;
1913
+ const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
1914
+ if (stripBody || defaultsCache) {
1915
+ try {
1916
+ if (defaultsCache) {
1917
+ response.headers.set("Cache-Control", "no-store");
1918
+ }
1919
+ if (!stripBody) return response;
1920
+ response.body.cancel().catch(() => {});
1921
+ return new Response(null, {
1922
+ status: response.status,
1923
+ statusText: response.statusText,
1924
+ headers: response.headers
1925
+ });
1926
+ } catch {
1927
+ const headers = new Headers(response.headers);
1928
+ if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
1929
+ if (stripBody) response.body.cancel().catch(() => {});
1930
+ return new Response(stripBody ? null : response.body, {
1931
+ status: response.status,
1932
+ statusText: response.statusText,
1933
+ headers
1934
+ });
1935
+ }
1936
+ }
1937
+ return response;
1205
1938
  }
1206
1939
 
1207
- export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, setServerFunctionsDev, subscribeFlightData, withMeta };
1940
+ 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 };