feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+3
View File
@@ -0,0 +1,3 @@
# @redis/client
The source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
+27
View File
@@ -0,0 +1,27 @@
export { RedisArgument, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping, DEFAULT_RESP, } from './lib/RESP/types';
export { RESP_TYPES } from './lib/RESP/decoder';
export { VerbatimString } from './lib/RESP/verbatim-string';
export { defineScript } from './lib/lua-script';
export { digest } from './lib/utils/digest';
export * from './lib/errors';
import RedisClient, { AnyRedisClientOptions, RedisClientOptions, RedisClientType } from './lib/client';
export { RedisClient, AnyRedisClientOptions, RedisClientOptions, RedisClientType };
export declare const createClient: typeof RedisClient.create;
export { CommandParser } from './lib/client/parser';
import { RedisClientPool, RedisPoolOptions, RedisClientPoolType } from './lib/client/pool';
export { RedisClientPool, RedisClientPoolType, RedisPoolOptions };
export declare const createClientPool: typeof RedisClientPool.create;
import RedisCluster, { RedisClusterOptions, RedisClusterType } from './lib/cluster';
export { RedisCluster, RedisClusterType, RedisClusterOptions };
export declare const createCluster: typeof RedisCluster.create;
import RedisSentinel from './lib/sentinel';
export { RedisSentinel };
export { RedisSentinelClient } from './lib/sentinel';
export { RedisSentinelOptions, RedisSentinelType, RedisSentinelClientType } from './lib/sentinel/types';
export declare const createSentinel: typeof RedisSentinel.create;
export { GEO_REPLY_WITH, GeoReplyWith } from './lib/commands/GEOSEARCH_WITH';
export { SetOptions, CLIENT_KILL_FILTERS, CLIENT_UNBLOCK_MODES, FAILOVER_MODES, CLUSTER_SLOT_STATES, COMMAND_LIST_FILTER_BY, REDIS_FLUSH_MODES, AR_PREDICATE_TYPES, AR_PREDICATE_COMBINATORS, AR_OPERATIONS } from './lib/commands';
export { BasicClientSideCache, BasicPooledClientSideCache } from './lib/client/cache';
export { OpenTelemetry } from './lib/opentelemetry';
export { CHANNELS, type ChannelEvents, type CommandTraceContext, type BatchCommandTraceContext, type BatchOperationContext, type ConnectTraceContext, type ConnectionReadyEvent, type ConnectionClosedEvent, type ConnectionRelaxedTimeoutEvent, type ConnectionHandoffEvent, type ClientErrorEvent, type MaintenanceNotificationEvent, type PubSubMessageEvent, type CacheRequestEvent, type CacheEvictionEvent, type CommandReplyEvent, type PoolConnectionWaitEvent, } from './lib/client/tracing';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,cAAc,cAAc,CAAC;AAE7B,OAAO,WAAW,EAAE,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,eAAe,EAAE,CAAC;AACnF,eAAO,MAAM,YAAY,2BAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,CAAC;AAClE,eAAO,MAAM,gBAAgB,+BAAyB,CAAC;AAEvD,OAAO,YAAY,EAAE,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAC/D,eAAO,MAAM,aAAa,4BAAsB,CAAC;AAEjD,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACxG,eAAO,MAAM,cAAc,6BAAuB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,cAAc,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnO,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,sBAAsB,CAAC"}
+65
View File
@@ -0,0 +1,65 @@
"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CHANNELS = exports.OpenTelemetry = exports.BasicPooledClientSideCache = exports.BasicClientSideCache = exports.AR_OPERATIONS = exports.AR_PREDICATE_COMBINATORS = exports.AR_PREDICATE_TYPES = exports.REDIS_FLUSH_MODES = exports.COMMAND_LIST_FILTER_BY = exports.CLUSTER_SLOT_STATES = exports.FAILOVER_MODES = exports.CLIENT_UNBLOCK_MODES = exports.CLIENT_KILL_FILTERS = exports.GEO_REPLY_WITH = exports.createSentinel = exports.RedisSentinelClient = exports.RedisSentinel = exports.createCluster = exports.RedisCluster = exports.createClientPool = exports.RedisClientPool = exports.createClient = exports.RedisClient = exports.digest = exports.defineScript = exports.VerbatimString = exports.RESP_TYPES = exports.DEFAULT_RESP = void 0;
var types_1 = require("./lib/RESP/types");
Object.defineProperty(exports, "DEFAULT_RESP", { enumerable: true, get: function () { return types_1.DEFAULT_RESP; } });
var decoder_1 = require("./lib/RESP/decoder");
Object.defineProperty(exports, "RESP_TYPES", { enumerable: true, get: function () { return decoder_1.RESP_TYPES; } });
var verbatim_string_1 = require("./lib/RESP/verbatim-string");
Object.defineProperty(exports, "VerbatimString", { enumerable: true, get: function () { return verbatim_string_1.VerbatimString; } });
var lua_script_1 = require("./lib/lua-script");
Object.defineProperty(exports, "defineScript", { enumerable: true, get: function () { return lua_script_1.defineScript; } });
var digest_1 = require("./lib/utils/digest");
Object.defineProperty(exports, "digest", { enumerable: true, get: function () { return digest_1.digest; } });
__exportStar(require("./lib/errors"), exports);
const client_1 = __importDefault(require("./lib/client"));
exports.RedisClient = client_1.default;
exports.createClient = client_1.default.create;
const pool_1 = require("./lib/client/pool");
Object.defineProperty(exports, "RedisClientPool", { enumerable: true, get: function () { return pool_1.RedisClientPool; } });
exports.createClientPool = pool_1.RedisClientPool.create;
const cluster_1 = __importDefault(require("./lib/cluster"));
exports.RedisCluster = cluster_1.default;
exports.createCluster = cluster_1.default.create;
const sentinel_1 = __importDefault(require("./lib/sentinel"));
exports.RedisSentinel = sentinel_1.default;
var sentinel_2 = require("./lib/sentinel");
Object.defineProperty(exports, "RedisSentinelClient", { enumerable: true, get: function () { return sentinel_2.RedisSentinelClient; } });
exports.createSentinel = sentinel_1.default.create;
var GEOSEARCH_WITH_1 = require("./lib/commands/GEOSEARCH_WITH");
Object.defineProperty(exports, "GEO_REPLY_WITH", { enumerable: true, get: function () { return GEOSEARCH_WITH_1.GEO_REPLY_WITH; } });
var commands_1 = require("./lib/commands");
Object.defineProperty(exports, "CLIENT_KILL_FILTERS", { enumerable: true, get: function () { return commands_1.CLIENT_KILL_FILTERS; } });
Object.defineProperty(exports, "CLIENT_UNBLOCK_MODES", { enumerable: true, get: function () { return commands_1.CLIENT_UNBLOCK_MODES; } });
Object.defineProperty(exports, "FAILOVER_MODES", { enumerable: true, get: function () { return commands_1.FAILOVER_MODES; } });
Object.defineProperty(exports, "CLUSTER_SLOT_STATES", { enumerable: true, get: function () { return commands_1.CLUSTER_SLOT_STATES; } });
Object.defineProperty(exports, "COMMAND_LIST_FILTER_BY", { enumerable: true, get: function () { return commands_1.COMMAND_LIST_FILTER_BY; } });
Object.defineProperty(exports, "REDIS_FLUSH_MODES", { enumerable: true, get: function () { return commands_1.REDIS_FLUSH_MODES; } });
Object.defineProperty(exports, "AR_PREDICATE_TYPES", { enumerable: true, get: function () { return commands_1.AR_PREDICATE_TYPES; } });
Object.defineProperty(exports, "AR_PREDICATE_COMBINATORS", { enumerable: true, get: function () { return commands_1.AR_PREDICATE_COMBINATORS; } });
Object.defineProperty(exports, "AR_OPERATIONS", { enumerable: true, get: function () { return commands_1.AR_OPERATIONS; } });
var cache_1 = require("./lib/client/cache");
Object.defineProperty(exports, "BasicClientSideCache", { enumerable: true, get: function () { return cache_1.BasicClientSideCache; } });
Object.defineProperty(exports, "BasicPooledClientSideCache", { enumerable: true, get: function () { return cache_1.BasicPooledClientSideCache; } });
var opentelemetry_1 = require("./lib/opentelemetry");
Object.defineProperty(exports, "OpenTelemetry", { enumerable: true, get: function () { return opentelemetry_1.OpenTelemetry; } });
var tracing_1 = require("./lib/client/tracing");
Object.defineProperty(exports, "CHANNELS", { enumerable: true, get: function () { return tracing_1.CHANNELS; } });
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,0CAS0B;AADxB,qGAAA,YAAY,OAAA;AAEd,8CAAgD;AAAvC,qGAAA,UAAU,OAAA;AACnB,8DAA4D;AAAnD,iHAAA,cAAc,OAAA;AACvB,+CAAgD;AAAvC,0GAAA,YAAY,OAAA;AACrB,6CAA4C;AAAnC,gGAAA,MAAM,OAAA;AACf,+CAA6B;AAE7B,0DAAuG;AAC9F,sBADF,gBAAW,CACE;AACP,QAAA,YAAY,GAAG,gBAAW,CAAC,MAAM,CAAC;AAG/C,4CAA2F;AAClF,gGADA,sBAAe,OACA;AACX,QAAA,gBAAgB,GAAG,sBAAe,CAAC,MAAM,CAAC;AAEvD,4DAAoF;AAC3E,uBADF,iBAAY,CACE;AACR,QAAA,aAAa,GAAG,iBAAY,CAAC,MAAM,CAAC;AAEjD,8DAA2C;AAClC,wBADF,kBAAa,CACE;AACtB,2CAAqD;AAA5C,+GAAA,mBAAmB,OAAA;AAEf,QAAA,cAAc,GAAG,kBAAa,CAAC,MAAM,CAAC;AAEnD,gEAA6E;AAApE,gHAAA,cAAc,OAAA;AAGvB,2CAAmO;AAA9M,+GAAA,mBAAmB,OAAA;AAAE,gHAAA,oBAAoB,OAAA;AAAE,0GAAA,cAAc,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAAE,kHAAA,sBAAsB,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAAE,8GAAA,kBAAkB,OAAA;AAAE,oHAAA,wBAAwB,OAAA;AAAE,yGAAA,aAAa,OAAA;AAE3M,4CAAsF;AAA7E,6GAAA,oBAAoB,OAAA;AAAE,mHAAA,0BAA0B,OAAA;AACzD,qDAAoD;AAA3C,8GAAA,aAAa,OAAA;AAEtB,gDAkB8B;AAjB5B,mGAAA,QAAQ,OAAA","sourcesContent":["export {\n /* CommandPolicies, */\n RedisArgument,\n RedisFunctions,\n RedisModules,\n RedisScripts,\n RespVersions,\n TypeMapping,\n DEFAULT_RESP,\n} from './lib/RESP/types';\nexport { RESP_TYPES } from './lib/RESP/decoder';\nexport { VerbatimString } from './lib/RESP/verbatim-string';\nexport { defineScript } from './lib/lua-script';\nexport { digest } from './lib/utils/digest';\nexport * from './lib/errors';\n\nimport RedisClient, { AnyRedisClientOptions, RedisClientOptions, RedisClientType } from './lib/client';\nexport { RedisClient, AnyRedisClientOptions, RedisClientOptions, RedisClientType };\nexport const createClient = RedisClient.create;\nexport { CommandParser } from './lib/client/parser';\n\nimport { RedisClientPool, RedisPoolOptions, RedisClientPoolType } from './lib/client/pool';\nexport { RedisClientPool, RedisClientPoolType, RedisPoolOptions };\nexport const createClientPool = RedisClientPool.create;\n\nimport RedisCluster, { RedisClusterOptions, RedisClusterType } from './lib/cluster';\nexport { RedisCluster, RedisClusterType, RedisClusterOptions };\nexport const createCluster = RedisCluster.create;\n\nimport RedisSentinel from './lib/sentinel';\nexport { RedisSentinel };\nexport { RedisSentinelClient } from './lib/sentinel';\nexport { RedisSentinelOptions, RedisSentinelType, RedisSentinelClientType } from './lib/sentinel/types';\nexport const createSentinel = RedisSentinel.create;\n\nexport { GEO_REPLY_WITH, GeoReplyWith } from './lib/commands/GEOSEARCH_WITH';\n\n\nexport { SetOptions, CLIENT_KILL_FILTERS, CLIENT_UNBLOCK_MODES, FAILOVER_MODES, CLUSTER_SLOT_STATES, COMMAND_LIST_FILTER_BY, REDIS_FLUSH_MODES, AR_PREDICATE_TYPES, AR_PREDICATE_COMBINATORS, AR_OPERATIONS } from './lib/commands'\n\nexport { BasicClientSideCache, BasicPooledClientSideCache } from './lib/client/cache';\nexport { OpenTelemetry } from './lib/opentelemetry';\n\nexport {\n CHANNELS,\n type ChannelEvents,\n type CommandTraceContext,\n type BatchCommandTraceContext,\n type BatchOperationContext,\n type ConnectTraceContext,\n type ConnectionReadyEvent,\n type ConnectionClosedEvent,\n type ConnectionRelaxedTimeoutEvent,\n type ConnectionHandoffEvent,\n type ClientErrorEvent,\n type MaintenanceNotificationEvent,\n type PubSubMessageEvent,\n type CacheRequestEvent,\n type CacheEvictionEvent,\n type CommandReplyEvent,\n type PoolConnectionWaitEvent,\n} from './lib/client/tracing';\n"]}
@@ -0,0 +1,40 @@
/// <reference types="node" />
import { ErrorReply } from '../errors';
import { TypeMapping } from './types';
export declare const RESP_TYPES: {
readonly NULL: 95;
readonly BOOLEAN: 35;
readonly NUMBER: 58;
readonly BIG_NUMBER: 40;
readonly DOUBLE: 44;
readonly SIMPLE_STRING: 43;
readonly BLOB_STRING: 36;
readonly VERBATIM_STRING: 61;
readonly SIMPLE_ERROR: 45;
readonly BLOB_ERROR: 33;
readonly ARRAY: 42;
readonly SET: 126;
readonly MAP: 37;
readonly PUSH: 62;
};
export declare const PUSH_TYPE_MAPPING: {
36: BufferConstructor;
};
interface DecoderOptions {
onReply(reply: any): unknown;
onErrorReply(err: ErrorReply): unknown;
onPush(push: Array<any>): unknown;
getTypeMapping(): TypeMapping;
}
export declare class Decoder {
#private;
onReply: (reply: any) => unknown;
onErrorReply: (err: ErrorReply) => unknown;
onPush: (push: any[]) => unknown;
getTypeMapping: () => TypeMapping;
constructor(config: DecoderOptions);
reset(): void;
write(chunk: any): void;
}
export {};
//# sourceMappingURL=decoder.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"decoder.d.ts","sourceRoot":"","sources":["../../../lib/RESP/decoder.ts"],"names":[],"mappings":";AAGA,OAAO,EAA0B,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAeb,CAAC;AAeX,eAAO,MAAM,iBAAiB;;CAE7B,CAAC;AAIF,UAAU,cAAc;IACtB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC;IAC7B,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;IACvC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IAClC,cAAc,IAAI,WAAW,CAAC;CAC/B;AAED,qBAAa,OAAO;;IAClB,OAAO,0BAAC;IACR,YAAY,+BAAC;IACb,MAAM,2BAAC;IACP,cAAc,oBAAC;gBAIH,MAAM,EAAE,cAAc;IAOlC,KAAK;IAKL,KAAK,CAAC,KAAK,KAAA;CAolCZ"}
@@ -0,0 +1,730 @@
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Decoder = exports.PUSH_TYPE_MAPPING = exports.RESP_TYPES = void 0;
/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-explicit-any */
// @ts-nocheck -- decoder uses untyped continuation callbacks; full typing tracked separately
const verbatim_string_1 = require("./verbatim-string");
const errors_1 = require("../errors");
// https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md
exports.RESP_TYPES = {
NULL: 95, // _
BOOLEAN: 35, // #
NUMBER: 58, // :
BIG_NUMBER: 40, // (
DOUBLE: 44, // ,
SIMPLE_STRING: 43, // +
BLOB_STRING: 36, // $
VERBATIM_STRING: 61, // =
SIMPLE_ERROR: 45, // -
BLOB_ERROR: 33, // !
ARRAY: 42, // *
SET: 126, // ~
MAP: 37, // %
PUSH: 62 // >
};
const ASCII = {
'\r': 13,
't': 116,
'+': 43,
'-': 45,
'0': 48,
'.': 46,
'i': 105,
'n': 110,
'E': 69,
'e': 101
};
exports.PUSH_TYPE_MAPPING = {
[exports.RESP_TYPES.BLOB_STRING]: Buffer
};
class Decoder {
onReply;
onErrorReply;
onPush;
getTypeMapping;
#cursor = 0;
#next;
constructor(config) {
this.onReply = config.onReply;
this.onErrorReply = config.onErrorReply;
this.onPush = config.onPush;
this.getTypeMapping = config.getTypeMapping;
}
reset() {
this.#cursor = 0;
this.#next = undefined;
}
write(chunk) {
if (this.#cursor >= chunk.length) {
this.#cursor -= chunk.length;
return;
}
if (this.#next) {
if (this.#next(chunk) || this.#cursor >= chunk.length) {
this.#cursor -= chunk.length;
return;
}
}
do {
const type = chunk[this.#cursor];
if (++this.#cursor === chunk.length) {
this.#next = this.#continueDecodeTypeValue.bind(this, type);
break;
}
if (this.#decodeTypeValue(type, chunk)) {
break;
}
} while (this.#cursor < chunk.length);
this.#cursor -= chunk.length;
}
#continueDecodeTypeValue(type, chunk) {
this.#next = undefined;
return this.#decodeTypeValue(type, chunk);
}
#decodeTypeValue(type, chunk) {
switch (type) {
case exports.RESP_TYPES.NULL:
this.onReply(this.#decodeNull());
return false;
case exports.RESP_TYPES.BOOLEAN:
return this.#handleDecodedValue(this.onReply, this.#decodeBoolean(chunk));
case exports.RESP_TYPES.NUMBER:
return this.#handleDecodedValue(this.onReply, this.#decodeNumber(this.getTypeMapping()[exports.RESP_TYPES.NUMBER], chunk));
case exports.RESP_TYPES.BIG_NUMBER:
return this.#handleDecodedValue(this.onReply, this.#decodeBigNumber(this.getTypeMapping()[exports.RESP_TYPES.BIG_NUMBER], chunk));
case exports.RESP_TYPES.DOUBLE:
return this.#handleDecodedValue(this.onReply, this.#decodeDouble(this.getTypeMapping()[exports.RESP_TYPES.DOUBLE], chunk));
case exports.RESP_TYPES.SIMPLE_STRING:
return this.#handleDecodedValue(this.onReply, this.#decodeSimpleString(this.getTypeMapping()[exports.RESP_TYPES.SIMPLE_STRING], chunk));
case exports.RESP_TYPES.BLOB_STRING:
return this.#handleDecodedValue(this.onReply, this.#decodeBlobString(this.getTypeMapping()[exports.RESP_TYPES.BLOB_STRING], chunk));
case exports.RESP_TYPES.VERBATIM_STRING:
return this.#handleDecodedValue(this.onReply, this.#decodeVerbatimString(this.getTypeMapping()[exports.RESP_TYPES.VERBATIM_STRING], chunk));
case exports.RESP_TYPES.SIMPLE_ERROR:
return this.#handleDecodedValue(this.onErrorReply, this.#decodeSimpleError(chunk));
case exports.RESP_TYPES.BLOB_ERROR:
return this.#handleDecodedValue(this.onErrorReply, this.#decodeBlobError(chunk));
case exports.RESP_TYPES.ARRAY:
return this.#handleDecodedValue(this.onReply, this.#decodeArray(this.getTypeMapping(), chunk));
case exports.RESP_TYPES.SET:
return this.#handleDecodedValue(this.onReply, this.#decodeSet(this.getTypeMapping(), chunk));
case exports.RESP_TYPES.MAP:
return this.#handleDecodedValue(this.onReply, this.#decodeMap(this.getTypeMapping(), chunk));
case exports.RESP_TYPES.PUSH:
return this.#handleDecodedValue(this.onPush, this.#decodeArray(exports.PUSH_TYPE_MAPPING, chunk));
default:
throw new Error(`Unknown RESP type ${type} "${String.fromCharCode(type)}"`);
}
}
#handleDecodedValue(cb, value) {
if (typeof value === 'function') {
this.#next = this.#continueDecodeValue.bind(this, cb, value);
return true;
}
cb(value);
return false;
}
#continueDecodeValue(cb, next, chunk) {
this.#next = undefined;
return this.#handleDecodedValue(cb, next(chunk));
}
#decodeNull() {
this.#cursor += 2; // skip \r\n
return null;
}
#decodeBoolean(chunk) {
const boolean = chunk[this.#cursor] === ASCII.t;
this.#cursor += 3; // skip {t | f}\r\n
return boolean;
}
#decodeNumber(type, chunk) {
if (type === String) {
return this.#decodeSimpleString(String, chunk);
}
switch (chunk[this.#cursor]) {
case ASCII['+']:
return this.#maybeDecodeNumberValue(false, chunk);
case ASCII['-']:
return this.#maybeDecodeNumberValue(true, chunk);
default:
return this.#decodeNumberValue(false, this.#decodeUnsingedNumber.bind(this, 0), chunk);
}
}
#maybeDecodeNumberValue(isNegative, chunk) {
const cb = this.#decodeUnsingedNumber.bind(this, 0);
return ++this.#cursor === chunk.length ?
this.#decodeNumberValue.bind(this, isNegative, cb) :
this.#decodeNumberValue(isNegative, cb, chunk);
}
#decodeNumberValue(isNegative, numberCb, chunk) {
const number = numberCb(chunk);
return typeof number === 'function' ?
this.#decodeNumberValue.bind(this, isNegative, number) :
isNegative ? -number : number;
}
#decodeUnsingedNumber(number, chunk) {
let cursor = this.#cursor;
do {
const byte = chunk[cursor];
if (byte === ASCII['\r']) {
this.#cursor = cursor + 2; // skip \r\n
return number;
}
number = number * 10 + byte - ASCII['0'];
} while (++cursor < chunk.length);
this.#cursor = cursor;
return this.#decodeUnsingedNumber.bind(this, number);
}
#decodeBigNumber(type, chunk) {
if (type === String) {
return this.#decodeSimpleString(String, chunk);
}
switch (chunk[this.#cursor]) {
case ASCII['+']:
return this.#maybeDecodeBigNumberValue(false, chunk);
case ASCII['-']:
return this.#maybeDecodeBigNumberValue(true, chunk);
default:
return this.#decodeBigNumberValue(false, this.#decodeUnsingedBigNumber.bind(this, 0n), chunk);
}
}
#maybeDecodeBigNumberValue(isNegative, chunk) {
const cb = this.#decodeUnsingedBigNumber.bind(this, 0n);
return ++this.#cursor === chunk.length ?
this.#decodeBigNumberValue.bind(this, isNegative, cb) :
this.#decodeBigNumberValue(isNegative, cb, chunk);
}
#decodeBigNumberValue(isNegative, bigNumberCb, chunk) {
const bigNumber = bigNumberCb(chunk);
return typeof bigNumber === 'function' ?
this.#decodeBigNumberValue.bind(this, isNegative, bigNumber) :
isNegative ? -bigNumber : bigNumber;
}
#decodeUnsingedBigNumber(bigNumber, chunk) {
let cursor = this.#cursor;
do {
const byte = chunk[cursor];
if (byte === ASCII['\r']) {
this.#cursor = cursor + 2; // skip \r\n
return bigNumber;
}
bigNumber = bigNumber * 10n + BigInt(byte - ASCII['0']);
} while (++cursor < chunk.length);
this.#cursor = cursor;
return this.#decodeUnsingedBigNumber.bind(this, bigNumber);
}
#decodeDouble(type, chunk) {
if (type === String) {
return this.#decodeSimpleString(String, chunk);
}
switch (chunk[this.#cursor]) {
case ASCII.n:
this.#cursor += 5; // skip nan\r\n
return NaN;
case ASCII['+']:
return this.#maybeDecodeDoubleInteger(false, chunk);
case ASCII['-']:
return this.#maybeDecodeDoubleInteger(true, chunk);
default:
return this.#decodeDoubleInteger(false, 0, chunk);
}
}
#maybeDecodeDoubleInteger(isNegative, chunk) {
return ++this.#cursor === chunk.length ?
this.#decodeDoubleInteger.bind(this, isNegative, 0) :
this.#decodeDoubleInteger(isNegative, 0, chunk);
}
#decodeDoubleInteger(isNegative, integer, chunk) {
if (chunk[this.#cursor] === ASCII.i) {
this.#cursor += 5; // skip inf\r\n
return isNegative ? -Infinity : Infinity;
}
return this.#continueDecodeDoubleInteger(isNegative, integer, chunk);
}
#continueDecodeDoubleInteger(isNegative, integer, chunk) {
let cursor = this.#cursor;
do {
const byte = chunk[cursor];
switch (byte) {
case ASCII['.']:
this.#cursor = cursor + 1; // skip .
return this.#cursor < chunk.length ?
this.#decodeDoubleDecimal(isNegative, 0, integer, chunk) :
this.#decodeDoubleDecimal.bind(this, isNegative, 0, integer);
case ASCII.E:
case ASCII.e: {
this.#cursor = cursor + 1; // skip E/e
const i = isNegative ? -integer : integer;
return this.#cursor < chunk.length ?
this.#decodeDoubleExponent(i, chunk) :
this.#decodeDoubleExponent.bind(this, i);
}
case ASCII['\r']:
this.#cursor = cursor + 2; // skip \r\n
return isNegative ? -integer : integer;
default:
integer = integer * 10 + byte - ASCII['0'];
}
} while (++cursor < chunk.length);
this.#cursor = cursor;
return this.#continueDecodeDoubleInteger.bind(this, isNegative, integer);
}
// Precalculated multipliers for decimal points to improve performance
// "... about 15 to 17 decimal places ..."
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#:~:text=about%2015%20to%2017%20decimal%20places
static #DOUBLE_DECIMAL_MULTIPLIERS = [
1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6,
1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12,
1e-13, 1e-14, 1e-15, 1e-16, 1e-17
];
#decodeDoubleDecimal(isNegative, decimalIndex, double, chunk) {
let cursor = this.#cursor;
do {
const byte = chunk[cursor];
switch (byte) {
case ASCII.E:
case ASCII.e: {
this.#cursor = cursor + 1; // skip E/e
const d = isNegative ? -double : double;
return this.#cursor === chunk.length ?
this.#decodeDoubleExponent.bind(this, d) :
this.#decodeDoubleExponent(d, chunk);
}
case ASCII['\r']:
this.#cursor = cursor + 2; // skip \r\n
return isNegative ? -double : double;
}
if (decimalIndex < _a.#DOUBLE_DECIMAL_MULTIPLIERS.length) {
double += (byte - ASCII['0']) * _a.#DOUBLE_DECIMAL_MULTIPLIERS[decimalIndex++];
}
} while (++cursor < chunk.length);
this.#cursor = cursor;
return this.#decodeDoubleDecimal.bind(this, isNegative, decimalIndex, double);
}
#decodeDoubleExponent(double, chunk) {
switch (chunk[this.#cursor]) {
case ASCII['+']:
return ++this.#cursor === chunk.length ?
this.#continueDecodeDoubleExponent.bind(this, false, double, 0) :
this.#continueDecodeDoubleExponent(false, double, 0, chunk);
case ASCII['-']:
return ++this.#cursor === chunk.length ?
this.#continueDecodeDoubleExponent.bind(this, true, double, 0) :
this.#continueDecodeDoubleExponent(true, double, 0, chunk);
}
return this.#continueDecodeDoubleExponent(false, double, 0, chunk);
}
#continueDecodeDoubleExponent(isNegative, double, exponent, chunk) {
let cursor = this.#cursor;
do {
const byte = chunk[cursor];
if (byte === ASCII['\r']) {
this.#cursor = cursor + 2; // skip \r\n
return double * 10 ** (isNegative ? -exponent : exponent);
}
exponent = exponent * 10 + byte - ASCII['0'];
} while (++cursor < chunk.length);
this.#cursor = cursor;
return this.#continueDecodeDoubleExponent.bind(this, isNegative, double, exponent);
}
#findCRLF(chunk, cursor) {
while (chunk[cursor] !== ASCII['\r']) {
if (++cursor === chunk.length) {
this.#cursor = chunk.length;
return -1;
}
}
this.#cursor = cursor + 2; // skip \r\n
return cursor;
}
#decodeSimpleString(type, chunk) {
const start = this.#cursor, crlfIndex = this.#findCRLF(chunk, start);
if (crlfIndex === -1) {
return this.#continueDecodeSimpleString.bind(this, [chunk.subarray(start)], type);
}
const slice = chunk.subarray(start, crlfIndex);
return type === Buffer ?
slice :
slice.toString();
}
#continueDecodeSimpleString(chunks, type, chunk) {
const start = this.#cursor, crlfIndex = this.#findCRLF(chunk, start);
if (crlfIndex === -1) {
chunks.push(chunk.subarray(start));
return this.#continueDecodeSimpleString.bind(this, chunks, type);
}
chunks.push(chunk.subarray(start, crlfIndex));
const buffer = Buffer.concat(chunks);
return type === Buffer ? buffer : buffer.toString();
}
#decodeBlobString(type, chunk) {
// RESP 2 bulk string null
// https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md#resp-bulk-strings
if (chunk[this.#cursor] === ASCII['-']) {
this.#cursor += 4; // skip -1\r\n
return null;
}
const length = this.#decodeUnsingedNumber(0, chunk);
if (typeof length === 'function') {
return this.#continueDecodeBlobStringLength.bind(this, length, type);
}
else if (this.#cursor >= chunk.length) {
return this.#decodeBlobStringWithLength.bind(this, length, type);
}
return this.#decodeBlobStringWithLength(length, type, chunk);
}
#continueDecodeBlobStringLength(lengthCb, type, chunk) {
const length = lengthCb(chunk);
if (typeof length === 'function') {
return this.#continueDecodeBlobStringLength.bind(this, length, type);
}
else if (this.#cursor >= chunk.length) {
return this.#decodeBlobStringWithLength.bind(this, length, type);
}
return this.#decodeBlobStringWithLength(length, type, chunk);
}
#decodeStringWithLength(length, skip, type, chunk) {
const end = this.#cursor + length;
if (end >= chunk.length) {
const slice = chunk.subarray(this.#cursor);
this.#cursor = chunk.length;
return this.#continueDecodeStringWithLength.bind(this, length - slice.length, [slice], skip, type);
}
const slice = chunk.subarray(this.#cursor, end);
this.#cursor = end + skip;
return type === Buffer ?
slice :
slice.toString();
}
#continueDecodeStringWithLength(length, chunks, skip, type, chunk) {
const end = this.#cursor + length;
if (end >= chunk.length) {
const slice = chunk.subarray(this.#cursor);
chunks.push(slice);
this.#cursor = chunk.length;
return this.#continueDecodeStringWithLength.bind(this, length - slice.length, chunks, skip, type);
}
chunks.push(chunk.subarray(this.#cursor, end));
this.#cursor = end + skip;
const buffer = Buffer.concat(chunks);
return type === Buffer ? buffer : buffer.toString();
}
#decodeBlobStringWithLength(length, type, chunk) {
return this.#decodeStringWithLength(length, 2, type, chunk);
}
#decodeVerbatimString(type, chunk) {
return this.#continueDecodeVerbatimStringLength(this.#decodeUnsingedNumber.bind(this, 0), type, chunk);
}
#continueDecodeVerbatimStringLength(lengthCb, type, chunk) {
const length = lengthCb(chunk);
return typeof length === 'function' ?
this.#continueDecodeVerbatimStringLength.bind(this, length, type) :
this.#decodeVerbatimStringWithLength(length, type, chunk);
}
#decodeVerbatimStringWithLength(length, type, chunk) {
const stringLength = length - 4; // skip <format>:
if (type === verbatim_string_1.VerbatimString) {
return this.#decodeVerbatimStringFormat(stringLength, chunk);
}
this.#cursor += 4; // skip <format>:
return this.#cursor >= chunk.length ?
this.#decodeBlobStringWithLength.bind(this, stringLength, type) :
this.#decodeBlobStringWithLength(stringLength, type, chunk);
}
#decodeVerbatimStringFormat(stringLength, chunk) {
const formatCb = this.#decodeStringWithLength.bind(this, 3, 1, String);
return this.#cursor >= chunk.length ?
this.#continueDecodeVerbatimStringFormat.bind(this, stringLength, formatCb) :
this.#continueDecodeVerbatimStringFormat(stringLength, formatCb, chunk);
}
#continueDecodeVerbatimStringFormat(stringLength, formatCb, chunk) {
const format = formatCb(chunk);
return typeof format === 'function' ?
this.#continueDecodeVerbatimStringFormat.bind(this, stringLength, format) :
this.#decodeVerbatimStringWithFormat(stringLength, format, chunk);
}
#decodeVerbatimStringWithFormat(stringLength, format, chunk) {
return this.#continueDecodeVerbatimStringWithFormat(format, this.#decodeBlobStringWithLength.bind(this, stringLength, String), chunk);
}
#continueDecodeVerbatimStringWithFormat(format, stringCb, chunk) {
const string = stringCb(chunk);
return typeof string === 'function' ?
this.#continueDecodeVerbatimStringWithFormat.bind(this, format, string) :
new verbatim_string_1.VerbatimString(format, string);
}
#decodeSimpleError(chunk) {
const string = this.#decodeSimpleString(String, chunk);
return typeof string === 'function' ?
this.#continueDecodeSimpleError.bind(this, string) :
new errors_1.SimpleError(string);
}
#continueDecodeSimpleError(stringCb, chunk) {
const string = stringCb(chunk);
return typeof string === 'function' ?
this.#continueDecodeSimpleError.bind(this, string) :
new errors_1.SimpleError(string);
}
#decodeBlobError(chunk) {
const string = this.#decodeBlobString(String, chunk);
return typeof string === 'function' ?
this.#continueDecodeBlobError.bind(this, string) :
new errors_1.BlobError(string);
}
#continueDecodeBlobError(stringCb, chunk) {
const string = stringCb(chunk);
return typeof string === 'function' ?
this.#continueDecodeBlobError.bind(this, string) :
new errors_1.BlobError(string);
}
#decodeNestedType(typeMapping, chunk) {
const type = chunk[this.#cursor];
return ++this.#cursor === chunk.length ?
this.#decodeNestedTypeValue.bind(this, type, typeMapping) :
this.#decodeNestedTypeValue(type, typeMapping, chunk);
}
#decodeNestedTypeValue(type, typeMapping, chunk) {
switch (type) {
case exports.RESP_TYPES.NULL:
return this.#decodeNull();
case exports.RESP_TYPES.BOOLEAN:
return this.#decodeBoolean(chunk);
case exports.RESP_TYPES.NUMBER:
return this.#decodeNumber(typeMapping[exports.RESP_TYPES.NUMBER], chunk);
case exports.RESP_TYPES.BIG_NUMBER:
return this.#decodeBigNumber(typeMapping[exports.RESP_TYPES.BIG_NUMBER], chunk);
case exports.RESP_TYPES.DOUBLE:
return this.#decodeDouble(typeMapping[exports.RESP_TYPES.DOUBLE], chunk);
case exports.RESP_TYPES.SIMPLE_STRING:
return this.#decodeSimpleString(typeMapping[exports.RESP_TYPES.SIMPLE_STRING], chunk);
case exports.RESP_TYPES.BLOB_STRING:
return this.#decodeBlobString(typeMapping[exports.RESP_TYPES.BLOB_STRING], chunk);
case exports.RESP_TYPES.VERBATIM_STRING:
return this.#decodeVerbatimString(typeMapping[exports.RESP_TYPES.VERBATIM_STRING], chunk);
case exports.RESP_TYPES.SIMPLE_ERROR:
return this.#decodeSimpleError(chunk);
case exports.RESP_TYPES.BLOB_ERROR:
return this.#decodeBlobError(chunk);
case exports.RESP_TYPES.ARRAY:
return this.#decodeArray(typeMapping, chunk);
case exports.RESP_TYPES.SET:
return this.#decodeSet(typeMapping, chunk);
case exports.RESP_TYPES.MAP:
return this.#decodeMap(typeMapping, chunk);
default:
throw new Error(`Unknown RESP type ${type} "${String.fromCharCode(type)}"`);
}
}
#decodeArray(typeMapping, chunk) {
// RESP 2 null
// https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md#resp-arrays
if (chunk[this.#cursor] === ASCII['-']) {
this.#cursor += 4; // skip -1\r\n
return null;
}
return this.#decodeArrayWithLength(this.#decodeUnsingedNumber(0, chunk), typeMapping, chunk);
}
#decodeArrayWithLength(length, typeMapping, chunk) {
return typeof length === 'function' ?
this.#continueDecodeArrayLength.bind(this, length, typeMapping) :
this.#decodeArrayItems(new Array(length), 0, typeMapping, chunk);
}
#continueDecodeArrayLength(lengthCb, typeMapping, chunk) {
return this.#decodeArrayWithLength(lengthCb(chunk), typeMapping, chunk);
}
#decodeArrayItems(array, filled, typeMapping, chunk) {
for (let i = filled; i < array.length; i++) {
if (this.#cursor >= chunk.length) {
return this.#decodeArrayItems.bind(this, array, i, typeMapping);
}
const item = this.#decodeNestedType(typeMapping, chunk);
if (typeof item === 'function') {
return this.#continueDecodeArrayItems.bind(this, array, i, item, typeMapping);
}
array[i] = item;
}
return array;
}
#continueDecodeArrayItems(array, filled, itemCb, typeMapping, chunk) {
const item = itemCb(chunk);
if (typeof item === 'function') {
return this.#continueDecodeArrayItems.bind(this, array, filled, item, typeMapping);
}
array[filled++] = item;
return this.#decodeArrayItems(array, filled, typeMapping, chunk);
}
#decodeSet(typeMapping, chunk) {
const length = this.#decodeUnsingedNumber(0, chunk);
if (typeof length === 'function') {
return this.#continueDecodeSetLength.bind(this, length, typeMapping);
}
return this.#decodeSetItems(length, typeMapping, chunk);
}
#continueDecodeSetLength(lengthCb, typeMapping, chunk) {
const length = lengthCb(chunk);
return typeof length === 'function' ?
this.#continueDecodeSetLength.bind(this, length, typeMapping) :
this.#decodeSetItems(length, typeMapping, chunk);
}
#decodeSetItems(length, typeMapping, chunk) {
return typeMapping[exports.RESP_TYPES.SET] === Set ?
this.#decodeSetAsSet(new Set(), length, typeMapping, chunk) :
this.#decodeArrayItems(new Array(length), 0, typeMapping, chunk);
}
#decodeSetAsSet(set, remaining, typeMapping, chunk) {
// using `remaining` instead of `length` & `set.size` to make it work even if the set contains duplicates
while (remaining > 0) {
if (this.#cursor >= chunk.length) {
return this.#decodeSetAsSet.bind(this, set, remaining, typeMapping);
}
const item = this.#decodeNestedType(typeMapping, chunk);
if (typeof item === 'function') {
return this.#continueDecodeSetAsSet.bind(this, set, remaining, item, typeMapping);
}
set.add(item);
--remaining;
}
return set;
}
#continueDecodeSetAsSet(set, remaining, itemCb, typeMapping, chunk) {
const item = itemCb(chunk);
if (typeof item === 'function') {
return this.#continueDecodeSetAsSet.bind(this, set, remaining, item, typeMapping);
}
set.add(item);
return this.#decodeSetAsSet(set, remaining - 1, typeMapping, chunk);
}
#decodeMap(typeMapping, chunk) {
const length = this.#decodeUnsingedNumber(0, chunk);
if (typeof length === 'function') {
return this.#continueDecodeMapLength.bind(this, length, typeMapping);
}
return this.#decodeMapItems(length, typeMapping, chunk);
}
#continueDecodeMapLength(lengthCb, typeMapping, chunk) {
const length = lengthCb(chunk);
return typeof length === 'function' ?
this.#continueDecodeMapLength.bind(this, length, typeMapping) :
this.#decodeMapItems(length, typeMapping, chunk);
}
#decodeMapItems(length, typeMapping, chunk) {
switch (typeMapping[exports.RESP_TYPES.MAP]) {
case Map:
return this.#decodeMapAsMap(new Map(), length, typeMapping, chunk);
case Array:
return this.#decodeArrayItems(new Array(length * 2), 0, typeMapping, chunk);
default:
return this.#decodeMapAsObject({}, length, typeMapping, chunk);
}
}
#decodeMapAsMap(map, remaining, typeMapping, chunk) {
// using `remaining` instead of `length` & `map.size` to make it work even if the map contains duplicate keys
while (remaining > 0) {
if (this.#cursor >= chunk.length) {
return this.#decodeMapAsMap.bind(this, map, remaining, typeMapping);
}
const key = this.#decodeMapKey(typeMapping, chunk);
if (typeof key === 'function') {
return this.#continueDecodeMapKey.bind(this, map, remaining, key, typeMapping);
}
if (this.#cursor >= chunk.length) {
return this.#continueDecodeMapValue.bind(this, map, remaining, key, this.#decodeNestedType.bind(this, typeMapping), typeMapping);
}
const value = this.#decodeNestedType(typeMapping, chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapValue.bind(this, map, remaining, key, value, typeMapping);
}
map.set(key, value);
--remaining;
}
return map;
}
#decodeMapKey(typeMapping, chunk) {
const type = chunk[this.#cursor];
return ++this.#cursor === chunk.length ?
this.#decodeMapKeyValue.bind(this, type, typeMapping) :
this.#decodeMapKeyValue(type, typeMapping, chunk);
}
#decodeMapKeyValue(type, typeMapping, chunk) {
switch (type) {
// decode simple string map key as string (and not as buffer)
case exports.RESP_TYPES.SIMPLE_STRING:
return this.#decodeSimpleString(String, chunk);
// decode blob string map key as string (and not as buffer)
case exports.RESP_TYPES.BLOB_STRING:
return this.#decodeBlobString(String, chunk);
default:
return this.#decodeNestedTypeValue(type, typeMapping, chunk);
}
}
#continueDecodeMapKey(map, remaining, keyCb, typeMapping, chunk) {
const key = keyCb(chunk);
if (typeof key === 'function') {
return this.#continueDecodeMapKey.bind(this, map, remaining, key, typeMapping);
}
if (this.#cursor >= chunk.length) {
return this.#continueDecodeMapValue.bind(this, map, remaining, key, this.#decodeNestedType.bind(this, typeMapping), typeMapping);
}
const value = this.#decodeNestedType(typeMapping, chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapValue.bind(this, map, remaining, key, value, typeMapping);
}
map.set(key, value);
return this.#decodeMapAsMap(map, remaining - 1, typeMapping, chunk);
}
#continueDecodeMapValue(map, remaining, key, valueCb, typeMapping, chunk) {
const value = valueCb(chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapValue.bind(this, map, remaining, key, value, typeMapping);
}
map.set(key, value);
return this.#decodeMapAsMap(map, remaining - 1, typeMapping, chunk);
}
#decodeMapAsObject(object, remaining, typeMapping, chunk) {
while (remaining > 0) {
if (this.#cursor >= chunk.length) {
return this.#decodeMapAsObject.bind(this, object, remaining, typeMapping);
}
const key = this.#decodeMapKey(typeMapping, chunk);
if (typeof key === 'function') {
return this.#continueDecodeMapAsObjectKey.bind(this, object, remaining, key, typeMapping);
}
if (this.#cursor >= chunk.length) {
return this.#continueDecodeMapAsObjectValue.bind(this, object, remaining, key, this.#decodeNestedType.bind(this, typeMapping), typeMapping);
}
const value = this.#decodeNestedType(typeMapping, chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapAsObjectValue.bind(this, object, remaining, key, value, typeMapping);
}
object[key] = value;
--remaining;
}
return object;
}
#continueDecodeMapAsObjectKey(object, remaining, keyCb, typeMapping, chunk) {
const key = keyCb(chunk);
if (typeof key === 'function') {
return this.#continueDecodeMapAsObjectKey.bind(this, object, remaining, key, typeMapping);
}
if (this.#cursor >= chunk.length) {
return this.#continueDecodeMapAsObjectValue.bind(this, object, remaining, key, this.#decodeNestedType.bind(this, typeMapping), typeMapping);
}
const value = this.#decodeNestedType(typeMapping, chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapAsObjectValue.bind(this, object, remaining, key, value, typeMapping);
}
object[key] = value;
return this.#decodeMapAsObject(object, remaining - 1, typeMapping, chunk);
}
#continueDecodeMapAsObjectValue(object, remaining, key, valueCb, typeMapping, chunk) {
const value = valueCb(chunk);
if (typeof value === 'function') {
return this.#continueDecodeMapAsObjectValue.bind(this, object, remaining, key, value, typeMapping);
}
object[key] = value;
return this.#decodeMapAsObject(object, remaining - 1, typeMapping, chunk);
}
}
exports.Decoder = Decoder;
_a = Decoder;
//# sourceMappingURL=decoder.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
import { RedisArgument } from './types';
export default function encodeCommand(args: ReadonlyArray<RedisArgument>): ReadonlyArray<RedisArgument>;
//# sourceMappingURL=encoder.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../../../lib/RESP/encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIxC,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC,aAAa,CAAC,CAuBtG"}
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const CRLF = '\r\n';
function encodeCommand(args) {
const toWrite = [];
let strings = '*' + args.length + CRLF;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (typeof arg === 'string') {
strings += '$' + Buffer.byteLength(arg) + CRLF + arg + CRLF;
}
else if (arg instanceof Buffer) {
toWrite.push(strings + '$' + arg.length.toString() + CRLF, arg);
strings = CRLF;
}
else {
throw new TypeError(`"arguments[${i}]" must be of type "string | Buffer", got ${typeof arg} instead.`);
}
}
toWrite.push(strings);
return toWrite;
}
exports.default = encodeCommand;
//# sourceMappingURL=encoder.js.map
@@ -0,0 +1 @@
{"version":3,"file":"encoder.js","sourceRoot":"","sources":["../../../lib/RESP/encoder.ts"],"names":[],"mappings":";;AAEA,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,SAAwB,aAAa,CAAC,IAAkC;IACtE,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,IAAI,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,IAAI,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;QAC9D,CAAC;aAAM,IAAI,GAAG,YAAY,MAAM,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CACV,OAAO,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,IAAI,EAC5C,GAAG,CACJ,CAAC;YACF,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,SAAS,CAAC,cAAc,CAAC,6CAA6C,OAAO,GAAG,WAAW,CAAC,CAAC;QACzG,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAvBD,gCAuBC","sourcesContent":["import { RedisArgument } from './types';\n\nconst CRLF = '\\r\\n';\n\nexport default function encodeCommand(args: ReadonlyArray<RedisArgument>): ReadonlyArray<RedisArgument> {\n const toWrite: Array<RedisArgument> = [];\n\n let strings = '*' + args.length + CRLF;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (typeof arg === 'string') {\n strings += '$' + Buffer.byteLength(arg) + CRLF + arg + CRLF;\n } else if (arg instanceof Buffer) {\n toWrite.push(\n strings + '$' + arg.length.toString() + CRLF,\n arg\n );\n strings = CRLF;\n } else {\n throw new TypeError(`\"arguments[${i}]\" must be of type \"string | Buffer\", got ${typeof arg} instead.`);\n }\n }\n\n toWrite.push(strings);\n\n return toWrite;\n}\n"]}
@@ -0,0 +1,121 @@
/// <reference types="node" />
import { CommandParser } from '../client/parser';
import { Tail } from '../commands/generic-transformers';
import { BlobError, SimpleError } from '../errors';
import { RedisScriptConfig, SHA1 } from '../lua-script';
import { RESP_TYPES } from './decoder';
import { VerbatimString } from './verbatim-string';
export type RESP_TYPES = typeof RESP_TYPES;
export type RespTypes = RESP_TYPES[keyof RESP_TYPES];
export interface RespType<RESP_TYPE extends RespTypes, DEFAULT, TYPES = never, TYPE_MAPPING = DEFAULT | TYPES> {
RESP_TYPE: RESP_TYPE;
DEFAULT: DEFAULT;
TYPES: TYPES;
TYPE_MAPPING: MappedType<TYPE_MAPPING>;
}
export interface NullReply extends RespType<RESP_TYPES['NULL'], null> {
}
export interface BooleanReply<T extends boolean = boolean> extends RespType<RESP_TYPES['BOOLEAN'], T> {
}
export interface NumberReply<T extends number = number> extends RespType<RESP_TYPES['NUMBER'], T, `${T}`, number | string> {
}
export interface BigNumberReply<T extends bigint = bigint> extends RespType<RESP_TYPES['BIG_NUMBER'], T, number | `${T}`, bigint | number | string> {
}
export interface DoubleReply<T extends number = number> extends RespType<RESP_TYPES['DOUBLE'], T, `${T}`, number | string> {
}
export interface SimpleStringReply<T extends string = string> extends RespType<RESP_TYPES['SIMPLE_STRING'], T, Buffer, string | Buffer> {
}
export interface BlobStringReply<T extends string = string> extends RespType<RESP_TYPES['BLOB_STRING'], T, Buffer, string | Buffer> {
toString(): string;
}
export interface VerbatimStringReply<T extends string = string> extends RespType<RESP_TYPES['VERBATIM_STRING'], T, Buffer | VerbatimString, string | Buffer | VerbatimString> {
}
export interface SimpleErrorReply extends RespType<RESP_TYPES['SIMPLE_ERROR'], SimpleError, Buffer> {
}
export interface BlobErrorReply extends RespType<RESP_TYPES['BLOB_ERROR'], BlobError, Buffer> {
}
export interface ArrayReply<T> extends RespType<RESP_TYPES['ARRAY'], Array<T>, never, Array<any>> {
}
export interface TuplesReply<T extends [...Array<unknown>]> extends RespType<RESP_TYPES['ARRAY'], T, never, Array<any>> {
}
export interface SetReply<T> extends RespType<RESP_TYPES['SET'], Array<T>, Set<T>, Array<any> | Set<any>> {
}
export interface MapReply<K, V> extends RespType<RESP_TYPES['MAP'], {
[key: string]: V;
}, Map<K, V> | Array<K | V>, Map<any, any> | Array<any>> {
}
type MapKeyValue = [key: BlobStringReply | SimpleStringReply, value: unknown];
type MapTuples = Array<MapKeyValue>;
type ExtractMapKey<T> = (T extends BlobStringReply<infer S> ? S : T extends SimpleStringReply<infer S> ? S : never);
export interface TuplesToMapReply<T extends MapTuples> extends RespType<RESP_TYPES['MAP'], {
[P in T[number] as ExtractMapKey<P[0]>]: P[1];
}, Map<ExtractMapKey<T[number][0]>, T[number][1]> | FlattenTuples<T>> {
}
type FlattenTuples<T> = (T extends [] ? [] : T extends [MapKeyValue] ? T[0] : T extends [MapKeyValue, ...infer R] ? [
...T[0],
...FlattenTuples<R>
] : never);
export type ReplyUnion = (NullReply | BooleanReply | NumberReply | BigNumberReply | DoubleReply | SimpleStringReply | BlobStringReply | VerbatimStringReply | SimpleErrorReply | BlobErrorReply | ArrayReply<ReplyUnion> | SetReply<ReplyUnion> | MapReply<ReplyUnion, ReplyUnion>);
export type MappedType<T> = ((...args: any) => T) | (new (...args: any) => T);
type InferTypeMapping<T> = T extends RespType<RespTypes, unknown, unknown, infer FLAG_TYPES> ? FLAG_TYPES : never;
export type TypeMapping = {
[P in RespTypes]?: MappedType<InferTypeMapping<Extract<ReplyUnion, RespType<P, any, any, any>>>>;
};
type MapKey<T, TYPE_MAPPING extends TypeMapping> = ReplyWithTypeMapping<T, TYPE_MAPPING & {
[RESP_TYPES.SIMPLE_STRING]: StringConstructor;
[RESP_TYPES.BLOB_STRING]: StringConstructor;
}>;
type UnwrapConstructor<T> = T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T;
export type UnwrapReply<REPLY extends RespType<any, any, any, any>> = REPLY['DEFAULT' | 'TYPES'];
export type ReplyWithTypeMapping<REPLY, TYPE_MAPPING extends TypeMapping> = (REPLY extends RespType<infer RESP_TYPE, infer DEFAULT, infer TYPES, unknown> ? TYPE_MAPPING[RESP_TYPE] extends MappedType<infer T> ? ReplyWithTypeMapping<Extract<DEFAULT | TYPES, UnwrapConstructor<T>>, TYPE_MAPPING> : ReplyWithTypeMapping<DEFAULT, TYPE_MAPPING> : (REPLY extends Array<infer T> ? Array<ReplyWithTypeMapping<T, TYPE_MAPPING>> : REPLY extends Set<infer T> ? Set<ReplyWithTypeMapping<T, TYPE_MAPPING>> : REPLY extends Map<infer K, infer V> ? Map<MapKey<K, TYPE_MAPPING>, ReplyWithTypeMapping<V, TYPE_MAPPING>> : REPLY extends Date | Buffer | Error ? REPLY : REPLY extends Record<PropertyKey, any> ? {
[P in keyof REPLY]: ReplyWithTypeMapping<REPLY[P], TYPE_MAPPING>;
} : REPLY));
export type TransformReply = (this: void, reply: any, preserve?: any, typeMapping?: TypeMapping) => any;
export type RedisArgument = string | Buffer;
export type CommandArguments = Array<RedisArgument> & {
preserve?: unknown;
};
export type Command = {
CACHEABLE?: boolean;
IS_READ_ONLY?: boolean;
/**
* @internal
* TODO: remove once `POLICIES` is implemented
*/
IS_FORWARD_COMMAND?: boolean;
NOT_KEYED_COMMAND?: true;
parseCommand(this: void, parser: CommandParser, ...args: Array<any>): void;
TRANSFORM_LEGACY_REPLY?: boolean;
transformReply: TransformReply | Record<RespVersions, TransformReply>;
};
export type RedisCommands = Record<string, Command>;
export type RedisModules = Record<string, RedisCommands>;
export interface RedisFunction extends Command {
NUMBER_OF_KEYS?: number;
}
export type RedisFunctions = Record<string, Record<string, RedisFunction>>;
export type RedisScript = RedisScriptConfig & SHA1;
export type RedisScripts = Record<string, RedisScript>;
export interface CommanderConfig<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions> {
modules?: M;
functions?: F;
scripts?: S;
/**
* Specifies the Redis Serialization Protocol version to use.
* RESP3 is the default (value 3), while RESP2 (value 2) remains available for compatibility.
* RESP3 provides
* additional data types and features introduced in Redis 6.0.
*/
RESP?: RESP;
}
type Resp2Array<T> = (T extends [] ? [] : T extends [infer ITEM] ? [Resp2Reply<ITEM>] : T extends [infer ITEM, ...infer REST] ? [
Resp2Reply<ITEM>,
...Resp2Array<REST>
] : T extends Array<infer ITEM> ? Array<Resp2Reply<ITEM>> : never);
export type Resp2Reply<RESP3REPLY> = (RESP3REPLY extends RespType<infer RESP_TYPE, infer DEFAULT, infer TYPES, unknown> ? RESP_TYPE extends RESP_TYPES['DOUBLE'] ? BlobStringReply : RESP_TYPE extends RESP_TYPES['ARRAY'] | RESP_TYPES['SET'] ? RespType<RESP_TYPE, Resp2Array<DEFAULT>> : RESP_TYPE extends RESP_TYPES['MAP'] ? RespType<RESP_TYPES['ARRAY'], Resp2Array<Extract<TYPES, Array<any>>>> : RESP3REPLY : RESP3REPLY);
export type RespVersions = 2 | 3;
export declare const DEFAULT_RESP: 3;
export type CommandReply<COMMAND extends Command, RESP extends RespVersions> = (COMMAND['transformReply'] extends (...args: any) => infer T ? T : COMMAND['transformReply'] extends Record<RESP, (...args: any) => infer T> ? T : ReplyUnion);
export type CommandSignature<COMMAND extends Command, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (...args: Tail<Parameters<COMMAND['parseCommand']>>) => Promise<ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>>;
export {};
//# sourceMappingURL=types.d.ts.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_RESP = void 0;
const decoder_1 = require("./decoder");
exports.DEFAULT_RESP = 3;
// export type CommandWithPoliciesSignature<
// COMMAND extends Command,
// RESP extends RespVersions,
// TYPE_MAPPING extends TypeMapping,
// POLICIES extends CommandPolicies
// > = (...args: Parameters<COMMAND['transformArguments']>) => Promise<
// ReplyWithPolicy<
// ReplyWithTypeMapping<CommandReply<COMMAND, RESP>, TYPE_MAPPING>,
// MergePolicies<COMMAND, POLICIES>
// >
// >;
// export type MergePolicies<
// COMMAND extends Command,
// POLICIES extends CommandPolicies
// > = Omit<COMMAND['POLICIES'], keyof POLICIES> & POLICIES;
// type ReplyWithPolicy<
// REPLY,
// POLICIES extends CommandPolicies,
// > = (
// POLICIES['request'] extends REQUEST_POLICIES['SPECIAL'] ? never :
// POLICIES['request'] extends null | undefined ? REPLY :
// unknown extends POLICIES['request'] ? REPLY :
// POLICIES['response'] extends RESPONSE_POLICIES['SPECIAL'] ? never :
// POLICIES['response'] extends RESPONSE_POLICIES['ALL_SUCCEEDED' | 'ONE_SUCCEEDED' | 'LOGICAL_AND'] ? REPLY :
// // otherwise, return array of replies
// Array<REPLY>
// );
//# sourceMappingURL=types.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
export declare class VerbatimString extends String {
format: string;
constructor(format: string, value: string);
}
//# sourceMappingURL=verbatim-string.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"verbatim-string.d.ts","sourceRoot":"","sources":["../../../lib/RESP/verbatim-string.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,MAAM;IAE/B,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM;CAIhB"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerbatimString = void 0;
class VerbatimString extends String {
format;
constructor(format, value) {
super(value);
this.format = format;
}
}
exports.VerbatimString = VerbatimString;
//# sourceMappingURL=verbatim-string.js.map
@@ -0,0 +1 @@
{"version":3,"file":"verbatim-string.js","sourceRoot":"","sources":["../../../lib/RESP/verbatim-string.ts"],"names":[],"mappings":";;;AAAA,MAAa,cAAe,SAAQ,MAAM;IAE/B;IADT,YACS,MAAc,EACrB,KAAa;QAEb,KAAK,CAAC,KAAK,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAQ;IAIvB,CAAC;CACF;AAPD,wCAOC","sourcesContent":["export class VerbatimString extends String {\n constructor(\n public format: string,\n value: string\n ) {\n super(value);\n }\n}\n"]}
@@ -0,0 +1,88 @@
import { Disposable } from './disposable';
/**
* Provides credentials asynchronously.
*/
export interface AsyncCredentialsProvider {
readonly type: 'async-credentials-provider';
credentials: () => Promise<BasicAuth>;
}
/**
* Provides credentials asynchronously with support for continuous updates via a subscription model.
* This is useful for environments where credentials are frequently rotated or updated or can be revoked.
*/
export interface StreamingCredentialsProvider {
readonly type: 'streaming-credentials-provider';
/**
* Provides initial credentials and subscribes to subsequent updates. This is used internally by the node-redis client
* to handle credential rotation and re-authentication.
*
* Note: The node-redis client manages the subscription lifecycle automatically. Users only need to implement
* onReAuthenticationError if they want to be notified about authentication failures.
*
* Error handling:
* - Errors received via onError indicate a fatal issue with the credentials stream
* - The stream is automatically closed(disposed) when onError occurs
* - onError typically mean the provider failed to fetch new credentials after retrying
*
* @example
* ```ts
* const provider = getStreamingProvider();
* const [initialCredentials, disposable] = await provider.subscribe({
* onNext: (newCredentials) => {
* // Handle credential update
* },
* onError: (error) => {
* // Handle fatal stream error
* }
* });
*
* @param listener - Callbacks to handle credential updates and errors
* @returns A Promise resolving to [initial credentials, cleanup function]
*/
subscribe: (listener: StreamingCredentialsListener<BasicAuth>) => Promise<[BasicAuth, Disposable]>;
/**
* Called when authentication fails or credentials cannot be renewed in time.
* Implement this to handle authentication errors in your application.
*
* @param error - Either a CredentialsError (invalid/expired credentials) or
* UnableToObtainNewCredentialsError (failed to fetch new credentials on time)
*/
onReAuthenticationError: (error: ReAuthenticationError) => void;
}
/**
* Type representing basic authentication credentials.
*/
export type BasicAuth = {
username?: string;
password?: string;
};
/**
* Callback to handle credential updates and errors.
*/
export type StreamingCredentialsListener<T> = {
onNext: (credentials: T) => void;
onError: (e: Error) => void;
};
/**
* Providers that can supply authentication credentials
*/
export type CredentialsProvider = AsyncCredentialsProvider | StreamingCredentialsProvider;
/**
* Errors that can occur during re-authentication.
*/
export type ReAuthenticationError = CredentialsError | UnableToObtainNewCredentialsError;
/**
* Thrown when re-authentication fails with provided credentials .
* e.g. when the credentials are invalid, expired or revoked.
*
*/
export declare class CredentialsError extends Error {
constructor(message: string);
}
/**
* Thrown when new credentials cannot be obtained before current ones expire
*/
export declare class UnableToObtainNewCredentialsError extends Error {
constructor(message: string);
}
//# sourceMappingURL=credentials-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"credentials-provider.d.ts","sourceRoot":"","sources":["../../../lib/authx/credentials-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,4BAA4B,CAAC;IAC5C,WAAW,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,CAAA;CACtC;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,gCAAgC,CAAC;IAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,4BAA4B,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAA;IAElG;;;;;;OAMG;IACH,uBAAuB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAEjE;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhE;;GAEG;AACH,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI;IAC5C,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7B,CAAA;AAGD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GAAG,4BAA4B,CAAA;AAEzF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,iCAAiC,CAAA;AAExF;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,OAAO,EAAE,MAAM;CAI5B"}
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnableToObtainNewCredentialsError = exports.CredentialsError = void 0;
/**
* Thrown when re-authentication fails with provided credentials .
* e.g. when the credentials are invalid, expired or revoked.
*
*/
class CredentialsError extends Error {
constructor(message) {
super(`Re-authentication with latest credentials failed: ${message}`);
this.name = 'CredentialsError';
}
}
exports.CredentialsError = CredentialsError;
/**
* Thrown when new credentials cannot be obtained before current ones expire
*/
class UnableToObtainNewCredentialsError extends Error {
constructor(message) {
super(`Unable to obtain new credentials : ${message}`);
this.name = 'UnableToObtainNewCredentialsError';
}
}
exports.UnableToObtainNewCredentialsError = UnableToObtainNewCredentialsError;
//# sourceMappingURL=credentials-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"credentials-provider.js","sourceRoot":"","sources":["../../../lib/authx/credentials-provider.ts"],"names":[],"mappings":";;;AAgFA;;;;GAIG;AACH,MAAa,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,qDAAqD,OAAO,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CAEF;AAND,4CAMC;AAED;;GAEG;AACH,MAAa,iCAAkC,SAAQ,KAAK;IAC1D,YAAY,OAAe;QACzB,KAAK,CAAC,sCAAsC,OAAO,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,mCAAmC,CAAC;IAClD,CAAC;CACF;AALD,8EAKC","sourcesContent":["import { Disposable } from './disposable';\n/**\n * Provides credentials asynchronously.\n */\nexport interface AsyncCredentialsProvider {\n readonly type: 'async-credentials-provider';\n credentials: () => Promise<BasicAuth>\n}\n\n/**\n * Provides credentials asynchronously with support for continuous updates via a subscription model.\n * This is useful for environments where credentials are frequently rotated or updated or can be revoked.\n */\nexport interface StreamingCredentialsProvider {\n readonly type: 'streaming-credentials-provider';\n\n /**\n * Provides initial credentials and subscribes to subsequent updates. This is used internally by the node-redis client\n * to handle credential rotation and re-authentication.\n *\n * Note: The node-redis client manages the subscription lifecycle automatically. Users only need to implement\n * onReAuthenticationError if they want to be notified about authentication failures.\n *\n * Error handling:\n * - Errors received via onError indicate a fatal issue with the credentials stream\n * - The stream is automatically closed(disposed) when onError occurs\n * - onError typically mean the provider failed to fetch new credentials after retrying\n *\n * @example\n * ```ts\n * const provider = getStreamingProvider();\n * const [initialCredentials, disposable] = await provider.subscribe({\n * onNext: (newCredentials) => {\n * // Handle credential update\n * },\n * onError: (error) => {\n * // Handle fatal stream error\n * }\n * });\n *\n * @param listener - Callbacks to handle credential updates and errors\n * @returns A Promise resolving to [initial credentials, cleanup function]\n */\n subscribe: (listener: StreamingCredentialsListener<BasicAuth>) => Promise<[BasicAuth, Disposable]>\n\n /**\n * Called when authentication fails or credentials cannot be renewed in time.\n * Implement this to handle authentication errors in your application.\n *\n * @param error - Either a CredentialsError (invalid/expired credentials) or\n * UnableToObtainNewCredentialsError (failed to fetch new credentials on time)\n */\n onReAuthenticationError: (error: ReAuthenticationError) => void;\n\n}\n\n/**\n * Type representing basic authentication credentials.\n */\nexport type BasicAuth = { username?: string, password?: string }\n\n/**\n * Callback to handle credential updates and errors.\n */\nexport type StreamingCredentialsListener<T> = {\n onNext: (credentials: T) => void;\n onError: (e: Error) => void;\n}\n\n\n/**\n * Providers that can supply authentication credentials\n */\nexport type CredentialsProvider = AsyncCredentialsProvider | StreamingCredentialsProvider\n\n/**\n * Errors that can occur during re-authentication.\n */\nexport type ReAuthenticationError = CredentialsError | UnableToObtainNewCredentialsError\n\n/**\n * Thrown when re-authentication fails with provided credentials .\n * e.g. when the credentials are invalid, expired or revoked.\n *\n */\nexport class CredentialsError extends Error {\n constructor(message: string) {\n super(`Re-authentication with latest credentials failed: ${message}`);\n this.name = 'CredentialsError';\n }\n\n}\n\n/**\n * Thrown when new credentials cannot be obtained before current ones expire\n */\nexport class UnableToObtainNewCredentialsError extends Error {\n constructor(message: string) {\n super(`Unable to obtain new credentials : ${message}`);\n this.name = 'UnableToObtainNewCredentialsError';\n }\n}"]}
@@ -0,0 +1,7 @@
/**
* Represents a resource that can be disposed.
*/
export interface Disposable {
dispose(): void;
}
//# sourceMappingURL=disposable.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"disposable.d.ts","sourceRoot":"","sources":["../../../lib/authx/disposable.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,CAAC;CACjB"}
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=disposable.js.map
@@ -0,0 +1 @@
{"version":3,"file":"disposable.js","sourceRoot":"","sources":["../../../lib/authx/disposable.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Represents a resource that can be disposed.\n */\nexport interface Disposable {\n dispose(): void;\n}"]}
@@ -0,0 +1,24 @@
/**
* An identity provider is responsible for providing a token that can be used to authenticate with a service.
*/
/**
* The response from an identity provider when requesting a token.
*
* note: "native" refers to the type of the token that the actual identity provider library is using.
*
* @type T The type of the native idp token.
* @property token The token.
* @property ttlMs The time-to-live of the token in epoch milliseconds extracted from the native token in local time.
*/
export type TokenResponse<T> = {
token: T;
ttlMs: number;
};
export interface IdentityProvider<T> {
/**
* Request a token from the identity provider.
* @returns A promise that resolves to an object containing the token and the time-to-live in epoch milliseconds.
*/
requestToken(): Promise<TokenResponse<T>>;
}
//# sourceMappingURL=identity-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"identity-provider.d.ts","sourceRoot":"","sources":["../../../lib/authx/identity-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C"}
@@ -0,0 +1,6 @@
"use strict";
/**
* An identity provider is responsible for providing a token that can be used to authenticate with a service.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=identity-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"identity-provider.js","sourceRoot":"","sources":["../../../lib/authx/identity-provider.ts"],"names":[],"mappings":";AAAA;;GAEG","sourcesContent":["/**\n * An identity provider is responsible for providing a token that can be used to authenticate with a service.\n */\n\n/**\n * The response from an identity provider when requesting a token.\n *\n * note: \"native\" refers to the type of the token that the actual identity provider library is using.\n *\n * @type T The type of the native idp token.\n * @property token The token.\n * @property ttlMs The time-to-live of the token in epoch milliseconds extracted from the native token in local time.\n */\nexport type TokenResponse<T> = { token: T, ttlMs: number };\n\nexport interface IdentityProvider<T> {\n /**\n * Request a token from the identity provider.\n * @returns A promise that resolves to an object containing the token and the time-to-live in epoch milliseconds.\n */\n requestToken(): Promise<TokenResponse<T>>;\n}"]}
@@ -0,0 +1,6 @@
export { TokenManager, TokenManagerConfig, TokenStreamListener, RetryPolicy, IDPError } from './token-manager';
export { CredentialsProvider, StreamingCredentialsProvider, UnableToObtainNewCredentialsError, CredentialsError, StreamingCredentialsListener, AsyncCredentialsProvider, ReAuthenticationError, BasicAuth } from './credentials-provider';
export { Token } from './token';
export { IdentityProvider, TokenResponse } from './identity-provider';
export { Disposable } from './disposable';
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/authx/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC/G,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,iCAAiC,EACjC,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,SAAS,EACV,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Token = exports.CredentialsError = exports.UnableToObtainNewCredentialsError = exports.IDPError = exports.TokenManager = void 0;
var token_manager_1 = require("./token-manager");
Object.defineProperty(exports, "TokenManager", { enumerable: true, get: function () { return token_manager_1.TokenManager; } });
Object.defineProperty(exports, "IDPError", { enumerable: true, get: function () { return token_manager_1.IDPError; } });
var credentials_provider_1 = require("./credentials-provider");
Object.defineProperty(exports, "UnableToObtainNewCredentialsError", { enumerable: true, get: function () { return credentials_provider_1.UnableToObtainNewCredentialsError; } });
Object.defineProperty(exports, "CredentialsError", { enumerable: true, get: function () { return credentials_provider_1.CredentialsError; } });
var token_1 = require("./token");
Object.defineProperty(exports, "Token", { enumerable: true, get: function () { return token_1.Token; } });
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/authx/index.ts"],"names":[],"mappings":";;;AAAA,iDAA+G;AAAtG,6GAAA,YAAY,OAAA;AAAwD,yGAAA,QAAQ,OAAA;AACrF,+DASgC;AAN9B,yIAAA,iCAAiC,OAAA;AACjC,wHAAA,gBAAgB,OAAA;AAMlB,iCAAgC;AAAvB,8FAAA,KAAK,OAAA","sourcesContent":["export { TokenManager, TokenManagerConfig, TokenStreamListener, RetryPolicy, IDPError } from './token-manager';\nexport {\n CredentialsProvider,\n StreamingCredentialsProvider,\n UnableToObtainNewCredentialsError,\n CredentialsError,\n StreamingCredentialsListener,\n AsyncCredentialsProvider,\n ReAuthenticationError,\n BasicAuth\n} from './credentials-provider';\nexport { Token } from './token';\nexport { IdentityProvider, TokenResponse } from './identity-provider';\n\nexport { Disposable } from './disposable'"]}
@@ -0,0 +1,165 @@
import { IdentityProvider } from './identity-provider';
import { Token } from './token';
import { Disposable } from './disposable';
/**
* The configuration for retrying token refreshes.
*/
export interface RetryPolicy {
/**
* The maximum number of attempts to retry token refreshes.
*/
maxAttempts: number;
/**
* The initial delay in milliseconds before the first retry.
*/
initialDelayMs: number;
/**
* The maximum delay in milliseconds between retries.
* The calculated delay will be capped at this value.
*/
maxDelayMs: number;
/**
* The multiplier for exponential backoff between retries.
* @example
* A value of 2 will double the delay each time:
* - 1st retry: initialDelayMs
* - 2nd retry: initialDelayMs * 2
* - 3rd retry: initialDelayMs * 4
*/
backoffMultiplier: number;
/**
* The percentage range of jitter to apply to the delay.
* The jitter will be evenly distributed between -jitterPercentage/2 and +jitterPercentage/2.
* @example
* A value of 10 will add or subtract up to 5% of the delay (ranging from 95% to 105% of the original delay).
*/
jitterPercentage?: number;
/**
* Function to classify errors from the identity provider as retryable or non-retryable.
* Used to determine if a token refresh failure should be retried based on the type of error.
*
* The default behavior is to retry all types of errors if no function is provided.
*
* Common use cases:
* - Network errors that may be transient (should retry)
* - Invalid credentials (should not retry)
* - Rate limiting responses (should retry)
*
* @param error - The error from the identity provider3
* @param attempt - Current retry attempt (0-based)
* @returns `true` if the error is considered transient and the operation should be retried
*
* @example
* ```typescript
* const retryPolicy: RetryPolicy = {
* maxAttempts: 3,
* initialDelayMs: 1000,
* maxDelayMs: 5000,
* backoffMultiplier: 2,
* isRetryable: (error) => {
* // Retry on network errors or rate limiting
* return error instanceof NetworkError ||
* error instanceof RateLimitError;
* }
* };
* ```
*/
isRetryable?: (error: unknown, attempt: number) => boolean;
}
/**
* the configuration for the TokenManager.
*/
export interface TokenManagerConfig {
/**
* Represents the ratio of a token's lifetime at which a refresh should be triggered.
* For example, a value of 0.75 means the token should be refreshed when 75% of its lifetime has elapsed (or when
* 25% of its lifetime remains).
*/
expirationRefreshRatio: number;
retry?: RetryPolicy;
}
/**
* IDPError indicates a failure from the identity provider.
*
* The `isRetryable` flag is determined by the RetryPolicy's error classification function - if an error is
* classified as retryable, it will be marked as transient and the token manager will attempt to recover.
*/
export declare class IDPError extends Error {
readonly message: string;
readonly isRetryable: boolean;
constructor(message: string, isRetryable: boolean);
}
/**
* TokenStreamListener is an interface for objects that listen to token changes.
*/
export type TokenStreamListener<T> = {
/**
* Called each time a new token is received.
* @param token
*/
onNext: (token: Token<T>) => void;
/**
* Called when an error occurs while calling the underlying IdentityProvider. The error can be
* transient and the token manager will attempt to obtain a token again if retry policy is configured.
*
* Only fatal errors will terminate the stream and stop the token manager.
*
* @param error
*/
onError: (error: IDPError) => void;
};
/**
* TokenManager is responsible for obtaining/refreshing tokens and notifying listeners about token changes.
* It uses an IdentityProvider to request tokens. The token refresh is scheduled based on the token's TTL and
* the expirationRefreshRatio configuration.
*
* The TokenManager should be disposed when it is no longer needed by calling the dispose method on the Disposable
* returned by start.
*/
export declare class TokenManager<T> {
private readonly identityProvider;
private readonly config;
private currentToken;
private refreshTimeout;
private listener;
private retryAttempt;
constructor(identityProvider: IdentityProvider<T>, config: TokenManagerConfig);
/**
* Starts the token manager and returns a Disposable that can be used to stop the token manager.
*
* @param listener The listener that will receive token updates.
* @param initialDelayMs The initial delay in milliseconds before the first token refresh.
*/
start(listener: TokenStreamListener<T>, initialDelayMs?: number): Disposable;
calculateRetryDelay(): number;
private shouldRetry;
isRunning(): boolean;
private refresh;
private handleNewToken;
/**
* Creates a Token object from a native token and sets it as the current token.
*
* @param nativeToken - The raw token received from the identity provider
* @param ttlMs - Time-to-live in milliseconds for the token
*
* @returns A new Token instance containing the wrapped native token and expiration details
*
*/
wrapAndSetCurrentToken(nativeToken: T, ttlMs: number): Token<T>;
private scheduleNextRefresh;
/**
* Calculates the time in milliseconds when the token should be refreshed
* based on the token's TTL and the expirationRefreshRatio configuration.
*
* @param token The token to calculate the refresh time for.
* @param now The current time in milliseconds. Defaults to Date.now().
*/
calculateRefreshTime(token: Token<T>, now?: number): number;
private stop;
/**
* Returns the current token or null if no token is available.
*/
getCurrentToken(): Token<T> | null;
private notifyError;
}
//# sourceMappingURL=token-manager.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../../lib/authx/token-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAiB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAEjC;;;;OAIG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAG/B,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;aACL,OAAO,EAAE,MAAM;aAAkB,WAAW,EAAE,OAAO;gBAArD,OAAO,EAAE,MAAM,EAAkB,WAAW,EAAE,OAAO;CAIlF;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI;IACnC;;;OAGG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAElC;;;;;;;OAOG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAEpC,CAAA;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY,CAAC,CAAC;IAOvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,YAAY,CAAa;gBAGd,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC,EACrC,MAAM,EAAE,kBAAkB;IAU7C;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,cAAc,GAAE,MAAU,GAAG,UAAU;IAe/E,mBAAmB,IAAI,MAAM;IAoBpC,OAAO,CAAC,WAAW;IAgBZ,SAAS,IAAI,OAAO;YAIb,OAAO;IAsBrB,OAAO,CAAC,cAAc,CAQrB;IAED;;;;;;;;OAQG;IACI,sBAAsB,CAAC,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;IAWtE,OAAO,CAAC,mBAAmB;IAa3B;;;;;;OAMG;IACI,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM;IAK9E,OAAO,CAAC,IAAI;IAYZ;;OAEG;IACI,eAAe,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;IAIzC,OAAO,CAAC,WAAW;CASpB"}
@@ -0,0 +1,184 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenManager = exports.IDPError = void 0;
const token_1 = require("./token");
/**
* IDPError indicates a failure from the identity provider.
*
* The `isRetryable` flag is determined by the RetryPolicy's error classification function - if an error is
* classified as retryable, it will be marked as transient and the token manager will attempt to recover.
*/
class IDPError extends Error {
message;
isRetryable;
constructor(message, isRetryable) {
super(message);
this.message = message;
this.isRetryable = isRetryable;
this.name = 'IDPError';
}
}
exports.IDPError = IDPError;
/**
* TokenManager is responsible for obtaining/refreshing tokens and notifying listeners about token changes.
* It uses an IdentityProvider to request tokens. The token refresh is scheduled based on the token's TTL and
* the expirationRefreshRatio configuration.
*
* The TokenManager should be disposed when it is no longer needed by calling the dispose method on the Disposable
* returned by start.
*/
class TokenManager {
identityProvider;
config;
currentToken = null;
refreshTimeout = null;
listener = null;
retryAttempt = 0;
constructor(identityProvider, config) {
this.identityProvider = identityProvider;
this.config = config;
if (this.config.expirationRefreshRatio > 1) {
throw new Error('expirationRefreshRatio must be less than or equal to 1');
}
if (this.config.expirationRefreshRatio < 0) {
throw new Error('expirationRefreshRatio must be greater or equal to 0');
}
}
/**
* Starts the token manager and returns a Disposable that can be used to stop the token manager.
*
* @param listener The listener that will receive token updates.
* @param initialDelayMs The initial delay in milliseconds before the first token refresh.
*/
start(listener, initialDelayMs = 0) {
if (this.listener) {
this.stop();
}
this.listener = listener;
this.retryAttempt = 0;
this.scheduleNextRefresh(initialDelayMs);
return {
dispose: () => this.stop()
};
}
calculateRetryDelay() {
if (!this.config.retry)
return 0;
const { initialDelayMs, maxDelayMs, backoffMultiplier, jitterPercentage } = this.config.retry;
let delay = initialDelayMs * Math.pow(backoffMultiplier, this.retryAttempt - 1);
delay = Math.min(delay, maxDelayMs);
if (jitterPercentage) {
const jitterRange = delay * (jitterPercentage / 100);
const jitterAmount = Math.random() * jitterRange - (jitterRange / 2);
delay += jitterAmount;
}
let result = Math.max(0, Math.floor(delay));
return result;
}
shouldRetry(error) {
if (!this.config.retry)
return false;
const { maxAttempts, isRetryable } = this.config.retry;
if (this.retryAttempt >= maxAttempts) {
return false;
}
if (isRetryable) {
return isRetryable(error, this.retryAttempt);
}
return false;
}
isRunning() {
return this.listener !== null;
}
async refresh() {
if (!this.listener) {
throw new Error('TokenManager is not running, but refresh was called');
}
try {
await this.identityProvider.requestToken().then(this.handleNewToken);
this.retryAttempt = 0;
}
catch (error) {
if (this.shouldRetry(error)) {
this.retryAttempt++;
const retryDelay = this.calculateRetryDelay();
this.notifyError(`Token refresh failed (attempt ${this.retryAttempt}), retrying in ${retryDelay}ms: ${error}`, true);
this.scheduleNextRefresh(retryDelay);
}
else {
this.notifyError(error, false);
this.stop();
}
}
}
handleNewToken = async ({ token: nativeToken, ttlMs }) => {
if (!this.listener) {
throw new Error('TokenManager is not running, but a new token was received');
}
const token = this.wrapAndSetCurrentToken(nativeToken, ttlMs);
this.listener.onNext(token);
this.scheduleNextRefresh(this.calculateRefreshTime(token));
};
/**
* Creates a Token object from a native token and sets it as the current token.
*
* @param nativeToken - The raw token received from the identity provider
* @param ttlMs - Time-to-live in milliseconds for the token
*
* @returns A new Token instance containing the wrapped native token and expiration details
*
*/
wrapAndSetCurrentToken(nativeToken, ttlMs) {
const now = Date.now();
const token = new token_1.Token(nativeToken, now + ttlMs, now);
this.currentToken = token;
return token;
}
scheduleNextRefresh(delayMs) {
if (this.refreshTimeout) {
clearTimeout(this.refreshTimeout);
this.refreshTimeout = null;
}
if (delayMs === 0) {
this.refresh();
}
else {
this.refreshTimeout = setTimeout(() => this.refresh(), delayMs);
}
}
/**
* Calculates the time in milliseconds when the token should be refreshed
* based on the token's TTL and the expirationRefreshRatio configuration.
*
* @param token The token to calculate the refresh time for.
* @param now The current time in milliseconds. Defaults to Date.now().
*/
calculateRefreshTime(token, now = Date.now()) {
const ttlMs = token.getTtlMs(now);
return Math.floor(ttlMs * this.config.expirationRefreshRatio);
}
stop() {
if (this.refreshTimeout) {
clearTimeout(this.refreshTimeout);
this.refreshTimeout = null;
}
this.listener = null;
this.currentToken = null;
this.retryAttempt = 0;
}
/**
* Returns the current token or null if no token is available.
*/
getCurrentToken() {
return this.currentToken;
}
notifyError(error, isRetryable) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (!this.listener) {
throw new Error(`TokenManager is not running but received an error: ${errorMessage}`);
}
this.listener.onError(new IDPError(errorMessage, isRetryable));
}
}
exports.TokenManager = TokenManager;
//# sourceMappingURL=token-manager.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
/**
* A token that can be used to authenticate with a service.
*/
export declare class Token<T> {
readonly value: T;
readonly expiresAtMs: number;
readonly receivedAtMs: number;
constructor(value: T, expiresAtMs: number, receivedAtMs: number);
/**
* Returns the time-to-live of the token in milliseconds.
* @param now The current time in milliseconds since the Unix epoch.
*/
getTtlMs(now: number): number;
}
//# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../lib/authx/token.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,KAAK,CAAC,CAAC;aAEA,KAAK,EAAE,CAAC;aAER,WAAW,EAAE,MAAM;aAEnB,YAAY,EAAE,MAAM;gBAJpB,KAAK,EAAE,CAAC,EAER,WAAW,EAAE,MAAM,EAEnB,YAAY,EAAE,MAAM;IAGtC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAM9B"}
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Token = void 0;
/**
* A token that can be used to authenticate with a service.
*/
class Token {
value;
expiresAtMs;
receivedAtMs;
constructor(value,
//represents the token deadline - the time in milliseconds since the Unix epoch at which the token expires
expiresAtMs,
//represents the time in milliseconds since the Unix epoch at which the token was received
receivedAtMs) {
this.value = value;
this.expiresAtMs = expiresAtMs;
this.receivedAtMs = receivedAtMs;
}
/**
* Returns the time-to-live of the token in milliseconds.
* @param now The current time in milliseconds since the Unix epoch.
*/
getTtlMs(now) {
if (this.expiresAtMs < now) {
return 0;
}
return this.expiresAtMs - now;
}
}
exports.Token = Token;
//# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../lib/authx/token.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,MAAa,KAAK;IAEE;IAEA;IAEA;IALlB,YACkB,KAAQ;IACxB,0GAA0G;IAC1F,WAAmB;IACnC,0FAA0F;IAC1E,YAAoB;QAJpB,UAAK,GAAL,KAAK,CAAG;QAER,gBAAW,GAAX,WAAW,CAAQ;QAEnB,iBAAY,GAAZ,YAAY,CAAQ;IACnC,CAAC;IAEJ;;;OAGG;IACH,QAAQ,CAAC,GAAW;QAClB,IAAI,IAAI,CAAC,WAAW,GAAG,GAAG,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;IAChC,CAAC;CACF;AAnBD,sBAmBC","sourcesContent":["/**\n * A token that can be used to authenticate with a service.\n */\nexport class Token<T> {\n constructor(\n public readonly value: T,\n //represents the token deadline - the time in milliseconds since the Unix epoch at which the token expires\n public readonly expiresAtMs: number,\n //represents the time in milliseconds since the Unix epoch at which the token was received\n public readonly receivedAtMs: number\n ) {}\n\n /**\n * Returns the time-to-live of the token in milliseconds.\n * @param now The current time in milliseconds since the Unix epoch.\n */\n getTtlMs(now: number): number {\n if (this.expiresAtMs < now) {\n return 0;\n }\n return this.expiresAtMs - now;\n }\n}"]}
@@ -0,0 +1,298 @@
/// <reference types="node" />
/// <reference types="node" />
import { EventEmitter } from 'stream';
import RedisClient from '.';
import { RedisArgument, ReplyUnion, TransformReply, TypeMapping } from '../RESP/types';
import { BasicCommandParser } from './parser';
/**
* A snapshot of cache statistics.
*
* This class provides an immutable view of the cache's operational statistics at a particular
* point in time. It is heavily inspired by the statistics reporting capabilities found in
* Ben Manes's Caffeine cache (https://github.com/ben-manes/caffeine).
*
* Instances of `CacheStats` are typically obtained from a {@link StatsCounter} and can be used
* for performance monitoring, debugging, or logging. It includes metrics such as hit rate,
* miss rate, load success/failure rates, average load penalty, and eviction counts.
*
* All statistics are non-negative. Rates and averages are typically in the range `[0.0, 1.0]`,
* or `0` if the an operation has not occurred (e.g. hit rate is 0 if there are no requests).
*
* Cache statistics are incremented according to specific rules:
* - When a cache lookup encounters an existing entry, hitCount is incremented.
* - When a cache lookup encounters a missing entry, missCount is incremented.
* - When a new entry is successfully loaded, loadSuccessCount is incremented and the
* loading time is added to totalLoadTime.
* - When an entry fails to load, loadFailureCount is incremented and the
* loading time is added to totalLoadTime.
* - When an entry is evicted due to size constraints or expiration,
* evictionCount is incremented.
*/
export declare class CacheStats {
readonly hitCount: number;
readonly missCount: number;
readonly loadSuccessCount: number;
readonly loadFailureCount: number;
readonly totalLoadTime: number;
readonly evictionCount: number;
/**
* Creates a new CacheStats instance with the specified statistics.
*/
private constructor();
/**
* Creates a new CacheStats instance with the specified statistics.
*
* @param hitCount - Number of cache hits
* @param missCount - Number of cache misses
* @param loadSuccessCount - Number of successful cache loads
* @param loadFailureCount - Number of failed cache loads
* @param totalLoadTime - Total load time in milliseconds
* @param evictionCount - Number of cache evictions
*/
static of(hitCount?: number, missCount?: number, loadSuccessCount?: number, loadFailureCount?: number, totalLoadTime?: number, evictionCount?: number): CacheStats;
/**
* Returns a statistics instance where no cache events have been recorded.
*
* @returns An empty statistics instance
*/
static empty(): CacheStats;
/**
* An empty stats instance with all counters set to zero.
*/
private static readonly EMPTY_STATS;
/**
* Returns the total number of times cache lookup methods have returned
* either a cached or uncached value.
*
* @returns Total number of requests (hits + misses)
*/
requestCount(): number;
/**
* Returns the hit rate of the cache.
* This is defined as hitCount / requestCount, or 1.0 when requestCount is 0.
*
* @returns The ratio of cache requests that were hits (between 0.0 and 1.0)
*/
hitRate(): number;
/**
* Returns the miss rate of the cache.
* This is defined as missCount / requestCount, or 0.0 when requestCount is 0.
*
* @returns The ratio of cache requests that were misses (between 0.0 and 1.0)
*/
missRate(): number;
/**
* Returns the total number of load operations (successful + failed).
*
* @returns Total number of load operations
*/
loadCount(): number;
/**
* Returns the ratio of cache loading attempts that failed.
* This is defined as loadFailureCount / loadCount, or 0.0 when loadCount is 0.
*
* @returns Ratio of load operations that failed (between 0.0 and 1.0)
*/
loadFailureRate(): number;
/**
* Returns the average time spent loading new values, in milliseconds.
* This is defined as totalLoadTime / loadCount, or 0.0 when loadCount is 0.
*
* @returns Average load time in milliseconds
*/
averageLoadPenalty(): number;
/**
* Returns a new CacheStats representing the difference between this CacheStats
* and another. Negative values are rounded up to zero.
*
* @param other - The statistics to subtract from this instance
* @returns The difference between this instance and other
*/
minus(other: CacheStats): CacheStats;
/**
* Returns a new CacheStats representing the sum of this CacheStats and another.
*
* @param other - The statistics to add to this instance
* @returns The sum of this instance and other
*/
plus(other: CacheStats): CacheStats;
}
/**
* An accumulator for cache statistics.
*
* This interface defines the contract for objects that record cache-related events
* such as hits, misses, loads (successes and failures), and evictions. The design
* is inspired by the statistics collection mechanisms in Ben Manes's Caffeine cache
* (https://github.com/ben-manes/caffeine).
*
* Implementations of this interface are responsible for aggregating these events.
* A snapshot of the current statistics can be obtained by calling the `snapshot()`
* method, which returns an immutable {@link CacheStats} object.
*
* Common implementations include `DefaultStatsCounter` for active statistics collection
* and `DisabledStatsCounter` for a no-op version when stats are not needed.
*/
export interface StatsCounter {
/**
* Records cache hits. This should be called when a cache request returns a cached value.
*
* @param count - The number of hits to record
*/
recordHits(count: number): void;
/**
* Records cache misses. This should be called when a cache request returns a value that was not
* found in the cache.
*
* @param count - The number of misses to record
*/
recordMisses(count: number): void;
/**
* Records the successful load of a new entry. This method should be called when a cache request
* causes an entry to be loaded and the loading completes successfully.
*
* @param loadTime - The number of milliseconds the cache spent computing or retrieving the new value
*/
recordLoadSuccess(loadTime: number): void;
/**
* Records the failed load of a new entry. This method should be called when a cache request
* causes an entry to be loaded, but an exception is thrown while loading the entry.
*
* @param loadTime - The number of milliseconds the cache spent computing or retrieving the new value
* prior to the failure
*/
recordLoadFailure(loadTime: number): void;
/**
* Records the eviction of an entry from the cache. This should only be called when an entry is
* evicted due to the cache's eviction strategy, and not as a result of manual invalidations.
*
* @param count - The number of evictions to record
*/
recordEvictions(count: number): void;
/**
* Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as it
* may be interleaved with update operations.
*
* @return A snapshot of this counter's values
*/
snapshot(): CacheStats;
}
type CachingClient = RedisClient<any, any, any, any, any>;
type CmdFunc = () => Promise<ReplyUnion>;
type EvictionPolicy = "LRU" | "FIFO";
/**
* Configuration options for Client Side Cache
*/
export interface ClientSideCacheConfig {
/**
* Time-to-live in milliseconds for cached entries.
* Use 0 for no expiration.
* @default 0
*/
ttl?: number;
/**
* Maximum number of entries to store in the cache.
* Use 0 for unlimited entries.
* @default 0
*/
maxEntries?: number;
/**
* Eviction policy to use when the cache reaches its capacity.
* - "LRU" (Least Recently Used): Evicts least recently accessed entries first
* - "FIFO" (First In First Out): Evicts oldest entries first
* @default "LRU"
*/
evictPolicy?: EvictionPolicy;
/**
* Whether to collect statistics about cache operations.
* @default true
*/
recordStats?: boolean;
}
interface ClientSideCacheEntry {
invalidate(): void;
validate(): boolean;
}
declare abstract class ClientSideCacheEntryBase implements ClientSideCacheEntry {
#private;
constructor(ttl: number);
invalidate(): void;
validate(): boolean;
}
declare class ClientSideCacheEntryValue extends ClientSideCacheEntryBase {
#private;
get value(): any;
constructor(ttl: number, value: any);
}
declare class ClientSideCacheEntryPromise extends ClientSideCacheEntryBase {
#private;
get promise(): Promise<ReplyUnion>;
constructor(ttl: number, sendCommandPromise: Promise<ReplyUnion>);
}
export declare abstract class ClientSideCacheProvider extends EventEmitter {
abstract handleCache(client: CachingClient, parser: BasicCommandParser, fn: CmdFunc, transformReply: TransformReply | undefined, typeMapping: TypeMapping | undefined): Promise<any>;
abstract trackingOn(): Array<RedisArgument>;
abstract invalidate(key: RedisArgument | null): void;
abstract clear(): void;
abstract stats(): CacheStats;
abstract size(): number;
abstract onError(): void;
abstract onClose(): void;
}
export declare class BasicClientSideCache extends ClientSideCacheProvider {
#private;
readonly ttl: number;
readonly maxEntries: number;
readonly lru: boolean;
recordEvictions(count: number): void;
recordHits(count: number): void;
recordMisses(count: number): void;
constructor(config?: ClientSideCacheConfig);
handleCache(client: CachingClient, parser: BasicCommandParser, fn: CmdFunc, transformReply?: TransformReply, typeMapping?: TypeMapping): Promise<any>;
trackingOn(): string[];
invalidate(key: RedisArgument | null): void;
clear(resetStats?: boolean): void;
get(cacheKey: string): ClientSideCacheEntry | undefined;
delete(cacheKey: string): void;
has(cacheKey: string): boolean;
set(cacheKey: string, cacheEntry: ClientSideCacheEntry, keys: Array<RedisArgument>): void;
size(): number;
createValueEntry(client: CachingClient, value: any): ClientSideCacheEntryValue;
createPromiseEntry(client: CachingClient, sendCommandPromise: Promise<ReplyUnion>): ClientSideCacheEntryPromise;
stats(): CacheStats;
onError(): void;
onClose(): void;
/**
* @internal
*/
deleteOldest(): void;
/**
* Get cache entries for debugging
* @internal
*/
entryEntries(): IterableIterator<[string, ClientSideCacheEntry]>;
/**
* Get key set entries for debugging
* @internal
*/
keySetEntries(): IterableIterator<[string, Set<string>]>;
}
export declare abstract class PooledClientSideCacheProvider extends BasicClientSideCache {
#private;
disable(): void;
enable(): void;
get(cacheKey: string): ClientSideCacheEntry | undefined;
has(cacheKey: string): boolean;
onPoolClose(): void;
}
export declare class BasicPooledClientSideCache extends PooledClientSideCacheProvider {
onError(): void;
onClose(): void;
}
export declare class PooledNoRedirectClientSideCache extends BasicPooledClientSideCache {
createValueEntry(client: CachingClient, value: any): ClientSideCacheEntryValue;
createPromiseEntry(client: CachingClient, sendCommandPromise: Promise<ReplyUnion>): ClientSideCacheEntryPromise;
onError(): void;
onClose(): void;
}
export {};
//# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../lib/client/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,634 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PooledNoRedirectClientSideCache = exports.BasicPooledClientSideCache = exports.PooledClientSideCacheProvider = exports.BasicClientSideCache = exports.ClientSideCacheProvider = exports.CacheStats = void 0;
const stream_1 = require("stream");
const tracing_1 = require("./tracing");
/**
* A snapshot of cache statistics.
*
* This class provides an immutable view of the cache's operational statistics at a particular
* point in time. It is heavily inspired by the statistics reporting capabilities found in
* Ben Manes's Caffeine cache (https://github.com/ben-manes/caffeine).
*
* Instances of `CacheStats` are typically obtained from a {@link StatsCounter} and can be used
* for performance monitoring, debugging, or logging. It includes metrics such as hit rate,
* miss rate, load success/failure rates, average load penalty, and eviction counts.
*
* All statistics are non-negative. Rates and averages are typically in the range `[0.0, 1.0]`,
* or `0` if the an operation has not occurred (e.g. hit rate is 0 if there are no requests).
*
* Cache statistics are incremented according to specific rules:
* - When a cache lookup encounters an existing entry, hitCount is incremented.
* - When a cache lookup encounters a missing entry, missCount is incremented.
* - When a new entry is successfully loaded, loadSuccessCount is incremented and the
* loading time is added to totalLoadTime.
* - When an entry fails to load, loadFailureCount is incremented and the
* loading time is added to totalLoadTime.
* - When an entry is evicted due to size constraints or expiration,
* evictionCount is incremented.
*/
class CacheStats {
hitCount;
missCount;
loadSuccessCount;
loadFailureCount;
totalLoadTime;
evictionCount;
/**
* Creates a new CacheStats instance with the specified statistics.
*/
constructor(hitCount, missCount, loadSuccessCount, loadFailureCount, totalLoadTime, evictionCount) {
this.hitCount = hitCount;
this.missCount = missCount;
this.loadSuccessCount = loadSuccessCount;
this.loadFailureCount = loadFailureCount;
this.totalLoadTime = totalLoadTime;
this.evictionCount = evictionCount;
if (hitCount < 0 ||
missCount < 0 ||
loadSuccessCount < 0 ||
loadFailureCount < 0 ||
totalLoadTime < 0 ||
evictionCount < 0) {
throw new Error('All statistics values must be non-negative');
}
}
/**
* Creates a new CacheStats instance with the specified statistics.
*
* @param hitCount - Number of cache hits
* @param missCount - Number of cache misses
* @param loadSuccessCount - Number of successful cache loads
* @param loadFailureCount - Number of failed cache loads
* @param totalLoadTime - Total load time in milliseconds
* @param evictionCount - Number of cache evictions
*/
static of(hitCount = 0, missCount = 0, loadSuccessCount = 0, loadFailureCount = 0, totalLoadTime = 0, evictionCount = 0) {
return new CacheStats(hitCount, missCount, loadSuccessCount, loadFailureCount, totalLoadTime, evictionCount);
}
/**
* Returns a statistics instance where no cache events have been recorded.
*
* @returns An empty statistics instance
*/
static empty() {
return CacheStats.EMPTY_STATS;
}
/**
* An empty stats instance with all counters set to zero.
*/
static EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);
/**
* Returns the total number of times cache lookup methods have returned
* either a cached or uncached value.
*
* @returns Total number of requests (hits + misses)
*/
requestCount() {
return this.hitCount + this.missCount;
}
/**
* Returns the hit rate of the cache.
* This is defined as hitCount / requestCount, or 1.0 when requestCount is 0.
*
* @returns The ratio of cache requests that were hits (between 0.0 and 1.0)
*/
hitRate() {
const requestCount = this.requestCount();
return requestCount === 0 ? 1.0 : this.hitCount / requestCount;
}
/**
* Returns the miss rate of the cache.
* This is defined as missCount / requestCount, or 0.0 when requestCount is 0.
*
* @returns The ratio of cache requests that were misses (between 0.0 and 1.0)
*/
missRate() {
const requestCount = this.requestCount();
return requestCount === 0 ? 0.0 : this.missCount / requestCount;
}
/**
* Returns the total number of load operations (successful + failed).
*
* @returns Total number of load operations
*/
loadCount() {
return this.loadSuccessCount + this.loadFailureCount;
}
/**
* Returns the ratio of cache loading attempts that failed.
* This is defined as loadFailureCount / loadCount, or 0.0 when loadCount is 0.
*
* @returns Ratio of load operations that failed (between 0.0 and 1.0)
*/
loadFailureRate() {
const loadCount = this.loadCount();
return loadCount === 0 ? 0.0 : this.loadFailureCount / loadCount;
}
/**
* Returns the average time spent loading new values, in milliseconds.
* This is defined as totalLoadTime / loadCount, or 0.0 when loadCount is 0.
*
* @returns Average load time in milliseconds
*/
averageLoadPenalty() {
const loadCount = this.loadCount();
return loadCount === 0 ? 0.0 : this.totalLoadTime / loadCount;
}
/**
* Returns a new CacheStats representing the difference between this CacheStats
* and another. Negative values are rounded up to zero.
*
* @param other - The statistics to subtract from this instance
* @returns The difference between this instance and other
*/
minus(other) {
return CacheStats.of(Math.max(0, this.hitCount - other.hitCount), Math.max(0, this.missCount - other.missCount), Math.max(0, this.loadSuccessCount - other.loadSuccessCount), Math.max(0, this.loadFailureCount - other.loadFailureCount), Math.max(0, this.totalLoadTime - other.totalLoadTime), Math.max(0, this.evictionCount - other.evictionCount));
}
/**
* Returns a new CacheStats representing the sum of this CacheStats and another.
*
* @param other - The statistics to add to this instance
* @returns The sum of this instance and other
*/
plus(other) {
return CacheStats.of(this.hitCount + other.hitCount, this.missCount + other.missCount, this.loadSuccessCount + other.loadSuccessCount, this.loadFailureCount + other.loadFailureCount, this.totalLoadTime + other.totalLoadTime, this.evictionCount + other.evictionCount);
}
}
exports.CacheStats = CacheStats;
/**
* A StatsCounter implementation that does nothing and always returns empty stats.
*/
class DisabledStatsCounter {
static INSTANCE = new DisabledStatsCounter();
constructor() { }
recordHits(count) { }
recordMisses(count) { }
recordLoadSuccess(loadTime) { }
recordLoadFailure(loadTime) { }
recordEvictions(count) { }
snapshot() { return CacheStats.empty(); }
}
/**
* Returns a StatsCounter that does not record any cache events.
*
* @return A StatsCounter that does not record metrics
*/
function disabledStatsCounter() {
return DisabledStatsCounter.INSTANCE;
}
/**
* A StatsCounter implementation that maintains cache statistics.
*/
class DefaultStatsCounter {
#hitCount = 0;
#missCount = 0;
#loadSuccessCount = 0;
#loadFailureCount = 0;
#totalLoadTime = 0;
#evictionCount = 0;
/**
* Records cache hits.
*
* @param count - The number of hits to record
*/
recordHits(count) {
this.#hitCount += count;
}
/**
* Records cache misses.
*
* @param count - The number of misses to record
*/
recordMisses(count) {
this.#missCount += count;
}
/**
* Records the successful load of a new entry.
*
* @param loadTime - The number of milliseconds spent loading the entry
*/
recordLoadSuccess(loadTime) {
this.#loadSuccessCount++;
this.#totalLoadTime += loadTime;
}
/**
* Records the failed load of a new entry.
*
* @param loadTime - The number of milliseconds spent attempting to load the entry
*/
recordLoadFailure(loadTime) {
this.#loadFailureCount++;
this.#totalLoadTime += loadTime;
}
/**
* Records cache evictions.
*
* @param count - The number of evictions to record
*/
recordEvictions(count) {
this.#evictionCount += count;
}
/**
* Returns a snapshot of the current statistics.
*
* @returns A snapshot of the current statistics
*/
snapshot() {
return CacheStats.of(this.#hitCount, this.#missCount, this.#loadSuccessCount, this.#loadFailureCount, this.#totalLoadTime, this.#evictionCount);
}
/**
* Creates a new DefaultStatsCounter.
*
* @returns A new DefaultStatsCounter instance
*/
static create() {
return new DefaultStatsCounter();
}
}
/**
* Generates a unique cache key from Redis command arguments
*
* @param redisArgs - Array of Redis command arguments
* @returns A unique string key for caching
*/
function generateCacheKey(redisArgs) {
const tmp = new Array(redisArgs.length * 2);
for (let i = 0; i < redisArgs.length; i++) {
tmp[i] = redisArgs[i].length;
tmp[i + redisArgs.length] = redisArgs[i];
}
return tmp.join('_');
}
class ClientSideCacheEntryBase {
#invalidated = false;
#expireTime;
constructor(ttl) {
if (ttl == 0) {
this.#expireTime = 0;
}
else {
this.#expireTime = Date.now() + ttl;
}
}
invalidate() {
this.#invalidated = true;
}
validate() {
return !this.#invalidated && (this.#expireTime == 0 || (Date.now() < this.#expireTime));
}
}
class ClientSideCacheEntryValue extends ClientSideCacheEntryBase {
#value;
get value() {
return this.#value;
}
constructor(ttl, value) {
super(ttl);
this.#value = value;
}
}
class ClientSideCacheEntryPromise extends ClientSideCacheEntryBase {
#sendCommandPromise;
get promise() {
return this.#sendCommandPromise;
}
constructor(ttl, sendCommandPromise) {
super(ttl);
this.#sendCommandPromise = sendCommandPromise;
}
}
class ClientSideCacheProvider extends stream_1.EventEmitter {
}
exports.ClientSideCacheProvider = ClientSideCacheProvider;
class BasicClientSideCache extends ClientSideCacheProvider {
#cacheKeyToEntryMap;
#keyToCacheKeySetMap;
ttl;
maxEntries;
lru;
#statsCounter;
recordEvictions(count) {
this.#statsCounter.recordEvictions(count);
}
recordHits(count) {
this.#statsCounter.recordHits(count);
}
recordMisses(count) {
this.#statsCounter.recordMisses(count);
}
constructor(config) {
super();
this.#cacheKeyToEntryMap = new Map();
this.#keyToCacheKeySetMap = new Map();
this.ttl = config?.ttl ?? 0;
this.maxEntries = config?.maxEntries ?? 0;
this.lru = config?.evictPolicy !== "FIFO";
const recordStats = config?.recordStats !== false;
this.#statsCounter = recordStats ? DefaultStatsCounter.create() : disabledStatsCounter();
}
/* logic of how caching works:
1. commands use a CommandParser
it enables us to define/retrieve
cacheKey - a unique key that corresponds to this command and its arguments
redisKeys - an array of redis keys as strings that if the key is modified, will cause redis to invalidate this result when cached
2. check if cacheKey is in our cache
2b1. if its a value cacheEntry - return it
2b2. if it's a promise cache entry - wait on promise and then go to 3c.
3. if cacheEntry is not in cache
3a. send the command save the promise into a a cacheEntry and then wait on result
3b. transform reply (if required) based on transformReply
3b. check the cacheEntry is still valid - in cache and hasn't been deleted)
3c. if valid - overwrite with value entry
4. return previously non cached result
*/
async handleCache(client, parser, fn, transformReply, typeMapping) {
let reply;
const cacheKey = generateCacheKey(parser.redisArgs);
// "2"
let cacheEntry = this.get(cacheKey);
if (cacheEntry) {
// If instanceof is "too slow", can add a "type" and then use an "as" cast to call proper getters.
if (cacheEntry instanceof ClientSideCacheEntryValue) { // "2b1"
this.#statsCounter.recordHits(1);
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_REQUEST, () => ({ result: 'hit', clientId: client._clientId }));
return structuredClone(cacheEntry.value);
}
else if (cacheEntry instanceof ClientSideCacheEntryPromise) { // 2b2
// This counts as a miss since the value hasn't been fully loaded yet.
this.#statsCounter.recordMisses(1);
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_REQUEST, () => ({ result: 'miss', clientId: client._clientId }));
reply = await cacheEntry.promise;
}
else {
throw new Error("unknown cache entry type");
}
}
else { // 3/3a
this.#statsCounter.recordMisses(1);
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_REQUEST, () => ({ result: 'miss', clientId: client._clientId }));
const startTime = performance.now();
const promise = fn();
cacheEntry = this.createPromiseEntry(client, promise);
this.set(cacheKey, cacheEntry, parser.keys);
try {
reply = await promise;
const loadTime = performance.now() - startTime;
this.#statsCounter.recordLoadSuccess(loadTime);
}
catch (err) {
const loadTime = performance.now() - startTime;
this.#statsCounter.recordLoadFailure(loadTime);
if (cacheEntry.validate()) {
this.delete(cacheKey);
}
throw err;
}
}
// 3b
let val;
if (transformReply) {
val = transformReply(reply, parser.preserve, typeMapping);
}
else {
val = reply;
}
// 3c
if (cacheEntry.validate()) { // revalidating promise entry (dont save value, if promise entry has been invalidated)
// 3d
cacheEntry = this.createValueEntry(client, val);
this.set(cacheKey, cacheEntry, parser.keys);
this.emit("cached-key", cacheKey);
}
else {
// cache entry for key got invalidated between execution and saving, so not saving
}
return structuredClone(val);
}
trackingOn() {
return ['CLIENT', 'TRACKING', 'ON'];
}
invalidate(key) {
if (key === null) {
// Server requested to invalidate all keys
const oldSize = this.size();
this.clear(false);
// Record invalidations as server-initiated evictions
if (oldSize > 0) {
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_EVICTION, () => ({ reason: 'invalidation', count: oldSize }));
}
this.emit("invalidate", key);
return;
}
const keySet = this.#keyToCacheKeySetMap.get(key.toString());
if (keySet) {
let deletedCount = 0;
for (const cacheKey of keySet) {
const entry = this.#cacheKeyToEntryMap.get(cacheKey);
if (entry) {
entry.invalidate();
deletedCount++;
}
this.#cacheKeyToEntryMap.delete(cacheKey);
}
this.#keyToCacheKeySetMap.delete(key.toString());
if (deletedCount > 0) {
// Record invalidations as server-initiated evictions
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_EVICTION, () => ({ reason: 'invalidation', count: deletedCount }));
}
}
this.emit('invalidate', key);
}
clear(resetStats = true) {
const oldSize = this.#cacheKeyToEntryMap.size;
this.#cacheKeyToEntryMap.clear();
this.#keyToCacheKeySetMap.clear();
if (resetStats) {
if (!(this.#statsCounter instanceof DisabledStatsCounter)) {
this.#statsCounter = DefaultStatsCounter.create();
}
}
else {
// If old entries were evicted due to clear, record them as evictions
if (oldSize > 0) {
this.#statsCounter.recordEvictions(oldSize);
}
}
}
get(cacheKey) {
const val = this.#cacheKeyToEntryMap.get(cacheKey);
if (val && !val.validate()) {
this.delete(cacheKey);
this.#statsCounter.recordEvictions(1);
// Entry failed validation - this is TTL expiry since invalidation marks are handled separately
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_EVICTION, () => ({ reason: 'ttl', count: 1 }));
this.emit("cache-evict", cacheKey);
return undefined;
}
if (val !== undefined && this.lru) {
this.#cacheKeyToEntryMap.delete(cacheKey);
this.#cacheKeyToEntryMap.set(cacheKey, val);
}
return val;
}
delete(cacheKey) {
const entry = this.#cacheKeyToEntryMap.get(cacheKey);
if (entry) {
entry.invalidate();
this.#cacheKeyToEntryMap.delete(cacheKey);
}
}
has(cacheKey) {
return this.#cacheKeyToEntryMap.has(cacheKey);
}
set(cacheKey, cacheEntry, keys) {
let count = this.#cacheKeyToEntryMap.size;
const oldEntry = this.#cacheKeyToEntryMap.get(cacheKey);
if (oldEntry) {
count--; // overwriting, so not incrementing
oldEntry.invalidate();
}
if (this.maxEntries > 0 && count >= this.maxEntries) {
this.deleteOldest();
this.#statsCounter.recordEvictions(1);
// Eviction due to cache capacity limit
(0, tracing_1.publish)(tracing_1.CHANNELS.CACHE_EVICTION, () => ({ reason: 'full', count: 1 }));
}
this.#cacheKeyToEntryMap.set(cacheKey, cacheEntry);
for (const key of keys) {
if (!this.#keyToCacheKeySetMap.has(key.toString())) {
this.#keyToCacheKeySetMap.set(key.toString(), new Set());
}
const cacheKeySet = this.#keyToCacheKeySetMap.get(key.toString());
cacheKeySet.add(cacheKey);
}
}
size() {
return this.#cacheKeyToEntryMap.size;
}
createValueEntry(client, value) {
return new ClientSideCacheEntryValue(this.ttl, value);
}
createPromiseEntry(client, sendCommandPromise) {
return new ClientSideCacheEntryPromise(this.ttl, sendCommandPromise);
}
stats() {
return this.#statsCounter.snapshot();
}
onError() {
this.clear();
}
onClose() {
this.clear();
}
/**
* @internal
*/
deleteOldest() {
const it = this.#cacheKeyToEntryMap[Symbol.iterator]();
const n = it.next();
if (!n.done) {
const key = n.value[0];
const entry = this.#cacheKeyToEntryMap.get(key);
if (entry) {
entry.invalidate();
}
this.#cacheKeyToEntryMap.delete(key);
}
}
/**
* Get cache entries for debugging
* @internal
*/
entryEntries() {
return this.#cacheKeyToEntryMap.entries();
}
/**
* Get key set entries for debugging
* @internal
*/
keySetEntries() {
return this.#keyToCacheKeySetMap.entries();
}
}
exports.BasicClientSideCache = BasicClientSideCache;
class PooledClientSideCacheProvider extends BasicClientSideCache {
#disabled = false;
disable() {
this.#disabled = true;
}
enable() {
this.#disabled = false;
}
get(cacheKey) {
if (this.#disabled) {
return undefined;
}
return super.get(cacheKey);
}
has(cacheKey) {
if (this.#disabled) {
return false;
}
return super.has(cacheKey);
}
onPoolClose() {
this.clear();
}
}
exports.PooledClientSideCacheProvider = PooledClientSideCacheProvider;
class BasicPooledClientSideCache extends PooledClientSideCacheProvider {
onError() {
this.clear(false);
}
onClose() {
this.clear(false);
}
}
exports.BasicPooledClientSideCache = BasicPooledClientSideCache;
class PooledClientSideCacheEntryValue extends ClientSideCacheEntryValue {
#creator;
constructor(ttl, creator, value) {
super(ttl, value);
this.#creator = creator;
}
validate() {
let ret = super.validate();
if (this.#creator) {
ret = ret && this.#creator.client.isReady && this.#creator.client.socketEpoch == this.#creator.epoch;
}
return ret;
}
}
class PooledClientSideCacheEntryPromise extends ClientSideCacheEntryPromise {
#creator;
constructor(ttl, creator, sendCommandPromise) {
super(ttl, sendCommandPromise);
this.#creator = creator;
}
validate() {
let ret = super.validate();
return ret && this.#creator.client.isReady && this.#creator.client.socketEpoch == this.#creator.epoch;
}
}
class PooledNoRedirectClientSideCache extends BasicPooledClientSideCache {
createValueEntry(client, value) {
const creator = {
epoch: client.socketEpoch,
client: client
};
return new PooledClientSideCacheEntryValue(this.ttl, creator, value);
}
createPromiseEntry(client, sendCommandPromise) {
const creator = {
epoch: client.socketEpoch,
client: client
};
return new PooledClientSideCacheEntryPromise(this.ttl, creator, sendCommandPromise);
}
onError() { }
onClose() { }
}
exports.PooledNoRedirectClientSideCache = PooledNoRedirectClientSideCache;
//# sourceMappingURL=cache.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,101 @@
/// <reference types="node" />
import { Decoder } from '../RESP/decoder';
import { TypeMapping, RespVersions, RedisArgument } from '../RESP/types';
import { ChannelListeners, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub';
import { MonitorCallback } from '.';
export interface CommandOptions<T = TypeMapping> {
chainId?: symbol;
asap?: boolean;
abortSignal?: AbortSignal;
/**
* Maps between RESP and JavaScript types
*/
typeMapping?: T;
/**
* Timeout for the command in milliseconds
*/
timeout?: number;
/**
* @internal
* The slot the command is targeted to (if any)
*/
slotNumber?: number;
}
export interface CommandToWrite extends CommandWaitingForReply {
args: ReadonlyArray<RedisArgument> | undefined;
chainId: symbol | undefined;
abort: {
signal: AbortSignal;
listener: () => unknown;
} | undefined;
timeout: {
signal: AbortSignal;
listener: () => unknown;
originalTimeout: number | undefined;
} | undefined;
slotNumber?: number;
}
interface CommandWaitingForReply {
resolve(reply?: unknown): void;
reject(err: unknown): void;
channelsCounter: number | undefined;
typeMapping: TypeMapping | undefined;
}
export type OnShardedChannelMoved = (channel: string, listeners: ChannelListeners) => void;
type PushHandler = (pushItems: Array<any>) => boolean;
export default class RedisCommandsQueue {
#private;
readonly decoder: Decoder;
setMaintenanceCommandTimeout(ms: number | undefined): void;
get isPubSubActive(): boolean;
/**
* Returns the total number of pending commands (waiting to write + waiting for reply).
*/
get pendingCount(): number;
constructor(respVersion: RespVersions, maxLength: number | null | undefined, onShardedChannelMoved: OnShardedChannelMoved, clientId: string);
addPushHandler(handler: PushHandler): void;
waitForInflightCommandsToComplete(options?: {
timeoutMs?: number;
flushOnTimeout?: boolean;
}): Promise<void>;
addCommand<T>(args: ReadonlyArray<RedisArgument>, options?: CommandOptions): Promise<T>;
subscribe<T extends boolean>(type: PubSubType, channels: string | Array<string>, listener: PubSubListener<T>, returnBuffers?: T): Promise<void> | undefined;
unsubscribe<T extends boolean>(type: PubSubType, channels?: string | Array<string>, listener?: PubSubListener<T>, returnBuffers?: T): Promise<void> | undefined;
removeAllPubSubListeners(): {
CHANNELS: PubSubTypeListeners;
PATTERNS: PubSubTypeListeners;
SHARDED: PubSubTypeListeners;
};
removeShardedPubSubListenersForSlots(slots: Set<number>): {
SHARDED: Map<string, ChannelListeners>;
};
resubscribe(chainId?: symbol): Promise<void[]> | undefined;
extendPubSubChannelListeners(type: PubSubType, channel: string, listeners: ChannelListeners): Promise<void> | undefined;
extendPubSubListeners(type: PubSubType, listeners: PubSubTypeListeners): Promise<void> | undefined;
getPubSubListeners(type: PubSubType): PubSubTypeListeners;
monitor(callback: MonitorCallback, options?: CommandOptions): Promise<void>;
resetDecoder(): void;
reset<T extends TypeMapping>(chainId: symbol, typeMapping?: T): Promise<unknown>;
isWaitingToWrite(): boolean;
commandsToWrite(): Generator<readonly RedisArgument[], void, unknown>;
flushWaitingForReply(err: Error): void;
flushAll(err: Error): void;
isEmpty(): boolean;
/**
*
* Extracts commands for the given slots from the toWrite queue.
* Some commands don't have "slotNumber", which means they are not designated to particular slot/node.
* We ignore those.
*/
extractCommandsForSlots(slots: Set<number>): CommandToWrite[];
/**
* Gets all commands from the write queue without removing them.
*/
extractAllCommands(): CommandToWrite[];
/**
* Prepends commands to the write queue in reverse.
*/
prependCommandsToWrite(commands: CommandToWrite[]): void;
}
export {};
//# sourceMappingURL=commands-queue.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"commands-queue.d.ts","sourceRoot":"","sources":["../../../lib/client/commands-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,502 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const linked_list_1 = require("./linked-list");
const encoder_1 = __importDefault(require("../RESP/encoder"));
const decoder_1 = require("../RESP/decoder");
const pub_sub_1 = require("./pub-sub");
const errors_1 = require("../errors");
const enterprise_maintenance_manager_1 = require("./enterprise-maintenance-manager");
const PONG = Buffer.from('pong'), RESET = Buffer.from('RESET');
const RESP2_PUSH_TYPE_MAPPING = {
...decoder_1.PUSH_TYPE_MAPPING,
[decoder_1.RESP_TYPES.SIMPLE_STRING]: Buffer
};
class RedisCommandsQueue {
#respVersion;
#maxLength;
#toWrite = new linked_list_1.DoublyLinkedList();
#waitingForReply = new linked_list_1.EmptyAwareSinglyLinkedList();
#onShardedChannelMoved;
#chainInExecution;
decoder;
#pubSub;
#clientId;
#pushHandlers = [this.#onPush.bind(this)];
#maintenanceCommandTimeout;
setMaintenanceCommandTimeout(ms) {
// Prevent possible api misuse
if (this.#maintenanceCommandTimeout === ms) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Queue already set maintenanceCommandTimeout to ${ms}, skipping`);
return;
}
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Setting maintenance command timeout to ${ms}`);
this.#maintenanceCommandTimeout = ms;
if (this.#maintenanceCommandTimeout === undefined) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Queue will keep maintenanceCommandTimeout for exisitng commands, just to be on the safe side. New commands will receive normal timeouts`);
return;
}
let counter = 0;
const total = this.#toWrite.length;
// Overwrite timeouts of all eligible toWrite commands
for (const node of this.#toWrite.nodes()) {
const command = node.value;
// Remove timeout listener if it exists
RedisCommandsQueue.#removeTimeoutListener(command);
counter++;
const newTimeout = this.#maintenanceCommandTimeout;
// Overwrite the command's timeout
const signal = AbortSignal.timeout(newTimeout);
command.timeout = {
signal,
listener: () => {
this.#toWrite.remove(node);
command.reject(new errors_1.CommandTimeoutDuringMaintenanceError(newTimeout));
},
originalTimeout: command.timeout?.originalTimeout,
};
signal.addEventListener("abort", command.timeout.listener, {
once: true,
});
}
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Total of ${counter} of ${total} timeouts reset to ${ms}`);
}
get isPubSubActive() {
return this.#pubSub.isActive;
}
/**
* Returns the total number of pending commands (waiting to write + waiting for reply).
*/
get pendingCount() {
return this.#toWrite.length + this.#waitingForReply.length;
}
constructor(respVersion, maxLength, onShardedChannelMoved, clientId) {
this.#respVersion = respVersion;
this.#maxLength = maxLength;
this.#onShardedChannelMoved = onShardedChannelMoved;
this.decoder = this.#initiateDecoder();
this.#clientId = clientId;
this.#pubSub = new pub_sub_1.PubSub(this.#clientId);
}
#onReply(reply) {
this.#waitingForReply.shift().resolve(reply);
}
#onErrorReply(err) {
this.#waitingForReply.shift().reject(err);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- heterogeneous push payload
#onPush(push) {
// TODO: type
if (this.#pubSub.handleMessageReply(push))
return true;
const isShardedUnsubscribe = pub_sub_1.PubSub.isShardedUnsubscribe(push);
if (isShardedUnsubscribe && !this.#waitingForReply.length) {
const channel = push[1].toString();
this.#onShardedChannelMoved(channel, this.#pubSub.removeShardedListeners(channel));
return true;
}
else if (isShardedUnsubscribe || pub_sub_1.PubSub.isStatusReply(push)) {
const head = this.#waitingForReply.head.value;
if ((Number.isNaN(head.channelsCounter) && push[2] === 0) ||
--head.channelsCounter === 0) {
this.#waitingForReply.shift().resolve();
}
return true;
}
return false;
}
#getTypeMapping() {
const head = this.#waitingForReply.head;
if (!head)
return decoder_1.PUSH_TYPE_MAPPING;
return head.value.typeMapping ?? {};
}
#initiateDecoder() {
return new decoder_1.Decoder({
onReply: (reply) => this.#onReply(reply),
onErrorReply: (err) => this.#onErrorReply(err),
//TODO: we can shave off a few cycles by not adding onPush handler at all if CSC is not used
onPush: (push) => {
for (const pushHandler of this.#pushHandlers) {
if (pushHandler(push))
return;
}
},
getTypeMapping: () => this.#getTypeMapping(),
});
}
addPushHandler(handler) {
this.#pushHandlers.push(handler);
}
async waitForInflightCommandsToComplete(options) {
// In-flight commands already completed
if (this.#waitingForReply.length === 0) {
return;
}
// Otherwise wait for in-flight commands to fire `empty` event
return new Promise((resolve) => {
const onEmpty = () => {
if (timeoutId)
clearTimeout(timeoutId);
resolve();
};
let timeoutId;
const timeoutMs = options?.timeoutMs;
if (timeoutMs !== undefined && timeoutMs > 0) {
timeoutId = setTimeout(() => {
this.#waitingForReply.events.off("empty", onEmpty);
const pendingCount = this.#waitingForReply.length;
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`waitForInflightCommandsToComplete timed out after ${timeoutMs}ms with ${pendingCount} commands still waiting`);
if (options?.flushOnTimeout && pendingCount > 0) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Flushing ${pendingCount} commands that timed out waiting for reply`);
this.#flushWaitingForReply(new errors_1.TimeoutError());
}
resolve(); // Resolve instead of reject - we don't want to fail the migration
}, timeoutMs);
}
this.#waitingForReply.events.once("empty", onEmpty);
});
}
addCommand(args, options) {
if (this.#maxLength &&
this.#toWrite.length + this.#waitingForReply.length >= this.#maxLength) {
return Promise.reject(new Error("The queue is full"));
}
else if (options?.abortSignal?.aborted) {
return Promise.reject(new errors_1.AbortError());
}
return new Promise((resolve, reject) => {
// eslint-disable-next-line prefer-const -- assigned after closures that reference it are constructed
let node;
const value = {
args,
chainId: options?.chainId,
abort: undefined,
timeout: undefined,
resolve,
reject,
channelsCounter: undefined,
typeMapping: options?.typeMapping,
};
value.slotNumber = options?.slotNumber;
// If #maintenanceCommandTimeout was explicitly set, we should
// use it instead of the timeout provided by the command
const timeout = this.#maintenanceCommandTimeout ?? options?.timeout;
const wasInMaintenance = this.#maintenanceCommandTimeout !== undefined;
if (timeout) {
const signal = AbortSignal.timeout(timeout);
value.timeout = {
signal,
listener: () => {
this.#toWrite.remove(node);
value.reject(wasInMaintenance
? new errors_1.CommandTimeoutDuringMaintenanceError(timeout)
: new errors_1.TimeoutError());
},
originalTimeout: options?.timeout,
};
signal.addEventListener("abort", value.timeout.listener, {
once: true,
});
}
const signal = options?.abortSignal;
if (signal) {
value.abort = {
signal,
listener: () => {
this.#toWrite.remove(node);
value.reject(new errors_1.AbortError());
},
};
signal.addEventListener("abort", value.abort.listener, { once: true });
}
node = this.#toWrite.add(value, options?.asap);
});
}
#addPubSubCommand(command, asap = false, chainId) {
return new Promise((resolve, reject) => {
this.#toWrite.add({
args: command.args,
chainId,
abort: undefined,
timeout: undefined,
resolve() {
command.resolve();
resolve();
},
reject(err) {
command.reject?.();
reject(err);
},
channelsCounter: command.channelsCounter,
typeMapping: decoder_1.PUSH_TYPE_MAPPING,
}, asap);
});
}
#setupPubSubHandler() {
// RESP3 uses `onPush` to handle PubSub, so no need to modify `onReply`
if (this.#respVersion !== 2)
return;
this.decoder.onReply = ((reply) => {
if (Array.isArray(reply)) {
if (this.#onPush(reply))
return;
const firstElement = typeof reply[0] === "string" ? Buffer.from(reply[0]) : reply[0];
if (PONG.equals(firstElement)) {
const { resolve, typeMapping } = this.#waitingForReply.shift(), buffer = (reply[1].length === 0 ? reply[0] : reply[1]);
resolve(typeMapping?.[decoder_1.RESP_TYPES.SIMPLE_STRING] === Buffer
? buffer
: buffer.toString());
return;
}
}
return this.#onReply(reply);
});
this.decoder.getTypeMapping = () => RESP2_PUSH_TYPE_MAPPING;
}
subscribe(type, channels, listener, returnBuffers) {
const command = this.#pubSub.subscribe(type, channels, listener, returnBuffers);
if (!command)
return;
this.#setupPubSubHandler();
return this.#addPubSubCommand(command);
}
#resetDecoderCallbacks() {
this.decoder.onReply = ((reply) => this.#onReply(reply));
this.decoder.getTypeMapping = () => this.#getTypeMapping();
}
unsubscribe(type, channels, listener, returnBuffers) {
const command = this.#pubSub.unsubscribe(type, channels, listener, returnBuffers);
if (!command)
return;
if (command && this.#respVersion === 2) {
// RESP2 modifies `onReply` to handle PubSub (see #setupPubSubHandler)
const { resolve } = command;
command.resolve = () => {
if (!this.#pubSub.isActive) {
this.#resetDecoderCallbacks();
}
resolve();
};
}
return this.#addPubSubCommand(command);
}
removeAllPubSubListeners() {
return this.#pubSub.removeAllListeners();
}
removeShardedPubSubListenersForSlots(slots) {
return this.#pubSub.removeShardedPubSubListenersForSlots(slots);
}
resubscribe(chainId) {
const commands = this.#pubSub.resubscribe();
if (!commands.length)
return;
this.#setupPubSubHandler();
return Promise.all(commands.map((command) => this.#addPubSubCommand(command, true, chainId)));
}
extendPubSubChannelListeners(type, channel, listeners) {
const command = this.#pubSub.extendChannelListeners(type, channel, listeners);
if (!command)
return;
this.#setupPubSubHandler();
return this.#addPubSubCommand(command);
}
extendPubSubListeners(type, listeners) {
const command = this.#pubSub.extendTypeListeners(type, listeners);
if (!command)
return;
this.#setupPubSubHandler();
return this.#addPubSubCommand(command);
}
getPubSubListeners(type) {
return this.#pubSub.listeners[type];
}
monitor(callback, options) {
return new Promise((resolve, reject) => {
const typeMapping = options?.typeMapping ?? {};
this.#toWrite.add({
args: ["MONITOR"],
chainId: options?.chainId,
abort: undefined,
timeout: undefined,
// using `resolve` instead of using `.then`/`await` to make sure it'll be called before processing the next reply
resolve: () => {
// after running `MONITOR` only `MONITOR` and `RESET` replies are expected
// any other command should cause an error
// if `RESET` already overrides `onReply`, set monitor as it's fallback
if (this.#resetFallbackOnReply) {
this.#resetFallbackOnReply = callback;
}
else {
this.decoder.onReply = callback;
}
this.decoder.getTypeMapping = () => typeMapping;
resolve();
},
reject,
channelsCounter: undefined,
typeMapping,
}, options?.asap);
});
}
resetDecoder() {
this.#resetDecoderCallbacks();
this.decoder.reset();
}
#resetFallbackOnReply;
async reset(chainId, typeMapping) {
return new Promise((resolve, reject) => {
// overriding onReply to handle `RESET` while in `MONITOR` or PubSub mode
this.#resetFallbackOnReply = this.decoder.onReply;
this.decoder.onReply = ((reply) => {
if ((typeof reply === "string" && reply === "RESET") ||
(reply instanceof Buffer && RESET.equals(reply))) {
this.#resetDecoderCallbacks();
this.#resetFallbackOnReply = undefined;
this.#pubSub.reset();
this.#waitingForReply.shift().resolve(reply);
return;
}
this.#resetFallbackOnReply(reply);
});
this.#toWrite.push({
args: ["RESET"],
chainId,
abort: undefined,
timeout: undefined,
resolve,
reject,
channelsCounter: undefined,
typeMapping,
});
});
}
isWaitingToWrite() {
return this.#toWrite.length > 0;
}
*commandsToWrite() {
let toSend = this.#toWrite.shift();
while (toSend) {
let encoded;
try {
encoded = (0, encoder_1.default)(toSend.args);
}
catch (err) {
toSend.reject(err);
toSend = this.#toWrite.shift();
continue;
}
// TODO reuse `toSend` or create new object?
toSend.args = undefined;
if (toSend.abort) {
RedisCommandsQueue.#removeAbortListener(toSend);
toSend.abort = undefined;
}
if (toSend.timeout) {
RedisCommandsQueue.#removeTimeoutListener(toSend);
toSend.timeout = undefined;
}
this.#chainInExecution = toSend.chainId;
toSend.chainId = undefined;
this.#waitingForReply.push(toSend);
yield encoded;
toSend = this.#toWrite.shift();
}
}
#flushWaitingForReply(err) {
for (const node of this.#waitingForReply) {
node.reject(err);
}
this.#waitingForReply.reset();
}
static #removeAbortListener(command) {
command.abort.signal.removeEventListener("abort", command.abort.listener);
}
static #removeTimeoutListener(command) {
command.timeout?.signal.removeEventListener("abort", command.timeout.listener);
}
static #flushToWrite(toBeSent, err) {
if (toBeSent.abort) {
RedisCommandsQueue.#removeAbortListener(toBeSent);
}
if (toBeSent.timeout) {
RedisCommandsQueue.#removeTimeoutListener(toBeSent);
}
toBeSent.reject(err);
}
flushWaitingForReply(err) {
this.resetDecoder();
this.#pubSub.reset();
this.#flushWaitingForReply(err);
if (!this.#chainInExecution)
return;
while (this.#toWrite.head?.value.chainId === this.#chainInExecution) {
RedisCommandsQueue.#flushToWrite(this.#toWrite.shift(), err);
}
this.#chainInExecution = undefined;
}
flushAll(err) {
this.resetDecoder();
this.#pubSub.reset();
this.#flushWaitingForReply(err);
for (const node of this.#toWrite) {
RedisCommandsQueue.#flushToWrite(node, err);
}
this.#toWrite.reset();
}
isEmpty() {
return this.#toWrite.length === 0 && this.#waitingForReply.length === 0;
}
/**
*
* Extracts commands for the given slots from the toWrite queue.
* Some commands don't have "slotNumber", which means they are not designated to particular slot/node.
* We ignore those.
*/
extractCommandsForSlots(slots) {
const result = [];
let current = this.#toWrite.head;
while (current !== undefined) {
if (current.value.slotNumber !== undefined &&
slots.has(current.value.slotNumber)) {
result.push(current.value);
const toRemove = current;
current = current.next;
this.#toWrite.remove(toRemove);
}
else {
// Move to next node even if we don't extract this command
current = current.next;
}
}
return result;
}
/**
* Gets all commands from the write queue without removing them.
*/
extractAllCommands() {
const result = [];
let current = this.#toWrite.head;
while (current) {
result.push(current.value);
this.#toWrite.remove(current);
current = current.next;
}
return result;
}
/**
* Prepends commands to the write queue in reverse.
*/
prependCommandsToWrite(commands) {
if (!commands.length) {
return;
}
for (let i = commands.length - 1; i >= 0; i--) {
this.#toWrite.unshift(commands[i]);
}
}
}
exports.default = RedisCommandsQueue;
//# sourceMappingURL=commands-queue.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,74 @@
import RedisClient, { AnyRedisClientOptions } from ".";
import RedisCommandsQueue from "./commands-queue";
import { RedisArgument } from "../RESP/types";
type RedisType = RedisClient<any, any, any, any, any>;
export declare const SMIGRATED_EVENT = "__SMIGRATED";
interface Address {
host: string;
port: number;
}
interface Destination {
addr: Address;
slots: (number | [number, number])[];
}
interface SMigratedEntry {
source: Address;
destinations: Destination[];
}
export interface SMigratedEvent {
seqId: number;
entries: SMigratedEntry[];
}
export declare const MAINTENANCE_EVENTS: {
readonly PAUSE_WRITING: "pause-writing";
readonly RESUME_WRITING: "resume-writing";
readonly TIMEOUTS_UPDATE: "timeouts-update";
};
export type DiagnosticsEvent = {
type: string;
timestamp: number;
data?: object;
};
export declare const dbgMaintenance: (...args: unknown[]) => void;
export declare const emitDiagnostics: (event: DiagnosticsEvent) => void;
export interface MaintenanceUpdate {
relaxedCommandTimeout?: number;
relaxedSocketTimeout?: number;
}
export default class EnterpriseMaintenanceManager {
#private;
static setupDefaultMaintOptions(options: AnyRedisClientOptions): void;
static getHandshakeCommand(options: AnyRedisClientOptions, clientId: string): Promise<{
cmd: Array<RedisArgument>;
errorHandler: (error: Error) => void;
} | undefined>;
constructor(commandsQueue: RedisCommandsQueue, client: RedisType, options: AnyRedisClientOptions);
/**
* Parses an SMIGRATED push message into a structured SMigratedEvent.
*
* SMIGRATED format:
* - SMIGRATED, "seqid", followed by a list of N triplets:
* - source endpoint
* - target endpoint
* - comma separated list of slot ranges
*
* A source and a target endpoint may appear in multiple triplets.
* There is no optimization of the source, dest, slot-range list in the SMIGRATED message.
* The client code should read through the entire list of triplets in order to get a full
* list of moved slots, or full list of sources and targets.
*
* Example:
* [ 'SMIGRATED', 15, [ [ '127.0.0.1:6379', '127.0.0.2:6379', '123,456,789-1000' ], [ '127.0.0.3:6380', '127.0.0.4:6380', '124,457,300-500' ] ] ]
* ^seq ^source1 ^destination1 ^slots ^source2 ^destination2 ^slots
*
* Result structure guarantees:
* - Each source address appears in exactly one entry (entries are deduplicated by source)
* - Within each entry, each destination address appears exactly once (destinations are deduplicated per source)
* - Each destination contains the complete list of slots that moved from that source to that destination
* - Note: The same destination address CAN appear under different sources (e.g., node X receives slots from both A and B)
*/
static parseSMigratedPush(push: any[]): SMigratedEvent;
}
export type MovingEndpointType = "auto" | "internal-ip" | "internal-fqdn" | "external-ip" | "external-fqdn" | "none";
export {};
//# sourceMappingURL=enterprise-maintenance-manager.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"enterprise-maintenance-manager.d.ts","sourceRoot":"","sources":["../../../lib/client/enterprise-maintenance-manager.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,EAAE,EAAE,qBAAqB,EAAE,MAAM,GAAG,CAAC;AACvD,OAAO,kBAAkB,MAAM,kBAAkB,CAAC;AAOlD,OAAO,EAAE,aAAa,EAAgB,MAAM,eAAe,CAAC;AAI5D,KAAK,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEtD,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;CACtC;AAED,UAAU,cAAc;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAYX,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,cAAc,YAAa,OAAO,EAAE,SAGhD,CAAC;AAEF,eAAO,MAAM,eAAe,UAAW,gBAAgB,SAKtD,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,CAAC,OAAO,OAAO,4BAA4B;;IAM/C,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,qBAAqB;WAgBjD,mBAAmB,CAC9B,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,MAAM,GACf,OAAO,CACN;QAAE,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QAAC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;KAAE,GACnE,SAAS,CACZ;gBAsCC,aAAa,EAAE,kBAAkB,EACjC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,qBAAqB;IA8MhC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IAEH,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc;CA6DvD;AAGD,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,aAAa,GACb,eAAe,GACf,aAAa,GACb,eAAe,GACf,MAAM,CAAC"}
@@ -0,0 +1,391 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.emitDiagnostics = exports.dbgMaintenance = exports.MAINTENANCE_EVENTS = exports.SMIGRATED_EVENT = void 0;
const net_1 = require("net");
const promises_1 = require("dns/promises");
const node_assert_1 = __importDefault(require("node:assert"));
const promises_2 = require("node:timers/promises");
const node_diagnostics_channel_1 = __importDefault(require("node:diagnostics_channel"));
const types_1 = require("../RESP/types");
const tracing_1 = require("./tracing");
exports.SMIGRATED_EVENT = "__SMIGRATED";
exports.MAINTENANCE_EVENTS = {
PAUSE_WRITING: "pause-writing",
RESUME_WRITING: "resume-writing",
TIMEOUTS_UPDATE: "timeouts-update",
};
const PN = {
MOVING: "MOVING",
MIGRATING: "MIGRATING",
MIGRATED: "MIGRATED",
FAILING_OVER: "FAILING_OVER",
FAILED_OVER: "FAILED_OVER",
SMIGRATING: "SMIGRATING",
SMIGRATED: "SMIGRATED",
};
const dbgMaintenance = (...args) => {
if (!process.env.REDIS_DEBUG_MAINTENANCE)
return;
return console.log(new Date().toISOString().slice(11, 23), "[MNT]", ...args);
};
exports.dbgMaintenance = dbgMaintenance;
const emitDiagnostics = (event) => {
if (!process.env.REDIS_EMIT_DIAGNOSTICS)
return;
const channel = node_diagnostics_channel_1.default.channel("redis.maintenance");
channel.publish(event);
};
exports.emitDiagnostics = emitDiagnostics;
class EnterpriseMaintenanceManager {
#commandsQueue;
#options;
#isMaintenance = 0;
#client;
static setupDefaultMaintOptions(options) {
if (options.maintNotifications === undefined) {
options.maintNotifications =
(options?.RESP ?? types_1.DEFAULT_RESP) === 3 ? "auto" : "disabled";
}
if (options.maintEndpointType === undefined) {
options.maintEndpointType = "auto";
}
if (options.maintRelaxedSocketTimeout === undefined) {
options.maintRelaxedSocketTimeout = 10000;
}
if (options.maintRelaxedCommandTimeout === undefined) {
options.maintRelaxedCommandTimeout = 10000;
}
}
static async getHandshakeCommand(options, clientId) {
if (options.maintNotifications === "disabled")
return;
const host = options.url
? new URL(options.url).hostname
: options.socket?.host;
if (!host)
return;
const tls = options.socket?.tls ?? false;
const movingEndpointType = await determineEndpoint(tls, host, options);
return {
cmd: [
"CLIENT",
"MAINT_NOTIFICATIONS",
"ON",
"moving-endpoint-type",
movingEndpointType,
],
errorHandler: (error) => {
(0, exports.dbgMaintenance)("handshake failed:", error);
if (options.maintNotifications === "enabled") {
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error,
origin: 'client',
internal: true,
clientId,
}));
throw error;
}
},
};
}
constructor(commandsQueue, client, options) {
this.#commandsQueue = commandsQueue;
this.#options = options;
this.#client = client;
this.#commandsQueue.addPushHandler(this.#onPush);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- heterogeneous push payload
#onPush = (push) => {
(0, exports.dbgMaintenance)("ONPUSH:", push.map(String));
if (!Array.isArray(push) || !Object.values(PN).includes(String(push[0]))) {
return false;
}
const type = String(push[0]);
(0, tracing_1.publish)(tracing_1.CHANNELS.MAINTENANCE, () => ({
notification: type,
clientId: this.#client._clientId,
}));
(0, exports.emitDiagnostics)({
type,
timestamp: Date.now(),
data: {
push: push.map(String),
},
});
switch (type) {
case PN.MOVING: {
// [ 'MOVING', '17', '15', '54.78.247.156:12075' ]
// ^seq ^after ^new ip
const afterSeconds = push[2];
const url = push[3] ? String(push[3]) : null;
(0, exports.dbgMaintenance)("Received MOVING:", afterSeconds, url);
this.#onMoving(afterSeconds, url);
return true;
}
case PN.MIGRATING:
case PN.SMIGRATING:
case PN.FAILING_OVER: {
(0, exports.dbgMaintenance)("Received MIGRATING|SMIGRATING|FAILING_OVER");
this.#onMigrating();
return true;
}
case PN.MIGRATED:
case PN.FAILED_OVER: {
(0, exports.dbgMaintenance)("Received MIGRATED|FAILED_OVER");
this.#onMigrated();
return true;
}
case PN.SMIGRATED: {
(0, exports.dbgMaintenance)("Received SMIGRATED");
this.#onSMigrated(push);
this.#onMigrated(); // Un-relax timeouts after slot migration completes
return true;
}
}
return false;
};
// Queue:
// toWrite [ C D E ]
// waitingForReply [ A B ] - aka In-flight commands
//
// time: ---1-2---3-4-5-6---------------------------
//
// 1. [EVENT] MOVING PN received
// 2. [ACTION] Pause writing ( we need to wait for new socket to connect and for all in-flight commands to complete )
// 3. [EVENT] New socket connected
// 4. [EVENT] In-flight commands completed
// 5. [ACTION] Destroy old socket
// 6. [ACTION] Resume writing -> we are going to write to the new socket from now on
#onMoving = async (afterSeconds, url) => {
// 1 [EVENT] MOVING PN received
this.#onMigrating();
let host;
let port;
// The special value `none` indicates that the `MOVING` message doesnt need
// to contain an endpoint. Instead it contains the value `null` then. In
// such a corner case, the client is expected to schedule a graceful
// reconnect to its currently configured endpoint after half of the grace
// period that was communicated by the server is over.
if (url === null) {
(0, node_assert_1.default)(this.#options.maintEndpointType === "none");
const { host: h, port: p } = this.#getAddress();
host = h;
port = p;
const waitTime = (afterSeconds * 1000) / 2;
(0, exports.dbgMaintenance)(`Wait for ${waitTime}ms`);
await (0, promises_2.setTimeout)(waitTime);
}
else {
const split = url.split(":");
host = split[0];
port = Number(split[1]);
}
// 2 [ACTION] Pause writing
(0, exports.dbgMaintenance)("Pausing writing of new commands to old socket");
this.#client._pause();
(0, exports.dbgMaintenance)("Creating new tmp client");
let start = performance.now();
// If the URL is provided, it takes precedense
// the options object could just be mutated
if (this.#options.url) {
const u = new URL(this.#options.url);
u.hostname = host;
u.port = String(port);
this.#options.url = u.toString();
}
else {
this.#options.socket = {
...this.#options.socket,
host,
port,
};
}
const tmpClient = this.#client.duplicate();
tmpClient.on("error", (error) => {
//We dont know how to handle tmp client errors
(0, exports.dbgMaintenance)(`[ERR]`, error);
});
(0, exports.dbgMaintenance)(`Tmp client created in ${(performance.now() - start).toFixed(2)}ms`);
(0, exports.dbgMaintenance)(`Set timeout for tmp client to ${this.#options.maintRelaxedSocketTimeout}`);
tmpClient._maintenanceUpdate({
relaxedCommandTimeout: this.#options.maintRelaxedCommandTimeout,
relaxedSocketTimeout: this.#options.maintRelaxedSocketTimeout,
});
(0, exports.dbgMaintenance)(`Connecting tmp client: ${host}:${port}`);
start = performance.now();
await tmpClient.connect();
(0, exports.dbgMaintenance)(`Connected to tmp client in ${(performance.now() - start).toFixed(2)}ms`);
// 3 [EVENT] New socket connected
(0, exports.dbgMaintenance)(`Wait for all in-flight commands to complete`);
await this.#commandsQueue.waitForInflightCommandsToComplete();
(0, exports.dbgMaintenance)(`In-flight commands completed`);
// 4 [EVENT] In-flight commands completed
(0, exports.dbgMaintenance)("Swap client sockets...");
const oldSocket = this.#client._ejectSocket();
const newSocket = tmpClient._ejectSocket();
this.#client._insertSocket(newSocket);
tmpClient._insertSocket(oldSocket);
tmpClient.destroy();
(0, exports.dbgMaintenance)("Swap client sockets done.");
// 5 + 6
(0, exports.dbgMaintenance)("Resume writing");
this.#client._unpause();
this.#onMigrated();
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_HANDOFF, () => ({ clientId: this.#client._clientId }));
};
#onMigrating = () => {
this.#isMaintenance++;
if (this.#isMaintenance > 1) {
(0, exports.dbgMaintenance)(`Timeout relaxation already done`);
return;
}
const update = {
relaxedCommandTimeout: this.#options.maintRelaxedCommandTimeout,
relaxedSocketTimeout: this.#options.maintRelaxedSocketTimeout,
};
this.#client._maintenanceUpdate(update);
};
#onMigrated = () => {
//ensure that #isMaintenance doesnt go under 0
this.#isMaintenance = Math.max(this.#isMaintenance - 1, 0);
if (this.#isMaintenance > 0) {
(0, exports.dbgMaintenance)(`Not ready to unrelax timeouts yet`);
return;
}
const update = {
relaxedCommandTimeout: undefined,
relaxedSocketTimeout: undefined,
};
this.#client._maintenanceUpdate(update);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- heterogeneous push payload
#onSMigrated = (push) => {
const smigratedEvent = _a.parseSMigratedPush(push);
(0, exports.dbgMaintenance)(`emit smigratedEvent`, smigratedEvent);
this.#client._handleSmigrated(smigratedEvent);
};
/**
* Parses an SMIGRATED push message into a structured SMigratedEvent.
*
* SMIGRATED format:
* - SMIGRATED, "seqid", followed by a list of N triplets:
* - source endpoint
* - target endpoint
* - comma separated list of slot ranges
*
* A source and a target endpoint may appear in multiple triplets.
* There is no optimization of the source, dest, slot-range list in the SMIGRATED message.
* The client code should read through the entire list of triplets in order to get a full
* list of moved slots, or full list of sources and targets.
*
* Example:
* [ 'SMIGRATED', 15, [ [ '127.0.0.1:6379', '127.0.0.2:6379', '123,456,789-1000' ], [ '127.0.0.3:6380', '127.0.0.4:6380', '124,457,300-500' ] ] ]
* ^seq ^source1 ^destination1 ^slots ^source2 ^destination2 ^slots
*
* Result structure guarantees:
* - Each source address appears in exactly one entry (entries are deduplicated by source)
* - Within each entry, each destination address appears exactly once (destinations are deduplicated per source)
* - Each destination contains the complete list of slots that moved from that source to that destination
* - Note: The same destination address CAN appear under different sources (e.g., node X receives slots from both A and B)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- heterogeneous push payload
static parseSMigratedPush(push) {
const map = new Map();
for (const [src, destination, slots] of push[2]) {
const source = String(src);
const [dHost, dPort] = String(destination).split(':');
// `slots` could be mix of single slots and ranges, for example: 123,456,789-1000
const parsedSlots = String(slots).split(',').map((singleOrRange) => {
const separatorIndex = singleOrRange.indexOf('-');
if (separatorIndex === -1) {
// Its single slot
return Number(singleOrRange);
}
// Its range
return [Number(singleOrRange.substring(0, separatorIndex)), Number(singleOrRange.substring(separatorIndex + 1))];
});
const destinations = map.get(source) ?? [];
const dest = destinations.find(d => d.addr.host === dHost && d.addr.port === Number(dPort));
if (dest) {
// destination already exists, just add the slots
dest.slots = dest.slots.concat(parsedSlots);
}
else {
destinations.push({
addr: {
host: dHost,
port: Number(dPort)
},
slots: parsedSlots
});
}
map.set(source, destinations);
}
const entries = [];
for (const [src, destinations] of map.entries()) {
const [host, port] = src.split(":");
entries.push({
source: {
host,
port: Number(port)
},
destinations
});
}
return {
seqId: push[1],
entries
};
}
#getAddress() {
(0, node_assert_1.default)(this.#options.socket !== undefined);
(0, node_assert_1.default)("host" in this.#options.socket);
(0, node_assert_1.default)(typeof this.#options.socket.host === "string");
const host = this.#options.socket.host;
(0, node_assert_1.default)(typeof this.#options.socket.port === "number");
const port = this.#options.socket.port;
return { host, port };
}
}
_a = EnterpriseMaintenanceManager;
exports.default = EnterpriseMaintenanceManager;
function isPrivateIP(ip) {
const version = (0, net_1.isIP)(ip);
if (version === 4) {
const octets = ip.split(".").map(Number);
return (octets[0] === 10 ||
(octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) ||
(octets[0] === 192 && octets[1] === 168));
}
if (version === 6) {
return (ip.startsWith("fc") || // Unique local
ip.startsWith("fd") || // Unique local
ip === "::1" || // Loopback
ip.startsWith("fe80") // Link-local unicast
);
}
return false;
}
async function determineEndpoint(tlsEnabled, host, options) {
(0, node_assert_1.default)(options.maintEndpointType !== undefined);
if (options.maintEndpointType !== "auto") {
(0, exports.dbgMaintenance)(`Determine endpoint type: ${options.maintEndpointType}`);
return options.maintEndpointType;
}
const ip = (0, net_1.isIP)(host) ? host : (await (0, promises_1.lookup)(host, { family: 0 })).address;
const isPrivate = isPrivateIP(ip);
let result;
if (tlsEnabled) {
result = isPrivate ? "internal-fqdn" : "external-fqdn";
}
else {
result = isPrivate ? "internal-ip" : "external-ip";
}
(0, exports.dbgMaintenance)(`Determine endpoint type: ${result}`);
return result;
}
//# sourceMappingURL=enterprise-maintenance-manager.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
import { RedisClusterClientOptions } from '../cluster';
/**
* @internal
*/
export declare enum ClientRole {
STANDALONE = "standalone",
CLUSTER = "cluster",
CLUSTER_NODE = "clusterNode",
POOL = "pool",
POOL_MEMBER = "poolMember",
SENTINEL = "sentinel",
SENTINEL_CLIENT = "sentinelClient"
}
/**
* Identity for all Redis clients.
* Contains only the stable identity info (id, role, parentId).
* Use dynamic methods to get current host/port/db to avoid drift.
* @internal
*/
export interface ClientIdentity {
readonly id: string;
role: ClientRole;
parentId?: string;
}
/**
* Generates a client identity ID in the format: $host:$port/$db-$hash
* Truncated to MAX_ID_LENGTH if necessary.
* @internal
*/
export declare const generateClientId: (host?: string, port?: number, db?: number) => string;
/**
* Generates a cluster identity ID by concatenating all root nodes: $host1:$port1,$host2:$port2,...-$hash
* Truncated to MAX_ID_LENGTH if necessary.
* @internal
*/
export declare const generateClusterClientId: (nodes: Array<RedisClusterClientOptions>) => string;
//# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../../lib/client/identity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AASvD;;GAEG;AACH,oBAAY,UAAU;IACpB,UAAU,eAAe;IACzB,OAAO,YAAY;IACnB,YAAY,gBAAgB;IAC5B,IAAI,SAAS;IACb,WAAW,eAAe;IAC1B,QAAQ,aAAa;IACrB,eAAe,mBAAmB;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UACpB,MAAM,SACN,MAAM,OACR,MAAM,KACV,MAKF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,UAC3B,MAAM,yBAAyB,CAAC,KACtC,MAQF,CAAC"}
@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateClusterClientId = exports.generateClientId = exports.ClientRole = void 0;
const node_crypto_1 = require("node:crypto");
/**
* Maximum length for client identity IDs.
* Keeps IDs reasonable for OpenTelemetry attributes and logging.
* @internal
*/
const MAX_ID_LENGTH = 128;
/**
* @internal
*/
var ClientRole;
(function (ClientRole) {
ClientRole["STANDALONE"] = "standalone";
ClientRole["CLUSTER"] = "cluster";
ClientRole["CLUSTER_NODE"] = "clusterNode";
ClientRole["POOL"] = "pool";
ClientRole["POOL_MEMBER"] = "poolMember";
ClientRole["SENTINEL"] = "sentinel";
ClientRole["SENTINEL_CLIENT"] = "sentinelClient";
})(ClientRole || (exports.ClientRole = ClientRole = {}));
/**
* Truncates a prefix string to fit within MAX_ID_LENGTH when combined with suffix.
* Format when truncated: {truncated_prefix}...-{hash}
* @internal
*/
const truncateId = (prefix, hash) => {
const suffix = `-${hash}`;
const fullId = `${prefix}${suffix}`;
if (fullId.length <= MAX_ID_LENGTH) {
return fullId;
}
// Reserve space for '...' and suffix
const ellipsis = '...';
const maxPrefixLength = MAX_ID_LENGTH - ellipsis.length - suffix.length;
const truncatedPrefix = prefix.substring(0, maxPrefixLength);
return `${truncatedPrefix}${ellipsis}${suffix}`;
};
/**
* Generates a client identity ID in the format: $host:$port/$db-$hash
* Truncated to MAX_ID_LENGTH if necessary.
* @internal
*/
const generateClientId = (host, port, db) => {
const hash = (0, node_crypto_1.randomBytes)(4).toString('hex');
const prefix = `${host ?? 'unknown'}:${port ?? 'unknown'}/${db ?? 'unknown'}`;
return truncateId(prefix, hash);
};
exports.generateClientId = generateClientId;
/**
* Generates a cluster identity ID by concatenating all root nodes: $host1:$port1,$host2:$port2,...-$hash
* Truncated to MAX_ID_LENGTH if necessary.
* @internal
*/
const generateClusterClientId = (nodes) => {
const hash = (0, node_crypto_1.randomBytes)(4).toString('hex');
const prefix = nodes
.map((n) => `${n?.socket?.host ?? 'unknown'}:${n?.socket?.port ?? 'unknown'}`)
.join(',');
return truncateId(prefix, hash);
};
exports.generateClusterClientId = generateClusterClientId;
//# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../lib/client/identity.ts"],"names":[],"mappings":";;;AAAA,6CAA0C;AAG1C;;;;GAIG;AACH,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B;;GAEG;AACH,IAAY,UAQX;AARD,WAAY,UAAU;IACpB,uCAAyB,CAAA;IACzB,iCAAmB,CAAA;IACnB,0CAA4B,CAAA;IAC5B,2BAAa,CAAA;IACb,wCAA0B,CAAA;IAC1B,mCAAqB,CAAA;IACrB,gDAAkC,CAAA;AACpC,CAAC,EARW,UAAU,0BAAV,UAAU,QAQrB;AAcD;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE;IAC1D,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;IAEpC,IAAI,MAAM,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC;IACvB,MAAM,eAAe,GAAG,aAAa,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IACxE,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAC7D,OAAO,GAAG,eAAe,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;AAClD,CAAC,CAAC;AAEF;;;;GAIG;AACI,MAAM,gBAAgB,GAAG,CAC9B,IAAa,EACb,IAAa,EACb,EAAW,EACH,EAAE;IACV,MAAM,IAAI,GAAG,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,IAAI,EAAE,IAAI,SAAS,EAAE,CAAC;IAC9E,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC;AATW,QAAA,gBAAgB,oBAS3B;AAEF;;;;GAIG;AACI,MAAM,uBAAuB,GAAG,CACrC,KAAuC,EAC/B,EAAE;IACV,MAAM,IAAI,GAAG,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,IAAI,SAAS,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,IAAI,SAAS,EAAE,CACzE;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC;AAVW,QAAA,uBAAuB,2BAUlC","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { RedisClusterClientOptions } from '../cluster';\n\n/**\n * Maximum length for client identity IDs.\n * Keeps IDs reasonable for OpenTelemetry attributes and logging.\n * @internal\n */\nconst MAX_ID_LENGTH = 128;\n\n/**\n * @internal\n */\nexport enum ClientRole {\n STANDALONE = 'standalone',\n CLUSTER = 'cluster',\n CLUSTER_NODE = 'clusterNode',\n POOL = 'pool',\n POOL_MEMBER = 'poolMember',\n SENTINEL = 'sentinel',\n SENTINEL_CLIENT = 'sentinelClient',\n}\n\n/**\n * Identity for all Redis clients.\n * Contains only the stable identity info (id, role, parentId).\n * Use dynamic methods to get current host/port/db to avoid drift.\n * @internal\n */\nexport interface ClientIdentity {\n readonly id: string;\n role: ClientRole;\n parentId?: string;\n}\n\n/**\n * Truncates a prefix string to fit within MAX_ID_LENGTH when combined with suffix.\n * Format when truncated: {truncated_prefix}...-{hash}\n * @internal\n */\nconst truncateId = (prefix: string, hash: string): string => {\n const suffix = `-${hash}`;\n const fullId = `${prefix}${suffix}`;\n\n if (fullId.length <= MAX_ID_LENGTH) {\n return fullId;\n }\n\n // Reserve space for '...' and suffix\n const ellipsis = '...';\n const maxPrefixLength = MAX_ID_LENGTH - ellipsis.length - suffix.length;\n const truncatedPrefix = prefix.substring(0, maxPrefixLength);\n return `${truncatedPrefix}${ellipsis}${suffix}`;\n};\n\n/**\n * Generates a client identity ID in the format: $host:$port/$db-$hash\n * Truncated to MAX_ID_LENGTH if necessary.\n * @internal\n */\nexport const generateClientId = (\n host?: string,\n port?: number,\n db?: number,\n): string => {\n const hash = randomBytes(4).toString('hex');\n\n const prefix = `${host ?? 'unknown'}:${port ?? 'unknown'}/${db ?? 'unknown'}`;\n return truncateId(prefix, hash);\n};\n\n/**\n * Generates a cluster identity ID by concatenating all root nodes: $host1:$port1,$host2:$port2,...-$hash\n * Truncated to MAX_ID_LENGTH if necessary.\n * @internal\n */\nexport const generateClusterClientId = (\n nodes: Array<RedisClusterClientOptions>,\n): string => {\n const hash = randomBytes(4).toString('hex');\n const prefix = nodes\n .map(\n (n) => `${n?.socket?.host ?? 'unknown'}:${n?.socket?.port ?? 'unknown'}`,\n )\n .join(',');\n return truncateId(prefix, hash);\n};\n"]}
@@ -0,0 +1,434 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import COMMANDS from '../commands';
import RedisSocket, { RedisSocketOptions } from './socket';
import { CredentialsProvider } from '../authx';
import RedisCommandsQueue, { CommandOptions } from './commands-queue';
import { EventEmitter } from 'node:events';
import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types';
import { RedisClientMultiCommandType } from './multi-command';
import { MULTI_MODE, MultiMode, RedisMultiQueuedCommand } from '../multi-command';
import { ScanOptions, ScanCommonOptions } from '../commands/SCAN';
import { RedisLegacyClientType } from './legacy-mode';
import { RedisPoolOptions } from './pool';
import { RedisVariadicArgument } from '../commands/generic-transformers';
import { ClientSideCacheConfig, ClientSideCacheProvider } from './cache';
import { CommandParser } from './parser';
import { MaintenanceUpdate, MovingEndpointType, SMigratedEvent } from './enterprise-maintenance-manager';
import { ClientRole } from './identity';
export interface RedisClientOptions<M extends RedisModules = RedisModules, F extends RedisFunctions = RedisFunctions, S extends RedisScripts = RedisScripts, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = TypeMapping, SocketOptions extends RedisSocketOptions = RedisSocketOptions> extends CommanderConfig<M, F, S, RESP> {
/**
* `redis[s]://[[username][:password]@][host][:port][/db-number]`
* See [`redis`](https://www.iana.org/assignments/uri-schemes/prov/redis) and [`rediss`](https://www.iana.org/assignments/uri-schemes/prov/rediss) IANA registration for more details
*/
url?: string;
/**
* Socket connection properties
*/
socket?: SocketOptions;
/**
* ACL username ([see ACL guide](https://redis.io/topics/acl))
*/
username?: string;
/**
* ACL password or the old "--requirepass" password
*/
password?: string;
/**
* Provides credentials for authentication. Can be set directly or will be created internally
* if username/password are provided instead. If both are supplied, this credentialsProvider
* takes precedence over username/password.
*/
credentialsProvider?: CredentialsProvider;
/**
* Client name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname))
*/
name?: string;
/**
* Redis database number (see [`SELECT`](https://redis.io/commands/select) command)
*/
database?: number;
/**
* Prefix prepended to every key sent to Redis (ioredis-compatible `keyPrefix`).
*
* Useful for isolating keyspaces — for example per-test isolation in CI, or
* separating an application's components (web app vs. background workers) within a
* single Redis instance.
*
* Matches ioredis semantics: only keys *sent* to Redis are prefixed. Keys *returned*
* by Redis (e.g. `KEYS`, `SCAN`, `RANDOMKEY`) are NOT un-prefixed, `SCAN`/`KEYS`
* `MATCH` patterns are NOT auto-prefixed, and Pub/Sub channels are NOT prefixed.
*
* @example
* ```
* const client = createClient({ keyPrefix: 'app:' });
* await client.set('key', 'value'); // stored as 'app:key'
* ```
*/
keyPrefix?: RedisArgument;
/**
* Maximum length of the client's internal command queue
*/
commandsQueueMaxLength?: number;
/**
* When `true`, commands are rejected when the client is reconnecting.
* When `false`, commands are queued for execution after reconnection.
*/
disableOfflineQueue?: boolean;
/**
* Connect in [`READONLY`](https://redis.io/commands/readonly) mode
*/
readonly?: boolean;
/**
* Send `PING` command at interval (in ms).
* Useful with Redis deployments that do not honor TCP Keep-Alive.
*/
pingInterval?: number;
/**
* Default command options to be applied to all commands executed through this client.
*
* These options can be overridden on a per-command basis when calling specific commands.
*
* @property {symbol} [chainId] - Identifier for chaining commands together
* @property {boolean} [asap] - When true, the command is executed as soon as possible
* @property {AbortSignal} [abortSignal] - AbortSignal to cancel the command
* @property {TypeMapping} [typeMapping] - Custom type mappings between RESP and JavaScript types
*
* @example Setting default command options
* ```
* const client = createClient({
* commandOptions: {
* asap: true,
* typeMapping: {
* // Custom type mapping configuration
* }
* }
* });
* ```
*/
commandOptions?: CommandOptions<TYPE_MAPPING>;
/**
* Client Side Caching configuration.
*
* Enables Redis Servers and Clients to work together to cache results from commands
* sent to a server. The server will notify the client when cached results are no longer valid.
*
* Note: Client Side Caching is only supported with RESP3.
*
* @example Anonymous cache configuration
* ```
* const client = createClient({
* RESP: 3,
* clientSideCache: {
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* }
* });
* ```
*
* @example Using a controllable cache
* ```
* const cache = new BasicClientSideCache({
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* });
* const client = createClient({
* RESP: 3,
* clientSideCache: cache
* });
* ```
*/
clientSideCache?: ClientSideCacheProvider | ClientSideCacheConfig;
/**
* If set to true, disables sending client identifier (user-agent like message) to the redis server
*/
disableClientInfo?: boolean;
/**
* Tag to append to library name that is sent to the Redis server
*/
clientInfoTag?: string;
/**
* When set to true, client tracking is turned on and the client emits `invalidate` events when it receives invalidation messages from the redis server.
* Mutually exclusive with `clientSideCache` option.
*/
emitInvalidate?: boolean;
/**
* Controls how the client handles Redis Enterprise maintenance push notifications.
*
* - `disabled`: The feature is not used by the client.
* - `enabled`: The client attempts to enable the feature on the server. If the server responds with an error, the connection is interrupted.
* - `auto`: The client attempts to enable the feature on the server. If the server returns an error, the client disables the feature and continues.
*
* The default is `auto`.
*/
maintNotifications?: 'disabled' | 'enabled' | 'auto';
/**
* Controls how the client requests the endpoint to reconnect to during a MOVING notification in Redis Enterprise maintenance.
*
* - `auto`: If the connection is opened to a name or IP address that is from/resolves to a reserved private IP range, request an internal endpoint (e.g., internal-ip), otherwise an external one. If TLS is enabled, then request a FQDN.
* - `internal-ip`: Enforce requesting the internal IP.
* - `internal-fqdn`: Enforce requesting the internal FQDN.
* - `external-ip`: Enforce requesting the external IP address.
* - `external-fqdn`: Enforce requesting the external FQDN.
* - `none`: Used to request a null endpoint, which tells the client to reconnect based on its current config
* The default is `auto`.
*/
maintEndpointType?: MovingEndpointType;
/**
* Specifies a more relaxed timeout (in milliseconds) for commands during a maintenance window.
* This helps minimize command timeouts during maintenance. Timeouts during maintenance period result
* in a `CommandTimeoutDuringMaintenance` error.
*
* The default is 10000
*/
maintRelaxedCommandTimeout?: number;
/**
* Specifies a more relaxed timeout (in milliseconds) for the socket during a maintenance window.
* This helps minimize socket timeouts during maintenance. Timeouts during maintenance period result
* in a `SocketTimeoutDuringMaintenance` error.
*
* The default is 10000
*/
maintRelaxedSocketTimeout?: number;
}
/**
* `RedisClientOptions` widened to accept any RESP version / module / function /
* script / type-mapping combination. Use when an internal callsite must accept
* any `RedisClientOptions` shape regardless of the public default (e.g. parsed
* URLs, sentinel node options, maintenance manager).
*/
export type AnyRedisClientOptions = RedisClientOptions<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
export type WithCommands<RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof typeof COMMANDS]: CommandSignature<(typeof COMMANDS)[P], RESP, TYPE_MAPPING>;
};
export type WithModules<M extends RedisModules, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof M]: {
[C in keyof M[P]]: CommandSignature<M[P][C], RESP, TYPE_MAPPING>;
};
};
export type WithFunctions<F extends RedisFunctions, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[L in keyof F]: {
[C in keyof F[L]]: CommandSignature<F[L][C], RESP, TYPE_MAPPING>;
};
};
export type WithScripts<S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof S]: CommandSignature<S[P], RESP, TYPE_MAPPING>;
};
export type RedisClientExtensions<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}> = (WithCommands<RESP, TYPE_MAPPING> & WithModules<M, RESP, TYPE_MAPPING> & WithFunctions<F, RESP, TYPE_MAPPING> & WithScripts<S, RESP, TYPE_MAPPING>);
export type RedisClientType<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}> = (RedisClient<M, F, S, RESP, TYPE_MAPPING> & RedisClientExtensions<M, F, S, RESP, TYPE_MAPPING>);
export interface ScanIteratorOptions {
cursor?: RedisArgument;
}
export type MonitorCallback<TYPE_MAPPING extends TypeMapping = TypeMapping> = (reply: ReplyWithTypeMapping<SimpleStringReply, TYPE_MAPPING>) => unknown;
export default class RedisClient<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends EventEmitter {
#private;
static factory<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3>(config?: CommanderConfig<M, F, S, RESP>): <TYPE_MAPPING extends TypeMapping = {}>(options?: Omit<RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisSocketOptions>, keyof CommanderConfig<M, F, S, RESP>> | undefined) => RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
static create<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}>(this: void, options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>): RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
static parseOptions<O extends AnyRedisClientOptions>(options: O): O;
static parseURL(url: string): AnyRedisClientOptions & {
socket: Exclude<AnyRedisClientOptions['socket'], undefined> & {
tls: boolean;
};
};
private _self;
private _commandOptions?;
get clientSideCache(): ClientSideCacheProvider | undefined;
get options(): RedisClientOptions<M, F, S, RESP>;
/**
* The configured key prefix (see {@link RedisClientOptions.keyPrefix}), if any.
* @internal
*/
get _keyPrefix(): RedisArgument | undefined;
/**
* @internal
* Returns the client ID for metrics attribution.
*/
get _clientId(): string;
/**
* @internal
* Sets the client identity. Used by pool/cluster/sentinel when creating child clients.
*/
_setIdentity(role: ClientRole, parentId?: string): void;
get isOpen(): boolean;
get isReady(): boolean;
get isPubSubActive(): boolean;
get socketEpoch(): number;
get isWatching(): boolean;
/**
* Indicates whether the client's WATCH command has been invalidated by a topology change.
* When this returns true, any transaction using WATCH will fail with a WatchError.
* @returns true if the watched keys have been modified, false otherwise
*/
get isDirtyWatch(): boolean;
/**
* Marks the client's WATCH command as invalidated due to a topology change.
* This will cause any subsequent EXEC in a transaction to fail with a WatchError.
* @param msg - The error message explaining why the WATCH is dirty
*/
setDirtyWatch(msg: string): void;
constructor(options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>);
/**
* @param credentials
*/
private reAuthenticate;
withCommandOptions<OPTIONS extends CommandOptions<TYPE_MAPPING>, TYPE_MAPPING extends TypeMapping>(options: OPTIONS): RedisClientType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
private _commandOptionsProxy;
/**
* Override the `typeMapping` command option
*/
withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING): RedisClientType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
/**
* Override the `abortSignal` command option
*/
withAbortSignal(abortSignal: AbortSignal): RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
/**
* Override the `asap` command option to `true`
*/
asap(): RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
/**
* Create the "legacy" (v3/callback) interface
*/
legacy(): RedisLegacyClientType;
/**
* Create {@link RedisClientPool `RedisClientPool`} using this client as a prototype
*/
createPool(options?: Partial<RedisPoolOptions>): import("./pool").RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING>;
duplicate<_M extends RedisModules = M, _F extends RedisFunctions = F, _S extends RedisScripts = S, _RESP extends RespVersions = RESP, _TYPE_MAPPING extends TypeMapping = TYPE_MAPPING>(overrides?: Partial<RedisClientOptions<_M, _F, _S, _RESP, _TYPE_MAPPING>>): RedisClientType<_M, _F, _S, _RESP, _TYPE_MAPPING>;
connect(): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
/**
* @internal
*/
_ejectSocket(): RedisSocket;
/**
* @internal
*/
_insertSocket(socket: RedisSocket): void;
/**
* @internal
*/
_maintenanceUpdate(update: MaintenanceUpdate): void;
/**
* @internal
*/
_pause(): void;
/**
* @internal
*/
_unpause(): void;
/**
* @internal
*/
_handleSmigrated(smigratedEvent: SMigratedEvent): void;
/**
* @internal
*/
_getQueue(): RedisCommandsQueue;
/**
* @internal
*/
_executeCommand(command: Command, parser: CommandParser, commandOptions: CommandOptions<TYPE_MAPPING> | undefined, transformReply: TransformReply | undefined): Promise<any>;
/**
* @internal
*/
_executeScript(script: RedisScript, parser: CommandParser, options: CommandOptions | undefined, transformReply: TransformReply | undefined): Promise<any>;
sendCommand<T = ReplyUnion>(args: ReadonlyArray<RedisArgument>, options?: CommandOptions): Promise<T>;
SELECT(db: number): Promise<void>;
select: (db: number) => Promise<void>;
SUBSCRIBE<T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
subscribe: <T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
UNSUBSCRIBE<T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<T>, bufferMode?: T): Promise<void>;
unsubscribe: <T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<T> | undefined, bufferMode?: T | undefined) => Promise<void>;
PSUBSCRIBE<T extends boolean = false>(patterns: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
pSubscribe: <T extends boolean = false>(patterns: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
PUNSUBSCRIBE<T extends boolean = false>(patterns?: string | Array<string>, listener?: PubSubListener<T>, bufferMode?: T): Promise<void>;
pUnsubscribe: <T extends boolean = false>(patterns?: string | Array<string>, listener?: PubSubListener<T> | undefined, bufferMode?: T | undefined) => Promise<void>;
SSUBSCRIBE<T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
sSubscribe: <T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
SUNSUBSCRIBE<T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<T>, bufferMode?: T): Promise<void>;
sUnsubscribe: <T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<T> | undefined, bufferMode?: T | undefined) => Promise<void>;
WATCH(key: RedisVariadicArgument): Promise<TYPE_MAPPING[43] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<"OK", T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : "OK">;
watch: (key: RedisVariadicArgument) => Promise<TYPE_MAPPING[43] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<"OK", T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : "OK">;
UNWATCH(): Promise<TYPE_MAPPING[43] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<"OK", T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : "OK">;
unwatch: () => Promise<TYPE_MAPPING[43] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<"OK", T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : "OK">;
getPubSubListeners(type: PubSubType): PubSubTypeListeners;
extendPubSubChannelListeners(type: PubSubType, channel: string, listeners: ChannelListeners): Promise<void>;
extendPubSubListeners(type: PubSubType, listeners: PubSubTypeListeners): Promise<void>;
/**
* @internal
*/
_executePipeline(commands: Array<RedisMultiQueuedCommand>, selectedDB?: number): Promise<unknown[]>;
/**
* @internal
*/
_executeMulti(commands: Array<RedisMultiQueuedCommand>, selectedDB?: number): Promise<unknown[]>;
MULTI<isTyped extends MultiMode = MULTI_MODE['TYPED']>(): RedisClientMultiCommandType<isTyped, [], M, F, S, RESP, TYPE_MAPPING>;
multi: <isTyped extends MultiMode = "typed">() => RedisClientMultiCommandType<isTyped, [], M, F, S, RESP, TYPE_MAPPING>;
/**
* Async iterator over the keyspace, issuing paged `SCAN` calls and yielding one array
* of keys per page until the cursor returns to `0`.
*
* @remarks
* With a configured {@link RedisClientOptions.keyPrefix}, the yielded keys are the
* **already-prefixed** keys as stored on the server — replies are never un-prefixed.
* Passing them straight back into a key-prefixed command prefixes them a second time
* (e.g. with `keyPrefix: 'app:'`, a yielded `'app:foo'` becomes `'app:app:foo'`):
*
* ```js
* for await (const keys of client.scanIterator()) {
* await client.mGet(keys); // ⚠️ double-prefixes -> reads 'app:app:foo'
* }
* ```
*
* Strip the prefix before reusing the keys, or read them with a client that has no
* `keyPrefix`.
*/
scanIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, options?: ScanOptions & ScanIteratorOptions): AsyncGenerator<TYPE_MAPPING[42] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<import("../RESP/types").BlobStringReply<string>[], T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : (TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string)[], void, unknown>;
hScanIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, key: RedisArgument, options?: ScanCommonOptions & ScanIteratorOptions): AsyncGenerator<{
field: TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string;
value: TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string;
}[], void, unknown>;
hScanValuesIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, key: RedisArgument, options?: ScanCommonOptions & ScanIteratorOptions): AsyncGenerator<(TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string)[], void, unknown>;
hScanNoValuesIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, key: RedisArgument, options?: ScanCommonOptions & ScanIteratorOptions): AsyncGenerator<(TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string)[], void, unknown>;
sScanIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, key: RedisArgument, options?: ScanCommonOptions & ScanIteratorOptions): AsyncGenerator<(TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string)[], void, unknown>;
zScanIterator(this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, key: RedisArgument, options?: ScanCommonOptions & ScanIteratorOptions): AsyncGenerator<{
value: TYPE_MAPPING[36] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<Buffer, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<string, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : string;
score: TYPE_MAPPING[44] extends import("../RESP/types").MappedType<infer T> ? ReplyWithTypeMapping<Extract<number, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> | ReplyWithTypeMapping<Extract<`${number}`, T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends BigIntConstructor ? bigint : T>, TYPE_MAPPING> : number;
}[], void, unknown>;
MONITOR(callback: MonitorCallback<TYPE_MAPPING>): Promise<void>;
monitor: (callback: MonitorCallback<TYPE_MAPPING>) => Promise<void>;
/**
* Reset the client to its default state (i.e. stop PubSub, stop monitoring, select default DB, etc.)
*/
reset(): Promise<void>;
/**
* If the client has state, reset it.
* An internal function to be used by wrapper class such as `RedisClientPool`.
* @internal
*/
resetIfDirty(): Promise<void> | undefined;
/**
* @deprecated use .close instead
*/
QUIT(): Promise<string>;
quit: () => Promise<string>;
/**
* @deprecated use .destroy instead
*/
disconnect(): Promise<void>;
/**
* Close the client. Wait for pending commands.
*/
close(): Promise<void>;
/**
* Destroy the client. Rejects all commands immediately.
*/
destroy(): void;
ref(): void;
unref(): void;
}
//# sourceMappingURL=index.d.ts.map
File diff suppressed because one or more lines are too long
+1196
View File
@@ -0,0 +1,1196 @@
"use strict";
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const commands_1 = __importDefault(require("../commands"));
const socket_1 = __importDefault(require("./socket"));
const authx_1 = require("../authx");
const commands_queue_1 = __importDefault(require("./commands-queue"));
const node_events_1 = require("node:events");
const commander_1 = require("../commander");
const errors_1 = require("../errors");
const node_url_1 = require("node:url");
const pub_sub_1 = require("./pub-sub");
const types_1 = require("../RESP/types");
const multi_command_1 = __importDefault(require("./multi-command"));
const HELLO_1 = __importDefault(require("../commands/HELLO"));
const legacy_mode_1 = require("./legacy-mode");
const pool_1 = require("./pool");
const generic_transformers_1 = require("../commands/generic-transformers");
const cache_1 = require("./cache");
const parser_1 = require("./parser");
const single_entry_cache_1 = __importDefault(require("../single-entry-cache"));
const package_json_1 = require("../../package.json");
const enterprise_maintenance_manager_1 = __importStar(require("./enterprise-maintenance-manager"));
const opentelemetry_1 = require("../opentelemetry");
const identity_1 = require("./identity");
const tracing_1 = require("./tracing");
const defaults_1 = require("../defaults");
const noop = () => { };
;
class RedisClient extends node_events_1.EventEmitter {
static #createCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this._self._executeCommand(command, parser, this._commandOptions, transformReply);
};
}
static #createModuleCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this._self._executeCommand(command, parser, this._self._commandOptions, transformReply);
};
}
static #createFunctionCommand(name, fn, resp) {
const prefix = (0, commander_1.functionArgumentsPrefix)(name, fn);
const transformReply = (0, commander_1.getTransformReply)(fn, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.push(...prefix);
fn.parseCommand(parser, ...args);
return this._self._executeCommand(fn, parser, this._self._commandOptions, transformReply);
};
}
static #createScriptCommand(script, resp) {
const prefix = (0, commander_1.scriptArgumentsPrefix)(script);
const transformReply = (0, commander_1.getTransformReply)(script, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.push(...prefix);
script.parseCommand(parser, ...args);
return this._executeScript(script, parser, this._commandOptions, transformReply);
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static #SingleEntryCache = new single_entry_cache_1.default();
static factory(config) {
let Client = _a.#SingleEntryCache.get(config);
if (!Client) {
Client = (0, commander_1.attachConfig)({
BaseClass: _a,
commands: commands_1.default,
createCommand: _a.#createCommand,
createModuleCommand: _a.#createModuleCommand,
createFunctionCommand: _a.#createFunctionCommand,
createScriptCommand: _a.#createScriptCommand,
config
});
Client.prototype.Multi = multi_command_1.default.extend(config);
_a.#SingleEntryCache.set(config, Client);
}
return (options) => {
// returning a "proxy" to prevent the namespaces._self to leak between "proxies"
return Object.create(new Client(options));
};
}
static create(options) {
return _a.factory(options)(options);
}
static parseOptions(options) {
if (options?.url) {
const parsed = _a.parseURL(options.url);
if (options.socket) {
if (options.socket.tls !== undefined && options.socket.tls !== parsed.socket.tls) {
throw new TypeError(`tls socket option is set to ${options.socket.tls} which is mismatch with protocol or the URL ${options.url} passed`);
}
parsed.socket = Object.assign(options.socket, parsed.socket);
}
Object.assign(options, parsed);
}
return options;
}
static parseURL(url) {
// unix:// URIs use a non-special scheme; WHATWG URL refuses to parse an
// authority (e.g. `user:pass@`) without a host, so handle it separately.
if (url.startsWith('unix:')) {
return _a.#parseUnixURL(url);
}
// https://www.iana.org/assignments/uri-schemes/prov/redis
const { hostname, port, protocol, username, password, pathname } = new node_url_1.URL(url), parsed = {
socket: {
// Use net.SocketAddress.parse() once supported.
host: hostname.replace(/^\[([0-9a-f:]+)\]$/, '$1'),
tls: false
}
};
if (protocol !== 'redis:' && protocol !== 'rediss:') {
throw new TypeError('Invalid protocol');
}
parsed.socket.tls = protocol === 'rediss:';
if (port) {
parsed.socket.port = Number(port);
}
if (username) {
parsed.username = decodeURIComponent(username);
}
if (password) {
parsed.password = decodeURIComponent(password);
}
if (username || password) {
parsed.credentialsProvider = {
type: 'async-credentials-provider',
credentials: async () => ({
username: username ? decodeURIComponent(username) : undefined,
password: password ? decodeURIComponent(password) : undefined
})
};
}
if (pathname.length > 1) {
const database = Number(pathname.substring(1));
if (isNaN(database)) {
throw new TypeError('Invalid pathname');
}
parsed.database = database;
}
return parsed;
}
static #parseUnixURL(url) {
// unix://[user[:password]@]/path/to/sock[?db=N]
const match = /^unix:\/\/(?:([^:@/]*)(?::([^@/]*))?@)?(\/[^?#]*)(?:\?([^#]*))?(?:#.*)?$/.exec(url);
if (!match || match[3] === '/') {
throw new TypeError('Invalid unix URL');
}
const [, username, password, rawPath, rawQuery] = match, parsed = {
socket: {
path: decodeURIComponent(rawPath),
tls: false
}
};
if (username) {
parsed.username = decodeURIComponent(username);
}
if (password) {
parsed.password = decodeURIComponent(password);
}
if (username || password) {
parsed.credentialsProvider = {
type: 'async-credentials-provider',
credentials: async () => ({
username: username ? decodeURIComponent(username) : undefined,
password: password ? decodeURIComponent(password) : undefined
})
};
}
if (rawQuery) {
const db = new URLSearchParams(rawQuery).get('db');
if (db !== null) {
const database = Number(db);
if (isNaN(database)) {
throw new TypeError('Invalid db query parameter');
}
parsed.database = database;
}
}
return parsed;
}
#options;
#socket;
#queue;
#selectedDB = 0;
#monitorCallback;
_self = this;
_commandOptions;
// flag used to annotate that the client
// was in a watch transaction when
// a topology change occurred
#dirtyWatch;
#watchEpoch;
#clientSideCache;
#credentialsSubscription = null;
// Flag used to pause writing to the socket during maintenance windows.
// When true, prevents new commands from being written while waiting for:
// 1. New socket to be ready after maintenance redirect
// 2. In-flight commands on the old socket to complete
#paused = false;
#clientIdentity;
#registered = false;
get clientSideCache() {
return this._self.#clientSideCache;
}
get options() {
return this._self.#options;
}
/**
* The configured key prefix (see {@link RedisClientOptions.keyPrefix}), if any.
* @internal
*/
get _keyPrefix() {
return this._self.#options.keyPrefix;
}
/**
* @internal
* Returns the client ID for metrics attribution.
*/
get _clientId() {
return this._self.#clientIdentity.id;
}
/**
* @internal
* Sets the client identity. Used by pool/cluster/sentinel when creating child clients.
*/
_setIdentity(role, parentId) {
this._self.#clientIdentity.role = role;
if (parentId) {
this._self.#clientIdentity.parentId = parentId;
}
}
/**
* @internal
* Creates a metrics handle for this client.
*/
#createMetricsHandle() {
return {
identity: this._self.#clientIdentity,
getAttributes: () => ({
host: this._self.#socket.host,
port: this._self.#socket.port,
db: this._self.#selectedDB,
clientId: this._self.#clientIdentity.id,
parentId: this._self.#clientIdentity.parentId,
isPubSub: this._self.#queue.isPubSubActive,
}),
getPendingRequests: () => this._self.#queue.pendingCount,
getCacheItemCount: () => this._self.#clientSideCache?.size() ?? 0,
isConnected: () => this._self.#socket.isReady
};
}
/**
* @internal
* Registers this client with the metrics registry.
*/
#registerForMetrics() {
if (this.#registered) {
return;
}
opentelemetry_1.ClientRegistry.instance.register(this.#createMetricsHandle());
this.#registered = true;
}
/**
* @internal
* Unregisters this client from the metrics registry.
*/
#unregisterFromMetrics() {
if (!this.#registered)
return;
opentelemetry_1.ClientRegistry.instance.unregister(this.#clientIdentity.id);
this.#registered = false;
}
get isOpen() {
return this._self.#socket.isOpen;
}
get isReady() {
return this._self.#socket.isReady;
}
get isPubSubActive() {
return this._self.#queue.isPubSubActive;
}
get socketEpoch() {
return this._self.#socket.socketEpoch;
}
get isWatching() {
return this._self.#watchEpoch !== undefined;
}
/**
* Indicates whether the client's WATCH command has been invalidated by a topology change.
* When this returns true, any transaction using WATCH will fail with a WatchError.
* @returns true if the watched keys have been modified, false otherwise
*/
get isDirtyWatch() {
return this._self.#dirtyWatch !== undefined;
}
/**
* Marks the client's WATCH command as invalidated due to a topology change.
* This will cause any subsequent EXEC in a transaction to fail with a WatchError.
* @param msg - The error message explaining why the WATCH is dirty
*/
setDirtyWatch(msg) {
this._self.#dirtyWatch = msg;
}
constructor(options) {
super();
this.#validateOptions(options);
this.#options = this.#initiateOptions(options);
const socketOpts = this.#options.socket;
this.#clientIdentity = {
id: (0, identity_1.generateClientId)(socketOpts?.host, socketOpts?.port, this.#selectedDB),
role: identity_1.ClientRole.STANDALONE
};
this.#queue = this.#initiateQueue(this.#clientIdentity.id);
this.#socket = this.#initiateSocket(this.#clientIdentity.id);
this.#registerForMetrics();
if (this.#options.maintNotifications !== 'disabled') {
new enterprise_maintenance_manager_1.default(this.#queue, this, this.#options);
}
;
if (this.#options.clientSideCache) {
if (this.#options.clientSideCache instanceof cache_1.ClientSideCacheProvider) {
this.#clientSideCache = this.#options.clientSideCache;
}
else {
const cscConfig = this.#options.clientSideCache;
this.#clientSideCache = new cache_1.BasicClientSideCache(cscConfig);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.#queue.addPushHandler((push) => {
if (push[0].toString() !== 'invalidate')
return false;
if (push[1] !== null) {
for (const key of push[1]) {
this.#clientSideCache?.invalidate(key);
}
}
else {
this.#clientSideCache?.invalidate(null);
}
return true;
});
}
else if (options?.emitInvalidate) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.#queue.addPushHandler((push) => {
if (push[0].toString() !== 'invalidate')
return false;
if (push[1] !== null) {
for (const key of push[1]) {
this.emit('invalidate', key);
}
}
else {
this.emit('invalidate', null);
}
return true;
});
}
}
#validateOptions(options) {
const resp = options?.RESP ?? types_1.DEFAULT_RESP;
if (options?.clientSideCache && resp !== 3) {
throw new Error('Client Side Caching is only supported with RESP3');
}
if (options?.emitInvalidate && resp !== 3) {
throw new Error('emitInvalidate is only supported with RESP3');
}
if (options?.clientSideCache && options?.emitInvalidate) {
throw new Error('emitInvalidate is not supported (or necessary) when clientSideCache is enabled');
}
if (options?.maintNotifications && options?.maintNotifications !== 'disabled' && resp !== 3) {
throw new Error('Graceful Maintenance is only supported with RESP3');
}
}
#initiateOptions(options = {}) {
// Convert username/password to credentialsProvider if no credentialsProvider is already in place
if (!options.credentialsProvider && (options.username || options.password)) {
options.credentialsProvider = {
type: 'async-credentials-provider',
credentials: async () => ({
username: options.username,
password: options.password
})
};
}
if (options.database) {
this._self.#selectedDB = options.database;
}
this._commandOptions = { timeout: defaults_1.DEFAULT_COMMAND_TIMEOUT, ...options.commandOptions };
if (options.maintNotifications !== 'disabled') {
enterprise_maintenance_manager_1.default.setupDefaultMaintOptions(options);
}
if (options.url) {
const parsedOptions = _a.parseOptions(options);
if (parsedOptions?.database) {
this._self.#selectedDB = parsedOptions.database;
}
return parsedOptions;
}
return options;
}
#initiateQueue(clientId) {
return new commands_queue_1.default(this.#options.RESP ?? types_1.DEFAULT_RESP, this.#options.commandsQueueMaxLength, (channel, listeners) => this.emit('sharded-channel-moved', channel, listeners), clientId);
}
/**
* @param credentials
*/
reAuthenticate = async (credentials) => {
// Re-authentication is not supported on RESP2 with PubSub active
if (!(this.isPubSubActive && (this.#options.RESP ?? types_1.DEFAULT_RESP) === 2)) {
await this.sendCommand((0, generic_transformers_1.parseArgs)(commands_1.default.AUTH, {
username: credentials.username,
password: credentials.password ?? ''
}));
}
};
#subscribeForStreamingCredentials(cp) {
return cp.subscribe({
onNext: credentials => {
this.reAuthenticate(credentials).catch(error => {
const errorMessage = error instanceof Error ? error.message : String(error);
cp.onReAuthenticationError(new authx_1.CredentialsError(errorMessage));
});
},
onError: (e) => {
const errorMessage = `Error from streaming credentials provider: ${e.message}`;
cp.onReAuthenticationError(new authx_1.UnableToObtainNewCredentialsError(errorMessage));
}
});
}
async #handshake(chainId, asap) {
const promises = [];
const commandsWithErrorHandlers = await this.#getHandshakeCommands();
if (asap)
commandsWithErrorHandlers.reverse();
for (const { cmd, errorHandler } of commandsWithErrorHandlers) {
promises.push(this.#queue
.addCommand(cmd, {
chainId,
asap
})
.catch(errorHandler));
}
return promises;
}
async #getHandshakeCommands() {
const commands = [];
const cp = this.#options.credentialsProvider;
const resp = this.#options.RESP ?? types_1.DEFAULT_RESP;
if (resp !== 2) {
const hello = {};
if (cp && cp.type === 'async-credentials-provider') {
const credentials = await cp.credentials();
if (credentials.password) {
hello.AUTH = {
username: credentials.username ?? 'default',
password: credentials.password
};
}
}
if (cp && cp.type === 'streaming-credentials-provider') {
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp);
this.#credentialsSubscription = disposable;
if (credentials.password) {
hello.AUTH = {
username: credentials.username ?? 'default',
password: credentials.password
};
}
}
if (this.#options.name) {
hello.SETNAME = this.#options.name;
}
commands.push({ cmd: (0, generic_transformers_1.parseArgs)(HELLO_1.default, resp, hello) });
}
else {
if (cp && cp.type === 'async-credentials-provider') {
const credentials = await cp.credentials();
if (credentials.username || credentials.password) {
commands.push({
cmd: (0, generic_transformers_1.parseArgs)(commands_1.default.AUTH, {
username: credentials.username,
password: credentials.password ?? ''
})
});
}
}
if (cp && cp.type === 'streaming-credentials-provider') {
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp);
this.#credentialsSubscription = disposable;
if (credentials.username || credentials.password) {
commands.push({
cmd: (0, generic_transformers_1.parseArgs)(commands_1.default.AUTH, {
username: credentials.username,
password: credentials.password ?? ''
})
});
}
}
if (this.#options.name) {
commands.push({
cmd: (0, generic_transformers_1.parseArgs)(commands_1.default.CLIENT_SETNAME, this.#options.name)
});
}
}
if (this.#selectedDB !== 0) {
commands.push({ cmd: ['SELECT', this.#selectedDB.toString()] });
}
if (this.#options.readonly) {
commands.push({ cmd: (0, generic_transformers_1.parseArgs)(commands_1.default.READONLY) });
}
if (!this.#options.disableClientInfo) {
commands.push({
cmd: ['CLIENT', 'SETINFO', 'LIB-VER', package_json_1.version],
errorHandler: () => {
// Client libraries are expected to pipeline this command
// after authentication on all connections and ignore failures
// since they could be connected to an older version that doesn't support them.
}
});
commands.push({
cmd: [
'CLIENT',
'SETINFO',
'LIB-NAME',
this.#options.clientInfoTag
? `node-redis(${this.#options.clientInfoTag})`
: 'node-redis'
],
errorHandler: () => {
// Client libraries are expected to pipeline this command
// after authentication on all connections and ignore failures
// since they could be connected to an older version that doesn't support them.
}
});
}
if (this.#clientSideCache) {
commands.push({ cmd: this.#clientSideCache.trackingOn() });
}
if (this.#options?.emitInvalidate) {
commands.push({ cmd: ['CLIENT', 'TRACKING', 'ON'] });
}
const maintenanceHandshakeCmd = await enterprise_maintenance_manager_1.default.getHandshakeCommand(this.#options, this._clientId);
if (maintenanceHandshakeCmd) {
commands.push(maintenanceHandshakeCmd);
}
;
return commands;
}
#attachListeners(socket) {
socket.on('data', chunk => {
try {
this.#queue.decoder.write(chunk);
}
catch (err) {
this.#queue.resetDecoder();
this.emit('error', err);
}
})
.on('error', err => {
this.emit('error', err);
this.#clientSideCache?.onError();
if (this.#socket.isOpen && !this.#options.disableOfflineQueue) {
this.#queue.flushWaitingForReply(err);
}
else {
this.#queue.flushAll(err);
}
})
.on('connect', () => this.emit('connect'))
.on('ready', () => {
this.emit('ready');
this.#setPingTimer();
this.#maybeScheduleWrite();
})
.on('reconnecting', () => this.emit('reconnecting'))
.on('drain', () => this.#maybeScheduleWrite())
.on('end', () => this.emit('end'));
}
#initiateSocket(clientId) {
const socketInitiator = async () => {
const promises = [], chainId = Symbol('Socket Initiator');
const resubscribePromise = this.#queue.resubscribe(chainId);
resubscribePromise?.catch(error => {
if (error.message && error.message.startsWith('MOVED')) {
this.emit('__MOVED', this._self.#queue.removeAllPubSubListeners());
}
});
if (resubscribePromise) {
promises.push(resubscribePromise);
}
if (this.#monitorCallback) {
promises.push(this.#queue.monitor(this.#monitorCallback, {
typeMapping: this._commandOptions?.typeMapping,
chainId,
asap: true
}));
}
promises.push(...(await this.#handshake(chainId, true)));
if (promises.length) {
this.#write();
return Promise.all(promises);
}
};
const socket = new socket_1.default(socketInitiator, clientId, this.#options.socket);
this.#attachListeners(socket);
return socket;
}
#pingTimer;
#setPingTimer() {
if (!this.#options.pingInterval || !this.#socket.isReady)
return;
clearTimeout(this.#pingTimer);
this.#pingTimer = setTimeout(() => {
if (!this.#socket.isReady)
return;
this.sendCommand(['PING'])
.then(reply => this.emit('ping-interval', reply))
.catch(err => this.emit('error', err))
.finally(() => this.#setPingTimer());
}, this.#options.pingInterval);
}
withCommandOptions(options) {
const proxy = Object.create(this._self);
proxy._commandOptions = { ...this._commandOptions, ...options };
return proxy;
}
_commandOptionsProxy(key, value) {
const proxy = Object.create(this._self);
proxy._commandOptions = { ...this._commandOptions, [key]: value };
return proxy;
}
/**
* Override the `typeMapping` command option
*/
withTypeMapping(typeMapping) {
return this._commandOptionsProxy('typeMapping', typeMapping);
}
/**
* Override the `abortSignal` command option
*/
withAbortSignal(abortSignal) {
return this._commandOptionsProxy('abortSignal', abortSignal);
}
/**
* Override the `asap` command option to `true`
*/
asap() {
return this._commandOptionsProxy('asap', true);
}
/**
* Create the "legacy" (v3/callback) interface
*/
legacy() {
return new legacy_mode_1.RedisLegacyClient(this);
}
/**
* Create {@link RedisClientPool `RedisClientPool`} using this client as a prototype
*/
createPool(options) {
return pool_1.RedisClientPool.create(this._self.#options, options);
}
duplicate(overrides) {
return new (Object.getPrototypeOf(this).constructor)({
...this._self.#options,
commandOptions: this._commandOptions,
...overrides
});
}
async connect() {
await (0, tracing_1.trace)(tracing_1.CHANNELS.TRACE_CONNECT, () => this._self.#socket.connect(), () => ({
...this._self.#socketTraceContext(),
clientId: this._self._clientId
}));
return this;
}
/**
* @internal
*/
_ejectSocket() {
const socket = this._self.#socket;
// @ts-expect-error null assignment is intentional during eject
this._self.#socket = null;
socket.removeAllListeners();
return socket;
}
/**
* @internal
*/
_insertSocket(socket) {
if (this._self.#socket) {
this._self._ejectSocket().destroy();
}
this._self.#socket = socket;
this._self.#attachListeners(this._self.#socket);
}
/**
* @internal
*/
_maintenanceUpdate(update) {
this._self.#socket.setMaintenanceTimeout(update.relaxedSocketTimeout);
this._self.#queue.setMaintenanceCommandTimeout(update.relaxedCommandTimeout);
}
/**
* @internal
*/
_pause() {
this._self.#paused = true;
}
/**
* @internal
*/
_unpause() {
this._self.#paused = false;
this._self.#maybeScheduleWrite();
}
/**
* @internal
*/
_handleSmigrated(smigratedEvent) {
this._self.emit(enterprise_maintenance_manager_1.SMIGRATED_EVENT, smigratedEvent);
}
/**
* @internal
*/
_getQueue() {
return this._self.#queue;
}
/**
* @internal
*/
async _executeCommand(command, parser, commandOptions, transformReply) {
const csc = this._self.#clientSideCache;
const defaultTypeMapping = this._self.#options.commandOptions === commandOptions ||
(this._self.#options.commandOptions?.typeMapping === commandOptions?.typeMapping);
const fn = () => { return this.sendCommand(parser.redisArgs, commandOptions); };
if (csc && command.CACHEABLE && defaultTypeMapping) {
return await csc.handleCache(this._self, parser, fn, transformReply, commandOptions?.typeMapping);
}
else {
const reply = await fn();
const finalReply = transformReply ? transformReply(reply, parser.preserve, commandOptions?.typeMapping) : reply;
(0, tracing_1.publish)(tracing_1.CHANNELS.COMMAND_REPLY, () => ({ args: (0, tracing_1.sanitizeArgs)(parser.redisArgs), reply: finalReply, clientId: this._self._clientId }));
return finalReply;
}
}
/**
* @internal
*/
async _executeScript(script, parser, options, transformReply) {
const args = parser.redisArgs;
let reply;
try {
reply = await this.sendCommand(args, options);
}
catch (err) {
if (!err?.message?.startsWith?.('NOSCRIPT'))
throw err;
args[0] = 'EVAL';
args[1] = script.SCRIPT;
reply = await this.sendCommand(args, options);
}
return transformReply ?
transformReply(reply, parser.preserve, options?.typeMapping) :
reply;
}
sendCommand(args, options) {
return (0, tracing_1.trace)(tracing_1.CHANNELS.TRACE_COMMAND, () => {
if (!this._self.#socket.isOpen) {
return Promise.reject(new errors_1.ClientClosedError());
}
else if (!this._self.#socket.isReady &&
this._self.#options.disableOfflineQueue) {
return Promise.reject(new errors_1.ClientOfflineError());
}
// Merge global options with provided options
const opts = {
...this._commandOptions,
...options,
};
const promise = this._self.#queue.addCommand(args, opts);
this._self.#scheduleWrite();
return promise;
}, () => this._self.#commandTraceContext(args));
}
#commandTraceContext(args) {
return {
command: String(args[0]).toUpperCase(),
args: (0, tracing_1.sanitizeArgs)(args),
database: this.#selectedDB,
clientId: this._clientId,
...this.#socketTraceContext()
};
}
#socketTraceContext() {
const socketOptions = this.#options.socket;
if (socketOptions && 'path' in socketOptions) {
return { serverAddress: socketOptions.path, serverPort: undefined };
}
return {
serverAddress: socketOptions?.host ?? 'localhost',
serverPort: socketOptions?.port ?? 6379
};
}
async SELECT(db) {
await this.sendCommand(['SELECT', db.toString()]);
this._self.#selectedDB = db;
}
select = this.SELECT;
#pubSubCommand(promise) {
if (promise === undefined)
return Promise.resolve();
this.#scheduleWrite();
return promise;
}
SUBSCRIBE(channels, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.subscribe(pub_sub_1.PUBSUB_TYPE.CHANNELS, channels, listener, bufferMode));
}
subscribe = this.SUBSCRIBE;
UNSUBSCRIBE(channels, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.unsubscribe(pub_sub_1.PUBSUB_TYPE.CHANNELS, channels, listener, bufferMode));
}
unsubscribe = this.UNSUBSCRIBE;
PSUBSCRIBE(patterns, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.subscribe(pub_sub_1.PUBSUB_TYPE.PATTERNS, patterns, listener, bufferMode));
}
pSubscribe = this.PSUBSCRIBE;
PUNSUBSCRIBE(patterns, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.unsubscribe(pub_sub_1.PUBSUB_TYPE.PATTERNS, patterns, listener, bufferMode));
}
pUnsubscribe = this.PUNSUBSCRIBE;
SSUBSCRIBE(channels, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.subscribe(pub_sub_1.PUBSUB_TYPE.SHARDED, channels, listener, bufferMode));
}
sSubscribe = this.SSUBSCRIBE;
SUNSUBSCRIBE(channels, listener, bufferMode) {
return this._self.#pubSubCommand(this._self.#queue.unsubscribe(pub_sub_1.PUBSUB_TYPE.SHARDED, channels, listener, bufferMode));
}
sUnsubscribe = this.SUNSUBSCRIBE;
async WATCH(key) {
// WATCH builds its arguments outside of the command parser, so the configured
// `keyPrefix` must be applied here too — otherwise WATCH would guard the unprefixed
// key while the transaction operates on the prefixed one, silently breaking the
// optimistic lock.
const reply = await this._self.sendCommand(['WATCH', ...(0, parser_1.prefixKeys)(this._self._keyPrefix, key)]);
this._self.#watchEpoch ??= this._self.socketEpoch;
return reply;
}
watch = this.WATCH;
async UNWATCH() {
const reply = await this._self.sendCommand(['UNWATCH']);
this._self.#watchEpoch = undefined;
return reply;
}
unwatch = this.UNWATCH;
getPubSubListeners(type) {
return this._self.#queue.getPubSubListeners(type);
}
extendPubSubChannelListeners(type, channel, listeners) {
return this._self.#pubSubCommand(this._self.#queue.extendPubSubChannelListeners(type, channel, listeners));
}
extendPubSubListeners(type, listeners) {
return this._self.#pubSubCommand(this._self.#queue.extendPubSubListeners(type, listeners));
}
#write() {
if (this.#paused) {
return;
}
this.#socket.write(this.#queue.commandsToWrite());
}
#scheduledWrite;
#scheduleWrite() {
if (!this.#socket.isReady || this.#scheduledWrite)
return;
this.#scheduledWrite = setImmediate(() => {
this.#write();
this.#scheduledWrite = undefined;
});
}
#maybeScheduleWrite() {
if (!this.#queue.isWaitingToWrite())
return;
this.#scheduleWrite();
}
/**
* @internal
*/
async _executePipeline(commands, selectedDB) {
if (!this._self.#socket.isOpen) {
return Promise.reject(new errors_1.ClientClosedError());
}
const batchSize = commands.length;
return (0, tracing_1.trace)(tracing_1.CHANNELS.TRACE_BATCH, async () => {
const chainId = Symbol('Pipeline Chain');
const promise = Promise.all(commands.map(({ args }) => {
const traced = (0, tracing_1.trace)(tracing_1.CHANNELS.TRACE_COMMAND, () => this._self.#queue.addCommand(args, {
chainId,
typeMapping: this._commandOptions?.typeMapping
}), () => ({
...this._self.#commandTraceContext(args),
batchMode: 'PIPELINE',
batchSize
}));
// Prevent unhandled rejection from tracePromise wrapper; individual
// rejections are collected by Promise.all, but the tracePromise wrapper
// is a separate branch that nobody awaits.
traced.catch(noop);
return traced;
}));
this._self.#scheduleWrite();
const result = await promise;
if (selectedDB !== undefined) {
this._self.#selectedDB = selectedDB;
}
return result;
}, () => ({
batchMode: 'PIPELINE',
batchSize,
database: this._self.#selectedDB,
clientId: this._self._clientId,
...this._self.#socketTraceContext()
}));
}
/**
* @internal
*/
async _executeMulti(commands, selectedDB) {
const dirtyWatch = this._self.#dirtyWatch;
this._self.#dirtyWatch = undefined;
const watchEpoch = this._self.#watchEpoch;
this._self.#watchEpoch = undefined;
if (!this._self.#socket.isOpen) {
throw new errors_1.ClientClosedError();
}
if (dirtyWatch) {
throw new errors_1.WatchError(dirtyWatch);
}
if (watchEpoch && watchEpoch !== this._self.socketEpoch) {
throw new errors_1.WatchError('Client reconnected after WATCH');
}
const batchSize = commands.length;
return (0, tracing_1.trace)(tracing_1.CHANNELS.TRACE_BATCH, async () => {
const typeMapping = this._commandOptions?.typeMapping;
const chainId = Symbol('MULTI Chain');
const promises = [
this._self.#queue.addCommand(['MULTI'], { chainId }),
];
for (const { args } of commands) {
promises.push(this._self.#queue.addCommand(args, {
chainId,
typeMapping
}));
}
promises.push(this._self.#queue.addCommand(['EXEC'], { chainId }));
this._self.#scheduleWrite();
const results = await Promise.all(promises), execResult = results[results.length - 1];
if (execResult === null) {
throw new errors_1.WatchError();
}
if (selectedDB !== undefined) {
this._self.#selectedDB = selectedDB;
}
return execResult;
}, () => ({
batchMode: 'MULTI',
batchSize,
database: this._self.#selectedDB,
clientId: this._self._clientId,
...this._self.#socketTraceContext()
}));
}
MULTI() {
return new (this.Multi)(this._executeMulti.bind(this), this._executePipeline.bind(this), this._commandOptions?.typeMapping, this._self._keyPrefix);
}
multi = this.MULTI;
/**
* Async iterator over the keyspace, issuing paged `SCAN` calls and yielding one array
* of keys per page until the cursor returns to `0`.
*
* @remarks
* With a configured {@link RedisClientOptions.keyPrefix}, the yielded keys are the
* **already-prefixed** keys as stored on the server — replies are never un-prefixed.
* Passing them straight back into a key-prefixed command prefixes them a second time
* (e.g. with `keyPrefix: 'app:'`, a yielded `'app:foo'` becomes `'app:app:foo'`):
*
* ```js
* for await (const keys of client.scanIterator()) {
* await client.mGet(keys); // ⚠️ double-prefixes -> reads 'app:app:foo'
* }
* ```
*
* Strip the prefix before reusing the keys, or read them with a client that has no
* `keyPrefix`.
*/
async *scanIterator(options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.scan(cursor, options);
cursor = reply.cursor;
yield reply.keys;
} while (cursor !== '0');
}
async *hScanIterator(key, options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.hScan(key, cursor, options);
cursor = reply.cursor;
yield reply.entries;
} while (cursor !== '0');
}
async *hScanValuesIterator(key, options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.hScanNoValues(key, cursor, options);
cursor = reply.cursor;
yield reply.fields;
} while (cursor !== '0');
}
async *hScanNoValuesIterator(key, options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.hScanNoValues(key, cursor, options);
cursor = reply.cursor;
yield reply.fields;
} while (cursor !== '0');
}
async *sScanIterator(key, options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.sScan(key, cursor, options);
cursor = reply.cursor;
yield reply.members;
} while (cursor !== '0');
}
async *zScanIterator(key, options) {
let cursor = options?.cursor ?? '0';
do {
const reply = await this.zScan(key, cursor, options);
cursor = reply.cursor;
yield reply.members;
} while (cursor !== '0');
}
async MONITOR(callback) {
const promise = this._self.#queue.monitor(callback, {
typeMapping: this._commandOptions?.typeMapping
});
this._self.#scheduleWrite();
await promise;
this._self.#monitorCallback = callback;
}
monitor = this.MONITOR;
/**
* Reset the client to its default state (i.e. stop PubSub, stop monitoring, select default DB, etc.)
*/
async reset() {
const chainId = Symbol('Reset Chain'), promises = [this._self.#queue.reset(chainId)], selectedDB = this._self.#options?.database ?? 0;
this._self.#credentialsSubscription?.dispose();
this._self.#credentialsSubscription = null;
promises.push(...(await this._self.#handshake(chainId, false)));
this._self.#scheduleWrite();
await Promise.all(promises);
this._self.#selectedDB = selectedDB;
this._self.#monitorCallback = undefined;
this._self.#dirtyWatch = undefined;
this._self.#watchEpoch = undefined;
}
/**
* If the client has state, reset it.
* An internal function to be used by wrapper class such as `RedisClientPool`.
* @internal
*/
resetIfDirty() {
let shouldReset = false;
if (this._self.#selectedDB !== (this._self.#options?.database ?? 0)) {
console.warn('Returning a client with a different selected DB');
shouldReset = true;
}
if (this._self.#monitorCallback) {
console.warn('Returning a client with active MONITOR');
shouldReset = true;
}
if (this._self.#queue.isPubSubActive) {
console.warn('Returning a client with active PubSub');
shouldReset = true;
}
if (this._self.#dirtyWatch || this._self.#watchEpoch) {
console.warn('Returning a client with active WATCH');
shouldReset = true;
}
if (shouldReset) {
return this.reset();
}
}
/**
* @deprecated use .close instead
*/
QUIT() {
this._self.#credentialsSubscription?.dispose();
this._self.#credentialsSubscription = null;
return this._self.#socket.quit(async () => {
clearTimeout(this._self.#pingTimer);
const quitPromise = this._self.#queue.addCommand(['QUIT']);
this._self.#scheduleWrite();
this._self.#unregisterFromMetrics();
return quitPromise;
});
}
quit = this.QUIT;
/**
* @deprecated use .destroy instead
*/
disconnect() {
return Promise.resolve(this.destroy());
}
/**
* Close the client. Wait for pending commands.
*/
close() {
return new Promise(resolve => {
clearTimeout(this._self.#pingTimer);
this._self.#socket.close();
this._self.#clientSideCache?.onClose();
if (this._self.#queue.isEmpty()) {
this._self.#unregisterFromMetrics();
this._self.#socket.destroySocket();
return resolve();
}
const maybeClose = () => {
if (!this._self.#queue.isEmpty())
return;
this._self.#socket.off('data', maybeClose);
this._self.#unregisterFromMetrics();
this._self.#socket.destroySocket();
resolve();
};
this._self.#socket.on('data', maybeClose);
this._self.#credentialsSubscription?.dispose();
this._self.#credentialsSubscription = null;
});
}
/**
* Destroy the client. Rejects all commands immediately.
*/
destroy() {
clearTimeout(this._self.#pingTimer);
this._self.#queue.flushAll(new errors_1.DisconnectsClientError());
this._self.#socket.destroy();
this._self.#clientSideCache?.onClose();
this._self.#unregisterFromMetrics();
this._self.#credentialsSubscription?.dispose();
this._self.#credentialsSubscription = null;
}
ref() {
this._self.#socket.ref();
}
unref() {
this._self.#socket.unref();
}
}
_a = RedisClient;
exports.default = RedisClient;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
/// <reference types="node" />
import { RedisModules, RedisFunctions, RedisScripts, RespVersions, Command, CommandArguments, ReplyUnion } from '../RESP/types';
import { RedisClientType } from '.';
import { ErrorReply } from '../errors';
import COMMANDS from '../commands';
type LegacyArgument = string | Buffer | number | Date;
type LegacyArguments = Array<LegacyArgument | LegacyArguments>;
type LegacyCallback = (err: ErrorReply | null, reply?: ReplyUnion) => unknown;
type LegacyCommandArguments = LegacyArguments | [
...args: LegacyArguments,
callback: LegacyCallback
];
type WithCommands = {
[P in keyof typeof COMMANDS]: (...args: LegacyCommandArguments) => void;
};
export type RedisLegacyClientType = RedisLegacyClient & WithCommands;
export declare class RedisLegacyClient {
#private;
static pushArguments(redisArgs: CommandArguments, args: LegacyArguments): void;
static getTransformReply(command: Command, resp: RespVersions): import("../RESP/types").TransformReply | undefined;
constructor(client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>);
sendCommand(...args: LegacyCommandArguments): void;
multi(): RedisLegacyMultiType;
}
type MultiWithCommands = {
[P in keyof typeof COMMANDS]: (...args: LegacyCommandArguments) => RedisLegacyMultiType;
};
export type RedisLegacyMultiType = LegacyMultiCommand & MultiWithCommands;
declare class LegacyMultiCommand {
#private;
static factory(resp: RespVersions): (client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>) => RedisLegacyMultiType;
constructor(client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>);
sendCommand(...args: LegacyArguments): this;
exec(cb?: (err: ErrorReply | null, replies?: Array<unknown>) => unknown): void;
}
export {};
//# sourceMappingURL=legacy-mode.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"legacy-mode.d.ts","sourceRoot":"","sources":["../../../lib/client/legacy-mode.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAgB,MAAM,eAAe,CAAC;AAC9I,OAAO,EAAE,eAAe,EAAE,MAAM,GAAG,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,QAAQ,MAAM,aAAa,CAAC;AAGnC,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAEtD,KAAK,eAAe,GAAG,KAAK,CAAC,cAAc,GAAG,eAAe,CAAC,CAAC;AAE/D,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,UAAU,KAAK,OAAO,CAAA;AAE7E,KAAK,sBAAsB,GAAG,eAAe,GAAG;IAC9C,GAAG,IAAI,EAAE,eAAe;IACxB,QAAQ,EAAE,cAAc;CACzB,CAAC;AAEF,KAAK,YAAY,GAAG;KACjB,CAAC,IAAI,MAAM,OAAO,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,sBAAsB,KAAK,IAAI;CACxE,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG,YAAY,CAAC;AAErE,qBAAa,iBAAiB;;IAY5B,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,eAAe;IAevE,MAAM,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;gBA4B3D,MAAM,EAAE,eAAe,CAAC,YAAY,EAAE,cAAc,EAAE,YAAY,CAAC;IAkBrE,WAAW,CAAC,GAAG,IAAI,EAAE,sBAAsB;IAe3C,KAAK;CAGN;AAED,KAAK,iBAAiB,GAAG;KACtB,CAAC,IAAI,MAAM,OAAO,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,sBAAsB,KAAK,oBAAoB;CACxF,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAE1E,cAAM,kBAAkB;;IAWtB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,YAaf,gBAAgB,YAAY,EAAE,cAAc,EAAE,YAAY,CAAC;gBAQjE,MAAM,EAAE,eAAe,CAAC,YAAY,EAAE,cAAc,EAAE,YAAY,CAAC;IAI/E,WAAW,CAAC,GAAG,IAAI,EAAE,eAAe;IAOpC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,OAAO;CAYxE"}
@@ -0,0 +1,122 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisLegacyClient = void 0;
const types_1 = require("../RESP/types");
const commander_1 = require("../commander");
const commands_1 = __importDefault(require("../commands"));
const multi_command_1 = __importDefault(require("../multi-command"));
class RedisLegacyClient {
static #transformArguments(redisArgs, args) {
let callback;
if (typeof args[args.length - 1] === 'function') {
callback = args.pop();
}
RedisLegacyClient.pushArguments(redisArgs, args);
return callback;
}
static pushArguments(redisArgs, args) {
for (let i = 0; i < args.length; ++i) {
const arg = args[i];
if (Array.isArray(arg)) {
RedisLegacyClient.pushArguments(redisArgs, arg);
}
else {
redisArgs.push(typeof arg === 'number' || arg instanceof Date ?
arg.toString() :
arg);
}
}
}
static getTransformReply(command, resp) {
return command.TRANSFORM_LEGACY_REPLY ?
(0, commander_1.getTransformReply)(command, resp) :
undefined;
}
static #createCommand(name, command, resp) {
const transformReply = RedisLegacyClient.getTransformReply(command, resp);
return function (...args) {
const redisArgs = [name], callback = RedisLegacyClient.#transformArguments(redisArgs, args), promise = this.#client.sendCommand(redisArgs);
if (!callback) {
promise.catch(err => this.#client.emit('error', err));
return;
}
promise
.then(reply => callback(null, transformReply ? transformReply(reply) : reply))
.catch(err => callback(err));
};
}
#client;
#Multi;
constructor(client) {
this.#client = client;
const RESP = client.options?.RESP ?? types_1.DEFAULT_RESP;
for (const [name, command] of Object.entries(commands_1.default)) {
// TODO: as any?
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic command attachment
this[name] = RedisLegacyClient.#createCommand(name, command, RESP);
}
this.#Multi = LegacyMultiCommand.factory(RESP);
}
sendCommand(...args) {
const redisArgs = [], callback = RedisLegacyClient.#transformArguments(redisArgs, args), promise = this.#client.sendCommand(redisArgs);
if (!callback) {
promise.catch(err => this.#client.emit('error', err));
return;
}
promise
.then(reply => callback(null, reply))
.catch(err => callback(err));
}
multi() {
return this.#Multi(this.#client);
}
}
exports.RedisLegacyClient = RedisLegacyClient;
class LegacyMultiCommand {
static #createCommand(name, command, resp) {
const transformReply = RedisLegacyClient.getTransformReply(command, resp);
return function (...args) {
const redisArgs = [name];
RedisLegacyClient.pushArguments(redisArgs, args);
this.#multi.addCommand(redisArgs, transformReply);
return this;
};
}
static factory(resp) {
const Multi = class extends LegacyMultiCommand {
};
for (const [name, command] of Object.entries(commands_1.default)) {
// TODO: as any?
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic command attachment
Multi.prototype[name] = LegacyMultiCommand.#createCommand(name, command, resp);
}
return (client) => {
return new Multi(client);
};
}
#multi = new multi_command_1.default();
#client;
constructor(client) {
this.#client = client;
}
sendCommand(...args) {
const redisArgs = [];
RedisLegacyClient.pushArguments(redisArgs, args);
this.#multi.addCommand(redisArgs);
return this;
}
exec(cb) {
const promise = this.#client._executeMulti(this.#multi.queue);
if (!cb) {
promise.catch(err => this.#client.emit('error', err));
return;
}
promise
.then(results => cb(null, this.#multi.transformReplies(results)))
.catch(err => cb?.(err));
}
}
//# sourceMappingURL=legacy-mode.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
/// <reference types="node" />
import EventEmitter from "events";
export interface DoublyLinkedNode<T> {
value: T;
previous: DoublyLinkedNode<T> | undefined;
next: DoublyLinkedNode<T> | undefined;
}
export declare class DoublyLinkedList<T> {
#private;
get length(): number;
get head(): DoublyLinkedNode<T> | undefined;
get tail(): DoublyLinkedNode<T> | undefined;
push(value: T): {
previous: DoublyLinkedNode<T> | undefined;
next: undefined;
value: T;
};
unshift(value: T): {
previous: undefined;
next: undefined;
value: T;
} | {
previous: undefined;
next: DoublyLinkedNode<T>;
value: T;
};
add(value: T, prepend?: boolean): {
previous: DoublyLinkedNode<T> | undefined;
next: undefined;
value: T;
} | {
previous: undefined;
next: DoublyLinkedNode<T>;
value: T;
};
shift(): T | undefined;
remove(node: DoublyLinkedNode<T>): void;
reset(): void;
[Symbol.iterator](): Generator<T, void, unknown>;
nodes(): Generator<DoublyLinkedNode<T>, void, unknown>;
}
export interface SinglyLinkedNode<T> {
value: T;
next: SinglyLinkedNode<T> | undefined;
removed: boolean;
}
export declare class SinglyLinkedList<T> {
#private;
get length(): number;
get head(): SinglyLinkedNode<T> | undefined;
get tail(): SinglyLinkedNode<T> | undefined;
push(value: T): {
value: T;
next: undefined;
removed: boolean;
};
remove(node: SinglyLinkedNode<T>, parent: SinglyLinkedNode<T> | undefined): void;
shift(): T | undefined;
reset(): void;
[Symbol.iterator](): Generator<T, void, unknown>;
}
export declare class EmptyAwareSinglyLinkedList<T> extends SinglyLinkedList<T> {
readonly events: EventEmitter;
reset(): void;
shift(): T | undefined;
remove(node: SinglyLinkedNode<T>, parent: SinglyLinkedNode<T> | undefined): void;
}
//# sourceMappingURL=linked-list.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"linked-list.d.ts","sourceRoot":"","sources":["../../../lib/client/linked-list.ts"],"names":[],"mappings":";AAAA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAElC,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC1C,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;CACvC;AAED,qBAAa,gBAAgB,CAAC,CAAC;;IAG7B,IAAI,MAAM,WAET;IAID,IAAI,IAAI,oCAEP;IAID,IAAI,IAAI,oCAEP;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;;;;;IAkBb,OAAO,CAAC,KAAK,EAAE,CAAC;;;;;;;;;IAkBhB,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,UAAQ;;;;;;;;;IAM7B,KAAK;IAeL,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAqBhC,KAAK;IAKJ,CAAC,MAAM,CAAC,QAAQ,CAAC;IAQjB,KAAK;CAQP;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IACtC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,gBAAgB,CAAC,CAAC;;IAG7B,IAAI,MAAM,WAET;IAID,IAAI,IAAI,oCAEP;IAID,IAAI,IAAI,oCAEP;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;;;;;IAgBb,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS;IAsBzE,KAAK;IAcL,KAAK;IAKJ,CAAC,MAAM,CAAC,QAAQ,CAAC;CAOnB;AAED,qBAAa,0BAA0B,CAAC,CAAC,CAAE,SAAQ,gBAAgB,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,MAAM,eAAsB;IACrC,KAAK;IAOL,KAAK,IAAI,CAAC,GAAG,SAAS;IAQtB,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS;CAQ1E"}
@@ -0,0 +1,212 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmptyAwareSinglyLinkedList = exports.SinglyLinkedList = exports.DoublyLinkedList = void 0;
const events_1 = __importDefault(require("events"));
class DoublyLinkedList {
#length = 0;
get length() {
return this.#length;
}
#head;
get head() {
return this.#head;
}
#tail;
get tail() {
return this.#tail;
}
push(value) {
++this.#length;
if (this.#tail === undefined) {
return this.#head = this.#tail = {
previous: this.#head,
next: undefined,
value
};
}
return this.#tail = this.#tail.next = {
previous: this.#tail,
next: undefined,
value
};
}
unshift(value) {
++this.#length;
if (this.#head === undefined) {
return this.#head = this.#tail = {
previous: undefined,
next: undefined,
value
};
}
return this.#head = this.#head.previous = {
previous: undefined,
next: this.#head,
value
};
}
add(value, prepend = false) {
return prepend ?
this.unshift(value) :
this.push(value);
}
shift() {
if (this.#head === undefined)
return undefined;
--this.#length;
const node = this.#head;
if (node.next) {
node.next.previous = undefined;
this.#head = node.next;
node.next = undefined;
}
else {
this.#head = this.#tail = undefined;
}
return node.value;
}
remove(node) {
if (this.#length === 0)
return;
--this.#length;
if (this.#tail === node) {
this.#tail = node.previous;
}
if (this.#head === node) {
this.#head = node.next;
}
else {
if (node.previous) {
node.previous.next = node.next;
}
if (node.next) {
node.next.previous = node.previous;
}
}
node.previous = undefined;
node.next = undefined;
}
reset() {
this.#length = 0;
this.#head = this.#tail = undefined;
}
*[Symbol.iterator]() {
let node = this.#head;
while (node !== undefined) {
yield node.value;
node = node.next;
}
}
*nodes() {
let node = this.#head;
while (node) {
const next = node.next;
yield node;
node = next;
}
}
}
exports.DoublyLinkedList = DoublyLinkedList;
class SinglyLinkedList {
#length = 0;
get length() {
return this.#length;
}
#head;
get head() {
return this.#head;
}
#tail;
get tail() {
return this.#tail;
}
push(value) {
++this.#length;
const node = {
value,
next: undefined,
removed: false
};
if (this.#head === undefined) {
return this.#head = this.#tail = node;
}
return this.#tail.next = this.#tail = node;
}
remove(node, parent) {
if (node.removed) {
throw new Error("node already removed");
}
--this.#length;
if (this.#head === node) {
if (this.#tail === node) {
this.#head = this.#tail = undefined;
}
else {
this.#head = node.next;
}
}
else if (this.#tail === node) {
this.#tail = parent;
parent.next = undefined;
}
else {
parent.next = node.next;
}
node.removed = true;
}
shift() {
if (this.#head === undefined)
return undefined;
const node = this.#head;
if (--this.#length === 0) {
this.#head = this.#tail = undefined;
}
else {
this.#head = node.next;
}
node.removed = true;
return node.value;
}
reset() {
this.#length = 0;
this.#head = this.#tail = undefined;
}
*[Symbol.iterator]() {
let node = this.#head;
while (node !== undefined) {
yield node.value;
node = node.next;
}
}
}
exports.SinglyLinkedList = SinglyLinkedList;
class EmptyAwareSinglyLinkedList extends SinglyLinkedList {
events = new events_1.default();
reset() {
const old = this.length;
super.reset();
if (old !== this.length && this.length === 0) {
this.events.emit('empty');
}
}
shift() {
const old = this.length;
const ret = super.shift();
if (old !== this.length && this.length === 0) {
this.events.emit('empty');
}
return ret;
}
remove(node, parent) {
const old = this.length;
super.remove(node, parent);
if (old !== this.length && this.length === 0) {
this.events.emit('empty');
}
}
}
exports.EmptyAwareSinglyLinkedList = EmptyAwareSinglyLinkedList;
//# sourceMappingURL=linked-list.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
import COMMANDS from '../commands';
import { MULTI_MODE, MULTI_REPLY, MultiMode, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command';
import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, TypeMapping, RedisArgument } from '../RESP/types';
import { Tail } from '../commands/generic-transformers';
type CommandSignature<REPLIES extends Array<unknown>, C extends Command, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (...args: Tail<Parameters<C['parseCommand']>>) => RedisClientMultiCommandTyped<[
...REPLIES,
ReplyWithTypeMapping<CommandReply<C, RESP>, TYPE_MAPPING>
], M, F, S, RESP, TYPE_MAPPING>;
type WithCommands<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof typeof COMMANDS]: CommandSignature<REPLIES, (typeof COMMANDS)[P], M, F, S, RESP, TYPE_MAPPING>;
};
type WithModules<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof M]: {
[C in keyof M[P]]: CommandSignature<REPLIES, M[P][C], M, F, S, RESP, TYPE_MAPPING>;
};
};
type WithFunctions<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[L in keyof F]: {
[C in keyof F[L]]: CommandSignature<REPLIES, F[L][C], M, F, S, RESP, TYPE_MAPPING>;
};
};
type WithScripts<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof S]: CommandSignature<REPLIES, S[P], M, F, S, RESP, TYPE_MAPPING>;
};
export type RedisClientMultiCommandTyped<REPLIES extends Array<any>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (RedisClientMultiCommand<REPLIES> & WithCommands<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithModules<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithFunctions<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithScripts<REPLIES, M, F, S, RESP, TYPE_MAPPING>);
type TypedOrAny<Flag extends MultiMode, T> = [
Flag
] extends [MULTI_MODE['TYPED']] ? T : any;
export type RedisClientMultiCommandType<isTyped extends MultiMode, REPLIES extends Array<any>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = TypedOrAny<isTyped, RedisClientMultiCommandTyped<REPLIES, M, F, S, RESP, TYPE_MAPPING>>;
type ExecuteMulti = (commands: Array<RedisMultiQueuedCommand>, selectedDB?: number) => Promise<Array<unknown>>;
export default class RedisClientMultiCommand<REPLIES = []> {
#private;
static extend<M extends RedisModules = Record<string, never>, F extends RedisFunctions = Record<string, never>, S extends RedisScripts = Record<string, never>, RESP extends RespVersions = 3>(config?: CommanderConfig<M, F, S, RESP>): any;
constructor(executeMulti: ExecuteMulti, executePipeline: ExecuteMulti, typeMapping?: TypeMapping, keyPrefix?: RedisArgument);
SELECT(db: number, transformReply?: TransformReply): this;
select: (db: number, transformReply?: TransformReply) => this;
addCommand(args: CommandArguments, transformReply?: TransformReply): this;
exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline?: boolean): Promise<MultiReplyType<T, REPLIES>>;
EXEC: <T extends MultiReply = "generic">(execAsPipeline?: boolean) => Promise<MultiReplyType<T, REPLIES>>;
execTyped(execAsPipeline?: boolean): Promise<REPLIES>;
execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>(): Promise<MultiReplyType<T, REPLIES>>;
execAsPipelineTyped(): Promise<REPLIES>;
/**
* Adds a raw command to the multi/pipeline queue.
*
* Note: Using this method breaks the type inference for `execTyped` and
* `execAsPipelineTyped`. This is a known limitation and will be addressed
* in the future.
*/
sendCommand(args: ReadonlyArray<RedisArgument>): this;
}
export {};
//# sourceMappingURL=multi-command.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"multi-command.d.ts","sourceRoot":"","sources":["../../../lib/client/multi-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commands_1 = __importDefault(require("../commands"));
const multi_command_1 = __importDefault(require("../multi-command"));
const commander_1 = require("../commander");
const parser_1 = require("./parser");
class RedisClientMultiCommand {
static #createCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this.#keyPrefix);
command.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
return this.addCommand(redisArgs, transformReply);
};
}
static #createModuleCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this._self.#keyPrefix);
command.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
return this._self.addCommand(redisArgs, transformReply);
};
}
static #createFunctionCommand(name, fn, resp) {
const prefix = (0, commander_1.functionArgumentsPrefix)(name, fn);
const transformReply = (0, commander_1.getTransformReply)(fn, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this._self.#keyPrefix);
parser.push(...prefix);
fn.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
return this._self.addCommand(redisArgs, transformReply);
};
}
static #createScriptCommand(script, resp) {
const transformReply = (0, commander_1.getTransformReply)(script, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this.#keyPrefix);
script.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
return this.#addScript(script, redisArgs, transformReply);
};
}
static extend(config) {
return (0, commander_1.attachConfig)({
BaseClass: RedisClientMultiCommand,
commands: commands_1.default,
createCommand: RedisClientMultiCommand.#createCommand,
createModuleCommand: RedisClientMultiCommand.#createModuleCommand,
createFunctionCommand: RedisClientMultiCommand.#createFunctionCommand,
createScriptCommand: RedisClientMultiCommand.#createScriptCommand,
config
});
}
#multi;
#executeMulti;
#executePipeline;
#keyPrefix;
#selectedDB;
constructor(executeMulti, executePipeline, typeMapping, keyPrefix) {
this.#multi = new multi_command_1.default(typeMapping);
this.#executeMulti = executeMulti;
this.#executePipeline = executePipeline;
this.#keyPrefix = keyPrefix;
}
SELECT(db, transformReply) {
this.#selectedDB = db;
this.#multi.addCommand(['SELECT', db.toString()], transformReply);
return this;
}
select = this.SELECT;
addCommand(args, transformReply) {
this.#multi.addCommand(args, transformReply);
return this;
}
#addScript(script, args, transformReply) {
this.#multi.addScript(script, args, transformReply);
return this;
}
async exec(execAsPipeline = false) {
if (execAsPipeline)
return this.execAsPipeline();
return this.#multi.transformReplies(await this.#executeMulti(this.#multi.queue, this.#selectedDB));
}
EXEC = this.exec;
execTyped(execAsPipeline = false) {
return this.exec(execAsPipeline);
}
async execAsPipeline() {
if (this.#multi.queue.length === 0)
return [];
return this.#multi.transformReplies(await this.#executePipeline(this.#multi.queue, this.#selectedDB));
}
execAsPipelineTyped() {
return this.execAsPipeline();
}
/**
* Adds a raw command to the multi/pipeline queue.
*
* Note: Using this method breaks the type inference for `execTyped` and
* `execAsPipelineTyped`. This is a known limitation and will be addressed
* in the future.
*/
sendCommand(args) {
const redisArgs = args.slice();
this.#multi.addCommand(redisArgs);
return this;
}
}
exports.default = RedisClientMultiCommand;
//# sourceMappingURL=multi-command.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
import { RedisArgument } from '../RESP/types';
import { RedisVariadicArgument } from '../commands/generic-transformers';
/**
* Prepends `keyPrefix` to `key`.
*
* Returns `key` unchanged when `keyPrefix` is `undefined` (the no-prefix hot path).
* Uses a plain string concatenation when both sides are strings, and falls back to
* `Buffer.concat` when either side is a `Buffer`.
*/
export declare function prefixKey(keyPrefix: RedisArgument | undefined, key: RedisArgument): RedisArgument;
/**
* Applies {@link prefixKey} to every key in a variadic argument, returning a new array.
*
* Used by the few commands that build their wire arguments outside of `parseCommand`
* (e.g. `WATCH`), so they still honor the configured `keyPrefix`.
*/
export declare function prefixKeys(keyPrefix: RedisArgument | undefined, keys: RedisVariadicArgument): Array<RedisArgument>;
export interface CommandParser {
redisArgs: ReadonlyArray<RedisArgument>;
keys: ReadonlyArray<RedisArgument>;
firstKey: RedisArgument | undefined;
preserve: unknown;
push: (...arg: Array<RedisArgument>) => unknown;
pushVariadic: (vals: RedisVariadicArgument) => unknown;
pushVariadicWithLength: (vals: RedisVariadicArgument) => unknown;
pushVariadicNumber: (vals: number | Array<number>) => unknown;
/**
* Push a key, applying the configured `keyPrefix` (if any).
* Pass `applyPrefix = false` to push a routing key that must NOT be prefixed
* (e.g. a sharded Pub/Sub channel in `SPUBLISH`).
*/
pushKey: (key: RedisArgument, applyPrefix?: boolean) => unknown;
pushKeys: (keys: RedisVariadicArgument) => unknown;
pushKeysLength: (keys: RedisVariadicArgument) => unknown;
}
export declare class BasicCommandParser implements CommandParser {
#private;
preserve: unknown;
/**
* @param keyPrefix Optional prefix prepended to every key pushed via
* `pushKey`/`pushKeys`/`pushKeysLength`. Empty values are treated as "no prefix".
*/
constructor(keyPrefix?: RedisArgument);
get redisArgs(): RedisArgument[];
get keys(): RedisArgument[];
get firstKey(): RedisArgument;
get cacheKey(): string;
push(...arg: Array<RedisArgument>): void;
pushVariadic(vals: RedisVariadicArgument): void;
pushVariadicWithLength(vals: RedisVariadicArgument): void;
pushVariadicNumber(vals: number | number[]): void;
pushKey(key: RedisArgument, applyPrefix?: boolean): void;
pushKeysLength(keys: RedisVariadicArgument): void;
pushKeys(keys: RedisVariadicArgument): void;
}
//# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../lib/client/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,aAAa,CAWjG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,IAAI,EAAE,qBAAqB,GAAG,KAAK,CAAC,aAAa,CAAC,CAIlH;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IACxC,IAAI,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IACnC,QAAQ,EAAE,aAAa,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAElB,IAAI,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC;IAChD,YAAY,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC;IACvD,sBAAsB,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC;IACjE,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAC9D;;;;OAIG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAChE,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC;IACnD,cAAc,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC;CAC1D;AAED,qBAAa,kBAAmB,YAAW,aAAa;;IAItD,QAAQ,EAAE,OAAO,CAAC;IAElB;;;OAGG;gBACS,SAAS,CAAC,EAAE,aAAa;IAIrC,IAAI,SAAS,oBAEZ;IAED,IAAI,IAAI,oBAEP;IAED,IAAI,QAAQ,kBAEX;IAED,IAAI,QAAQ,WASX;IAED,IAAI,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC;IAIjC,YAAY,CAAC,IAAI,EAAE,qBAAqB;IAUxC,sBAAsB,CAAC,IAAI,EAAE,qBAAqB;IASlD,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAoB1C,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,WAAW,UAAO;IAI9C,cAAc,CAAC,IAAI,EAAE,qBAAqB;IAS1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB;CASrC"}
@@ -0,0 +1,130 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BasicCommandParser = exports.prefixKeys = exports.prefixKey = void 0;
/**
* Prepends `keyPrefix` to `key`.
*
* Returns `key` unchanged when `keyPrefix` is `undefined` (the no-prefix hot path).
* Uses a plain string concatenation when both sides are strings, and falls back to
* `Buffer.concat` when either side is a `Buffer`.
*/
function prefixKey(keyPrefix, key) {
if (keyPrefix === undefined)
return key;
if (typeof keyPrefix === 'string' && typeof key === 'string') {
return keyPrefix + key;
}
return Buffer.concat([
typeof keyPrefix === 'string' ? Buffer.from(keyPrefix) : keyPrefix,
typeof key === 'string' ? Buffer.from(key) : key
]);
}
exports.prefixKey = prefixKey;
/**
* Applies {@link prefixKey} to every key in a variadic argument, returning a new array.
*
* Used by the few commands that build their wire arguments outside of `parseCommand`
* (e.g. `WATCH`), so they still honor the configured `keyPrefix`.
*/
function prefixKeys(keyPrefix, keys) {
return Array.isArray(keys)
? keys.map(key => prefixKey(keyPrefix, key))
: [prefixKey(keyPrefix, keys)];
}
exports.prefixKeys = prefixKeys;
class BasicCommandParser {
#redisArgs = [];
#keys = [];
#keyPrefix;
preserve;
/**
* @param keyPrefix Optional prefix prepended to every key pushed via
* `pushKey`/`pushKeys`/`pushKeysLength`. Empty values are treated as "no prefix".
*/
constructor(keyPrefix) {
this.#keyPrefix = keyPrefix === undefined || keyPrefix.length === 0 ? undefined : keyPrefix;
}
get redisArgs() {
return this.#redisArgs;
}
get keys() {
return this.#keys;
}
get firstKey() {
return this.#keys[0];
}
get cacheKey() {
const tmp = new Array(this.#redisArgs.length * 2);
for (let i = 0; i < this.#redisArgs.length; i++) {
tmp[i] = this.#redisArgs[i].length;
tmp[i + this.#redisArgs.length] = this.#redisArgs[i];
}
return tmp.join('_');
}
push(...arg) {
this.#redisArgs.push(...arg);
}
;
pushVariadic(vals) {
if (Array.isArray(vals)) {
for (const val of vals) {
this.push(val);
}
}
else {
this.push(vals);
}
}
pushVariadicWithLength(vals) {
if (Array.isArray(vals)) {
this.#redisArgs.push(vals.length.toString());
}
else {
this.#redisArgs.push('1');
}
this.pushVariadic(vals);
}
pushVariadicNumber(vals) {
if (Array.isArray(vals)) {
for (const val of vals) {
this.push(val.toString());
}
}
else {
this.push(vals.toString());
}
}
/**
* Registers a key as both a routing key (`keys`) and a wire argument (`redisArgs`),
* prepending `keyPrefix` when `applyPrefix` is true.
*/
#addKey(key, applyPrefix) {
const finalKey = applyPrefix ? prefixKey(this.#keyPrefix, key) : key;
this.#keys.push(finalKey);
this.#redisArgs.push(finalKey);
}
pushKey(key, applyPrefix = true) {
this.#addKey(key, applyPrefix);
}
pushKeysLength(keys) {
if (Array.isArray(keys)) {
this.#redisArgs.push(keys.length.toString());
}
else {
this.#redisArgs.push('1');
}
this.pushKeys(keys);
}
pushKeys(keys) {
if (Array.isArray(keys)) {
for (const key of keys) {
this.#addKey(key, true);
}
}
else {
this.#addKey(keys, true);
}
}
}
exports.BasicCommandParser = BasicCommandParser;
//# sourceMappingURL=parser.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,147 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { NON_STICKY_COMMANDS } from '../commands';
import { CommandSignature, RedisArgument, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping } from '../RESP/types';
import { RedisClientType, RedisClientOptions, WithModules, WithFunctions, WithScripts } from '.';
import { EventEmitter } from 'node:events';
import { CommandOptions } from './commands-queue';
import { RedisClientMultiCommandType } from './multi-command';
import { ClientSideCacheConfig, PooledClientSideCacheProvider } from './cache';
import { MULTI_MODE, MultiMode } from '../multi-command';
import { ClientIdentity } from './identity';
export interface RedisPoolOptions {
/**
* The minimum number of clients to keep in the pool (>= 1).
*/
minimum: number;
/**
* The maximum number of clients to keep in the pool (>= {@link RedisPoolOptions.minimum} >= 1).
*/
maximum: number;
/**
* The maximum time a task can wait for a client to become available (>= 0).
*/
acquireTimeout: number;
/**
* The delay in milliseconds before a cleanup operation is performed on idle clients.
*
* After this delay, the pool will check if there are too many idle clients and destroy
* excess ones to maintain optimal pool size.
*/
cleanupDelay: number;
/**
* Client Side Caching configuration for the pool.
*
* Enables Redis Servers and Clients to work together to cache results from commands
* sent to a server. The server will notify the client when cached results are no longer valid.
* In pooled mode, the cache is shared across all clients in the pool.
*
* Note: Client Side Caching is only supported with RESP3.
*
* @example Anonymous cache configuration
* ```
* const client = createClientPool({RESP: 3}, {
* clientSideCache: {
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* },
* minimum: 5
* });
* ```
*
* @example Using a controllable cache
* ```
* const cache = new BasicPooledClientSideCache({
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* });
* const client = createClientPool({RESP: 3}, {
* clientSideCache: cache,
* minimum: 5
* });
* ```
*/
clientSideCache?: PooledClientSideCacheProvider | ClientSideCacheConfig;
}
export type PoolTask<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping, T = unknown> = (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>) => T;
type PoolWithCommands<RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof typeof NON_STICKY_COMMANDS]: CommandSignature<(typeof NON_STICKY_COMMANDS)[P], RESP, TYPE_MAPPING>;
};
export type RedisClientPoolType<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}> = (RedisClientPool<M, F, S, RESP, TYPE_MAPPING> & PoolWithCommands<RESP, TYPE_MAPPING> & WithModules<M, RESP, TYPE_MAPPING> & WithFunctions<F, RESP, TYPE_MAPPING> & WithScripts<S, RESP, TYPE_MAPPING>);
export declare class RedisClientPool<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}> extends EventEmitter {
#private;
static create<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping = {}>(clientOptions?: Omit<RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>, "clientSideCache">, options?: Partial<RedisPoolOptions>): RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING>;
/**
* The number of idle clients.
*/
get idleClients(): number;
/**
* The number of clients in use.
*/
get clientsInUse(): number;
/**
* The total number of clients in the pool (including connecting, idle, and in use).
*/
get totalClients(): number;
/**
* The number of tasks waiting for a client to become available.
*/
get tasksQueueLength(): number;
/**
* Whether the pool is open (either connecting or connected).
*/
get isOpen(): boolean;
/**
* Whether the pool is closing (*not* closed).
*/
get isClosing(): boolean;
get clientSideCache(): PooledClientSideCacheProvider | undefined;
/**
* @internal
* Returns the pool identity for tracking in metrics.
*/
get identity(): ClientIdentity;
/**
* You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`},
* {@link RedisClientPool.fromClient `RedisClientPool.fromClient`},
* or {@link RedisClientPool.fromOptions `RedisClientPool.fromOptions`}...
*/
constructor(clientOptions?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>, options?: Partial<RedisPoolOptions>);
private _self;
private _commandOptions?;
/**
* The configured key prefix (see {@link RedisClientOptions.keyPrefix}), if any.
* The pool builds the (prefixed) parser itself, so it is stripped from the options
* handed to pooled clients to avoid double-prefixing.
* @internal
*/
_keyPrefix?: RedisArgument;
withCommandOptions<OPTIONS extends CommandOptions<TYPE_MAPPING>, TYPE_MAPPING extends TypeMapping>(options: OPTIONS): RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
private _commandOptionsProxy;
/**
* Override the `typeMapping` command option
*/
withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING): RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
/**
* Override the `abortSignal` command option
*/
withAbortSignal(abortSignal: AbortSignal): RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING>;
/**
* Override the `asap` command option to `true`
* TODO: remove?
*/
asap(): RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING>;
connect(): Promise<RedisClientPoolType<M, F, S, RESP, TYPE_MAPPING> | undefined>;
execute<T>(fn: PoolTask<M, F, S, RESP, TYPE_MAPPING, T>): Promise<Awaited<T>>;
cleanupTimeout?: NodeJS.Timeout;
sendCommand(args: Array<RedisArgument>, options?: CommandOptions): Promise<import("../RESP/types").ReplyUnion>;
MULTI<isTyped extends MultiMode = MULTI_MODE['TYPED']>(): RedisClientMultiCommandType<isTyped, [], M, F, S, RESP, TYPE_MAPPING>;
multi: <isTyped extends MultiMode = "typed">() => RedisClientMultiCommandType<isTyped, [], M, F, S, RESP, TYPE_MAPPING>;
close(): Promise<void>;
destroy(): void;
}
export {};
//# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../lib/client/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,394 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisClientPool = void 0;
const commands_1 = require("../commands");
const _1 = __importDefault(require("."));
const node_events_1 = require("node:events");
const node_perf_hooks_1 = require("node:perf_hooks");
const linked_list_1 = require("./linked-list");
const errors_1 = require("../errors");
const commander_1 = require("../commander");
const multi_command_1 = __importDefault(require("./multi-command"));
const cache_1 = require("./cache");
const parser_1 = require("./parser");
const single_entry_cache_1 = __importDefault(require("../single-entry-cache"));
const tracing_1 = require("./tracing");
const identity_1 = require("./identity");
class RedisClientPool extends node_events_1.EventEmitter {
static #createCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this.execute(client => client._executeCommand(command, parser, this._commandOptions, transformReply));
};
}
static #createModuleCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this._self.execute(client => client._executeCommand(command, parser, this._self._commandOptions, transformReply));
};
}
static #createFunctionCommand(name, fn, resp) {
const prefix = (0, commander_1.functionArgumentsPrefix)(name, fn);
const transformReply = (0, commander_1.getTransformReply)(fn, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.push(...prefix);
fn.parseCommand(parser, ...args);
return this._self.execute(client => client._executeCommand(fn, parser, this._self._commandOptions, transformReply));
};
}
static #createScriptCommand(script, resp) {
const prefix = (0, commander_1.scriptArgumentsPrefix)(script);
const transformReply = (0, commander_1.getTransformReply)(script, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.pushVariadic(prefix);
script.parseCommand(parser, ...args);
return this.execute(client => client._executeScript(script, parser, this._commandOptions, transformReply));
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- generic cache, keys/values vary per call site
static #SingleEntryCache = new single_entry_cache_1.default();
static create(clientOptions, options) {
let Pool = RedisClientPool.#SingleEntryCache.get(clientOptions);
if (!Pool) {
Pool = (0, commander_1.attachConfig)({
BaseClass: RedisClientPool,
commands: commands_1.NON_STICKY_COMMANDS,
createCommand: RedisClientPool.#createCommand,
createModuleCommand: RedisClientPool.#createModuleCommand,
createFunctionCommand: RedisClientPool.#createFunctionCommand,
createScriptCommand: RedisClientPool.#createScriptCommand,
config: clientOptions
});
Pool.prototype.Multi = multi_command_1.default.extend(clientOptions);
RedisClientPool.#SingleEntryCache.set(clientOptions, Pool);
}
// returning a "proxy" to prevent the namespaces._self to leak between "proxies"
return Object.create(new Pool(clientOptions, options));
}
// TODO: defaults
static #DEFAULTS = {
minimum: 1,
maximum: 100,
acquireTimeout: 3000,
cleanupDelay: 3000
};
#clientFactory;
#options;
#identity;
#idleClients = new linked_list_1.SinglyLinkedList();
/**
* The number of idle clients.
*/
get idleClients() {
return this._self.#idleClients.length;
}
#clientsInUse = new linked_list_1.DoublyLinkedList();
/**
* The number of clients in use.
*/
get clientsInUse() {
return this._self.#clientsInUse.length;
}
/**
* The total number of clients in the pool (including connecting, idle, and in use).
*/
get totalClients() {
return this._self.#idleClients.length + this._self.#clientsInUse.length;
}
#tasksQueue = new linked_list_1.SinglyLinkedList();
/**
* The number of tasks waiting for a client to become available.
*/
get tasksQueueLength() {
return this._self.#tasksQueue.length;
}
#isOpen = false;
/**
* Whether the pool is open (either connecting or connected).
*/
get isOpen() {
return this._self.#isOpen;
}
#isClosing = false;
/**
* Whether the pool is closing (*not* closed).
*/
get isClosing() {
return this._self.#isClosing;
}
/**
* Resolve function called when all in-flight tasks complete during close().
* Used to signal that the pool has finished draining and clients can be closed.
*/
#drainResolve;
#clientSideCache;
get clientSideCache() {
return this._self.#clientSideCache;
}
/**
* @internal
* Returns the pool identity for tracking in metrics.
*/
get identity() {
return this._self.#identity;
}
/**
* You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`},
* {@link RedisClientPool.fromClient `RedisClientPool.fromClient`},
* or {@link RedisClientPool.fromOptions `RedisClientPool.fromOptions`}...
*/
constructor(clientOptions, options) {
super();
const socketOpts = clientOptions?.socket;
this.#identity = {
id: (0, identity_1.generateClientId)(socketOpts?.host, socketOpts?.port, clientOptions?.database),
role: identity_1.ClientRole.POOL,
};
this.#options = {
...RedisClientPool.#DEFAULTS,
...options
};
if (options?.clientSideCache) {
if (clientOptions === undefined) {
clientOptions = {};
}
if (options.clientSideCache instanceof cache_1.PooledClientSideCacheProvider) {
this.#clientSideCache = clientOptions.clientSideCache = options.clientSideCache;
}
else {
const cscConfig = options.clientSideCache;
this.#clientSideCache = clientOptions.clientSideCache = new cache_1.BasicPooledClientSideCache(cscConfig);
// this.#clientSideCache = clientOptions.clientSideCache = new PooledNoRedirectClientSideCache(cscConfig);
}
}
// Capture the key prefix for the pool's own parser construction, then strip it from
// the pooled clients' options: the pool builds the (already prefixed) parser and hands
// it to a pooled client, which never re-parses — so inner clients must not re-prefix.
this._keyPrefix = clientOptions?.keyPrefix;
if (clientOptions?.keyPrefix !== undefined) {
clientOptions = { ...clientOptions, keyPrefix: undefined };
}
this.#clientFactory = _1.default.factory(clientOptions).bind(undefined, clientOptions);
this._commandOptions = clientOptions?.commandOptions;
}
_self = this;
_commandOptions;
/**
* The configured key prefix (see {@link RedisClientOptions.keyPrefix}), if any.
* The pool builds the (prefixed) parser itself, so it is stripped from the options
* handed to pooled clients to avoid double-prefixing.
* @internal
*/
_keyPrefix;
withCommandOptions(options) {
const proxy = Object.create(this._self);
proxy._commandOptions = options;
return proxy;
}
// Plain (not `#`) method so it can be invoked on prototype-derived proxies
// returned by `withCommandOptions(...)` — JS private (`#`) methods aren't
// accessible through the prototype chain, which would force the helper to
// be called via `this._self`, discarding any prior proxy overrides.
_commandOptionsProxy(key, value) {
const proxy = Object.create(this._self);
proxy._commandOptions = { ...this._commandOptions, [key]: value };
return proxy;
}
/**
* Override the `typeMapping` command option
*/
withTypeMapping(typeMapping) {
return this._commandOptionsProxy('typeMapping', typeMapping);
}
/**
* Override the `abortSignal` command option
*/
withAbortSignal(abortSignal) {
return this._commandOptionsProxy('abortSignal', abortSignal);
}
/**
* Override the `asap` command option to `true`
* TODO: remove?
*/
asap() {
return this._commandOptionsProxy('asap', true);
}
async connect() {
if (this._self.#isOpen)
return; // TODO: throw error?
this._self.#isOpen = true;
const promises = [];
while (promises.length < this._self.#options.minimum) {
promises.push(this._self.#create());
}
try {
await Promise.all(promises);
}
catch (err) {
this.destroy();
throw err;
}
return this;
}
async #create() {
const client = this._self.#clientFactory();
client._setIdentity(identity_1.ClientRole.POOL_MEMBER, this._self.#identity.id);
client.on('error', (err) => this.emit('error', err));
// Track the client as "in use" during connect so it counts toward capacity.
// If we waited to add it until after connect, the pool would think it doesn't
// exist yet and could spin up extra clients when multiple tasks queue up.
const node = this._self.#clientsInUse.push(client);
try {
await client.connect();
}
catch (err) {
this._self.#clientsInUse.remove(node);
throw err;
}
this._self.#returnClient(node);
}
execute(fn) {
return new Promise((resolve, reject) => {
if (this._self.#isClosing || !this._self.#isOpen) {
return reject(new errors_1.ClientClosedError());
}
const waitStartTimestamp = node_perf_hooks_1.performance.now();
const client = this._self.#idleClients.shift(), { tail } = this._self.#tasksQueue;
if (!client) {
let timeout;
if (this._self.#options.acquireTimeout > 0) {
timeout = setTimeout(() => {
this._self.#tasksQueue.remove(task, tail);
reject(new errors_1.TimeoutError('Timeout waiting for a client')); // TODO: message
}, this._self.#options.acquireTimeout);
}
const task = this._self.#tasksQueue.push({
timeout,
// @ts-expect-error -- resolve generic variance
resolve,
reject,
fn,
waitStartTimestamp,
});
if (this.totalClients < this._self.#options.maximum) {
this._self.#create();
}
return;
}
const node = this._self.#clientsInUse.push(client);
(0, tracing_1.publish)(tracing_1.CHANNELS.POOL_CONNECTION_WAIT, () => ({ clientId: client._clientId, waitStartTimestamp }));
// @ts-expect-error -- resolve generic variance
this._self.#executeTask(node, resolve, reject, fn);
});
}
#executeTask(node, resolve, reject, fn) {
const result = fn(node.value);
if (result instanceof Promise) {
result
.then(resolve, reject)
.finally(() => {
this.#returnClient(node);
});
}
else {
resolve(result);
this.#returnClient(node);
}
}
#returnClient(node) {
const task = this.#tasksQueue.shift();
if (task) {
clearTimeout(task.timeout);
(0, tracing_1.publish)(tracing_1.CHANNELS.POOL_CONNECTION_WAIT, () => ({ clientId: node.value._clientId, waitStartTimestamp: task.waitStartTimestamp }));
this.#executeTask(node, task.resolve, task.reject, task.fn);
return;
}
this.#clientsInUse.remove(node);
this.#idleClients.push(node.value);
// If closing and all tasks are done, signal the drain is complete
if (this.#isClosing && this.#clientsInUse.length === 0) {
this.#drainResolve?.();
return;
}
this.#scheduleCleanup();
}
cleanupTimeout;
#scheduleCleanup() {
if (this.totalClients <= this.#options.minimum)
return;
clearTimeout(this.cleanupTimeout);
this.cleanupTimeout = setTimeout(() => this.#cleanup(), this.#options.cleanupDelay);
}
#cleanup() {
const toDestroy = Math.min(this.#idleClients.length, this.totalClients - this.#options.minimum);
for (let i = 0; i < toDestroy; i++) {
// TODO: shift vs pop
const client = this.#idleClients.shift();
client.destroy();
}
}
sendCommand(args, options) {
const mergedOptions = { ...this._commandOptions, ...options };
return this.execute(client => client.sendCommand(args, mergedOptions));
}
MULTI() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- access to dynamic Multi class
return new this.Multi((commands, selectedDB) => this.execute(client => client._executeMulti(commands, selectedDB)), commands => this.execute(client => client._executePipeline(commands)), this._commandOptions?.typeMapping, this._self._keyPrefix);
}
multi = this.MULTI;
async close() {
if (this._self.#isClosing)
return; // TODO: throw err?
if (!this._self.#isOpen)
return; // TODO: throw err?
this._self.#isClosing = true;
clearTimeout(this._self.cleanupTimeout);
try {
// Wait for all in-flight and queued tasks to complete
if (this._self.#clientsInUse.length > 0) {
await new Promise(resolve => {
this._self.#drainResolve = resolve;
});
}
// Now all tasks are done, close all clients (which are now idle)
const promises = [];
for (const client of this._self.#idleClients) {
promises.push(client.close());
}
await Promise.all(promises);
this._self.#clientSideCache?.onPoolClose();
this._self.#idleClients.reset();
this._self.#clientsInUse.reset();
}
catch {
}
finally {
this._self.#drainResolve = undefined;
this._self.#isClosing = false;
this._self.#isOpen = false;
}
}
destroy() {
for (const client of this._self.#idleClients) {
client.destroy();
}
this._self.#idleClients.reset();
for (const client of this._self.#clientsInUse) {
client.destroy();
}
this._self.#clientSideCache?.onPoolClose();
this._self.#clientsInUse.reset();
this._self.#isOpen = false;
}
}
exports.RedisClientPool = RedisClientPool;
//# sourceMappingURL=pool.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
/// <reference types="node" />
import { RedisArgument } from '../RESP/types';
import { CommandToWrite } from './commands-queue';
export declare const PUBSUB_TYPE: {
readonly CHANNELS: "CHANNELS";
readonly PATTERNS: "PATTERNS";
readonly SHARDED: "SHARDED";
};
export type PUBSUB_TYPE = typeof PUBSUB_TYPE;
export type PubSubType = PUBSUB_TYPE[keyof PUBSUB_TYPE];
export type PubSubListener<RETURN_BUFFERS extends boolean = false> = <T extends RETURN_BUFFERS extends true ? Buffer : string>(message: T, channel: T) => unknown;
export interface ChannelListeners {
unsubscribing: boolean;
buffers: Set<PubSubListener<true>>;
strings: Set<PubSubListener<false>>;
}
export type PubSubTypeListeners = Map<string, ChannelListeners>;
export type PubSubListeners = Record<PubSubType, PubSubTypeListeners>;
export type PubSubCommand = (Required<Pick<CommandToWrite, 'args' | 'channelsCounter' | 'resolve'>> & {
reject: undefined | (() => unknown);
});
export declare class PubSub {
#private;
constructor(clientId: string);
static isStatusReply(reply: Array<Buffer>): boolean;
static isShardedUnsubscribe(reply: Array<Buffer>): boolean;
get isActive(): boolean;
readonly listeners: PubSubListeners;
subscribe<T extends boolean>(type: PubSubType, channels: string | Array<string>, listener: PubSubListener<T>, returnBuffers?: T): {
args: RedisArgument[];
channelsCounter: number;
resolve: () => void;
reject: () => void;
} | undefined;
extendChannelListeners(type: PubSubType, channel: string, listeners: ChannelListeners): {
args: (string | Buffer)[];
channelsCounter: number;
resolve: () => number;
reject: () => void;
} | undefined;
extendTypeListeners(type: PubSubType, listeners: PubSubTypeListeners): {
args: RedisArgument[];
channelsCounter: number;
resolve: () => number;
reject: () => void;
} | undefined;
unsubscribe<T extends boolean>(type: PubSubType, channels?: string | Array<string>, listener?: PubSubListener<T>, returnBuffers?: T): {
args: RedisArgument[];
channelsCounter: number;
resolve: () => void;
reject: undefined;
} | undefined;
reset(): void;
resubscribe(): PubSubCommand[];
handleMessageReply(reply: Array<Buffer>): boolean;
removeShardedListeners(channel: string): ChannelListeners;
removeAllListeners(): {
CHANNELS: PubSubTypeListeners;
PATTERNS: PubSubTypeListeners;
SHARDED: PubSubTypeListeners;
};
removeShardedPubSubListenersForSlots(slots: Set<number>): {
SHARDED: Map<string, ChannelListeners>;
};
}
//# sourceMappingURL=pub-sub.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"pub-sub.d.ts","sourceRoot":"","sources":["../../../lib/client/pub-sub.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIlD,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,WAAW,CAAC,CAAC;AAoBxD,MAAM,MAAM,cAAc,CACxB,cAAc,SAAS,OAAO,GAAG,KAAK,IACpC,CAAC,CAAC,SAAS,cAAc,SAAS,IAAI,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC;AAEjG,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;CACrC;AAED,MAAM,MAAM,mBAAmB,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,CAC1B,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAAC,CAAC,GAAG;IACvE,MAAM,EAAE,SAAS,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;CACrC,CACF,CAAC;AAEF,qBAAa,MAAM;;gBAGL,QAAQ,EAAE,MAAM;IAI5B,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO;IAWnD,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO;IAoB1D,IAAI,QAAQ,YAEX;IAED,QAAQ,CAAC,SAAS,EAAE,eAAe,CAIjC;IAEF,SAAS,CAAC,CAAC,SAAS,OAAO,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EAChC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAC3B,aAAa,CAAC,EAAE,CAAC;;;;;;IAkDnB,sBAAsB,CACpB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB;;;;;;IA0C7B,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,mBAAmB;;;;;;IAuBpE,WAAW,CAAC,CAAC,SAAS,OAAO,EAC3B,IAAI,EAAE,UAAU,EAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EACjC,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,EAC5B,aAAa,CAAC,EAAE,CAAC;;;;;;IAsGnB,KAAK;IAKL,WAAW;IA+CX,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO;IA6BjD,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB;IAOzD,kBAAkB;;;;;IAgBlB,oCAAoC,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;;;CAkDxD"}
@@ -0,0 +1,351 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PubSub = exports.PUBSUB_TYPE = void 0;
const cluster_key_slot_1 = __importDefault(require("cluster-key-slot"));
const tracing_1 = require("./tracing");
exports.PUBSUB_TYPE = {
CHANNELS: 'CHANNELS',
PATTERNS: 'PATTERNS',
SHARDED: 'SHARDED'
};
const COMMANDS = {
[exports.PUBSUB_TYPE.CHANNELS]: {
subscribe: Buffer.from('subscribe'),
unsubscribe: Buffer.from('unsubscribe'),
message: Buffer.from('message')
},
[exports.PUBSUB_TYPE.PATTERNS]: {
subscribe: Buffer.from('psubscribe'),
unsubscribe: Buffer.from('punsubscribe'),
message: Buffer.from('pmessage')
},
[exports.PUBSUB_TYPE.SHARDED]: {
subscribe: Buffer.from('ssubscribe'),
unsubscribe: Buffer.from('sunsubscribe'),
message: Buffer.from('smessage')
}
};
class PubSub {
#clientId;
constructor(clientId) {
this.#clientId = clientId;
}
static isStatusReply(reply) {
const firstElement = typeof reply[0] === 'string' ? Buffer.from(reply[0]) : reply[0];
return (COMMANDS[exports.PUBSUB_TYPE.CHANNELS].subscribe.equals(firstElement) ||
COMMANDS[exports.PUBSUB_TYPE.CHANNELS].unsubscribe.equals(firstElement) ||
COMMANDS[exports.PUBSUB_TYPE.PATTERNS].subscribe.equals(firstElement) ||
COMMANDS[exports.PUBSUB_TYPE.PATTERNS].unsubscribe.equals(firstElement) ||
COMMANDS[exports.PUBSUB_TYPE.SHARDED].subscribe.equals(firstElement));
}
static isShardedUnsubscribe(reply) {
const firstElement = typeof reply[0] === 'string' ? Buffer.from(reply[0]) : reply[0];
return COMMANDS[exports.PUBSUB_TYPE.SHARDED].unsubscribe.equals(firstElement);
}
static #channelsArray(channels) {
return (Array.isArray(channels) ? channels : [channels]);
}
static #listenersSet(listeners, returnBuffers) {
return (returnBuffers ? listeners.buffers : listeners.strings);
}
#subscribing = 0;
#isActive = false;
get isActive() {
return this.#isActive;
}
listeners = {
[exports.PUBSUB_TYPE.CHANNELS]: new Map(),
[exports.PUBSUB_TYPE.PATTERNS]: new Map(),
[exports.PUBSUB_TYPE.SHARDED]: new Map()
};
subscribe(type, channels, listener, returnBuffers) {
const args = [COMMANDS[type].subscribe], channelsArray = PubSub.#channelsArray(channels);
for (const channel of channelsArray) {
let channelListeners = this.listeners[type].get(channel);
if (!channelListeners || channelListeners.unsubscribing) {
args.push(channel);
}
}
if (args.length === 1) {
// all channels are already subscribed, add listeners without issuing a command
for (const channel of channelsArray) {
PubSub.#listenersSet(this.listeners[type].get(channel), returnBuffers).add(listener);
}
return;
}
this.#isActive = true;
this.#subscribing++;
return {
args,
channelsCounter: args.length - 1,
resolve: () => {
this.#subscribing--;
for (const channel of channelsArray) {
let listeners = this.listeners[type].get(channel);
if (!listeners) {
listeners = {
unsubscribing: false,
buffers: new Set(),
strings: new Set()
};
this.listeners[type].set(channel, listeners);
}
PubSub.#listenersSet(listeners, returnBuffers).add(listener);
}
},
reject: () => {
this.#subscribing--;
this.#updateIsActive();
}
};
}
extendChannelListeners(type, channel, listeners) {
if (!this.#extendChannelListeners(type, channel, listeners))
return;
this.#isActive = true;
this.#subscribing++;
return {
args: [
COMMANDS[type].subscribe,
channel
],
channelsCounter: 1,
resolve: () => this.#subscribing--,
reject: () => {
this.#subscribing--;
this.#updateIsActive();
}
};
}
#extendChannelListeners(type, channel, listeners) {
const existingListeners = this.listeners[type].get(channel);
if (!existingListeners) {
this.listeners[type].set(channel, listeners);
return true;
}
for (const listener of listeners.buffers) {
existingListeners.buffers.add(listener);
}
for (const listener of listeners.strings) {
existingListeners.strings.add(listener);
}
return false;
}
extendTypeListeners(type, listeners) {
const args = [COMMANDS[type].subscribe];
for (const [channel, channelListeners] of listeners) {
if (this.#extendChannelListeners(type, channel, channelListeners)) {
args.push(channel);
}
}
if (args.length === 1)
return;
this.#isActive = true;
this.#subscribing++;
return {
args,
channelsCounter: args.length - 1,
resolve: () => this.#subscribing--,
reject: () => {
this.#subscribing--;
this.#updateIsActive();
}
};
}
unsubscribe(type, channels, listener, returnBuffers) {
const listeners = this.listeners[type];
if (!channels) {
return this.#unsubscribeCommand([COMMANDS[type].unsubscribe],
// cannot use `this.#subscribed` because there might be some `SUBSCRIBE` commands in the queue
// cannot use `this.#subscribed + this.#subscribing` because some `SUBSCRIBE` commands might fail
NaN, () => listeners.clear());
}
const channelsArray = PubSub.#channelsArray(channels);
if (!listener) {
return this.#unsubscribeCommand([COMMANDS[type].unsubscribe, ...channelsArray], channelsArray.length, () => {
for (const channel of channelsArray) {
listeners.delete(channel);
}
});
}
const args = [COMMANDS[type].unsubscribe];
for (const channel of channelsArray) {
const sets = listeners.get(channel);
if (sets) {
let current, other;
if (returnBuffers) {
current = sets.buffers;
other = sets.strings;
}
else {
current = sets.strings;
other = sets.buffers;
}
const currentSize = current.has(listener) ? current.size - 1 : current.size;
if (currentSize !== 0 || other.size !== 0)
continue;
sets.unsubscribing = true;
}
args.push(channel);
}
if (args.length === 1) {
// all channels has other listeners,
// delete the listeners without issuing a command
for (const channel of channelsArray) {
PubSub.#listenersSet(listeners.get(channel), returnBuffers).delete(listener);
}
return;
}
return this.#unsubscribeCommand(args, args.length - 1, () => {
for (const channel of channelsArray) {
const sets = listeners.get(channel);
if (!sets)
continue;
(returnBuffers ? sets.buffers : sets.strings).delete(listener);
if (sets.buffers.size === 0 && sets.strings.size === 0) {
listeners.delete(channel);
}
}
});
}
#unsubscribeCommand(args, channelsCounter, removeListeners) {
return {
args,
channelsCounter,
resolve: () => {
removeListeners();
this.#updateIsActive();
},
reject: undefined
};
}
#updateIsActive() {
this.#isActive = (this.listeners[exports.PUBSUB_TYPE.CHANNELS].size !== 0 ||
this.listeners[exports.PUBSUB_TYPE.PATTERNS].size !== 0 ||
this.listeners[exports.PUBSUB_TYPE.SHARDED].size !== 0 ||
this.#subscribing !== 0);
}
reset() {
this.#isActive = false;
this.#subscribing = 0;
}
resubscribe() {
const commands = [];
for (const [type, listeners] of Object.entries(this.listeners)) {
if (!listeners.size)
continue;
this.#isActive = true;
if (type === exports.PUBSUB_TYPE.SHARDED) {
this.#shardedResubscribe(commands, listeners);
}
else {
this.#normalResubscribe(commands, type, listeners);
}
}
return commands;
}
#normalResubscribe(commands, type, listeners) {
this.#subscribing++;
const callback = () => this.#subscribing--;
commands.push({
args: [
COMMANDS[type].subscribe,
...listeners.keys()
],
channelsCounter: listeners.size,
resolve: callback,
reject: callback
});
}
#shardedResubscribe(commands, listeners) {
const callback = () => this.#subscribing--;
for (const channel of listeners.keys()) {
this.#subscribing++;
commands.push({
args: [
COMMANDS[exports.PUBSUB_TYPE.SHARDED].subscribe,
channel
],
channelsCounter: 1,
resolve: callback,
reject: callback
});
}
}
handleMessageReply(reply) {
const firstElement = typeof reply[0] === 'string' ? Buffer.from(reply[0]) : reply[0];
if (COMMANDS[exports.PUBSUB_TYPE.CHANNELS].message.equals(firstElement)) {
this.#emitPubSubMessage(exports.PUBSUB_TYPE.CHANNELS, reply[2], reply[1]);
return true;
}
else if (COMMANDS[exports.PUBSUB_TYPE.PATTERNS].message.equals(firstElement)) {
this.#emitPubSubMessage(exports.PUBSUB_TYPE.PATTERNS, reply[3], reply[2], reply[1]);
return true;
}
else if (COMMANDS[exports.PUBSUB_TYPE.SHARDED].message.equals(firstElement)) {
this.#emitPubSubMessage(exports.PUBSUB_TYPE.SHARDED, reply[2], reply[1]);
return true;
}
return false;
}
removeShardedListeners(channel) {
const listeners = this.listeners[exports.PUBSUB_TYPE.SHARDED].get(channel);
this.listeners[exports.PUBSUB_TYPE.SHARDED].delete(channel);
this.#updateIsActive();
return listeners;
}
removeAllListeners() {
const result = {
[exports.PUBSUB_TYPE.CHANNELS]: this.listeners[exports.PUBSUB_TYPE.CHANNELS],
[exports.PUBSUB_TYPE.PATTERNS]: this.listeners[exports.PUBSUB_TYPE.PATTERNS],
[exports.PUBSUB_TYPE.SHARDED]: this.listeners[exports.PUBSUB_TYPE.SHARDED]
};
this.#updateIsActive();
this.listeners[exports.PUBSUB_TYPE.CHANNELS] = new Map();
this.listeners[exports.PUBSUB_TYPE.PATTERNS] = new Map();
this.listeners[exports.PUBSUB_TYPE.SHARDED] = new Map();
return result;
}
removeShardedPubSubListenersForSlots(slots) {
const sharded = new Map();
for (const [chanel, value] of this.listeners[exports.PUBSUB_TYPE.SHARDED]) {
if (slots.has((0, cluster_key_slot_1.default)(chanel))) {
sharded.set(chanel, value);
this.listeners[exports.PUBSUB_TYPE.SHARDED].delete(chanel);
}
}
this.#updateIsActive();
return {
[exports.PUBSUB_TYPE.SHARDED]: sharded
};
}
#emitPubSubMessage(type, message, channel, pattern) {
const keyString = (pattern ?? channel).toString(), listeners = this.listeners[type].get(keyString);
if (!listeners)
return;
(0, tracing_1.publish)(tracing_1.CHANNELS.PUBSUB, () => ({
direction: 'in',
clientId: this.#clientId,
channel,
sharded: type === exports.PUBSUB_TYPE.SHARDED,
}));
for (const listener of listeners.buffers) {
listener(message, channel);
}
if (!listeners.strings.size)
return;
const channelString = pattern ? channel.toString() : keyString, messageString = channelString === '__redis__:invalidate' ?
// https://github.com/redis/redis/pull/7469
// https://github.com/redis/redis/issues/7463
(message === null ? null : message.map(x => x.toString())) :
message.toString();
for (const listener of listeners.strings) {
listener(messageString, channelString);
}
}
}
exports.PubSub = PubSub;
//# sourceMappingURL=pub-sub.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,61 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { EventEmitter } from 'node:events';
import net from 'node:net';
import tls from 'node:tls';
import { RedisArgument } from '../RESP/types';
type NetOptions = {
tls?: false;
};
type ReconnectStrategyFunction = (retries: number, cause: Error) => false | Error | number;
type RedisSocketOptionsCommon = {
/**
* Connection timeout (in milliseconds)
*/
connectTimeout?: number;
/**
* When the socket closes unexpectedly (without calling `.close()`/`.destroy()`), the client uses `reconnectStrategy` to decide what to do. The following values are supported:
* 1. `false` -> do not reconnect, close the client and flush the command queue.
* 2. `number` -> wait for `X` milliseconds before reconnecting.
* 3. `(retries: number, cause: Error) => false | number | Error` -> `number` is the same as configuring a `number` directly, `Error` is the same as `false`, but with a custom error.
*/
reconnectStrategy?: false | number | ReconnectStrategyFunction;
/**
* The timeout (in milliseconds) after which the socket will be closed. `undefined` means no timeout.
*/
socketTimeout?: number;
};
type RedisTcpOptions = RedisSocketOptionsCommon & NetOptions & Omit<net.TcpNetConnectOpts, 'timeout' | 'onread' | 'readable' | 'writable' | 'port'> & {
port?: number;
};
type RedisTlsOptions = RedisSocketOptionsCommon & tls.ConnectionOptions & {
tls: true;
};
type RedisIpcOptions = RedisSocketOptionsCommon & Omit<net.IpcNetConnectOpts, 'timeout' | 'onread' | 'readable' | 'writable'> & {
tls: false;
};
export type RedisTcpSocketOptions = RedisTcpOptions | RedisTlsOptions;
export type RedisSocketOptions = RedisTcpSocketOptions | RedisIpcOptions;
export type RedisSocketInitiator = () => void | Promise<unknown>;
export default class RedisSocket extends EventEmitter {
#private;
get isOpen(): boolean;
get isReady(): boolean;
get socketEpoch(): number;
get host(): string | undefined;
get port(): number | undefined;
constructor(initiator: RedisSocketInitiator, clientId: string, options?: RedisSocketOptions);
connect(): Promise<void>;
setMaintenanceTimeout(ms?: number): void;
write(iterable: Iterable<ReadonlyArray<RedisArgument>>): void;
quit<T>(fn: () => Promise<T>): Promise<T>;
close(): void;
destroy(): void;
destroySocket(): void;
ref(): void;
unref(): void;
defaultReconnectStrategy(retries: number, cause: unknown): number | false;
}
export {};
//# sourceMappingURL=socket.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../lib/client/socket.ts"],"names":[],"mappings":";;;AAAA,OAAO,EAAE,YAAY,EAAQ,MAAM,aAAa,CAAC;AACjD,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAK9C,KAAK,UAAU,GAAG;IAChB,GAAG,CAAC,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,yBAAyB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE3F,KAAK,wBAAwB,GAAG;IAC9B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,yBAAyB,CAAC;IAC/D;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAA;AAED,KAAK,eAAe,GAAG,wBAAwB,GAAG,UAAU,GAAG,IAAI,CACjE,GAAG,CAAC,iBAAiB,EACrB,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CACxD,GAAG;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG,wBAAwB,GAAG,GAAG,CAAC,iBAAiB,GAAG;IACxE,GAAG,EAAE,IAAI,CAAC;CACX,CAAA;AAED,KAAK,eAAe,GAAG,wBAAwB,GAAG,IAAI,CACpD,GAAG,CAAC,iBAAiB,EACrB,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAC/C,GAAG;IACF,GAAG,EAAE,KAAK,CAAC;CACZ,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG,eAAe,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAAG,qBAAqB,GAAG,eAAe,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEjE,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;;IAcnD,IAAI,MAAM,YAET;IAID,IAAI,OAAO,YAEV;IAMD,IAAI,WAAW,WAEd;IAED,IAAI,IAAI,uBAEP;IAED,IAAI,IAAI,uBAEP;gBAGC,SAAS,EAAE,oBAAoB,EAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB;IAqIxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D9B,qBAAqB,CAAC,EAAE,CAAC,EAAE,MAAM;IAoFjC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IAyBhD,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAW/C,KAAK;IAQL,OAAO;IASP,aAAa;IAab,GAAG;IAKH,KAAK;IAKL,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAazD"}
@@ -0,0 +1,376 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_events_1 = require("node:events");
const node_net_1 = __importDefault(require("node:net"));
const node_tls_1 = __importDefault(require("node:tls"));
const errors_1 = require("../errors");
const promises_1 = require("node:timers/promises");
const enterprise_maintenance_manager_1 = require("./enterprise-maintenance-manager");
const tracing_1 = require("./tracing");
const defaults_1 = require("../defaults");
class RedisSocket extends node_events_1.EventEmitter {
#initiator;
#connectTimeout;
#reconnectStrategy;
#socketFactory;
#socketTimeout;
#clientId;
#maintenanceTimeout;
#socket;
#isOpen = false;
get isOpen() {
return this.#isOpen;
}
#isReady = false;
get isReady() {
return this.#isReady;
}
#isSocketUnrefed = false;
#socketEpoch = 0;
get socketEpoch() {
return this.#socketEpoch;
}
get host() {
return this.#socket?.remoteAddress;
}
get port() {
return this.#socket?.remotePort;
}
constructor(initiator, clientId, options) {
super();
this.#initiator = initiator;
this.#connectTimeout = options?.connectTimeout ?? 5000;
this.#reconnectStrategy = this.#createReconnectStrategy(options);
this.#socketFactory = this.#createSocketFactory(options);
this.#socketTimeout = options?.socketTimeout;
this.#clientId = clientId;
}
#createReconnectStrategy(options) {
const strategy = options?.reconnectStrategy;
if (strategy === false || typeof strategy === 'number') {
return () => strategy;
}
if (strategy) {
return (retries, cause) => {
try {
const retryIn = strategy(retries, cause);
if (retryIn !== false && !(retryIn instanceof Error) && typeof retryIn !== 'number') {
throw new TypeError(`Reconnect strategy should return \`false | Error | number\`, got ${retryIn} instead`);
}
return retryIn;
}
catch (err) {
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'client',
internal: false,
clientId: this.#clientId
}));
this.emit('error', err);
return this.defaultReconnectStrategy(retries, err);
}
};
}
return this.defaultReconnectStrategy;
}
#createSocketFactory(options) {
// TLS
if (options?.tls === true) {
const withDefaults = {
...options,
port: options?.port ?? 6379,
// https://nodejs.org/api/tls.html#tlsconnectoptions-callback "Any socket.connect() option not already listed"
// @types/node is... incorrect...
// @ts-expect-error - @types/node omits socket.connect noDelay.
noDelay: options?.noDelay ?? true,
// @ts-expect-error - @types/node omits socket.connect keepAlive.
keepAlive: options?.keepAlive ?? true,
// @ts-expect-error - @types/node omits socket.connect keepAliveInitialDelay.
keepAliveInitialDelay: options?.keepAliveInitialDelay ?? defaults_1.DEFAULT_KEEPALIVE_INITIAL_DELAY,
timeout: undefined,
onread: undefined,
readable: true,
writable: true
};
return {
create() {
return node_tls_1.default.connect(withDefaults);
},
event: 'secureConnect'
};
}
// IPC
if (options && 'path' in options) {
const withDefaults = {
...options,
timeout: undefined,
onread: undefined,
readable: true,
writable: true
};
return {
create() {
return node_net_1.default.createConnection(withDefaults);
},
event: 'connect'
};
}
// TCP
const withDefaults = {
...options,
port: options?.port ?? 6379,
noDelay: options?.noDelay ?? true,
keepAlive: options?.keepAlive ?? true,
keepAliveInitialDelay: options?.keepAliveInitialDelay ?? defaults_1.DEFAULT_KEEPALIVE_INITIAL_DELAY,
timeout: undefined,
onread: undefined,
readable: true,
writable: true
};
return {
create() {
return node_net_1.default.createConnection(withDefaults);
},
event: 'connect'
};
}
#shouldReconnect(retries, cause) {
const retryIn = this.#reconnectStrategy(retries, cause);
if (retryIn === false) {
this.#isOpen = false;
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: cause,
origin: 'client',
internal: false,
clientId: this.#clientId
}));
this.emit('error', cause);
return cause;
}
else if (retryIn instanceof Error) {
this.#isOpen = false;
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: cause,
origin: 'client',
internal: false,
clientId: this.#clientId
}));
this.emit('error', cause);
return new errors_1.ReconnectStrategyError(retryIn, cause);
}
return retryIn;
}
async connect() {
if (this.#isOpen) {
throw new Error('Socket already opened');
}
this.#isOpen = true;
return this.#connect();
}
async #connect() {
let retries = 0;
do {
try {
const connectStartTime = performance.now();
this.#socket = await this.#createSocket();
this.emit('connect');
try {
await this.#initiator();
// Check if socket was closed/destroyed during initiator execution
if (!this.#socket || this.#socket.destroyed || !this.#socket.readable || !this.#socket.writable) {
const retryIn = this.#shouldReconnect(retries++, new errors_1.SocketClosedUnexpectedlyError());
if (typeof retryIn !== 'number') {
throw retryIn;
}
await (0, promises_1.setTimeout)(retryIn);
this.emit('reconnecting');
continue;
}
}
catch (err) {
this.#socket.destroy();
this.#socket = undefined;
throw err;
}
this.#isReady = true;
this.#socketEpoch++;
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_READY, () => ({
clientId: this.#clientId,
serverAddress: this.host,
serverPort: this.port,
createTimeMs: performance.now() - connectStartTime,
}));
this.emit('ready');
}
catch (err) {
const retryIn = this.#shouldReconnect(retries++, err);
if (typeof retryIn !== 'number') {
throw retryIn;
}
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'client',
internal: false,
clientId: this.#clientId
}));
this.emit('error', err);
await (0, promises_1.setTimeout)(retryIn);
this.emit('reconnecting');
}
} while (this.#isOpen && !this.#isReady);
}
setMaintenanceTimeout(ms) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Set socket timeout to ${ms}`);
if (this.#maintenanceTimeout === ms) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`Socket already set maintenanceCommandTimeout to ${ms}, skipping`);
return;
}
;
this.#maintenanceTimeout = ms;
if (ms !== undefined) {
this.#socket?.setTimeout(ms);
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_RELAXED_TIMEOUT, () => ({ clientId: this.#clientId, value: 1 }));
}
else {
this.#socket?.setTimeout(this.#socketTimeout ?? 0);
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_RELAXED_TIMEOUT, () => ({ clientId: this.#clientId, value: -1 }));
}
}
async #createSocket() {
const socket = this.#socketFactory.create();
let onTimeout;
if (this.#connectTimeout !== undefined) {
onTimeout = () => socket.destroy(new errors_1.ConnectionTimeoutError());
socket.once('timeout', onTimeout);
socket.setTimeout(this.#connectTimeout);
}
if (this.#isSocketUnrefed) {
socket.unref();
}
await (0, node_events_1.once)(socket, this.#socketFactory.event);
if (onTimeout) {
socket.removeListener('timeout', onTimeout);
}
if (this.#socketTimeout) {
socket.once('timeout', () => {
const error = this.#maintenanceTimeout
? new errors_1.SocketTimeoutDuringMaintenanceError(this.#maintenanceTimeout)
: new errors_1.SocketTimeoutError(this.#socketTimeout);
socket.destroy(error);
});
socket.setTimeout(this.#socketTimeout);
}
socket
.once('error', err => this.#onSocketError(err))
.once('close', hadError => {
if (hadError || !this.#isOpen || this.#socket !== socket)
return;
this.#onSocketError(new errors_1.SocketClosedUnexpectedlyError());
})
.on('drain', () => this.emit('drain'))
.on('data', data => this.emit('data', data));
return socket;
}
#onSocketError(err) {
const wasReady = this.#isReady;
this.#isReady = false;
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'client',
internal: false,
clientId: this.#clientId
}));
this.emit('error', err);
if (wasReady) {
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_CLOSED, () => ({ clientId: this.#clientId, reason: 'error', wasConnected: true }));
}
if (!wasReady || !this.#isOpen || typeof this.#shouldReconnect(0, err) !== 'number')
return;
this.emit('reconnecting');
this.#connect().catch(() => {
// the error was already emitted, silently ignore it
});
}
write(iterable) {
if (!this.#socket || !this.#socket.writable)
return;
this.#socket.cork();
try {
for (const args of iterable) {
for (const toWrite of args) {
this.#socket.write(toWrite);
}
if (this.#socket.writableNeedDrain)
break;
}
}
catch (err) {
// net.Socket.write can throw synchronously on a half-closed socket
// (writeAfterFIN -> EPIPE) before the 'close' event fires. The pending
// command has already been moved to #waitingForReply by the queue's
// generator, so the close handler will reject it on reconnect.
if (!err || err.code !== 'EPIPE') {
throw err;
}
}
finally {
this.#socket.uncork();
}
}
async quit(fn) {
if (!this.#isOpen) {
throw new errors_1.ClientClosedError();
}
this.#isOpen = false;
const reply = await fn();
this.destroySocket();
return reply;
}
close() {
if (!this.#isOpen) {
throw new errors_1.ClientClosedError();
}
this.#isOpen = false;
}
destroy() {
if (!this.#isOpen) {
throw new errors_1.ClientClosedError();
}
this.#isOpen = false;
this.destroySocket();
}
destroySocket() {
const wasReady = this.#isReady;
this.#isReady = false;
if (this.#socket) {
this.#socket.destroy();
this.#socket = undefined;
}
(0, tracing_1.publish)(tracing_1.CHANNELS.CONNECTION_CLOSED, () => ({ clientId: this.#clientId, reason: 'application_close', wasConnected: wasReady }));
this.emit('end');
}
ref() {
this.#isSocketUnrefed = false;
this.#socket?.ref();
}
unref() {
this.#isSocketUnrefed = true;
this.#socket?.unref();
}
defaultReconnectStrategy(retries, cause) {
// By default, do not reconnect on socket timeout.
if (cause instanceof errors_1.SocketTimeoutError) {
return false;
}
// Generate a random jitter between 0 200 ms:
const jitter = Math.floor(Math.random() * 200);
// Delay is an exponential back off, (times^2) * 50 ms, with a maximum value of 2000 ms:
const delay = Math.min(Math.pow(2, retries) * 50, 2000);
return delay + jitter;
}
}
exports.default = RedisSocket;
//# sourceMappingURL=socket.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
/// <reference types="node" />
import type * as DC from 'node:diagnostics_channel';
export declare const CHANNELS: {
readonly TRACE_COMMAND: "node-redis:command";
readonly TRACE_BATCH: "node-redis:batch";
readonly TRACE_CONNECT: "node-redis:connect";
readonly CONNECTION_READY: "node-redis:connection:ready";
readonly CONNECTION_CLOSED: "node-redis:connection:closed";
readonly CONNECTION_RELAXED_TIMEOUT: "node-redis:connection:relaxed-timeout";
readonly CONNECTION_HANDOFF: "node-redis:connection:handoff";
readonly ERROR: "node-redis:error";
readonly MAINTENANCE: "node-redis:maintenance";
readonly PUBSUB: "node-redis:pubsub";
readonly CACHE_REQUEST: "node-redis:cache:request";
readonly CACHE_EVICTION: "node-redis:cache:eviction";
readonly COMMAND_REPLY: "node-redis:command:reply";
readonly POOL_CONNECTION_WAIT: "node-redis:pool:connection-wait";
};
/**
* Sanitizes the arguments of a command to remove sensitive information.
*/
export declare function sanitizeArgs(args: ReadonlyArray<unknown>): ReadonlyArray<string>;
export interface CommandTraceContext {
command: string;
args: ReadonlyArray<string>;
database: number;
serverAddress: string;
serverPort: number | undefined;
clientId: string;
}
export interface BatchCommandTraceContext extends CommandTraceContext {
batchMode: 'MULTI' | 'PIPELINE';
batchSize: number;
}
export interface ConnectTraceContext {
serverAddress: string;
serverPort: number | undefined;
clientId: string;
}
export interface BatchOperationContext {
batchMode: 'MULTI' | 'PIPELINE';
batchSize: number;
database: number;
serverAddress: string;
serverPort: number | undefined;
clientId: string;
}
type CommandContext = CommandTraceContext | BatchCommandTraceContext;
interface TracingChannelContextMap {
[CHANNELS.TRACE_COMMAND]: CommandContext;
[CHANNELS.TRACE_BATCH]: BatchOperationContext;
[CHANNELS.TRACE_CONNECT]: ConnectTraceContext;
}
export declare function getTracingChannel<K extends keyof TracingChannelContextMap>(name: K): DC.TracingChannel<TracingChannelContextMap[K]> | undefined;
export declare function trace<K extends keyof TracingChannelContextMap, T>(name: K, fn: () => Promise<T>, contextFactory: () => TracingChannelContextMap[K]): Promise<T>;
export interface ConnectionReadyEvent {
clientId: string;
serverAddress: string | undefined;
serverPort: number | undefined;
createTimeMs: number;
}
export interface ConnectionClosedEvent {
clientId: string;
reason: string;
wasConnected: boolean;
}
export interface ConnectionRelaxedTimeoutEvent {
clientId: string;
value: number;
}
export interface ConnectionHandoffEvent {
clientId: string;
}
export interface ClientErrorEvent {
error: Error;
origin: string;
internal: boolean;
clientId?: string;
retryCount?: number;
}
export interface MaintenanceNotificationEvent {
notification: string;
clientId?: string;
}
export interface PubSubMessageEvent {
direction: 'in' | 'out';
clientId: string;
channel?: unknown;
sharded?: boolean;
}
export interface CacheRequestEvent {
result: string;
clientId?: string;
}
export interface CacheEvictionEvent {
reason: string;
count: number;
clientId?: string;
}
export interface CommandReplyEvent {
args: ReadonlyArray<unknown>;
reply: unknown;
clientId: string;
}
export interface PoolConnectionWaitEvent {
clientId: string;
waitStartTimestamp: number;
}
export interface ChannelEvents {
[CHANNELS.CONNECTION_READY]: ConnectionReadyEvent;
[CHANNELS.CONNECTION_CLOSED]: ConnectionClosedEvent;
[CHANNELS.CONNECTION_RELAXED_TIMEOUT]: ConnectionRelaxedTimeoutEvent;
[CHANNELS.CONNECTION_HANDOFF]: ConnectionHandoffEvent;
[CHANNELS.ERROR]: ClientErrorEvent;
[CHANNELS.MAINTENANCE]: MaintenanceNotificationEvent;
[CHANNELS.PUBSUB]: PubSubMessageEvent;
[CHANNELS.CACHE_REQUEST]: CacheRequestEvent;
[CHANNELS.CACHE_EVICTION]: CacheEvictionEvent;
[CHANNELS.COMMAND_REPLY]: CommandReplyEvent;
[CHANNELS.POOL_CONNECTION_WAIT]: PoolConnectionWaitEvent;
}
interface Channel {
readonly hasSubscribers: boolean;
publish(message: unknown): void;
subscribe(handler: (message: any) => void): void;
unsubscribe(handler: (message: any) => void): void;
}
export declare function getChannel(name: string): Channel | undefined;
export declare function publish<K extends keyof ChannelEvents>(name: K, factory: () => ChannelEvents[K]): void;
export {};
//# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../../lib/client/tracing.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAcpD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;CAiBX,CAAC;AAcX;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CA2BhF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,cAAc,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;AAErE,UAAU,wBAAwB;IAChC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC;IACzC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IAC9C,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;CAC/C;AAcD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EACxE,IAAI,EAAE,CAAC,GACN,EAAE,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAE5D;AAED,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAAE,CAAC,EAC/D,IAAI,EAAE,CAAC,EACP,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,cAAc,EAAE,MAAM,wBAAwB,CAAC,CAAC,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAMZ;AAOD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,IAAI,GAAG,KAAK,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAGD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IAClD,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IACpD,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,6BAA6B,CAAC;IACrE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IACtD,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACnC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,4BAA4B,CAAC;IACrD,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IACtC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAC5C,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAC9C,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAC5C,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,uBAAuB,CAAC;CAC1D;AAED,UAAU,OAAO;IACf,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IACjD,WAAW,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;CACpD;AAiBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE5D;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,aAAa,EACnD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,aAAa,CAAC,CAAC,CAAC,GAC9B,IAAI,CAKN"}
@@ -0,0 +1,122 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.publish = exports.getChannel = exports.trace = exports.getTracingChannel = exports.sanitizeArgs = exports.CHANNELS = void 0;
const dc = (() => {
try {
return ('getBuiltinModule' in process)
? process.getBuiltinModule('node:diagnostics_channel')
: require('node:diagnostics_channel');
}
catch {
return undefined;
}
})();
const hasTracingChannel = typeof dc?.tracingChannel === 'function';
exports.CHANNELS = {
// TracingChannel (async lifecycle)
TRACE_COMMAND: 'node-redis:command',
TRACE_BATCH: 'node-redis:batch',
TRACE_CONNECT: 'node-redis:connect',
// Point events (fire-and-forget)
CONNECTION_READY: 'node-redis:connection:ready',
CONNECTION_CLOSED: 'node-redis:connection:closed',
CONNECTION_RELAXED_TIMEOUT: 'node-redis:connection:relaxed-timeout',
CONNECTION_HANDOFF: 'node-redis:connection:handoff',
ERROR: 'node-redis:error',
MAINTENANCE: 'node-redis:maintenance',
PUBSUB: 'node-redis:pubsub',
CACHE_REQUEST: 'node-redis:cache:request',
CACHE_EVICTION: 'node-redis:cache:eviction',
COMMAND_REPLY: 'node-redis:command:reply',
POOL_CONNECTION_WAIT: 'node-redis:pool:connection-wait',
};
/**
* Argument sanitization rules adapted from @opentelemetry/redis-common (Apache 2.0).
* Controls how many arguments after the command name are included in trace context.
* https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/packages/redis-common/src/index.ts
*/
const SERIALIZATION_SUBSETS = [
{ regex: /^ECHO/i, args: 0 },
{ regex: /^(LPUSH|MSET|PFA|PUBLISH|RPUSH|SADD|SET|SPUBLISH|XADD|ZADD)/i, args: 1 },
{ regex: /^(HSET|HMSET|LSET|LINSERT)/i, args: 2 },
{ regex: /^(ACL|BIT|B[LRZ]|CLIENT|CLUSTER|CONFIG|COMMAND|DECR|DEL|EVAL|EX|FUNCTION|GEO|GET|HINCR|HMGET|HSCAN|INCR|L[TRLM]|MEMORY|P[EFISTU]|RPOP|S[CDIMORSU]|XACK|X[CDGILPRT]|Z[CDILMPRS])/i, args: -1 },
];
/**
* Sanitizes the arguments of a command to remove sensitive information.
*/
function sanitizeArgs(args) {
if (args.length === 0)
return [];
const commandName = String(args[0]);
let allowedArgCount = 0; // default: command name only for unlisted commands
for (const subset of SERIALIZATION_SUBSETS) {
if (subset.regex.test(commandName)) {
allowedArgCount = subset.args;
break;
}
}
// All args are safe (structural/read commands)
if (allowedArgCount === -1) {
return args.map(a => String(a));
}
const result = [commandName];
for (let i = 1; i < args.length; i++) {
if (i <= allowedArgCount) {
result.push(String(args[i]));
}
else {
result.push('?');
}
}
return result;
}
exports.sanitizeArgs = sanitizeArgs;
// Eagerly resolve tracing channels at module load time.
// Check explicitly for `false` rather than truthiness because `hasSubscribers`
// is not available on all Node.js versions that support TracingChannel.
// When `hasSubscribers` is `undefined` (older Node), we assume there are
// subscribers and trace unconditionally, keeping the zero-cost optimization
// only for versions where we can reliably check.
const tracingChannels = hasTracingChannel ? {
[exports.CHANNELS.TRACE_COMMAND]: dc.tracingChannel(exports.CHANNELS.TRACE_COMMAND),
[exports.CHANNELS.TRACE_BATCH]: dc.tracingChannel(exports.CHANNELS.TRACE_BATCH),
[exports.CHANNELS.TRACE_CONNECT]: dc.tracingChannel(exports.CHANNELS.TRACE_CONNECT),
} : undefined;
function getTracingChannel(name) {
return tracingChannels?.[name];
}
exports.getTracingChannel = getTracingChannel;
function trace(name, fn, contextFactory) {
const channel = tracingChannels?.[name];
if (channel && channel.hasSubscribers !== false) {
return channel.tracePromise(fn, contextFactory());
}
return fn();
}
exports.trace = trace;
// Eagerly resolve point-event channels at module load time
const pointChannels = dc?.channel ? {
[exports.CHANNELS.CONNECTION_READY]: dc.channel(exports.CHANNELS.CONNECTION_READY),
[exports.CHANNELS.CONNECTION_CLOSED]: dc.channel(exports.CHANNELS.CONNECTION_CLOSED),
[exports.CHANNELS.CONNECTION_RELAXED_TIMEOUT]: dc.channel(exports.CHANNELS.CONNECTION_RELAXED_TIMEOUT),
[exports.CHANNELS.CONNECTION_HANDOFF]: dc.channel(exports.CHANNELS.CONNECTION_HANDOFF),
[exports.CHANNELS.ERROR]: dc.channel(exports.CHANNELS.ERROR),
[exports.CHANNELS.MAINTENANCE]: dc.channel(exports.CHANNELS.MAINTENANCE),
[exports.CHANNELS.PUBSUB]: dc.channel(exports.CHANNELS.PUBSUB),
[exports.CHANNELS.CACHE_REQUEST]: dc.channel(exports.CHANNELS.CACHE_REQUEST),
[exports.CHANNELS.CACHE_EVICTION]: dc.channel(exports.CHANNELS.CACHE_EVICTION),
[exports.CHANNELS.COMMAND_REPLY]: dc.channel(exports.CHANNELS.COMMAND_REPLY),
[exports.CHANNELS.POOL_CONNECTION_WAIT]: dc.channel(exports.CHANNELS.POOL_CONNECTION_WAIT),
} : undefined;
function getChannel(name) {
return pointChannels?.[name];
}
exports.getChannel = getChannel;
function publish(name, factory) {
const ch = pointChannels?.[name];
if (ch?.hasSubscribers) {
ch.publish(factory());
}
}
exports.publish = publish;
//# sourceMappingURL=tracing.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
import type { ClusterTopologyRefreshOnReconnectionAttemptStrategy } from './index';
/**
* Tracks which cluster node clients are currently reconnecting and decides when
* to trigger a cluster topology refresh based on a configurable strategy.
*
* The strategy can be:
* - `undefined` - uses the default delay (5 seconds)
* - `false` or `0` - disables topology refresh on reconnection
* - a positive integer - delay in ms after the first reconnection attempt before refreshing
* - a function - custom logic receiving the timestamp of the first reconnection attempt,
* returning a delay or `false`/`undefined` to skip
*
* After the delay elapses, {@link onReconnectionAttempt} returns `true` once to signal
* that a refresh should be scheduled, then resets the timer.
*/
export default class ClusterReconnectionTracker {
#private;
constructor(strategy?: ClusterTopologyRefreshOnReconnectionAttemptStrategy);
get reconnectingAddresses(): Set<string>;
get firstReconnectionAt(): number | undefined;
/**
* Records a reconnection attempt for the given client and evaluates whether
* the configured delay has elapsed since the first attempt in this cycle.
*
* @returns `true` if a topology refresh should be triggered, `false` otherwise
* @throws If a user-supplied strategy function returns an invalid value
*/
onReconnectionAttempt(clientId: string, address: string, now?: number): boolean;
/** Removes a client from tracking (e.g. when it reconnects successfully or disconnects) */
removeClient(clientId: string): void;
/** Resets all tracking state (e.g. on cluster disconnect or destroy) */
clear(): void;
}
//# sourceMappingURL=cluster-reconnection-tracker.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"cluster-reconnection-tracker.d.ts","sourceRoot":"","sources":["../../../lib/cluster/cluster-reconnection-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mDAAmD,EAAE,MAAM,SAAS,CAAC;AAEnF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,OAAO,0BAA0B;;gBA+BjC,QAAQ,CAAC,EAAE,mDAAmD;IAK1E,IAAI,qBAAqB,gBAExB;IAED,IAAI,mBAAmB,uBAEtB;IAED;;;;;;OAMG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa;IAiBzE,2FAA2F;IAC3F,YAAY,CAAC,QAAQ,EAAE,MAAM;IAM7B,wEAAwE;IACxE,KAAK;CAqCN"}
@@ -0,0 +1,112 @@
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Tracks which cluster node clients are currently reconnecting and decides when
* to trigger a cluster topology refresh based on a configurable strategy.
*
* The strategy can be:
* - `undefined` - uses the default delay (5 seconds)
* - `false` or `0` - disables topology refresh on reconnection
* - a positive integer - delay in ms after the first reconnection attempt before refreshing
* - a function - custom logic receiving the timestamp of the first reconnection attempt,
* returning a delay or `false`/`undefined` to skip
*
* After the delay elapses, {@link onReconnectionAttempt} returns `true` once to signal
* that a refresh should be scheduled, then resets the timer.
*/
class ClusterReconnectionTracker {
/** Default delay (ms) before triggering a topology refresh after reconnection starts */
static #DEFAULT_TOPOLOGY_REFRESH_ON_RECONNECTION_ATTEMPT = 5000;
#strategy;
/** Maps client ID to its node address for clients currently in a reconnecting state */
#reconnectingClients = new Map();
/** Timestamp of the first reconnection attempt in the current reconnection cycle */
#firstReconnectionAt;
/**
* Validates that a strategy value is acceptable before use.
* @throws If the strategy is not supported
*/
#validate(strategy) {
if (strategy === undefined ||
strategy === false ||
typeof strategy === 'function' ||
(typeof strategy === 'number' &&
Number.isInteger(strategy) &&
strategy >= 0)) {
return;
}
throw new TypeError('topologyRefreshOnReconnectionAttempt must be undefined, false, a non-negative integer, or a function');
}
constructor(strategy) {
this.#validate(strategy);
this.#strategy = strategy;
}
get reconnectingAddresses() {
return new Set(this.#reconnectingClients.values());
}
get firstReconnectionAt() {
return this.#firstReconnectionAt;
}
/**
* Records a reconnection attempt for the given client and evaluates whether
* the configured delay has elapsed since the first attempt in this cycle.
*
* @returns `true` if a topology refresh should be triggered, `false` otherwise
* @throws If a user-supplied strategy function returns an invalid value
*/
onReconnectionAttempt(clientId, address, now = Date.now()) {
if (this.#strategy === false || this.#strategy === 0) {
return false;
}
this.#reconnectingClients.set(clientId, address);
this.#firstReconnectionAt ??= now;
const delay = this.#getDelay(this.#firstReconnectionAt);
if (delay === undefined || now - this.#firstReconnectionAt < delay) {
return false;
}
this.#firstReconnectionAt = now;
return true;
}
/** Removes a client from tracking (e.g. when it reconnects successfully or disconnects) */
removeClient(clientId) {
if (!this.#reconnectingClients.delete(clientId))
return;
this.#clearTimestampIfClean();
}
/** Resets all tracking state (e.g. on cluster disconnect or destroy) */
clear() {
this.#reconnectingClients.clear();
this.#firstReconnectionAt = undefined;
}
/**
* Evaluates the configured strategy to determine the delay before a topology refresh.
* @returns The delay in ms, or `undefined` if no refresh should occur
*/
#getDelay(firstReconnectionAt) {
if (this.#strategy === undefined) {
return _a.#DEFAULT_TOPOLOGY_REFRESH_ON_RECONNECTION_ATTEMPT;
}
if (this.#strategy === false) {
return;
}
if (typeof this.#strategy === 'number') {
return this.#strategy;
}
const delay = this.#strategy(firstReconnectionAt);
if (delay === false || delay === undefined || delay === 0)
return;
if (!Number.isInteger(delay) || delay < 0) {
throw new TypeError(`topologyRefreshOnReconnectionAttempt should return \`false | undefined | number\`, got ${delay} instead`);
}
return delay;
}
#clearTimestampIfClean() {
if (this.#reconnectingClients.size === 0) {
this.#firstReconnectionAt = undefined;
}
}
}
_a = ClusterReconnectionTracker;
exports.default = ClusterReconnectionTracker;
//# sourceMappingURL=cluster-reconnection-tracker.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
/// <reference types="node" />
/// <reference types="node" />
import type { RedisClusterOptions } from '.';
import { RedisClientType } from '../client';
import { EventEmitter } from 'node:stream';
import { ChannelListeners } from '../client/pub-sub';
import { RedisArgument, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping } from '../RESP/types';
import { PooledClientSideCacheProvider } from '../client/cache';
interface NodeAddress {
host: string;
port: number;
}
export type NodeAddressMap = {
[address: string]: NodeAddress;
} | ((address: string) => NodeAddress | undefined);
export declare const RESUBSCRIBE_LISTENERS_EVENT = "__resubscribeListeners";
export interface Node<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
address: string;
client?: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
connectPromise?: Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
}
export interface ShardNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends Node<M, F, S, RESP, TYPE_MAPPING>, NodeAddress {
id: string;
readonly: boolean;
}
export interface MasterNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends ShardNode<M, F, S, RESP, TYPE_MAPPING> {
pubSub?: {
connectPromise?: Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
};
}
export interface Shard<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
master: MasterNode<M, F, S, RESP, TYPE_MAPPING>;
replicas?: Array<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
nodesIterator?: IterableIterator<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
}
type PubSubNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (Omit<Node<M, F, S, RESP, TYPE_MAPPING>, 'client'> & Required<Pick<Node<M, F, S, RESP, TYPE_MAPPING>, 'client'>>);
export type OnShardedChannelMovedError = (err: unknown, channel: string, listeners?: ChannelListeners) => void;
export default class RedisClusterSlots<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
#private;
slots: Shard<M, F, S, RESP, TYPE_MAPPING>[];
masters: MasterNode<M, F, S, RESP, TYPE_MAPPING>[];
replicas: ShardNode<M, F, S, RESP, TYPE_MAPPING>[];
readonly nodeByAddress: Map<string, ShardNode<M, F, S, RESP, TYPE_MAPPING> | MasterNode<M, F, S, RESP, TYPE_MAPPING>>;
pubSubNode?: PubSubNode<M, F, S, RESP, TYPE_MAPPING>;
clientSideCache?: PooledClientSideCacheProvider;
smigratedSeqIdsSeen: Set<number>;
get isOpen(): boolean;
constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>, emit: EventEmitter['emit'], clusterClientId: string);
connect(): Promise<void>;
nodeClient(node: ShardNode<M, F, S, RESP, TYPE_MAPPING>): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
rediscover(startWith?: RedisClientType<M, F, S, RESP>, excludedAddresses?: ReadonlySet<string>): Promise<void>;
/**
* @deprecated Use `close` instead.
*/
quit(): Promise<void>;
/**
* @deprecated Use `destroy` instead.
*/
disconnect(): Promise<void>;
close(): Promise<void>;
destroy(): void;
getClientAndSlotNumber(firstKey: RedisArgument | undefined, isReadonly: boolean | undefined): Promise<{
client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
slotNumber?: number;
}>;
getClientForKey(key: RedisArgument, isReadonly: boolean | undefined): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
_randomNodeIterator?: IterableIterator<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
getRandomNode(): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
getSlotRandomNode(slotNumber: number): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
getMasterByAddress(address: string): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>> | undefined;
getPubSubClient(): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
executeUnsubscribeCommand(unsubscribe: (client: RedisClientType<M, F, S, RESP>) => Promise<void>): Promise<void>;
getShardedPubSubClient(channel: string): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
executeShardedUnsubscribeCommand(channel: string, unsubscribe: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>) => Promise<void>): Promise<void>;
}
export {};
//# sourceMappingURL=cluster-slots.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"cluster-slots.d.ts","sourceRoot":"","sources":["../../../lib/cluster/cluster-slots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,769 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RESUBSCRIBE_LISTENERS_EVENT = void 0;
const errors_1 = require("../errors");
const client_1 = __importDefault(require("../client"));
const pub_sub_1 = require("../client/pub-sub");
const types_1 = require("../RESP/types");
const cluster_key_slot_1 = __importDefault(require("cluster-key-slot"));
const cache_1 = require("../client/cache");
const enterprise_maintenance_manager_1 = require("../client/enterprise-maintenance-manager");
const identity_1 = require("../client/identity");
const cluster_reconnection_tracker_1 = __importDefault(require("./cluster-reconnection-tracker"));
exports.RESUBSCRIBE_LISTENERS_EVENT = '__resubscribeListeners';
class RedisClusterSlots {
static #SLOTS = 16384;
#options;
#clientFactory;
#emit;
#clusterClientId;
#reconnectionTracker;
slots = new Array(_a.#SLOTS);
masters = new Array();
replicas = new Array();
nodeByAddress = new Map();
pubSubNode;
clientSideCache;
smigratedSeqIdsSeen = new Set;
#topologyRefreshPromise;
#isOpen = false;
get isOpen() {
return this.#isOpen;
}
#validateOptions(options) {
if (options?.clientSideCache && (options?.RESP ?? types_1.DEFAULT_RESP) !== 3) {
throw new Error('Client Side Caching is only supported with RESP3');
}
}
constructor(options, emit, clusterClientId) {
this.#validateOptions(options);
this.#options = options;
this.#clusterClientId = clusterClientId;
this.#reconnectionTracker = new cluster_reconnection_tracker_1.default(options.topologyRefreshOnReconnectionAttemptStrategy);
if (options?.clientSideCache) {
if (options.clientSideCache instanceof cache_1.PooledClientSideCacheProvider) {
this.clientSideCache = options.clientSideCache;
}
else {
this.clientSideCache = new cache_1.BasicPooledClientSideCache(options.clientSideCache);
}
}
this.#clientFactory = client_1.default.factory(this.#options);
this.#emit = emit;
}
async connect() {
if (this.#isOpen) {
throw new Error('Cluster already open');
}
this.#isOpen = true;
try {
await this.#discoverWithRootNodes();
this.#emit('connect');
}
catch (err) {
this.#isOpen = false;
throw err;
}
}
async #discoverWithRootNodes() {
const start = Math.floor(Math.random() * this.#options.rootNodes.length);
for (let i = start; i < this.#options.rootNodes.length; i++) {
if (!this.#isOpen)
throw new Error('Cluster closed');
if (await this.#discover(this.#options.rootNodes[i])) {
return;
}
}
for (let i = 0; i < start; i++) {
if (!this.#isOpen)
throw new Error('Cluster closed');
if (await this.#discover(this.#options.rootNodes[i])) {
return;
}
}
throw new errors_1.RootNodesUnavailableError();
}
#resetSlots() {
this.slots = new Array(_a.#SLOTS);
this.masters = [];
this.replicas = [];
this._randomNodeIterator = undefined;
}
async #discover(rootNode) {
this.clientSideCache?.clear();
this.clientSideCache?.disable();
try {
const addressesInUse = new Set(), promises = [], eagerConnect = this.#options.minimizeConnections !== true;
const shards = await this.#getShards(rootNode);
this.#resetSlots(); // Reset slots AFTER shards have been fetched to prevent a race condition
for (const { from, to, master, replicas } of shards) {
const shard = {
master: this.#initiateSlotNode(master, false, eagerConnect, addressesInUse, promises)
};
if (this.#options.useReplicas) {
shard.replicas = replicas.map(replica => this.#initiateSlotNode(replica, true, eagerConnect, addressesInUse, promises));
}
for (let i = from; i <= to; i++) {
this.slots[i] = shard;
}
}
if (this.pubSubNode && !addressesInUse.has(this.pubSubNode.address)) {
const channelsListeners = this.pubSubNode.client.getPubSubListeners(pub_sub_1.PUBSUB_TYPE.CHANNELS), patternsListeners = this.pubSubNode.client.getPubSubListeners(pub_sub_1.PUBSUB_TYPE.PATTERNS);
this.#reconnectionTracker.removeClient(this.pubSubNode.client._clientId);
this.pubSubNode.client.destroy();
if (channelsListeners.size || patternsListeners.size) {
promises.push(this.#initiatePubSubClient({
[pub_sub_1.PUBSUB_TYPE.CHANNELS]: channelsListeners,
[pub_sub_1.PUBSUB_TYPE.PATTERNS]: patternsListeners
}));
}
}
//Keep only the nodes that are still in use
for (const [address, node] of this.nodeByAddress.entries()) {
if (addressesInUse.has(address))
continue;
const { pubSub } = node;
if (pubSub) {
const listeners = pubSub.client._getQueue().removeAllPubSubListeners();
if (listeners.CHANNELS.size || listeners.PATTERNS.size || listeners.SHARDED.size) {
this.#emit(exports.RESUBSCRIBE_LISTENERS_EVENT, listeners);
}
}
if (node.client) {
this.#reconnectionTracker.removeClient(node.client._clientId);
node.client.destroy();
node.client = undefined;
}
if (pubSub) {
this.#reconnectionTracker.removeClient(pubSub.client._clientId);
pubSub.client.destroy();
node.pubSub = undefined;
}
this.nodeByAddress.delete(address);
}
await Promise.all(promises);
this.clientSideCache?.enable();
return true;
}
catch (err) {
this.#emit('error', err);
return false;
}
}
#handleSmigrated = async (event) => {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: handle smigrated`, JSON.stringify(event, null, 2));
if (this.smigratedSeqIdsSeen.has(event.seqId)) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: sequence id ${event.seqId} already seen, abort`);
return;
}
this.smigratedSeqIdsSeen.add(event.seqId);
for (const entry of event.entries) {
const sourceAddress = `${entry.source.host}:${entry.source.port}`;
const sourceNode = this.nodeByAddress.get(sourceAddress);
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Looking for sourceAddress=${sourceAddress}. Available addresses in nodeByAddress: ${Array.from(this.nodeByAddress.keys()).join(', ')}`);
if (!sourceNode) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: address ${sourceAddress} not in 'nodeByAddress', skipping this entry`);
continue;
}
if (sourceNode.client === undefined) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Node for ${sourceAddress} does not have a client, skipping this entry`);
continue;
}
// Track all slots being moved for this entry (used for pubsub listener handling)
const allMovingSlots = new Set();
try {
// 1. Pausing
// 1.1 Normal
sourceNode.client?._pause();
// 1.2 Sharded pubsub
if ('pubSub' in sourceNode) {
sourceNode.pubSub?.client._pause();
}
// 2. Process each destination: create nodes, update slot mappings, extract commands, unpause
let lastDestNode;
for (const { addr: { host, port }, slots } of entry.destinations) {
const destinationAddress = `${host}:${port}`;
let destMasterNode = this.nodeByAddress.get(destinationAddress);
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Looking for destAddress=${destinationAddress}. Found in nodeByAddress: ${destMasterNode ? 'YES' : 'NO'}`);
let destShard;
// 2.1 Create new Master if needed
if (!destMasterNode) {
const promises = [];
destMasterNode = this.#initiateSlotNode({ host: host, port: port, id: `smigrated-${host}:${port}` }, false, true, new Set(), promises);
await Promise.all([...promises, this.#initiateShardedPubSubClient(destMasterNode)]);
// Pause new destination until migration is complete
destMasterNode.client?._pause();
destMasterNode.pubSub?.client._pause();
// In case destination node didnt exist, this means Shard didnt exist as well, so creating a new Shard is completely fine
destShard = {
master: destMasterNode
};
}
else {
// DEBUG: Log all master hosts/ports in slots array to diagnose mismatch
const allMasters = [...new Set(this.slots)].map(s => `${s.master.host}:${s.master.port}`);
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Searching for shard with host=${host}, port=${port}. Available masters in slots: ${allMasters.join(', ')}`);
// In case destination node existed, this means there was a Shard already, so its best if we can find it.
const existingShard = this.slots.find(shard => shard.master.host === host && shard.master.port === port);
if (!existingShard) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)("Could not find shard");
throw new Error('Could not find shard');
}
destShard = existingShard;
// Pause existing destination during command transfer
destMasterNode.client?._pause();
destMasterNode.pubSub?.client._pause();
}
// Track last destination for slotless commands later
lastDestNode = destMasterNode;
// 3. Convert slots to Set and update shard mappings
const destinationSlots = new Set();
for (const slot of slots) {
if (typeof slot === 'number') {
this.slots[slot] = destShard;
destinationSlots.add(slot);
allMovingSlots.add(slot);
}
else {
for (let s = slot[0]; s <= slot[1]; s++) {
this.slots[s] = destShard;
destinationSlots.add(s);
allMovingSlots.add(s);
}
}
}
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Updated slots to point to destination ${destMasterNode.address}. Sample slots: ${Array.from(slots).slice(0, 5).join(', ')}${slots.length > 5 ? '...' : ''}`);
// 4. Extract commands for this destination's slots and prepend to destination queue
const commandsForDestination = sourceNode.client._getQueue().extractCommandsForSlots(destinationSlots);
destMasterNode.client?._getQueue().prependCommandsToWrite(commandsForDestination);
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Extracted ${commandsForDestination.length} commands for ${destinationSlots.size} slots, prepended to ${destMasterNode.address}`);
// 5. Unpause destination
destMasterNode.client?._unpause();
destMasterNode.pubSub?.client._unpause();
}
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Total ${allMovingSlots.size} slots moved from ${sourceAddress}. Sample: ${Array.from(allMovingSlots).slice(0, 10).join(', ')}${allMovingSlots.size > 10 ? '...' : ''}`);
// 6. Wait for inflight commands on source to complete (with timeout to prevent hangs)
const INFLIGHT_TIMEOUT_MS = 5000; // 5 seconds max wait for inflight commands
const inflightPromises = [];
const inflightOptions = { timeoutMs: INFLIGHT_TIMEOUT_MS, flushOnTimeout: true };
inflightPromises.push(sourceNode.client._getQueue().waitForInflightCommandsToComplete(inflightOptions));
if ('pubSub' in sourceNode && sourceNode.pubSub !== undefined) {
inflightPromises.push(sourceNode.pubSub.client._getQueue().waitForInflightCommandsToComplete(inflightOptions));
}
if (this.pubSubNode?.address === sourceAddress) {
inflightPromises.push(this.pubSubNode.client._getQueue().waitForInflightCommandsToComplete(inflightOptions));
}
await Promise.all(inflightPromises);
// 7. Handle source cleanup
const sourceStillHasSlots = this.slots.find(slot => slot.master.address === sourceAddress) !== undefined;
if (sourceStillHasSlots) {
// Handle sharded pubsub listeners for moving slots
if ('pubSub' in sourceNode) {
const listeners = sourceNode.pubSub?.client._getQueue().removeShardedPubSubListenersForSlots(allMovingSlots);
this.#emit(exports.RESUBSCRIBE_LISTENERS_EVENT, listeners);
}
// Unpause source since it still has slots
sourceNode.client?._unpause();
if ('pubSub' in sourceNode) {
sourceNode.pubSub?.client._unpause();
}
}
else {
// Source has no slots left - move remaining slotless commands and cleanup
const remainingCommands = sourceNode.client._getQueue().extractAllCommands();
if (remainingCommands.length > 0 && lastDestNode) {
lastDestNode.client?._getQueue().prependCommandsToWrite(remainingCommands);
// Trigger write scheduling since commands were added after destination was unpaused
lastDestNode.client?._unpause();
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Moved ${remainingCommands.length} remaining slotless commands to ${lastDestNode.address}`);
}
if ('pubSub' in sourceNode) {
const listeners = sourceNode.pubSub?.client._getQueue().removeAllPubSubListeners();
this.#emit(exports.RESUBSCRIBE_LISTENERS_EVENT, listeners);
}
// Remove all local references to the dying shard's clients
this.masters = this.masters.filter(master => master.address !== sourceAddress);
this.replicas = this.replicas.filter(replica => replica.address !== sourceAddress);
this.nodeByAddress.delete(sourceAddress);
// Handle pubSubNode replacement BEFORE destroying source connections
// This ensures subscriptions are resubscribed on a new node before the old connection is lost
if (this.pubSubNode?.address === sourceAddress) {
const channelsListeners = this.pubSubNode.client.getPubSubListeners(pub_sub_1.PUBSUB_TYPE.CHANNELS), patternsListeners = this.pubSubNode.client.getPubSubListeners(pub_sub_1.PUBSUB_TYPE.PATTERNS);
const oldPubSubClient = this.pubSubNode.client;
if (channelsListeners.size || patternsListeners.size) {
await this.#initiatePubSubClient({
[pub_sub_1.PUBSUB_TYPE.CHANNELS]: channelsListeners,
[pub_sub_1.PUBSUB_TYPE.PATTERNS]: patternsListeners
});
}
else {
this.pubSubNode = undefined;
}
this.#reconnectionTracker.removeClient(oldPubSubClient._clientId);
oldPubSubClient.destroy();
}
// Destroy source connections (use destroy() instead of close() since the node is being removed
// and close() can hang if the server is not responding)
this.#reconnectionTracker.removeClient(sourceNode.client?._clientId);
sourceNode.client.destroy();
if ('pubSub' in sourceNode && sourceNode.pubSub) {
this.#reconnectionTracker.removeClient(sourceNode.pubSub.client._clientId);
sourceNode.pubSub.client.destroy();
}
}
}
catch (err) {
(0, enterprise_maintenance_manager_1.dbgMaintenance)(`[CSlots]: Error during SMIGRATED handling for source ${sourceAddress}: ${err}`);
// Ensure we unpause source on error to prevent deadlock
sourceNode.client?._unpause();
if ('pubSub' in sourceNode) {
sourceNode.pubSub?.client._unpause();
}
this.#emit('error', err);
}
}
};
async #getShards(rootNode) {
const options = this.#clientOptionsDefaults(rootNode);
options.socket ??= {};
options.socket.reconnectStrategy = false;
options.RESP = this.#options.RESP;
options.commandOptions = undefined;
options.maintNotifications = 'disabled';
// TODO: find a way to avoid type casting
const client = await this.#clientFactory(options)
.on('error', err => this.#emit('error', err))
.connect();
try {
// switch to `CLUSTER SHARDS` when Redis 7.0 will be the minimum supported version
return await client.clusterSlots();
}
finally {
client.destroy();
}
}
#getNodeAddress(address) {
switch (typeof this.#options.nodeAddressMap) {
case 'object':
return this.#options.nodeAddressMap[address];
case 'function':
return this.#options.nodeAddressMap(address);
}
}
#nodeClientOptions(node) {
return {
socket: this.#getNodeAddress(node.address) ?? {
host: node.host,
port: node.port
}
};
}
#clientOptionsDefaults(options) {
if (!this.#options.defaults)
return options;
let socket;
if (this.#options.defaults.socket) {
socket = {
...this.#options.defaults.socket,
...options?.socket
};
}
else {
socket = options?.socket;
}
return {
...this.#options.defaults,
...options,
socket: socket
};
}
#initiateSlotNode(shard, readonly, eagerConnent, addressesInUse, promises) {
const address = `${shard.host}:${shard.port}`;
let node = this.nodeByAddress.get(address);
if (!node) {
node = {
...shard,
address,
readonly,
client: undefined,
connectPromise: undefined
};
if (eagerConnent) {
promises.push(this.#createNodeClient(node));
}
this.nodeByAddress.set(address, node);
}
if (!addressesInUse.has(address)) {
addressesInUse.add(address);
(readonly ? this.replicas : this.masters).push(node);
}
return node;
}
#createClient(node, readonly = node.readonly) {
const socket = this.#getNodeAddress(node.address) ??
{ host: node.host, port: node.port, };
const clientInfo = Object.freeze({
host: socket.host,
port: socket.port,
});
const address = node.address;
const emit = this.#emit;
let wasReady = false;
const client = this.#clientFactory(this.#clientOptionsDefaults({
clientSideCache: this.clientSideCache,
RESP: this.#options.RESP,
socket,
readonly,
}));
client._setIdentity(identity_1.ClientRole.CLUSTER_NODE, this.#clusterClientId);
client
.on('error', error => emit('node-error', error, clientInfo))
.on('reconnecting', () => {
emit('node-reconnecting', clientInfo);
if (!wasReady)
return;
this.#onNodeReconnectionAttempt(client._clientId, address);
})
.on('ready', () => {
wasReady = true;
this.#reconnectionTracker.removeClient(client._clientId);
})
.once('ready', () => emit('node-ready', clientInfo))
.once('connect', () => emit('node-connect', clientInfo))
.once('end', () => {
this.#reconnectionTracker.removeClient(client._clientId);
emit('node-disconnect', clientInfo);
})
.on(enterprise_maintenance_manager_1.SMIGRATED_EVENT, this.#handleSmigrated)
.on('__MOVED', async (allPubSubListeners) => {
await this.rediscover(client);
this.#emit(exports.RESUBSCRIBE_LISTENERS_EVENT, allPubSubListeners);
});
return client;
}
#createNodeClient(node, readonly) {
const client = node.client = this.#createClient(node, readonly);
return node.connectPromise = client.connect()
.finally(() => node.connectPromise = undefined);
}
nodeClient(node) {
// if the node is connecting
if (node.connectPromise)
return node.connectPromise;
// if the node is connected
if (node.client)
return Promise.resolve(node.client);
// if the not is disconnected
return this.#createNodeClient(node);
}
#runningRediscoverPromise;
async rediscover(startWith, excludedAddresses) {
this.#runningRediscoverPromise ??= this.#rediscover(startWith, excludedAddresses)
.finally(() => {
this.#runningRediscoverPromise = undefined;
});
return this.#runningRediscoverPromise;
}
async #rediscover(startWith, excludedAddresses) {
if (startWith && await this.#discover(startWith.options))
return;
if (await this.#discoverWithKnownNodes(excludedAddresses))
return;
return this.#discoverWithRootNodes();
}
async #discoverWithKnownNodes(excludedAddresses) {
const candidates = [];
const deferredCandidates = [];
const seen = new Set();
for (const nodes of [this.masters, this.replicas]) {
for (const node of nodes) {
if (excludedAddresses?.has(node.address) || seen.has(node.address))
continue;
seen.add(node.address);
if (node.client?.isReady) {
candidates.push(node);
}
else {
deferredCandidates.push(node);
}
}
}
return (await this.#discoverWithKnownNodeCandidates(candidates) ||
await this.#discoverWithKnownNodeCandidates(deferredCandidates));
}
async #discoverWithKnownNodeCandidates(candidates) {
if (!candidates.length) {
return false;
}
const start = Math.floor(Math.random() * candidates.length);
for (let i = 0; i < candidates.length; i++) {
if (!this.#isOpen) {
continue;
}
const candidate = candidates[(start + i) % candidates.length];
if (await this.#discover(this.#nodeClientOptions(candidate))) {
return true;
}
}
return false;
}
#onNodeReconnectionAttempt(clientId, address) {
let shouldRefresh;
try {
shouldRefresh = this.#reconnectionTracker.onReconnectionAttempt(clientId, address);
}
catch (err) {
this.#emit('error', err);
return;
}
if (shouldRefresh) {
this.#scheduleTopologyRefresh(this.#reconnectionTracker.reconnectingAddresses);
}
}
#scheduleTopologyRefresh(excludedAddresses) {
if (!this.#isOpen || this.#topologyRefreshPromise)
return;
this.#topologyRefreshPromise = this.rediscover(undefined, new Set(excludedAddresses))
.catch(err => this.#emit('error', err))
.finally(() => {
this.#topologyRefreshPromise = undefined;
});
}
/**
* @deprecated Use `close` instead.
*/
quit() {
return this.#destroy(client => client.quit());
}
/**
* @deprecated Use `destroy` instead.
*/
disconnect() {
return this.#destroy(client => client.disconnect());
}
close() {
return this.#destroy(client => client.close());
}
destroy() {
this.#isOpen = false;
for (const client of this.#clients()) {
client.destroy();
}
if (this.pubSubNode) {
this.pubSubNode.client.destroy();
this.pubSubNode = undefined;
}
this.#resetSlots();
this.nodeByAddress.clear();
this.#reconnectionTracker.clear();
this.#emit('disconnect');
}
*#clients() {
for (const master of this.masters) {
if (master.client) {
yield master.client;
}
if (master.pubSub) {
yield master.pubSub.client;
}
}
for (const replica of this.replicas) {
if (replica.client) {
yield replica.client;
}
}
}
async #destroy(fn) {
this.#isOpen = false;
const promises = [];
for (const client of this.#clients()) {
promises.push(fn(client));
}
if (this.pubSubNode) {
promises.push(fn(this.pubSubNode.client));
this.pubSubNode = undefined;
}
this.#resetSlots();
this.nodeByAddress.clear();
this.#reconnectionTracker.clear();
await Promise.allSettled(promises);
this.#emit('disconnect');
}
async getClientAndSlotNumber(firstKey, isReadonly) {
if (!firstKey) {
return {
client: await this.nodeClient(this.getRandomNode())
};
}
const slotNumber = (0, cluster_key_slot_1.default)(firstKey);
if (!isReadonly) {
return {
client: await this.nodeClient(this.slots[slotNumber].master),
slotNumber
};
}
return {
client: await this.nodeClient(this.getSlotRandomNode(slotNumber)),
slotNumber
};
}
getClientForKey(key, isReadonly) {
const slotNumber = (0, cluster_key_slot_1.default)(key);
if (isReadonly) {
return this.nodeClient(this.getSlotRandomNode(slotNumber));
}
return this.nodeClient(this.slots[slotNumber].master);
}
*#iterateAllNodes() {
if (this.masters.length + this.replicas.length === 0)
return;
let i = Math.floor(Math.random() * (this.masters.length + this.replicas.length));
if (i < this.masters.length) {
do {
yield this.masters[i];
} while (++i < this.masters.length);
for (const replica of this.replicas) {
yield replica;
}
}
else {
i -= this.masters.length;
do {
yield this.replicas[i];
} while (++i < this.replicas.length);
}
while (true) {
for (const master of this.masters) {
yield master;
}
for (const replica of this.replicas) {
yield replica;
}
}
}
_randomNodeIterator;
getRandomNode() {
this._randomNodeIterator ??= this.#iterateAllNodes();
return this._randomNodeIterator.next().value;
}
*#slotNodesIterator(slot) {
let i = Math.floor(Math.random() * (1 + slot.replicas.length));
if (i < slot.replicas.length) {
do {
yield slot.replicas[i];
} while (++i < slot.replicas.length);
}
while (true) {
yield slot.master;
for (const replica of slot.replicas) {
yield replica;
}
}
}
getSlotRandomNode(slotNumber) {
const slot = this.slots[slotNumber];
if (!slot.replicas?.length) {
return slot.master;
}
slot.nodesIterator ??= this.#slotNodesIterator(slot);
return slot.nodesIterator.next().value;
}
getMasterByAddress(address) {
const master = this.nodeByAddress.get(address);
if (!master)
return;
return this.nodeClient(master);
}
getPubSubClient() {
if (!this.pubSubNode)
return this.#initiatePubSubClient();
return this.pubSubNode.connectPromise ?? Promise.resolve(this.pubSubNode.client);
}
async #initiatePubSubClient(toResubscribe) {
const index = Math.floor(Math.random() * (this.masters.length + this.replicas.length)), node = index < this.masters.length ?
this.masters[index] :
this.replicas[index - this.masters.length], client = this.#createClient(node, false);
this.pubSubNode = {
address: node.address,
client,
connectPromise: client.connect()
.then(async (client) => {
if (toResubscribe) {
await Promise.all([
client.extendPubSubListeners(pub_sub_1.PUBSUB_TYPE.CHANNELS, toResubscribe[pub_sub_1.PUBSUB_TYPE.CHANNELS]),
client.extendPubSubListeners(pub_sub_1.PUBSUB_TYPE.PATTERNS, toResubscribe[pub_sub_1.PUBSUB_TYPE.PATTERNS])
]);
}
this.pubSubNode.connectPromise = undefined;
return client;
})
.catch(err => {
this.pubSubNode = undefined;
throw err;
})
};
return this.pubSubNode.connectPromise;
}
async executeUnsubscribeCommand(unsubscribe) {
const client = await this.getPubSubClient();
await unsubscribe(client);
if (!client.isPubSubActive) {
this.#reconnectionTracker.removeClient(client._clientId);
client.destroy();
this.pubSubNode = undefined;
}
}
getShardedPubSubClient(channel) {
const { master } = this.slots[(0, cluster_key_slot_1.default)(channel)];
if (!master.pubSub)
return this.#initiateShardedPubSubClient(master);
return master.pubSub.connectPromise ?? Promise.resolve(master.pubSub.client);
}
async #initiateShardedPubSubClient(master) {
const client = this.#createClient(master, false);
client.on('sharded-channel-moved', async (channel, listeners) => {
try {
await this.rediscover(client);
const redirectTo = await this.getShardedPubSubClient(channel);
await redirectTo.extendPubSubChannelListeners(pub_sub_1.PUBSUB_TYPE.SHARDED, channel, listeners);
}
catch (err) {
this.#emit('sharded-channel-moved-error', err, channel, listeners);
}
});
master.pubSub = {
client,
connectPromise: client.connect()
.then(client => {
master.pubSub.connectPromise = undefined;
return client;
})
.catch(err => {
master.pubSub = undefined;
throw err;
})
};
return master.pubSub.connectPromise;
}
async executeShardedUnsubscribeCommand(channel, unsubscribe) {
const { master } = this.slots[(0, cluster_key_slot_1.default)(channel)];
if (!master.pubSub)
return;
const client = master.pubSub.connectPromise ?
await master.pubSub.connectPromise :
master.pubSub.client;
await unsubscribe(client);
if (!client.isPubSubActive) {
this.#reconnectionTracker.removeClient(client._clientId);
client.destroy();
master.pubSub = undefined;
}
}
}
_a = RedisClusterSlots;
exports.default = RedisClusterSlots;
//# sourceMappingURL=cluster-slots.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,231 @@
/// <reference types="node" />
import { RedisClientOptions, RedisClientType, WithFunctions, WithModules, WithScripts } from '../client';
import { CommandOptions } from '../client/commands-queue';
import { CommandArguments, CommanderConfig, CommandSignature, TypeMapping, RedisArgument, RedisFunctions, RedisModules, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
import { NON_STICKY_COMMANDS } from '../commands';
import { EventEmitter } from 'node:events';
import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots';
import { RedisClusterMultiCommandType } from './multi-command';
import { PubSubListener, PubSubListeners } from '../client/pub-sub';
import { RedisTcpSocketOptions } from '../client/socket';
import { ClientSideCacheConfig, PooledClientSideCacheProvider } from '../client/cache';
import { ClientIdentity } from '../client/identity';
export type ClusterTopologyRefreshOnReconnectionAttemptStrategy = false | number | ((firstReconnectionAt: number) => false | number | undefined);
type WithCommands<RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof typeof NON_STICKY_COMMANDS]: CommandSignature<(typeof NON_STICKY_COMMANDS)[P], RESP, TYPE_MAPPING>;
};
interface ClusterCommander<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends CommanderConfig<M, F, S, RESP> {
commandOptions?: ClusterCommandOptions<TYPE_MAPPING>;
/**
* Prefix prepended to every key sent to Redis (ioredis-compatible `keyPrefix`).
*
* Applied by the cluster client itself; the slot of each command is computed from the
* prefixed key, so routing stays correct. It is intentionally a cluster-level option
* (not a per-node `rootNodes`/`defaults` option) so it is applied exactly once.
*
* Matches ioredis semantics: only keys *sent* to Redis are prefixed. Keys *returned*
* by Redis are NOT un-prefixed, `MATCH` patterns are NOT auto-prefixed, and Pub/Sub
* channels are NOT prefixed.
*/
keyPrefix?: RedisArgument;
}
export type RedisClusterClientOptions = Omit<RedisClientOptions<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisTcpSocketOptions>, keyof ClusterCommander<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>>;
export interface RedisClusterOptions<M extends RedisModules = RedisModules, F extends RedisFunctions = RedisFunctions, S extends RedisScripts = RedisScripts, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = TypeMapping> extends ClusterCommander<M, F, S, RESP, TYPE_MAPPING> {
/**
* Should contain details for some of the cluster nodes that the client will use to discover
* the "cluster topology". We recommend including details for at least 3 nodes here.
*/
rootNodes: Array<RedisClusterClientOptions>;
/**
* Default values used for every client in the cluster. Use this to specify global values,
* for example: ACL credentials, timeouts, TLS configuration etc.
*/
defaults?: Partial<RedisClusterClientOptions>;
/**
* When `true`, `.connect()` will only discover the cluster topology, without actually connecting to all the nodes.
* Useful for short-term or PubSub-only connections.
*/
minimizeConnections?: boolean;
/**
* When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes.
*/
useReplicas?: boolean;
/**
* The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors.
*/
maxCommandRedirections?: number;
/**
* The number of milliseconds after the first post-ready node reconnection attempt
* before background cluster topology refreshes are triggered. Omitted or `undefined`
* uses the default delay of `5000`.
* Use `false` or `0` to disable reconnect-triggered topology refreshes. A function can
* return the delay dynamically, or `false`/`undefined`/`0` to skip the refresh attempt.
* Concurrent refreshes are de-duplicated.
*/
topologyRefreshOnReconnectionAttemptStrategy?: ClusterTopologyRefreshOnReconnectionAttemptStrategy;
/**
* Mapping between the addresses in the cluster (see `CLUSTER SHARDS`) and the addresses the client should connect to
* Useful when the cluster is running on another network
*/
nodeAddressMap?: NodeAddressMap;
/**
* Client Side Caching configuration for the pool.
*
* Enables Redis Servers and Clients to work together to cache results from commands
* sent to a server. The server will notify the client when cached results are no longer valid.
* In pooled mode, the cache is shared across all clients in the pool.
*
* Note: Client Side Caching is only supported with RESP3.
*
* @example Anonymous cache configuration
* ```
* const client = createCluster({
* clientSideCache: {
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* },
* minimum: 5
* });
* ```
*
* @example Using a controllable cache
* ```
* const cache = new BasicPooledClientSideCache({
* ttl: 0,
* maxEntries: 0,
* evictPolicy: "LRU"
* });
* const client = createCluster({
* clientSideCache: cache,
* minimum: 5
* });
* ```
*/
clientSideCache?: PooledClientSideCacheProvider | ClientSideCacheConfig;
}
export type RedisClusterType<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}> = (RedisCluster<M, F, S, RESP, TYPE_MAPPING> & WithCommands<RESP, TYPE_MAPPING> & WithModules<M, RESP, TYPE_MAPPING> & WithFunctions<F, RESP, TYPE_MAPPING> & WithScripts<S, RESP, TYPE_MAPPING>);
export type ClusterCommandOptions<TYPE_MAPPING extends TypeMapping = TypeMapping> = CommandOptions<TYPE_MAPPING>;
export default class RedisCluster<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends EventEmitter {
#private;
static factory<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}>(config?: ClusterCommander<M, F, S, RESP, TYPE_MAPPING>): (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => RedisClusterType<M, F, S, RESP, TYPE_MAPPING>;
static create<M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = {}>(options?: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>): RedisClusterType<M, F, S, RESP, TYPE_MAPPING>;
readonly _options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>;
readonly _slots: RedisClusterSlots<M, F, S, RESP, TYPE_MAPPING>;
private _self;
private _commandOptions?;
/**
* An array of the cluster slots, each slot contain its `master` and `replicas`.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
*/
get slots(): import("./cluster-slots").Shard<M, F, S, RESP, TYPE_MAPPING>[];
get clientSideCache(): PooledClientSideCacheProvider | undefined;
/**
* The configured key prefix (see {@link RedisClusterOptions.keyPrefix}), if any.
* @internal
*/
get _keyPrefix(): RedisArgument | undefined;
/**
* An array of the cluster masters.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific master node.
*/
get masters(): import("./cluster-slots").MasterNode<M, F, S, RESP, TYPE_MAPPING>[];
/**
* An array of the cluster replicas.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific replica node.
*/
get replicas(): ShardNode<M, F, S, RESP, TYPE_MAPPING>[];
/**
* A map form a node address (`<host>:<port>`) to its shard, each shard contain its `master` and `replicas`.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
*/
get nodeByAddress(): Map<string, import("./cluster-slots").MasterNode<M, F, S, RESP, TYPE_MAPPING> | ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
/**
* The current pub/sub node.
*/
get pubSubNode(): (Omit<import("./cluster-slots").Node<M, F, S, RESP, TYPE_MAPPING>, "client"> & Required<Pick<import("./cluster-slots").Node<M, F, S, RESP, TYPE_MAPPING>, "client">>) | undefined;
get isOpen(): boolean;
/**
* @internal
* Returns the cluster identity for tracking in metrics.
*/
get identity(): ClientIdentity;
constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>);
duplicate<_M extends RedisModules = M, _F extends RedisFunctions = F, _S extends RedisScripts = S, _RESP extends RespVersions = RESP, _TYPE_MAPPING extends TypeMapping = TYPE_MAPPING>(overrides?: Partial<RedisClusterOptions<_M, _F, _S, _RESP, _TYPE_MAPPING>>): RedisClusterType<_M, _F, _S, _RESP, _TYPE_MAPPING>;
connect(): Promise<RedisClusterType<M, F, S, RESP, TYPE_MAPPING>>;
withCommandOptions<OPTIONS extends ClusterCommandOptions<TYPE_MAPPING>, TYPE_MAPPING extends TypeMapping>(options: OPTIONS): RedisClusterType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
private _commandOptionsProxy;
/**
* Override the `typeMapping` command option
*/
withTypeMapping<TYPE_MAPPING extends TypeMapping>(typeMapping: TYPE_MAPPING): RedisClusterType<M, F, S, RESP, TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}>;
_handleAsk<T>(fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, opts?: ClusterCommandOptions) => Promise<T>): (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, options?: ClusterCommandOptions) => Promise<T>;
_execute<T>(firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, options: ClusterCommandOptions | undefined, fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>, opts?: ClusterCommandOptions) => Promise<T>): Promise<T>;
sendCommand<T = ReplyUnion>(firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, args: CommandArguments, options?: ClusterCommandOptions): Promise<T>;
MULTI(routing?: RedisArgument): RedisClusterMultiCommandType<[], M, F, S, RESP, TYPE_MAPPING>;
multi: (routing?: RedisArgument) => RedisClusterMultiCommandType<[], M, F, S, RESP, TYPE_MAPPING>;
SUBSCRIBE<T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
subscribe: <T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
UNSUBSCRIBE<T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<boolean>, bufferMode?: T): Promise<void>;
unsubscribe: <T extends boolean = false>(channels?: string | Array<string>, listener?: PubSubListener<boolean>, bufferMode?: T | undefined) => Promise<void>;
PSUBSCRIBE<T extends boolean = false>(patterns: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
pSubscribe: <T extends boolean = false>(patterns: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
PUNSUBSCRIBE<T extends boolean = false>(patterns?: string | Array<string>, listener?: PubSubListener<T>, bufferMode?: T): Promise<void>;
pUnsubscribe: <T extends boolean = false>(patterns?: string | Array<string>, listener?: PubSubListener<T> | undefined, bufferMode?: T | undefined) => Promise<void>;
SSUBSCRIBE<T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T): Promise<void>;
sSubscribe: <T extends boolean = false>(channels: string | Array<string>, listener: PubSubListener<T>, bufferMode?: T | undefined) => Promise<void>;
SUNSUBSCRIBE<T extends boolean = false>(channels: string | Array<string>, listener?: PubSubListener<T>, bufferMode?: T): Promise<void>;
resubscribeAllPubSubListeners(allListeners: Partial<PubSubListeners>): void;
sUnsubscribe: <T extends boolean = false>(channels: string | Array<string>, listener?: PubSubListener<T> | undefined, bufferMode?: T | undefined) => Promise<void>;
/**
* @deprecated Use `close` instead.
*/
quit(): Promise<void>;
/**
* @deprecated Use `destroy` instead.
*/
disconnect(): Promise<void>;
close(): Promise<void>;
destroy(): void;
nodeClient(node: ShardNode<M, F, S, RESP, TYPE_MAPPING>): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
/**
* Returns a random node from the cluster.
* Userful for running "forward" commands (like PUBLISH) on a random node.
*/
getRandomNode(): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
/**
* Get a random node from a slot.
* Useful for running readonly commands on a slot.
*/
getSlotRandomNode(slot: number): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
/**
* Returns the connected node client responsible for the given key's slot.
* Useful for connection-level operations that the cluster client does not expose
* directly, such as `WATCH` followed by `MULTI`/`EXEC`:
*
* ```javascript
* const nodeClient = await cluster.getNodeClientForKey(key);
* await nodeClient.WATCH(key);
* const value = await nodeClient.GET(key);
* const reply = await nodeClient.MULTI()
* .SET(key, calculateNewValue(value))
* .EXEC(); // `null` if `key` changed, retry
* ```
*
* @param key - The key whose slot determines the node.
* @param isReadonly - If `true`, may return a replica client; otherwise returns the slot master.
*/
getNodeClientForKey(key: RedisArgument, isReadonly?: boolean): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
/**
* @deprecated use `.masters` instead
* TODO
*/
getMasters(): import("./cluster-slots").MasterNode<M, F, S, RESP, TYPE_MAPPING>[];
/**
* @deprecated use `.slots[<SLOT>]` instead
* TODO
*/
getSlotMaster(slot: number): import("./cluster-slots").MasterNode<M, F, S, RESP, TYPE_MAPPING>;
}
export {};
//# sourceMappingURL=index.d.ts.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,450 @@
"use strict";
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__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 });
const commands_1 = require("../commands");
const node_events_1 = require("node:events");
const commander_1 = require("../commander");
const cluster_slots_1 = __importStar(require("./cluster-slots"));
const multi_command_1 = __importDefault(require("./multi-command"));
const errors_1 = require("../errors");
const parser_1 = require("../client/parser");
const ASKING_1 = require("../commands/ASKING");
const single_entry_cache_1 = __importDefault(require("../single-entry-cache"));
const tracing_1 = require("../client/tracing");
const identity_1 = require("../client/identity");
const defaults_1 = require("../defaults");
class RedisCluster extends node_events_1.EventEmitter {
static #createCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this._self._execute(parser.firstKey, command.IS_READ_ONLY, this._commandOptions, (client, opts) => client._executeCommand(command, parser, opts, transformReply));
};
}
static #createModuleCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
command.parseCommand(parser, ...args);
return this._self._execute(parser.firstKey, command.IS_READ_ONLY, this._self._commandOptions, (client, opts) => client._executeCommand(command, parser, opts, transformReply));
};
}
static #createFunctionCommand(name, fn, resp) {
const prefix = (0, commander_1.functionArgumentsPrefix)(name, fn);
const transformReply = (0, commander_1.getTransformReply)(fn, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.push(...prefix);
fn.parseCommand(parser, ...args);
return this._self._execute(parser.firstKey, fn.IS_READ_ONLY, this._self._commandOptions, (client, opts) => client._executeCommand(fn, parser, opts, transformReply));
};
}
static #createScriptCommand(script, resp) {
const prefix = (0, commander_1.scriptArgumentsPrefix)(script);
const transformReply = (0, commander_1.getTransformReply)(script, resp);
return async function (...args) {
const parser = new parser_1.BasicCommandParser(this._self._keyPrefix);
parser.push(...prefix);
script.parseCommand(parser, ...args);
return this._self._execute(parser.firstKey, script.IS_READ_ONLY, this._commandOptions, (client, opts) => client._executeScript(script, parser, opts, transformReply));
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- cache stores dynamically generated cluster subclasses
static #SingleEntryCache = new single_entry_cache_1.default();
static factory(config) {
let Cluster = RedisCluster.#SingleEntryCache.get(config);
if (!Cluster) {
Cluster = (0, commander_1.attachConfig)({
BaseClass: RedisCluster,
commands: commands_1.NON_STICKY_COMMANDS,
createCommand: RedisCluster.#createCommand,
createModuleCommand: RedisCluster.#createModuleCommand,
createFunctionCommand: RedisCluster.#createFunctionCommand,
createScriptCommand: RedisCluster.#createScriptCommand,
config
});
Cluster.prototype.Multi = multi_command_1.default.extend(config);
RedisCluster.#SingleEntryCache.set(config, Cluster);
}
return (options) => {
// returning a "proxy" to prevent the namespaces._self to leak between "proxies"
return Object.create(new Cluster(options));
};
}
static create(options) {
return RedisCluster.factory(options)(options);
}
_options;
_slots;
#identity;
_self = this;
_commandOptions;
/**
* An array of the cluster slots, each slot contain its `master` and `replicas`.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
*/
get slots() {
return this._self._slots.slots;
}
get clientSideCache() {
return this._self._slots.clientSideCache;
}
/**
* The configured key prefix (see {@link RedisClusterOptions.keyPrefix}), if any.
* @internal
*/
get _keyPrefix() {
return this._self._options.keyPrefix;
}
/**
* An array of the cluster masters.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific master node.
*/
get masters() {
return this._self._slots.masters;
}
/**
* An array of the cluster replicas.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific replica node.
*/
get replicas() {
return this._self._slots.replicas;
}
/**
* A map form a node address (`<host>:<port>`) to its shard, each shard contain its `master` and `replicas`.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
*/
get nodeByAddress() {
return this._self._slots.nodeByAddress;
}
/**
* The current pub/sub node.
*/
get pubSubNode() {
return this._self._slots.pubSubNode;
}
get isOpen() {
return this._self._slots.isOpen;
}
/**
* @internal
* Returns the cluster identity for tracking in metrics.
*/
get identity() {
return this._self.#identity;
}
constructor(options) {
super();
this.#identity = {
id: (0, identity_1.generateClusterClientId)(options.rootNodes),
role: identity_1.ClientRole.CLUSTER
};
this._options = options;
this._slots = new cluster_slots_1.default(options, this.emit.bind(this), this.#identity.id);
this.on(cluster_slots_1.RESUBSCRIBE_LISTENERS_EVENT, this.resubscribeAllPubSubListeners.bind(this));
this._commandOptions = { timeout: defaults_1.DEFAULT_COMMAND_TIMEOUT, ...options?.commandOptions };
}
duplicate(overrides) {
return new (Object.getPrototypeOf(this).constructor)({
...this._self._options,
commandOptions: this._commandOptions,
...overrides
});
}
async connect() {
await this._self._slots.connect();
return this;
}
withCommandOptions(options) {
const proxy = Object.create(this);
proxy._commandOptions = options;
return proxy;
}
_commandOptionsProxy(key, value) {
const proxy = Object.create(this);
proxy._commandOptions = { ...this._commandOptions, [key]: value };
return proxy;
}
/**
* Override the `typeMapping` command option
*/
withTypeMapping(typeMapping) {
return this._commandOptionsProxy('typeMapping', typeMapping);
}
// /**
// * Override the `policies` command option
// * TODO
// */
// withPolicies<POLICIES extends CommandPolicies> (policies: POLICIES) {
// return this._commandOptionsProxy('policies', policies);
// }
_handleAsk(fn) {
return async (client, options) => {
const chainId = Symbol("asking chain");
const opts = options ? { ...options } : {};
opts.chainId = chainId;
const ret = await Promise.all([
client.sendCommand([ASKING_1.ASKING_CMD], { chainId: chainId }),
fn(client, opts)
]);
return ret[1];
};
}
async _execute(firstKey, isReadonly, options, fn) {
const maxCommandRedirections = this._options.maxCommandRedirections ?? 16;
let { client, slotNumber } = await this._slots.getClientAndSlotNumber(firstKey, isReadonly);
let i = 0;
let myFn = fn;
while (true) {
try {
const opts = { ...options, slotNumber };
return await myFn(client, opts);
}
catch (_err) {
const err = _err;
myFn = fn;
// TODO: error class
if (++i > maxCommandRedirections || !(err instanceof Error)) {
if (err instanceof Error) {
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'cluster',
internal: false,
clientId: client._clientId,
retryCount: i,
}));
}
throw err;
}
if (err.message.startsWith('ASK')) {
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'cluster',
internal: true,
clientId: client._clientId,
retryCount: i,
}));
const address = err.message.substring(err.message.lastIndexOf(' ') + 1);
let redirectTo = await this._slots.getMasterByAddress(address);
if (!redirectTo) {
await this._slots.rediscover(client);
redirectTo = await this._slots.getMasterByAddress(address);
}
if (!redirectTo) {
throw new Error(`Cannot find node ${address}`);
}
client = redirectTo;
myFn = this._handleAsk(fn);
continue;
}
if (err.message.startsWith('MOVED')) {
(0, tracing_1.publish)(tracing_1.CHANNELS.ERROR, () => ({
error: err,
origin: 'cluster',
internal: true,
clientId: client._clientId,
retryCount: i,
}));
await this._slots.rediscover(client);
const clientAndSlot = await this._slots.getClientAndSlotNumber(firstKey, isReadonly);
client = clientAndSlot.client;
slotNumber = clientAndSlot.slotNumber;
continue;
}
throw err;
}
}
}
async sendCommand(firstKey, isReadonly, args, options) {
// Merge global options with local options
const opts = {
...this._commandOptions,
...options
};
return this._self._execute(firstKey, isReadonly, opts, (client, opts) => client.sendCommand(args, opts));
}
MULTI(routing) {
return new this.Multi(async (firstKey, isReadonly, commands) => {
const { client } = await this._self._slots.getClientAndSlotNumber(firstKey, isReadonly);
return client._executeMulti(commands);
}, async (firstKey, isReadonly, commands) => {
const { client } = await this._self._slots.getClientAndSlotNumber(firstKey, isReadonly);
return client._executePipeline(commands);
}, routing, this._commandOptions?.typeMapping, this._self._keyPrefix);
}
multi = this.MULTI;
async SUBSCRIBE(channels, listener, bufferMode) {
return (await this._self._slots.getPubSubClient())
.SUBSCRIBE(channels, listener, bufferMode);
}
subscribe = this.SUBSCRIBE;
async UNSUBSCRIBE(channels, listener, bufferMode) {
return this._self._slots.executeUnsubscribeCommand(client => client.UNSUBSCRIBE(channels, listener, bufferMode));
}
unsubscribe = this.UNSUBSCRIBE;
async PSUBSCRIBE(patterns, listener, bufferMode) {
return (await this._self._slots.getPubSubClient())
.PSUBSCRIBE(patterns, listener, bufferMode);
}
pSubscribe = this.PSUBSCRIBE;
async PUNSUBSCRIBE(patterns, listener, bufferMode) {
return this._self._slots.executeUnsubscribeCommand(client => client.PUNSUBSCRIBE(patterns, listener, bufferMode));
}
pUnsubscribe = this.PUNSUBSCRIBE;
async SSUBSCRIBE(channels, listener, bufferMode) {
const maxCommandRedirections = this._self._options.maxCommandRedirections ?? 16, firstChannel = Array.isArray(channels) ? channels[0] : channels;
let client = await this._self._slots.getShardedPubSubClient(firstChannel);
for (let i = 0;; i++) {
try {
return await client.SSUBSCRIBE(channels, listener, bufferMode);
}
catch (err) {
if (++i > maxCommandRedirections || !(err instanceof errors_1.ErrorReply)) {
throw err;
}
if (err.message.startsWith('MOVED')) {
await this._self._slots.rediscover(client);
client = await this._self._slots.getShardedPubSubClient(firstChannel);
continue;
}
throw err;
}
}
}
sSubscribe = this.SSUBSCRIBE;
SUNSUBSCRIBE(channels, listener, bufferMode) {
return this._self._slots.executeShardedUnsubscribeCommand(Array.isArray(channels) ? channels[0] : channels, client => client.SUNSUBSCRIBE(channels, listener, bufferMode));
}
resubscribeAllPubSubListeners(allListeners) {
if (allListeners.CHANNELS) {
for (const [channel, listeners] of allListeners.CHANNELS) {
listeners.buffers.forEach(bufListener => {
this.subscribe(channel, bufListener, true);
});
listeners.strings.forEach(strListener => {
this.subscribe(channel, strListener);
});
}
}
if (allListeners.PATTERNS) {
for (const [channel, listeners] of allListeners.PATTERNS) {
listeners.buffers.forEach(bufListener => {
this.pSubscribe(channel, bufListener, true);
});
listeners.strings.forEach(strListener => {
this.pSubscribe(channel, strListener);
});
}
}
if (allListeners.SHARDED) {
for (const [channel, listeners] of allListeners.SHARDED) {
listeners.buffers.forEach(bufListener => {
this.sSubscribe(channel, bufListener, true);
});
listeners.strings.forEach(strListener => {
this.sSubscribe(channel, strListener);
});
}
}
}
sUnsubscribe = this.SUNSUBSCRIBE;
/**
* @deprecated Use `close` instead.
*/
quit() {
return this._self._slots.quit();
}
/**
* @deprecated Use `destroy` instead.
*/
disconnect() {
return this._self._slots.disconnect();
}
close() {
this._self._slots.clientSideCache?.onPoolClose();
return this._self._slots.close();
}
destroy() {
this._self._slots.clientSideCache?.onPoolClose();
return this._self._slots.destroy();
}
nodeClient(node) {
return this._self._slots.nodeClient(node);
}
/**
* Returns a random node from the cluster.
* Userful for running "forward" commands (like PUBLISH) on a random node.
*/
getRandomNode() {
return this._self._slots.getRandomNode();
}
/**
* Get a random node from a slot.
* Useful for running readonly commands on a slot.
*/
getSlotRandomNode(slot) {
return this._self._slots.getSlotRandomNode(slot);
}
/**
* Returns the connected node client responsible for the given key's slot.
* Useful for connection-level operations that the cluster client does not expose
* directly, such as `WATCH` followed by `MULTI`/`EXEC`:
*
* ```javascript
* const nodeClient = await cluster.getNodeClientForKey(key);
* await nodeClient.WATCH(key);
* const value = await nodeClient.GET(key);
* const reply = await nodeClient.MULTI()
* .SET(key, calculateNewValue(value))
* .EXEC(); // `null` if `key` changed, retry
* ```
*
* @param key - The key whose slot determines the node.
* @param isReadonly - If `true`, may return a replica client; otherwise returns the slot master.
*/
getNodeClientForKey(key, isReadonly) {
return this._self._slots.getClientForKey(key, isReadonly);
}
/**
* @deprecated use `.masters` instead
* TODO
*/
getMasters() {
return this.masters;
}
/**
* @deprecated use `.slots[<SLOT>]` instead
* TODO
*/
getSlotMaster(slot) {
return this.slots[slot].master;
}
}
exports.default = RedisCluster;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,50 @@
import { NON_STICKY_COMMANDS } from '../commands';
import { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command';
import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, TypeMapping, RedisArgument } from '../RESP/types';
import { Tail } from '../commands/generic-transformers';
type CommandSignature<REPLIES extends Array<unknown>, C extends Command, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (...args: Tail<Parameters<C['parseCommand']>>) => RedisClusterMultiCommandType<[
...REPLIES,
ReplyWithTypeMapping<CommandReply<C, RESP>, TYPE_MAPPING>
], M, F, S, RESP, TYPE_MAPPING>;
type WithCommands<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof typeof NON_STICKY_COMMANDS]: CommandSignature<REPLIES, (typeof NON_STICKY_COMMANDS)[P], M, F, S, RESP, TYPE_MAPPING>;
};
type WithModules<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof M]: {
[C in keyof M[P]]: CommandSignature<REPLIES, M[P][C], M, F, S, RESP, TYPE_MAPPING>;
};
};
type WithFunctions<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[L in keyof F]: {
[C in keyof F[L]]: CommandSignature<REPLIES, F[L][C], M, F, S, RESP, TYPE_MAPPING>;
};
};
type WithScripts<REPLIES extends Array<unknown>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = {
[P in keyof S]: CommandSignature<REPLIES, S[P], M, F, S, RESP, TYPE_MAPPING>;
};
export type RedisClusterMultiCommandType<REPLIES extends Array<any>, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (RedisClusterMultiCommand<REPLIES> & WithCommands<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithModules<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithFunctions<REPLIES, M, F, S, RESP, TYPE_MAPPING> & WithScripts<REPLIES, M, F, S, RESP, TYPE_MAPPING>);
export type ClusterMultiExecute = (firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, commands: Array<RedisMultiQueuedCommand>) => Promise<Array<unknown>>;
export default class RedisClusterMultiCommand<REPLIES = []> {
#private;
static extend<M extends RedisModules = Record<string, never>, F extends RedisFunctions = Record<string, never>, S extends RedisScripts = Record<string, never>, RESP extends RespVersions = 3>(config?: CommanderConfig<M, F, S, RESP>): any;
constructor(executeMulti: ClusterMultiExecute, executePipeline: ClusterMultiExecute, routing: RedisArgument | undefined, typeMapping?: TypeMapping, keyPrefix?: RedisArgument);
addCommand(firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, args: CommandArguments, transformReply?: TransformReply): this;
exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline?: boolean): Promise<MultiReplyType<T, REPLIES>>;
EXEC: <T extends MultiReply = "generic">(execAsPipeline?: boolean) => Promise<MultiReplyType<T, REPLIES>>;
execTyped(execAsPipeline?: boolean): Promise<REPLIES>;
execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>(): Promise<MultiReplyType<T, REPLIES>>;
execAsPipelineTyped(): Promise<REPLIES>;
/**
* Adds a raw command to the multi/pipeline queue.
*
* Note: Using this method breaks the type inference for `execTyped` and
* `execAsPipelineTyped`. This is a known limitation and will be addressed
* in the future.
*/
sendCommand(args: ReadonlyArray<RedisArgument>, options?: {
firstKey?: RedisArgument;
isReadonly?: boolean;
}): this;
}
export {};
//# sourceMappingURL=multi-command.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"multi-command.d.ts","sourceRoot":"","sources":["../../../lib/cluster/multi-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commands_1 = require("../commands");
const multi_command_1 = __importDefault(require("../multi-command"));
const commander_1 = require("../commander");
const parser_1 = require("../client/parser");
class RedisClusterMultiCommand {
static #createCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this.#keyPrefix);
command.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
const firstKey = parser.firstKey;
return this.addCommand(firstKey, command.IS_READ_ONLY, redisArgs, transformReply);
};
}
static #createModuleCommand(command, resp) {
const transformReply = (0, commander_1.getTransformReply)(command, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this._self.#keyPrefix);
command.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
const firstKey = parser.firstKey;
return this._self.addCommand(firstKey, command.IS_READ_ONLY, redisArgs, transformReply);
};
}
static #createFunctionCommand(name, fn, resp) {
const prefix = (0, commander_1.functionArgumentsPrefix)(name, fn);
const transformReply = (0, commander_1.getTransformReply)(fn, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this._self.#keyPrefix);
parser.push(...prefix);
fn.parseCommand(parser, ...args);
const redisArgs = parser.redisArgs;
redisArgs.preserve = parser.preserve;
const firstKey = parser.firstKey;
return this._self.addCommand(firstKey, fn.IS_READ_ONLY, redisArgs, transformReply);
};
}
static #createScriptCommand(script, resp) {
const transformReply = (0, commander_1.getTransformReply)(script, resp);
return function (...args) {
const parser = new parser_1.BasicCommandParser(this.#keyPrefix);
script.parseCommand(parser, ...args);
const scriptArgs = parser.redisArgs;
scriptArgs.preserve = parser.preserve;
const firstKey = parser.firstKey;
return this.#addScript(firstKey, script.IS_READ_ONLY, script, scriptArgs, transformReply);
};
}
static extend(config) {
return (0, commander_1.attachConfig)({
BaseClass: RedisClusterMultiCommand,
commands: commands_1.NON_STICKY_COMMANDS,
createCommand: RedisClusterMultiCommand.#createCommand,
createModuleCommand: RedisClusterMultiCommand.#createModuleCommand,
createFunctionCommand: RedisClusterMultiCommand.#createFunctionCommand,
createScriptCommand: RedisClusterMultiCommand.#createScriptCommand,
config
});
}
#multi;
#executeMulti;
#executePipeline;
#firstKey;
#isReadonly = true;
#keyPrefix;
constructor(executeMulti, executePipeline, routing, typeMapping, keyPrefix) {
this.#multi = new multi_command_1.default(typeMapping);
this.#executeMulti = executeMulti;
this.#executePipeline = executePipeline;
// An explicit routing key must be prefixed too, so it hashes to the same slot as the
// (prefixed) keys the commands inside the transaction operate on.
this.#firstKey = routing === undefined ? undefined : (0, parser_1.prefixKey)(keyPrefix, routing);
this.#keyPrefix = keyPrefix;
}
#setState(firstKey, isReadonly) {
this.#firstKey ??= firstKey;
this.#isReadonly &&= isReadonly;
}
addCommand(firstKey, isReadonly, args, transformReply) {
this.#setState(firstKey, isReadonly);
this.#multi.addCommand(args, transformReply);
return this;
}
#addScript(firstKey, isReadonly, script, args, transformReply) {
this.#setState(firstKey, isReadonly);
this.#multi.addScript(script, args, transformReply);
return this;
}
async exec(execAsPipeline = false) {
if (execAsPipeline)
return this.execAsPipeline();
return this.#multi.transformReplies(await this.#executeMulti(this.#firstKey, this.#isReadonly, this.#multi.queue));
}
EXEC = this.exec;
execTyped(execAsPipeline = false) {
return this.exec(execAsPipeline);
}
async execAsPipeline() {
if (this.#multi.queue.length === 0)
return [];
return this.#multi.transformReplies(await this.#executePipeline(this.#firstKey, this.#isReadonly, this.#multi.queue));
}
execAsPipelineTyped() {
return this.execAsPipeline();
}
/**
* Adds a raw command to the multi/pipeline queue.
*
* Note: Using this method breaks the type inference for `execTyped` and
* `execAsPipelineTyped`. This is a known limitation and will be addressed
* in the future.
*/
sendCommand(args, options) {
const redisArgs = args.slice();
this.addCommand(options?.firstKey, options?.isReadonly, redisArgs);
return this;
}
}
exports.default = RedisClusterMultiCommand;
//# sourceMappingURL=multi-command.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
/// <reference types="node" />
import { Command, CommanderConfig, RedisArgument, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions, TransformReply } from './RESP/types';
interface AttachConfigOptions<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions> {
BaseClass: new (...args: any) => any;
commands: RedisCommands;
createCommand(command: Command, resp: RespVersions): (...args: any) => any;
createModuleCommand(command: Command, resp: RespVersions): (...args: any) => any;
createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions): (...args: any) => any;
createScriptCommand(script: RedisScript, resp: RespVersions): (...args: any) => any;
config?: CommanderConfig<M, F, S, RESP>;
}
export declare function attachConfig<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions>({ BaseClass, commands, createCommand, createModuleCommand, createFunctionCommand, createScriptCommand, config }: AttachConfigOptions<M, F, S, RESP>): any;
export declare function getTransformReply(command: Command, resp: RespVersions): TransformReply | undefined;
export declare function functionArgumentsPrefix(name: string, fn: RedisFunction): RedisArgument[];
export declare function scriptArgumentsPrefix(script: RedisScript): (string | Buffer)[];
export {};
//# sourceMappingURL=commander.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../../lib/commander.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAgB,MAAM,cAAc,CAAC;AAE1M,UAAU,mBAAmB,CAC3B,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,cAAc,EACxB,CAAC,SAAS,YAAY,EACtB,IAAI,SAAS,YAAY;IAGzB,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IACrC,QAAQ,EAAE,aAAa,CAAC;IAExB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAE3E,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEjF,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAElG,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IACpF,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;CACzC;AAED,wBAAgB,YAAY,CAC1B,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,cAAc,EACxB,CAAC,SAAS,YAAY,EACtB,IAAI,SAAS,YAAY,EACzB,EACA,SAAS,EACT,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,MAAM,EACP,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,OAwCpC;AAgCD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,cAAc,GAAG,SAAS,CAQlG;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,mBAWtE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,uBAWxD"}
+100
View File
@@ -0,0 +1,100 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.scriptArgumentsPrefix = exports.functionArgumentsPrefix = exports.getTransformReply = exports.attachConfig = void 0;
const types_1 = require("./RESP/types");
function attachConfig({ BaseClass, commands, createCommand, createModuleCommand, createFunctionCommand, createScriptCommand, config }) {
const RESP = config?.RESP ?? types_1.DEFAULT_RESP,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic prototype patching
Class = class extends BaseClass {
};
for (const [name, command] of Object.entries(commands)) {
Class.prototype[name] = createCommand(command, RESP);
}
if (config?.modules) {
for (const [moduleName, module] of Object.entries(config.modules)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic command namespace
const fns = {};
for (const [name, command] of Object.entries(module)) {
fns[name] = createModuleCommand(command, RESP);
}
attachNamespace(Class.prototype, moduleName, fns);
}
}
if (config?.functions) {
for (const [library, commands] of Object.entries(config.functions)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic command namespace
const fns = {};
for (const [name, command] of Object.entries(commands)) {
fns[name] = createFunctionCommand(name, command, RESP);
}
attachNamespace(Class.prototype, library, fns);
}
}
if (config?.scripts) {
for (const [name, script] of Object.entries(config.scripts)) {
Class.prototype[name] = createScriptCommand(script, RESP);
}
}
return Class;
}
exports.attachConfig = attachConfig;
// Per-receiver namespace cache. Keyed by the receiver (original instance or any
// `withCommandOptions(...)` proxy) so each one gets a namespace bound to itself
// via `_self`. Caching the namespace as an own property on the receiver — which
// is what an earlier version did — leaks across the prototype chain: a proxy
// created via `Object.create(original)` would inherit the original's cached
// namespace and `_self` would point back to the original, silently bypassing
// the proxy's command-options overrides for every module/function command.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- namespaces are dynamically shaped per module
const namespaceCache = new WeakMap();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic prototype patching helper
function attachNamespace(prototype, name, fns) {
Object.defineProperty(prototype, name, {
get() {
let perReceiver = namespaceCache.get(this);
if (perReceiver === undefined) {
perReceiver = new Map();
namespaceCache.set(this, perReceiver);
}
let value = perReceiver.get(name);
if (value === undefined) {
value = Object.create(fns);
value._self = this;
perReceiver.set(name, value);
}
return value;
}
});
}
function getTransformReply(command, resp) {
switch (typeof command.transformReply) {
case 'function':
return command.transformReply;
case 'object':
return command.transformReply[resp];
}
}
exports.getTransformReply = getTransformReply;
function functionArgumentsPrefix(name, fn) {
const prefix = [
fn.IS_READ_ONLY ? 'FCALL_RO' : 'FCALL',
name
];
if (fn.NUMBER_OF_KEYS !== undefined) {
prefix.push(fn.NUMBER_OF_KEYS.toString());
}
return prefix;
}
exports.functionArgumentsPrefix = functionArgumentsPrefix;
function scriptArgumentsPrefix(script) {
const prefix = [
script.IS_READ_ONLY ? 'EVALSHA_RO' : 'EVALSHA',
script.SHA1
];
if (script.NUMBER_OF_KEYS !== undefined) {
prefix.push(script.NUMBER_OF_KEYS.toString());
}
return prefix;
}
exports.scriptArgumentsPrefix = scriptArgumentsPrefix;
//# sourceMappingURL=commander.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, ArrayReply, BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, categoryName?: RedisArgument) => void;
readonly transformReply: () => ArrayReply<BlobStringReply>;
};
export default _default;
//# sourceMappingURL=ACL_CAT.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_CAT.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_CAT.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAK7D,aAAa,iBAAiB,aAAa;mCAMlB,WAAW,eAAe,CAAC;;AAT3E,wBAU6B"}
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, categoryName) {
parser.push('ACL', 'CAT');
if (categoryName) {
parser.push(categoryName);
}
},
transformReply: undefined
};
//# sourceMappingURL=ACL_CAT.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_CAT.js","sourceRoot":"","sources":["../../../lib/commands/ACL_CAT.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,YAA4B;QAC9D,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1B,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,cAAc,EAAE,SAAyD;CAC/C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, ArrayReply, BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, categoryName?: RedisArgument) {\n parser.push('ACL', 'CAT');\n if (categoryName) {\n parser.push(categoryName);\n }\n },\n transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,11 @@
import { CommandParser } from '../client/parser';
import { NumberReply } from '../RESP/types';
import { RedisVariadicArgument } from './generic-transformers';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, username: RedisVariadicArgument) => void;
readonly transformReply: () => NumberReply;
};
export default _default;
//# sourceMappingURL=ACL_DELUSER.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_DELUSER.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_DELUSER.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;;;;gDAKxC,aAAa,YAAY,qBAAqB;mCAIrB,WAAW;;AAP3D,wBAQ6B"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, username) {
parser.push('ACL', 'DELUSER');
parser.pushVariadic(username);
},
transformReply: undefined
};
//# sourceMappingURL=ACL_DELUSER.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_DELUSER.js","sourceRoot":"","sources":["../../../lib/commands/ACL_DELUSER.ts"],"names":[],"mappings":";;AAIA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,QAA+B;QACjE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC9B,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IACD,cAAc,EAAE,SAAyC;CAC/B,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { NumberReply, Command } from '../RESP/types';\nimport { RedisVariadicArgument } from './generic-transformers';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, username: RedisVariadicArgument) {\n parser.push('ACL', 'DELUSER');\n parser.pushVariadic(username);\n },\n transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, SimpleStringReply, BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, username: RedisArgument, command: Array<RedisArgument>) => void;
readonly transformReply: () => SimpleStringReply<'OK'> | BlobStringReply;
};
export default _default;
//# sourceMappingURL=ACL_DRYRUN.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_DRYRUN.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_DRYRUN.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAKpE,aAAa,YAAY,aAAa,WAAW,MAAM,aAAa,CAAC;mCAG5C,kBAAkB,IAAI,CAAC,GAAG,eAAe;;AANzF,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, username, command) {
parser.push('ACL', 'DRYRUN', username, ...command);
},
transformReply: undefined
};
//# sourceMappingURL=ACL_DRYRUN.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_DRYRUN.js","sourceRoot":"","sources":["../../../lib/commands/ACL_DRYRUN.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,QAAuB,EAAE,OAA6B;QACxF,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC;IACrD,CAAC;IACD,cAAc,EAAE,SAAuE;CAC7D,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, SimpleStringReply, BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, username: RedisArgument, command: Array<RedisArgument>) {\n parser.push('ACL', 'DRYRUN', username, ...command);\n },\n transformReply: undefined as unknown as () => SimpleStringReply<'OK'> | BlobStringReply\n} as const satisfies Command;\n\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, bits?: number) => void;
readonly transformReply: () => BlobStringReply;
};
export default _default;
//# sourceMappingURL=ACL_GENPASS.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_GENPASS.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_GENPASS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAKlC,aAAa,SAAS,MAAM;mCAMH,eAAe;;AAT/D,wBAU6B"}
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, bits) {
parser.push('ACL', 'GENPASS');
if (bits) {
parser.push(bits.toString());
}
},
transformReply: undefined
};
//# sourceMappingURL=ACL_GENPASS.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_GENPASS.js","sourceRoot":"","sources":["../../../lib/commands/ACL_GENPASS.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,IAAa;QAC/C,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC9B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,cAAc,EAAE,SAA6C;CACnC,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, bits?: number) {\n parser.push('ACL', 'GENPASS');\n if (bits) {\n parser.push(bits.toString());\n }\n },\n transformReply: undefined as unknown as () => BlobStringReply\n} as const satisfies Command;\n\n"]}
@@ -0,0 +1,66 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply } from '../RESP/types';
type AclUser = TuplesToMapReply<[
[
BlobStringReply<'flags'>,
ArrayReply<BlobStringReply>
],
[
BlobStringReply<'passwords'>,
ArrayReply<BlobStringReply>
],
[
BlobStringReply<'commands'>,
BlobStringReply
],
/** changed to BlobStringReply in 7.0 */
[
BlobStringReply<'keys'>,
ArrayReply<BlobStringReply> | BlobStringReply
],
/** added in 6.2, changed to BlobStringReply in 7.0 */
[
BlobStringReply<'channels'>,
ArrayReply<BlobStringReply> | BlobStringReply
],
/** added in 7.0 */
[
BlobStringReply<'selectors'>,
ArrayReply<TuplesToMapReply<[
[
BlobStringReply<'commands'>,
BlobStringReply
],
[
BlobStringReply<'keys'>,
BlobStringReply
],
[
BlobStringReply<'channels'>,
BlobStringReply
]
]>>
]
]>;
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, username: RedisArgument) => void;
readonly transformReply: {
readonly 2: (reply: [BlobStringReply<"flags">, import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>, BlobStringReply<"passwords">, import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>, BlobStringReply<"commands">, BlobStringReply<string>, BlobStringReply<"keys">, BlobStringReply<string> | import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>, BlobStringReply<"channels">, BlobStringReply<string> | import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>, BlobStringReply<"selectors">, import("../RESP/types").RespType<42, import("../RESP/types").RespType<42, [BlobStringReply<"commands">, BlobStringReply<string>, BlobStringReply<"keys">, BlobStringReply<string>, BlobStringReply<"channels">, BlobStringReply<string>], never, [BlobStringReply<"commands">, BlobStringReply<string>, BlobStringReply<"keys">, BlobStringReply<string>, BlobStringReply<"channels">, BlobStringReply<string>]>[], never, import("../RESP/types").RespType<42, [BlobStringReply<"commands">, BlobStringReply<string>, BlobStringReply<"keys">, BlobStringReply<string>, BlobStringReply<"channels">, BlobStringReply<string>], never, [BlobStringReply<"commands">, BlobStringReply<string>, BlobStringReply<"keys">, BlobStringReply<string>, BlobStringReply<"channels">, BlobStringReply<string>]>[]>]) => {
flags: import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>;
passwords: import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>;
commands: BlobStringReply<string>;
keys: BlobStringReply<string> | import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>;
channels: BlobStringReply<string> | import("../RESP/types").RespType<42, BlobStringReply<string>[], never, BlobStringReply<string>[]>;
selectors: {
commands: BlobStringReply<string>;
keys: BlobStringReply<string>;
channels: BlobStringReply<string>;
}[];
};
readonly 3: () => AclUser;
};
};
export default _default;
//# sourceMappingURL=ACL_GETUSER.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_GETUSER.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_GETUSER.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAoC,MAAM,eAAe,CAAC;AAE/H,KAAK,OAAO,GAAG,gBAAgB,CAAC;IAC9B;QAAC,eAAe,CAAC,OAAO,CAAC;QAAE,UAAU,CAAC,eAAe,CAAC;KAAC;IACvD;QAAC,eAAe,CAAC,WAAW,CAAC;QAAE,UAAU,CAAC,eAAe,CAAC;KAAC;IAC3D;QAAC,eAAe,CAAC,UAAU,CAAC;QAAE,eAAe;KAAC;IAC9C,wCAAwC;IACxC;QAAC,eAAe,CAAC,MAAM,CAAC;QAAE,UAAU,CAAC,eAAe,CAAC,GAAG,eAAe;KAAC;IACxE,sDAAsD;IACtD;QAAC,eAAe,CAAC,UAAU,CAAC;QAAE,UAAU,CAAC,eAAe,CAAC,GAAG,eAAe;KAAC;IAC5E,mBAAmB;IACnB;QAAC,eAAe,CAAC,WAAW,CAAC;QAAE,UAAU,CAAC,gBAAgB,CAAC;YACzD;gBAAC,eAAe,CAAC,UAAU,CAAC;gBAAE,eAAe;aAAC;YAC9C;gBAAC,eAAe,CAAC,MAAM,CAAC;gBAAE,eAAe;aAAC;YAC1C;gBAAC,eAAe,CAAC,UAAU,CAAC;gBAAE,eAAe;aAAC;SAC/C,CAAC,CAAC;KAAC;CACL,CAAC,CAAC;;;;gDAKoB,aAAa,YAAY,aAAa;;;;;;;;;;;;;;;;;AAH7D,wBAwB6B"}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, username) {
parser.push('ACL', 'GETUSER', username);
},
transformReply: {
2: (reply) => ({
flags: reply[1],
passwords: reply[3],
commands: reply[5],
keys: reply[7],
channels: reply[9],
selectors: reply[11]?.map(selector => {
const inferred = selector;
return {
commands: inferred[1],
keys: inferred[3],
channels: inferred[5]
};
})
}),
3: undefined
}
};
//# sourceMappingURL=ACL_GETUSER.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_GETUSER.js","sourceRoot":"","sources":["../../../lib/commands/ACL_GETUSER.ts"],"names":[],"mappings":";;AAmBA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,QAAuB;QACzD,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IACD,cAAc,EAAE;QACd,CAAC,EAAE,CAAC,KAAuC,EAAE,EAAE,CAAC,CAAC;YAC/C,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;YACf,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YACnB,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAClB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YACd,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAClB,SAAS,EAAG,KAAK,CAAC,EAAE,CAA8C,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE;gBACjF,MAAM,QAAQ,GAAG,QAAmD,CAAC;gBACrE,OAAO;oBACL,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACrB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACjB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;iBACtB,CAAC;YACJ,CAAC,CAAC;SACH,CAAC;QACF,CAAC,EAAE,SAAqC;KACzC;CACyB,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';\n\ntype AclUser = TuplesToMapReply<[\n [BlobStringReply<'flags'>, ArrayReply<BlobStringReply>],\n [BlobStringReply<'passwords'>, ArrayReply<BlobStringReply>],\n [BlobStringReply<'commands'>, BlobStringReply],\n /** changed to BlobStringReply in 7.0 */\n [BlobStringReply<'keys'>, ArrayReply<BlobStringReply> | BlobStringReply],\n /** added in 6.2, changed to BlobStringReply in 7.0 */\n [BlobStringReply<'channels'>, ArrayReply<BlobStringReply> | BlobStringReply],\n /** added in 7.0 */\n [BlobStringReply<'selectors'>, ArrayReply<TuplesToMapReply<[\n [BlobStringReply<'commands'>, BlobStringReply],\n [BlobStringReply<'keys'>, BlobStringReply],\n [BlobStringReply<'channels'>, BlobStringReply]\n ]>>],\n]>;\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, username: RedisArgument) {\n parser.push('ACL', 'GETUSER', username);\n },\n transformReply: {\n 2: (reply: UnwrapReply<Resp2Reply<AclUser>>) => ({\n flags: reply[1],\n passwords: reply[3],\n commands: reply[5],\n keys: reply[7],\n channels: reply[9],\n selectors: (reply[11] as unknown as UnwrapReply<typeof reply[11]>)?.map(selector => {\n const inferred = selector as unknown as UnwrapReply<typeof selector>;\n return {\n commands: inferred[1],\n keys: inferred[3],\n channels: inferred[5]\n };\n })\n }),\n 3: undefined as unknown as () => AclUser\n }\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { ArrayReply, BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => ArrayReply<BlobStringReply>;
};
export default _default;
//# sourceMappingURL=ACL_LIST.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LIST.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_LIST.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAK9C,aAAa;mCAGY,WAAW,eAAe,CAAC;;AAN3E,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser) {
parser.push('ACL', 'LIST');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_LIST.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LIST.js","sourceRoot":"","sources":["../../../lib/commands/ACL_LIST.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,cAAc,EAAE,SAAyD;CAC/C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { ArrayReply, BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'LIST');\n },\n transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { SimpleStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => SimpleStringReply<'OK'>;
};
export default _default;
//# sourceMappingURL=ACL_LOAD.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOAD.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_LOAD.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAW,MAAM,eAAe,CAAC;;;;gDAKpC,aAAa;mCAGY,kBAAkB,IAAI,CAAC;;AANvE,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser) {
parser.push('ACL', 'LOAD');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_LOAD.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOAD.js","sourceRoot":"","sources":["../../../lib/commands/ACL_LOAD.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,cAAc,EAAE,SAAqD;CAC3C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { SimpleStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'LOAD');\n },\n transformReply: undefined as unknown as () => SimpleStringReply<'OK'>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,69 @@
import { CommandParser } from '../client/parser';
import { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, DoubleReply, UnwrapReply, Resp2Reply, TypeMapping } from '../RESP/types';
export type AclLogReply = ArrayReply<TuplesToMapReply<[
[
BlobStringReply<'count'>,
NumberReply
],
[
BlobStringReply<'reason'>,
BlobStringReply
],
[
BlobStringReply<'context'>,
BlobStringReply
],
[
BlobStringReply<'object'>,
BlobStringReply
],
[
BlobStringReply<'username'>,
BlobStringReply
],
[
BlobStringReply<'age-seconds'>,
DoubleReply
],
[
BlobStringReply<'client-info'>,
BlobStringReply
],
/** added in 7.0 */
[
BlobStringReply<'entry-id'>,
NumberReply
],
/** added in 7.0 */
[
BlobStringReply<'timestamp-created'>,
NumberReply
],
/** added in 7.0 */
[
BlobStringReply<'timestamp-last-updated'>,
NumberReply
]
]>>;
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, count?: number) => void;
readonly transformReply: {
readonly 2: (reply: UnwrapReply<Resp2Reply<AclLogReply>>, preserve?: unknown, typeMapping?: TypeMapping) => {
count: NumberReply<number>;
reason: BlobStringReply<string>;
context: BlobStringReply<string>;
object: BlobStringReply<string>;
username: BlobStringReply<string>;
'age-seconds': DoubleReply<number>;
'client-info': BlobStringReply<string>;
'entry-id': NumberReply<number>;
'timestamp-created': NumberReply<number>;
'timestamp-last-updated': NumberReply<number>;
}[];
readonly 3: () => AclLogReply;
};
};
export default _default;
//# sourceMappingURL=ACL_LOG.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOG.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_LOG.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAW,WAAW,EAAE,MAAM,eAAe,CAAC;AAGvJ,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,gBAAgB,CAAC;IACpD;QAAC,eAAe,CAAC,OAAO,CAAC;QAAE,WAAW;KAAC;IACvC;QAAC,eAAe,CAAC,QAAQ,CAAC;QAAE,eAAe;KAAC;IAC5C;QAAC,eAAe,CAAC,SAAS,CAAC;QAAE,eAAe;KAAC;IAC7C;QAAC,eAAe,CAAC,QAAQ,CAAC;QAAE,eAAe;KAAC;IAC5C;QAAC,eAAe,CAAC,UAAU,CAAC;QAAE,eAAe;KAAC;IAC9C;QAAC,eAAe,CAAC,aAAa,CAAC;QAAE,WAAW;KAAC;IAC7C;QAAC,eAAe,CAAC,aAAa,CAAC;QAAE,eAAe;KAAC;IACjD,mBAAmB;IACnB;QAAC,eAAe,CAAC,UAAU,CAAC;QAAE,WAAW;KAAC;IAC1C,mBAAmB;IACnB;QAAC,eAAe,CAAC,mBAAmB,CAAC;QAAE,WAAW;KAAC;IACnD,mBAAmB;IACnB;QAAC,eAAe,CAAC,wBAAwB,CAAC;QAAE,WAAW;KAAC;CACzD,CAAC,CAAC,CAAC;;;;gDAKmB,aAAa,UAAU,MAAM;;4BAOrC,YAAY,WAAW,WAAW,CAAC,CAAC,aAAa,OAAO,gBAAgB,WAAW;;;;;;;;;;;;;;;AAVlG,wBA6B6B"}
@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const generic_transformers_1 = require("./generic-transformers");
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, count) {
parser.push('ACL', 'LOG');
if (count != undefined) {
parser.push(count.toString());
}
},
transformReply: {
2: (reply, preserve, typeMapping) => {
return reply.map(item => {
const inferred = item;
return {
count: inferred[1],
reason: inferred[3],
context: inferred[5],
object: inferred[7],
username: inferred[9],
'age-seconds': generic_transformers_1.transformDoubleReply[2](inferred[11], preserve, typeMapping),
'client-info': inferred[13],
'entry-id': inferred[15],
'timestamp-created': inferred[17],
'timestamp-last-updated': inferred[19]
};
});
},
3: undefined
}
};
//# sourceMappingURL=ACL_LOG.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOG.js","sourceRoot":"","sources":["../../../lib/commands/ACL_LOG.ts"],"names":[],"mappings":";;AAEA,iEAA8D;AAkB9D,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,KAAc;QAChD,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1B,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,cAAc,EAAE;QACd,CAAC,EAAE,CAAC,KAA2C,EAAE,QAAkB,EAAE,WAAyB,EAAE,EAAE;YAChG,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;gBACtB,MAAM,QAAQ,GAAG,IAA2C,CAAC;gBAC7D,OAAO;oBACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;oBAClB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACnB,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACpB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACnB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACrB,aAAa,EAAE,2CAAoB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC;oBAC3E,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;oBAC3B,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;oBACxB,mBAAmB,EAAE,QAAQ,CAAC,EAAE,CAAC;oBACjC,wBAAwB,EAAE,QAAQ,CAAC,EAAE,CAAC;iBACvC,CAAC;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,CAAC,EAAE,SAAyC;KAC7C;CACyB,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, DoubleReply, UnwrapReply, Resp2Reply, Command, TypeMapping } from '../RESP/types';\nimport { transformDoubleReply } from './generic-transformers';\n\nexport type AclLogReply = ArrayReply<TuplesToMapReply<[\n [BlobStringReply<'count'>, NumberReply],\n [BlobStringReply<'reason'>, BlobStringReply],\n [BlobStringReply<'context'>, BlobStringReply],\n [BlobStringReply<'object'>, BlobStringReply],\n [BlobStringReply<'username'>, BlobStringReply],\n [BlobStringReply<'age-seconds'>, DoubleReply],\n [BlobStringReply<'client-info'>, BlobStringReply],\n /** added in 7.0 */\n [BlobStringReply<'entry-id'>, NumberReply],\n /** added in 7.0 */\n [BlobStringReply<'timestamp-created'>, NumberReply],\n /** added in 7.0 */\n [BlobStringReply<'timestamp-last-updated'>, NumberReply]\n]>>;\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, count?: number) {\n parser.push('ACL', 'LOG');\n if (count != undefined) {\n parser.push(count.toString());\n }\n },\n transformReply: {\n 2: (reply: UnwrapReply<Resp2Reply<AclLogReply>>, preserve?: unknown, typeMapping?: TypeMapping) => {\n return reply.map(item => {\n const inferred = item as unknown as UnwrapReply<typeof item>;\n return {\n count: inferred[1],\n reason: inferred[3],\n context: inferred[5],\n object: inferred[7],\n username: inferred[9],\n 'age-seconds': transformDoubleReply[2](inferred[11], preserve, typeMapping),\n 'client-info': inferred[13],\n 'entry-id': inferred[15],\n 'timestamp-created': inferred[17],\n 'timestamp-last-updated': inferred[19]\n };\n })\n },\n 3: undefined as unknown as () => AclLogReply\n }\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { SimpleStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => SimpleStringReply<'OK'>;
};
export default _default;
//# sourceMappingURL=ACL_LOG_RESET.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOG_RESET.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_LOG_RESET.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAW,MAAM,eAAe,CAAC;;;;gDAMpC,aAAa;mCAGY,kBAAkB,IAAI,CAAC;;AANvE,wBAO6B"}
@@ -0,0 +1,15 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ACL_LOG_1 = __importDefault(require("./ACL_LOG"));
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: ACL_LOG_1.default.IS_READ_ONLY,
parseCommand(parser) {
parser.push('ACL', 'LOG', 'RESET');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_LOG_RESET.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_LOG_RESET.js","sourceRoot":"","sources":["../../../lib/commands/ACL_LOG_RESET.ts"],"names":[],"mappings":";;;;;AAEA,wDAAgC;AAEhC,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,iBAAO,CAAC,YAAY;IAClC,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,cAAc,EAAE,SAAqD;CAC3C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { SimpleStringReply, Command } from '../RESP/types';\nimport ACL_LOG from './ACL_LOG';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: ACL_LOG.IS_READ_ONLY,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'LOG', 'RESET');\n },\n transformReply: undefined as unknown as () => SimpleStringReply<'OK'>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { SimpleStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => SimpleStringReply<'OK'>;
};
export default _default;
//# sourceMappingURL=ACL_SAVE.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_SAVE.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_SAVE.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAW,MAAM,eAAe,CAAC;;;;gDAKpC,aAAa;mCAGY,kBAAkB,IAAI,CAAC;;AANvE,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser) {
parser.push('ACL', 'SAVE');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_SAVE.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_SAVE.js","sourceRoot":"","sources":["../../../lib/commands/ACL_SAVE.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,cAAc,EAAE,SAAqD;CAC3C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { SimpleStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'SAVE');\n },\n transformReply: undefined as unknown as () => SimpleStringReply<'OK'>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,11 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, SimpleStringReply } from '../RESP/types';
import { RedisVariadicArgument } from './generic-transformers';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, username: RedisArgument, rule: RedisVariadicArgument) => void;
readonly transformReply: () => SimpleStringReply<'OK'>;
};
export default _default;
//# sourceMappingURL=ACL_SETUSER.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_SETUSER.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_SETUSER.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAW,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;;;;gDAKxC,aAAa,YAAY,aAAa,QAAQ,qBAAqB;mCAI1C,kBAAkB,IAAI,CAAC;;AAPvE,wBAQ6B"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser, username, rule) {
parser.push('ACL', 'SETUSER', username);
parser.pushVariadic(rule);
},
transformReply: undefined
};
//# sourceMappingURL=ACL_SETUSER.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_SETUSER.js","sourceRoot":"","sources":["../../../lib/commands/ACL_SETUSER.ts"],"names":[],"mappings":";;AAIA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,QAAuB,EAAE,IAA2B;QACtF,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,cAAc,EAAE,SAAqD;CAC3C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, SimpleStringReply, Command } from '../RESP/types';\nimport { RedisVariadicArgument } from './generic-transformers';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, username: RedisArgument, rule: RedisVariadicArgument) {\n parser.push('ACL', 'SETUSER', username);\n parser.pushVariadic(rule);\n },\n transformReply: undefined as unknown as () => SimpleStringReply<'OK'>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { ArrayReply, BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => ArrayReply<BlobStringReply>;
};
export default _default;
//# sourceMappingURL=ACL_USERS.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_USERS.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_USERS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAK9C,aAAa;mCAGY,WAAW,eAAe,CAAC;;AAN3E,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser) {
parser.push('ACL', 'USERS');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_USERS.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_USERS.js","sourceRoot":"","sources":["../../../lib/commands/ACL_USERS.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,cAAc,EAAE,SAAyD;CAC/C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { ArrayReply, BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'USERS');\n },\n transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,10 @@
import { CommandParser } from '../client/parser';
import { BlobStringReply } from '../RESP/types';
declare const _default: {
readonly NOT_KEYED_COMMAND: true;
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser) => void;
readonly transformReply: () => BlobStringReply;
};
export default _default;
//# sourceMappingURL=ACL_WHOAMI.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_WHOAMI.d.ts","sourceRoot":"","sources":["../../../lib/commands/ACL_WHOAMI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;;;;gDAKlC,aAAa;mCAGY,eAAe;;AAN/D,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(parser) {
parser.push('ACL', 'WHOAMI');
},
transformReply: undefined
};
//# sourceMappingURL=ACL_WHOAMI.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ACL_WHOAMI.js","sourceRoot":"","sources":["../../../lib/commands/ACL_WHOAMI.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB;QAChC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC/B,CAAC;IACD,cAAc,EAAE,SAA6C;CACnC,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { BlobStringReply, Command } from '../RESP/types';\n\nexport default {\n NOT_KEYED_COMMAND: true,\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser) {\n parser.push('ACL', 'WHOAMI');\n },\n transformReply: undefined as unknown as () => BlobStringReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, NumberReply } from '../RESP/types';
declare const _default: {
readonly IS_READ_ONLY: false;
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, value: RedisArgument) => void;
readonly transformReply: () => NumberReply;
};
export default _default;
//# sourceMappingURL=APPEND.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"APPEND.d.ts","sourceRoot":"","sources":["../../../lib/commands/APPEND.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;;;gDAI7C,aAAa,OAAO,aAAa,SAAS,aAAa;mCAM9B,WAAW;;AAR3D,wBAS6B"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
IS_READ_ONLY: false,
parseCommand(parser, key, value) {
parser.push('APPEND');
parser.pushKey(key);
parser.push(value);
},
transformReply: undefined
};
//# sourceMappingURL=APPEND.js.map
@@ -0,0 +1 @@
{"version":3,"file":"APPEND.js","sourceRoot":"","sources":["../../../lib/commands/APPEND.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,YAAY,EAAE,KAAK;IACnB,YAAY,CAAC,MAAqB,EAAE,GAAkB,EAAE,KAAoB;QAC1E,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,cAAc,EAAE,SAAyC;CAC/B,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, NumberReply, Command } from '../RESP/types';\n\nexport default {\n IS_READ_ONLY: false,\n parseCommand(parser: CommandParser, key: RedisArgument, value: RedisArgument) {\n parser.push('APPEND');\n parser.pushKey(key);\n parser.push(value);\n },\n\n transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, NumberReply } from '../RESP/types';
declare const _default: {
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument) => void;
readonly transformReply: () => NumberReply;
};
export default _default;
//# sourceMappingURL=ARCOUNT.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARCOUNT.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARCOUNT.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;;;gDAI7C,aAAa,OAAO,aAAa;mCAIR,WAAW;;AAN3D,wBAO6B"}
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
IS_READ_ONLY: true,
parseCommand(parser, key) {
parser.push('ARCOUNT');
parser.pushKey(key);
},
transformReply: undefined
};
//# sourceMappingURL=ARCOUNT.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARCOUNT.js","sourceRoot":"","sources":["../../../lib/commands/ARCOUNT.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,GAAkB;QACpD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,cAAc,EAAE,SAAyC;CAC/B,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, NumberReply, Command } from '../RESP/types';\n\nexport default {\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, key: RedisArgument) {\n parser.push('ARCOUNT');\n parser.pushKey(key);\n },\n transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, NumberReply } from '../RESP/types';
export type ArIndex = number | string;
declare const _default: {
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, indices: ArIndex | Array<ArIndex>) => void;
readonly transformReply: () => NumberReply;
};
export default _default;
//# sourceMappingURL=ARDEL.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARDEL.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARDEL.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;AAEpE,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;;gDAGf,aAAa,OAAO,aAAa,WAAW,OAAO,GAAG,MAAM,OAAO,CAAC;mCAS3C,WAAW;;AAV3D,wBAW6B"}
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
parseCommand(parser, key, indices) {
parser.push('ARDEL');
parser.pushKey(key);
if (Array.isArray(indices)) {
for (const i of indices)
parser.push(i.toString());
}
else {
parser.push(indices.toString());
}
},
transformReply: undefined
};
//# sourceMappingURL=ARDEL.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARDEL.js","sourceRoot":"","sources":["../../../lib/commands/ARDEL.ts"],"names":[],"mappings":";;AAKA,kBAAe;IACb,YAAY,CAAC,MAAqB,EAAE,GAAkB,EAAE,OAAiC;QACvF,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,IAAI,OAAO;gBAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,cAAc,EAAE,SAAyC;CAC/B,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, NumberReply, Command } from '../RESP/types';\n\nexport type ArIndex = number | string;\n\nexport default {\n parseCommand(parser: CommandParser, key: RedisArgument, indices: ArIndex | Array<ArIndex>) {\n parser.push('ARDEL');\n parser.pushKey(key);\n if (Array.isArray(indices)) {\n for (const i of indices) parser.push(i.toString());\n } else {\n parser.push(indices.toString());\n }\n },\n transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, NumberReply } from '../RESP/types';
export type ArDelRangeRange = [start: number | string, end: number | string];
declare const _default: {
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, ranges: Array<ArDelRangeRange>) => void;
readonly transformReply: () => NumberReply;
};
export default _default;
//# sourceMappingURL=ARDELRANGE.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARDELRANGE.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARDELRANGE.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;;gDAIjE,aAAa,OAChB,aAAa,UACV,MAAM,eAAe,CAAC;mCASc,WAAW;;AAb3D,wBAc6B"}
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
parseCommand(parser, key, ranges) {
parser.push('ARDELRANGE');
parser.pushKey(key);
for (const [start, end] of ranges) {
parser.push(start.toString(), end.toString());
}
},
transformReply: undefined
};
//# sourceMappingURL=ARDELRANGE.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARDELRANGE.js","sourceRoot":"","sources":["../../../lib/commands/ARDELRANGE.ts"],"names":[],"mappings":";;AAKA,kBAAe;IACb,YAAY,CACV,MAAqB,EACrB,GAAkB,EAClB,MAA8B;QAE9B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAEpB,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IACD,cAAc,EAAE,SAAyC;CAC/B,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, NumberReply, Command } from '../RESP/types';\n\nexport type ArDelRangeRange = [start: number | string, end: number | string];\n\nexport default {\n parseCommand(\n parser: CommandParser,\n key: RedisArgument,\n ranges: Array<ArDelRangeRange>\n ) {\n parser.push('ARDELRANGE');\n parser.pushKey(key);\n\n for (const [start, end] of ranges) {\n parser.push(start.toString(), end.toString());\n }\n },\n transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, BlobStringReply, NullReply } from '../RESP/types';
declare const _default: {
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, index: number | string) => void;
readonly transformReply: () => BlobStringReply | NullReply;
};
export default _default;
//# sourceMappingURL=ARGET.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGET.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARGET.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAW,MAAM,eAAe,CAAC;;;gDAI5D,aAAa,OAAO,aAAa,SAAS,MAAM,GAAG,MAAM;mCAKhC,eAAe,GAAG,SAAS;;AAP3E,wBAQ6B"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
IS_READ_ONLY: true,
parseCommand(parser, key, index) {
parser.push('ARGET');
parser.pushKey(key);
parser.push(index.toString());
},
transformReply: undefined
};
//# sourceMappingURL=ARGET.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGET.js","sourceRoot":"","sources":["../../../lib/commands/ARGET.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,GAAkB,EAAE,KAAsB;QAC5E,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,cAAc,EAAE,SAAyD;CAC/C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, key: RedisArgument, index: number | string) {\n parser.push('ARGET');\n parser.pushKey(key);\n parser.push(index.toString());\n },\n transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n"]}
@@ -0,0 +1,9 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, ArrayReply, BlobStringReply, NullReply } from '../RESP/types';
declare const _default: {
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, start: number | string, end: number | string) => void;
readonly transformReply: () => ArrayReply<BlobStringReply | NullReply>;
};
export default _default;
//# sourceMappingURL=ARGETRANGE.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGETRANGE.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARGETRANGE.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAW,MAAM,eAAe,CAAC;;;gDAIxE,aAAa,OAAO,aAAa,SAAS,MAAM,GAAG,MAAM,OAAO,MAAM,GAAG,MAAM;mCAKtD,WAAW,eAAe,GAAG,SAAS,CAAC;;AAPvF,wBAQ6B"}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
IS_READ_ONLY: true,
parseCommand(parser, key, start, end) {
parser.push('ARGETRANGE');
parser.pushKey(key);
parser.push(start.toString(), end.toString());
},
transformReply: undefined
};
//# sourceMappingURL=ARGETRANGE.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGETRANGE.js","sourceRoot":"","sources":["../../../lib/commands/ARGETRANGE.ts"],"names":[],"mappings":";;AAGA,kBAAe;IACb,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,GAAkB,EAAE,KAAsB,EAAE,GAAoB;QAClG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,cAAc,EAAE,SAAqE;CAC3D,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, ArrayReply, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, key: RedisArgument, start: number | string, end: number | string) {\n parser.push('ARGETRANGE');\n parser.pushKey(key);\n parser.push(start.toString(), end.toString());\n },\n transformReply: undefined as unknown as () => ArrayReply<BlobStringReply | NullReply>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,36 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, ArrayReply, NumberReply } from '../RESP/types';
import { Tail } from './generic-transformers';
export declare const AR_PREDICATE_TYPES: {
readonly EXACT: "EXACT";
readonly MATCH: "MATCH";
readonly GLOB: "GLOB";
readonly RE: "RE";
};
export type ArPredicateType = typeof AR_PREDICATE_TYPES[keyof typeof AR_PREDICATE_TYPES];
export declare const AR_PREDICATE_COMBINATORS: {
readonly AND: "AND";
readonly OR: "OR";
};
export type ArPredicateCombinator = typeof AR_PREDICATE_COMBINATORS[keyof typeof AR_PREDICATE_COMBINATORS];
export type ArGrepPredicate = [type: ArPredicateType, value: RedisArgument];
/**
* Bound for an ARGREP range: a numeric index, the literal `'-'` (open-ended
* lower bound), the literal `'+'` (open-ended upper bound), or a decimal
* string (use this when an index would exceed `Number.MAX_SAFE_INTEGER`).
*/
export type ArGrepBound = number | '-' | '+' | string;
export interface ArGrepOptions {
COMBINATOR?: ArPredicateCombinator;
LIMIT?: number;
NOCASE?: boolean;
}
export declare function parseArGrepArguments(parser: CommandParser, key: RedisArgument, start: ArGrepBound, end: ArGrepBound, predicates: Array<ArGrepPredicate>, options?: ArGrepOptions): void;
export type ArGrepArguments = Tail<Parameters<typeof parseArGrepArguments>>;
declare const _default: {
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, key: RedisArgument, start: ArGrepBound, end: ArGrepBound, predicates: ArGrepPredicate[], options?: ArGrepOptions | undefined) => void;
readonly transformReply: () => ArrayReply<NumberReply>;
};
export default _default;
//# sourceMappingURL=ARGREP.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGREP.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARGREP.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAW,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,OAAO,kBAAkB,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAEzF,eAAO,MAAM,wBAAwB;;;CAG3B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,OAAO,wBAAwB,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3G,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,EAClC,OAAO,CAAC,EAAE,aAAa,QAuBxB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC;;;gDAIrD,aAAa;mCAIY,WAAW,WAAW,CAAC;;AANvE,wBAO6B"}
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseArGrepArguments = exports.AR_PREDICATE_COMBINATORS = exports.AR_PREDICATE_TYPES = void 0;
exports.AR_PREDICATE_TYPES = {
EXACT: 'EXACT',
MATCH: 'MATCH',
GLOB: 'GLOB',
RE: 'RE'
};
exports.AR_PREDICATE_COMBINATORS = {
AND: 'AND',
OR: 'OR'
};
function parseArGrepArguments(parser, key, start, end, predicates, options) {
parser.pushKey(key);
parser.push(typeof start === 'number' ? start.toString() : start, typeof end === 'number' ? end.toString() : end);
for (const [type, value] of predicates) {
parser.push(type, value);
}
if (options?.COMBINATOR !== undefined) {
parser.push(options.COMBINATOR);
}
if (options?.LIMIT !== undefined) {
parser.push('LIMIT', options.LIMIT.toString());
}
if (options?.NOCASE) {
parser.push('NOCASE');
}
}
exports.parseArGrepArguments = parseArGrepArguments;
exports.default = {
IS_READ_ONLY: true,
parseCommand(parser, ...args) {
parser.push('ARGREP');
parseArGrepArguments(parser, ...args);
},
transformReply: undefined
};
//# sourceMappingURL=ARGREP.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGREP.js","sourceRoot":"","sources":["../../../lib/commands/ARGREP.ts"],"names":[],"mappings":";;;AAIa,QAAA,kBAAkB,GAAG;IAChC,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,EAAE,EAAE,IAAI;CACA,CAAC;AAIE,QAAA,wBAAwB,GAAG;IACtC,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,IAAI;CACA,CAAC;AAmBX,SAAgB,oBAAoB,CAClC,MAAqB,EACrB,GAAkB,EAClB,KAAkB,EAClB,GAAgB,EAChB,UAAkC,EAClC,OAAuB;IAEvB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpB,MAAM,CAAC,IAAI,CACT,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,EACpD,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAC/C,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,OAAO,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AA7BD,oDA6BC;AAID,kBAAe;IACb,YAAY,EAAE,IAAI;IAClB,YAAY,CAAC,MAAqB,EAAE,GAAG,IAAqB;QAC1D,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtB,oBAAoB,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,cAAc,EAAE,SAAqD;CAC3C,CAAC","sourcesContent":["import { CommandParser } from '../client/parser';\nimport { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types';\nimport { Tail } from './generic-transformers';\n\nexport const AR_PREDICATE_TYPES = {\n EXACT: 'EXACT',\n MATCH: 'MATCH',\n GLOB: 'GLOB',\n RE: 'RE'\n} as const;\n\nexport type ArPredicateType = typeof AR_PREDICATE_TYPES[keyof typeof AR_PREDICATE_TYPES];\n\nexport const AR_PREDICATE_COMBINATORS = {\n AND: 'AND',\n OR: 'OR'\n} as const;\n\nexport type ArPredicateCombinator = typeof AR_PREDICATE_COMBINATORS[keyof typeof AR_PREDICATE_COMBINATORS];\n\nexport type ArGrepPredicate = [type: ArPredicateType, value: RedisArgument];\n\n/**\n * Bound for an ARGREP range: a numeric index, the literal `'-'` (open-ended\n * lower bound), the literal `'+'` (open-ended upper bound), or a decimal\n * string (use this when an index would exceed `Number.MAX_SAFE_INTEGER`).\n */\nexport type ArGrepBound = number | '-' | '+' | string;\n\nexport interface ArGrepOptions {\n COMBINATOR?: ArPredicateCombinator;\n LIMIT?: number;\n NOCASE?: boolean;\n}\n\nexport function parseArGrepArguments(\n parser: CommandParser,\n key: RedisArgument,\n start: ArGrepBound,\n end: ArGrepBound,\n predicates: Array<ArGrepPredicate>,\n options?: ArGrepOptions\n) {\n parser.pushKey(key);\n parser.push(\n typeof start === 'number' ? start.toString() : start,\n typeof end === 'number' ? end.toString() : end\n );\n\n for (const [type, value] of predicates) {\n parser.push(type, value);\n }\n\n if (options?.COMBINATOR !== undefined) {\n parser.push(options.COMBINATOR);\n }\n\n if (options?.LIMIT !== undefined) {\n parser.push('LIMIT', options.LIMIT.toString());\n }\n\n if (options?.NOCASE) {\n parser.push('NOCASE');\n }\n}\n\nexport type ArGrepArguments = Tail<Parameters<typeof parseArGrepArguments>>;\n\nexport default {\n IS_READ_ONLY: true,\n parseCommand(parser: CommandParser, ...args: ArGrepArguments) {\n parser.push('ARGREP');\n parseArGrepArguments(parser, ...args);\n },\n transformReply: undefined as unknown as () => ArrayReply<NumberReply>\n} as const satisfies Command;\n"]}
@@ -0,0 +1,16 @@
import { CommandParser } from '../client/parser';
import { ArrayReply, TuplesReply, NumberReply, BlobStringReply } from '../RESP/types';
export type ArGrepWithValuesReply = Array<{
index: NumberReply;
value: BlobStringReply;
}>;
declare const _default: {
readonly IS_READ_ONLY: true;
readonly parseCommand: (this: void, parser: CommandParser, key: import("../RESP/types").RedisArgument, start: import("./ARGREP").ArGrepBound, end: import("./ARGREP").ArGrepBound, predicates: import("./ARGREP").ArGrepPredicate[], options?: import("./ARGREP").ArGrepOptions | undefined) => void;
readonly transformReply: (reply: ArrayReply<TuplesReply<[index: NumberReply, value: BlobStringReply]>>) => {
index: NumberReply<number>;
value: BlobStringReply<string>;
}[];
};
export default _default;
//# sourceMappingURL=ARGREP_WITHVALUES.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ARGREP_WITHVALUES.d.ts","sourceRoot":"","sources":["../../../lib/commands/ARGREP_WITHVALUES.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAwB,MAAM,eAAe,CAAC;AAG5G,MAAM,MAAM,qBAAqB,GAAG,KAAK,CAAC;IACxC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC,CAAC;;;gDAIoB,aAAa;qCAKV,WAAW,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC;;;;;AAP/F,wBAc6B"}
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ARGREP_1 = require("./ARGREP");
exports.default = {
IS_READ_ONLY: true,
parseCommand(parser, ...args) {
parser.push('ARGREP');
(0, ARGREP_1.parseArGrepArguments)(parser, ...args);
parser.push('WITHVALUES');
},
transformReply: (reply) => {
const unwrapped = reply;
return unwrapped.map(pair => {
const [index, value] = pair;
return { index, value };
});
}
};
//# sourceMappingURL=ARGREP_WITHVALUES.js.map

Some files were not shown because too many files have changed in this diff Show More