@tanstack/start-client-core 1.143.9 → 1.143.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/client-rpc/serverFnFetcher.js +4 -9
- package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -1
- package/dist/esm/constants.d.ts +1 -0
- package/dist/esm/constants.js +2 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/createServerFn.d.ts +1 -2
- package/dist/esm/createServerFn.js +104 -70
- package/dist/esm/createServerFn.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +7 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/safeObjectMerge.d.ts +10 -0
- package/dist/esm/safeObjectMerge.js +30 -0
- package/dist/esm/safeObjectMerge.js.map +1 -0
- package/package.json +3 -3
- package/src/client-rpc/serverFnFetcher.ts +26 -12
- package/src/constants.ts +1 -0
- package/src/createServerFn.ts +145 -99
- package/src/index.tsx +2 -1
- package/src/safeObjectMerge.ts +38 -0
|
@@ -21,7 +21,7 @@ async function serverFnFetcher(url, args, handler) {
|
|
|
21
21
|
const first = _first;
|
|
22
22
|
const type = first.data instanceof FormData ? "formData" : "payload";
|
|
23
23
|
const headers = first.headers ? new Headers(first.headers) : new Headers();
|
|
24
|
-
headers.set("x-tsr-
|
|
24
|
+
headers.set("x-tsr-serverFn", "true");
|
|
25
25
|
if (type === "payload") {
|
|
26
26
|
headers.set("accept", "application/x-ndjson, application/json");
|
|
27
27
|
}
|
|
@@ -114,14 +114,6 @@ async function getResponse(fn) {
|
|
|
114
114
|
const contentType = response.headers.get("content-type");
|
|
115
115
|
invariant(contentType, "expected content-type header to be set");
|
|
116
116
|
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED);
|
|
117
|
-
if (!response.ok) {
|
|
118
|
-
if (serializedByStart && contentType.includes("application/json")) {
|
|
119
|
-
const jsonPayload = await response.json();
|
|
120
|
-
const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins });
|
|
121
|
-
throw result;
|
|
122
|
-
}
|
|
123
|
-
throw new Error(await response.text());
|
|
124
|
-
}
|
|
125
117
|
if (serializedByStart) {
|
|
126
118
|
let result;
|
|
127
119
|
if (contentType.includes("application/x-ndjson")) {
|
|
@@ -155,6 +147,9 @@ async function getResponse(fn) {
|
|
|
155
147
|
}
|
|
156
148
|
return jsonPayload;
|
|
157
149
|
}
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
throw new Error(await response.text());
|
|
152
|
+
}
|
|
158
153
|
return response;
|
|
159
154
|
}
|
|
160
155
|
async function processServerFnResponse({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverFnFetcher.js","sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import { encode, isNotFound, parseRedirect } from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n} from '../constants'\nimport type { FunctionMiddlewareClientFnOptions } from '../createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\n/**\n * Checks if an object has at least one own enumerable property.\n * More efficient than Object.keys(obj).length > 0 as it short-circuits on first property.\n */\nconst hop = Object.prototype.hasOwnProperty\nfunction hasOwnProperties(obj: object): boolean {\n for (const _ in obj) {\n if (hop.call(obj, _)) {\n return true\n }\n }\n return false\n}\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n const _first = args[0]\n\n const first = _first as FunctionMiddlewareClientFnOptions<any, any, any> & {\n headers?: HeadersInit\n }\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = first.headers ? new Headers(first.headers) : new Headers()\n headers.set('x-tsr-redirect', 'manual')\n\n if (type === 'payload') {\n headers.set('accept', 'application/x-ndjson, application/json')\n }\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n if (type === 'formData') {\n throw new Error('FormData is not supported with GET requests')\n }\n const serializedPayload = await serializePayload(first)\n if (serializedPayload !== undefined) {\n const encodedPayload = encode({\n payload: serializedPayload,\n })\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n let body = undefined\n if (first.method === 'POST') {\n const fetchBody = await getFetchBody(first)\n if (fetchBody?.contentType) {\n headers.set('content-type', fetchBody.contentType)\n }\n body = fetchBody?.body\n }\n\n return await getResponse(async () =>\n handler(url, {\n method: first.method,\n headers,\n signal: first.signal,\n body,\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<string | undefined> {\n let payloadAvailable = false\n const payloadToSerialize: any = {}\n if (opts.data !== undefined) {\n payloadAvailable = true\n payloadToSerialize['data'] = opts.data\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n payloadAvailable = true\n payloadToSerialize['context'] = opts.context\n }\n\n if (payloadAvailable) {\n return serialize(payloadToSerialize)\n }\n return undefined\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetchBody(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<{ body: FormData | string; contentType?: string } | undefined> {\n if (opts.data instanceof FormData) {\n let serializedContext = undefined\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n serializedContext = await serialize(opts.context)\n }\n if (serializedContext !== undefined) {\n opts.data.set(TSS_FORMDATA_CONTEXT, serializedContext)\n }\n return { body: opts.data }\n }\n const serializedBody = await serializePayload(opts)\n if (serializedBody) {\n return { body: serializedBody, contentType: 'application/json' }\n }\n return undefined\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n let response: Response\n try {\n response = await fn()\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 const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)\n // If the response is not ok, throw an error\n if (!response.ok) {\n if (serializedByStart && contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n throw result\n }\n\n throw new Error(await response.text())\n }\n\n if (serializedByStart) {\n let result\n if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occurred?\n console.error(msg, error)\n },\n })\n }\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n return result\n }\n\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serverFnFetcher.js","sources":["../../../src/client-rpc/serverFnFetcher.ts"],"sourcesContent":["import { encode, isNotFound, parseRedirect } from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getDefaultSerovalPlugins } from '../getDefaultSerovalPlugins'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n} from '../constants'\nimport type { FunctionMiddlewareClientFnOptions } from '../createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\n/**\n * Checks if an object has at least one own enumerable property.\n * More efficient than Object.keys(obj).length > 0 as it short-circuits on first property.\n */\nconst hop = Object.prototype.hasOwnProperty\nfunction hasOwnProperties(obj: object): boolean {\n for (const _ in obj) {\n if (hop.call(obj, _)) {\n return true\n }\n }\n return false\n}\n// caller =>\n// serverFnFetcher =>\n// client =>\n// server =>\n// fn =>\n// seroval =>\n// client middleware =>\n// serverFnFetcher =>\n// caller\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n const _first = args[0]\n\n const first = _first as FunctionMiddlewareClientFnOptions<any, any, any> & {\n headers?: HeadersInit\n }\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = first.headers ? new Headers(first.headers) : new Headers()\n headers.set('x-tsr-serverFn', 'true')\n\n if (type === 'payload') {\n headers.set('accept', 'application/x-ndjson, application/json')\n }\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n if (type === 'formData') {\n throw new Error('FormData is not supported with GET requests')\n }\n const serializedPayload = await serializePayload(first)\n if (serializedPayload !== undefined) {\n const encodedPayload = encode({\n payload: serializedPayload,\n })\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n let body = undefined\n if (first.method === 'POST') {\n const fetchBody = await getFetchBody(first)\n if (fetchBody?.contentType) {\n headers.set('content-type', fetchBody.contentType)\n }\n body = fetchBody?.body\n }\n\n return await getResponse(async () =>\n handler(url, {\n method: first.method,\n headers,\n signal: first.signal,\n body,\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<string | undefined> {\n let payloadAvailable = false\n const payloadToSerialize: any = {}\n if (opts.data !== undefined) {\n payloadAvailable = true\n payloadToSerialize['data'] = opts.data\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n payloadAvailable = true\n payloadToSerialize['context'] = opts.context\n }\n\n if (payloadAvailable) {\n return serialize(payloadToSerialize)\n }\n return undefined\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetchBody(\n opts: FunctionMiddlewareClientFnOptions<any, any, any>,\n): Promise<{ body: FormData | string; contentType?: string } | undefined> {\n if (opts.data instanceof FormData) {\n let serializedContext = undefined\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && hasOwnProperties(opts.context)) {\n serializedContext = await serialize(opts.context)\n }\n if (serializedContext !== undefined) {\n opts.data.set(TSS_FORMDATA_CONTEXT, serializedContext)\n }\n return { body: opts.data }\n }\n const serializedBody = await serializePayload(opts)\n if (serializedBody) {\n return { body: serializedBody, contentType: 'application/json' }\n }\n return undefined\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n let response: Response\n try {\n response = await fn() // client => server => fn => server => client\n } catch (error) {\n if (error instanceof Response) {\n response = error\n } else {\n console.log(error)\n throw error\n }\n }\n\n if (response.headers.get(X_TSS_RAW_RESPONSE) === 'true') {\n return response\n }\n\n const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)\n\n // If the response is serialized by the start server, we need to process it\n // differently than a normal response.\n if (serializedByStart) {\n let result\n // If it's a stream from the start serializer, process it as such\n if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occurred?\n console.error(msg, error)\n },\n })\n }\n // If it's a JSON response, it can be simpler\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n\n return result\n }\n\n // If it wasn't processed by the start serializer, check\n // if it's JSON\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n // Otherwise, if it's not OK, throw the content\n if (!response.ok) {\n throw new Error(await response.text())\n }\n\n // Or return the response itself\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare const TSS_SERVER_FUNCTION: unique symbol;
|
|
|
3
3
|
export declare const TSS_SERVER_FUNCTION_FACTORY: unique symbol;
|
|
4
4
|
export declare const X_TSS_SERIALIZED = "x-tss-serialized";
|
|
5
5
|
export declare const X_TSS_RAW_RESPONSE = "x-tss-raw";
|
|
6
|
+
export declare const X_TSS_CONTEXT = "x-tss-context";
|
|
6
7
|
export {};
|
package/dist/esm/constants.js
CHANGED
|
@@ -5,10 +5,12 @@ const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(
|
|
|
5
5
|
);
|
|
6
6
|
const X_TSS_SERIALIZED = "x-tss-serialized";
|
|
7
7
|
const X_TSS_RAW_RESPONSE = "x-tss-raw";
|
|
8
|
+
const X_TSS_CONTEXT = "x-tss-context";
|
|
8
9
|
export {
|
|
9
10
|
TSS_FORMDATA_CONTEXT,
|
|
10
11
|
TSS_SERVER_FUNCTION,
|
|
11
12
|
TSS_SERVER_FUNCTION_FACTORY,
|
|
13
|
+
X_TSS_CONTEXT,
|
|
12
14
|
X_TSS_RAW_RESPONSE,
|
|
13
15
|
X_TSS_SERIALIZED
|
|
14
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const TSS_FORMDATA_CONTEXT = '__TSS_CONTEXT'\nexport const TSS_SERVER_FUNCTION = Symbol.for('TSS_SERVER_FUNCTION')\nexport const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(\n 'TSS_SERVER_FUNCTION_FACTORY',\n)\n\nexport const X_TSS_SERIALIZED = 'x-tss-serialized'\nexport const X_TSS_RAW_RESPONSE = 'x-tss-raw'\nexport {}\n"],"names":[],"mappings":"AAAO,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB,OAAO,IAAI,qBAAqB;AAC5D,MAAM,8BAA8B,OAAO;AAAA,EAChD;AACF;AAEO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;"}
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const TSS_FORMDATA_CONTEXT = '__TSS_CONTEXT'\nexport const TSS_SERVER_FUNCTION = Symbol.for('TSS_SERVER_FUNCTION')\nexport const TSS_SERVER_FUNCTION_FACTORY = Symbol.for(\n 'TSS_SERVER_FUNCTION_FACTORY',\n)\n\nexport const X_TSS_SERIALIZED = 'x-tss-serialized'\nexport const X_TSS_RAW_RESPONSE = 'x-tss-raw'\nexport const X_TSS_CONTEXT = 'x-tss-context'\nexport {}\n"],"names":[],"mappings":"AAAO,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB,OAAO,IAAI,qBAAqB;AAC5D,MAAM,8BAA8B,OAAO;AAAA,EAChD;AACF;AAEO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;"}
|
|
@@ -105,7 +105,7 @@ export interface ServerFnTypes<in out TRegister, in out TMethod extends Method,
|
|
|
105
105
|
allInput: IntersectAllValidatorInputs<TMiddlewares, TInputValidator>;
|
|
106
106
|
allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>;
|
|
107
107
|
}
|
|
108
|
-
export declare function flattenMiddlewares(middlewares: Array<
|
|
108
|
+
export declare function flattenMiddlewares<T extends AnyFunctionMiddleware | AnyRequestMiddleware>(middlewares: Array<T>, maxDepth?: number): Array<T>;
|
|
109
109
|
export type ServerFnMiddlewareOptions = {
|
|
110
110
|
method: Method;
|
|
111
111
|
data: any;
|
|
@@ -123,5 +123,4 @@ export type NextFn = (ctx: ServerFnMiddlewareResult) => Promise<ServerFnMiddlewa
|
|
|
123
123
|
export type MiddlewareFn = (ctx: ServerFnMiddlewareOptions & {
|
|
124
124
|
next: NextFn;
|
|
125
125
|
}) => Promise<ServerFnMiddlewareResult>;
|
|
126
|
-
export declare const applyMiddleware: (middlewareFn: MiddlewareFn, ctx: ServerFnMiddlewareOptions, nextFn: NextFn) => Promise<ServerFnMiddlewareResult>;
|
|
127
126
|
export declare function execValidator(validator: AnyValidator, input: unknown): unknown;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { isRedirect, isNotFound } from "@tanstack/router-core";
|
|
2
1
|
import { mergeHeaders } from "@tanstack/router-core/ssr/client";
|
|
2
|
+
import { parseRedirect, isRedirect } from "@tanstack/router-core";
|
|
3
3
|
import { TSS_SERVER_FUNCTION_FACTORY } from "./constants.js";
|
|
4
4
|
import { getStartOptions } from "./getStartOptions.js";
|
|
5
5
|
import { getStartContextServerOnly } from "./getStartContextServerOnly.js";
|
|
6
|
+
import { createNullProtoObject, safeObjectMerge } from "./safeObjectMerge.js";
|
|
6
7
|
const createServerFn = (options, __opts) => {
|
|
7
8
|
const resolvedOptions = __opts || options || {};
|
|
8
9
|
if (typeof resolvedOptions.method === "undefined") {
|
|
@@ -42,17 +43,20 @@ const createServerFn = (options, __opts) => {
|
|
|
42
43
|
];
|
|
43
44
|
return Object.assign(
|
|
44
45
|
async (opts) => {
|
|
45
|
-
|
|
46
|
+
const result = await executeMiddleware(resolvedMiddleware, "client", {
|
|
46
47
|
...extractedFn,
|
|
47
48
|
...newOptions,
|
|
48
49
|
data: opts?.data,
|
|
49
50
|
headers: opts?.headers,
|
|
50
51
|
signal: opts?.signal,
|
|
51
|
-
context:
|
|
52
|
-
}).then((d) => {
|
|
53
|
-
if (d.error) throw d.error;
|
|
54
|
-
return d.result;
|
|
52
|
+
context: createNullProtoObject()
|
|
55
53
|
});
|
|
54
|
+
const redirect = parseRedirect(result.error);
|
|
55
|
+
if (redirect) {
|
|
56
|
+
throw redirect;
|
|
57
|
+
}
|
|
58
|
+
if (result.error) throw result.error;
|
|
59
|
+
return result.result;
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
// This copies over the URL, function ID
|
|
@@ -65,21 +69,24 @@ const createServerFn = (options, __opts) => {
|
|
|
65
69
|
const ctx = {
|
|
66
70
|
...extractedFn,
|
|
67
71
|
...opts,
|
|
68
|
-
context:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
context: safeObjectMerge(
|
|
73
|
+
serverContextAfterGlobalMiddlewares,
|
|
74
|
+
opts.context
|
|
75
|
+
),
|
|
72
76
|
signal,
|
|
73
77
|
request: startContext.request
|
|
74
78
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const result = await executeMiddleware(
|
|
80
|
+
resolvedMiddleware,
|
|
81
|
+
"server",
|
|
82
|
+
ctx
|
|
83
|
+
).then((d) => ({
|
|
84
|
+
// Only send the result and sendContext back to the client
|
|
85
|
+
result: d.result,
|
|
86
|
+
error: d.error,
|
|
87
|
+
context: d.sendContext
|
|
88
|
+
}));
|
|
89
|
+
return result;
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
);
|
|
@@ -96,58 +103,108 @@ const createServerFn = (options, __opts) => {
|
|
|
96
103
|
};
|
|
97
104
|
async function executeMiddleware(middlewares, env, opts) {
|
|
98
105
|
const globalMiddlewares = getStartOptions()?.functionMiddleware || [];
|
|
99
|
-
|
|
106
|
+
let flattenedMiddlewares = flattenMiddlewares([
|
|
100
107
|
...globalMiddlewares,
|
|
101
108
|
...middlewares
|
|
102
109
|
]);
|
|
103
|
-
|
|
110
|
+
if (env === "server") {
|
|
111
|
+
const startContext = getStartContextServerOnly({ throwIfNotFound: false });
|
|
112
|
+
if (startContext?.executedRequestMiddlewares) {
|
|
113
|
+
flattenedMiddlewares = flattenedMiddlewares.filter(
|
|
114
|
+
(m) => !startContext.executedRequestMiddlewares.has(m)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const callNextMiddleware = async (ctx) => {
|
|
104
119
|
const nextMiddleware = flattenedMiddlewares.shift();
|
|
105
120
|
if (!nextMiddleware) {
|
|
106
121
|
return ctx;
|
|
107
122
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let middlewareFn = void 0;
|
|
115
|
-
if (env === "client") {
|
|
116
|
-
if ("client" in nextMiddleware.options) {
|
|
117
|
-
middlewareFn = nextMiddleware.options.client;
|
|
123
|
+
try {
|
|
124
|
+
if ("inputValidator" in nextMiddleware.options && nextMiddleware.options.inputValidator && env === "server") {
|
|
125
|
+
ctx.data = await execValidator(
|
|
126
|
+
nextMiddleware.options.inputValidator,
|
|
127
|
+
ctx.data
|
|
128
|
+
);
|
|
118
129
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
let middlewareFn = void 0;
|
|
131
|
+
if (env === "client") {
|
|
132
|
+
if ("client" in nextMiddleware.options) {
|
|
133
|
+
middlewareFn = nextMiddleware.options.client;
|
|
134
|
+
}
|
|
135
|
+
} else if ("server" in nextMiddleware.options) {
|
|
136
|
+
middlewareFn = nextMiddleware.options.server;
|
|
137
|
+
}
|
|
138
|
+
if (middlewareFn) {
|
|
139
|
+
const userNext = async (userCtx = {}) => {
|
|
140
|
+
const nextCtx = {
|
|
141
|
+
...ctx,
|
|
142
|
+
...userCtx,
|
|
143
|
+
context: safeObjectMerge(ctx.context, userCtx.context),
|
|
144
|
+
sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),
|
|
145
|
+
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
146
|
+
result: userCtx.result !== void 0 ? userCtx.result : userCtx instanceof Response ? userCtx : ctx.result,
|
|
147
|
+
error: userCtx.error ?? ctx.error
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
return await callNextMiddleware(nextCtx);
|
|
151
|
+
} catch (error) {
|
|
126
152
|
return {
|
|
127
|
-
...
|
|
153
|
+
...nextCtx,
|
|
128
154
|
error
|
|
129
155
|
};
|
|
130
156
|
}
|
|
131
|
-
|
|
157
|
+
};
|
|
158
|
+
const result = await middlewareFn({
|
|
159
|
+
...ctx,
|
|
160
|
+
next: userNext
|
|
132
161
|
});
|
|
133
|
-
|
|
162
|
+
if (isRedirect(result)) {
|
|
163
|
+
return {
|
|
164
|
+
...ctx,
|
|
165
|
+
error: result
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (result instanceof Response) {
|
|
169
|
+
return {
|
|
170
|
+
...ctx,
|
|
171
|
+
result
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (!result) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"User middleware returned undefined. You must call next() or return a result in your middlewares."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
return callNextMiddleware(ctx);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
...ctx,
|
|
185
|
+
error
|
|
186
|
+
};
|
|
134
187
|
}
|
|
135
|
-
return next(ctx);
|
|
136
188
|
};
|
|
137
|
-
return
|
|
189
|
+
return callNextMiddleware({
|
|
138
190
|
...opts,
|
|
139
191
|
headers: opts.headers || {},
|
|
140
192
|
sendContext: opts.sendContext || {},
|
|
141
|
-
context: opts.context ||
|
|
193
|
+
context: opts.context || createNullProtoObject()
|
|
142
194
|
});
|
|
143
195
|
}
|
|
144
|
-
function flattenMiddlewares(middlewares) {
|
|
196
|
+
function flattenMiddlewares(middlewares, maxDepth = 100) {
|
|
145
197
|
const seen = /* @__PURE__ */ new Set();
|
|
146
198
|
const flattened = [];
|
|
147
|
-
const recurse = (middleware) => {
|
|
199
|
+
const recurse = (middleware, depth) => {
|
|
200
|
+
if (depth > maxDepth) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
148
205
|
middleware.forEach((m) => {
|
|
149
206
|
if (m.options.middleware) {
|
|
150
|
-
recurse(m.options.middleware);
|
|
207
|
+
recurse(m.options.middleware, depth + 1);
|
|
151
208
|
}
|
|
152
209
|
if (!seen.has(m)) {
|
|
153
210
|
seen.add(m);
|
|
@@ -155,31 +212,9 @@ function flattenMiddlewares(middlewares) {
|
|
|
155
212
|
}
|
|
156
213
|
});
|
|
157
214
|
};
|
|
158
|
-
recurse(middlewares);
|
|
215
|
+
recurse(middlewares, 0);
|
|
159
216
|
return flattened;
|
|
160
217
|
}
|
|
161
|
-
const applyMiddleware = async (middlewareFn, ctx, nextFn) => {
|
|
162
|
-
return middlewareFn({
|
|
163
|
-
...ctx,
|
|
164
|
-
next: (async (userCtx = {}) => {
|
|
165
|
-
return nextFn({
|
|
166
|
-
...ctx,
|
|
167
|
-
...userCtx,
|
|
168
|
-
context: {
|
|
169
|
-
...ctx.context,
|
|
170
|
-
...userCtx.context
|
|
171
|
-
},
|
|
172
|
-
sendContext: {
|
|
173
|
-
...ctx.sendContext,
|
|
174
|
-
...userCtx.sendContext ?? {}
|
|
175
|
-
},
|
|
176
|
-
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
177
|
-
result: userCtx.result !== void 0 ? userCtx.result : userCtx instanceof Response ? userCtx : ctx.result,
|
|
178
|
-
error: userCtx.error ?? ctx.error
|
|
179
|
-
});
|
|
180
|
-
})
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
218
|
function execValidator(validator, input) {
|
|
184
219
|
if (validator == null) return {};
|
|
185
220
|
if ("~standard" in validator) {
|
|
@@ -223,7 +258,6 @@ function serverFnBaseToMiddleware(options) {
|
|
|
223
258
|
};
|
|
224
259
|
}
|
|
225
260
|
export {
|
|
226
|
-
applyMiddleware,
|
|
227
261
|
createServerFn,
|
|
228
262
|
execValidator,
|
|
229
263
|
executeMiddleware,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createServerFn.js","sources":["../../src/createServerFn.ts"],"sourcesContent":["import { isNotFound, isRedirect } from '@tanstack/router-core'\nimport { mergeHeaders } from '@tanstack/router-core/ssr/client'\n\nimport { TSS_SERVER_FUNCTION_FACTORY } from './constants'\nimport { getStartOptions } from './getStartOptions'\nimport { getStartContextServerOnly } from './getStartContextServerOnly'\nimport type { TSS_SERVER_FUNCTION } from './constants'\nimport type {\n AnyValidator,\n Constrain,\n Expand,\n Register,\n RegisteredSerializableInput,\n ResolveValidatorInput,\n ValidateSerializable,\n ValidateSerializableInput,\n Validator,\n} from '@tanstack/router-core'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AssignAllServerFnContext,\n FunctionMiddlewareClientFnResult,\n FunctionMiddlewareServerFnResult,\n IntersectAllValidatorInputs,\n IntersectAllValidatorOutputs,\n} from './createMiddleware'\n\ntype TODO = any\n\nexport type CreateServerFn<TRegister> = <\n TMethod extends Method,\n TResponse = unknown,\n TMiddlewares = undefined,\n TInputValidator = undefined,\n>(\n options?: {\n method?: TMethod\n },\n __opts?: ServerFnBaseOptions<\n TRegister,\n TMethod,\n TResponse,\n TMiddlewares,\n TInputValidator\n >,\n) => ServerFnBuilder<TRegister, TMethod>\n\nexport const createServerFn: CreateServerFn<Register> = (options, __opts) => {\n const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<\n any,\n any,\n any,\n any,\n any\n >\n\n if (typeof resolvedOptions.method === 'undefined') {\n resolvedOptions.method = 'GET' as Method\n }\n\n const res: ServerFnBuilder<Register, Method> = {\n options: resolvedOptions as any,\n middleware: (middleware) => {\n // multiple calls to `middleware()` merge the middlewares with the previously supplied ones\n // this is primarily useful for letting users create their own abstractions on top of `createServerFn`\n\n const newMiddleware = [...(resolvedOptions.middleware || [])]\n middleware.map((m) => {\n if (TSS_SERVER_FUNCTION_FACTORY in m) {\n if (m.options.middleware) {\n newMiddleware.push(...m.options.middleware)\n }\n } else {\n newMiddleware.push(m)\n }\n })\n\n const newOptions = {\n ...resolvedOptions,\n middleware: newMiddleware,\n }\n const res = createServerFn(undefined, newOptions) as any\n res[TSS_SERVER_FUNCTION_FACTORY] = true\n return res\n },\n inputValidator: (inputValidator) => {\n const newOptions = { ...resolvedOptions, inputValidator }\n return createServerFn(undefined, newOptions) as any\n },\n handler: (...args) => {\n // This function signature changes due to AST transformations\n // in the babel plugin. We need to cast it to the correct\n // function signature post-transformation\n const [extractedFn, serverFn] = args as unknown as [\n CompiledFetcherFn<Register, any>,\n ServerFn<Register, Method, any, any, any>,\n ]\n\n // Keep the original function around so we can use it\n // in the server environment\n const newOptions = { ...resolvedOptions, extractedFn, serverFn }\n\n const resolvedMiddleware = [\n ...(newOptions.middleware || []),\n serverFnBaseToMiddleware(newOptions),\n ]\n\n // We want to make sure the new function has the same\n // properties as the original function\n\n return Object.assign(\n async (opts?: CompiledFetcherFnOptions) => {\n // Start by executing the client-side middleware chain\n return executeMiddleware(resolvedMiddleware, 'client', {\n ...extractedFn,\n ...newOptions,\n data: opts?.data as any,\n headers: opts?.headers,\n signal: opts?.signal,\n context: {},\n }).then((d) => {\n if (d.error) throw d.error\n return d.result\n })\n },\n {\n // This copies over the URL, function ID\n ...extractedFn,\n // The extracted function on the server-side calls\n // this function\n __executeServer: async (opts: any, signal: AbortSignal) => {\n const startContext = getStartContextServerOnly()\n const serverContextAfterGlobalMiddlewares =\n startContext.contextAfterGlobalMiddlewares\n const ctx = {\n ...extractedFn,\n ...opts,\n context: {\n ...serverContextAfterGlobalMiddlewares,\n ...opts.context,\n },\n signal,\n request: startContext.request,\n }\n\n return executeMiddleware(resolvedMiddleware, 'server', ctx).then(\n (d) => ({\n // Only send the result and sendContext back to the client\n result: d.result,\n error: d.error,\n context: d.sendContext,\n }),\n )\n },\n },\n ) as any\n },\n } as ServerFnBuilder<Register, Method>\n const fun = (options?: { method?: Method }) => {\n const newOptions = {\n ...resolvedOptions,\n ...options,\n }\n return createServerFn(undefined, newOptions) as any\n }\n return Object.assign(fun, res) as any\n}\n\nexport async function executeMiddleware(\n middlewares: Array<AnyFunctionMiddleware | AnyRequestMiddleware>,\n env: 'client' | 'server',\n opts: ServerFnMiddlewareOptions,\n): Promise<ServerFnMiddlewareResult> {\n const globalMiddlewares = getStartOptions()?.functionMiddleware || []\n const flattenedMiddlewares = flattenMiddlewares([\n ...globalMiddlewares,\n ...middlewares,\n ])\n\n const next: NextFn = async (ctx) => {\n // Get the next middleware\n const nextMiddleware = flattenedMiddlewares.shift()\n\n // If there are no more middlewares, return the context\n if (!nextMiddleware) {\n return ctx\n }\n\n if (\n 'inputValidator' in nextMiddleware.options &&\n nextMiddleware.options.inputValidator &&\n env === 'server'\n ) {\n // Execute the middleware's input function\n ctx.data = await execValidator(\n nextMiddleware.options.inputValidator,\n ctx.data,\n )\n }\n\n let middlewareFn: MiddlewareFn | undefined = undefined\n if (env === 'client') {\n if ('client' in nextMiddleware.options) {\n middlewareFn = nextMiddleware.options.client as MiddlewareFn | undefined\n }\n }\n // env === 'server'\n else if ('server' in nextMiddleware.options) {\n middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined\n }\n\n if (middlewareFn) {\n // Execute the middleware\n return applyMiddleware(middlewareFn, ctx, async (newCtx) => {\n return next(newCtx).catch((error: any) => {\n if (isRedirect(error) || isNotFound(error)) {\n return {\n ...newCtx,\n error,\n }\n }\n\n throw error\n })\n })\n }\n\n return next(ctx)\n }\n\n // Start the middleware chain\n return next({\n ...opts,\n headers: opts.headers || {},\n sendContext: opts.sendContext || {},\n context: opts.context || {},\n })\n}\n\nexport type CompiledFetcherFnOptions = {\n method: Method\n data: unknown\n headers?: HeadersInit\n signal?: AbortSignal\n context?: any\n}\n\nexport type Fetcher<TMiddlewares, TInputValidator, TResponse> =\n undefined extends IntersectAllValidatorInputs<TMiddlewares, TInputValidator>\n ? OptionalFetcher<TMiddlewares, TInputValidator, TResponse>\n : RequiredFetcher<TMiddlewares, TInputValidator, TResponse>\n\nexport interface FetcherBase {\n [TSS_SERVER_FUNCTION]: true\n url: string\n __executeServer: (opts: {\n method: Method\n data: unknown\n headers?: HeadersInit\n context?: any\n signal: AbortSignal\n }) => Promise<unknown>\n}\n\nexport interface OptionalFetcher<TMiddlewares, TInputValidator, TResponse>\n extends FetcherBase {\n (\n options?: OptionalFetcherDataOptions<TMiddlewares, TInputValidator>,\n ): Promise<Awaited<TResponse>>\n}\n\nexport interface RequiredFetcher<TMiddlewares, TInputValidator, TResponse>\n extends FetcherBase {\n (\n opts: RequiredFetcherDataOptions<TMiddlewares, TInputValidator>,\n ): Promise<Awaited<TResponse>>\n}\n\nexport type FetcherBaseOptions = {\n headers?: HeadersInit\n signal?: AbortSignal\n}\n\nexport interface OptionalFetcherDataOptions<TMiddlewares, TInputValidator>\n extends FetcherBaseOptions {\n data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>\n}\n\nexport interface RequiredFetcherDataOptions<TMiddlewares, TInputValidator>\n extends FetcherBaseOptions {\n data: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>\n}\n\nexport type RscStream<T> = {\n __cacheState: T\n}\n\nexport type Method = 'GET' | 'POST'\n\nexport type ServerFnReturnType<TRegister, TResponse> =\n TResponse extends PromiseLike<infer U>\n ? Promise<ServerFnReturnType<TRegister, U>>\n : TResponse extends Response\n ? TResponse\n : ValidateSerializableInput<TRegister, TResponse>\n\nexport type ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse,\n> = (\n ctx: ServerFnCtx<TRegister, TMiddlewares, TInputValidator>,\n) => ServerFnReturnType<TRegister, TResponse>\n\nexport interface ServerFnCtx<TRegister, TMiddlewares, TInputValidator> {\n data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>>\n context: Expand<AssignAllServerFnContext<TRegister, TMiddlewares, {}>>\n signal: AbortSignal\n}\n\nexport type CompiledFetcherFn<TRegister, TResponse> = {\n (\n opts: CompiledFetcherFnOptions & ServerFnBaseOptions<TRegister, Method>,\n ): Promise<TResponse>\n url: string\n}\n\nexport type ServerFnBaseOptions<\n TRegister,\n TMethod extends Method = 'GET',\n TResponse = unknown,\n TMiddlewares = unknown,\n TInputValidator = unknown,\n> = {\n method: TMethod\n middleware?: Constrain<\n TMiddlewares,\n ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware>\n >\n inputValidator?: ConstrainValidator<TRegister, TMethod, TInputValidator>\n extractedFn?: CompiledFetcherFn<TRegister, TResponse>\n serverFn?: ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse\n >\n functionId: string\n}\n\nexport type ValidateValidatorInput<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> = TMethod extends 'POST'\n ? ResolveValidatorInput<TInputValidator> extends FormData\n ? ResolveValidatorInput<TInputValidator>\n : ValidateSerializable<\n ResolveValidatorInput<TInputValidator>,\n RegisteredSerializableInput<TRegister>\n >\n : ValidateSerializable<\n ResolveValidatorInput<TInputValidator>,\n RegisteredSerializableInput<TRegister>\n >\n\nexport type ValidateValidator<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> =\n ValidateValidatorInput<\n TRegister,\n TMethod,\n TInputValidator\n > extends infer TInput\n ? Validator<TInput, any>\n : never\n\nexport type ConstrainValidator<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> =\n | (unknown extends TInputValidator\n ? TInputValidator\n : ResolveValidatorInput<TInputValidator> extends ValidateValidator<\n TRegister,\n TMethod,\n TInputValidator\n >\n ? TInputValidator\n : never)\n | ValidateValidator<TRegister, TMethod, TInputValidator>\n\nexport type AppendMiddlewares<TMiddlewares, TNewMiddlewares> =\n TMiddlewares extends ReadonlyArray<any>\n ? TNewMiddlewares extends ReadonlyArray<any>\n ? readonly [...TMiddlewares, ...TNewMiddlewares]\n : TMiddlewares\n : TNewMiddlewares\n\nexport interface ServerFnMiddleware<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> {\n middleware: <const TNewMiddlewares>(\n middlewares: Constrain<\n TNewMiddlewares,\n ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware | AnyServerFn>\n >,\n ) => ServerFnAfterMiddleware<\n TRegister,\n TMethod,\n AppendMiddlewares<TMiddlewares, TNewMiddlewares>,\n TInputValidator\n >\n}\n\nexport interface ServerFnAfterMiddleware<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, TMiddlewares, undefined>,\n ServerFnValidator<TRegister, TMethod, TMiddlewares>,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {\n <TNewMethod extends Method = TMethod>(options?: {\n method?: TNewMethod\n }): ServerFnAfterMiddleware<\n TRegister,\n TNewMethod,\n TMiddlewares,\n TInputValidator\n >\n}\n\nexport type ValidatorFn<TRegister, TMethod extends Method, TMiddlewares> = <\n TInputValidator,\n>(\n inputValidator: ConstrainValidator<TRegister, TMethod, TInputValidator>,\n) => ServerFnAfterValidator<TRegister, TMethod, TMiddlewares, TInputValidator>\n\nexport interface ServerFnValidator<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n> {\n inputValidator: ValidatorFn<TRegister, TMethod, TMiddlewares>\n}\n\nexport interface ServerFnAfterValidator<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, TMiddlewares, TInputValidator>,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}\n\nexport interface ServerFnAfterTyper<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}\n\n// Handler\nexport interface ServerFnHandler<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> {\n handler: <TNewResponse>(\n fn?: ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TNewResponse\n >,\n ) => Fetcher<TMiddlewares, TInputValidator, TNewResponse>\n}\n\nexport interface ServerFnBuilder<TRegister, TMethod extends Method = 'GET'>\n extends ServerFnWithTypes<\n TRegister,\n TMethod,\n undefined,\n undefined,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, undefined, undefined>,\n ServerFnValidator<TRegister, TMethod, undefined>,\n ServerFnHandler<TRegister, TMethod, undefined, undefined> {\n options: ServerFnBaseOptions<\n TRegister,\n TMethod,\n unknown,\n undefined,\n undefined\n >\n}\n\nexport interface ServerFnWithTypes<\n in out TRegister,\n in out TMethod extends Method,\n in out TMiddlewares,\n in out TInputValidator,\n in out TResponse,\n> {\n '~types': ServerFnTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse\n >\n options: ServerFnBaseOptions<\n TRegister,\n TMethod,\n unknown,\n undefined,\n undefined\n >\n [TSS_SERVER_FUNCTION_FACTORY]: true\n}\n\nexport type AnyServerFn = ServerFnWithTypes<any, any, any, any, any>\n\nexport interface ServerFnTypes<\n in out TRegister,\n in out TMethod extends Method,\n in out TMiddlewares,\n in out TInputValidator,\n in out TResponse,\n> {\n method: TMethod\n middlewares: TMiddlewares\n inputValidator: TInputValidator\n response: TResponse\n allServerContext: AssignAllServerFnContext<TRegister, TMiddlewares>\n allInput: IntersectAllValidatorInputs<TMiddlewares, TInputValidator>\n allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>\n}\n\nexport function flattenMiddlewares(\n middlewares: Array<AnyFunctionMiddleware | AnyRequestMiddleware>,\n): Array<AnyFunctionMiddleware | AnyRequestMiddleware> {\n const seen = new Set<AnyFunctionMiddleware | AnyRequestMiddleware>()\n const flattened: Array<AnyFunctionMiddleware | AnyRequestMiddleware> = []\n\n const recurse = (\n middleware: Array<AnyFunctionMiddleware | AnyRequestMiddleware>,\n ) => {\n middleware.forEach((m) => {\n if (m.options.middleware) {\n recurse(m.options.middleware)\n }\n\n if (!seen.has(m)) {\n seen.add(m)\n flattened.push(m)\n }\n })\n }\n\n recurse(middlewares)\n\n return flattened\n}\n\nexport type ServerFnMiddlewareOptions = {\n method: Method\n data: any\n headers?: HeadersInit\n signal?: AbortSignal\n sendContext?: any\n context?: any\n functionId: string\n}\n\nexport type ServerFnMiddlewareResult = ServerFnMiddlewareOptions & {\n result?: unknown\n error?: unknown\n}\n\nexport type NextFn = (\n ctx: ServerFnMiddlewareResult,\n) => Promise<ServerFnMiddlewareResult>\n\nexport type MiddlewareFn = (\n ctx: ServerFnMiddlewareOptions & {\n next: NextFn\n },\n) => Promise<ServerFnMiddlewareResult>\n\nexport const applyMiddleware = async (\n middlewareFn: MiddlewareFn,\n ctx: ServerFnMiddlewareOptions,\n nextFn: NextFn,\n) => {\n return middlewareFn({\n ...ctx,\n next: (async (\n userCtx: ServerFnMiddlewareResult | undefined = {} as any,\n ) => {\n // Return the next middleware\n return nextFn({\n ...ctx,\n ...userCtx,\n context: {\n ...ctx.context,\n ...userCtx.context,\n },\n sendContext: {\n ...ctx.sendContext,\n ...(userCtx.sendContext ?? {}),\n },\n headers: mergeHeaders(ctx.headers, userCtx.headers),\n result:\n userCtx.result !== undefined\n ? userCtx.result\n : userCtx instanceof Response\n ? userCtx\n : (ctx as any).result,\n error: userCtx.error ?? (ctx as any).error,\n })\n }) as any,\n } as any)\n}\n\nexport function execValidator(\n validator: AnyValidator,\n input: unknown,\n): unknown {\n if (validator == null) return {}\n\n if ('~standard' in validator) {\n const result = validator['~standard'].validate(input)\n\n if (result instanceof Promise)\n throw new Error('Async validation not supported')\n\n if (result.issues)\n throw new Error(JSON.stringify(result.issues, undefined, 2))\n\n return result.value\n }\n\n if ('parse' in validator) {\n return validator.parse(input)\n }\n\n if (typeof validator === 'function') {\n return validator(input)\n }\n\n throw new Error('Invalid validator type!')\n}\n\nfunction serverFnBaseToMiddleware(\n options: ServerFnBaseOptions<any, any, any, any, any>,\n): AnyFunctionMiddleware {\n return {\n '~types': undefined!,\n options: {\n inputValidator: options.inputValidator,\n client: async ({ next, sendContext, ...ctx }) => {\n const payload = {\n ...ctx,\n // switch the sendContext over to context\n context: sendContext,\n } as any\n\n // Execute the extracted function\n // but not before serializing the context\n const res = await options.extractedFn?.(payload)\n\n return next(res) as unknown as FunctionMiddlewareClientFnResult<\n any,\n any,\n any\n >\n },\n server: async ({ next, ...ctx }) => {\n // Execute the server function\n const result = await options.serverFn?.(ctx as TODO)\n\n return next({\n ...ctx,\n result,\n } as any) as unknown as FunctionMiddlewareServerFnResult<\n any,\n any,\n any,\n any,\n any\n >\n },\n },\n }\n}\n"],"names":["res","options"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createServerFn.js","sources":["../../src/createServerFn.ts"],"sourcesContent":["import { mergeHeaders } from '@tanstack/router-core/ssr/client'\n\nimport { isRedirect, parseRedirect } from '@tanstack/router-core'\nimport { TSS_SERVER_FUNCTION_FACTORY } from './constants'\nimport { getStartOptions } from './getStartOptions'\nimport { getStartContextServerOnly } from './getStartContextServerOnly'\nimport { createNullProtoObject, safeObjectMerge } from './safeObjectMerge'\nimport type {\n AnyValidator,\n Constrain,\n Expand,\n Register,\n RegisteredSerializableInput,\n ResolveValidatorInput,\n ValidateSerializable,\n ValidateSerializableInput,\n Validator,\n} from '@tanstack/router-core'\nimport type { TSS_SERVER_FUNCTION } from './constants'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AssignAllServerFnContext,\n FunctionMiddlewareClientFnResult,\n FunctionMiddlewareServerFnResult,\n IntersectAllValidatorInputs,\n IntersectAllValidatorOutputs,\n} from './createMiddleware'\n\ntype TODO = any\n\nexport type CreateServerFn<TRegister> = <\n TMethod extends Method,\n TResponse = unknown,\n TMiddlewares = undefined,\n TInputValidator = undefined,\n>(\n options?: {\n method?: TMethod\n },\n __opts?: ServerFnBaseOptions<\n TRegister,\n TMethod,\n TResponse,\n TMiddlewares,\n TInputValidator\n >,\n) => ServerFnBuilder<TRegister, TMethod>\n\nexport const createServerFn: CreateServerFn<Register> = (options, __opts) => {\n const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<\n any,\n any,\n any,\n any,\n any\n >\n\n if (typeof resolvedOptions.method === 'undefined') {\n resolvedOptions.method = 'GET' as Method\n }\n\n const res: ServerFnBuilder<Register, Method> = {\n options: resolvedOptions as any,\n middleware: (middleware) => {\n // multiple calls to `middleware()` merge the middlewares with the previously supplied ones\n // this is primarily useful for letting users create their own abstractions on top of `createServerFn`\n\n const newMiddleware = [...(resolvedOptions.middleware || [])]\n middleware.map((m) => {\n if (TSS_SERVER_FUNCTION_FACTORY in m) {\n if (m.options.middleware) {\n newMiddleware.push(...m.options.middleware)\n }\n } else {\n newMiddleware.push(m)\n }\n })\n\n const newOptions = {\n ...resolvedOptions,\n middleware: newMiddleware,\n }\n const res = createServerFn(undefined, newOptions) as any\n res[TSS_SERVER_FUNCTION_FACTORY] = true\n return res\n },\n inputValidator: (inputValidator) => {\n const newOptions = { ...resolvedOptions, inputValidator }\n return createServerFn(undefined, newOptions) as any\n },\n handler: (...args) => {\n // This function signature changes due to AST transformations\n // in the babel plugin. We need to cast it to the correct\n // function signature post-transformation\n const [extractedFn, serverFn] = args as unknown as [\n CompiledFetcherFn<Register, any>,\n ServerFn<Register, Method, any, any, any>,\n ]\n\n // Keep the original function around so we can use it\n // in the server environment\n const newOptions = { ...resolvedOptions, extractedFn, serverFn }\n\n const resolvedMiddleware = [\n ...(newOptions.middleware || []),\n serverFnBaseToMiddleware(newOptions),\n ]\n\n // We want to make sure the new function has the same\n // properties as the original function\n\n return Object.assign(\n async (opts?: CompiledFetcherFnOptions) => {\n // Start by executing the client-side middleware chain\n const result = await executeMiddleware(resolvedMiddleware, 'client', {\n ...extractedFn,\n ...newOptions,\n data: opts?.data as any,\n headers: opts?.headers,\n signal: opts?.signal,\n context: createNullProtoObject(),\n })\n\n const redirect = parseRedirect(result.error)\n if (redirect) {\n throw redirect\n }\n\n if (result.error) throw result.error\n return result.result\n },\n {\n // This copies over the URL, function ID\n ...extractedFn,\n // The extracted function on the server-side calls\n // this function\n __executeServer: async (opts: any, signal: AbortSignal) => {\n const startContext = getStartContextServerOnly()\n const serverContextAfterGlobalMiddlewares =\n startContext.contextAfterGlobalMiddlewares\n // Use safeObjectMerge for opts.context which comes from client\n const ctx = {\n ...extractedFn,\n ...opts,\n context: safeObjectMerge(\n serverContextAfterGlobalMiddlewares,\n opts.context,\n ),\n signal,\n request: startContext.request,\n }\n\n const result = await executeMiddleware(\n resolvedMiddleware,\n 'server',\n ctx,\n ).then((d) => ({\n // Only send the result and sendContext back to the client\n result: d.result,\n error: d.error,\n context: d.sendContext,\n }))\n\n return result\n },\n },\n ) as any\n },\n } as ServerFnBuilder<Register, Method>\n const fun = (options?: { method?: Method }) => {\n const newOptions = {\n ...resolvedOptions,\n ...options,\n }\n return createServerFn(undefined, newOptions) as any\n }\n return Object.assign(fun, res) as any\n}\n\nexport async function executeMiddleware(\n middlewares: Array<AnyFunctionMiddleware | AnyRequestMiddleware>,\n env: 'client' | 'server',\n opts: ServerFnMiddlewareOptions,\n): Promise<ServerFnMiddlewareResult> {\n const globalMiddlewares = getStartOptions()?.functionMiddleware || []\n let flattenedMiddlewares = flattenMiddlewares([\n ...globalMiddlewares,\n ...middlewares,\n ])\n\n // On server, filter out middlewares that already executed in the request phase\n // to prevent duplicate execution (issue #5239)\n if (env === 'server') {\n const startContext = getStartContextServerOnly({ throwIfNotFound: false })\n if (startContext?.executedRequestMiddlewares) {\n flattenedMiddlewares = flattenedMiddlewares.filter(\n (m) => !startContext.executedRequestMiddlewares.has(m),\n )\n }\n }\n\n const callNextMiddleware: NextFn = async (ctx) => {\n // Get the next middleware\n const nextMiddleware = flattenedMiddlewares.shift()\n\n // If there are no more middlewares, return the context\n if (!nextMiddleware) {\n return ctx\n }\n\n // Execute the middleware\n try {\n if (\n 'inputValidator' in nextMiddleware.options &&\n nextMiddleware.options.inputValidator &&\n env === 'server'\n ) {\n // Execute the middleware's input function\n ctx.data = await execValidator(\n nextMiddleware.options.inputValidator,\n ctx.data,\n )\n }\n\n let middlewareFn: MiddlewareFn | undefined = undefined\n if (env === 'client') {\n if ('client' in nextMiddleware.options) {\n middlewareFn = nextMiddleware.options.client as\n | MiddlewareFn\n | undefined\n }\n }\n // env === 'server'\n else if ('server' in nextMiddleware.options) {\n middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined\n }\n\n if (middlewareFn) {\n const userNext = async (\n userCtx: ServerFnMiddlewareResult | undefined = {} as any,\n ) => {\n // Return the next middleware\n // Use safeObjectMerge for context objects to prevent prototype pollution\n const nextCtx = {\n ...ctx,\n ...userCtx,\n context: safeObjectMerge(ctx.context, userCtx.context),\n sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),\n headers: mergeHeaders(ctx.headers, userCtx.headers),\n result:\n userCtx.result !== undefined\n ? userCtx.result\n : userCtx instanceof Response\n ? userCtx\n : (ctx as any).result,\n error: userCtx.error ?? (ctx as any).error,\n }\n\n try {\n return await callNextMiddleware(nextCtx)\n } catch (error: any) {\n return {\n ...nextCtx,\n error,\n }\n }\n }\n\n // Execute the middleware\n const result = await middlewareFn({\n ...ctx,\n next: userNext as any,\n } as any)\n\n // If result is NOT a ctx object, we need to return it as\n // the { result }\n if (isRedirect(result)) {\n return {\n ...ctx,\n error: result,\n }\n }\n\n if (result instanceof Response) {\n return {\n ...ctx,\n result,\n }\n }\n\n if (!(result as any)) {\n throw new Error(\n 'User middleware returned undefined. You must call next() or return a result in your middlewares.',\n )\n }\n\n return result\n }\n\n return callNextMiddleware(ctx)\n } catch (error: any) {\n return {\n ...ctx,\n error,\n }\n }\n }\n\n // Start the middleware chain\n return callNextMiddleware({\n ...opts,\n headers: opts.headers || {},\n sendContext: opts.sendContext || {},\n context: opts.context || createNullProtoObject(),\n })\n}\n\nexport type CompiledFetcherFnOptions = {\n method: Method\n data: unknown\n headers?: HeadersInit\n signal?: AbortSignal\n context?: any\n}\n\nexport type Fetcher<TMiddlewares, TInputValidator, TResponse> =\n undefined extends IntersectAllValidatorInputs<TMiddlewares, TInputValidator>\n ? OptionalFetcher<TMiddlewares, TInputValidator, TResponse>\n : RequiredFetcher<TMiddlewares, TInputValidator, TResponse>\n\nexport interface FetcherBase {\n [TSS_SERVER_FUNCTION]: true\n url: string\n __executeServer: (opts: {\n method: Method\n data: unknown\n headers?: HeadersInit\n context?: any\n signal: AbortSignal\n }) => Promise<unknown>\n}\n\nexport interface OptionalFetcher<TMiddlewares, TInputValidator, TResponse>\n extends FetcherBase {\n (\n options?: OptionalFetcherDataOptions<TMiddlewares, TInputValidator>,\n ): Promise<Awaited<TResponse>>\n}\n\nexport interface RequiredFetcher<TMiddlewares, TInputValidator, TResponse>\n extends FetcherBase {\n (\n opts: RequiredFetcherDataOptions<TMiddlewares, TInputValidator>,\n ): Promise<Awaited<TResponse>>\n}\n\nexport type FetcherBaseOptions = {\n headers?: HeadersInit\n signal?: AbortSignal\n}\n\nexport interface OptionalFetcherDataOptions<TMiddlewares, TInputValidator>\n extends FetcherBaseOptions {\n data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>\n}\n\nexport interface RequiredFetcherDataOptions<TMiddlewares, TInputValidator>\n extends FetcherBaseOptions {\n data: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>\n}\n\nexport type RscStream<T> = {\n __cacheState: T\n}\n\nexport type Method = 'GET' | 'POST'\n\nexport type ServerFnReturnType<TRegister, TResponse> =\n TResponse extends PromiseLike<infer U>\n ? Promise<ServerFnReturnType<TRegister, U>>\n : TResponse extends Response\n ? TResponse\n : ValidateSerializableInput<TRegister, TResponse>\n\nexport type ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse,\n> = (\n ctx: ServerFnCtx<TRegister, TMiddlewares, TInputValidator>,\n) => ServerFnReturnType<TRegister, TResponse>\n\nexport interface ServerFnCtx<TRegister, TMiddlewares, TInputValidator> {\n data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>>\n context: Expand<AssignAllServerFnContext<TRegister, TMiddlewares, {}>>\n signal: AbortSignal\n}\n\nexport type CompiledFetcherFn<TRegister, TResponse> = {\n (\n opts: CompiledFetcherFnOptions & ServerFnBaseOptions<TRegister, Method>,\n ): Promise<TResponse>\n url: string\n}\n\nexport type ServerFnBaseOptions<\n TRegister,\n TMethod extends Method = 'GET',\n TResponse = unknown,\n TMiddlewares = unknown,\n TInputValidator = unknown,\n> = {\n method: TMethod\n middleware?: Constrain<\n TMiddlewares,\n ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware>\n >\n inputValidator?: ConstrainValidator<TRegister, TMethod, TInputValidator>\n extractedFn?: CompiledFetcherFn<TRegister, TResponse>\n serverFn?: ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse\n >\n functionId: string\n}\n\nexport type ValidateValidatorInput<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> = TMethod extends 'POST'\n ? ResolveValidatorInput<TInputValidator> extends FormData\n ? ResolveValidatorInput<TInputValidator>\n : ValidateSerializable<\n ResolveValidatorInput<TInputValidator>,\n RegisteredSerializableInput<TRegister>\n >\n : ValidateSerializable<\n ResolveValidatorInput<TInputValidator>,\n RegisteredSerializableInput<TRegister>\n >\n\nexport type ValidateValidator<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> =\n ValidateValidatorInput<\n TRegister,\n TMethod,\n TInputValidator\n > extends infer TInput\n ? Validator<TInput, any>\n : never\n\nexport type ConstrainValidator<\n TRegister,\n TMethod extends Method,\n TInputValidator,\n> =\n | (unknown extends TInputValidator\n ? TInputValidator\n : ResolveValidatorInput<TInputValidator> extends ValidateValidator<\n TRegister,\n TMethod,\n TInputValidator\n >\n ? TInputValidator\n : never)\n | ValidateValidator<TRegister, TMethod, TInputValidator>\n\nexport type AppendMiddlewares<TMiddlewares, TNewMiddlewares> =\n TMiddlewares extends ReadonlyArray<any>\n ? TNewMiddlewares extends ReadonlyArray<any>\n ? readonly [...TMiddlewares, ...TNewMiddlewares]\n : TMiddlewares\n : TNewMiddlewares\n\nexport interface ServerFnMiddleware<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> {\n middleware: <const TNewMiddlewares>(\n middlewares: Constrain<\n TNewMiddlewares,\n ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware | AnyServerFn>\n >,\n ) => ServerFnAfterMiddleware<\n TRegister,\n TMethod,\n AppendMiddlewares<TMiddlewares, TNewMiddlewares>,\n TInputValidator\n >\n}\n\nexport interface ServerFnAfterMiddleware<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, TMiddlewares, undefined>,\n ServerFnValidator<TRegister, TMethod, TMiddlewares>,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {\n <TNewMethod extends Method = TMethod>(options?: {\n method?: TNewMethod\n }): ServerFnAfterMiddleware<\n TRegister,\n TNewMethod,\n TMiddlewares,\n TInputValidator\n >\n}\n\nexport type ValidatorFn<TRegister, TMethod extends Method, TMiddlewares> = <\n TInputValidator,\n>(\n inputValidator: ConstrainValidator<TRegister, TMethod, TInputValidator>,\n) => ServerFnAfterValidator<TRegister, TMethod, TMiddlewares, TInputValidator>\n\nexport interface ServerFnValidator<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n> {\n inputValidator: ValidatorFn<TRegister, TMethod, TMiddlewares>\n}\n\nexport interface ServerFnAfterValidator<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, TMiddlewares, TInputValidator>,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}\n\nexport interface ServerFnAfterTyper<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> extends ServerFnWithTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n undefined\n >,\n ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}\n\n// Handler\nexport interface ServerFnHandler<\n TRegister,\n TMethod extends Method,\n TMiddlewares,\n TInputValidator,\n> {\n handler: <TNewResponse>(\n fn?: ServerFn<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TNewResponse\n >,\n ) => Fetcher<TMiddlewares, TInputValidator, TNewResponse>\n}\n\nexport interface ServerFnBuilder<TRegister, TMethod extends Method = 'GET'>\n extends ServerFnWithTypes<\n TRegister,\n TMethod,\n undefined,\n undefined,\n undefined\n >,\n ServerFnMiddleware<TRegister, TMethod, undefined, undefined>,\n ServerFnValidator<TRegister, TMethod, undefined>,\n ServerFnHandler<TRegister, TMethod, undefined, undefined> {\n options: ServerFnBaseOptions<\n TRegister,\n TMethod,\n unknown,\n undefined,\n undefined\n >\n}\n\nexport interface ServerFnWithTypes<\n in out TRegister,\n in out TMethod extends Method,\n in out TMiddlewares,\n in out TInputValidator,\n in out TResponse,\n> {\n '~types': ServerFnTypes<\n TRegister,\n TMethod,\n TMiddlewares,\n TInputValidator,\n TResponse\n >\n options: ServerFnBaseOptions<\n TRegister,\n TMethod,\n unknown,\n undefined,\n undefined\n >\n [TSS_SERVER_FUNCTION_FACTORY]: true\n}\n\nexport type AnyServerFn = ServerFnWithTypes<any, any, any, any, any>\n\nexport interface ServerFnTypes<\n in out TRegister,\n in out TMethod extends Method,\n in out TMiddlewares,\n in out TInputValidator,\n in out TResponse,\n> {\n method: TMethod\n middlewares: TMiddlewares\n inputValidator: TInputValidator\n response: TResponse\n allServerContext: AssignAllServerFnContext<TRegister, TMiddlewares>\n allInput: IntersectAllValidatorInputs<TMiddlewares, TInputValidator>\n allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>\n}\n\nexport function flattenMiddlewares<\n T extends AnyFunctionMiddleware | AnyRequestMiddleware,\n>(middlewares: Array<T>, maxDepth: number = 100): Array<T> {\n const seen = new Set<T>()\n const flattened: Array<T> = []\n\n const recurse = (middleware: Array<T>, depth: number) => {\n if (depth > maxDepth) {\n throw new Error(\n `Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`,\n )\n }\n middleware.forEach((m) => {\n if (m.options.middleware) {\n recurse(m.options.middleware as Array<T>, depth + 1)\n }\n\n if (!seen.has(m)) {\n seen.add(m)\n flattened.push(m)\n }\n })\n }\n\n recurse(middlewares, 0)\n\n return flattened\n}\n\nexport type ServerFnMiddlewareOptions = {\n method: Method\n data: any\n headers?: HeadersInit\n signal?: AbortSignal\n sendContext?: any\n context?: any\n functionId: string\n}\n\nexport type ServerFnMiddlewareResult = ServerFnMiddlewareOptions & {\n result?: unknown\n error?: unknown\n}\n\nexport type NextFn = (\n ctx: ServerFnMiddlewareResult,\n) => Promise<ServerFnMiddlewareResult>\n\nexport type MiddlewareFn = (\n ctx: ServerFnMiddlewareOptions & {\n next: NextFn\n },\n) => Promise<ServerFnMiddlewareResult>\n\nexport function execValidator(\n validator: AnyValidator,\n input: unknown,\n): unknown {\n if (validator == null) return {}\n\n if ('~standard' in validator) {\n const result = validator['~standard'].validate(input)\n\n if (result instanceof Promise)\n throw new Error('Async validation not supported')\n\n if (result.issues)\n throw new Error(JSON.stringify(result.issues, undefined, 2))\n\n return result.value\n }\n\n if ('parse' in validator) {\n return validator.parse(input)\n }\n\n if (typeof validator === 'function') {\n return validator(input)\n }\n\n throw new Error('Invalid validator type!')\n}\n\nfunction serverFnBaseToMiddleware(\n options: ServerFnBaseOptions<any, any, any, any, any>,\n): AnyFunctionMiddleware {\n return {\n '~types': undefined!,\n options: {\n inputValidator: options.inputValidator,\n client: async ({ next, sendContext, ...ctx }) => {\n const payload = {\n ...ctx,\n // switch the sendContext over to context\n context: sendContext,\n } as any\n\n // Execute the extracted function\n // but not before serializing the context\n const res = await options.extractedFn?.(payload)\n\n return next(res) as unknown as FunctionMiddlewareClientFnResult<\n any,\n any,\n any\n >\n },\n server: async ({ next, ...ctx }) => {\n // Execute the server function\n const result = await options.serverFn?.(ctx as TODO)\n\n return next({\n ...ctx,\n result,\n } as any) as unknown as FunctionMiddlewareServerFnResult<\n any,\n any,\n any,\n any,\n any\n >\n },\n },\n }\n}\n"],"names":["res","options"],"mappings":";;;;;;AAiDO,MAAM,iBAA2C,CAAC,SAAS,WAAW;AAC3E,QAAM,kBAAmB,UAAU,WAAW,CAAA;AAQ9C,MAAI,OAAO,gBAAgB,WAAW,aAAa;AACjD,oBAAgB,SAAS;AAAA,EAC3B;AAEA,QAAM,MAAyC;AAAA,IAC7C,SAAS;AAAA,IACT,YAAY,CAAC,eAAe;AAI1B,YAAM,gBAAgB,CAAC,GAAI,gBAAgB,cAAc,CAAA,CAAG;AAC5D,iBAAW,IAAI,CAAC,MAAM;AACpB,YAAI,+BAA+B,GAAG;AACpC,cAAI,EAAE,QAAQ,YAAY;AACxB,0BAAc,KAAK,GAAG,EAAE,QAAQ,UAAU;AAAA,UAC5C;AAAA,QACF,OAAO;AACL,wBAAc,KAAK,CAAC;AAAA,QACtB;AAAA,MACF,CAAC;AAED,YAAM,aAAa;AAAA,QACjB,GAAG;AAAA,QACH,YAAY;AAAA,MAAA;AAEd,YAAMA,OAAM,eAAe,QAAW,UAAU;AAChDA,WAAI,2BAA2B,IAAI;AACnC,aAAOA;AAAAA,IACT;AAAA,IACA,gBAAgB,CAAC,mBAAmB;AAClC,YAAM,aAAa,EAAE,GAAG,iBAAiB,eAAA;AACzC,aAAO,eAAe,QAAW,UAAU;AAAA,IAC7C;AAAA,IACA,SAAS,IAAI,SAAS;AAIpB,YAAM,CAAC,aAAa,QAAQ,IAAI;AAOhC,YAAM,aAAa,EAAE,GAAG,iBAAiB,aAAa,SAAA;AAEtD,YAAM,qBAAqB;AAAA,QACzB,GAAI,WAAW,cAAc,CAAA;AAAA,QAC7B,yBAAyB,UAAU;AAAA,MAAA;AAMrC,aAAO,OAAO;AAAA,QACZ,OAAO,SAAoC;AAEzC,gBAAM,SAAS,MAAM,kBAAkB,oBAAoB,UAAU;AAAA,YACnE,GAAG;AAAA,YACH,GAAG;AAAA,YACH,MAAM,MAAM;AAAA,YACZ,SAAS,MAAM;AAAA,YACf,QAAQ,MAAM;AAAA,YACd,SAAS,sBAAA;AAAA,UAAsB,CAChC;AAED,gBAAM,WAAW,cAAc,OAAO,KAAK;AAC3C,cAAI,UAAU;AACZ,kBAAM;AAAA,UACR;AAEA,cAAI,OAAO,MAAO,OAAM,OAAO;AAC/B,iBAAO,OAAO;AAAA,QAChB;AAAA,QACA;AAAA;AAAA,UAEE,GAAG;AAAA;AAAA;AAAA,UAGH,iBAAiB,OAAO,MAAW,WAAwB;AACzD,kBAAM,eAAe,0BAAA;AACrB,kBAAM,sCACJ,aAAa;AAEf,kBAAM,MAAM;AAAA,cACV,GAAG;AAAA,cACH,GAAG;AAAA,cACH,SAAS;AAAA,gBACP;AAAA,gBACA,KAAK;AAAA,cAAA;AAAA,cAEP;AAAA,cACA,SAAS,aAAa;AAAA,YAAA;AAGxB,kBAAM,SAAS,MAAM;AAAA,cACnB;AAAA,cACA;AAAA,cACA;AAAA,YAAA,EACA,KAAK,CAAC,OAAO;AAAA;AAAA,cAEb,QAAQ,EAAE;AAAA,cACV,OAAO,EAAE;AAAA,cACT,SAAS,EAAE;AAAA,YAAA,EACX;AAEF,mBAAO;AAAA,UACT;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AAAA,EAAA;AAEF,QAAM,MAAM,CAACC,aAAkC;AAC7C,UAAM,aAAa;AAAA,MACjB,GAAG;AAAA,MACH,GAAGA;AAAAA,IAAA;AAEL,WAAO,eAAe,QAAW,UAAU;AAAA,EAC7C;AACA,SAAO,OAAO,OAAO,KAAK,GAAG;AAC/B;AAEA,eAAsB,kBACpB,aACA,KACA,MACmC;AACnC,QAAM,oBAAoB,mBAAmB,sBAAsB,CAAA;AACnE,MAAI,uBAAuB,mBAAmB;AAAA,IAC5C,GAAG;AAAA,IACH,GAAG;AAAA,EAAA,CACJ;AAID,MAAI,QAAQ,UAAU;AACpB,UAAM,eAAe,0BAA0B,EAAE,iBAAiB,OAAO;AACzE,QAAI,cAAc,4BAA4B;AAC5C,6BAAuB,qBAAqB;AAAA,QAC1C,CAAC,MAAM,CAAC,aAAa,2BAA2B,IAAI,CAAC;AAAA,MAAA;AAAA,IAEzD;AAAA,EACF;AAEA,QAAM,qBAA6B,OAAO,QAAQ;AAEhD,UAAM,iBAAiB,qBAAqB,MAAA;AAG5C,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,IACT;AAGA,QAAI;AACF,UACE,oBAAoB,eAAe,WACnC,eAAe,QAAQ,kBACvB,QAAQ,UACR;AAEA,YAAI,OAAO,MAAM;AAAA,UACf,eAAe,QAAQ;AAAA,UACvB,IAAI;AAAA,QAAA;AAAA,MAER;AAEA,UAAI,eAAyC;AAC7C,UAAI,QAAQ,UAAU;AACpB,YAAI,YAAY,eAAe,SAAS;AACtC,yBAAe,eAAe,QAAQ;AAAA,QAGxC;AAAA,MACF,WAES,YAAY,eAAe,SAAS;AAC3C,uBAAe,eAAe,QAAQ;AAAA,MACxC;AAEA,UAAI,cAAc;AAChB,cAAM,WAAW,OACf,UAAgD,OAC7C;AAGH,gBAAM,UAAU;AAAA,YACd,GAAG;AAAA,YACH,GAAG;AAAA,YACH,SAAS,gBAAgB,IAAI,SAAS,QAAQ,OAAO;AAAA,YACrD,aAAa,gBAAgB,IAAI,aAAa,QAAQ,WAAW;AAAA,YACjE,SAAS,aAAa,IAAI,SAAS,QAAQ,OAAO;AAAA,YAClD,QACE,QAAQ,WAAW,SACf,QAAQ,SACR,mBAAmB,WACjB,UACC,IAAY;AAAA,YACrB,OAAO,QAAQ,SAAU,IAAY;AAAA,UAAA;AAGvC,cAAI;AACF,mBAAO,MAAM,mBAAmB,OAAO;AAAA,UACzC,SAAS,OAAY;AACnB,mBAAO;AAAA,cACL,GAAG;AAAA,cACH;AAAA,YAAA;AAAA,UAEJ;AAAA,QACF;AAGA,cAAM,SAAS,MAAM,aAAa;AAAA,UAChC,GAAG;AAAA,UACH,MAAM;AAAA,QAAA,CACA;AAIR,YAAI,WAAW,MAAM,GAAG;AACtB,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,OAAO;AAAA,UAAA;AAAA,QAEX;AAEA,YAAI,kBAAkB,UAAU;AAC9B,iBAAO;AAAA,YACL,GAAG;AAAA,YACH;AAAA,UAAA;AAAA,QAEJ;AAEA,YAAI,CAAE,QAAgB;AACpB,gBAAM,IAAI;AAAA,YACR;AAAA,UAAA;AAAA,QAEJ;AAEA,eAAO;AAAA,MACT;AAEA,aAAO,mBAAmB,GAAG;AAAA,IAC/B,SAAS,OAAY;AACnB,aAAO;AAAA,QACL,GAAG;AAAA,QACH;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAGA,SAAO,mBAAmB;AAAA,IACxB,GAAG;AAAA,IACH,SAAS,KAAK,WAAW,CAAA;AAAA,IACzB,aAAa,KAAK,eAAe,CAAA;AAAA,IACjC,SAAS,KAAK,WAAW,sBAAA;AAAA,EAAsB,CAChD;AACH;AA+UO,SAAS,mBAEd,aAAuB,WAAmB,KAAe;AACzD,QAAM,2BAAW,IAAA;AACjB,QAAM,YAAsB,CAAA;AAE5B,QAAM,UAAU,CAAC,YAAsB,UAAkB;AACvD,QAAI,QAAQ,UAAU;AACpB,YAAM,IAAI;AAAA,QACR,gDAAgD,QAAQ;AAAA,MAAA;AAAA,IAE5D;AACA,eAAW,QAAQ,CAAC,MAAM;AACxB,UAAI,EAAE,QAAQ,YAAY;AACxB,gBAAQ,EAAE,QAAQ,YAAwB,QAAQ,CAAC;AAAA,MACrD;AAEA,UAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,aAAK,IAAI,CAAC;AACV,kBAAU,KAAK,CAAC;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,UAAQ,aAAa,CAAC;AAEtB,SAAO;AACT;AA2BO,SAAS,cACd,WACA,OACS;AACT,MAAI,aAAa,KAAM,QAAO,CAAA;AAE9B,MAAI,eAAe,WAAW;AAC5B,UAAM,SAAS,UAAU,WAAW,EAAE,SAAS,KAAK;AAEpD,QAAI,kBAAkB;AACpB,YAAM,IAAI,MAAM,gCAAgC;AAElD,QAAI,OAAO;AACT,YAAM,IAAI,MAAM,KAAK,UAAU,OAAO,QAAQ,QAAW,CAAC,CAAC;AAE7D,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI,WAAW,WAAW;AACxB,WAAO,UAAU,MAAM,KAAK;AAAA,EAC9B;AAEA,MAAI,OAAO,cAAc,YAAY;AACnC,WAAO,UAAU,KAAK;AAAA,EACxB;AAEA,QAAM,IAAI,MAAM,yBAAyB;AAC3C;AAEA,SAAS,yBACP,SACuB;AACvB,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,MACP,gBAAgB,QAAQ;AAAA,MACxB,QAAQ,OAAO,EAAE,MAAM,aAAa,GAAG,UAAU;AAC/C,cAAM,UAAU;AAAA,UACd,GAAG;AAAA;AAAA,UAEH,SAAS;AAAA,QAAA;AAKX,cAAM,MAAM,MAAM,QAAQ,cAAc,OAAO;AAE/C,eAAO,KAAK,GAAG;AAAA,MAKjB;AAAA,MACA,QAAQ,OAAO,EAAE,MAAM,GAAG,UAAU;AAElC,cAAM,SAAS,MAAM,QAAQ,WAAW,GAAW;AAEnD,eAAO,KAAK;AAAA,UACV,GAAG;AAAA,UACH;AAAA,QAAA,CACM;AAAA,MAOV;AAAA,IAAA;AAAA,EACF;AAEJ;"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ export { createIsomorphicFn, createServerOnlyFn, createClientOnlyFn, type Isomor
|
|
|
4
4
|
export { createServerFn } from './createServerFn.js';
|
|
5
5
|
export { createMiddleware, type IntersectAllValidatorInputs, type IntersectAllValidatorOutputs, type FunctionMiddlewareServerFn, type AnyFunctionMiddleware, type FunctionMiddlewareOptions, type FunctionMiddlewareWithTypes, type FunctionMiddlewareValidator, type FunctionMiddlewareServer, type FunctionMiddlewareAfterClient, type FunctionMiddlewareAfterServer, type FunctionMiddleware, type FunctionMiddlewareAfterMiddleware, type FunctionMiddlewareClientFnOptions, type FunctionMiddlewareClientFnResult, type FunctionMiddlewareClientNextFn, type FunctionClientResultWithContext, type AssignAllClientContextBeforeNext, type AssignAllMiddleware, type FunctionMiddlewareAfterValidator, type FunctionMiddlewareClientFn, type FunctionMiddlewareServerFnResult, type FunctionMiddlewareClient, type FunctionMiddlewareServerFnOptions, type FunctionMiddlewareServerNextFn, type FunctionServerResultWithContext, type AnyRequestMiddleware, type RequestMiddlewareOptions, type RequestMiddlewareWithTypes, type RequestMiddlewareServer, type RequestMiddlewareAfterServer, type RequestMiddleware, type RequestMiddlewareAfterMiddleware, type RequestServerFn, type RequestMiddlewareServerFnResult, type RequestServerOptions, type RequestServerNextFn, type RequestServerNextFnOptions, type RequestServerResult, } from './createMiddleware.js';
|
|
6
6
|
export type { CompiledFetcherFnOptions, CompiledFetcherFn, Fetcher, RscStream, FetcherBaseOptions, ServerFn, ServerFnCtx, MiddlewareFn, ServerFnMiddlewareOptions, ServerFnMiddlewareResult, ServerFnBuilder, ServerFnBaseOptions, NextFn, Method, OptionalFetcher, RequiredFetcher, } from './createServerFn.js';
|
|
7
|
-
export {
|
|
8
|
-
export { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_SERIALIZED, X_TSS_RAW_RESPONSE, } from './constants.js';
|
|
7
|
+
export { execValidator, flattenMiddlewares, executeMiddleware, } from './createServerFn.js';
|
|
8
|
+
export { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_SERIALIZED, X_TSS_RAW_RESPONSE, X_TSS_CONTEXT, } from './constants.js';
|
|
9
9
|
export type * from './serverRoute.js';
|
|
10
10
|
export type * from './startEntry.js';
|
|
11
11
|
export { createStart } from './createStart.js';
|
|
@@ -14,3 +14,4 @@ export type { Register } from '@tanstack/router-core';
|
|
|
14
14
|
export { getRouterInstance } from './getRouterInstance.js';
|
|
15
15
|
export { getDefaultSerovalPlugins } from './getDefaultSerovalPlugins.js';
|
|
16
16
|
export { getGlobalStartContext } from './getGlobalStartContext.js';
|
|
17
|
+
export { safeObjectMerge, createNullProtoObject } from './safeObjectMerge.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { hydrate, json, mergeHeaders } from "@tanstack/router-core/ssr/client";
|
|
2
2
|
import { createClientOnlyFn, createIsomorphicFn, createServerOnlyFn } from "@tanstack/start-fn-stubs";
|
|
3
|
-
import {
|
|
3
|
+
import { createServerFn, execValidator, executeMiddleware, flattenMiddlewares } from "./createServerFn.js";
|
|
4
4
|
import { createMiddleware } from "./createMiddleware.js";
|
|
5
|
-
import { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED } from "./constants.js";
|
|
5
|
+
import { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_CONTEXT, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED } from "./constants.js";
|
|
6
6
|
import { createStart } from "./createStart.js";
|
|
7
7
|
import { getRouterInstance } from "./getRouterInstance.js";
|
|
8
8
|
import { getDefaultSerovalPlugins } from "./getDefaultSerovalPlugins.js";
|
|
9
9
|
import { getGlobalStartContext } from "./getGlobalStartContext.js";
|
|
10
|
+
import { createNullProtoObject, safeObjectMerge } from "./safeObjectMerge.js";
|
|
10
11
|
export {
|
|
11
12
|
TSS_FORMDATA_CONTEXT,
|
|
12
13
|
TSS_SERVER_FUNCTION,
|
|
14
|
+
X_TSS_CONTEXT,
|
|
13
15
|
X_TSS_RAW_RESPONSE,
|
|
14
16
|
X_TSS_SERIALIZED,
|
|
15
|
-
applyMiddleware,
|
|
16
17
|
createClientOnlyFn,
|
|
17
18
|
createIsomorphicFn,
|
|
18
19
|
createMiddleware,
|
|
20
|
+
createNullProtoObject,
|
|
19
21
|
createServerFn,
|
|
20
22
|
createServerOnlyFn,
|
|
21
23
|
createStart,
|
|
@@ -27,6 +29,7 @@ export {
|
|
|
27
29
|
getRouterInstance,
|
|
28
30
|
hydrate,
|
|
29
31
|
json,
|
|
30
|
-
mergeHeaders
|
|
32
|
+
mergeHeaders,
|
|
33
|
+
safeObjectMerge
|
|
31
34
|
};
|
|
32
35
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge target and source into a new null-proto object, filtering dangerous keys.
|
|
3
|
+
*/
|
|
4
|
+
export declare function safeObjectMerge<T extends Record<string, unknown>>(target: T | undefined, source: Record<string, unknown> | null | undefined): T;
|
|
5
|
+
/**
|
|
6
|
+
* Create a null-prototype object, optionally copying from source.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createNullProtoObject<T extends object>(source?: T): {
|
|
9
|
+
[K in keyof T]: T[K];
|
|
10
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function isSafeKey(key) {
|
|
2
|
+
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
|
|
3
|
+
}
|
|
4
|
+
function safeObjectMerge(target, source) {
|
|
5
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
6
|
+
if (target) {
|
|
7
|
+
for (const key of Object.keys(target)) {
|
|
8
|
+
if (isSafeKey(key)) result[key] = target[key];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (source && typeof source === "object") {
|
|
12
|
+
for (const key of Object.keys(source)) {
|
|
13
|
+
if (isSafeKey(key)) result[key] = source[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
function createNullProtoObject(source) {
|
|
19
|
+
if (!source) return /* @__PURE__ */ Object.create(null);
|
|
20
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
21
|
+
for (const key of Object.keys(source)) {
|
|
22
|
+
if (isSafeKey(key)) obj[key] = source[key];
|
|
23
|
+
}
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
createNullProtoObject,
|
|
28
|
+
safeObjectMerge
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=safeObjectMerge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeObjectMerge.js","sources":["../../src/safeObjectMerge.ts"],"sourcesContent":["function isSafeKey(key: string): boolean {\n return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'\n}\n\n/**\n * Merge target and source into a new null-proto object, filtering dangerous keys.\n */\nexport function safeObjectMerge<T extends Record<string, unknown>>(\n target: T | undefined,\n source: Record<string, unknown> | null | undefined,\n): T {\n const result = Object.create(null) as T\n if (target) {\n for (const key of Object.keys(target)) {\n if (isSafeKey(key)) result[key as keyof T] = target[key] as T[keyof T]\n }\n }\n if (source && typeof source === 'object') {\n for (const key of Object.keys(source)) {\n if (isSafeKey(key)) result[key as keyof T] = source[key] as T[keyof T]\n }\n }\n return result\n}\n\n/**\n * Create a null-prototype object, optionally copying from source.\n */\nexport function createNullProtoObject<T extends object>(\n source?: T,\n): { [K in keyof T]: T[K] } {\n if (!source) return Object.create(null)\n const obj = Object.create(null)\n for (const key of Object.keys(source)) {\n if (isSafeKey(key)) obj[key] = (source as Record<string, unknown>)[key]\n }\n return obj\n}\n"],"names":[],"mappings":"AAAA,SAAS,UAAU,KAAsB;AACvC,SAAO,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ;AACjE;AAKO,SAAS,gBACd,QACA,QACG;AACH,QAAM,SAAS,uBAAO,OAAO,IAAI;AACjC,MAAI,QAAQ;AACV,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,UAAU,GAAG,UAAU,GAAc,IAAI,OAAO,GAAG;AAAA,IACzD;AAAA,EACF;AACA,MAAI,UAAU,OAAO,WAAW,UAAU;AACxC,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,UAAU,GAAG,UAAU,GAAc,IAAI,OAAO,GAAG;AAAA,IACzD;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,sBACd,QAC0B;AAC1B,MAAI,CAAC,OAAQ,QAAO,uBAAO,OAAO,IAAI;AACtC,QAAM,MAAM,uBAAO,OAAO,IAAI;AAC9B,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,QAAI,UAAU,GAAG,OAAO,GAAG,IAAK,OAAmC,GAAG;AAAA,EACxE;AACA,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-client-core",
|
|
3
|
-
"version": "1.143.
|
|
3
|
+
"version": "1.143.12",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
"seroval": "^1.4.1",
|
|
67
67
|
"tiny-invariant": "^1.3.3",
|
|
68
68
|
"tiny-warning": "^1.0.3",
|
|
69
|
-
"@tanstack/router-core": "1.143.6",
|
|
70
69
|
"@tanstack/start-fn-stubs": "1.143.8",
|
|
71
|
-
"@tanstack/
|
|
70
|
+
"@tanstack/router-core": "1.143.6",
|
|
71
|
+
"@tanstack/start-storage-context": "1.143.12"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"clean": "rimraf ./dist && rimraf ./coverage",
|
|
@@ -25,6 +25,15 @@ function hasOwnProperties(obj: object): boolean {
|
|
|
25
25
|
}
|
|
26
26
|
return false
|
|
27
27
|
}
|
|
28
|
+
// caller =>
|
|
29
|
+
// serverFnFetcher =>
|
|
30
|
+
// client =>
|
|
31
|
+
// server =>
|
|
32
|
+
// fn =>
|
|
33
|
+
// seroval =>
|
|
34
|
+
// client middleware =>
|
|
35
|
+
// serverFnFetcher =>
|
|
36
|
+
// caller
|
|
28
37
|
|
|
29
38
|
export async function serverFnFetcher(
|
|
30
39
|
url: string,
|
|
@@ -43,7 +52,7 @@ export async function serverFnFetcher(
|
|
|
43
52
|
|
|
44
53
|
// Arrange the headers
|
|
45
54
|
const headers = first.headers ? new Headers(first.headers) : new Headers()
|
|
46
|
-
headers.set('x-tsr-
|
|
55
|
+
headers.set('x-tsr-serverFn', 'true')
|
|
47
56
|
|
|
48
57
|
if (type === 'payload') {
|
|
49
58
|
headers.set('accept', 'application/x-ndjson, application/json')
|
|
@@ -146,7 +155,7 @@ async function getFetchBody(
|
|
|
146
155
|
async function getResponse(fn: () => Promise<Response>) {
|
|
147
156
|
let response: Response
|
|
148
157
|
try {
|
|
149
|
-
response = await fn()
|
|
158
|
+
response = await fn() // client => server => fn => server => client
|
|
150
159
|
} catch (error) {
|
|
151
160
|
if (error instanceof Response) {
|
|
152
161
|
response = error
|
|
@@ -159,22 +168,16 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
159
168
|
if (response.headers.get(X_TSS_RAW_RESPONSE) === 'true') {
|
|
160
169
|
return response
|
|
161
170
|
}
|
|
171
|
+
|
|
162
172
|
const contentType = response.headers.get('content-type')
|
|
163
173
|
invariant(contentType, 'expected content-type header to be set')
|
|
164
174
|
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)
|
|
165
|
-
// If the response is not ok, throw an error
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
if (serializedByStart && contentType.includes('application/json')) {
|
|
168
|
-
const jsonPayload = await response.json()
|
|
169
|
-
const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
|
|
170
|
-
throw result
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
throw new Error(await response.text())
|
|
174
|
-
}
|
|
175
175
|
|
|
176
|
+
// If the response is serialized by the start server, we need to process it
|
|
177
|
+
// differently than a normal response.
|
|
176
178
|
if (serializedByStart) {
|
|
177
179
|
let result
|
|
180
|
+
// If it's a stream from the start serializer, process it as such
|
|
178
181
|
if (contentType.includes('application/x-ndjson')) {
|
|
179
182
|
const refs = new Map()
|
|
180
183
|
result = await processServerFnResponse({
|
|
@@ -187,17 +190,22 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
187
190
|
},
|
|
188
191
|
})
|
|
189
192
|
}
|
|
193
|
+
// If it's a JSON response, it can be simpler
|
|
190
194
|
if (contentType.includes('application/json')) {
|
|
191
195
|
const jsonPayload = await response.json()
|
|
192
196
|
result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
|
|
193
197
|
}
|
|
198
|
+
|
|
194
199
|
invariant(result, 'expected result to be resolved')
|
|
195
200
|
if (result instanceof Error) {
|
|
196
201
|
throw result
|
|
197
202
|
}
|
|
203
|
+
|
|
198
204
|
return result
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
// If it wasn't processed by the start serializer, check
|
|
208
|
+
// if it's JSON
|
|
201
209
|
if (contentType.includes('application/json')) {
|
|
202
210
|
const jsonPayload = await response.json()
|
|
203
211
|
const redirect = parseRedirect(jsonPayload)
|
|
@@ -210,6 +218,12 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
210
218
|
return jsonPayload
|
|
211
219
|
}
|
|
212
220
|
|
|
221
|
+
// Otherwise, if it's not OK, throw the content
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error(await response.text())
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Or return the response itself
|
|
213
227
|
return response
|
|
214
228
|
}
|
|
215
229
|
|
package/src/constants.ts
CHANGED
package/src/createServerFn.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { isNotFound, isRedirect } from '@tanstack/router-core'
|
|
2
1
|
import { mergeHeaders } from '@tanstack/router-core/ssr/client'
|
|
3
2
|
|
|
3
|
+
import { isRedirect, parseRedirect } from '@tanstack/router-core'
|
|
4
4
|
import { TSS_SERVER_FUNCTION_FACTORY } from './constants'
|
|
5
5
|
import { getStartOptions } from './getStartOptions'
|
|
6
6
|
import { getStartContextServerOnly } from './getStartContextServerOnly'
|
|
7
|
-
import
|
|
7
|
+
import { createNullProtoObject, safeObjectMerge } from './safeObjectMerge'
|
|
8
8
|
import type {
|
|
9
9
|
AnyValidator,
|
|
10
10
|
Constrain,
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ValidateSerializableInput,
|
|
17
17
|
Validator,
|
|
18
18
|
} from '@tanstack/router-core'
|
|
19
|
+
import type { TSS_SERVER_FUNCTION } from './constants'
|
|
19
20
|
import type {
|
|
20
21
|
AnyFunctionMiddleware,
|
|
21
22
|
AnyRequestMiddleware,
|
|
@@ -112,17 +113,22 @@ export const createServerFn: CreateServerFn<Register> = (options, __opts) => {
|
|
|
112
113
|
return Object.assign(
|
|
113
114
|
async (opts?: CompiledFetcherFnOptions) => {
|
|
114
115
|
// Start by executing the client-side middleware chain
|
|
115
|
-
|
|
116
|
+
const result = await executeMiddleware(resolvedMiddleware, 'client', {
|
|
116
117
|
...extractedFn,
|
|
117
118
|
...newOptions,
|
|
118
119
|
data: opts?.data as any,
|
|
119
120
|
headers: opts?.headers,
|
|
120
121
|
signal: opts?.signal,
|
|
121
|
-
context:
|
|
122
|
-
}).then((d) => {
|
|
123
|
-
if (d.error) throw d.error
|
|
124
|
-
return d.result
|
|
122
|
+
context: createNullProtoObject(),
|
|
125
123
|
})
|
|
124
|
+
|
|
125
|
+
const redirect = parseRedirect(result.error)
|
|
126
|
+
if (redirect) {
|
|
127
|
+
throw redirect
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (result.error) throw result.error
|
|
131
|
+
return result.result
|
|
126
132
|
},
|
|
127
133
|
{
|
|
128
134
|
// This copies over the URL, function ID
|
|
@@ -133,25 +139,30 @@ export const createServerFn: CreateServerFn<Register> = (options, __opts) => {
|
|
|
133
139
|
const startContext = getStartContextServerOnly()
|
|
134
140
|
const serverContextAfterGlobalMiddlewares =
|
|
135
141
|
startContext.contextAfterGlobalMiddlewares
|
|
142
|
+
// Use safeObjectMerge for opts.context which comes from client
|
|
136
143
|
const ctx = {
|
|
137
144
|
...extractedFn,
|
|
138
145
|
...opts,
|
|
139
|
-
context:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
context: safeObjectMerge(
|
|
147
|
+
serverContextAfterGlobalMiddlewares,
|
|
148
|
+
opts.context,
|
|
149
|
+
),
|
|
143
150
|
signal,
|
|
144
151
|
request: startContext.request,
|
|
145
152
|
}
|
|
146
153
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
const result = await executeMiddleware(
|
|
155
|
+
resolvedMiddleware,
|
|
156
|
+
'server',
|
|
157
|
+
ctx,
|
|
158
|
+
).then((d) => ({
|
|
159
|
+
// Only send the result and sendContext back to the client
|
|
160
|
+
result: d.result,
|
|
161
|
+
error: d.error,
|
|
162
|
+
context: d.sendContext,
|
|
163
|
+
}))
|
|
164
|
+
|
|
165
|
+
return result
|
|
155
166
|
},
|
|
156
167
|
},
|
|
157
168
|
) as any
|
|
@@ -173,12 +184,23 @@ export async function executeMiddleware(
|
|
|
173
184
|
opts: ServerFnMiddlewareOptions,
|
|
174
185
|
): Promise<ServerFnMiddlewareResult> {
|
|
175
186
|
const globalMiddlewares = getStartOptions()?.functionMiddleware || []
|
|
176
|
-
|
|
187
|
+
let flattenedMiddlewares = flattenMiddlewares([
|
|
177
188
|
...globalMiddlewares,
|
|
178
189
|
...middlewares,
|
|
179
190
|
])
|
|
180
191
|
|
|
181
|
-
|
|
192
|
+
// On server, filter out middlewares that already executed in the request phase
|
|
193
|
+
// to prevent duplicate execution (issue #5239)
|
|
194
|
+
if (env === 'server') {
|
|
195
|
+
const startContext = getStartContextServerOnly({ throwIfNotFound: false })
|
|
196
|
+
if (startContext?.executedRequestMiddlewares) {
|
|
197
|
+
flattenedMiddlewares = flattenedMiddlewares.filter(
|
|
198
|
+
(m) => !startContext.executedRequestMiddlewares.has(m),
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const callNextMiddleware: NextFn = async (ctx) => {
|
|
182
204
|
// Get the next middleware
|
|
183
205
|
const nextMiddleware = flattenedMiddlewares.shift()
|
|
184
206
|
|
|
@@ -187,54 +209,110 @@ export async function executeMiddleware(
|
|
|
187
209
|
return ctx
|
|
188
210
|
}
|
|
189
211
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
ctx.data
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
// Execute the middleware
|
|
213
|
+
try {
|
|
214
|
+
if (
|
|
215
|
+
'inputValidator' in nextMiddleware.options &&
|
|
216
|
+
nextMiddleware.options.inputValidator &&
|
|
217
|
+
env === 'server'
|
|
218
|
+
) {
|
|
219
|
+
// Execute the middleware's input function
|
|
220
|
+
ctx.data = await execValidator(
|
|
221
|
+
nextMiddleware.options.inputValidator,
|
|
222
|
+
ctx.data,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
201
225
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
let middlewareFn: MiddlewareFn | undefined = undefined
|
|
227
|
+
if (env === 'client') {
|
|
228
|
+
if ('client' in nextMiddleware.options) {
|
|
229
|
+
middlewareFn = nextMiddleware.options.client as
|
|
230
|
+
| MiddlewareFn
|
|
231
|
+
| undefined
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// env === 'server'
|
|
235
|
+
else if ('server' in nextMiddleware.options) {
|
|
236
|
+
middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined
|
|
206
237
|
}
|
|
207
|
-
}
|
|
208
|
-
// env === 'server'
|
|
209
|
-
else if ('server' in nextMiddleware.options) {
|
|
210
|
-
middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined
|
|
211
|
-
}
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
239
|
+
if (middlewareFn) {
|
|
240
|
+
const userNext = async (
|
|
241
|
+
userCtx: ServerFnMiddlewareResult | undefined = {} as any,
|
|
242
|
+
) => {
|
|
243
|
+
// Return the next middleware
|
|
244
|
+
// Use safeObjectMerge for context objects to prevent prototype pollution
|
|
245
|
+
const nextCtx = {
|
|
246
|
+
...ctx,
|
|
247
|
+
...userCtx,
|
|
248
|
+
context: safeObjectMerge(ctx.context, userCtx.context),
|
|
249
|
+
sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),
|
|
250
|
+
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
251
|
+
result:
|
|
252
|
+
userCtx.result !== undefined
|
|
253
|
+
? userCtx.result
|
|
254
|
+
: userCtx instanceof Response
|
|
255
|
+
? userCtx
|
|
256
|
+
: (ctx as any).result,
|
|
257
|
+
error: userCtx.error ?? (ctx as any).error,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
return await callNextMiddleware(nextCtx)
|
|
262
|
+
} catch (error: any) {
|
|
218
263
|
return {
|
|
219
|
-
...
|
|
264
|
+
...nextCtx,
|
|
220
265
|
error,
|
|
221
266
|
}
|
|
222
267
|
}
|
|
268
|
+
}
|
|
223
269
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
270
|
+
// Execute the middleware
|
|
271
|
+
const result = await middlewareFn({
|
|
272
|
+
...ctx,
|
|
273
|
+
next: userNext as any,
|
|
274
|
+
} as any)
|
|
275
|
+
|
|
276
|
+
// If result is NOT a ctx object, we need to return it as
|
|
277
|
+
// the { result }
|
|
278
|
+
if (isRedirect(result)) {
|
|
279
|
+
return {
|
|
280
|
+
...ctx,
|
|
281
|
+
error: result,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (result instanceof Response) {
|
|
286
|
+
return {
|
|
287
|
+
...ctx,
|
|
288
|
+
result,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
228
291
|
|
|
229
|
-
|
|
292
|
+
if (!(result as any)) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
'User middleware returned undefined. You must call next() or return a result in your middlewares.',
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return callNextMiddleware(ctx)
|
|
302
|
+
} catch (error: any) {
|
|
303
|
+
return {
|
|
304
|
+
...ctx,
|
|
305
|
+
error,
|
|
306
|
+
}
|
|
307
|
+
}
|
|
230
308
|
}
|
|
231
309
|
|
|
232
310
|
// Start the middleware chain
|
|
233
|
-
return
|
|
311
|
+
return callNextMiddleware({
|
|
234
312
|
...opts,
|
|
235
313
|
headers: opts.headers || {},
|
|
236
314
|
sendContext: opts.sendContext || {},
|
|
237
|
-
context: opts.context ||
|
|
315
|
+
context: opts.context || createNullProtoObject(),
|
|
238
316
|
})
|
|
239
317
|
}
|
|
240
318
|
|
|
@@ -571,18 +649,21 @@ export interface ServerFnTypes<
|
|
|
571
649
|
allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>
|
|
572
650
|
}
|
|
573
651
|
|
|
574
|
-
export function flattenMiddlewares
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const seen = new Set<
|
|
578
|
-
const flattened: Array<
|
|
652
|
+
export function flattenMiddlewares<
|
|
653
|
+
T extends AnyFunctionMiddleware | AnyRequestMiddleware,
|
|
654
|
+
>(middlewares: Array<T>, maxDepth: number = 100): Array<T> {
|
|
655
|
+
const seen = new Set<T>()
|
|
656
|
+
const flattened: Array<T> = []
|
|
579
657
|
|
|
580
|
-
const recurse = (
|
|
581
|
-
|
|
582
|
-
|
|
658
|
+
const recurse = (middleware: Array<T>, depth: number) => {
|
|
659
|
+
if (depth > maxDepth) {
|
|
660
|
+
throw new Error(
|
|
661
|
+
`Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`,
|
|
662
|
+
)
|
|
663
|
+
}
|
|
583
664
|
middleware.forEach((m) => {
|
|
584
665
|
if (m.options.middleware) {
|
|
585
|
-
recurse(m.options.middleware)
|
|
666
|
+
recurse(m.options.middleware as Array<T>, depth + 1)
|
|
586
667
|
}
|
|
587
668
|
|
|
588
669
|
if (!seen.has(m)) {
|
|
@@ -592,7 +673,7 @@ export function flattenMiddlewares(
|
|
|
592
673
|
})
|
|
593
674
|
}
|
|
594
675
|
|
|
595
|
-
recurse(middlewares)
|
|
676
|
+
recurse(middlewares, 0)
|
|
596
677
|
|
|
597
678
|
return flattened
|
|
598
679
|
}
|
|
@@ -622,41 +703,6 @@ export type MiddlewareFn = (
|
|
|
622
703
|
},
|
|
623
704
|
) => Promise<ServerFnMiddlewareResult>
|
|
624
705
|
|
|
625
|
-
export const applyMiddleware = async (
|
|
626
|
-
middlewareFn: MiddlewareFn,
|
|
627
|
-
ctx: ServerFnMiddlewareOptions,
|
|
628
|
-
nextFn: NextFn,
|
|
629
|
-
) => {
|
|
630
|
-
return middlewareFn({
|
|
631
|
-
...ctx,
|
|
632
|
-
next: (async (
|
|
633
|
-
userCtx: ServerFnMiddlewareResult | undefined = {} as any,
|
|
634
|
-
) => {
|
|
635
|
-
// Return the next middleware
|
|
636
|
-
return nextFn({
|
|
637
|
-
...ctx,
|
|
638
|
-
...userCtx,
|
|
639
|
-
context: {
|
|
640
|
-
...ctx.context,
|
|
641
|
-
...userCtx.context,
|
|
642
|
-
},
|
|
643
|
-
sendContext: {
|
|
644
|
-
...ctx.sendContext,
|
|
645
|
-
...(userCtx.sendContext ?? {}),
|
|
646
|
-
},
|
|
647
|
-
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
648
|
-
result:
|
|
649
|
-
userCtx.result !== undefined
|
|
650
|
-
? userCtx.result
|
|
651
|
-
: userCtx instanceof Response
|
|
652
|
-
? userCtx
|
|
653
|
-
: (ctx as any).result,
|
|
654
|
-
error: userCtx.error ?? (ctx as any).error,
|
|
655
|
-
})
|
|
656
|
-
}) as any,
|
|
657
|
-
} as any)
|
|
658
|
-
}
|
|
659
|
-
|
|
660
706
|
export function execValidator(
|
|
661
707
|
validator: AnyValidator,
|
|
662
708
|
input: unknown,
|
package/src/index.tsx
CHANGED
|
@@ -72,7 +72,6 @@ export type {
|
|
|
72
72
|
RequiredFetcher,
|
|
73
73
|
} from './createServerFn'
|
|
74
74
|
export {
|
|
75
|
-
applyMiddleware,
|
|
76
75
|
execValidator,
|
|
77
76
|
flattenMiddlewares,
|
|
78
77
|
executeMiddleware,
|
|
@@ -83,6 +82,7 @@ export {
|
|
|
83
82
|
TSS_SERVER_FUNCTION,
|
|
84
83
|
X_TSS_SERIALIZED,
|
|
85
84
|
X_TSS_RAW_RESPONSE,
|
|
85
|
+
X_TSS_CONTEXT,
|
|
86
86
|
} from './constants'
|
|
87
87
|
|
|
88
88
|
export type * from './serverRoute'
|
|
@@ -100,3 +100,4 @@ export type { Register } from '@tanstack/router-core'
|
|
|
100
100
|
export { getRouterInstance } from './getRouterInstance'
|
|
101
101
|
export { getDefaultSerovalPlugins } from './getDefaultSerovalPlugins'
|
|
102
102
|
export { getGlobalStartContext } from './getGlobalStartContext'
|
|
103
|
+
export { safeObjectMerge, createNullProtoObject } from './safeObjectMerge'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function isSafeKey(key: string): boolean {
|
|
2
|
+
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merge target and source into a new null-proto object, filtering dangerous keys.
|
|
7
|
+
*/
|
|
8
|
+
export function safeObjectMerge<T extends Record<string, unknown>>(
|
|
9
|
+
target: T | undefined,
|
|
10
|
+
source: Record<string, unknown> | null | undefined,
|
|
11
|
+
): T {
|
|
12
|
+
const result = Object.create(null) as T
|
|
13
|
+
if (target) {
|
|
14
|
+
for (const key of Object.keys(target)) {
|
|
15
|
+
if (isSafeKey(key)) result[key as keyof T] = target[key] as T[keyof T]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (source && typeof source === 'object') {
|
|
19
|
+
for (const key of Object.keys(source)) {
|
|
20
|
+
if (isSafeKey(key)) result[key as keyof T] = source[key] as T[keyof T]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a null-prototype object, optionally copying from source.
|
|
28
|
+
*/
|
|
29
|
+
export function createNullProtoObject<T extends object>(
|
|
30
|
+
source?: T,
|
|
31
|
+
): { [K in keyof T]: T[K] } {
|
|
32
|
+
if (!source) return Object.create(null)
|
|
33
|
+
const obj = Object.create(null)
|
|
34
|
+
for (const key of Object.keys(source)) {
|
|
35
|
+
if (isSafeKey(key)) obj[key] = (source as Record<string, unknown>)[key]
|
|
36
|
+
}
|
|
37
|
+
return obj
|
|
38
|
+
}
|