Files
skills/docs/foundation/flow/idempotency/src/store/redis-store.md
2026-05-29 08:20:56 +01:00

2.9 KiB

redis-store

Purpose

Provides a Redis-backed IdempotencyStoreAdapter<T> with automatic key expiration using Redis native TTL. By accepting a generic RedisClientAdapter interface rather than a concrete library, this module integrates with ioredis, redis, Upstash Redis, or any compatible client without coupling to a specific driver.

Features

  • JSON-serialised records stored as Redis string values
  • Automatic TTL set on every set() call based on record.expiresAt
  • Fallback TTL (defaultTtlSeconds) for records whose expiry is in the past
  • Configurable key prefix for namespace isolation (multi-tenant)
  • cleanup() scans expired records server-side for manual purging
  • Generic <T> type parameter for typed payloads

Usage: Basic Example

import { RedisIdempotencyStore } from "@suaveplan/idempotency/store";
import type { RedisClientAdapter } from "@suaveplan/idempotency/store";

declare const redisAdapter: RedisClientAdapter; // your ioredis wrapper

const store = new RedisIdempotencyStore({
  client: redisAdapter,
  prefix: "app:idempotency:",
});

Usage: Advanced Example

import { RedisIdempotencyStore } from "@suaveplan/idempotency/store";

const store = new RedisIdempotencyStore({
  client: redisAdapter,
  prefix: `tenant:${tenantId}:idempotency:`,
  defaultTtlSeconds: 3600, // 1 hour fallback
});

// Manual cleanup (most records expire via Redis TTL automatically)
const removed = await store.cleanup(Date.now());
console.log(`Manually removed ${removed} stale records`);

API Reference

new RedisIdempotencyStore<T>(config: RedisIdempotencyStoreConfig)

Option Type Default Description
client RedisClientAdapter required Redis client adapter
prefix string "idempotency:" Key namespace prefix
defaultTtlSeconds number 86400 Fallback TTL when expiresAt is past

get(key): Promise<IdempotencyRecord<T> | undefined>

Fetches and JSON-parses the record. Returns undefined if not found (key expired or never set).

set(key, record): Promise<void>

Serialises the record as JSON and stores it with a TTL computed from record.expiresAt - Date.now().

delete(key): Promise<boolean>

Calls DEL on the prefixed key; returns true if a key was removed.

cleanup(beforeTimestamp): Promise<number>

Scans all keys under the prefix, parses each, and deletes those with expiresAt < beforeTimestamp. Returns the count removed.

Implementation Notes

  • Redis TTL handles the common expiration case automatically; cleanup is only needed for records that were not expired by Redis (e.g. records with very long TTLs where early manual deletion is desired).
  • scan is used rather than KEYS to avoid blocking the Redis event loop in production.
  • JSON serialisation assumes records are fully serialisable; Date objects should be stored as timestamps (numbers).