mastercache
Version:
Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers
170 lines (143 loc) • 4.71 kB
text/typescript
import type { TransportEncoder } from '@boringnode/bus/types/main';
import { CacheBusMessageType } from '../../types/bus';
import type { CacheBusMessage } from '../../types/bus';
/**
* A Binary Encoder that encodes and decodes CacheBusMessage
*
* The encoding is as follows:
* - The bus ID is encoded as a UTF8 string and directly appended to the resulting buffer.
* Note that the length of the bus ID should be specified in the constructor.
*
* - The message type is encoded as a single byte, with 0x01 for 'Set' message, and 0x02 for a 'Delete' message
*
* - The keys are encoded as follows:
* - A 4-byte big-endian integer representing the length of the key in bytes
* - The key itself
*
* - These components are concatenated together in the order busId -> type -> keys
*
*/
export class BinaryEncoder implements TransportEncoder {
#busIdLength: number;
/**
* We assume the bus ID is a string of length 24 by default.
* Because this is the default length of a cuid
*/
constructor(busIdLength = 24) {
this.#busIdLength = busIdLength;
}
protected busMessageTypeToNum(type: CacheBusMessageType): number {
if (type === CacheBusMessageType.Set) return 0x01;
if (type === CacheBusMessageType.Clear) return 0x02;
return 0x03;
}
protected numToBusMessageType(num: number): CacheBusMessageType {
if (num === 0x01) return CacheBusMessageType.Set;
if (num === 0x02) return CacheBusMessageType.Clear;
return CacheBusMessageType.Delete;
}
/**
* Encode the given message into a Buffer
*/
encode(message: any): string | Buffer {
const payload = message.payload as Omit<CacheBusMessage, 'busId'>;
/**
* Compute the total size needed for storing the keys
*/
const totalKeysLength = payload.keys.reduce(
(sum, key) => sum + 4 + Buffer.byteLength(key, 'utf8'),
0,
);
const namespaceKeyLength = payload.namespace ? Buffer.byteLength(payload.namespace, 'utf8') : 0;
const totalLength = this.#busIdLength + 1 + 4 + namespaceKeyLength + totalKeysLength;
/**
* Allocate a single buffer for the entire message
*/
const buffer = Buffer.alloc(totalLength);
/**
* 1. write the bus ID
*/
buffer.write(message.busId, 0, this.#busIdLength, 'utf8');
/**
* 2. write the message type. 0x01 for 'Set' message, and 0x02 for a 'Delete' message
*/
buffer.writeUInt8(this.busMessageTypeToNum(payload.type), this.#busIdLength);
/**
* 3. Write the namespace
*/
let offset = this.#busIdLength + 1;
/**
* Write the length of the namespace key
*/
buffer.writeUInt32BE(namespaceKeyLength, offset);
offset += 4;
/**
* Write the namespace itself, if not empty
*/
if (payload.namespace) {
buffer.write(payload.namespace, offset, namespaceKeyLength, 'utf8');
offset += namespaceKeyLength;
}
/**
* 4. Write the keys
*/
for (const key of payload.keys) {
/**
* Compute the length of the key in bytes and write it as a 4-byte big-endian integer
*/
const keyLength = Buffer.byteLength(key, 'utf8');
buffer.writeUInt32BE(keyLength, offset);
offset += 4;
/**
* Write the key itself
*/
buffer.write(key, offset, keyLength, 'utf8');
offset += keyLength;
}
return buffer;
}
/**
* Decode the given Buffer into a CacheBusMessage
*/
decode(data: string | Buffer): any {
let offset = 0;
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'binary');
/**
* First #busIdLength bytes are the bus ID
*/
const busId = buffer.toString('utf8', offset, this.#busIdLength);
offset += this.#busIdLength;
/**
* Then comes the message type as a single byte
*/
const typeValue = buffer.readUInt8(offset++);
const type = this.numToBusMessageType(typeValue);
/**
* Then the namespace
*/
const namespaceKeyLength = buffer.readUInt32BE(offset);
offset += 4;
const namespace = namespaceKeyLength
? buffer.toString('utf8', offset, offset + namespaceKeyLength)
: '';
offset += namespaceKeyLength;
/**
* Finally, the keys
*/
const keys = [];
while (offset < buffer.length) {
/**
* First 4 bytes are the length of the key in bytes
*/
const keyLength = buffer.readUInt32BE(offset);
offset += 4;
/**
* Then comes the key itself
*/
const key = buffer.toString('utf8', offset, offset + keyLength);
offset += keyLength;
keys.push(key);
}
return { busId, payload: { keys, type, namespace } };
}
}