@tanstack/start-client-core 1.166.9 → 1.166.10
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/dist/esm/client/ServerFunctionSerializationAdapter.js +15 -14
- package/dist/esm/client/ServerFunctionSerializationAdapter.js.map +1 -1
- package/dist/esm/client/hydrateStart.js +26 -31
- package/dist/esm/client/hydrateStart.js.map +1 -1
- package/dist/esm/client/index.js +1 -4
- package/dist/esm/client-rpc/createClientRpc.js +16 -15
- package/dist/esm/client-rpc/createClientRpc.js.map +1 -1
- package/dist/esm/client-rpc/frame-decoder.js +229 -241
- package/dist/esm/client-rpc/frame-decoder.js.map +1 -1
- package/dist/esm/client-rpc/index.js +1 -4
- package/dist/esm/client-rpc/serverFnFetcher.js +210 -259
- package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -1
- package/dist/esm/constants.js +41 -49
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/createMiddleware.js +25 -36
- package/dist/esm/createMiddleware.js.map +1 -1
- package/dist/esm/createServerFn.js +187 -261
- package/dist/esm/createServerFn.js.map +1 -1
- package/dist/esm/createStart.js +25 -29
- package/dist/esm/createStart.js.map +1 -1
- package/dist/esm/fake-start-entry.js +7 -8
- package/dist/esm/fake-start-entry.js.map +1 -1
- package/dist/esm/getDefaultSerovalPlugins.js +7 -11
- package/dist/esm/getDefaultSerovalPlugins.js.map +1 -1
- package/dist/esm/getGlobalStartContext.js +10 -13
- package/dist/esm/getGlobalStartContext.js.map +1 -1
- package/dist/esm/getRouterInstance.js +7 -6
- package/dist/esm/getRouterInstance.js.map +1 -1
- package/dist/esm/getStartContextServerOnly.js +7 -6
- package/dist/esm/getStartContextServerOnly.js.map +1 -1
- package/dist/esm/getStartOptions.js +7 -6
- package/dist/esm/getStartOptions.js.map +1 -1
- package/dist/esm/index.js +6 -37
- package/dist/esm/safeObjectMerge.js +24 -24
- package/dist/esm/safeObjectMerge.js.map +1 -1
- package/package.json +4 -4
- package/dist/esm/client/index.js.map +0 -1
- package/dist/esm/client-rpc/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
|
@@ -1,275 +1,226 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fromCrossJSON, toJSONAsync } from "seroval";
|
|
3
|
-
import invariant from "tiny-invariant";
|
|
1
|
+
import { TSS_CONTENT_TYPE_FRAMED, TSS_FORMDATA_CONTEXT, validateFramedProtocolVersion } from "../constants.js";
|
|
4
2
|
import { getDefaultSerovalPlugins } from "../getDefaultSerovalPlugins.js";
|
|
5
|
-
import { TSS_CONTENT_TYPE_FRAMED, TSS_FORMDATA_CONTEXT, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED, validateFramedProtocolVersion } from "../constants.js";
|
|
6
3
|
import { createFrameDecoder } from "./frame-decoder.js";
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import { createRawStreamDeserializePlugin, encode, isNotFound, parseRedirect } from "@tanstack/router-core";
|
|
5
|
+
import { fromCrossJSON, toJSONAsync } from "seroval";
|
|
6
|
+
import invariant from "tiny-invariant";
|
|
7
|
+
//#region src/client-rpc/serverFnFetcher.ts
|
|
8
|
+
var serovalPlugins = null;
|
|
9
|
+
/**
|
|
10
|
+
* Checks if an object has at least one own enumerable property.
|
|
11
|
+
* More efficient than Object.keys(obj).length > 0 as it short-circuits on first property.
|
|
12
|
+
*/
|
|
13
|
+
var hop = Object.prototype.hasOwnProperty;
|
|
9
14
|
function hasOwnProperties(obj) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
15
|
+
for (const _ in obj) if (hop.call(obj, _)) return true;
|
|
16
|
+
return false;
|
|
16
17
|
}
|
|
17
18
|
async function serverFnFetcher(url, args, handler) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
let body = void 0;
|
|
50
|
-
if (first.method === "POST") {
|
|
51
|
-
const fetchBody = await getFetchBody(first);
|
|
52
|
-
if (fetchBody?.contentType) {
|
|
53
|
-
headers.set("content-type", fetchBody.contentType);
|
|
54
|
-
}
|
|
55
|
-
body = fetchBody?.body;
|
|
56
|
-
}
|
|
57
|
-
return await getResponse(
|
|
58
|
-
async () => fetchImpl(url, {
|
|
59
|
-
method: first.method,
|
|
60
|
-
headers,
|
|
61
|
-
signal: first.signal,
|
|
62
|
-
body
|
|
63
|
-
})
|
|
64
|
-
);
|
|
19
|
+
if (!serovalPlugins) serovalPlugins = getDefaultSerovalPlugins();
|
|
20
|
+
const first = args[0];
|
|
21
|
+
const fetchImpl = first.fetch ?? handler;
|
|
22
|
+
const type = first.data instanceof FormData ? "formData" : "payload";
|
|
23
|
+
const headers = first.headers ? new Headers(first.headers) : new Headers();
|
|
24
|
+
headers.set("x-tsr-serverFn", "true");
|
|
25
|
+
if (type === "payload") headers.set("accept", `${TSS_CONTENT_TYPE_FRAMED}, application/x-ndjson, application/json`);
|
|
26
|
+
if (first.method === "GET") {
|
|
27
|
+
if (type === "formData") throw new Error("FormData is not supported with GET requests");
|
|
28
|
+
const serializedPayload = await serializePayload(first);
|
|
29
|
+
if (serializedPayload !== void 0) {
|
|
30
|
+
const encodedPayload = encode({ payload: serializedPayload });
|
|
31
|
+
if (url.includes("?")) url += `&${encodedPayload}`;
|
|
32
|
+
else url += `?${encodedPayload}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let body = void 0;
|
|
36
|
+
if (first.method === "POST") {
|
|
37
|
+
const fetchBody = await getFetchBody(first);
|
|
38
|
+
if (fetchBody?.contentType) headers.set("content-type", fetchBody.contentType);
|
|
39
|
+
body = fetchBody?.body;
|
|
40
|
+
}
|
|
41
|
+
return await getResponse(async () => fetchImpl(url, {
|
|
42
|
+
method: first.method,
|
|
43
|
+
headers,
|
|
44
|
+
signal: first.signal,
|
|
45
|
+
body
|
|
46
|
+
}));
|
|
65
47
|
}
|
|
66
48
|
async function serializePayload(opts) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return serialize(payloadToSerialize);
|
|
79
|
-
}
|
|
80
|
-
return void 0;
|
|
49
|
+
let payloadAvailable = false;
|
|
50
|
+
const payloadToSerialize = {};
|
|
51
|
+
if (opts.data !== void 0) {
|
|
52
|
+
payloadAvailable = true;
|
|
53
|
+
payloadToSerialize["data"] = opts.data;
|
|
54
|
+
}
|
|
55
|
+
if (opts.context && hasOwnProperties(opts.context)) {
|
|
56
|
+
payloadAvailable = true;
|
|
57
|
+
payloadToSerialize["context"] = opts.context;
|
|
58
|
+
}
|
|
59
|
+
if (payloadAvailable) return serialize(payloadToSerialize);
|
|
81
60
|
}
|
|
82
61
|
async function serialize(data) {
|
|
83
|
-
|
|
84
|
-
await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins }))
|
|
85
|
-
);
|
|
62
|
+
return JSON.stringify(await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins })));
|
|
86
63
|
}
|
|
87
64
|
async function getFetchBody(opts) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (serializedBody) {
|
|
100
|
-
return { body: serializedBody, contentType: "application/json" };
|
|
101
|
-
}
|
|
102
|
-
return void 0;
|
|
65
|
+
if (opts.data instanceof FormData) {
|
|
66
|
+
let serializedContext = void 0;
|
|
67
|
+
if (opts.context && hasOwnProperties(opts.context)) serializedContext = await serialize(opts.context);
|
|
68
|
+
if (serializedContext !== void 0) opts.data.set(TSS_FORMDATA_CONTEXT, serializedContext);
|
|
69
|
+
return { body: opts.data };
|
|
70
|
+
}
|
|
71
|
+
const serializedBody = await serializePayload(opts);
|
|
72
|
+
if (serializedBody) return {
|
|
73
|
+
body: serializedBody,
|
|
74
|
+
contentType: "application/json"
|
|
75
|
+
};
|
|
103
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Retrieves a response from a given function and manages potential errors
|
|
79
|
+
* and special response types including redirects and not found errors.
|
|
80
|
+
*
|
|
81
|
+
* @param fn - The function to execute for obtaining the response.
|
|
82
|
+
* @returns The processed response from the function.
|
|
83
|
+
* @throws If the response is invalid or an error occurs during processing.
|
|
84
|
+
*/
|
|
104
85
|
async function getResponse(fn) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const jsonPayload = await response.json();
|
|
163
|
-
const redirect = parseRedirect(jsonPayload);
|
|
164
|
-
if (redirect) {
|
|
165
|
-
throw redirect;
|
|
166
|
-
}
|
|
167
|
-
if (isNotFound(jsonPayload)) {
|
|
168
|
-
throw jsonPayload;
|
|
169
|
-
}
|
|
170
|
-
return jsonPayload;
|
|
171
|
-
}
|
|
172
|
-
if (!response.ok) {
|
|
173
|
-
throw new Error(await response.text());
|
|
174
|
-
}
|
|
175
|
-
return response;
|
|
86
|
+
let response;
|
|
87
|
+
try {
|
|
88
|
+
response = await fn();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof Response) response = error;
|
|
91
|
+
else {
|
|
92
|
+
console.log(error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (response.headers.get("x-tss-raw") === "true") return response;
|
|
97
|
+
const contentType = response.headers.get("content-type");
|
|
98
|
+
invariant(contentType, "expected content-type header to be set");
|
|
99
|
+
if (!!response.headers.get("x-tss-serialized")) {
|
|
100
|
+
let result;
|
|
101
|
+
if (contentType.includes("application/x-tss-framed")) {
|
|
102
|
+
validateFramedProtocolVersion(contentType);
|
|
103
|
+
if (!response.body) throw new Error("No response body for framed response");
|
|
104
|
+
const { getOrCreateStream, jsonChunks } = createFrameDecoder(response.body);
|
|
105
|
+
const plugins = [createRawStreamDeserializePlugin(getOrCreateStream), ...serovalPlugins || []];
|
|
106
|
+
const refs = /* @__PURE__ */ new Map();
|
|
107
|
+
result = await processFramedResponse({
|
|
108
|
+
jsonStream: jsonChunks,
|
|
109
|
+
onMessage: (msg) => fromCrossJSON(msg, {
|
|
110
|
+
refs,
|
|
111
|
+
plugins
|
|
112
|
+
}),
|
|
113
|
+
onError(msg, error) {
|
|
114
|
+
console.error(msg, error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
} else if (contentType.includes("application/x-ndjson")) {
|
|
118
|
+
const refs = /* @__PURE__ */ new Map();
|
|
119
|
+
result = await processServerFnResponse({
|
|
120
|
+
response,
|
|
121
|
+
onMessage: (msg) => fromCrossJSON(msg, {
|
|
122
|
+
refs,
|
|
123
|
+
plugins: serovalPlugins
|
|
124
|
+
}),
|
|
125
|
+
onError(msg, error) {
|
|
126
|
+
console.error(msg, error);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
} else if (contentType.includes("application/json")) result = fromCrossJSON(await response.json(), { plugins: serovalPlugins });
|
|
130
|
+
invariant(result, "expected result to be resolved");
|
|
131
|
+
if (result instanceof Error) throw result;
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
if (contentType.includes("application/json")) {
|
|
135
|
+
const jsonPayload = await response.json();
|
|
136
|
+
const redirect = parseRedirect(jsonPayload);
|
|
137
|
+
if (redirect) throw redirect;
|
|
138
|
+
if (isNotFound(jsonPayload)) throw jsonPayload;
|
|
139
|
+
return jsonPayload;
|
|
140
|
+
}
|
|
141
|
+
if (!response.ok) throw new Error(await response.text());
|
|
142
|
+
return response;
|
|
176
143
|
}
|
|
177
|
-
async function processServerFnResponse({
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (done) {
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
} catch (err) {
|
|
237
|
-
onError?.("Stream processing error:", err);
|
|
238
|
-
}
|
|
239
|
-
})();
|
|
240
|
-
return onMessage(firstObject);
|
|
144
|
+
async function processServerFnResponse({ response, onMessage, onError }) {
|
|
145
|
+
if (!response.body) throw new Error("No response body");
|
|
146
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
147
|
+
let buffer = "";
|
|
148
|
+
let firstRead = false;
|
|
149
|
+
let firstObject;
|
|
150
|
+
while (!firstRead) {
|
|
151
|
+
const { value, done } = await reader.read();
|
|
152
|
+
if (value) buffer += value;
|
|
153
|
+
if (buffer.length === 0 && done) throw new Error("Stream ended before first object");
|
|
154
|
+
if (buffer.endsWith("\n")) {
|
|
155
|
+
const lines = buffer.split("\n").filter(Boolean);
|
|
156
|
+
const firstLine = lines[0];
|
|
157
|
+
if (!firstLine) throw new Error("No JSON line in the first chunk");
|
|
158
|
+
firstObject = JSON.parse(firstLine);
|
|
159
|
+
firstRead = true;
|
|
160
|
+
buffer = lines.slice(1).join("\n");
|
|
161
|
+
} else {
|
|
162
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
163
|
+
if (newlineIndex >= 0) {
|
|
164
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
165
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
166
|
+
if (line.length > 0) {
|
|
167
|
+
firstObject = JSON.parse(line);
|
|
168
|
+
firstRead = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
(async () => {
|
|
174
|
+
try {
|
|
175
|
+
while (true) {
|
|
176
|
+
const { value, done } = await reader.read();
|
|
177
|
+
if (value) buffer += value;
|
|
178
|
+
const lastNewline = buffer.lastIndexOf("\n");
|
|
179
|
+
if (lastNewline >= 0) {
|
|
180
|
+
const chunk = buffer.slice(0, lastNewline);
|
|
181
|
+
buffer = buffer.slice(lastNewline + 1);
|
|
182
|
+
const lines = chunk.split("\n").filter(Boolean);
|
|
183
|
+
for (const line of lines) try {
|
|
184
|
+
onMessage(JSON.parse(line));
|
|
185
|
+
} catch (e) {
|
|
186
|
+
onError?.(`Invalid JSON line: ${line}`, e);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (done) break;
|
|
190
|
+
}
|
|
191
|
+
} catch (err) {
|
|
192
|
+
onError?.("Stream processing error:", err);
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
return onMessage(firstObject);
|
|
241
196
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
onError?.("Stream processing error:", err);
|
|
268
|
-
}
|
|
269
|
-
})();
|
|
270
|
-
return onMessage(firstObject);
|
|
197
|
+
/**
|
|
198
|
+
* Processes a framed response where each JSON chunk is a complete JSON string
|
|
199
|
+
* (already decoded by frame decoder).
|
|
200
|
+
*/
|
|
201
|
+
async function processFramedResponse({ jsonStream, onMessage, onError }) {
|
|
202
|
+
const reader = jsonStream.getReader();
|
|
203
|
+
const { value: firstValue, done: firstDone } = await reader.read();
|
|
204
|
+
if (firstDone || !firstValue) throw new Error("Stream ended before first object");
|
|
205
|
+
const firstObject = JSON.parse(firstValue);
|
|
206
|
+
(async () => {
|
|
207
|
+
try {
|
|
208
|
+
while (true) {
|
|
209
|
+
const { value, done } = await reader.read();
|
|
210
|
+
if (done) break;
|
|
211
|
+
if (value) try {
|
|
212
|
+
onMessage(JSON.parse(value));
|
|
213
|
+
} catch (e) {
|
|
214
|
+
onError?.(`Invalid JSON: ${value}`, e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
onError?.("Stream processing error:", err);
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
return onMessage(firstObject);
|
|
271
222
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
//# sourceMappingURL=serverFnFetcher.js.map
|
|
223
|
+
//#endregion
|
|
224
|
+
export { serverFnFetcher };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=serverFnFetcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverFnFetcher.js","sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import {\n createRawStreamDeserializePlugin,\n encode,\n isNotFound,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_CONTENT_TYPE_FRAMED,\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n validateFramedProtocolVersion,\n} from '../constants'\nimport { createFrameDecoder } from './frame-decoder'\nimport type { FunctionMiddlewareClientFnOptions } from '../createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\n/**\n * Checks if an object has at least one own enumerable property.\n * More efficient than Object.keys(obj).length > 0 as it short-circuits on first property.\n */\nconst hop = Object.prototype.hasOwnProperty\nfunction hasOwnProperties(obj: object): boolean {\n for (const _ in obj) {\n if (hop.call(obj, _)) {\n return true\n }\n }\n return false\n}\n// caller =>\n// serverFnFetcher =>\n// client =>\n// server =>\n// fn =>\n// seroval =>\n// client middleware =>\n// serverFnFetcher =>\n// caller\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n const _first = args[0]\n\n const first = _first as FunctionMiddlewareClientFnOptions<any, any, any> & {\n headers?: HeadersInit\n }\n\n // Use custom fetch if provided, otherwise fall back to the passed handler (global fetch)\n const fetchImpl = first.fetch ?? handler\n\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = first.headers ? new Headers(first.headers) : new Headers()\n headers.set('x-tsr-serverFn', 'true')\n\n if (type === 'payload') {\n headers.set(\n 'accept',\n `${TSS_CONTENT_TYPE_FRAMED}, application/x-ndjson, application/json`,\n )\n }\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n if (type === 'formData') {\n throw new Error('FormData is not supported with GET requests')\n }\n const serializedPayload = await serializePayload(first)\n if (serializedPayload !== undefined) {\n const encodedPayload = encode({\n payload: serializedPayload,\n })\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n let body = undefined\n if (first.method === 'POST') {\n const fetchBody = await getFetchBody(first)\n if (fetchBody?.contentType) {\n headers.set('content-type', fetchBody.contentType)\n }\n body = fetchBody?.body\n }\n\n return await getResponse(async () =>\n fetchImpl(url, {\n method: first.method,\n headers,\n signal: first.signal,\n body,\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<string | undefined> {\n let payloadAvailable = false\n const payloadToSerialize: any = {}\n if (opts.data !== undefined) {\n payloadAvailable = true\n payloadToSerialize['data'] = opts.data\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n payloadAvailable = true\n payloadToSerialize['context'] = opts.context\n }\n\n if (payloadAvailable) {\n return serialize(payloadToSerialize)\n }\n return undefined\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetchBody(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<{ body: FormData | string; contentType?: string } | undefined> {\n if (opts.data instanceof FormData) {\n let serializedContext = undefined\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n serializedContext = await serialize(opts.context)\n }\n if (serializedContext !== undefined) {\n opts.data.set(TSS_FORMDATA_CONTEXT, serializedContext)\n }\n return { body: opts.data }\n }\n const serializedBody = await serializePayload(opts)\n if (serializedBody) {\n return { body: serializedBody, contentType: 'application/json' }\n }\n return undefined\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n let response: Response\n try {\n response = await fn() // client => server => fn => server => client\n } catch (error) {\n if (error instanceof Response) {\n response = error\n } else {\n console.log(error)\n throw error\n }\n }\n\n if (response.headers.get(X_TSS_RAW_RESPONSE) === 'true') {\n return response\n }\n\n const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)\n\n // If the response is serialized by the start server, we need to process it\n // differently than a normal response.\n if (serializedByStart) {\n let result\n\n // If it's a framed response (contains RawStream), use frame decoder\n if (contentType.includes(TSS_CONTENT_TYPE_FRAMED)) {\n // Validate protocol version compatibility\n validateFramedProtocolVersion(contentType)\n\n if (!response.body) {\n throw new Error('No response body for framed response')\n }\n\n const { getOrCreateStream, jsonChunks } = createFrameDecoder(\n response.body,\n )\n\n // Create deserialize plugin that wires up the raw streams\n const rawStreamPlugin =\n createRawStreamDeserializePlugin(getOrCreateStream)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n const refs = new Map()\n result = await processFramedResponse({\n jsonStream: jsonChunks,\n onMessage: (msg: any) => fromCrossJSON(msg, { refs, plugins }),\n onError(msg, error) {\n console.error(msg, error)\n },\n })\n }\n // If it's a stream from the start serializer, process it as such\n else if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occurred?\n console.error(msg, error)\n },\n })\n }\n // If it's a JSON response, it can be simpler\n else if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n\n return result\n }\n\n // If it wasn't processed by the start serializer, check\n // if it's JSON\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n // Otherwise, if it's not OK, throw the content\n if (!response.ok) {\n throw new Error(await response.text())\n }\n\n // Or return the response itself\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n\n/**\n * Processes a framed response where each JSON chunk is a complete JSON string\n * (already decoded by frame decoder).\n */\nasync function processFramedResponse({\n jsonStream,\n onMessage,\n onError,\n}: {\n jsonStream: ReadableStream<string>\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n const reader = jsonStream.getReader()\n\n // Read first JSON frame - this is the main result\n const { value: firstValue, done: firstDone } = await reader.read()\n if (firstDone || !firstValue) {\n throw new Error('Stream ended before first object')\n }\n\n // Each frame is a complete JSON string\n const firstObject = JSON.parse(firstValue)\n\n // Process remaining frames asynchronously (for streaming refs like RawStream)\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (value) {\n try {\n onMessage(JSON.parse(value))\n } catch (e) {\n onError?.(`Invalid JSON: ${value}`, e)\n }\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serverFnFetcher.js","names":[],"sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import {\n createRawStreamDeserializePlugin,\n encode,\n isNotFound,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_CONTENT_TYPE_FRAMED,\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n validateFramedProtocolVersion,\n} from '../constants'\nimport { createFrameDecoder } from './frame-decoder'\nimport type { FunctionMiddlewareClientFnOptions } from '../createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\n/**\n * Checks if an object has at least one own enumerable property.\n * More efficient than Object.keys(obj).length > 0 as it short-circuits on first property.\n */\nconst hop = Object.prototype.hasOwnProperty\nfunction hasOwnProperties(obj: object): boolean {\n for (const _ in obj) {\n if (hop.call(obj, _)) {\n return true\n }\n }\n return false\n}\n// caller =>\n// serverFnFetcher =>\n// client =>\n// server =>\n// fn =>\n// seroval =>\n// client middleware =>\n// serverFnFetcher =>\n// caller\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n const _first = args[0]\n\n const first = _first as FunctionMiddlewareClientFnOptions<any, any, any> & {\n headers?: HeadersInit\n }\n\n // Use custom fetch if provided, otherwise fall back to the passed handler (global fetch)\n const fetchImpl = first.fetch ?? handler\n\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = first.headers ? new Headers(first.headers) : new Headers()\n headers.set('x-tsr-serverFn', 'true')\n\n if (type === 'payload') {\n headers.set(\n 'accept',\n `${TSS_CONTENT_TYPE_FRAMED}, application/x-ndjson, application/json`,\n )\n }\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n if (type === 'formData') {\n throw new Error('FormData is not supported with GET requests')\n }\n const serializedPayload = await serializePayload(first)\n if (serializedPayload !== undefined) {\n const encodedPayload = encode({\n payload: serializedPayload,\n })\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n let body = undefined\n if (first.method === 'POST') {\n const fetchBody = await getFetchBody(first)\n if (fetchBody?.contentType) {\n headers.set('content-type', fetchBody.contentType)\n }\n body = fetchBody?.body\n }\n\n return await getResponse(async () =>\n fetchImpl(url, {\n method: first.method,\n headers,\n signal: first.signal,\n body,\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<string | undefined> {\n let payloadAvailable = false\n const payloadToSerialize: any = {}\n if (opts.data !== undefined) {\n payloadAvailable = true\n payloadToSerialize['data'] = opts.data\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n payloadAvailable = true\n payloadToSerialize['context'] = opts.context\n }\n\n if (payloadAvailable) {\n return serialize(payloadToSerialize)\n }\n return undefined\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetchBody(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<{ body: FormData | string; contentType?: string } | undefined> {\n if (opts.data instanceof FormData) {\n let serializedContext = undefined\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n serializedContext = await serialize(opts.context)\n }\n if (serializedContext !== undefined) {\n opts.data.set(TSS_FORMDATA_CONTEXT, serializedContext)\n }\n return { body: opts.data }\n }\n const serializedBody = await serializePayload(opts)\n if (serializedBody) {\n return { body: serializedBody, contentType: 'application/json' }\n }\n return undefined\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n let response: Response\n try {\n response = await fn() // client => server => fn => server => client\n } catch (error) {\n if (error instanceof Response) {\n response = error\n } else {\n console.log(error)\n throw error\n }\n }\n\n if (response.headers.get(X_TSS_RAW_RESPONSE) === 'true') {\n return response\n }\n\n const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)\n\n // If the response is serialized by the start server, we need to process it\n // differently than a normal response.\n if (serializedByStart) {\n let result\n\n // If it's a framed response (contains RawStream), use frame decoder\n if (contentType.includes(TSS_CONTENT_TYPE_FRAMED)) {\n // Validate protocol version compatibility\n validateFramedProtocolVersion(contentType)\n\n if (!response.body) {\n throw new Error('No response body for framed response')\n }\n\n const { getOrCreateStream, jsonChunks } = createFrameDecoder(\n response.body,\n )\n\n // Create deserialize plugin that wires up the raw streams\n const rawStreamPlugin =\n createRawStreamDeserializePlugin(getOrCreateStream)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n const refs = new Map()\n result = await processFramedResponse({\n jsonStream: jsonChunks,\n onMessage: (msg: any) => fromCrossJSON(msg, { refs, plugins }),\n onError(msg, error) {\n console.error(msg, error)\n },\n })\n }\n // If it's a stream from the start serializer, process it as such\n else if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occurred?\n console.error(msg, error)\n },\n })\n }\n // If it's a JSON response, it can be simpler\n else if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n\n return result\n }\n\n // If it wasn't processed by the start serializer, check\n // if it's JSON\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n // Otherwise, if it's not OK, throw the content\n if (!response.ok) {\n throw new Error(await response.text())\n }\n\n // Or return the response itself\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n\n/**\n * Processes a framed response where each JSON chunk is a complete JSON string\n * (already decoded by frame decoder).\n */\nasync function processFramedResponse({\n jsonStream,\n onMessage,\n onError,\n}: {\n jsonStream: ReadableStream<string>\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n const reader = jsonStream.getReader()\n\n // Read first JSON frame - this is the main result\n const { value: firstValue, done: firstDone } = await reader.read()\n if (firstDone || !firstValue) {\n throw new Error('Stream ended before first object')\n }\n\n // Each frame is a complete JSON string\n const firstObject = JSON.parse(firstValue)\n\n // Process remaining frames asynchronously (for streaming refs like RawStream)\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (value) {\n try {\n onMessage(JSON.parse(value))\n } catch (e) {\n onError?.(`Invalid JSON: ${value}`, e)\n }\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"mappings":"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"}
|