memoradb
Version:
A lightweight in-memory key-value store similar to Redis
154 lines (132 loc) • 4.08 kB
text/typescript
import net from "net";
const port: number = process.argv[2] ? parseInt(process.argv[2]) : 6379;
class MemoraDB {
private store: Map<string, string | number>;
private expirations: Map<string, number>;
constructor() {
this.store = new Map();
this.expirations = new Map();
}
set(
key: string,
value: string | number,
expiry: number | null = null
): string {
this.store.set(key, value);
if (expiry) {
this.setExpiry(key, expiry);
}
return "OK";
}
get(key: string): string | number | null {
return this.store.has(key) ? this.store.get(key)! : null;
}
del(key: string): string {
this.store.delete(key);
this.expirations.delete(key);
return "OK";
}
expire(key: string, seconds: number): number {
if (this.store.has(key)) {
this.setExpiry(key, seconds);
return 1;
}
return 0;
}
ttl(key: string): number {
if (!this.expirations.has(key)) return -1;
const expiryTime = this.expirations.get(key)!;
const remaining = Math.ceil((expiryTime - Date.now()) / 1000);
return remaining > 0 ? remaining : -2;
}
persist(key: string): number {
return this.expirations.delete(key) ? 1 : 0;
}
flushAll(): string {
this.store.clear();
this.expirations.clear();
return "OK";
}
incr(key: string): number {
if (!this.store.has(key) || isNaN(Number(this.store.get(key)))) {
this.store.set(key, 0);
}
this.store.set(key, Number(this.store.get(key)) + 1);
return Number(this.store.get(key));
}
decr(key: string): number {
if (!this.store.has(key) || isNaN(Number(this.store.get(key)))) {
this.store.set(key, 0);
}
this.store.set(key, Number(this.store.get(key)) - 1);
return Number(this.store.get(key));
}
private setExpiry(key: string, seconds: number): void {
const expiryTime = Date.now() + seconds * 1000;
this.expirations.set(key, expiryTime);
setTimeout(() => {
if (Date.now() >= expiryTime) {
this.del(key);
}
}, seconds * 1000);
}
}
const db = new MemoraDB();
const server = net.createServer((socket) => {
socket.write(`MemoraDB Server Connected\nMemoraDB:${port}> `);
let buffer = "";
socket.on("data", (data) => {
buffer += data.toString();
if (buffer.includes("\n")) {
const lines = buffer.split("\n");
buffer = lines.pop() || "";
lines.forEach((line) => {
const input = line.trim().split(/\s+/);
const command = input[0]?.toUpperCase();
let response = "Unknown command";
switch (command) {
case "SET":
response = db.set(
input[1],
input[2],
input[3] === "EX" ? parseInt(input[4]) : null
);
break;
case "GET":
response = db.get(input[1])?.toString() || "(nil)";
break;
case "DEL":
response = db.del(input[1]);
break;
case "EXPIRE":
response = db.expire(input[1], parseInt(input[2])).toString();
break;
case "TTL":
response = db.ttl(input[1]).toString();
break;
case "PERSIST":
response = db.persist(input[1]).toString();
break;
case "FLUSHALL":
response = db.flushAll();
break;
case "INCR":
response = db.incr(input[1]).toString();
break;
case "DECR":
response = db.decr(input[1]).toString();
break;
default:
response = "Unknown command";
}
socket.write(response + "\nMemoraDB:" + port + "> ");
});
}
});
socket.on("end", () => console.log("Client disconnected"));
});
server.listen(port, () => {
console.log(`MemoraDB Server listening on port ${port}`);
});
export default MemoraDB;