Last active
May 8, 2025 18:17
-
-
Save tracepanic/88109d0cfc6c2094540bab6d504cf543 to your computer and use it in GitHub Desktop.
How to use actionkit for server actions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { BadRequestException, HttpException, handleAction } from "@repo/actionkit"; | |
import { initializeLMS } from "@/lib/server"; | |
import { ZodError } from "zod"; | |
const { data, message, success, error } = await handleAction(initializeLMS,user,school); | |
// This is fully typesafe, handleAction can take minimum one argument for the server action | |
// Based on the number of arguements the server actions need you must pass typesafe number of arguements | |
// Data is undefined if the action is not returning anything or typesafe to the action response | |
// Message is always available same to success error, error can be undefined or of type ServerActionError | |
type ServerActionError = string | object | ZodError | HttpException; | |
interface ServerActionResponse<T> { | |
success: boolean; | |
message: string; | |
data?: T; | |
error?: ServerActionError; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ZodError } from "zod"; | |
export const HttpStatus = { | |
// 1xx Informational | |
CONTINUE: 100, | |
SWITCHING_PROTOCOLS: 101, | |
PROCESSING: 102, | |
EARLY_HINTS: 103, | |
// 2xx Success | |
OK: 200, | |
CREATED: 201, | |
ACCEPTED: 202, | |
NON_AUTHORITATIVE_INFORMATION: 203, | |
NO_CONTENT: 204, | |
RESET_CONTENT: 205, | |
PARTIAL_CONTENT: 206, | |
MULTI_STATUS: 207, | |
ALREADY_REPORTED: 208, | |
IM_USED: 226, | |
// 3xx Redirection | |
MULTIPLE_CHOICES: 300, | |
MOVED_PERMANENTLY: 301, | |
FOUND: 302, | |
SEE_OTHER: 303, | |
NOT_MODIFIED: 304, | |
USE_PROXY: 305, | |
TEMPORARY_REDIRECT: 307, | |
PERMANENT_REDIRECT: 308, | |
// 4xx Client Error | |
BAD_REQUEST: 400, | |
UNAUTHORIZED: 401, | |
PAYMENT_REQUIRED: 402, | |
FORBIDDEN: 403, | |
NOT_FOUND: 404, | |
METHOD_NOT_ALLOWED: 405, | |
NOT_ACCEPTABLE: 406, | |
PROXY_AUTHENTICATION_REQUIRED: 407, | |
REQUEST_TIMEOUT: 408, | |
CONFLICT: 409, | |
GONE: 410, | |
LENGTH_REQUIRED: 411, | |
PRECONDITION_FAILED: 412, | |
PAYLOAD_TOO_LARGE: 413, | |
URI_TOO_LONG: 414, | |
UNSUPPORTED_MEDIA_TYPE: 415, | |
RANGE_NOT_SATISFIABLE: 416, | |
EXPECTATION_FAILED: 417, | |
IM_A_TEAPOT: 418, | |
MISDIRECTED_REQUEST: 421, | |
UNPROCESSABLE_ENTITY: 422, | |
LOCKED: 423, | |
FAILED_DEPENDENCY: 424, | |
TOO_EARLY: 425, | |
UPGRADE_REQUIRED: 426, | |
PRECONDITION_REQUIRED: 428, | |
TOO_MANY_REQUESTS: 429, | |
REQUEST_HEADER_FIELDS_TOO_LARGE: 431, | |
UNAVAILABLE_FOR_LEGAL_REASONS: 451, | |
// 5xx Server Error | |
INTERNAL_SERVER_ERROR: 500, | |
NOT_IMPLEMENTED: 501, | |
BAD_GATEWAY: 502, | |
SERVICE_UNAVAILABLE: 503, | |
GATEWAY_TIMEOUT: 504, | |
HTTP_VERSION_NOT_SUPPORTED: 505, | |
VARIANT_ALSO_NEGOTIATES: 506, | |
INSUFFICIENT_STORAGE: 507, | |
LOOP_DETECTED: 508, | |
NOT_EXTENDED: 510, | |
NETWORK_AUTHENTICATION_REQUIRED: 511, | |
} as const; | |
type HttpStatusCode = (typeof HttpStatus)[keyof typeof HttpStatus]; | |
export class HttpException extends Error { | |
status: HttpStatusCode; | |
override message: string; | |
error?: any; | |
constructor(message: string, status: HttpStatusCode, error?: any) { | |
super(message); | |
this.status = status; | |
this.message = message; | |
this.error = error; | |
this.name = "HttpException"; | |
} | |
} | |
export class BadRequestException extends HttpException { | |
constructor(message: string = "Bad Request", error?: any) { | |
super(message, 400, error); | |
this.name = "BadRequestException"; | |
} | |
} | |
export class NotFoundException extends HttpException { | |
constructor(message: string = "Not Found", error?: any) { | |
super(message, 404, error); | |
this.name = "NotFoundException"; | |
} | |
} | |
export class InternalServerErrorException extends HttpException { | |
constructor(message: string = "Internal Server Error", error?: any) { | |
super(message, 500, error); | |
this.name = "InternalServerErrorException"; | |
} | |
} | |
export type ServerActionError = string | object | ZodError | HttpException; | |
export interface ServerActionResponse<T> { | |
success: boolean; | |
message: string; | |
data?: T; | |
error?: ServerActionError; | |
} | |
export async function handleAction<T, Args extends any[] = any[]>( | |
fn: (...args: Args) => Promise<T>, | |
...args: Args | |
): Promise<ServerActionResponse<T>> { | |
try { | |
const data: T = await fn(...args); | |
return { | |
success: true, | |
message: "Operation successful", | |
data, | |
}; | |
} catch (error: any) { | |
if (error instanceof ZodError) { | |
return { | |
success: false, | |
message: "Validation Error", | |
error: error, | |
}; | |
} | |
if (error instanceof HttpException) { | |
return { | |
success: false, | |
message: error.message, | |
error: { | |
status: error.status, | |
message: error.message, | |
error: error.error, | |
}, | |
}; | |
} | |
return { | |
success: false, | |
message: error?.message || "Internal Server Error", | |
error, | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use server"; | |
import { db } from "@/db"; | |
import { schools } from "@/db/schema/school"; | |
import { users } from "@/db/schema/users"; | |
import { CreateSchoolSchema, SignupSchema } from "@/lib/schema"; | |
import { BadRequestException } from "@repo/actionkit"; | |
import { z } from "zod"; | |
export async function initializeLMS( | |
user: z.infer<typeof SignupSchema>, | |
school: z.infer<typeof CreateSchoolSchema>, | |
): Promise<{ success: boolean }> { | |
const validUser = SignupSchema.parse(user); | |
const validSchool = CreateSchoolSchema.parse(school); | |
// Dont worry about zod errors we catch all errors | |
// If its a zod error we will tell you a custom message for zod error in response message and error | |
// Or you can safepaserse and throw your own error | |
const [dbUser, dbSchool] = await Promise.all([ | |
db.select().from(users).limit(1).execute, | |
db.select().from(schools).limit(1).execute, | |
]); | |
if (dbUser.length === 1 || dbSchool.length === 1) { | |
throw new BadRequestException("LMS is already initialized"); | |
} | |
// You can throw any http exception from actionkit we extend the Error class | |
// You can add extra info the error as below | |
throw new BadRequestException('Invalid input provided', { details: 'Missing required field' }); | |
// Do initialization | |
return { success: true } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment