W
Wasp2mo ago
JakeLoew

Use case for functions exposed by wasp/server/operations

When you define an operation in main.wasp, the framework compiles and creates 2 exports of the same name. One from wasp/client/operations and one from wasp/server/operations. Of couse you would invoke the operation from wasp/client/operations to on the frontend. I'm wondering: what is the use case for the function exported from wasp/server/operations?
5 Replies
kapa.ai
kapa.ai2mo ago
Hi @JakeLoew. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The functions exported from wasp/server/operations are primarily used for server-side operations, particularly when you need to call one operation from another on the server. This allows for a consistent and type-safe way to interact with your defined operations within the server environment. Here are the main use cases and considerations for using functions from wasp/server/operations: 1. Calling operations from other operations: You can import and use these functions when you need to call one action or query from another on the server side. 2. Server-side context: When using operations imported from wasp/server/operations, you need to provide a context that contains user information (without entities). If your operation doesn't require authentication, you can pass an empty object. 3. Type safety: For TypeScript users, importing from wasp/server/operations also provides type definitions for your operations, enhancing type safety in your server-side code. It's important to note that you should not import from wasp/client/operations on the server side, as this was incorrectly stated in older documentation. The correct approach is to use wasp/server/operations for server-side usage. Wasp Discord Support Thread (https://discord.com/channels/686873244791210014/1218567315185209464) mentions this distinction and the need to update the documentation to reflect the correct usage. Here's an example of how you might use a server-side operation import:
import { createFoo } from 'wasp/server/operations'

// Use it on the server
const result = await createFoo({ context: { user: /* ... */ } }, /* args */)
import { createFoo } from 'wasp/server/operations'

// Use it on the server
const result = await createFoo({ context: { user: /* ... */ } }, /* args */)
It's worth noting that there's an open issue to improve the documentation on this topic, as mentioned in the GitHub Issue: Fix docs on calling queries and actions on the server (https://github.com/wasp-lang/wasp/issues/1909). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JakeLoew, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
miho
miho2mo ago
@Filip one for you
Filip
Filip2mo ago
@JakeLoew Great question, we should maybe elaborate on it in the docs. Classic examples are: - Seeding the database - Using your action from another action (e.g., you could use prepareTransaction and sendTransaction in prepareAndSendTransaction) - Calling actions from jobs (e.g., archiveOldItems action that admins can call from a frontend dashboard, or is executed every week) You can theoretically accomplish most (if not all) of these use cases by: - Keeping your action minimal - Extracting the common logic into its functions which you then reuse (in the main action, other actions, jobs, seeding functions, etc.). But it's often more ergonomic to call the action directly: - You don't have to pass in the context (e.g., prisma entities) explicitly, Wasp will inject it for you. - You can just forward the payloads (and the auth user) into the nested action without any changes. - You don't need to think about extracting stuff and reusability too early in the project
JakeLoew
JakeLoewOP2mo ago
You don't have to pass in the context (e.g., prisma entities) explicitly, Wasp will inject it for you.
As a matter of fact, that is what prompted me to ask this question. In my experience, the typescript compiler expects me to pass context as an argument at site where I'm invoking the action. The screenshot is from a new wasp app using the to-do template where getTasks is defined as:
import { type Task } from "wasp/entities";
import { HttpError } from "wasp/server";
import { type GetTasks } from "wasp/server/operations";

export const getTasks = ((_args, context) => {
if (!context.user) {
throw new HttpError(401);
}

return context.entities.Task.findMany({
where: { user: { id: context.user.id } },
orderBy: { id: "asc" },
});
}) satisfies GetTasks<void, Task[]>;
import { type Task } from "wasp/entities";
import { HttpError } from "wasp/server";
import { type GetTasks } from "wasp/server/operations";

export const getTasks = ((_args, context) => {
if (!context.user) {
throw new HttpError(401);
}

return context.entities.Task.findMany({
where: { user: { id: context.user.id } },
orderBy: { id: "asc" },
});
}) satisfies GetTasks<void, Task[]>;
No description
Filip
Filip2mo ago
True, but the only context you must pass is the user. That's because if it's an authenticated operation, Wasp can't know which user to inject. But it does inject everything else (prisma entities) and keeps those injections up to date when you change the entity list in the Wasp file.

Did you find this page helpful?