@solidjs/web 2.0.0-rc.0 → 2.0.0-rc.2

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/dev.cjs +40 -11
  3. package/dist/dev.js +39 -12
  4. package/dist/server.cjs +419 -69
  5. package/dist/server.js +411 -73
  6. package/dist/web.cjs +37 -11
  7. package/dist/web.js +36 -12
  8. package/frames/dist/client.cjs +94 -8
  9. package/frames/dist/client.dev.cjs +97 -8
  10. package/frames/dist/client.dev.js +98 -9
  11. package/frames/dist/client.js +95 -9
  12. package/frames/dist/server.cjs +266 -56
  13. package/frames/dist/server.js +267 -57
  14. package/package.json +4 -3
  15. package/serialization/dist/decode.cjs +32 -3
  16. package/serialization/dist/decode.js +33 -4
  17. package/serialization/dist/serialization.cjs +32 -3
  18. package/serialization/dist/serialization.js +33 -4
  19. package/server-functions/dist/client.cjs +196 -8
  20. package/server-functions/dist/client.js +195 -9
  21. package/server-functions/dist/server.cjs +201 -36
  22. package/server-functions/dist/server.dev.cjs +201 -36
  23. package/server-functions/dist/server.dev.js +199 -37
  24. package/server-functions/dist/server.js +199 -37
  25. package/types/core.d.ts +3 -0
  26. package/types/frames/frame-client.d.ts +18 -0
  27. package/types/index.d.ts +16 -2
  28. package/types/jsx.d.ts +9 -0
  29. package/types/server-functions/client.d.ts +61 -0
  30. package/types/server-functions/server.d.ts +94 -1
  31. package/types/server-mock.d.ts +11 -2
  32. package/types/server.d.ts +23 -2
  33. package/types-cjs/core.d.cts +3 -0
  34. package/types-cjs/frames/frame-client.d.cts +18 -0
  35. package/types-cjs/index.d.cts +16 -2
  36. package/types-cjs/jsx.d.cts +9 -0
  37. package/types-cjs/server-functions/client.d.cts +61 -0
  38. package/types-cjs/server-functions/server.d.cts +94 -1
  39. package/types-cjs/server-mock.d.cts +11 -2
  40. package/types-cjs/server.d.cts +23 -2
@@ -16,6 +16,7 @@ function withMeta(fn, meta) {
16
16
  Object.assign(metadata, meta);
17
17
  return fn;
18
18
  }
19
+ const LIVE_SOURCE = Symbol.for("solid.LiveSource");
19
20
  const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
20
21
  function provideServerFunctionRPC(rpc) {
21
22
  globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
@@ -136,7 +137,7 @@ const BodyFormat = {
136
137
  Uint8Array: "7",
137
138
  Json: "8"
138
139
  };
139
- const JSON_SAFE_DEPTH_LIMIT = 10000;
140
+ const JSON_SAFE_DEPTH_LIMIT = 4096;
140
141
  const EXIT = {};
141
142
  function isJSONSafe(value) {
142
143
  const stack = [value];
@@ -163,6 +164,7 @@ function isJSONSafe(value) {
163
164
  } else {
164
165
  const proto = Object.getPrototypeOf(v);
165
166
  if (proto !== Object.prototype && proto !== null) return false;
167
+ if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
166
168
  for (const k in v) stack.push(v[k]);
167
169
  }
168
170
  }
@@ -364,7 +366,7 @@ async function deserializeStream(source, codecOptions) {
364
366
  function interpretChunk(chunk) {
365
367
  return deserializeChunk(JSON.parse(chunk));
366
368
  }
367
- void reader.drain(interpretChunk);
369
+ reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
368
370
  return interpretChunk(result.value);
369
371
  }
370
372
  return undefined;
@@ -392,6 +394,30 @@ const config = {
392
394
  responseHandler: undefined,
393
395
  serializeArgs: undefined
394
396
  };
397
+ const CALL_OBSERVERS = new Set();
398
+ function notifyCallObservers(type, id, instance, value, meta) {
399
+ if (CALL_OBSERVERS.size === 0) return;
400
+ const field = type === "request" ? "request" : "response";
401
+ const time = performance.now();
402
+ for (const observer of new Set(CALL_OBSERVERS)) {
403
+ try {
404
+ observer({
405
+ type,
406
+ id,
407
+ instance,
408
+ [field]: value.clone(),
409
+ meta,
410
+ time
411
+ });
412
+ } catch (error) {
413
+ console.error(error);
414
+ }
415
+ }
416
+ }
417
+ function observeServerFunctionCalls(observer) {
418
+ CALL_OBSERVERS.add(observer);
419
+ return () => CALL_OBSERVERS.delete(observer);
420
+ }
395
421
  function serializeArguments(args) {
396
422
  if (!config.serializeArgs) {
397
423
  throw new Error("Server function arguments are sent as JSON by default and these " + "arguments are not JSON-serializable. Call enableRichArguments() " + '(from "@solidjs/web/server-functions/rich-args") once at startup ' + "to send Dates, Maps, Sets, typed arrays, etc. through the codec — " + "or pass a single Blob/FormData/File argument, which has a native " + "HTTP encoding.");
@@ -421,13 +447,18 @@ function provideRPC() {
421
447
  decodeResponse
422
448
  });
423
449
  }
450
+ function serverFunctionFailure(response, value) {
451
+ const error = value ?? new Error(`Server function call failed with status ${response.status}`);
452
+ if (error instanceof Error && !("status" in error)) error.status = response.status;
453
+ return error;
454
+ }
424
455
  async function createRequest(base, id, instance, options, meta) {
425
456
  const headers = {
426
457
  ...options.headers,
427
458
  [FUNCTION_HEADER]: id,
428
459
  [INSTANCE_HEADER]: instance
429
460
  };
430
- if (getFlightDataConsumer() && (!options.method || options.method.toUpperCase() !== "GET")) {
461
+ if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
431
462
  headers[SINGLE_FLIGHT_HEADER] = "true";
432
463
  }
433
464
  let init = {
@@ -441,7 +472,12 @@ async function createRequest(base, id, instance, options, meta) {
441
472
  meta
442
473
  })) || init;
443
474
  }
444
- return fetch(base, init);
475
+ if (CALL_OBSERVERS.size === 0) return fetch(base, init);
476
+ const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
477
+ notifyCallObservers("request", id, instance, request, meta);
478
+ const response = await fetch(request);
479
+ notifyCallObservers("response", id, instance, response, meta);
480
+ return response;
445
481
  }
446
482
  async function initializeResponse(base, id, instance, options, args, meta) {
447
483
  if (args.length === 0) {
@@ -509,6 +545,11 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
509
545
  id,
510
546
  meta
511
547
  }) : undefined;
548
+ const controller = options.signal ? undefined : new AbortController();
549
+ if (controller) options = {
550
+ ...options,
551
+ signal: controller.signal
552
+ };
512
553
  const response = await initializeResponse(base, id, instance, options, args, meta);
513
554
  if (handler) {
514
555
  const handled = handler.handle(response, {
@@ -519,6 +560,7 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
519
560
  });
520
561
  if (handled !== undefined) return handled;
521
562
  }
563
+ const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
522
564
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
523
565
  const consumer = getFlightDataConsumer();
524
566
  if (consumer) {
@@ -526,8 +568,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
526
568
  await consumer(payload.data, {
527
569
  response
528
570
  });
529
- if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
530
- throw payload.value;
571
+ if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
572
+ throw serverFunctionFailure(response, payload.value);
531
573
  }
532
574
  return payload.value;
533
575
  }
@@ -536,8 +578,22 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
536
578
  return response;
537
579
  }
538
580
  const result = await decodeResponse(response.clone());
539
- if (response.headers.has(ERROR_HEADER)) {
540
- throw result;
581
+ if (failed) {
582
+ throw serverFunctionFailure(response, result);
583
+ }
584
+ if (controller && result?.[Symbol.asyncIterator]) {
585
+ return {
586
+ [Symbol.asyncIterator]() {
587
+ const it = result[Symbol.asyncIterator]();
588
+ return {
589
+ next: () => it.next(),
590
+ return: value => (controller.abort(), Promise.resolve({
591
+ done: true,
592
+ value
593
+ }))
594
+ };
595
+ }
596
+ };
541
597
  }
542
598
  return result;
543
599
  }
@@ -607,6 +663,136 @@ function GET(fn) {
607
663
  method: "GET"
608
664
  });
609
665
  }
666
+ function live(fn) {
667
+ if (!isServerFunction(fn)) {
668
+ throw new Error("live expects a server function reference");
669
+ }
670
+ const id = fn.id;
671
+ const metadata = {
672
+ ...getServerFunctionMetadata(fn),
673
+ live: true
674
+ };
675
+ const wrapped = (...args) => {
676
+ const iterable = {
677
+ [LIVE_SOURCE]: true,
678
+ [Symbol.asyncIterator]() {
679
+ let it;
680
+ let connected = false;
681
+ let attempts = 0;
682
+ let stopped = false;
683
+ let ended = false;
684
+ let timer, wake;
685
+ const DONE = {
686
+ done: true,
687
+ value: undefined
688
+ };
689
+ const emit = (state, error) => {
690
+ try {
691
+ iterable.onstatus && iterable.onstatus(state, error);
692
+ } catch {}
693
+ };
694
+ const emitClosed = error => {
695
+ if (ended) return;
696
+ ended = true;
697
+ emit("closed", error);
698
+ };
699
+ const closeIt = value => {
700
+ const current = it;
701
+ it = undefined;
702
+ if (current) {
703
+ try {
704
+ const r = current.return && current.return(value);
705
+ if (r && typeof r.then === "function") r.then(undefined, () => {});
706
+ } catch {}
707
+ }
708
+ };
709
+ const callOnce = () => {
710
+ if (metadata.method === "GET") return fn(...args);
711
+ const handler = config.responseHandler;
712
+ if (handler && handler.intercept) {
713
+ const hit = handler.intercept({
714
+ id,
715
+ meta: metadata,
716
+ args
717
+ });
718
+ if (hit !== undefined) return hit;
719
+ }
720
+ return fetchServerFunction(fn.url, id, {
721
+ read: true
722
+ }, args, metadata, args);
723
+ };
724
+ const pull = async () => {
725
+ while (!stopped) {
726
+ try {
727
+ if (!it) {
728
+ const result = await callOnce();
729
+ connected = true;
730
+ it = result !== null && typeof result === "object" && result[Symbol.asyncIterator] ? result[Symbol.asyncIterator]() : async function* () {
731
+ yield result;
732
+ }();
733
+ if (stopped) {
734
+ closeIt();
735
+ return DONE;
736
+ }
737
+ emit("connected");
738
+ }
739
+ const r = await it.next();
740
+ if (r.done) {
741
+ emitClosed();
742
+ return DONE;
743
+ }
744
+ if (stopped) return DONE;
745
+ attempts = 0;
746
+ return r;
747
+ } catch (error) {
748
+ if (!connected) throw error;
749
+ if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
750
+ stopped = true;
751
+ emitClosed(error);
752
+ throw error;
753
+ }
754
+ it = undefined;
755
+ emit("reconnecting", error);
756
+ await new Promise(resolve => {
757
+ wake = resolve;
758
+ timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
759
+ if (typeof addEventListener === "function") addEventListener("online", resolve, {
760
+ once: true
761
+ });
762
+ });
763
+ clearTimeout(timer);
764
+ if (typeof removeEventListener === "function") removeEventListener("online", wake);
765
+ timer = wake = undefined;
766
+ }
767
+ }
768
+ return DONE;
769
+ };
770
+ return {
771
+ next: () => pull(),
772
+ return(value) {
773
+ stopped = true;
774
+ if (timer !== undefined) clearTimeout(timer);
775
+ if (wake) wake();
776
+ closeIt(value);
777
+ emitClosed();
778
+ return Promise.resolve({
779
+ done: true,
780
+ value
781
+ });
782
+ }
783
+ };
784
+ }
785
+ };
786
+ return iterable;
787
+ };
788
+ wrapped[SERVER_FUNCTION_METADATA] = metadata;
789
+ wrapped.id = id;
790
+ Object.defineProperty(wrapped, "url", {
791
+ get: () => fn.url,
792
+ configurable: true
793
+ });
794
+ return wrapped;
795
+ }
610
796
  function registerServerReference() {
611
797
  throw new Error("registerServerReference must not be called in the client build");
612
798
  }
@@ -614,4 +800,4 @@ function getServerFunctionInvocation() {
614
800
  return undefined;
615
801
  }
616
802
 
617
- export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, isServerFunction, registerServerReference, serializeString, subscribeFlightData, withMeta };
803
+ export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerReference, serializeString, subscribeFlightData, withMeta };
@@ -28,6 +28,7 @@ function withMeta(fn, meta) {
28
28
  Object.assign(metadata, meta);
29
29
  return fn;
30
30
  }
31
+ const LIVE_SOURCE = Symbol.for("solid.LiveSource");
31
32
  const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
32
33
  function provideServerFunctionRPC(rpc) {
33
34
  globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
@@ -132,7 +133,7 @@ const BodyFormat = {
132
133
  Uint8Array: "7",
133
134
  Json: "8"
134
135
  };
135
- const JSON_SAFE_DEPTH_LIMIT = 10000;
136
+ const JSON_SAFE_DEPTH_LIMIT = 4096;
136
137
  const EXIT = {};
137
138
  function isJSONSafe(value) {
138
139
  const stack = [value];
@@ -159,6 +160,7 @@ function isJSONSafe(value) {
159
160
  } else {
160
161
  const proto = Object.getPrototypeOf(v);
161
162
  if (proto !== Object.prototype && proto !== null) return false;
163
+ if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
162
164
  for (const k in v) stack.push(v[k]);
163
165
  }
164
166
  }
@@ -321,27 +323,6 @@ class ChunkReader {
321
323
  }
322
324
  }
323
325
  }
324
- function serializeStream(value, codecOptions) {
325
- return new ReadableStream({
326
- async start(controller) {
327
- const {
328
- serializeJSON
329
- } = await import('@solidjs/web/serialization');
330
- serializeJSON(value, {
331
- ...codecOptions,
332
- onParse(node) {
333
- controller.enqueue(createChunk(JSON.stringify(node)));
334
- },
335
- onDone() {
336
- controller.close();
337
- },
338
- onError(error) {
339
- controller.error(error);
340
- }
341
- });
342
- }
343
- });
344
- }
345
326
  async function deserializeStream(source, codecOptions) {
346
327
  if (!source.body) {
347
328
  throw new Error("missing body");
@@ -356,7 +337,7 @@ async function deserializeStream(source, codecOptions) {
356
337
  function interpretChunk(chunk) {
357
338
  return deserializeChunk(JSON.parse(chunk));
358
339
  }
359
- void reader.drain(interpretChunk);
340
+ reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
360
341
  return interpretChunk(result.value);
361
342
  }
362
343
  return undefined;
@@ -381,6 +362,7 @@ async function decodeResponsePayload(response, codecOptions) {
381
362
  };
382
363
  }
383
364
 
365
+ typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
384
366
  const RequestContext = Symbol.for("solid.RequestContext");
385
367
  function getRequestEvent() {
386
368
  return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || solidJs.sharedConfig.context && solidJs.sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
@@ -512,7 +494,8 @@ const config = {
512
494
  transformFlightResult: undefined,
513
495
  transformDirectResult: undefined,
514
496
  handleNoJS: undefined,
515
- endpoint: "/_server"
497
+ endpoint: "/_server",
498
+ csrf: true
516
499
  };
517
500
  function configureServerFunctionsServer({
518
501
  provideEvent,
@@ -523,6 +506,7 @@ function configureServerFunctionsServer({
523
506
  transformDirectResult,
524
507
  handleNoJS,
525
508
  endpoint,
509
+ csrf,
526
510
  codec
527
511
  } = {}) {
528
512
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
@@ -533,6 +517,7 @@ function configureServerFunctionsServer({
533
517
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
534
518
  if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
535
519
  if (endpoint !== undefined) config.endpoint = endpoint;
520
+ if (csrf !== undefined) config.csrf = csrf;
536
521
  if (codec !== undefined) configureServerFunctionsCodec(codec);
537
522
  }
538
523
  function provideEvent(event, fn) {
@@ -636,6 +621,29 @@ function GET(fn) {
636
621
  method: "GET"
637
622
  });
638
623
  }
624
+ function live(fn) {
625
+ if (!isServerFunction(fn) || typeof fn.id !== "string") {
626
+ throw new Error("live expects a server function reference");
627
+ }
628
+ const metadata = {
629
+ ...getServerFunctionMetadata(fn),
630
+ live: true
631
+ };
632
+ const wrapped = async (...args) => {
633
+ const result = await fn(...args);
634
+ if (result !== null && typeof result === "object" && result[Symbol.asyncIterator]) {
635
+ result[LIVE_SOURCE] = true;
636
+ }
637
+ return result;
638
+ };
639
+ wrapped[SERVER_FUNCTION_METADATA] = metadata;
640
+ wrapped.id = fn.id;
641
+ Object.defineProperty(wrapped, "url", {
642
+ get: () => fn.url,
643
+ configurable: true
644
+ });
645
+ return wrapped;
646
+ }
639
647
  function getServerFunctionInvocation() {
640
648
  return getEventServerFunctionInvocation(getRequestEvent());
641
649
  }
@@ -807,14 +815,102 @@ function isFormPost(request) {
807
815
  const type = request.headers.get("content-type") || "";
808
816
  return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
809
817
  }
810
- function serializedResponse(value, headers, codec) {
818
+ function serializeResponseStream(value, codecOptions, signal) {
819
+ let closeIterator = null;
820
+ let closed = false;
821
+ let cancelSerialize = null;
822
+ let onAbort = null;
823
+ const teardown = () => {
824
+ if (closed) return;
825
+ closed = true;
826
+ if (onAbort) signal.removeEventListener("abort", onAbort);
827
+ if (cancelSerialize) cancelSerialize();
828
+ if (closeIterator) closeIterator();
829
+ };
830
+ if (value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
831
+ const source = value;
832
+ value = {
833
+ [Symbol.asyncIterator]() {
834
+ const it = source[Symbol.asyncIterator]();
835
+ let finished = false;
836
+ closeIterator = () => {
837
+ if (finished) return;
838
+ finished = true;
839
+ try {
840
+ const returned = it.return && it.return();
841
+ if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
842
+ } catch {}
843
+ };
844
+ if (closed) closeIterator();
845
+ return {
846
+ next: () => finished ? Promise.resolve({
847
+ done: true,
848
+ value: undefined
849
+ }) : it.next()
850
+ };
851
+ }
852
+ };
853
+ }
854
+ return new ReadableStream({
855
+ async start(controller) {
856
+ if (signal) {
857
+ if (signal.aborted) {
858
+ teardown();
859
+ controller.close();
860
+ return;
861
+ }
862
+ onAbort = () => {
863
+ const alreadyClosed = closed;
864
+ teardown();
865
+ if (!alreadyClosed) {
866
+ try {
867
+ controller.error(signal.reason || new Error("The operation was aborted."));
868
+ } catch {}
869
+ }
870
+ };
871
+ signal.addEventListener("abort", onAbort);
872
+ }
873
+ const {
874
+ serializeJSON
875
+ } = await import('@solidjs/web/serialization');
876
+ if (closed) {
877
+ try {
878
+ controller.close();
879
+ } catch {}
880
+ return;
881
+ }
882
+ cancelSerialize = serializeJSON(value, {
883
+ ...codecOptions,
884
+ onParse(node) {
885
+ if (!closed) controller.enqueue(createChunk(JSON.stringify(node)));
886
+ },
887
+ onDone() {
888
+ if (closed) return;
889
+ closed = true;
890
+ if (onAbort) signal.removeEventListener("abort", onAbort);
891
+ controller.close();
892
+ },
893
+ onError(error) {
894
+ if (closed) return;
895
+ closed = true;
896
+ if (onAbort) signal.removeEventListener("abort", onAbort);
897
+ controller.error(error);
898
+ }
899
+ });
900
+ },
901
+ cancel() {
902
+ teardown();
903
+ }
904
+ });
905
+ }
906
+ function serializedResponse(value, headers, codec, signal) {
811
907
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
812
908
  headers.set("Content-Type", "text/plain");
813
- return new Response(serializeStream(value, codec), {
909
+ return new Response(serializeResponseStream(value, codec, signal), {
814
910
  headers
815
911
  });
816
912
  }
817
- function encodeResult(value, headers, status, codec) {
913
+ function encodeResult(value, headers, status, codec, signal) {
818
914
  const direct = getHeadersAndBody(value);
819
915
  if (direct) {
820
916
  for (const [key, val] of Object.entries(direct.headers || {})) {
@@ -842,7 +938,7 @@ function encodeResult(value, headers, status, codec) {
842
938
  }
843
939
  } catch {
844
940
  }
845
- const response = serializedResponse(value, headers, codec);
941
+ const response = serializedResponse(value, headers, codec, signal);
846
942
  return status === 200 ? response : new Response(response.body, {
847
943
  status,
848
944
  headers
@@ -858,31 +954,96 @@ function sanitizeServerError(value) {
858
954
  if (isSafeError(value)) return value;
859
955
  return new Error(GENERIC_SERVER_ERROR_MESSAGE);
860
956
  }
957
+ function observeServerFunctionCalls() {
958
+ return () => {};
959
+ }
960
+ async function matchesOrigin(origin, request, matcher) {
961
+ if (matcher === undefined) return origin === new URL(request.url).origin;
962
+ if (typeof matcher === "function") return !!(await matcher(origin, request));
963
+ return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
964
+ }
965
+ async function allowsServerFunctionRequest(request, options) {
966
+ const fetchSite = request.headers.get("Sec-Fetch-Site");
967
+ if (fetchSite === "same-origin") return true;
968
+ if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
969
+ return false;
970
+ }
971
+ const origin = request.headers.get("Origin");
972
+ if (origin !== null) return matchesOrigin(origin, request, options.origin);
973
+ const referer = request.headers.get("Referer");
974
+ if (referer !== null) {
975
+ try {
976
+ return matchesOrigin(new URL(referer).origin, request, options.origin);
977
+ } catch {
978
+ return false;
979
+ }
980
+ }
981
+ return options.allowRequestsWithoutOriginCheck === true;
982
+ }
983
+ const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
984
+ function withCSRFVary(response) {
985
+ const current = response.headers.get("Vary");
986
+ if (current === "*") return response;
987
+ const values = current ? current.split(",").map(value => value.trim()) : [];
988
+ const names = new Set(values.map(value => value.toLowerCase()));
989
+ for (const value of CSRF_VARY) {
990
+ if (!names.has(value.toLowerCase())) values.push(value);
991
+ }
992
+ const vary = values.join(", ");
993
+ try {
994
+ response.headers.set("Vary", vary);
995
+ return response;
996
+ } catch {
997
+ const headers = new Headers(response.headers);
998
+ headers.set("Vary", vary);
999
+ return new Response(response.body, {
1000
+ status: response.status,
1001
+ statusText: response.statusText,
1002
+ headers
1003
+ });
1004
+ }
1005
+ }
1006
+ function forbiddenResponse() {
1007
+ return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
1008
+ status: 403,
1009
+ headers: {
1010
+ "Cache-Control": "no-store"
1011
+ }
1012
+ }));
1013
+ }
861
1014
  async function handleServerFunctionRequest(request, options = {}) {
862
1015
  const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
863
1016
  const url = new URL(request.url);
1017
+ const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
1018
+ const protectsRequest = csrf !== false;
1019
+ if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
1020
+ return forbiddenResponse();
1021
+ }
864
1022
  const instance = request.headers.get(INSTANCE_HEADER);
865
1023
  const functionId = resolveFunctionId(request, url);
866
1024
  if (!functionId) {
867
- return new Response(DEV ? "Server function not found" : null, {
1025
+ const response = new Response(DEV ? "Server function not found" : null, {
868
1026
  status: 404
869
1027
  });
1028
+ return protectsRequest ? withCSRFVary(response) : response;
870
1029
  }
871
1030
  let serverFunction;
872
1031
  try {
873
1032
  serverFunction = getServerFunction(functionId);
874
1033
  } catch {
875
- return new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1034
+ const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
876
1035
  status: 404
877
1036
  });
1037
+ return protectsRequest ? withCSRFVary(response) : response;
878
1038
  }
879
1039
  if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
880
- return new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
1040
+ const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
881
1041
  status: 405,
882
1042
  headers: {
883
1043
  Allow: "POST"
884
1044
  }
885
1045
  });
1046
+ return protectsRequest ? withCSRFVary(response) : response;
886
1047
  }
887
1048
  const event = options.createEvent ? options.createEvent(request) : {
888
1049
  request,
@@ -970,9 +1131,9 @@ async function handleServerFunctionRequest(request, options = {}) {
970
1131
  if (!instance) {
971
1132
  if (handleNoJS) return handleNoJS(result, request, parsed);
972
1133
  if (result instanceof Response) return result;
973
- return encodeResult(result, headers, 200, codec);
1134
+ return encodeResult(result, headers, 200, codec, request.signal);
974
1135
  }
975
- return encodeResult(result, headers, status, codec);
1136
+ return encodeResult(result, headers, status, codec, request.signal);
976
1137
  } catch (x) {
977
1138
  if (x instanceof Response || isResponseEnvelope(x)) {
978
1139
  if (transformResult) {
@@ -1026,7 +1187,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1026
1187
  if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
1027
1188
  if (x instanceof Response) return x;
1028
1189
  }
1029
- return encodeResult(x, headers, status, codec);
1190
+ return encodeResult(x, headers, status, codec, request.signal);
1030
1191
  }
1031
1192
  const safe = sanitizeServerError(x);
1032
1193
  if (!instance) {
@@ -1038,10 +1199,11 @@ async function handleServerFunctionRequest(request, options = {}) {
1038
1199
  }
1039
1200
  const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
1040
1201
  headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
1041
- return encodeResult(safe, headers, 200, codec);
1202
+ return encodeResult(safe, headers, 200, codec, request.signal);
1042
1203
  }
1043
1204
  };
1044
- return commitEventResponse(await dispatch(), event);
1205
+ const response = commitEventResponse(await dispatch(), event);
1206
+ return protectsRequest ? withCSRFVary(response) : response;
1045
1207
  }
1046
1208
 
1047
1209
  exports.ERROR_HEADER = ERROR_HEADER;
@@ -1069,9 +1231,12 @@ exports.getServerFunctionMetadata = getServerFunctionMetadata;
1069
1231
  exports.handleServerFunctionRequest = handleServerFunctionRequest;
1070
1232
  exports.hasFlashCookie = hasFlashCookie;
1071
1233
  exports.isServerFunction = isServerFunction;
1234
+ exports.live = live;
1235
+ exports.observeServerFunctionCalls = observeServerFunctionCalls;
1072
1236
  exports.registerServerFunction = registerServerFunction;
1073
1237
  exports.registerServerReference = registerServerReference;
1074
1238
  exports.sanitizeServerError = sanitizeServerError;
1239
+ exports.serializeResponseStream = serializeResponseStream;
1075
1240
  exports.setServerFunctionsDev = setServerFunctionsDev;
1076
1241
  exports.subscribeFlightData = subscribeFlightData;
1077
1242
  exports.withMeta = withMeta;