@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
@@ -26,6 +26,7 @@ function withMeta(fn, meta) {
26
26
  Object.assign(metadata, meta);
27
27
  return fn;
28
28
  }
29
+ const LIVE_SOURCE = Symbol.for("solid.LiveSource");
29
30
  const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
30
31
  function provideServerFunctionRPC(rpc) {
31
32
  globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
@@ -130,7 +131,7 @@ const BodyFormat = {
130
131
  Uint8Array: "7",
131
132
  Json: "8"
132
133
  };
133
- const JSON_SAFE_DEPTH_LIMIT = 10000;
134
+ const JSON_SAFE_DEPTH_LIMIT = 4096;
134
135
  const EXIT = {};
135
136
  function isJSONSafe(value) {
136
137
  const stack = [value];
@@ -157,6 +158,7 @@ function isJSONSafe(value) {
157
158
  } else {
158
159
  const proto = Object.getPrototypeOf(v);
159
160
  if (proto !== Object.prototype && proto !== null) return false;
161
+ if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
160
162
  for (const k in v) stack.push(v[k]);
161
163
  }
162
164
  }
@@ -319,27 +321,6 @@ class ChunkReader {
319
321
  }
320
322
  }
321
323
  }
322
- function serializeStream(value, codecOptions) {
323
- return new ReadableStream({
324
- async start(controller) {
325
- const {
326
- serializeJSON
327
- } = await import('@solidjs/web/serialization');
328
- serializeJSON(value, {
329
- ...codecOptions,
330
- onParse(node) {
331
- controller.enqueue(createChunk(JSON.stringify(node)));
332
- },
333
- onDone() {
334
- controller.close();
335
- },
336
- onError(error) {
337
- controller.error(error);
338
- }
339
- });
340
- }
341
- });
342
- }
343
324
  async function deserializeStream(source, codecOptions) {
344
325
  if (!source.body) {
345
326
  throw new Error("missing body");
@@ -354,7 +335,7 @@ async function deserializeStream(source, codecOptions) {
354
335
  function interpretChunk(chunk) {
355
336
  return deserializeChunk(JSON.parse(chunk));
356
337
  }
357
- void reader.drain(interpretChunk);
338
+ reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
358
339
  return interpretChunk(result.value);
359
340
  }
360
341
  return undefined;
@@ -379,6 +360,7 @@ async function decodeResponsePayload(response, codecOptions) {
379
360
  };
380
361
  }
381
362
 
363
+ typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
382
364
  const RequestContext = Symbol.for("solid.RequestContext");
383
365
  function getRequestEvent() {
384
366
  return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || sharedConfig.context && 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;
@@ -510,7 +492,8 @@ const config = {
510
492
  transformFlightResult: undefined,
511
493
  transformDirectResult: undefined,
512
494
  handleNoJS: undefined,
513
- endpoint: "/_server"
495
+ endpoint: "/_server",
496
+ csrf: true
514
497
  };
515
498
  function configureServerFunctionsServer({
516
499
  provideEvent,
@@ -521,6 +504,7 @@ function configureServerFunctionsServer({
521
504
  transformDirectResult,
522
505
  handleNoJS,
523
506
  endpoint,
507
+ csrf,
524
508
  codec
525
509
  } = {}) {
526
510
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
@@ -531,6 +515,7 @@ function configureServerFunctionsServer({
531
515
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
532
516
  if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
533
517
  if (endpoint !== undefined) config.endpoint = endpoint;
518
+ if (csrf !== undefined) config.csrf = csrf;
534
519
  if (codec !== undefined) configureServerFunctionsCodec(codec);
535
520
  }
536
521
  function provideEvent(event, fn) {
@@ -634,6 +619,29 @@ function GET(fn) {
634
619
  method: "GET"
635
620
  });
636
621
  }
622
+ function live(fn) {
623
+ if (!isServerFunction(fn) || typeof fn.id !== "string") {
624
+ throw new Error("live expects a server function reference");
625
+ }
626
+ const metadata = {
627
+ ...getServerFunctionMetadata(fn),
628
+ live: true
629
+ };
630
+ const wrapped = async (...args) => {
631
+ const result = await fn(...args);
632
+ if (result !== null && typeof result === "object" && result[Symbol.asyncIterator]) {
633
+ result[LIVE_SOURCE] = true;
634
+ }
635
+ return result;
636
+ };
637
+ wrapped[SERVER_FUNCTION_METADATA] = metadata;
638
+ wrapped.id = fn.id;
639
+ Object.defineProperty(wrapped, "url", {
640
+ get: () => fn.url,
641
+ configurable: true
642
+ });
643
+ return wrapped;
644
+ }
637
645
  function getServerFunctionInvocation() {
638
646
  return getEventServerFunctionInvocation(getRequestEvent());
639
647
  }
@@ -805,14 +813,102 @@ function isFormPost(request) {
805
813
  const type = request.headers.get("content-type") || "";
806
814
  return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
807
815
  }
808
- function serializedResponse(value, headers, codec) {
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();
827
+ };
828
+ if (value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
829
+ const source = value;
830
+ value = {
831
+ [Symbol.asyncIterator]() {
832
+ const it = source[Symbol.asyncIterator]();
833
+ let finished = false;
834
+ closeIterator = () => {
835
+ if (finished) return;
836
+ finished = true;
837
+ try {
838
+ const returned = it.return && it.return();
839
+ if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
840
+ } catch {}
841
+ };
842
+ if (closed) closeIterator();
843
+ return {
844
+ next: () => finished ? Promise.resolve({
845
+ done: true,
846
+ value: undefined
847
+ }) : it.next()
848
+ };
849
+ }
850
+ };
851
+ }
852
+ return new ReadableStream({
853
+ async start(controller) {
854
+ if (signal) {
855
+ if (signal.aborted) {
856
+ teardown();
857
+ controller.close();
858
+ return;
859
+ }
860
+ onAbort = () => {
861
+ const alreadyClosed = closed;
862
+ teardown();
863
+ if (!alreadyClosed) {
864
+ try {
865
+ controller.error(signal.reason || new Error("The operation was aborted."));
866
+ } catch {}
867
+ }
868
+ };
869
+ signal.addEventListener("abort", onAbort);
870
+ }
871
+ const {
872
+ serializeJSON
873
+ } = await import('@solidjs/web/serialization');
874
+ if (closed) {
875
+ try {
876
+ controller.close();
877
+ } catch {}
878
+ return;
879
+ }
880
+ cancelSerialize = serializeJSON(value, {
881
+ ...codecOptions,
882
+ onParse(node) {
883
+ if (!closed) controller.enqueue(createChunk(JSON.stringify(node)));
884
+ },
885
+ onDone() {
886
+ if (closed) return;
887
+ closed = true;
888
+ if (onAbort) signal.removeEventListener("abort", onAbort);
889
+ controller.close();
890
+ },
891
+ onError(error) {
892
+ if (closed) return;
893
+ closed = true;
894
+ if (onAbort) signal.removeEventListener("abort", onAbort);
895
+ controller.error(error);
896
+ }
897
+ });
898
+ },
899
+ cancel() {
900
+ teardown();
901
+ }
902
+ });
903
+ }
904
+ function serializedResponse(value, headers, codec, signal) {
809
905
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
810
906
  headers.set("Content-Type", "text/plain");
811
- return new Response(serializeStream(value, codec), {
907
+ return new Response(serializeResponseStream(value, codec, signal), {
812
908
  headers
813
909
  });
814
910
  }
815
- function encodeResult(value, headers, status, codec) {
911
+ function encodeResult(value, headers, status, codec, signal) {
816
912
  const direct = getHeadersAndBody(value);
817
913
  if (direct) {
818
914
  for (const [key, val] of Object.entries(direct.headers || {})) {
@@ -840,7 +936,7 @@ function encodeResult(value, headers, status, codec) {
840
936
  }
841
937
  } catch {
842
938
  }
843
- const response = serializedResponse(value, headers, codec);
939
+ const response = serializedResponse(value, headers, codec, signal);
844
940
  return status === 200 ? response : new Response(response.body, {
845
941
  status,
846
942
  headers
@@ -856,31 +952,96 @@ function sanitizeServerError(value) {
856
952
  if (isSafeError(value)) return value;
857
953
  return new Error(GENERIC_SERVER_ERROR_MESSAGE);
858
954
  }
955
+ function observeServerFunctionCalls() {
956
+ return () => {};
957
+ }
958
+ async function matchesOrigin(origin, request, matcher) {
959
+ if (matcher === undefined) return origin === new URL(request.url).origin;
960
+ if (typeof matcher === "function") return !!(await matcher(origin, request));
961
+ return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
962
+ }
963
+ async function allowsServerFunctionRequest(request, options) {
964
+ const fetchSite = request.headers.get("Sec-Fetch-Site");
965
+ if (fetchSite === "same-origin") return true;
966
+ if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
967
+ return false;
968
+ }
969
+ const origin = request.headers.get("Origin");
970
+ if (origin !== null) return matchesOrigin(origin, request, options.origin);
971
+ const referer = request.headers.get("Referer");
972
+ if (referer !== null) {
973
+ try {
974
+ return matchesOrigin(new URL(referer).origin, request, options.origin);
975
+ } catch {
976
+ return false;
977
+ }
978
+ }
979
+ return options.allowRequestsWithoutOriginCheck === true;
980
+ }
981
+ const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
982
+ function withCSRFVary(response) {
983
+ const current = response.headers.get("Vary");
984
+ if (current === "*") return response;
985
+ const values = current ? current.split(",").map(value => value.trim()) : [];
986
+ const names = new Set(values.map(value => value.toLowerCase()));
987
+ for (const value of CSRF_VARY) {
988
+ if (!names.has(value.toLowerCase())) values.push(value);
989
+ }
990
+ const vary = values.join(", ");
991
+ try {
992
+ response.headers.set("Vary", vary);
993
+ return response;
994
+ } catch {
995
+ const headers = new Headers(response.headers);
996
+ headers.set("Vary", vary);
997
+ return new Response(response.body, {
998
+ status: response.status,
999
+ statusText: response.statusText,
1000
+ headers
1001
+ });
1002
+ }
1003
+ }
1004
+ function forbiddenResponse() {
1005
+ return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
1006
+ status: 403,
1007
+ headers: {
1008
+ "Cache-Control": "no-store"
1009
+ }
1010
+ }));
1011
+ }
859
1012
  async function handleServerFunctionRequest(request, options = {}) {
860
1013
  const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
861
1014
  const url = new URL(request.url);
1015
+ const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
1016
+ const protectsRequest = csrf !== false;
1017
+ if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
1018
+ return forbiddenResponse();
1019
+ }
862
1020
  const instance = request.headers.get(INSTANCE_HEADER);
863
1021
  const functionId = resolveFunctionId(request, url);
864
1022
  if (!functionId) {
865
- return new Response(DEV ? "Server function not found" : null, {
1023
+ const response = new Response(DEV ? "Server function not found" : null, {
866
1024
  status: 404
867
1025
  });
1026
+ return protectsRequest ? withCSRFVary(response) : response;
868
1027
  }
869
1028
  let serverFunction;
870
1029
  try {
871
1030
  serverFunction = getServerFunction(functionId);
872
1031
  } catch {
873
- return new Response(DEV ? `Unknown server function: ${functionId}` : null, {
1032
+ const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
874
1033
  status: 404
875
1034
  });
1035
+ return protectsRequest ? withCSRFVary(response) : response;
876
1036
  }
877
1037
  if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
878
- return new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
1038
+ const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
879
1039
  status: 405,
880
1040
  headers: {
881
1041
  Allow: "POST"
882
1042
  }
883
1043
  });
1044
+ return protectsRequest ? withCSRFVary(response) : response;
884
1045
  }
885
1046
  const event = options.createEvent ? options.createEvent(request) : {
886
1047
  request,
@@ -968,9 +1129,9 @@ async function handleServerFunctionRequest(request, options = {}) {
968
1129
  if (!instance) {
969
1130
  if (handleNoJS) return handleNoJS(result, request, parsed);
970
1131
  if (result instanceof Response) return result;
971
- return encodeResult(result, headers, 200, codec);
1132
+ return encodeResult(result, headers, 200, codec, request.signal);
972
1133
  }
973
- return encodeResult(result, headers, status, codec);
1134
+ return encodeResult(result, headers, status, codec, request.signal);
974
1135
  } catch (x) {
975
1136
  if (x instanceof Response || isResponseEnvelope(x)) {
976
1137
  if (transformResult) {
@@ -1024,7 +1185,7 @@ async function handleServerFunctionRequest(request, options = {}) {
1024
1185
  if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
1025
1186
  if (x instanceof Response) return x;
1026
1187
  }
1027
- return encodeResult(x, headers, status, codec);
1188
+ return encodeResult(x, headers, status, codec, request.signal);
1028
1189
  }
1029
1190
  const safe = sanitizeServerError(x);
1030
1191
  if (!instance) {
@@ -1036,10 +1197,11 @@ async function handleServerFunctionRequest(request, options = {}) {
1036
1197
  }
1037
1198
  const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
1038
1199
  headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
1039
- return encodeResult(safe, headers, 200, codec);
1200
+ return encodeResult(safe, headers, 200, codec, request.signal);
1040
1201
  }
1041
1202
  };
1042
- return commitEventResponse(await dispatch(), event);
1203
+ const response = commitEventResponse(await dispatch(), event);
1204
+ return protectsRequest ? withCSRFVary(response) : response;
1043
1205
  }
1044
1206
 
1045
- 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, registerServerFunction, registerServerReference, sanitizeServerError, setServerFunctionsDev, subscribeFlightData, withMeta };
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 };