npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dokv-client

v2.0.1

Published

零配置的 KV 存储客户端,开箱即用,支持 TypeScript

Readme

dokv-client

npm version TypeScript License: MIT

企业级 KV 存储客户端,支持多项目数据隔离,开箱即用。

✨ 特性

  • 🚀 零配置 - 内置认证,无需任何配置即可使用
  • 🔒 项目隔离 - 通过 projectId 自动隔离不同项目的数据
  • 📦 轻量级 - 仅一个依赖,支持 Node.js 和浏览器
  • 🔑 TypeScript - 完整的类型定义,优秀的 IDE 支持
  • 🔄 自动重试 - 内置重试机制,提高可靠性
  • 🎯 简单 API - 直观的方法命名,易于使用
  • 🏷️ 命名空间 - 支持多租户数据隔离
  • 📊 统计信息 - 内置请求统计和性能监控
  • 🛡️ 错误处理 - 完善的错误处理和类型定义

📦 安装

npm install dokv-client
yarn add dokv-client
pnpm 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

📚 相关链接