Skip to content

Commit f1b1f79

Browse files
committed
fix: doc corrections, release prep and changelog restructure
- Fix CacheLike interface docs: remove non-existent generic, mark optional methods (getStats?/destroy?/invalidateByTag? etc) - Fix MultiLevelCacheOptions.remote: optional not required - Fix readThrough signature: CacheLike<V> -> CacheLike (no generic) - Update test count in README: 440 -> 470 - Restructure CHANGELOG.md into index + changelogs/v1.0.0.md per RULES §7 - Add changelogs/TEMPLATE.md for future versions - Fix package.json exports: move 'types' condition before import/require - Add changelogs/ to npm files array - Add statements:100 coverage threshold in vitest.config.ts - Export SetOptions type from main entry (src/index.ts) - Fix api-reference.md: remove false Redis SHA-256 key compress claim - Fix getting-started.md: multiple API usage corrections - Fix rspress.config.ts: update GitHub URL
1 parent 02fb92a commit f1b1f79

14 files changed

Lines changed: 906 additions & 200 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,85 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
> 📖 每个版本的完整变更详情位于 [`changelogs/`](./changelogs/) 目录。
9+
810
---
911

1012
## [1.0.0] - 2026-03-22
1113

12-
首个正式发布版本。零运行时依赖的 Node.js 多层缓存库,统一工作区所有项目的缓存基础设施。
13-
14-
### Added(新增)
15-
16-
#### 核心类型与接口(`cache-hub`
17-
- 新增 `CacheLike` 接口:定义统一缓存契约,覆盖 `get / set / del / exists / has / clear / keys / getMany / setMany / delMany / delPattern / getStats / destroy` 共 13 个方法
18-
- 新增 `CacheStats` 类型:统一统计信息结构(`hits / misses / sets / deletes / evictions / memoryUsage / hitRate`
19-
- 新增 `MemoryCacheOptions` 配置类型
20-
21-
#### MemoryCache(`cache-hub`
22-
- 基于 ES6 `Map` 实现的 O(1) LRU 淘汰引擎
23-
- TTL 支持:惰性过期(`get` 时判断)+ 可选周期清理(`cleanupInterval`
24-
- 双重容量限制:`maxEntries`(条目数)+ `maxMemory`(字节估算)
25-
- 批量操作:`getMany / setMany / delMany`
26-
- 模式删除:`delPattern(pattern)``*` 通配符 → 正则)
27-
- 统计信息:`hits / misses / evictions / hitRate` 等,可通过 `enableStats` 开关
28-
- 标签索引:`enableTags=true` 时维护 `tagIndex`,支持 `invalidateByTag(tag)`
29-
- `enabled` 开关:`false``get` 返回 `undefined``set` 不写入
30-
- `destroy()`:清理周期定时器并清空缓存
31-
32-
#### stableStringify(`cache-hub/stringify`
33-
- `stableStringify(value, options?)`:对象键字母排序、`NaN` 固定输出 `"__NaN__"`、循环引用输出 `"[CIRCULAR]"`
34-
- `RegExp` / `Date` 特殊处理,数组保序
35-
- `customSerializer` 插件钩子:支持 BSON ObjectId 等自定义类型扩展
36-
37-
#### readThrough(`cache-hub/read-through`
38-
- `readThrough(cache, ttlMs, key, fetcher)`:缓存命中直返,未命中执行 fetcher 并写缓存
39-
- 并发去重(in-flight map):相同 key 的并发请求共享同一 Promise
40-
- `ttl ≤ 0` 时直接执行 fetcher 不写缓存
41-
- 内置溢出保护(INFLIGHT_MAX_SIZE = 10000,超限清理最旧 10%)
42-
- 内置超时清理(INFLIGHT_TIMEOUT_MS = 300000ms,防止内存泄漏)
43-
- `undefined` 不写缓存,`null` 视为有效空值
44-
45-
#### MultiLevelCache(`cache-hub/multi-level`
46-
- L1(本地)+ L2(远端)双层缓存,均接受 `CacheLike` 接口
47-
- 写策略:`'both'`(同步双写)/ `'local-first-async-remote'`(本地优先异步写远端)
48-
- 远端命中回填本地(`backfillOnRemoteHit`,默认开启,可关闭)
49-
- 远端操作超时保护(`remoteTimeoutMs`,默认 50ms,超时降级不报错)
50-
- 可选分布式失效广播回调(`publish?: (keys: string[]) => void`
51-
52-
#### RedisCacheAdapter(`cache-hub/redis`
53-
- `createRedisCacheAdapter(urlOrInstance)`:将 ioredis 实例包装为 `CacheLike`
54-
- 支持 URL 字符串初始化(自动创建 ioredis 连接)或传入已有实例
55-
- `delPattern / keys` 使用 SCAN 游标迭代,禁止 `KEYS` 命令(防阻塞)
56-
- `close()`:仅关闭自己创建的连接(外部传入的连接不关闭)
57-
- 超长 key(> 512 字节)自动 SHA-256 压缩,`pattern` 超长时截断并 `console.warn`
58-
- JSON 序列化存储,支持 `null` 作为有效缓存值
59-
60-
#### FunctionCache / withCache(`cache-hub/function-cache`
61-
- `withCache(fn, options)`:装饰器,自动缓存异步函数返回值
62-
- 键生成:`namespace:fnName:stableStringify(args)`,超长键自动 SHA-256 压缩
63-
- 并发去重:相同参数的并发调用共享同一 Promise
64-
- 条件缓存:`condition(result)` 返回 `false` 时不写缓存
65-
- `FunctionCache` 类:支持 `register / execute / invalidate / getStats`,适合多函数统一管理
66-
- Per-function TTL / keyBuilder / condition 配置,可覆盖全局默认值
67-
68-
#### DistributedCacheInvalidator(`cache-hub/distributed`
69-
- `DistributedCacheInvalidator`:基于 Redis Pub/Sub 的跨实例缓存失效广播
70-
- 自动过滤自身发出的消息(`instanceId` 隔离)
71-
- 支持监听多个本地缓存实例(`watchedCaches`
72-
- 支持 `redisUrl` 字符串或已有 ioredis 连接两种初始化方式
73-
- `invalidate(keys)` / `close()` 生命周期管理
74-
- `getStats()` 返回发布/接收消息计数
75-
76-
### Build(构建)
77-
- ESM + CJS 双格式产出(`dist/esm/` + `dist/cjs/` + `dist/types/`
78-
- 多入口按需导入:`cache-hub``cache-hub/redis``cache-hub/multi-level``cache-hub/function-cache``cache-hub/distributed``cache-hub/stringify``cache-hub/read-through`
79-
- CJS 产物兼容性修补:`import.meta.url``__filename``scripts/build-cjs.mjs`
80-
- 零运行时依赖(`dependencies: {}`),ioredis 为可选 peerDependency(`>=5.0.0`
14+
🎉 首个正式发布版本。零运行时依赖的 Node.js 多层缓存库,统一工作区所有项目的缓存基础设施。
8115

82-
### Tests(测试)
83-
- 440 个单元测试,全部通过,全维度覆盖率 100%(Statements / Branches / Functions / Lines)
84-
- 30 个集成测试(`test/integration/redis.integration.test.ts`),需本地 Redis 连接
85-
- 支持 `npm test`(单元)、`npm run test:integration`(集成,需 Redis)
86-
- 支持 `REDIS_URL` 环境变量自定义 Redis 地址,`SKIP_INTEGRATION=true` 跳过集成测试
16+
[→ 查看完整变更详情](./changelogs/v1.0.0.md)
8717

8818
---
8919

README.md

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { MemoryCache } from 'cache-hub';
4646

4747
const cache = new MemoryCache({
4848
maxEntries: 1000, // 最多 1000 条
49-
ttl: 60_000, // 默认 TTL 60 秒
49+
defaultTtl: 60_000, // 默认 TTL 60 秒
5050
enableStats: true,
5151
});
5252

@@ -63,7 +63,7 @@ console.log(stats.hitRate); // 0~1 命中率
6363
import { MemoryCache } from 'cache-hub';
6464
import { readThrough } from 'cache-hub/read-through';
6565

66-
const cache = new MemoryCache({ ttl: 30_000 });
66+
const cache = new MemoryCache({ defaultTtl: 30_000 });
6767

6868
// 缓存命中直返,未命中执行 fetcher 并写缓存
6969
// 相同 key 的并发请求共享同一 Promise(并发去重)
@@ -102,7 +102,7 @@ import { MemoryCache } from 'cache-hub';
102102
import { MultiLevelCache } from 'cache-hub/multi-level';
103103
import { createRedisCacheAdapter } from 'cache-hub/redis';
104104

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

108108
const cache = new MultiLevelCache({
@@ -130,12 +130,12 @@ const local = new MemoryCache({ maxEntries: 1000 });
130130
// 多个服务实例各自持有本地缓存,通过 Redis Pub/Sub 广播失效
131131
const invalidator = new DistributedCacheInvalidator({
132132
redisUrl: process.env.REDIS_URL ?? 'redis://localhost:6379',
133-
watchedCaches: [local],
133+
cache: local,
134134
channel: 'app:cache-invalidation',
135135
});
136136

137-
// 发布失效事件,其他实例收到后自动清除本地缓存
138-
await invalidator.invalidate(['user:1', 'user:2']);
137+
// 广播失效事件(支持通配符 *),其他实例收到后自动对本地缓存执行 delPattern
138+
await invalidator.invalidate('user:*');
139139

140140
// 应用退出时关闭连接
141141
await invalidator.close();
@@ -158,11 +158,11 @@ import type { CacheLike, CacheStats, MemoryCacheOptions } from 'cache-hub';
158158

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

@@ -171,20 +171,24 @@ import type { CacheLike, CacheStats, MemoryCacheOptions } from 'cache-hub';
171171
所有缓存实现均满足此接口,可互相替换:
172172

173173
```typescript
174-
interface CacheLike<V = unknown> {
175-
get(key: string): V | undefined | Promise<V | undefined>;
176-
set(key: string, value: V, ttl?: number): void | Promise<void>;
177-
del(key: string): void | Promise<void>;
174+
interface CacheLike {
175+
get<T = any>(key: string): T | undefined | Promise<T | undefined>;
176+
set(key: string, value: any, ttl?: number): void | Promise<void>;
177+
del(key: string): boolean | Promise<boolean>;
178178
exists(key: string): boolean | Promise<boolean>;
179179
has(key: string): boolean | Promise<boolean>; // exists 的同步别名
180180
clear(): void | Promise<void>;
181-
keys(): string[] | Promise<string[]>;
182-
getMany(keys: string[]): Map<string, V> | Promise<Map<string, V>>;
183-
setMany(entries: Map<string, V>, ttl?: number): void | Promise<void>;
184-
delMany(keys: string[]): void | Promise<void>;
185-
delPattern(pattern: string): void | Promise<void>; // 支持 * 通配符
186-
getStats(): CacheStats;
187-
destroy(): void | Promise<void>;
181+
keys(pattern?: string): string[] | Promise<string[]>;
182+
getMany(keys: string[]): Record<string, any> | Promise<Record<string, any>>;
183+
setMany(entries: Record<string, any>, ttl?: number): boolean | Promise<boolean>;
184+
delMany(keys: string[]): number | Promise<number>;
185+
delPattern(pattern: string): number | Promise<number>; // 支持 * 通配符
186+
// 可选扩展
187+
invalidateByTag?(tag: string): void | Promise<void>;
188+
getStats?(): CacheStats;
189+
resetStats?(): void;
190+
destroy?(): void;
191+
setLockManager?(lm: LockManager): void;
188192
}
189193
```
190194

@@ -196,7 +200,7 @@ interface CacheLike<V = unknown> {
196200
import { readThrough } from 'cache-hub/read-through';
197201

198202
function readThrough<V>(
199-
cache: CacheLike<V>,
203+
cache: CacheLike,
200204
ttl: number,
201205
key: string,
202206
fetcher: () => Promise<V>
@@ -220,11 +224,11 @@ new MultiLevelCache(options: MultiLevelCacheOptions)
220224
| 选项 | 类型 | 默认值 | 说明 |
221225
|------|------|--------|------|
222226
| `local` | `CacheLike` | 必填 | L1 本地缓存 |
223-
| `remote` | `CacheLike` | 必填 | L2 远端缓存 |
227+
| `remote` | `CacheLike` | | L2 远端缓存(可选,未传时作为单级本地缓存运行)|
224228
| `writePolicy` | `'both' \| 'local-first-async-remote'` | `'both'` | 写策略 |
225229
| `backfillOnRemoteHit` | `boolean` | `true` | L2 命中时回填 L1 |
226230
| `remoteTimeoutMs` | `number` | `50` | 远端超时(毫秒),超时降级不报错 |
227-
| `publish` | `(keys: string[]) => void` || 分布式失效广播回调 |
231+
| `publish` | `(msg: { type: string; pattern: string; ts: number }) => void` || `delPattern` 时触发的分布式失效广播回调 |
228232

229233
---
230234

@@ -270,13 +274,13 @@ const cachedFn = withCache(asyncFn, {
270274
#### `FunctionCache`
271275

272276
```typescript
273-
const fc = new FunctionCache({ cache, ttl: 30_000 });
277+
const fc = new FunctionCache(cache, { ttl: 30_000 });
274278
275279
fc.register('getUser', async (id: number) => db.findUser(id));
276280
fc.register('getProduct', async (id: number) => db.findProduct(id), { ttl: 10_000 });
277281
278-
const user = await fc.execute('getUser', [1]);
279-
await fc.invalidate('getUser', [1]); // 使指定参数的缓存失效
282+
const user = await fc.execute('getUser', 1);
283+
await fc.invalidate('getUser', 1); // 使指定参数的缓存失效
280284
281285
const stats = fc.getStats(); // { getUser: { hits, misses, ... } }
282286
```
@@ -293,19 +297,19 @@ new DistributedCacheInvalidator(options: DistributedInvalidatorOptions)
293297

294298
| 选项 | 类型 | 说明 |
295299
|------|------|------|
296-
| `redisUrl` | `string` | Redis URL,与 `redis` 二选一 |
300+
| `cache` | `CacheLike` | 必填,接收失效消息时对该实例执行 `delPattern` |
301+
| `redisUrl` | `string` | Redis URL,与 `redis` 二选一,均未提供时默认 `redis://localhost:6379` |
297302
| `redis` | `ioredis` | 已有 Redis 连接,与 `redisUrl` 二选一 |
298-
| `watchedCaches` | `CacheLike[]` | 接收到失效消息时清除这些缓存 |
299-
| `channel` | `string` | Pub/Sub 频道名,默认 `'cache-hub:invalidation'` |
303+
| `channel` | `string` | Pub/Sub 频道名,默认 `'cache-hub:invalidate'` |
300304
| `instanceId` | `string` | 实例唯一 ID,用于过滤自身消息(默认随机生成) |
301305

302306
```typescript
303-
// 发布失效事件
304-
await invalidator.invalidate(['key1', 'key2']);
307+
// 广播失效事件(支持通配符 *)
308+
await invalidator.invalidate('user:*');
305309
306310
// 查看统计
307311
const stats = invalidator.getStats();
308-
// { published: 5, received: 12, selfFiltered: 5 }
312+
// { messagesSent: 5, messagesReceived: 12, invalidationsTriggered: 7, errors: 0, instanceId: '...', channel: '...' }
309313
310314
// 关闭连接
311315
await invalidator.close();
@@ -341,7 +345,7 @@ stableStringify(value, {
341345
## 测试
342346

343347
```bash
344-
# 单元测试(440 个,无需外部依赖
348+
# 全量测试(470 个,集成测试在无 Redis 时自动跳过
345349
npm test
346350
347351
# 单元测试 + 覆盖率报告
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# 需求定义 — 发布前配置与文档修复
2+
3+
**Bug ID**: BUG-发布前配置与文档修复
4+
**发现于**: 会话20(2026-03-22)audit 工作流(全面体检)
5+
**修复于**: 会话21(2026-03-22)fix 工作流
6+
**严重程度**: 🔴 高(B01 虚假 API 声明)+ 🟡 中(W01~W05 发布配置缺陷)
7+
8+
---
9+
10+
## 问题描述
11+
12+
audit 全面体检(会话20,5轮收敛)发现以下 6 项问题,分为两类:
13+
14+
### B01 — 虚假 API 声明(🔴 高优先级)
15+
16+
`website/docs/guide/api-reference.md``CHANGELOG.md` 均声称 `RedisCacheAdapter` 会对超过 512 字节的 key 自动进行 SHA-256 哈希压缩。
17+
18+
**实际情况**`redis-adapter.ts`**不存在**任何 key 哈希压缩逻辑。SHA-256 压缩仅存在于 `function-cache.ts`(阈值为 1024 字节,约束 A09)。Redis 适配器仅对 `pattern` 超过 512 字符时做截断并打印 `console.warn`(约束 A10),key 无任何长度限制。
19+
20+
用户若按文档描述假设 key 会被自动压缩,将对存储行为产生错误认知,导致调试困难。
21+
22+
### W01~W05 — 发布配置缺陷(🟡 中优先级)
23+
24+
| # | 问题 | 风险 |
25+
|---|------|------|
26+
| W01 | `package.json` 缺少 `prepublishOnly` 脚本 | 发布时无安全门禁,可能发布未通过测试或构建失败的版本 |
27+
| W02 | `package.json` 缺少 `main` / `types` 顶层字段 | 旧版 TypeScript(`moduleResolution: node`)和部分 bundler 无法正确解析包入口 |
28+
| W03 | `SetOptions` 类型未从主入口 `cache-hub` 导出 | 消费者无法通过 `import type { SetOptions } from 'cache-hub'` 使用标签写入类型 |
29+
| W04 | `CHANGELOG.md``CacheStats` 描述缺少 `entries``memoryUsageMB` 两个字段 | 更新日志与实际类型不一致,读者误以为这两个字段不存在 |
30+
| W05 | `package.json``files` 数组未包含 `CHANGELOG.md` | npm publish tarball 不含更新日志,消费者无法在离线包中查阅变更历史 |
31+
32+
---
33+
34+
## 影响范围
35+
36+
| 文件 | 问题 |
37+
|------|------|
38+
| `website/docs/guide/api-reference.md` | B01:虚假"超长 key SHA-256 压缩"行 |
39+
| `CHANGELOG.md` | B01:虚假 Redis key 压缩描述;W04:CacheStats 字段列表不完整 |
40+
| `package.json` | W01:缺 prepublishOnly;W02:缺 main/types;W05:files 不含 CHANGELOG.md |
41+
| `src/index.ts` | W03:缺 SetOptions re-export |
42+
43+
---
44+
45+
## 根因分析
46+
47+
- **B01**:会话16 自动生成文档时,将 `function-cache.ts` 的 SHA-256 key 压缩行为误归属到 `RedisCacheAdapter`,未与 `redis-adapter.ts` 源码交叉验证。
48+
- **W01**:会话16 生成 `package.json` 时未参照 profile/05-发布规范.md 的发布前检查项。
49+
- **W02**:会话16 采用现代 `exports` 字段配置,未补充传统兼容字段。
50+
- **W03**`SetOptions``MemoryCache.set()` 的扩展参数类型,会话16 仅导出了核心类型,遗漏了此类型。
51+
- **W04**:会话16 手写 CHANGELOG 时 `CacheStats` 字段列表未与 `src/types.ts` 对齐,`entries``memoryUsageMB` 两个字段被遗漏。
52+
- **W05**:会话16 配置 `files` 时仅考虑产物目录,未将 `CHANGELOG.md` 纳入发布清单。
53+
54+
---
55+
56+
## 期望行为
57+
58+
- `api-reference.md``CHANGELOG.md``RedisCacheAdapter` 的描述与源码一致:仅 pattern 截断,key 无限制
59+
- 执行 `npm publish` 前自动运行 typecheck + 测试 + 构建
60+
- 旧版 TypeScript 和 bundler 可通过顶层 `main`/`types` 字段解析包
61+
- 消费者可通过 `import type { SetOptions } from 'cache-hub'` 使用标签写入 API
62+
- `CacheStats``entries``memoryUsageMB` 字段在 CHANGELOG 中有记录
63+
- npm tarball 包含 `CHANGELOG.md`
64+
65+
---
66+
67+
## 验收标准
68+
69+
- [x] `api-reference.md` 删除虚假"超长 key SHA-256 压缩"行
70+
- [x] `CHANGELOG.md` Redis 描述修正为仅 pattern 截断
71+
- [x] `CHANGELOG.md` CacheStats 补充 `entries` / `memoryUsageMB`
72+
- [x] `package.json` 添加 `prepublishOnly` 脚本
73+
- [x] `package.json` 添加顶层 `main` / `types` 字段
74+
- [x] `src/index.ts` 导出 `SetOptions` 类型
75+
- [x] `package.json` `files` 添加 `"CHANGELOG.md"`
76+
- [x] `npm test` 470 tests 全部通过
77+
- [x] `npm run typecheck` 无错误
78+
- [x] `docs:build` 构建成功

0 commit comments

Comments
 (0)