@vercel/kv2 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +87 -0
  2. package/SKILL.md +65 -0
  3. package/dist/blob-format.d.ts +35 -0
  4. package/dist/blob-format.d.ts.map +1 -0
  5. package/dist/blob-format.js +91 -0
  6. package/dist/blob-format.js.map +1 -0
  7. package/dist/blob-store.d.ts +11 -0
  8. package/dist/blob-store.d.ts.map +1 -0
  9. package/dist/blob-store.js +32 -0
  10. package/dist/blob-store.js.map +1 -0
  11. package/dist/cache.d.ts +33 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +146 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/cached-kv.d.ts +63 -0
  16. package/dist/cached-kv.d.ts.map +1 -0
  17. package/dist/cached-kv.js +891 -0
  18. package/dist/cached-kv.js.map +1 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +342 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/create-kv.d.ts +86 -0
  24. package/dist/create-kv.d.ts.map +1 -0
  25. package/dist/create-kv.js +125 -0
  26. package/dist/create-kv.js.map +1 -0
  27. package/dist/disk-cache.d.ts.map +1 -0
  28. package/dist/disk-cache.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +13 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/indexed-kv.d.ts +44 -0
  34. package/dist/indexed-kv.d.ts.map +1 -0
  35. package/dist/indexed-kv.js +373 -0
  36. package/dist/indexed-kv.js.map +1 -0
  37. package/dist/manifest-log.d.ts +57 -0
  38. package/dist/manifest-log.d.ts.map +1 -0
  39. package/dist/manifest-log.js +128 -0
  40. package/dist/manifest-log.js.map +1 -0
  41. package/dist/memory-cache.d.ts +22 -0
  42. package/dist/memory-cache.d.ts.map +1 -0
  43. package/dist/memory-cache.js +90 -0
  44. package/dist/memory-cache.js.map +1 -0
  45. package/dist/proxy-cache.d.ts +40 -0
  46. package/dist/proxy-cache.d.ts.map +1 -0
  47. package/dist/proxy-cache.js +124 -0
  48. package/dist/proxy-cache.js.map +1 -0
  49. package/dist/readme.test.d.ts +9 -0
  50. package/dist/readme.test.d.ts.map +1 -0
  51. package/dist/readme.test.js +285 -0
  52. package/dist/readme.test.js.map +1 -0
  53. package/dist/schema/define-schema.d.ts +35 -0
  54. package/dist/schema/define-schema.d.ts.map +1 -0
  55. package/dist/schema/define-schema.js +70 -0
  56. package/dist/schema/define-schema.js.map +1 -0
  57. package/dist/schema/index.d.ts +4 -0
  58. package/dist/schema/index.d.ts.map +1 -0
  59. package/dist/schema/index.js +5 -0
  60. package/dist/schema/index.js.map +1 -0
  61. package/dist/schema/key-builders.d.ts +40 -0
  62. package/dist/schema/key-builders.d.ts.map +1 -0
  63. package/dist/schema/key-builders.js +124 -0
  64. package/dist/schema/key-builders.js.map +1 -0
  65. package/dist/schema/schema-kv.d.ts +48 -0
  66. package/dist/schema/schema-kv.d.ts.map +1 -0
  67. package/dist/schema/schema-kv.js +96 -0
  68. package/dist/schema/schema-kv.js.map +1 -0
  69. package/dist/schema/tree.d.ts +14 -0
  70. package/dist/schema/tree.d.ts.map +1 -0
  71. package/dist/schema/tree.js +135 -0
  72. package/dist/schema/tree.js.map +1 -0
  73. package/dist/schema/types.d.ts +135 -0
  74. package/dist/schema/types.d.ts.map +1 -0
  75. package/dist/schema/types.js +2 -0
  76. package/dist/schema/types.js.map +1 -0
  77. package/dist/testing/core-tests.d.ts +30 -0
  78. package/dist/testing/core-tests.d.ts.map +1 -0
  79. package/dist/testing/core-tests.js +383 -0
  80. package/dist/testing/core-tests.js.map +1 -0
  81. package/dist/testing/create-kv-test-setup.d.ts +21 -0
  82. package/dist/testing/create-kv-test-setup.d.ts.map +1 -0
  83. package/dist/testing/create-kv-test-setup.js +25 -0
  84. package/dist/testing/create-kv-test-setup.js.map +1 -0
  85. package/dist/testing/debug-manifest.d.ts +2 -0
  86. package/dist/testing/debug-manifest.d.ts.map +1 -0
  87. package/dist/testing/debug-manifest.js +14 -0
  88. package/dist/testing/debug-manifest.js.map +1 -0
  89. package/dist/testing/fake-blob-store.d.ts +23 -0
  90. package/dist/testing/fake-blob-store.d.ts.map +1 -0
  91. package/dist/testing/fake-blob-store.js +158 -0
  92. package/dist/testing/fake-blob-store.js.map +1 -0
  93. package/dist/testing/fake-cache.d.ts +54 -0
  94. package/dist/testing/fake-cache.d.ts.map +1 -0
  95. package/dist/testing/fake-cache.js +137 -0
  96. package/dist/testing/fake-cache.js.map +1 -0
  97. package/dist/testing/index.d.ts +34 -0
  98. package/dist/testing/index.d.ts.map +1 -0
  99. package/dist/testing/index.js +101 -0
  100. package/dist/testing/index.js.map +1 -0
  101. package/dist/testing/manifest-test-setup.d.ts +22 -0
  102. package/dist/testing/manifest-test-setup.d.ts.map +1 -0
  103. package/dist/testing/manifest-test-setup.js +43 -0
  104. package/dist/testing/manifest-test-setup.js.map +1 -0
  105. package/dist/testing/perf-test.d.ts +13 -0
  106. package/dist/testing/perf-test.d.ts.map +1 -0
  107. package/dist/testing/perf-test.js +101 -0
  108. package/dist/testing/perf-test.js.map +1 -0
  109. package/dist/testing/run-tests.d.ts +2 -0
  110. package/dist/testing/run-tests.d.ts.map +1 -0
  111. package/dist/testing/run-tests.js +141 -0
  112. package/dist/testing/run-tests.js.map +1 -0
  113. package/dist/testing/setup.d.ts +2 -0
  114. package/dist/testing/setup.d.ts.map +1 -0
  115. package/dist/testing/setup.js +3 -0
  116. package/dist/testing/setup.js.map +1 -0
  117. package/dist/testing/test-index.d.ts +28 -0
  118. package/dist/testing/test-index.d.ts.map +1 -0
  119. package/dist/testing/test-index.js +35 -0
  120. package/dist/testing/test-index.js.map +1 -0
  121. package/dist/testing/test-setup.d.ts +32 -0
  122. package/dist/testing/test-setup.d.ts.map +1 -0
  123. package/dist/testing/test-setup.js +72 -0
  124. package/dist/testing/test-setup.js.map +1 -0
  125. package/dist/testing/upstream-kv-test-setup.d.ts +30 -0
  126. package/dist/testing/upstream-kv-test-setup.d.ts.map +1 -0
  127. package/dist/testing/upstream-kv-test-setup.js +66 -0
  128. package/dist/testing/upstream-kv-test-setup.js.map +1 -0
  129. package/dist/testing/vitest-compat.d.ts +92 -0
  130. package/dist/testing/vitest-compat.d.ts.map +1 -0
  131. package/dist/testing/vitest-compat.js +601 -0
  132. package/dist/testing/vitest-compat.js.map +1 -0
  133. package/dist/tracing.d.ts +71 -0
  134. package/dist/tracing.d.ts.map +1 -0
  135. package/dist/tracing.js +232 -0
  136. package/dist/tracing.js.map +1 -0
  137. package/dist/typed-kv.d.ts +120 -0
  138. package/dist/typed-kv.d.ts.map +1 -0
  139. package/dist/typed-kv.js +565 -0
  140. package/dist/typed-kv.js.map +1 -0
  141. package/dist/typed-upstream-kv.d.ts +17 -0
  142. package/dist/typed-upstream-kv.d.ts.map +1 -0
  143. package/dist/typed-upstream-kv.js +38 -0
  144. package/dist/typed-upstream-kv.js.map +1 -0
  145. package/dist/types.d.ts +199 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +23 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/upstream-kv.d.ts +84 -0
  150. package/dist/upstream-kv.d.ts.map +1 -0
  151. package/dist/upstream-kv.js +375 -0
  152. package/dist/upstream-kv.js.map +1 -0
  153. package/docs/api-reference.md +222 -0
  154. package/docs/caching.md +60 -0
  155. package/docs/cli.md +123 -0
  156. package/docs/copy-on-write-branches.md +98 -0
  157. package/docs/getting-started.md +61 -0
  158. package/docs/indexes.md +122 -0
  159. package/docs/iterating-and-pagination.md +93 -0
  160. package/docs/metadata.md +82 -0
  161. package/docs/optimistic-locking.md +72 -0
  162. package/docs/schema-and-trees.md +222 -0
  163. package/docs/streaming.md +61 -0
  164. package/docs/testing-and-tracing.md +141 -0
  165. package/docs/typed-stores.md +68 -0
  166. package/package.json +63 -0
package/docs/cli.md ADDED
@@ -0,0 +1,123 @@
1
+ [Home](../README.md) | [Previous: Testing and Tracing](testing-and-tracing.md) | [Next: API Reference](api-reference.md)
2
+
3
+ # CLI Explorer
4
+
5
+ An interactive command-line tool for exploring and manipulating KV stores. Read-only by default; pass `--allow-writes` to enable mutations.
6
+
7
+ ## Installation
8
+
9
+ The package exposes a `kv2` binary. After installing:
10
+
11
+ ```bash
12
+ npx kv2 help
13
+ ```
14
+
15
+ Or add it as a dev dependency and run via your package manager:
16
+
17
+ ```bash
18
+ npm install -D @vercel/kv2
19
+ npx kv2 keys
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ kv2 [options] [command] [args...]
26
+ kv2 [options] # interactive REPL
27
+ ```
28
+
29
+ ```bash
30
+ # One-shot commands
31
+ kv2 keys # List keys (first 100)
32
+ kv2 keys users/ # List keys under a prefix
33
+ kv2 --all keys # List all keys
34
+ kv2 --limit 500 keys # List first 500 keys
35
+ kv2 get users/alice # Print value as JSON to stdout
36
+ kv2 --verbose get users/alice # Also show version/metadata on stderr
37
+ kv2 --allow-writes set foo '{"a":1}' # Set a value
38
+ kv2 --allow-writes del foo # Delete a key
39
+
40
+ # Interactive REPL
41
+ kv2 # Launches kv2> prompt
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ | Command | Description | Writes? |
47
+ |---------|-------------|---------|
48
+ | `keys [prefix]` | List keys, one per line | No |
49
+ | `get <key>` | Print JSON value to stdout | No |
50
+ | `set <key> <json> [metadata-json]` | Set a value | Yes |
51
+ | `del <key>` | Delete a key | Yes |
52
+ | `help` | Show usage | No |
53
+
54
+ Write commands (`set`, `del`) are rejected unless `--allow-writes` is passed.
55
+
56
+ ## Options
57
+
58
+ | Option | Description | Default |
59
+ |--------|-------------|---------|
60
+ | `--prefix <prefix>` | Key prefix passed to `createKV()` | _(none)_ |
61
+ | `--env <env>` | Override `VERCEL_ENV` | `development` |
62
+ | `--branch <branch>` | Override `VERCEL_GIT_COMMIT_REF` | `local` |
63
+ | `--limit <n>` | Max keys to list | `100` |
64
+ | `--all` | List all keys (no limit) | _(disabled)_ |
65
+ | `--allow-writes` | Enable write operations | _(disabled)_ |
66
+ | `--verbose` | Show metadata and version on `get` | _(disabled)_ |
67
+
68
+ ## Prefix matters
69
+
70
+ The blob path structure is `cached-kv/{env}/{branch}/{prefix}{key}.value`. If your app uses `createKV({ prefix: "myapp/" })`, the CLI must use `--prefix myapp/` to see those keys:
71
+
72
+ ```bash
73
+ kv2 --prefix myapp/ keys
74
+ kv2 --prefix myapp/ get users/alice
75
+ ```
76
+
77
+ ## stdout / stderr contract
78
+
79
+ In one-shot mode, stdout contains only data (for piping) and stderr contains everything else (status, colors, errors).
80
+
81
+ | Command | stdout | stderr |
82
+ |---------|--------|--------|
83
+ | `keys` | one key per line | `N key(s)` count |
84
+ | `get` | JSON value (pretty-printed) | key, version, metadata (with `--verbose`) |
85
+ | `get` (not found) | _(nothing)_ | "Key not found: X" |
86
+ | `set` | _(nothing)_ | "Set X (version: ...)" |
87
+ | `del` | _(nothing)_ | "Deleted X" |
88
+
89
+ This enables piping:
90
+
91
+ ```bash
92
+ kv2 keys | wc -l # Count keys
93
+ kv2 get users/alice | jq .name # Extract field
94
+ kv2 keys | xargs -I{} kv2 get {} # Dump all values
95
+ ```
96
+
97
+ ## Interactive REPL
98
+
99
+ Running `kv2` without a command launches an interactive REPL. On startup it prints the current context:
100
+
101
+ ```
102
+ KV CLI Explorer
103
+ env: development
104
+ branch: local
105
+ prefix: (none)
106
+ writes: disabled
107
+ Type "help" for commands, Ctrl+C to exit.
108
+
109
+ kv2> keys
110
+ kv2> get users/alice
111
+ kv2> exit
112
+ ```
113
+
114
+ The same commands work in REPL mode. Type `exit`, `quit`, or press Ctrl+C to leave.
115
+
116
+ ## Environment
117
+
118
+ The CLI automatically loads `.env.local` via dotenv (if installed). You can also export `BLOB_READ_WRITE_TOKEN` directly:
119
+
120
+ ```bash
121
+ export BLOB_READ_WRITE_TOKEN=vercel_blob_...
122
+ kv2 keys
123
+ ```
@@ -0,0 +1,98 @@
1
+ [Home](../README.md) | [Previous: Streaming](streaming.md) | [Next: Testing and Tracing](testing-and-tracing.md)
2
+
3
+ # Copy-on-Write Branches
4
+
5
+ ## The Problem
6
+
7
+ Most apps store data alongside code, but deployment pipelines treat them very differently. Code is branched, reviewed, and merged — you can test a feature branch in isolation without touching production. Data usually isn't: your preview deployment either points at production data (risky — a bug can corrupt it) or starts with an empty database (useless — you can't test real workflows).
8
+
9
+ ## How Copy-on-Write Solves This
10
+
11
+ Copy-on-write gives every preview branch a virtual fork of production data — without copying anything. On a preview branch:
12
+
13
+ - **Reads** check the branch's local storage first, then transparently fall back to production
14
+ - **Writes** go only to local storage — production is never modified
15
+ - **Deletes** create a tombstone so the key stops falling back to production
16
+
17
+ The result: your preview deployment sees the full production dataset, but any changes it makes are isolated. When you merge, the branch's writes are discarded — the code change is what matters, not the test data.
18
+
19
+ ```typescript
20
+ import { createKV } from "@vercel/kv2";
21
+
22
+ // On preview branch "feature-x":
23
+ const kv = createKV({ prefix: "app/" });
24
+
25
+ // Read falls back to production/main if not found locally
26
+ const user = await kv.get("users/alice"); // Found in production
27
+
28
+ // Write only affects this branch
29
+ await kv.set("users/alice", { name: "Alice Updated" });
30
+
31
+ // Delete creates tombstone (won't fall back to production)
32
+ await kv.delete("users/bob");
33
+ ```
34
+
35
+ ## When to Use This
36
+
37
+ Copy-on-write is most valuable when:
38
+
39
+ - **Preview deployments need realistic data** — test a new dashboard layout against actual content, not seed data
40
+ - **Multiple developers work in parallel** — each branch gets its own isolated overlay, no cross-contamination
41
+ - **You want safe data migrations** — test a schema change against production data in a preview branch before merging
42
+ - **Staging environments** — create a long-lived branch that mirrors production for QA
43
+
44
+ It's less useful when your data is purely ephemeral (e.g. session tokens) or when each environment genuinely needs its own independent dataset.
45
+
46
+ ## Automatic Environment Detection
47
+
48
+ `createKV()` detects the environment from Vercel's built-in variables and sets up upstream automatically — no configuration needed for the common case:
49
+
50
+ | Environment | Behavior |
51
+ |-------------|----------|
52
+ | `production` + `main` | Direct KV2, no upstream (it's the root) |
53
+ | `preview` branch | UpstreamKV with fallback to same env + `main` |
54
+ | Same env and branch as upstream | Direct KV2, no upstream (avoid self-reference) |
55
+
56
+ ## Configuring Upstream
57
+
58
+ ### Custom Upstream
59
+
60
+ Point a branch at a specific upstream environment and branch:
61
+
62
+ ```typescript
63
+ import { createKV } from "@vercel/kv2";
64
+
65
+ const kv = createKV({
66
+ prefix: "app/",
67
+ upstream: { env: "production", branch: "main" },
68
+ });
69
+ ```
70
+
71
+ ### Disabled Upstream
72
+
73
+ For fully isolated environments with no fallback:
74
+
75
+ ```typescript
76
+ import { createKV } from "@vercel/kv2";
77
+
78
+ const kv = createKV({
79
+ prefix: "app/",
80
+ upstream: null, // no fallback — isolated environment
81
+ });
82
+ ```
83
+
84
+ ## The Manifest
85
+
86
+ `UpstreamKV` uses a `ManifestLog` to track which keys have been written or deleted locally. On read, it checks the manifest to decide whether to read from local storage or fall back to upstream.
87
+
88
+ The manifest is stored in a separate blob namespace (`_manifest/{env}/{branch}/`) to avoid interfering with data key listings.
89
+
90
+ ## Branch Name Encoding
91
+
92
+ Branch names are URL-encoded and lowercased to be filesystem-safe. Long branch names (> 64 chars) are truncated with a hash suffix to preserve uniqueness:
93
+
94
+ ```
95
+ feature/foo → feature%2Ffoo
96
+ feature-foo → feature-foo
97
+ very-long-name… → very-long-name…-<8-char-hash>
98
+ ```
@@ -0,0 +1,61 @@
1
+ [Home](../README.md) | [Next: Iterating and Pagination](iterating-and-pagination.md)
2
+
3
+ # Getting Started
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vercel/kv2
9
+ # or
10
+ pnpm add @vercel/kv2
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { createKV } from "@vercel/kv2";
17
+
18
+ // Creates KV with automatic upstream fallback to production/main
19
+ const kv = createKV({ prefix: "myapp/" });
20
+
21
+ interface User {
22
+ name: string;
23
+ email: string;
24
+ }
25
+
26
+ // Type-safe sub-store
27
+ const users = kv.getStore<User>("users/");
28
+
29
+ await users.set("alice", { name: "Alice", email: "alice@example.com" });
30
+
31
+ const result = await users.get("alice");
32
+ if (result.exists) {
33
+ console.log((await result.value).name); // "Alice"
34
+ }
35
+ ```
36
+
37
+ ## Environment Setup
38
+
39
+ `@vercel/kv2` requires a Vercel Blob token and uses environment variables for automatic branch detection:
40
+
41
+ | Variable | Required | Description |
42
+ |----------|----------|-------------|
43
+ | `BLOB_READ_WRITE_TOKEN` | Yes | Vercel Blob access token |
44
+ | `VERCEL_ENV` | Auto | Set by Vercel (`production`, `preview`, `development`) |
45
+ | `VERCEL_GIT_COMMIT_REF` | Auto | Current git branch, set by Vercel |
46
+
47
+ For local development, set the token in a `.env` file:
48
+
49
+ ```
50
+ BLOB_READ_WRITE_TOKEN=vercel_blob_...
51
+ ```
52
+
53
+ ## How It Works
54
+
55
+ `@vercel/kv2` stores data as JSON blobs in Vercel Blob storage with an edge cache layer for low-latency reads:
56
+
57
+ 1. **Writes** go directly to Vercel Blob, then invalidate the cache
58
+ 2. **Reads** check the edge cache first, falling back to Blob storage on cache miss
59
+ 3. **Preview branches** use copy-on-write: reads fall back to production data, writes stay isolated to the branch
60
+
61
+ This gives you strong consistency for writes with fast cached reads, while branch isolation enables safe preview deployments without affecting production data.
@@ -0,0 +1,122 @@
1
+ [Home](../README.md) | [Previous: Schema and Trees](schema-and-trees.md) | [Next: Caching](caching.md)
2
+
3
+ # Indexes
4
+
5
+ Secondary indexes allow you to look up entries by attributes other than the primary key.
6
+
7
+ ## Defining Indexes
8
+
9
+ Pass an `indexes` record to `getStore()`. Each index defines a `key` function that extracts the index key from the value:
10
+
11
+ ```typescript
12
+ interface Doc {
13
+ slug: string;
14
+ status: string;
15
+ tags: string[];
16
+ title: string;
17
+ content: string;
18
+ authorId: string;
19
+ }
20
+
21
+ const docs = kv.getStore<Doc, undefined, "bySlug" | "byStatus">("docs/", {
22
+ bySlug: {
23
+ key: (doc) => doc.slug,
24
+ unique: true,
25
+ },
26
+ byStatus: {
27
+ key: (doc) => doc.status,
28
+ },
29
+ });
30
+ ```
31
+
32
+ ## Unique Indexes
33
+
34
+ Set `unique: true` to enforce uniqueness. A `KVIndexConflictError` is thrown if a duplicate is detected:
35
+
36
+ ```typescript
37
+ import { KVIndexConflictError } from "@vercel/kv2";
38
+
39
+ try {
40
+ await docs.set("doc-2", {
41
+ slug: "existing-slug", // already used by another doc
42
+ status: "draft",
43
+ tags: [],
44
+ title: "Duplicate",
45
+ content: "",
46
+ authorId: "author-1",
47
+ });
48
+ } catch (error) {
49
+ if (error instanceof KVIndexConflictError) {
50
+ console.log(error.indexName); // "bySlug"
51
+ console.log(error.indexKey); // "existing-slug"
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Multi-Value Indexes
57
+
58
+ Return an array from the `key` function to index an entry under multiple keys:
59
+
60
+ ```typescript
61
+ interface Doc {
62
+ slug: string;
63
+ status: string;
64
+ tags: string[];
65
+ title: string;
66
+ content: string;
67
+ authorId: string;
68
+ }
69
+
70
+ const docs = kv.getStore<Doc, undefined, "byTag">("docs/", {
71
+ byTag: {
72
+ key: (doc) => doc.tags, // each tag becomes an index entry
73
+ },
74
+ });
75
+
76
+ await docs.set("doc-1", {
77
+ slug: "hello",
78
+ status: "published",
79
+ tags: ["typescript", "tutorial"],
80
+ title: "Hello",
81
+ content: "World",
82
+ authorId: "author-1",
83
+ });
84
+
85
+ // Find all docs tagged "typescript"
86
+ for await (const key of docs.keys({ byTag: "typescript" })) {
87
+ console.log(key); // "doc-1"
88
+ }
89
+ ```
90
+
91
+ ## Non-Unique Indexes
92
+
93
+ Query non-unique indexes with `keys()` and `entries()`:
94
+
95
+ ```typescript
96
+ // Find all published docs
97
+ for await (const key of docs.keys({ byStatus: "published" })) {
98
+ console.log(key);
99
+ }
100
+
101
+ // Iterate entries by index
102
+ for await (const [key, entry] of docs.entries({ byStatus: "draft" })) {
103
+ console.log(key, (await entry.value).title);
104
+ }
105
+ ```
106
+
107
+ ## Index Maintenance
108
+
109
+ Indexes are maintained automatically on `set()` and `delete()`. When you update a value, old index entries are removed and new ones are created.
110
+
111
+ ## Reindexing
112
+
113
+ If indexes are added after data exists, or if index data becomes inconsistent, rebuild indexes with `reindex()`:
114
+
115
+ ```typescript
116
+ const result = await docs.reindex();
117
+ console.log(result.indexed); // number of entries reindexed
118
+ ```
119
+
120
+ ## Orphan Handling
121
+
122
+ Index entries pointing to deleted primary keys are self-healing: they are automatically cleaned up during reads.
@@ -0,0 +1,93 @@
1
+ [Home](../README.md) | [Previous: Getting Started](getting-started.md) | [Next: Typed Stores](typed-stores.md)
2
+
3
+ # Iterating and Pagination
4
+
5
+ ## Iterating Keys
6
+
7
+ Use `keys()` to iterate over all keys in a store:
8
+
9
+ ```typescript
10
+ for await (const key of users.keys()) {
11
+ console.log(key);
12
+ }
13
+ ```
14
+
15
+ Filter by prefix:
16
+
17
+ ```typescript
18
+ for await (const key of kv.keys("users/active/")) {
19
+ console.log(key);
20
+ }
21
+ ```
22
+
23
+ ## Iterating Entries
24
+
25
+ `entries()` fetches values concurrently (default: 20 concurrent requests):
26
+
27
+ ```typescript
28
+ for await (const [key, entry] of users.entries()) {
29
+ console.log(key, await entry.value);
30
+ }
31
+ ```
32
+
33
+ With a prefix filter:
34
+
35
+ ```typescript
36
+ for await (const [key, entry] of users.entries("active/")) {
37
+ console.log(key, entry.metadata);
38
+ }
39
+ ```
40
+
41
+ ## Fetching Multiple Keys
42
+
43
+ Use `getMany()` to fetch specific keys with bounded concurrency:
44
+
45
+ ```typescript
46
+ const results = await users.getMany(["alice", "bob", "charlie"]);
47
+ for (const [key, entry] of results) {
48
+ console.log(key, await entry.value);
49
+ }
50
+ ```
51
+
52
+ ## Cursor-Based Pagination
53
+
54
+ Both `keys()` and `entries()` support cursor-based pagination for HTTP APIs.
55
+
56
+ ### Paginating Keys
57
+
58
+ ```typescript
59
+ const page = await kv.keys("users/").page(10);
60
+ console.log(page.keys); // string[]
61
+ console.log(page.cursor); // string | undefined (pass to next call)
62
+ ```
63
+
64
+ ### Paginating Entries
65
+
66
+ ```typescript
67
+ const page = await kv.entries<User>("users/").page(10);
68
+ for (const [key, entry] of page.entries) {
69
+ console.log(key, await entry.value);
70
+ }
71
+ ```
72
+
73
+ ### Next.js API Route Example
74
+
75
+ ```typescript
76
+ async function GET(req: Request) {
77
+ const url = new URL(req.url);
78
+ const cursor = url.searchParams.get("cursor") ?? undefined;
79
+
80
+ const { keys, cursor: nextCursor } = await users.keys().page(20, cursor);
81
+ const entries = await users.getMany(keys);
82
+
83
+ return Response.json({
84
+ users: await Promise.all(
85
+ keys.map(async (k) => {
86
+ const entry = entries.get(k);
87
+ return entry ? await entry.value : null;
88
+ }),
89
+ ),
90
+ nextCursor,
91
+ });
92
+ }
93
+ ```
@@ -0,0 +1,82 @@
1
+ [Home](../README.md) | [Previous: Optimistic Locking](optimistic-locking.md) | [Next: Schema and Trees](schema-and-trees.md)
2
+
3
+ # Metadata
4
+
5
+ ## Typed Metadata
6
+
7
+ Parameterize `createKV<M>()` with a metadata type. Metadata is stored alongside each entry and is available immediately (no lazy loading):
8
+
9
+ ```typescript
10
+ import { createKV } from "@vercel/kv2";
11
+
12
+ interface Metadata {
13
+ updatedAt: number;
14
+ version: number;
15
+ }
16
+
17
+ interface User {
18
+ name: string;
19
+ email: string;
20
+ }
21
+
22
+ const kv = createKV<Metadata>({ prefix: "app/" });
23
+ const users = kv.getStore<User>("users/");
24
+
25
+ // Metadata is required on set
26
+ await users.set("alice", { name: "Alice", email: "alice@example.com" }, { updatedAt: Date.now(), version: 1 });
27
+
28
+ const result = await users.get("alice");
29
+ if (result.exists) {
30
+ console.log(result.metadata.version); // 1 — available immediately
31
+ console.log((await result.value).name); // "Alice" — lazy loaded
32
+ }
33
+ ```
34
+
35
+ ## Metadata on Sub-Stores
36
+
37
+ Sub-stores inherit the metadata type from their parent:
38
+
39
+ ```typescript
40
+ import { createKV } from "@vercel/kv2";
41
+
42
+ interface Metadata {
43
+ updatedAt: number;
44
+ version: number;
45
+ }
46
+
47
+ interface Post {
48
+ title: string;
49
+ content: string;
50
+ }
51
+
52
+ const kv = createKV<Metadata>({ prefix: "app/" });
53
+ const posts = kv.getStore<Post>("posts/");
54
+
55
+ // Same metadata type required
56
+ await posts.set("hello", { title: "Hello", content: "World" }, { updatedAt: Date.now(), version: 1 });
57
+ ```
58
+
59
+ ## Metadata Without Values
60
+
61
+ Since metadata is available without loading the value, you can filter entries efficiently:
62
+
63
+ ```typescript
64
+ import { createKV } from "@vercel/kv2";
65
+
66
+ interface Metadata {
67
+ updatedAt: number;
68
+ version: number;
69
+ }
70
+
71
+ const kv = createKV<Metadata>({ prefix: "app/" });
72
+
73
+ // Iterate entries and inspect metadata before deciding to load the value
74
+ for await (const [key, entry] of kv.entries("posts/")) {
75
+ // metadata is immediately available
76
+ if (entry.metadata.updatedAt > Date.now() - 86400_000) {
77
+ // Only load the value for recent entries
78
+ const value = await entry.value;
79
+ console.log(key, value);
80
+ }
81
+ }
82
+ ```
@@ -0,0 +1,72 @@
1
+ [Home](../README.md) | [Previous: Typed Stores](typed-stores.md) | [Next: Metadata](metadata.md)
2
+
3
+ # Optimistic Locking
4
+
5
+ ## Why Optimistic Locking?
6
+
7
+ KV stores don't have multi-key transactions the way a SQL database does. If two requests read a value, modify it, and write it back at the same time, one update silently overwrites the other — a lost update.
8
+
9
+ Optimistic locking prevents this: every entry carries a `version` (an etag). When you write, you can say "only succeed if the version is still what I read." If someone else wrote in between, the store rejects your write with a `KVVersionConflictError` and you retry with fresh data. No locks are held, so throughput stays high — conflicts are detected, not prevented.
10
+
11
+ ## Version-Based Updates
12
+
13
+ The simplest API is `entry.update()`. It captures the version at read time and passes it through automatically:
14
+
15
+ ```typescript
16
+ const entry = await users.get("alice");
17
+ if (entry.exists) {
18
+ const user = await entry.value;
19
+ user.name = "Alice Updated";
20
+
21
+ // Throws KVVersionConflictError if another process modified it
22
+ await entry.update(user);
23
+ }
24
+ ```
25
+
26
+ ## Transactions via Read-Modify-Write
27
+
28
+ Because there are no multi-key locks, the pattern for a "transaction" is a retry loop: read the current state, compute the new state, attempt the write, and retry if a conflict is detected. This is safe for any single-key mutation:
29
+
30
+ ```typescript
31
+ import { KVVersionConflictError } from "@vercel/kv2";
32
+
33
+ async function incrementCounter(key: string): Promise<number> {
34
+ for (let attempt = 0; attempt < 3; attempt++) {
35
+ const entry = await kv.get<{ count: number }>(key);
36
+ if (!entry.exists) throw new Error("Key not found");
37
+
38
+ const current = await entry.value;
39
+ try {
40
+ await entry.update({ count: current.count + 1 });
41
+ return current.count + 1;
42
+ } catch (error) {
43
+ if (error instanceof KVVersionConflictError) continue;
44
+ throw error;
45
+ }
46
+ }
47
+ throw new Error("Max retries exceeded");
48
+ }
49
+ ```
50
+
51
+ This is the same approach databases like DynamoDB and CouchDB use for conditional writes. It works well when conflicts are rare (most workloads) and degrades gracefully when they aren't — retries are cheap because reads are cached.
52
+
53
+ ## Manual Version Control
54
+
55
+ For more control, pass `expectedVersion` in `SetOptions` directly:
56
+
57
+ ```typescript
58
+ const { version } = await kv.set("counter", { count: 0 }, metadata);
59
+
60
+ // Later, conditionally update only if version matches
61
+ await kv.set("counter", { count: 1 }, metadata, { expectedVersion: version });
62
+ ```
63
+
64
+ This is useful when the version comes from an external source (e.g. a form submission that includes the etag it was editing).
65
+
66
+ ## Create-Only Writes
67
+
68
+ Use `override: false` to ensure a key is only written if it doesn't already exist — useful for generating unique IDs or claiming resources:
69
+
70
+ ```typescript
71
+ await kv.set("user/new-id", userData, metadata, { override: false });
72
+ ```