@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.6

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 (61) hide show
  1. package/dist/dev.cjs +163 -36
  2. package/dist/dev.js +161 -37
  3. package/dist/server.cjs +178 -37
  4. package/dist/server.js +177 -38
  5. package/dist/web.cjs +143 -36
  6. package/dist/web.js +141 -37
  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 +497 -44
  12. package/frames/dist/server.js +497 -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 +164 -35
  25. package/server-functions/dist/client.js +161 -36
  26. package/server-functions/dist/server.cjs +899 -136
  27. package/server-functions/dist/server.dev.cjs +919 -136
  28. package/server-functions/dist/server.dev.js +915 -137
  29. package/server-functions/dist/server.js +895 -137
  30. package/types/client.d.ts +2 -1
  31. package/types/constants.d.ts +3 -1
  32. package/types/cookies.d.ts +6 -14
  33. package/types/frames/frame-client.d.ts +4 -0
  34. package/types/frames/serializer-decode.d.ts +14 -1
  35. package/types/frames/serializer.d.ts +7 -0
  36. package/types/jsx.d.ts +11 -16
  37. package/types/response.d.ts +11 -0
  38. package/types/serializer-decode.d.ts +14 -1
  39. package/types/serializer.d.ts +7 -0
  40. package/types/server-functions/client.d.ts +22 -2
  41. package/types/server-functions/flash.d.ts +9 -0
  42. package/types/server-functions/server.d.ts +131 -11
  43. package/types/server-functions/shared.d.ts +77 -14
  44. package/types/server-mock.d.ts +17 -2
  45. package/types/server.d.ts +16 -2
  46. package/types-cjs/client.d.cts +2 -1
  47. package/types-cjs/constants.d.cts +3 -1
  48. package/types-cjs/cookies.d.cts +6 -14
  49. package/types-cjs/frames/frame-client.d.cts +4 -0
  50. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  51. package/types-cjs/frames/serializer.d.cts +7 -0
  52. package/types-cjs/jsx.d.cts +11 -16
  53. package/types-cjs/response.d.cts +11 -0
  54. package/types-cjs/serializer-decode.d.cts +14 -1
  55. package/types-cjs/serializer.d.cts +7 -0
  56. package/types-cjs/server-functions/client.d.cts +22 -2
  57. package/types-cjs/server-functions/flash.d.cts +9 -0
  58. package/types-cjs/server-functions/server.d.cts +131 -11
  59. package/types-cjs/server-functions/shared.d.cts +77 -14
  60. package/types-cjs/server-mock.d.cts +17 -2
  61. package/types-cjs/server.d.cts +16 -2
@@ -68,17 +68,30 @@ function configureServerFunctionsCodec(codec) {
68
68
  function getServerFunctionsCodec() {
69
69
  return codecConfig.codec;
70
70
  }
71
+ const UNNAMED_FLIGHT_SOURCE = "true";
71
72
  const flightConfig = {
72
- consumer: undefined
73
+ consumers: new Map()
73
74
  };
74
- function subscribeFlightData(consumer) {
75
- flightConfig.consumer = consumer;
75
+ function assertFlightSource(source) {
76
+ if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
77
+ 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).`);
78
+ }
79
+ }
80
+ function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
81
+ const named = typeof sourceOrConsumer === "string";
82
+ if (named) assertFlightSource(sourceOrConsumer);
83
+ const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
84
+ const consumer = named ? maybeConsumer : sourceOrConsumer;
85
+ flightConfig.consumers.set(source, consumer);
76
86
  return () => {
77
- if (flightConfig.consumer === consumer) flightConfig.consumer = undefined;
87
+ if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
78
88
  };
79
89
  }
80
- function getFlightDataConsumer() {
81
- return flightConfig.consumer;
90
+ function getFlightDataConsumer(source) {
91
+ return flightConfig.consumers.get(source === undefined ? UNNAMED_FLIGHT_SOURCE : source);
92
+ }
93
+ function getFlightDataSourceIds() {
94
+ return [...flightConfig.consumers.keys()];
82
95
  }
83
96
  function frameAddress(id, args) {
84
97
  return args && args.length ? id + ":" + hashArguments(args) : id;
@@ -128,15 +141,27 @@ function serverFunctionAddress(endpoint, id) {
128
141
  const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
129
142
  return `${mount}/${encodeURIComponent(id)}`;
130
143
  }
144
+ function serverFunctionDataAddress(endpoint, id) {
145
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
146
+ return `${mount}/data/${encodeURIComponent(id)}`;
147
+ }
131
148
  function parseServerFunctionAddress(pathname, endpoint) {
132
149
  const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
133
150
  if (!pathname.startsWith(mount)) return null;
134
151
  const rest = pathname.slice(mount.length);
135
152
  if (!rest.startsWith("/")) return null;
136
- const segment = rest.slice(1);
153
+ let segment = rest.slice(1);
154
+ let data = false;
155
+ if (segment.startsWith("data/")) {
156
+ segment = segment.slice(5);
157
+ data = true;
158
+ }
137
159
  if (!segment || segment.includes("/")) return null;
138
160
  try {
139
- return decodeURIComponent(segment);
161
+ return {
162
+ id: decodeURIComponent(segment),
163
+ data
164
+ };
140
165
  } catch {
141
166
  return null;
142
167
  }
@@ -168,6 +193,28 @@ function decodeErrorHeaderValue(value) {
168
193
  }
169
194
  const INSTANCE_HEADER = "X-Server-Function-Instance";
170
195
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
196
+ const UNKNOWN_HEADER = "X-Server-Function-Unknown";
197
+ const REDIRECT_HEADER = "X-Server-Function-Redirect";
198
+ function decodeRedirectHeaderValue(value) {
199
+ if (typeof value !== "string") return undefined;
200
+ const at = value.indexOf(" ");
201
+ if (at < 0) return undefined;
202
+ const status = Number(value.slice(0, at));
203
+ const url = value.slice(at + 1);
204
+ if (!Number.isInteger(status) || !url) return undefined;
205
+ if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
206
+ let parsed;
207
+ try {
208
+ parsed = new URL(url);
209
+ } catch {
210
+ return undefined;
211
+ }
212
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
213
+ return {
214
+ status,
215
+ url
216
+ };
217
+ }
171
218
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
172
219
  const FILE_FORM_KEY = "__server_function_file__";
173
220
  const BodyFormat = {
@@ -210,7 +257,11 @@ function isJSONSafe(value) {
210
257
  const proto = Object.getPrototypeOf(v);
211
258
  if (proto !== Object.prototype && proto !== null) return false;
212
259
  if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
213
- for (const k in v) stack.push(v[k]);
260
+ for (const k in v) {
261
+ const descriptor = Object.getOwnPropertyDescriptor(v, k);
262
+ if (descriptor === undefined || !("value" in descriptor)) return false;
263
+ stack.push(descriptor.value);
264
+ }
214
265
  }
215
266
  }
216
267
  return true;
@@ -319,18 +370,34 @@ function createChunk(data) {
319
370
  class ChunkReader {
320
371
  constructor(stream) {
321
372
  this.reader = stream.getReader();
322
- this.buffer = new Uint8Array(0);
373
+ this.store = new Uint8Array(0);
374
+ this.buffer = this.store;
323
375
  this.done = false;
324
376
  }
325
377
  async readChunk() {
326
378
  const chunk = await this.reader.read();
327
- if (!chunk.done) {
328
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
329
- newBuffer.set(this.buffer);
330
- newBuffer.set(chunk.value, this.buffer.length);
331
- this.buffer = newBuffer;
332
- } else {
379
+ if (chunk.done) {
333
380
  this.done = true;
381
+ return;
382
+ }
383
+ const incoming = chunk.value;
384
+ const store = this.store;
385
+ const start = this.buffer.byteOffset;
386
+ const end = start + this.buffer.length;
387
+ const needed = this.buffer.length + incoming.length;
388
+ if (end + incoming.length <= store.length) {
389
+ store.set(incoming, end);
390
+ this.buffer = store.subarray(start, end + incoming.length);
391
+ } else if (needed <= store.length) {
392
+ store.copyWithin(0, start, end);
393
+ store.set(incoming, this.buffer.length);
394
+ this.buffer = store.subarray(0, needed);
395
+ } else {
396
+ const grown = new Uint8Array(Math.max(needed, store.length * 2));
397
+ grown.set(this.buffer);
398
+ grown.set(incoming, this.buffer.length);
399
+ this.store = grown;
400
+ this.buffer = grown.subarray(0, needed);
334
401
  }
335
402
  }
336
403
  async next() {
@@ -372,6 +439,18 @@ class ChunkReader {
372
439
  }
373
440
  }
374
441
  }
442
+ const ERROR_TRAILER_PREFIX = "!";
443
+ function errorFromTrailer(payload) {
444
+ let shape;
445
+ try {
446
+ shape = JSON.parse(payload.slice(1));
447
+ } catch {
448
+ shape = null;
449
+ }
450
+ const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
451
+ if (shape && typeof shape.name === "string") error.name = shape.name;
452
+ return error;
453
+ }
375
454
  function serializeStream(value, codecOptions) {
376
455
  return new ReadableStream({
377
456
  async start(controller) {
@@ -404,11 +483,17 @@ async function deserializeStream(source, codecOptions) {
404
483
  const reader = new ChunkReader(source.body);
405
484
  const result = await reader.next();
406
485
  if (!result.done) {
486
+ if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
487
+ throw errorFromTrailer(result.value);
488
+ }
407
489
  const {
408
490
  createJSONDeserializer
409
491
  } = await import('@solidjs/web/serialization/decode');
410
492
  const deserializeChunk = createJSONDeserializer(codecOptions);
411
493
  function interpretChunk(chunk) {
494
+ if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
495
+ throw errorFromTrailer(chunk);
496
+ }
412
497
  return deserializeChunk(JSON.parse(chunk));
413
498
  }
414
499
  reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
@@ -473,7 +558,8 @@ function serverFunctionUrl(id, boundArgs) {
473
558
  return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
474
559
  }
475
560
  function parseServerFunctionUrl(url) {
476
- return parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
561
+ const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
562
+ return parsed && parsed.id;
477
563
  }
478
564
  function serializeArguments(args) {
479
565
  if (!config.serializeArgs) {
@@ -507,18 +593,41 @@ function provideRPC() {
507
593
  decodeResponse
508
594
  });
509
595
  }
596
+ function dataAddressFor(base) {
597
+ const splitAt = base.search(/[?#]/);
598
+ const path = splitAt < 0 ? base : base.slice(0, splitAt);
599
+ const rest = splitAt < 0 ? "" : base.slice(splitAt);
600
+ const slash = path.lastIndexOf("/");
601
+ if (path.endsWith("/data/", slash + 1)) return base;
602
+ return `${path.slice(0, slash + 1)}data/${path.slice(slash + 1)}${rest}`;
603
+ }
510
604
  function serverFunctionFailure(response, value) {
511
- const error = value ?? new Error(`Server function call failed with status ${response.status}`);
512
- if (error instanceof Error && !("status" in error)) error.status = response.status;
605
+ const unknown = response.headers.get(UNKNOWN_HEADER) !== null;
606
+ 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}`);
607
+ if (error instanceof Error && !("status" in error)) {
608
+ error.status = response.status;
609
+ const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
610
+ if (retryAfter !== undefined) error.retryAfter = retryAfter;
611
+ }
612
+ if (unknown && error instanceof Error) error.unknownFunction = true;
513
613
  return error;
514
614
  }
615
+ function parseRetryAfter(header) {
616
+ if (!header) return undefined;
617
+ const trimmed = header.trim();
618
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
619
+ const date = Date.parse(trimmed);
620
+ if (!Number.isNaN(date)) return Math.max(0, Math.ceil((date - Date.now()) / 1000));
621
+ return undefined;
622
+ }
515
623
  async function createRequest(base, id, instance, options, meta) {
516
624
  const headers = {
517
625
  ...options.headers,
518
626
  [INSTANCE_HEADER]: instance
519
627
  };
520
- if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
521
- headers[SINGLE_FLIGHT_HEADER] = "true";
628
+ const flightSources = getFlightDataSourceIds();
629
+ if (flightSources.length > 0 && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
630
+ headers[SINGLE_FLIGHT_HEADER] = flightSources.join(",");
522
631
  }
523
632
  let init = {
524
633
  method: "POST",
@@ -526,10 +635,19 @@ async function createRequest(base, id, instance, options, meta) {
526
635
  headers
527
636
  };
528
637
  if (config.prepareRequest) {
529
- init = (await config.prepareRequest(init, {
638
+ const prepared = await config.prepareRequest(init, {
530
639
  id,
531
640
  meta
532
- })) || init;
641
+ });
642
+ if (prepared && prepared !== init) {
643
+ if (typeof prepared !== "object") {
644
+ throw new Error("prepareRequest must return (or mutate and return) the RequestInit it received; " + `it returned a ${typeof prepared}. Spread the init: ` + "init => ({ ...init, headers: { ...init.headers, ... } })");
645
+ }
646
+ if (!new Headers(prepared.headers).has(INSTANCE_HEADER)) {
647
+ throw new Error("prepareRequest returned an init without the transport headers " + `(${INSTANCE_HEADER}), which would send the call without its payload or ` + "protocol. Spread the init it received: " + "init => ({ ...init, headers: { ...init.headers, ... } })");
648
+ }
649
+ }
650
+ init = prepared || init;
533
651
  }
534
652
  const send = config.fetch || fetch;
535
653
  if (CALL_OBSERVERS.size === 0) return send(base, init);
@@ -626,23 +744,29 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
626
744
  if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
627
745
  throw serverFunctionFailure(response, undefined);
628
746
  }
629
- const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
747
+ const failed = response.headers.has(ERROR_HEADER);
630
748
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
631
- const consumer = getFlightDataConsumer();
632
- if (consumer) {
749
+ const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
750
+ const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
751
+ if (consumers.length > 0) {
633
752
  const payload = await decodeResponse(response);
634
- await consumer(payload.data, {
635
- response
636
- });
637
- if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
753
+ for (const [source, consumer] of consumers) {
754
+ await consumer(payload.data[source], {
755
+ response
756
+ });
757
+ }
758
+ if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
638
759
  throw serverFunctionFailure(response, payload.value);
639
760
  }
640
761
  return payload.value;
641
762
  }
642
763
  }
643
- if (response.headers.has("Location") || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER)) {
764
+ 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) {
644
765
  return response;
645
766
  }
767
+ if (response.status < 300 && !response.headers.has(BODY_FORMAT_HEADER) && !response.headers.has("X-Content-Raw")) {
768
+ throw serverFunctionFailure(response, new Error(`Server function response carries no recognized encoding (status ${response.status}` + `${response.headers.get("Content-Type") ? `, content-type ${response.headers.get("Content-Type")}` : ""}): answered by something other than the server function runtime`));
769
+ }
646
770
  const result = await decodeResponse(response.clone());
647
771
  if (failed) {
648
772
  throw serverFunctionFailure(response, result);
@@ -678,7 +802,7 @@ function createServerReference(id, name, base) {
678
802
  });
679
803
  if (hit !== undefined) return hit;
680
804
  }
681
- return fetchServerFunction(base || serverFunctionAddress(config.endpoint, id), id, invokeOptions ? {
805
+ return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
682
806
  ...invokeOptions
683
807
  } : {}, args, metadata);
684
808
  };
@@ -715,7 +839,7 @@ function GET(fn) {
715
839
  if (hit !== undefined) return hit;
716
840
  }
717
841
  const opts = invokeOptions || {};
718
- const address = serverFunctionAddress(config.endpoint, id);
842
+ const address = serverFunctionDataAddress(config.endpoint, id);
719
843
  if (!args.length) {
720
844
  return fetchServerFunction(address, id, {
721
845
  ...opts,
@@ -845,7 +969,7 @@ function live(fn) {
845
969
  emitClosed(error);
846
970
  throw error;
847
971
  }
848
- if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
972
+ 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") {
849
973
  stopped = true;
850
974
  emitClosed(error);
851
975
  throw error;
@@ -854,7 +978,8 @@ function live(fn) {
854
978
  emit("reconnecting", error);
855
979
  await new Promise(resolve => {
856
980
  wake = resolve;
857
- timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
981
+ const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
982
+ timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
858
983
  if (typeof addEventListener === "function") addEventListener("online", resolve, {
859
984
  once: true
860
985
  });
@@ -911,20 +1036,24 @@ exports.ERROR_HEADER = ERROR_HEADER;
911
1036
  exports.FLASH_COOKIE = FLASH_COOKIE;
912
1037
  exports.GET = GET;
913
1038
  exports.INSTANCE_HEADER = INSTANCE_HEADER;
1039
+ exports.REDIRECT_HEADER = REDIRECT_HEADER;
914
1040
  exports.REVALIDATE_HEADER = REVALIDATE_HEADER;
915
1041
  exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
916
1042
  exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
1043
+ exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
917
1044
  exports.clearFlashCookie = clearFlashCookie;
918
1045
  exports.configureServerFunctionsClient = configureServerFunctionsClient;
919
1046
  exports.createChunk = createChunk;
920
1047
  exports.createServerReference = createServerReference;
921
1048
  exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
1049
+ exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
922
1050
  exports.decodeResponse = decodeResponse;
923
1051
  exports.decodeResponsePayload = decodeResponsePayload;
924
1052
  exports.deserializeStream = deserializeStream;
925
1053
  exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
926
1054
  exports.frameAddress = frameAddress;
927
1055
  exports.getFlightDataConsumer = getFlightDataConsumer;
1056
+ exports.getFlightDataSourceIds = getFlightDataSourceIds;
928
1057
  exports.getServerFunctionInvocation = getServerFunctionInvocation;
929
1058
  exports.getServerFunctionMetadata = getServerFunctionMetadata;
930
1059
  exports.getServerFunctionsCodec = getServerFunctionsCodec;