UNPKG

@valkey/valkey-glide

Version:

General Language Independent Driver for the Enterprise (GLIDE) for Valkey

1,183 lines 349 kB
"use strict"; /** * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseClient = exports.ObjectType = exports.NodeDiscoveryMode = exports.ServiceType = exports.ALL_PATTERNS = exports.ALL_CHANNELS = exports.Decoder = exports.ProtocolVersion = void 0; exports.convertGlideRecord = convertGlideRecord; exports.convertGlideRecordToRecord = convertGlideRecordToRecord; exports.isGlideRecord = isGlideRecord; exports.convertRecordToGlideRecord = convertRecordToGlideRecord; /** * Note: 'eslint-disable-line @typescript-eslint/no-unused-vars' is used intentionally * to suppress unused import errors for types referenced only in JSDoc. */ const long_1 = __importDefault(require("long")); const net = __importStar(require("net")); const minimal_1 = require("protobufjs/minimal"); const _1 = require("."); const ProtobufMessage_1 = require("../build-ts/ProtobufMessage"); var ProtocolVersion; (function (ProtocolVersion) { /** Use RESP2 to communicate with the server nodes. */ ProtocolVersion[ProtocolVersion["RESP2"] = 1] = "RESP2"; /** Use RESP3 to communicate with the server nodes. */ ProtocolVersion[ProtocolVersion["RESP3"] = 0] = "RESP3"; })(ProtocolVersion || (exports.ProtocolVersion = ProtocolVersion = {})); /** * Enum representing the different types of decoders. */ var Decoder; (function (Decoder) { /** * Decodes the response into a buffer array. */ Decoder[Decoder["Bytes"] = 0] = "Bytes"; /** * Decodes the response into a string. */ Decoder[Decoder["String"] = 1] = "String"; })(Decoder || (exports.Decoder = Decoder = {})); /** * Constant representing "all channels" for unsubscribe operations. * Use this to unsubscribe from all channel subscriptions at once. * * @example * ```typescript * await client.unsubscribeLazy(ALL_CHANNELS); * ``` */ exports.ALL_CHANNELS = null; /** * Constant representing "all patterns" for punsubscribe operations. * Use this to unsubscribe from all pattern subscriptions at once. * * @example * ```typescript * await client.punsubscribeLazy(ALL_PATTERNS); * ``` */ exports.ALL_PATTERNS = null; /** * @internal * Convert `GlideRecord<number>` recevied after resolving the value pointer into `SortedSetDataType`. */ function convertGlideRecordForSortedSet(res) { return res.map((e) => { return { element: e.key, score: e.value }; }); } /** * @internal * This function converts an input from GlideRecord or Record types to GlideRecord. * * @param keysAndValues - key names and their values. * @returns GlideRecord array containing keys and their values. */ function convertGlideRecord(keysAndValues) { if (!Array.isArray(keysAndValues)) { return Object.entries(keysAndValues).map(([key, value]) => { return { key, value }; }); } return keysAndValues; } /** * @internal * Recursively downcast `GlideRecord` to `Record`. Use if `data` keys are always strings. */ function convertGlideRecordToRecord(data) { const res = {}; for (const pair of data) { let newVal = pair.value; if (isGlideRecord(pair.value)) { newVal = convertGlideRecordToRecord(pair.value); } else if (isGlideRecordArray(pair.value)) { newVal = pair.value.map(convertGlideRecordToRecord); } res[pair.key] = newVal; } return res; } /** * @internal * Check whether an object is a `GlideRecord` (see {@link GlideRecord}). */ function isGlideRecord(obj) { return (Array.isArray(obj) && obj.length > 0 && typeof obj[0] === "object" && "key" in obj[0] && "value" in obj[0]); } /** * @internal * Check whether an object is a `GlideRecord[]` (see {@link GlideRecord}). */ function isGlideRecordArray(obj) { return Array.isArray(obj) && obj.length > 0 && isGlideRecord(obj[0]); } /** * @internal * Reverse of {@link convertGlideRecordToRecord}. */ function convertRecordToGlideRecord(data) { return Object.entries(data).map(([key, value]) => { return { key, value }; }); } /** Represents the types of services that can be used for IAM authentication. */ var ServiceType; (function (ServiceType) { ServiceType["Elasticache"] = "Elasticache"; ServiceType["MemoryDB"] = "MemoryDB"; })(ServiceType || (exports.ServiceType = ServiceType = {})); /** * Controls how the client discovers node roles and topology in standalone mode. */ var NodeDiscoveryMode; (function (NodeDiscoveryMode) { /** Default: verify node roles via INFO REPLICATION, use only provided addresses. */ NodeDiscoveryMode[NodeDiscoveryMode["Standard"] = 0] = "Standard"; /** Skip role detection entirely. Trust provided addresses as-is; first address is primary. * Use when connecting through a proxy (e.g., Envoy) or when the topology is known and static. * Note: Do not set `clientName` when using this mode with a proxy. */ NodeDiscoveryMode[NodeDiscoveryMode["Static"] = 1] = "Static"; /** Discover full topology (primary + all replicas) from any starting node. * Provide any single node address and the client will find and connect to all other nodes. */ NodeDiscoveryMode[NodeDiscoveryMode["DiscoverAll"] = 2] = "DiscoverAll"; })(NodeDiscoveryMode || (exports.NodeDiscoveryMode = NodeDiscoveryMode = {})); /** * Enum of Valkey data types * `STRING` * `LIST` * `SET` * `ZSET` * `HASH` * `STREAM` */ var ObjectType; (function (ObjectType) { ObjectType["STRING"] = "String"; ObjectType["LIST"] = "List"; ObjectType["SET"] = "Set"; ObjectType["ZSET"] = "ZSet"; ObjectType["HASH"] = "Hash"; ObjectType["STREAM"] = "Stream"; })(ObjectType || (exports.ObjectType = ObjectType = {})); function getRequestErrorClass(type) { if (type === ProtobufMessage_1.response.RequestErrorType.Disconnect) { return _1.ConnectionError; } if (type === ProtobufMessage_1.response.RequestErrorType.ExecAbort) { return _1.ExecAbortError; } if (type === ProtobufMessage_1.response.RequestErrorType.Timeout) { return _1.TimeoutError; } if (type === ProtobufMessage_1.response.RequestErrorType.Unspecified) { return _1.RequestError; } return _1.RequestError; } /** * Base client interface for GLIDE */ class BaseClient { socket; promiseCallbackFunctions = []; availableCallbackSlots = []; requestWriter = new minimal_1.BufferWriter(); writeInProgress = false; remainingReadData; requestTimeout; // Timeout in milliseconds isClosed = false; defaultDecoder = Decoder.String; pubsubFutures = []; pendingPushNotification = []; inflightRequestsLimit; config; configurePubsub(options, configuration) { if (options.pubsubSubscriptions) { if (options.protocol == ProtocolVersion.RESP2) { throw new _1.ConfigurationError("PubSub subscriptions require RESP3 protocol, but RESP2 was configured."); } const { context, callback } = options.pubsubSubscriptions; if (context && !callback) { throw new _1.ConfigurationError("PubSub subscriptions with a context require a callback function to be configured."); } configuration.pubsubSubscriptions = ProtobufMessage_1.connection_request.PubSubSubscriptions.create({}); for (const [channelType, channelsPatterns] of Object.entries(options.pubsubSubscriptions.channelsAndPatterns)) { let entry = configuration.pubsubSubscriptions .channelsOrPatternsByType[parseInt(channelType)]; if (!entry) { entry = ProtobufMessage_1.connection_request.PubSubChannelsOrPatterns.create({ channelsOrPatterns: [], }); configuration.pubsubSubscriptions.channelsOrPatternsByType[parseInt(channelType)] = entry; } for (const channelPattern of channelsPatterns) { entry.channelsOrPatterns.push(Buffer.from(channelPattern)); } } } } handleReadData(data) { const buf = this.remainingReadData ? Buffer.concat([this.remainingReadData, data]) : data; let lastPos = 0; const reader = minimal_1.Reader.create(buf); while (reader.pos < reader.len) { lastPos = reader.pos; let message = undefined; try { message = ProtobufMessage_1.response.Response.decodeDelimited(reader); } catch (err) { if (err instanceof RangeError) { // Partial response received, more data is required this.remainingReadData = buf.slice(lastPos); return; } else { // Unhandled error const err_message = `Failed to decode the response: ${err}`; _1.Logger.log("error", "connection", err_message); this.close(err_message); return; } } if (message.isPush) { this.processPush(message); } else { this.processResponse(message); } } this.remainingReadData = undefined; } toProtobufRoute(route) { if (!route) { return undefined; } if (route === "allPrimaries") { return ProtobufMessage_1.command_request.Routes.create({ simpleRoutes: ProtobufMessage_1.command_request.SimpleRoutes.AllPrimaries, }); } else if (route === "allNodes") { return ProtobufMessage_1.command_request.Routes.create({ simpleRoutes: ProtobufMessage_1.command_request.SimpleRoutes.AllNodes, }); } else if (route === "randomNode") { return ProtobufMessage_1.command_request.Routes.create({ simpleRoutes: ProtobufMessage_1.command_request.SimpleRoutes.Random, }); } else if (route.type === "primarySlotKey") { return ProtobufMessage_1.command_request.Routes.create({ slotKeyRoute: ProtobufMessage_1.command_request.SlotKeyRoute.create({ slotType: ProtobufMessage_1.command_request.SlotTypes.Primary, slotKey: route.key, }), }); } else if (route.type === "replicaSlotKey") { return ProtobufMessage_1.command_request.Routes.create({ slotKeyRoute: ProtobufMessage_1.command_request.SlotKeyRoute.create({ slotType: ProtobufMessage_1.command_request.SlotTypes.Replica, slotKey: route.key, }), }); } else if (route.type === "primarySlotId") { return ProtobufMessage_1.command_request.Routes.create({ slotKeyRoute: ProtobufMessage_1.command_request.SlotIdRoute.create({ slotType: ProtobufMessage_1.command_request.SlotTypes.Primary, slotId: route.id, }), }); } else if (route.type === "replicaSlotId") { return ProtobufMessage_1.command_request.Routes.create({ slotKeyRoute: ProtobufMessage_1.command_request.SlotIdRoute.create({ slotType: ProtobufMessage_1.command_request.SlotTypes.Replica, slotId: route.id, }), }); } else if (route.type === "routeByAddress") { let port = route.port; let host = route.host; if (port === undefined) { const split = host.split(":"); if (split.length !== 2) { throw new _1.RequestError("No port provided, expected host to be formatted as `{hostname}:{port}`. Received " + host); } host = split[0]; port = Number(split[1]); } return ProtobufMessage_1.command_request.Routes.create({ byAddressRoute: { host, port }, }); } } dropCommandSpan(spanPtr) { if (spanPtr === null || spanPtr === undefined) return; if (typeof spanPtr === "number") { return (0, _1.dropOtelSpan)(BigInt(spanPtr)); // Convert number to BigInt } else if (spanPtr instanceof long_1.default) { return (0, _1.dropOtelSpan)(BigInt(spanPtr.toString())); // Convert Long to BigInt via string } } processResponse(message) { if (message.closingError != null) { this.close(message.closingError); return; } const [resolve, reject, decoder = this.defaultDecoder] = this.promiseCallbackFunctions[message.callbackIdx]; this.availableCallbackSlots.push(message.callbackIdx); if (message.requestError != null) { const errorType = getRequestErrorClass(message.requestError.type); reject(new errorType(message.requestError.message ?? undefined)); } else if (message.respPointer != null) { const ptrNum = typeof message.respPointer === "number" ? message.respPointer : message.respPointer.toNumber(); try { resolve((0, _1.valueFromPointer)(ptrNum, decoder === Decoder.String)); } catch (err) { _1.Logger.log("error", "Decoder", `Decoding error: '${err}'`); reject(err instanceof _1.ValkeyError ? err : new Error(`Decoding error: '${err}'. \n NOTE: If this was thrown during a command with write operations, the data could be UNRECOVERABLY LOST.`)); } } else if (message.constantResponse === ProtobufMessage_1.response.ConstantResponse.OK) { resolve("OK"); } else { resolve(null); } this.dropCommandSpan(message.rootSpanPtr); } processPush(response) { if (response.closingError != null || !response.respPointer) { const errMsg = response.closingError ? response.closingError : "Client Error - push notification without resp_pointer"; this.close(errMsg); return; } const [callback, context] = this.getPubsubCallbackAndContext(this.config); if (callback) { const pubsubMessage = this.notificationToPubSubMessageSafe(response); if (pubsubMessage) { callback(pubsubMessage, context); } } else { this.pendingPushNotification.push(response); this.completePubSubFuturesSafe(); } } constructor(socket, options) { // if logger has been initialized by the external-user on info level this log will be shown _1.Logger.log("info", "Client lifetime", `construct client`); this.config = options; this.requestTimeout = options?.requestTimeout ?? _1.DEFAULT_REQUEST_TIMEOUT_IN_MILLISECONDS; this.socket = socket; this.socket .on("data", (data) => this.handleReadData(data)) .on("error", (err) => { console.error(`Server closed: ${err}`); this.close(); }); this.defaultDecoder = options?.defaultDecoder ?? Decoder.String; this.inflightRequestsLimit = options?.inflightRequestsLimit ?? _1.DEFAULT_INFLIGHT_REQUESTS_LIMIT; } getCallbackIndex() { return (this.availableCallbackSlots.pop() ?? this.promiseCallbackFunctions.length); } writeBufferedRequestsToSocket() { this.writeInProgress = true; const requests = this.requestWriter.finish(); this.requestWriter.reset(); this.socket.write(requests, undefined, () => { if (this.requestWriter.len > 0) { this.writeBufferedRequestsToSocket(); } else { this.writeInProgress = false; } }); } ensureClientIsOpen() { if (this.isClosed) { throw new _1.ClosingError("Unable to execute requests; the client is closed. Please create a new client."); } } /** * @internal * * Creates a promise that resolves or rejects based on the result of a command request. * * @template T - The type of the result expected from the promise. * @param command - A single command or an array of commands to be executed, array of commands represents a batch and not a single command. * @param options - Optional settings for the write operation, including route, batch options and decoder. * @param isAtomic - Indicates whether the operation should be executed atomically (AKA as a Transaction, in the case of a batch). Defaults to `false`. * @param raiseOnError - Determines whether to raise an error if any of the commands fails, in the case of a Batch and not a single command. Defaults to `false`. * @returns A promise that resolves with the result of the command(s) or rejects with an error. */ createWritePromise(command, options = {}, isAtomic = false, raiseOnError = false) { this.ensureClientIsOpen(); const route = this.toProtobufRoute(options?.route); const callbackIndex = this.getCallbackIndex(); const basePromise = new Promise((resolve, reject) => { // Create a span only if the OpenTelemetry is enabled and measure statistics only according to the requests percentage configuration let spanPtr = null; if (_1.OpenTelemetry.shouldSample()) { const commandName = command instanceof ProtobufMessage_1.command_request.Command ? ProtobufMessage_1.command_request.RequestType[command.requestType] : "Batch"; const parentCtx = _1.OpenTelemetry.getParentSpanContext(); const pair = parentCtx ? (0, _1.createOtelSpanWithTraceContext)(commandName, parentCtx.traceId, parentCtx.spanId, parentCtx.traceFlags, parentCtx.traceState) : (0, _1.createLeakedOtelSpan)(commandName); spanPtr = new long_1.default(pair[0], pair[1]); } this.promiseCallbackFunctions[callbackIndex] = [ resolve, reject, options?.decoder, ]; this.writeOrBufferCommandRequest(callbackIndex, command, route, spanPtr, isAtomic, raiseOnError, options); }); if (!Array.isArray(command)) { return basePromise; } return basePromise.then((result) => { if (Array.isArray(result)) { const loopLen = result.length; for (let i = 0; i < loopLen; i++) { const item = result[i]; // Check if the item is an instance of napi Error // Can be checked by checking if the constructor name is "Error" // and if there is a name property with the value "RequestError" (that we added in the Rust code) if (item?.constructor?.name === "Error" && /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ item.name === "RequestError") { Object.setPrototypeOf(item, _1.RequestError.prototype); } } } return result; }); } createUpdateConnectionPasswordPromise(command) { this.ensureClientIsOpen(); return new Promise((resolve, reject) => { const callbackIdx = this.getCallbackIndex(); this.promiseCallbackFunctions[callbackIdx] = [resolve, reject]; this.writeOrBufferRequest(new ProtobufMessage_1.command_request.CommandRequest({ callbackIdx, updateConnectionPassword: command, }), (message, writer) => { ProtobufMessage_1.command_request.CommandRequest.encodeDelimited(message, writer); }); }); } createRefreshIamTokenPromise(command) { this.ensureClientIsOpen(); return new Promise((resolve, reject) => { const callbackIdx = this.getCallbackIndex(); this.promiseCallbackFunctions[callbackIdx] = [resolve, reject]; this.writeOrBufferRequest(new ProtobufMessage_1.command_request.CommandRequest({ callbackIdx, refreshIamToken: command, }), (message, writer) => { ProtobufMessage_1.command_request.CommandRequest.encodeDelimited(message, writer); }); }); } createGetCacheMetricsPromise(command) { this.ensureClientIsOpen(); return new Promise((resolve, reject) => { const callbackIdx = this.getCallbackIndex(); this.promiseCallbackFunctions[callbackIdx] = [resolve, reject]; this.writeOrBufferRequest(new ProtobufMessage_1.command_request.CommandRequest({ callbackIdx, getCacheMetrics: command, }), (message, writer) => { ProtobufMessage_1.command_request.CommandRequest.encodeDelimited(message, writer); }); }); } /** * @internal * Get cache metrics. * * @param metricsType - Type of metric to retrieve (e.g., hit rate, miss rate). * @returns The requested cache metric. * @throws RequestError if client-side caching is not enabled or metrics tracking is disabled. */ async getCacheMetrics(metricsType) { const getCacheMetrics = ProtobufMessage_1.command_request.GetCacheMetrics.create({ metricsTypes: metricsType, }); return await this.createGetCacheMetricsPromise(getCacheMetrics); } createScriptInvocationPromise(command, options = {}) { this.ensureClientIsOpen(); return new Promise((resolve, reject) => { const callbackIdx = this.getCallbackIndex(); this.promiseCallbackFunctions[callbackIdx] = [ resolve, reject, options?.decoder, ]; this.writeOrBufferRequest(new ProtobufMessage_1.command_request.CommandRequest({ callbackIdx, scriptInvocation: command, }), (message, writer) => { ProtobufMessage_1.command_request.CommandRequest.encodeDelimited(message, writer); }); }); } /** * @internal * * @param callbackIdx - The requests callback index. * @param command - A single command or an array of commands to be executed, array of commands represents a batch and not a single command. * @param route - Optional routing information for the command. * @param isAtomic - Indicates whether the operation should be executed atomically (AKA as a Transaction, in the case of a batch). Defaults to `false`. * @param raiseOnError - Determines whether to raise an error if any of the commands fails, in the case of a Batch and not a single command. Defaults to `false`. * @param options - Optional settings for batch requests. */ writeOrBufferCommandRequest(callbackIdx, command, route, commandSpanPtr, isAtomic = false, raiseOnError = false, options = {}) { if (isAtomic && "retryStrategy" in options) { throw new _1.RequestError("Retry strategy is not supported for atomic batches."); } const isBatch = Array.isArray(command); let batch; if (isBatch) { let retryServerError; let retryConnectionError; if ("retryStrategy" in options) { retryServerError = options.retryStrategy?.retryServerError; retryConnectionError = options.retryStrategy?.retryConnectionError; } batch = ProtobufMessage_1.command_request.Batch.create({ isAtomic, commands: command, raiseOnError, timeout: options.timeout, retryServerError, retryConnectionError, }); } const message = ProtobufMessage_1.command_request.CommandRequest.create({ callbackIdx, singleCommand: isBatch ? undefined : command, batch, route, rootSpanPtr: commandSpanPtr, }); this.writeOrBufferRequest(message, (msg, writer) => { ProtobufMessage_1.command_request.CommandRequest.encodeDelimited(msg, writer); }); } writeOrBufferRequest(message, encodeDelimited) { encodeDelimited(message, this.requestWriter); if (this.writeInProgress) { return; } this.writeBufferedRequestsToSocket(); } // Define a common function to process the result of a batch with set commands /** * @internal */ processResultWithSetCommands(result, setCommandsIndexes) { if (result === null) { return null; } for (let i = 0, len = setCommandsIndexes.length; i < len; i++) { if (Array.isArray(result[setCommandsIndexes[i]])) { result[setCommandsIndexes[i]] = new Set(result[setCommandsIndexes[i]]); } } return result; } cancelPubSubFuturesWithExceptionSafe(exception) { while (this.pubsubFutures.length > 0) { const nextFuture = this.pubsubFutures.shift(); if (nextFuture) { const [, reject] = nextFuture; reject(exception); } } } isPubsubConfigured(config) { return !!config.pubsubSubscriptions; } getPubsubCallbackAndContext(config) { if (config.pubsubSubscriptions) { return [ config.pubsubSubscriptions.callback, config.pubsubSubscriptions.context, ]; } return [null, null]; } async getPubSubMessage() { if (this.isClosed) { throw new _1.ClosingError("Unable to execute requests; the client is closed. Please create a new client."); } // only throw error if BOTH config exists AND callback exists if (this.isPubsubConfigured(this.config) && this.getPubsubCallbackAndContext(this.config)[0]) { throw new _1.ConfigurationError("The operation will never complete since messages will be passed to the configured callback."); } return new Promise((resolve, reject) => { this.pubsubFutures.push([resolve, reject]); this.completePubSubFuturesSafe(); }); } tryGetPubSubMessage(decoder) { if (this.isClosed) { throw new _1.ClosingError("Unable to execute requests; the client is closed. Please create a new client."); } // only throw error if BOTH config exists AND callback exists if (this.isPubsubConfigured(this.config) && this.getPubsubCallbackAndContext(this.config)[0]) { throw new _1.ConfigurationError("The operation will never complete since messages will be passed to the configured callback."); } let msg = null; this.completePubSubFuturesSafe(); while (this.pendingPushNotification.length > 0 && !msg) { const pushNotification = this.pendingPushNotification.shift(); msg = this.notificationToPubSubMessageSafe(pushNotification, decoder); } return msg; } notificationToPubSubMessageSafe(pushNotification, decoder) { let msg = null; const responsePointer = pushNotification.respPointer; let nextPushNotificationValue = {}; const isStringDecoder = (decoder ?? this.defaultDecoder) === Decoder.String; if (responsePointer) { const ptrNum = typeof responsePointer === "number" ? responsePointer : responsePointer.toNumber(); nextPushNotificationValue = (0, _1.valueFromPointer)(ptrNum, isStringDecoder); const messageKind = nextPushNotificationValue["kind"]; if (messageKind === "Disconnect") { _1.Logger.log("warn", "disconnect notification", "Transport disconnected, messages might be lost"); } else if (messageKind === "Message" || messageKind === "PMessage" || messageKind === "SMessage") { const values = nextPushNotificationValue["values"]; if (messageKind === "PMessage") { msg = { message: values[2], channel: values[1], pattern: values[0], }; } else { msg = { message: values[1], channel: values[0], pattern: null, }; } } else if (messageKind === "PSubscribe" || messageKind === "Subscribe" || messageKind === "SSubscribe" || messageKind === "Unsubscribe" || messageKind === "SUnsubscribe" || messageKind === "PUnsubscribe") { // pass } else { _1.Logger.log("error", "unknown notification", `Unknown notification: '${messageKind}'`); } } return msg; } completePubSubFuturesSafe() { while (this.pendingPushNotification.length > 0 && this.pubsubFutures.length > 0) { const nextPushNotification = this.pendingPushNotification.shift(); const pubsubMessage = this.notificationToPubSubMessageSafe(nextPushNotification); if (pubsubMessage) { const [resolve] = this.pubsubFutures.shift(); resolve(pubsubMessage); } } } /** Get the value associated with the given `key`, or `null` if no such `key` exists. * * @see {@link https://valkey.io/commands/get/|valkey.io} for details. * * @param key - The `key` to retrieve from the database. * @param options - (Optional) See {@link DecoderOption}. * @returns If `key` exists, returns the value of `key`. Otherwise, return `null`. * * @example * ```typescript * // Example usage of get method to retrieve the value of a key * const result = await client.get("key"); * console.log(result); * // Output: 'value' * * // Example usage of get method to retrieve the value of a key with Bytes decoder * const result = await client.get("key", { decoder: Decoder.Bytes }); * console.log(result); * // Output: <Buffer 76 61 6c 75 65> * ``` */ async get(key, options) { return this.createWritePromise((0, _1.createGet)(key), options); } /** * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}. * * @see {@link https://valkey.io/commands/getex/|valkey.op} for more details. * @remarks Since Valkey version 6.2.0. * * @param key - The key to retrieve from the database. * @param options - (Optional) Additional Parameters: * - (Optional) `expiry`: expiriation to the given key: * `"persist"` will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. * - (Optional) `decoder`: see {@link DecoderOption}. * @returns If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. * * @example * ```typescript * const result = await client.getex("key", {expiry: { type: TimeUnit.Seconds, count: 5 }}); * console.log(result); * // Output: 'value' * ``` */ async getex(key, options) { return this.createWritePromise((0, _1.createGetEx)(key, options?.expiry), options); } /** * Gets a string value associated with the given `key`and deletes the key. * * @see {@link https://valkey.io/commands/getdel/|valkey.io} for details. * * @param key - The key to retrieve from the database. * @param options - (Optional) See {@link DecoderOption}. * @returns If `key` exists, returns the `value` of `key`. Otherwise, return `null`. * * @example * ```typescript * const result = client.getdel("key"); * console.log(result); * // Output: 'value' * * const value = client.getdel("key"); // value is null * ``` */ async getdel(key, options) { return this.createWritePromise((0, _1.createGetDel)(key), options); } /** * Returns the substring of the string value stored at `key`, determined by the byte offsets * `start` and `end` (both are inclusive). Negative offsets can be used in order to provide * an offset starting from the end of the string. So `-1` means the last character, `-2` the * penultimate and so forth. If `key` does not exist, an empty string is returned. If `start` * or `end` are out of range, returns the substring within the valid range of the string. * * @see {@link https://valkey.io/commands/getrange/|valkey.io} for details. * * @param key - The key of the string. * @param start - The starting byte offset. * @param end - The ending byte offset. * @param options - (Optional) See {@link DecoderOption}. * @returns A substring extracted from the value stored at `key`. * * @example * ```typescript * await client.set("mykey", "This is a string") * let result = await client.getrange("mykey", 0, 3) * console.log(result); * // Output: "This" * * result = await client.getrange("mykey", -3, -1) * console.log(result); * // Output: "ing" * // extracted last 3 characters of a string * * result = await client.getrange("mykey", 0, 100) * console.log(result); * // Output: "This is a string" * * result = await client.getrange("mykey", 5, 6) * console.log(result); * // Output: "" * ``` */ async getrange(key, start, end, options) { return this.createWritePromise((0, _1.createGetRange)(key, start, end), options); } /** Set the given key with the given value. Return value is dependent on the passed options. * * @see {@link https://valkey.io/commands/set/|valkey.io} for details. * * @param key - The key to store. * @param value - The value to store with the given key. * @param options - (Optional) See {@link SetOptions} and {@link DecoderOption}. * @returns - If the value is successfully set, return OK. * If `conditional` in `options` is not set, the value will be set regardless of prior value existence. * If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist` or `onlyIfEqual` conditions, return `null`. * If `returnOldValue` is set, return the old value as a string. * * @example * ```typescript * // Example usage of set method to set a key-value pair * const result = await client.set("my_key", "my_value"); * console.log(result); // Output: 'OK' * * // Example usage of set method with conditional options and expiration * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: TimeUnit.Seconds, count: 5 }}); * console.log(result2); * // Output: 'OK' * // Set "new_value" to `key" only if `key` already exists, and set the key expiration to 5 seconds. * * // Example usage of set method with conditional options and returning old value * const result3 = await client.set("key", "value", {conditionalSet: "onlyIfDoesNotExist", returnOldValue: true}); * console.log(result3); * // Output: 'new_value' * // Returns the old value of `key`. * * // Example usage of get method to retrieve the value of a key * const result4 = await client.get("key"); * console.log(result4); * // Output: 'new_value' * // Value wasn't modified back to being "value" because of "NX" flag. * * // Example usage of set method with conditional option IFEQ * await client.set("key", "value we will compare to"); * const result5 = await client.set("key", "new_value", {conditionalSet: "onlyIfEqual", comparisonValue: "value we will compare to"}); * console.log(result5); * // Output: 'OK' * // Set "new_value" to "key" only if comparisonValue is equal to the current value of "key". * * const result6 = await client.set("key", "another_new_value", {conditionalSet: "onlyIfEqual", comparisonValue: "value we will compare to"}); * console.log(result6); * // Output: null * // Value wasn't set because the comparisonValue is not equal to the current value of `key`. Value of `key` remains "new_value". * ``` */ async set(key, value, options) { return this.createWritePromise((0, _1.createSet)(key, value, options), options); } /** * Removes the specified keys. A key is ignored if it does not exist. * * @see {@link https://valkey.io/commands/del/|valkey.io} for details. * * @remarks In cluster mode, if keys in `keys` map to different hash slots, * the command will be split across these slots and executed separately for each. * This means the command is atomic only at the slot level. If one or more slot-specific * requests fail, the entire call will return the first encountered error, even * though some requests may have succeeded while others did not. * If this behavior impacts your application logic, consider splitting the * request into sub-requests per slot to ensure atomicity. * * @param keys - The keys we wanted to remove. * @returns The number of keys that were removed. * * @example * ```typescript * // Example usage of del method to delete an existing key * await client.set("my_key", "my_value"); * const result = await client.del(["my_key"]); * console.log(result); // Output: 1 * ``` * * @example * ```typescript * // Example usage of del method for a non-existing key * const result = await client.del(["non_existing_key"]); * console.log(result); // Output: 0 * ``` */ async del(keys) { return this.createWritePromise((0, _1.createDel)(keys)); } /** * Serialize the value stored at `key` in a Valkey-specific format and return it to the user. * * @see {@link https://valkey.io/commands/dump/|valkey.io} for details. * * @param key - The `key` to serialize. * @returns The serialized value of the data stored at `key`. If `key` does not exist, `null` will be returned. * * @example * ```typescript * let result = await client.dump("myKey"); * console.log(result); * // Result contains the serialized value of `myKey` * ``` * * @example * ```typescript * result = await client.dump("nonExistingKey"); * console.log(result); * // Output: null * ``` */ async dump(key) { return this.createWritePromise((0, _1.createDump)(key), { decoder: Decoder.Bytes, }); } /** * Create a `key` associated with a `value` that is obtained by deserializing the provided * serialized `value` (obtained via {@link dump}). * * @see {@link https://valkey.io/commands/restore/|valkey.io} for details. * @remarks `options.idletime` and `options.frequency` modifiers cannot be set at the same time. * * @param key - The `key` to create. * @param ttl - The expiry time (in milliseconds). If `0`, the `key` will persist. * @param value - The serialized value to deserialize and assign to `key`. * @param options - (Optional) Restore options {@link RestoreOptions}. * @returns Return "OK" if the `key` was successfully restored with a `value`. * * @example * ```typescript * const result = await client.restore("myKey", 0, value); * console.log(result); // Output: "OK" * ``` * * @example * ```typescript * const result = await client.restore("myKey", 1000, value, {replace: true, absttl: true}); * console.log(result); // Output: "OK" * ``` * * @example * ```typescript * const result = await client.restore("myKey", 0, value, {replace: true, idletime: 10}); * console.log(result); // Output: "OK" * ``` * * @example * ```typescript * const result = await client.restore("myKey", 0, value, {replace: true, frequency: 10}); * console.log(result); // Output: "OK" * ``` */ async restore(key, ttl, value, options) { return this.createWritePromise((0, _1.createRestore)(key, ttl, value, options), { decoder: Decoder.String }); } /** Retrieve the values of multiple keys. * * @see {@link https://valkey.io/commands/mget/|valkey.io} for details. * * @remarks In cluster mode, if keys in `keys` map to different hash slots, * the command will be split across these slots and executed separately for each. * This means the command is atomic only at the slot level. If one or more slot-specific * requests fail, the entire call will return the first encountered error, even * though some requests may have succeeded while others did not. * If this behavior impacts your application logic, consider splitting the * request into sub-requests per slot to ensure atomicity. * * @param keys - A list of keys to retrieve values for. * @param options - (Optional) See {@link DecoderOption}. * @returns A list of values corresponding to the provided keys. If a key is not found, * its corresponding value in the list will be null. * * @example * ```typescript * // Example usage of mget method to retrieve values of multiple keys * await client.set("key1", "value1"); * await client.set("key2", "value2"); * const result = await client.mget(["key1", "key2"]); * console.log(result); * // Output: ['value1', 'value2'] * ``` */ async mget(keys, options) { return this.createWritePromise((0, _1.createMGet)(keys), options); } /** Set multiple keys to multiple values in a single operation. * * @see {@link https://valkey.io/commands/mset/|valkey.io} for details. * * @remarks In cluster mode, if keys in `keyValueMap` map to different hash slots, * the command will be split across these slots and executed separately for each. * This means the command is atomic only at the slot level. If one or more slot-specific * requests fail, the entire call will return the first encountered error, even * though some requests may have succeeded while others did not. * If this behavior impacts your application logic, consider splitting the * request into sub-requests per slot to ensure atomicity. * * @param keysAndValues - A list of key-value pairs to set. * * @returns A simple "OK" response. * * @example * ```typescript * // Example usage of mset method to set values for multiple keys * const result = await client.mset({"key1": "value1", "key2": "value2"}); * console.log(result); // Output: 'OK' * ``` * * @example * ```typescript * // Example usage of mset method to set values for multiple keys (GlideRecords allow binary data in the key) * const result = await client.mset([{key: "key1", value: "value1"}, {key: "key2", value: "value2"}]); * console.log(result); // Output: 'OK' * ``` */ async mset(keysAndValues) { return this.createWritePromise((0, _1.createMSet)(convertGlideRecord(keysAndValues))); } /** * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or * more keys already exist, the entire operation fails. * * @see {@link https://valkey.io/commands/msetnx/|valkey.io} for more details. * @remarks When in cluster mode, all keys in `keyValueMap` must map to the same hash slot. * * @param keysAndValues - A list of key-value pairs to set. * @returns `true` if all keys were set. `false` if no key was set. * * @example * ```typescript * const result1 = await client.msetnx({"key1": "value1", "key2": "value2"}); * console.log(result1); // Output: `true` * * const result2 = await client.msetnx({"key2": "value4", "key3": "value5"}); * console.log(result2); // Output: `false` * ``` */ async msetnx(keysAndValues) { return this.createWritePromise((0, _1.createMSetNX)(convertGlideRecord(keysAndValues))); } /** * Move `key` from the currently selected database to the database specified by `dbIndex`. * * @remarks Move is available for cluster mode since Valkey 9.0.0 and above. * * @see {@link https://valkey.io/commands/move/|valkey.io} for more details. * * @param key - The key to move. * @param dbIndex - The index of the database to move `key` to. * @returns `true` if `key` was moved, or `false` if the `key` already exists in the destination * database or does not exist in the source database. * * @example * ```typescript * const result = await client.move("key", 1); * console.log(result); // Output: true * ``` */ async move(key, dbIndex) { return this.createWritePromise((0, _1.createMove)(key, dbIndex)); } /** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. * * @see {@link https://valkey.io/commands/incr/|valkey.io} for details. * * @param key - The key to increment its value. * @returns the value of `key` after the increment. * * @example * ```typescript * // Example usage of incr method to increment the value of a key * await client.set("my_counter", "10"); * const result = await client.incr("my_counter"); * console.log(result); // Output: 11 * ``` */ async incr(key) { return this.createWritePromise((0, _1.createIncr)(key)); } /** Increments the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. * * @see {@link https://valkey.io/commands/incrby/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. * @returns the value of `key` after the increment. * * @example * ```typescript * // Example usage of incrBy method to increment the value of a key by a specified amount * await client.set("my_counter", "10"); * const result = await client.incrBy("my_counter", 5); * console.log(result); // Output: 15 * ``` */ async incrBy(key, amount) { return this.createWritePromise((0, _1.createIncrBy)(key, amount)); } /** Increment the string representing a floating point number stored at `key` by `amount`. * By using a negative increment value, the result is that the value stored at `key` is decremented. * If `key` does not exist, it is set to 0 before performing the operation. * * @see {@link https://valkey.io/commands/incrbyfloat/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. * @returns the value of `k