Version: v0.5.0
In-memory data platform.
BMis starts as a minimal key-value engine and is evolving toward a networked database with persistence, messaging, and distributed capabilities.
v0.5.0 — TypeScript interactive CLI plus a TCP server over a shared in-memory key-value store, with unit tests for the database, parser, command executer, RESP encoder, and TCP server. On start, BMis listens on 127.0.0.1:6379. Clients send one plain-text command per line; replies are RESP. The local CLI remains available. The database layer uses an injectable Storage backend (defaults to in-memory). Values are stored with type metadata — strings and lists are supported and can be inspected with TYPE. Integer strings can be incremented or decremented with INCR and DECR. Lists support LPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN, LINDEX, LSET, LTRIM, RPOPLPUSH, and LPOS. Keys can expire via EXPIRE, SET ... EX, or remaining TTL can be queried with TTL; expired keys are removed lazily on GET, TTL, and TYPE, and SET clears expiration when overwriting a key.
| Command | Args | Description | Example |
|---|---|---|---|
SET |
key, value [EX seconds] |
Store a key-value pair, optionally with expiration | SET session active EX 60 → OK |
GET |
key | Retrieve a value by key | GET name → Mayur (or null if missing or expired) |
DEL |
key [key ...] | Remove one or more keys | DEL name city → 2 (count of keys deleted) |
EXISTS |
key [key ...] | Count how many keys exist | EXISTS name missing → 1 |
EXPIRE |
key, seconds | Set a key's time-to-live in seconds (0 expires immediately) |
EXPIRE session 60 → 1 (or 0 if key missing) |
TTL |
key | Get remaining TTL in seconds | TTL session → 60 (or -1 / -2; see below) |
TYPE |
key | Get the type of a key | TYPE name → string (or list / none) |
INCR |
key | Increment an integer string value by 1 | INCR counter → 11 (creates key as 1 if missing) |
DECR |
key | Decrement an integer string value by 1 | DECR counter → 9 (creates key as -1 if missing) |
LPUSH |
key, value [value ...] | Prepend one or more values to a list | LPUSH fruits apple banana → 2 |
RPUSH |
key, value [value ...] | Append one or more values to a list | RPUSH fruits apple banana → 2 |
LPOP |
key | Remove and return the first list element | LPOP fruits → apple (or null if missing) |
RPOP |
key | Remove and return the last list element | RPOP fruits → orange (or null if missing) |
LRANGE |
key, start, stop | Return a range of list elements (inclusive) | LRANGE fruits 0 -1 → full list |
LLEN |
key | Get the length of a list | LLEN fruits → 3 (or 0 if missing) |
LINDEX |
key, index | Get a list element by index | LINDEX fruits 0 → apple (or null if out of range) |
LSET |
key, index, value | Set a list element at index | LSET fruits 1 mango → OK |
LTRIM |
key, start, stop | Trim a list to the given inclusive range | LTRIM fruits 1 2 → OK |
RPOPLPUSH |
source, destination | Pop the last element of source and prepend it to destination |
RPOPLPUSH source dest → C (or null if source is missing) |
LPOS |
key, element [RANK rank] [COUNT count] |
Return the index of an element, or a list of indexes when COUNT is set |
LPOS users John → 1 (or null if missing) |
Commands are case-insensitive. For SET, everything after the key is the value (spaces allowed), unless EX seconds is appended to set expiration in the same command.
Errors:
- Unknown command →
ERR unknown command '<COMMAND>' - Wrong arity →
ERR wrong number of arguments for <COMMAND> command - Invalid
EXPIREseconds (non-integer or out of range) →ERR value is not an integer or out of range - Invalid
INCR/DECRvalue (non-integer) →ERR value is not an integer or out of range - Invalid
SET ... EXsyntax, or an unknownLPOSoption →ERR syntax error - Invalid
SET ... EXseconds →ERR invalid expire time in 'SET' command - Invalid
LPOSrank (non-integer or0) or non-integerCOUNT→ERR value is not an integer or out of range - Negative
LPOSCOUNT→ERR count should be > 0 - Wrong type for list operation →
WRONGTYPE Operation against a key holding the wrong kind of value - Missing list for
LSET→ERR no such key - Out-of-range index for
LSET→ERR index out of range
src/
├── index.ts # CLI + TCP server entry point
├── types.ts # Shared TypeScript types
├── server/
│ ├── tcp-server.ts # TCP server (default 127.0.0.1:6379)
│ └── protocol.ts # RESP encoder for TCP replies
├── database/
│ ├── database.ts # Typed value store with injectable Storage backend
│ └── storage.ts # Low-level Map-backed storage layer
└── commands/
├── command-executer.ts # Routes parsed input to command handlers
├── parser.ts # Parses CLI input into command and args
├── commands.ts # Command registry
├── set.ts # SET command
├── get.ts # GET command
├── del.ts # DEL command
├── exists.ts # EXISTS command
├── expire.ts # EXPIRE command
├── ttl.ts # TTL command
├── type.ts # TYPE command
├── incr.ts # INCR command
├── decr.ts # DECR command
├── lpush.ts # LPUSH command
├── rpush.ts # RPUSH command
├── lpop.ts # LPOP command
├── rpop.ts # RPOP command
├── lrange.ts # LRANGE command
├── llen.ts # LLEN command
├── lindex.ts # LINDEX command
├── lset.ts # LSET command
├── ltrim.ts # LTRIM command
├── rpoplpush.ts # RPOPLPUSH command
└── lpos.ts # LPOS command
tests/
├── database.test.ts # Database unit tests (node:test)
├── commands.test.ts # CommandExecuter / CLI command tests
├── parser.test.ts # CLI input parser tests
└── server/
├── protocol.test.ts # RESP encoder tests
└── tcp-server.test.ts # TCP server tests
- Node.js (LTS recommended)
- TypeScript (installed via
npm install; used as a dev dependency)
Tests use the built-in node:test runner against the compiled dist/ output.
npm install
npm run build
npm startnpm start runs the compiled entry point (node dist/src/index.js). It starts the TCP server on 127.0.0.1:6379 and the interactive CLI. npm test builds first, then runs tests from dist/.
npm testFor a full walkthrough of commands, TCP usage, responses, and errors, see USER_GUIDE.md.
Starts an interactive session (TCP is also listening):
BMIS TCP server listening on 127.0.0.1:6379
Welcome to BMis CLI
Type commands like: SET name Mayur
BMis> SET name Mayur
OK
BMis> GET name
Mayur
BMis> EXISTS name
1
BMis> DEL name
1
BMis> GET name
null
BMis> SET
ERR wrong number of arguments for SET command
Connect with a line-based client (for example nc 127.0.0.1 6379) and send the same commands, one per line. Replies use RESP (+OK, :n, bulk strings, arrays, $-1 for null, and - errors). Inbound RESP, such as from redis-cli, is not parsed yet.
Incremental versions toward:
- Multiple native data structures (strings and lists supported; hashes pending)
- Key expiration
- TCP networking and client-server communication (plain-text commands on
127.0.0.1:6379in place) - RESP-compatible protocol (reply encoding in place; command parsing still plain text)
- Persistence (injectable
Storagelayer in place) - Pub/Sub
- Replication and distributed capabilities
See BMis Mission — Redis-Inspired In-Memory Data Platform.md for the full mission and design principles, and BMis — Docker Runtime Standard.md for the intended Docker-first runtime model.
ISC