or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdclient-creation.mderror-handling.mdhttp-links.mdindex.mdutility-links.mdwebsocket-links.md

error-handling.mddocs/

0

# Error Handling

1

2

Comprehensive error handling system with structured error information and type guards for error identification. The tRPC client provides detailed error information including server responses, network issues, and client-side problems.

3

4

## Capabilities

5

6

### TRPCClientError

7

8

Main error class for all tRPC client operations, extending the standard JavaScript Error with structured error data and metadata.

9

10

```typescript { .api }

11

/**

12

* Main error class for tRPC client operations

13

* Extends Error with structured error data and metadata

14

*/

15

class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes> extends Error {

16

/** Error message */

17

readonly message: string;

18

19

/** Structured error data from server */

20

readonly shape: Maybe<inferErrorShape<TRouterOrProcedure>>;

21

22

/** Error-specific data payload */

23

readonly data: Maybe<inferErrorShape<TRouterOrProcedure>['data']>;

24

25

/** Original error cause */

26

readonly cause: Error;

27

28

/** Additional metadata (e.g., HTTP response details) */

29

meta: Record<string, unknown>;

30

31

constructor(

32

message: string,

33

opts?: {

34

result?: Maybe<TRPCErrorResponse<inferErrorShape<TRouterOrProcedure>>>;

35

cause?: Error;

36

meta?: Record<string, unknown>;

37

}

38

);

39

40

/**

41

* Creates TRPCClientError from various error sources

42

* @param cause - Error source (Error, TRPCErrorResponse, or object)

43

* @param opts - Additional options including metadata

44

* @returns Properly formatted TRPCClientError instance

45

*/

46

static from<TRouterOrProcedure extends InferrableClientTypes>(

47

cause: Error | TRPCErrorResponse | object,

48

opts?: { meta?: Record<string, unknown> }

49

): TRPCClientError<TRouterOrProcedure>;

50

}

51

52

/** Server error response structure */

53

interface TRPCErrorResponse<TShape> {

54

error: {

55

code: number;

56

message: string;

57

data?: TShape['data'];

58

};

59

}

60

61

/** Utility type for nullable values */

62

type Maybe<T> = T | null | undefined;

63

64

/** Infer error shape from router or procedure type */

65

type inferErrorShape<TInferrable extends InferrableClientTypes> =

66

inferClientTypes<TInferrable>['errorShape'];

67

```

68

69

**Usage Examples:**

70

71

```typescript

72

import { TRPCClientError, isTRPCClientError } from "@trpc/client";

73

74

// Basic error handling

75

try {

76

const user = await client.user.getById.query({ id: 999 });

77

} catch (error) {

78

if (error instanceof TRPCClientError) {

79

console.log("tRPC Error:", error.message);

80

console.log("Error code:", error.data?.code);

81

console.log("Error details:", error.data);

82

83

// Access HTTP metadata if available

84

if (error.meta?.response) {

85

console.log("HTTP status:", error.meta.response.status);

86

}

87

}

88

}

89

90

// Create error from different sources

91

const networkError = new Error("Network timeout");

92

const trpcError = TRPCClientError.from(networkError, {

93

meta: { source: "network" }

94

});

95

96

// Server error response

97

const serverErrorResponse = {

98

error: {

99

code: -32602,

100

message: "Invalid params",

101

data: { field: "id", issue: "required" }

102

}

103

};

104

const validationError = TRPCClientError.from(serverErrorResponse);

105

```

106

107

### isTRPCClientError

108

109

Type guard function to safely check if an unknown error is a TRPCClientError instance.

110

111

```typescript { .api }

112

/**

113

* Type guard to check if error is TRPCClientError instance

114

* @param cause - Unknown error value to check

115

* @returns True if cause is TRPCClientError, false otherwise

116

*/

117

function isTRPCClientError<TInferrable extends InferrableClientTypes>(

118

cause: unknown

119

): cause is TRPCClientError<TInferrable>;

120

```

121

122

**Usage Examples:**

123

124

```typescript

125

// Safe error type checking

126

function handleError(error: unknown) {

127

if (isTRPCClientError(error)) {

128

// TypeScript knows this is TRPCClientError

129

console.log("tRPC error:", error.message);

130

console.log("Error shape:", error.shape);

131

console.log("Error data:", error.data);

132

133

// Handle specific error codes

134

if (error.data?.code === "UNAUTHORIZED") {

135

redirectToLogin();

136

} else if (error.data?.code === "FORBIDDEN") {

137

showAccessDeniedMessage();

138

}

139

} else if (error instanceof Error) {

140

// Standard JavaScript error

141

console.log("Standard error:", error.message);

142

} else {

143

// Unknown error type

144

console.log("Unknown error:", error);

145

}

146

}

147

148

// Use in async error handling

149

async function fetchUserData(id: number) {

150

try {

151

return await client.user.getById.query({ id });

152

} catch (error) {

153

if (isTRPCClientError(error)) {

154

// Handle tRPC-specific errors

155

throw new Error(`Failed to fetch user: ${error.message}`);

156

}

157

// Re-throw other errors

158

throw error;

159

}

160

}

161

```

162

163

### Error Categories

164

165

Different types of errors that can occur in tRPC client operations.

166

167

```typescript { .api }

168

/** Network and connection errors */

169

interface NetworkError {

170

type: 'network';

171

cause: Error;

172

meta?: {

173

url?: string;

174

method?: string;

175

timeout?: boolean;

176

};

177

}

178

179

/** HTTP response errors */

180

interface HTTPError {

181

type: 'http';

182

status: number;

183

statusText: string;

184

meta: {

185

response: Response;

186

responseJSON?: any;

187

};

188

}

189

190

/** Server-side validation or business logic errors */

191

interface ServerError {

192

type: 'server';

193

code: string | number;

194

message: string;

195

data?: Record<string, any>;

196

}

197

198

/** Client-side validation or configuration errors */

199

interface ClientError {

200

type: 'client';

201

cause: Error;

202

operation?: string;

203

}

204

```

205

206

**Error Category Examples:**

207

208

```typescript

209

// Network error handling

210

try {

211

const result = await client.posts.getAll.query();

212

} catch (error) {

213

if (isTRPCClientError(error)) {

214

// Check if it's a network error

215

if (error.cause?.name === 'TypeError' && error.cause?.message.includes('fetch')) {

216

console.log("Network error - check connection");

217

showOfflineMessage();

218

return;

219

}

220

221

// Check HTTP status codes

222

const httpStatus = error.meta?.response?.status;

223

if (httpStatus === 500) {

224

console.log("Server error - try again later");

225

showRetryButton();

226

} else if (httpStatus === 404) {

227

console.log("Resource not found");

228

showNotFoundMessage();

229

}

230

231

// Check tRPC error codes

232

if (error.data?.code === 'TIMEOUT') {

233

console.log("Request timeout - try again");

234

} else if (error.data?.code === 'PARSE_ERROR') {

235

console.log("Invalid request format");

236

}

237

}

238

}

239

```

240

241

### HTTP Error Details

242

243

Access detailed HTTP response information from failed requests.

244

245

```typescript { .api }

246

/** HTTP response metadata structure */

247

interface HTTPResponseMeta {

248

response: Response;

249

responseJSON?: any;

250

request?: {

251

url: string;

252

method: string;

253

headers: Record<string, string>;

254

};

255

}

256

```

257

258

**HTTP Error Examples:**

259

260

```typescript

261

// Detailed HTTP error analysis

262

try {

263

const user = await client.user.update.mutate({ id: 1, name: "New Name" });

264

} catch (error) {

265

if (isTRPCClientError(error) && error.meta?.response) {

266

const response = error.meta.response as Response;

267

268

console.log("HTTP Status:", response.status);

269

console.log("Status Text:", response.statusText);

270

console.log("Response Headers:", Object.fromEntries(response.headers.entries()));

271

272

// Handle specific HTTP status codes

273

switch (response.status) {

274

case 400:

275

console.log("Bad Request - check input data");

276

if (error.data?.validationErrors) {

277

displayValidationErrors(error.data.validationErrors);

278

}

279

break;

280

case 401:

281

console.log("Unauthorized - refresh token");

282

await refreshAuthToken();

283

break;

284

case 403:

285

console.log("Forbidden - insufficient permissions");

286

showPermissionError();

287

break;

288

case 404:

289

console.log("Not Found - resource doesn't exist");

290

redirectToNotFound();

291

break;

292

case 429:

293

console.log("Rate Limited - wait before retry");

294

const retryAfter = response.headers.get('Retry-After');

295

scheduleRetry(parseInt(retryAfter || '60'));

296

break;

297

case 500:

298

console.log("Server Error - report to support");

299

reportServerError(error);

300

break;

301

}

302

303

// Access raw response body if available

304

if (error.meta.responseJSON) {

305

console.log("Response Body:", error.meta.responseJSON);

306

}

307

}

308

}

309

```

310

311

### Validation Error Handling

312

313

Handle structured validation errors from input/output schema validation.

314

315

```typescript { .api }

316

/** Validation error structure */

317

interface ValidationError {

318

code: string;

319

message: string;

320

path: (string | number)[];

321

expected?: any;

322

received?: any;

323

}

324

325

/** Validation error response */

326

interface ValidationErrorResponse {

327

code: 'BAD_REQUEST' | 'VALIDATION_ERROR';

328

message: string;

329

validationErrors: ValidationError[];

330

}

331

```

332

333

**Validation Error Examples:**

334

335

```typescript

336

// Handle input validation errors

337

try {

338

const user = await client.user.create.mutate({

339

name: "", // Invalid empty name

340

email: "invalid-email", // Invalid email format

341

age: -5, // Invalid negative age

342

});

343

} catch (error) {

344

if (isTRPCClientError(error) && error.data?.code === 'BAD_REQUEST') {

345

const validationErrors = error.data.validationErrors as ValidationError[];

346

347

validationErrors.forEach((err) => {

348

const fieldPath = err.path.join('.');

349

console.log(`Validation error in ${fieldPath}: ${err.message}`);

350

351

// Show field-specific errors in UI

352

if (fieldPath === 'email') {

353

showFieldError('email', 'Please enter a valid email address');

354

} else if (fieldPath === 'name') {

355

showFieldError('name', 'Name is required');

356

} else if (fieldPath === 'age') {

357

showFieldError('age', 'Age must be a positive number');

358

}

359

});

360

}

361

}

362

363

// Handle output validation errors (server response doesn't match schema)

364

try {

365

const posts = await client.posts.getAll.query();

366

} catch (error) {

367

if (isTRPCClientError(error) && error.data?.code === 'INTERNAL_SERVER_ERROR') {

368

if (error.message.includes('output validation')) {

369

console.error("Server returned invalid data format");

370

// Report to monitoring service

371

reportDataIntegrityIssue(error);

372

}

373

}

374

}

375

```

376

377

### Error Recovery Strategies

378

379

Common patterns for recovering from different types of errors.

380

381

```typescript { .api }

382

/** Retry configuration options */

383

interface RetryOptions {

384

maxAttempts: number;

385

delayMs: number | ((attempt: number) => number);

386

retryIf: (error: TRPCClientError<any>) => boolean;

387

}

388

```

389

390

**Error Recovery Examples:**

391

392

```typescript

393

// Exponential backoff retry

394

async function withRetry<T>(

395

operation: () => Promise<T>,

396

options: RetryOptions

397

): Promise<T> {

398

let lastError: TRPCClientError<any>;

399

400

for (let attempt = 0; attempt < options.maxAttempts; attempt++) {

401

try {

402

return await operation();

403

} catch (error) {

404

if (!isTRPCClientError(error) || !options.retryIf(error)) {

405

throw error;

406

}

407

408

lastError = error;

409

410

if (attempt < options.maxAttempts - 1) {

411

const delay = typeof options.delayMs === 'function'

412

? options.delayMs(attempt)

413

: options.delayMs;

414

await new Promise(resolve => setTimeout(resolve, delay));

415

}

416

}

417

}

418

419

throw lastError;

420

}

421

422

// Usage with retry strategy

423

const userData = await withRetry(

424

() => client.user.getById.query({ id: 1 }),

425

{

426

maxAttempts: 3,

427

delayMs: (attempt) => Math.min(1000 * Math.pow(2, attempt), 10000),

428

retryIf: (error) => {

429

// Retry on network errors and 5xx server errors

430

const isNetworkError = error.cause?.name === 'TypeError';

431

const isServerError = error.meta?.response?.status >= 500;

432

return isNetworkError || isServerError;

433

}

434

}

435

);

436

437

// Graceful degradation

438

async function getUserWithFallback(id: number) {

439

try {

440

return await client.user.getById.query({ id });

441

} catch (error) {

442

if (isTRPCClientError(error)) {

443

console.warn("Failed to fetch user, using cached data:", error.message);

444

return getCachedUser(id);

445

}

446

throw error;

447

}

448

}

449

450

// Circuit breaker pattern

451

class CircuitBreaker {

452

private failures = 0;

453

private readonly threshold = 5;

454

private readonly timeoutMs = 30000;

455

private lastFailTime = 0;

456

457

async execute<T>(operation: () => Promise<T>): Promise<T> {

458

if (this.isOpen()) {

459

throw new Error("Circuit breaker is open");

460

}

461

462

try {

463

const result = await operation();

464

this.onSuccess();

465

return result;

466

} catch (error) {

467

this.onFailure();

468

throw error;

469

}

470

}

471

472

private isOpen(): boolean {

473

return this.failures >= this.threshold &&

474

(Date.now() - this.lastFailTime) < this.timeoutMs;

475

}

476

477

private onSuccess(): void {

478

this.failures = 0;

479

}

480

481

private onFailure(): void {

482

this.failures++;

483

this.lastFailTime = Date.now();

484

}

485

}

486

```

487

488

### Error Monitoring and Reporting

489

490

Integration with error monitoring services for production error tracking.

491

492

```typescript { .api }

493

/** Error context for monitoring services */

494

interface ErrorContext {

495

operation: {

496

type: 'query' | 'mutation' | 'subscription';

497

path: string;

498

input?: any;

499

};

500

user?: {

501

id: string;

502

email?: string;

503

};

504

session?: {

505

id: string;

506

duration: number;

507

};

508

environment: {

509

userAgent: string;

510

url: string;

511

timestamp: number;

512

};

513

}

514

```

515

516

**Error Monitoring Examples:**

517

518

```typescript

519

// Global error handler for monitoring

520

function setupErrorMonitoring() {

521

const originalConsoleError = console.error;

522

523

console.error = (...args) => {

524

args.forEach(arg => {

525

if (isTRPCClientError(arg)) {

526

reportErrorToMonitoring(arg);

527

}

528

});

529

originalConsoleError(...args);

530

};

531

}

532

533

function reportErrorToMonitoring(error: TRPCClientError<any>) {

534

const context: ErrorContext = {

535

operation: {

536

type: error.meta?.operationType || 'unknown',

537

path: error.meta?.operationPath || 'unknown',

538

input: error.meta?.operationInput,

539

},

540

user: getCurrentUser(),

541

session: getCurrentSession(),

542

environment: {

543

userAgent: navigator.userAgent,

544

url: window.location.href,

545

timestamp: Date.now(),

546

},

547

};

548

549

// Send to monitoring service (Sentry, Datadog, etc.)

550

monitoringService.captureException(error, context);

551

}

552

553

// Custom error boundary for React applications

554

class TRPCErrorBoundary extends React.Component {

555

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {

556

if (isTRPCClientError(error)) {

557

reportErrorToMonitoring(error);

558

559

// Show user-friendly error message

560

this.setState({

561

hasError: true,

562

errorType: 'trpc',

563

canRetry: this.isRetryableError(error),

564

});

565

}

566

}

567

568

private isRetryableError(error: TRPCClientError<any>): boolean {

569

const httpStatus = error.meta?.response?.status;

570

return httpStatus === 429 || httpStatus >= 500 ||

571

error.cause?.name === 'TypeError';

572

}

573

}

574

```