diff --git a/examples/node/cluster_example.ts b/examples/node/cluster_example.ts new file mode 100644 index 0000000000..b45fe53d49 --- /dev/null +++ b/examples/node/cluster_example.ts @@ -0,0 +1,146 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + ClosingError, + ConnectionError, + GlideClusterClient, + InfoOptions, + Logger, + RequestError, + TimeoutError, +} from "@valkey/valkey-glide"; + +/** + * Creates and returns a GlideClusterClient instance. + * This function initializes a GlideClusterClient with the provided list of nodes. + * The nodesList may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + * @returns An instance of GlideClusterClient connected to the discovered nodes. + */ +async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { + const addresses = nodesList.map((node) => ({ + host: node.host, + port: node.port, + })); + + // Check `GlideClusterClientConfiguration` for additional options. + return await GlideClusterClient.createClient({ + addresses: addresses, + // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. + // useTLS: true, + }); +} + +/** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. + * @param client An instance of GlideClusterClient. + */ +async function appLogic(client: GlideClusterClient) { + // Send SET and GET + const setResponse = await client.set("foo", "bar"); + Logger.log("info", "app", `Set response is: ${setResponse}`); + + const getResponse = await client.get("foo"); + Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`); + + // Send PING to all primaries (according to Redis's PING request_policy) + const pong = await client.ping(); + Logger.log("info", "app", `PING response: ${pong}`); + + // Send INFO REPLICATION to all nodes + const infoReplResps = await client.info({ + sections: [InfoOptions.Replication], + }); + const infoReplicationValues = Object.values(infoReplResps); + + Logger.log( + "info", + "app", + `INFO REPLICATION responses from all nodes are:\n`, + ); + + infoReplicationValues.forEach((item) => + Logger.log("info", "glide", item as string), + ); +} + +/** + * Executes the application logic with exception handling. + */ +async function execAppLogic() { + // Loop through with exception handling + while (true) { + let client; + + try { + client = await createClient(); + return await appLogic(client); + } catch (error) { + switch (true) { + case error instanceof ClosingError: + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if ((error as ClosingError).message.includes("NOAUTH")) { + Logger.log( + "error", + "glide", + `Authentication error encountered: ${error}`, + ); + } else { + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); + } + + throw error; + case error instanceof TimeoutError: + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log("error", "glide", `Timeout error: ${error}`); + throw error; + case error instanceof ConnectionError: + // The client wasn't able to reestablish the connection within the given retries + Logger.log("error", "glide", `Connection error: ${error}`); + throw error; + case error instanceof RequestError: + // Other error reported during a request, such as a server response error + Logger.log( + "error", + "glide", + `RequestError encountered: ${error}`, + ); + throw error; + default: + Logger.log("error", "glide", `Unexpected error: ${error}`); + throw error; + } + } finally { + try { + if (client) { + await client.close(); + } + } catch (error) { + Logger.log( + "warn", + "glide", + `Error encountered while closing the client: ${error}`, + ); + } + } + } +} + +function main() { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig("info"); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig("info", fileName); + execAppLogic(); +} + +main(); diff --git a/examples/node/standalone_example.ts b/examples/node/standalone_example.ts new file mode 100644 index 0000000000..0716cd8475 --- /dev/null +++ b/examples/node/standalone_example.ts @@ -0,0 +1,130 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + ClosingError, + ConnectionError, + GlideClient, + Logger, + RequestError, + TimeoutError, +} from "@valkey/valkey-glide"; + +/** + * Creates and returns a GlideClient instance. + * This function initializes a GlideClient with the provided list of nodes. + * The nodes_list may contain either only primary node or a mix of primary + * and replica nodes. The GlideClient use these nodes to connect to + * the Standalone setup servers. + * @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + * @returns An instance of GlideClient connected to the discovered nodes. + */ +async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { + const addresses = nodesList.map((node) => ({ + host: node.host, + port: node.port, + })); + + // Check `GlideClientConfiguration` for additional options. + return await GlideClient.createClient({ + addresses: addresses, + // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. + // useTLS: true, + }); +} + +/** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClient. + * @param client An instance of GlideClient. + */ +async function appLogic(client: GlideClient) { + // Send SET and GET + const setResponse = await client.set("foo", "bar"); + Logger.log("info", "app", `Set response is: ${setResponse}`); + + const getResponse = await client.get("foo"); + Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`); + + // Send PING to primary + const pong = await client.ping(); + Logger.log("info", "app", `PING response: ${pong}`); +} + +/** + * Executes the application logic with exception handling. + */ +async function execAppLogic() { + // Loop through with exception handling + while (true) { + let client; + + try { + client = await createClient(); + return await appLogic(client); + } catch (error) { + switch (true) { + case error instanceof ClosingError: + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if ((error as ClosingError).message.includes("NOAUTH")) { + Logger.log( + "error", + "glide", + `Authentication error encountered: ${error}`, + ); + } else { + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); + } + + throw error; + case error instanceof TimeoutError: + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log("error", "glide", `Timeout error: ${error}`); + throw error; + case error instanceof ConnectionError: + // The client wasn't able to reestablish the connection within the given retries + Logger.log("error", "glide", `Connection error: ${error}`); + throw error; + case error instanceof RequestError: + // Other error reported during a request, such as a server response error + Logger.log( + "error", + "glide", + `RequestError encountered: ${error}`, + ); + throw error; + default: + Logger.log("error", "glide", `Unexpected error: ${error}`); + throw error; + } + } finally { + try { + if (client) { + await client.close(); + } + } catch (error) { + Logger.log( + "warn", + "glide", + `Error encountered while closing the client: ${error}`, + ); + } + } + } +} + +function main() { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig("info"); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig("info", fileName); + execAppLogic(); +} + +main(); diff --git a/examples/python/cluster_example.py b/examples/python/cluster_example.py index e26986bc87..c3cefbd14f 100644 --- a/examples/python/cluster_example.py +++ b/examples/python/cluster_example.py @@ -92,12 +92,13 @@ async def exec_app_logic(): "glide", f"Authentication error encountered: {e}", ) - raise e - Logger.log( - LogLevel.WARN, - "glide", - f"Client has closed and needs to be re-created: {e}", - ) + else: + Logger.log( + LogLevel.WARN, + "glide", + f"Client has closed and needs to be re-created: {e}", + ) + raise e except TimeoutError as e: # A request timed out. You may choose to retry the execution based on your application's logic Logger.log(LogLevel.ERROR, "glide", f"TimeoutError encountered: {e}") diff --git a/package.json b/package.json index 35dc96c83b..fa682d7107 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "devDependencies": { - "@eslint/js": "^9.10.0", - "@types/eslint__js": "^8.42.3", - "@types/eslint-config-prettier": "^6.11.3", - "eslint": "^9.10.0", - "eslint-config-prettier": "^9.1.0", - "prettier": "^3.3.3", - "typescript": "^5.6.2", - "typescript-eslint": "^8.5.0" - } + "devDependencies": { + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", + "@types/eslint-config-prettier": "^6.11.3", + "eslint": "^9.10.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.3", + "typescript": "^5.6.2", + "typescript-eslint": "^8.5.0" + } }