Files
micro-service-api/node_modules/@redis/client/dist/lib/client/socket.js
2025-11-11 12:36:06 +07:00

303 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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");
class RedisSocket extends node_events_1.EventEmitter {
#initiator;
#connectTimeout;
#reconnectStrategy;
#socketFactory;
#socketTimeout;
#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;
}
constructor(initiator, options) {
super();
this.#initiator = initiator;
this.#connectTimeout = options?.connectTimeout ?? 5000;
this.#reconnectStrategy = this.#createReconnectStrategy(options);
this.#socketFactory = this.#createSocketFactory(options);
this.#socketTimeout = options?.socketTimeout;
}
#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) {
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
noDelay: options?.noDelay ?? true,
// https://nodejs.org/api/tls.html#tlsconnectoptions-callback "Any socket.connect() option not already listed"
// @types/node is... incorrect...
// @ts-expect-error
keepAlive: options?.keepAlive ?? true,
// https://nodejs.org/api/tls.html#tlsconnectoptions-callback "Any socket.connect() option not already listed"
// @types/node is... incorrect...
// @ts-expect-error
keepAliveInitialDelay: options?.keepAliveInitialDelay ?? 5000,
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 ?? 5000,
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;
this.emit('error', cause);
return cause;
}
else if (retryIn instanceof Error) {
this.#isOpen = false;
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 {
this.#socket = await this.#createSocket();
this.emit('connect');
try {
await this.#initiator();
}
catch (err) {
this.#socket.destroy();
this.#socket = undefined;
throw err;
}
this.#isReady = true;
this.#socketEpoch++;
this.emit('ready');
}
catch (err) {
const retryIn = this.#shouldReconnect(retries++, err);
if (typeof retryIn !== 'number') {
throw retryIn;
}
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);
}
else {
this.#socket?.setTimeout(this.#socketTimeout ?? 0);
}
}
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;
this.emit('error', err);
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)
return;
this.#socket.cork();
for (const args of iterable) {
for (const toWrite of args) {
this.#socket.write(toWrite);
}
if (this.#socket.writableNeedDrain)
break;
}
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() {
this.#isReady = false;
if (this.#socket) {
this.#socket.destroy();
this.#socket = undefined;
}
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