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

advanced-features.mddocs/

0

# Advanced Features

1

2

Advanced functionality including local transport for in-process communication, unstable internal APIs, and utility functions for specialized use cases.

3

4

## Capabilities

5

6

### unstable_localLink

7

8

Creates a local transport link that allows direct in-process communication with a tRPC router, bypassing HTTP entirely. This is useful for server-side rendering, testing, or when both client and server code run in the same process.

9

10

```typescript { .api }

11

/**

12

* Creates a local transport link for in-process router communication

13

* @param opts - Local link configuration options

14

* @returns Local transport link that calls router procedures directly

15

*/

16

function unstable_localLink<TRouter extends AnyRouter>(

17

opts: LocalLinkOptions<TRouter>

18

): TRPCLink<TRouter>;

19

20

interface LocalLinkOptions<TRouter extends AnyRouter> {

21

/** tRPC router instance to call directly */

22

router: TRouter;

23

24

/** Function to create request context */

25

createContext: () => Promise<inferRouterContext<TRouter>>;

26

27

/** Error handler for procedure errors */

28

onError?: (opts: ErrorHandlerOptions<inferRouterContext<TRouter>>) => void;

29

30

/** Data transformation configuration */

31

transformer?: inferClientTypes<TRouter>['transformer'];

32

}

33

34

interface ErrorHandlerOptions<TContext> {

35

/** The tRPC error that occurred */

36

error: TRPCError;

37

/** Operation type that failed */

38

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

39

/** Procedure path that failed */

40

path: string;

41

/** Input data that caused the error */

42

input: unknown;

43

/** Request context at time of error */

44

ctx: TContext | undefined;

45

}

46

47

type inferRouterContext<TRouter extends AnyRouter> =

48

TRouter['_def']['_config']['$types']['ctx'];

49

50

/**

51

* @deprecated Renamed to unstable_localLink, will be removed in future version

52

*/

53

const experimental_localLink = unstable_localLink;

54

```

55

56

**Usage Examples:**

57

58

```typescript

59

import { createTRPCClient, unstable_localLink } from "@trpc/client";

60

import { appRouter } from "./server/router";

61

62

// Basic local link setup

63

const client = createTRPCClient<typeof appRouter>({

64

links: [

65

unstable_localLink({

66

router: appRouter,

67

createContext: async () => ({

68

user: { id: '1', name: 'Test User' },

69

db: mockDatabase,

70

}),

71

}),

72

],

73

});

74

75

// Server-side rendering with Next.js

76

export async function getServerSideProps() {

77

const client = createTRPCClient<AppRouter>({

78

links: [

79

unstable_localLink({

80

router: appRouter,

81

createContext: async () => ({

82

// Create context for SSR

83

user: await getServerSideUser(),

84

db: database,

85

}),

86

}),

87

],

88

});

89

90

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

91

92

return {

93

props: {

94

posts,

95

},

96

};

97

}

98

99

// Testing with local link

100

describe('User operations', () => {

101

const createTestClient = () => createTRPCClient<AppRouter>({

102

links: [

103

unstable_localLink({

104

router: appRouter,

105

createContext: async () => ({

106

user: { id: 'test-user', role: 'admin' },

107

db: testDatabase,

108

}),

109

onError: ({ error, type, path, input }) => {

110

console.error(`Test error in ${type} ${path}:`, error.message);

111

console.error('Input:', input);

112

},

113

}),

114

],

115

});

116

117

it('should create user', async () => {

118

const client = createTestClient();

119

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

120

name: 'Test User',

121

email: 'test@example.com',

122

});

123

expect(user.name).toBe('Test User');

124

});

125

});

126

127

// Development mode with fallback

128

const isDevelopment = process.env.NODE_ENV === 'development';

129

const useLocalRouter = process.env.USE_LOCAL_ROUTER === 'true';

130

131

const client = createTRPCClient<AppRouter>({

132

links: [

133

splitLink({

134

condition: () => isDevelopment && useLocalRouter,

135

true: unstable_localLink({

136

router: appRouter,

137

createContext: async () => developmentContext,

138

}),

139

false: httpBatchLink({

140

url: 'http://localhost:3000/trpc',

141

}),

142

}),

143

],

144

});

145

```

146

147

### Data Transformers

148

149

Utilities for configuring data transformation between client and server, supporting serialization of complex types like dates, sets, and custom objects.

150

151

```typescript { .api }

152

/**

153

* Resolves transformer configuration to a combined data transformer

154

* @param transformer - Transformer options (undefined, single, or separate input/output)

155

* @returns Combined transformer with input and output serialization

156

*/

157

function getTransformer(

158

transformer: TransformerOptions<any>['transformer']

159

): CombinedDataTransformer;

160

161

interface CombinedDataTransformer {

162

/** Input data transformation (client → server) */

163

input: {

164

serialize: (data: any) => any;

165

deserialize: (data: any) => any;

166

};

167

/** Output data transformation (server → client) */

168

output: {

169

serialize: (data: any) => any;

170

deserialize: (data: any) => any;

171

};

172

}

173

174

interface DataTransformerOptions {

175

/** Serialize function for outgoing data */

176

serialize: (data: any) => any;

177

/** Deserialize function for incoming data */

178

deserialize: (data: any) => any;

179

}

180

181

type TransformerOptions<TRoot extends Pick<AnyClientTypes, 'transformer'>> =

182

TRoot['transformer'] extends true

183

? { transformer: DataTransformerOptions }

184

: { transformer?: never };

185

```

186

187

**Transformer Examples:**

188

189

```typescript

190

import superjson from "superjson";

191

import { createTRPCClient, httpBatchLink } from "@trpc/client";

192

193

// Using superjson for Date, Set, Map, etc.

194

const client = createTRPCClient<AppRouter>({

195

links: [

196

httpBatchLink({

197

url: "http://localhost:3000/trpc",

198

transformer: superjson,

199

}),

200

],

201

});

202

203

// Custom transformer for specific data types

204

const customTransformer = {

205

serialize: (data: any) => {

206

// Convert BigInt to string for JSON serialization

207

return JSON.parse(JSON.stringify(data, (key, value) =>

208

typeof value === 'bigint' ? value.toString() + 'n' : value

209

));

210

},

211

deserialize: (data: any) => {

212

// Convert string back to BigInt

213

return JSON.parse(JSON.stringify(data), (key, value) => {

214

if (typeof value === 'string' && value.endsWith('n')) {

215

return BigInt(value.slice(0, -1));

216

}

217

return value;

218

});

219

},

220

};

221

222

const client = createTRPCClient<AppRouter>({

223

links: [

224

httpBatchLink({

225

url: "http://localhost:3000/trpc",

226

transformer: customTransformer,

227

}),

228

],

229

});

230

231

// Separate input and output transformers

232

const asymmetricTransformer = {

233

input: {

234

serialize: (data: any) => {

235

// Client → Server transformation

236

return JSON.stringify(data);

237

},

238

deserialize: (data: any) => {

239

// Server processing of client data

240

return JSON.parse(data);

241

},

242

},

243

output: {

244

serialize: (data: any) => {

245

// Server → Client preparation

246

return JSON.stringify(data);

247

},

248

deserialize: (data: any) => {

249

// Client processing of server data

250

return JSON.parse(data);

251

},

252

},

253

};

254

```

255

256

### Fetch Utilities

257

258

Utilities for resolving and configuring fetch implementations across different environments.

259

260

```typescript { .api }

261

/**

262

* Resolves fetch implementation from custom, window, or globalThis

263

* @param customFetchImpl - Optional custom fetch implementation

264

* @returns Resolved fetch function

265

* @throws Error if no fetch implementation is available

266

*/

267

function getFetch(customFetchImpl?: FetchEsque | NativeFetchEsque): FetchEsque;

268

269

/** Standard fetch function interface */

270

type FetchEsque = (input: RequestInfo, init?: RequestInit) => Promise<Response>;

271

272

/** Native fetch with additional Node.js compatibility */

273

type NativeFetchEsque = typeof fetch;

274

275

/** Request configuration interface */

276

interface RequestInitEsque {

277

method?: string;

278

headers?: Record<string, string> | Headers;

279

body?: string | FormData | URLSearchParams;

280

signal?: AbortSignal;

281

}

282

283

/** Response interface for fetch operations */

284

interface ResponseEsque {

285

ok: boolean;

286

status: number;

287

statusText: string;

288

headers: Headers;

289

json(): Promise<any>;

290

text(): Promise<string>;

291

}

292

```

293

294

**Fetch Utility Examples:**

295

296

```typescript

297

import { getFetch } from "@trpc/client";

298

import fetch from "node-fetch";

299

300

// Node.js environment

301

const nodeFetch = getFetch(fetch as any);

302

303

// Browser environment (automatic detection)

304

const browserFetch = getFetch(); // Uses window.fetch

305

306

// Custom fetch with middleware

307

const customFetch: FetchEsque = async (input, init) => {

308

console.log("Making request to:", input);

309

const response = await fetch(input, init);

310

console.log("Response status:", response.status);

311

return response;

312

};

313

314

const client = createTRPCClient<AppRouter>({

315

links: [

316

httpBatchLink({

317

url: "http://localhost:3000/trpc",

318

fetch: customFetch,

319

}),

320

],

321

});

322

323

// Fetch with retry logic

324

const retryFetch: FetchEsque = async (input, init) => {

325

const maxRetries = 3;

326

let lastError: Error;

327

328

for (let attempt = 0; attempt < maxRetries; attempt++) {

329

try {

330

const response = await fetch(input, init);

331

if (response.ok) {

332

return response;

333

}

334

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

335

} catch (error) {

336

lastError = error as Error;

337

if (attempt < maxRetries - 1) {

338

await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));

339

}

340

}

341

}

342

343

throw lastError!;

344

};

345

```

346

347

### Connection State Management

348

349

Types and utilities for managing connection states in WebSocket and subscription scenarios.

350

351

```typescript { .api }

352

/** Connection state for real-time transports */

353

type TRPCConnectionState<TError = unknown> =

354

| { type: 'connecting' }

355

| { type: 'open' }

356

| { type: 'closed' }

357

| { type: 'error'; error: TError };

358

359

/** Observable connection state management */

360

interface ConnectionStateManager<TError> {

361

/** Current connection state */

362

state: TRPCConnectionState<TError>;

363

/** Subscribe to state changes */

364

subscribe: (observer: { next: (state: TRPCConnectionState<TError>) => void }) => Unsubscribable;

365

/** Update connection state */

366

next: (state: TRPCConnectionState<TError>) => void;

367

}

368

```

369

370

**Connection State Examples:**

371

372

```typescript

373

// Monitor connection state in React

374

function useConnectionState() {

375

const [connectionState, setConnectionState] = useState<TRPCConnectionState>({ type: 'closed' });

376

377

useEffect(() => {

378

const wsClient = createWSClient({

379

url: "ws://localhost:3001",

380

onOpen: () => setConnectionState({ type: 'open' }),

381

onClose: () => setConnectionState({ type: 'closed' }),

382

onError: (error) => setConnectionState({ type: 'error', error }),

383

});

384

385

// Monitor connection state changes

386

const subscription = wsClient.connectionState.subscribe({

387

next: setConnectionState,

388

});

389

390

return () => {

391

subscription.unsubscribe();

392

wsClient.close();

393

};

394

}, []);

395

396

return connectionState;

397

}

398

399

// Connection state in local link

400

const client = createTRPCClient<AppRouter>({

401

links: [

402

unstable_localLink({

403

router: appRouter,

404

createContext: async () => ({ db: mockDatabase }),

405

onError: ({ error, type, path }) => {

406

// Handle local procedure errors

407

console.error(`Local ${type} error in ${path}:`, error.message);

408

},

409

}),

410

],

411

});

412

```

413

414

### Type Utilities

415

416

Advanced TypeScript utilities for working with tRPC client types.

417

418

```typescript { .api }

419

/** Infer client types from router definition */

420

type inferClientTypes<TRouter extends AnyRouter> = TRouter['_def']['_config']['$types'];

421

422

/** Infer router client interface */

423

type inferRouterClient<TRouter extends AnyRouter> = TRPCClient<TRouter>;

424

425

/** Infer procedure input type */

426

type inferProcedureInput<TProcedure extends AnyProcedure> = TProcedure['_def']['input'];

427

428

/** Infer procedure output type */

429

type inferProcedureOutput<TProcedure extends AnyProcedure> = TProcedure['_def']['output'];

430

431

/** Any router type constraint */

432

type AnyRouter = {

433

_def: {

434

_config: { $types: any };

435

record: Record<string, any>;

436

};

437

};

438

439

/** Any procedure type constraint */

440

type AnyProcedure = {

441

_def: {

442

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

443

input: any;

444

output: any;

445

};

446

};

447

```

448

449

**Type Utility Examples:**

450

451

```typescript

452

import type { inferRouterClient, inferProcedureInput } from "@trpc/client";

453

454

// Infer client type from router

455

type MyClient = inferRouterClient<typeof appRouter>;

456

457

// Infer input type for specific procedure

458

type CreateUserInput = inferProcedureInput<typeof appRouter.user.create>;

459

460

// Type-safe client wrapper

461

class TypedTRPCClient<TRouter extends AnyRouter> {

462

constructor(private client: inferRouterClient<TRouter>) {}

463

464

async safeQuery<TPath extends keyof TRouter['_def']['record']>(

465

path: TPath,

466

input?: inferProcedureInput<TRouter['_def']['record'][TPath]>

467

) {

468

try {

469

return await (this.client as any)[path].query(input);

470

} catch (error) {

471

if (isTRPCClientError(error)) {

472

console.error(`Query ${String(path)} failed:`, error.message);

473

return null;

474

}

475

throw error;

476

}

477

}

478

}

479

480

// Usage with inferred types

481

const typedClient = new TypedTRPCClient(client);

482

const user = await typedClient.safeQuery('user.getById', { id: 1 });

483

```

484

485

### Development and Testing Utilities

486

487

Utilities for development, testing, and debugging tRPC applications.

488

489

```typescript { .api }

490

/** Mock client factory for testing */

491

interface MockClientOptions<TRouter extends AnyRouter> {

492

router?: Partial<TRouter>;

493

mockResponses?: Record<string, any>;

494

delay?: number;

495

errorRate?: number;

496

}

497

498

/** Development debugging helpers */

499

interface DebugOptions {

500

logLevel: 'none' | 'error' | 'warn' | 'info' | 'debug';

501

includeContext: boolean;

502

includeInput: boolean;

503

includeOutput: boolean;

504

}

505

```

506

507

**Development Utility Examples:**

508

509

```typescript

510

// Mock client for testing

511

function createMockClient<TRouter extends AnyRouter>(

512

options: MockClientOptions<TRouter>

513

): TRPCClient<TRouter> {

514

return createTRPCClient<TRouter>({

515

links: [

516

unstable_localLink({

517

router: {

518

user: {

519

getById: async ({ input }: { input: { id: number } }) => {

520

await new Promise(resolve => setTimeout(resolve, options.delay || 0));

521

522

if (Math.random() < (options.errorRate || 0)) {

523

throw new Error('Mock error');

524

}

525

526

return options.mockResponses?.[`user.getById.${input.id}`] || {

527

id: input.id,

528

name: `Mock User ${input.id}`,

529

};

530

},

531

},

532

} as any,

533

createContext: async () => ({}),

534

}),

535

],

536

});

537

}

538

539

// Debug client wrapper

540

function createDebugClient<TRouter extends AnyRouter>(

541

baseClient: TRPCClient<TRouter>,

542

options: DebugOptions

543

): TRPCClient<TRouter> {

544

const handler = {

545

get(target: any, prop: string) {

546

const value = target[prop];

547

548

if (typeof value === 'object' && value !== null) {

549

return new Proxy(value, handler);

550

}

551

552

if (typeof value === 'function' && ['query', 'mutate', 'subscribe'].includes(prop)) {

553

return new Proxy(value, {

554

apply(target, thisArg, args) {

555

if (options.logLevel !== 'none') {

556

console.log(`Debug: ${prop} called with:`, args);

557

}

558

559

const result = target.apply(thisArg, args);

560

561

if (result instanceof Promise) {

562

return result.then(

563

(data) => {

564

if (options.includeOutput && options.logLevel !== 'none') {

565

console.log(`Debug: ${prop} result:`, data);

566

}

567

return data;

568

},

569

(error) => {

570

if (options.logLevel !== 'none') {

571

console.error(`Debug: ${prop} error:`, error);

572

}

573

throw error;

574

}

575

);

576

}

577

578

return result;

579

},

580

});

581

}

582

583

return value;

584

},

585

};

586

587

return new Proxy(baseClient, handler);

588

}

589

590

// Usage in tests

591

describe('User API', () => {

592

const mockClient = createMockClient<AppRouter>({

593

mockResponses: {

594

'user.getById.1': { id: 1, name: 'Alice', email: 'alice@example.com' },

595

'user.getById.2': { id: 2, name: 'Bob', email: 'bob@example.com' },

596

},

597

delay: 100,

598

errorRate: 0.1,

599

});

600

601

it('should fetch user by ID', async () => {

602

const user = await mockClient.user.getById.query({ id: 1 });

603

expect(user.name).toBe('Alice');

604

});

605

});

606

```