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})`);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.

- @srvquery/core: UDP/HTTP transport and binary parsing primitives.

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

- @srvquery/protocol-openmp: SA-MP / open.mp server query protocol client and schemas. Supports SA-MP and open.mp.

- @srvquery/protocol-fivem: FiveM / RedM (FXServer) HTTP query protocol client and schemas. Supports FiveM and RedM.

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

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/coreEvery 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" }); // ValvePlayersResponses 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.

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,

},

});- retriesis the total number of attempts, including the first request. Set it to- 1to disable retries entirely.

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

- fatalcan stop retrying immediately for errors that retries can't fix (for example, an unreachable host).

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

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;

}

}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- Node.js v24.14.1, pinned in.node-version

- Corepack

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 installRun 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 oxfmtBuild a specific package layer when working on a narrower change:

pnpm build:packages:core

pnpm build:packages:protocolsApply automatic formatting or lint fixes with:

pnpm fmt

pnpm lint:fix- Commits follow Conventional Commits and are linted by commitlint via a Husky commit-msghook.

- 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 surepnpm fmt:check,pnpm lint, andpnpm buildpass before opening a pull request.