@solidjs/web 2.0.0-rc.4 → 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 (57) hide show
  1. package/dist/dev.cjs +52 -7
  2. package/dist/dev.js +51 -8
  3. package/dist/server.cjs +162 -35
  4. package/dist/server.js +161 -36
  5. package/dist/web.cjs +32 -7
  6. package/dist/web.js +31 -8
  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 +150 -33
  25. package/server-functions/dist/client.js +147 -34
  26. package/server-functions/dist/server.cjs +731 -118
  27. package/server-functions/dist/server.dev.cjs +751 -118
  28. package/server-functions/dist/server.dev.js +747 -119
  29. package/server-functions/dist/server.js +727 -119
  30. package/types/cookies.d.ts +6 -14
  31. package/types/frames/frame-client.d.ts +4 -0
  32. package/types/frames/serializer-decode.d.ts +14 -1
  33. package/types/frames/serializer.d.ts +7 -0
  34. package/types/jsx.d.ts +11 -16
  35. package/types/response.d.ts +11 -0
  36. package/types/serializer-decode.d.ts +14 -1
  37. package/types/serializer.d.ts +7 -0
  38. package/types/server-functions/client.d.ts +22 -2
  39. package/types/server-functions/flash.d.ts +9 -0
  40. package/types/server-functions/server.d.ts +58 -9
  41. package/types/server-functions/shared.d.ts +77 -14
  42. package/types/server-mock.d.ts +17 -2
  43. package/types/server.d.ts +16 -2
  44. package/types-cjs/cookies.d.cts +6 -14
  45. package/types-cjs/frames/frame-client.d.cts +4 -0
  46. package/types-cjs/frames/serializer-decode.d.cts +14 -1
  47. package/types-cjs/frames/serializer.d.cts +7 -0
  48. package/types-cjs/jsx.d.cts +11 -16
  49. package/types-cjs/response.d.cts +11 -0
  50. package/types-cjs/serializer-decode.d.cts +14 -1
  51. package/types-cjs/serializer.d.cts +7 -0
  52. package/types-cjs/server-functions/client.d.cts +22 -2
  53. package/types-cjs/server-functions/flash.d.cts +9 -0
  54. package/types-cjs/server-functions/server.d.cts +58 -9
  55. package/types-cjs/server-functions/shared.d.cts +77 -14
  56. package/types-cjs/server-mock.d.cts +17 -2
  57. package/types-cjs/server.d.cts +16 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solidjs/web",
3
3
  "description": "Solid's web runtime: client rendering, hydration, SSR, and DOM-specific control flow (Portal, Dynamic).",
4
- "version": "2.0.0-rc.4",
4
+ "version": "2.0.0-rc.5",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "homepage": "https://solidjs.com",
@@ -398,7 +398,7 @@
398
398
  "seroval-plugins": "~1.5.4"
399
399
  },
400
400
  "peerDependencies": {
401
- "solid-js": "^2.0.0-rc.4"
401
+ "solid-js": "^2.0.0-rc.5"
402
402
  },
403
403
  "devDependencies": {
404
404
  "@codspeed/vitest-plugin": "^5.4.0",
@@ -85,12 +85,14 @@ const JSON_CODEC_DEPTH_LIMIT = 64;
85
85
  function resolveCodecOptions({
86
86
  plugins,
87
87
  disabledFeatures,
88
- depthLimit
88
+ depthLimit,
89
+ serializeErrorStacks
89
90
  } = {}) {
90
91
  return {
91
92
  plugins: resolveSerializerPlugins(plugins),
92
93
  disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
93
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
94
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit,
95
+ serializeErrorStacks
94
96
  };
95
97
  }
96
98
  function createJSONDeserializer(options) {
@@ -83,12 +83,14 @@ const JSON_CODEC_DEPTH_LIMIT = 64;
83
83
  function resolveCodecOptions({
84
84
  plugins,
85
85
  disabledFeatures,
86
- depthLimit
86
+ depthLimit,
87
+ serializeErrorStacks
87
88
  } = {}) {
88
89
  return {
89
90
  plugins: resolveSerializerPlugins(plugins),
90
91
  disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
91
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
92
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit,
93
+ serializeErrorStacks
92
94
  };
93
95
  }
94
96
  function createJSONDeserializer(options) {
@@ -85,12 +85,14 @@ const JSON_CODEC_DEPTH_LIMIT = 64;
85
85
  function resolveCodecOptions({
86
86
  plugins,
87
87
  disabledFeatures,
88
- depthLimit
88
+ depthLimit,
89
+ serializeErrorStacks
89
90
  } = {}) {
90
91
  return {
91
92
  plugins: resolveSerializerPlugins(plugins),
92
93
  disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
93
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
94
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit,
95
+ serializeErrorStacks
94
96
  };
95
97
  }
96
98
  function createJSONDeserializer(options) {
@@ -133,7 +135,7 @@ function createJSONDataTable(options) {
133
135
  }
134
136
 
135
137
  const DEFAULT_DISABLED_FEATURES = seroval.Feature.AggregateError | seroval.Feature.BigIntTypedArray;
136
- const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : seroval.Feature.ErrorPrototypeStack;
138
+ const serializeOnlyDisabledFeatures = serializeErrorStacks => (serializeErrorStacks === undefined ? process.env.NODE_ENV === "development" : serializeErrorStacks) ? 0 : seroval.Feature.ErrorPrototypeStack;
137
139
  const HYDRATION_GLOBAL = "_$HY.r";
138
140
  const createPlugin = seroval.createPlugin;
139
141
  const OpaqueReference = seroval.OpaqueReference;
@@ -141,7 +143,7 @@ function createSerializer(options) {
141
143
  return new seroval.Serializer({
142
144
  ...options,
143
145
  plugins: resolveSerializerPlugins(options.plugins),
144
- disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures()
146
+ disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures(options.serializeErrorStacks)
145
147
  });
146
148
  }
147
149
  function createHydrationSerializer({
@@ -175,7 +177,7 @@ function serializeJSON(value, {
175
177
  onDone,
176
178
  onError,
177
179
  ...resolved,
178
- disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
180
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(resolved.serializeErrorStacks)
179
181
  });
180
182
  }
181
183
  function createJSONSerializer({
@@ -184,12 +186,14 @@ function createJSONSerializer({
184
186
  onError,
185
187
  plugins,
186
188
  disabledFeatures,
187
- depthLimit
189
+ depthLimit,
190
+ serializeErrorStacks
188
191
  }) {
189
192
  const resolved = resolveCodecOptions({
190
193
  plugins,
191
194
  disabledFeatures,
192
- depthLimit
195
+ depthLimit,
196
+ serializeErrorStacks
193
197
  });
194
198
  const refs = new Map();
195
199
  const cancels = new Set();
@@ -211,7 +215,7 @@ function createJSONSerializer({
211
215
  const stream = seroval.toCrossJSONStream(value, {
212
216
  refs,
213
217
  plugins: resolved.plugins,
214
- disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(),
218
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(resolved.serializeErrorStacks),
215
219
  onParse(node, initial) {
216
220
  onData({
217
221
  key,
@@ -83,12 +83,14 @@ const JSON_CODEC_DEPTH_LIMIT = 64;
83
83
  function resolveCodecOptions({
84
84
  plugins,
85
85
  disabledFeatures,
86
- depthLimit
86
+ depthLimit,
87
+ serializeErrorStacks
87
88
  } = {}) {
88
89
  return {
89
90
  plugins: resolveSerializerPlugins(plugins),
90
91
  disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
91
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
92
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit,
93
+ serializeErrorStacks
92
94
  };
93
95
  }
94
96
  function createJSONDeserializer(options) {
@@ -131,7 +133,7 @@ function createJSONDataTable(options) {
131
133
  }
132
134
 
133
135
  const DEFAULT_DISABLED_FEATURES = Feature.AggregateError | Feature.BigIntTypedArray;
134
- const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
136
+ const serializeOnlyDisabledFeatures = serializeErrorStacks => (serializeErrorStacks === undefined ? process.env.NODE_ENV === "development" : serializeErrorStacks) ? 0 : Feature.ErrorPrototypeStack;
135
137
  const HYDRATION_GLOBAL = "_$HY.r";
136
138
  const createPlugin = createPlugin$1;
137
139
  const OpaqueReference = OpaqueReference$1;
@@ -139,7 +141,7 @@ function createSerializer(options) {
139
141
  return new Serializer({
140
142
  ...options,
141
143
  plugins: resolveSerializerPlugins(options.plugins),
142
- disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures()
144
+ disabledFeatures: (options.disabledFeatures === undefined ? DEFAULT_DISABLED_FEATURES : options.disabledFeatures) | serializeOnlyDisabledFeatures(options.serializeErrorStacks)
143
145
  });
144
146
  }
145
147
  function createHydrationSerializer({
@@ -173,7 +175,7 @@ function serializeJSON(value, {
173
175
  onDone,
174
176
  onError,
175
177
  ...resolved,
176
- disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
178
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(resolved.serializeErrorStacks)
177
179
  });
178
180
  }
179
181
  function createJSONSerializer({
@@ -182,12 +184,14 @@ function createJSONSerializer({
182
184
  onError,
183
185
  plugins,
184
186
  disabledFeatures,
185
- depthLimit
187
+ depthLimit,
188
+ serializeErrorStacks
186
189
  }) {
187
190
  const resolved = resolveCodecOptions({
188
191
  plugins,
189
192
  disabledFeatures,
190
- depthLimit
193
+ depthLimit,
194
+ serializeErrorStacks
191
195
  });
192
196
  const refs = new Map();
193
197
  const cancels = new Set();
@@ -209,7 +213,7 @@ function createJSONSerializer({
209
213
  const stream = toCrossJSONStream(value, {
210
214
  refs,
211
215
  plugins: resolved.plugins,
212
- disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(),
216
+ disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures(resolved.serializeErrorStacks),
213
217
  onParse(node, initial) {
214
218
  onData({
215
219
  key,
@@ -19,6 +19,13 @@ export interface WebSerializerOptions {
19
19
  * any override — serialized stacks leak server paths to the client.
20
20
  */
21
21
  disabledFeatures?: number;
22
+ /**
23
+ * Whether serialized `Error`s carry their `.stack`. Defaults to
24
+ * `NODE_ENV === "development"`; set `false` to pin production disclosure
25
+ * to the deployment rather than the ambient variable (#3152 — see
26
+ * `JSONCodecOptions.serializeErrorStacks`).
27
+ */
28
+ serializeErrorStacks?: boolean;
22
29
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
23
30
  plugins?: SerializerPlugin[];
24
31
  /** Receives each emitted script chunk. */
@@ -105,6 +105,18 @@ export interface JSONCodecOptions {
105
105
  disabledFeatures?: number;
106
106
  /** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
107
107
  depthLimit?: number;
108
+ /**
109
+ * Whether serialized `Error`s carry their `.stack` — server file paths,
110
+ * internal function names, the shape of the deployment — to the peer.
111
+ * Defaults to `NODE_ENV === "development"`, but that signal describes the
112
+ * PROCESS, not the artifact: a production build running with
113
+ * `NODE_ENV=development` (a base image, a stray dotenv) ships stacks to
114
+ * the wire — including for errors marked safe with `markSafeError`, whose
115
+ * stacks traverse application code (#3152). Set `false` to pin production
116
+ * disclosure regardless of the ambient variable. Encode-side only; the
117
+ * decode side is always permissive, so it need not match peers.
118
+ */
119
+ serializeErrorStacks?: boolean;
108
120
  }
109
121
  /**
110
122
  * A resident, response-scoped decode table over the keyed JSON codec: apply
@@ -142,10 +154,11 @@ export declare const DEFAULT_WEB_PLUGINS: any; /**
142
154
  * Integration-facing; may change (see the entry banner).
143
155
  */
144
156
  export declare function resolveSerializerPlugins(customPlugins?: SerializerPlugin[]): SerializerPlugin[];
145
- export declare function resolveCodecOptions({ plugins, disabledFeatures, depthLimit }?: JSONCodecOptions): {
157
+ export declare function resolveCodecOptions({ plugins, disabledFeatures, depthLimit, serializeErrorStacks }?: JSONCodecOptions): {
146
158
  plugins: SerializerPlugin<any, any>[];
147
159
  disabledFeatures: any;
148
160
  depthLimit: number;
161
+ serializeErrorStacks: boolean;
149
162
  }; /**
150
163
  * Creates the decoding counterpart of `serializeJSON`. Cross-references
151
164
  * between chunks resolve through state shared across calls, so all chunks
@@ -19,6 +19,13 @@ export interface WebSerializerOptions {
19
19
  * any override — serialized stacks leak server paths to the client.
20
20
  */
21
21
  disabledFeatures?: number;
22
+ /**
23
+ * Whether serialized `Error`s carry their `.stack`. Defaults to
24
+ * `NODE_ENV === "development"`; set `false` to pin production disclosure
25
+ * to the deployment rather than the ambient variable (#3152 — see
26
+ * `JSONCodecOptions.serializeErrorStacks`).
27
+ */
28
+ serializeErrorStacks?: boolean;
22
29
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
23
30
  plugins?: SerializerPlugin[];
24
31
  /** Receives each emitted script chunk. */
@@ -19,6 +19,13 @@ export interface WebSerializerOptions {
19
19
  * any override — serialized stacks leak server paths to the client.
20
20
  */
21
21
  disabledFeatures?: number;
22
+ /**
23
+ * Whether serialized `Error`s carry their `.stack`. Defaults to
24
+ * `NODE_ENV === "development"`; set `false` to pin production disclosure
25
+ * to the deployment rather than the ambient variable (#3152 — see
26
+ * `JSONCodecOptions.serializeErrorStacks`).
27
+ */
28
+ serializeErrorStacks?: boolean;
22
29
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
23
30
  plugins?: SerializerPlugin[];
24
31
  /** Receives each emitted script chunk. */
@@ -105,6 +105,18 @@ export interface JSONCodecOptions {
105
105
  disabledFeatures?: number;
106
106
  /** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
107
107
  depthLimit?: number;
108
+ /**
109
+ * Whether serialized `Error`s carry their `.stack` — server file paths,
110
+ * internal function names, the shape of the deployment — to the peer.
111
+ * Defaults to `NODE_ENV === "development"`, but that signal describes the
112
+ * PROCESS, not the artifact: a production build running with
113
+ * `NODE_ENV=development` (a base image, a stray dotenv) ships stacks to
114
+ * the wire — including for errors marked safe with `markSafeError`, whose
115
+ * stacks traverse application code (#3152). Set `false` to pin production
116
+ * disclosure regardless of the ambient variable. Encode-side only; the
117
+ * decode side is always permissive, so it need not match peers.
118
+ */
119
+ serializeErrorStacks?: boolean;
108
120
  }
109
121
  /**
110
122
  * A resident, response-scoped decode table over the keyed JSON codec: apply
@@ -142,10 +154,11 @@ export declare const DEFAULT_WEB_PLUGINS: any; /**
142
154
  * Integration-facing; may change (see the entry banner).
143
155
  */
144
156
  export declare function resolveSerializerPlugins(customPlugins?: SerializerPlugin[]): SerializerPlugin[];
145
- export declare function resolveCodecOptions({ plugins, disabledFeatures, depthLimit }?: JSONCodecOptions): {
157
+ export declare function resolveCodecOptions({ plugins, disabledFeatures, depthLimit, serializeErrorStacks }?: JSONCodecOptions): {
146
158
  plugins: SerializerPlugin<any, any>[];
147
159
  disabledFeatures: any;
148
160
  depthLimit: number;
161
+ serializeErrorStacks: boolean;
149
162
  }; /**
150
163
  * Creates the decoding counterpart of `serializeJSON`. Cross-references
151
164
  * between chunks resolve through state shared across calls, so all chunks
@@ -19,6 +19,13 @@ export interface WebSerializerOptions {
19
19
  * any override — serialized stacks leak server paths to the client.
20
20
  */
21
21
  disabledFeatures?: number;
22
+ /**
23
+ * Whether serialized `Error`s carry their `.stack`. Defaults to
24
+ * `NODE_ENV === "development"`; set `false` to pin production disclosure
25
+ * to the deployment rather than the ambient variable (#3152 — see
26
+ * `JSONCodecOptions.serializeErrorStacks`).
27
+ */
28
+ serializeErrorStacks?: boolean;
22
29
  /** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
23
30
  plugins?: SerializerPlugin[];
24
31
  /** Receives each emitted script chunk. */
@@ -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",
@@ -626,21 +735,24 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
626
735
  if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
627
736
  throw serverFunctionFailure(response, undefined);
628
737
  }
629
- const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
738
+ const failed = response.headers.has(ERROR_HEADER);
630
739
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
631
- const consumer = getFlightDataConsumer();
632
- if (consumer) {
740
+ const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
741
+ const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
742
+ if (consumers.length > 0) {
633
743
  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)) {
744
+ for (const [source, consumer] of consumers) {
745
+ await consumer(payload.data[source], {
746
+ response
747
+ });
748
+ }
749
+ if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
638
750
  throw serverFunctionFailure(response, payload.value);
639
751
  }
640
752
  return payload.value;
641
753
  }
642
754
  }
643
- if (response.headers.has("Location") || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER)) {
755
+ 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
756
  return response;
645
757
  }
646
758
  const result = await decodeResponse(response.clone());
@@ -678,7 +790,7 @@ function createServerReference(id, name, base) {
678
790
  });
679
791
  if (hit !== undefined) return hit;
680
792
  }
681
- return fetchServerFunction(base || serverFunctionAddress(config.endpoint, id), id, invokeOptions ? {
793
+ return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
682
794
  ...invokeOptions
683
795
  } : {}, args, metadata);
684
796
  };
@@ -715,7 +827,7 @@ function GET(fn) {
715
827
  if (hit !== undefined) return hit;
716
828
  }
717
829
  const opts = invokeOptions || {};
718
- const address = serverFunctionAddress(config.endpoint, id);
830
+ const address = serverFunctionDataAddress(config.endpoint, id);
719
831
  if (!args.length) {
720
832
  return fetchServerFunction(address, id, {
721
833
  ...opts,
@@ -845,7 +957,7 @@ function live(fn) {
845
957
  emitClosed(error);
846
958
  throw error;
847
959
  }
848
- if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
960
+ 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
961
  stopped = true;
850
962
  emitClosed(error);
851
963
  throw error;
@@ -854,7 +966,8 @@ function live(fn) {
854
966
  emit("reconnecting", error);
855
967
  await new Promise(resolve => {
856
968
  wake = resolve;
857
- timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
969
+ const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
970
+ timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
858
971
  if (typeof addEventListener === "function") addEventListener("online", resolve, {
859
972
  once: true
860
973
  });
@@ -911,20 +1024,24 @@ exports.ERROR_HEADER = ERROR_HEADER;
911
1024
  exports.FLASH_COOKIE = FLASH_COOKIE;
912
1025
  exports.GET = GET;
913
1026
  exports.INSTANCE_HEADER = INSTANCE_HEADER;
1027
+ exports.REDIRECT_HEADER = REDIRECT_HEADER;
914
1028
  exports.REVALIDATE_HEADER = REVALIDATE_HEADER;
915
1029
  exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
916
1030
  exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
1031
+ exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
917
1032
  exports.clearFlashCookie = clearFlashCookie;
918
1033
  exports.configureServerFunctionsClient = configureServerFunctionsClient;
919
1034
  exports.createChunk = createChunk;
920
1035
  exports.createServerReference = createServerReference;
921
1036
  exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
1037
+ exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
922
1038
  exports.decodeResponse = decodeResponse;
923
1039
  exports.decodeResponsePayload = decodeResponsePayload;
924
1040
  exports.deserializeStream = deserializeStream;
925
1041
  exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
926
1042
  exports.frameAddress = frameAddress;
927
1043
  exports.getFlightDataConsumer = getFlightDataConsumer;
1044
+ exports.getFlightDataSourceIds = getFlightDataSourceIds;
928
1045
  exports.getServerFunctionInvocation = getServerFunctionInvocation;
929
1046
  exports.getServerFunctionMetadata = getServerFunctionMetadata;
930
1047
  exports.getServerFunctionsCodec = getServerFunctionsCodec;