T
TanStack12mo ago
vicious-gold

Is there correct way to create abstraction around createServerFn?

Hello, I'm trying to create abstraction on top of createServerFn to validate payload using zod. But I'm running into strange errors. Here is very simplified example:
import { createServerFn } from "@tanstack/start";

const createServerFnWithConsoleLog: typeof createServerFn = (...args) => {
"use server"; // The error is same whether this directive is here or not.
console.log("Hello from wrapper");

return createServerFn(...args);
};

export const testFn = createServerFnWithConsoleLog("GET", async () => {
return "Hello world!";
});
import { createServerFn } from "@tanstack/start";

const createServerFnWithConsoleLog: typeof createServerFn = (...args) => {
"use server"; // The error is same whether this directive is here or not.
console.log("Hello from wrapper");

return createServerFn(...args);
};

export const testFn = createServerFnWithConsoleLog("GET", async () => {
return "Hello world!";
});
With this code I'm getting:
Error: Invariant failed: createServerFn must be called with a function that is marked with the 'use server' pragma. Are you using the @tanstack/router-plugin/vite ?
Error: Invariant failed: createServerFn must be called with a function that is marked with the 'use server' pragma. Are you using the @tanstack/router-plugin/vite ?
Is there correct way to do this?
15 Replies
fair-rose
fair-rose12mo ago
testFn must have 'useServer'
vicious-gold
vicious-goldOP12mo ago
Thanks for the quick reply! While that worked, is there no better way to do this? As you can imagine, I'd want to use the custom createServerFn function for most of my server actions, and adding "use server" everywhere is not idea.
fair-rose
fair-rose12mo ago
this could work (did not try it)
import { createServerFn } from "@tanstack/start";

function createServerFnWithConsoleLog<TMethod extends 'GET' | 'POST', TPayload = undefined, TResponse = unknown>(method: TMethod, fn: FetchFn<TPayload, TResponse>): Fetcher<TPayload, TResponse> {
return createServerFn(method, () => {
"use server";
console.log("Hello from wrapper");
return fn();
}

import { createServerFn } from "@tanstack/start";

function createServerFnWithConsoleLog<TMethod extends 'GET' | 'POST', TPayload = undefined, TResponse = unknown>(method: TMethod, fn: FetchFn<TPayload, TResponse>): Fetcher<TPayload, TResponse> {
return createServerFn(method, () => {
"use server";
console.log("Hello from wrapper");
return fn();
}

obviously this is not ideal from typesafety perspective
vicious-gold
vicious-goldOP12mo ago
When I try this, I get instead ReferenceError: fn is not defined. Full code:
function createServerFnWithConsoleLog<
TMethod extends "GET" | "POST",
TPayload = undefined,
TResponse = unknown,
>(
method: TMethod,
fn: FetchFn<TPayload, TResponse>,
): Fetcher<TPayload, TResponse> {
return createServerFn(method, (...args) => {
"use server";
console.log("Hello from wrapper");
return fn(...args);
});
}

export const testFn = createServerFnWithConsoleLog("GET", async () => {
return "Hello world!";
});
function createServerFnWithConsoleLog<
TMethod extends "GET" | "POST",
TPayload = undefined,
TResponse = unknown,
>(
method: TMethod,
fn: FetchFn<TPayload, TResponse>,
): Fetcher<TPayload, TResponse> {
return createServerFn(method, (...args) => {
"use server";
console.log("Hello from wrapper");
return fn(...args);
});
}

export const testFn = createServerFnWithConsoleLog("GET", async () => {
return "Hello world!";
});
Why would this not be ideal for typesafety?
fair-rose
fair-rose12mo ago
i thought it might lose the return type i just looked into what we do with server functions the reason you get this reference error is that our compiler just treats createServerFn as a macro and extracts the function out of it
fair-rose
fair-rose12mo ago
GitHub
router/packages/start-vite-plugin/src/compilers.ts at main · TanSta...
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
vicious-gold
vicious-goldOP12mo ago
Return type is being inferred properly.
fair-rose
fair-rose12mo ago
GitHub
router/packages/start-vite-plugin/src/compilers.ts at main · TanSta...
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
fair-rose
fair-rose12mo ago
so it might just that your usage of that spread is not handled yet correctly in the compiler can you please create a github issue with a complete minimal example, than we can check whether we can support this
vicious-gold
vicious-goldOP12mo ago
I'm slightly confused. Do you want github issue for the missing "use server"?
fair-rose
fair-rose12mo ago
for your wrapper use case whether that should also get an automatic "use server" directive or not i guess we have to draw the line somewhere but i am not too deep into this right now so the other maintainers will be able to help you out there
vicious-gold
vicious-goldOP12mo ago
Will do.
vicious-gold
vicious-goldOP12mo ago
@Manuel Schiller issue created: https://github.com/TanStack/router/issues/2393
GitHub
Issues with creating abstractions around createServerFn · Issue #...
Which project does this relate to? Start Describe the bug I ran into these issues while I was trying to create abstraction around createServerFn that validates payload using zod. There are two diff...
correct-apricot
correct-apricot12mo ago
I think Tanner is working on this exact improvement to createServerFn
plain-purple
plain-purple12mo ago
You can’t really create abstractions around use server or create server function They are extracted But you can create utilities for use around them or inside them Eg, createServerFn(withStuff(fn)) Or withClientStuff(createServerFn(fn)) Once you call create Server Fn or use use server, might as well rip that code right out of context. Its a serialization boundary just like a fetch request

Did you find this page helpful?