UNPKG

@joshbetz/memcached

Version:

Memcached client for modern Node JS

205 lines (204 loc) 7.99 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const net_1 = require("net"); const events_1 = require("events"); class Memcached extends events_1.EventEmitter { constructor(port, host, opts) { super(); this.isReady = false; this.opts = Object.assign({ prefix: '', socketTimeout: 100, }, opts); // setup client this.client = (0, net_1.createConnection)({ port, host }); this.client.once('connect', () => this.client.setTimeout(0)); this.client.once('ready', () => { this.isReady = true; }); this.client.setTimeout(this.opts.socketTimeout, () => { this.emit('error', new Error('Socket Timeout')); this.client.destroy(); }); // forward errors this.client.on('error', (error) => { this.emit('error', error); }); } ready() { return __awaiter(this, void 0, void 0, function* () { if (this.isReady) { return true; } return new Promise((resolve, reject) => { this.once('error', reject); this.client.once('ready', resolve); }); }); } command(cmd, key, args = []) { return __awaiter(this, void 0, void 0, function* () { cmd = cmd.toLowerCase(); if (key) { // keys cannot contain whitespace key = key.replace(/\s+/, '_'); if (this.opts.prefix) { key = this.opts.prefix + key; } if (key.length > 250) { throw new Error('Invalid key'); } args.unshift(key); } const command = `${cmd} ${args.join(' ')}\r\n`; return new Promise((resolve, reject) => { const isError = (data) => { const errors = [ // error strings https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L156 data.indexOf('ERROR\r\n'), data.indexOf('CLIENT_ERROR'), data.indexOf('SERVER_ERROR'), ]; return errors.some(token => token >= 0); }; const onSimpleMessage = (data) => { if (isError(data)) { return reject(data); } return resolve(data.toString()); }; let buffer = ''; const onBufferedMessage = (data) => { if (isError(data)) { this.client.off('data', onBufferedMessage); return reject(buffer); } buffer += data; if (data.indexOf('END\r\n') < 0) { // Keep looking for terminating tokens return; } this.client.off('data', onBufferedMessage); return resolve(buffer); }; switch (cmd) { case 'get': case 'gets': case 'gat': case 'gats': case 'mg': case 'stat': this.client.on('data', onBufferedMessage); break; default: this.client.once('data', onSimpleMessage); break; } this.client.write(command); setTimeout(() => { this.client.removeAllListeners(); reject(new Error('Command Timeout')); }, Math.max(this.opts.socketTimeout, 1000)).unref(); }); }); } flush() { return __awaiter(this, void 0, void 0, function* () { return this.command('flush_all'); }); } store(command, key, value, ttl = 0) { return __awaiter(this, void 0, void 0, function* () { if (ttl > 60 * 60 * 24 * 30) { // Memcached considers ttls over 30 days to be // Unix timestamps. This is confusing and usually // leads to bugs. Just error in this case. throw new Error('Invalid TTL'); } // Cast value to a string so we can take the length value = value.toString(); const message = yield this.command(command, key, ['0', `${ttl}`, `${value.length}\r\n${value}`]); if (message.indexOf('STORED') !== 0) { return false; } return true; }); } set(key, value, ttl = 0) { return __awaiter(this, void 0, void 0, function* () { return this.store('set', key, value, ttl); }); } add(key, value, ttl = 0) { return __awaiter(this, void 0, void 0, function* () { return this.store('add', key, value, ttl); }); } get(key) { return __awaiter(this, void 0, void 0, function* () { const message = yield this.command('get', key); if (message === 'END\r\n') { return false; } // start after the \r\n const start = message.indexOf('\r\n') + 2; const end = message.indexOf('\r\nEND\r\n'); return message.substring(start, end); }); } del(key) { return __awaiter(this, void 0, void 0, function* () { const message = yield this.command('delete', key); if (message.indexOf('DELETED') !== 0) { return false; } return true; }); } incr(key, value = 1) { return __awaiter(this, void 0, void 0, function* () { const message = yield this.command('incr', key, [`${value}`]); if (message === 'NOT_FOUND\r\n') { return false; } const end = message.indexOf('\r\n'); return Number.parseInt(message.substring(0, end), 10); }); } decr(key, value = 1) { return __awaiter(this, void 0, void 0, function* () { const message = yield this.command('decr', key, [`${value}`]); if (message === 'NOT_FOUND\r\n') { return false; } const end = message.indexOf('\r\n'); return Number.parseInt(message.substring(0, end), 10); }); } ping() { return __awaiter(this, void 0, void 0, function* () { const message = yield this.command('version'); return message.indexOf('VERSION') === 0; }); } end() { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => { const timeout = setTimeout(() => this.client.destroy(), this.opts.socketTimeout); this.client.once('close', () => { clearTimeout(timeout); resolve(); }); this.client.end(); }); }); } } exports.default = Memcached;