@solidjs/web 2.0.0-rc.2 → 2.0.0-rc.4
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/dist/dev.cjs +433 -72
- package/dist/dev.js +426 -71
- package/dist/server.cjs +39 -21
- package/dist/server.js +40 -23
- package/dist/web.cjs +421 -70
- package/dist/web.js +414 -69
- package/frames/dist/client.cjs +8 -8
- package/frames/dist/client.dev.cjs +8 -8
- package/frames/dist/client.dev.js +8 -8
- package/frames/dist/client.js +8 -8
- package/frames/dist/server.cjs +28 -18
- package/frames/dist/server.js +29 -19
- package/package.json +20 -22
- package/serialization/dist/decode.cjs +3 -3
- package/serialization/dist/decode.js +3 -3
- package/serialization/dist/serialization.cjs +7 -11
- package/serialization/dist/serialization.js +7 -6
- package/serialization/types/index.d.ts +69 -157
- package/serialization/types/serializer-decode.d.ts +89 -112
- package/serialization/types/serializer.d.ts +94 -0
- package/serialization/types-cjs/index.d.cts +69 -157
- package/serialization/types-cjs/serializer-decode.d.cts +89 -112
- package/serialization/types-cjs/serializer.d.cts +94 -0
- package/server-functions/dist/client.cjs +126 -19
- package/server-functions/dist/client.js +123 -19
- package/server-functions/dist/server.cjs +156 -28
- package/server-functions/dist/server.dev.cjs +156 -28
- package/server-functions/dist/server.dev.js +153 -28
- package/server-functions/dist/server.js +153 -28
- package/types/client.d.ts +152 -290
- package/types/constants.d.ts +18 -0
- package/types/cookies.d.ts +31 -75
- package/types/frames/frame-client.d.ts +287 -277
- package/types/frames/frame-container-plugin.d.ts +71 -0
- package/types/frames/frame-sink.d.ts +58 -160
- package/types/frames/frame-transport.d.ts +161 -194
- package/types/frames/serializer-decode.d.ts +159 -0
- package/types/frames/serializer.d.ts +69 -157
- package/types/head.d.ts +16 -0
- package/types/index.d.ts +1 -65
- package/types/index.server.d.ts +125 -0
- package/types/jsx.d.ts +5 -13
- package/types/patch-driver.d.ts +3 -0
- package/types/reconcile.d.ts +1 -0
- package/types/render.d.ts +4 -0
- package/types/response.d.ts +48 -152
- package/types/serializer-decode.d.ts +89 -112
- package/types/serializer.d.ts +69 -157
- package/types/server-functions/client.d.ts +124 -222
- package/types/server-functions/flash.d.ts +10 -30
- package/types/server-functions/registry.d.ts +100 -0
- package/types/server-functions/rich-args.d.ts +1 -10
- package/types/server-functions/server.d.ts +295 -574
- package/types/server-functions/shared.d.ts +155 -458
- package/types/server-mock.d.ts +2 -2
- package/types/server.d.ts +242 -434
- package/types-cjs/client.d.cts +152 -290
- package/types-cjs/constants.d.cts +18 -0
- package/types-cjs/cookies.d.cts +31 -75
- package/types-cjs/frames/frame-client.d.cts +287 -277
- package/types-cjs/frames/frame-container-plugin.d.cts +71 -0
- package/types-cjs/frames/frame-sink.d.cts +58 -160
- package/types-cjs/frames/frame-transport.d.cts +161 -194
- package/types-cjs/frames/serializer-decode.d.cts +159 -0
- package/types-cjs/frames/serializer.d.cts +69 -157
- package/types-cjs/head.d.cts +16 -0
- package/types-cjs/index.d.cts +1 -65
- package/types-cjs/index.server.d.cts +125 -0
- package/types-cjs/jsx.d.cts +5 -13
- package/types-cjs/patch-driver.d.cts +3 -0
- package/types-cjs/reconcile.d.cts +1 -0
- package/types-cjs/render.d.cts +4 -0
- package/types-cjs/response.d.cts +48 -152
- package/types-cjs/serializer-decode.d.cts +89 -112
- package/types-cjs/serializer.d.cts +69 -157
- package/types-cjs/server-functions/client.d.cts +124 -222
- package/types-cjs/server-functions/flash.d.cts +10 -30
- package/types-cjs/server-functions/registry.d.cts +100 -0
- package/types-cjs/server-functions/rich-args.d.cts +1 -10
- package/types-cjs/server-functions/server.d.cts +295 -574
- package/types-cjs/server-functions/shared.d.cts +155 -458
- package/types-cjs/server-mock.d.cts +2 -2
- package/types-cjs/server.d.cts +242 -434
- package/LICENSE +0 -21
- package/types/core.d.ts +0 -9
- package/types-cjs/core.d.cts +0 -9
|
@@ -28,6 +28,32 @@ function withMeta(fn, meta) {
|
|
|
28
28
|
Object.assign(metadata, meta);
|
|
29
29
|
return fn;
|
|
30
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
|
+
}
|
|
31
57
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
32
58
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
33
59
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -92,7 +118,23 @@ function subscribeFlightData(consumer) {
|
|
|
92
118
|
return () => {
|
|
93
119
|
};
|
|
94
120
|
}
|
|
95
|
-
|
|
121
|
+
function serverFunctionAddress(endpoint, id) {
|
|
122
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
123
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
124
|
+
}
|
|
125
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
126
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
127
|
+
if (!pathname.startsWith(mount)) return null;
|
|
128
|
+
const rest = pathname.slice(mount.length);
|
|
129
|
+
if (!rest.startsWith("/")) return null;
|
|
130
|
+
const segment = rest.slice(1);
|
|
131
|
+
if (!segment || segment.includes("/")) return null;
|
|
132
|
+
try {
|
|
133
|
+
return decodeURIComponent(segment);
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
96
138
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
97
139
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
98
140
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -131,7 +173,8 @@ const BodyFormat = {
|
|
|
131
173
|
File: "5",
|
|
132
174
|
ArrayBuffer: "6",
|
|
133
175
|
Uint8Array: "7",
|
|
134
|
-
Json: "8"
|
|
176
|
+
Json: "8",
|
|
177
|
+
Void: "9"
|
|
135
178
|
};
|
|
136
179
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
137
180
|
const EXIT = {};
|
|
@@ -551,6 +594,9 @@ function getServerFunction(id) {
|
|
|
551
594
|
throw new Error("invalid server function: " + id);
|
|
552
595
|
}
|
|
553
596
|
function registerServerReference(id, fn, name) {
|
|
597
|
+
if (typeof fn !== "function") {
|
|
598
|
+
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.`);
|
|
599
|
+
}
|
|
554
600
|
registerServerFunction(id, fn);
|
|
555
601
|
return {
|
|
556
602
|
id,
|
|
@@ -558,6 +604,33 @@ function registerServerReference(id, fn, name) {
|
|
|
558
604
|
name
|
|
559
605
|
};
|
|
560
606
|
}
|
|
607
|
+
function inProcessInvoker(call) {
|
|
608
|
+
return (args, options) => {
|
|
609
|
+
const signal = options && options.signal;
|
|
610
|
+
if (!signal) return call(...args);
|
|
611
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
612
|
+
return new Promise((resolve, reject) => {
|
|
613
|
+
const onAbort = () => reject(signal.reason);
|
|
614
|
+
signal.addEventListener("abort", onAbort, {
|
|
615
|
+
once: true
|
|
616
|
+
});
|
|
617
|
+
let result;
|
|
618
|
+
try {
|
|
619
|
+
result = call(...args);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
signal.removeEventListener("abort", onAbort);
|
|
622
|
+
return reject(error);
|
|
623
|
+
}
|
|
624
|
+
Promise.resolve(result).then(value => {
|
|
625
|
+
signal.removeEventListener("abort", onAbort);
|
|
626
|
+
resolve(value);
|
|
627
|
+
}, error => {
|
|
628
|
+
signal.removeEventListener("abort", onAbort);
|
|
629
|
+
reject(error);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
}
|
|
561
634
|
function createServerReference({
|
|
562
635
|
id,
|
|
563
636
|
fn,
|
|
@@ -568,13 +641,15 @@ function createServerReference({
|
|
|
568
641
|
const metadata = name === undefined ? {} : {
|
|
569
642
|
name
|
|
570
643
|
};
|
|
571
|
-
|
|
644
|
+
const invokeChannel = inProcessInvoker((...args) => proxy(...args));
|
|
645
|
+
const proxy = new Proxy(fn, {
|
|
572
646
|
get(target, prop) {
|
|
573
647
|
if (prop === "id") return id;
|
|
574
648
|
if (prop === "url") {
|
|
575
|
-
return
|
|
649
|
+
return serverFunctionAddress(config.endpoint, id);
|
|
576
650
|
}
|
|
577
651
|
if (prop === SERVER_FUNCTION_METADATA) return metadata;
|
|
652
|
+
if (prop === SERVER_FUNCTION_INVOKE) return invokeChannel;
|
|
578
653
|
return target[prop];
|
|
579
654
|
},
|
|
580
655
|
apply(target, thisArg, args) {
|
|
@@ -611,6 +686,7 @@ function createServerReference({
|
|
|
611
686
|
}) : result;
|
|
612
687
|
}
|
|
613
688
|
});
|
|
689
|
+
return proxy;
|
|
614
690
|
}
|
|
615
691
|
function GET(fn) {
|
|
616
692
|
if (!isServerFunction(fn) || typeof fn.id !== "string") {
|
|
@@ -637,6 +713,7 @@ function live(fn) {
|
|
|
637
713
|
return result;
|
|
638
714
|
};
|
|
639
715
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
716
|
+
wrapped[SERVER_FUNCTION_INVOKE] = inProcessInvoker(wrapped);
|
|
640
717
|
wrapped.id = fn.id;
|
|
641
718
|
Object.defineProperty(wrapped, "url", {
|
|
642
719
|
get: () => fn.url,
|
|
@@ -650,24 +727,23 @@ function getServerFunctionInvocation() {
|
|
|
650
727
|
function getEventServerFunctionInvocation(event) {
|
|
651
728
|
return event && INVOCATIONS.get(event);
|
|
652
729
|
}
|
|
653
|
-
function resolveFunctionId(
|
|
654
|
-
|
|
655
|
-
if (reference) {
|
|
656
|
-
return reference.split("#")[0];
|
|
657
|
-
}
|
|
658
|
-
return url.searchParams.get("id");
|
|
730
|
+
function resolveFunctionId(url) {
|
|
731
|
+
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
659
732
|
}
|
|
660
733
|
async function parseArguments(request, url, instance, codec) {
|
|
661
734
|
const parsed = [];
|
|
662
735
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
736
|
+
const args = url.searchParams.get("args");
|
|
737
|
+
if (args && (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
738
|
+
const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
|
|
739
|
+
if (!Array.isArray(result)) {
|
|
740
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
741
|
+
}
|
|
742
|
+
for (const arg of result) {
|
|
743
|
+
parsed.push(arg);
|
|
670
744
|
}
|
|
745
|
+
} else if (!args && url.search && (request.method === "GET" || request.method === "HEAD")) {
|
|
746
|
+
parsed.push(url.searchParams);
|
|
671
747
|
}
|
|
672
748
|
if (request.method === "POST" && request.body !== null) {
|
|
673
749
|
const decoded = await extractBody(request.clone(), codec);
|
|
@@ -922,6 +998,7 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
922
998
|
});
|
|
923
999
|
}
|
|
924
1000
|
if (value === undefined) {
|
|
1001
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
925
1002
|
return new Response(null, {
|
|
926
1003
|
status,
|
|
927
1004
|
headers
|
|
@@ -957,6 +1034,17 @@ function sanitizeServerError(value) {
|
|
|
957
1034
|
function observeServerFunctionCalls() {
|
|
958
1035
|
return () => {};
|
|
959
1036
|
}
|
|
1037
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
1038
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
1039
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
1040
|
+
if (!isJSONSafe(boundArgs)) {
|
|
1041
|
+
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.");
|
|
1042
|
+
}
|
|
1043
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1044
|
+
}
|
|
1045
|
+
function parseServerFunctionUrl(url) {
|
|
1046
|
+
return parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1047
|
+
}
|
|
960
1048
|
async function matchesOrigin(origin, request, matcher) {
|
|
961
1049
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
962
1050
|
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
@@ -1014,18 +1102,20 @@ function forbiddenResponse() {
|
|
|
1014
1102
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1015
1103
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
1016
1104
|
const url = new URL(request.url);
|
|
1105
|
+
const method = request.method;
|
|
1106
|
+
const functionId = resolveFunctionId(url);
|
|
1107
|
+
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1017
1108
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1018
|
-
const protectsRequest = csrf !== false;
|
|
1109
|
+
const protectsRequest = csrf !== false && !declaredRead;
|
|
1019
1110
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1020
|
-
return forbiddenResponse();
|
|
1111
|
+
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1021
1112
|
}
|
|
1022
1113
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
1023
|
-
const functionId = resolveFunctionId(request, url);
|
|
1024
1114
|
if (!functionId) {
|
|
1025
1115
|
const response = new Response(DEV ? "Server function not found" : null, {
|
|
1026
1116
|
status: 404
|
|
1027
1117
|
});
|
|
1028
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1118
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1029
1119
|
}
|
|
1030
1120
|
let serverFunction;
|
|
1031
1121
|
try {
|
|
@@ -1034,16 +1124,16 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1034
1124
|
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1035
1125
|
status: 404
|
|
1036
1126
|
});
|
|
1037
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1127
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1038
1128
|
}
|
|
1039
|
-
if (
|
|
1129
|
+
if (method !== "POST" && !declaredRead) {
|
|
1040
1130
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1041
1131
|
status: 405,
|
|
1042
1132
|
headers: {
|
|
1043
|
-
Allow: "POST"
|
|
1133
|
+
Allow: METHODS.get(functionId) === "GET" ? "POST, GET, HEAD" : "POST"
|
|
1044
1134
|
}
|
|
1045
1135
|
});
|
|
1046
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1136
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1047
1137
|
}
|
|
1048
1138
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
1049
1139
|
request,
|
|
@@ -1056,7 +1146,15 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1056
1146
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1057
1147
|
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
1058
1148
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
1059
|
-
|
|
1149
|
+
let parsed;
|
|
1150
|
+
try {
|
|
1151
|
+
parsed = await parseArguments(request, url, instance, codec);
|
|
1152
|
+
} catch {
|
|
1153
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1154
|
+
status: 400
|
|
1155
|
+
});
|
|
1156
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1157
|
+
}
|
|
1060
1158
|
const flightContext = {
|
|
1061
1159
|
id: functionId,
|
|
1062
1160
|
args: parsed,
|
|
@@ -1203,15 +1301,42 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1203
1301
|
}
|
|
1204
1302
|
};
|
|
1205
1303
|
const response = commitEventResponse(await dispatch(), event);
|
|
1206
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1304
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1305
|
+
}
|
|
1306
|
+
function finalizeTransportResponse(response, method) {
|
|
1307
|
+
const stripBody = method === "HEAD" && response.body !== null;
|
|
1308
|
+
if (stripBody || !response.headers.has("Cache-Control")) {
|
|
1309
|
+
try {
|
|
1310
|
+
if (!response.headers.has("Cache-Control")) {
|
|
1311
|
+
response.headers.set("Cache-Control", "no-store");
|
|
1312
|
+
}
|
|
1313
|
+
if (!stripBody) return response;
|
|
1314
|
+
response.body.cancel().catch(() => {});
|
|
1315
|
+
return new Response(null, {
|
|
1316
|
+
status: response.status,
|
|
1317
|
+
statusText: response.statusText,
|
|
1318
|
+
headers: response.headers
|
|
1319
|
+
});
|
|
1320
|
+
} catch {
|
|
1321
|
+
const headers = new Headers(response.headers);
|
|
1322
|
+
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1323
|
+
if (stripBody) response.body.cancel().catch(() => {});
|
|
1324
|
+
return new Response(stripBody ? null : response.body, {
|
|
1325
|
+
status: response.status,
|
|
1326
|
+
statusText: response.statusText,
|
|
1327
|
+
headers
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return response;
|
|
1207
1332
|
}
|
|
1208
1333
|
|
|
1209
1334
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
1210
1335
|
exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
1211
|
-
exports.FUNCTION_HEADER = FUNCTION_HEADER;
|
|
1212
1336
|
exports.GENERIC_SERVER_ERROR_MESSAGE = GENERIC_SERVER_ERROR_MESSAGE;
|
|
1213
1337
|
exports.GET = GET;
|
|
1214
1338
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
1339
|
+
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
1215
1340
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
1216
1341
|
exports.clearFlashCookie = clearFlashCookie;
|
|
1217
1342
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
@@ -1230,13 +1355,16 @@ exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
|
1230
1355
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
1231
1356
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
1232
1357
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1358
|
+
exports.invoke = invoke;
|
|
1233
1359
|
exports.isServerFunction = isServerFunction;
|
|
1234
1360
|
exports.live = live;
|
|
1235
1361
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1362
|
+
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
1236
1363
|
exports.registerServerFunction = registerServerFunction;
|
|
1237
1364
|
exports.registerServerReference = registerServerReference;
|
|
1238
1365
|
exports.sanitizeServerError = sanitizeServerError;
|
|
1239
1366
|
exports.serializeResponseStream = serializeResponseStream;
|
|
1367
|
+
exports.serverFunctionUrl = serverFunctionUrl;
|
|
1240
1368
|
exports.setServerFunctionsDev = setServerFunctionsDev;
|
|
1241
1369
|
exports.subscribeFlightData = subscribeFlightData;
|
|
1242
1370
|
exports.withMeta = withMeta;
|
|
@@ -26,6 +26,32 @@ function withMeta(fn, meta) {
|
|
|
26
26
|
Object.assign(metadata, meta);
|
|
27
27
|
return fn;
|
|
28
28
|
}
|
|
29
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
30
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
31
|
+
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.",
|
|
32
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
33
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
34
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
35
|
+
};
|
|
36
|
+
function invoke(fn, options, ...args) {
|
|
37
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
38
|
+
if (!channel) {
|
|
39
|
+
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.");
|
|
40
|
+
}
|
|
41
|
+
if (options === null || typeof options !== "object") {
|
|
42
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
43
|
+
}
|
|
44
|
+
const picked = {};
|
|
45
|
+
for (const key in options) {
|
|
46
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
47
|
+
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."));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
51
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
52
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
53
|
+
return channel(args, picked);
|
|
54
|
+
}
|
|
29
55
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
30
56
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
31
57
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -90,7 +116,23 @@ function subscribeFlightData(consumer) {
|
|
|
90
116
|
return () => {
|
|
91
117
|
};
|
|
92
118
|
}
|
|
93
|
-
|
|
119
|
+
function serverFunctionAddress(endpoint, id) {
|
|
120
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
121
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
122
|
+
}
|
|
123
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
124
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
125
|
+
if (!pathname.startsWith(mount)) return null;
|
|
126
|
+
const rest = pathname.slice(mount.length);
|
|
127
|
+
if (!rest.startsWith("/")) return null;
|
|
128
|
+
const segment = rest.slice(1);
|
|
129
|
+
if (!segment || segment.includes("/")) return null;
|
|
130
|
+
try {
|
|
131
|
+
return decodeURIComponent(segment);
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
94
136
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
95
137
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
96
138
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -129,7 +171,8 @@ const BodyFormat = {
|
|
|
129
171
|
File: "5",
|
|
130
172
|
ArrayBuffer: "6",
|
|
131
173
|
Uint8Array: "7",
|
|
132
|
-
Json: "8"
|
|
174
|
+
Json: "8",
|
|
175
|
+
Void: "9"
|
|
133
176
|
};
|
|
134
177
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
135
178
|
const EXIT = {};
|
|
@@ -549,6 +592,9 @@ function getServerFunction(id) {
|
|
|
549
592
|
throw new Error("invalid server function: " + id);
|
|
550
593
|
}
|
|
551
594
|
function registerServerReference(id, fn, name) {
|
|
595
|
+
if (typeof fn !== "function") {
|
|
596
|
+
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.`);
|
|
597
|
+
}
|
|
552
598
|
registerServerFunction(id, fn);
|
|
553
599
|
return {
|
|
554
600
|
id,
|
|
@@ -556,6 +602,33 @@ function registerServerReference(id, fn, name) {
|
|
|
556
602
|
name
|
|
557
603
|
};
|
|
558
604
|
}
|
|
605
|
+
function inProcessInvoker(call) {
|
|
606
|
+
return (args, options) => {
|
|
607
|
+
const signal = options && options.signal;
|
|
608
|
+
if (!signal) return call(...args);
|
|
609
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
610
|
+
return new Promise((resolve, reject) => {
|
|
611
|
+
const onAbort = () => reject(signal.reason);
|
|
612
|
+
signal.addEventListener("abort", onAbort, {
|
|
613
|
+
once: true
|
|
614
|
+
});
|
|
615
|
+
let result;
|
|
616
|
+
try {
|
|
617
|
+
result = call(...args);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
signal.removeEventListener("abort", onAbort);
|
|
620
|
+
return reject(error);
|
|
621
|
+
}
|
|
622
|
+
Promise.resolve(result).then(value => {
|
|
623
|
+
signal.removeEventListener("abort", onAbort);
|
|
624
|
+
resolve(value);
|
|
625
|
+
}, error => {
|
|
626
|
+
signal.removeEventListener("abort", onAbort);
|
|
627
|
+
reject(error);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
};
|
|
631
|
+
}
|
|
559
632
|
function createServerReference({
|
|
560
633
|
id,
|
|
561
634
|
fn,
|
|
@@ -566,13 +639,15 @@ function createServerReference({
|
|
|
566
639
|
const metadata = name === undefined ? {} : {
|
|
567
640
|
name
|
|
568
641
|
};
|
|
569
|
-
|
|
642
|
+
const invokeChannel = inProcessInvoker((...args) => proxy(...args));
|
|
643
|
+
const proxy = new Proxy(fn, {
|
|
570
644
|
get(target, prop) {
|
|
571
645
|
if (prop === "id") return id;
|
|
572
646
|
if (prop === "url") {
|
|
573
|
-
return
|
|
647
|
+
return serverFunctionAddress(config.endpoint, id);
|
|
574
648
|
}
|
|
575
649
|
if (prop === SERVER_FUNCTION_METADATA) return metadata;
|
|
650
|
+
if (prop === SERVER_FUNCTION_INVOKE) return invokeChannel;
|
|
576
651
|
return target[prop];
|
|
577
652
|
},
|
|
578
653
|
apply(target, thisArg, args) {
|
|
@@ -609,6 +684,7 @@ function createServerReference({
|
|
|
609
684
|
}) : result;
|
|
610
685
|
}
|
|
611
686
|
});
|
|
687
|
+
return proxy;
|
|
612
688
|
}
|
|
613
689
|
function GET(fn) {
|
|
614
690
|
if (!isServerFunction(fn) || typeof fn.id !== "string") {
|
|
@@ -635,6 +711,7 @@ function live(fn) {
|
|
|
635
711
|
return result;
|
|
636
712
|
};
|
|
637
713
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
714
|
+
wrapped[SERVER_FUNCTION_INVOKE] = inProcessInvoker(wrapped);
|
|
638
715
|
wrapped.id = fn.id;
|
|
639
716
|
Object.defineProperty(wrapped, "url", {
|
|
640
717
|
get: () => fn.url,
|
|
@@ -648,24 +725,23 @@ function getServerFunctionInvocation() {
|
|
|
648
725
|
function getEventServerFunctionInvocation(event) {
|
|
649
726
|
return event && INVOCATIONS.get(event);
|
|
650
727
|
}
|
|
651
|
-
function resolveFunctionId(
|
|
652
|
-
|
|
653
|
-
if (reference) {
|
|
654
|
-
return reference.split("#")[0];
|
|
655
|
-
}
|
|
656
|
-
return url.searchParams.get("id");
|
|
728
|
+
function resolveFunctionId(url) {
|
|
729
|
+
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
657
730
|
}
|
|
658
731
|
async function parseArguments(request, url, instance, codec) {
|
|
659
732
|
const parsed = [];
|
|
660
733
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
734
|
+
const args = url.searchParams.get("args");
|
|
735
|
+
if (args && (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
736
|
+
const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
|
|
737
|
+
if (!Array.isArray(result)) {
|
|
738
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
739
|
+
}
|
|
740
|
+
for (const arg of result) {
|
|
741
|
+
parsed.push(arg);
|
|
668
742
|
}
|
|
743
|
+
} else if (!args && url.search && (request.method === "GET" || request.method === "HEAD")) {
|
|
744
|
+
parsed.push(url.searchParams);
|
|
669
745
|
}
|
|
670
746
|
if (request.method === "POST" && request.body !== null) {
|
|
671
747
|
const decoded = await extractBody(request.clone(), codec);
|
|
@@ -920,6 +996,7 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
920
996
|
});
|
|
921
997
|
}
|
|
922
998
|
if (value === undefined) {
|
|
999
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
923
1000
|
return new Response(null, {
|
|
924
1001
|
status,
|
|
925
1002
|
headers
|
|
@@ -955,6 +1032,17 @@ function sanitizeServerError(value) {
|
|
|
955
1032
|
function observeServerFunctionCalls() {
|
|
956
1033
|
return () => {};
|
|
957
1034
|
}
|
|
1035
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
1036
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
1037
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
1038
|
+
if (!isJSONSafe(boundArgs)) {
|
|
1039
|
+
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.");
|
|
1040
|
+
}
|
|
1041
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1042
|
+
}
|
|
1043
|
+
function parseServerFunctionUrl(url) {
|
|
1044
|
+
return parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1045
|
+
}
|
|
958
1046
|
async function matchesOrigin(origin, request, matcher) {
|
|
959
1047
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
960
1048
|
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
@@ -1012,18 +1100,20 @@ function forbiddenResponse() {
|
|
|
1012
1100
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1013
1101
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
1014
1102
|
const url = new URL(request.url);
|
|
1103
|
+
const method = request.method;
|
|
1104
|
+
const functionId = resolveFunctionId(url);
|
|
1105
|
+
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1015
1106
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1016
|
-
const protectsRequest = csrf !== false;
|
|
1107
|
+
const protectsRequest = csrf !== false && !declaredRead;
|
|
1017
1108
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1018
|
-
return forbiddenResponse();
|
|
1109
|
+
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1019
1110
|
}
|
|
1020
1111
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
1021
|
-
const functionId = resolveFunctionId(request, url);
|
|
1022
1112
|
if (!functionId) {
|
|
1023
1113
|
const response = new Response(DEV ? "Server function not found" : null, {
|
|
1024
1114
|
status: 404
|
|
1025
1115
|
});
|
|
1026
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1116
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1027
1117
|
}
|
|
1028
1118
|
let serverFunction;
|
|
1029
1119
|
try {
|
|
@@ -1032,16 +1122,16 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1032
1122
|
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1033
1123
|
status: 404
|
|
1034
1124
|
});
|
|
1035
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1125
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1036
1126
|
}
|
|
1037
|
-
if (
|
|
1127
|
+
if (method !== "POST" && !declaredRead) {
|
|
1038
1128
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1039
1129
|
status: 405,
|
|
1040
1130
|
headers: {
|
|
1041
|
-
Allow: "POST"
|
|
1131
|
+
Allow: METHODS.get(functionId) === "GET" ? "POST, GET, HEAD" : "POST"
|
|
1042
1132
|
}
|
|
1043
1133
|
});
|
|
1044
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1134
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1045
1135
|
}
|
|
1046
1136
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
1047
1137
|
request,
|
|
@@ -1054,7 +1144,15 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1054
1144
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1055
1145
|
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
1056
1146
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
1057
|
-
|
|
1147
|
+
let parsed;
|
|
1148
|
+
try {
|
|
1149
|
+
parsed = await parseArguments(request, url, instance, codec);
|
|
1150
|
+
} catch {
|
|
1151
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1152
|
+
status: 400
|
|
1153
|
+
});
|
|
1154
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1155
|
+
}
|
|
1058
1156
|
const flightContext = {
|
|
1059
1157
|
id: functionId,
|
|
1060
1158
|
args: parsed,
|
|
@@ -1201,7 +1299,34 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1201
1299
|
}
|
|
1202
1300
|
};
|
|
1203
1301
|
const response = commitEventResponse(await dispatch(), event);
|
|
1204
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1302
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1303
|
+
}
|
|
1304
|
+
function finalizeTransportResponse(response, method) {
|
|
1305
|
+
const stripBody = method === "HEAD" && response.body !== null;
|
|
1306
|
+
if (stripBody || !response.headers.has("Cache-Control")) {
|
|
1307
|
+
try {
|
|
1308
|
+
if (!response.headers.has("Cache-Control")) {
|
|
1309
|
+
response.headers.set("Cache-Control", "no-store");
|
|
1310
|
+
}
|
|
1311
|
+
if (!stripBody) return response;
|
|
1312
|
+
response.body.cancel().catch(() => {});
|
|
1313
|
+
return new Response(null, {
|
|
1314
|
+
status: response.status,
|
|
1315
|
+
statusText: response.statusText,
|
|
1316
|
+
headers: response.headers
|
|
1317
|
+
});
|
|
1318
|
+
} catch {
|
|
1319
|
+
const headers = new Headers(response.headers);
|
|
1320
|
+
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1321
|
+
if (stripBody) response.body.cancel().catch(() => {});
|
|
1322
|
+
return new Response(stripBody ? null : response.body, {
|
|
1323
|
+
status: response.status,
|
|
1324
|
+
statusText: response.statusText,
|
|
1325
|
+
headers
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return response;
|
|
1205
1330
|
}
|
|
1206
1331
|
|
|
1207
|
-
export { ERROR_HEADER, FLASH_COOKIE,
|
|
1332
|
+
export { ERROR_HEADER, FLASH_COOKIE, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, serverFunctionUrl, setServerFunctionsDev, subscribeFlightData, withMeta };
|