dokv-client
v2.0.1
Published
零配置的 KV 存储客户端,开箱即用,支持 TypeScript
Maintainers
Readme
dokv-client
企业级 KV 存储客户端,支持多项目数据隔离,开箱即用。
✨ 特性
- 🚀 零配置 - 内置认证,无需任何配置即可使用
- 🔒 项目隔离 - 通过 projectId 自动隔离不同项目的数据
- 📦 轻量级 - 仅一个依赖,支持 Node.js 和浏览器
- 🔑 TypeScript - 完整的类型定义,优秀的 IDE 支持
- 🔄 自动重试 - 内置重试机制,提高可靠性
- 🎯 简单 API - 直观的方法命名,易于使用
- 🏷️ 命名空间 - 支持多租户数据隔离
- 📊 统计信息 - 内置请求统计和性能监控
- 🛡️ 错误处理 - 完善的错误处理和类型定义
📦 安装
npm install dokv-clientyarn add dokv-clientpnpm add dokv-client🚀 快速开始
基础使用
import createClient from 'dokv-client'
// 创建客户端(需要指定项目ID)
const kv = createClient('my-saas-app')
// 存储数据(实际键名:my-saas-app/user:123)
await kv.set('user:123', { name: 'John', age: 30 })
// 获取数据
const user = await kv.get('user:123')
console.log(user) // { name: 'John', age: 30 }
// 删除数据
await kv.delete('user:123')
// 检查是否存在
const exists = await kv.exists('user:123')
console.log(exists) // false多项目隔离示例
// 项目 A
const kvA = createClient('project-a')
await kvA.set('user:123', { name: 'Alice' })
// 项目 B
const kvB = createClient('project-b')
await kvB.set('user:123', { name: 'Bob' })
// 两个项目的数据完全隔离
console.log(await kvA.get('user:123')) // { name: 'Alice' }
console.log(await kvB.get('user:123')) // { name: 'Bob' }TypeScript 使用
import createClient from 'dokv-client'
import type { KVItem } from 'dokv-client'
interface User {
name: string
age: number
email?: string
}
const kv = createClient('my-saas-app')
// 类型安全的操作
await kv.set<User>('user:123', {
name: 'John',
age: 30
})
const user = await kv.get<User>('user:123')
// user 的类型是 User | null
// 获取完整信息
const userInfo = await kv.getInfo<User>('user:123')
// userInfo 的类型是 KVItem<User> | null高级功能
const kv = createClient('my-saas-app')
// 设置 TTL(5分钟后过期)
await kv.set('cache:data', data, { ttl: 300 })
// 添加元数据
await kv.set('config', data, {
metadata: { version: '1.0', author: 'admin' }
})
// 使用命名空间(多级路径)
const appKV = kv.namespace('mobile-app')
await appKV.set('user:123', userData)
// 实际键名:my-saas-app/mobile-app/user:123
const keys = await appKV.list()
// 嵌套命名空间
const adminKV = appKV.namespace('admin')
await adminKV.set('settings', adminConfig)
// 实际键名:my-saas-app/mobile-app/admin/settings
// 批量操作
await kv.mset([
['key1', 'value1'],
['key2', { data: 'value2' }, { ttl: 600 }]
])
const values = await kv.mget(['key1', 'key2'])
// Cache-Aside 模式
const data = await kv.getOrSet('expensive-data',
async () => {
// 缓存未命中时执行
return await fetchExpensiveData()
},
{ ttl: 3600 }
)📋 API 文档
核心方法
| 方法 | 描述 | 返回值 |
|------|------|--------|
| set(key, value, options?) | 设置键值对 | Promise<void> |
| get(key, options?) | 获取值 | Promise<T \| null> |
| getInfo(key, options?) | 获取完整信息 | Promise<KVItem<T> \| null> |
| delete(key, options?) | 删除键值对 | Promise<boolean> |
| exists(key, options?) | 检查是否存在 | Promise<boolean> |
| list(options?) | 列出所有键 | Promise<string[]> |
批量操作
| 方法 | 描述 | 返回值 |
|------|------|--------|
| mset(entries) | 批量设置 | Promise<KVBatchResult[]> |
| mget(keys) | 批量获取 | Promise<Array<T \| null>> |
| mdelete(keys) | 批量删除 | Promise<KVBatchResult[]> |
实用方法
| 方法 | 描述 | 返回值 |
|------|------|--------|
| getOrSet(key, factory, options?) | 缓存模式 | Promise<T> |
| namespace(name) | 创建命名空间实例 | KVClient |
| clear(namespace?) | 清空数据 | Promise<void> |
| healthCheck() | 健康检查 | Promise<boolean> |
| getStats() | 获取统计信息 | ClientStats |
⚙️ 配置选项
自定义配置
import { KVClient } from 'dokv-client'
const customKV = new KVClient({
projectId: 'my-saas-app', // 必填:项目标识符
endpoint: 'https://your-kv-endpoint.com',
token: 'your-custom-token',
timeout: 10000,
maxRetries: 5,
retryDelay: 2000,
keySeparator: '/', // 键名分隔符,默认 '/'
debug: true
})环境变量
支持通过环境变量配置(projectId 仍需代码中指定):
DOKV_ENDPOINT=https://custom-endpoint.com
DOKV_TOKEN=custom-token
DOKV_TIMEOUT=10000
DOKV_DEBUG=true// projectId 必须在代码中指定,确保每个项目明确声明
const kv = createClient('my-saas-app') // 会自动读取环境变量预定义环境
import { createClientForEnv } from 'dokv-client'
// 使用 staging 环境
const stagingClient = createClientForEnv('my-saas-app', 'staging')
// 使用 production 环境
const prodClient = createClientForEnv('my-saas-app', 'production')🔧 错误处理
import { KVError, ErrorCode } from 'dokv-client'
try {
await kv.set('key', value)
} catch (error) {
if (error instanceof KVError) {
switch (error.code) {
case ErrorCode.RATE_LIMIT_EXCEEDED:
// 处理限流
console.log('Rate limited, waiting...')
await sleep(1000)
break
case ErrorCode.INVALID_KEY:
// 处理无效键名
console.error('Invalid key format')
break
case ErrorCode.PAYLOAD_TOO_LARGE:
// 处理数据过大
console.error('Data too large')
break
default:
console.error(error.message)
}
}
}💡 最佳实践
1. 键名规范
// 推荐的键名格式
'user:123' // 用户数据
'session:abc123' // 会话数据
'cache:api:users' // 缓存数据
'config:app:theme' // 配置数据2. 使用命名空间
// 为不同应用隔离数据
const webApp = kv.namespace('web')
const mobileApp = kv.namespace('mobile')
const adminApp = kv.namespace('admin')
await webApp.set('config', webConfig)
await mobileApp.set('config', mobileConfig)
await adminApp.set('config', adminConfig)3. 合理设置 TTL
// 短期缓存 (5分钟)
await kv.set('cache:temp', data, { ttl: 300 })
// 会话数据 (1小时)
await kv.set('session:abc', sessionData, { ttl: 3600 })
// 配置数据 (1天)
await kv.set('config:theme', themeConfig, { ttl: 86400 })
// 长期存储 (不设置 TTL)
await kv.set('user:profile', userData)4. 批量操作优化
const keys = ['key1', 'key2', 'key3']
// ❌ 不推荐:串行操作
for (const key of keys) {
await kv.get(key)
}
// ✅ 推荐:批量操作
const values = await kv.mget(keys)
// ✅ 推荐:并行操作
const promises = keys.map(key => kv.get(key))
const values = await Promise.all(promises)5. 错误恢复
// 使用 getOrSet 处理缓存失效
const data = await kv.getOrSet(
'api:data',
async () => {
const response = await fetch('/api/data')
return response.json()
},
{
ttl: 600,
returnStaleOnError: true // 失败时返回过期数据
}
)🔍 监控和调试
获取统计信息
// 获取客户端统计信息
const stats = kv.getStats()
console.log(stats)
// {
// totalRequests: 150,
// successRequests: 148,
// failedRequests: 2,
// averageResponseTime: 45,
// cacheHits: 32,
// retryAttempts: 3,
// lastReset: 1704067200000
// }
// 重置统计信息
kv.resetStats()调试模式
// 启用调试模式
const debugKV = new KVClient({ debug: true })
// 或通过环境变量
// DOKV_DEBUG=true node your-app.js健康检查
// 检查服务是否可用
const isHealthy = await kv.healthCheck()
if (!isHealthy) {
console.error('KV service is not available')
}
// 使用便捷函数
import { healthCheck } from 'dokv-client'
const isHealthy = await healthCheck()📝 实际应用示例
用户会话管理
class SessionManager {
constructor() {
this.kv = kv.namespace('sessions')
}
async createSession(userId, data) {
const sessionId = this.generateSessionId()
const session = {
userId,
data,
createdAt: Date.now()
}
// 会话 1 小时后过期
await this.kv.set(sessionId, session, { ttl: 3600 })
return sessionId
}
async getSession(sessionId) {
return await this.kv.get(sessionId)
}
async destroySession(sessionId) {
return await this.kv.delete(sessionId)
}
async extendSession(sessionId, ttl = 3600) {
const session = await this.kv.get(sessionId)
if (session) {
await this.kv.set(sessionId, session, { ttl })
return true
}
return false
}
}API 缓存管理
class APICache {
constructor(namespace = 'api-cache') {
this.kv = kv.namespace(namespace)
}
async get(endpoint, ttl = 300) {
const cacheKey = this.getCacheKey(endpoint)
return await this.kv.getOrSet(
cacheKey,
async () => {
console.log(`Fetching data for ${endpoint}`)
const response = await fetch(endpoint)
return response.json()
},
{
ttl,
returnStaleOnError: true // 失败时返回过期数据
}
)
}
async invalidate(endpoint) {
const cacheKey = this.getCacheKey(endpoint)
return await this.kv.delete(cacheKey)
}
async clear() {
return await this.kv.clear()
}
getCacheKey(endpoint) {
return `endpoint:${Buffer.from(endpoint).toString('base64')}`
}
}配置管理
class ConfigManager {
constructor() {
this.kv = kv.namespace('config')
}
async get(key, defaultValue = null) {
const value = await this.kv.get(key)
return value ?? defaultValue
}
async set(key, value) {
await this.kv.set(key, value, {
metadata: {
updatedAt: new Date().toISOString(),
updatedBy: 'system'
}
})
}
async getAll() {
const keys = await this.kv.list()
const configs = {}
const values = await this.kv.mget(keys)
keys.forEach((key, index) => {
configs[key] = values[index]
})
return configs
}
async reset() {
await this.kv.clear()
}
}🔄 迁移指南
从原生 fetch 迁移
// 之前
const response = await fetch('https://dokv.pwtk.cc/kv/my-key', {
headers: {
'Authorization': 'Bearer pwtk-api-key-2025'
}
})
const data = await response.json()
// 现在
import kv from 'dokv-client'
const data = await kv.get('my-key')从其他 KV 客户端迁移
// 之前(Redis 风格)
await redis.set('key', JSON.stringify(value))
const value = JSON.parse(await redis.get('key'))
// 现在
await kv.set('key', value) // 自动序列化
const value = await kv.get('key') // 自动反序列化🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
MIT License
