Open Finkes opened 4 months ago
Interesting! I think ideally, next-superjson-plugin would be performing a compile-time transform to cover this as well. I'm not sure if that's possible though, are there any good ways of detecting a server action definition or call inside an AST?
For a more manual solution, do you think this TypeScript hack would work?
"use server"
function wrapWithSuperJSON<ServerAction extends () => Promise<any>>(serverAction: ServerAction): ServerAction {
if (typeof window === 'undefined') return serverAction.then(SuperJSON.serialize)
return serverAction.then(SuperJSON.deserialize)
}
export const serverAction = wrapWithSuperJSON(() => new Prisma.Decimal(10))
// client side
const response = await serverAction()
I haven't tried this out, so let me know if this doesn't work.
Thank you @Skn0tt for your quick support! I really appreciate that. I agree, making next-superjson-plugin handle this automatically would be great.
are there any good ways of detecting a server action definition or call inside an AST?
I'm pretty sure there is a way, but unfortunately I don't have a deeper understanding on how things work behind the scenes, yet.
I tried your proposal, but it looks like the deserialization function isn't executed at all:
"user server"
function wrapWithSuperJSON<ServerAction extends () => Promise<any>>(
serverAction: ServerAction,
): ServerAction {
if (typeof window === "undefined")
return serverAction().then(SuperJSON.serialize) as any as ServerAction;
return serverAction().then(SuperJSON.deserialize) as any as ServerAction;
}
export const serverAction = async () =>
wrapWithSuperJSON(async () => {
return Promise.resolve(new Prisma.Decimal(10));
});
// client side
const result = await serverAction();
console.log(result);
Output:
But thanks to your proposal I have found another workaround which involves wrappers on both sides:
// wrapper functions
/**
* Wrap a next.js server action with SuperJSON (serialize)
* @param serverAction
*/
export function serializeWithSuperJSON<ServerAction extends () => Promise<any>>(
serverAction: ServerAction,
): ReturnType<ServerAction> {
return serverAction().then(
SuperJSON.serialize,
) as any as ReturnType<ServerAction>;
}
/**
* Wrap a next.js server action with SuperJSON (deserialize)
* @param serverAction
*/
export function deserializeWithSuperJSON<
ServerAction extends () => Promise<any>,
>(serverAction: ServerAction) {
return serverAction().then(SuperJSON.deserialize) as any as Promise<
ReturnType<ServerAction>
>;
}
// server side
"user server"
export async function serverAction() {
return await serializeWithSuperJSON(async () => {
return new Prisma.Decimal(10);
});
}
// client side
const result = await deserializeWithSuperJSON(serverAction);
console.log(Prisma.Decimal.isDecimal(result));
Note that it looks like server actions require the function
keyword as described by this error message.
Good to hear you found a workaround! It's really hard to keep up with all the new shenanigans Next.js comes up with, and I have a feeling that supporting server actions in next-superjson-plugin
will be close to impossible. Ideally, Next.js added something like tRPCs transformer
option: https://trpc.io/docs/server/data-transformers#using-superjson
I'm using the next-superjson-plugin to pass data from next.js server components to client components and this works like a charm.
Besides server components next.js also provides server actions, a new way of fetching data from the backend to to client by directly calling a backend function from the client side.
Server actions have the same serialization problem like passing data from server components to client components: behind the scenes data is serialized as JSON string on the server and then passed to the client. Therefore the following example doesn't work as expected, since the custom type is lost on the client side
As a workaround we can use
SuperJSON.serialize()
andSuperJSON.deserialize()
like this:However with this solution we are loosing the TypeScript type safety and we have to assign the return types manually.
Is there any way to integrate SuperJSON into next.js server actions? Maybe there is a way to provide a custom serializer to next.js?