carlos-menezes/srvquery

The TypeScript toolkit for querying game servers.

0

stars

35

commits

TypeScript

primary language

Sep 12, 2026

updated

README

srvquery

GitHub CI TypeScript pnpm

srvquery is a fully typed TypeScript toolkit for querying game servers.

It gives you raw binary and transport primitives (@srvquery/core) alongside ready-to-use, schema-validated protocol clients (@srvquery/protocol-*) so you can fetch a server's status, player list and rules with a single await, or drop down to the wire format and build your own protocol on top of the same building blocks.

import { createValveProtocol } from "@srvquery/protocol-valve";

const server = createValveProtocol({ host: "127.0.0.1", port: 27015 });
const info = await server.query({ opcode: "INFO" });

console.log(`${info.name}: ${info.map} (${info.players}/${info.maxPlayers})`);

Architecture

flowchart LR
  server[Game server]
  core["@srvquery/core<br/>Transport and binary primitives"]
  protocol["@srvquery/protocol-*<br/>Protocol implementation and schemas"]
  application[Your application]

  server -->|datagrams| core
  core -->|packets| protocol
  protocol -->|typed, validated responses| application

@srvquery/core owns everything protocol-agnostic: opening and closing connections, matching requests to responses, retrying failed attempts and reading/writing binary payloads through BufferCursor. Protocol packages build request packets, reassemble and decode responses using core's primitives, validate the result against a schema and return a plain, structured object. Each layer can be consumed independently: for example, you could use only @srvquery/core to implement a protocol this repository doesn't ship yet.

Packages

Protocols

  • @srvquery/protocol-valve: Valve server query protocol client and schemas. Supports Counter-Strike 2, Counter-Strike: Source, Team Fortress 2, Garry's Mod, Left 4 Dead, Left 4 Dead 2, Half-Life 2: Deathmatch, Day of Defeat: Source, DayZ, Arma 2, Arma 3, Rust, ARK: Survival Evolved, ARK: Survival Ascended, 7 Days to Die, Conan Exiles, Squad and other A2S-compatible servers. npm
  • @srvquery/protocol-openmp: SA-MP / open.mp server query protocol client and schemas. Supports SA-MP and open.mp. npm
  • @srvquery/protocol-fivem: FiveM / RedM (FXServer) HTTP query protocol client and schemas. Supports FiveM and RedM. npm
  • @srvquery/protocol-minecraft-java: Minecraft Java Edition Server List Ping protocol client and schemas. Supports direct status and latency queries by host and port.
  • @srvquery/protocol-minecraft-bedrock: Minecraft Bedrock RakNet unconnected ping protocol client and schemas. Supports direct status and latency queries by host and port.

Installation

Install @srvquery/core with the protocol package your application needs, e.g.:

pnpm add @srvquery/core @srvquery/protocol-valve
# or pnpm add @srvquery/core @srvquery/protocol-<proto>

Install @srvquery/core on its own only if you need its transport or binary primitives directly, for example to implement a new protocol:

pnpm add @srvquery/core

Core concepts

Type-safe responses

Every query is generic over its opcode, so the return type of query(...) is inferred automatically.

const info = await server.query({ opcode: "INFO" }); // ValveServerInfo
const players = await server.query({ opcode: "PLAYERS" }); // ValvePlayers

Responses are validated at runtime against a Zod schema before being returned. If a server sends a malformed or unexpected payload, query(...) rejects with a ZodError instead of handing your application silently corrupt data.

Retries and backoff

Protocol clients make up to three attempts for each UDP request by default. Between attempts they use the exported backoffStrategy, an exponential delay starting at 100ms.

Customize retries when creating a protocol client:

import { QueryTransportError, backoffStrategy } from "@srvquery/core";
import { createValveProtocol } from "@srvquery/protocol-valve";

const server = createValveProtocol({
  host: "127.0.0.1",
  port: 27015,
  retry: {
    retries: 5,
    strategy: backoffStrategy,
    fatal: (error) => error instanceof QueryTransportError,
  },
});
  • retries is the total number of attempts, including the first request. Set it to 1 to disable retries entirely.
  • strategy receives the completed attempt number and returns the delay in milliseconds before the next attempt: supply your own function for linear, jittered, or fixed-delay backoff.
  • fatal can stop retrying immediately for errors that retries can't fix (for example, an unreachable host).

Error handling

Queries can fail in two distinct ways, both exported from @srvquery/core so you can branch on instanceof:

ErrorThrown when
QueryTimeoutErrorNo response was received within the timeout window, after all retries were exhausted.
QueryTransportErrorA transport-level failure occurred: a socket error, a non-2xx HTTP response, or a malformed response body.
import { QueryTransportError, QueryTimeoutError } from "@srvquery/core";

try {
  const info = await server.query({ opcode: "INFO" });
} catch (error) {
  if (error instanceof QueryTimeoutError) {
    console.error(`${error.host}:${error.port} did not respond after ${error.attempts} attempt(s)`);
  } else if (error instanceof QueryTransportError) {
    console.error("Transport failure:", error.cause);
  } else {
    throw error;
  }
}

Socket lifecycle

Every query(...) call opens a socket scoped to that single request response exchange and closes it automatically: you never have to manage a connection pool or worry about leaking file descriptors. If you work with @srvquery/core's createUdpSocket directly, the same guarantee is available through explicit resource management:

import { createUdpSocket } from "@srvquery/core";

using socket = createUdpSocket({ host: "127.0.0.1", port: 27015 });
// socket.close() runs automatically when `socket` leaves scope

Development

Requirements

Install or activate the Node.js version in .node-version with your preferred version manager. The repository also pins pnpm through the packageManager field in package.json; enable Corepack and install that pnpm version before installing dependencies:

corepack enable
corepack install
pnpm install

Commands

Run commands from the repository root:

pnpm build       # build every package (rolldown + tsc), respecting dependency order
pnpm test        # run every package's Vitest suite
pnpm lint        # lint with oxlint
pnpm fmt:check   # verify formatting with oxfmt

Build a specific package layer when working on a narrower change:

pnpm build:packages:core
pnpm build:packages:protocols

Apply automatic formatting or lint fixes with:

pnpm fmt
pnpm lint:fix

Contributing

  • Commits follow Conventional Commits and are linted by commitlint via a Husky commit-msg hook.
  • Staged files are linted and formatted automatically before each commit through lint-staged.
  • CI (see .github/workflows/ci.yml) runs the same format check, lint, and build steps required locally: make sure pnpm fmt:check, pnpm lint, and pnpm build pass before opening a pull request.

Contributors

carlos-menezes

34 commits

Copilot

1 commits

carlos-menezes/srvquery

The TypeScript toolkit for querying game servers.

0

stars

35

commits

TypeScript

primary language

Sep 12, 2026

updated

README

srvquery

GitHub CI TypeScript pnpm

srvquery is a fully typed TypeScript toolkit for querying game servers.

It gives you raw binary and transport primitives (@srvquery/core) alongside ready-to-use, schema-validated protocol clients (@srvquery/protocol-*) so you can fetch a server's status, player list and rules with a single await, or drop down to the wire format and build your own protocol on top of the same building blocks.

import { createValveProtocol } from "@srvquery/protocol-valve";

const server = createValveProtocol({ host: "127.0.0.1", port: 27015 });
const info = await server.query({ opcode: "INFO" });

console.log(`${info.name}: ${info.map} (${info.players}/${info.maxPlayers})`);

Architecture

flowchart LR
  server[Game server]
  core["@srvquery/core<br/>Transport and binary primitives"]
  protocol["@srvquery/protocol-*<br/>Protocol implementation and schemas"]
  application[Your application]

  server -->|datagrams| core
  core -->|packets| protocol
  protocol -->|typed, validated responses| application

@srvquery/core owns everything protocol-agnostic: opening and closing connections, matching requests to responses, retrying failed attempts and reading/writing binary payloads through BufferCursor. Protocol packages build request packets, reassemble and decode responses using core's primitives, validate the result against a schema and return a plain, structured object. Each layer can be consumed independently: for example, you could use only @srvquery/core to implement a protocol this repository doesn't ship yet.

Packages

Protocols

  • @srvquery/protocol-valve: Valve server query protocol client and schemas. Supports Counter-Strike 2, Counter-Strike: Source, Team Fortress 2, Garry's Mod, Left 4 Dead, Left 4 Dead 2, Half-Life 2: Deathmatch, Day of Defeat: Source, DayZ, Arma 2, Arma 3, Rust, ARK: Survival Evolved, ARK: Survival Ascended, 7 Days to Die, Conan Exiles, Squad and other A2S-compatible servers. npm
  • @srvquery/protocol-openmp: SA-MP / open.mp server query protocol client and schemas. Supports SA-MP and open.mp. npm
  • @srvquery/protocol-fivem: FiveM / RedM (FXServer) HTTP query protocol client and schemas. Supports FiveM and RedM. npm
  • @srvquery/protocol-minecraft-java: Minecraft Java Edition Server List Ping protocol client and schemas. Supports direct status and latency queries by host and port.
  • @srvquery/protocol-minecraft-bedrock: Minecraft Bedrock RakNet unconnected ping protocol client and schemas. Supports direct status and latency queries by host and port.

Installation

Install @srvquery/core with the protocol package your application needs, e.g.:

pnpm add @srvquery/core @srvquery/protocol-valve
# or pnpm add @srvquery/core @srvquery/protocol-<proto>

Install @srvquery/core on its own only if you need its transport or binary primitives directly, for example to implement a new protocol:

pnpm add @srvquery/core

Core concepts

Type-safe responses

Every query is generic over its opcode, so the return type of query(...) is inferred automatically.

const info = await server.query({ opcode: "INFO" }); // ValveServerInfo
const players = await server.query({ opcode: "PLAYERS" }); // ValvePlayers

Responses are validated at runtime against a Zod schema before being returned. If a server sends a malformed or unexpected payload, query(...) rejects with a ZodError instead of handing your application silently corrupt data.

Retries and backoff

Protocol clients make up to three attempts for each UDP request by default. Between attempts they use the exported backoffStrategy, an exponential delay starting at 100ms.

Customize retries when creating a protocol client:

import { QueryTransportError, backoffStrategy } from "@srvquery/core";
import { createValveProtocol } from "@srvquery/protocol-valve";

const server = createValveProtocol({
  host: "127.0.0.1",
  port: 27015,
  retry: {
    retries: 5,
    strategy: backoffStrategy,
    fatal: (error) => error instanceof QueryTransportError,
  },
});
  • retries is the total number of attempts, including the first request. Set it to 1 to disable retries entirely.
  • strategy receives the completed attempt number and returns the delay in milliseconds before the next attempt: supply your own function for linear, jittered, or fixed-delay backoff.
  • fatal can stop retrying immediately for errors that retries can't fix (for example, an unreachable host).

Error handling

Queries can fail in two distinct ways, both exported from @srvquery/core so you can branch on instanceof:

ErrorThrown when
QueryTimeoutErrorNo response was received within the timeout window, after all retries were exhausted.
QueryTransportErrorA transport-level failure occurred: a socket error, a non-2xx HTTP response, or a malformed response body.
import { QueryTransportError, QueryTimeoutError } from "@srvquery/core";

try {
  const info = await server.query({ opcode: "INFO" });
} catch (error) {
  if (error instanceof QueryTimeoutError) {
    console.error(`${error.host}:${error.port} did not respond after ${error.attempts} attempt(s)`);
  } else if (error instanceof QueryTransportError) {
    console.error("Transport failure:", error.cause);
  } else {
    throw error;
  }
}

Socket lifecycle

Every query(...) call opens a socket scoped to that single request response exchange and closes it automatically: you never have to manage a connection pool or worry about leaking file descriptors. If you work with @srvquery/core's createUdpSocket directly, the same guarantee is available through explicit resource management:

import { createUdpSocket } from "@srvquery/core";

using socket = createUdpSocket({ host: "127.0.0.1", port: 27015 });
// socket.close() runs automatically when `socket` leaves scope

Development

Requirements

Install or activate the Node.js version in .node-version with your preferred version manager. The repository also pins pnpm through the packageManager field in package.json; enable Corepack and install that pnpm version before installing dependencies:

corepack enable
corepack install
pnpm install

Commands

Run commands from the repository root:

pnpm build       # build every package (rolldown + tsc), respecting dependency order
pnpm test        # run every package's Vitest suite
pnpm lint        # lint with oxlint
pnpm fmt:check   # verify formatting with oxfmt

Build a specific package layer when working on a narrower change:

pnpm build:packages:core
pnpm build:packages:protocols

Apply automatic formatting or lint fixes with:

pnpm fmt
pnpm lint:fix

Contributing

  • Commits follow Conventional Commits and are linted by commitlint via a Husky commit-msg hook.
  • Staged files are linted and formatted automatically before each commit through lint-staged.
  • CI (see .github/workflows/ci.yml) runs the same format check, lint, and build steps required locally: make sure pnpm fmt:check, pnpm lint, and pnpm build pass before opening a pull request.

Contributors

carlos-menezes

34 commits

Copilot

1 commits

Languages

TypeScript

99.7%