@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.
@@ -1,70 +1,60 @@
1
- import { isPlainObject, encode, parseRedirect, isNotFound } from "@tanstack/router-core";
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
- if (isPlainObject(_first) && _first.method) {
13
- const first = _first;
14
- const type = first.data instanceof FormData ? "formData" : "payload";
15
- const headers = new Headers({
16
- "x-tsr-redirect": "manual",
17
- ...first.headers instanceof Headers ? Object.fromEntries(first.headers.entries()) : first.headers
18
- });
19
- if (type === "payload") {
20
- headers.set("accept", "application/x-ndjson, application/json");
21
- }
22
- if (first.method === "GET") {
23
- if (type === "formData") {
24
- throw new Error("FormData is not supported with GET requests");
25
- }
26
- const serializedPayload = await serializePayload(first);
27
- if (serializedPayload !== void 0) {
28
- const encodedPayload = encode({
29
- payload: await serializePayload(first)
30
- });
31
- if (url.includes("?")) {
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
- if (url.includes("?")) {
39
- url += `&createServerFn`;
40
- } else {
41
- url += `?createServerFn`;
42
- }
43
- let body = void 0;
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
- return await getResponse(
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: "POST",
63
- headers: {
64
- Accept: "application/json",
65
- "Content-Type": "application/json"
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 && Object.keys(opts.context).length > 0) {
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 && Object.keys(opts.context).length > 0) {
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
- const response = await (async () => {
111
- try {
112
- return await fn();
113
- } catch (error) {
114
- if (error instanceof Response) {
115
- return error;
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":";;;;;AAYA,IAAI,iBAAwD;AAM5D,MAAM,MAAM,OAAO,UAAU;AAC7B,SAAS,iBAAiB,KAAsB;AAC9C,aAAW,KAAK,KAAK;AACnB,QAAI,IAAI,KAAK,KAAK,CAAC,GAAG;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAWA,eAAsB,gBACpB,KACA,MACA,SACA;AACA,MAAI,CAAC,gBAAgB;AACnB,qBAAiB,yBAAA;AAAA,EACnB;AACA,QAAM,SAAS,KAAK,CAAC;AAErB,QAAM,QAAQ;AAGd,QAAM,OAAO,MAAM,gBAAgB,WAAW,aAAa;AAG3D,QAAM,UAAU,MAAM,UAAU,IAAI,QAAQ,MAAM,OAAO,IAAI,IAAI,QAAA;AACjE,UAAQ,IAAI,kBAAkB,MAAM;AAEpC,MAAI,SAAS,WAAW;AACtB,YAAQ,IAAI,UAAU,wCAAwC;AAAA,EAChE;AAGA,MAAI,MAAM,WAAW,OAAO;AAC1B,QAAI,SAAS,YAAY;AACvB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AACA,UAAM,oBAAoB,MAAM,iBAAiB,KAAK;AACtD,QAAI,sBAAsB,QAAW;AACnC,YAAM,iBAAiB,OAAO;AAAA,QAC5B,SAAS;AAAA,MAAA,CACV;AACD,UAAI,IAAI,SAAS,GAAG,GAAG;AACrB,eAAO,IAAI,cAAc;AAAA,MAC3B,OAAO;AACL,eAAO,IAAI,cAAc;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO;AACX,MAAI,MAAM,WAAW,QAAQ;AAC3B,UAAM,YAAY,MAAM,aAAa,KAAK;AAC1C,QAAI,WAAW,aAAa;AAC1B,cAAQ,IAAI,gBAAgB,UAAU,WAAW;AAAA,IACnD;AACA,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO,MAAM;AAAA,IAAY,YACvB,QAAQ,KAAK;AAAA,MACX,QAAQ,MAAM;AAAA,MACd;AAAA,MACA,QAAQ,MAAM;AAAA,MACd;AAAA,IAAA,CACD;AAAA,EAAA;AAEL;AAEA,eAAe,iBACb,MAC6B;AAC7B,MAAI,mBAAmB;AACvB,QAAM,qBAA0B,CAAA;AAChC,MAAI,KAAK,SAAS,QAAW;AAC3B,uBAAmB;AACnB,uBAAmB,MAAM,IAAI,KAAK;AAAA,EACpC;AAGA,MAAI,KAAK,WAAW,iBAAiB,KAAK,OAAO,GAAG;AAClD,uBAAmB;AACnB,uBAAmB,SAAS,IAAI,KAAK;AAAA,EACvC;AAEA,MAAI,kBAAkB;AACpB,WAAO,UAAU,kBAAkB;AAAA,EACrC;AACA,SAAO;AACT;AAEA,eAAe,UAAU,MAAW;AAClC,SAAO,KAAK;AAAA,IACV,MAAM,QAAQ,QAAQ,YAAY,MAAM,EAAE,SAAS,gBAAiB,CAAC;AAAA,EAAA;AAEzE;AAEA,eAAe,aACb,MACwE;AACxE,MAAI,KAAK,gBAAgB,UAAU;AACjC,QAAI,oBAAoB;AAExB,QAAI,KAAK,WAAW,iBAAiB,KAAK,OAAO,GAAG;AAClD,0BAAoB,MAAM,UAAU,KAAK,OAAO;AAAA,IAClD;AACA,QAAI,sBAAsB,QAAW;AACnC,WAAK,KAAK,IAAI,sBAAsB,iBAAiB;AAAA,IACvD;AACA,WAAO,EAAE,MAAM,KAAK,KAAA;AAAA,EACtB;AACA,QAAM,iBAAiB,MAAM,iBAAiB,IAAI;AAClD,MAAI,gBAAgB;AAClB,WAAO,EAAE,MAAM,gBAAgB,aAAa,mBAAA;AAAA,EAC9C;AACA,SAAO;AACT;AAUA,eAAe,YAAY,IAA6B;AACtD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,GAAA;AAAA,EACnB,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,iBAAW;AAAA,IACb,OAAO;AACL,cAAQ,IAAI,KAAK;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ,IAAI,kBAAkB,MAAM,QAAQ;AACvD,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc;AACvD,YAAU,aAAa,wCAAwC;AAC/D,QAAM,oBAAoB,CAAC,CAAC,SAAS,QAAQ,IAAI,gBAAgB;AAIjE,MAAI,mBAAmB;AACrB,QAAI;AAEJ,QAAI,YAAY,SAAS,sBAAsB,GAAG;AAChD,YAAM,2BAAW,IAAA;AACjB,eAAS,MAAM,wBAAwB;AAAA,QACrC;AAAA,QACA,WAAW,CAAC,QACV,cAAc,KAAK,EAAE,MAAM,SAAS,gBAAiB;AAAA,QACvD,QAAQ,KAAK,OAAO;AAElB,kBAAQ,MAAM,KAAK,KAAK;AAAA,QAC1B;AAAA,MAAA,CACD;AAAA,IACH;AAEA,QAAI,YAAY,SAAS,kBAAkB,GAAG;AAC5C,YAAM,cAAc,MAAM,SAAS,KAAA;AACnC,eAAS,cAAc,aAAa,EAAE,SAAS,gBAAiB;AAAA,IAClE;AAEA,cAAU,QAAQ,gCAAgC;AAClD,QAAI,kBAAkB,OAAO;AAC3B,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AAIA,MAAI,YAAY,SAAS,kBAAkB,GAAG;AAC5C,UAAM,cAAc,MAAM,SAAS,KAAA;AACnC,UAAM,WAAW,cAAc,WAAW;AAC1C,QAAI,UAAU;AACZ,YAAM;AAAA,IACR;AACA,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,MAAM,SAAS,MAAM;AAAA,EACvC;AAGA,SAAO;AACT;AAEA,eAAe,wBAAwB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,kBAAkB;AAAA,EACpC;AAEA,QAAM,SAAS,SAAS,KAAK,YAAY,IAAI,kBAAA,CAAmB,EAAE,UAAA;AAElE,MAAI,SAAS;AACb,MAAI,YAAY;AAChB,MAAI;AAEJ,SAAO,CAAC,WAAW;AACjB,UAAM,EAAE,OAAO,KAAA,IAAS,MAAM,OAAO,KAAA;AACrC,QAAI,MAAO,WAAU;AAErB,QAAI,OAAO,WAAW,KAAK,MAAM;AAC/B,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAGA,QAAI,OAAO,SAAS,IAAI,GAAG;AACzB,YAAM,QAAQ,OAAO,MAAM,IAAI,EAAE,OAAO,OAAO;AAC/C,YAAM,YAAY,MAAM,CAAC;AACzB,UAAI,CAAC,UAAW,OAAM,IAAI,MAAM,iCAAiC;AACjE,oBAAc,KAAK,MAAM,SAAS;AAClC,kBAAY;AACZ,eAAS,MAAM,MAAM,CAAC,EAAE,KAAK,IAAI;AAAA,IACnC,OAAO;AAEL,YAAM,eAAe,OAAO,QAAQ,IAAI;AACxC,UAAI,gBAAgB,GAAG;AACrB,cAAM,OAAO,OAAO,MAAM,GAAG,YAAY,EAAE,KAAA;AAC3C,iBAAS,OAAO,MAAM,eAAe,CAAC;AACtC,YAAI,KAAK,SAAS,GAAG;AACnB,wBAAc,KAAK,MAAM,IAAI;AAC7B,sBAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGC,GAAC,YAAY;AACZ,QAAI;AAEF,aAAO,MAAM;AACX,cAAM,EAAE,OAAO,KAAA,IAAS,MAAM,OAAO,KAAA;AACrC,YAAI,MAAO,WAAU;AAErB,cAAM,cAAc,OAAO,YAAY,IAAI;AAC3C,YAAI,eAAe,GAAG;AACpB,gBAAM,QAAQ,OAAO,MAAM,GAAG,WAAW;AACzC,mBAAS,OAAO,MAAM,cAAc,CAAC;AACrC,gBAAM,QAAQ,MAAM,MAAM,IAAI,EAAE,OAAO,OAAO;AAE9C,qBAAW,QAAQ,OAAO;AACxB,gBAAI;AACF,wBAAU,KAAK,MAAM,IAAI,CAAC;AAAA,YAC5B,SAAS,GAAG;AACV,wBAAU,sBAAsB,IAAI,IAAI,CAAC;AAAA,YAC3C;AAAA,UACF;AAAA,QACF;AAEA,YAAI,MAAM;AACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,gBAAU,4BAA4B,GAAG;AAAA,IAC3C;AAAA,EACF,GAAA;AAEA,SAAO,UAAU,WAAW;AAC9B;"}
@@ -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 {};
@@ -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<AnyFunctionMiddleware | AnyRequestMiddleware>): Array<AnyFunctionMiddleware | AnyRequestMiddleware>;
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
- return executeMiddleware(resolvedMiddleware, "client", {
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
- ...serverContextAfterGlobalMiddlewares,
70
- ...opts.context
71
- },
72
+ context: safeObjectMerge(
73
+ serverContextAfterGlobalMiddlewares,
74
+ opts.context
75
+ ),
72
76
  signal,
73
77
  request: startContext.request
74
78
  };
75
- return executeMiddleware(resolvedMiddleware, "server", ctx).then(
76
- (d) => ({
77
- // Only send the result and sendContext back to the client
78
- result: d.result,
79
- error: d.error,
80
- context: d.sendContext
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
- const flattenedMiddlewares = flattenMiddlewares([
106
+ let flattenedMiddlewares = flattenMiddlewares([
100
107
  ...globalMiddlewares,
101
108
  ...middlewares
102
109
  ]);
103
- const next = async (ctx) => {
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
- if ("inputValidator" in nextMiddleware.options && nextMiddleware.options.inputValidator && env === "server") {
109
- ctx.data = await execValidator(
110
- nextMiddleware.options.inputValidator,
111
- ctx.data
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
- } else if ("server" in nextMiddleware.options) {
120
- middlewareFn = nextMiddleware.options.server;
121
- }
122
- if (middlewareFn) {
123
- return applyMiddleware(middlewareFn, ctx, async (newCtx) => {
124
- return next(newCtx).catch((error) => {
125
- if (isRedirect(error) || isNotFound(error)) {
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
- ...newCtx,
153
+ ...nextCtx,
128
154
  error
129
155
  };
130
156
  }
131
- throw error;
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 next({
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,