first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
# `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
```ts
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
```ts
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).