UNPKG

beanstalkd-protocol

Version:
228 lines (177 loc) 5.88 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertArgs = convertArgs; var _misc = require('./misc'); var _default = require('./default'); var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class BeanstalkdProtocol { constructor() { this.reset(); } reset() { this.types = Object.assign({}, _default.types); this.commandMap = {}; this.replyMap = {}; _default.commands.forEach(signature => { this.addCommand(signature); }); _default.replies.forEach(signature => { this.addReply(signature); }); } addType(key, type) { this.types[key] = type; } add(signature, key) { if (signature.substr(-2) !== _misc.CRLF.toString()) { throw new Error(`${key} ${signature} does not end in CRLF`); } let parts = signature.split(_misc.CRLF.toString()).slice(0, -1).map(part => { return part.split(_misc.SPACE.toString()); }); let identifier = parts[0].shift(); parts = parts.map(part => { return part.map(arg => { arg = arg.substr(1, arg.length - 2); if (!this.types[arg]) throw new Error(`arg ${arg} does not have a type defined`); return arg; }); }); let args = parts.reduce((args, part) => args.concat(part), []); let existing = this[key + 'Map'][identifier]; if (!existing) { this[key + 'Map'][identifier] = { args: args, parts, argsOptional: false }; } else if (existing.args.length !== args.length) { existing.argsOptional = true; if (!existing.args.length && args.length) { existing.args = args; } } } parse(buffer, key) { if (buffer.length < 4) return [buffer, null]; let boundary = buffer.indexOf(_misc.CRLF); if (boundary === -1) return [buffer, null]; let specMap = this[key + 'Map']; if (buffer.indexOf(' ') === -1) { let identifier = buffer.slice(0, boundary).toString(); if (specMap[identifier]) { return [null, { [key]: identifier, args: {} }]; } } let parts = [buffer.slice(0, boundary).toString().split(_misc.SPACE.toString())]; let identifier = parts[0].shift(); let spec = specMap[identifier]; let remainder = buffer.length > boundary + _misc.CRLF.length ? buffer.slice(boundary + _misc.CRLF.length) : null; let args = parts[0]; if (!spec) { return [remainder, { [key]: identifier, args: {}, unknown: true }]; } if (spec.parts.length > 1) { for (let i = 1; i < spec.parts.length; i++) { if (!remainder) return [buffer, null]; let previous = i && spec.parts[i - 1] || null; let bytes = previous[previous.length - 1] === 'bytes' && parseInt(args[args.length - 1], 10) || null; if (bytes > remainder.length) return [buffer, null]; let boundary = bytes || remainder.indexOf(_misc.CRLF); if (boundary === -1) return [buffer, null]; // TODO: Support non buffer args let part = remainder.slice(0, boundary); remainder = remainder.length > boundary + _misc.CRLF.length ? remainder.slice(remainder + _misc.CRLF.length) : null; args.push(part); } } return [remainder, { [key]: identifier, args: convertArgs(spec, args, this.types) }]; } build(identifier, args, key) { let spec = this[key + 'Map'][identifier], isArray = args && Array.isArray(args), expectsArgsLength = spec.args.length, argsOptional = spec.argsOptional, argsLength = args && (isArray ? args.length : Object.keys(args).length); if (!isArray && args) { args = spec.args.map(key => args[key]); } (0, _assert2.default)(argsOptional || !expectsArgsLength || args || args.length, `${identifier} requires args`); (0, _assert2.default)(argsOptional || !args || !args.length || expectsArgsLength === argsLength, `${identifier} expects ${spec.args.length} args`); if (!args || !argsLength) { return Buffer.from(identifier + _misc.CRLF); } if (spec.parts.length < 2) { return Buffer.from(identifier + ' ' + args.join(' ') + _misc.CRLF); } let buffers = [Buffer.from(identifier + ' ')]; let offset = 0; spec.parts.forEach(function (part) { let partArgs = args.slice(offset, offset + part.length); offset += part.length; partArgs.forEach(function (arg, i) { if (i) { buffers.push(_misc.SPACE); } if (Buffer.isBuffer(arg)) { buffers.push(arg); } else { buffers.push(Buffer.from(arg.toString())); } }); buffers.push(_misc.CRLF); }); return Buffer.concat(buffers); } addCommand(signature) { this.add(signature, 'command'); } parseCommand(buffer) { return this.parse(buffer, 'command'); } buildCommand(command, args) { return this.build(command, args, 'command'); } buildPut(args) { let isArray = Array.isArray(args); if (!isArray) { let spec = this.commandMap['put']; args = spec.args.map(key => args[key]); } let body = args.pop(); args.push(body.length); args.push(body); return this.buildCommand('put', args); } addReply(signature) { this.add(signature, 'reply'); } parseReply(buffer) { return this.parse(buffer, 'reply'); } buildReply(reply, args) { return this.build(reply, args, 'reply'); } } exports.default = BeanstalkdProtocol; function convertArgs(spec, args, types) { return (0, _misc.reduce)(args, function (args, arg, i) { let key = spec.args[i]; args[key] = types[key](arg); return args; }, {}); }