npm package: @periodic/vanadium
Description: Deterministic idempotency and distributed lock engine for backend systems
Author: Uday Thakur
Latest version: 1.0.0

# ⬡ Periodic Vanadium

[![npm version](https://img.shields.io/npm/v/@periodic/vanadium.svg)](https://www.npmjs.com/package/@periodic/vanadium)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)

**Production-grade, deterministic idempotency and distributed lock engine for Node.js with TypeScript support**

Part of the **Periodic** series of Node.js packages by Uday Thakur.

---

## 💡 Why Vanadium?

**Vanadium** gets its name from the chemical element renowned for its role as a stabilizing agent — added to steel to prevent structural failure under repeated stress. Just like vanadium strengthens metal against fatigue, this library **strengthens your backend against the failures that come from calling the same operation more than once**.

In chemistry, vanadium is a redox workhorse — capable of holding multiple oxidation states, switching between them reliably and reversibly. Similarly, **@periodic/vanadium** manages execution state transitions with the same precision: from `IN_PROGRESS` to `COMPLETED`, from `COMPLETED` back to a cached result, from a crashed execution to a safe takeover.

The name represents:
- **Stability**: Guarantees single-execution semantics under any retry pressure
- **Resilience**: Survives crashes, restarts, and concurrent callers without corruption
- **Precision**: Deterministic state transitions with no ambiguity at the boundaries
- **Clarity**: Explains *why* an execution was skipped, not just *that* it was

Just as vanadium is the hidden ingredient that makes critical infrastructure hold together, **@periodic/vanadium** is the execution primitive that makes your critical operations safe to call more than once.

---

## 🎯 Why Choose Vanadium?

In distributed systems, the same operation can be triggered multiple times — and most backends have no defense against it:

- **Network retries** silently re-submit requests that already succeeded
- **Message queues** deliver events at-least-once, never exactly-once
- **Webhook providers** re-send on timeout, no matter what happened the first time
- **UI double-submit** fires before the first response arrives
- **Cron overlap** starts two workers on the same job simultaneously
- **Crash recovery** replays operations that were mid-execution when a process died

Without idempotency primitives, each of these scenarios produces duplicate charges, duplicate emails, duplicate records, or corrupted state. **The bug is invisible until it hits production.**

**Periodic Vanadium** provides the perfect solution:

✅ **Zero dependencies** — Pure TypeScript core, adapters are opt-in  
✅ **Framework-agnostic** — Works with Express, Fastify, or no framework at all  
✅ **Idempotency Engine** — Guarantees a function executes exactly once per key  
✅ **Distributed Lock Engine** — Mutual exclusion across processes and machines  
✅ **6 Storage Adapters** — Memory, Redis, PostgreSQL, MongoDB, Mongoose, Prisma  
✅ **Circuit Breaker** — Protects against storage failures cascading into outages  
✅ **HTTP Middleware** — Drop-in Express and Fastify support  
✅ **Crash Recovery** — Safely retakes expired IN_PROGRESS records  
✅ **Payload Hashing** — Detects mismatched retries with the wrong parameters  
✅ **Lifecycle Hooks** — Observable without mutating state  
✅ **OpenTelemetry** — Built-in OTEL metrics exporter  
✅ **Type-safe** — Strict TypeScript from the ground up  
✅ **No global state** — No side effects on import  
✅ **Production-ready** — Non-blocking, never crashes your app

---

## 📦 Installation

```bash
npm install @periodic/vanadium
```

Or with yarn:

```bash
yarn add @periodic/vanadium
```

**Optional peer dependencies** (install only what you need):

```bash
# Storage adapters
npm install redis              # For Redis
npm install pg                 # For PostgreSQL
npm install mongodb            # For MongoDB
npm install mongoose           # For Mongoose
npm install @prisma/client     # For Prisma

# Exporters
npm install @opentelemetry/api # For OpenTelemetry
```

---

## 🚀 Quick Start

```typescript
import { createIdempotency, createMemoryAdapter } from '@periodic/vanadium';

// 1. Create an idempotency engine
const idempotency = createIdempotency({
  adapter: createMemoryAdapter(),
  ttlMs: 86_400_000, // cache completed results for 24 hours
});

// 2. Wrap any critical operation
const result = await idempotency.execute('payment:order_123', async () => {
  return chargeCard({ amount: 100 });
});

// 3. Call it again with the same key — fn never runs a second time
const same = await idempotency.execute('payment:order_123', async () => {
  return chargeCard({ amount: 100 }); // skipped — returns cached result
});
```

**Example event output:**

```json
{
  "key": "payment:order_123",
  "status": "COMPLETED",
  "result": { "chargeId": "ch_abc123", "status": "succeeded" },
  "attempts": 1,
  "createdAt": 1708000000000,
  "updatedAt": 1708000000312,
  "expiresAt": 1708086400000
}
```

---

## 🧠 Core Concepts

### The `createIdempotency` Function

- **`createIdempotency` is the primary factory function**
- Returns a configured idempotency engine instance
- Accepts a storage adapter and flexible configuration options
- **This is the main entry point for idempotent execution**
- No global state, safe for multi-tenant apps

**Typical usage:**
- Application code creates an engine with `createIdempotency()`
- Critical operations are wrapped with `idempotency.execute(key, fn)`
- Duplicate calls with the same key return the cached result immediately
- Lifecycle hooks and metrics give full observability into every execution

```typescript
const idempotency = createIdempotency({
  adapter: createRedisAdapter({ client }),
  ttlMs: 86_400_000,
  inProgressExpiryMs: 300_000,
  hashPayload: true,
  hooks: {
    onDuplicateHit: (ctx) => logger.info('duplicate deflected', ctx),
    onTakeover: (ctx) => logger.warn('crash recovery takeover', ctx),
  },
});
```

### The `createLock` Function

- **`createLock` is the factory for distributed mutual exclusion**
- Guarantees only one caller executes a block at a time, across processes
- Locks auto-expire after `ttlMs` — no permanent deadlocks
- Safe release is enforced via owner tokens — non-owners cannot unlock

```typescript
const lock = createLock({
  adapter: createRedisAdapter({ client }),
  ttlMs: 10_000,
  maxWaitMs: 5_000,
});

await lock.acquire('inventory:prod_001', async () => {
  await updateInventory('prod_001'); // only one caller at a time
});
```

### Execution Lifecycle

**Design principle:**
> Same key → same result, always. The function runs once, the result lives forever (until TTL). Everything else is just a cache hit.

```
First call       → Write IN_PROGRESS → Execute fn → Write COMPLETED → Return result
Duplicate call   → Find COMPLETED → Return cached result (fn never called)
Concurrent call  → Find IN_PROGRESS (not expired) → Throw VanadiumError(IN_PROGRESS)
Crash recovery   → Find IN_PROGRESS (expired) → Atomic takeover → Re-execute fn
```

---

## ✨ Features

### 🔁 Idempotency Engine

Guarantee a function executes exactly once per key, no matter how many times it's called:

```typescript
const idempotency = createIdempotency({
  adapter: createMemoryAdapter(),
  ttlMs: 86_400_000,
  inProgressExpiryMs: 300_000, // allow crash takeover after 5 minutes
  hashPayload: true,           // detect mismatched retries
  cacheFailures: false,        // re-execute on failure by default
});

const result = await idempotency.execute(
  'payment:order_123',
  async () => chargeCard(),
  { amount: 100, currency: 'USD' }, // payload hash — mismatched retry = error
);
```

### 🔒 Distributed Lock Engine

Mutual exclusion across processes — safe under 100+ simultaneous callers:

```typescript
const lock = createLock({
  adapter: createRedisAdapter({ client }),
  ttlMs: 10_000,       // auto-expire after 10s (deadlock protection)
  maxWaitMs: 5_000,    // wait up to 5s before failing
  retryIntervalMs: 50, // check every 50ms while waiting
});

const result = await lock.acquire('inventory:prod_001', async () => {
  return updateInventory('prod_001');
});
```

### 🗄️ Storage Adapters

Six adapters, one interface — behavior is identical across all backends:

```typescript
// In-memory (zero dependencies — dev, test, single-process)
createMemoryAdapter({ maxKeys: 50_000 })

// Redis (recommended for production)
createRedisAdapter({ client, keyPrefix: 'vanadium:', useLua: true })

// PostgreSQL
createPostgresAdapter({ client: pool, tableName: 'vanadium_records' })

// MongoDB
createMongoAdapter({ client, dbName: 'myapp', useTransactions: true })

// Mongoose
createMongooseAdapter({ model: VanadiumRecord, useTransactions: true })

// Prisma
createPrismaAdapter({ prisma, modelName: 'vanadiumRecord' })
```

### 🛡️ Circuit Breaker

Protect your app from storage failures cascading into full outages:

```typescript
import { createCircuitBreaker } from '@periodic/vanadium';

const protectedAdapter = createCircuitBreaker(redisAdapter, {
  failureThreshold: 5,   // open after 5 consecutive failures
  resetTimeoutMs: 30_000, // probe again after 30s
});

const idempotency = createIdempotency({ adapter: protectedAdapter, ttlMs: 60_000 });
```

**States:** `CLOSED` (normal) → `OPEN` (all calls fail immediately) → `HALF_OPEN` (one probe) → `CLOSED`

### 🌐 HTTP Middleware

Drop-in idempotency for Express and Fastify routes:

```typescript
// Express
app.post('/payments', vanadiumMiddleware(idempotency), async (req, res) => {
  const result = await processPayment(req.body);
  res.json(result);
});

// Fastify
await app.register(vanadiumFastifyPlugin, { idempotency });
```

**Client usage:**
```http
POST /payments HTTP/1.1
Idempotency-Key: payment-attempt-uuid-here
Content-Type: application/json
```

First request executes the handler and caches the response. Every duplicate request with the same key gets the cached response — handler never runs again.

### 🪝 Lifecycle Hooks

Hook into execution events for observability without mutating state:

```typescript
const idempotency = createIdempotency({
  adapter,
  ttlMs: 60_000,
  hooks: {
    onBeforeExecute: async (ctx) => logger.info('executing', { key: ctx.key }),
    onAfterExecute: async (ctx) => logger.info('completed', { key: ctx.key, durationMs: ctx.durationMs }),
    onDuplicateHit: async (ctx) => logger.info('duplicate deflected', { key: ctx.key }),
    onTakeover: async (ctx) => logger.warn('crash recovery', { key: ctx.key, attempts: ctx.attempts }),
    onStorageError: async (err, key) => Sentry.captureException(err, { extra: { key } }),
  },
});
```

### 📊 Metrics

Per-instance metrics, never global:

```typescript
const metrics = idempotency.getMetrics();
// {
//   totalExecutions: 42,
//   totalDuplicates: 15,
//   totalTakeovers: 2,
//   totalStorageErrors: 0,
//   inProgressCount: 0,
//   totalPayloadMismatches: 1,
//   totalFailuresCached: 0,
// }

idempotency.resetMetrics();
```

### 📡 OpenTelemetry

Built-in OTEL metrics without hard-requiring the SDK:

```typescript
import { createOtelExporter } from '@periodic/vanadium';

const idempotency = createIdempotency({
  adapter,
  ttlMs: 60_000,
  hooks: createVanadiumMetrics(metrics.getMeter('my-service')),
});
```

---

## 📚 Common Patterns

### 1. Payment Processing

```typescript
app.post('/payments', vanadiumMiddleware(idempotency), async (req, res) => {
  const charge = await idempotency.execute(
    `payment:${req.headers['idempotency-key']}`,
    async () => stripe.charges.create(req.body),
    req.body, // hash payload — catches mismatched retries
  );
  res.json({ chargeId: charge.id, status: charge.status });
});
```

### 2. Webhook Deduplication

```typescript
app.post('/webhooks/stripe', async (req, res) => {
  await idempotency.execute(`stripe:${req.body.id}`, async () => {
    const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], secret);
    switch (event.type) {
      case 'payment_intent.succeeded': await fulfillOrder(event.data.object); break;
      case 'customer.subscription.deleted': await cancelSubscription(event.data.object); break;
    }
  });
  res.sendStatus(200);
});
```

### 3. Distributed Cron Lock

```typescript
async function runDailyReport(): Promise<void> {
  await lock.acquire('cron:daily-report', async () => {
    const today = new Date().toISOString().split('T')[0];
    await idempotency.execute(`report:${today}`, async () => {
      await generateAndSendDailyReport();
    });
  });
}
```

### 4. Crash-Safe Job Runner

```typescript
const jobRunner = createIdempotency({
  adapter: redisAdapter,
  ttlMs: 7 * 24 * 60 * 60 * 1000,     // cache results for 7 days
  inProgressExpiryMs: 10 * 60 * 1000,  // allow takeover after 10 minutes
});

async function processJob(jobId: string): Promise<void> {
  await jobRunner.execute(`job:${jobId}`, async () => {
    await runHeavyComputation(jobId);
  });
}
```

### 5. Inventory Update with Lock

```typescript
const inventoryLock = createLock({
  adapter: redisAdapter,
  ttlMs: 5_000,
  maxWaitMs: 3_000,
  retryIntervalMs: 100,
});

async function reserveInventory(productId: string, quantity: number): Promise<boolean> {
  return inventoryLock.acquire(`inventory:${productId}`, async () => {
    const current = await db.inventory.findOne({ productId });
    if (current.stock < quantity) return false;
    await db.inventory.update({ productId }, { $inc: { stock: -quantity } });
    return true;
  });
}
```

### 6. Double Submit Protection

```typescript
// Assign a UUID to every form on load, send as Idempotency-Key on submit
app.post('/orders', async (req, res) => {
  const formId = req.headers['idempotency-key'];
  if (!formId) return res.status(400).json({ error: 'Missing Idempotency-Key' });

  const order = await idempotency.execute(`form-submit:${formId}`, async () => {
    return createOrder(req.body);
  });

  res.status(201).json(order);
});
```

### 7. Severity-Based Error Routing

```typescript
const idempotency = createIdempotency({
  adapter,
  ttlMs: 60_000,
  hooks: {
    onStorageError: async (err, key) => {
      Sentry.captureException(err, { extra: { key } });
    },
    onTakeover: async (ctx) => {
      sendToSlack(`⚠️ Crash recovery on ${ctx.key} — attempt ${ctx.attempts}`);
    },
  },
});
```

### 8. Production Configuration

```typescript
import { createIdempotency, createLock, createRedisAdapter, createCircuitBreaker } from '@periodic/vanadium';

const isDevelopment = process.env.NODE_ENV === 'development';

const adapter = isDevelopment
  ? createMemoryAdapter()
  : createCircuitBreaker(
      createRedisAdapter({ client: redis, keyPrefix: 'vanadium:', useLua: true }),
      { failureThreshold: 5, resetTimeoutMs: 30_000 },
    );

export const idempotency = createIdempotency({
  adapter,
  ttlMs: 86_400_000,
  inProgressExpiryMs: 300_000,
  hashPayload: !isDevelopment,
  hooks: {
    onAfterExecute: (ctx) => logger.info('vanadium.execute', ctx),
    onDuplicateHit: (ctx) => logger.info('vanadium.duplicate', ctx),
    onStorageError: (err, key) => logger.error('vanadium.storage_error', { err, key }),
  },
});

export const lock = createLock({
  adapter,
  ttlMs: 10_000,
  maxWaitMs: isDevelopment ? 0 : 5_000,
});

export default idempotency;
```

---

## 🎛️ Configuration Options

### `createIdempotency` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `adapter` | `StorageAdapter` | required | Storage backend |
| `ttlMs` | `number` | `86_400_000` | Completed result TTL (24h) |
| `inProgressExpiryMs` | `number` | `300_000` | IN_PROGRESS expiry before crash takeover (5m) |
| `hashPayload` | `boolean` | `false` | Enable payload hash mismatch detection |
| `cacheFailures` | `boolean` | `false` | Cache thrown errors (prevents re-execution on failure) |
| `clock` | `() => number` | `Date.now` | Injectable clock for deterministic testing |
| `onDuplicate` | `(ctx) => void` | — | Shorthand callback on duplicate detection |
| `hooks` | `IdempotencyHooks` | — | Full lifecycle hook object |

### `createLock` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `adapter` | `StorageAdapter` | required | Storage backend |
| `ttlMs` | `number` | required | Lock TTL — auto-expires to prevent deadlocks |
| `retryIntervalMs` | `number` | `50` | How often to retry when waiting for a lock |
| `maxWaitMs` | `number` | `0` | Max wait time (`0` = fail immediately if locked) |
| `clock` | `() => number` | `Date.now` | Injectable clock for deterministic testing |
| `hooks` | `LockHooks` | — | Lifecycle hook object |

### `createMemoryAdapter` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `maxKeys` | `number` | `Infinity` | LRU eviction threshold |
| `clock` | `() => number` | `Date.now` | Injectable clock for testing |

### `createCircuitBreaker` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `failureThreshold` | `number` | `5` | Consecutive failures before OPEN |
| `resetTimeoutMs` | `number` | `30_000` | Time in OPEN before HALF_OPEN probe |
| `halfOpenMaxCalls` | `number` | `1` | Max probe calls in HALF_OPEN state |

---

## 📋 API Reference

### Idempotency

```typescript
createIdempotency(options: IdempotencyOptions): IdempotencyEngine
idempotency.execute(key: string, fn: () => Promise<T>, payload?: unknown): Promise<T>
idempotency.getMetrics(): VanadiumMetrics
idempotency.resetMetrics(): void
```

### Locks

```typescript
createLock(options: LockOptions): LockEngine
lock.acquire(key: string, fn: () => Promise<T>): Promise<T>
lock.getMetrics(): VanadiumMetrics
```

### Storage Adapters

```typescript
createMemoryAdapter(options?): MemoryAdapter
createRedisAdapter(options): RedisAdapter
createPostgresAdapter(options): PostgresAdapter
createMongoAdapter(options): MongoAdapter
createMongooseAdapter(options): MongooseAdapter
createPrismaAdapter(options): PrismaAdapter
createCircuitBreaker(adapter, options?): CircuitBreakerAdapter
```

### HTTP Middleware

```typescript
vanadiumMiddleware(idempotency: IdempotencyEngine, options?): RequestHandler  // Express
vanadiumFastifyPlugin(idempotency: IdempotencyEngine, options?): FastifyPlugin  // Fastify
```

### Error Handling

```typescript
isVanadiumError(err: unknown): err is VanadiumError

// err.type values:
'DUPLICATE_EXECUTION'
'IN_PROGRESS'
'LOCK_ACQUISITION_FAILED'
'LOCK_TIMEOUT'
'PAYLOAD_MISMATCH'
'CONFIGURATION_ERROR'
'STORAGE_ERROR'
'STATE_TRANSITION_ERROR'
```

### Event Structure

```typescript
interface StoredRecord<T = unknown> {
  key: string;
  status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
  result?: T;
  payloadHash?: string;
  ownerToken?: string;
  attempts: number;
  createdAt: number;
  updatedAt: number;
  expiresAt?: number;
}
```

---

## 🧩 Architecture

```
@periodic/vanadium/
├── src/
│   ├── types/
│   │   └── index.ts          # All shared type definitions (StoredRecord, StorageAdapter, etc.)
│   ├── errors/
│   │   └── index.ts          # VanadiumError class + all factory functions
│   ├── core/
│   │   ├── stateMachine.ts   # Valid state transitions (IN_PROGRESS→COMPLETED|FAILED)
│   │   ├── metrics.ts        # MetricsStore — per-instance counters
│   │   └── concurrencyGuard.ts # In-process deduplication (local optimization layer)
│   ├── idempotency/
│   │   └── engine.ts         # IdempotencyEngineImpl + createIdempotency()
│   ├── lock/
│   │   └── engine.ts         # LockEngineImpl + createLock()
│   ├── adapters/              # Storage adapter implementations
│   │   ├── memory/index.ts   # MemoryAdapter (built-in, LRU, TTL, CAS, zero deps)
│   │   ├── redis/index.ts    # RedisAdapter (Lua CAS, atomic ops)
│   │   ├── postgres/index.ts # PostgresAdapter (advisory locks)
│   │   ├── mongodb/index.ts  # MongoAdapter (findOneAndUpdate CAS)
│   │   ├── mongoose/index.ts # MongooseAdapter
│   │   └── prisma/index.ts   # PrismaAdapter
│   ├── resilience/
│   │   └── circuitBreaker.ts # CircuitBreakerAdapter (CLOSED/OPEN/HALF_OPEN)
│   ├── cleanup/
│   │   └── engine.ts         # CleanupEngine (background stale record cleanup)
│   ├── http/
│   │   ├── express.ts        # Express middleware
│   │   └── fastify.ts        # Fastify plugin
│   ├── observability/
│   │   └── metrics.ts        # OTel-compatible metrics
│   └── utils/
│       ├── crypto.ts         # SHA-256 hashing, UUID token generation
│       ├── keys.ts           # Key validation and namespacing
│       └── sleep.ts          # Non-blocking sleep + jitter
```

**Design Philosophy:**
- **Core** is pure TypeScript with no dependencies
- **Adapters** implement a single `StorageAdapter` interface — swap without changing application code
- **HTTP middleware** is thin — it delegates entirely to the idempotency engine
- **Circuit breaker** wraps any adapter — composable, not built-in
- **Hooks** are observer-only — they can never affect execution outcome
- Easy to extend with custom adapters

---

## 📈 Performance

Vanadium is optimized for production workloads:

- **Zero blocking** — All storage operations are async, never delay response
- **In-process coalescing** — Concurrent calls for the same key within one process are deduplicated before hitting storage
- **LRU eviction** — Memory adapter is bounded and never grows unbounded
- **Lua scripts** — Redis CAS is atomic at the server, no round-trip races
- **Hook isolation** — Hook errors are silently swallowed, never affect execution
- **No monkey-patching** — Clean hooks only, no prototype mutation

---

## 🚫 Explicit Non-Goals

This package **intentionally does not** include:

❌ Message queuing (use BullMQ, RabbitMQ, or Kafka)  
❌ Job scheduling (use cron libraries or cloud schedulers)  
❌ Distributed consensus (use etcd or ZooKeeper)  
❌ Retry logic (it prevents redundant retries, not manages them)  
❌ Business data storage (it stores execution state, not your data)  
❌ Built-in dashboards (use Grafana, Datadog, etc.)  
❌ Blocking behavior in production  
❌ Magic or implicit behavior on import  
❌ Configuration files (configure in code)

Focus on doing one thing well: **deterministic, safe, single-execution semantics for critical operations**.

---

## 🎨 TypeScript Support

Full TypeScript support with complete type safety:

```typescript
import type {
  StorageAdapter,
  StoredRecord,
  IdempotencyOptions,
  LockOptions,
  VanadiumMetrics,
  VanadiumErrorType,
  IdempotencyHooks,
  LockHooks,
} from '@periodic/vanadium';

// Fully generic — type inference works automatically
const result: string = await idempotency.execute('key', async () => 'hello');

// Explicit generic when needed
const record: { id: number } = await idempotency.execute<{ id: number }>(
  'key',
  async () => ({ id: 42 }),
);
```

---

## 🧪 Testing

```bash
# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch
```

**Note:** All tests achieve >80% code coverage.

---

## 🤝 Related Packages

Part of the **Periodic** series by Uday Thakur:

- [**@periodic/iridium**](https://www.npmjs.com/package/@periodic/iridium) - Structured logging
- [**@periodic/arsenic**](https://www.npmjs.com/package/@periodic/arsenic) - Semantic runtime monitoring
- [**@periodic/zirconium**](https://www.npmjs.com/package/@periodic/zirconium) - Environment configuration
- [**@periodic/obsidian**](https://www.npmjs.com/package/@periodic/obsidian) - HTTP error handling
- [**@periodic/titanium**](https://www.npmjs.com/package/@periodic/titanium) - Rate limiting
- [**@periodic/osmium**](https://www.npmjs.com/package/@periodic/osmium) - Redis caching

Build complete, production-ready APIs with the Periodic series!

---

## 📖 Documentation

- [Quick Start Guide](QUICKSTART.md)
- [Storage Adapter Guide](ADAPTERS.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

---

## 🛠️ Production Recommendations

### Environment Variables

```bash
NODE_ENV=production
REDIS_URL=redis://...
```

### Log Aggregation

Pair with `@periodic/iridium` for structured JSON output:

```typescript
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { createIdempotency } from '@periodic/vanadium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const idempotency = createIdempotency({
  adapter,
  ttlMs: 86_400_000,
  hooks: {
    onAfterExecute: (ctx) => logger.info('vanadium.execute', ctx),
    onDuplicateHit: (ctx) => logger.info('vanadium.duplicate', ctx),
    onStorageError: (err, key) => logger.error('vanadium.storage_error', { err, key }),
  },
});

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.
```

### Observability

Integrate with error tracking and metrics:

```typescript
const idempotency = createIdempotency({
  adapter,
  ttlMs: 86_400_000,
  hooks: {
    onTakeover: (ctx) => {
      Sentry.captureEvent({ message: `crash recovery: ${ctx.key}`, extra: ctx });
    },
    onStorageError: (err, key) => {
      Sentry.captureException(err, { extra: { key } });
    },
  },
});
```

---

## 📝 License

MIT © [Uday Thakur](LICENSE)

---

## 🙏 Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

- Code of conduct
- Development setup
- Pull request process
- Coding standards
- Architecture principles

---

## 📞 Support

- 📧 **Email:** udaythakurwork@gmail.com
- 🐛 **Issues:** [GitHub Issues](https://github.com/udaythakur7469/periodic-vanadium/issues)
- 💬 **Discussions:** [GitHub Discussions](https://github.com/udaythakur7469/periodic-vanadium/discussions)

---

## 🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!

---

**Built with ❤️ by Uday Thakur for production-grade Node.js applications**