Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@vercel/kv2

Package Overview
Dependencies
Maintainers
380
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/kv2 - npm Package Compare versions

Comparing version
0.0.11
to
0.0.12
+83
-0
docs/indexes.md

@@ -172,2 +172,85 @@ [Home](../README.md) | [Previous: Schema and Trees](schema-and-trees.md) | [Next: Caching](caching.md)

## Key Design
Index keys are scanned in **lexicographic (ascending) order**. This section covers patterns for getting the most out of your index key design.
### Pad numbers with leading zeros
Lexicographic sort treats numbers as strings, so `"9"` sorts after `"10"`. Pad numeric values to a fixed width:
```typescript
// Bad: "9" > "10" lexicographically
key: (o) => `${o.priority}`
// Good: "009" < "010"
key: (o) => String(o.priority).padStart(3, "0")
```
### Use ISO dates for chronological order
ISO 8601 strings (`"2024-01-15T10:30:00Z"`) sort correctly as-is — no padding needed.
### Descending order
There is no built-in DESC option. Instead, invert the sort key so that lexicographic ascending order produces the desired descending result.
**For timestamps** — subtract from a far-future epoch and pad to fixed width:
```typescript
const MAX_TS = 8_640_000_000_000; // year 2243
const messages = kv.getStore<Message, undefined, "byNewest">("messages/", {
byNewest: {
key: (m) => String(MAX_TS - new Date(m.createdAt).getTime()).padStart(14, "0"),
},
});
// Most recent messages first
for await (const [key, entry] of messages.entries({ byNewest: { prefix: "" } })) {
console.log((await entry.value).createdAt);
}
```
**For scores/priorities** — subtract from a known max:
```typescript
const MAX_SCORE = 1_000_000;
const leaderboard = kv.getStore<Player, undefined, "byTopScore">("players/", {
byTopScore: {
key: (p) => String(MAX_SCORE - p.score).padStart(7, "0"),
},
});
```
**Combined with grouping** — composite key with an inverted sort field:
```typescript
const MAX_TS = 8_640_000_000_000;
const sessions = kv.getStore<Session, undefined, "byOwnerNewest">("sessions/", {
byOwnerNewest: {
key: (s) => {
const inv = String(MAX_TS - new Date(s.createdAt).getTime()).padStart(14, "0");
return `${s.ownerId}/${inv}`;
},
},
});
// user-42's most recent sessions first
sessions.entries({ byOwnerNewest: { prefix: "user-42/" } })
```
### Use separators to prevent prefix collisions
Always include a delimiter (typically `/`) between fields in composite keys:
```typescript
// Bad: prefix "user-4" matches "user-4", "user-42", "user-400"
key: (s) => `${s.ownerId}${s.createdAt}`
// Good: prefix "user-4/" only matches user-4
key: (s) => `${s.ownerId}/${s.createdAt}`
```
## Index Maintenance

@@ -174,0 +257,0 @@

+1
-1
{
"name": "@vercel/kv2",
"version": "0.0.11",
"version": "0.0.12",
"description": "KV2 - A type-safe KV store backed by Vercel private blobs with regional caching",

@@ -5,0 +5,0 @@ "type": "module",

+92
-22

@@ -11,17 +11,87 @@ # Using @vercel/kv2

## Usage pattern
## Usage patterns
Always use typed stores via `kv.getStore<V>(prefix)` rather than calling `kv.get`/`kv.set` directly. Typed stores enforce value types, scope keys by prefix, and support secondary indexes. Raw KV access should only be used for advanced scenarios like cross-store operations or dynamic key patterns.
### Use typed stores, not raw KV
Always use `kv.getStore<V>(prefix)` rather than `kv.get`/`kv.set` directly. Typed stores enforce value types, scope keys by prefix, and support indexes.
```typescript
const kv = createKV({ prefix: "myapp/" });
// Good: typed store with enforced value type
const users = kv.getStore<User>("users/");
await users.set("alice", { name: "Alice", email: "alice@example.com" });
```
// Avoid: raw access loses type safety and prefix scoping
await kv.set("users/alice", someValue);
### Reading values
`get()` returns a discriminated union — always check `exists` before accessing `value`. Note that `value` is a `Promise` (lazy-parsed), so it needs `await`.
```typescript
const result = await users.get("alice");
if (result.exists) {
const user = await result.value; // Promise<User>, not User
console.log(user.name, result.metadata);
}
```
### Indexes
Define indexes in `getStore()` to enable lookups by secondary keys. The third type parameter is a union of index names.
```typescript
const users = kv.getStore<User, undefined, "byEmail" | "byRole">("users/", {
byEmail: { key: (u) => u.email, unique: true },
byRole: { key: (u) => u.role },
});
// Exact match on unique index — returns single result
const result = await users.get({ byEmail: "alice@example.com" });
// Exact match on non-unique index — iterate multiple results
for await (const key of users.keys({ byRole: "admin" })) { ... }
// Prefix scan — sorted iteration over a range
for await (const [key, entry] of users.entries({ byRole: { prefix: "admin" } })) { ... }
// Empty prefix — all entries sorted by index key
for await (const key of users.keys({ byRole: { prefix: "" } })) { ... }
```
Composite index keys enable "group by X, sort by Y" patterns. Use `/` separators between fields and invert values for descending order (see `docs/indexes.md` for details).
```typescript
const MAX_TS = 8_640_000_000_000;
const sessions = kv.getStore<Session, undefined, "byUserNewest">("sessions/", {
byUserNewest: {
key: (s) => `${s.userId}/${String(MAX_TS - Date.parse(s.createdAt)).padStart(14, "0")}`,
},
});
// user-42's most recent sessions first
sessions.entries({ byUserNewest: { prefix: "user-42/" } })
```
### Iteration and pagination
```typescript
// Async iteration
for await (const [key, entry] of users.entries()) {
console.log(key, await entry.value);
}
// Cursor-based pagination
const page1 = await users.keys().page(20);
const page2 = await users.keys().page(20, page1.cursor);
```
### Safe updates with optimistic locking
Use `entry.update()` to do read-modify-write with automatic version checking. Throws `KVVersionConflictError` on conflict.
```typescript
const result = await users.get("alice");
if (result.exists) {
const user = await result.value;
await result.update({ ...user, role: "admin" });
}
```
## Docs

@@ -31,17 +101,17 @@

| File | Topic |
|------|-------|
| `getting-started.md` | Installation, quick start, environment setup |
| `typed-stores.md` | Type-safe sub-stores, key prefixing, nesting |
| `iterating-and-pagination.md` | keys, entries, getMany, cursor pagination |
| `optimistic-locking.md` | Versions, conflict detection, retry patterns |
| `metadata.md` | Typed per-entry metadata |
| `schema-and-trees.md` | defineSchema, tree loading, key builders |
| `indexes.md` | Secondary indexes, unique constraints, reindexing |
| `caching.md` | Cache hierarchy, TTL, custom cache |
| `streaming.md` | Binary format, large values, streaming reads/writes |
| `copy-on-write-branches.md` | Branch isolation, upstream config |
| `testing-and-tracing.md` | Unit testing, tracers, stats |
| `cli.md` | CLI explorer reference |
| `api-reference.md` | Full interface and options documentation |
| File | Topics |
|------|--------|
| `getting-started.md` | Installation, prerequisites, environment variables (`BLOB_READ_WRITE_TOKEN`), how edge caching and CoW branching work |
| `typed-stores.md` | `getStore<V>()`, type-safe values, automatic key prefixing, nested/hierarchical stores |
| `iterating-and-pagination.md` | `keys()`, `entries()`, `getMany()`, prefix filtering, cursor-based `page()` pagination, Next.js API route example |
| `optimistic-locking.md` | Versions/etags, `entry.update()`, read-modify-write retry loops, `expectedVersion`, create-only writes (`override: false`) |
| `metadata.md` | Typed per-entry metadata via `createKV<M>()`, metadata inheritance in sub-stores, filtering by metadata without loading values |
| `schema-and-trees.md` | `defineSchema()`, `createSchemaKV()`, hierarchical entity models, type-safe key builders, `tree()` loading with lazy children |
| `indexes.md` | Defining indexes, unique constraints (`KVIndexConflictError`), multi-value indexes (arrays), prefix queries for sorted iteration, composite index keys (group + sort), key design (padding, DESC via inverted keys, separators), `reindex()`, orphan self-healing |
| `caching.md` | Write-through caching, tag-based invalidation, cache hierarchy (Runtime Cache, MemoryCache, ProxyCache), TTL configuration |
| `streaming.md` | `result.stream` for large value reads, `ReadableStream` writes, binary format, `largeValueThreshold` (default 1MB) |
| `copy-on-write-branches.md` | Preview branch data isolation, virtual forking with upstream fallback, tombstones, automatic environment detection, ManifestLog, branch name encoding |
| `testing-and-tracing.md` | `FakeBlobStore` for unit tests, integration tests with `INTEGRATION_TEST=1`, tracers: no-op, console, OpenTelemetry (`createOtelTracer`), stats (`createStatsTracer`) |
| `cli.md` | `kv2` CLI: `keys`, `get`, `set`, `del` commands, `--prefix`/`--env`/`--branch` options, interactive REPL, stdout/stderr contract for piping |
| `api-reference.md` | Full API: `createKV()`, `KVLike`, `KVGetResult`, `SetOptions`, `TypedKV`, `KV2`, `UpstreamKV`, `SchemaKV`, `KeysIterable`, `EntriesIterable`, error classes, environment variables |

@@ -62,3 +132,3 @@ ## Types

| `cached-kv.d.ts` | `KV2` class |
| `typed-kv.d.ts` | `TypedKV` class, `IndexDef`, `IndexQuery` |
| `typed-kv.d.ts` | `TypedKV` class, `IndexDef`, `IndexQuery`, `IndexQueryValue` |
| `create-kv.d.ts` | `createKV()`, `CreateKVOptions`, `UpstreamConfig` |

@@ -65,0 +135,0 @@ | `upstream-kv.d.ts` | `UpstreamKV` class |