@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.
- package/README.md +1 -1
- package/dist/dev.cjs +40 -11
- package/dist/dev.js +39 -12
- package/dist/server.cjs +419 -69
- package/dist/server.js +411 -73
- package/dist/web.cjs +37 -11
- package/dist/web.js +36 -12
- package/frames/dist/client.cjs +94 -8
- package/frames/dist/client.dev.cjs +97 -8
- package/frames/dist/client.dev.js +98 -9
- package/frames/dist/client.js +95 -9
- package/frames/dist/server.cjs +266 -56
- package/frames/dist/server.js +267 -57
- package/package.json +4 -3
- package/serialization/dist/decode.cjs +32 -3
- package/serialization/dist/decode.js +33 -4
- package/serialization/dist/serialization.cjs +32 -3
- package/serialization/dist/serialization.js +33 -4
- package/server-functions/dist/client.cjs +196 -8
- package/server-functions/dist/client.js +195 -9
- package/server-functions/dist/server.cjs +201 -36
- package/server-functions/dist/server.dev.cjs +201 -36
- package/server-functions/dist/server.dev.js +199 -37
- package/server-functions/dist/server.js +199 -37
- package/types/core.d.ts +3 -0
- package/types/frames/frame-client.d.ts +18 -0
- package/types/index.d.ts +16 -2
- package/types/jsx.d.ts +9 -0
- package/types/server-functions/client.d.ts +61 -0
- package/types/server-functions/server.d.ts +94 -1
- package/types/server-mock.d.ts +11 -2
- package/types/server.d.ts +23 -2
- package/types-cjs/core.d.cts +3 -0
- package/types-cjs/frames/frame-client.d.cts +18 -0
- package/types-cjs/index.d.cts +16 -2
- package/types-cjs/jsx.d.cts +9 -0
- package/types-cjs/server-functions/client.d.cts +61 -0
- package/types-cjs/server-functions/server.d.cts +94 -1
- package/types-cjs/server-mock.d.cts +11 -2
- 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 =
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|