Request Lifecycle
Understanding the request lifecycle helps you debug issues and extend @t-req/core’s behavior.
Overview
Section titled “Overview”A request goes through three main phases:
Parse → Interpolate → Execute1. Parse Phase
Section titled “1. Parse Phase”The .http file content is parsed into a structured ParsedRequest object:
import { parse } from '@t-req/core';
const requests = parse(`### Get UsersGET https://api.example.com/users/{{userId}}Authorization: Bearer {{token}}`);
console.log(requests[0]);// {// name: "Get Users",// method: "GET",// url: "https://api.example.com/users/{{userId}}",// headers: { Authorization: "Bearer {{token}}" },// body: undefined,// meta: {}// }At this stage, variables are not replaced—they remain as {{variable}} placeholders.
2. Interpolate Phase
Section titled “2. Interpolate Phase”Variables and resolvers are applied to produce the final request values:
import { interpolate } from '@t-req/core';
const url = interpolate( 'https://api.example.com/users/{{userId}}', { userId: '123' });// "https://api.example.com/users/123"The interpolator:
- Finds all
{{...}}placeholders - Looks up each variable in the provided context
- Calls resolvers for
$resolver()patterns - Replaces placeholders with actual values
3. Execute Phase
Section titled “3. Execute Phase”The interpolated request is executed via the transport layer:
// Internally, @t-req/core builds a fetch Request and executes itconst response = await fetch(url, { method: 'GET', headers: { Authorization: 'Bearer actual-token' },});Client vs Engine
Section titled “Client vs Engine”@t-req/core provides two APIs with different levels of abstraction:
Client (High-Level)
Section titled “Client (High-Level)”The createClient() API handles the full lifecycle automatically:
import { createClient } from '@t-req/core';import { createNodeIO } from '@t-req/core/runtime';
const client = createClient({ io: createNodeIO(), variables: { userId: '123' },});
// Parse + Interpolate + Execute in one callconst response = await client.run('./api/user.http');Engine (Low-Level)
Section titled “Engine (Low-Level)”The createEngine() API gives you more control and emits events:
import { createEngine } from '@t-req/core';import { createFetchTransport } from '@t-req/core/runtime';
const engine = createEngine({ transport: createFetchTransport(fetch), onEvent: (event) => { switch (event.type) { case 'request:start': console.log('Starting:', event.request.url); break; case 'request:complete': console.log('Status:', event.response.status); break; case 'request:error': console.error('Error:', event.error); break; } },});
await engine.runString('GET https://example.com\n');File Loading
Section titled “File Loading”When using client.run('./file.http'), the file is loaded before parsing:
Load File → Parse → Interpolate → ExecuteFor file references in the body (< ./payload.json), loading happens during execution:
Parse → Interpolate → Load Referenced Files → ExecuteRequest Options
Section titled “Request Options”Each phase can be customized with options:
const response = await client.run('./api/user.http', { // Interpolation options variables: { userId: '123' },
// Execution options timeout: 5000, signal: controller.signal,});Error Handling by Phase
Section titled “Error Handling by Phase”Errors can occur at each phase:
| Phase | Error Type | Example |
|---|---|---|
| Parse | ParseError | Invalid .http syntax |
| Interpolate | Variable not found | Missing {{variable}} |
| Execute | Network error | Connection refused |
| Execute | Timeout | Request exceeded timeout |
try { await client.run('./api.http');} catch (error) { if (error instanceof ParseError) { console.error('Invalid .http syntax'); } else if (error.name === 'AbortError') { console.error('Request timed out or cancelled'); } else { console.error('Network error:', error.message); }}