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

typescript-a2s

v1.0.1

Published

TypeScript library to query Source and GoldSource servers using Valve's Server Query Protocol

Readme

TypeScript A2S

ALL CODE GENERATED BY VIBE CODING

A modern, fully-typed TypeScript library for querying Source and GoldSource game servers using Valve's A2S (Application-level Server Query) protocol. This is a complete TypeScript/Node.js implementation inspired by the original python-a2s library.

Features

  • 🚀 Full TypeScript Support: Complete type definitions with generics and strict typing
  • 🎯 Comprehensive Queries: Server info, player lists, and server rules/configuration
  • 🔧 Multi-Engine Support: Both Source Engine and GoldSource Engine compatibility
  • 📦 Modern Build System: Built with Vite for optimal performance and tree-shaking
  • 🌐 Universal Compatibility: ESM and CommonJS support for all Node.js environments
  • 🛡️ Robust Error Handling: Custom exception types with detailed error information
  • Async/Promise API: Modern Promise-based interface with async/await support
  • 🔤 Flexible Encoding: UTF-8, Latin1, or raw Buffer support for international servers
  • 🧩 Advanced Utilities: ByteReader/ByteWriter for custom protocol implementations
  • 🔀 Multi-packet Support: Automatic handling of fragmented server responses
  • 🗜️ Compression Support: Built-in bz2 decompression for compressed responses
  • 🧪 Fully Tested: Comprehensive test suite with 100% coverage

Installation

npm install typescript-a2s
# or
yarn add typescript-a2s
# or
pnpm add typescript-a2s

Quick Start

import { info, players, rules } from 'typescript-a2s';

// Query server information
const serverInfo = await info('127.0.0.1', 27015);
console.log(`Server: ${serverInfo.serverName}`);
console.log(`Map: ${serverInfo.mapName}`);
console.log(`Players: ${serverInfo.playerCount}/${serverInfo.maxPlayers}`);
console.log(`Ping: ${serverInfo.ping}ms`);

// Query connected players
const playerList = await players('127.0.0.1', 27015);
playerList.forEach((player) => {
  const duration = Math.floor(player.duration / 60);
  console.log(`${player.name}: ${player.score} points (${duration}m)`);
});

// Query server configuration
const serverRules = await rules('127.0.0.1', 27015);
console.log(
  `Server has ${Object.keys(serverRules).length} configuration rules`
);

API Reference

Core Functions

info(address: string, port: number, timeout?: number, encoding?: string | null): Promise<SourceInfo | GoldSrcInfo>

Queries comprehensive server information including name, map, player counts, and technical details.

Parameters:

  • address - Server IP address or hostname
  • port - Server query port (usually game port + 1)
  • timeout - Request timeout in seconds (default: 3.0)
  • encoding - String encoding: 'utf-8', 'latin1', or null for raw Buffer (default: 'utf-8')

Returns: Promise resolving to engine-specific server information

players(address: string, port: number, timeout?: number, encoding?: string | null): Promise<Player[]>

Retrieves a list of currently connected players with scores and connection times.

Parameters: Same as info()

Returns: Promise resolving to an array of player objects

rules(address: string, port: number, timeout?: number, encoding?: string | null): Promise<Rules>

Fetches server configuration variables and custom settings.

Parameters: Same as info()

Returns: Promise resolving to a key-value object of server rules

Advanced Client Class

A2SClient

Advanced client class for persistent connections and batch queries.

import { A2SClient } from 'typescript-a2s';

const client = new A2SClient('127.0.0.1', 27015, 5000, 'utf-8');

try {
  // Do NOT use Promise.all with a single A2SClient instance!
  // The A2S protocol and UDP socket are not concurrency-safe.
  // Always await each query sequentially to avoid protocol errors.
  const serverInfo = await client.info();
  const playerList = await client.players();
  const serverRules = await client.rules();

  console.log('Query results:', {
    server: serverInfo.serverName,
    players: playerList.length,
    rules: Object.keys(serverRules).length,
  });
} finally {
  client.close(); // Always clean up resources
}

Constructor Parameters:

  • address - Server IP address or hostname
  • port - Server query port
  • timeout - Request timeout in milliseconds (default: 3000)
  • encoding - String encoding (default: 'utf-8')

Type Definitions

SourceInfo - Source Engine Servers

interface SourceInfo {
  protocol: number;
  serverName: string | Buffer;
  mapName: string | Buffer;
  folder: string | Buffer;
  game: string | Buffer;
  appId: number;
  playerCount: number;
  maxPlayers: number;
  botCount: number;
  serverType: string | Buffer;
  platform: string | Buffer;
  passwordProtected: boolean;
  vacEnabled: boolean;
  version: string | Buffer;
  edf: number;
  ping: number;

  // Optional extended fields
  port?: number;
  steamId?: bigint;
  stvPort?: number;
  stvName?: string | Buffer;
  keywords?: string | Buffer;
  gameId?: bigint;
}

GoldSrcInfo - GoldSource Engine Servers

interface GoldSrcInfo {
  address: string | Buffer;
  serverName: string | Buffer;
  mapName: string | Buffer;
  folder: string | Buffer;
  game: string | Buffer;
  playerCount: number;
  maxPlayers: number;
  protocol: number;
  serverType: string | Buffer;
  platform: string | Buffer;
  passwordProtected: boolean;
  isMod: boolean;
  vacEnabled: boolean;
  botCount: number;
  ping: number;

  // Optional mod information
  modWebsite?: string | Buffer;
  modDownload?: string | Buffer;
  modVersion?: number;
  modSize?: number;
  multiplayerOnly?: boolean;
  usesCustomDll?: boolean;
}

Player<T> - Player Information

interface Player<T = string> {
  index: number; // Player slot index
  name: T; // Player name (string or Buffer based on encoding)
  score: number; // Player score/kills
  duration: number; // Connection time in seconds
}

Rules<T> - Server Configuration

type Rules<T = string> = Record<string, T>;

Exception Handling

Custom Exception Types

// Protocol-level errors
class BrokenMessageError extends Error {
  // Thrown when server response is malformed or invalid
}

// Buffer operation errors
class BufferExhaustedError extends BrokenMessageError {
  // Thrown when trying to read beyond buffer boundaries
}

Error Handling Example

import { info, BrokenMessageError, BufferExhaustedError } from 'typescript-a2s';

try {
  const serverInfo = await info('127.0.0.1', 27015);
  console.log(serverInfo);
} catch (error) {
  if (error instanceof BrokenMessageError) {
    console.error('Server returned invalid data:', error.message);
  } else if (error instanceof BufferExhaustedError) {
    console.error('Data parsing error:', error.message);
  } else if (error.code === 'ECONNREFUSED') {
    console.error('Server is offline or unreachable');
  } else if (error.code === 'ETIMEDOUT') {
    console.error('Query timed out - server may be overloaded');
  } else {
    console.error('Unexpected error:', error);
  }
}

Advanced Utilities

Binary Data Processing

import { ByteReader, ByteWriter } from 'typescript-a2s';

// Reading binary data
const reader = new ByteReader(buffer, true, 'utf-8');
const value = reader.readUint32();
const text = reader.readString();

// Writing binary data
const writer = new ByteWriter();
writer.writeUint32(12345);
writer.writeString('Hello World');
const result = writer.toBuffer();

Constants and Defaults

import {
  DEFAULT_TIMEOUT, // 3.0 seconds
  DEFAULT_ENCODING, // 'utf-8'
  DEFAULT_RETRIES, // 5 attempts
} from 'typescript-a2s';

Usage Examples

Basic Server Monitoring

import { info, players, rules } from 'typescript-a2s';

async function monitorServer(address: string, port: number) {
  try {
    // This is safe because info() and players() are stateless functions.
    // Do NOT use Promise.all with a single A2SClient instance!
    const [serverInfo, playerList] = await Promise.all([
      info(address, port),
      players(address, port),
    ]);

    console.log(`📊 ${serverInfo.serverName}`);
    console.log(`🗺️  Map: ${serverInfo.mapName}`);
    console.log(
      `👥 Players: ${serverInfo.playerCount}/${serverInfo.maxPlayers}`
    );
    console.log(`🏓 Ping: ${(serverInfo.ping * 1000).toFixed(1)}ms`);

    if (playerList.length > 0) {
      console.log('\n🎮 Top Players:');
      playerList
        .sort((a, b) => b.score - a.score)
        .slice(0, 5)
        .forEach((player, i) => {
          const time = Math.floor(player.duration / 60);
          console.log(
            `  ${i + 1}. ${player.name} - ${player.score} (${time}m)`
          );
        });
    }
  } catch (error) {
    console.error(`❌ Failed to query ${address}:${port}`, error.message);
  }
}

await monitorServer('127.0.0.1', 27015);

Multi-Server Batch Queries

import { A2SClient } from 'typescript-a2s';

const servers = [
  { name: 'Server 1', host: '127.0.0.1', port: 27015 },
  { name: 'Server 2', host: '127.0.0.1', port: 27016 },
  { name: 'Server 3', host: '127.0.0.1', port: 27017 },
];

async function queryMultipleServers() {
  const results = await Promise.allSettled(
    servers.map(async (server) => {
      const client = new A2SClient(server.host, server.port, 3000);
      try {
        const info = await client.info();
        return {
          server: server.name,
          name: info.serverName,
          players: info.playerCount,
          maxPlayers: info.maxPlayers,
          ping: info.ping,
        };
      } finally {
        client.close();
      }
    })
  );

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      const data = result.value;
      console.log(
        `✅ ${data.server}: ${data.players}/${data.maxPlayers} players`
      );
    } else {
      console.log(`❌ ${servers[index].name}: ${result.reason.message}`);
    }
  });
}

await queryMultipleServers();

Encoding and Internationalization

import { info } from 'typescript-a2s';

async function testEncodings(address: string, port: number) {
  // UTF-8 encoding (default)
  const utf8Info = await info(address, port, 3000, 'utf-8');
  console.log('UTF-8 server name:', utf8Info.serverName);

  // Latin1 encoding for older servers
  const latin1Info = await info(address, port, 3000, 'latin1');
  console.log('Latin1 server name:', latin1Info.serverName);

  // Raw binary data (no string decoding)
  const rawInfo = await info(address, port, 3000, null);
  console.log('Raw server name buffer:', rawInfo.serverName);

  // Convert raw buffer to string manually if needed
  if (Buffer.isBuffer(rawInfo.serverName)) {
    const decoded = rawInfo.serverName.toString('utf-8');
    console.log('Manually decoded:', decoded);
  }
}

await testEncodings('127.0.0.1', 27015);

Real-time Server Monitoring

import { info } from 'typescript-a2s';

class ServerMonitor {
  private interval: NodeJS.Timeout | null = null;
  private lastPlayerCount = -1;

  async start(address: string, port: number, intervalMs = 10000) {
    console.log(`🔄 Starting monitor for ${address}:${port}`);

    this.interval = setInterval(async () => {
      try {
        const serverInfo = await info(address, port, 2000);

        if (serverInfo.playerCount !== this.lastPlayerCount) {
          const timestamp = new Date().toLocaleTimeString();
          const change =
            this.lastPlayerCount === -1
              ? ''
              : ` (${serverInfo.playerCount > this.lastPlayerCount ? '+' : ''}${
                  serverInfo.playerCount - this.lastPlayerCount
                })`;

          console.log(
            `[${timestamp}] ${serverInfo.serverName}: ` +
              `${serverInfo.playerCount}/${serverInfo.maxPlayers}${change}`
          );

          this.lastPlayerCount = serverInfo.playerCount;
        }
      } catch (error) {
        console.error(
          `[${new Date().toLocaleTimeString()}] Error:`,
          error.message
        );
      }
    }, intervalMs);
  }

  stop() {
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
      console.log('🛑 Monitor stopped');
    }
  }
}

const monitor = new ServerMonitor();
await monitor.start('127.0.0.1', 27015);

// Stop monitoring on Ctrl+C
process.on('SIGINT', () => {
  monitor.stop();
  process.exit(0);
});

Important Notes & Best Practices

Server Behavior Variations

  • Data Consistency: Some servers may return inconsistent or incomplete data. Always validate critical fields before use.
  • Player Count Discrepancies: The playerCount in server info may not exactly match the length of the players array due to server implementation differences.
  • Port Configuration: Query ports often differ from game connection ports. Common patterns include game port + 1, or separate configured query ports.

Protocol Limitations

  • Player Limits: The A2S protocol cannot return more than 255 players due to byte field limitations.
  • Unicode Support: String encoding varies by server configuration. Use appropriate encoding settings for international servers.
  • Rate Limiting: This library does not implement rate limiting. Implement appropriate delays between requests to avoid being blocked.

Performance Considerations

  • Connection Management: Use A2SClient class for multiple queries to the same server to reuse connections.
  • Timeout Tuning: Adjust timeout values based on network conditions and server responsiveness.
  • Error Handling: Always implement proper error handling, especially for production monitoring systems.

Supported Games & Engines

Source Engine Games

  • Half-Life 2
  • Team Fortress 2
  • Counter-Strike: Global Offensive
  • Counter-Strike: Source
  • Left 4 Dead 2
  • Portal 2
  • Garry's Mod
  • Day of Defeat: Source

GoldSource Engine Games

  • Half-Life
  • Counter-Strike 1.6
  • Day of Defeat
  • Team Fortress Classic
  • Ricochet

Development

Project Setup

# Clone the repository
git clone https://github.com/Yepoleb/python-a2s.git
cd python-a2s/typescript-a2s

# Install dependencies
pnpm install

# Run development server
pnpm dev

Build Commands

# Build for production
pnpm build

# Generate type declarations
pnpm build:types

# Run tests
pnpm test

# Run tests with UI
pnpm test:ui

# Generate coverage report
pnpm test:coverage

# Lint code
pnpm lint

# Fix linting issues
pnpm lint:fix

# Run example
pnpm example

Project Structure

typescript-a2s/
├── src/
│   ├── a2s.ts           # Core client implementation
│   ├── socket.ts        # UDP network layer
│   ├── protocols.ts     # A2S protocol handlers
│   ├── fragment.ts      # Multi-packet response handling
│   ├── byteio.ts        # Binary data processing
│   ├── types.ts         # TypeScript definitions
│   ├── exceptions.ts    # Error classes
│   ├── defaults.ts      # Configuration constants
│   ├── index.ts         # Public API exports
│   └── __tests__/       # Test suite
├── examples/            # Usage examples
├── dist/               # Built output
└── docs/               # Documentation

Requirements

  • Node.js: 18.0.0 or higher
  • TypeScript: 5.0.0 or higher (for development)
  • Dependencies: Zero runtime dependencies

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines.

Related Projects

Acknowledgments

  • Based on the excellent python-a2s library by Yepoleb
  • Valve Corporation for the A2S protocol specification
  • The TypeScript and Node.js communities for excellent tooling