refactor: introduce typescript for static type checking

We use JSDoc for documentation, but these annotations
were not being validated. This meant that type information
could become outdated or incorrect without any warning.

This commit introduces the TypeScript compiler (`tsc`) as a static
analysis tool to leverage our existing JSDoc comments.

To support this, JSDoc annotations across the codebase have been
improved for accuracy. Additionally, the `varint` module now uses a
custom `VarIntError` class for better type inference and error handling.
A new `typecheck` script has been added to `package.json` to run this
validation.
This commit is contained in:
2025-06-19 02:57:11 +03:00
parent 7322034aba
commit a1b999ca4e
6 changed files with 168 additions and 80 deletions

View File

@ -14,15 +14,22 @@ const debug = createDebug("mineping:java");
/**
* Represents the structured and user-friendly response from a server ping.
* The fields and their optionality are based on the official protocol documentation.
* The fields and their optionality are based on the protocol documentation.
* See [Status Response Documentation](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response) for more details.
* @typedef {object} JavaPingResponse
* @property {{ name: string, protocol: number }} version - Contains the server's version name and protocol number
* @property {{ max: number, online: number, sample?: Array<{ name: string, id: string }> } | undefined} players - Player count and a sample of online players.
* @property {object | string | undefined} description - Optional. The server's Message of the Day (MOTD)
* @property {string | undefined} favicon - Optional. A Base64-encoded 64x64 PNG image data URI
* @property {boolean | undefined} enforcesSecureChat - Optional. True if the server requires clients to have a Mojang-signed public key
* @property {boolean | undefined} preventsChatReports - Optional. True if a mod is installed to disable chat reporting
* @see {@link https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response}
* @property {{ name: string, protocol: number }} version - Contains the server's version name and protocol number.
* @property {{ max: number, online: number, sample?: Array<{ name: string, id: string }> }} [players] - Player count and a sample of online players.
* @property {object | string} [description] - The server's Message of the Day (MOTD).
* @property {string} [favicon] - A Base64-encoded 64x64 PNG image data URI.
* @property {boolean} [enforcesSecureChat] - True if the server requires clients to have a Mojang-signed public key.
* @property {boolean} [preventsChatReports] - True if a mod is installed to disable chat reporting.
*/
/**
* @typedef {object} JavaPingOptions
* @property {number} [port=25565] - The fallback port if an SRV record is not found.
* @property {number} [timeout=5000] - The connection timeout in milliseconds.
* @property {number} [protocolVersion=-1] - The protocol version to use in the handshake. `-1` is for auto-detection.
*/
/**
@ -106,24 +113,24 @@ function processResponse(buffer) {
return { response, remainder };
} catch (err) {
// If the buffer is too short for a VarInt, it's a recoverable state.
if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) {
debug("buffer underflow while parsing VarInt, waiting for more data");
return null; // Wait for more data.
if (err instanceof varint.VarIntError) {
if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) {
debug("buffer underflow while parsing VarInt, waiting for more data");
return null; // Wait for more data.
}
// For malformed VarInts or JSON, throw the error to reject the promise.
throw err;
}
// For malformed VarInts or JSON, throw the error to reject the promise.
throw err;
}
}
/**
* Pings a Minecraft Java Edition server.
* Asynchronously Pings a Minecraft Java Edition server.
* This function performs an SRV lookup and then attempts to connect and retrieve the server status.
* @param {string} host The server address to ping
* @param {object} [options={}] Optional configuration
* @param {number} [options.port=25565] The fallback port if an SRV record is not found
* @param {number} [options.timeout=5000] The connection timeout in milliseconds
* @param {number} [options.protocolVersion=-1] The protocol version to use in the handshake. `-1` is for auto-detection
* @returns {Promise<JavaPingResponse>} A promise that resolves with the server's status
* @param {string} host - The server address to ping.
* @param {JavaPingOptions} [options={}] - Optional configuration.
* @returns {Promise<JavaPingResponse>} A promise that resolves with the server's status.
*/
export async function pingJava(host, options = {}) {
if (typeof host !== "string" || host.trim() === "") {
@ -150,10 +157,17 @@ export async function pingJava(host, options = {}) {
}
} catch (err) {
// Common errors like ENODATA or ENOTFOUND are expected when a server
// does not have an SRV record, so we ignore them and proceed.
if (!["ENODATA", "ENOTFOUND"].includes(err.code)) {
debug("SRV lookup for %s failed (%s), using fallback", host, err.code);
// For other errors we should re-throw.
// does not have an SRV record, so we ignore them and proceed
if (
err instanceof Error &&
"code" in err &&
(err.code === "ENODATA" || err.code === "ENOTFOUND")
) {
// Non fatal DNS error, log it and continue
debug("SRV lookup for %s failed (%s), using fallback.", host, err.code);
} else {
// Re-throw anything else to fail the operation
debug("SRV lookup for %s failed unexpectedly, re-throwing.", host, err);
throw err;
}
}