JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 216
  • Score
    100M100P100Q82412F
  • License MIT

Headless HTTP testing engine with Postman collection support, dynamic variables, and script-based automation.

Package Exports

  • @http-forge/core

Readme

@http-forge/core

Standalone HTTP testing engine with Postman collection support and JavaScript-based automation.

npm version License: MIT

📦 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.json and .forge.json files
  • 📝 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)
  • 👁️ 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) and full Chai expect() chains
  • 🔐 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

npm install @http-forge/core

Or using the tarball:

npm install ./http-forge-core-0.1.0.tgz

⚡ 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 results

With 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 results

Dynamic 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 cookies from response
if (pm.response.headers.has('Set-Cookie')) {
    pm.cookies.set('authCookie', data.authCookie);
}

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()]
});

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);
});

// List all active cookies
const allCookies = pm.cookies.list();  // [{name, value}, ...]

// Clear cookies
pm.cookies.clear();  // When switching users/sessions

Cookies 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 timeout (ms)
    
    // 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(): IRequestHistory

ExecuteResult

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>>;
}

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 oneOf schemas from merged parameters, deriving combined enum hints for UI display

Export (OpenApiExporter):

  • Generates OpenAPI 3.0.3 specs from collections
  • 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

🛠️ 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.json

Folder 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

📝 Changelog

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, and oneOf on both KeyValueEntry and PathParamEntry
  • 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)
  • oneOf import with UI hintsapplyOneOfHints() derives combined enum arrays and type hints from oneOf variants for downstream UI rendering (combobox vs strict dropdown)
  • format vs pattern separationformat is a semantic hint (e.g. "uuid"), pattern is an enforced regex. Both stored and round-tripped independently

0.2.4 (File Watching & Request Documentation)

  • File watching for CollectionService - Automatic reload and onCollectionsChanged callback when collection files change on disk
  • File watching for EnvironmentConfigService - Automatic reload and onEnvironmentsChanged callback when environment JSON files change on disk
  • Request documentation (doc.md) - Persist per-request Markdown documentation as doc.md alongside request.json
  • doc field on UnifiedRequest / CollectionRequest - Optional doc?: string field for request documentation content
  • fileWatcherFactory bootstrap option - Pass IFileWatcherFactory to 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.iterationData scope - Data-driven testing with iteration variables
  • Response status getters - response.to.be.ok, .error, .clientError, .serverError work as getters and functions
  • Response headers HeaderList - .get(), .has(), .toObject(), .each() on pm.response.headers
  • pm.cookies.toObject() - Flat {name: value} cookie map
  • replaceIn() on all scopes - Available on pm.variables, pm.environment, pm.collectionVariables, pm.globals
  • Sandbox globals - xml2Json(), jsonStringify(), jsonParse() available in scripts
  • Request headers API - .toObject(), .each() on pm.request.headers
  • pm.execution.setNextRequest() - Runner flow control (jump to named request or stop with null)
  • 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.url as Url object - Postman SDK-compatible Url with getHost(), 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