@tanstack/start-server-core 1.167.10 → 1.167.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/createSsrRpc.d.ts +1 -3
- package/dist/esm/createSsrRpc.js +2 -2
- package/dist/esm/createSsrRpc.js.map +1 -1
- package/dist/esm/createStartHandler.js +23 -9
- package/dist/esm/createStartHandler.js.map +1 -1
- package/dist/esm/fake-start-server-fn-resolver.d.ts +3 -1
- package/dist/esm/fake-start-server-fn-resolver.js +1 -1
- package/dist/esm/fake-start-server-fn-resolver.js.map +1 -1
- package/dist/esm/frame-protocol.d.ts +14 -2
- package/dist/esm/frame-protocol.js +83 -70
- package/dist/esm/frame-protocol.js.map +1 -1
- package/dist/esm/serializer/ServerFunctionSerializationAdapter.js +1 -1
- package/dist/esm/serializer/ServerFunctionSerializationAdapter.js.map +1 -1
- package/dist/esm/server-functions-handler.js +46 -30
- package/dist/esm/server-functions-handler.js.map +1 -1
- package/dist/esm/transformAssetUrls.js +6 -8
- package/dist/esm/transformAssetUrls.js.map +1 -1
- package/dist/esm/virtual-modules.d.ts +1 -0
- package/dist/esm/virtual-modules.js +2 -1
- package/dist/esm/virtual-modules.js.map +1 -1
- package/package.json +2 -2
- package/src/createSsrRpc.ts +2 -9
- package/src/createStartHandler.ts +44 -14
- package/src/fake-start-server-fn-resolver.ts +4 -1
- package/src/frame-protocol.ts +131 -89
- package/src/serializer/ServerFunctionSerializationAdapter.ts +1 -1
- package/src/server-functions-handler.ts +104 -54
- package/src/tanstack-start.d.ts +3 -1
- package/src/transformAssetUrls.ts +13 -18
- package/src/virtual-modules.ts +1 -0
|
@@ -6,13 +6,12 @@ import { createRawStreamRPCPlugin, invariant, isNotFound, isRedirect } from "@ta
|
|
|
6
6
|
import { fromJSON, toCrossJSONAsync, toCrossJSONStream } from "seroval";
|
|
7
7
|
//#region src/server-functions-handler.ts
|
|
8
8
|
var serovalPlugins = void 0;
|
|
9
|
-
var textEncoder = new TextEncoder();
|
|
10
9
|
var FORM_DATA_CONTENT_TYPES = ["multipart/form-data", "application/x-www-form-urlencoded"];
|
|
11
10
|
var MAX_PAYLOAD_SIZE = 1e6;
|
|
12
11
|
var handleServerAction = async ({ request, context, serverFnId }) => {
|
|
13
12
|
const methodUpper = request.method.toUpperCase();
|
|
14
13
|
const url = new URL(request.url);
|
|
15
|
-
const action = await getServerFnById(serverFnId, {
|
|
14
|
+
const action = await getServerFnById(serverFnId, { origin: "client" });
|
|
16
15
|
if (action.method && methodUpper !== action.method) return new Response(`expected ${action.method} method. Got ${methodUpper}`, {
|
|
17
16
|
status: 405,
|
|
18
17
|
headers: { Allow: action.method }
|
|
@@ -76,8 +75,26 @@ var handleServerAction = async ({ request, context, serverFnId }) => {
|
|
|
76
75
|
const alsResponse = getResponse();
|
|
77
76
|
if (res !== void 0) {
|
|
78
77
|
const rawStreams = /* @__PURE__ */ new Map();
|
|
78
|
+
let initialPhase = true;
|
|
79
|
+
let lateStreamWriter;
|
|
80
|
+
let lateStreamReadable = void 0;
|
|
81
|
+
const pendingLateStreams = [];
|
|
79
82
|
const plugins = [createRawStreamRPCPlugin((id, stream) => {
|
|
80
|
-
|
|
83
|
+
if (initialPhase) {
|
|
84
|
+
rawStreams.set(id, stream);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (lateStreamWriter) {
|
|
88
|
+
lateStreamWriter.write({
|
|
89
|
+
id,
|
|
90
|
+
stream
|
|
91
|
+
}).catch(() => {});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
pendingLateStreams.push({
|
|
95
|
+
id,
|
|
96
|
+
stream
|
|
97
|
+
});
|
|
81
98
|
}), ...serovalPlugins || []];
|
|
82
99
|
let done = false;
|
|
83
100
|
const callbacks = {
|
|
@@ -104,6 +121,7 @@ var handleServerAction = async ({ request, context, serverFnId }) => {
|
|
|
104
121
|
callbacks.onError(error);
|
|
105
122
|
}
|
|
106
123
|
});
|
|
124
|
+
initialPhase = false;
|
|
107
125
|
if (done && rawStreams.size === 0) return new Response(nonStreamingBody ? JSON.stringify(nonStreamingBody) : void 0, {
|
|
108
126
|
status: alsResponse.status,
|
|
109
127
|
statusText: alsResponse.statusText,
|
|
@@ -112,8 +130,13 @@ var handleServerAction = async ({ request, context, serverFnId }) => {
|
|
|
112
130
|
[X_TSS_SERIALIZED]: "true"
|
|
113
131
|
}
|
|
114
132
|
});
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
const { readable, writable } = new TransformStream();
|
|
134
|
+
lateStreamReadable = readable;
|
|
135
|
+
lateStreamWriter = writable.getWriter();
|
|
136
|
+
for (const registration of pendingLateStreams) lateStreamWriter.write(registration).catch(() => {});
|
|
137
|
+
pendingLateStreams.length = 0;
|
|
138
|
+
const multiplexedStream = createMultiplexedStream(new ReadableStream({
|
|
139
|
+
start(controller) {
|
|
117
140
|
callbacks.onParse = (value) => {
|
|
118
141
|
controller.enqueue(JSON.stringify(value) + "\n");
|
|
119
142
|
};
|
|
@@ -121,36 +144,29 @@ var handleServerAction = async ({ request, context, serverFnId }) => {
|
|
|
121
144
|
try {
|
|
122
145
|
controller.close();
|
|
123
146
|
} catch {}
|
|
147
|
+
lateStreamWriter?.close().catch(() => {}).finally(() => {
|
|
148
|
+
lateStreamWriter = void 0;
|
|
149
|
+
});
|
|
124
150
|
};
|
|
125
|
-
callbacks.onError = (error) =>
|
|
126
|
-
if (nonStreamingBody !== void 0) callbacks.onParse(nonStreamingBody);
|
|
127
|
-
} }), rawStreams);
|
|
128
|
-
return new Response(multiplexedStream, {
|
|
129
|
-
status: alsResponse.status,
|
|
130
|
-
statusText: alsResponse.statusText,
|
|
131
|
-
headers: {
|
|
132
|
-
"Content-Type": TSS_CONTENT_TYPE_FRAMED_VERSIONED,
|
|
133
|
-
[X_TSS_SERIALIZED]: "true"
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
const stream = new ReadableStream({ start(controller) {
|
|
138
|
-
callbacks.onParse = (value) => controller.enqueue(textEncoder.encode(JSON.stringify(value) + "\n"));
|
|
139
|
-
callbacks.onDone = () => {
|
|
140
|
-
try {
|
|
141
|
-
controller.close();
|
|
142
|
-
} catch (error) {
|
|
151
|
+
callbacks.onError = (error) => {
|
|
143
152
|
controller.error(error);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
lateStreamWriter?.abort(error).catch(() => {}).finally(() => {
|
|
154
|
+
lateStreamWriter = void 0;
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
if (nonStreamingBody !== void 0) callbacks.onParse(nonStreamingBody);
|
|
158
|
+
if (done) callbacks.onDone();
|
|
159
|
+
},
|
|
160
|
+
cancel() {
|
|
161
|
+
lateStreamWriter?.abort().catch(() => {});
|
|
162
|
+
lateStreamWriter = void 0;
|
|
163
|
+
}
|
|
164
|
+
}), rawStreams, lateStreamReadable);
|
|
165
|
+
return new Response(multiplexedStream, {
|
|
150
166
|
status: alsResponse.status,
|
|
151
167
|
statusText: alsResponse.statusText,
|
|
152
168
|
headers: {
|
|
153
|
-
"Content-Type":
|
|
169
|
+
"Content-Type": TSS_CONTENT_TYPE_FRAMED_VERSIONED,
|
|
154
170
|
[X_TSS_SERIALIZED]: "true"
|
|
155
171
|
}
|
|
156
172
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-functions-handler.js","names":[],"sources":["../../src/server-functions-handler.ts"],"sourcesContent":["import {\n createRawStreamRPCPlugin,\n invariant,\n isNotFound,\n isRedirect,\n} from '@tanstack/router-core'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n getDefaultSerovalPlugins,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport { fromJSON, toCrossJSONAsync, toCrossJSONStream } from 'seroval'\nimport { getResponse } from './request-response'\nimport { getServerFnById } from './getServerFnById'\nimport {\n TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n createMultiplexedStream,\n} from './frame-protocol'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\n// Cache serovalPlugins at module level to avoid repeated calls\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | undefined = undefined\n\n// Cache TextEncoder for NDJSON serialization\nconst textEncoder = new TextEncoder()\n\n// Known FormData 'Content-Type' header values - module-level constant\nconst FORM_DATA_CONTENT_TYPES = [\n 'multipart/form-data',\n 'application/x-www-form-urlencoded',\n]\n\n// Maximum payload size for GET requests (1MB)\nconst MAX_PAYLOAD_SIZE = 1_000_000\n\nexport const handleServerAction = async ({\n request,\n context,\n serverFnId,\n}: {\n request: Request\n context: any\n serverFnId: string\n}) => {\n const method = request.method\n const methodUpper = method.toUpperCase()\n const url = new URL(request.url)\n\n const action = await getServerFnById(serverFnId, { fromClient: true })\n\n // Early method check: reject mismatched HTTP methods before parsing\n // the request payload (FormData, JSON, query string, etc.)\n if (action.method && methodUpper !== action.method) {\n return new Response(\n `expected ${action.method} method. Got ${methodUpper}`,\n {\n status: 405,\n headers: {\n Allow: action.method,\n },\n },\n )\n }\n\n const isServerFn = request.headers.get('x-tsr-serverFn') === 'true'\n\n // Initialize serovalPlugins lazily (cached at module level)\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n\n const contentType = request.headers.get('Content-Type')\n\n function parsePayload(payload: any) {\n const parsedPayload = fromJSON(payload, { plugins: serovalPlugins })\n return parsedPayload as any\n }\n\n const response = await (async () => {\n try {\n let res = await (async () => {\n // FormData\n if (\n FORM_DATA_CONTENT_TYPES.some(\n (type) => contentType && contentType.includes(type),\n )\n ) {\n // We don't support GET requests with FormData payloads... that seems impossible\n if (methodUpper === 'GET') {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: GET requests with FormData payloads are not supported',\n )\n }\n\n invariant()\n }\n const formData = await request.formData()\n const serializedContext = formData.get(TSS_FORMDATA_CONTEXT)\n formData.delete(TSS_FORMDATA_CONTEXT)\n\n const params = {\n context,\n data: formData,\n method: methodUpper,\n }\n if (typeof serializedContext === 'string') {\n try {\n const parsedContext = JSON.parse(serializedContext)\n const deserializedContext = fromJSON(parsedContext, {\n plugins: serovalPlugins,\n })\n if (\n typeof deserializedContext === 'object' &&\n deserializedContext\n ) {\n params.context = safeObjectMerge(\n deserializedContext as Record<string, unknown>,\n context,\n )\n }\n } catch (e) {\n // Log warning for debugging but don't expose to client\n if (process.env.NODE_ENV === 'development') {\n console.warn('Failed to parse FormData context:', e)\n }\n }\n }\n\n return await action(params)\n }\n\n // Get requests use the query string\n if (methodUpper === 'GET') {\n // Get payload directly from searchParams\n const payloadParam = url.searchParams.get('payload')\n // Reject oversized payloads to prevent DoS\n if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {\n throw new Error('Payload too large')\n }\n // If there's a payload, we should try to parse it\n const payload: any = payloadParam\n ? parsePayload(JSON.parse(payloadParam))\n : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n // Send it through!\n return await action(payload)\n }\n\n let jsonPayload\n if (contentType?.includes('application/json')) {\n jsonPayload = await request.json()\n }\n\n const payload = jsonPayload ? parsePayload(jsonPayload) : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n return await action(payload)\n })()\n\n const unwrapped = res.result || res.error\n\n if (isNotFound(res)) {\n res = isNotFoundResponse(res)\n }\n\n if (!isServerFn) {\n return unwrapped\n }\n\n if (unwrapped instanceof Response) {\n if (isRedirect(unwrapped)) {\n return unwrapped\n }\n unwrapped.headers.set(X_TSS_RAW_RESPONSE, 'true')\n return unwrapped\n }\n\n return serializeResult(res)\n\n function serializeResult(res: unknown): Response {\n let nonStreamingBody: any = undefined\n\n const alsResponse = getResponse()\n if (res !== undefined) {\n // Collect raw streams encountered during serialization\n const rawStreams = new Map<number, ReadableStream<Uint8Array>>()\n const rawStreamPlugin = createRawStreamRPCPlugin(\n (id: number, stream: ReadableStream<Uint8Array>) => {\n rawStreams.set(id, stream)\n },\n )\n\n // Build plugins with RawStreamRPCPlugin first (before default SSR plugin)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n // first run without the stream in case `result` does not need streaming\n let done = false as boolean\n const callbacks: {\n onParse: (value: any) => void\n onDone: () => void\n onError: (error: any) => void\n } = {\n onParse: (value) => {\n nonStreamingBody = value\n },\n onDone: () => {\n done = true\n },\n onError: (error) => {\n throw error\n },\n }\n toCrossJSONStream(res, {\n refs: new Map(),\n plugins,\n onParse(value) {\n callbacks.onParse(value)\n },\n onDone() {\n callbacks.onDone()\n },\n onError: (error) => {\n callbacks.onError(error)\n },\n })\n\n // If no raw streams and done synchronously, return simple JSON\n if (done && rawStreams.size === 0) {\n return new Response(\n nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,\n {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n },\n )\n }\n\n // If we have raw streams, use framed protocol\n if (rawStreams.size > 0) {\n // Create a stream of JSON chunks (NDJSON style)\n const jsonStream = new ReadableStream<string>({\n start(controller) {\n callbacks.onParse = (value) => {\n controller.enqueue(JSON.stringify(value) + '\\n')\n }\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch {\n // Already closed\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // Emit initial body if we have one\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n\n // Create multiplexed stream with JSON and raw streams\n const multiplexedStream = createMultiplexedStream(\n jsonStream,\n rawStreams,\n )\n\n return new Response(multiplexedStream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n // No raw streams but not done yet - use standard NDJSON streaming\n const stream = new ReadableStream({\n start(controller) {\n callbacks.onParse = (value) =>\n controller.enqueue(\n textEncoder.encode(JSON.stringify(value) + '\\n'),\n )\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch (error) {\n controller.error(error)\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // stream initial body\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n return new Response(stream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/x-ndjson',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n return new Response(undefined, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n })\n }\n } catch (error: any) {\n if (error instanceof Response) {\n return error\n }\n // else if (\n // isPlainObject(error) &&\n // 'result' in error &&\n // error.result instanceof Response\n // ) {\n // return error.result\n // }\n\n // Currently this server-side context has no idea how to\n // build final URLs, so we need to defer that to the client.\n // The client will check for __redirect and __notFound keys,\n // and if they exist, it will handle them appropriately.\n\n if (isNotFound(error)) {\n return isNotFoundResponse(error)\n }\n\n console.info()\n console.info('Server Fn Error!')\n console.info()\n console.error(error)\n console.info()\n\n const serializedError = JSON.stringify(\n await Promise.resolve(\n toCrossJSONAsync(error, {\n refs: new Map(),\n plugins: serovalPlugins,\n }),\n ),\n )\n const response = getResponse()\n return new Response(serializedError, {\n status: response.status ?? 500,\n statusText: response.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n })()\n\n return response\n}\n\nfunction isNotFoundResponse(error: any) {\n const { headers, ...rest } = error\n\n return new Response(JSON.stringify(rest), {\n status: 404,\n headers: {\n 'Content-Type': 'application/json',\n ...(headers || {}),\n },\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-functions-handler.js","names":[],"sources":["../../src/server-functions-handler.ts"],"sourcesContent":["import {\n createRawStreamRPCPlugin,\n invariant,\n isNotFound,\n isRedirect,\n} from '@tanstack/router-core'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n getDefaultSerovalPlugins,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport { fromJSON, toCrossJSONAsync, toCrossJSONStream } from 'seroval'\nimport { getResponse } from './request-response'\nimport { getServerFnById } from './getServerFnById'\nimport {\n TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n createMultiplexedStream,\n} from './frame-protocol'\nimport type { LateStreamRegistration } from './frame-protocol'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\n// Cache serovalPlugins at module level to avoid repeated calls\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | undefined = undefined\n\n// Known FormData 'Content-Type' header values - module-level constant\nconst FORM_DATA_CONTENT_TYPES = [\n 'multipart/form-data',\n 'application/x-www-form-urlencoded',\n]\n\n// Maximum payload size for GET requests (1MB)\nconst MAX_PAYLOAD_SIZE = 1_000_000\n\nexport const handleServerAction = async ({\n request,\n context,\n serverFnId,\n}: {\n request: Request\n context: any\n serverFnId: string\n}) => {\n const method = request.method\n const methodUpper = method.toUpperCase()\n const url = new URL(request.url)\n\n const action = await getServerFnById(serverFnId, { origin: 'client' })\n\n // Early method check: reject mismatched HTTP methods before parsing\n // the request payload (FormData, JSON, query string, etc.)\n if (action.method && methodUpper !== action.method) {\n return new Response(\n `expected ${action.method} method. Got ${methodUpper}`,\n {\n status: 405,\n headers: {\n Allow: action.method,\n },\n },\n )\n }\n\n const isServerFn = request.headers.get('x-tsr-serverFn') === 'true'\n\n // Initialize serovalPlugins lazily (cached at module level)\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n\n const contentType = request.headers.get('Content-Type')\n\n function parsePayload(payload: any) {\n const parsedPayload = fromJSON(payload, { plugins: serovalPlugins })\n return parsedPayload as any\n }\n\n const response = await (async () => {\n try {\n let res = await (async () => {\n // FormData\n if (\n FORM_DATA_CONTENT_TYPES.some(\n (type) => contentType && contentType.includes(type),\n )\n ) {\n // We don't support GET requests with FormData payloads... that seems impossible\n if (methodUpper === 'GET') {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: GET requests with FormData payloads are not supported',\n )\n }\n\n invariant()\n }\n const formData = await request.formData()\n const serializedContext = formData.get(TSS_FORMDATA_CONTEXT)\n formData.delete(TSS_FORMDATA_CONTEXT)\n\n const params = {\n context,\n data: formData,\n method: methodUpper,\n }\n if (typeof serializedContext === 'string') {\n try {\n const parsedContext = JSON.parse(serializedContext)\n const deserializedContext = fromJSON(parsedContext, {\n plugins: serovalPlugins,\n })\n if (\n typeof deserializedContext === 'object' &&\n deserializedContext\n ) {\n params.context = safeObjectMerge(\n deserializedContext as Record<string, unknown>,\n context,\n )\n }\n } catch (e) {\n // Log warning for debugging but don't expose to client\n if (process.env.NODE_ENV === 'development') {\n console.warn('Failed to parse FormData context:', e)\n }\n }\n }\n\n return await action(params)\n }\n\n // Get requests use the query string\n if (methodUpper === 'GET') {\n // Get payload directly from searchParams\n const payloadParam = url.searchParams.get('payload')\n // Reject oversized payloads to prevent DoS\n if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {\n throw new Error('Payload too large')\n }\n // If there's a payload, we should try to parse it\n const payload: any = payloadParam\n ? parsePayload(JSON.parse(payloadParam))\n : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n // Send it through!\n return await action(payload)\n }\n\n let jsonPayload\n if (contentType?.includes('application/json')) {\n jsonPayload = await request.json()\n }\n\n const payload = jsonPayload ? parsePayload(jsonPayload) : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n return await action(payload)\n })()\n\n const unwrapped = res.result || res.error\n\n if (isNotFound(res)) {\n res = isNotFoundResponse(res)\n }\n\n if (!isServerFn) {\n return unwrapped\n }\n\n if (unwrapped instanceof Response) {\n if (isRedirect(unwrapped)) {\n return unwrapped\n }\n unwrapped.headers.set(X_TSS_RAW_RESPONSE, 'true')\n return unwrapped\n }\n\n return serializeResult(res)\n\n function serializeResult(res: unknown): Response {\n let nonStreamingBody: any = undefined\n\n const alsResponse = getResponse()\n if (res !== undefined) {\n // Collect raw streams encountered during initial synchronous serialization\n const rawStreams = new Map<number, ReadableStream<Uint8Array>>()\n\n // Track whether we're still in the initial synchronous phase\n // After initial phase, new RawStreams go to lateStreamWriter\n let initialPhase = true\n\n // Late stream registration for RawStreams discovered after initial pass\n // (e.g., from resolved Promises)\n let lateStreamWriter:\n | WritableStreamDefaultWriter<LateStreamRegistration>\n | undefined\n let lateStreamReadable:\n | ReadableStream<LateStreamRegistration>\n | undefined = undefined\n const pendingLateStreams: Array<LateStreamRegistration> = []\n\n const rawStreamPlugin = createRawStreamRPCPlugin(\n (id: number, stream: ReadableStream<Uint8Array>) => {\n if (initialPhase) {\n rawStreams.set(id, stream)\n return\n }\n\n if (lateStreamWriter) {\n // Late stream - write to the late stream channel\n lateStreamWriter.write({ id, stream }).catch(() => {\n // Ignore write errors - stream may be closed\n })\n return\n }\n\n // Discovered after initial phase but before writer exists.\n pendingLateStreams.push({ id, stream })\n },\n )\n\n // Build plugins with RawStreamRPCPlugin first (before default SSR plugin)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n // first run without the stream in case `result` does not need streaming\n let done = false as boolean\n const callbacks: {\n onParse: (value: any) => void\n onDone: () => void\n onError: (error: any) => void\n } = {\n onParse: (value) => {\n nonStreamingBody = value\n },\n onDone: () => {\n done = true\n },\n onError: (error) => {\n throw error\n },\n }\n toCrossJSONStream(res, {\n refs: new Map(),\n plugins,\n onParse(value) {\n callbacks.onParse(value)\n },\n onDone() {\n callbacks.onDone()\n },\n onError: (error) => {\n callbacks.onError(error)\n },\n })\n\n // End of initial synchronous phase - any new RawStreams are \"late\"\n initialPhase = false\n\n // If any RawStreams are discovered after this point but before the\n // late-stream writer exists, we buffer them and flush once the writer\n // is ready. This avoids an occasional missed-stream race.\n\n // If no raw streams and done synchronously, return simple JSON\n if (done && rawStreams.size === 0) {\n return new Response(\n nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,\n {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n },\n )\n }\n\n // Not done synchronously or has raw streams - use framed protocol\n // This supports late RawStreams from resolved Promises\n const { readable, writable } =\n new TransformStream<LateStreamRegistration>()\n lateStreamReadable = readable\n lateStreamWriter = writable.getWriter()\n\n // Flush any late streams that were discovered in the small window\n // between end of initial serialization and writer setup.\n for (const registration of pendingLateStreams) {\n lateStreamWriter.write(registration).catch(() => {\n // Ignore write errors - stream may be closed\n })\n }\n pendingLateStreams.length = 0\n\n // Create a stream of JSON chunks\n const jsonStream = new ReadableStream<string>({\n start(controller) {\n callbacks.onParse = (value) => {\n controller.enqueue(JSON.stringify(value) + '\\n')\n }\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch {\n // Already closed\n }\n // Close late stream writer when JSON serialization is done\n // Any RawStreams not yet discovered won't be sent\n lateStreamWriter\n ?.close()\n .catch(() => {\n // Ignore close errors\n })\n .finally(() => {\n lateStreamWriter = undefined\n })\n }\n\n callbacks.onError = (error) => {\n controller.error(error)\n lateStreamWriter\n ?.abort(error)\n .catch(() => {\n // Ignore abort errors\n })\n .finally(() => {\n lateStreamWriter = undefined\n })\n }\n\n // Emit initial body if we have one\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n // If serialization already completed synchronously, close now\n // This handles the case where onDone was called during toCrossJSONStream\n // before we overwrote callbacks.onDone\n if (done) {\n callbacks.onDone()\n }\n },\n cancel() {\n lateStreamWriter?.abort().catch(() => {})\n lateStreamWriter = undefined\n },\n })\n\n // Create multiplexed stream with JSON, initial raw streams, and late streams\n const multiplexedStream = createMultiplexedStream(\n jsonStream,\n rawStreams,\n lateStreamReadable,\n )\n\n return new Response(multiplexedStream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n return new Response(undefined, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n })\n }\n } catch (error: any) {\n if (error instanceof Response) {\n return error\n }\n // else if (\n // isPlainObject(error) &&\n // 'result' in error &&\n // error.result instanceof Response\n // ) {\n // return error.result\n // }\n\n // Currently this server-side context has no idea how to\n // build final URLs, so we need to defer that to the client.\n // The client will check for __redirect and __notFound keys,\n // and if they exist, it will handle them appropriately.\n\n if (isNotFound(error)) {\n return isNotFoundResponse(error)\n }\n\n console.info()\n console.info('Server Fn Error!')\n console.info()\n console.error(error)\n console.info()\n\n const serializedError = JSON.stringify(\n await Promise.resolve(\n toCrossJSONAsync(error, {\n refs: new Map(),\n plugins: serovalPlugins,\n }),\n ),\n )\n const response = getResponse()\n return new Response(serializedError, {\n status: response.status ?? 500,\n statusText: response.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n })()\n\n return response\n}\n\nfunction isNotFoundResponse(error: any) {\n const { headers, ...rest } = error\n\n return new Response(JSON.stringify(rest), {\n status: 404,\n headers: {\n 'Content-Type': 'application/json',\n ...(headers || {}),\n },\n })\n}\n"],"mappings":";;;;;;;AAwBA,IAAI,iBAA6D,KAAA;AAGjE,IAAM,0BAA0B,CAC9B,uBACA,oCACD;AAGD,IAAM,mBAAmB;AAEzB,IAAa,qBAAqB,OAAO,EACvC,SACA,SACA,iBAKI;CAEJ,MAAM,cADS,QAAQ,OACI,aAAa;CACxC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;CAEhC,MAAM,SAAS,MAAM,gBAAgB,YAAY,EAAE,QAAQ,UAAU,CAAC;AAItE,KAAI,OAAO,UAAU,gBAAgB,OAAO,OAC1C,QAAO,IAAI,SACT,YAAY,OAAO,OAAO,eAAe,eACzC;EACE,QAAQ;EACR,SAAS,EACP,OAAO,OAAO,QACf;EACF,CACF;CAGH,MAAM,aAAa,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAG7D,KAAI,CAAC,eACH,kBAAiB,0BAA0B;CAG7C,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe;CAEvD,SAAS,aAAa,SAAc;AAElC,SADsB,SAAS,SAAS,EAAE,SAAS,gBAAgB,CAAC;;AAuVtE,QAnViB,OAAO,YAAY;AAClC,MAAI;GACF,IAAI,MAAM,OAAO,YAAY;AAE3B,QACE,wBAAwB,MACrB,SAAS,eAAe,YAAY,SAAS,KAAK,CACpD,EACD;AAEA,SAAI,gBAAgB,OAAO;AACzB,UAAA,QAAA,IAAA,aAA6B,aAC3B,OAAM,IAAI,MACR,0EACD;AAGH,iBAAW;;KAEb,MAAM,WAAW,MAAM,QAAQ,UAAU;KACzC,MAAM,oBAAoB,SAAS,IAAI,qBAAqB;AAC5D,cAAS,OAAO,qBAAqB;KAErC,MAAM,SAAS;MACb;MACA,MAAM;MACN,QAAQ;MACT;AACD,SAAI,OAAO,sBAAsB,SAC/B,KAAI;MAEF,MAAM,sBAAsB,SADN,KAAK,MAAM,kBAAkB,EACC,EAClD,SAAS,gBACV,CAAC;AACF,UACE,OAAO,wBAAwB,YAC/B,oBAEA,QAAO,UAAU,gBACf,qBACA,QACD;cAEI,GAAG;AAEV,UAAA,QAAA,IAAA,aAA6B,cAC3B,SAAQ,KAAK,qCAAqC,EAAE;;AAK1D,YAAO,MAAM,OAAO,OAAO;;AAI7B,QAAI,gBAAgB,OAAO;KAEzB,MAAM,eAAe,IAAI,aAAa,IAAI,UAAU;AAEpD,SAAI,gBAAgB,aAAa,SAAS,iBACxC,OAAM,IAAI,MAAM,oBAAoB;KAGtC,MAAM,UAAe,eACjB,aAAa,KAAK,MAAM,aAAa,CAAC,GACtC,EAAE;AACN,aAAQ,UAAU,gBAAgB,QAAQ,SAAS,QAAQ;AAC3D,aAAQ,SAAS;AAEjB,YAAO,MAAM,OAAO,QAAQ;;IAG9B,IAAI;AACJ,QAAI,aAAa,SAAS,mBAAmB,CAC3C,eAAc,MAAM,QAAQ,MAAM;IAGpC,MAAM,UAAU,cAAc,aAAa,YAAY,GAAG,EAAE;AAC5D,YAAQ,UAAU,gBAAgB,QAAQ,SAAS,QAAQ;AAC3D,YAAQ,SAAS;AACjB,WAAO,MAAM,OAAO,QAAQ;OAC1B;GAEJ,MAAM,YAAY,IAAI,UAAU,IAAI;AAEpC,OAAI,WAAW,IAAI,CACjB,OAAM,mBAAmB,IAAI;AAG/B,OAAI,CAAC,WACH,QAAO;AAGT,OAAI,qBAAqB,UAAU;AACjC,QAAI,WAAW,UAAU,CACvB,QAAO;AAET,cAAU,QAAQ,IAAI,oBAAoB,OAAO;AACjD,WAAO;;AAGT,UAAO,gBAAgB,IAAI;GAE3B,SAAS,gBAAgB,KAAwB;IAC/C,IAAI,mBAAwB,KAAA;IAE5B,MAAM,cAAc,aAAa;AACjC,QAAI,QAAQ,KAAA,GAAW;KAErB,MAAM,6BAAa,IAAI,KAAyC;KAIhE,IAAI,eAAe;KAInB,IAAI;KAGJ,IAAI,qBAEY,KAAA;KAChB,MAAM,qBAAoD,EAAE;KAuB5D,MAAM,UAAU,CArBQ,0BACrB,IAAY,WAAuC;AAClD,UAAI,cAAc;AAChB,kBAAW,IAAI,IAAI,OAAO;AAC1B;;AAGF,UAAI,kBAAkB;AAEpB,wBAAiB,MAAM;QAAE;QAAI;QAAQ,CAAC,CAAC,YAAY,GAEjD;AACF;;AAIF,yBAAmB,KAAK;OAAE;OAAI;OAAQ,CAAC;OAE1C,EAGiC,GAAI,kBAAkB,EAAE,CAAE;KAG5D,IAAI,OAAO;KACX,MAAM,YAIF;MACF,UAAU,UAAU;AAClB,0BAAmB;;MAErB,cAAc;AACZ,cAAO;;MAET,UAAU,UAAU;AAClB,aAAM;;MAET;AACD,uBAAkB,KAAK;MACrB,sBAAM,IAAI,KAAK;MACf;MACA,QAAQ,OAAO;AACb,iBAAU,QAAQ,MAAM;;MAE1B,SAAS;AACP,iBAAU,QAAQ;;MAEpB,UAAU,UAAU;AAClB,iBAAU,QAAQ,MAAM;;MAE3B,CAAC;AAGF,oBAAe;AAOf,SAAI,QAAQ,WAAW,SAAS,EAC9B,QAAO,IAAI,SACT,mBAAmB,KAAK,UAAU,iBAAiB,GAAG,KAAA,GACtD;MACE,QAAQ,YAAY;MACpB,YAAY,YAAY;MACxB,SAAS;OACP,gBAAgB;QACf,mBAAmB;OACrB;MACF,CACF;KAKH,MAAM,EAAE,UAAU,aAChB,IAAI,iBAAyC;AAC/C,0BAAqB;AACrB,wBAAmB,SAAS,WAAW;AAIvC,UAAK,MAAM,gBAAgB,mBACzB,kBAAiB,MAAM,aAAa,CAAC,YAAY,GAE/C;AAEJ,wBAAmB,SAAS;KAwD5B,MAAM,oBAAoB,wBArDP,IAAI,eAAuB;MAC5C,MAAM,YAAY;AAChB,iBAAU,WAAW,UAAU;AAC7B,mBAAW,QAAQ,KAAK,UAAU,MAAM,GAAG,KAAK;;AAElD,iBAAU,eAAe;AACvB,YAAI;AACF,oBAAW,OAAO;gBACZ;AAKR,0BACI,OAAO,CACR,YAAY,GAEX,CACD,cAAc;AACb,4BAAmB,KAAA;UACnB;;AAGN,iBAAU,WAAW,UAAU;AAC7B,mBAAW,MAAM,MAAM;AACvB,0BACI,MAAM,MAAM,CACb,YAAY,GAEX,CACD,cAAc;AACb,4BAAmB,KAAA;UACnB;;AAIN,WAAI,qBAAqB,KAAA,EACvB,WAAU,QAAQ,iBAAiB;AAKrC,WAAI,KACF,WAAU,QAAQ;;MAGtB,SAAS;AACP,yBAAkB,OAAO,CAAC,YAAY,GAAG;AACzC,0BAAmB,KAAA;;MAEtB,CAAC,EAKA,YACA,mBACD;AAED,YAAO,IAAI,SAAS,mBAAmB;MACrC,QAAQ,YAAY;MACpB,YAAY,YAAY;MACxB,SAAS;OACP,gBAAgB;QACf,mBAAmB;OACrB;MACF,CAAC;;AAGJ,WAAO,IAAI,SAAS,KAAA,GAAW;KAC7B,QAAQ,YAAY;KACpB,YAAY,YAAY;KACzB,CAAC;;WAEG,OAAY;AACnB,OAAI,iBAAiB,SACnB,QAAO;AAeT,OAAI,WAAW,MAAM,CACnB,QAAO,mBAAmB,MAAM;AAGlC,WAAQ,MAAM;AACd,WAAQ,KAAK,mBAAmB;AAChC,WAAQ,MAAM;AACd,WAAQ,MAAM,MAAM;AACpB,WAAQ,MAAM;GAEd,MAAM,kBAAkB,KAAK,UAC3B,MAAM,QAAQ,QACZ,iBAAiB,OAAO;IACtB,sBAAM,IAAI,KAAK;IACf,SAAS;IACV,CAAC,CACH,CACF;GACD,MAAM,WAAW,aAAa;AAC9B,UAAO,IAAI,SAAS,iBAAiB;IACnC,QAAQ,SAAS,UAAU;IAC3B,YAAY,SAAS;IACrB,SAAS;KACP,gBAAgB;MACf,mBAAmB;KACrB;IACF,CAAC;;KAEF;;AAKN,SAAS,mBAAmB,OAAY;CACtC,MAAM,EAAE,SAAS,GAAG,SAAS;AAE7B,QAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;EACxC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,GAAI,WAAW,EAAE;GAClB;EACF,CAAC"}
|
|
@@ -132,11 +132,9 @@ async function transformManifestAssets(source, transformFn, _opts) {
|
|
|
132
132
|
url: source.clientEntry,
|
|
133
133
|
kind: "clientEntry"
|
|
134
134
|
}));
|
|
135
|
-
const rootRoute = manifest.routes[rootRouteId];
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
rootRoute.assets.push(buildClientEntryScriptTag(transformedClientEntry.href, source.injectedHeadScripts));
|
|
139
|
-
}
|
|
135
|
+
const rootRoute = manifest.routes[rootRouteId] = manifest.routes[rootRouteId] || {};
|
|
136
|
+
rootRoute.assets = rootRoute.assets || [];
|
|
137
|
+
rootRoute.assets.push(buildClientEntryScriptTag(transformedClientEntry.href, source.injectedHeadScripts));
|
|
140
138
|
return manifest;
|
|
141
139
|
}
|
|
142
140
|
/**
|
|
@@ -150,10 +148,10 @@ function buildManifestWithClientEntry(source) {
|
|
|
150
148
|
const baseRootRoute = source.manifest.routes[rootRouteId];
|
|
151
149
|
return { routes: {
|
|
152
150
|
...source.manifest.routes,
|
|
153
|
-
|
|
151
|
+
[rootRouteId]: {
|
|
154
152
|
...baseRootRoute,
|
|
155
|
-
assets: [...baseRootRoute
|
|
156
|
-
}
|
|
153
|
+
assets: [...baseRootRoute?.assets || [], scriptTag]
|
|
154
|
+
}
|
|
157
155
|
} };
|
|
158
156
|
}
|
|
159
157
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformAssetUrls.js","names":[],"sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import { resolveManifestAssetLink, rootRouteId } from '@tanstack/router-core'\n\nimport type {\n AssetCrossOrigin,\n Awaitable,\n Manifest,\n ManifestAssetLink,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type { AssetCrossOrigin }\n\nexport type TransformAssetKind = 'modulepreload' | 'stylesheet' | 'clientEntry'\n\ntype TransformAssetsShorthandCrossOriginKind = Exclude<\n TransformAssetKind,\n 'clientEntry'\n>\n\nexport type AssetUrlType = TransformAssetKind\n\nexport interface TransformAssetsContext {\n url: string\n kind: TransformAssetKind\n}\n\nexport type TransformAssetResult =\n | string\n | {\n href: string\n crossOrigin?: AssetCrossOrigin\n }\n\nexport type TransformAssetsFn = (\n context: TransformAssetsContext,\n) => Awaitable<TransformAssetResult>\n\nexport interface TransformAssetUrlsContext {\n url: string\n type: AssetUrlType\n}\n\nexport type TransformAssetUrlsFn = (\n context: TransformAssetUrlsContext,\n) => Awaitable<string>\n\nexport type CreateTransformAssetUrlsContext =\n | {\n /** True when the server is computing the cached manifest during startup warmup. */\n warmup: true\n }\n | {\n /**\n * The current Request.\n *\n * Only available during request handling (i.e. when `warmup: false`).\n */\n request: Request\n /** False when transforming URLs as part of request handling. */\n warmup: false\n }\n\n/**\n * Async factory that runs once per manifest computation and returns the\n * per-asset transform.\n */\nexport type CreateTransformAssetUrlsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetUrlsFn>\n\nexport type CreateTransformAssetsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetsFn>\n\ntype TransformAssetUrlsOptionsBase = {\n /**\n * Whether to cache the transformed manifest after the first request.\n *\n * When `true` (default), the transform runs once on the first request and\n * the resulting manifest is reused for all subsequent requests in production.\n *\n * Set to `false` for per-request transforms (e.g. geo-routing to different\n * CDNs based on request headers).\n *\n * @default true\n */\n cache?: boolean\n\n /**\n * When `true`, warms up the cached transformed manifest in the background when\n * the server starts (production only).\n *\n * This can reduce latency for the first request when `cache` is `true`.\n * Has no effect when `cache: false` (per-request transforms) or in dev mode.\n *\n * @default false\n */\n warmup?: boolean\n}\n\nexport type TransformAssetUrlsOptions =\n | (TransformAssetUrlsOptionsBase & {\n /**\n * The transform to apply to asset URLs. Can be a string prefix or a callback.\n *\n * **String** — prepended to every asset URL.\n * **Callback** — receives `{ url, type }` and returns a new URL.\n */\n transform: string | TransformAssetUrlsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n /**\n * Create a per-asset transform function.\n *\n * This factory runs once per manifest computation (per request when\n * `cache: false`, or once per server when `cache: true`). It can do async\n * setup work (fetch config, read from a KV, etc.) and return a fast\n * per-asset transformer.\n */\n createTransform: CreateTransformAssetUrlsFn\n transform?: never\n })\n\nexport type TransformAssetsOptions =\n | (TransformAssetUrlsOptionsBase & {\n transform: string | TransformAssetsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n createTransform: CreateTransformAssetsFn\n transform?: never\n })\n\nexport type TransformAssetUrls =\n | string\n | TransformAssetUrlsFn\n | TransformAssetUrlsOptions\n\n/**\n * Per-kind crossOrigin configuration for the object shorthand.\n *\n * Accepts either a single value applied to all asset kinds, or a per-kind\n * record (matching `HeadContent`'s `assetCrossOrigin` shape):\n *\n * ```ts\n * // All assets get the same value\n * crossOrigin: 'anonymous'\n *\n * // Different values per kind\n * crossOrigin: { modulepreload: 'anonymous', stylesheet: 'use-credentials' }\n * ```\n */\nexport type TransformAssetsCrossOriginConfig =\n | AssetCrossOrigin\n | Partial<Record<TransformAssetsShorthandCrossOriginKind, AssetCrossOrigin>>\n\n/**\n * Object shorthand for `transformAssets`. Combines a URL prefix with optional\n * per-asset `crossOrigin` without needing a callback:\n *\n * ```ts\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: 'anonymous',\n * }\n * ```\n */\nexport interface TransformAssetsObjectShorthand {\n /** URL prefix prepended to every asset URL. */\n prefix: string\n /**\n * Optional crossOrigin attribute applied to manifest-managed `<link>` assets.\n *\n * Accepts a single value or a per-kind record.\n */\n crossOrigin?: TransformAssetsCrossOriginConfig\n}\n\nexport type TransformAssets =\n | string\n | TransformAssetsFn\n | TransformAssetsObjectShorthand\n | TransformAssetsOptions\n\nexport type ResolvedTransformAssetsConfig =\n | {\n type: 'transform'\n transformFn: TransformAssetsFn\n cache: boolean\n }\n | {\n type: 'createTransform'\n createTransform: CreateTransformAssetsFn\n cache: boolean\n }\n\nlet hasWarnedAboutDeprecatedTransformAssetUrls = false\n\nexport function warnDeprecatedTransformAssetUrls() {\n if (\n (process.env.NODE_ENV === 'development' ||\n process.env.TSS_DEV_SERVER === 'true') &&\n !hasWarnedAboutDeprecatedTransformAssetUrls\n ) {\n hasWarnedAboutDeprecatedTransformAssetUrls = true\n console.warn(\n '[TanStack Start] `transformAssetUrls` is deprecated. Use `transformAssets` instead.',\n )\n }\n}\n\nfunction normalizeTransformAssetResult(\n result: TransformAssetResult,\n): Exclude<TransformAssetResult, string> {\n if (typeof result === 'string') {\n return { href: result }\n }\n\n return result\n}\n\nfunction resolveTransformAssetsCrossOrigin(\n config: TransformAssetsCrossOriginConfig | undefined,\n kind: TransformAssetsShorthandCrossOriginKind,\n): AssetCrossOrigin | undefined {\n if (!config) return undefined\n if (typeof config === 'string') return config\n\n return config[kind]\n}\n\nfunction isObjectShorthand(\n transform: TransformAssetsObjectShorthand | TransformAssetsOptions,\n): transform is TransformAssetsObjectShorthand {\n return 'prefix' in transform\n}\n\nexport function resolveTransformAssetsConfig(\n transform: TransformAssets,\n): ResolvedTransformAssetsConfig {\n if (typeof transform === 'string') {\n const prefix = transform\n return {\n type: 'transform',\n transformFn: ({ url }) => ({ href: `${prefix}${url}` }),\n cache: true,\n }\n }\n\n if (typeof transform === 'function') {\n return {\n type: 'transform',\n transformFn: transform,\n cache: true,\n }\n }\n\n // Object shorthand: { prefix, crossOrigin? }\n if (isObjectShorthand(transform)) {\n const { prefix, crossOrigin } = transform\n\n return {\n type: 'transform',\n transformFn: ({ url, kind }) => {\n const href = `${prefix}${url}`\n\n if (kind === 'clientEntry') {\n return { href }\n }\n\n const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind)\n return co ? { href, crossOrigin: co } : { href }\n },\n cache: true,\n }\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n type: 'createTransform',\n createTransform: transform.createTransform,\n cache: transform.cache !== false,\n }\n }\n\n const transformFn =\n typeof transform.transform === 'string'\n ? ((({ url }: TransformAssetsContext) => ({\n href: `${transform.transform}${url}`,\n })) as TransformAssetsFn)\n : transform.transform\n\n return {\n type: 'transform',\n transformFn,\n cache: transform.cache !== false,\n }\n}\n\nexport function adaptTransformAssetUrlsToTransformAssets(\n transformFn: TransformAssetUrlsFn,\n): TransformAssetsFn {\n return async ({ url, kind }) => ({\n href: await transformFn({ url, type: kind }),\n })\n}\n\nexport function adaptTransformAssetUrlsConfigToTransformAssets(\n transform: TransformAssetUrls,\n): TransformAssets {\n warnDeprecatedTransformAssetUrls()\n\n if (typeof transform === 'string') {\n return transform\n }\n\n if (typeof transform === 'function') {\n return adaptTransformAssetUrlsToTransformAssets(transform)\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n createTransform: async (ctx: CreateTransformAssetUrlsContext) =>\n adaptTransformAssetUrlsToTransformAssets(\n await transform.createTransform(ctx),\n ),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n }\n\n return {\n transform:\n typeof transform.transform === 'string'\n ? transform.transform\n : adaptTransformAssetUrlsToTransformAssets(transform.transform),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n}\n\nexport interface StartManifestWithClientEntry {\n manifest: Manifest\n clientEntry: string\n /** Script content prepended before the client entry import (dev only) */\n injectedHeadScripts?: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL and optional injected head scripts.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n injectedHeadScripts?: string,\n): RouterManagedTag {\n const clientEntryLiteral = JSON.stringify(clientEntry)\n let script = `import(${clientEntryLiteral})`\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts};${script}`\n }\n return {\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n }\n}\n\nfunction assignManifestAssetLink(\n link: ManifestAssetLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): ManifestAssetLink {\n if (typeof link === 'string') {\n return next.crossOrigin ? next : next.href\n }\n\n return next.crossOrigin ? next : { href: next.href }\n}\n\nexport async function transformManifestAssets(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetsFn,\n _opts?: {\n clone?: boolean\n },\n): Promise<Manifest> {\n const manifest = structuredClone(source.manifest)\n\n for (const route of Object.values(manifest.routes)) {\n if (route.preloads) {\n route.preloads = await Promise.all(\n route.preloads.map(async (link) => {\n const resolved = resolveManifestAssetLink(link)\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: resolved.href,\n kind: 'modulepreload',\n }),\n )\n\n return assignManifestAssetLink(link, {\n href: result.href,\n crossOrigin: result.crossOrigin,\n })\n }),\n )\n }\n\n if (route.assets) {\n for (const asset of route.assets) {\n if (asset.tag === 'link' && asset.attrs?.href) {\n const rel = asset.attrs.rel\n const relTokens = typeof rel === 'string' ? rel.split(/\\s+/) : []\n\n if (!relTokens.includes('stylesheet')) {\n continue\n }\n\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: asset.attrs.href,\n kind: 'stylesheet',\n }),\n )\n\n asset.attrs.href = result.href\n if (result.crossOrigin) {\n asset.attrs.crossOrigin = result.crossOrigin\n } else {\n delete asset.attrs.crossOrigin\n }\n }\n }\n }\n }\n\n const transformedClientEntry = normalizeTransformAssetResult(\n await transformFn({\n url: source.clientEntry,\n kind: 'clientEntry',\n }),\n )\n\n const rootRoute = manifest.routes[rootRouteId]\n if (rootRoute) {\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry.href,\n source.injectedHeadScripts,\n ),\n )\n }\n\n return manifest\n}\n\n/**\n * Builds a final Manifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssetUrls option is provided.\n *\n * Returns a new manifest object so the cached base manifest is never mutated.\n */\nexport function buildManifestWithClientEntry(\n source: StartManifestWithClientEntry,\n): Manifest {\n const scriptTag = buildClientEntryScriptTag(\n source.clientEntry,\n source.injectedHeadScripts,\n )\n\n const baseRootRoute = source.manifest.routes[rootRouteId]\n const routes = {\n ...source.manifest.routes,\n ...(baseRootRoute\n ? {\n [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute.assets || []), scriptTag],\n },\n }\n : {}),\n }\n\n return { routes }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transformAssetUrls.js","names":[],"sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import { resolveManifestAssetLink, rootRouteId } from '@tanstack/router-core'\n\nimport type {\n AssetCrossOrigin,\n Awaitable,\n Manifest,\n ManifestAssetLink,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type { AssetCrossOrigin }\n\nexport type TransformAssetKind = 'modulepreload' | 'stylesheet' | 'clientEntry'\n\ntype TransformAssetsShorthandCrossOriginKind = Exclude<\n TransformAssetKind,\n 'clientEntry'\n>\n\nexport type AssetUrlType = TransformAssetKind\n\nexport interface TransformAssetsContext {\n url: string\n kind: TransformAssetKind\n}\n\nexport type TransformAssetResult =\n | string\n | {\n href: string\n crossOrigin?: AssetCrossOrigin\n }\n\nexport type TransformAssetsFn = (\n context: TransformAssetsContext,\n) => Awaitable<TransformAssetResult>\n\nexport interface TransformAssetUrlsContext {\n url: string\n type: AssetUrlType\n}\n\nexport type TransformAssetUrlsFn = (\n context: TransformAssetUrlsContext,\n) => Awaitable<string>\n\nexport type CreateTransformAssetUrlsContext =\n | {\n /** True when the server is computing the cached manifest during startup warmup. */\n warmup: true\n }\n | {\n /**\n * The current Request.\n *\n * Only available during request handling (i.e. when `warmup: false`).\n */\n request: Request\n /** False when transforming URLs as part of request handling. */\n warmup: false\n }\n\n/**\n * Async factory that runs once per manifest computation and returns the\n * per-asset transform.\n */\nexport type CreateTransformAssetUrlsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetUrlsFn>\n\nexport type CreateTransformAssetsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetsFn>\n\ntype TransformAssetUrlsOptionsBase = {\n /**\n * Whether to cache the transformed manifest after the first request.\n *\n * When `true` (default), the transform runs once on the first request and\n * the resulting manifest is reused for all subsequent requests in production.\n *\n * Set to `false` for per-request transforms (e.g. geo-routing to different\n * CDNs based on request headers).\n *\n * @default true\n */\n cache?: boolean\n\n /**\n * When `true`, warms up the cached transformed manifest in the background when\n * the server starts (production only).\n *\n * This can reduce latency for the first request when `cache` is `true`.\n * Has no effect when `cache: false` (per-request transforms) or in dev mode.\n *\n * @default false\n */\n warmup?: boolean\n}\n\nexport type TransformAssetUrlsOptions =\n | (TransformAssetUrlsOptionsBase & {\n /**\n * The transform to apply to asset URLs. Can be a string prefix or a callback.\n *\n * **String** — prepended to every asset URL.\n * **Callback** — receives `{ url, type }` and returns a new URL.\n */\n transform: string | TransformAssetUrlsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n /**\n * Create a per-asset transform function.\n *\n * This factory runs once per manifest computation (per request when\n * `cache: false`, or once per server when `cache: true`). It can do async\n * setup work (fetch config, read from a KV, etc.) and return a fast\n * per-asset transformer.\n */\n createTransform: CreateTransformAssetUrlsFn\n transform?: never\n })\n\nexport type TransformAssetsOptions =\n | (TransformAssetUrlsOptionsBase & {\n transform: string | TransformAssetsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n createTransform: CreateTransformAssetsFn\n transform?: never\n })\n\nexport type TransformAssetUrls =\n | string\n | TransformAssetUrlsFn\n | TransformAssetUrlsOptions\n\n/**\n * Per-kind crossOrigin configuration for the object shorthand.\n *\n * Accepts either a single value applied to all asset kinds, or a per-kind\n * record (matching `HeadContent`'s `assetCrossOrigin` shape):\n *\n * ```ts\n * // All assets get the same value\n * crossOrigin: 'anonymous'\n *\n * // Different values per kind\n * crossOrigin: { modulepreload: 'anonymous', stylesheet: 'use-credentials' }\n * ```\n */\nexport type TransformAssetsCrossOriginConfig =\n | AssetCrossOrigin\n | Partial<Record<TransformAssetsShorthandCrossOriginKind, AssetCrossOrigin>>\n\n/**\n * Object shorthand for `transformAssets`. Combines a URL prefix with optional\n * per-asset `crossOrigin` without needing a callback:\n *\n * ```ts\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: 'anonymous',\n * }\n * ```\n */\nexport interface TransformAssetsObjectShorthand {\n /** URL prefix prepended to every asset URL. */\n prefix: string\n /**\n * Optional crossOrigin attribute applied to manifest-managed `<link>` assets.\n *\n * Accepts a single value or a per-kind record.\n */\n crossOrigin?: TransformAssetsCrossOriginConfig\n}\n\nexport type TransformAssets =\n | string\n | TransformAssetsFn\n | TransformAssetsObjectShorthand\n | TransformAssetsOptions\n\nexport type ResolvedTransformAssetsConfig =\n | {\n type: 'transform'\n transformFn: TransformAssetsFn\n cache: boolean\n }\n | {\n type: 'createTransform'\n createTransform: CreateTransformAssetsFn\n cache: boolean\n }\n\nlet hasWarnedAboutDeprecatedTransformAssetUrls = false\n\nexport function warnDeprecatedTransformAssetUrls() {\n if (\n (process.env.NODE_ENV === 'development' ||\n process.env.TSS_DEV_SERVER === 'true') &&\n !hasWarnedAboutDeprecatedTransformAssetUrls\n ) {\n hasWarnedAboutDeprecatedTransformAssetUrls = true\n console.warn(\n '[TanStack Start] `transformAssetUrls` is deprecated. Use `transformAssets` instead.',\n )\n }\n}\n\nfunction normalizeTransformAssetResult(\n result: TransformAssetResult,\n): Exclude<TransformAssetResult, string> {\n if (typeof result === 'string') {\n return { href: result }\n }\n\n return result\n}\n\nfunction resolveTransformAssetsCrossOrigin(\n config: TransformAssetsCrossOriginConfig | undefined,\n kind: TransformAssetsShorthandCrossOriginKind,\n): AssetCrossOrigin | undefined {\n if (!config) return undefined\n if (typeof config === 'string') return config\n\n return config[kind]\n}\n\nfunction isObjectShorthand(\n transform: TransformAssetsObjectShorthand | TransformAssetsOptions,\n): transform is TransformAssetsObjectShorthand {\n return 'prefix' in transform\n}\n\nexport function resolveTransformAssetsConfig(\n transform: TransformAssets,\n): ResolvedTransformAssetsConfig {\n if (typeof transform === 'string') {\n const prefix = transform\n return {\n type: 'transform',\n transformFn: ({ url }) => ({ href: `${prefix}${url}` }),\n cache: true,\n }\n }\n\n if (typeof transform === 'function') {\n return {\n type: 'transform',\n transformFn: transform,\n cache: true,\n }\n }\n\n // Object shorthand: { prefix, crossOrigin? }\n if (isObjectShorthand(transform)) {\n const { prefix, crossOrigin } = transform\n\n return {\n type: 'transform',\n transformFn: ({ url, kind }) => {\n const href = `${prefix}${url}`\n\n if (kind === 'clientEntry') {\n return { href }\n }\n\n const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind)\n return co ? { href, crossOrigin: co } : { href }\n },\n cache: true,\n }\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n type: 'createTransform',\n createTransform: transform.createTransform,\n cache: transform.cache !== false,\n }\n }\n\n const transformFn =\n typeof transform.transform === 'string'\n ? ((({ url }: TransformAssetsContext) => ({\n href: `${transform.transform}${url}`,\n })) as TransformAssetsFn)\n : transform.transform\n\n return {\n type: 'transform',\n transformFn,\n cache: transform.cache !== false,\n }\n}\n\nexport function adaptTransformAssetUrlsToTransformAssets(\n transformFn: TransformAssetUrlsFn,\n): TransformAssetsFn {\n return async ({ url, kind }) => ({\n href: await transformFn({ url, type: kind }),\n })\n}\n\nexport function adaptTransformAssetUrlsConfigToTransformAssets(\n transform: TransformAssetUrls,\n): TransformAssets {\n warnDeprecatedTransformAssetUrls()\n\n if (typeof transform === 'string') {\n return transform\n }\n\n if (typeof transform === 'function') {\n return adaptTransformAssetUrlsToTransformAssets(transform)\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n createTransform: async (ctx: CreateTransformAssetUrlsContext) =>\n adaptTransformAssetUrlsToTransformAssets(\n await transform.createTransform(ctx),\n ),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n }\n\n return {\n transform:\n typeof transform.transform === 'string'\n ? transform.transform\n : adaptTransformAssetUrlsToTransformAssets(transform.transform),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n}\n\nexport interface StartManifestWithClientEntry {\n manifest: Manifest\n clientEntry: string\n /** Script content prepended before the client entry import (dev only) */\n injectedHeadScripts?: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL and optional injected head scripts.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n injectedHeadScripts?: string,\n): RouterManagedTag {\n const clientEntryLiteral = JSON.stringify(clientEntry)\n let script = `import(${clientEntryLiteral})`\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts};${script}`\n }\n return {\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n }\n}\n\nfunction assignManifestAssetLink(\n link: ManifestAssetLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): ManifestAssetLink {\n if (typeof link === 'string') {\n return next.crossOrigin ? next : next.href\n }\n\n return next.crossOrigin ? next : { href: next.href }\n}\n\nexport async function transformManifestAssets(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetsFn,\n _opts?: {\n clone?: boolean\n },\n): Promise<Manifest> {\n const manifest = structuredClone(source.manifest)\n\n for (const route of Object.values(manifest.routes)) {\n if (route.preloads) {\n route.preloads = await Promise.all(\n route.preloads.map(async (link) => {\n const resolved = resolveManifestAssetLink(link)\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: resolved.href,\n kind: 'modulepreload',\n }),\n )\n\n return assignManifestAssetLink(link, {\n href: result.href,\n crossOrigin: result.crossOrigin,\n })\n }),\n )\n }\n\n if (route.assets) {\n for (const asset of route.assets) {\n if (asset.tag === 'link' && asset.attrs?.href) {\n const rel = asset.attrs.rel\n const relTokens = typeof rel === 'string' ? rel.split(/\\s+/) : []\n\n if (!relTokens.includes('stylesheet')) {\n continue\n }\n\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: asset.attrs.href,\n kind: 'stylesheet',\n }),\n )\n\n asset.attrs.href = result.href\n if (result.crossOrigin) {\n asset.attrs.crossOrigin = result.crossOrigin\n } else {\n delete asset.attrs.crossOrigin\n }\n }\n }\n }\n }\n\n const transformedClientEntry = normalizeTransformAssetResult(\n await transformFn({\n url: source.clientEntry,\n kind: 'clientEntry',\n }),\n )\n\n const rootRoute = (manifest.routes[rootRouteId] =\n manifest.routes[rootRouteId] || {})\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry.href,\n source.injectedHeadScripts,\n ),\n )\n\n return manifest\n}\n\n/**\n * Builds a final Manifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssetUrls option is provided.\n *\n * Returns a new manifest object so the cached base manifest is never mutated.\n */\nexport function buildManifestWithClientEntry(\n source: StartManifestWithClientEntry,\n): Manifest {\n const scriptTag = buildClientEntryScriptTag(\n source.clientEntry,\n source.injectedHeadScripts,\n )\n\n const baseRootRoute = source.manifest.routes[rootRouteId]\n const routes = {\n ...source.manifest.routes,\n [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute?.assets || []), scriptTag],\n },\n }\n\n return { routes }\n}\n"],"mappings":"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"}
|
|
@@ -2,4 +2,5 @@ export declare const VIRTUAL_MODULES: {
|
|
|
2
2
|
readonly startManifest: "tanstack-start-manifest:v";
|
|
3
3
|
readonly injectedHeadScripts: "tanstack-start-injected-head-scripts:v";
|
|
4
4
|
readonly serverFnResolver: "#tanstack-start-server-fn-resolver";
|
|
5
|
+
readonly pluginAdapters: "#tanstack-start-plugin-adapters";
|
|
5
6
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
var VIRTUAL_MODULES = {
|
|
3
3
|
startManifest: "tanstack-start-manifest:v",
|
|
4
4
|
injectedHeadScripts: "tanstack-start-injected-head-scripts:v",
|
|
5
|
-
serverFnResolver: "#tanstack-start-server-fn-resolver"
|
|
5
|
+
serverFnResolver: "#tanstack-start-server-fn-resolver",
|
|
6
|
+
pluginAdapters: "#tanstack-start-plugin-adapters"
|
|
6
7
|
};
|
|
7
8
|
//#endregion
|
|
8
9
|
export { VIRTUAL_MODULES };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-modules.js","names":[],"sources":["../../src/virtual-modules.ts"],"sourcesContent":["export const VIRTUAL_MODULES = {\n startManifest: 'tanstack-start-manifest:v',\n injectedHeadScripts: 'tanstack-start-injected-head-scripts:v',\n serverFnResolver: '#tanstack-start-server-fn-resolver',\n} as const\n"],"mappings":";AAAA,IAAa,kBAAkB;CAC7B,eAAe;CACf,qBAAqB;CACrB,kBAAkB;
|
|
1
|
+
{"version":3,"file":"virtual-modules.js","names":[],"sources":["../../src/virtual-modules.ts"],"sourcesContent":["export const VIRTUAL_MODULES = {\n startManifest: 'tanstack-start-manifest:v',\n injectedHeadScripts: 'tanstack-start-injected-head-scripts:v',\n serverFnResolver: '#tanstack-start-server-fn-resolver',\n pluginAdapters: '#tanstack-start-plugin-adapters',\n} as const\n"],"mappings":";AAAA,IAAa,kBAAkB;CAC7B,eAAe;CACf,qBAAqB;CACrB,kBAAkB;CAClB,gBAAgB;CACjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-server-core",
|
|
3
|
-
"version": "1.167.
|
|
3
|
+
"version": "1.167.12",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"h3-v2": "npm:h3@2.0.1-rc.16",
|
|
67
|
-
"seroval": "^1.
|
|
67
|
+
"seroval": "^1.5.0",
|
|
68
68
|
"@tanstack/history": "1.161.6",
|
|
69
69
|
"@tanstack/router-core": "1.168.9",
|
|
70
70
|
"@tanstack/start-client-core": "1.167.10",
|
package/src/createSsrRpc.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { TSS_SERVER_FUNCTION } from '@tanstack/start-client-core'
|
|
2
2
|
import { getServerFnById } from './getServerFnById'
|
|
3
3
|
import type { ClientFnMeta } from '@tanstack/start-client-core'
|
|
4
|
-
import type { ServerFn } from '#tanstack-start-server-fn-resolver'
|
|
5
4
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
export const createSsrRpc = (functionId: string, importer?: SsrRpcImporter) => {
|
|
5
|
+
export const createSsrRpc = (functionId: string) => {
|
|
9
6
|
const url = process.env.TSS_SERVER_FN_BASE + functionId
|
|
10
7
|
const serverFnMeta: ClientFnMeta = { id: functionId }
|
|
11
8
|
|
|
12
9
|
const fn = async (...args: Array<any>): Promise<any> => {
|
|
13
|
-
|
|
14
|
-
// Otherwise, fall back to manifest lookup (client-to-server call, server functions that are only referenced on the server or if the provider environment is not SSR)
|
|
15
|
-
const serverFn = importer
|
|
16
|
-
? await importer()
|
|
17
|
-
: await getServerFnById(functionId)
|
|
10
|
+
const serverFn = await getServerFnById(functionId, { origin: 'server' })
|
|
18
11
|
return serverFn(...args)
|
|
19
12
|
}
|
|
20
13
|
|
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
getNormalizedURL,
|
|
16
16
|
getOrigin,
|
|
17
17
|
} from '@tanstack/router-core/ssr/server'
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
getStartContext,
|
|
20
|
+
runWithStartContext,
|
|
21
|
+
} from '@tanstack/start-storage-context'
|
|
19
22
|
import { requestHandler } from './request-response'
|
|
20
23
|
import { getStartManifest } from './router-manifest'
|
|
21
24
|
import { handleServerAction } from './server-functions-handler'
|
|
@@ -41,6 +44,7 @@ import type { RequestHandler } from './request-handler'
|
|
|
41
44
|
import type {
|
|
42
45
|
AnyRoute,
|
|
43
46
|
AnyRouter,
|
|
47
|
+
AnySerializationAdapter,
|
|
44
48
|
Manifest,
|
|
45
49
|
Register,
|
|
46
50
|
} from '@tanstack/router-core'
|
|
@@ -205,14 +209,20 @@ function getStartResponseHeaders(opts: { router: AnyRouter }) {
|
|
|
205
209
|
return headers
|
|
206
210
|
}
|
|
207
211
|
|
|
212
|
+
interface PluginAdaptersEntry {
|
|
213
|
+
hasPluginAdapters: boolean
|
|
214
|
+
pluginSerializationAdapters: Array<AnySerializationAdapter>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface Entries {
|
|
218
|
+
startEntry: StartEntry
|
|
219
|
+
routerEntry: RouterEntry
|
|
220
|
+
pluginAdapters: PluginAdaptersEntry
|
|
221
|
+
}
|
|
222
|
+
|
|
208
223
|
// Cached entries - promises stored immediately to prevent concurrent imports
|
|
209
224
|
// that can cause race conditions during module initialization
|
|
210
|
-
let entriesPromise:
|
|
211
|
-
| Promise<{
|
|
212
|
-
startEntry: StartEntry
|
|
213
|
-
routerEntry: RouterEntry
|
|
214
|
-
}>
|
|
215
|
-
| undefined
|
|
225
|
+
let entriesPromise: Promise<Entries> | undefined
|
|
216
226
|
let baseManifestPromise: Promise<StartManifestWithClientEntry> | undefined
|
|
217
227
|
|
|
218
228
|
/**
|
|
@@ -221,12 +231,20 @@ let baseManifestPromise: Promise<StartManifestWithClientEntry> | undefined
|
|
|
221
231
|
*/
|
|
222
232
|
let cachedFinalManifestPromise: Promise<Manifest> | undefined
|
|
223
233
|
|
|
224
|
-
async function loadEntries() {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
async function loadEntries(): Promise<Entries> {
|
|
235
|
+
const [routerEntry, startEntry, pluginAdapters] = await Promise.all([
|
|
236
|
+
// @ts-ignore When building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core
|
|
237
|
+
import('#tanstack-router-entry'),
|
|
238
|
+
// @ts-ignore When building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core
|
|
239
|
+
import('#tanstack-start-entry'),
|
|
240
|
+
// @ts-ignore When building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core
|
|
241
|
+
import('#tanstack-start-plugin-adapters'),
|
|
242
|
+
])
|
|
243
|
+
return {
|
|
244
|
+
routerEntry: routerEntry as unknown as RouterEntry,
|
|
245
|
+
startEntry: startEntry as unknown as StartEntry,
|
|
246
|
+
pluginAdapters: pluginAdapters as unknown as PluginAdaptersEntry,
|
|
247
|
+
}
|
|
230
248
|
}
|
|
231
249
|
|
|
232
250
|
function getEntries() {
|
|
@@ -552,8 +570,12 @@ export function createStartHandler<TRegister = Register>(
|
|
|
552
570
|
(await entries.startEntry.startInstance?.getOptions()) ||
|
|
553
571
|
({} as AnyStartInstanceOptions)
|
|
554
572
|
|
|
573
|
+
const { hasPluginAdapters, pluginSerializationAdapters } =
|
|
574
|
+
entries.pluginAdapters
|
|
575
|
+
|
|
555
576
|
const serializationAdapters = [
|
|
556
577
|
...(startOptions.serializationAdapters || []),
|
|
578
|
+
...(hasPluginAdapters ? pluginSerializationAdapters : []),
|
|
557
579
|
ServerFunctionSerializationAdapter,
|
|
558
580
|
]
|
|
559
581
|
|
|
@@ -623,6 +645,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
623
645
|
contextAfterGlobalMiddlewares: context,
|
|
624
646
|
request,
|
|
625
647
|
executedRequestMiddlewares,
|
|
648
|
+
handlerType: 'serverFn',
|
|
626
649
|
},
|
|
627
650
|
() =>
|
|
628
651
|
handleServerAction({
|
|
@@ -675,6 +698,8 @@ export function createStartHandler<TRegister = Register>(
|
|
|
675
698
|
attachRouterServerSsrUtils({
|
|
676
699
|
router: routerInstance,
|
|
677
700
|
manifest,
|
|
701
|
+
getRequestAssets: () =>
|
|
702
|
+
getStartContext({ throwIfNotFound: false })?.requestAssets,
|
|
678
703
|
})
|
|
679
704
|
|
|
680
705
|
routerInstance.update({ additionalContext: { serverContext } })
|
|
@@ -684,7 +709,11 @@ export function createStartHandler<TRegister = Register>(
|
|
|
684
709
|
return routerInstance.state.redirect
|
|
685
710
|
}
|
|
686
711
|
|
|
687
|
-
|
|
712
|
+
// Pass request-scoped assets to dehydrate for manifest injection
|
|
713
|
+
const ctx = getStartContext({ throwIfNotFound: false })
|
|
714
|
+
await routerInstance.serverSsr!.dehydrate({
|
|
715
|
+
requestAssets: ctx?.requestAssets,
|
|
716
|
+
})
|
|
688
717
|
|
|
689
718
|
const responseHeaders = getStartResponseHeaders({
|
|
690
719
|
router: routerInstance,
|
|
@@ -707,6 +736,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
707
736
|
contextAfterGlobalMiddlewares: context,
|
|
708
737
|
request,
|
|
709
738
|
executedRequestMiddlewares,
|
|
739
|
+
handlerType: 'router',
|
|
710
740
|
},
|
|
711
741
|
async () => {
|
|
712
742
|
try {
|