@tanstack/start-client-core 1.167.1 → 1.167.3
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,9 +1,8 @@
|
|
|
1
1
|
import { TSS_CONTENT_TYPE_FRAMED, TSS_FORMDATA_CONTEXT, validateFramedProtocolVersion } from "../constants.js";
|
|
2
2
|
import { getDefaultSerovalPlugins } from "../getDefaultSerovalPlugins.js";
|
|
3
3
|
import { createFrameDecoder } from "./frame-decoder.js";
|
|
4
|
-
import { createRawStreamDeserializePlugin, encode, isNotFound, parseRedirect } from "@tanstack/router-core";
|
|
4
|
+
import { createRawStreamDeserializePlugin, encode, invariant, isNotFound, parseRedirect } from "@tanstack/router-core";
|
|
5
5
|
import { fromCrossJSON, toJSONAsync } from "seroval";
|
|
6
|
-
import invariant from "tiny-invariant";
|
|
7
6
|
//#region src/client-rpc/serverFnFetcher.ts
|
|
8
7
|
var serovalPlugins = null;
|
|
9
8
|
/**
|
|
@@ -95,7 +94,10 @@ async function getResponse(fn) {
|
|
|
95
94
|
}
|
|
96
95
|
if (response.headers.get("x-tss-raw") === "true") return response;
|
|
97
96
|
const contentType = response.headers.get("content-type");
|
|
98
|
-
|
|
97
|
+
if (!contentType) {
|
|
98
|
+
if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: expected content-type header to be set");
|
|
99
|
+
invariant();
|
|
100
|
+
}
|
|
99
101
|
if (!!response.headers.get("x-tss-serialized")) {
|
|
100
102
|
let result;
|
|
101
103
|
if (contentType.includes("application/x-tss-framed")) {
|
|
@@ -127,7 +129,10 @@ async function getResponse(fn) {
|
|
|
127
129
|
}
|
|
128
130
|
});
|
|
129
131
|
} else if (contentType.includes("application/json")) result = fromCrossJSON(await response.json(), { plugins: serovalPlugins });
|
|
130
|
-
|
|
132
|
+
if (!result) {
|
|
133
|
+
if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: expected result to be resolved");
|
|
134
|
+
invariant();
|
|
135
|
+
}
|
|
131
136
|
if (result instanceof Error) throw result;
|
|
132
137
|
return result;
|
|
133
138
|
}
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"serverFnFetcher.js","names":[],"sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import {\n createRawStreamDeserializePlugin,\n encode,\n invariant,\n isNotFound,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\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 if (!contentType) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: expected content-type header to be set',\n )\n }\n\n invariant()\n }\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 if (!result) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: expected result to be resolved')\n }\n\n invariant()\n }\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-client-core",
|
|
3
|
-
"version": "1.167.
|
|
3
|
+
"version": "1.167.3",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,11 +67,9 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"seroval": "^1.4.2",
|
|
70
|
-
"
|
|
71
|
-
"tiny-warning": "^1.0.3",
|
|
70
|
+
"@tanstack/router-core": "1.168.3",
|
|
72
71
|
"@tanstack/start-fn-stubs": "1.161.6",
|
|
73
|
-
"@tanstack/
|
|
74
|
-
"@tanstack/start-storage-context": "1.166.15"
|
|
72
|
+
"@tanstack/start-storage-context": "1.166.17"
|
|
75
73
|
},
|
|
76
74
|
"devDependencies": {
|
|
77
75
|
"@tanstack/intent": "^0.0.14",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRawStreamDeserializePlugin,
|
|
3
3
|
encode,
|
|
4
|
+
invariant,
|
|
4
5
|
isNotFound,
|
|
5
6
|
parseRedirect,
|
|
6
7
|
} from '@tanstack/router-core'
|
|
7
8
|
import { fromCrossJSON, toJSONAsync } from 'seroval'
|
|
8
|
-
import invariant from 'tiny-invariant'
|
|
9
9
|
import { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'
|
|
10
10
|
import {
|
|
11
11
|
TSS_CONTENT_TYPE_FRAMED,
|
|
@@ -185,7 +185,15 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
const contentType = response.headers.get('content-type')
|
|
188
|
-
|
|
188
|
+
if (!contentType) {
|
|
189
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
190
|
+
throw new Error(
|
|
191
|
+
'Invariant failed: expected content-type header to be set',
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
invariant()
|
|
196
|
+
}
|
|
189
197
|
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)
|
|
190
198
|
|
|
191
199
|
// If the response is serialized by the start server, we need to process it
|
|
@@ -239,7 +247,13 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
239
247
|
result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
|
|
240
248
|
}
|
|
241
249
|
|
|
242
|
-
|
|
250
|
+
if (!result) {
|
|
251
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
252
|
+
throw new Error('Invariant failed: expected result to be resolved')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
invariant()
|
|
256
|
+
}
|
|
243
257
|
if (result instanceof Error) {
|
|
244
258
|
throw result
|
|
245
259
|
}
|