@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
|
@@ -16,6 +16,32 @@ function withMeta(fn, meta) {
|
|
|
16
16
|
Object.assign(metadata, meta);
|
|
17
17
|
return fn;
|
|
18
18
|
}
|
|
19
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
20
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
21
|
+
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.",
|
|
22
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
23
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
24
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
25
|
+
};
|
|
26
|
+
function invoke(fn, options, ...args) {
|
|
27
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
28
|
+
if (!channel) {
|
|
29
|
+
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.");
|
|
30
|
+
}
|
|
31
|
+
if (options === null || typeof options !== "object") {
|
|
32
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
33
|
+
}
|
|
34
|
+
const picked = {};
|
|
35
|
+
for (const key in options) {
|
|
36
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
37
|
+
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."));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
41
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
42
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
43
|
+
return channel(args, picked);
|
|
44
|
+
}
|
|
19
45
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
20
46
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
21
47
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -96,7 +122,23 @@ function stableString(value, seen) {
|
|
|
96
122
|
}
|
|
97
123
|
return out + "}";
|
|
98
124
|
}
|
|
99
|
-
|
|
125
|
+
function serverFunctionAddress(endpoint, id) {
|
|
126
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
127
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
128
|
+
}
|
|
129
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
130
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
131
|
+
if (!pathname.startsWith(mount)) return null;
|
|
132
|
+
const rest = pathname.slice(mount.length);
|
|
133
|
+
if (!rest.startsWith("/")) return null;
|
|
134
|
+
const segment = rest.slice(1);
|
|
135
|
+
if (!segment || segment.includes("/")) return null;
|
|
136
|
+
try {
|
|
137
|
+
return decodeURIComponent(segment);
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
100
142
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
101
143
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
102
144
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -135,7 +177,8 @@ const BodyFormat = {
|
|
|
135
177
|
File: "5",
|
|
136
178
|
ArrayBuffer: "6",
|
|
137
179
|
Uint8Array: "7",
|
|
138
|
-
Json: "8"
|
|
180
|
+
Json: "8",
|
|
181
|
+
Void: "9"
|
|
139
182
|
};
|
|
140
183
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
141
184
|
const EXIT = {};
|
|
@@ -390,6 +433,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
390
433
|
|
|
391
434
|
const config = {
|
|
392
435
|
endpoint: "/_server",
|
|
436
|
+
fetch: undefined,
|
|
393
437
|
prepareRequest: undefined,
|
|
394
438
|
responseHandler: undefined,
|
|
395
439
|
serializeArgs: undefined
|
|
@@ -418,6 +462,17 @@ function observeServerFunctionCalls(observer) {
|
|
|
418
462
|
CALL_OBSERVERS.add(observer);
|
|
419
463
|
return () => CALL_OBSERVERS.delete(observer);
|
|
420
464
|
}
|
|
465
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
466
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
467
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
468
|
+
if (!isJSONSafe(boundArgs)) {
|
|
469
|
+
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.");
|
|
470
|
+
}
|
|
471
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
472
|
+
}
|
|
473
|
+
function parseServerFunctionUrl(url) {
|
|
474
|
+
return parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
|
|
475
|
+
}
|
|
421
476
|
function serializeArguments(args) {
|
|
422
477
|
if (!config.serializeArgs) {
|
|
423
478
|
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.");
|
|
@@ -427,17 +482,20 @@ function serializeArguments(args) {
|
|
|
427
482
|
function configureServerFunctionsClient({
|
|
428
483
|
endpoint,
|
|
429
484
|
codec,
|
|
485
|
+
fetch,
|
|
430
486
|
prepareRequest,
|
|
431
487
|
responseHandler,
|
|
432
488
|
serializeArgs
|
|
433
489
|
} = {}) {
|
|
434
490
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
435
491
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
492
|
+
if (fetch !== undefined) config.fetch = fetch;
|
|
436
493
|
if (prepareRequest !== undefined) config.prepareRequest = prepareRequest;
|
|
437
494
|
if (responseHandler !== undefined) config.responseHandler = responseHandler;
|
|
438
495
|
if (serializeArgs !== undefined) config.serializeArgs = serializeArgs;
|
|
439
496
|
}
|
|
440
497
|
let INSTANCE = 0;
|
|
498
|
+
const MAX_GET_URL_LENGTH = 2000;
|
|
441
499
|
let rpcProvided = false;
|
|
442
500
|
function provideRPC() {
|
|
443
501
|
if (rpcProvided) return;
|
|
@@ -455,7 +513,6 @@ function serverFunctionFailure(response, value) {
|
|
|
455
513
|
async function createRequest(base, id, instance, options, meta) {
|
|
456
514
|
const headers = {
|
|
457
515
|
...options.headers,
|
|
458
|
-
[FUNCTION_HEADER]: id,
|
|
459
516
|
[INSTANCE_HEADER]: instance
|
|
460
517
|
};
|
|
461
518
|
if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
@@ -472,10 +529,14 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
472
529
|
meta
|
|
473
530
|
})) || init;
|
|
474
531
|
}
|
|
475
|
-
|
|
476
|
-
|
|
532
|
+
const send = config.fetch || fetch;
|
|
533
|
+
if (CALL_OBSERVERS.size === 0) return send(base, init);
|
|
534
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), {
|
|
535
|
+
...init,
|
|
536
|
+
body: init.body instanceof ReadableStream ? undefined : init.body
|
|
537
|
+
});
|
|
477
538
|
notifyCallObservers("request", id, instance, request, meta);
|
|
478
|
-
const response = await
|
|
539
|
+
const response = await send(base, init);
|
|
479
540
|
notifyCallObservers("response", id, instance, response, meta);
|
|
480
541
|
return response;
|
|
481
542
|
}
|
|
@@ -560,6 +621,9 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
560
621
|
});
|
|
561
622
|
if (handled !== undefined) return handled;
|
|
562
623
|
}
|
|
624
|
+
if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
|
|
625
|
+
throw serverFunctionFailure(response, undefined);
|
|
626
|
+
}
|
|
563
627
|
const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
|
|
564
628
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
565
629
|
const consumer = getFlightDataConsumer();
|
|
@@ -602,7 +666,7 @@ function createServerReference(id, name, base) {
|
|
|
602
666
|
const metadata = name === undefined ? {} : {
|
|
603
667
|
name
|
|
604
668
|
};
|
|
605
|
-
const
|
|
669
|
+
const run = (args, invokeOptions) => {
|
|
606
670
|
const handler = config.responseHandler;
|
|
607
671
|
if (handler && handler.intercept) {
|
|
608
672
|
const hit = handler.intercept({
|
|
@@ -612,14 +676,18 @@ function createServerReference(id, name, base) {
|
|
|
612
676
|
});
|
|
613
677
|
if (hit !== undefined) return hit;
|
|
614
678
|
}
|
|
615
|
-
return fetchServerFunction(base || config.endpoint, id,
|
|
679
|
+
return fetchServerFunction(base || serverFunctionAddress(config.endpoint, id), id, invokeOptions ? {
|
|
680
|
+
...invokeOptions
|
|
681
|
+
} : {}, args, metadata);
|
|
616
682
|
};
|
|
683
|
+
const fn = (...args) => run(args);
|
|
617
684
|
fn[SERVER_FUNCTION_METADATA] = metadata;
|
|
685
|
+
fn[SERVER_FUNCTION_INVOKE] = run;
|
|
618
686
|
return new Proxy(fn, {
|
|
619
687
|
get(target, prop) {
|
|
620
688
|
if (prop === "id") return id;
|
|
621
689
|
if (prop === "url") {
|
|
622
|
-
return base ||
|
|
690
|
+
return base || serverFunctionAddress(config.endpoint, id);
|
|
623
691
|
}
|
|
624
692
|
return target[prop];
|
|
625
693
|
}
|
|
@@ -634,7 +702,7 @@ function GET(fn) {
|
|
|
634
702
|
const metadata = {
|
|
635
703
|
...getServerFunctionMetadata(fn)
|
|
636
704
|
};
|
|
637
|
-
const
|
|
705
|
+
const run = async (args, invokeOptions) => {
|
|
638
706
|
const handler = config.responseHandler;
|
|
639
707
|
if (handler && handler.intercept) {
|
|
640
708
|
const hit = handler.intercept({
|
|
@@ -644,19 +712,34 @@ function GET(fn) {
|
|
|
644
712
|
});
|
|
645
713
|
if (hit !== undefined) return hit;
|
|
646
714
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
715
|
+
const opts = invokeOptions || {};
|
|
716
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
717
|
+
if (!args.length) {
|
|
718
|
+
return fetchServerFunction(address, id, {
|
|
719
|
+
...opts,
|
|
720
|
+
method: "GET"
|
|
721
|
+
}, [], metadata, args);
|
|
722
|
+
}
|
|
723
|
+
const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
|
|
724
|
+
const url = `${address}?args=${encodeURIComponent(encoded)}`;
|
|
725
|
+
const absolute = new URL(url, globalThis.location?.href || "http://localhost").href;
|
|
726
|
+
if (absolute.length > MAX_GET_URL_LENGTH) {
|
|
727
|
+
return fetchServerFunction(address, id, {
|
|
728
|
+
...opts,
|
|
729
|
+
read: true
|
|
730
|
+
}, args, metadata);
|
|
651
731
|
}
|
|
652
|
-
return fetchServerFunction(
|
|
732
|
+
return fetchServerFunction(url, id, {
|
|
733
|
+
...opts,
|
|
653
734
|
method: "GET"
|
|
654
735
|
}, [], metadata, args);
|
|
655
736
|
};
|
|
737
|
+
const wrapped = (...args) => run(args);
|
|
656
738
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
739
|
+
wrapped[SERVER_FUNCTION_INVOKE] = run;
|
|
657
740
|
wrapped.id = id;
|
|
658
741
|
Object.defineProperty(wrapped, "url", {
|
|
659
|
-
get: () =>
|
|
742
|
+
get: () => serverFunctionAddress(config.endpoint, id),
|
|
660
743
|
configurable: true
|
|
661
744
|
});
|
|
662
745
|
return withMeta(wrapped, {
|
|
@@ -672,7 +755,7 @@ function live(fn) {
|
|
|
672
755
|
...getServerFunctionMetadata(fn),
|
|
673
756
|
live: true
|
|
674
757
|
};
|
|
675
|
-
const
|
|
758
|
+
const makeIterable = (args, invokeOptions) => {
|
|
676
759
|
const iterable = {
|
|
677
760
|
[LIVE_SOURCE]: true,
|
|
678
761
|
[Symbol.asyncIterator]() {
|
|
@@ -686,6 +769,12 @@ function live(fn) {
|
|
|
686
769
|
done: true,
|
|
687
770
|
value: undefined
|
|
688
771
|
};
|
|
772
|
+
const invokeSignal = invokeOptions && invokeOptions.signal;
|
|
773
|
+
const controller = new AbortController();
|
|
774
|
+
const wireOptions = {
|
|
775
|
+
...invokeOptions,
|
|
776
|
+
signal: invokeSignal ? AbortSignal.any([invokeSignal, controller.signal]) : controller.signal
|
|
777
|
+
};
|
|
689
778
|
const emit = (state, error) => {
|
|
690
779
|
try {
|
|
691
780
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
@@ -707,7 +796,7 @@ function live(fn) {
|
|
|
707
796
|
}
|
|
708
797
|
};
|
|
709
798
|
const callOnce = () => {
|
|
710
|
-
if (metadata.method === "GET") return fn(
|
|
799
|
+
if (metadata.method === "GET") return fn[SERVER_FUNCTION_INVOKE](args, wireOptions);
|
|
711
800
|
const handler = config.responseHandler;
|
|
712
801
|
if (handler && handler.intercept) {
|
|
713
802
|
const hit = handler.intercept({
|
|
@@ -718,6 +807,7 @@ function live(fn) {
|
|
|
718
807
|
if (hit !== undefined) return hit;
|
|
719
808
|
}
|
|
720
809
|
return fetchServerFunction(fn.url, id, {
|
|
810
|
+
...wireOptions,
|
|
721
811
|
read: true
|
|
722
812
|
}, args, metadata, args);
|
|
723
813
|
};
|
|
@@ -732,6 +822,7 @@ function live(fn) {
|
|
|
732
822
|
}();
|
|
733
823
|
if (stopped) {
|
|
734
824
|
closeIt();
|
|
825
|
+
controller.abort();
|
|
735
826
|
return DONE;
|
|
736
827
|
}
|
|
737
828
|
emit("connected");
|
|
@@ -745,7 +836,13 @@ function live(fn) {
|
|
|
745
836
|
attempts = 0;
|
|
746
837
|
return r;
|
|
747
838
|
} catch (error) {
|
|
839
|
+
if (stopped) return DONE;
|
|
748
840
|
if (!connected) throw error;
|
|
841
|
+
if (invokeSignal && invokeSignal.aborted) {
|
|
842
|
+
stopped = true;
|
|
843
|
+
emitClosed(error);
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
749
846
|
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
|
|
750
847
|
stopped = true;
|
|
751
848
|
emitClosed(error);
|
|
@@ -759,9 +856,13 @@ function live(fn) {
|
|
|
759
856
|
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
760
857
|
once: true
|
|
761
858
|
});
|
|
859
|
+
if (invokeSignal) invokeSignal.addEventListener("abort", resolve, {
|
|
860
|
+
once: true
|
|
861
|
+
});
|
|
762
862
|
});
|
|
763
863
|
clearTimeout(timer);
|
|
764
864
|
if (typeof removeEventListener === "function") removeEventListener("online", wake);
|
|
865
|
+
if (invokeSignal) invokeSignal.removeEventListener("abort", wake);
|
|
765
866
|
timer = wake = undefined;
|
|
766
867
|
}
|
|
767
868
|
}
|
|
@@ -774,6 +875,7 @@ function live(fn) {
|
|
|
774
875
|
if (timer !== undefined) clearTimeout(timer);
|
|
775
876
|
if (wake) wake();
|
|
776
877
|
closeIt(value);
|
|
878
|
+
controller.abort();
|
|
777
879
|
emitClosed();
|
|
778
880
|
return Promise.resolve({
|
|
779
881
|
done: true,
|
|
@@ -785,7 +887,9 @@ function live(fn) {
|
|
|
785
887
|
};
|
|
786
888
|
return iterable;
|
|
787
889
|
};
|
|
890
|
+
const wrapped = (...args) => makeIterable(args);
|
|
788
891
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
892
|
+
wrapped[SERVER_FUNCTION_INVOKE] = makeIterable;
|
|
789
893
|
wrapped.id = id;
|
|
790
894
|
Object.defineProperty(wrapped, "url", {
|
|
791
895
|
get: () => fn.url,
|
|
@@ -800,4 +904,4 @@ function getServerFunctionInvocation() {
|
|
|
800
904
|
return undefined;
|
|
801
905
|
}
|
|
802
906
|
|
|
803
|
-
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE,
|
|
907
|
+
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerReference, serializeString, serverFunctionUrl, subscribeFlightData, withMeta };
|
|
@@ -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;
|