@pingpong-js/core
v1.0.1
Published
Core utilities and types for PingPong HTTP client ecosystem
Maintainers
Readme
🏓 @pingpong-js/core
Reusable TypeScript utilities for building HTTP testing tools.
🎯 Overview
PingPong Core is a collection of zero-dependency TypeScript utilities for HTTP testing, API automation, and request/response manipulation. Used by the PingPong CLI and Browser Extension.
✨ Features
- 🌐 HTTP Types - Complete HTTP type definitions (HttpRequest, HttpResponse, HttpClientOptions)
- 🔗 URL Utilities - Query parameter building, URL joining, param merging
- 📋 Header Utilities - Case-insensitive header operations (RFC 2616 compliant)
- 🔄 VariableResolver - Environment variable substitution with template support
- 🍪 CookieJar - Cookie management with domain/path/expiration handling
- 🔍 JSONPath - Extract values from JSON responses
- ✅ AssertionEngine - Validate responses with intuitive syntax
- ⚡ LoadTester - Performance testing with concurrent request handling
- 📊 Metrics Collection - Latency, throughput, and performance tracking
- 🎯 Zero Dependencies - No external packages required
- 🔒 Type-Safe - Full TypeScript support with strict mode
- ⚡ Optimized - ~37KB gzipped with compiled TypeScript
📦 Installation
npm install @pingpong-js/core⚡ Performance
- Bundle Size: ~37KB gzipped (optimized with minification)
- Startup Time: Instant imports with tree-shakeable modules
- Memory Efficiency: Lightweight utilities with no external dependencies
- Build Size: ~44KB uncompressed TypeScript output
🚀 Quick Start
import {
buildURL,
getHeader,
VariableResolver,
JSONPath,
AssertionEngine,
CookieJar,
LoadTester
} from '@pingpong-js/core';
// URL building with query parameters
const url = buildURL('https://api.example.com/users', {
page: 1,
limit: 10,
tags: ['new', 'featured'] // Arrays automatically handled
});
// → https://api.example.com/users?page=1&limit=10&tags=new&tags=featured
// Case-insensitive header access
const headers = { 'Content-Type': 'application/json' };
const contentType = getHeader(headers, 'content-type'); // Works with any casing
// → 'application/json'
// Variable substitution
const resolver = new VariableResolver({ API_KEY: 'secret123' });
const apiUrl = resolver.resolve('https://api.mockly.codes?key={{API_KEY}}');
// → https://api.mockly.codes?key=secret123
// JSON extraction
const data = { user: { id: 123, name: 'John' } };
const userId = JSONPath.extract(data, '$.user.id');
// → 123
// Response validation
const assertions = AssertionEngine.runAssertions(response, [
'status == 200',
'body.user.email exists',
'time < 1000'
]);
// Cookie management
const jar = new CookieJar();
jar.setCookie('session=abc; Domain=example.com; Path=/');
const cookies = jar.getCookiesForRequest('https://example.com/api');📚 API Documentation
URL Utilities
Build URLs with query parameters, join paths, and merge params.
import { buildURL, joinURL, mergeParams, parseQueryString } from '@pingpong-js/core';
// Build URL with query parameters
const url = buildURL('https://api.example.com/users', {
page: 1,
limit: 10,
tags: ['new', 'featured'], // Arrays become repeated params
active: true
});
// → https://api.example.com/users?page=1&limit=10&tags=new&tags=featured&active=true
// Join base URL with path
const fullUrl = joinURL('https://api.example.com', '/users/123');
// → https://api.example.com/users/123
// Merge multiple param objects (later overrides earlier)
const merged = mergeParams(
{ api_key: 'secret', version: 'v1' },
{ version: 'v2', page: 1 } // version overrides
);
// → { api_key: 'secret', version: 'v2', page: 1 }
// Parse query string from URL
const params = parseQueryString('https://api.example.com/users?page=1&limit=10');
// → { page: '1', limit: '10' }Functions:
buildURL(baseURL: string, params?: Record<string, any>): string- Build URL with encoded paramsjoinURL(baseURL: string, path: string): string- Properly join base URL with pathmergeParams(...params: Array<Record<string, any> | undefined>): Record<string, any>- Merge param objectsparseQueryString(url: string): Record<string, string>- Extract params from URL
Header Utilities
Case-insensitive header operations complying with RFC 2616.
import { getHeader, setHeader, deleteHeader, hasHeader, normalizeHeaders } from '@pingpong-js/core';
const headers = { 'Content-Type': 'application/json' };
// Get header (case-insensitive)
getHeader(headers, 'content-type'); // 'application/json'
getHeader(headers, 'Content-Type'); // 'application/json'
getHeader(headers, 'CONTENT-TYPE'); // 'application/json'
// Set header (preserves original casing)
setHeader(headers, 'Authorization', 'Bearer token');
// → { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }
// Check if header exists (case-insensitive)
hasHeader(headers, 'content-type'); // true
hasHeader(headers, 'X-Custom'); // false
// Delete header (case-insensitive)
deleteHeader(headers, 'content-type');
// → { 'Authorization': 'Bearer token' }
// Normalize all header names to lowercase
const normalized = normalizeHeaders({ 'Content-Type': 'application/json' });
// → { 'content-type': 'application/json' }Functions:
getHeader(headers: HttpHeaders, name: string): string | undefined- Get header valuesetHeader(headers: HttpHeaders, name: string, value: string): void- Set header valuedeleteHeader(headers: HttpHeaders, name: string): void- Remove headerhasHeader(headers: HttpHeaders, name: string): boolean- Check if header existsnormalizeHeaders(headers: HttpHeaders): HttpHeaders- Lowercase all header names
HTTP Types
Complete TypeScript type definitions for HTTP operations.
import type {
HttpMethod,
HttpHeaders,
HttpRequest,
HttpResponse,
HttpClientOptions,
RequestOptions
} from '@pingpong-js/core';
// All HTTP types are available for reuse across packages
const request: HttpRequest = {
method: 'GET',
url: 'https://api.example.com/users',
headers: { 'Authorization': 'Bearer token' },
params: { page: 1, limit: 10 } // New in v1.1.0
};
const response: HttpResponse = {
status: 200,
statusText: 'OK',
headers: { 'content-type': 'application/json' },
body: '{"data":[]}',
time: 245,
size: 1024,
getHeader: (name: string) => 'application/json' // New in v1.1.0
};Types Available:
HttpMethod- 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'HttpHeaders- Record<string, string>HttpRequest- Complete request structure with params supportHttpResponse- Complete response structure with getHeader methodHttpClientOptions- Client configuration with params supportRequestOptions- Per-request options with params supportRetryOptions- Retry configurationStreamResponse- Streaming response structure
VariableResolver
Substitute variables in strings with support for multiple formats.
import { VariableResolver } from '@pingpong-js/core';
const vars = {
BASE_URL: 'https://api.mockly.codes',
API_KEY: 'secret123',
VERSION: 'v1'
};
const resolver = new VariableResolver(vars);
// Supports multiple formats
resolver.resolve('{{BASE_URL}}/{{VERSION}}/users');
// → https://api.mockly.codes/v1/users
resolver.resolve('${BASE_URL}/users');
// → https://api.mockly.codes/users
resolver.resolve('$BASE_URL/users?key=$API_KEY');
// → https://api.mockly.codes/users?key=secret123
// Nested objects
resolver.resolve('{{user.name}}'); // Supports nested pathsMethods:
resolve(input: string): string- Replace variables in stringresolveObject(obj: any): any- Deep resolve variables in objects
JSONPath
Extract values from JSON using JSONPath expressions.
import { JSONPath } from '@pingpong-js/core';
const data = {
user: {
id: 123,
name: 'John Doe',
emails: ['[email protected]', '[email protected]']
},
posts: [
{ id: 1, title: 'First Post' },
{ id: 2, title: 'Second Post' }
]
};
// Extract values
JSONPath.extract(data, '$.user.id');
// → 123
JSONPath.extract(data, '$.user.emails[0]');
// → '[email protected]'
JSONPath.extract(data, '$.posts[0].title');
// → 'First Post'
JSONPath.extract(data, '$.user.emails[*]');
// → ['[email protected]', '[email protected]']Supported Syntax:
$.property- Root property$.nested.value- Nested property$.array[0]- Array element by index$.array[*]- All array elements$.array[-1]- Last array element
CookieJar
Manage cookies with domain/path/expiration support.
import { CookieJar } from '@pingpong-js/core';
const jar = new CookieJar();
// Set cookies from Set-Cookie header
jar.setCookie('session=abc123; Domain=example.com; Path=/; HttpOnly');
jar.setCookie('token=xyz789; Domain=.example.com; Secure');
// Get cookies for a request
const cookies = jar.getCookiesForRequest('https://example.com/api');
// → 'session=abc123; token=xyz789'
// Get all cookies
const allCookies = jar.getAllCookies();
// Clear cookies
jar.clearCookies('example.com');
jar.clearAllCookies();
// Export/Import
const exported = jar.export();
jar.import(exported);Methods:
setCookie(cookieString: string, url?: string): voidgetCookiesForRequest(url: string): stringgetAllCookies(): Cookie[]clearCookies(domain?: string): voidclearAllCookies(): voidexport(): Cookie[]import(cookies: Cookie[]): void
AssertionEngine
Validate HTTP responses with intuitive assertion syntax.
import { AssertionEngine } from '@pingpong-js/core';
const response = {
status: 200,
headers: { 'content-type': 'application/json' },
body: { user: { id: 123, email: '[email protected]' } },
time: 245 // milliseconds
};
// Run assertions
const results = AssertionEngine.runAssertions(response, [
'status == 200',
'time < 1000',
'header.content-type == "application/json"',
'body.user.id == 123',
'body.user.email exists',
'body.user.email contains "@example.com"',
'body.user.name startsWith "John"'
]);
// Check if all passed
const allPassed = results.every(r => r.passed);
// Results structure
/*
[
{ assertion: 'status == 200', passed: true, message: '✓ Passed' },
{ assertion: 'time < 1000', passed: true, message: '✓ Passed' },
...
]
*/Supported Operators:
==,!=- Equality>,>=,<,<=- Comparisonexists- Property existscontains- String/array containsstartsWith,endsWith- String operations
Supported Paths:
status- HTTP status codetime- Response time in millisecondsheader.name- Response headerbody.path.to.value- JSON body value
LoadTester
Performance testing with concurrent request handling.
import { LoadTester } from '@pingpong-js/core';
const tester = new LoadTester();
// Simple load test
const results = await tester.run({
url: 'https://api.mockly.codes/users',
method: 'GET',
totalRequests: 100,
concurrency: 10
});
// Advanced configuration
const results = await tester.run({
url: 'https://api.mockly.codes/users',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test User' }),
phases: [
{ duration: 30, arrivalRate: 10 }, // 10 req/s for 30s
{ duration: 60, arrivalRate: 20, rampTo: 50 } // Ramp 20→50 req/s
],
timeout: 5000
});
// Results structure
/*
{
totalRequests: 100,
completedRequests: 98,
failedRequests: 2,
requestsPerSecond: 9.8,
latency: {
min: 89,
mean: 245,
median: 234,
p95: 389,
p99: 456,
max: 567
},
statusCodes: {
200: 98,
500: 2
},
errors: [
{ message: 'ETIMEDOUT', count: 2 }
],
virtualUsers: {
min: 1,
max: 10,
peak: 10
},
throughput: {
bytesReceived: 102400,
bytesSent: 20480
}
}
*/Configuration:
url: string- Target URLmethod: string- HTTP methodheaders?: object- Request headersbody?: string- Request bodytotalRequests?: number- Total requests (default: 100)concurrency?: number- Concurrent requests (default: 10)phases?: LoadTestPhase[]- Load test phasestimeout?: number- Request timeout (default: 30000ms)
Metrics:
- Latency percentiles (min, mean, median, p95, p99, max)
- Throughput (requests/sec, bytes/sec)
- Status code distribution
- Error breakdown
- Virtual user statistics
🎯 Use Cases
API Testing Framework
import { AssertionEngine, CookieJar } from '@pingpong-js/core';
async function testAPI() {
const jar = new CookieJar();
// Login
const loginResponse = await fetch('https://api.mockly.codes/auth/login', {
method: 'POST',
body: JSON.stringify({ email: '[email protected]', password: 'pass' })
});
jar.setCookie(loginResponse.headers.get('set-cookie')!);
// Validate
const assertions = AssertionEngine.runAssertions(loginResponse, [
'status == 200',
'body.token exists'
]);
return assertions.every(a => a.passed);
}Environment Configuration
import { VariableResolver } from '@pingpong-js/core';
const envs = {
dev: { BASE_URL: 'https://pingpong.codes/', API_KEY: 'dev_key' },
prod: { BASE_URL: 'https://api.mockly.codes', API_KEY: 'prod_key' }
};
const resolver = new VariableResolver(envs.dev);
const config = {
apiUrl: resolver.resolve('{{BASE_URL}}/api/v1'),
headers: { 'X-API-Key': resolver.resolve('{{API_KEY}}') }
};Response Processing
import { JSONPath } from '@pingpong-js/core';
async function processResponse(response: Response) {
const data = await response.json();
// Extract specific values
const userId = JSONPath.extract(data, '$.user.id');
const emails = JSONPath.extract(data, '$.user.emails[*]');
const firstPost = JSONPath.extract(data, '$.posts[0].title');
return { userId, emails, firstPost };
}Performance Testing
import { LoadTester } from '@pingpong-js/core';
async function benchmarkAPI() {
const tester = new LoadTester();
const results = await tester.run({
url: 'https://api.mockly.codes/health',
method: 'GET',
phases: [
{ duration: 10, arrivalRate: 1 },
{ duration: 20, arrivalRate: 10 },
{ duration: 30, arrivalRate: 50 }
]
});
console.log('P95 Latency:', results.latency.p95 + 'ms');
console.log('Success Rate:',
(results.completedRequests / results.totalRequests * 100) + '%');
return results;
}🔧 TypeScript Types
Full TypeScript definitions included:
import type {
Cookie,
LoadTestConfig,
LoadTestPhase,
LoadTestResult,
LoadTestMetrics,
AssertionResult,
HttpResponse
} from '@pingpong-js/core';🌐 Browser Support
Works in both Node.js and browser environments:
// Node.js
import { JSONPath } from '@pingpong-js/core';
// Browser (with bundler)
import { JSONPath } from '@pingpong-js/core';
// Browser (ESM)
<script type="module">
import { JSONPath } from '@pingpong-js/core';
</script>📖 Related Projects
- @pingpong-js/cli - Command-line API testing tool
- @pingpong-js/fetch - Universal HTTP client
- PingPong Extension - Browser extension for API testing
- Full Documentation - Complete project docs
🐛 Issues & Support
- Bug reports: GitHub Issues
- Questions: GitHub Discussions
- Email: [email protected]
📄 License
MIT License - see LICENSE
🤝 Contributing
Contributions welcome! See CONTRIBUTING.md
Made with ❤️ by 0xdps
🏓 PingPong Core - Reusable HTTP testing utilities 🚀
