npm package: @pingpong-js/core
Description: Core utilities and types for PingPong HTTP client ecosystem
Author: Devendra Pratap
Latest version: 1.0.1

# 🏓 @pingpong-js/core

Reusable TypeScript utilities for building HTTP testing tools.

[![npm version](https://img.shields.io/npm/v/@pingpong-js/core.svg)](https://www.npmjs.com/package/@pingpong-js/core)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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

```bash
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

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
import { JSONPath } from '@pingpong-js/core';

const data = {
  user: {
    id: 123,
    name: 'John Doe',
    emails: ['john@example.com', 'john.doe@example.com']
  },
  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]');
// → 'john@example.com'

JSONPath.extract(data, '$.posts[0].title');
// → 'First Post'

JSONPath.extract(data, '$.user.emails[*]');
// → ['john@example.com', 'john.doe@example.com']
```

**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.

```typescript
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.

```typescript
import { AssertionEngine } from '@pingpong-js/core';

const response = {
  status: 200,
  headers: { 'content-type': 'application/json' },
  body: { user: { id: 123, email: 'john@example.com' } },
  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.

```typescript
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

```typescript
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: 'user@example.com', 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

```typescript
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

```typescript
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

```typescript
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:

```typescript
import type {
  Cookie,
  LoadTestConfig,
  LoadTestPhase,
  LoadTestResult,
  LoadTestMetrics,
  AssertionResult,
  HttpResponse
} from '@pingpong-js/core';
```

## 🌐 Browser Support

Works in both Node.js and browser environments:

```typescript
// 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](https://www.npmjs.com/package/@pingpong-js/cli)** - Command-line API testing tool
- **[@pingpong-js/fetch](https://www.npmjs.com/package/@pingpong-js/fetch)** - Universal HTTP client
- **[PingPong Extension](https://github.com/0xdps/pingpong-extension)** - Browser extension for API testing
- **[Full Documentation](https://github.com/0xdps/pingpong-extension)** - Complete project docs

## 🐛 Issues & Support

- **Bug reports**: [GitHub Issues](https://github.com/0xdps/pingpong-extension/issues)
- **Questions**: [GitHub Discussions](https://github.com/0xdps/pingpong-extension/discussions)
- **Email**: dps.manit@gmail.com

## 📄 License

MIT License - see [LICENSE](https://github.com/0xdps/pingpong-extension/blob/trunk/LICENSE)

## 🤝 Contributing

Contributions welcome! See [CONTRIBUTING.md](https://github.com/0xdps/pingpong-extension/blob/trunk/CONTRIBUTING.md)

---

**Made with ❤️ by [0xdps](https://0xdps.xyz)**

🏓 **PingPong Core - Reusable HTTP testing utilities** 🚀