---
name: reviewing-rpc-api-design
description: Reviews Fusion.js RPC handler patterns including handler typing, ResponseError usage, createReactQueryRpcHooks compliance, Atreyu config completeness, and API documentation (JSDoc). Use when conducting code reviews for RPC handlers and data fetching code.
---

# RPC API Design Review Patterns

**Version**: 2.0 | **Updated**: March 2026

> Reviews Fusion.js RPC handlers — not REST endpoints. Focus areas: handler typing, error handling, Atreyu config, React Query hook patterns, and documentation.

## Detection Patterns

### Handler Structure Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| Missing `createPlugin` | Critical | Raw function instead of plugin | Handler not registered via DI |
| No `deps` declaration | Warning | Plugin missing dependency injection | Hardcoded service references |
| Missing `Context` param | Warning | Handler lacks request context | `(args) => {}` instead of `(args, ctx) => {}` |
| Overly broad handler | Warning | Single handler doing too many things | Handler with 5+ service calls |
| Inconsistent naming | Info | Mixed naming conventions | `getUser` vs `userGet` vs `fetchUser` |
| Missing return type | Info | No explicit return type on handler | Implicit `any` return |

### Handler Typing Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| No `ExtractServiceType` | Warning | Missing type export for hooks | Hooks lose type inference |
| Untyped args | Critical | Handler args typed as `any` | `(args: any, ctx) => {}` |
| Missing IDL types | Warning | Hand-written types instead of generated | Not using `@uber/idl-flow` imports |
| Implicit return type | Info | Return type not annotated | Relies on inference for complex returns |
| Missing `RpcHandlers` export | Warning | No shared type for `createReactQueryRpcHooks` | Hook factory has no generic |

### ResponseError Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| Raw `throw new Error()` | Critical | Not using `ResponseError` | Loses error code semantics |
| Missing error code | Critical | `ResponseError` without `ResponseErrorCode` | `new ResponseError('fail')` |
| Generic error messages | Warning | Non-actionable messages | `'Something went wrong'` |
| Leaking internals | Critical | Stack traces or DB errors exposed | SQL error in ResponseError message |
| Missing error wrapping | Warning | Atreyu errors not caught/re-wrapped | Raw service errors reaching client |
| Wrong error code | Warning | Mismatch between situation and code | Using `BAD_REQUEST` for auth failure |
| No error handling | Critical | Handler with no try/catch around service calls | Atreyu errors propagate raw |

### createReactQueryRpcHooks Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| Missing typed hooks | Critical | Direct `useQuery` instead of `useRpcQuery` | Loses RPC type binding |
| No `RpcHandlers` generic | Warning | `createReactQueryRpcHooks()` without type | Untyped hook factory |
| `isLoading` usage | Warning | Using deprecated React Query v4 flag | Should use `isPending` |
| Missing cache invalidation | Warning | Mutation without `invalidateQueries` | Stale data after mutation |
| No `useRpcQueryKey` | Info | Manual query key construction | Should use `useRpcQueryKey` helper |
| Missing `enabled` guard | Warning | Query fires with undefined args | `useRpcQuery('getUser', { id: undefined })` |

### Atreyu Config Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| Missing service in config | Critical | Handler calls unmapped service | Service not in `atreyu.config.ts` |
| Missing `protoPath` | Critical | gRPC service without proto reference | Type generation will fail |
| Missing `package` | Warning | Proto config without package name | Ambiguous proto resolution |
| Hardcoded service URL | Critical | Direct URL instead of Atreyu config | `fetch('http://user-service:8080')` |
| Wrong `type` field | Warning | Mismatched service type | Using `'http'` for gRPC service |
| Stale proto path | Warning | Proto path points to moved/deleted file | IDL import will fail at build |

### Pagination Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| No pagination on list handler | Warning | List returns unbounded results | `listUsers` with no limit |
| Offset pagination | Info | Using offset instead of cursor | Performance degrades at scale |
| Missing `hasMore` flag | Warning | Client cannot detect end of list | No way to stop infinite scroll |
| No limit cap | Warning | Client can request unlimited items | `limit: 100000` allowed |

### Documentation Issues

| Pattern | Severity | Detection | Example |
|---------|----------|-----------|---------|
| Handler without JSDoc | Warning | No docs on handler method | `getUser: async (args, ctx) => {}` |
| Missing @param for args | Warning | Args shape undocumented | Complex args without field docs |
| Missing @throws | Info | ResponseError conditions undocumented | Caller does not know error cases |
| Outdated JSDoc | Warning | Docs do not match current signature | Args changed but docs not updated |
| Missing type documentation | Info | Complex types without descriptions | `type UserFilters = {...}` no context |

## Review Checklist

### Handler Design
- [ ] Handlers defined via `createPlugin` with `deps` and `provides`
- [ ] All handler args typed (no `any`)
- [ ] `Context` parameter present on all handlers
- [ ] Handler names follow `verbNoun` convention
- [ ] `ExtractServiceType` used to export handler types
- [ ] One domain per handler plugin (not mixing unrelated handlers)

### Error Handling
- [ ] All errors use `ResponseError` with `ResponseErrorCode`
- [ ] Error messages are user-facing (no internal details)
- [ ] Atreyu errors caught and re-wrapped with meaningful messages
- [ ] Error codes match the actual error situation
- [ ] Service error metadata logged before re-wrapping

### Client Consumption
- [ ] `createReactQueryRpcHooks<RpcHandlers>()` used with type generic
- [ ] Components use `useRpcQuery` / `useRpcMutation` (not raw `useQuery`)
- [ ] `isPending` used instead of `isLoading`
- [ ] Mutations invalidate relevant queries via `useRpcQueryKey`
- [ ] Queries use `enabled` guard for conditional fetching

### Atreyu Config
- [ ] All services have entries in `atreyu.config.ts`
- [ ] `protoPath` points to valid `.proto` file
- [ ] `package` field matches proto package declaration
- [ ] `type` field matches service protocol (grpc, http, etc.)
- [ ] IDL types imported from `@uber/idl-flow` (not hand-written)

### Pagination
- [ ] List handlers accept `cursor` and `limit` params
- [ ] Response includes `hasMore` and `nextCursor`
- [ ] Server enforces max limit cap
- [ ] Client uses `useRpcInfiniteQuery` for paginated lists

### Documentation
- [ ] Handler methods have JSDoc with @param and @returns
- [ ] Error conditions documented with @throws
- [ ] Complex arg types have field descriptions
- [ ] Examples in docs are accurate

## Feedback Templates

### Critical: Raw Error in Handler

```
BLOCKER: Raw Error thrown in RPC handler

**Problem**: Handler throws `new Error()` instead of `ResponseError`
**Location**: `src/rpc-handlers/user-handlers.ts:42`
**Risk**: Client receives untyped error without error code, breaking error handling
**Fix**: Use `ResponseError` with appropriate `ResponseErrorCode`

```typescript
// Before (loses error semantics)
throw new Error('User not found');

// After (typed error with code)
import { ResponseError, ResponseErrorCode } from 'fusion-plugin-rpc';
throw new ResponseError('User not found', ResponseErrorCode.NOT_FOUND);
```
```

### Critical: Missing Type Inference

```
BLOCKER: Missing ExtractServiceType export

**Problem**: Handler plugin has no type export for client hooks
**Location**: `src/rpc-handlers/user-handlers.ts`
**Risk**: `createReactQueryRpcHooks` has no type argument, all hooks return `unknown`
**Fix**: Export handler type via `ExtractServiceType`

```typescript
import type { ExtractServiceType } from 'fusion-core';
import type { UserHandlersPlugin } from './user-handlers';

export type RpcHandlers = ExtractServiceType<typeof UserHandlersPlugin>;
```
```

### Warning: Direct useQuery Instead of useRpcQuery

```
WARNING: Bypassing typed RPC hooks

**Problem**: Component uses `useQuery` directly instead of `useRpcQuery`
**Location**: `src/components/user-profile.tsx:15`
**Risk**: Loses type binding to RPC handler, args and return type unchecked
**Fix**: Use `useRpcQuery` from `createReactQueryRpcHooks`

```tsx
// Before (untyped)
import { useQuery } from '@tanstack/react-query';
const { data } = useQuery({ queryKey: ['getUser', id], queryFn: () => rpc.getUser({ id }) });

// After (typed)
import { useRpcQuery } from '../hooks/rpc';
const { data } = useRpcQuery('getUser', { id });
```
```

### Warning: Unwrapped Atreyu Error

```
WARNING: Atreyu error not wrapped in ResponseError

**Problem**: Service call error propagates raw to client
**Location**: `src/rpc-handlers/order-handlers.ts:28`
**Risk**: Client receives AtreyuError with internal metadata (_atreyu.service, etc.)
**Fix**: Catch Atreyu errors and re-wrap as ResponseError

```typescript
// Before (raw propagation)
getOrder: async (args, ctx) => {
  return await services.orderService.getOrder(args, ctx); // Throws AtreyuError on failure
}

// After (wrapped)
getOrder: async (args, ctx) => {
  try {
    return await services.orderService.getOrder(args, ctx);
  } catch (err) {
    const atreyuErr = err as AtreyuError;
    console.error('Order service error:', atreyuErr._atreyu);
    throw new ResponseError(
      atreyuErr.body?.message ?? 'Failed to fetch order',
      ResponseErrorCode.INTERNAL_SERVER_ERROR,
    );
  }
}
```
```

### Info: Missing Atreyu Config Entry

```
Nit: Service missing from Atreyu config

**Problem**: Handler calls `services.notificationService` but it is not in `atreyu.config.ts`
**Location**: `src/rpc-handlers/notification-handlers.ts:12`
**Risk**: Runtime failure when service connector cannot resolve service
**Fix**: Add service entry to `atreyu.config.ts`

```typescript
// atreyu.config.ts
notificationService: {
  type: 'grpc',
  config: {
    service: 'notification-service',
    protoPath: 'idl/code.uber.internal/notification/v1/notification.proto',
    package: 'uber.notification.v1',
  },
},
```
```

## Common False Positives

| False Positive | Why It's OK | Context |
|----------------|-------------|---------|
| No JSDoc on internal helper | Not a handler method | Private utility within handler file |
| `any` in test mock | Test-only code | `getMockRpcHandlers` return type |
| No `Context` on utility fn | Not an RPC handler | Pure function called by handler |
| Missing pagination on `getUser` | Single resource | Not a list operation |
| No `enabled` guard | Args always defined | Parent ensures props exist |
| Offset pagination | Legacy service constraint | Backend only supports offset |

## Evaluation

```bash
# Check for createPlugin handler patterns
grep -rn "createPlugin" --include="*.ts" | grep -c "provides"

# Check for ResponseError usage (should be > 0)
grep -rn "ResponseError" --include="*.ts" | wc -l

# Check for ResponseErrorCode usage
grep -rn "ResponseErrorCode" --include="*.ts" | wc -l

# Check for ExtractServiceType exports
grep -rn "ExtractServiceType" --include="*.ts" | wc -l

# Check for createReactQueryRpcHooks with type generic
grep -rn "createReactQueryRpcHooks<" --include="*.ts" | wc -l

# Check for useRpcQuery/useRpcMutation usage
grep -rn "useRpcQuery\|useRpcMutation\|useRpcInfiniteQuery" --include="*.tsx" | wc -l

# Check for Atreyu config completeness
grep -rn "protoPath" --include="*.ts" | wc -l

# Anti-patterns (should be 0)
grep -rn "throw new Error(" --include="*.ts" | grep -v "node_modules\|__tests__" | wc -l
grep -rn "isLoading" --include="*.tsx" | grep -v "node_modules\|__tests__" | wc -l
grep -rn "useQuery(" --include="*.tsx" | grep -v "useRpcQuery\|useRpcInfiniteQuery\|node_modules" | wc -l

# Check for raw Atreyu errors (should be 0 — all should be wrapped)
grep -rn "AtreyuError" --include="*.ts" | grep -v "catch\|import\|type" | wc -l

# Deprecated patterns (should be 0)
grep -rn "NextResponse\|NextRequest\|next/server" --include="*.ts" | wc -l
grep -rn "protectedProcedure\|publicProcedure\|TRPCError\|initTRPC" --include="*.ts" | wc -l
```
