You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

12 KiB

可降级缓存系统实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 构建 packages/cache 包,实现 Redis 主存储 + Memory 内存降级的通用 K-V 缓存系统

Architecture: 分层适配器架构:CacheDriver 接口定义 get/set/del/exists/clear,MemoryDriverRedisDriver 分别实现, CacheManager 按 drivers 数组顺序尝试调用方透明降级

Tech Stack: TypeScript, ioredis, Node.js


文件结构

packages/cache/
├── package.json
├── index.ts
└── lib/
    ├── types.ts                   ← CacheDriver 接口、CacheManagerOptions、CacheEntry
    ├── managers/
    │   └── cache-manager.ts       ← CacheManager 实现(降级逻辑)
    └── drivers/
        ├── base.ts                ← 抽象基类 AbstractCacheDriver
        ├── memory-driver.ts       ← Map 实现
        └── redis-driver.ts        ← ioredis 实现

Task 1: 项目脚手架

Files:

  • Create: packages/cache/package.json

  • Create: packages/cache/index.ts

  • Create: packages/cache/tsconfig.json

  • Create: packages/cache/lib/types.ts

  • Step 1: 创建 package.json

{
  "name": "cache",
  "version": "0.1.0",
  "type": "module",
  "exports": {
    ".": "./index.ts"
  },
  "dependencies": {
    "ioredis": "^5.4.1"
  },
  "devDependencies": {
    "@types/node": "^20.0.0"
  }
}

Run: ls packages/cache/package.json Expected: 文件存在

  • Step 2: 创建 types.ts
export interface CacheEntry<T = unknown> {
  value: T
  expiresAt: number | null // Unix ms,null = 永不过期
}

export interface CacheDriver {
  name: string
  get<T = unknown>(key: string): Promise<T | null>
  set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>
  del(key: string): Promise<void>
  exists(key: string): Promise<boolean>
  clear(): Promise<void>
}

export interface CacheManagerOptions {
  drivers: CacheDriver[]
  defaultTtl?: number
}

Run: cat packages/cache/lib/types.ts Expected: 文件存在,CacheDriver、CacheManagerOptions、CacheEntry 导出

  • Step 3: 创建 index.ts 骨架
export * from './lib/types'

Run: cat packages/cache/index.ts Expected: export * from './lib/types'

  • Step 4: 提交
git add packages/cache/package.json packages/cache/index.ts packages/cache/tsconfig.json packages/cache/lib/types.ts
git commit -m "feat(cache): scaffold package and define core types

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

Task 2: MemoryDriver

Files:

  • Create: packages/cache/lib/drivers/memory-driver.ts

  • Step 1: 实现 MemoryDriver

import type { CacheDriver } from '../types'

interface MemoryEntry {
  value: unknown
  expiresAt: number | null
}

export class MemoryDriver implements CacheDriver {
  name = 'memory'
  private store = new Map<string, MemoryEntry>()

  async get<T>(key: string): Promise<T | null> {
    const entry = this.store.get(key)
    if (!entry) return null

    if (entry.expiresAt !== null && entry.expiresAt < Date.now()) {
      this.store.delete(key)
      return null
    }

    return entry.value as T
  }

  async set<T>(key: string, value: T, ttl = 0): Promise<void> {
    const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : null
    this.store.set(key, { value, expiresAt })
  }

  async del(key: string): Promise<void> {
    this.store.delete(key)
  }

  async exists(key: string): Promise<boolean> {
    const entry = this.store.get(key)
    if (!entry) return false

    if (entry.expiresAt !== null && entry.expiresAt < Date.now()) {
      this.store.delete(key)
      return false
    }

    return true
  }

  async clear(): Promise<void> {
    this.store.clear()
  }
}

Run: cat packages/cache/lib/drivers/memory-driver.ts Expected: 文件存在,类导出

  • Step 2: 更新 index.ts 导出 MemoryDriver
export * from './lib/types'
export { MemoryDriver } from './lib/drivers/memory-driver'

Run: cat packages/cache/index.ts Expected: 包含 MemoryDriver 导出

  • Step 3: 提交
git add packages/cache/lib/drivers/memory-driver.ts packages/cache/index.ts
git commit -m "feat(cache): implement MemoryDriver

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

Task 3: RedisDriver

Files:

  • Create: packages/cache/lib/drivers/redis-driver.ts

  • Step 1: 实现 RedisDriver

import Redis from 'ioredis'
import type { CacheDriver } from '../types'

export interface RedisDriverOptions {
  host: string
  port: number
  password?: string
  db?: number
}

export class RedisDriver implements CacheDriver {
  name = 'redis'
  private redis: Redis

  constructor(options: RedisDriverOptions) {
    this.redis = new Redis({
      host: options.host,
      port: options.port,
      password: options.password,
      db: options.db ?? 0,
      lazyConnect: true,
    })
  }

  private serialize<T>(value: T): string {
    return JSON.stringify(value)
  }

  private deserialize<T>(data: string): T {
    return JSON.parse(data) as T
  }

  async get<T>(key: string): Promise<T | null> {
    const data = await this.redis.get(key)
    if (!data) return null
    return this.deserialize<T>(data)
  }

  async set<T>(key: string, value: T, ttl = 0): Promise<void> {
    const serialized = this.serialize(value)
    if (ttl > 0) {
      await this.redis.set(key, serialized, 'EX', ttl)
    } else {
      await this.redis.set(key, serialized)
    }
  }

  async del(key: string): Promise<void> {
    await this.redis.del(key)
  }

  async exists(key: string): Promise<boolean> {
    const result = await this.redis.exists(key)
    return result === 1
  }

  async clear(): Promise<void> {
    await this.redis.flushdb()
  }

  async disconnect(): Promise<void> {
    await this.redis.quit()
  }
}

Run: cat packages/cache/lib/drivers/redis-driver.ts Expected: 文件存在,RedisDriver 导出,connect lazy

  • Step 2: 更新 index.ts 导出 RedisDriver
export * from './lib/types'
export { MemoryDriver } from './lib/drivers/memory-driver'
export { RedisDriver } from './lib/drivers/redis-driver'
export type { RedisDriverOptions } from './lib/drivers/redis-driver'

Run: cat packages/cache/index.ts Expected: 包含 RedisDriver 导出

  • Step 3: 提交
git add packages/cache/lib/drivers/redis-driver.ts packages/cache/index.ts
git commit -m "feat(cache): implement RedisDriver with ioredis

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

Task 4: CacheManager

Files:

  • Create: packages/cache/lib/managers/cache-manager.ts

  • Step 1: 实现 CacheManager

import type { CacheDriver, CacheManagerOptions } from '../types'

export class CacheManager {
  private drivers: CacheDriver[]
  private defaultTtl: number

  constructor(options: CacheManagerOptions) {
    if (!options.drivers || options.drivers.length === 0) {
      throw new Error('[cache] at least one driver is required')
    }
    this.drivers = options.drivers
    this.defaultTtl = options.defaultTtl ?? 0
  }

  async get<T>(key: string): Promise<T | null> {
    for (const driver of this.drivers) {
      try {
        const value = await driver.get<T>(key)
        return value
      } catch (err) {
        console.warn(`[cache] ${driver.name} get failed:`, err)
        continue
      }
    }
    return null
  }

  async set<T>(key: string, value: T, ttl?: number): Promise<void> {
    const effectiveTtl = ttl ?? this.defaultTtl
    await Promise.all(
      this.drivers.map(async (driver) => {
        try {
          await driver.set(key, value, effectiveTtl)
        } catch (err) {
          console.warn(`[cache] ${driver.name} set failed:`, err)
        }
      })
    )
  }

  async del(key: string): Promise<void> {
    await Promise.all(
      this.drivers.map(async (driver) => {
        try {
          await driver.del(key)
        } catch (err) {
          console.warn(`[cache] ${driver.name} del failed:`, err)
        }
      })
    )
  }

  async exists(key: string): Promise<boolean> {
    for (const driver of this.drivers) {
      try {
        const exists = await driver.exists(key)
        if (exists) return true
      } catch (err) {
        console.warn(`[cache] ${driver.name} exists failed:`, err)
        continue
      }
    }
    return false
  }

  async clear(): Promise<void> {
    await Promise.all(
      this.drivers.map(async (driver) => {
        try {
          await driver.clear()
        } catch (err) {
          console.warn(`[cache] ${driver.name} clear failed:`, err)
        }
      })
    )
  }
}

Run: cat packages/cache/lib/managers/cache-manager.ts Expected: 文件存在,CacheManager 导出

  • Step 2: 更新 index.ts 导出 CacheManager
export * from './lib/types'
export { MemoryDriver } from './lib/drivers/memory-driver'
export { RedisDriver } from './lib/drivers/redis-driver'
export { CacheManager } from './lib/managers/cache-manager'
export type { RedisDriverOptions } from './lib/drivers/redis-driver'

Run: cat packages/cache/index.ts Expected: 包含 CacheManager 导出

  • Step 3: 提交
git add packages/cache/lib/managers/cache-manager.ts packages/cache/index.ts
git commit -m "feat(cache): implement CacheManager with fallback logic

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

Task 5: createCache 工厂函数

Files:

  • Modify: packages/cache/index.ts

  • Step 1: 实现 createCache 工厂函数

import { CacheManager } from './lib/managers/cache-manager'
import { MemoryDriver } from './lib/drivers/memory-driver'
import { RedisDriver } from './lib/drivers/redis-driver'
import type { CacheDriver, CacheManagerOptions } from './lib/types'
import type { RedisDriverOptions } from './lib/drivers/redis-driver'

export * from './lib/types'
export { MemoryDriver } from './lib/drivers/memory-driver'
export { RedisDriver } from './lib/drivers/redis-driver'
export { CacheManager } from './lib/managers/cache-manager'
export type { RedisDriverOptions } from './lib/drivers/redis-driver'

export interface CacheFactoryOptions {
  redis?: RedisDriverOptions
  memory?: boolean
  defaultTtl?: number
}

export function createCache(options: CacheFactoryOptions): CacheManager {
  const drivers: CacheDriver[] = []

  if (options.redis) {
    drivers.push(new RedisDriver(options.redis))
  }

  if (options.memory !== false) {
    drivers.push(new MemoryDriver())
  }

  if (drivers.length === 0) {
    throw new Error('[cache] at least one driver (redis or memory) is required')
  }

  return new CacheManager({ drivers, defaultTtl: options.defaultTtl })
}

Run: cat packages/cache/index.ts Expected: createCache 工厂函数存在

  • Step 2: 提交
git add packages/cache/index.ts
git commit -m "feat(cache): add createCache factory function

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

Task 6: 验证构建

  • Step 1: 安装依赖

Run: cd /home/dash/coding/nuxt-app && bun add -w cache Expected: ioredis 被添加到 packages/cache/package.json

  • Step 2: 类型检查

Run: cd packages/cache && npx tsc --noEmit Expected: 无错误输出

  • Step 3: 提交
git add -A
git commit -m "chore(cache): add dependencies and verify build

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"

自检清单

  • spec 覆盖:CacheDriver 接口 ✓ / MemoryDriver ✓ / RedisDriver ✓ / CacheManager ✓ / createCache ✓ / 降级逻辑 ✓
  • 无 placeholder:无 TBD/TODO/类似占位符
  • 类型一致性:get/set/del/exists/clear 方法签名在各驱动一致 ✓