RPC Status Codes & Errors
gRPC status codes and error handling in JustScale
The @justscale/rpc package provides a complete implementation of gRPC status codes and error handling. Throw typed errors in your handlers and they'll be properly transmitted to clients.
GrpcStatus Enum
All 17 standard gRPC status codes are available:
| Code | Name | Description |
|---|---|---|
| 0 | OK | Not an error; returned on success |
| 1 | CANCELLED | Operation was cancelled by the caller |
| 2 | UNKNOWN | Unknown error |
| 3 | INVALID_ARGUMENT | Client specified an invalid argument |
| 4 | DEADLINE_EXCEEDED | Deadline expired before operation completed |
| 5 | NOT_FOUND | Requested entity was not found |
| 6 | ALREADY_EXISTS | Entity already exists |
| 7 | PERMISSION_DENIED | Caller lacks permission |
| 8 | RESOURCE_EXHAUSTED | Resource has been exhausted (quota, etc.) |
| 9 | FAILED_PRECONDITION | System not in required state |
| 10 | ABORTED | Operation aborted (concurrency issue) |
| 11 | OUT_OF_RANGE | Operation attempted past valid range |
| 12 | UNIMPLEMENTED | Operation not implemented or supported |
| 13 | INTERNAL | Internal server error |
| 14 | UNAVAILABLE | Service currently unavailable |
| 15 | DATA_LOSS | Unrecoverable data loss or corruption |
| 16 | UNAUTHENTICATED | Invalid authentication credentials |
import { GrpcStatus } from '@justscale/rpc'
// Access status codes
GrpcStatus.OK // 0
GrpcStatus.NOT_FOUND // 5
GrpcStatus.INTERNAL // 13
GrpcStatus.UNAUTHENTICATED // 16GrpcError Class
The GrpcError class represents gRPC errors with status codes. Throw these in your handlers and they'll be properly serialized and sent to clients.
import { GrpcError, GrpcStatus } from '@justscale/rpc'
// Basic error
throw new GrpcError(GrpcStatus.NOT_FOUND, 'User not found')
// With additional details
throw new GrpcError(GrpcStatus.INVALID_ARGUMENT, 'Invalid email format', {
field: 'email',
value: 'not-an-email',
})GrpcError provides useful properties:
code- The numeric gRPC status codemessage- Human-readable error messagedetails- Optional additional error contextstatusName- Human-readable status name (e.g., "NOT_FOUND")
const error = new GrpcError(GrpcStatus.NOT_FOUND, 'User not found')
error.code // 5
error.message // "User not found"
error.statusName // "NOT_FOUND"
error.toString() // "GrpcError [NOT_FOUND]: User not found"Error Helper Functions
The grpcError helper provides factory functions for each status code, making error creation more readable:
import { grpcError } from '@justscale/rpc'
// Not found
throw grpcError.notFound('User not found')
// Invalid argument with details
throw grpcError.invalidArgument('Email is invalid', { field: 'email' })
// Permission denied
throw grpcError.permissionDenied('Admin access required')
// Unauthenticated
throw grpcError.unauthenticated('Token expired')
// All available helpers:
grpcError.cancelled(message, details?)
grpcError.invalidArgument(message, details?)
grpcError.deadlineExceeded(message, details?)
grpcError.notFound(message, details?)
grpcError.alreadyExists(message, details?)
grpcError.permissionDenied(message, details?)
grpcError.resourceExhausted(message, details?)
grpcError.failedPrecondition(message, details?)
grpcError.aborted(message, details?)
grpcError.outOfRange(message, details?)
grpcError.unimplemented(message, details?)
grpcError.internal(message, details?)
grpcError.unavailable(message, details?)
grpcError.dataLoss(message, details?)
grpcError.unauthenticated(message, details?)Error Handling in Handlers
Throw GrpcError in your RPC handlers. The framework catches them and sends the appropriate status code to clients:
import { grpcError, isGrpcError } from '@justscale/rpc'
const UserController = createController
.implements(UserServiceContract)
.create({
inject: { users: UserRepository },
methods: ({ users }) => ({
GetUser: async ({ body }) => {
const user = await users.get(User.ref`${body.id}`)
if (!user) {
throw grpcError.notFound(`User ${body.id} not found`)
}
return user
},
CreateUser: async ({ body }) => {
const existing = await users.findByEmail(body.email)
if (existing) {
throw grpcError.alreadyExists('Email already registered')
}
return users.create(body)
},
UpdateUser: async ({ body }) => {
if (!body.id) {
throw grpcError.invalidArgument('User ID is required')
}
// ...
},
}),
})Use isGrpcError to check if an error is a GrpcError:
import { isGrpcError, GrpcStatus } from '@justscale/rpc'
try {
await client.GetUser({ id: 'unknown' })
} catch (err) {
if (isGrpcError(err) && err.code === GrpcStatus.NOT_FOUND) {
// Handle not found specifically
}
}Automatic Error Mapping
The errorToGrpcStatus function automatically maps common errors to appropriate gRPC status codes based on error message patterns:
import { errorToGrpcStatus, GrpcError } from '@justscale/rpc'
// Automatically maps error messages to status codes
const error = new Error('User not found')
const { code, message } = errorToGrpcStatus(error)
// code = GrpcStatus.NOT_FOUND (5)
// Pattern matching:
// "not found", "does not exist" -> NOT_FOUND
// "unauthorized", "invalid token" -> UNAUTHENTICATED
// "permission denied", "forbidden" -> PERMISSION_DENIED
// "already exists", "duplicate" -> ALREADY_EXISTS
// "invalid", "validation" -> INVALID_ARGUMENT
// "timeout", "deadline" -> DEADLINE_EXCEEDED
// "cancelled", "aborted" -> CANCELLED
// "unavailable", "connection refused" -> UNAVAILABLE
// "exhausted", "rate limit", "quota" -> RESOURCE_EXHAUSTED
// Everything else -> INTERNALYou can also convert any error to a GrpcError using GrpcError.from():
import { GrpcError } from '@justscale/rpc'
// Convert any error to GrpcError
const anyError = new Error('Something went wrong')
const grpcErr = GrpcError.from(anyError)
// Returns GrpcError with INTERNAL status
// GrpcErrors pass through unchanged
const existing = grpcError.notFound('User not found')
GrpcError.from(existing) === existing // true