Install
npx skillscat add elie222/inbox-zero/server-actions Install via the SkillsCat registry.
SKILL.md
Server Actions
Format and Structure
Server actions should follow this format:
Files:
apps/web/utils/actions/NAME.validation.tsapps/web/utils/actions/NAME.ts
For apps/web/utils/actions/NAME.validation.ts:
import { z } from "zod";
// Example: Schema for updating AI settings
export const saveAiSettingsBody = z.object({
aiProvider: z.string().optional(), // Adjust types as needed
aiModel: z.string().optional(),
aiApiKey: z.string().optional(),
});
export type SaveAiSettingsBody = z.infer<typeof saveAiSettingsBody>;
// Example: Schema for updating email settings (requires emailAccountId binding)
export const saveEmailUpdateSettingsBody = z.object({
statsEmailFrequency: z.string().optional(), // Use specific enum/types if applicable
summaryEmailFrequency: z.string().optional(),
});
export type SaveEmailUpdateSettingsBody = z.infer<
typeof saveEmailUpdateSettingsBody
>;
For apps/web/utils/actions/NAME.ts:
"use server";
import { actionClient, actionClientUser } from "@/utils/actions/safe-action";
import {
saveAiSettingsBody,
saveEmailUpdateSettingsBody,
} from "@/utils/actions/settings.validation"; // Adjust path
import prisma from "@/utils/prisma";
import { revalidatePath } from "next/cache"; // Import if needed for cache invalidation
// Example using actionClientUser (requires authenticated user context)
export const updateAiSettingsAction = actionClientUser
.metadata({ name: "updateAiSettings" }) // For logging/instrumentation
.schema(saveAiSettingsBody) // Zod schema for input validation
.action(
async ({
ctx: { userId }, // Access context provided by the safe-action client
parsedInput: { aiProvider, aiModel, aiApiKey }, // Validated and typed input
}) => {
await prisma.user.update({
where: { id: userId },
data: { aiProvider, aiModel, aiApiKey },
});
},
);
// Example using actionClient (requires authenticated user + bound emailAccountId)
export const updateEmailSettingsAction = actionClient
.metadata({ name: "updateEmailSettings" })
.schema(saveEmailUpdateSettingsBody)
// Note: emailAccountId is bound when calling this action from the client
.action(
async ({
ctx: { emailAccountId }, // Access context (includes userId, email etc.)
parsedInput: { statsEmailFrequency, summaryEmailFrequency },
}) => {
await prisma.emailAccount.update({
where: { id: emailAccountId },
data: {
statsEmailFrequency,
summaryEmailFrequency,
},
});
},
);
Implementation Guidelines
- Use
next-safe-action: Implement all server actions using thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient). - Choose the Right Client:
actionClientUser: Use when only authenticated user context (userId) is needed.actionClient: Use when both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.adminActionClient: Use for actions restricted to admin users.
- Input Validation: Define input validation schemas using Zod in the corresponding
.validation.tsfile. These schemas are used bynext-safe-action(.schema()) and can also be reused on the client for form validation. - Context (
ctx): Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler. - Mutations Only: Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations).
- For data fetching, use dedicated GET API Routes combined with SWR Hooks.
- Error Handling:
next-safe-actionprovides centralized error handling. UseSafeErrorfor expected/handled errors within actions if needed (seeapps/web/utils/actions/safe-action.ts). - Instrumentation: Sentry instrumentation is automatically applied via
withServerActionInstrumentationwithin the safe action clients. Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. - Cache Invalidation: If an action modifies data displayed elsewhere, use
revalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.