@http-forge/core
v0.5.3
Published
Headless HTTP testing engine with Postman collection support, dynamic variables, and script-based automation.
Maintainers
Readme
@http-forge/core
Standalone HTTP testing engine with Postman collection support and JavaScript-based automation.
📦 What is @http-forge/core?
@http-forge/core is a headless, framework-agnostic HTTP execution engine with full Postman collection compatibility. Execute complex API workflows, test suites, and automated flows without the overhead of a UI.
Core Features:
- 🚀 Postman Collections - Load and execute
.postman_collection.jsonand.forge.jsonfiles - 📝 JavaScript Scripting - Pre-request and post-response scripts with full
pm.*API (variables, assertions, execution flow, visualizer) - 🔄 Dynamic Variables - Built-in generators:
{{$randomInt}},{{$timestamp}},{{$uuid}},{{$guid}}, etc. - 🌍 Environments - Full variable scoping (globals, collection, environment, session, iterationData)
- � Cloud Secret Resolvers - Resolve
{{secret:alias/path}}from AWS Secrets Manager, Azure Key Vault, Google Secret Manager, HashiCorp Vault, 1Password, and Doppler; credentials read from the ambient environment, never from config - �👁️ File Watching - Automatic reload on collection/environment file changes with notification callbacks
- 🍪 Cookie Persistence - Automatic cookie storage and reuse,
pm.cookies.jar()and.toObject() - 📊 Test Assertions - BDD-style testing with
pm.test()(sync/async),pm.test.skip()/.fail()/.index(), full Chaiexpect()chains (incl..contain()), and JSON Schema validation viapm.response.to.have.jsonSchema() - 🔐 CryptoJS - Full crypto library: hash, HMAC, AES/DES/TripleDES, PBKDF2, encoding helpers
- 🎯 Execution Flow -
pm.setNextRequest(),pm.execution.skipRequest()for suite runner flow control - 📈 Visualizer -
pm.visualizer.set(template, data)for custom Handlebars-based HTML output - 🔌 Extensible - Custom interceptors, HTTP clients, and module loaders
Ideal for:
- CI/CD pipeline integration (GitHub Actions, GitLab CI, Jenkins)
- Headless API testing and contract validation
- Building custom API testing CLIs
- Load testing and performance monitoring
- Automated integration test suites
🎯 Installation
Requires Node.js 20+.
npm install @http-forge/coreOr using the tarball:
npm install ./http-forge-core-0.1.0.tgzRuntime APIs (Headless + MCP)
For current programmatic runtime APIs, see RUNTIME-API.md:
runRequest(options)runCollection(options)runSuite(options)createMcpRuntime(options)
This covers direct execution, embeddable MCP runtime lifecycle, response include options, and report behavior.
⚡ Quick Start
Basic Usage
import { ForgeContainer } from '@http-forge/core';
// Create a container with default settings
const forge = new ForgeContainer();
// Load a collection
const collection = await forge.loadCollection('./my-api.forge.json');
// Execute a request
const result = await forge.execute(collection.items[0], collection);
console.log(result.response.status); // 200
console.log(result.response.body); // Response data
console.log(result.postResponseResult?.assertions); // Test resultsWith Environment Variables
const forge = new ForgeContainer();
// Set environment variables
forge.setEnvironment({
baseUrl: 'https://api.example.com',
apiKey: 'your-api-key',
timeout: '5000'
});
// Variables are automatically interpolated in requests
// URL: {{baseUrl}}/users -> https://api.example.com/users
const result = await forge.execute(request, collection);With Custom Configuration
const forge = new ForgeContainer({
// Use native Node.js http/https instead of fetch
useNativeHttp: true,
// Enable automatic cookie management
enableCookies: true,
// Set request timeout
requestTimeout: 10000,
// Enable request history
enableHistory: true,
maxHistoryEntries: 50,
// Storage format
storageFormat: 'folder' // or 'file'
});📚 Core Concepts
ForgeContainer
The main entry point - a dependency injection container that wires up all components.
const forge = new ForgeContainer(options);
// Load collections
const collection = await forge.loadCollection(path);
const folderCollection = await forge.loadFolderCollection(path);
// Execute requests
const result = await forge.execute(request, collection, options);
// Manage environments
forge.setEnvironment(variables);
forge.setActiveEnvironment(name);
const resolved = forge.getResolvedEnvironment();
// Access services
const executor = forge.getRequestExecutor();
const loader = forge.getCollectionLoader();Request Execution
Execute requests with full control over the execution pipeline:
const result = await forge.execute(request, collection, {
environment: 'production',
overrides: {
url: 'https://override.com/api',
headers: { 'X-Custom': 'value' }
},
skipPreRequest: false,
skipPostResponse: false,
timeout: 5000
});
// Access results
console.log(result.response); // HTTP response
console.log(result.preRequestResult); // Pre-request script output
console.log(result.postResponseResult); // Test resultsDynamic Variables
Automatic variable generation within request templates:
// URL with dynamic timestamp
https://api.example.com/events?timestamp={{$timestamp}}
// Headers with unique ID
X-Request-ID: {{$uuid}}
// Query parameters with random value
?page=1&seed={{$randomInt:1:100}}Supported dynamic variables:
{{$randomInt}}- Random integer (0-2147483647){{$randomInt:min:max}}- Random integer in range{{$timestamp}}- Current Unix timestamp (seconds){{$uuid}}- UUID v4{{$guid}}- GUID (alias for uuid){{$randomString}}- 10-char alphanumeric string{{$randomHexadecimal}}- Random hex string{{$isoTimestamp}}- ISO 8601 timestamp
Script Execution
Run pre-request and post-response scripts with full Postman API compatibility:
Pre-request script - Set variables & modify request:
// Set variables across scopes
pm.variables.set('requestId', pm.variables.randomUUID());
pm.environment.set('token', 'abc-123');
pm.collectionVariables.set('counter', '1');
// Modify request headers
pm.request.headers.add({
name: 'X-Request-ID',
value: pm.variables.get('requestId')
});
pm.request.headers.update({
name: 'Authorization',
value: 'Bearer ' + pm.environment.get('token')
});
// Modify URL and body
pm.request.url = 'https://api.example.com' + pm.request.url;
pm.request.body.raw = JSON.stringify({ timestamp: Date.now() });
// Set cookies for next request
pm.cookies.set('sessionId', 'sess_abc123');Post-response script - Test & extract data:
// Run assertions
pm.test('Status is 200', () => {
pm.expect(pm.response.code).to.equal(200);
});
pm.test('Response time under 1s', () => {
pm.expect(pm.response.responseTime).to.be.below(1000);
});
// Extract data for next request
const data = pm.response.json();
pm.environment.set('userId', data.id);
pm.environment.set('authToken', data.token);
// Store non-string values (auto-serialized with type safety)
pm.environment.set('userList', data.users); // Array → stored with type marker
pm.environment.set('config', { retries: 3 }); // Object → stored with type marker
pm.environment.set('count', 42); // Number → stored with type marker
// get() auto-deserializes back to the original type
const users = pm.environment.get('userList'); // → Array
const config = pm.environment.get('config'); // → Object
const count = pm.environment.get('count'); // → 42 (number)
// Strings are never misinterpreted — "true" stays a string, true stays a boolean
pm.environment.set('flag', true); // boolean
pm.environment.set('label', 'true'); // string
pm.environment.get('flag'); // → true (boolean)
pm.environment.get('label'); // → "true" (string)
// Store cookies from response
if (pm.response.headers.has('Set-Cookie')) {
pm.cookies.set('authCookie', data.authCookie);
}Response Body Type
Like Postman, pm.response.body is the raw response string — it is not eagerly parsed. Parse JSON on demand:
const data = pm.response.json(); // parsed object (null if not JSON)
const text = pm.response.text(); // raw string
const obj = JSON.parse(pm.response.body); // body is a stringjson() tolerates an already-parsed object body for backward compatibility, but new code should treat pm.response.body as a string.
Legacy Postman Globals
Older Postman scripts and many Newman-exported collections rely on bare globals from the pre-pm.* sandbox. The core injects these so legacy scripts run unchanged.
Available in both pre-request and test scripts: request ({ id, name, description, url, method, headers, data }), environment (read snapshot), globals (read snapshot), data (iteration row), iteration (index), and tv4 (tv4.validate(data, schema) — when the optional tv4 module is installed).
Available in test scripts only: responseBody (raw string), responseCode ({ code, name, detail }), responseHeaders, responseTime, responseCookies (cookie objects with name, value, domain, path, expires, maxAge, httpOnly, secure, sameSite), tests (tests['name'] = boolean), plus postman.getResponseHeader(name) and postman.getResponseCookie(name) (case-insensitive; getResponseCookie returns the full cookie object, both return null if absent).
// Legacy-style test script — runs unmodified
tests['status 200'] = responseCode.code === 200;
tests['has token'] = JSON.parse(responseBody).token !== undefined;
tests['is json'] = /application\/json/.test(postman.getResponseHeader('Content-Type'));
environmentandglobalsare read snapshots — direct assignment was never persisted in legacy Postman either. Usepostman.setEnvironmentVariable()/postman.setGlobalVariable()(or the modernpm.environment.set()/pm.globals.set()) to persist changes.
Script Scope
Control how script levels (collection → folder → request) and the pre-request/post-response phases share a JavaScript scope via scripts.scope in http-forge.config.json (or the scriptScope SDK option):
| Mode | Behavior |
|------|----------|
| 'shared' (default) | All levels and both phases run in one scope. var/function and global assignments leak across them; lowest overhead. Top-level let/const are scoped to a single phase. |
| 'isolated' | Each level runs in its own scope, matching Postman. Declarations cannot collide or leak; pass state through pm.variables / pm.environment / pm.globals. |
const forge = ForgeContainer.create({ scriptScope: 'isolated' });Use 'isolated' when importing Postman collections whose scripts re-declare the same identifiers at multiple levels, which would otherwise throw Identifier already declared in shared mode.
Async Script Execution (Event-Loop Draining)
Like Postman, scripts do not end when their synchronous code returns. After the sync body completes, the core keeps the sandbox alive and drains pending timers (setTimeout/setInterval) and microtasks (un-awaited Promises), then commits variable scopes — so deferred pm.globals / pm.environment / pm.variables writes are visible to the next request without await.
// Post-response script — committed for the next request, no await required
setTimeout(() => pm.globals.set('authToken', generateToken()), 1500);The drain is bounded by scripts.timeout (the scriptTimeout SDK option), default 5000 ms, which caps both synchronous CPU time and the async-drain window:
const forge = ForgeContainer.create({ scriptTimeout: 10000 });| Behavior | Notes |
|----------|-------|
| Sandbox kept alive after sync return | Pending timers/microtasks drained before the request advances |
| Deferred pm.* writes committed | Visible to the next request without await |
| Errors in timer/Promise callbacks | Reported as a console error, non-fatal — the request/test is not failed |
| Runaway timers | A never-clearing setInterval or infinite timer loop is force-cancelled at the timeout, with a warning |
| No async work | Returns immediately; draining adds no measurable latency |
Divergences from Postman: uses real Node timers with a poll-based wall-clock drain (no exact pending-promise count, so pathological microtask recursion is bounded by the budget rather than a precise idle signal); a single scripts.timeout bounds both CPU time and the async window.
Environment Management
// Define multiple environments
forge.setEnvironmentConfig({
dev: { baseUrl: 'https://dev.api.com', apiKey: 'dev-key' },
staging: { baseUrl: 'https://staging.api.com', apiKey: 'staging-key' },
prod: { baseUrl: 'https://api.com', apiKey: 'prod-key' }
});
// Switch environments
forge.setActiveEnvironment('prod');
// Get resolved variables (with inheritance and overrides)
const vars = forge.getResolvedEnvironment('prod');🔧 Advanced Features
Custom HTTP Client
Implement your own HTTP client:
import { IHttpClient, HttpRequest, HttpResponse } from '@http-forge/core';
class CustomHttpClient implements IHttpClient {
async send(request: HttpRequest): Promise<HttpResponse> {
// Your custom HTTP logic
return {
status: 200,
statusText: 'OK',
headers: {},
body: {},
duration: 100,
size: 1024
};
}
}
const forge = new ForgeContainer({
httpClient: new CustomHttpClient()
});Request/Response Interceptors
Add custom interceptors to modify requests and responses:
import { IRequestInterceptor, IResponseInterceptor } from '@http-forge/core';
// Request interceptor
class AuthInterceptor implements IRequestInterceptor {
async intercept(request: HttpRequest): Promise<HttpRequest> {
request.headers['Authorization'] = `Bearer ${getToken()}`;
return request;
}
}
// Response interceptor
class LoggingInterceptor implements IResponseInterceptor {
async intercept(response: HttpResponse, request: HttpRequest): Promise<HttpResponse> {
console.log(`${request.method} ${request.url} -> ${response.status}`);
return response;
}
}
const forge = new ForgeContainer({
requestInterceptors: [new AuthInterceptor()],
responseInterceptors: [new LoggingInterceptor()]
});Cookie Management & Persistence
Automatic cookie storage and reuse across multi-request flows:
const forge = new ForgeContainer({
enableCookies: true // Cookies persist across requests in session
});
// Login - response sets Session-ID cookie
const loginResult = await forge.execute(loginRequest, collection);
// Subsequent requests automatically include Session-ID
// No need to manually extract and re-add cookies
const dataResult = await forge.execute(dataRequest, collection);
const updateResult = await forge.execute(updateRequest, collection);Access cookies in scripts:
// Pre-request script - read stored cookies
if (pm.cookies.has('sessionId')) {
const sid = pm.cookies.get('sessionId');
pm.request.headers.add({
name: 'Cookie',
value: 'sessionId=' + sid
});
}
// Post-response script - store new cookies
pm.response.cookies.forEach(cookie => {
pm.cookies.set(cookie.name, cookie.value);
});
// Read a response cookie (Postman-compatible CookieList)
const sid = pm.response.cookies.get('sessionId'); // value string
const sidCookie = pm.response.cookies.one('sessionId'); // full object
if (sidCookie?.secure && sidCookie.maxAge > 0) { /* ... */ }
// List all active cookies
const allCookies = pm.cookies.list(); // [{name, value}, ...]
// Clear cookies
pm.cookies.clear(); // When switching users/sessionsCookies are automatically extracted from Set-Cookie response headers and reused in subsequent Cookie request headers.
Request History
Track all executed requests:
const forge = new ForgeContainer({
enableHistory: true,
maxHistoryEntries: 100
});
// Execute requests
await forge.execute(request1, collection);
await forge.execute(request2, collection);
// Access history
const history = forge.getRequestHistory();
const entries = history.getAll(); // All requests
const byId = history.getByRequestId(id); // Specific request history📖 API Reference
ForgeContainer
Constructor Options:
interface ForgeContainerOptions {
forgeRoot?: string; // Path to http-forge folder
storageFormat?: 'file' | 'folder'; // Collection storage format
// HTTP Settings
useNativeHttp?: boolean; // Use native http/https
httpClient?: IHttpClient; // Custom HTTP client
httpSettings?: RequestSettings; // Default HTTP settings
requestTimeout?: number; // Request timeout (ms)
// Cookie Settings
enableCookies?: boolean; // Enable cookie jar
cookieJar?: ICookieJar; // Custom cookie jar
// Interceptors
requestInterceptors?: IRequestInterceptor[];
responseInterceptors?: IResponseInterceptor[];
errorInterceptors?: IErrorInterceptor[];
// Script Settings
scriptRunner?: IScriptRunner; // Custom script runner
scriptTimeout?: number; // Script budget (ms); caps CPU time + async-drain window. Default 5000
// History
enableHistory?: boolean; // Enable request history
maxHistoryEntries?: number; // Max history size
// File Watching
fileWatcherFactory?: IFileWatcherFactory; // Watch for collection/environment file changes
}Methods:
// Collection loading
loadCollection(path: string): Promise<UnifiedCollection>
loadFolderCollection(path: string): Promise<UnifiedCollection>
// Request execution
execute(
request: UnifiedRequest,
collection: UnifiedCollection,
options?: ExecuteOptions
): Promise<ExecuteResult>
// Environment management
setEnvironment(variables: Record<string, string>): void
setEnvironmentConfig(config: EnvironmentConfig): void
setActiveEnvironment(name: string): void
getResolvedEnvironment(name?: string): Record<string, string>
// Service access
getRequestExecutor(): RequestExecutor
getCollectionLoader(): ICollectionLoader
getEnvironmentResolver(): EnvironmentResolver
getRequestHistory(): IRequestHistoryExecuteResult
interface ExecuteResult {
response: HttpResponse; // HTTP response
preRequestResult?: ScriptResult; // Pre-request script output
postResponseResult?: ScriptResult; // Test results
requestId: string; // Unique request ID
timestamp: number; // Execution timestamp
}HttpResponse
interface HttpResponse {
status: number; // HTTP status code
statusText: string; // Status text
headers: Record<string, string>; // Response headers
body: any; // Parsed response body
cookies?: Cookie[]; // Response cookies
duration: number; // Request duration (ms)
size: number; // Response size (bytes)
redirected?: boolean; // Whether redirected
}KeyValueEntry
Used for headers and query parameters in CollectionRequest. Supports OpenAPI metadata for generation and validation.
interface KeyValueEntry {
key: string;
value: string;
enabled?: boolean;
// OpenAPI metadata (all optional, backward-compatible)
type?: 'string' | 'integer' | 'number' | 'boolean' | 'array';
required?: boolean;
description?: string;
format?: string; // Semantic hint (e.g. "uuid", "date-time")
enum?: string[]; // Allowed values
deprecated?: boolean;
// Extended constraint fields for full OpenAPI round-trip
pattern?: string; // Regex validation pattern
minimum?: number;
maximum?: number;
exclusiveMinimum?: number;
exclusiveMaximum?: number;
minLength?: number;
maxLength?: number;
oneOf?: Array<Record<string, any>>; // Merged constraint variants
}PathParamEntry
Used for path parameters (:param in URLs). Same constraint fields as KeyValueEntry minus key/enabled.
interface PathParamEntry {
value: string;
type?: 'string' | 'integer' | 'number' | 'boolean';
description?: string;
format?: string;
enum?: string[];
deprecated?: boolean;
pattern?: string;
minimum?: number;
maximum?: number;
exclusiveMinimum?: number;
exclusiveMaximum?: number;
minLength?: number;
maxLength?: number;
oneOf?: Array<Record<string, any>>;
}Postman Collection Import / Export
CollectionService can import and export Postman Collection v2.1 JSON files, preserving all request body types, auth, scripts, and folder structure.
Import (importCollection(filePath)):
Detects Postman format by info._postman_id and converts all five body modes:
| Postman mode | HTTP Forge type | Notes |
|---|---|---|
| raw | raw | format inferred from options.raw.language (json/xml/html/javascript/text) |
| urlencoded | x-www-form-urlencoded | Fields mapped to [{key, value, enabled}] |
| formdata | form-data | Fields mapped to [{key, value, type, enabled}] |
| graphql | graphql | variables JSON string parsed to object |
| binary | binary | File path not portable; imported with empty content |
Auth types bearer, basic, and apikey are converted. Pre-request and test scripts (prerequest/test events) are imported as scripts.preRequest/scripts.postResponse. Folder hierarchy and item order are preserved.
The whole collection is built in memory and persisted with a single saveCollection()
call. FolderCollectionStore.save() writes sibling items and each request's
independent files (body, docs, scripts, schemas) in parallel and writes each
metadata file once; fresh imports skip stale-body cleanup and schema-existence
probes. Loaders read JSON via a readJsonFileSafe helper that silently skips
empty or half-written files, so a watcher reloading mid-import never throws
Unexpected end of JSON input.
Export (exportCollection(collectionId, filePath)):
Converts back to Postman v2.1 JSON. All body types round-trip correctly: x-www-form-urlencoded exports as mode: "urlencoded", form-data as mode: "formdata", graphql as mode: "graphql", and binary as mode: "binary".
const service = container.getCollectionService();
// Import
const collection = await service.importCollection('./my-api.postman_collection.json');
// Export
await service.exportCollection(collection.id, './exported.postman_collection.json');OpenAPI Import / Export
The core library includes full OpenAPI 3.0.3 import and export with constraint preservation.
Import (OpenApiImporter):
- Parses OpenAPI 3.0 YAML/JSON specs into
UnifiedCollection - Extracts all parameter schema constraints (
pattern,minimum,maximum,exclusiveMinimum,exclusiveMaximum,minLength,maxLength,enum,format) - Preserves
oneOfschemas from merged parameters, deriving combined enum hints for UI display
Export (OpenApiExporter):
- Generates OpenAPI 3.0.3 specs from collections
- Host-variable server resolution — Any
{{varName}}that prefixes a request URL (e.g.{{dcqHost}}/api/...) is stripped from the path and resolved to a server URL entry. If the variable resolves in the active environment(s), a concreteurlis added toservers. If unresolvable, an OAS server-variable entry is emitted:url: '{varName}', variables: { varName: { default: '' } }. This handles arbitrary host variables beyond{{baseUrl}}. - Environment variable placeholder sanitization —
{{varName}}tokens in parameter examples (path, query, header) and request body examples are replaced with<varName>so the exported spec contains readable human placeholders rather than raw template syntax. - Collision-aware merging: When multiple requests normalize to the same path + HTTP method, they are merged into a single operation:
- Descriptions are appended, tags are unioned
- Parameters with the same constraint kind (both enum, both pattern, etc.) are merged in-place (union enum values, widen numeric ranges, alternation-join patterns)
- Parameters with different constraint kinds are wrapped in
oneOf— each variant keeps its self-consistent schema
- All constraint fields round-trip without data loss
� Exported Types
Configuration
HttpForgeConfig is the shape of http-forge.config.json. Key sub-interfaces:
| Interface | Description |
|---|---|
| StorageConfig | Root/history/results paths |
| RequestConfig | Timeout, redirects, SSL |
| ScriptsConfig | Module search paths |
| RunnerConfig | Retention, index page size |
| EnvironmentsConfig | Default environment |
| RestClientExportConfig | REST Client export path/merge settings |
| McpConfig | MCP server project-level settings (see below) |
| ProxyConfig | HTTP/HTTPS proxy URLs |
McpConfig — controls what the HTTP Forge MCP server exposes to AI agents. All fields are optional; the service fills defaults automatically via IConfigService.getMcpConfig().
interface McpConfig {
excludedCollections: string[]; // IDs or names to hide (default: [] = expose all)
excludedSuites: string[]; // IDs or names to hide (default: [] = expose all)
toolPrefix: string; // Prefix for every tool name (default: "")
maxRequestsPerCall: number; // Safety cap per collection/suite call (default: 500; min 1, max 10000)
toolMode: 'flat' | 'drilldown' | 'auto'; // How tools are exposed (default: 'auto')
drilldownThreshold: number; // 'auto' switches to drill-down above this request-tool count (default: 100; min 10, max 500)
toolPageSize: number; // Max tools per tools/list page (default: 150; 0 = no pagination; min 10, max 1000)
cors: {
allowedOrigins: string[]; // CORS origins (default: ["http://localhost","http://127.0.0.1"])
};
}toolMode controls how collections/requests are surfaced as MCP tools:
auto(default) — usesflatbelowdrilldownThreshold(100 requests), then switches todrilldown. Adapts automatically as the workspace grows.drilldown— a small fixed set of generic tools (list_collections,list_requests,run_request,run_folder,run_collection,run_suite) whose arguments select the target. Keeps the tool list tiny regardless of scale.flat— one tool per request, folder, collection, and suite. Only suitable for small, stable collections (<100 requests). Avoid for large workspaces — the tool list bloats the AI context window and degrades tool-selection accuracy.
Use IConfigService.getMcpConfig() to read the resolved config with all defaults applied.
�🛠️ Use Cases
CLI Tool
#!/usr/bin/env node
import { ForgeContainer } from '@http-forge/core';
async function runTests(collectionPath: string) {
const forge = new ForgeContainer();
const collection = await forge.loadCollection(collectionPath);
for (const request of collection.items) {
const result = await forge.execute(request, collection);
const tests = result.postResponseResult?.assertions || [];
console.log(`\n${request.name}: ${result.response.status}`);
tests.forEach(test => {
console.log(` ${test.passed ? '✓' : '✗'} ${test.name}`);
});
}
}
runTests(process.argv[2]);CI/CD Integration
import { ForgeContainer } from '@http-forge/core';
async function ciTest() {
const forge = new ForgeContainer({
enableCookies: true,
requestTimeout: 30000
});
forge.setEnvironment({
baseUrl: process.env.API_URL,
apiKey: process.env.API_KEY
});
const collection = await forge.loadCollection('./api-tests.forge.json');
let failedTests = 0;
for (const request of collection.items) {
const result = await forge.execute(request, collection);
const failed = result.postResponseResult?.assertions?.filter(t => !t.passed) || [];
failedTests += failed.length;
}
process.exit(failedTests > 0 ? 1 : 0);
}Custom Testing Framework
import { ForgeContainer } from '@http-forge/core';
class ApiTestRunner {
private forge: ForgeContainer;
constructor() {
this.forge = new ForgeContainer({
enableCookies: true,
enableHistory: true
});
}
async runSuite(suites: TestSuite[]) {
for (const suite of suites) {
await this.runTests(suite);
}
}
async runTests(suite: TestSuite) {
const collection = await this.forge.loadCollection(suite.collection);
for (const request of collection.items) {
const result = await this.forge.execute(request, collection);
suite.results.push(result);
}
}
}Multi-Request Workflows
Execute dependent request chains with automatic cookie and variable management:
import { ForgeContainer } from '@http-forge/core';
async function apiAuthWorkflow() {
const forge = new ForgeContainer({
enableCookies: true, // Cookies persist across requests
enableHistory: true
});
// Request 1: Login (sets session cookie)
const loginReq = {
name: 'Login',
method: 'POST',
url: 'https://api.example.com/auth/login',
body: { type: 'raw', content: JSON.stringify({
username: '{{email}}',
password: '{{password}}'
})},
scripts: {
postResponse: `
const { token, userId } = pm.response.json();
pm.environment.set('authToken', token);
pm.environment.set('userId', userId);
`
}
};
const loginResult = await forge.execute(loginReq, collection);
console.log('✓ Logged in, token:', forge.getResolvedEnvironment()['authToken']);
// Request 2: Fetch user profile (uses session cookie automatically)
const profileReq = {
name: 'Get Profile',
method: 'GET',
url: 'https://api.example.com/users/{{userId}}',
headers: {
'Authorization': 'Bearer {{authToken}}' // Uses token from login
}
};
const profileResult = await forge.execute(profileReq, collection);
console.log('✓ Profile:', profileResult.response.body);
// Request 3: Update profile (session cookie still active)
const updateReq = {
name: 'Update Profile',
method: 'PATCH',
url: 'https://api.example.com/users/{{userId}}',
headers: {
'Authorization': 'Bearer {{authToken}}'
},
body: { type: 'raw', content: JSON.stringify({ status: 'active' })}
};
const updateResult = await forge.execute(updateReq, collection);
console.log('✓ Updated profile');
// Session automatically logged out - cookies cleared
forge.getRequestHistory().clear();
}📦 Storage Formats
File Format (Single JSON)
my-api.forge.jsonFolder Format (Directory Structure)
my-api/
collection.json
requests/
login/
request.json
doc.md # Optional request documentation (Markdown)
users/
get-users/
request.json
doc.md
create-user/
request.json🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT © Henry Huang
🔗 Links
📝 Changelog
0.2.16 (Duplicate Collection & Folder)
- ✅
duplicateCollection(sourceId, newName)— Duplicates a collection including all nested folders, requests, scripts, and schemas - ✅
duplicateFolder(collectionId, folderId, newName)— Duplicates a folder within a collection, preserving all nested request content - ✅
getItemPath(collectionId, itemId)— Resolves the disk path of a folder or request within a collection
0.2.6 (OpenAPI Export — Environment Variable Handling)
- ✅ Host-variable URL stripping — Any leading
{{varName}}in a request URL (e.g.{{dcqHost}}/DCQ/templates/GetEpg) is now correctly stripped from the path. The variable name is resolved viaenvConfigService.resolveVariables()and the resulting URL is added toservers. When the variable cannot be resolved, an OAS server-variable entry (url: '{varName}', variables: { varName: { default: '' } }) is emitted instead. Previously, unknown host variables were converted to/{varName}/...path segments. - ✅ Example sanitization —
{{varName}}placeholders in parameter examples (path, query, header) and request body examples are replaced with<varName>so they render as readable human placeholders instead of raw template syntax in the exported spec.
0.2.5 (OpenAPI Constraint Round-Trip & Collision Merging)
- ✅ Full parameter constraint round-trip — OpenAPI import/export now preserves all schema constraint fields:
pattern,minimum,maximum,exclusiveMinimum,exclusiveMaximum,minLength,maxLength, andoneOfon bothKeyValueEntryandPathParamEntry - ✅ Collision-aware OpenAPI export — When multiple requests normalize to the same path + HTTP method, the exporter merges them into a single operation with intelligent parameter schema merging (same-kind merge in-place, different-kind wraps in
oneOf) - ✅
oneOfimport with UI hints —applyOneOfHints()derives combined enum arrays and type hints fromoneOfvariants for downstream UI rendering (combobox vs strict dropdown) - ✅
formatvspatternseparation —formatis a semantic hint (e.g."uuid"),patternis an enforced regex. Both stored and round-tripped independently
0.2.4 (File Watching & Request Documentation)
- ✅ File watching for CollectionService - Automatic reload and
onCollectionsChangedcallback when collection files change on disk - ✅ File watching for EnvironmentConfigService - Automatic reload and
onEnvironmentsChangedcallback when environment JSON files change on disk - ✅ Request documentation (
doc.md) - Persist per-request Markdown documentation asdoc.mdalongsiderequest.json - ✅
docfield on UnifiedRequest / CollectionRequest - Optionaldoc?: stringfield for request documentation content - ✅
fileWatcherFactorybootstrap option - PassIFileWatcherFactoryto enable file watching in both collection and environment services
0.2.0 (Postman API Parity)
- ✅ Async
pm.test()support - Tests with async callbacks are properly awaited - ✅ Expect chain assertions -
.a(type),.an(type),.deep.equal(),.lengthOf(),.exist,.members(),.keys(),.string() - ✅
pm.iterationDatascope - Data-driven testing with iteration variables - ✅ Response status getters -
response.to.be.ok,.error,.clientError,.serverErrorwork as getters and functions - ✅ Response headers HeaderList -
.get(),.has(),.toObject(),.each()onpm.response.headers - ✅
pm.cookies.toObject()- Flat{name: value}cookie map - ✅
replaceIn()on all scopes - Available onpm.variables,pm.environment,pm.collectionVariables,pm.globals - ✅ Sandbox globals -
xml2Json(),jsonStringify(),jsonParse()available in scripts - ✅ Request headers API -
.toObject(),.each()onpm.request.headers - ✅
pm.execution.setNextRequest()- Runner flow control (jump to named request or stop withnull) - ✅
pm.execution.skipRequest()- Skip HTTP call from pre-request scripts - ✅
pm.setNextRequest()- Top-level alias for flow control - ✅
pm.visualizer.set(template, data)- Custom HTML visualization with Handlebars templates - ✅
pm.request.urlas Url object - Postman SDK-compatible Url withgetHost(),getPath(),getQueryString(),protocol,host,port,path,query,hash - ✅ Full CryptoJS - AES/DES/TripleDES encrypt/decrypt, PBKDF2, all hash algorithms (SHA1/256/384/512/SHA3/MD5/RIPEMD160), HMAC, encoding helpers (Hex/Base64/Utf8/Latin1/Utf16)
0.1.0 (Initial Release)
- ✅ Core request execution with Postman collection support
- ✅ Dynamic variables - 7 generators for on-the-fly value generation
- ✅ Postman-compatible scripting -
pm.*API with full feature parity - ✅ Variable scoping - globals, collection, environment, session, flow-level
- ✅ Cookie persistence - automatic storage and reuse across request chains
- ✅ Pre-request & post-response scripts with shared VM context
- ✅ Test assertions with BDD-style
pm.test()and expect chains - ✅ Environment management with variable substitution
- ✅ Cookie jar with domain-aware matching
- ✅ Request/response interceptors for customization
- ✅ Request history tracking
- ✅ File and folder collection formats (Postman-compatible)
- ✅ Full TypeScript support with comprehensive types
- ✅ Dependency injection for extensibility
- ✅ Comprehensive test coverage
