@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
@@ -66,17 +66,30 @@ function configureServerFunctionsCodec(codec) {
66
66
  function getServerFunctionsCodec() {
67
67
  return codecConfig.codec;
68
68
  }
69
+ const UNNAMED_FLIGHT_SOURCE = "true";
69
70
  const flightConfig = {
70
- consumer: undefined
71
+ consumers: new Map()
71
72
  };
72
- function subscribeFlightData(consumer) {
73
- 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);
74
84
  return () => {
75
- if (flightConfig.consumer === consumer) flightConfig.consumer = undefined;
85
+ if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
76
86
  };
77
87
  }
78
- function getFlightDataConsumer() {
79
- 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()];
80
93
  }
81
94
  function frameAddress(id, args) {
82
95
  return args && args.length ? id + ":" + hashArguments(args) : id;
@@ -126,15 +139,27 @@ function serverFunctionAddress(endpoint, id) {
126
139
  const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
127
140
  return `${mount}/${encodeURIComponent(id)}`;
128
141
  }
142
+ function serverFunctionDataAddress(endpoint, id) {
143
+ const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
144
+ return `${mount}/data/${encodeURIComponent(id)}`;
145
+ }
129
146
  function parseServerFunctionAddress(pathname, endpoint) {
130
147
  const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
131
148
  if (!pathname.startsWith(mount)) return null;
132
149
  const rest = pathname.slice(mount.length);
133
150
  if (!rest.startsWith("/")) return null;
134
- const segment = rest.slice(1);
151
+ let segment = rest.slice(1);
152
+ let data = false;
153
+ if (segment.startsWith("data/")) {
154
+ segment = segment.slice(5);
155
+ data = true;
156
+ }
135
157
  if (!segment || segment.includes("/")) return null;
136
158
  try {
137
- return decodeURIComponent(segment);
159
+ return {
160
+ id: decodeURIComponent(segment),
161
+ data
162
+ };
138
163
  } catch {
139
164
  return null;
140
165
  }
@@ -166,6 +191,28 @@ function decodeErrorHeaderValue(value) {
166
191
  }
167
192
  const INSTANCE_HEADER = "X-Server-Function-Instance";
168
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
+ }
169
216
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
170
217
  const FILE_FORM_KEY = "__server_function_file__";
171
218
  const BodyFormat = {
@@ -208,7 +255,11 @@ function isJSONSafe(value) {
208
255
  const proto = Object.getPrototypeOf(v);
209
256
  if (proto !== Object.prototype && proto !== null) return false;
210
257
  if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
211
- 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
+ }
212
263
  }
213
264
  }
214
265
  return true;
@@ -317,18 +368,34 @@ function createChunk(data) {
317
368
  class ChunkReader {
318
369
  constructor(stream) {
319
370
  this.reader = stream.getReader();
320
- this.buffer = new Uint8Array(0);
371
+ this.store = new Uint8Array(0);
372
+ this.buffer = this.store;
321
373
  this.done = false;
322
374
  }
323
375
  async readChunk() {
324
376
  const chunk = await this.reader.read();
325
- if (!chunk.done) {
326
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
327
- newBuffer.set(this.buffer);
328
- newBuffer.set(chunk.value, this.buffer.length);
329
- this.buffer = newBuffer;
330
- } else {
377
+ if (chunk.done) {
331
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);
332
399
  }
333
400
  }
334
401
  async next() {
@@ -370,6 +437,18 @@ class ChunkReader {
370
437
  }
371
438
  }
372
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
+ }
373
452
  function serializeStream(value, codecOptions) {
374
453
  return new ReadableStream({
375
454
  async start(controller) {
@@ -402,11 +481,17 @@ async function deserializeStream(source, codecOptions) {
402
481
  const reader = new ChunkReader(source.body);
403
482
  const result = await reader.next();
404
483
  if (!result.done) {
484
+ if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
485
+ throw errorFromTrailer(result.value);
486
+ }
405
487
  const {
406
488
  createJSONDeserializer
407
489
  } = await import('@solidjs/web/serialization/decode');
408
490
  const deserializeChunk = createJSONDeserializer(codecOptions);
409
491
  function interpretChunk(chunk) {
492
+ if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
493
+ throw errorFromTrailer(chunk);
494
+ }
410
495
  return deserializeChunk(JSON.parse(chunk));
411
496
  }
412
497
  reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
@@ -471,7 +556,8 @@ function serverFunctionUrl(id, boundArgs) {
471
556
  return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
472
557
  }
473
558
  function parseServerFunctionUrl(url) {
474
- return parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
559
+ const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
560
+ return parsed && parsed.id;
475
561
  }
476
562
  function serializeArguments(args) {
477
563
  if (!config.serializeArgs) {
@@ -505,18 +591,41 @@ function provideRPC() {
505
591
  decodeResponse
506
592
  });
507
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
+ }
508
602
  function serverFunctionFailure(response, value) {
509
- const error = value ?? new Error(`Server function call failed with status ${response.status}`);
510
- 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;
511
611
  return error;
512
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
+ }
513
621
  async function createRequest(base, id, instance, options, meta) {
514
622
  const headers = {
515
623
  ...options.headers,
516
624
  [INSTANCE_HEADER]: instance
517
625
  };
518
- if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
519
- 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(",");
520
629
  }
521
630
  let init = {
522
631
  method: "POST",
@@ -524,10 +633,19 @@ async function createRequest(base, id, instance, options, meta) {
524
633
  headers
525
634
  };
526
635
  if (config.prepareRequest) {
527
- init = (await config.prepareRequest(init, {
636
+ const prepared = await config.prepareRequest(init, {
528
637
  id,
529
638
  meta
530
- })) || init;
639
+ });
640
+ if (prepared && prepared !== init) {
641
+ if (typeof prepared !== "object") {
642
+ 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, ... } })");
643
+ }
644
+ if (!new Headers(prepared.headers).has(INSTANCE_HEADER)) {
645
+ 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, ... } })");
646
+ }
647
+ }
648
+ init = prepared || init;
531
649
  }
532
650
  const send = config.fetch || fetch;
533
651
  if (CALL_OBSERVERS.size === 0) return send(base, init);
@@ -624,23 +742,29 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
624
742
  if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
625
743
  throw serverFunctionFailure(response, undefined);
626
744
  }
627
- const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
745
+ const failed = response.headers.has(ERROR_HEADER);
628
746
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
629
- const consumer = getFlightDataConsumer();
630
- if (consumer) {
747
+ const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
748
+ const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
749
+ if (consumers.length > 0) {
631
750
  const payload = await decodeResponse(response);
632
- await consumer(payload.data, {
633
- response
634
- });
635
- if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
751
+ for (const [source, consumer] of consumers) {
752
+ await consumer(payload.data[source], {
753
+ response
754
+ });
755
+ }
756
+ if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
636
757
  throw serverFunctionFailure(response, payload.value);
637
758
  }
638
759
  return payload.value;
639
760
  }
640
761
  }
641
- if (response.headers.has("Location") || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER)) {
762
+ 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) {
642
763
  return response;
643
764
  }
765
+ if (response.status < 300 && !response.headers.has(BODY_FORMAT_HEADER) && !response.headers.has("X-Content-Raw")) {
766
+ 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`));
767
+ }
644
768
  const result = await decodeResponse(response.clone());
645
769
  if (failed) {
646
770
  throw serverFunctionFailure(response, result);
@@ -676,7 +800,7 @@ function createServerReference(id, name, base) {
676
800
  });
677
801
  if (hit !== undefined) return hit;
678
802
  }
679
- return fetchServerFunction(base || serverFunctionAddress(config.endpoint, id), id, invokeOptions ? {
803
+ return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
680
804
  ...invokeOptions
681
805
  } : {}, args, metadata);
682
806
  };
@@ -713,7 +837,7 @@ function GET(fn) {
713
837
  if (hit !== undefined) return hit;
714
838
  }
715
839
  const opts = invokeOptions || {};
716
- const address = serverFunctionAddress(config.endpoint, id);
840
+ const address = serverFunctionDataAddress(config.endpoint, id);
717
841
  if (!args.length) {
718
842
  return fetchServerFunction(address, id, {
719
843
  ...opts,
@@ -843,7 +967,7 @@ function live(fn) {
843
967
  emitClosed(error);
844
968
  throw error;
845
969
  }
846
- if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
970
+ 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") {
847
971
  stopped = true;
848
972
  emitClosed(error);
849
973
  throw error;
@@ -852,7 +976,8 @@ function live(fn) {
852
976
  emit("reconnecting", error);
853
977
  await new Promise(resolve => {
854
978
  wake = resolve;
855
- timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
979
+ const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
980
+ timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
856
981
  if (typeof addEventListener === "function") addEventListener("online", resolve, {
857
982
  once: true
858
983
  });
@@ -904,4 +1029,4 @@ function getServerFunctionInvocation() {
904
1029
  return undefined;
905
1030
  }
906
1031
 
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 };
1032
+ 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 };