<!-- Markdown mirror of https://justscale.sh/docs/rpc/status-errors -->

# 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 |

TypeScript

```typescript
import { GrpcStatus } from '@justscale/rpc'

// Access status codes
GrpcStatus.OK              // 0
GrpcStatus.NOT_FOUND       // 5
GrpcStatus.INTERNAL        // 13
GrpcStatus.UNAUTHENTICATED // 16
```

## GrpcError 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.

TypeScript

```typescript
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 code
- message - Human-readable error message
- details - Optional additional error context
- statusName - Human-readable status name (e.g., "NOT_FOUND")

TypeScript

```typescript
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:

TypeScript

```typescript
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:

TypeScript

```typescript
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:

TypeScript

```typescript
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:

TypeScript

```typescript
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                  -> INTERNAL
```

You can also convert any error to a GrpcError using `GrpcError.from()`:

TypeScript

```typescript
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
```

## Next Steps

- RPC Overview
- RPC Controllers
- RPC Client
