@valkey/client
Version:
The source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
251 lines (250 loc) • 8.93 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("../../errors");
const buffer_1 = require("./composers/buffer");
const string_1 = require("./composers/string");
// RESP2 specification
// https://redis.io/topics/protocol
var Types;
(function (Types) {
Types[Types["SIMPLE_STRING"] = 43] = "SIMPLE_STRING";
Types[Types["ERROR"] = 45] = "ERROR";
Types[Types["INTEGER"] = 58] = "INTEGER";
Types[Types["BULK_STRING"] = 36] = "BULK_STRING";
Types[Types["ARRAY"] = 42] = "ARRAY"; // *
})(Types || (Types = {}));
var ASCII;
(function (ASCII) {
ASCII[ASCII["CR"] = 13] = "CR";
ASCII[ASCII["ZERO"] = 48] = "ZERO";
ASCII[ASCII["MINUS"] = 45] = "MINUS";
})(ASCII || (ASCII = {}));
// Using TypeScript `private` and not the build-in `#` to avoid __classPrivateFieldGet and __classPrivateFieldSet
class RESP2Decoder {
constructor(options) {
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: options
});
Object.defineProperty(this, "cursor", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "type", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "bufferComposer", {
enumerable: true,
configurable: true,
writable: true,
value: new buffer_1.default()
});
Object.defineProperty(this, "stringComposer", {
enumerable: true,
configurable: true,
writable: true,
value: new string_1.default()
});
Object.defineProperty(this, "currentStringComposer", {
enumerable: true,
configurable: true,
writable: true,
value: this.stringComposer
});
Object.defineProperty(this, "integer", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "isNegativeInteger", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "bulkStringRemainingLength", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "arraysInProcess", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "initializeArray", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "arrayItemType", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
reset() {
this.cursor = 0;
this.type = undefined;
this.bufferComposer.reset();
this.stringComposer.reset();
this.currentStringComposer = this.stringComposer;
}
write(chunk) {
while (this.cursor < chunk.length) {
if (!this.type) {
this.currentStringComposer = this.options.returnStringsAsBuffers() ?
this.bufferComposer :
this.stringComposer;
this.type = chunk[this.cursor];
if (++this.cursor >= chunk.length)
break;
}
const reply = this.parseType(chunk, this.type);
if (reply === undefined)
break;
this.type = undefined;
this.options.onReply(reply);
}
this.cursor -= chunk.length;
}
parseType(chunk, type, arraysToKeep) {
switch (type) {
case Types.SIMPLE_STRING:
return this.parseSimpleString(chunk);
case Types.ERROR:
return this.parseError(chunk);
case Types.INTEGER:
return this.parseInteger(chunk);
case Types.BULK_STRING:
return this.parseBulkString(chunk);
case Types.ARRAY:
return this.parseArray(chunk, arraysToKeep);
}
}
compose(chunk, composer) {
for (let i = this.cursor; i < chunk.length; i++) {
if (chunk[i] === ASCII.CR) {
const reply = composer.end(chunk.subarray(this.cursor, i));
this.cursor = i + 2;
return reply;
}
}
const toWrite = chunk.subarray(this.cursor);
composer.write(toWrite);
this.cursor = chunk.length;
}
parseSimpleString(chunk) {
return this.compose(chunk, this.currentStringComposer);
}
parseError(chunk) {
const message = this.compose(chunk, this.stringComposer);
if (message !== undefined) {
return new errors_1.ErrorReply(message);
}
}
parseInteger(chunk) {
if (this.isNegativeInteger === undefined) {
this.isNegativeInteger = chunk[this.cursor] === ASCII.MINUS;
if (this.isNegativeInteger && ++this.cursor === chunk.length)
return;
}
do {
const byte = chunk[this.cursor];
if (byte === ASCII.CR) {
const integer = this.isNegativeInteger ? -this.integer : this.integer;
this.integer = 0;
this.isNegativeInteger = undefined;
this.cursor += 2;
return integer;
}
this.integer = this.integer * 10 + byte - ASCII.ZERO;
} while (++this.cursor < chunk.length);
}
parseBulkString(chunk) {
if (this.bulkStringRemainingLength === undefined) {
const length = this.parseInteger(chunk);
if (length === undefined)
return;
if (length === -1)
return null;
this.bulkStringRemainingLength = length;
if (this.cursor >= chunk.length)
return;
}
const end = this.cursor + this.bulkStringRemainingLength;
if (chunk.length >= end) {
const reply = this.currentStringComposer.end(chunk.subarray(this.cursor, end));
this.bulkStringRemainingLength = undefined;
this.cursor = end + 2;
return reply;
}
const toWrite = chunk.subarray(this.cursor);
this.currentStringComposer.write(toWrite);
this.bulkStringRemainingLength -= toWrite.length;
this.cursor = chunk.length;
}
parseArray(chunk, arraysToKeep = 0) {
if (this.initializeArray || this.arraysInProcess.length === arraysToKeep) {
const length = this.parseInteger(chunk);
if (length === undefined) {
this.initializeArray = true;
return undefined;
}
this.initializeArray = false;
this.arrayItemType = undefined;
if (length === -1) {
return this.returnArrayReply(null, arraysToKeep, chunk);
}
else if (length === 0) {
return this.returnArrayReply([], arraysToKeep, chunk);
}
this.arraysInProcess.push({
array: new Array(length),
pushCounter: 0
});
}
while (this.cursor < chunk.length) {
if (!this.arrayItemType) {
this.arrayItemType = chunk[this.cursor];
if (++this.cursor >= chunk.length)
break;
}
const item = this.parseType(chunk, this.arrayItemType, arraysToKeep + 1);
if (item === undefined)
break;
this.arrayItemType = undefined;
const reply = this.pushArrayItem(item, arraysToKeep);
if (reply !== undefined)
return reply;
}
}
returnArrayReply(reply, arraysToKeep, chunk) {
if (this.arraysInProcess.length <= arraysToKeep)
return reply;
return this.pushArrayItem(reply, arraysToKeep, chunk);
}
pushArrayItem(item, arraysToKeep, chunk) {
const to = this.arraysInProcess[this.arraysInProcess.length - 1];
to.array[to.pushCounter] = item;
if (++to.pushCounter === to.array.length) {
return this.returnArrayReply(this.arraysInProcess.pop().array, arraysToKeep, chunk);
}
else if (chunk && chunk.length > this.cursor) {
return this.parseArray(chunk, arraysToKeep);
}
}
}
exports.default = RESP2Decoder;