@hile/cache 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI.md +232 -0
- package/README.md +38 -223
- package/package.json +5 -4
package/AI.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# AI Guide For @hile/cache
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Purpose: Build typed Redis read-through caches with TTL, tags, fieldable hashes, negative/stale options, and singleflight refreshes.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Use this file when an AI agent installs the npm package and needs package-local examples, package selection rules, boundaries, and verification steps.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Package Selection
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
| User asks for | Use | Also read |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Add read-through Redis cache | `@hile/cache` | `packages/infrastructure.md`, `recipes/redis-cache-singleflight.md` |
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Infrastructure Helpers
|
|
28
|
+
|
|
29
|
+
Packages: `@hile/typeorm`, `@hile/ioredis`, `@hile/logger`, `@hile/cache`, `@hile/schedule`, `@hile/loader`.
|
|
30
|
+
|
|
31
|
+
## Use When
|
|
32
|
+
|
|
33
|
+
Use these packages for database connections, Redis connections, structured logging, typed Redis caches, scheduled jobs, and file-system loaders.
|
|
34
|
+
|
|
35
|
+
## Do Not Use When
|
|
36
|
+
|
|
37
|
+
- Do not use default TypeORM or Redis services for multiple connections.
|
|
38
|
+
- Do not use `@hile/cache` without returning `new Cache(...)` from the loader function.
|
|
39
|
+
- Do not use `@hile/schedule` distributed mode without Redis lock TTLs sized for job duration.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @hile/typeorm @hile/ioredis @hile/logger @hile/cache @hile/schedule @hile/loader
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Imports
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import typeormService, { transaction } from '@hile/typeorm'
|
|
51
|
+
import redisService from '@hile/ioredis'
|
|
52
|
+
import { createLogger } from '@hile/logger'
|
|
53
|
+
import { Cache, defineCache, RedisCache } from '@hile/cache'
|
|
54
|
+
import { Scheduler, defineJob } from '@hile/schedule'
|
|
55
|
+
import { scanDirectory, compileRoutePath, toRouterPath, normalizePath, Loader } from '@hile/loader'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Copy-Paste Example
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { loadService } from '@hile/core'
|
|
62
|
+
import redisService from '@hile/ioredis'
|
|
63
|
+
import { Cache, defineCache, RedisCache } from '@hile/cache'
|
|
64
|
+
|
|
65
|
+
const userProfile = defineCache('user:{id:string}:profile', async ({ id }) => {
|
|
66
|
+
const user = await loadUser(id)
|
|
67
|
+
return new Cache(user).setExpire(300)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const redis = await loadService(redisService)
|
|
71
|
+
const cache = new RedisCache('app:', redis)
|
|
72
|
+
const users = await cache.loadCache(userProfile)
|
|
73
|
+
const user = await users.read({ id: 'u1' })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## More Examples
|
|
77
|
+
|
|
78
|
+
TypeORM transaction with compensation:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
await transaction(ds, async (runner, rollback) => {
|
|
82
|
+
const user = await runner.manager.save(User, input)
|
|
83
|
+
rollback(() => redis.del(`user:${user.id}`))
|
|
84
|
+
await runner.manager.save(AuditLog, { userId: user.id, action: 'create' })
|
|
85
|
+
return user
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Distributed scheduled job:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const scheduler = new Scheduler()
|
|
93
|
+
scheduler.add('daily-report', '0 8 * * *', async () => {
|
|
94
|
+
await sendDailyReport()
|
|
95
|
+
}, {
|
|
96
|
+
distributed: {
|
|
97
|
+
redis,
|
|
98
|
+
ttl: 60_000,
|
|
99
|
+
namespace: 'reports',
|
|
100
|
+
policy: 'skip-if-locked',
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Compose With
|
|
106
|
+
|
|
107
|
+
- Use `RedisCache` with `@hile/ioredis`.
|
|
108
|
+
- Use `defineCache(..., { singleflight: true })` to reduce stampedes; internally it uses Redis locks.
|
|
109
|
+
- Use scheduler distributed mode with `@hile/redis-lock`.
|
|
110
|
+
- Use `Loader` only when building a new file-system based convention.
|
|
111
|
+
|
|
112
|
+
## Runtime And Lifecycle Notes
|
|
113
|
+
|
|
114
|
+
- `Cache(undefined)` removes the key unless negative caching is configured.
|
|
115
|
+
- `Cache#setExpire(seconds)` uses seconds.
|
|
116
|
+
- `defineCache` typed placeholders support `string`, `number`, and `boolean`.
|
|
117
|
+
- `RedisCache.loadCache()` returns `read`, `write`, `remove`, `has`, and `multi`.
|
|
118
|
+
- `RedisCache.removeTag(tag)` removes tagged cache entries.
|
|
119
|
+
- `fieldable` caches use Redis hashes and cannot combine with stale or negative cache options.
|
|
120
|
+
- `Scheduler.add()` supports cron strings and `{ delay }`.
|
|
121
|
+
- `Scheduler.load()` reads default exports from `*.schedule.*` files produced by `defineJob()`.
|
|
122
|
+
- `scanDirectory()` matches `.ts`, `.js`, `.tsx`, `.jsx`, and `.mjs`.
|
|
123
|
+
|
|
124
|
+
## Anti-Patterns
|
|
125
|
+
|
|
126
|
+
- Returning raw values from `defineCache` handlers instead of `new Cache(value)`.
|
|
127
|
+
- Treating cache as source of truth.
|
|
128
|
+
- Forgetting to destroy manually created Redis or TypeORM clients.
|
|
129
|
+
- Scheduling jobs without idempotency when side effects can repeat.
|
|
130
|
+
|
|
131
|
+
## Verification Checklist
|
|
132
|
+
|
|
133
|
+
- Manual Redis clients call `disconnect()` during cleanup.
|
|
134
|
+
- Manual TypeORM data sources call `destroy()` during cleanup.
|
|
135
|
+
- Cache keys include app/tenant prefixes when shared Redis is used.
|
|
136
|
+
- Scheduled jobs have clear duplicate-run policy.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Related Recipes
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Redis Cache With Singleflight
|
|
145
|
+
|
|
146
|
+
## Complete Example
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { loadService } from '@hile/core'
|
|
150
|
+
import redisService from '@hile/ioredis'
|
|
151
|
+
import { Cache, defineCache, RedisCache } from '@hile/cache'
|
|
152
|
+
|
|
153
|
+
const userProfileCache = defineCache(
|
|
154
|
+
'user:{id:string}:profile',
|
|
155
|
+
async ({ id }) => {
|
|
156
|
+
const profile = await loadProfileFromDatabase(id)
|
|
157
|
+
if (!profile) return new Cache(undefined)
|
|
158
|
+
return new Cache(profile).setExpire(300)
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
singleflight: { ttl: 10_000, wait: 10_000 },
|
|
162
|
+
stale: { ttl: 60 },
|
|
163
|
+
negative: { ttl: 30 },
|
|
164
|
+
tags: (params, data) => data ? [`user:${params.id}`] : [],
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const redis = await loadService(redisService)
|
|
169
|
+
const cache = new RedisCache('app:', redis)
|
|
170
|
+
const userProfiles = await cache.loadCache(userProfileCache)
|
|
171
|
+
|
|
172
|
+
const profile = await userProfiles.read({ id: 'u1' })
|
|
173
|
+
await userProfiles.remove({ id: 'u1' })
|
|
174
|
+
await cache.removeTag('user:u1')
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## File Layout
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
src/
|
|
181
|
+
caches/user-profile.cache.ts
|
|
182
|
+
models/users/get-profile.model.ts
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## User Intent
|
|
186
|
+
|
|
187
|
+
Use this recipe when reads are expensive and Redis should provide read-through caching with stampede protection.
|
|
188
|
+
|
|
189
|
+
## Packages To Use
|
|
190
|
+
|
|
191
|
+
- `@hile/cache`
|
|
192
|
+
- `@hile/ioredis`
|
|
193
|
+
- `@hile/redis-lock` indirectly through cache singleflight
|
|
194
|
+
|
|
195
|
+
## Implementation Steps
|
|
196
|
+
|
|
197
|
+
1. Define a typed key with placeholders.
|
|
198
|
+
2. Return `new Cache(value)` from the loader function.
|
|
199
|
+
3. Add TTL with `setExpire(seconds)`.
|
|
200
|
+
4. Enable `singleflight` for expensive reads.
|
|
201
|
+
5. Remove cache entries after writes.
|
|
202
|
+
|
|
203
|
+
## Failure And Cleanup Behavior
|
|
204
|
+
|
|
205
|
+
- Stale cache serves previous values while refresh runs.
|
|
206
|
+
- Negative cache stores missing values only when configured.
|
|
207
|
+
- Cache is not source of truth; database writes should remove or refresh related keys.
|
|
208
|
+
|
|
209
|
+
## Verification Checklist
|
|
210
|
+
|
|
211
|
+
- Cache handler returns `Cache`.
|
|
212
|
+
- Key params match placeholders.
|
|
213
|
+
- Redis prefix is app-specific.
|
|
214
|
+
- Update flows call `remove()` or `removeTag()`.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# Global Guardrails
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
## Never Generate These Patterns
|
|
223
|
+
|
|
224
|
+
- Do not call `loadService()` at module top level; it starts resources during import.
|
|
225
|
+
- Do not default-export plain functions from `*.boot.*` files; `hile start` expects a Hile service.
|
|
226
|
+
- Do not set `ctx.body` and also return a controller value.
|
|
227
|
+
- Do not assume `@hile/http` Zod validation mutates or coerces `ctx.query`, `ctx.params`, or `ctx.request.body`.
|
|
228
|
+
- Do not put reusable business logic only in controllers, pages, queue workers, or message handlers.
|
|
229
|
+
- Do not use old message examples that append a secondary response getter; current request APIs return promises directly.
|
|
230
|
+
- Do not claim exactly-once delivery or execution from Redis locks, queues, idempotency, or rate limits.
|
|
231
|
+
- Do not use queue `jobId` as the only side-effect idempotency boundary.
|
|
232
|
+
- Do not log the entire async context by default.
|
package/README.md
CHANGED
|
@@ -1,244 +1,59 @@
|
|
|
1
1
|
# @hile/cache
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Build typed Redis read-through caches with TTL, tags, fieldable hashes, negative/stale options, and singleflight refreshes.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This README is intentionally short and example-first. The complete AI-facing guide ships in `AI.md` in this package.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
pnpm add @hile/cache
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
运行时需提供 `ioredis` 的 `Redis` 实例;在 Hile 应用中通常通过 `@hile/ioredis` 的 `createRedis()` 或 `loadService(ioredisService)` 获取。
|
|
14
|
-
|
|
15
|
-
## 快速开始
|
|
16
|
-
|
|
17
|
-
### 1. 定义缓存
|
|
18
|
-
|
|
19
|
-
使用 `defineCache` 定义一条缓存:指定 key 模板和回源函数。
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { defineCache, Cache } from '@hile/cache';
|
|
23
|
-
|
|
24
|
-
const userCache = defineCache('user:{id:string}:info', async (params) => {
|
|
25
|
-
// params 的类型自动推导为 { id: string }
|
|
26
|
-
const data = await fetchUserFromDB(params.id);
|
|
27
|
-
return new Cache(data).setExpire(300); // 5 分钟 TTL
|
|
28
|
-
}, {
|
|
29
|
-
singleflight: true,
|
|
30
|
-
stale: { ttl: 60 },
|
|
31
|
-
negative: { ttl: 30 },
|
|
32
|
-
tags: ({ id }) => ['users', `user:${id}`],
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### 2. 使用缓存
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { loadService } from '@hile/core';
|
|
40
|
-
import redisService from '@hile/ioredis';
|
|
41
|
-
import { RedisCache } from '@hile/cache';
|
|
42
|
-
|
|
43
|
-
const redis = await loadService(redisService);
|
|
44
|
-
const cache = new RedisCache('myapp:', redis); // 前缀 + Redis 实例
|
|
45
|
-
|
|
46
|
-
const { read, write, remove, has } = await cache.loadCache(userCache);
|
|
47
|
-
|
|
48
|
-
// 读穿透:miss 时自动回源并写入
|
|
49
|
-
const user = await read({ id: 'u-001' });
|
|
50
|
-
|
|
51
|
-
// 强制刷新
|
|
52
|
-
await write({ id: 'u-001' });
|
|
53
|
-
|
|
54
|
-
// 判断是否存在
|
|
55
|
-
const exists = await has({ id: 'u-001' });
|
|
56
|
-
|
|
57
|
-
// 删除
|
|
58
|
-
await remove({ id: 'u-001' });
|
|
59
|
-
|
|
60
|
-
// 按 tag 批量失效
|
|
61
|
-
await cache.removeTag('users');
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## 核心概念
|
|
67
|
-
|
|
68
|
-
### 类型安全 key 模板
|
|
69
|
-
|
|
70
|
-
key 模板使用 `{name:type}` 占位符,编译期自动推导参数类型:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
defineCache('user:{id:string}:posts:{page:number}:{verified:boolean}', ...)
|
|
74
|
-
// params → { id: string; page: number; verified: boolean }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
支持的类型:`string`、`number`、`boolean`。
|
|
78
|
-
|
|
79
|
-
### Cache 类
|
|
80
|
-
|
|
81
|
-
`Cache<R>` 包装回源数据,提供 TTL 控制:
|
|
82
|
-
|
|
83
|
-
| 方法 | 说明 |
|
|
84
|
-
|------|------|
|
|
85
|
-
| `new Cache(data)` | 创建缓存数据,expire=0 永不过期 |
|
|
86
|
-
| `.setExpire(seconds)` | 设置 TTL(秒) |
|
|
87
|
-
|
|
88
|
-
### 读穿透(Read-Through)
|
|
89
|
-
|
|
90
|
-
`RedisCache._read` 的调用链路:
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
read(params)
|
|
94
|
-
→ EXISTS key
|
|
95
|
-
├─ true → GET key → JSON.parse → 返回
|
|
96
|
-
└─ false → write(params) → handler 回源 → SET/SETEX → 返回
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Singleflight
|
|
9
|
+
## When To Use
|
|
100
10
|
|
|
101
|
-
|
|
11
|
+
Use these packages for database connections, Redis connections, structured logging, typed Redis caches, scheduled jobs, and file-system loaders.
|
|
102
12
|
|
|
103
|
-
|
|
104
|
-
const userCache = defineCache('user:{id:string}', fetchUser, {
|
|
105
|
-
singleflight: {
|
|
106
|
-
ttl: 10_000,
|
|
107
|
-
wait: 10_000,
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Stale Cache
|
|
113
|
-
|
|
114
|
-
`stale: { ttl }` 会在数据过了 `Cache#setExpire()` 的 fresh 时间后继续保留一段 stale 时间。读取 stale 数据时立即返回旧值,并在后台刷新。
|
|
115
|
-
|
|
116
|
-
### Negative Cache
|
|
117
|
-
|
|
118
|
-
默认 `new Cache(undefined)` 不写 Redis。配置 `negative: { ttl }` 后会写入一个短 TTL 哨兵,避免不存在的数据反复回源。
|
|
119
|
-
|
|
120
|
-
### Tags
|
|
121
|
-
|
|
122
|
-
`tags` 会把缓存 key 记录到 Redis set,之后可用 `cache.removeTag(tag)` 批量删除。
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## API 参考
|
|
127
|
-
|
|
128
|
-
### defineCache
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
function defineCache<T extends string = string, R = any>(
|
|
132
|
-
key: T,
|
|
133
|
-
fn: (params: ExtractParams<T>) => Promise<Cache<R>>,
|
|
134
|
-
options?: boolean | DefineCacheOptions<T, R>
|
|
135
|
-
): DefineCacheResult<T, R>;
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
第三个参数仍兼容旧的 `boolean` fieldable 写法;新能力使用 options 对象。
|
|
139
|
-
|
|
140
|
-
> `fieldable` 使用 Redis Hash 存储,不兼容 `stale` 和 `negative` 这类依赖字符串 payload 的策略;组合使用会在 `defineCache()` 阶段直接抛出配置错误。
|
|
141
|
-
|
|
142
|
-
### RedisCache
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
class RedisCache {
|
|
146
|
-
constructor(prefix: string, redis: Redis, options?: { locks?: RedisLock });
|
|
147
|
-
|
|
148
|
-
loadCache<T extends string, R>(
|
|
149
|
-
target: DefineCacheResult<T, R>
|
|
150
|
-
): Promise<{
|
|
151
|
-
write(params: ExtractParams<T>): Promise<R | undefined>;
|
|
152
|
-
read(params: ExtractParams<T>): Promise<R | undefined>;
|
|
153
|
-
remove(params: ExtractParams<T>): Promise<number>;
|
|
154
|
-
has(params: ExtractParams<T>): Promise<boolean>;
|
|
155
|
-
}>;
|
|
156
|
-
|
|
157
|
-
removeTag(tag: string): Promise<number>;
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
| 方法 | 返回值 | 说明 |
|
|
162
|
-
|------|--------|------|
|
|
163
|
-
| `read` | `R \| undefined` | 读穿透:EXISTS → GET / miss 则回源写入 |
|
|
164
|
-
| `write` | `R \| undefined` | 强制执行回源并写入 Redis(data=undefined 则删除 key) |
|
|
165
|
-
| `remove` | `number` | 删除缓存,返回 0/1 |
|
|
166
|
-
| `has` | `boolean` | 检查 key 是否存在 |
|
|
167
|
-
| `removeTag` | `number` | 删除某个 tag 下记录的所有缓存 key |
|
|
168
|
-
|
|
169
|
-
### Redis 中的 key 结构
|
|
13
|
+
## Install
|
|
170
14
|
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
例如 `prefix = "myapp:"`、key 模板为 `user:{id:string}:info`、params 为 `{ id: "u-001" }` 时:
|
|
176
|
-
```
|
|
177
|
-
myapp:user:u-001:info
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @hile/cache
|
|
178
17
|
```
|
|
179
18
|
|
|
180
|
-
|
|
19
|
+
## Copy-Paste Example
|
|
181
20
|
|
|
182
|
-
|
|
21
|
+
```ts
|
|
22
|
+
import { loadService } from '@hile/core'
|
|
23
|
+
import redisService from '@hile/ioredis'
|
|
24
|
+
import { Cache, defineCache, RedisCache } from '@hile/cache'
|
|
183
25
|
|
|
184
|
-
|
|
26
|
+
const userProfile = defineCache('user:{id:string}:profile', async ({ id }) => {
|
|
27
|
+
const user = await loadUser(id)
|
|
28
|
+
return new Cache(user).setExpire(300)
|
|
29
|
+
})
|
|
185
30
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
}
|
|
31
|
+
const redis = await loadService(redisService)
|
|
32
|
+
const cache = new RedisCache('app:', redis)
|
|
33
|
+
const users = await cache.loadCache(userProfile)
|
|
34
|
+
const user = await users.read({ id: 'u1' })
|
|
192
35
|
```
|
|
193
36
|
|
|
194
|
-
|
|
195
|
-
import { loadService } from '@hile/core';
|
|
196
|
-
import redisService from '@hile/ioredis';
|
|
197
|
-
import { defineCache, Cache, RedisCache } from '@hile/cache';
|
|
37
|
+
## Boundaries
|
|
198
38
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## 完整示例
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import { loadService } from '@hile/core';
|
|
210
|
-
import redisService from '@hile/ioredis';
|
|
211
|
-
import { defineCache, Cache, RedisCache } from '@hile/cache';
|
|
212
|
-
|
|
213
|
-
const redis = await loadService(redisService);
|
|
39
|
+
- Do not use default TypeORM or Redis services for multiple connections.
|
|
40
|
+
- Do not use `@hile/cache` without returning `new Cache(...)` from the loader function.
|
|
41
|
+
- Do not use `@hile/schedule` distributed mode without Redis lock TTLs sized for job duration.
|
|
214
42
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return new Cache(user).setExpire(600);
|
|
220
|
-
});
|
|
43
|
+
- Returning raw values from `defineCache` handlers instead of `new Cache(value)`.
|
|
44
|
+
- Treating cache as source of truth.
|
|
45
|
+
- Forgetting to destroy manually created Redis or TypeORM clients.
|
|
46
|
+
- Scheduling jobs without idempotency when side effects can repeat.
|
|
221
47
|
|
|
222
|
-
|
|
223
|
-
const post = await db.query('SELECT * FROM posts WHERE id = $1', [id]);
|
|
224
|
-
return new Cache(post).setExpire(3600);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const cache = new RedisCache('myapp:', redis);
|
|
228
|
-
|
|
229
|
-
// 批量加载
|
|
230
|
-
const [userOps, postOps] = await Promise.all([
|
|
231
|
-
cache.loadCache(userCache),
|
|
232
|
-
cache.loadCache(postCache),
|
|
233
|
-
]);
|
|
234
|
-
|
|
235
|
-
// 使用
|
|
236
|
-
const user = await userOps.read({ id: 'u-001' });
|
|
237
|
-
const post = await postOps.read({ id: 'p-001' });
|
|
238
|
-
```
|
|
48
|
+
## Verify
|
|
239
49
|
|
|
240
|
-
|
|
50
|
+
- Manual Redis clients call `disconnect()` during cleanup.
|
|
51
|
+
- Manual TypeORM data sources call `destroy()` during cleanup.
|
|
52
|
+
- Cache keys include app/tenant prefixes when shared Redis is used.
|
|
53
|
+
- Scheduled jobs have clear duplicate-run policy.
|
|
241
54
|
|
|
242
|
-
##
|
|
55
|
+
## More Context
|
|
243
56
|
|
|
244
|
-
|
|
57
|
+
- `AI.md` in this package: full package-local AI guide.
|
|
58
|
+
- Root `llms-full.txt`: full monorepo AI context.
|
|
59
|
+
- Root `references/`: source files copied from `docs/ai`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/cache",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"README.md"
|
|
13
|
+
"README.md",
|
|
14
|
+
"AI.md"
|
|
14
15
|
],
|
|
15
16
|
"license": "MIT",
|
|
16
17
|
"publishConfig": {
|
|
@@ -21,8 +22,8 @@
|
|
|
21
22
|
"vitest": "^4.0.18"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@hile/redis-lock": "^3.0.
|
|
25
|
+
"@hile/redis-lock": "^3.0.2",
|
|
25
26
|
"ioredis": "^5.11.0"
|
|
26
27
|
},
|
|
27
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "0985b6f8abc1f4de0a36324063585fdc3ac1375b"
|
|
28
29
|
}
|