@litert/redis
Version:
A redis protocol implement for Node.js.
246 lines (169 loc) • 5.64 kB
text/typescript
/**
* Copyright 2025 Angus.Fenying <fenying@litert.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as A from './Common';
import * as E from './Errors';
import * as C from './Constants';
import PROTO_DELIMITER = C.PROTO_DELIMITER;
import PROTO_NULL = C.PROTO_NULL;
import PROTO_DELIMITER_VALUE = C.PROTO_DELIMITER_VALUE;
export class Encoder implements A.IEncoder {
protected _getStringEncodedLength(val: string | Buffer): number {
const l = Buffer.byteLength(val);
return 5 + l + l.toString().length;
}
protected _writeString(
target: Buffer,
val: string | Buffer,
pos: number
): number {
pos += target.write(
`$${Buffer.byteLength(val)}`,
pos
);
pos += PROTO_DELIMITER.copy(target, pos);
if (typeof val === 'string') {
pos += target.write(val, pos);
}
else {
pos += val.copy(target, pos);
}
pos += PROTO_DELIMITER.copy(target, pos);
return pos;
}
public encodeNull(): Buffer {
return PROTO_NULL.subarray();
}
public encodeCommand(cmd: string | Buffer, values?: Array<string | Buffer | number>): Buffer {
let length: number = 3;
if (values) {
for (let i = 0; i < values.length; i++) {
if (typeof values[i] === 'number') {
values[i] = values[i].toString();
}
}
length += (1 + values.length).toString().length;
for (const el of values) {
length += this._getStringEncodedLength(el as string);
}
}
else {
length += 1;
}
length += this._getStringEncodedLength(cmd);
const ret = Buffer.allocUnsafe(length);
let pos: number = 0;
pos += ret.write(
`*${values ? values.length + 1 : 1}`,
pos
);
pos += PROTO_DELIMITER.copy(ret, pos);
pos = this._writeString(
ret,
cmd,
pos
);
if (values) {
for (const el of values) {
pos = this._writeString(
ret,
el as string,
pos
);
}
}
return ret;
}
public encodeString(data: string | Buffer): Buffer {
const ret = Buffer.allocUnsafe(this._getStringEncodedLength(data));
this._writeString(ret, data, 0);
return ret;
}
public encodeMessage(data: string | Buffer): Buffer {
if (data.indexOf(PROTO_DELIMITER_VALUE) > -1) {
throw new E.E_PROTOCOL_ERROR({
'message': 'CRLF is forbidden in MESSAGE.'
});
}
const length = Buffer.byteLength(data);
const ret = Buffer.allocUnsafe(length + 3);
ret[0] = 43;
if (typeof data === 'string') {
ret.write(data, 1);
}
else {
data.copy(ret, 1);
}
PROTO_DELIMITER.copy(ret, length + 1);
return ret;
}
public encodeFailure(data: string | Buffer): Buffer {
if (data.indexOf(PROTO_DELIMITER_VALUE) > -1) {
throw new E.E_PROTOCOL_ERROR({
'message': 'CRLF is forbidden in MESSAGE.'
});
}
const length = Buffer.byteLength(data);
const ret = Buffer.allocUnsafe(length + 3);
ret[0] = 45;
if (typeof data === 'string') {
ret.write(data, 1);
}
else {
data.copy(ret, 1);
}
PROTO_DELIMITER.copy(ret, length + 1);
return ret;
}
public encodeInteger(val: number): Buffer {
const data = val.toString();
const len = Buffer.byteLength(data);
const ret = Buffer.allocUnsafe(3 + len);
ret[0] = 58;
ret.write(data, 1);
PROTO_DELIMITER.copy(ret, len + 1);
return ret;
}
public encodeList(data: A.ListItem[]): Buffer {
const ret: Buffer[] = [
Buffer.from(`*${data.length}`),
PROTO_DELIMITER
];
for (const item of data) {
switch (item[0]) {
case A.EDataType.FAILURE:
ret.push(this.encodeString(item[1]));
break;
case A.EDataType.MESSAGE:
ret.push(this.encodeMessage(item[1]));
break;
case A.EDataType.STRING:
ret.push(this.encodeString(item[1]));
break;
case A.EDataType.INTEGER:
ret.push(this.encodeInteger(item[1]));
break;
case A.EDataType.LIST:
ret.push(this.encodeList(item[1]));
break;
case A.EDataType.NULL:
ret.push(this.encodeNull());
break;
}
}
return Buffer.concat(ret);
}
}
export default Encoder;