UNPKG

node-resque

Version:

an opinionated implementation of resque in node

176 lines (153 loc) 4.96 kB
import { EventEmitter } from "events"; import { Redis, Cluster } from "ioredis"; import * as fs from "fs"; import * as path from "path"; import { ConnectionOptions } from ".."; interface EventListeners { [key: string]: (...args: any[]) => void; } export class Connection extends EventEmitter { options: ConnectionOptions; private eventListeners: EventListeners; connected: boolean; redis: Redis | Cluster; constructor(options: ConnectionOptions = {}) { super(); options.pkg = options.pkg ?? "ioredis"; options.host = options.host ?? "127.0.0.1"; options.port = options.port ?? 6379; options.database = options.database ?? 0; options.namespace = options.namespace ?? "resque"; options.scanCount = options.scanCount ?? 10; options.options = options.options ?? {}; this.options = options; this.eventListeners = {}; this.connected = false; } async connect() { const connectionTestAndLoadLua = async () => { try { await this.redis.set(this.key("connection_test_key"), "ok"); const data = await this.redis.get(this.key("connection_test_key")); if (data !== "ok") { throw new Error("cannot read connection test key"); } this.connected = true; this.loadLua(); } catch (error) { this.connected = false; this.emit("error", error); } }; if (this.options.redis) { this.redis = this.options.redis; } else { const Pkg = require(this.options.pkg); if ( typeof Pkg.createClient === "function" && this.options.pkg !== "ioredis" ) { this.redis = Pkg.createClient( this.options.port, this.options.host, this.options.options, ); } else { this.options.options.db = this.options.database; this.redis = new Pkg( this.options.port, this.options.host, this.options.options, ); } } this.eventListeners.error = (error: Error) => { this.emit("error", error); }; this.eventListeners.end = () => { this.connected = false; }; Object.entries(this.eventListeners).forEach(([eventName, eventHandler]) => { this.redis.on(eventName, eventHandler); }); if (!this.options.redis && typeof this.redis.select === "function") { await this.redis.select(this.options.database); } await connectionTestAndLoadLua(); } loadLua() { // even though ioredis-mock can run LUA, cjson is not available if (this.options.pkg === "ioredis-mock") return; const luaDir = path.join(__dirname, "..", "..", "lua"); const files = fs.readdirSync(luaDir); for (const file of files) { const { name } = path.parse(file); const contents = fs.readFileSync(path.join(luaDir, file)).toString(); const lines = contents.split("\n"); // see https://github.com/actionhero/node-resque/issues/465 for why we split only on *nix line breaks const encodedMetadata = lines[0].replace(/^-- /, ""); const metadata = JSON.parse(encodedMetadata); this.redis.defineCommand(name, { numberOfKeys: metadata.numberOfKeys, lua: contents, }); } } async getKeys( match: string, count: number = null, keysAry: string[] = [], cursor = 0, ): Promise<string[]> { if (count === null || count === undefined) { count = this.options.scanCount || 10; } if (this.redis && typeof this.redis.scan === "function") { const [newCursor, matches] = await this.redis.scan( cursor, "MATCH", match, "COUNT", count, ); if (matches && matches.length > 0) { keysAry = keysAry.concat(matches); } if (newCursor === "0") return keysAry; return this.getKeys(match, count, keysAry, parseInt(newCursor)); } this.emit( "error", new Error( "You must establish a connection to redis before running the getKeys command.", ), ); } end() { Object.entries(this.eventListeners).forEach(([eventName, eventHandler]) => { this.redis.off(eventName, eventHandler); }); // Only disconnect if we established the redis connection on our own. if (!this.options.redis && this.connected) { if (typeof this.redis.disconnect === "function") { this.redis.disconnect(); } if (typeof this.redis.quit === "function") { this.redis.quit(); } } this.connected = false; } key(arg: any, arg2?: any, arg3?: any, arg4?: any): string { let args; args = arguments.length >= 1 ? [].slice.call(arguments, 0) : []; if (Array.isArray(this.options.namespace)) { args.unshift(...this.options.namespace); } else { args.unshift(this.options.namespace); } args = args.filter((e: any) => { return String(e).trim(); }); return args.join(":"); } }