Skip to content

Instantly share code, notes, and snippets.

@intellectronica
Last active December 4, 2025 19:23
Show Gist options
  • Select an option

  • Save intellectronica/6c33248314fd501797aae34cfec153ff to your computer and use it in GitHub Desktop.

Select an option

Save intellectronica/6c33248314fd501797aae34cfec153ff to your computer and use it in GitHub Desktop.
Upstash Redis Key-Value Store SKILL

Upstash Redis Key-Value Store SKILL

Get upstash-redis-kv.zip

Use this skill to read from and write to Upstash's Redis-compatible key-value store via REST API. Supports the full range of Redis data structures: strings, hashes, lists, sets, and sorted sets—ideal for caching, counters, leaderboards, queues, and persistent storage.

Works with Claude (by extracting it to ~/.claude/skills/) or with any other agent using Skillz.

Setup

  1. Create a free Redis database at console.upstash.com
  2. Copy your REST URL and token from the database details page
  3. Set environment variables:
    export UPSTASH_REDIS_REST_URL="https://your-db.upstash.io"
    export UPSTASH_REDIS_REST_TOKEN="your-token"

Usage

Store my API response in the KV store under the key "weather:london"
Store all the items from the result with their uid and description in upstash
Get me all the todo items in the kv store that have "foo" in their description
Cache the last response in Redis for 1h

Happy Stashing!

🫶 Eleanor (@intellectronica)


name description
upstash-redis-kv
Read and write to Upstash Redis-compatible key-value store via REST API. Use when there is a need to save or retrieve key-value data, use Redis features (caching, counters, lists, sets, hashes, sorted sets, etc.) for the current interaction, or when the user explicitly asks to use Upstash or Redis.

Upstash Redis Key-Value Store

Interact with Upstash's Redis-compatible key-value store using the REST interface.

Script Location

bun run scripts/upstash-client.ts <command> [args...]

IMPORTANT: Always run with bun run, not directly.

Configuration

Environment Variables

The script uses these environment variables by default:

  • UPSTASH_REDIS_REST_URL - The Upstash REST API URL
  • UPSTASH_REDIS_REST_TOKEN - The Upstash REST API token

Overriding Credentials

If the user provides credentials from another source (conversation context, a file, etc.), use the --url and --token flags to override environment variables:

bun run scripts/upstash-client.ts --url "https://..." --token "AX..." GET mykey

Priority: Command-line flags > Environment variables

Command Reference

String Commands

# Get/Set
GET <key>
SET <key> <value> [--ex seconds] [--px ms] [--nx] [--xx] [--keepttl] [--get]
SETNX <key> <value>                    # Set if not exists
SETEX <key> <seconds> <value>          # Set with expiration

# Multiple keys (key/value pairs)
MGET <key1> [key2...]
MSET <key1> <val1> [key2 val2...]
MSETNX <key1> <val1> [key2 val2...]    # Set all if none exist

# Counters
INCR <key>
INCRBY <key> <increment>
INCRBYFLOAT <key> <increment>
DECR <key>
DECRBY <key> <decrement>

# String manipulation
APPEND <key> <value>
STRLEN <key>
GETRANGE <key> <start> <end>
SETRANGE <key> <offset> <value>

Hash Commands

Hashes store field-value pairs. Pass fields and values as alternating arguments:

# Set hash fields (field/value pairs)
HSET <key> <field1> <val1> [field2 val2...]
HSETNX <key> <field> <value>           # Set field if not exists

# Get hash fields
HGET <key> <field>
HMGET <key> <field1> [field2...]
HGETALL <key>

# Hash operations
HDEL <key> <field1> [field2...]
HEXISTS <key> <field>
HKEYS <key>
HVALS <key>
HLEN <key>
HINCRBY <key> <field> <increment>
HINCRBYFLOAT <key> <field> <increment>
HSCAN <key> <cursor> [MATCH pattern] [COUNT count]

Examples:

# Store user data
bun run scripts/upstash-client.ts HSET user:1 name "John" email "john@example.com" age 30

# Get single field
bun run scripts/upstash-client.ts HGET user:1 name

# Get all fields
bun run scripts/upstash-client.ts HGETALL user:1

# Increment numeric field
bun run scripts/upstash-client.ts HINCRBY user:1 age 1

List Commands

Lists are ordered collections. Values are pushed/popped from left (head) or right (tail):

# Push elements
LPUSH <key> <val1> [val2...]           # Push to head
RPUSH <key> <val1> [val2...]           # Push to tail
LPUSHX <key> <val1> [val2...]          # Push if list exists
RPUSHX <key> <val1> [val2...]

# Pop elements
LPOP <key> [count]
RPOP <key> [count]

# Access elements
LRANGE <key> <start> <stop>            # Get range (0 = first, -1 = last)
LLEN <key>
LINDEX <key> <index>

# Modify
LSET <key> <index> <value>
LREM <key> <count> <value>             # Remove count occurrences
LTRIM <key> <start> <stop>             # Keep only range
LINSERT <key> <BEFORE|AFTER> <pivot> <value>
LPOS <key> <value>
LMOVE <src> <dst> <LEFT|RIGHT> <LEFT|RIGHT>

Examples:

# Create a task queue
bun run scripts/upstash-client.ts RPUSH tasks "task1" "task2" "task3"

# Get all tasks
bun run scripts/upstash-client.ts LRANGE tasks 0 -1

# Pop task from front (FIFO queue)
bun run scripts/upstash-client.ts LPOP tasks

# Pop task from back (LIFO stack)
bun run scripts/upstash-client.ts RPOP tasks

Set Commands

Sets store unique, unordered members:

# Add/remove members
SADD <key> <member1> [member2...]
SREM <key> <member1> [member2...]

# Query
SMEMBERS <key>
SISMEMBER <key> <member>
SMISMEMBER <key> <member1> [member2...]
SCARD <key>

# Random access
SPOP <key> [count]
SRANDMEMBER <key> [count]

# Set operations
SINTER <key1> [key2...]
SINTERSTORE <dest> <key1> [key2...]
SUNION <key1> [key2...]
SUNIONSTORE <dest> <key1> [key2...]
SDIFF <key1> [key2...]
SDIFFSTORE <dest> <key1> [key2...]
SMOVE <src> <dst> <member>
SSCAN <key> <cursor> [MATCH pattern] [COUNT count]

Examples:

# Add tags
bun run scripts/upstash-client.ts SADD article:1:tags "javascript" "redis" "nodejs"

# Check membership
bun run scripts/upstash-client.ts SISMEMBER article:1:tags "javascript"

# Get all members
bun run scripts/upstash-client.ts SMEMBERS article:1:tags

# Find common tags between articles
bun run scripts/upstash-client.ts SINTER article:1:tags article:2:tags

Sorted Set Commands

Sorted sets store members with scores for ranking:

# Add members (score/member pairs)
ZADD <key> <score1> <member1> [score2 member2...] [--nx] [--xx] [--gt] [--lt] [--ch]

# Remove
ZREM <key> <member1> [member2...]
ZREMRANGEBYRANK <key> <start> <stop>
ZREMRANGEBYSCORE <key> <min> <max>

# Scores and ranks
ZSCORE <key> <member>
ZMSCORE <key> <member1> [member2...]
ZRANK <key> <member>                   # Rank (low to high)
ZREVRANK <key> <member>                # Rank (high to low)
ZINCRBY <key> <increment> <member>

# Range queries
ZRANGE <key> <start> <stop> [--withscores] [--rev] [--byscore] [--bylex]
ZRANGEBYSCORE <key> <min> <max> [--withscores] [--limit off,count]
ZREVRANGE <key> <start> <stop> [--withscores]
ZREVRANGEBYSCORE <key> <max> <min> [--withscores] [--limit off,count]

# Counting
ZCARD <key>
ZCOUNT <key> <min> <max>

# Pop
ZPOPMIN <key> [count]
ZPOPMAX <key> [count]

# Set operations
ZINTERSTORE <dest> <numkeys> <key1> [key2...]
ZUNIONSTORE <dest> <numkeys> <key1> [key2...]
ZSCAN <key> <cursor> [MATCH pattern] [COUNT count]

Examples:

# Create leaderboard (score member pairs)
bun run scripts/upstash-client.ts ZADD leaderboard 1000 "player1" 1500 "player2" 1200 "player3"

# Get top 3 with scores (highest first)
bun run scripts/upstash-client.ts ZRANGE leaderboard 0 2 --rev --withscores

# Get player's rank
bun run scripts/upstash-client.ts ZREVRANK leaderboard "player2"

# Increment player's score
bun run scripts/upstash-client.ts ZINCRBY leaderboard 100 "player1"

# Get players with scores between 1000 and 1500
bun run scripts/upstash-client.ts ZRANGEBYSCORE leaderboard 1000 1500 --withscores

Key Commands

# Delete
DEL <key1> [key2...]
UNLINK <key1> [key2...]                # Async delete

# Existence/Type
EXISTS <key1> [key2...]
TYPE <key>

# Expiration
EXPIRE <key> <seconds>
EXPIREAT <key> <timestamp>
PEXPIRE <key> <milliseconds>
PEXPIREAT <key> <timestamp>
TTL <key>
PTTL <key>
PERSIST <key>                          # Remove expiration

# Rename
RENAME <key> <newkey>
RENAMENX <key> <newkey>

# Search
KEYS <pattern>                         # Use with caution in production
SCAN <cursor> [MATCH pattern] [COUNT count]

# Other
COPY <src> <dst>
DUMP <key>
TOUCH <key1> [key2...]
RANDOMKEY
OBJECT ENCODING|FREQ|IDLETIME|REFCOUNT <key>

Examples:

# Set key with 1 hour expiration
bun run scripts/upstash-client.ts SET session:abc "data"
bun run scripts/upstash-client.ts EXPIRE session:abc 3600

# Or in one command
bun run scripts/upstash-client.ts SET session:abc "data" --ex 3600

# Check TTL
bun run scripts/upstash-client.ts TTL session:abc

# Scan keys matching pattern
bun run scripts/upstash-client.ts SCAN 0 MATCH "user:*" COUNT 100

Server Commands

PING [message]
ECHO <message>
DBSIZE
TIME
INFO [section]
FLUSHDB                                # Delete all keys in current DB (DANGEROUS)
FLUSHALL                               # Delete all keys in all DBs (DANGEROUS)

Command Options

SET Options

--ex <seconds>     # Expire in seconds
--px <ms>          # Expire in milliseconds
--exat <ts>        # Expire at Unix timestamp (seconds)
--pxat <ts>        # Expire at Unix timestamp (ms)
--nx               # Only set if key does not exist
--xx               # Only set if key exists
--keepttl          # Retain existing TTL
--get              # Return old value

ZADD Options

--nx               # Only add new members
--xx               # Only update existing members
--gt               # Only update if new score > current
--lt               # Only update if new score < current
--ch               # Return number of changed elements

ZRANGE Options

--withscores       # Include scores in output
--byscore          # Range by score instead of rank
--bylex            # Range by lexicographical order
--rev              # Reverse order
--limit off,count  # Limit results (e.g., --limit 0,10)

Output Format

  • String values: Printed directly
  • null/nil: Prints (nil)
  • Objects/arrays: Pretty-printed as JSON

Confirmation Behaviour

Default: Ask for Confirmation

Before executing any destructive operation (write, modify, or delete), you MUST ask the user for confirmation. This includes:

Write operations:

  • SET, SETNX, SETEX, PSETEX, MSET, MSETNX
  • HSET, HSETNX
  • LPUSH, RPUSH, LPUSHX, RPUSHX, LSET, LINSERT
  • SADD
  • ZADD

Modify operations:

  • INCR, INCRBY, INCRBYFLOAT, DECR, DECRBY
  • APPEND, SETRANGE
  • HINCRBY, HINCRBYFLOAT
  • LREM, LTRIM, LMOVE
  • ZINCRBY

Delete operations:

  • DEL, UNLINK
  • HDEL
  • LPOP, RPOP
  • SREM, SPOP, SMOVE
  • ZREM, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZPOPMIN, ZPOPMAX
  • FLUSHDB, FLUSHALL (extra caution)

TTL/Rename operations:

  • EXPIRE, EXPIREAT, PEXPIRE, PEXPIREAT, PERSIST
  • RENAME, RENAMENX

Example confirmation prompt:

"I'm about to HSET user:1 with fields {name: "John", email: "john@example.com"}. Proceed?"

YOLO Mode: Skip Confirmation

If the user indicates they do not want to be asked for confirmation, respect this for all subsequent operations. Indicators include:

  • "YOLO mode"
  • "Don't ask for confirmation"
  • "You're free to make changes without asking"
  • "Just do it"
  • "No need to confirm"
  • "Auto-approve" or "auto-confirm"
  • Any similar phrasing indicating blanket approval

Once YOLO mode is activated, proceed with destructive operations without asking, but still inform the user what was done.

Example:

Set user:1 with {name: "John", email: "john@example.com"} - done.

Error Handling

If credentials are missing or invalid, the script will exit with an error message. Ensure the user has configured either:

  1. Environment variables (UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN)
  2. Or provides credentials via --url and --token flags

When to Use This Skill

  • User explicitly asks to store or retrieve data from Upstash/Redis
  • Need to persist data across conversations or sessions
  • Implementing caching for expensive operations
  • Maintaining counters, rate limits, or statistics
  • Storing user preferences or session data
  • Building leaderboards or rankings (sorted sets)
  • Managing queues or task lists (lists)
  • Tagging or categorisation (sets)
  • Storing structured objects (hashes)
  • Any scenario requiring fast key-value storage
#!/usr/bin/env bun
import { parseArgs } from "node:util";
import { Redis } from "@upstash/redis";
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
url: { type: "string" },
token: { type: "string" },
help: { type: "boolean", short: "h" },
// SET options
ex: { type: "string" },
px: { type: "string" },
exat: { type: "string" },
pxat: { type: "string" },
nx: { type: "boolean" },
xx: { type: "boolean" },
keepttl: { type: "boolean" },
get: { type: "boolean" },
// ZRANGE/ZADD options
withscores: { type: "boolean" },
byscore: { type: "boolean" },
bylex: { type: "boolean" },
rev: { type: "boolean" },
limit: { type: "string" },
// ZADD-specific options
gt: { type: "boolean" },
lt: { type: "boolean" },
ch: { type: "boolean" },
// JSON input for complex objects
json: { type: "boolean" },
},
allowPositionals: true,
});
function printHelp() {
console.log(`Usage: bun run upstash-client.ts [options] <command> [args...]
Connection Options:
--url <url> Upstash REST URL (or UPSTASH_REDIS_REST_URL env var)
--token <token> Upstash REST token (or UPSTASH_REDIS_REST_TOKEN env var)
-h, --help Show this help
SET Options:
--ex <seconds> Set expiration in seconds
--px <ms> Set expiration in milliseconds
--exat <ts> Set expiration as Unix timestamp (seconds)
--pxat <ts> Set expiration as Unix timestamp (milliseconds)
--nx Only set if key does not exist
--xx Only set if key already exists
--keepttl Retain existing TTL
--get Return old value
ZADD Options:
--nx Only add new elements
--xx Only update existing elements
--gt Only update when new score > current score
--lt Only update when new score < current score
--ch Return number of changed elements
ZRANGE Options:
--withscores Include scores in output
--byscore Interpret range as score range
--bylex Interpret range as lexicographical range
--rev Reverse order
--limit <off,count> Limit results (e.g., --limit 0,10)
General:
--json Parse next argument as JSON (for complex objects)
Commands:
STRING:
GET <key>
SET <key> <value> [--ex N] [--px N] [--nx] [--xx]
MGET <key1> [key2...]
MSET <key1> <val1> [key2 val2...]
INCR <key>
INCRBY <key> <increment>
DECR <key>
DECRBY <key> <decrement>
APPEND <key> <value>
STRLEN <key>
HASH:
HGET <key> <field>
HSET <key> <field1> <val1> [field2 val2...]
HMGET <key> <field1> [field2...]
HGETALL <key>
HDEL <key> <field1> [field2...]
HEXISTS <key> <field>
HKEYS <key>
HVALS <key>
HLEN <key>
HINCRBY <key> <field> <increment>
HSETNX <key> <field> <value>
LIST:
LPUSH <key> <val1> [val2...]
RPUSH <key> <val1> [val2...]
LPOP <key> [count]
RPOP <key> [count]
LRANGE <key> <start> <stop>
LLEN <key>
LINDEX <key> <index>
LSET <key> <index> <value>
LREM <key> <count> <value>
LTRIM <key> <start> <stop>
LINSERT <key> <BEFORE|AFTER> <pivot> <value>
SET:
SADD <key> <member1> [member2...]
SREM <key> <member1> [member2...]
SMEMBERS <key>
SISMEMBER <key> <member>
SCARD <key>
SPOP <key> [count]
SRANDMEMBER <key> [count]
SINTER <key1> [key2...]
SUNION <key1> [key2...]
SDIFF <key1> [key2...]
SORTED SET:
ZADD <key> <score1> <member1> [score2 member2...] [--nx] [--xx] [--gt] [--lt] [--ch]
ZREM <key> <member1> [member2...]
ZSCORE <key> <member>
ZRANK <key> <member>
ZREVRANK <key> <member>
ZRANGE <key> <start> <stop> [--withscores] [--byscore] [--bylex] [--rev]
ZRANGEBYSCORE <key> <min> <max> [--withscores] [--limit off,count]
ZCARD <key>
ZCOUNT <key> <min> <max>
ZINCRBY <key> <increment> <member>
ZPOPMIN <key> [count]
ZPOPMAX <key> [count]
KEY:
DEL <key1> [key2...]
EXISTS <key1> [key2...]
EXPIRE <key> <seconds>
EXPIREAT <key> <timestamp>
TTL <key>
PTTL <key>
PERSIST <key>
KEYS <pattern>
SCAN <cursor> [MATCH pattern] [COUNT count]
TYPE <key>
RENAME <key> <newkey>
RENAMENX <key> <newkey>
SERVER:
DBSIZE
FLUSHDB
PING
ECHO <message>
Examples:
# Basic string operations
bun run upstash-client.ts SET mykey "hello world"
bun run upstash-client.ts GET mykey
bun run upstash-client.ts SET session:123 "data" --ex 3600
# Hash operations (field/value pairs)
bun run upstash-client.ts HSET user:1 name "John" email "john@example.com" age 30
bun run upstash-client.ts HGET user:1 name
bun run upstash-client.ts HGETALL user:1
# List operations
bun run upstash-client.ts LPUSH tasks "task1" "task2" "task3"
bun run upstash-client.ts LRANGE tasks 0 -1
# Set operations
bun run upstash-client.ts SADD tags "javascript" "redis" "nodejs"
bun run upstash-client.ts SMEMBERS tags
# Sorted set operations (score member pairs)
bun run upstash-client.ts ZADD leaderboard 1000 "player1" 1500 "player2"
bun run upstash-client.ts ZRANGE leaderboard 0 -1 --withscores --rev
# Multiple keys
bun run upstash-client.ts MSET key1 "val1" key2 "val2" key3 "val3"
bun run upstash-client.ts MGET key1 key2 key3
# With explicit credentials
bun run upstash-client.ts --url https://... --token AX... GET mykey`);
}
if (values.help || positionals.length === 0) {
printHelp();
process.exit(values.help ? 0 : 1);
}
const url = values.url || process.env.UPSTASH_REDIS_REST_URL;
const token = values.token || process.env.UPSTASH_REDIS_REST_TOKEN;
if (!url || !token) {
console.error("Error: Missing credentials");
console.error("Provide --url and --token, or set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN");
process.exit(1);
}
const redis = new Redis({ url, token });
const [rawCmd, ...args] = positionals;
const cmd = rawCmd.toUpperCase();
// Helper to parse a value (try JSON, fall back to string/number)
function parseValue(val: string): any {
if (values.json) {
try {
return JSON.parse(val);
} catch {
return val;
}
}
// Try to parse as number
const num = Number(val);
if (!isNaN(num) && val.trim() !== "") {
return num;
}
// Try to parse as JSON object/array
if ((val.startsWith("{") && val.endsWith("}")) || (val.startsWith("[") && val.endsWith("]"))) {
try {
return JSON.parse(val);
} catch {
return val;
}
}
return val;
}
// Helper to convert pairs to object: [k1, v1, k2, v2] -> {k1: v1, k2: v2}
function pairsToObject(pairs: string[]): Record<string, any> {
const obj: Record<string, any> = {};
for (let i = 0; i < pairs.length; i += 2) {
if (i + 1 < pairs.length) {
obj[pairs[i]] = parseValue(pairs[i + 1]);
}
}
return obj;
}
// Helper to convert score/member pairs: [s1, m1, s2, m2] -> [{score: s1, member: m1}, ...]
function scoreMemberPairs(pairs: string[]): Array<{ score: number; member: string }> {
const result: Array<{ score: number; member: string }> = [];
for (let i = 0; i < pairs.length; i += 2) {
if (i + 1 < pairs.length) {
result.push({ score: Number(pairs[i]), member: pairs[i + 1] });
}
}
return result;
}
// Build SET options
function buildSetOptions(): Record<string, any> | undefined {
const opts: Record<string, any> = {};
if (values.ex) opts.ex = Number(values.ex);
if (values.px) opts.px = Number(values.px);
if (values.exat) opts.exat = Number(values.exat);
if (values.pxat) opts.pxat = Number(values.pxat);
if (values.nx) opts.nx = true;
if (values.xx) opts.xx = true;
if (values.keepttl) opts.keepttl = true;
if (values.get) opts.get = true;
return Object.keys(opts).length > 0 ? opts : undefined;
}
// Build ZADD options
function buildZaddOptions(): Record<string, any> | undefined {
const opts: Record<string, any> = {};
if (values.nx) opts.nx = true;
if (values.xx) opts.xx = true;
if (values.gt) opts.gt = true;
if (values.lt) opts.lt = true;
if (values.ch) opts.ch = true;
return Object.keys(opts).length > 0 ? opts : undefined;
}
// Build ZRANGE options
function buildZrangeOptions(): Record<string, any> | undefined {
const opts: Record<string, any> = {};
if (values.withscores) opts.withScores = true;
if (values.byscore) opts.byScore = true;
if (values.bylex) opts.byLex = true;
if (values.rev) opts.rev = true;
if (values.limit) {
const [offset, count] = values.limit.split(",").map(Number);
opts.offset = offset;
opts.count = count;
}
return Object.keys(opts).length > 0 ? opts : undefined;
}
// Parse SCAN arguments: SCAN cursor [MATCH pattern] [COUNT count]
function parseScanArgs(args: string[]): { cursor: number; options?: { match?: string; count?: number } } {
const cursor = Number(args[0]) || 0;
const options: { match?: string; count?: number } = {};
for (let i = 1; i < args.length; i++) {
const arg = args[i].toUpperCase();
if (arg === "MATCH" && i + 1 < args.length) {
options.match = args[++i];
} else if (arg === "COUNT" && i + 1 < args.length) {
options.count = Number(args[++i]);
}
}
return { cursor, options: Object.keys(options).length > 0 ? options : undefined };
}
async function execute(): Promise<any> {
// STRING COMMANDS
switch (cmd) {
case "GET":
return redis.get(args[0]);
case "SET": {
const opts = buildSetOptions();
return opts
? redis.set(args[0], parseValue(args[1]), opts)
: redis.set(args[0], parseValue(args[1]));
}
case "SETNX":
return redis.setnx(args[0], parseValue(args[1]));
case "SETEX":
return redis.setex(args[0], Number(args[1]), parseValue(args[2]));
case "PSETEX":
return redis.psetex(args[0], Number(args[1]), parseValue(args[2]));
case "MGET":
return redis.mget(...args);
case "MSET":
return redis.mset(pairsToObject(args));
case "MSETNX":
return redis.msetnx(pairsToObject(args));
case "INCR":
return redis.incr(args[0]);
case "INCRBY":
return redis.incrby(args[0], Number(args[1]));
case "INCRBYFLOAT":
return redis.incrbyfloat(args[0], Number(args[1]));
case "DECR":
return redis.decr(args[0]);
case "DECRBY":
return redis.decrby(args[0], Number(args[1]));
case "APPEND":
return redis.append(args[0], args[1]);
case "STRLEN":
return redis.strlen(args[0]);
case "GETRANGE":
return redis.getrange(args[0], Number(args[1]), Number(args[2]));
case "SETRANGE":
return redis.setrange(args[0], Number(args[1]), args[2]);
// HASH COMMANDS
case "HGET":
return redis.hget(args[0], args[1]);
case "HSET":
return redis.hset(args[0], pairsToObject(args.slice(1)));
case "HSETNX":
return redis.hsetnx(args[0], args[1], parseValue(args[2]));
case "HMSET":
return redis.hset(args[0], pairsToObject(args.slice(1)));
case "HMGET":
return redis.hmget(args[0], ...args.slice(1));
case "HGETALL":
return redis.hgetall(args[0]);
case "HDEL":
return redis.hdel(args[0], ...args.slice(1));
case "HEXISTS":
return redis.hexists(args[0], args[1]);
case "HKEYS":
return redis.hkeys(args[0]);
case "HVALS":
return redis.hvals(args[0]);
case "HLEN":
return redis.hlen(args[0]);
case "HINCRBY":
return redis.hincrby(args[0], args[1], Number(args[2]));
case "HINCRBYFLOAT":
return redis.hincrbyfloat(args[0], args[1], Number(args[2]));
case "HSCAN": {
const cursor = Number(args[1]) || 0;
const scanOpts: { match?: string; count?: number } = {};
for (let i = 2; i < args.length; i++) {
if (args[i].toUpperCase() === "MATCH" && i + 1 < args.length) scanOpts.match = args[++i];
if (args[i].toUpperCase() === "COUNT" && i + 1 < args.length) scanOpts.count = Number(args[++i]);
}
return redis.hscan(args[0], cursor, Object.keys(scanOpts).length > 0 ? scanOpts : undefined);
}
// LIST COMMANDS
case "LPUSH":
return redis.lpush(args[0], ...args.slice(1).map(parseValue));
case "RPUSH":
return redis.rpush(args[0], ...args.slice(1).map(parseValue));
case "LPUSHX":
return redis.lpushx(args[0], ...args.slice(1).map(parseValue));
case "RPUSHX":
return redis.rpushx(args[0], ...args.slice(1).map(parseValue));
case "LPOP":
return args.length > 1 ? redis.lpop(args[0], Number(args[1])) : redis.lpop(args[0]);
case "RPOP":
return args.length > 1 ? redis.rpop(args[0], Number(args[1])) : redis.rpop(args[0]);
case "LRANGE":
return redis.lrange(args[0], Number(args[1]), Number(args[2]));
case "LLEN":
return redis.llen(args[0]);
case "LINDEX":
return redis.lindex(args[0], Number(args[1]));
case "LSET":
return redis.lset(args[0], Number(args[1]), parseValue(args[2]));
case "LREM":
return redis.lrem(args[0], Number(args[1]), parseValue(args[2]));
case "LTRIM":
return redis.ltrim(args[0], Number(args[1]), Number(args[2]));
case "LINSERT":
return redis.linsert(
args[0],
args[1].toLowerCase() as "before" | "after",
parseValue(args[2]),
parseValue(args[3])
);
case "LPOS":
return redis.lpos(args[0], parseValue(args[1]));
case "LMOVE":
return redis.lmove(
args[0],
args[1],
args[2].toUpperCase() as "LEFT" | "RIGHT",
args[3].toUpperCase() as "LEFT" | "RIGHT"
);
// SET COMMANDS
case "SADD":
return redis.sadd(args[0], ...args.slice(1).map(parseValue));
case "SREM":
return redis.srem(args[0], ...args.slice(1).map(parseValue));
case "SMEMBERS":
return redis.smembers(args[0]);
case "SISMEMBER":
return redis.sismember(args[0], parseValue(args[1]));
case "SMISMEMBER":
return redis.smismember(args[0], args.slice(1).map(parseValue));
case "SCARD":
return redis.scard(args[0]);
case "SPOP":
return args.length > 1 ? redis.spop(args[0], Number(args[1])) : redis.spop(args[0]);
case "SRANDMEMBER":
return args.length > 1 ? redis.srandmember(args[0], Number(args[1])) : redis.srandmember(args[0]);
case "SINTER":
return redis.sinter(...args);
case "SINTERSTORE":
return redis.sinterstore(args[0], ...args.slice(1));
case "SUNION":
return redis.sunion(...args);
case "SUNIONSTORE":
return redis.sunionstore(args[0], ...args.slice(1));
case "SDIFF":
return redis.sdiff(...args);
case "SDIFFSTORE":
return redis.sdiffstore(args[0], ...args.slice(1));
case "SMOVE":
return redis.smove(args[0], args[1], parseValue(args[2]));
case "SSCAN": {
const cursor = Number(args[1]) || 0;
const scanOpts: { match?: string; count?: number } = {};
for (let i = 2; i < args.length; i++) {
if (args[i].toUpperCase() === "MATCH" && i + 1 < args.length) scanOpts.match = args[++i];
if (args[i].toUpperCase() === "COUNT" && i + 1 < args.length) scanOpts.count = Number(args[++i]);
}
return redis.sscan(args[0], cursor, Object.keys(scanOpts).length > 0 ? scanOpts : undefined);
}
// SORTED SET COMMANDS
case "ZADD": {
const zaddOpts = buildZaddOptions();
const members = scoreMemberPairs(args.slice(1));
return zaddOpts
? redis.zadd(args[0], zaddOpts, ...members)
: redis.zadd(args[0], ...members);
}
case "ZREM":
return redis.zrem(args[0], ...args.slice(1));
case "ZSCORE":
return redis.zscore(args[0], args[1]);
case "ZMSCORE":
return redis.zmscore(args[0], ...args.slice(1));
case "ZRANK":
return redis.zrank(args[0], args[1]);
case "ZREVRANK":
return redis.zrevrank(args[0], args[1]);
case "ZRANGE": {
const opts = buildZrangeOptions();
return opts
? redis.zrange(args[0], args[1], args[2], opts)
: redis.zrange(args[0], args[1], args[2]);
}
case "ZRANGEBYSCORE": {
const opts: { withScores?: boolean; offset?: number; count?: number } = {};
if (values.withscores) opts.withScores = true;
if (values.limit) {
const [offset, count] = values.limit.split(",").map(Number);
opts.offset = offset;
opts.count = count;
}
return Object.keys(opts).length > 0
? redis.zrange(args[0], args[1], args[2], { byScore: true, ...opts })
: redis.zrange(args[0], args[1], args[2], { byScore: true });
}
case "ZREVRANGE": {
const opts = buildZrangeOptions();
return redis.zrange(args[0], args[1], args[2], { rev: true, ...opts });
}
case "ZREVRANGEBYSCORE": {
const opts: { withScores?: boolean; offset?: number; count?: number } = {};
if (values.withscores) opts.withScores = true;
if (values.limit) {
const [offset, count] = values.limit.split(",").map(Number);
opts.offset = offset;
opts.count = count;
}
return redis.zrange(args[0], args[2], args[1], { byScore: true, rev: true, ...opts });
}
case "ZCARD":
return redis.zcard(args[0]);
case "ZCOUNT":
return redis.zcount(args[0], args[1], args[2]);
case "ZINCRBY":
return redis.zincrby(args[0], Number(args[1]), args[2]);
case "ZPOPMIN":
return args.length > 1 ? redis.zpopmin(args[0], Number(args[1])) : redis.zpopmin(args[0]);
case "ZPOPMAX":
return args.length > 1 ? redis.zpopmax(args[0], Number(args[1])) : redis.zpopmax(args[0]);
case "ZREMRANGEBYRANK":
return redis.zremrangebyrank(args[0], Number(args[1]), Number(args[2]));
case "ZREMRANGEBYSCORE":
return redis.zremrangebyscore(args[0], args[1], args[2]);
case "ZINTERSTORE":
return redis.zinterstore(args[0], Number(args[1]), ...args.slice(2));
case "ZUNIONSTORE":
return redis.zunionstore(args[0], Number(args[1]), ...args.slice(2));
case "ZSCAN": {
const cursor = Number(args[1]) || 0;
const scanOpts: { match?: string; count?: number } = {};
for (let i = 2; i < args.length; i++) {
if (args[i].toUpperCase() === "MATCH" && i + 1 < args.length) scanOpts.match = args[++i];
if (args[i].toUpperCase() === "COUNT" && i + 1 < args.length) scanOpts.count = Number(args[++i]);
}
return redis.zscan(args[0], cursor, Object.keys(scanOpts).length > 0 ? scanOpts : undefined);
}
// KEY COMMANDS
case "DEL":
return redis.del(...args);
case "UNLINK":
return redis.unlink(...args);
case "EXISTS":
return redis.exists(...args);
case "EXPIRE":
return redis.expire(args[0], Number(args[1]));
case "EXPIREAT":
return redis.expireat(args[0], Number(args[1]));
case "PEXPIRE":
return redis.pexpire(args[0], Number(args[1]));
case "PEXPIREAT":
return redis.pexpireat(args[0], Number(args[1]));
case "TTL":
return redis.ttl(args[0]);
case "PTTL":
return redis.pttl(args[0]);
case "PERSIST":
return redis.persist(args[0]);
case "KEYS":
return redis.keys(args[0]);
case "SCAN": {
const { cursor, options } = parseScanArgs(args);
return options ? redis.scan(cursor, options) : redis.scan(cursor);
}
case "TYPE":
return redis.type(args[0]);
case "RENAME":
return redis.rename(args[0], args[1]);
case "RENAMENX":
return redis.renamenx(args[0], args[1]);
case "COPY":
return redis.copy(args[0], args[1]);
case "DUMP":
return redis.dump(args[0]);
case "OBJECT": {
const subcmd = args[0].toUpperCase();
if (subcmd === "ENCODING") return redis.objectEncoding(args[1]);
if (subcmd === "FREQ") return redis.objectFreq(args[1]);
if (subcmd === "IDLETIME") return redis.objectIdletime(args[1]);
if (subcmd === "REFCOUNT") return redis.objectRefcount(args[1]);
throw new Error(`Unknown OBJECT subcommand: ${subcmd}`);
}
case "RANDOMKEY":
return redis.randomkey();
case "TOUCH":
return redis.touch(...args);
// SERVER COMMANDS
case "DBSIZE":
return redis.dbsize();
case "FLUSHDB":
return redis.flushdb();
case "FLUSHALL":
return redis.flushall();
case "PING":
return args.length > 0 ? redis.ping(args[0]) : redis.ping();
case "ECHO":
return redis.echo(args[0]);
case "TIME":
return redis.time();
case "INFO":
return redis.info(args[0]);
// JSON COMMANDS (if using Upstash JSON)
case "JSON.SET":
return (redis as any).json.set(args[0], args[1], parseValue(args[2]));
case "JSON.GET":
return (redis as any).json.get(args[0], args.length > 1 ? args[1] : "$");
case "JSON.DEL":
return (redis as any).json.del(args[0], args.length > 1 ? args[1] : "$");
default:
throw new Error(`Unknown or unsupported command: ${cmd}`);
}
}
try {
const result = await execute();
if (result === null || result === undefined) {
console.log("(nil)");
} else if (typeof result === "object") {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(result);
}
} catch (err) {
console.error(`Error: ${err instanceof Error ? err.message : err}`);
process.exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment