@tanstack/start-client-core
Version:
Modern and scalable routing for React applications
200 lines (199 loc) • 6.78 kB
JavaScript
import { TSS_SERVER_FUNCTION_FACTORY } from "./constants.js";
import { getStartOptions } from "./getStartOptions.js";
import { getStartContextServerOnly } from "./getStartContextServerOnly.js";
import { createNullProtoObject, safeObjectMerge } from "./safeObjectMerge.js";
import { mergeHeaders } from "@tanstack/router-core/ssr/client";
import { isRedirect, parseRedirect } from "@tanstack/router-core";
//#region src/createServerFn.ts
var createServerFn = (options, __opts) => {
const resolvedOptions = __opts || options || {};
if (typeof resolvedOptions.method === "undefined") resolvedOptions.method = "GET";
const res = {
options: resolvedOptions,
middleware: (middleware) => {
const newMiddleware = [...resolvedOptions.middleware || []];
middleware.map((m) => {
if (TSS_SERVER_FUNCTION_FACTORY in m) {
if (m.options.middleware) newMiddleware.push(...m.options.middleware);
} else newMiddleware.push(m);
});
const res = createServerFn(void 0, {
...resolvedOptions,
middleware: newMiddleware
});
res[TSS_SERVER_FUNCTION_FACTORY] = true;
return res;
},
inputValidator: (inputValidator) => {
return createServerFn(void 0, {
...resolvedOptions,
inputValidator
});
},
handler: (...args) => {
const [extractedFn, serverFn] = args;
const newOptions = {
...resolvedOptions,
extractedFn,
serverFn
};
const resolvedMiddleware = [...newOptions.middleware || [], serverFnBaseToMiddleware(newOptions)];
extractedFn.method = resolvedOptions.method;
return Object.assign(async (opts) => {
const result = await executeMiddleware(resolvedMiddleware, "client", {
...extractedFn,
...newOptions,
data: opts?.data,
headers: opts?.headers,
signal: opts?.signal,
fetch: opts?.fetch,
context: createNullProtoObject()
});
const redirect = parseRedirect(result.error);
if (redirect) throw redirect;
if (result.error) throw result.error;
return result.result;
}, {
...extractedFn,
method: resolvedOptions.method,
__executeServer: async (opts) => {
const startContext = getStartContextServerOnly();
const serverContextAfterGlobalMiddlewares = startContext.contextAfterGlobalMiddlewares;
return await executeMiddleware(resolvedMiddleware, "server", {
...extractedFn,
...opts,
serverFnMeta: extractedFn.serverFnMeta,
context: safeObjectMerge(serverContextAfterGlobalMiddlewares, opts.context),
request: startContext.request
}).then((d) => ({
result: d.result,
error: d.error,
context: d.sendContext
}));
}
});
}
};
const fun = (options) => {
return createServerFn(void 0, {
...resolvedOptions,
...options
});
};
return Object.assign(fun, res);
};
async function executeMiddleware(middlewares, env, opts) {
let flattenedMiddlewares = flattenMiddlewares([...getStartOptions()?.functionMiddleware || [], ...middlewares]);
if (env === "server") {
const startContext = getStartContextServerOnly({ throwIfNotFound: false });
if (startContext?.executedRequestMiddlewares) flattenedMiddlewares = flattenedMiddlewares.filter((m) => !startContext.executedRequestMiddlewares.has(m));
}
const callNextMiddleware = async (ctx) => {
const nextMiddleware = flattenedMiddlewares.shift();
if (!nextMiddleware) return ctx;
try {
if ("inputValidator" in nextMiddleware.options && nextMiddleware.options.inputValidator && env === "server") ctx.data = await execValidator(nextMiddleware.options.inputValidator, ctx.data);
let middlewareFn = void 0;
if (env === "client") {
if ("client" in nextMiddleware.options) middlewareFn = nextMiddleware.options.client;
} else if ("server" in nextMiddleware.options) middlewareFn = nextMiddleware.options.server;
if (middlewareFn) {
const userNext = async (userCtx = {}) => {
const result = await callNextMiddleware({
...ctx,
...userCtx,
context: safeObjectMerge(ctx.context, userCtx.context),
sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),
headers: mergeHeaders(ctx.headers, userCtx.headers),
_callSiteFetch: ctx._callSiteFetch,
fetch: ctx._callSiteFetch ?? userCtx.fetch ?? ctx.fetch,
result: userCtx.result !== void 0 ? userCtx.result : userCtx instanceof Response ? userCtx : ctx.result,
error: userCtx.error ?? ctx.error
});
if (result.error) throw result.error;
return result;
};
const result = await middlewareFn({
...ctx,
next: userNext
});
if (isRedirect(result)) return {
...ctx,
error: result
};
if (result instanceof Response) return {
...ctx,
result
};
if (!result) throw new Error("User middleware returned undefined. You must call next() or return a result in your middlewares.");
return result;
}
return callNextMiddleware(ctx);
} catch (error) {
return {
...ctx,
error
};
}
};
return callNextMiddleware({
...opts,
headers: opts.headers || {},
sendContext: opts.sendContext || {},
context: opts.context || createNullProtoObject(),
_callSiteFetch: opts.fetch
});
}
function flattenMiddlewares(middlewares, maxDepth = 100) {
const seen = /* @__PURE__ */ new Set();
const flattened = [];
const recurse = (middleware, depth) => {
if (depth > maxDepth) throw new Error(`Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`);
middleware.forEach((m) => {
if (m.options.middleware) recurse(m.options.middleware, depth + 1);
if (!seen.has(m)) {
seen.add(m);
flattened.push(m);
}
});
};
recurse(middlewares, 0);
return flattened;
}
async function execValidator(validator, input) {
if (validator == null) return {};
if ("~standard" in validator) {
const result = await validator["~standard"].validate(input);
if (result.issues) throw new Error(JSON.stringify(result.issues, void 0, 2));
return result.value;
}
if ("parse" in validator) return validator.parse(input);
if (typeof validator === "function") return validator(input);
throw new Error("Invalid validator type!");
}
function serverFnBaseToMiddleware(options) {
return {
"~types": void 0,
options: {
inputValidator: options.inputValidator,
client: async ({ next, sendContext, fetch, ...ctx }) => {
const payload = {
...ctx,
context: sendContext,
fetch
};
return next(await options.extractedFn?.(payload));
},
server: async ({ next, ...ctx }) => {
const result = await options.serverFn?.(ctx);
return next({
...ctx,
result
});
}
}
};
}
//#endregion
export { createServerFn, execValidator, executeMiddleware, flattenMiddlewares };
//# sourceMappingURL=createServerFn.js.map