{"_id":"@abinashpatri/cache","_rev":"2-3ce7d7d649d8cba35b30a811df03d3ff","name":"@abinashpatri/cache","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@abinashpatri/cache","version":"1.0.0","keywords":["utils","redis"],"author":{"name":"Abinash Patri"},"license":"MIT","_id":"@abinashpatri/cache@1.0.0","maintainers":[{"name":"abinashpatri","email":"abinashpatri33@gmail.com"}],"dist":{"shasum":"250e52affcbe62953d9faf4dac659bbe941d24ba","tarball":"https://registry.npmjs.org/@abinashpatri/cache/-/cache-1.0.0.tgz","fileCount":9,"integrity":"sha512-76vOu0WWztAN/2PkBfqG4pLPFZQbpl8bhfq6bORG9V2GNJN0LstlowZ4MTOFY+rv5dv2uyDh1XdklGw3SEZ2DQ==","signatures":[{"sig":"MEUCIAWdBewUWCU/hhA/yiYyDVBRBZdG3XRwlmoIFY39HCcjAiEA+/iHNTR7ze5LGTpige1zO7DlC63z+JwJt0ONVYIR+1g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19638},"main":"./dist/index.js","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"abinashpatri","email":"abinashpatri33@gmail.com"},"_npmVersion":"11.6.2","description":"Production-grade cache & rate limit utility library","directories":{},"_nodeVersion":"24.11.1","dependencies":{"redis":"^5.11.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^10.1.0","prettier":"^3.8.1","typescript":"^5.9.3","@types/node":"^25.5.0","@types/express":"^5.0.6"},"_npmOperationalInternal":{"tmp":"tmp/cache_1.0.0_1774192679947_0.6032358454063607","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@abinashpatri/cache","version":"1.0.1","description":"Production-grade cache & rate limit utility library","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","require":"./dist/index.js","import":"./dist/index.mjs"}},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["utils","redis"],"author":{"name":"Abinash Patri"},"license":"MIT","type":"commonjs","devDependencies":{"@types/express":"^5.0.6","@types/node":"^25.5.0","eslint":"^10.1.0","prettier":"^3.8.1","tsup":"^8.5.1","typescript":"^5.9.3"},"dependencies":{"express-rate-limit":"^8.3.1","rate-limit-redis":"^4.3.1","redis":"^5.11.0"},"_id":"@abinashpatri/cache@1.0.1","_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-wEOTch1FdzkjNIDYwKmKUIL2TXd4vYvtpndSE2qH+KjsMFsGyPvbyPKGxzXDyZkOZwHaOek0Vyk+jEEqHhTsaA==","shasum":"7cd033af8e3cd161ef0abfb42a6c2ebf9526c3eb","tarball":"https://registry.npmjs.org/@abinashpatri/cache/-/cache-1.0.1.tgz","fileCount":9,"unpackedSize":28927,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDToNKy3xqN/tAaRqoM0W+/J1OZK970+MIa68iPVKd2ZQIhAJps0UD9X2jsQyLQuP593UqMM4eHjkInLaVVvDqWDZHJ"}]},"_npmUser":{"name":"abinashpatri","email":"abinashpatri33@gmail.com"},"directories":{},"maintainers":[{"name":"abinashpatri","email":"abinashpatri33@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cache_1.0.1_1774193150828_0.17697101491674805"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-22T15:17:59.840Z","modified":"2026-03-22T15:25:51.153Z","1.0.0":"2026-03-22T15:18:00.085Z","1.0.1":"2026-03-22T15:25:50.984Z"},"author":{"name":"Abinash Patri"},"license":"MIT","keywords":["utils","redis"],"description":"Production-grade cache & rate limit utility library","maintainers":[{"name":"abinashpatri","email":"abinashpatri33@gmail.com"}],"readme":"# @abinashpatri/cache\n\nProduction-ready Redis cache helper for Node.js and TypeScript.\n\nBuilt for simple integration with:\n- safe JSON serialization/deserialization\n- TTL with jitter to reduce cache stampedes\n- auto-cache wrapper (`remember`)\n- Redis-backed Express rate limiting (`express-rate-limit` + Redis store)\n- ESM + CommonJS support\n- full TypeScript typings\n\n## Installation\n\n```bash\nnpm install @abinashpatri/cache\n```\n\n## Requirements\n\n- Node.js 18+\n- A running Redis instance\n\nDefault Redis URL used by the library:\n\n```txt\nredis://127.0.0.1:6379\n```\n\n## Quick Start\n\n```ts\nimport { connect, remember, disconnect } from \"@abinashpatri/cache\";\n\nasync function main() {\n  await connect(process.env.REDIS_URL);\n\n  const user = await remember(\"user:42\", 60, async () => {\n    // Replace with DB/API call\n    return { id: 42, name: \"Abinash\" };\n  });\n\n  console.log(user);\n  await disconnect();\n}\n\nmain().catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n```\n\n## API\n\n### `connect(url?: string): Promise<void>`\n\nInitializes Redis connection safely.\n\n- idempotent (safe to call multiple times)\n- default URL is `redis://127.0.0.1:6379`\n\n```ts\nawait connect();\n// or\nawait connect(\"redis://localhost:6379\");\n```\n\n### `disconnect(): Promise<void>`\n\nGracefully closes Redis connection.\n\n```ts\nawait disconnect();\n```\n\n### `ping(): Promise<string>`\n\nHealth check for Redis connection.\n\n```ts\nconst status = await ping(); // \"PONG\"\n```\n\n### `get<T = unknown>(key: string): Promise<T | null>`\n\nReads and parses JSON value from cache.\n\n- returns `null` when key does not exist\n- returns `null` when cached value is invalid JSON\n\n```ts\nconst profile = await get<{ id: number; name: string }>(\"user:42\");\n```\n\n### `set<T>(key: string, value: T, ttl = 60): Promise<void>`\n\nStores JSON value in cache with TTL (seconds).\n\n- adds jitter (`0-9s`) to TTL internally to spread expirations\n\n```ts\nawait set(\"user:42\", { id: 42, name: \"Abinash\" }, 120);\n```\n\n### `del(key: string): Promise<void>`\n\nDeletes a cache key.\n\n```ts\nawait del(\"user:42\");\n```\n\n### `remember<T>(key: string, ttl: number, fn: () => Promise<T>): Promise<T>`\n\nAuto-cache helper:\n1. returns cached value when available\n2. otherwise executes `fn`\n3. stores result in Redis with TTL\n4. returns computed result\n\n```ts\nconst posts = await remember(\"posts:home\", 30, async () => {\n  return fetchPostsFromDb();\n});\n```\n\n### `createRedisRateLimiter(options?): RateLimitRequestHandler`\n\nCreates an Express middleware that stores rate-limit counters in Redis.\n\nDefaults:\n- `windowMs`: `15 * 60 * 1000` (15 minutes)\n- `limit`: `100`\n- `standardHeaders`: `\"draft-7\"`\n- `legacyHeaders`: `false`\n- Redis key `prefix`: `\"rl:\"`\n\n```ts\nimport express from \"express\";\nimport { connect, createRedisRateLimiter } from \"@abinashpatri/cache\";\n\nconst app = express();\n\nawait connect(process.env.REDIS_URL);\n\nconst limiter = createRedisRateLimiter({\n  windowMs: 15 * 60 * 1000,\n  limit: 100,\n  prefix: \"rate-limit:\",\n  message: {\n    success: false,\n    message: \"Too many requests, please try again later.\",\n  },\n});\n\napp.use(\"/api\", limiter);\n\napp.get(\"/api/health\", (_req, res) => {\n  res.json({ ok: true });\n});\n```\n\nImportant: call `connect()` before creating or using the limiter so Redis is ready.\n\n## Rate Limit Patterns (Express)\n\n### 1) Global limiter (all routes)\n\n```ts\nconst globalLimiter = createRedisRateLimiter({\n  windowMs: 15 * 60 * 1000,\n  limit: 300,\n  prefix: \"rl:global:\",\n});\n\napp.use(globalLimiter);\n```\n\n### 2) Router-level limiter (only `/api`)\n\n```ts\nconst apiLimiter = createRedisRateLimiter({\n  windowMs: 60 * 1000,\n  limit: 100,\n  prefix: \"rl:api:\",\n});\n\napp.use(\"/api\", apiLimiter);\n```\n\n### 3) Route-level limiter (single endpoint)\n\n```ts\nconst loginLimiter = createRedisRateLimiter({\n  windowMs: 10 * 60 * 1000,\n  limit: 5,\n  prefix: \"rl:login:\",\n});\n\napp.post(\"/auth/login\", loginLimiter, loginHandler);\n```\n\n### 4) Different limits for different routes\n\n```ts\nconst strictLimiter = createRedisRateLimiter({ windowMs: 60 * 1000, limit: 20, prefix: \"rl:strict:\" });\nconst relaxedLimiter = createRedisRateLimiter({ windowMs: 60 * 1000, limit: 200, prefix: \"rl:relaxed:\" });\n\napp.use(\"/auth\", strictLimiter);\napp.use(\"/public\", relaxedLimiter);\n```\n\n### 5) Per-user/API key rate limit (custom key generator)\n\n```ts\nconst userLimiter = createRedisRateLimiter({\n  windowMs: 60 * 1000,\n  limit: 60,\n  prefix: \"rl:user:\",\n  keyGenerator: (req) => {\n    const userId = req.headers[\"x-user-id\"];\n    if (typeof userId === \"string\" && userId.length > 0) return `user:${userId}`;\n    return req.ip ?? \"unknown\";\n  },\n});\n\napp.use(\"/v1\", userLimiter);\n```\n\n### 6) Skip successful requests (limit only failed attempts)\n\nUseful for brute-force protection on auth endpoints.\n\n```ts\nconst authLimiter = createRedisRateLimiter({\n  windowMs: 15 * 60 * 1000,\n  limit: 10,\n  prefix: \"rl:auth:\",\n  skipSuccessfulRequests: true,\n});\n\napp.post(\"/auth/login\", authLimiter, loginHandler);\n```\n\n### 7) Custom 429 response handler\n\n```ts\nconst customHandlerLimiter = createRedisRateLimiter({\n  windowMs: 60 * 1000,\n  limit: 50,\n  prefix: \"rl:custom:\",\n  handler: (req, res) => {\n    res.status(429).json({\n      success: false,\n      message: \"Rate limit exceeded\",\n      route: req.originalUrl,\n    });\n  },\n});\n\napp.use(\"/api\", customHandlerLimiter);\n```\n\n### 8) Proxy/LB deployment setup (important)\n\n```ts\napp.set(\"trust proxy\", 1);\n```\n\nSet this when running behind Nginx, Cloudflare, Render, Railway, Heroku, or similar proxy/CDN, so client IP based limiting is correct.\n\n## Import Styles\n\n### Named imports (recommended)\n\n```ts\nimport {\n  connect,\n  get,\n  set,\n  del,\n  remember,\n  ping,\n  disconnect,\n  createRedisRateLimiter,\n} from \"@abinashpatri/cache\";\n```\n\n### Default import\n\n```ts\nimport cache from \"@abinashpatri/cache\";\n\nawait cache.connect();\nawait cache.set(\"k\", { ok: true }, 60);\nconst limiter = cache.createRedisRateLimiter({ limit: 50 });\n```\n\n## Production Usage Notes\n\n- Call `connect()` once during app startup.\n- Reuse this single connection across your application.\n- Always `await disconnect()` during graceful shutdown.\n- Keep TTL short for rapidly changing data and longer for stable data.\n- Use key namespacing (example: `service:entity:id`) to avoid collisions.\n\n## Error Handling Pattern\n\n```ts\nimport { connect } from \"@abinashpatri/cache\";\n\ntry {\n  await connect(process.env.REDIS_URL);\n} catch (err) {\n  console.error(\"Failed to connect Redis\", err);\n  // decide fallback strategy (disable cache / fail fast)\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}