@solidjs/start 1.2.1 → 1.3.0

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.
package/config/index.d.ts CHANGED
@@ -26,6 +26,19 @@ type SolidStartInlineConfig = {
26
26
  experimental?: {
27
27
  islands?: boolean;
28
28
  };
29
+ serialization?: {
30
+ /**
31
+ * The serialization mode to use for server functions.
32
+ * The "js" mode uses a custom binary format that is more efficient than JSON, but requires a custom deserializer (with `eval()`) on the client.
33
+ * A strong CSP should block `eval()` executions, which would prevent the "js" mode from working.
34
+ * The "json" mode uses JSON for serialization, which is less efficient but can be deserialized with `JSON.parse` on the client.
35
+ *
36
+ * @default "js"
37
+ * @
38
+ * @warning on v2, "json" will be the default.
39
+ */
40
+ mode?: "js" | "json";
41
+ };
29
42
  vite?:
30
43
  | ViteCustomizableConfig
31
44
  | ((options: { router: "server" | "client" | "server-function" }) => ViteCustomizableConfig);
package/config/index.js CHANGED
@@ -89,6 +89,7 @@ export function defineConfig(baseConfig = {}) {
89
89
  }
90
90
  }
91
91
  });
92
+
92
93
  const routeDir = join(start.appRoot, start.routeDir);
93
94
  let server = start.server;
94
95
  if (!start.ssr) {
@@ -99,6 +100,8 @@ export function defineConfig(baseConfig = {}) {
99
100
  entryExtension = ".jsx";
100
101
  }
101
102
 
103
+ const serializationMode = start.serialization?.mode || 'js'
104
+
102
105
  return createApp({
103
106
  server: {
104
107
  compressPublicAssets: {
@@ -167,6 +170,7 @@ export function defineConfig(baseConfig = {}) {
167
170
  "import.meta.env.SSR": JSON.stringify(true),
168
171
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
169
172
  "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
173
+ "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode),
170
174
  ...userConfig.define
171
175
  }
172
176
  })
@@ -234,6 +238,7 @@ export function defineConfig(baseConfig = {}) {
234
238
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
235
239
  "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
236
240
  "import.meta.env.SERVER_BASE_URL": JSON.stringify(server?.baseURL ?? ""),
241
+ "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode),
237
242
  ...userConfig.define
238
243
  }
239
244
  })
@@ -293,6 +298,7 @@ export function defineConfig(baseConfig = {}) {
293
298
  "import.meta.env.SSR": JSON.stringify(true),
294
299
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
295
300
  "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
301
+ "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode),
296
302
  ...userConfig.define
297
303
  }
298
304
  })
@@ -0,0 +1,6 @@
1
+ export declare function serializeToJSStream(id: string, value: any): ReadableStream<any>;
2
+ export declare function serializeToJSONStream(value: any): ReadableStream<any>;
3
+ export declare function serializeToJSONString(value: any): Promise<string>;
4
+ export declare function deserializeFromJSONString(json: string): Promise<unknown>;
5
+ export declare function deserializeJSONStream(response: Response | Request): Promise<unknown>;
6
+ export declare function deserializeJSStream(id: string, response: Response): Promise<unknown>;
@@ -0,0 +1,212 @@
1
+ import { crossSerializeStream, deserialize, Feature, fromCrossJSON, fromJSON, getCrossReferenceHeader, toCrossJSONStream, toJSONAsync, } from "seroval";
2
+ import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLPlugin, URLSearchParamsPlugin, } from "seroval-plugins/web";
3
+ // TODO(Alexis): if we can, allow providing an option to extend these.
4
+ const DEFAULT_PLUGINS = [
5
+ AbortSignalPlugin,
6
+ CustomEventPlugin,
7
+ DOMExceptionPlugin,
8
+ EventPlugin,
9
+ FormDataPlugin,
10
+ HeadersPlugin,
11
+ ReadableStreamPlugin,
12
+ RequestPlugin,
13
+ ResponsePlugin,
14
+ URLSearchParamsPlugin,
15
+ URLPlugin,
16
+ ];
17
+ const MAX_SERIALIZATION_DEPTH_LIMIT = 64;
18
+ const DISABLED_FEATURES = Feature.RegExp;
19
+ /**
20
+ * Alexis:
21
+ *
22
+ * A "chunk" is a piece of data emitted by the streaming serializer.
23
+ * Each chunk is represented by a 32-bit value (encoded in hexadecimal),
24
+ * followed by the encoded string (8-bit representation). This format
25
+ * is important so we know how much of the chunk being streamed we
26
+ * are expecting before parsing the entire string data.
27
+ *
28
+ * This is sort of a bootleg "multipart/form-data" except it's bad at
29
+ * handling File/Blob LOL
30
+ *
31
+ * The format is as follows:
32
+ * ;0xFFFFFFFF;<string data>
33
+ */
34
+ function createChunk(data) {
35
+ const encodeData = new TextEncoder().encode(data);
36
+ const bytes = encodeData.length;
37
+ const baseHex = bytes.toString(16);
38
+ const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
39
+ const head = new TextEncoder().encode(`;0x${totalHex};`);
40
+ const chunk = new Uint8Array(12 + bytes);
41
+ chunk.set(head);
42
+ chunk.set(encodeData, 12);
43
+ return chunk;
44
+ }
45
+ export function serializeToJSStream(id, value) {
46
+ return new ReadableStream({
47
+ start(controller) {
48
+ crossSerializeStream(value, {
49
+ scopeId: id,
50
+ plugins: DEFAULT_PLUGINS,
51
+ onSerialize(data, initial) {
52
+ controller.enqueue(createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data));
53
+ },
54
+ onDone() {
55
+ controller.close();
56
+ },
57
+ onError(error) {
58
+ controller.error(error);
59
+ },
60
+ });
61
+ },
62
+ });
63
+ }
64
+ export function serializeToJSONStream(value) {
65
+ return new ReadableStream({
66
+ start(controller) {
67
+ toCrossJSONStream(value, {
68
+ disabledFeatures: DISABLED_FEATURES,
69
+ depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT,
70
+ plugins: DEFAULT_PLUGINS,
71
+ onParse(node) {
72
+ controller.enqueue(createChunk(JSON.stringify(node)));
73
+ },
74
+ onDone() {
75
+ controller.close();
76
+ },
77
+ onError(error) {
78
+ controller.error(error);
79
+ },
80
+ });
81
+ },
82
+ });
83
+ }
84
+ class SerovalChunkReader {
85
+ reader;
86
+ buffer;
87
+ done;
88
+ constructor(stream) {
89
+ this.reader = stream.getReader();
90
+ this.buffer = new Uint8Array(0);
91
+ this.done = false;
92
+ }
93
+ async readChunk() {
94
+ // if there's no chunk, read again
95
+ const chunk = await this.reader.read();
96
+ if (!chunk.done) {
97
+ // repopulate the buffer
98
+ const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
99
+ newBuffer.set(this.buffer);
100
+ newBuffer.set(chunk.value, this.buffer.length);
101
+ this.buffer = newBuffer;
102
+ }
103
+ else {
104
+ this.done = true;
105
+ }
106
+ }
107
+ async next() {
108
+ // Check if the buffer is empty
109
+ if (this.buffer.length === 0) {
110
+ // if we are already done...
111
+ if (this.done) {
112
+ return {
113
+ done: true,
114
+ value: undefined,
115
+ };
116
+ }
117
+ // Otherwise, read a new chunk
118
+ await this.readChunk();
119
+ return await this.next();
120
+ }
121
+ // Read the "byte header"
122
+ // The byte header tells us how big the expected data is
123
+ // so we know how much data we should wait before we
124
+ // deserialize the data
125
+ const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
126
+ const bytes = Number.parseInt(head, 16); // ;0x00000000;
127
+ // Check if the buffer has enough bytes to be parsed
128
+ while (bytes > this.buffer.length - 12) {
129
+ // If it's not enough, and the reader is done
130
+ // then the chunk is invalid.
131
+ if (this.done) {
132
+ throw new Error("Malformed server function stream.");
133
+ }
134
+ // Otherwise, we read more chunks
135
+ await this.readChunk();
136
+ }
137
+ // Extract the exact chunk as defined by the byte header
138
+ const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
139
+ // The rest goes to the buffer
140
+ this.buffer = this.buffer.subarray(12 + bytes);
141
+ // Deserialize the chunk
142
+ return {
143
+ done: false,
144
+ value: partial,
145
+ };
146
+ }
147
+ async drain(interpret) {
148
+ while (true) {
149
+ const result = await this.next();
150
+ if (result.done) {
151
+ break;
152
+ }
153
+ else {
154
+ interpret(result.value);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ export async function serializeToJSONString(value) {
160
+ // const response = new Response(serializeToJSONStream(value));
161
+ // return await response.text();
162
+ return JSON.stringify(toJSONAsync(value, {
163
+ plugins: DEFAULT_PLUGINS,
164
+ depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT,
165
+ disabledFeatures: DISABLED_FEATURES,
166
+ }));
167
+ }
168
+ export async function deserializeFromJSONString(json) {
169
+ return fromJSON(JSON.parse(json), {
170
+ plugins: DEFAULT_PLUGINS,
171
+ disabledFeatures: DISABLED_FEATURES,
172
+ });
173
+ }
174
+ export async function deserializeJSONStream(response) {
175
+ if (!response.body) {
176
+ throw new Error("missing body");
177
+ }
178
+ const reader = new SerovalChunkReader(response.body);
179
+ const result = await reader.next();
180
+ if (!result.done) {
181
+ const refs = new Map();
182
+ function interpretChunk(chunk) {
183
+ const value = fromCrossJSON(JSON.parse(chunk), {
184
+ refs,
185
+ disabledFeatures: DISABLED_FEATURES,
186
+ depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT,
187
+ plugins: DEFAULT_PLUGINS,
188
+ });
189
+ return value;
190
+ }
191
+ void reader.drain(interpretChunk);
192
+ return interpretChunk(result.value);
193
+ }
194
+ return undefined;
195
+ }
196
+ export async function deserializeJSStream(id, response) {
197
+ if (!response.body) {
198
+ throw new Error("missing body");
199
+ }
200
+ const reader = new SerovalChunkReader(response.body);
201
+ const result = await reader.next();
202
+ if (!result.done) {
203
+ reader.drain(deserialize).then(() => {
204
+ // @ts-ignore
205
+ delete $R[id];
206
+ }, () => {
207
+ // no-op
208
+ });
209
+ return deserialize(result.value);
210
+ }
211
+ return undefined;
212
+ }
@@ -1,8 +1,5 @@
1
1
  /// <reference types="vinxi/types/server" />
2
2
  import { parseSetCookie } from "cookie-es";
3
- import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval";
4
- // @ts-ignore
5
- import { CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLPlugin, URLSearchParamsPlugin } from "seroval-plugins/web";
6
3
  import { sharedConfig } from "solid-js";
7
4
  import { renderToString } from "solid-js/web";
8
5
  import { provideRequestEvent } from "solid-js/web/storage";
@@ -13,47 +10,7 @@ import { getExpectedRedirectStatus } from "../server/handler";
13
10
  import { createPageEvent } from "../server/pageEvent";
14
11
  // @ts-ignore
15
12
  import serverFnManifest from "solidstart:server-fn-manifest";
16
- function createChunk(data) {
17
- const encodeData = new TextEncoder().encode(data);
18
- const bytes = encodeData.length;
19
- const baseHex = bytes.toString(16);
20
- const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
21
- const head = new TextEncoder().encode(`;0x${totalHex};`);
22
- const chunk = new Uint8Array(12 + bytes);
23
- chunk.set(head);
24
- chunk.set(encodeData, 12);
25
- return chunk;
26
- }
27
- function serializeToStream(id, value) {
28
- return new ReadableStream({
29
- start(controller) {
30
- crossSerializeStream(value, {
31
- scopeId: id,
32
- plugins: [
33
- CustomEventPlugin,
34
- DOMExceptionPlugin,
35
- EventPlugin,
36
- FormDataPlugin,
37
- HeadersPlugin,
38
- ReadableStreamPlugin,
39
- RequestPlugin,
40
- ResponsePlugin,
41
- URLSearchParamsPlugin,
42
- URLPlugin
43
- ],
44
- onSerialize(data, initial) {
45
- controller.enqueue(createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data));
46
- },
47
- onDone() {
48
- controller.close();
49
- },
50
- onError(error) {
51
- controller.error(error);
52
- }
53
- });
54
- }
55
- });
56
- }
13
+ import { deserializeFromJSONString, serializeToJSONStream, serializeToJSStream } from "./serialization";
57
14
  async function handleServerFunction(h3Event) {
58
15
  const event = getFetchEvent(h3Event);
59
16
  const request = event.request;
@@ -64,7 +21,7 @@ async function handleServerFunction(h3Event) {
64
21
  let functionId, name;
65
22
  if (serverReference) {
66
23
  invariant(typeof serverReference === "string", "Invalid server function");
67
- [functionId, name] = serverReference.split("#");
24
+ [functionId, name] = decodeURIComponent(serverReference).split("#");
68
25
  }
69
26
  else {
70
27
  functionId = url.searchParams.get("id");
@@ -100,23 +57,10 @@ async function handleServerFunction(h3Event) {
100
57
  if (!instance || h3Event.method === "GET") {
101
58
  const args = url.searchParams.get("args");
102
59
  if (args) {
103
- const json = JSON.parse(args);
104
- (json.t
105
- ? fromJSON(json, {
106
- plugins: [
107
- CustomEventPlugin,
108
- DOMExceptionPlugin,
109
- EventPlugin,
110
- FormDataPlugin,
111
- HeadersPlugin,
112
- ReadableStreamPlugin,
113
- RequestPlugin,
114
- ResponsePlugin,
115
- URLSearchParamsPlugin,
116
- URLPlugin
117
- ]
118
- })
119
- : json).forEach((arg) => parsed.push(arg));
60
+ const result = (await deserializeFromJSONString(args));
61
+ for (const arg of result) {
62
+ parsed.push(arg);
63
+ }
120
64
  }
121
65
  }
122
66
  if (h3Event.method === "POST") {
@@ -129,38 +73,26 @@ async function handleServerFunction(h3Event) {
129
73
  const isH3EventBodyStreamLocked = (isReadableStream && h3Request.locked) ||
130
74
  (hasReadableStream && h3Request.body.locked);
131
75
  const requestBody = isReadableStream ? h3Request : h3Request.body;
132
- if (contentType?.startsWith("multipart/form-data") ||
76
+ // workaround for https://github.com/unjs/nitro/issues/1721
77
+ // (issue only in edge runtimes and netlify preset)
78
+ const tmpReq = isH3EventBodyStreamLocked
79
+ ? request
80
+ : new Request(request, { ...request, body: requestBody });
81
+ if (request.headers.get('x-serialized')) {
82
+ parsed = await deserializeFromJSONString(await tmpReq.text());
83
+ }
84
+ else if (contentType?.startsWith("multipart/form-data") ||
133
85
  contentType?.startsWith("application/x-www-form-urlencoded")) {
134
86
  // workaround for https://github.com/unjs/nitro/issues/1721
135
87
  // (issue only in edge runtimes and netlify preset)
136
- parsed.push(await (isH3EventBodyStreamLocked
137
- ? request
138
- : new Request(request, { ...request, body: requestBody })).formData());
88
+ parsed.push(await tmpReq.formData());
139
89
  // what should work when #1721 is fixed
140
90
  // parsed.push(await request.formData);
141
91
  }
142
- else if (contentType?.startsWith("application/json")) {
143
- // workaround for https://github.com/unjs/nitro/issues/1721
144
- // (issue only in edge runtimes and netlify preset)
145
- const tmpReq = isH3EventBodyStreamLocked
146
- ? request
147
- : new Request(request, { ...request, body: requestBody });
92
+ else if (contentType?.startsWith('application/json')) {
148
93
  // what should work when #1721 is fixed
149
94
  // just use request.json() here
150
- parsed = fromJSON(await tmpReq.json(), {
151
- plugins: [
152
- CustomEventPlugin,
153
- DOMExceptionPlugin,
154
- EventPlugin,
155
- FormDataPlugin,
156
- HeadersPlugin,
157
- ReadableStreamPlugin,
158
- RequestPlugin,
159
- ResponsePlugin,
160
- URLSearchParamsPlugin,
161
- URLPlugin
162
- ]
163
- });
95
+ parsed = await tmpReq.json();
164
96
  }
165
97
  }
166
98
  try {
@@ -196,8 +128,12 @@ async function handleServerFunction(h3Event) {
196
128
  // handle no JS success case
197
129
  if (!instance)
198
130
  return handleNoJS(result, request, parsed);
199
- setHeader(h3Event, "content-type", "text/javascript");
200
- return serializeToStream(instance, result);
131
+ setHeader(h3Event, 'x-serialized', 'true');
132
+ if (import.meta.env.SEROVAL_MODE === 'js') {
133
+ setHeader(h3Event, "content-type", "text/javascript");
134
+ return serializeToJSStream(instance, result);
135
+ }
136
+ return serializeToJSONStream(result);
201
137
  }
202
138
  catch (x) {
203
139
  if (x instanceof Response) {
@@ -225,8 +161,12 @@ async function handleServerFunction(h3Event) {
225
161
  x = handleNoJS(x, request, parsed, true);
226
162
  }
227
163
  if (instance) {
228
- setHeader(h3Event, "content-type", "text/javascript");
229
- return serializeToStream(instance, x);
164
+ setHeader(h3Event, 'x-serialized', 'true');
165
+ if (import.meta.env.SEROVAL_MODE === 'js') {
166
+ setHeader(h3Event, "content-type", "text/javascript");
167
+ return serializeToJSStream(instance, x);
168
+ }
169
+ return serializeToJSONStream(x);
230
170
  }
231
171
  return x;
232
172
  }
@@ -1,97 +1,5 @@
1
- import { deserialize, toJSONAsync } from "seroval";
2
- import { CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLPlugin, URLSearchParamsPlugin } from "seroval-plugins/web";
3
1
  import { createIslandReference } from "../server/islands/index";
4
- class SerovalChunkReader {
5
- reader;
6
- buffer;
7
- done;
8
- constructor(stream) {
9
- this.reader = stream.getReader();
10
- this.buffer = new Uint8Array(0);
11
- this.done = false;
12
- }
13
- async readChunk() {
14
- // if there's no chunk, read again
15
- const chunk = await this.reader.read();
16
- if (!chunk.done) {
17
- // repopulate the buffer
18
- let newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
19
- newBuffer.set(this.buffer);
20
- newBuffer.set(chunk.value, this.buffer.length);
21
- this.buffer = newBuffer;
22
- }
23
- else {
24
- this.done = true;
25
- }
26
- }
27
- async next() {
28
- // Check if the buffer is empty
29
- if (this.buffer.length === 0) {
30
- // if we are already done...
31
- if (this.done) {
32
- return {
33
- done: true,
34
- value: undefined
35
- };
36
- }
37
- // Otherwise, read a new chunk
38
- await this.readChunk();
39
- return await this.next();
40
- }
41
- // Read the "byte header"
42
- // The byte header tells us how big the expected data is
43
- // so we know how much data we should wait before we
44
- // deserialize the data
45
- const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
46
- const bytes = Number.parseInt(head, 16); // ;0x00000000;
47
- if (Number.isNaN(bytes)) {
48
- throw new Error(`Malformed server function stream header: ${head}`);
49
- }
50
- // Check if the buffer has enough bytes to be parsed
51
- while (bytes > this.buffer.length - 12) {
52
- // If it's not enough, and the reader is done
53
- // then the chunk is invalid.
54
- if (this.done) {
55
- throw new Error("Malformed server function stream.");
56
- }
57
- // Otherwise, we read more chunks
58
- await this.readChunk();
59
- }
60
- // Extract the exact chunk as defined by the byte header
61
- const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
62
- // The rest goes to the buffer
63
- this.buffer = this.buffer.subarray(12 + bytes);
64
- // Deserialize the chunk
65
- return {
66
- done: false,
67
- value: deserialize(partial)
68
- };
69
- }
70
- async drain() {
71
- while (true) {
72
- const result = await this.next();
73
- if (result.done) {
74
- break;
75
- }
76
- }
77
- }
78
- }
79
- async function deserializeStream(id, response) {
80
- if (!response.body) {
81
- throw new Error("missing body");
82
- }
83
- const reader = new SerovalChunkReader(response.body);
84
- const result = await reader.next();
85
- if (!result.done) {
86
- reader.drain().then(() => {
87
- // @ts-ignore
88
- delete $R[id];
89
- }, () => {
90
- // no-op
91
- });
92
- }
93
- return result.value;
94
- }
2
+ import { deserializeJSONStream, deserializeJSStream, serializeToJSONString, } from "./serialization";
95
3
  let INSTANCE = 0;
96
4
  function createRequest(base, id, instance, options) {
97
5
  return fetch(base, {
@@ -99,23 +7,11 @@ function createRequest(base, id, instance, options) {
99
7
  ...options,
100
8
  headers: {
101
9
  ...options.headers,
102
- "X-Server-Id": id,
10
+ "X-Server-Id": encodeURIComponent(id),
103
11
  "X-Server-Instance": instance
104
12
  }
105
13
  });
106
14
  }
107
- const plugins = [
108
- CustomEventPlugin,
109
- DOMExceptionPlugin,
110
- EventPlugin,
111
- FormDataPlugin,
112
- HeadersPlugin,
113
- ReadableStreamPlugin,
114
- RequestPlugin,
115
- ResponsePlugin,
116
- URLSearchParamsPlugin,
117
- URLPlugin
118
- ];
119
15
  async function fetchServerFunction(base, id, options, args) {
120
16
  const instance = `server-fn:${INSTANCE++}`;
121
17
  const response = await (args.length === 0
@@ -126,12 +22,21 @@ async function fetchServerFunction(base, id, options, args) {
126
22
  ? createRequest(base, id, instance, {
127
23
  ...options,
128
24
  body: args[0],
129
- headers: { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" }
25
+ headers: {
26
+ ...options.headers,
27
+ "Content-Type": "application/x-www-form-urlencoded",
28
+ },
130
29
  })
131
30
  : createRequest(base, id, instance, {
132
31
  ...options,
133
- body: JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))),
134
- headers: { ...options.headers, "Content-Type": "application/json" }
32
+ body: await serializeToJSONString(args),
33
+ // duplex: 'half',
34
+ // body: serializeToJSONStream(args),
35
+ headers: {
36
+ ...options.headers,
37
+ "x-serialized": "true",
38
+ "Content-Type": "text/plain"
39
+ },
135
40
  }));
136
41
  if (response.headers.has("Location") ||
137
42
  response.headers.has("X-Revalidate") ||
@@ -139,21 +44,30 @@ async function fetchServerFunction(base, id, options, args) {
139
44
  if (response.body) {
140
45
  /* @ts-ignore-next-line */
141
46
  response.customBody = () => {
142
- return deserializeStream(instance, response);
47
+ if (import.meta.env.SEROVAL_MODE === "js") {
48
+ return deserializeJSStream(instance, response.clone());
49
+ }
50
+ return deserializeJSONStream(response.clone());
143
51
  };
144
52
  }
145
53
  return response;
146
54
  }
147
55
  const contentType = response.headers.get("Content-Type");
56
+ const cloned = response.clone();
148
57
  let result;
149
58
  if (contentType && contentType.startsWith("text/plain")) {
150
- result = await response.text();
59
+ result = await cloned.text();
151
60
  }
152
61
  else if (contentType && contentType.startsWith("application/json")) {
153
- result = await response.json();
62
+ result = await cloned.json();
154
63
  }
155
- else {
156
- result = await deserializeStream(instance, response);
64
+ else if (response.headers.get("x-serialized")) {
65
+ if (import.meta.env.SEROVAL_MODE === "js") {
66
+ result = await deserializeJSStream(instance, cloned);
67
+ }
68
+ else {
69
+ result = await deserializeJSONStream(cloned);
70
+ }
157
71
  }
158
72
  if (response.headers.has("X-Error")) {
159
73
  throw result;
@@ -178,7 +92,7 @@ export function createServerReference(fn, id, name) {
178
92
  return fetchServerFunction(encodeArgs
179
93
  ? url +
180
94
  (args.length
181
- ? `&args=${encodeURIComponent(JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))))}`
95
+ ? `&args=${encodeURIComponent(await serializeToJSONString(args))}`
182
96
  : "")
183
97
  : `${baseURL}/_server`, `${id}#${name}`, options, encodeArgs ? [] : args);
184
98
  };
@@ -190,7 +104,7 @@ export function createServerReference(fn, id, name) {
190
104
  },
191
105
  apply(target, thisArg, args) {
192
106
  return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, {}, args);
193
- }
107
+ },
194
108
  });
195
109
  }
196
110
  export function createClientReference(Component, id, name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  }
56
56
  },
57
57
  "devDependencies": {
58
- "solid-js": "^1.9.5",
58
+ "solid-js": "^1.9.11",
59
59
  "vinxi": "^0.5.7",
60
60
  "vitest": "3.0.5"
61
61
  },
@@ -68,13 +68,13 @@
68
68
  "error-stack-parser": "^2.1.4",
69
69
  "html-to-image": "^1.11.11",
70
70
  "radix3": "^1.1.0",
71
- "seroval": "^1.4.1",
72
- "seroval-plugins": "^1.4.0",
71
+ "seroval": "^1.5.0",
72
+ "seroval-plugins": "^1.5.0",
73
73
  "shiki": "^1.26.1",
74
74
  "source-map-js": "^1.0.2",
75
75
  "terracotta": "^1.0.4",
76
76
  "tinyglobby": "^0.2.2",
77
- "vite-plugin-solid": "^2.11.1"
77
+ "vite-plugin-solid": "^2.11.10"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "vinxi": "^0.5.7"