Loading...
Loading...
Compare original and translation side by side
undefinedundefined
**Basic Usage**:
```typescript
import { kv } from '@vercel/kv';
// Set with TTL (expires in 1 hour)
await kv.setex('session:abc', 3600, { userId: 123 });
// Get
const session = await kv.get('session:abc');
// Increment counter (atomic)
const views = await kv.incr('views:post:123');user:123123
**基本用法**:
```typescript
import { kv } from '@vercel/kv';
// 设置带TTL的键(1小时后过期)
await kv.setex('session:abc', 3600, { userId: 123 });
// 获取值
const session = await kv.get('session:abc');
// 原子性递增计数器
const views = await kv.incr('views:post:123');user:123123const cached = await kv.get(`post:${slug}`);
if (cached) return cached;
const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
await kv.setex(`post:${slug}`, 3600, post); // Cache 1 hour
return post;async function checkRateLimit(ip: string): Promise<boolean> {
const key = `ratelimit:${ip}`;
const current = await kv.incr(key);
if (current === 1) await kv.expire(key, 60); // 60s window
return current <= 10; // 10 requests per window
}const sessionId = crypto.randomUUID();
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, { userId });const pipeline = kv.pipeline();
pipeline.set('user:1', data);
pipeline.incr('counter');
const results = await pipeline.exec(); // Single round-tripuser:123post:abc:viewsratelimit:ip:endpointconst cached = await kv.get(`post:${slug}`);
if (cached) return cached;
const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
await kv.setex(`post:${slug}`, 3600, post); // 缓存1小时
return post;async function checkRateLimit(ip: string): Promise<boolean> {
const key = `ratelimit:${ip}`;
const current = await kv.incr(key);
if (current === 1) await kv.expire(key, 60); // 60秒窗口
return current <= 10; // 每个窗口最多10次请求
}const sessionId = crypto.randomUUID();
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, { userId });const pipeline = kv.pipeline();
pipeline.set('user:1', data);
pipeline.incr('counter');
const results = await pipeline.exec(); // 单次往返请求user:123post:abc:viewsratelimit:ip:endpointsetexsetuser:123123setexsetuser:123123Error: KV_REST_API_URL is not definedKV_REST_API_TOKEN is not defined@vercel/kvvercel env pull .env.local.env.local.gitignoreturbo.json{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }Error: KV_REST_API_URL is not definedKV_REST_API_TOKEN is not defined@vercel/kvvercel env pull .env.local.env.local.gitignoreturbo.json{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }TypeError: Do not know how to serialize a BigInthset()hset()'123456'hgetall()'code_123456'String(value.field)hgetall()TypeError: Do not know how to serialize a BigInthset()hset()'123456'hgetall()'code_123456'hgetall()String(value.field)cachedatatempfeature:id:typecachedatatempfeature:id:typeset()setex()setex(key, ttl, value)set()setex()setex(key, ttl, value)Error: Rate limit exceededError: Rate limit exceededError: Value too largeError: Value too largekv.get<T>()nullkv.get()unknownkv.get<T>()nullget()const rawData = await kv.get('key'); const data = rawData as MyType | null;kv.get<T>()nullkv.get()unknownkv.get<T>()nullget()const rawData = await kv.get('key'); const data = rawData as MyType | null;pipeline.exec()pipeline.exec()numberstringscan()stringnumbercountlet cursor: string = "0"cursor !== "0"!== 0numberstringscan()stringnumbercountlet cursor: string = "0"cursor !== "0"!== 0expire(key, newTTL)expire(key, newTTL)for awaitkv.scanIterator()sscanIterator()scan()scanIterator()// Don't use scanIterator() - it hangs in v2.0.0+
for await (const key of kv.scanIterator()) {
// This loop never terminates
}
// Use manual scan with cursor instead
let cursor: string = "0"; // v3.x uses string
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
for (const key of keys) {
const value = await kv.get(key);
// process key/value
}
} while (cursor !== "0");kv.scanIterator()for awaitsscanIterator()scan()scanIterator()// 不要使用scanIterator() - v2.0.0+中会挂起
for await (const key of kv.scanIterator()) {
// 此循环永不终止
}
// 改用手动扫描加游标
let cursor: string = "0"; // v3.x使用string类型
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
for (const key of keys) {
const value = await kv.get(key);
// 处理键/值
}
} while (cursor !== "0");kv.zrange(key, 0, -1, { rev: true })revrevzrevrange()// This sometimes returns empty array (BUG)
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1, { rev: true });
// [] - but CLI shows 12 items
// Workaround 1: Omit rev flag and reverse in-memory
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1);
const reversedChats = chats.reverse();
// Workaround 2: Use zrevrange instead
const chats = await kv.zrevrange(`user:chat:${userId}`, 0, -1);kv.zrange(key, 0, -1, { rev: true })revrevzrevrange()// 此代码有时返回空数组(BUG)
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1, { rev: true });
// [] - 但CLI显示有12条数据
// 解决方案1:省略rev标志并在内存中反转
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1);
const reversedChats = chats.reverse();
// 解决方案2:改用zrevrange
const chats = await kv.zrevrange(`user:chat:${userId}`, 0, -1);kv.get()nullunstable_noStore()cache: 'no-store'import { unstable_noStore as noStore } from 'next/cache';
export async function getData() {
noStore(); // Force dynamic rendering
const data = await kv.get('mykey');
return data; // Now returns correct value on first call
}
// Or add retry logic
async function getWithRetry(key: string, retries = 2) {
let data = await kv.get(key);
let attempt = 0;
while (!data && attempt < retries) {
await new Promise(r => setTimeout(r, 100));
data = await kv.get(key);
attempt++;
}
return data;
}kv.get()nullunstable_noStore()cache: 'no-store'import { unstable_noStore as noStore } from 'next/cache';
export async function getData() {
noStore(); // 强制动态渲染
const data = await kv.get('mykey');
return data; // 现在首次调用会返回正确值
}
// 或添加重试逻辑
async function getWithRetry(key: string, retries = 2) {
let data = await kv.get(key);
let attempt = 0;
while (!data && attempt < retries) {
await new Promise(r => setTimeout(r, 100));
data = await kv.get(key);
attempt++;
}
return data;
}Uncaught ReferenceError: process is not defined@vercel/kvprocess.env@vercel/kvprocess.envdefinecreateClientimport.meta.envvite-plugin-node-polyfills// Option 1: Vite config with define
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
'process.env.KV_REST_API_URL': JSON.stringify(env.KV_REST_API_URL),
'process.env.KV_REST_API_TOKEN': JSON.stringify(env.KV_REST_API_TOKEN),
},
};
});
// Option 2: Use createClient with import.meta.env
import { createClient } from '@vercel/kv';
const kv = createClient({
url: import.meta.env.VITE_KV_REST_API_URL,
token: import.meta.env.VITE_KV_REST_API_TOKEN,
});@vercel/kvUncaught ReferenceError: process is not definedprocess.env@vercel/kvprocess.envdefinecreateClientimport.meta.envvite-plugin-node-polyfills// 选项1:Vite配置中使用define
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
'process.env.KV_REST_API_URL': JSON.stringify(env.KV_REST_API_URL),
'process.env.KV_REST_API_TOKEN': JSON.stringify(env.KV_REST_API_TOKEN),
},
};
});
// 选项2:使用createClient结合import.meta.env
import { createClient } from '@vercel/kv';
const kv = createClient({
url: import.meta.env.VITE_KV_REST_API_URL,
token: import.meta.env.VITE_KV_REST_API_TOKEN,
});kv.get()unstable_noStore()// In Server Actions
'use server'
import { unstable_noStore as noStore } from 'next/cache';
export async function logChat(text: string) {
noStore(); // Force dynamic rendering
let n_usage = await kv.get('n_usage');
// Now returns fresh value, not cached
}
// Or use route handlers (automatically dynamic)
// app/api/chat/route.ts
export async function GET() {
let n_usage = await kv.get('n_usage'); // Fresh data
return Response.json({ n_usage });
}kv.get()unstable_noStore()// 在Server Actions中
'use server'
import { unstable_noStore as noStore } from 'next/cache';
export async function logChat(text: string) {
noStore(); // 强制动态渲染
let n_usage = await kv.get('n_usage');
// 现在返回最新值,而非缓存值
}
// 或使用路由处理程序(自动动态)
// app/api/chat/route.ts
export async function GET() {
let n_usage = await kv.get('n_usage'); // 最新数据
return Response.json({ n_usage });
}stringnumbercursor !== 0cursor !== "0"// v2.x
let cursor: number = 0;
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== 0);
// v3.x
let cursor: string = "0";
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== "0");stringnumbercursor !== 0cursor !== "0"// v2.x
let cursor: number = 0;
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== 0);
// v3.x
let cursor: string = "0";
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== "0");enableAutoPipelining: false// If auto-pipelining causes issues, disable it:
import { createClient } from '@vercel/kv';
const kv = createClient({
url: process.env.KV_REST_API_URL,
token: process.env.KV_REST_API_TOKEN,
enableAutoPipelining: false // Disable auto-pipelining
});scanIterator()enableAutoPipelining: false// 如果自动管道导致问题,可禁用:
import { createClient } from '@vercel/kv';
const kv = createClient({
url: process.env.KV_REST_API_URL,
token: process.env.KV_REST_API_TOKEN,
enableAutoPipelining: false // 禁用自动管道
});scanIterator()@vercel/kv@upstash/redis// @vercel/kv doesn't have stream methods
// await kv.xAdd(...) // TypeError: kv.xAdd is not a function
// Use Upstash Redis client directly
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.KV_REST_API_URL!,
token: process.env.KV_REST_API_TOKEN!,
});
await redis.xadd('stream:events', '*', { event: 'user.login' });
const messages = await redis.xread('stream:events', '0');@vercel/kv@upstash/redis// @vercel/kv没有流方法
// await kv.xAdd(...) // TypeError: kv.xAdd is not a function
// 直接使用Upstash Redis客户端
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.KV_REST_API_URL!,
token: process.env.KV_REST_API_TOKEN!,
});
await redis.xadd('stream:events', '*', { event: 'user.login' });
const messages = await redis.xread('stream:events', '0');kv.hgetall()await kv.hset('foobar', { '1834': 'https://example.com' });
const data = await kv.hgetall('foobar');
console.log(typeof data); // "object" not "string"
// It's already an object - use directly
console.log(data['1834']); // 'https://example.com'
// If you need JSON string
const jsonString = JSON.stringify(data);kv.hgetall()await kv.hset('foobar', { '1834': 'https://example.com' });
const data = await kv.hgetall('foobar');
console.log(typeof data); // "object"而非"string"
// 它已经是对象 - 直接使用
console.log(data['1834']); // 'https://example.com'
// 如果需要JSON字符串
const jsonString = JSON.stringify(data);const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
const acquired = await kv.setnx(lockKey, lockValue);
if (acquired) {
await kv.expire(lockKey, 10); // TTL prevents deadlock
try {
await processOrders();
} finally {
const current = await kv.get(lockKey);
if (current === lockValue) await kv.del(lockKey);
}
}await kv.zadd('leaderboard', { score, member: userId.toString() });
// Note: zrange with { rev: true } has a known bug (Issue #12)
// Use zrevrange instead for reliability
const top = await kv.zrevrange('leaderboard', 0, 9, { withScores: true });
const rank = await kv.zrevrank('leaderboard', userId.toString());const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
const acquired = await kv.setnx(lockKey, lockValue);
if (acquired) {
await kv.expire(lockKey, 10); // TTL防止死锁
try {
await processOrders();
} finally {
const current = await kv.get(lockKey);
if (current === lockValue) await kv.del(lockKey);
}
}await kv.zadd('leaderboard', { score, member: userId.toString() });
// 注意:带{ rev: true }的zrange存在已知bug(问题12)
// 为了可靠性,改用zrevrange
const top = await kv.zrevrange('leaderboard', 0, 9, { withScores: true });
const rank = await kv.zrevrank('leaderboard', userId.toString());