npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pingpong-js/core

v1.0.1

Published

Core utilities and types for PingPong HTTP client ecosystem

Readme

🏓 @pingpong-js/core

Reusable TypeScript utilities for building HTTP testing tools.

npm version License: MIT

🎯 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 params
  • joinURL(baseURL: string, path: string): string - Properly join base URL with path
  • mergeParams(...params: Array<Record<string, any> | undefined>): Record<string, any> - Merge param objects
  • parseQueryString(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 value
  • setHeader(headers: HttpHeaders, name: string, value: string): void - Set header value
  • deleteHeader(headers: HttpHeaders, name: string): void - Remove header
  • hasHeader(headers: HttpHeaders, name: string): boolean - Check if header exists
  • normalizeHeaders(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 support
  • HttpResponse - Complete response structure with getHeader method
  • HttpClientOptions - Client configuration with params support
  • RequestOptions - Per-request options with params support
  • RetryOptions - Retry configuration
  • StreamResponse - 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 paths

Methods:

  • resolve(input: string): string - Replace variables in string
  • resolveObject(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): void
  • getCookiesForRequest(url: string): string
  • getAllCookies(): Cookie[]
  • clearCookies(domain?: string): void
  • clearAllCookies(): void
  • export(): 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
  • >, >=, <, <= - Comparison
  • exists - Property exists
  • contains - String/array contains
  • startsWith, endsWith - String operations

Supported Paths:

  • status - HTTP status code
  • time - Response time in milliseconds
  • header.name - Response header
  • body.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 URL
  • method: string - HTTP method
  • headers?: object - Request headers
  • body?: string - Request body
  • totalRequests?: number - Total requests (default: 100)
  • concurrency?: number - Concurrent requests (default: 10)
  • phases?: LoadTestPhase[] - Load test phases
  • timeout?: 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

🐛 Issues & Support

📄 License

MIT License - see LICENSE

🤝 Contributing

Contributions welcome! See CONTRIBUTING.md


Made with ❤️ by 0xdps

🏓 PingPong Core - Reusable HTTP testing utilities 🚀