Skip to content

vextjs/cacheHub

Repository files navigation

cache-hub

零运行时依赖的 Node.js 多层缓存库。开箱即用的本地内存缓存(LRU + TTL)、可选 Redis 远端缓存、多级联动、函数装饰器与分布式失效广播——通过统一的 CacheLike 接口无侵入接入任何 Node.js 服务。

Node.js License: MIT Coverage: 100%


特性

  • 零运行时依赖dependencies 永远为空,不会污染你的依赖树
  • LRU + TTL 内存缓存 — 基于 ES6 Map 实现 O(1) 淘汰,支持双重容量限制(条目数 + 内存字节)
  • 多级缓存 — L1 本地 + L2 远端,自动回填、超时降级、写策略可配
  • Redis 适配器 — 将 ioredis 包装为 CacheLike,SCAN 替代 KEYS,无阻塞
  • 函数装饰器withCache 一行代码缓存任意异步函数,并发去重 + 条件缓存
  • 分布式失效 — Redis Pub/Sub 广播跨实例缓存清除
  • 稳定序列化stableStringify 生成确定性缓存键,处理循环引用与特殊类型
  • CJS + ESM 双格式 — 支持 requireimport,多入口按需导入

安装

npm install cache-hub

可选:Redis 功能

Redis 适配器和分布式失效需要 ioredis

npm install ioredis

快速开始

内存缓存

import { MemoryCache } from 'cache-hub';

const cache = new MemoryCache({
    maxEntries: 1000,       // 最多 1000 条
    defaultTtl: 60_000,     // 默认 TTL 60 秒
    enableStats: true,
});

await cache.set('user:1', { name: 'Alice' });
const user = await cache.get('user:1');  // { name: 'Alice' }

const stats = cache.getStats();
console.log(stats.hitRate);  // 0~1 命中率

读穿缓存

import { MemoryCache } from 'cache-hub';
import { readThrough } from 'cache-hub/read-through';

const cache = new MemoryCache({ defaultTtl: 30_000 });

// 缓存命中直返,未命中执行 fetcher 并写缓存
// 相同 key 的并发请求共享同一 Promise(并发去重)
const user = await readThrough(cache, 30_000, 'user:1', async () => {
    return db.findUser(1);
});

函数装饰器

import { withCache } from 'cache-hub/function-cache';
import { MemoryCache } from 'cache-hub';

const cache = new MemoryCache({ maxEntries: 500 });

const getUser = withCache(
    async (userId: number) => db.findUser(userId),
    {
        cache,
        ttl: 60_000,
        namespace: 'users',
        // 条件缓存:仅缓存非空结果
        condition: (result) => result !== null,
    }
);

// 参数相同的并发调用只执行一次 db.findUser
const user = await getUser(1);

多级缓存(L1 本地 + L2 Redis)

import { MemoryCache } from 'cache-hub';
import { MultiLevelCache } from 'cache-hub/multi-level';
import { createRedisCacheAdapter } from 'cache-hub/redis';

const local = new MemoryCache({ maxEntries: 500, defaultTtl: 30_000 });
const remote = createRedisCacheAdapter('redis://localhost:6379');

const cache = new MultiLevelCache({
    local,
    remote,
    remoteTimeoutMs: 50,          // 远端超时降级,不影响可用性
    backfillOnRemoteHit: true,    // L2 命中时自动回填 L1
    writePolicy: 'both',          // 同步双写
});

await cache.set('product:42', data, 120_000);
const product = await cache.get('product:42');  // 先查 L1,再查 L2

await remote.close();

分布式缓存失效

import { MemoryCache } from 'cache-hub';
import { DistributedCacheInvalidator } from 'cache-hub/distributed';

const local = new MemoryCache({ maxEntries: 1000 });

// 多个服务实例各自持有本地缓存,通过 Redis Pub/Sub 广播失效
const invalidator = new DistributedCacheInvalidator({
    redisUrl: process.env.REDIS_URL ?? 'redis://localhost:6379',
    cache: local,
    channel: 'app:cache-invalidation',
});

// 广播失效事件(支持通配符 *),其他实例收到后自动对本地缓存执行 delPattern
await invalidator.invalidate('user:*');

// 应用退出时关闭连接
await invalidator.close();

模块参考

cache-hub 采用多入口按需导入,避免捆绑不需要的模块。

cache-hub — 核心

import { MemoryCache } from 'cache-hub';
import type { CacheLike, CacheStats, MemoryCacheOptions } from 'cache-hub';

new MemoryCache(options?)

选项 类型 默认值 说明
maxEntries number 10000 最大条目数,超限 LRU 淘汰
maxMemory number 0 最大内存(字节估算),超限 LRU 淘汰;0 表示无内存限制
defaultTtl number 0(不过期) 默认 TTL(毫秒)
cleanupInterval number 0(不清理) 周期清理间隔(毫秒)
enableStats boolean true 开启命中率统计
enableTags boolean false 开启标签索引,支持 invalidateByTag
enabled boolean true false 时禁用缓存

CacheLike 接口

所有缓存实现均满足此接口,可互相替换:

interface CacheLike {
    get<T = any>(key: string): T | undefined | Promise<T | undefined>;
    set(key: string, value: any, ttl?: number): void | Promise<void>;
    del(key: string): boolean | Promise<boolean>;
    exists(key: string): boolean | Promise<boolean>;
    has(key: string): boolean | Promise<boolean>;         // exists 的同步别名
    clear(): void | Promise<void>;
    keys(pattern?: string): string[] | Promise<string[]>;
    getMany(keys: string[]): Record<string, any> | Promise<Record<string, any>>;
    setMany(entries: Record<string, any>, ttl?: number): boolean | Promise<boolean>;
    delMany(keys: string[]): number | Promise<number>;
    delPattern(pattern: string): number | Promise<number>;   // 支持 * 通配符
    // 可选扩展
    invalidateByTag?(tag: string): void | Promise<void>;
    getStats?(): CacheStats;
    resetStats?(): void;
    destroy?(): void;
    setLockManager?(lm: LockManager): void;
}

cache-hub/read-through — 读穿缓存

import { readThrough } from 'cache-hub/read-through';

function readThrough<V>(
    cache: CacheLike,
    ttl: number,
    key: string,
    fetcher: () => Promise<V>
): Promise<V>
  • ttl ≤ 0:直接执行 fetcher,不写缓存
  • fetcher 返回 null:写入缓存(有效空值);返回 undefined:不写缓存
  • 内置并发去重(同 key 共享 Promise)+ 超时防泄漏(300s)

cache-hub/multi-level — 多级缓存

import { MultiLevelCache } from 'cache-hub/multi-level';

new MultiLevelCache(options: MultiLevelCacheOptions)
选项 类型 默认值 说明
local CacheLike 必填 L1 本地缓存
remote CacheLike L2 远端缓存(可选,未传时作为单级本地缓存运行)
writePolicy 'both' | 'local-first-async-remote' 'both' 写策略
backfillOnRemoteHit boolean true L2 命中时回填 L1
remoteTimeoutMs number 50 远端超时(毫秒),超时降级不报错
publish (msg: { type: string; pattern: string; ts: number }) => void delPattern 时触发的分布式失效广播回调

cache-hub/redis — Redis 适配器

import { createRedisCacheAdapter } from 'cache-hub/redis';

// 方式一:URL 字符串(自动创建连接)
const adapter = createRedisCacheAdapter('redis://localhost:6379');

// 方式二:已有 ioredis 实例(不会被 close() 关闭)
import Redis from 'ioredis';
const redis = new Redis();
const adapter = createRedisCacheAdapter(redis);

// 用完后关闭(仅关闭自己创建的连接)
await adapter.close();

需要安装 ioredisnpm install ioredis


cache-hub/function-cache — 函数缓存

import { withCache, FunctionCache } from 'cache-hub/function-cache';

withCache(fn, options)

const cachedFn = withCache(asyncFn, {
    cache,                          // CacheLike 实例
    ttl?: number,                   // 毫秒,默认 60000
    namespace?: string,             // 键前缀,默认函数名
    keyBuilder?: (...args) => string,  // 自定义键生成
    condition?: (result) => boolean,   // 返回 false 时不写缓存
});

FunctionCache

const fc = new FunctionCache(cache, { ttl: 30_000 });

fc.register('getUser', async (id: number) => db.findUser(id));
fc.register('getProduct', async (id: number) => db.findProduct(id), { ttl: 10_000 });

const user = await fc.execute('getUser', 1);
await fc.invalidate('getUser', 1);  // 使指定参数的缓存失效

const stats = fc.getStats();  // { getUser: { hits, misses, ... } }

cache-hub/distributed — 分布式失效

import { DistributedCacheInvalidator } from 'cache-hub/distributed';

new DistributedCacheInvalidator(options: DistributedInvalidatorOptions)
选项 类型 说明
cache CacheLike 必填,接收失效消息时对该实例执行 delPattern
redisUrl string Redis URL,与 redis 二选一,均未提供时默认 redis://localhost:6379
redis ioredis 已有 Redis 连接,与 redisUrl 二选一
channel string Pub/Sub 频道名,默认 'cache-hub:invalidate'
instanceId string 实例唯一 ID,用于过滤自身消息(默认随机生成)
// 广播失效事件(支持通配符 *)
await invalidator.invalidate('user:*');

// 查看统计
const stats = invalidator.getStats();
// { messagesSent: 5, messagesReceived: 12, invalidationsTriggered: 7, errors: 0, instanceId: '...', channel: '...' }

// 关闭连接
await invalidator.close();

需要安装 ioredisnpm install ioredis


cache-hub/stringify — 稳定序列化

import { stableStringify } from 'cache-hub/stringify';

// 键排序,确定性输出
stableStringify({ b: 2, a: 1 })          // '{"a":1,"b":2}'

// 特殊值处理
stableStringify(NaN)                      // '"__NaN__"'(避免与字符串 "NaN" 碰撞)
stableStringify({ a: { ref: undefined } }) // 循环引用输出 "[CIRCULAR]"

// 自定义序列化器(如 BSON ObjectId)
stableStringify(value, {
    customSerializer: (v) => {
        if (v instanceof ObjectId) return v.toHexString();
        return undefined;  // undefined 表示使用默认序列化
    }
});

测试

# 全量测试(470 个,集成测试在无 Redis 时自动跳过)
npm test

# 单元测试 + 覆盖率报告
npm run test:coverage

# 集成测试(30 个,需要本地 Redis)
npm run test:integration

# 指定 Redis 地址
REDIS_URL=redis://myhost:6380 npm run test:integration

# 跳过集成测试
SKIP_INTEGRATION=true npm run test:integration

覆盖率目标:Statements / Branches / Functions / Lines 全部 100%


构建

# 完整构建(ESM + CJS + 类型声明)
npm run build

# 仅类型检查
npm run typecheck

构建产物:

dist/
├── esm/      # ES Module 格式(.js)
├── cjs/      # CommonJS 格式(.js)
└── types/    # TypeScript 类型声明(.d.ts)

Node.js 版本兼容性

Node.js 支持
16 LTS
18 LTS
20 LTS
22 LTS

License

MIT

About

A centralized cache management hub for modern applications, providing unified APIs, multi-layer caching support, and scalable cache strategies.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors