@solidjs/web 2.0.0-rc.3 → 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 +347 -11
- package/dist/dev.js +347 -13
- package/dist/server.cjs +5 -4
- package/dist/server.js +5 -4
- package/dist/web.cjs +338 -11
- package/dist/web.js +338 -13
- package/package.json +2 -2
- 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 +4 -2
- package/types/index.d.ts +1 -0
- package/types/patch-driver.d.ts +3 -0
- package/types/response.d.ts +9 -1
- package/types/server-functions/client.d.ts +37 -6
- package/types/server-functions/registry.d.ts +38 -1
- package/types/server-functions/server.d.ts +9 -6
- package/types/server-functions/shared.d.ts +46 -3
- package/types-cjs/client.d.cts +4 -2
- package/types-cjs/index.d.cts +1 -0
- package/types-cjs/patch-driver.d.cts +3 -0
- package/types-cjs/response.d.cts +9 -1
- package/types-cjs/server-functions/client.d.cts +37 -6
- package/types-cjs/server-functions/registry.d.cts +38 -1
- package/types-cjs/server-functions/server.d.cts +9 -6
- package/types-cjs/server-functions/shared.d.cts +46 -3
|
@@ -18,6 +18,32 @@ function withMeta(fn, meta) {
|
|
|
18
18
|
Object.assign(metadata, meta);
|
|
19
19
|
return fn;
|
|
20
20
|
}
|
|
21
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
22
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
23
|
+
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.",
|
|
24
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
25
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
26
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
27
|
+
};
|
|
28
|
+
function invoke(fn, options, ...args) {
|
|
29
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
30
|
+
if (!channel) {
|
|
31
|
+
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.");
|
|
32
|
+
}
|
|
33
|
+
if (options === null || typeof options !== "object") {
|
|
34
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
35
|
+
}
|
|
36
|
+
const picked = {};
|
|
37
|
+
for (const key in options) {
|
|
38
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
39
|
+
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."));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
43
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
44
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
45
|
+
return channel(args, picked);
|
|
46
|
+
}
|
|
21
47
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
22
48
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
23
49
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -98,7 +124,23 @@ function stableString(value, seen) {
|
|
|
98
124
|
}
|
|
99
125
|
return out + "}";
|
|
100
126
|
}
|
|
101
|
-
|
|
127
|
+
function serverFunctionAddress(endpoint, id) {
|
|
128
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
129
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
130
|
+
}
|
|
131
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
132
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
133
|
+
if (!pathname.startsWith(mount)) return null;
|
|
134
|
+
const rest = pathname.slice(mount.length);
|
|
135
|
+
if (!rest.startsWith("/")) return null;
|
|
136
|
+
const segment = rest.slice(1);
|
|
137
|
+
if (!segment || segment.includes("/")) return null;
|
|
138
|
+
try {
|
|
139
|
+
return decodeURIComponent(segment);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
102
144
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
103
145
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
104
146
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -137,7 +179,8 @@ const BodyFormat = {
|
|
|
137
179
|
File: "5",
|
|
138
180
|
ArrayBuffer: "6",
|
|
139
181
|
Uint8Array: "7",
|
|
140
|
-
Json: "8"
|
|
182
|
+
Json: "8",
|
|
183
|
+
Void: "9"
|
|
141
184
|
};
|
|
142
185
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
143
186
|
const EXIT = {};
|
|
@@ -392,6 +435,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
392
435
|
|
|
393
436
|
const config = {
|
|
394
437
|
endpoint: "/_server",
|
|
438
|
+
fetch: undefined,
|
|
395
439
|
prepareRequest: undefined,
|
|
396
440
|
responseHandler: undefined,
|
|
397
441
|
serializeArgs: undefined
|
|
@@ -420,6 +464,17 @@ function observeServerFunctionCalls(observer) {
|
|
|
420
464
|
CALL_OBSERVERS.add(observer);
|
|
421
465
|
return () => CALL_OBSERVERS.delete(observer);
|
|
422
466
|
}
|
|
467
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
468
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
469
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
470
|
+
if (!isJSONSafe(boundArgs)) {
|
|
471
|
+
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.");
|
|
472
|
+
}
|
|
473
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
474
|
+
}
|
|
475
|
+
function parseServerFunctionUrl(url) {
|
|
476
|
+
return parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
|
|
477
|
+
}
|
|
423
478
|
function serializeArguments(args) {
|
|
424
479
|
if (!config.serializeArgs) {
|
|
425
480
|
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.");
|
|
@@ -429,17 +484,20 @@ function serializeArguments(args) {
|
|
|
429
484
|
function configureServerFunctionsClient({
|
|
430
485
|
endpoint,
|
|
431
486
|
codec,
|
|
487
|
+
fetch,
|
|
432
488
|
prepareRequest,
|
|
433
489
|
responseHandler,
|
|
434
490
|
serializeArgs
|
|
435
491
|
} = {}) {
|
|
436
492
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
437
493
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
494
|
+
if (fetch !== undefined) config.fetch = fetch;
|
|
438
495
|
if (prepareRequest !== undefined) config.prepareRequest = prepareRequest;
|
|
439
496
|
if (responseHandler !== undefined) config.responseHandler = responseHandler;
|
|
440
497
|
if (serializeArgs !== undefined) config.serializeArgs = serializeArgs;
|
|
441
498
|
}
|
|
442
499
|
let INSTANCE = 0;
|
|
500
|
+
const MAX_GET_URL_LENGTH = 2000;
|
|
443
501
|
let rpcProvided = false;
|
|
444
502
|
function provideRPC() {
|
|
445
503
|
if (rpcProvided) return;
|
|
@@ -457,7 +515,6 @@ function serverFunctionFailure(response, value) {
|
|
|
457
515
|
async function createRequest(base, id, instance, options, meta) {
|
|
458
516
|
const headers = {
|
|
459
517
|
...options.headers,
|
|
460
|
-
[FUNCTION_HEADER]: id,
|
|
461
518
|
[INSTANCE_HEADER]: instance
|
|
462
519
|
};
|
|
463
520
|
if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
@@ -474,10 +531,14 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
474
531
|
meta
|
|
475
532
|
})) || init;
|
|
476
533
|
}
|
|
477
|
-
|
|
478
|
-
|
|
534
|
+
const send = config.fetch || fetch;
|
|
535
|
+
if (CALL_OBSERVERS.size === 0) return send(base, init);
|
|
536
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), {
|
|
537
|
+
...init,
|
|
538
|
+
body: init.body instanceof ReadableStream ? undefined : init.body
|
|
539
|
+
});
|
|
479
540
|
notifyCallObservers("request", id, instance, request, meta);
|
|
480
|
-
const response = await
|
|
541
|
+
const response = await send(base, init);
|
|
481
542
|
notifyCallObservers("response", id, instance, response, meta);
|
|
482
543
|
return response;
|
|
483
544
|
}
|
|
@@ -562,6 +623,9 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
562
623
|
});
|
|
563
624
|
if (handled !== undefined) return handled;
|
|
564
625
|
}
|
|
626
|
+
if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
|
|
627
|
+
throw serverFunctionFailure(response, undefined);
|
|
628
|
+
}
|
|
565
629
|
const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
|
|
566
630
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
567
631
|
const consumer = getFlightDataConsumer();
|
|
@@ -604,7 +668,7 @@ function createServerReference(id, name, base) {
|
|
|
604
668
|
const metadata = name === undefined ? {} : {
|
|
605
669
|
name
|
|
606
670
|
};
|
|
607
|
-
const
|
|
671
|
+
const run = (args, invokeOptions) => {
|
|
608
672
|
const handler = config.responseHandler;
|
|
609
673
|
if (handler && handler.intercept) {
|
|
610
674
|
const hit = handler.intercept({
|
|
@@ -614,14 +678,18 @@ function createServerReference(id, name, base) {
|
|
|
614
678
|
});
|
|
615
679
|
if (hit !== undefined) return hit;
|
|
616
680
|
}
|
|
617
|
-
return fetchServerFunction(base || config.endpoint, id,
|
|
681
|
+
return fetchServerFunction(base || serverFunctionAddress(config.endpoint, id), id, invokeOptions ? {
|
|
682
|
+
...invokeOptions
|
|
683
|
+
} : {}, args, metadata);
|
|
618
684
|
};
|
|
685
|
+
const fn = (...args) => run(args);
|
|
619
686
|
fn[SERVER_FUNCTION_METADATA] = metadata;
|
|
687
|
+
fn[SERVER_FUNCTION_INVOKE] = run;
|
|
620
688
|
return new Proxy(fn, {
|
|
621
689
|
get(target, prop) {
|
|
622
690
|
if (prop === "id") return id;
|
|
623
691
|
if (prop === "url") {
|
|
624
|
-
return base ||
|
|
692
|
+
return base || serverFunctionAddress(config.endpoint, id);
|
|
625
693
|
}
|
|
626
694
|
return target[prop];
|
|
627
695
|
}
|
|
@@ -636,7 +704,7 @@ function GET(fn) {
|
|
|
636
704
|
const metadata = {
|
|
637
705
|
...getServerFunctionMetadata(fn)
|
|
638
706
|
};
|
|
639
|
-
const
|
|
707
|
+
const run = async (args, invokeOptions) => {
|
|
640
708
|
const handler = config.responseHandler;
|
|
641
709
|
if (handler && handler.intercept) {
|
|
642
710
|
const hit = handler.intercept({
|
|
@@ -646,19 +714,34 @@ function GET(fn) {
|
|
|
646
714
|
});
|
|
647
715
|
if (hit !== undefined) return hit;
|
|
648
716
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
717
|
+
const opts = invokeOptions || {};
|
|
718
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
719
|
+
if (!args.length) {
|
|
720
|
+
return fetchServerFunction(address, id, {
|
|
721
|
+
...opts,
|
|
722
|
+
method: "GET"
|
|
723
|
+
}, [], metadata, args);
|
|
724
|
+
}
|
|
725
|
+
const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
|
|
726
|
+
const url = `${address}?args=${encodeURIComponent(encoded)}`;
|
|
727
|
+
const absolute = new URL(url, globalThis.location?.href || "http://localhost").href;
|
|
728
|
+
if (absolute.length > MAX_GET_URL_LENGTH) {
|
|
729
|
+
return fetchServerFunction(address, id, {
|
|
730
|
+
...opts,
|
|
731
|
+
read: true
|
|
732
|
+
}, args, metadata);
|
|
653
733
|
}
|
|
654
|
-
return fetchServerFunction(
|
|
734
|
+
return fetchServerFunction(url, id, {
|
|
735
|
+
...opts,
|
|
655
736
|
method: "GET"
|
|
656
737
|
}, [], metadata, args);
|
|
657
738
|
};
|
|
739
|
+
const wrapped = (...args) => run(args);
|
|
658
740
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
741
|
+
wrapped[SERVER_FUNCTION_INVOKE] = run;
|
|
659
742
|
wrapped.id = id;
|
|
660
743
|
Object.defineProperty(wrapped, "url", {
|
|
661
|
-
get: () =>
|
|
744
|
+
get: () => serverFunctionAddress(config.endpoint, id),
|
|
662
745
|
configurable: true
|
|
663
746
|
});
|
|
664
747
|
return withMeta(wrapped, {
|
|
@@ -674,7 +757,7 @@ function live(fn) {
|
|
|
674
757
|
...getServerFunctionMetadata(fn),
|
|
675
758
|
live: true
|
|
676
759
|
};
|
|
677
|
-
const
|
|
760
|
+
const makeIterable = (args, invokeOptions) => {
|
|
678
761
|
const iterable = {
|
|
679
762
|
[LIVE_SOURCE]: true,
|
|
680
763
|
[Symbol.asyncIterator]() {
|
|
@@ -688,6 +771,12 @@ function live(fn) {
|
|
|
688
771
|
done: true,
|
|
689
772
|
value: undefined
|
|
690
773
|
};
|
|
774
|
+
const invokeSignal = invokeOptions && invokeOptions.signal;
|
|
775
|
+
const controller = new AbortController();
|
|
776
|
+
const wireOptions = {
|
|
777
|
+
...invokeOptions,
|
|
778
|
+
signal: invokeSignal ? AbortSignal.any([invokeSignal, controller.signal]) : controller.signal
|
|
779
|
+
};
|
|
691
780
|
const emit = (state, error) => {
|
|
692
781
|
try {
|
|
693
782
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
@@ -709,7 +798,7 @@ function live(fn) {
|
|
|
709
798
|
}
|
|
710
799
|
};
|
|
711
800
|
const callOnce = () => {
|
|
712
|
-
if (metadata.method === "GET") return fn(
|
|
801
|
+
if (metadata.method === "GET") return fn[SERVER_FUNCTION_INVOKE](args, wireOptions);
|
|
713
802
|
const handler = config.responseHandler;
|
|
714
803
|
if (handler && handler.intercept) {
|
|
715
804
|
const hit = handler.intercept({
|
|
@@ -720,6 +809,7 @@ function live(fn) {
|
|
|
720
809
|
if (hit !== undefined) return hit;
|
|
721
810
|
}
|
|
722
811
|
return fetchServerFunction(fn.url, id, {
|
|
812
|
+
...wireOptions,
|
|
723
813
|
read: true
|
|
724
814
|
}, args, metadata, args);
|
|
725
815
|
};
|
|
@@ -734,6 +824,7 @@ function live(fn) {
|
|
|
734
824
|
}();
|
|
735
825
|
if (stopped) {
|
|
736
826
|
closeIt();
|
|
827
|
+
controller.abort();
|
|
737
828
|
return DONE;
|
|
738
829
|
}
|
|
739
830
|
emit("connected");
|
|
@@ -747,7 +838,13 @@ function live(fn) {
|
|
|
747
838
|
attempts = 0;
|
|
748
839
|
return r;
|
|
749
840
|
} catch (error) {
|
|
841
|
+
if (stopped) return DONE;
|
|
750
842
|
if (!connected) throw error;
|
|
843
|
+
if (invokeSignal && invokeSignal.aborted) {
|
|
844
|
+
stopped = true;
|
|
845
|
+
emitClosed(error);
|
|
846
|
+
throw error;
|
|
847
|
+
}
|
|
751
848
|
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
|
|
752
849
|
stopped = true;
|
|
753
850
|
emitClosed(error);
|
|
@@ -761,9 +858,13 @@ function live(fn) {
|
|
|
761
858
|
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
762
859
|
once: true
|
|
763
860
|
});
|
|
861
|
+
if (invokeSignal) invokeSignal.addEventListener("abort", resolve, {
|
|
862
|
+
once: true
|
|
863
|
+
});
|
|
764
864
|
});
|
|
765
865
|
clearTimeout(timer);
|
|
766
866
|
if (typeof removeEventListener === "function") removeEventListener("online", wake);
|
|
867
|
+
if (invokeSignal) invokeSignal.removeEventListener("abort", wake);
|
|
767
868
|
timer = wake = undefined;
|
|
768
869
|
}
|
|
769
870
|
}
|
|
@@ -776,6 +877,7 @@ function live(fn) {
|
|
|
776
877
|
if (timer !== undefined) clearTimeout(timer);
|
|
777
878
|
if (wake) wake();
|
|
778
879
|
closeIt(value);
|
|
880
|
+
controller.abort();
|
|
779
881
|
emitClosed();
|
|
780
882
|
return Promise.resolve({
|
|
781
883
|
done: true,
|
|
@@ -787,7 +889,9 @@ function live(fn) {
|
|
|
787
889
|
};
|
|
788
890
|
return iterable;
|
|
789
891
|
};
|
|
892
|
+
const wrapped = (...args) => makeIterable(args);
|
|
790
893
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
894
|
+
wrapped[SERVER_FUNCTION_INVOKE] = makeIterable;
|
|
791
895
|
wrapped.id = id;
|
|
792
896
|
Object.defineProperty(wrapped, "url", {
|
|
793
897
|
get: () => fn.url,
|
|
@@ -805,10 +909,10 @@ function getServerFunctionInvocation() {
|
|
|
805
909
|
exports.ChunkReader = ChunkReader;
|
|
806
910
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
807
911
|
exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
808
|
-
exports.FUNCTION_HEADER = FUNCTION_HEADER;
|
|
809
912
|
exports.GET = GET;
|
|
810
913
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
811
914
|
exports.REVALIDATE_HEADER = REVALIDATE_HEADER;
|
|
915
|
+
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
812
916
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
813
917
|
exports.clearFlashCookie = clearFlashCookie;
|
|
814
918
|
exports.configureServerFunctionsClient = configureServerFunctionsClient;
|
|
@@ -825,10 +929,13 @@ exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
|
825
929
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
826
930
|
exports.getServerFunctionsCodec = getServerFunctionsCodec;
|
|
827
931
|
exports.hasFlashCookie = hasFlashCookie;
|
|
932
|
+
exports.invoke = invoke;
|
|
828
933
|
exports.isServerFunction = isServerFunction;
|
|
829
934
|
exports.live = live;
|
|
830
935
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
936
|
+
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
831
937
|
exports.registerServerReference = registerServerReference;
|
|
832
938
|
exports.serializeString = serializeString;
|
|
939
|
+
exports.serverFunctionUrl = serverFunctionUrl;
|
|
833
940
|
exports.subscribeFlightData = subscribeFlightData;
|
|
834
941
|
exports.withMeta = withMeta;
|
|
@@ -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 };
|