npm package: @gantryland/task-cache
Description: Task-function cache wrappers for @gantryland/task
Latest version: 0.5.0

# @gantryland/task-cache

Task-function cache wrappers with TTL, stale-while-revalidate, and in-flight dedupe.

## Installation

```bash
npm install @gantryland/task @gantryland/task-cache
```

## Quick Start

```typescript
import { Task } from "@gantryland/task";
import { MemoryCacheStore, cache } from "@gantryland/task-cache";

const store = new MemoryCacheStore();
const usersTask = new Task(() => fetch("/api/users").then((r) => r.json())).pipe(
  cache("users", store, { ttl: 60_000 }),
);

await usersTask.run();
await usersTask.run();
```

## Exports

| Export | Kind | What it does |
| --- | --- | --- |
| `MemoryCacheStore` | Class | Provides an in-memory `CacheStore` backed by `Map`. |
| `cache` | Wrapper factory | Returns a TTL cache wrapper with optional in-flight dedupe. |
| `staleWhileRevalidate` | Wrapper factory | Returns an SWR wrapper with stale window background refresh. |
| `CacheKey` | Type | Represents supported cache keys (`string | number | symbol`). |
| `CacheEntry` | Type | Represents cached value data with `updatedAt` timestamp. |
| `CacheStore` | Type | Represents the minimal cache store contract (`get`, `set`, `delete`). |
| `CacheOptions` | Type | Represents options for `cache` (`ttl?`, `dedupe?`). |
| `StaleWhileRevalidateOptions` | Type | Represents options for `staleWhileRevalidate` (`ttl`, `staleTtl?`, `dedupe?`). |

## API Reference

### `MemoryCacheStore`

Provides an in-memory `CacheStore` implementation backed by `Map`.

| Method | Signature | Description |
| --- | --- | --- |
| `get` | `<T>(key: CacheKey) => CacheEntry<T> \| undefined` | Returns cached entry for `key`, if present. |
| `set` | `<T>(key: CacheKey, entry: CacheEntry<T>) => void` | Stores or replaces entry for `key`. |
| `delete` | `(key: CacheKey) => void` | Removes entry for `key`. |

### `cache`

```typescript
cache<T, Args extends unknown[] = []>(
  key: CacheKey,
  store: CacheStore,
  options?: CacheOptions,
): TaskOperator<T, T, Args>
```

Returns a wrapper that serves fresh cache hits and resolves source on miss or stale entry.

### `staleWhileRevalidate`

```typescript
staleWhileRevalidate<T, Args extends unknown[] = []>(
  key: CacheKey,
  store: CacheStore,
  options: StaleWhileRevalidateOptions,
): TaskOperator<T, T, Args>
```

Returns a wrapper that can return stale values within `staleTtl` while refreshing in background.

### Types

```typescript
type CacheKey = string | number | symbol;

type CacheEntry<T> = {
  value: T;
  updatedAt: number;
};

type CacheStore = {
  get<T>(key: CacheKey): CacheEntry<T> | undefined;
  set<T>(key: CacheKey, entry: CacheEntry<T>): void;
  delete(key: CacheKey): void;
};

type CacheOptions = {
  ttl?: number;
  dedupe?: boolean;
};

type StaleWhileRevalidateOptions = CacheOptions & {
  ttl: number;
  staleTtl?: number;
};
```

## Practical Use Cases

### Example: TTL Cache for List Endpoints

```typescript
const store = new MemoryCacheStore();

const listUsers = new Task(() => fetch("/api/users").then((r) => r.json())).pipe(
  cache("users:list", store, { ttl: 30_000 }),
);
```

### Example: Stale-While-Revalidate for Dashboards

```typescript
const getStats = new Task(() => fetch("/api/stats").then((r) => r.json())).pipe(
  staleWhileRevalidate("stats", store, {
    ttl: 10_000,
    staleTtl: 50_000,
  }),
);
```

### Example: Disable Dedupe for Independent Refreshes

```typescript
const getFeed = new Task(() => fetch("/api/feed").then((r) => r.json())).pipe(
  cache("feed", store, { ttl: 5_000, dedupe: false }),
);
```

## Runtime Semantics

- `cache` returns cached value when entry is fresh; otherwise it executes and stores on success.
- `cache` validates `ttl` as a non-negative finite number when provided.
- `staleWhileRevalidate` validates `ttl` and `staleTtl` as non-negative finite numbers.
- Fresh SWR hit returns immediately without background work.
- Stale-window SWR hit returns stale value and triggers background revalidation.
- Background revalidation errors are ignored for the caller path.
- `dedupe` defaults to `true` so same key and in-flight requests share one promise.