or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-operations.mderror-handling.mdindex.mdinterceptors.mdutilities.md

interceptors.mddocs/

0

# Request and Response Interceptors

1

2

Lifecycle hooks for modifying requests, handling responses, implementing logging, authentication, and custom logic during the fetch process.

3

4

## Capabilities

5

6

### Interceptor System Overview

7

8

ofetch provides four lifecycle hooks that allow you to intercept and modify requests and responses at different stages of the fetch process.

9

10

```typescript { .api }

11

interface FetchHooks<T = any, R extends ResponseType = ResponseType> {

12

onRequest?: MaybeArray<FetchHook<FetchContext<T, R>>>;

13

onRequestError?: MaybeArray<FetchHook<FetchContext<T, R> & { error: Error }>>;

14

onResponse?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;

15

onResponseError?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;

16

}

17

18

type FetchHook<C extends FetchContext = FetchContext> = (

19

context: C

20

) => MaybePromise<void>;

21

22

type MaybePromise<T> = T | Promise<T>;

23

type MaybeArray<T> = T | T[];

24

```

25

26

### Request Interceptor

27

28

Called before the request is sent, allowing modification of options, URL, headers, and logging.

29

30

```typescript { .api }

31

/**

32

* Hook called before request is sent

33

* @param context - Contains request and options that can be modified

34

*/

35

onRequest?: MaybeArray<FetchHook<FetchContext<T, R>>>;

36

37

interface FetchContext<T = any, R extends ResponseType = ResponseType> {

38

request: FetchRequest;

39

options: ResolvedFetchOptions<R>;

40

response?: FetchResponse<T>;

41

error?: Error;

42

}

43

```

44

45

**Usage Examples:**

46

47

```typescript

48

import { ofetch } from "ofetch";

49

50

// Add authentication token

51

const api = ofetch.create({

52

baseURL: "https://api.example.com",

53

async onRequest({ request, options }) {

54

// Add auth token to all requests

55

const token = await getAuthToken();

56

options.headers.set("Authorization", `Bearer ${token}`);

57

}

58

});

59

60

// Request logging

61

const api = ofetch.create({

62

onRequest({ request, options }) {

63

console.log(`[${options.method || "GET"}] ${request}`);

64

console.log("Headers:", Object.fromEntries(options.headers));

65

}

66

});

67

68

// Add timestamp to query parameters

69

const api = ofetch.create({

70

onRequest({ request, options }) {

71

options.query = options.query || {};

72

options.query.timestamp = Date.now();

73

}

74

});

75

76

// Multiple request hooks

77

const data = await ofetch("/api/data", {

78

onRequest: [

79

({ options }) => {

80

options.headers.set("X-Client", "ofetch");

81

},

82

async ({ options }) => {

83

const userId = await getCurrentUserId();

84

options.headers.set("X-User-ID", userId);

85

}

86

]

87

});

88

```

89

90

### Request Error Interceptor

91

92

Called when the fetch request fails (network error, timeout, etc.), before retry logic is applied.

93

94

```typescript { .api }

95

/**

96

* Hook called when fetch request encounters an error

97

* @param context - Contains request, options, and error

98

*/

99

onRequestError?: MaybeArray<FetchHook<FetchContext<T, R> & { error: Error }>>;

100

```

101

102

**Usage Examples:**

103

104

```typescript

105

import { ofetch } from "ofetch";

106

107

// Log request errors

108

const api = ofetch.create({

109

onRequestError({ request, error }) {

110

console.error(`Request failed: ${request}`, error.message);

111

}

112

});

113

114

// Custom error handling

115

const api = ofetch.create({

116

onRequestError({ request, options, error }) {

117

if (error.name === "TimeoutError") {

118

console.log(`Request to ${request} timed out after ${options.timeout}ms`);

119

} else if (error.name === "AbortError") {

120

console.log(`Request to ${request} was aborted`);

121

}

122

}

123

});

124

125

// Error metrics collection

126

const api = ofetch.create({

127

onRequestError({ request, error }) {

128

metrics.increment("http.request.error", {

129

url: String(request),

130

error_type: error.name

131

});

132

}

133

});

134

```

135

136

### Response Interceptor

137

138

Called after a successful response is received and parsed, allowing response modification and logging.

139

140

```typescript { .api }

141

/**

142

* Hook called after successful response is received and parsed

143

* @param context - Contains request, options, and response with parsed data

144

*/

145

onResponse?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;

146

147

interface FetchResponse<T> extends Response {

148

_data?: T;

149

}

150

```

151

152

**Usage Examples:**

153

154

```typescript

155

import { ofetch } from "ofetch";

156

157

// Response logging

158

const api = ofetch.create({

159

onResponse({ request, response }) {

160

console.log(`[${response.status}] ${request}`);

161

console.log("Response time:", response.headers.get("x-response-time"));

162

}

163

});

164

165

// Response caching

166

const cache = new Map();

167

const api = ofetch.create({

168

onResponse({ request, response }) {

169

if (response.status === 200) {

170

cache.set(String(request), response._data);

171

}

172

}

173

});

174

175

// Response transformation

176

const api = ofetch.create({

177

onResponse({ response }) {

178

// Transform all responses to include metadata

179

if (response._data && typeof response._data === "object") {

180

response._data.meta = {

181

status: response.status,

182

timestamp: Date.now()

183

};

184

}

185

}

186

});

187

188

// Response validation

189

const api = ofetch.create({

190

onResponse({ response }) {

191

if (response._data && !isValidResponse(response._data)) {

192

throw new Error("Invalid response format");

193

}

194

}

195

});

196

```

197

198

### Response Error Interceptor

199

200

Called when a response has an error status (4xx, 5xx) but fetch succeeded, before retry logic is applied.

201

202

```typescript { .api }

203

/**

204

* Hook called when response has error status (4xx, 5xx)

205

* @param context - Contains request, options, and error response

206

*/

207

onResponseError?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;

208

```

209

210

**Usage Examples:**

211

212

```typescript

213

import { ofetch } from "ofetch";

214

215

// Handle authentication errors

216

const api = ofetch.create({

217

async onResponseError({ response }) {

218

if (response.status === 401) {

219

await refreshAuthToken();

220

// Note: This won't retry the request automatically

221

// You'd need to handle retry manually or let default retry logic handle it

222

}

223

}

224

});

225

226

// Log error responses

227

const api = ofetch.create({

228

onResponseError({ request, response }) {

229

console.error(`[${response.status}] ${request}:`, response._data);

230

}

231

});

232

233

// Custom error handling by status

234

const api = ofetch.create({

235

onResponseError({ response }) {

236

switch (response.status) {

237

case 400:

238

console.error("Bad request:", response._data);

239

break;

240

case 403:

241

console.error("Forbidden - check permissions");

242

break;

243

case 429:

244

console.warn("Rate limited - request will be retried");

245

break;

246

case 500:

247

console.error("Server error:", response._data);

248

break;

249

}

250

}

251

});

252

```

253

254

### Hook Chaining and Arrays

255

256

Multiple hooks can be provided as arrays and will be executed sequentially.

257

258

**Usage Examples:**

259

260

```typescript

261

import { ofetch } from "ofetch";

262

263

// Multiple hooks as array

264

const api = ofetch.create({

265

onRequest: [

266

({ options }) => {

267

options.headers.set("X-Client", "ofetch");

268

},

269

async ({ options }) => {

270

const token = await getToken();

271

options.headers.set("Authorization", `Bearer ${token}`);

272

},

273

({ request, options }) => {

274

console.log(`[${options.method || "GET"}] ${request}`);

275

}

276

],

277

onResponse: [

278

({ response }) => {

279

console.log(`Response: ${response.status}`);

280

},

281

({ response }) => {

282

metrics.recordResponseTime(response.headers.get("x-response-time"));

283

}

284

]

285

});

286

287

// Per-request hooks combined with instance hooks

288

const data = await api("/api/data", {

289

onRequest({ options }) {

290

options.headers.set("X-Custom", "per-request");

291

},

292

onResponse({ response }) {

293

console.log("Per-request response handler");

294

}

295

});

296

```

297

298

### Advanced Hook Patterns

299

300

Complex interceptor patterns for authentication, caching, and error handling.

301

302

**Usage Examples:**

303

304

```typescript

305

import { ofetch } from "ofetch";

306

307

// Automatic token refresh with retry

308

const api = ofetch.create({

309

baseURL: "https://api.example.com",

310

async onRequest({ options }) {

311

const token = await getValidToken(); // Refreshes if expired

312

options.headers.set("Authorization", `Bearer ${token}`);

313

},

314

async onResponseError({ response, request, options }) {

315

if (response.status === 401) {

316

await clearTokenCache();

317

// Let retry logic handle the retry with fresh token

318

}

319

}

320

});

321

322

// Request/response correlation

323

const correlationMap = new Map();

324

const api = ofetch.create({

325

onRequest({ request, options }) {

326

const id = Math.random().toString(36);

327

options.headers.set("X-Correlation-ID", id);

328

correlationMap.set(id, { start: Date.now(), request });

329

},

330

onResponse({ response }) {

331

const id = response.headers.get("X-Correlation-ID");

332

if (id && correlationMap.has(id)) {

333

const { start, request } = correlationMap.get(id);

334

console.log(`${request} completed in ${Date.now() - start}ms`);

335

correlationMap.delete(id);

336

}

337

}

338

});

339

340

// Conditional interceptors

341

const api = ofetch.create({

342

onRequest({ request, options }) {

343

// Only add analytics for specific endpoints

344

if (String(request).includes("/api/analytics")) {

345

options.headers.set("X-Analytics", "true");

346

}

347

},

348

onResponse({ response, request }) {

349

// Only cache GET requests with 2xx status

350

if (options.method === "GET" && response.ok) {

351

cacheResponse(String(request), response._data);

352

}

353

}

354

});

355

```