@solidjs/web 2.0.0-rc.3 → 2.0.0-rc.5

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 (65) hide show
  1. package/dist/dev.cjs +398 -17
  2. package/dist/dev.js +396 -19
  3. package/dist/server.cjs +166 -38
  4. package/dist/server.js +165 -39
  5. package/dist/web.cjs +369 -17
  6. package/dist/web.js +367 -19
  7. package/frames/dist/client.cjs +41 -8
  8. package/frames/dist/client.dev.cjs +41 -8
  9. package/frames/dist/client.dev.js +41 -8
  10. package/frames/dist/client.js +41 -8
  11. package/frames/dist/server.cjs +432 -44
  12. package/frames/dist/server.js +432 -44
  13. package/package.json +2 -2
  14. package/serialization/dist/decode.cjs +4 -2
  15. package/serialization/dist/decode.js +4 -2
  16. package/serialization/dist/serialization.cjs +12 -8
  17. package/serialization/dist/serialization.js +12 -8
  18. package/serialization/types/index.d.ts +7 -0
  19. package/serialization/types/serializer-decode.d.ts +14 -1
  20. package/serialization/types/serializer.d.ts +7 -0
  21. package/serialization/types-cjs/index.d.cts +7 -0
  22. package/serialization/types-cjs/serializer-decode.d.cts +14 -1
  23. package/serialization/types-cjs/serializer.d.cts +7 -0
  24. package/server-functions/dist/client.cjs +271 -47
  25. package/server-functions/dist/client.js +264 -47
  26. package/server-functions/dist/server.cjs +872 -131
  27. package/server-functions/dist/server.dev.cjs +892 -131
  28. package/server-functions/dist/server.dev.js +884 -131
  29. package/server-functions/dist/server.js +864 -131
  30. package/types/client.d.ts +4 -2
  31. package/types/cookies.d.ts +6 -14
  32. package/types/frames/frame-client.d.ts +4 -0
  33. package/types/frames/serializer-decode.d.ts +14 -1
  34. package/types/frames/serializer.d.ts +7 -0
  35. package/types/index.d.ts +1 -0
  36. package/types/jsx.d.ts +11 -16
  37. package/types/patch-driver.d.ts +3 -0
  38. package/types/response.d.ts +20 -1
  39. package/types/serializer-decode.d.ts +14 -1
  40. package/types/serializer.d.ts +7 -0
  41. package/types/server-functions/client.d.ts +58 -7
  42. package/types/server-functions/flash.d.ts +9 -0
  43. package/types/server-functions/registry.d.ts +38 -1
  44. package/types/server-functions/server.d.ts +66 -14
  45. package/types/server-functions/shared.d.ts +122 -16
  46. package/types/server-mock.d.ts +17 -2
  47. package/types/server.d.ts +16 -2
  48. package/types-cjs/client.d.cts +4 -2
  49. package/types-cjs/cookies.d.cts +6 -14
  50. package/types-cjs/frames/frame-client.d.cts +4 -0
  51. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  52. package/types-cjs/frames/serializer.d.cts +7 -0
  53. package/types-cjs/index.d.cts +1 -0
  54. package/types-cjs/jsx.d.cts +11 -16
  55. package/types-cjs/patch-driver.d.cts +3 -0
  56. package/types-cjs/response.d.cts +20 -1
  57. package/types-cjs/serializer-decode.d.cts +14 -1
  58. package/types-cjs/serializer.d.cts +7 -0
  59. package/types-cjs/server-functions/client.d.cts +58 -7
  60. package/types-cjs/server-functions/flash.d.cts +9 -0
  61. package/types-cjs/server-functions/registry.d.cts +38 -1
  62. package/types-cjs/server-functions/server.d.cts +66 -14
  63. package/types-cjs/server-functions/shared.d.cts +122 -16
  64. package/types-cjs/server-mock.d.cts +17 -2
  65. package/types-cjs/server.d.cts +16 -2
@@ -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) {
@@ -40,17 +66,30 @@ function configureServerFunctionsCodec(codec) {
40
66
  function getServerFunctionsCodec() {
41
67
  return codecConfig.codec;
42
68
  }
69
+ const UNNAMED_FLIGHT_SOURCE = "true";
43
70
  const flightConfig = {
44
- consumer: undefined
71
+ consumers: new Map()
45
72
  };
46
- function subscribeFlightData(consumer) {
47
- flightConfig.consumer = consumer;
73
+ function assertFlightSource(source) {
74
+ if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
75
+ throw new TypeError(`Invalid flight data source id "${source}": ids ride the ` + `${SINGLE_FLIGHT_HEADER} header as a comma-separated list, and "true" is ` + `reserved for the unnamed registration (the bare consumer/hook signatures).`);
76
+ }
77
+ }
78
+ function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
79
+ const named = typeof sourceOrConsumer === "string";
80
+ if (named) assertFlightSource(sourceOrConsumer);
81
+ const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
82
+ const consumer = named ? maybeConsumer : sourceOrConsumer;
83
+ flightConfig.consumers.set(source, consumer);
48
84
  return () => {
49
- if (flightConfig.consumer === consumer) flightConfig.consumer = undefined;
85
+ if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
50
86
  };
51
87
  }
52
- function getFlightDataConsumer() {
53
- return flightConfig.consumer;
88
+ function getFlightDataConsumer(source) {
89
+ return flightConfig.consumers.get(source === undefined ? UNNAMED_FLIGHT_SOURCE : source);
90
+ }
91
+ function getFlightDataSourceIds() {
92
+ return [...flightConfig.consumers.keys()];
54
93
  }
55
94
  function frameAddress(id, args) {
56
95
  return args && args.length ? id + ":" + hashArguments(args) : id;
@@ -96,7 +135,35 @@ function stableString(value, seen) {
96
135
  }
97
136
  return out + "}";
98
137
  }
99
- const FUNCTION_HEADER = "X-Server-Function-Id";
138
+ function serverFunctionAddress(endpoint, id) {
139
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
140
+ return `${mount}/${encodeURIComponent(id)}`;
141
+ }
142
+ function serverFunctionDataAddress(endpoint, id) {
143
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
144
+ return `${mount}/data/${encodeURIComponent(id)}`;
145
+ }
146
+ function parseServerFunctionAddress(pathname, endpoint) {
147
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
148
+ if (!pathname.startsWith(mount)) return null;
149
+ const rest = pathname.slice(mount.length);
150
+ if (!rest.startsWith("/")) return null;
151
+ let segment = rest.slice(1);
152
+ let data = false;
153
+ if (segment.startsWith("data/")) {
154
+ segment = segment.slice(5);
155
+ data = true;
156
+ }
157
+ if (!segment || segment.includes("/")) return null;
158
+ try {
159
+ return {
160
+ id: decodeURIComponent(segment),
161
+ data
162
+ };
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
100
167
  const ERROR_HEADER = "X-Server-Function-Error";
101
168
  const ERROR_HEADER_MARKER = "=?1?";
102
169
  const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
@@ -124,6 +191,28 @@ function decodeErrorHeaderValue(value) {
124
191
  }
125
192
  const INSTANCE_HEADER = "X-Server-Function-Instance";
126
193
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
194
+ const UNKNOWN_HEADER = "X-Server-Function-Unknown";
195
+ const REDIRECT_HEADER = "X-Server-Function-Redirect";
196
+ function decodeRedirectHeaderValue(value) {
197
+ if (typeof value !== "string") return undefined;
198
+ const at = value.indexOf(" ");
199
+ if (at < 0) return undefined;
200
+ const status = Number(value.slice(0, at));
201
+ const url = value.slice(at + 1);
202
+ if (!Number.isInteger(status) || !url) return undefined;
203
+ if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
204
+ let parsed;
205
+ try {
206
+ parsed = new URL(url);
207
+ } catch {
208
+ return undefined;
209
+ }
210
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
211
+ return {
212
+ status,
213
+ url
214
+ };
215
+ }
127
216
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
128
217
  const FILE_FORM_KEY = "__server_function_file__";
129
218
  const BodyFormat = {
@@ -135,7 +224,8 @@ const BodyFormat = {
135
224
  File: "5",
136
225
  ArrayBuffer: "6",
137
226
  Uint8Array: "7",
138
- Json: "8"
227
+ Json: "8",
228
+ Void: "9"
139
229
  };
140
230
  const JSON_SAFE_DEPTH_LIMIT = 4096;
141
231
  const EXIT = {};
@@ -165,7 +255,11 @@ function isJSONSafe(value) {
165
255
  const proto = Object.getPrototypeOf(v);
166
256
  if (proto !== Object.prototype && proto !== null) return false;
167
257
  if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
168
- for (const k in v) stack.push(v[k]);
258
+ for (const k in v) {
259
+ const descriptor = Object.getOwnPropertyDescriptor(v, k);
260
+ if (descriptor === undefined || !("value" in descriptor)) return false;
261
+ stack.push(descriptor.value);
262
+ }
169
263
  }
170
264
  }
171
265
  return true;
@@ -274,18 +368,34 @@ function createChunk(data) {
274
368
  class ChunkReader {
275
369
  constructor(stream) {
276
370
  this.reader = stream.getReader();
277
- this.buffer = new Uint8Array(0);
371
+ this.store = new Uint8Array(0);
372
+ this.buffer = this.store;
278
373
  this.done = false;
279
374
  }
280
375
  async readChunk() {
281
376
  const chunk = await this.reader.read();
282
- if (!chunk.done) {
283
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
284
- newBuffer.set(this.buffer);
285
- newBuffer.set(chunk.value, this.buffer.length);
286
- this.buffer = newBuffer;
287
- } else {
377
+ if (chunk.done) {
288
378
  this.done = true;
379
+ return;
380
+ }
381
+ const incoming = chunk.value;
382
+ const store = this.store;
383
+ const start = this.buffer.byteOffset;
384
+ const end = start + this.buffer.length;
385
+ const needed = this.buffer.length + incoming.length;
386
+ if (end + incoming.length <= store.length) {
387
+ store.set(incoming, end);
388
+ this.buffer = store.subarray(start, end + incoming.length);
389
+ } else if (needed <= store.length) {
390
+ store.copyWithin(0, start, end);
391
+ store.set(incoming, this.buffer.length);
392
+ this.buffer = store.subarray(0, needed);
393
+ } else {
394
+ const grown = new Uint8Array(Math.max(needed, store.length * 2));
395
+ grown.set(this.buffer);
396
+ grown.set(incoming, this.buffer.length);
397
+ this.store = grown;
398
+ this.buffer = grown.subarray(0, needed);
289
399
  }
290
400
  }
291
401
  async next() {
@@ -327,6 +437,18 @@ class ChunkReader {
327
437
  }
328
438
  }
329
439
  }
440
+ const ERROR_TRAILER_PREFIX = "!";
441
+ function errorFromTrailer(payload) {
442
+ let shape;
443
+ try {
444
+ shape = JSON.parse(payload.slice(1));
445
+ } catch {
446
+ shape = null;
447
+ }
448
+ const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
449
+ if (shape && typeof shape.name === "string") error.name = shape.name;
450
+ return error;
451
+ }
330
452
  function serializeStream(value, codecOptions) {
331
453
  return new ReadableStream({
332
454
  async start(controller) {
@@ -359,11 +481,17 @@ async function deserializeStream(source, codecOptions) {
359
481
  const reader = new ChunkReader(source.body);
360
482
  const result = await reader.next();
361
483
  if (!result.done) {
484
+ if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
485
+ throw errorFromTrailer(result.value);
486
+ }
362
487
  const {
363
488
  createJSONDeserializer
364
489
  } = await import('@solidjs/web/serialization/decode');
365
490
  const deserializeChunk = createJSONDeserializer(codecOptions);
366
491
  function interpretChunk(chunk) {
492
+ if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
493
+ throw errorFromTrailer(chunk);
494
+ }
367
495
  return deserializeChunk(JSON.parse(chunk));
368
496
  }
369
497
  reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
@@ -390,6 +518,7 @@ async function decodeResponsePayload(response, codecOptions) {
390
518
 
391
519
  const config = {
392
520
  endpoint: "/_server",
521
+ fetch: undefined,
393
522
  prepareRequest: undefined,
394
523
  responseHandler: undefined,
395
524
  serializeArgs: undefined
@@ -418,6 +547,18 @@ function observeServerFunctionCalls(observer) {
418
547
  CALL_OBSERVERS.add(observer);
419
548
  return () => CALL_OBSERVERS.delete(observer);
420
549
  }
550
+ function serverFunctionUrl(id, boundArgs) {
551
+ const address = serverFunctionAddress(config.endpoint, id);
552
+ if (!boundArgs || !boundArgs.length) return address;
553
+ if (!isJSONSafe(boundArgs)) {
554
+ 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.");
555
+ }
556
+ return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
557
+ }
558
+ function parseServerFunctionUrl(url) {
559
+ const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
560
+ return parsed && parsed.id;
561
+ }
421
562
  function serializeArguments(args) {
422
563
  if (!config.serializeArgs) {
423
564
  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 +568,20 @@ function serializeArguments(args) {
427
568
  function configureServerFunctionsClient({
428
569
  endpoint,
429
570
  codec,
571
+ fetch,
430
572
  prepareRequest,
431
573
  responseHandler,
432
574
  serializeArgs
433
575
  } = {}) {
434
576
  if (endpoint !== undefined) config.endpoint = endpoint;
435
577
  if (codec !== undefined) configureServerFunctionsCodec(codec);
578
+ if (fetch !== undefined) config.fetch = fetch;
436
579
  if (prepareRequest !== undefined) config.prepareRequest = prepareRequest;
437
580
  if (responseHandler !== undefined) config.responseHandler = responseHandler;
438
581
  if (serializeArgs !== undefined) config.serializeArgs = serializeArgs;
439
582
  }
440
583
  let INSTANCE = 0;
584
+ const MAX_GET_URL_LENGTH = 2000;
441
585
  let rpcProvided = false;
442
586
  function provideRPC() {
443
587
  if (rpcProvided) return;
@@ -447,19 +591,41 @@ function provideRPC() {
447
591
  decodeResponse
448
592
  });
449
593
  }
594
+ function dataAddressFor(base) {
595
+ const splitAt = base.search(/[?#]/);
596
+ const path = splitAt < 0 ? base : base.slice(0, splitAt);
597
+ const rest = splitAt < 0 ? "" : base.slice(splitAt);
598
+ const slash = path.lastIndexOf("/");
599
+ if (path.endsWith("/data/", slash + 1)) return base;
600
+ return `${path.slice(0, slash + 1)}data/${path.slice(slash + 1)}${rest}`;
601
+ }
450
602
  function serverFunctionFailure(response, value) {
451
- const error = value ?? new Error(`Server function call failed with status ${response.status}`);
452
- if (error instanceof Error && !("status" in error)) error.status = response.status;
603
+ const unknown = response.headers.get(UNKNOWN_HEADER) !== null;
604
+ const error = value ?? new Error(unknown ? "Server function is not part of the deployment that answered (version skew or removed function)" : `Server function call failed with status ${response.status}`);
605
+ if (error instanceof Error && !("status" in error)) {
606
+ error.status = response.status;
607
+ const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
608
+ if (retryAfter !== undefined) error.retryAfter = retryAfter;
609
+ }
610
+ if (unknown && error instanceof Error) error.unknownFunction = true;
453
611
  return error;
454
612
  }
613
+ function parseRetryAfter(header) {
614
+ if (!header) return undefined;
615
+ const trimmed = header.trim();
616
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
617
+ const date = Date.parse(trimmed);
618
+ if (!Number.isNaN(date)) return Math.max(0, Math.ceil((date - Date.now()) / 1000));
619
+ return undefined;
620
+ }
455
621
  async function createRequest(base, id, instance, options, meta) {
456
622
  const headers = {
457
623
  ...options.headers,
458
- [FUNCTION_HEADER]: id,
459
624
  [INSTANCE_HEADER]: instance
460
625
  };
461
- if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
462
- headers[SINGLE_FLIGHT_HEADER] = "true";
626
+ const flightSources = getFlightDataSourceIds();
627
+ if (flightSources.length > 0 && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
628
+ headers[SINGLE_FLIGHT_HEADER] = flightSources.join(",");
463
629
  }
464
630
  let init = {
465
631
  method: "POST",
@@ -472,10 +638,14 @@ async function createRequest(base, id, instance, options, meta) {
472
638
  meta
473
639
  })) || init;
474
640
  }
475
- if (CALL_OBSERVERS.size === 0) return fetch(base, init);
476
- const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
641
+ const send = config.fetch || fetch;
642
+ if (CALL_OBSERVERS.size === 0) return send(base, init);
643
+ const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), {
644
+ ...init,
645
+ body: init.body instanceof ReadableStream ? undefined : init.body
646
+ });
477
647
  notifyCallObservers("request", id, instance, request, meta);
478
- const response = await fetch(request);
648
+ const response = await send(base, init);
479
649
  notifyCallObservers("response", id, instance, response, meta);
480
650
  return response;
481
651
  }
@@ -560,21 +730,27 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
560
730
  });
561
731
  if (handled !== undefined) return handled;
562
732
  }
563
- const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
733
+ if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
734
+ throw serverFunctionFailure(response, undefined);
735
+ }
736
+ const failed = response.headers.has(ERROR_HEADER);
564
737
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
565
- const consumer = getFlightDataConsumer();
566
- if (consumer) {
738
+ const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
739
+ const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
740
+ if (consumers.length > 0) {
567
741
  const payload = await decodeResponse(response);
568
- await consumer(payload.data, {
569
- response
570
- });
571
- if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
742
+ for (const [source, consumer] of consumers) {
743
+ await consumer(payload.data[source], {
744
+ response
745
+ });
746
+ }
747
+ if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
572
748
  throw serverFunctionFailure(response, payload.value);
573
749
  }
574
750
  return payload.value;
575
751
  }
576
752
  }
577
- if (response.headers.has("Location") || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER)) {
753
+ if (response.headers.has(REDIRECT_HEADER) || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER) || response.status >= 300 && response.status < 400 && response.status !== 304) {
578
754
  return response;
579
755
  }
580
756
  const result = await decodeResponse(response.clone());
@@ -602,7 +778,7 @@ function createServerReference(id, name, base) {
602
778
  const metadata = name === undefined ? {} : {
603
779
  name
604
780
  };
605
- const fn = (...args) => {
781
+ const run = (args, invokeOptions) => {
606
782
  const handler = config.responseHandler;
607
783
  if (handler && handler.intercept) {
608
784
  const hit = handler.intercept({
@@ -612,14 +788,18 @@ function createServerReference(id, name, base) {
612
788
  });
613
789
  if (hit !== undefined) return hit;
614
790
  }
615
- return fetchServerFunction(base || config.endpoint, id, {}, args, metadata);
791
+ return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
792
+ ...invokeOptions
793
+ } : {}, args, metadata);
616
794
  };
795
+ const fn = (...args) => run(args);
617
796
  fn[SERVER_FUNCTION_METADATA] = metadata;
797
+ fn[SERVER_FUNCTION_INVOKE] = run;
618
798
  return new Proxy(fn, {
619
799
  get(target, prop) {
620
800
  if (prop === "id") return id;
621
801
  if (prop === "url") {
622
- return base || `${config.endpoint}?id=${encodeURIComponent(id)}`;
802
+ return base || serverFunctionAddress(config.endpoint, id);
623
803
  }
624
804
  return target[prop];
625
805
  }
@@ -634,7 +814,7 @@ function GET(fn) {
634
814
  const metadata = {
635
815
  ...getServerFunctionMetadata(fn)
636
816
  };
637
- const wrapped = async (...args) => {
817
+ const run = async (args, invokeOptions) => {
638
818
  const handler = config.responseHandler;
639
819
  if (handler && handler.intercept) {
640
820
  const hit = handler.intercept({
@@ -644,19 +824,34 @@ function GET(fn) {
644
824
  });
645
825
  if (hit !== undefined) return hit;
646
826
  }
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)}`;
827
+ const opts = invokeOptions || {};
828
+ const address = serverFunctionDataAddress(config.endpoint, id);
829
+ if (!args.length) {
830
+ return fetchServerFunction(address, id, {
831
+ ...opts,
832
+ method: "GET"
833
+ }, [], metadata, args);
651
834
  }
652
- return fetchServerFunction(base, id, {
835
+ const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
836
+ const url = `${address}?args=${encodeURIComponent(encoded)}`;
837
+ const absolute = new URL(url, globalThis.location?.href || "http://localhost").href;
838
+ if (absolute.length > MAX_GET_URL_LENGTH) {
839
+ return fetchServerFunction(address, id, {
840
+ ...opts,
841
+ read: true
842
+ }, args, metadata);
843
+ }
844
+ return fetchServerFunction(url, id, {
845
+ ...opts,
653
846
  method: "GET"
654
847
  }, [], metadata, args);
655
848
  };
849
+ const wrapped = (...args) => run(args);
656
850
  wrapped[SERVER_FUNCTION_METADATA] = metadata;
851
+ wrapped[SERVER_FUNCTION_INVOKE] = run;
657
852
  wrapped.id = id;
658
853
  Object.defineProperty(wrapped, "url", {
659
- get: () => `${config.endpoint}?id=${encodeURIComponent(id)}`,
854
+ get: () => serverFunctionAddress(config.endpoint, id),
660
855
  configurable: true
661
856
  });
662
857
  return withMeta(wrapped, {
@@ -672,7 +867,7 @@ function live(fn) {
672
867
  ...getServerFunctionMetadata(fn),
673
868
  live: true
674
869
  };
675
- const wrapped = (...args) => {
870
+ const makeIterable = (args, invokeOptions) => {
676
871
  const iterable = {
677
872
  [LIVE_SOURCE]: true,
678
873
  [Symbol.asyncIterator]() {
@@ -686,6 +881,12 @@ function live(fn) {
686
881
  done: true,
687
882
  value: undefined
688
883
  };
884
+ const invokeSignal = invokeOptions && invokeOptions.signal;
885
+ const controller = new AbortController();
886
+ const wireOptions = {
887
+ ...invokeOptions,
888
+ signal: invokeSignal ? AbortSignal.any([invokeSignal, controller.signal]) : controller.signal
889
+ };
689
890
  const emit = (state, error) => {
690
891
  try {
691
892
  iterable.onstatus && iterable.onstatus(state, error);
@@ -707,7 +908,7 @@ function live(fn) {
707
908
  }
708
909
  };
709
910
  const callOnce = () => {
710
- if (metadata.method === "GET") return fn(...args);
911
+ if (metadata.method === "GET") return fn[SERVER_FUNCTION_INVOKE](args, wireOptions);
711
912
  const handler = config.responseHandler;
712
913
  if (handler && handler.intercept) {
713
914
  const hit = handler.intercept({
@@ -718,6 +919,7 @@ function live(fn) {
718
919
  if (hit !== undefined) return hit;
719
920
  }
720
921
  return fetchServerFunction(fn.url, id, {
922
+ ...wireOptions,
721
923
  read: true
722
924
  }, args, metadata, args);
723
925
  };
@@ -732,6 +934,7 @@ function live(fn) {
732
934
  }();
733
935
  if (stopped) {
734
936
  closeIt();
937
+ controller.abort();
735
938
  return DONE;
736
939
  }
737
940
  emit("connected");
@@ -745,8 +948,14 @@ function live(fn) {
745
948
  attempts = 0;
746
949
  return r;
747
950
  } catch (error) {
951
+ if (stopped) return DONE;
748
952
  if (!connected) throw error;
749
- if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
953
+ if (invokeSignal && invokeSignal.aborted) {
954
+ stopped = true;
955
+ emitClosed(error);
956
+ throw error;
957
+ }
958
+ if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 425 && error.status !== 429 && typeof error.retryAfter !== "number") {
750
959
  stopped = true;
751
960
  emitClosed(error);
752
961
  throw error;
@@ -755,13 +964,18 @@ function live(fn) {
755
964
  emit("reconnecting", error);
756
965
  await new Promise(resolve => {
757
966
  wake = resolve;
758
- timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
967
+ const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
968
+ timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
759
969
  if (typeof addEventListener === "function") addEventListener("online", resolve, {
760
970
  once: true
761
971
  });
972
+ if (invokeSignal) invokeSignal.addEventListener("abort", resolve, {
973
+ once: true
974
+ });
762
975
  });
763
976
  clearTimeout(timer);
764
977
  if (typeof removeEventListener === "function") removeEventListener("online", wake);
978
+ if (invokeSignal) invokeSignal.removeEventListener("abort", wake);
765
979
  timer = wake = undefined;
766
980
  }
767
981
  }
@@ -774,6 +988,7 @@ function live(fn) {
774
988
  if (timer !== undefined) clearTimeout(timer);
775
989
  if (wake) wake();
776
990
  closeIt(value);
991
+ controller.abort();
777
992
  emitClosed();
778
993
  return Promise.resolve({
779
994
  done: true,
@@ -785,7 +1000,9 @@ function live(fn) {
785
1000
  };
786
1001
  return iterable;
787
1002
  };
1003
+ const wrapped = (...args) => makeIterable(args);
788
1004
  wrapped[SERVER_FUNCTION_METADATA] = metadata;
1005
+ wrapped[SERVER_FUNCTION_INVOKE] = makeIterable;
789
1006
  wrapped.id = id;
790
1007
  Object.defineProperty(wrapped, "url", {
791
1008
  get: () => fn.url,
@@ -800,4 +1017,4 @@ function getServerFunctionInvocation() {
800
1017
  return undefined;
801
1018
  }
802
1019
 
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 };
1020
+ export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, GET, INSTANCE_HEADER, REDIRECT_HEADER, REVALIDATE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, UNKNOWN_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeRedirectHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getFlightDataSourceIds, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerReference, serializeString, serverFunctionUrl, subscribeFlightData, withMeta };