Skip to content

askalf/redisflex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@askalf/redisflex

One Redis API. Two modes. Same call sites.

Switch between real Redis (ioredis) and in-process Redis (Map + EventEmitter) with one line of config. Production runs on real Redis; dev / standalone / "no-Docker mode" runs in-process. Same get/set, hashes, lists, sorted sets, pub/sub — drop the Redis server when you don't need it.

Also ships a BullMQ-shaped in-memory queue so you can drop the Redis dep entirely for queueing too.

npm install @askalf/redisflex

CI npm License

Why

Most apps use Redis for three things: cache, pub/sub, and queueing. Most dev environments don't want to spin up a Redis server for any of them. Most CI environments REALLY don't want it.

redisflex swaps a real Redis connection for an in-process implementation that speaks the same surface — get/set, hashes, lists, sorted sets, pub/sub, expiry, and a sliding-window-rate-limit-shaped Lua eval. Plus a tiny BullMQ-API-compatible queue if you use BullMQ for jobs.

Your call sites stay identical. Flip the mode in config and your app no longer needs Redis to run.

Use it

Direct

import { createRedisAdapter } from '@askalf/redisflex';

// Production — real Redis
const redis = createRedisAdapter({
  mode: 'ioredis',
  url: process.env.REDIS_URL!,
});

// Dev / standalone — in-process
const redis = createRedisAdapter({ mode: 'memory' });

await redis.set('user:1', 'alice');
await redis.publish('events', 'user.created');

From environment

import { createRedisAdapterFromEnv } from '@askalf/redisflex';

// REDISFLEX_MODE=memory     → in-process
// otherwise                 → ioredis at $REDIS_URL (or redis://localhost:6379)
const redis = createRedisAdapterFromEnv();

Custom env-var names:

const redis = createRedisAdapterFromEnv({
  modeEnvVar: 'MYAPP_MODE',
  urlEnvVar: 'MYAPP_REDIS',
  defaultUrl: null, // null = throw if URL missing, instead of defaulting
});

In-memory queue

import { InMemoryQueue, InMemoryWorker } from '@askalf/redisflex';

const queue = new InMemoryQueue('emails');
const worker = new InMemoryWorker(
  'emails',
  async (job) => {
    await sendEmail(job.data);
  },
  { queue, concurrency: 4 },
);

worker.on('completed', (job) => console.log('sent', job.id));
worker.on('failed', (job, err) => console.error('failed', job.id, err));

await queue.add('welcome', { to: 'alice@example.com' });
await queue.add('reminder', { to: 'bob@example.com' }, {
  delay: 60_000,
  attempts: 3,
  backoff: { delay: 5000 }, // exponential: 5s, 10s, 20s
});

The shape matches BullMQ's Queue / Worker so you can swap to real BullMQ later by changing imports.

What's covered

Family Operations
Key/Value get, set, setex, del, keys, exists
Hashes hset, hget, hgetall, hdel
Lists rpush, lrange
TTL expire, pexpire
Sorted Sets zadd, zcard, zrange, zremrangebyscore
Pub/Sub publish, subscribe, unsubscribe, psubscribe, on('message'/'pmessage')
Scripting eval (sliding-window-rate-limit shape recognized in memory mode)
Lifecycle duplicate, quit

That's enough surface for cache, pub/sub, BullMQ-style queues, sliding-window rate limits, and most idiomatic Redis usage. Streams, geo, cluster, MULTI/EXEC, bitmap ops aren't covered. Open an issue if you need one — most are mechanical to add.

Lua eval in memory mode

Memory mode recognizes the canonical sliding-window-rate-limit Lua script (ZADD + ZREMRANGEBYSCORE + ZCARD + EXPIRE, args layout [key, now, window-ms, member?]) and returns the post-add count. That's enough for typical rate-limiter use.

Anything else: returns 0 and prints a stderr warning once per process. Pass { silentEvalFallback: true } to suppress the warning. If you need a different Lua script handled natively, open an issue — they're ~5 lines each in memory-adapter.ts.

Pub/Sub: duplicate() semantics

Real Redis requires a separate connection for pub/sub vs commands. IoRedisAdapter.duplicate() calls Redis.prototype.duplicate() — a fresh TCP connection. MemoryRedisAdapter.duplicate() returns a new adapter that shares the underlying EventEmitter + state, so pub/sub fans out the same way real Redis does.

What it isn't

  • Not a cluster client. ioredis mode supports cluster URLs; memory mode is single-node by definition.
  • Not durable. Memory mode loses everything on process restart. Persistence is your application's problem (database run table, snapshot to disk, etc.).
  • Not a full Lua interpreter. See above.
  • Not a substitute for real Redis under load. The data structures are correct but not optimized for high throughput. Use real Redis in production.

License

MIT — see LICENSE.

Also by askalf

Project What it does
arnie Portable IT troubleshooting companion. Networking, AD, Windows Update, package managers, log triage, hardware checks.
brio Capability layer for AI workloads — semantic cache, cost tiering, policy. Sits in front of any Anthropic-compat endpoint.
browser-bridge Stealth headless Chromium in a container. CDP on 9222 — Playwright/Puppeteer/MCP-compatible.
claude-bridge Bridge Claude Code sessions to Discord.
dario Local LLM router. Use your Claude Max/Pro subscription as an API.
deepdive Local research agent. Plan → search → fetch → extract → synthesize. Cited answers.
git-providers Unified GitHub + GitLab + Bitbucket Cloud REST clients behind one GitProvider interface. Plus a 44-entry api-key-provider taxonomy.
hands Cross-platform computer-use agent. Mouse, keyboard, screen.
install-kit curl-pipe-bash template for self-hosted Docker apps.
pgflex One Postgres API. Two modes (real PG ↔ PGlite WASM).

About

One Redis API. Two modes. ioredis for production, in-process Map+EventEmitter for standalone / dev. Also ships a BullMQ-shaped in-memory queue so you can drop the Redis dep entirely when you don't need it.

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors