@valkey/valkey-glide
Version:
General Language Independent Driver for the Enterprise (GLIDE) for Valkey
1,183 lines • 349 kB
JavaScript
"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