@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.
Files changed (86) hide show
  1. package/dist/dev.cjs +433 -72
  2. package/dist/dev.js +426 -71
  3. package/dist/server.cjs +39 -21
  4. package/dist/server.js +40 -23
  5. package/dist/web.cjs +421 -70
  6. package/dist/web.js +414 -69
  7. package/frames/dist/client.cjs +8 -8
  8. package/frames/dist/client.dev.cjs +8 -8
  9. package/frames/dist/client.dev.js +8 -8
  10. package/frames/dist/client.js +8 -8
  11. package/frames/dist/server.cjs +28 -18
  12. package/frames/dist/server.js +29 -19
  13. package/package.json +20 -22
  14. package/serialization/dist/decode.cjs +3 -3
  15. package/serialization/dist/decode.js +3 -3
  16. package/serialization/dist/serialization.cjs +7 -11
  17. package/serialization/dist/serialization.js +7 -6
  18. package/serialization/types/index.d.ts +69 -157
  19. package/serialization/types/serializer-decode.d.ts +89 -112
  20. package/serialization/types/serializer.d.ts +94 -0
  21. package/serialization/types-cjs/index.d.cts +69 -157
  22. package/serialization/types-cjs/serializer-decode.d.cts +89 -112
  23. package/serialization/types-cjs/serializer.d.cts +94 -0
  24. package/server-functions/dist/client.cjs +126 -19
  25. package/server-functions/dist/client.js +123 -19
  26. package/server-functions/dist/server.cjs +156 -28
  27. package/server-functions/dist/server.dev.cjs +156 -28
  28. package/server-functions/dist/server.dev.js +153 -28
  29. package/server-functions/dist/server.js +153 -28
  30. package/types/client.d.ts +152 -290
  31. package/types/constants.d.ts +18 -0
  32. package/types/cookies.d.ts +31 -75
  33. package/types/frames/frame-client.d.ts +287 -277
  34. package/types/frames/frame-container-plugin.d.ts +71 -0
  35. package/types/frames/frame-sink.d.ts +58 -160
  36. package/types/frames/frame-transport.d.ts +161 -194
  37. package/types/frames/serializer-decode.d.ts +159 -0
  38. package/types/frames/serializer.d.ts +69 -157
  39. package/types/head.d.ts +16 -0
  40. package/types/index.d.ts +1 -65
  41. package/types/index.server.d.ts +125 -0
  42. package/types/jsx.d.ts +5 -13
  43. package/types/patch-driver.d.ts +3 -0
  44. package/types/reconcile.d.ts +1 -0
  45. package/types/render.d.ts +4 -0
  46. package/types/response.d.ts +48 -152
  47. package/types/serializer-decode.d.ts +89 -112
  48. package/types/serializer.d.ts +69 -157
  49. package/types/server-functions/client.d.ts +124 -222
  50. package/types/server-functions/flash.d.ts +10 -30
  51. package/types/server-functions/registry.d.ts +100 -0
  52. package/types/server-functions/rich-args.d.ts +1 -10
  53. package/types/server-functions/server.d.ts +295 -574
  54. package/types/server-functions/shared.d.ts +155 -458
  55. package/types/server-mock.d.ts +2 -2
  56. package/types/server.d.ts +242 -434
  57. package/types-cjs/client.d.cts +152 -290
  58. package/types-cjs/constants.d.cts +18 -0
  59. package/types-cjs/cookies.d.cts +31 -75
  60. package/types-cjs/frames/frame-client.d.cts +287 -277
  61. package/types-cjs/frames/frame-container-plugin.d.cts +71 -0
  62. package/types-cjs/frames/frame-sink.d.cts +58 -160
  63. package/types-cjs/frames/frame-transport.d.cts +161 -194
  64. package/types-cjs/frames/serializer-decode.d.cts +159 -0
  65. package/types-cjs/frames/serializer.d.cts +69 -157
  66. package/types-cjs/head.d.cts +16 -0
  67. package/types-cjs/index.d.cts +1 -65
  68. package/types-cjs/index.server.d.cts +125 -0
  69. package/types-cjs/jsx.d.cts +5 -13
  70. package/types-cjs/patch-driver.d.cts +3 -0
  71. package/types-cjs/reconcile.d.cts +1 -0
  72. package/types-cjs/render.d.cts +4 -0
  73. package/types-cjs/response.d.cts +48 -152
  74. package/types-cjs/serializer-decode.d.cts +89 -112
  75. package/types-cjs/serializer.d.cts +69 -157
  76. package/types-cjs/server-functions/client.d.cts +124 -222
  77. package/types-cjs/server-functions/flash.d.cts +10 -30
  78. package/types-cjs/server-functions/registry.d.cts +100 -0
  79. package/types-cjs/server-functions/rich-args.d.cts +1 -10
  80. package/types-cjs/server-functions/server.d.cts +295 -574
  81. package/types-cjs/server-functions/shared.d.cts +155 -458
  82. package/types-cjs/server-mock.d.cts +2 -2
  83. package/types-cjs/server.d.cts +242 -434
  84. package/LICENSE +0 -21
  85. package/types/core.d.ts +0 -9
  86. 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
- const FUNCTION_HEADER = "X-Server-Function-Id";
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
- if (CALL_OBSERVERS.size === 0) return fetch(base, init);
476
- const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
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 fetch(request);
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 fn = (...args) => {
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, {}, args, metadata);
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 || `${config.endpoint}?id=${encodeURIComponent(id)}`;
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 wrapped = async (...args) => {
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
- let base = `${config.endpoint}?id=${encodeURIComponent(id)}`;
648
- if (args.length) {
649
- const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
650
- base += `&args=${encodeURIComponent(encoded)}`;
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(base, id, {
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: () => `${config.endpoint}?id=${encodeURIComponent(id)}`,
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 wrapped = (...args) => {
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(...args);
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, FUNCTION_HEADER, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerReference, serializeString, subscribeFlightData, withMeta };
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
- const FUNCTION_HEADER = "X-Server-Function-Id";
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
- return new Proxy(fn, {
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 `${config.endpoint}?id=${encodeURIComponent(id)}`;
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(request, url) {
654
- const reference = request.headers.get(FUNCTION_HEADER);
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
- if (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized) {
664
- const args = url.searchParams.get("args");
665
- if (args) {
666
- const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
667
- for (const arg of result) {
668
- parsed.push(arg);
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 (request.method === "GET" && METHODS.get(functionId) !== "GET") {
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
- const parsed = await parseArguments(request, url, instance, codec);
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;