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/core
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.
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).Queries can fail in two distinct ways, both exported from @srvquery/core so you can branch on instanceof:
| Error | Thrown when |
|---|---|
QueryTimeoutError | No response was received within the timeout window, after all retries were exhausted. |
QueryTransportError | A 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;
}
}
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
v24.14.1, pinned in .node-versionInstall 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
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
commit-msg hook..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.34 commits
1 commits
TypeScript
99.7%
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/core
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.
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).Queries can fail in two distinct ways, both exported from @srvquery/core so you can branch on instanceof:
| Error | Thrown when |
|---|---|
QueryTimeoutError | No response was received within the timeout window, after all retries were exhausted. |
QueryTransportError | A 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;
}
}
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
v24.14.1, pinned in .node-versionInstall 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
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
commit-msg hook..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.34 commits
1 commits
TypeScript
99.7%