@tanstack/start-client-core 1.143.8 → 1.143.12
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-rpc/serverFnFetcher.js +55 -70
- package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -1
- package/dist/esm/constants.d.ts +1 -0
- package/dist/esm/constants.js +2 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/createServerFn.d.ts +1 -2
- package/dist/esm/createServerFn.js +104 -70
- package/dist/esm/createServerFn.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +7 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/safeObjectMerge.d.ts +10 -0
- package/dist/esm/safeObjectMerge.js +30 -0
- package/dist/esm/safeObjectMerge.js.map +1 -0
- package/package.json +3 -3
- package/src/client-rpc/serverFnFetcher.ts +86 -91
- package/src/constants.ts +1 -0
- package/src/createServerFn.ts +145 -99
- package/src/index.tsx +2 -1
- package/src/safeObjectMerge.ts +38 -0
|
@@ -1,70 +1,60 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encode, parseRedirect, isNotFound } from "@tanstack/router-core";
|
|
2
2
|
import { fromCrossJSON, toJSONAsync } from "seroval";
|
|
3
3
|
import invariant from "tiny-invariant";
|
|
4
4
|
import { getDefaultSerovalPlugins } from "../getDefaultSerovalPlugins.js";
|
|
5
5
|
import { TSS_FORMDATA_CONTEXT, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED } from "../constants.js";
|
|
6
6
|
let serovalPlugins = null;
|
|
7
|
+
const hop = Object.prototype.hasOwnProperty;
|
|
8
|
+
function hasOwnProperties(obj) {
|
|
9
|
+
for (const _ in obj) {
|
|
10
|
+
if (hop.call(obj, _)) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
7
16
|
async function serverFnFetcher(url, args, handler) {
|
|
8
17
|
if (!serovalPlugins) {
|
|
9
18
|
serovalPlugins = getDefaultSerovalPlugins();
|
|
10
19
|
}
|
|
11
20
|
const _first = args[0];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
url += `&${encodedPayload}`;
|
|
33
|
-
} else {
|
|
34
|
-
url += `?${encodedPayload}`;
|
|
35
|
-
}
|
|
21
|
+
const first = _first;
|
|
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") {
|
|
26
|
+
headers.set("accept", "application/x-ndjson, application/json");
|
|
27
|
+
}
|
|
28
|
+
if (first.method === "GET") {
|
|
29
|
+
if (type === "formData") {
|
|
30
|
+
throw new Error("FormData is not supported with GET requests");
|
|
31
|
+
}
|
|
32
|
+
const serializedPayload = await serializePayload(first);
|
|
33
|
+
if (serializedPayload !== void 0) {
|
|
34
|
+
const encodedPayload = encode({
|
|
35
|
+
payload: serializedPayload
|
|
36
|
+
});
|
|
37
|
+
if (url.includes("?")) {
|
|
38
|
+
url += `&${encodedPayload}`;
|
|
39
|
+
} else {
|
|
40
|
+
url += `?${encodedPayload}`;
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (first.method === "POST") {
|
|
45
|
-
const fetchBody = await getFetchBody(first);
|
|
46
|
-
if (fetchBody?.contentType) {
|
|
47
|
-
headers.set("content-type", fetchBody.contentType);
|
|
48
|
-
}
|
|
49
|
-
body = fetchBody?.body;
|
|
43
|
+
}
|
|
44
|
+
let body = void 0;
|
|
45
|
+
if (first.method === "POST") {
|
|
46
|
+
const fetchBody = await getFetchBody(first);
|
|
47
|
+
if (fetchBody?.contentType) {
|
|
48
|
+
headers.set("content-type", fetchBody.contentType);
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
async () => handler(url, {
|
|
53
|
-
method: first.method,
|
|
54
|
-
headers,
|
|
55
|
-
signal: first.signal,
|
|
56
|
-
body
|
|
57
|
-
})
|
|
58
|
-
);
|
|
50
|
+
body = fetchBody?.body;
|
|
59
51
|
}
|
|
60
52
|
return await getResponse(
|
|
61
|
-
() => handler(url, {
|
|
62
|
-
method:
|
|
63
|
-
headers
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
body: JSON.stringify(args)
|
|
53
|
+
async () => handler(url, {
|
|
54
|
+
method: first.method,
|
|
55
|
+
headers,
|
|
56
|
+
signal: first.signal,
|
|
57
|
+
body
|
|
68
58
|
})
|
|
69
59
|
);
|
|
70
60
|
}
|
|
@@ -75,7 +65,7 @@ async function serializePayload(opts) {
|
|
|
75
65
|
payloadAvailable = true;
|
|
76
66
|
payloadToSerialize["data"] = opts.data;
|
|
77
67
|
}
|
|
78
|
-
if (opts.context &&
|
|
68
|
+
if (opts.context && hasOwnProperties(opts.context)) {
|
|
79
69
|
payloadAvailable = true;
|
|
80
70
|
payloadToSerialize["context"] = opts.context;
|
|
81
71
|
}
|
|
@@ -92,7 +82,7 @@ async function serialize(data) {
|
|
|
92
82
|
async function getFetchBody(opts) {
|
|
93
83
|
if (opts.data instanceof FormData) {
|
|
94
84
|
let serializedContext = void 0;
|
|
95
|
-
if (opts.context &&
|
|
85
|
+
if (opts.context && hasOwnProperties(opts.context)) {
|
|
96
86
|
serializedContext = await serialize(opts.context);
|
|
97
87
|
}
|
|
98
88
|
if (serializedContext !== void 0) {
|
|
@@ -107,31 +97,23 @@ async function getFetchBody(opts) {
|
|
|
107
97
|
return void 0;
|
|
108
98
|
}
|
|
109
99
|
async function getResponse(fn) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
100
|
+
let response;
|
|
101
|
+
try {
|
|
102
|
+
response = await fn();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof Response) {
|
|
105
|
+
response = error;
|
|
106
|
+
} else {
|
|
117
107
|
console.log(error);
|
|
118
108
|
throw error;
|
|
119
109
|
}
|
|
120
|
-
}
|
|
110
|
+
}
|
|
121
111
|
if (response.headers.get(X_TSS_RAW_RESPONSE) === "true") {
|
|
122
112
|
return response;
|
|
123
113
|
}
|
|
124
114
|
const contentType = response.headers.get("content-type");
|
|
125
115
|
invariant(contentType, "expected content-type header to be set");
|
|
126
116
|
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED);
|
|
127
|
-
if (!response.ok) {
|
|
128
|
-
if (serializedByStart && contentType.includes("application/json")) {
|
|
129
|
-
const jsonPayload = await response.json();
|
|
130
|
-
const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins });
|
|
131
|
-
throw result;
|
|
132
|
-
}
|
|
133
|
-
throw new Error(await response.text());
|
|
134
|
-
}
|
|
135
117
|
if (serializedByStart) {
|
|
136
118
|
let result;
|
|
137
119
|
if (contentType.includes("application/x-ndjson")) {
|
|
@@ -165,6 +147,9 @@ async function getResponse(fn) {
|
|
|
165
147
|
}
|
|
166
148
|
return jsonPayload;
|
|
167
149
|
}
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
throw new Error(await response.text());
|
|
152
|
+
}
|
|
168
153
|
return response;
|
|
169
154
|
}
|
|
170
155
|
async function processServerFnResponse({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverFnFetcher.js","sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import {\n encode,\n isNotFound,\n isPlainObject,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n} from '../constants'\nimport type { FunctionMiddlewareClientFnOptions } from '../createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\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 // If createServerFn was used to wrap the fetcher,\n // We need to handle the arguments differently\n if (isPlainObject(_first) && _first.method) {\n const first = _first as FunctionMiddlewareClientFnOptions<any, any, any> & {\n headers: HeadersInit\n }\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = new Headers({\n 'x-tsr-redirect': 'manual',\n ...(first.headers instanceof Headers\n ? Object.fromEntries(first.headers.entries())\n : first.headers),\n })\n\n if (type === 'payload') {\n headers.set('accept', 'application/x-ndjson, application/json')\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: await serializePayload(first),\n })\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n if (url.includes('?')) {\n url += `&createServerFn`\n } else {\n url += `?createServerFn`\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 handler(url, {\n method: first.method,\n headers,\n signal: first.signal,\n body,\n }),\n )\n }\n\n // If not a custom fetcher, it was probably\n // a `use server` function, so just proxy the arguments\n // through as a POST request\n return await getResponse(() =>\n handler(url, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(args),\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 && Object.keys(opts.context).length > 0) {\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 && Object.keys(opts.context).length > 0) {\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 const response = await (async () => {\n try {\n return await fn()\n } catch (error) {\n if (error instanceof Response) {\n return error\n }\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 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 // If the response is not ok, throw an error\n if (!response.ok) {\n if (serializedByStart && contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n throw result\n }\n\n throw new Error(await response.text())\n }\n\n if (serializedByStart) {\n let result\n 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 (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n return result\n }\n\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 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"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serverFnFetcher.js","sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import { encode, isNotFound, parseRedirect } from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n} from '../constants'\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 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('accept', 'application/x-ndjson, application/json')\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 handler(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 // If it's a stream from the start serializer, process it as such\n 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 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"],"names":[],"mappings":"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"}
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare const TSS_SERVER_FUNCTION: unique symbol;
|
|
|
3
3
|
export declare const TSS_SERVER_FUNCTION_FACTORY: unique symbol;
|
|
4
4
|
export declare const X_TSS_SERIALIZED = "x-tss-serialized";
|
|
5
5
|
export declare const X_TSS_RAW_RESPONSE = "x-tss-raw";
|
|
6
|
+
export declare const X_TSS_CONTEXT = "x-tss-context";
|
|
6
7
|
export {};
|
package/dist/esm/constants.js
CHANGED
|
@@ -5,10 +5,12 @@ const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(
|
|
|
5
5
|
);
|
|
6
6
|
const X_TSS_SERIALIZED = "x-tss-serialized";
|
|
7
7
|
const X_TSS_RAW_RESPONSE = "x-tss-raw";
|
|
8
|
+
const X_TSS_CONTEXT = "x-tss-context";
|
|
8
9
|
export {
|
|
9
10
|
TSS_FORMDATA_CONTEXT,
|
|
10
11
|
TSS_SERVER_FUNCTION,
|
|
11
12
|
TSS_SERVER_FUNCTION_FACTORY,
|
|
13
|
+
X_TSS_CONTEXT,
|
|
12
14
|
X_TSS_RAW_RESPONSE,
|
|
13
15
|
X_TSS_SERIALIZED
|
|
14
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const TSS_FORMDATA_CONTEXT = '__TSS_CONTEXT'\nexport const TSS_SERVER_FUNCTION = Symbol.for('TSS_SERVER_FUNCTION')\nexport const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(\n 'TSS_SERVER_FUNCTION_FACTORY',\n)\n\nexport const X_TSS_SERIALIZED = 'x-tss-serialized'\nexport const X_TSS_RAW_RESPONSE = 'x-tss-raw'\nexport {}\n"],"names":[],"mappings":"AAAO,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB,OAAO,IAAI,qBAAqB;AAC5D,MAAM,8BAA8B,OAAO;AAAA,EAChD;AACF;AAEO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;"}
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const TSS_FORMDATA_CONTEXT = '__TSS_CONTEXT'\nexport const TSS_SERVER_FUNCTION = Symbol.for('TSS_SERVER_FUNCTION')\nexport const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(\n 'TSS_SERVER_FUNCTION_FACTORY',\n)\n\nexport const X_TSS_SERIALIZED = 'x-tss-serialized'\nexport const X_TSS_RAW_RESPONSE = 'x-tss-raw'\nexport const X_TSS_CONTEXT = 'x-tss-context'\nexport {}\n"],"names":[],"mappings":"AAAO,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB,OAAO,IAAI,qBAAqB;AAC5D,MAAM,8BAA8B,OAAO;AAAA,EAChD;AACF;AAEO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;"}
|
|
@@ -105,7 +105,7 @@ export interface ServerFnTypes<in out TRegister, in out TMethod extends Method,
|
|
|
105
105
|
allInput: IntersectAllValidatorInputs<TMiddlewares, TInputValidator>;
|
|
106
106
|
allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>;
|
|
107
107
|
}
|
|
108
|
-
export declare function flattenMiddlewares(middlewares: Array<
|
|
108
|
+
export declare function flattenMiddlewares<T extends AnyFunctionMiddleware | AnyRequestMiddleware>(middlewares: Array<T>, maxDepth?: number): Array<T>;
|
|
109
109
|
export type ServerFnMiddlewareOptions = {
|
|
110
110
|
method: Method;
|
|
111
111
|
data: any;
|
|
@@ -123,5 +123,4 @@ export type NextFn = (ctx: ServerFnMiddlewareResult) => Promise<ServerFnMiddlewa
|
|
|
123
123
|
export type MiddlewareFn = (ctx: ServerFnMiddlewareOptions & {
|
|
124
124
|
next: NextFn;
|
|
125
125
|
}) => Promise<ServerFnMiddlewareResult>;
|
|
126
|
-
export declare const applyMiddleware: (middlewareFn: MiddlewareFn, ctx: ServerFnMiddlewareOptions, nextFn: NextFn) => Promise<ServerFnMiddlewareResult>;
|
|
127
126
|
export declare function execValidator(validator: AnyValidator, input: unknown): unknown;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { isRedirect, isNotFound } from "@tanstack/router-core";
|
|
2
1
|
import { mergeHeaders } from "@tanstack/router-core/ssr/client";
|
|
2
|
+
import { parseRedirect, isRedirect } from "@tanstack/router-core";
|
|
3
3
|
import { TSS_SERVER_FUNCTION_FACTORY } from "./constants.js";
|
|
4
4
|
import { getStartOptions } from "./getStartOptions.js";
|
|
5
5
|
import { getStartContextServerOnly } from "./getStartContextServerOnly.js";
|
|
6
|
+
import { createNullProtoObject, safeObjectMerge } from "./safeObjectMerge.js";
|
|
6
7
|
const createServerFn = (options, __opts) => {
|
|
7
8
|
const resolvedOptions = __opts || options || {};
|
|
8
9
|
if (typeof resolvedOptions.method === "undefined") {
|
|
@@ -42,17 +43,20 @@ const createServerFn = (options, __opts) => {
|
|
|
42
43
|
];
|
|
43
44
|
return Object.assign(
|
|
44
45
|
async (opts) => {
|
|
45
|
-
|
|
46
|
+
const result = await executeMiddleware(resolvedMiddleware, "client", {
|
|
46
47
|
...extractedFn,
|
|
47
48
|
...newOptions,
|
|
48
49
|
data: opts?.data,
|
|
49
50
|
headers: opts?.headers,
|
|
50
51
|
signal: opts?.signal,
|
|
51
|
-
context:
|
|
52
|
-
}).then((d) => {
|
|
53
|
-
if (d.error) throw d.error;
|
|
54
|
-
return d.result;
|
|
52
|
+
context: createNullProtoObject()
|
|
55
53
|
});
|
|
54
|
+
const redirect = parseRedirect(result.error);
|
|
55
|
+
if (redirect) {
|
|
56
|
+
throw redirect;
|
|
57
|
+
}
|
|
58
|
+
if (result.error) throw result.error;
|
|
59
|
+
return result.result;
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
// This copies over the URL, function ID
|
|
@@ -65,21 +69,24 @@ const createServerFn = (options, __opts) => {
|
|
|
65
69
|
const ctx = {
|
|
66
70
|
...extractedFn,
|
|
67
71
|
...opts,
|
|
68
|
-
context:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
context: safeObjectMerge(
|
|
73
|
+
serverContextAfterGlobalMiddlewares,
|
|
74
|
+
opts.context
|
|
75
|
+
),
|
|
72
76
|
signal,
|
|
73
77
|
request: startContext.request
|
|
74
78
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const result = await executeMiddleware(
|
|
80
|
+
resolvedMiddleware,
|
|
81
|
+
"server",
|
|
82
|
+
ctx
|
|
83
|
+
).then((d) => ({
|
|
84
|
+
// Only send the result and sendContext back to the client
|
|
85
|
+
result: d.result,
|
|
86
|
+
error: d.error,
|
|
87
|
+
context: d.sendContext
|
|
88
|
+
}));
|
|
89
|
+
return result;
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
);
|
|
@@ -96,58 +103,108 @@ const createServerFn = (options, __opts) => {
|
|
|
96
103
|
};
|
|
97
104
|
async function executeMiddleware(middlewares, env, opts) {
|
|
98
105
|
const globalMiddlewares = getStartOptions()?.functionMiddleware || [];
|
|
99
|
-
|
|
106
|
+
let flattenedMiddlewares = flattenMiddlewares([
|
|
100
107
|
...globalMiddlewares,
|
|
101
108
|
...middlewares
|
|
102
109
|
]);
|
|
103
|
-
|
|
110
|
+
if (env === "server") {
|
|
111
|
+
const startContext = getStartContextServerOnly({ throwIfNotFound: false });
|
|
112
|
+
if (startContext?.executedRequestMiddlewares) {
|
|
113
|
+
flattenedMiddlewares = flattenedMiddlewares.filter(
|
|
114
|
+
(m) => !startContext.executedRequestMiddlewares.has(m)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const callNextMiddleware = async (ctx) => {
|
|
104
119
|
const nextMiddleware = flattenedMiddlewares.shift();
|
|
105
120
|
if (!nextMiddleware) {
|
|
106
121
|
return ctx;
|
|
107
122
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let middlewareFn = void 0;
|
|
115
|
-
if (env === "client") {
|
|
116
|
-
if ("client" in nextMiddleware.options) {
|
|
117
|
-
middlewareFn = nextMiddleware.options.client;
|
|
123
|
+
try {
|
|
124
|
+
if ("inputValidator" in nextMiddleware.options && nextMiddleware.options.inputValidator && env === "server") {
|
|
125
|
+
ctx.data = await execValidator(
|
|
126
|
+
nextMiddleware.options.inputValidator,
|
|
127
|
+
ctx.data
|
|
128
|
+
);
|
|
118
129
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
let middlewareFn = void 0;
|
|
131
|
+
if (env === "client") {
|
|
132
|
+
if ("client" in nextMiddleware.options) {
|
|
133
|
+
middlewareFn = nextMiddleware.options.client;
|
|
134
|
+
}
|
|
135
|
+
} else if ("server" in nextMiddleware.options) {
|
|
136
|
+
middlewareFn = nextMiddleware.options.server;
|
|
137
|
+
}
|
|
138
|
+
if (middlewareFn) {
|
|
139
|
+
const userNext = async (userCtx = {}) => {
|
|
140
|
+
const nextCtx = {
|
|
141
|
+
...ctx,
|
|
142
|
+
...userCtx,
|
|
143
|
+
context: safeObjectMerge(ctx.context, userCtx.context),
|
|
144
|
+
sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),
|
|
145
|
+
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
146
|
+
result: userCtx.result !== void 0 ? userCtx.result : userCtx instanceof Response ? userCtx : ctx.result,
|
|
147
|
+
error: userCtx.error ?? ctx.error
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
return await callNextMiddleware(nextCtx);
|
|
151
|
+
} catch (error) {
|
|
126
152
|
return {
|
|
127
|
-
...
|
|
153
|
+
...nextCtx,
|
|
128
154
|
error
|
|
129
155
|
};
|
|
130
156
|
}
|
|
131
|
-
|
|
157
|
+
};
|
|
158
|
+
const result = await middlewareFn({
|
|
159
|
+
...ctx,
|
|
160
|
+
next: userNext
|
|
132
161
|
});
|
|
133
|
-
|
|
162
|
+
if (isRedirect(result)) {
|
|
163
|
+
return {
|
|
164
|
+
...ctx,
|
|
165
|
+
error: result
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (result instanceof Response) {
|
|
169
|
+
return {
|
|
170
|
+
...ctx,
|
|
171
|
+
result
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (!result) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"User middleware returned undefined. You must call next() or return a result in your middlewares."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
return callNextMiddleware(ctx);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
...ctx,
|
|
185
|
+
error
|
|
186
|
+
};
|
|
134
187
|
}
|
|
135
|
-
return next(ctx);
|
|
136
188
|
};
|
|
137
|
-
return
|
|
189
|
+
return callNextMiddleware({
|
|
138
190
|
...opts,
|
|
139
191
|
headers: opts.headers || {},
|
|
140
192
|
sendContext: opts.sendContext || {},
|
|
141
|
-
context: opts.context ||
|
|
193
|
+
context: opts.context || createNullProtoObject()
|
|
142
194
|
});
|
|
143
195
|
}
|
|
144
|
-
function flattenMiddlewares(middlewares) {
|
|
196
|
+
function flattenMiddlewares(middlewares, maxDepth = 100) {
|
|
145
197
|
const seen = /* @__PURE__ */ new Set();
|
|
146
198
|
const flattened = [];
|
|
147
|
-
const recurse = (middleware) => {
|
|
199
|
+
const recurse = (middleware, depth) => {
|
|
200
|
+
if (depth > maxDepth) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
148
205
|
middleware.forEach((m) => {
|
|
149
206
|
if (m.options.middleware) {
|
|
150
|
-
recurse(m.options.middleware);
|
|
207
|
+
recurse(m.options.middleware, depth + 1);
|
|
151
208
|
}
|
|
152
209
|
if (!seen.has(m)) {
|
|
153
210
|
seen.add(m);
|
|
@@ -155,31 +212,9 @@ function flattenMiddlewares(middlewares) {
|
|
|
155
212
|
}
|
|
156
213
|
});
|
|
157
214
|
};
|
|
158
|
-
recurse(middlewares);
|
|
215
|
+
recurse(middlewares, 0);
|
|
159
216
|
return flattened;
|
|
160
217
|
}
|
|
161
|
-
const applyMiddleware = async (middlewareFn, ctx, nextFn) => {
|
|
162
|
-
return middlewareFn({
|
|
163
|
-
...ctx,
|
|
164
|
-
next: (async (userCtx = {}) => {
|
|
165
|
-
return nextFn({
|
|
166
|
-
...ctx,
|
|
167
|
-
...userCtx,
|
|
168
|
-
context: {
|
|
169
|
-
...ctx.context,
|
|
170
|
-
...userCtx.context
|
|
171
|
-
},
|
|
172
|
-
sendContext: {
|
|
173
|
-
...ctx.sendContext,
|
|
174
|
-
...userCtx.sendContext ?? {}
|
|
175
|
-
},
|
|
176
|
-
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
177
|
-
result: userCtx.result !== void 0 ? userCtx.result : userCtx instanceof Response ? userCtx : ctx.result,
|
|
178
|
-
error: userCtx.error ?? ctx.error
|
|
179
|
-
});
|
|
180
|
-
})
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
218
|
function execValidator(validator, input) {
|
|
184
219
|
if (validator == null) return {};
|
|
185
220
|
if ("~standard" in validator) {
|
|
@@ -223,7 +258,6 @@ function serverFnBaseToMiddleware(options) {
|
|
|
223
258
|
};
|
|
224
259
|
}
|
|
225
260
|
export {
|
|
226
|
-
applyMiddleware,
|
|
227
261
|
createServerFn,
|
|
228
262
|
execValidator,
|
|
229
263
|
executeMiddleware,
|