@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.
- package/README.md +87 -0
- package/SKILL.md +65 -0
- package/dist/blob-format.d.ts +35 -0
- package/dist/blob-format.d.ts.map +1 -0
- package/dist/blob-format.js +91 -0
- package/dist/blob-format.js.map +1 -0
- package/dist/blob-store.d.ts +11 -0
- package/dist/blob-store.d.ts.map +1 -0
- package/dist/blob-store.js +32 -0
- package/dist/blob-store.js.map +1 -0
- package/dist/cache.d.ts +33 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +146 -0
- package/dist/cache.js.map +1 -0
- package/dist/cached-kv.d.ts +63 -0
- package/dist/cached-kv.d.ts.map +1 -0
- package/dist/cached-kv.js +891 -0
- package/dist/cached-kv.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +342 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-kv.d.ts +86 -0
- package/dist/create-kv.d.ts.map +1 -0
- package/dist/create-kv.js +125 -0
- package/dist/create-kv.js.map +1 -0
- package/dist/disk-cache.d.ts.map +1 -0
- package/dist/disk-cache.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/indexed-kv.d.ts +44 -0
- package/dist/indexed-kv.d.ts.map +1 -0
- package/dist/indexed-kv.js +373 -0
- package/dist/indexed-kv.js.map +1 -0
- package/dist/manifest-log.d.ts +57 -0
- package/dist/manifest-log.d.ts.map +1 -0
- package/dist/manifest-log.js +128 -0
- package/dist/manifest-log.js.map +1 -0
- package/dist/memory-cache.d.ts +22 -0
- package/dist/memory-cache.d.ts.map +1 -0
- package/dist/memory-cache.js +90 -0
- package/dist/memory-cache.js.map +1 -0
- package/dist/proxy-cache.d.ts +40 -0
- package/dist/proxy-cache.d.ts.map +1 -0
- package/dist/proxy-cache.js +124 -0
- package/dist/proxy-cache.js.map +1 -0
- package/dist/readme.test.d.ts +9 -0
- package/dist/readme.test.d.ts.map +1 -0
- package/dist/readme.test.js +285 -0
- package/dist/readme.test.js.map +1 -0
- package/dist/schema/define-schema.d.ts +35 -0
- package/dist/schema/define-schema.d.ts.map +1 -0
- package/dist/schema/define-schema.js +70 -0
- package/dist/schema/define-schema.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/key-builders.d.ts +40 -0
- package/dist/schema/key-builders.d.ts.map +1 -0
- package/dist/schema/key-builders.js +124 -0
- package/dist/schema/key-builders.js.map +1 -0
- package/dist/schema/schema-kv.d.ts +48 -0
- package/dist/schema/schema-kv.d.ts.map +1 -0
- package/dist/schema/schema-kv.js +96 -0
- package/dist/schema/schema-kv.js.map +1 -0
- package/dist/schema/tree.d.ts +14 -0
- package/dist/schema/tree.d.ts.map +1 -0
- package/dist/schema/tree.js +135 -0
- package/dist/schema/tree.js.map +1 -0
- package/dist/schema/types.d.ts +135 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +2 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/testing/core-tests.d.ts +30 -0
- package/dist/testing/core-tests.d.ts.map +1 -0
- package/dist/testing/core-tests.js +383 -0
- package/dist/testing/core-tests.js.map +1 -0
- package/dist/testing/create-kv-test-setup.d.ts +21 -0
- package/dist/testing/create-kv-test-setup.d.ts.map +1 -0
- package/dist/testing/create-kv-test-setup.js +25 -0
- package/dist/testing/create-kv-test-setup.js.map +1 -0
- package/dist/testing/debug-manifest.d.ts +2 -0
- package/dist/testing/debug-manifest.d.ts.map +1 -0
- package/dist/testing/debug-manifest.js +14 -0
- package/dist/testing/debug-manifest.js.map +1 -0
- package/dist/testing/fake-blob-store.d.ts +23 -0
- package/dist/testing/fake-blob-store.d.ts.map +1 -0
- package/dist/testing/fake-blob-store.js +158 -0
- package/dist/testing/fake-blob-store.js.map +1 -0
- package/dist/testing/fake-cache.d.ts +54 -0
- package/dist/testing/fake-cache.d.ts.map +1 -0
- package/dist/testing/fake-cache.js +137 -0
- package/dist/testing/fake-cache.js.map +1 -0
- package/dist/testing/index.d.ts +34 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +101 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/manifest-test-setup.d.ts +22 -0
- package/dist/testing/manifest-test-setup.d.ts.map +1 -0
- package/dist/testing/manifest-test-setup.js +43 -0
- package/dist/testing/manifest-test-setup.js.map +1 -0
- package/dist/testing/perf-test.d.ts +13 -0
- package/dist/testing/perf-test.d.ts.map +1 -0
- package/dist/testing/perf-test.js +101 -0
- package/dist/testing/perf-test.js.map +1 -0
- package/dist/testing/run-tests.d.ts +2 -0
- package/dist/testing/run-tests.d.ts.map +1 -0
- package/dist/testing/run-tests.js +141 -0
- package/dist/testing/run-tests.js.map +1 -0
- package/dist/testing/setup.d.ts +2 -0
- package/dist/testing/setup.d.ts.map +1 -0
- package/dist/testing/setup.js +3 -0
- package/dist/testing/setup.js.map +1 -0
- package/dist/testing/test-index.d.ts +28 -0
- package/dist/testing/test-index.d.ts.map +1 -0
- package/dist/testing/test-index.js +35 -0
- package/dist/testing/test-index.js.map +1 -0
- package/dist/testing/test-setup.d.ts +32 -0
- package/dist/testing/test-setup.d.ts.map +1 -0
- package/dist/testing/test-setup.js +72 -0
- package/dist/testing/test-setup.js.map +1 -0
- package/dist/testing/upstream-kv-test-setup.d.ts +30 -0
- package/dist/testing/upstream-kv-test-setup.d.ts.map +1 -0
- package/dist/testing/upstream-kv-test-setup.js +66 -0
- package/dist/testing/upstream-kv-test-setup.js.map +1 -0
- package/dist/testing/vitest-compat.d.ts +92 -0
- package/dist/testing/vitest-compat.d.ts.map +1 -0
- package/dist/testing/vitest-compat.js +601 -0
- package/dist/testing/vitest-compat.js.map +1 -0
- package/dist/tracing.d.ts +71 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +232 -0
- package/dist/tracing.js.map +1 -0
- package/dist/typed-kv.d.ts +120 -0
- package/dist/typed-kv.d.ts.map +1 -0
- package/dist/typed-kv.js +565 -0
- package/dist/typed-kv.js.map +1 -0
- package/dist/typed-upstream-kv.d.ts +17 -0
- package/dist/typed-upstream-kv.d.ts.map +1 -0
- package/dist/typed-upstream-kv.js +38 -0
- package/dist/typed-upstream-kv.js.map +1 -0
- package/dist/types.d.ts +199 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/upstream-kv.d.ts +84 -0
- package/dist/upstream-kv.d.ts.map +1 -0
- package/dist/upstream-kv.js +375 -0
- package/dist/upstream-kv.js.map +1 -0
- package/docs/api-reference.md +222 -0
- package/docs/caching.md +60 -0
- package/docs/cli.md +123 -0
- package/docs/copy-on-write-branches.md +98 -0
- package/docs/getting-started.md +61 -0
- package/docs/indexes.md +122 -0
- package/docs/iterating-and-pagination.md +93 -0
- package/docs/metadata.md +82 -0
- package/docs/optimistic-locking.md +72 -0
- package/docs/schema-and-trees.md +222 -0
- package/docs/streaming.md +61 -0
- package/docs/testing-and-tracing.md +141 -0
- package/docs/typed-stores.md +68 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @vercel/kv2
|
|
2
|
+
|
|
3
|
+
A type-safe key-value store backed by Vercel Blob with edge caching and copy-on-write branch isolation.
|
|
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
|
+
const kv = createKV({ prefix: "myapp/" });
|
|
19
|
+
|
|
20
|
+
interface User {
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const users = kv.getStore<User>("users/");
|
|
26
|
+
|
|
27
|
+
await users.set("alice", { name: "Alice", email: "alice@example.com" });
|
|
28
|
+
|
|
29
|
+
const result = await users.get("alice");
|
|
30
|
+
if (result.exists) {
|
|
31
|
+
console.log((await result.value).name); // "Alice"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Delete, iterate keys, entries, getMany — see docs
|
|
35
|
+
await users.delete("alice");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
| Feature | Description | Docs |
|
|
41
|
+
|---------|-------------|------|
|
|
42
|
+
| **Typed Stores** | Type-safe sub-stores with automatic key prefixing | [Typed Stores](docs/typed-stores.md) |
|
|
43
|
+
| **Iteration** | `entries()` and `getMany()` with bounded concurrency | [Iterating and Pagination](docs/iterating-and-pagination.md) |
|
|
44
|
+
| **Pagination** | Cursor-based pagination for HTTP APIs | [Iterating and Pagination](docs/iterating-and-pagination.md) |
|
|
45
|
+
| **Optimistic Locking** | Version-based conflict detection and retry | [Optimistic Locking](docs/optimistic-locking.md) |
|
|
46
|
+
| **Metadata** | Typed per-entry metadata, available without loading values | [Metadata](docs/metadata.md) |
|
|
47
|
+
| **Schema & Trees** | Hierarchical data with batched tree loading | [Schema and Trees](docs/schema-and-trees.md) |
|
|
48
|
+
| **Indexes** | Secondary indexes with unique constraints | [Indexes](docs/indexes.md) |
|
|
49
|
+
| **Edge Caching** | Write-through cache with tag invalidation | [Caching](docs/caching.md) |
|
|
50
|
+
| **Streaming** | Large values streamed without buffering | [Streaming](docs/streaming.md) |
|
|
51
|
+
| **Copy-on-Write** | Preview branches inherit from production | [Copy-on-Write Branches](docs/copy-on-write-branches.md) |
|
|
52
|
+
| **CLI Explorer** | Interactive KV store explorer for debugging | [CLI](docs/cli.md) |
|
|
53
|
+
|
|
54
|
+
## Documentation
|
|
55
|
+
|
|
56
|
+
1. [Getting Started](docs/getting-started.md) — installation, quick start, environment setup
|
|
57
|
+
2. [Iterating and Pagination](docs/iterating-and-pagination.md) — keys, entries, getMany, cursor pagination
|
|
58
|
+
3. [Typed Stores](docs/typed-stores.md) — getStore, key prefixing, nested stores
|
|
59
|
+
4. [Optimistic Locking](docs/optimistic-locking.md) — versions, conflict detection, retry patterns
|
|
60
|
+
5. [Metadata](docs/metadata.md) — typed metadata, filtering without loading values
|
|
61
|
+
6. [Schema and Trees](docs/schema-and-trees.md) — defineSchema, tree loading, key builders
|
|
62
|
+
7. [Indexes](docs/indexes.md) — secondary indexes, unique constraints, reindexing
|
|
63
|
+
8. [Caching](docs/caching.md) — cache hierarchy, TTL, custom cache
|
|
64
|
+
9. [Streaming](docs/streaming.md) — binary format, large values, streaming reads/writes
|
|
65
|
+
10. [Copy-on-Write Branches](docs/copy-on-write-branches.md) — branch isolation, upstream config
|
|
66
|
+
11. [Testing and Tracing](docs/testing-and-tracing.md) — FakeBlobStore, tracers, stats
|
|
67
|
+
12. [CLI](docs/cli.md) — interactive KV store explorer
|
|
68
|
+
13. [API Reference](docs/api-reference.md) — full interface and options documentation
|
|
69
|
+
|
|
70
|
+
## Environment Variables
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
BLOB_READ_WRITE_TOKEN=vercel_blob_...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See [Getting Started](docs/getting-started.md) for full environment setup.
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm test # Unit tests (fake blob store)
|
|
82
|
+
pnpm test:integration # Integration tests (real Vercel Blob)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
ISC
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Using @vercel/kv2
|
|
2
|
+
|
|
3
|
+
## Getting started
|
|
4
|
+
|
|
5
|
+
Read the README for a quick overview and code examples:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
node_modules/@vercel/kv2/README.md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Docs
|
|
12
|
+
|
|
13
|
+
Detailed documentation is available in the package's `docs/` directory. Read files as needed from `node_modules/@vercel/kv2/docs/`:
|
|
14
|
+
|
|
15
|
+
| File | Topic |
|
|
16
|
+
|------|-------|
|
|
17
|
+
| `getting-started.md` | Installation, quick start, environment setup |
|
|
18
|
+
| `typed-stores.md` | Type-safe sub-stores, key prefixing, nesting |
|
|
19
|
+
| `iterating-and-pagination.md` | keys, entries, getMany, cursor pagination |
|
|
20
|
+
| `optimistic-locking.md` | Versions, conflict detection, retry patterns |
|
|
21
|
+
| `metadata.md` | Typed per-entry metadata |
|
|
22
|
+
| `schema-and-trees.md` | defineSchema, tree loading, key builders |
|
|
23
|
+
| `indexes.md` | Secondary indexes, unique constraints, reindexing |
|
|
24
|
+
| `caching.md` | Cache hierarchy, TTL, custom cache |
|
|
25
|
+
| `streaming.md` | Binary format, large values, streaming reads/writes |
|
|
26
|
+
| `copy-on-write-branches.md` | Branch isolation, upstream config |
|
|
27
|
+
| `testing-and-tracing.md` | FakeBlobStore, tracers, stats |
|
|
28
|
+
| `cli.md` | CLI explorer reference |
|
|
29
|
+
| `api-reference.md` | Full interface and options documentation |
|
|
30
|
+
|
|
31
|
+
## Types
|
|
32
|
+
|
|
33
|
+
All public types and interfaces are exported from the main entry point. To look up type signatures, read:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
node_modules/@vercel/kv2/dist/index.d.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Key type definition files in `node_modules/@vercel/kv2/dist/`:
|
|
40
|
+
|
|
41
|
+
| File | Types |
|
|
42
|
+
|------|-------|
|
|
43
|
+
| `types.d.ts` | `KVLike`, `KVEntry`, `KVGetResult`, `KVSetResult`, `KeysIterable`, `KeysPage`, `EntriesIterable`, `SetOptions`, `BlobStore`, `Tracer` |
|
|
44
|
+
| `cached-kv.d.ts` | `KV2` class |
|
|
45
|
+
| `typed-kv.d.ts` | `TypedKV` class, `IndexDef`, `IndexQuery` |
|
|
46
|
+
| `create-kv.d.ts` | `createKV()`, `CreateKVOptions`, `UpstreamConfig` |
|
|
47
|
+
| `upstream-kv.d.ts` | `UpstreamKV` class |
|
|
48
|
+
| `manifest-log.d.ts` | `ManifestLog`, `KeyMeta` |
|
|
49
|
+
| `tracing.d.ts` | Tracer factories, `TimingStats` |
|
|
50
|
+
| `schema/index.d.ts` | `defineSchema`, `createSchemaKV`, `SchemaKV`, `TreeNode` |
|
|
51
|
+
|
|
52
|
+
## Verifying KV state
|
|
53
|
+
|
|
54
|
+
The package ships a `kv2` CLI for inspecting and debugging the KV store:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx kv2 keys # List keys (first 100)
|
|
58
|
+
npx kv2 --all keys # List all keys
|
|
59
|
+
npx kv2 get <key> # Print a value as JSON
|
|
60
|
+
npx kv2 --verbose get <key> # Also show version and metadata
|
|
61
|
+
npx kv2 --allow-writes set <key> <json> # Write a value
|
|
62
|
+
npx kv2 --allow-writes del <key> # Delete a key
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `--prefix <prefix>` if your app uses `createKV({ prefix: "myapp/" })`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StoredEntry } from "./types.js";
|
|
2
|
+
export interface ParsedBlob<M> {
|
|
3
|
+
header: StoredEntry<M>;
|
|
4
|
+
payload: Buffer | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Detects whether a blob uses pure JSON format or binary format.
|
|
8
|
+
* Pure JSON starts with '{' (0x7B), binary format starts with uint32 length.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPureJsonFormat(buffer: Buffer): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Parses a blob buffer into header and optional payload.
|
|
13
|
+
* Automatically detects format based on first byte.
|
|
14
|
+
*
|
|
15
|
+
* @throws {SyntaxError} If JSON parsing fails
|
|
16
|
+
* @throws {RangeError} If header length exceeds buffer size
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseBlob<M>(buffer: Buffer): ParsedBlob<M>;
|
|
19
|
+
/**
|
|
20
|
+
* Creates a blob buffer from header and optional payload.
|
|
21
|
+
* Uses pure JSON format when no payload, binary format otherwise.
|
|
22
|
+
*
|
|
23
|
+
* @throws {Error} If header exceeds MAX_HEADER_SIZE in binary format
|
|
24
|
+
*/
|
|
25
|
+
export declare function createBlob<M>(header: StoredEntry<M>, payload?: Buffer): Buffer;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts just the header from a blob without fully parsing the payload.
|
|
28
|
+
* Useful for metadata-only reads.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseHeader<M>(buffer: Buffer): StoredEntry<M>;
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a blob has a payload (large value stored after header).
|
|
33
|
+
*/
|
|
34
|
+
export declare function hasPayload<M>(header: StoredEntry<M>): boolean;
|
|
35
|
+
//# sourceMappingURL=blob-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-format.d.ts","sourceRoot":"","sources":["../src/blob-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAuC1D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC3B,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,OAAO,CAAC,EAAE,MAAM,GACd,MAAM,CA8BR;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAE7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAE7D"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const HEADER_LENGTH_BYTES = 4;
|
|
2
|
+
// Max header size ensures uint32 BE first byte < 0x7B ('{'), preventing format ambiguity
|
|
3
|
+
// 0x7B000000 = 2,063,597,568 bytes; we use 100MB as a practical limit
|
|
4
|
+
const MAX_HEADER_SIZE = 100 * 1024 * 1024;
|
|
5
|
+
// ASCII code for '{'
|
|
6
|
+
const OPEN_BRACE = 0x7b;
|
|
7
|
+
/**
|
|
8
|
+
* Detects whether a blob uses pure JSON format or binary format.
|
|
9
|
+
* Pure JSON starts with '{' (0x7B), binary format starts with uint32 length.
|
|
10
|
+
*/
|
|
11
|
+
export function isPureJsonFormat(buffer) {
|
|
12
|
+
return buffer.length > 0 && buffer[0] === OPEN_BRACE;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parses a blob buffer into header and optional payload.
|
|
16
|
+
* Automatically detects format based on first byte.
|
|
17
|
+
*
|
|
18
|
+
* @throws {SyntaxError} If JSON parsing fails
|
|
19
|
+
* @throws {RangeError} If header length exceeds buffer size
|
|
20
|
+
*/
|
|
21
|
+
export function parseBlob(buffer) {
|
|
22
|
+
if (buffer.length === 0) {
|
|
23
|
+
throw new Error("Cannot parse empty buffer");
|
|
24
|
+
}
|
|
25
|
+
if (isPureJsonFormat(buffer)) {
|
|
26
|
+
// Pure JSON format: entire buffer is the header
|
|
27
|
+
const header = JSON.parse(buffer.toString("utf-8"));
|
|
28
|
+
return { header, payload: null };
|
|
29
|
+
}
|
|
30
|
+
// Binary format: length-prefixed header + optional payload
|
|
31
|
+
if (buffer.length < HEADER_LENGTH_BYTES) {
|
|
32
|
+
throw new Error(`Buffer too small for binary format: ${buffer.length} bytes`);
|
|
33
|
+
}
|
|
34
|
+
const headerLength = buffer.readUInt32BE(0);
|
|
35
|
+
const headerEnd = HEADER_LENGTH_BYTES + headerLength;
|
|
36
|
+
if (headerEnd > buffer.length) {
|
|
37
|
+
throw new RangeError(`Header length ${headerLength} exceeds buffer size ${buffer.length}`);
|
|
38
|
+
}
|
|
39
|
+
// Parse header JSON
|
|
40
|
+
const headerJson = buffer
|
|
41
|
+
.subarray(HEADER_LENGTH_BYTES, headerEnd)
|
|
42
|
+
.toString("utf-8");
|
|
43
|
+
const header = JSON.parse(headerJson);
|
|
44
|
+
// Extract payload if present (for "raw-*" encodings)
|
|
45
|
+
const hasPayload = header.encoding === "raw-json" || header.encoding === "raw-binary";
|
|
46
|
+
const payload = hasPayload ? buffer.subarray(headerEnd) : null;
|
|
47
|
+
return { header, payload };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a blob buffer from header and optional payload.
|
|
51
|
+
* Uses pure JSON format when no payload, binary format otherwise.
|
|
52
|
+
*
|
|
53
|
+
* @throws {Error} If header exceeds MAX_HEADER_SIZE in binary format
|
|
54
|
+
*/
|
|
55
|
+
export function createBlob(header, payload) {
|
|
56
|
+
const headerJson = JSON.stringify(header);
|
|
57
|
+
const headerBuffer = Buffer.from(headerJson, "utf-8");
|
|
58
|
+
// Use binary format if payload exists OR if encoding indicates payload follows
|
|
59
|
+
const hasPayload = payload ||
|
|
60
|
+
header.encoding === "raw-json" ||
|
|
61
|
+
header.encoding === "raw-binary";
|
|
62
|
+
if (hasPayload) {
|
|
63
|
+
// Safety: header size must be < MAX_HEADER_SIZE to ensure first byte < 0x7B
|
|
64
|
+
if (headerBuffer.length >= MAX_HEADER_SIZE) {
|
|
65
|
+
throw new Error(`Header too large: ${headerBuffer.length} bytes (max ${MAX_HEADER_SIZE})`);
|
|
66
|
+
}
|
|
67
|
+
// Binary format: length-prefixed header + payload
|
|
68
|
+
const lengthBuffer = Buffer.alloc(HEADER_LENGTH_BYTES);
|
|
69
|
+
lengthBuffer.writeUInt32BE(headerBuffer.length, 0);
|
|
70
|
+
if (payload) {
|
|
71
|
+
return Buffer.concat([lengthBuffer, headerBuffer, payload]);
|
|
72
|
+
}
|
|
73
|
+
return Buffer.concat([lengthBuffer, headerBuffer]);
|
|
74
|
+
}
|
|
75
|
+
// Pure JSON format: just the header (no length prefix)
|
|
76
|
+
return headerBuffer;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts just the header from a blob without fully parsing the payload.
|
|
80
|
+
* Useful for metadata-only reads.
|
|
81
|
+
*/
|
|
82
|
+
export function parseHeader(buffer) {
|
|
83
|
+
return parseBlob(buffer).header;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Checks if a blob has a payload (large value stored after header).
|
|
87
|
+
*/
|
|
88
|
+
export function hasPayload(header) {
|
|
89
|
+
return header.encoding === "raw-json" || header.encoding === "raw-binary";
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=blob-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-format.js","sourceRoot":"","sources":["../src/blob-format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type GetBlobResult, type GetCommandOptions, type ListBlobResult, type ListCommandOptions, type PutBlobResult, type PutCommandOptions } from "@vercel/blob";
|
|
2
|
+
import type { BlobStore, PutBody } from "./types.js";
|
|
3
|
+
export declare class VercelBlobStore implements BlobStore {
|
|
4
|
+
private token?;
|
|
5
|
+
constructor(token?: string);
|
|
6
|
+
get(pathname: string, options: GetCommandOptions): Promise<GetBlobResult | null>;
|
|
7
|
+
put(pathname: string, body: PutBody, options: PutCommandOptions): Promise<PutBlobResult>;
|
|
8
|
+
del(urlOrPathname: string | string[]): Promise<void>;
|
|
9
|
+
list(options?: ListCommandOptions): Promise<ListBlobResult>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=blob-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-store.d.ts","sourceRoot":"","sources":["../src/blob-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAKtB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErD,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,KAAK,CAAC,CAAS;gBAEX,KAAK,CAAC,EAAE,MAAM;IAIpB,GAAG,CACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,iBAAiB,GACxB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAQ1B,GAAG,CACR,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,iBAAiB,GACxB,OAAO,CAAC,aAAa,CAAC;IAOnB,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;CAMjE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { del, get, list, put, } from "@vercel/blob";
|
|
2
|
+
export class VercelBlobStore {
|
|
3
|
+
token;
|
|
4
|
+
constructor(token) {
|
|
5
|
+
this.token = token;
|
|
6
|
+
}
|
|
7
|
+
async get(pathname, options) {
|
|
8
|
+
return get(pathname, {
|
|
9
|
+
useCache: false,
|
|
10
|
+
...options,
|
|
11
|
+
token: this.token,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async put(pathname, body, options) {
|
|
15
|
+
return put(pathname, body, {
|
|
16
|
+
...options,
|
|
17
|
+
token: this.token,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async del(urlOrPathname) {
|
|
21
|
+
return del(urlOrPathname, {
|
|
22
|
+
token: this.token,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async list(options) {
|
|
26
|
+
return list({
|
|
27
|
+
...options,
|
|
28
|
+
token: this.token,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=blob-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-store.js","sourceRoot":"","sources":["../src/blob-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAON,GAAG,EACH,GAAG,EACH,IAAI,EACJ,GAAG,GACH,MAAM,cAAc,CAAC;AAGtB,MAAM,OAAO,eAAe;IACnB,KAAK,CAAU;IAEvB,YAAY,KAAc;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,GAAG,CACR,QAAgB,EAChB,OAA0B;QAE1B,OAAO,GAAG,CAAC,QAAQ,EAAE;YACpB,QAAQ,EAAE,KAAK;YACf,GAAG,OAAO;YACV,KAAK,EAAE,IAAI,CAAC,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CACR,QAAgB,EAChB,IAAa,EACb,OAA0B;QAE1B,OAAO,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE;YAC1B,GAAG,OAAO;YACV,KAAK,EAAE,IAAI,CAAC,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAgC;QACzC,OAAO,GAAG,CAAC,aAAa,EAAE;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACtC,OAAO,IAAI,CAAC;YACX,GAAG,OAAO;YACV,KAAK,EAAE,IAAI,CAAC,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;CACD"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CacheLike, CachedEntry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Encode cache keys/tags to be safe for HTTP headers (used by Vercel cache tags).
|
|
4
|
+
* HTTP headers only allow ASCII printable characters (0x20-0x7E), excluding certain chars.
|
|
5
|
+
* We use percent-encoding for non-ASCII and problematic characters, keeping ASCII readable.
|
|
6
|
+
* @internal Exported for testing
|
|
7
|
+
*/
|
|
8
|
+
export declare function encodeCacheKey(path: string): string;
|
|
9
|
+
/** Error handler function type for KVCache error logging */
|
|
10
|
+
export type ErrorHandler = (message: string, error: unknown) => void;
|
|
11
|
+
export interface KVCacheOptions {
|
|
12
|
+
ttl: number;
|
|
13
|
+
/** Optional cache implementation for testing */
|
|
14
|
+
cache?: CacheLike;
|
|
15
|
+
/** Optional error handler for testing (defaults to console.error) */
|
|
16
|
+
onError?: ErrorHandler;
|
|
17
|
+
}
|
|
18
|
+
export declare class KVCache {
|
|
19
|
+
private ttl;
|
|
20
|
+
private useProxy;
|
|
21
|
+
private useMemory;
|
|
22
|
+
private injectedCache;
|
|
23
|
+
private errorHandler;
|
|
24
|
+
constructor(options: KVCacheOptions | number);
|
|
25
|
+
private getCache;
|
|
26
|
+
private getCacheKey;
|
|
27
|
+
private getCacheTag;
|
|
28
|
+
private sleep;
|
|
29
|
+
get<M>(path: string): Promise<CachedEntry<M> | null>;
|
|
30
|
+
set<M>(path: string, entry: CachedEntry<M>): Promise<void>;
|
|
31
|
+
invalidate(path: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAOzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBnD;AAcD,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,qBAAa,OAAO;IACnB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,YAAY,CAAe;gBAEvB,OAAO,EAAE,cAAc,GAAG,MAAM;YAgB9B,QAAQ;IAiBtB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,KAAK;IAIP,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAYpD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB1D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2B7C"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { getMemoryCache, shouldUseMemoryCache } from "./memory-cache.js";
|
|
2
|
+
import { getProxyCache, shouldUseProxyCache } from "./proxy-cache.js";
|
|
3
|
+
const CACHE_KEY_PREFIX = "cached-kv:";
|
|
4
|
+
const MAX_CACHE_SIZE = 1 * 1024 * 1024; // 1MB - keep cache lightweight
|
|
5
|
+
const INVALIDATE_RETRIES = 3;
|
|
6
|
+
const INVALIDATE_BACKOFF_MS = [100, 500, 1000];
|
|
7
|
+
/**
|
|
8
|
+
* Encode cache keys/tags to be safe for HTTP headers (used by Vercel cache tags).
|
|
9
|
+
* HTTP headers only allow ASCII printable characters (0x20-0x7E), excluding certain chars.
|
|
10
|
+
* We use percent-encoding for non-ASCII and problematic characters, keeping ASCII readable.
|
|
11
|
+
* @internal Exported for testing
|
|
12
|
+
*/
|
|
13
|
+
export function encodeCacheKey(path) {
|
|
14
|
+
let result = "";
|
|
15
|
+
for (const char of path) {
|
|
16
|
+
const code = char.charCodeAt(0);
|
|
17
|
+
// Keep ASCII printable chars except %, ", and + (which we use for space encoding)
|
|
18
|
+
if (code >= 0x21 &&
|
|
19
|
+
code <= 0x7e &&
|
|
20
|
+
char !== "%" &&
|
|
21
|
+
char !== '"' &&
|
|
22
|
+
char !== "+") {
|
|
23
|
+
result += char;
|
|
24
|
+
}
|
|
25
|
+
else if (char === " ") {
|
|
26
|
+
result += "+"; // Space as + for readability
|
|
27
|
+
}
|
|
28
|
+
else if (char === "+") {
|
|
29
|
+
result += "%2B"; // Encode + to avoid collision with space
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Percent-encode everything else (unicode, control chars, etc.)
|
|
33
|
+
const encoded = encodeURIComponent(char);
|
|
34
|
+
result += encoded;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
// Lazy import to avoid @vercel/functions initialization message when using proxy
|
|
40
|
+
let vercelCache = null;
|
|
41
|
+
async function getVercelCache() {
|
|
42
|
+
if (!vercelCache) {
|
|
43
|
+
const { getCache } = await import("@vercel/functions");
|
|
44
|
+
vercelCache = getCache();
|
|
45
|
+
}
|
|
46
|
+
return vercelCache;
|
|
47
|
+
}
|
|
48
|
+
let loggedProxyUsage = false;
|
|
49
|
+
export class KVCache {
|
|
50
|
+
ttl;
|
|
51
|
+
useProxy;
|
|
52
|
+
useMemory;
|
|
53
|
+
injectedCache;
|
|
54
|
+
errorHandler;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
if (typeof options === "number") {
|
|
57
|
+
// Legacy: just TTL
|
|
58
|
+
this.ttl = options;
|
|
59
|
+
this.injectedCache = null;
|
|
60
|
+
this.errorHandler = (msg, err) => console.error(msg, err);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.ttl = options.ttl;
|
|
64
|
+
this.injectedCache = options.cache ?? null;
|
|
65
|
+
this.errorHandler =
|
|
66
|
+
options.onError ?? ((msg, err) => console.error(msg, err));
|
|
67
|
+
}
|
|
68
|
+
this.useProxy = shouldUseProxyCache();
|
|
69
|
+
this.useMemory = shouldUseMemoryCache();
|
|
70
|
+
}
|
|
71
|
+
async getCache() {
|
|
72
|
+
if (this.injectedCache) {
|
|
73
|
+
return this.injectedCache;
|
|
74
|
+
}
|
|
75
|
+
if (this.useProxy) {
|
|
76
|
+
if (!loggedProxyUsage) {
|
|
77
|
+
console.log("[KVCache] Using proxy cache for integration tests");
|
|
78
|
+
loggedProxyUsage = true;
|
|
79
|
+
}
|
|
80
|
+
return getProxyCache();
|
|
81
|
+
}
|
|
82
|
+
if (this.useMemory) {
|
|
83
|
+
return getMemoryCache();
|
|
84
|
+
}
|
|
85
|
+
return getVercelCache();
|
|
86
|
+
}
|
|
87
|
+
getCacheKey(path) {
|
|
88
|
+
return `${CACHE_KEY_PREFIX}${encodeCacheKey(path)}`;
|
|
89
|
+
}
|
|
90
|
+
getCacheTag(path) {
|
|
91
|
+
return `${CACHE_KEY_PREFIX}${encodeCacheKey(path)}`;
|
|
92
|
+
}
|
|
93
|
+
sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
async get(path) {
|
|
97
|
+
try {
|
|
98
|
+
const cache = await this.getCache();
|
|
99
|
+
const key = this.getCacheKey(path);
|
|
100
|
+
const result = await cache.get(key);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
this.errorHandler("[KVCache] cache read failed:", err);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async set(path, entry) {
|
|
109
|
+
// Don't cache entries larger than max size
|
|
110
|
+
if (entry.size > MAX_CACHE_SIZE) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const cache = await this.getCache();
|
|
115
|
+
const key = this.getCacheKey(path);
|
|
116
|
+
const tag = this.getCacheTag(path);
|
|
117
|
+
await cache.set(key, entry, {
|
|
118
|
+
tags: [tag],
|
|
119
|
+
ttl: this.ttl,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
this.errorHandler("[KVCache] cache write failed:", err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async invalidate(path) {
|
|
127
|
+
const tag = this.getCacheTag(path);
|
|
128
|
+
let lastError;
|
|
129
|
+
for (let attempt = 0; attempt < INVALIDATE_RETRIES; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
const cache = await this.getCache();
|
|
132
|
+
await cache.expireTag(tag);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
lastError = err;
|
|
137
|
+
this.errorHandler(`[KVCache] invalidation failed (attempt ${attempt + 1}/${INVALIDATE_RETRIES}):`, err);
|
|
138
|
+
if (attempt < INVALIDATE_RETRIES - 1) {
|
|
139
|
+
await this.sleep(INVALIDATE_BACKOFF_MS[attempt]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
this.errorHandler(`[KVCache] invalidation failed after ${INVALIDATE_RETRIES} retries, giving up:`, lastError);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { TypedKV } from "./typed-kv.js";
|
|
2
|
+
import type { EntriesIterable, KV2Options, KVEntry, KVGetResult, KVLike, KVSetResult, KeysIterable, SetOptions } from "./types.js";
|
|
3
|
+
export declare class KV2<M = undefined> implements KVLike<M> {
|
|
4
|
+
private prefix;
|
|
5
|
+
private blobStore;
|
|
6
|
+
private cache;
|
|
7
|
+
private largeValueThreshold;
|
|
8
|
+
private tracer;
|
|
9
|
+
constructor(options?: KV2Options);
|
|
10
|
+
private getFullPath;
|
|
11
|
+
private getListPrefix;
|
|
12
|
+
private stripPrefix;
|
|
13
|
+
private readBlob;
|
|
14
|
+
/**
|
|
15
|
+
* Reads blob header without buffering the entire payload.
|
|
16
|
+
* For binary format (large values), returns a reader positioned at the payload.
|
|
17
|
+
* For pure JSON format (small values), returns the complete buffer.
|
|
18
|
+
*/
|
|
19
|
+
private readBlobStreaming;
|
|
20
|
+
private readBlobWithConsistencyCheck;
|
|
21
|
+
/**
|
|
22
|
+
* Streaming version of readBlobWithConsistencyCheck.
|
|
23
|
+
* Returns header and a reader for the payload without buffering.
|
|
24
|
+
*/
|
|
25
|
+
private readBlobStreamingWithConsistencyCheck;
|
|
26
|
+
private sleep;
|
|
27
|
+
private serializeValue;
|
|
28
|
+
private deserializeValue;
|
|
29
|
+
get<V = unknown>(key: string): Promise<KVGetResult<V, M>>;
|
|
30
|
+
/**
|
|
31
|
+
* Creates a result from a fully-buffered blob (pure JSON format or cached).
|
|
32
|
+
*/
|
|
33
|
+
private createResultFromBuffer;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a result with true streaming for binary format (large values).
|
|
36
|
+
* The payload is streamed directly from the blob store without buffering.
|
|
37
|
+
*/
|
|
38
|
+
private createStreamingResult;
|
|
39
|
+
private resolveValue;
|
|
40
|
+
private getPayloadBytes;
|
|
41
|
+
set<V = unknown>(key: string, value: V | ReadableStream<Uint8Array>, ...[metadata, options]: undefined extends M ? [M?, SetOptions?] : [M, SetOptions?]): Promise<KVSetResult>;
|
|
42
|
+
private concatStreams;
|
|
43
|
+
delete(key: string): Promise<void>;
|
|
44
|
+
keys(prefix?: string): KeysIterable;
|
|
45
|
+
/**
|
|
46
|
+
* Fetch multiple keys concurrently with bounded concurrency.
|
|
47
|
+
* Returns a Map of key -> entry for all existing keys.
|
|
48
|
+
*
|
|
49
|
+
* @param keys - Array of keys to fetch
|
|
50
|
+
* @param concurrency - Number of concurrent get operations (default: 10)
|
|
51
|
+
*/
|
|
52
|
+
getMany<V = unknown>(keys: string[], concurrency?: number): Promise<Map<string, KVEntry<V, M>>>;
|
|
53
|
+
/**
|
|
54
|
+
* Iterate over key-value entries with concurrent fetching.
|
|
55
|
+
* Yields [key, entry] pairs as soon as each fetch completes.
|
|
56
|
+
*
|
|
57
|
+
* @param prefix - Optional prefix to filter keys
|
|
58
|
+
* @param concurrency - Number of concurrent get operations (default: 20)
|
|
59
|
+
*/
|
|
60
|
+
entries<V = unknown>(prefix?: string, concurrency?: number): EntriesIterable<V, M>;
|
|
61
|
+
getStore<V, SubM = M, I extends string = never>(subPrefix: string, indexes?: Record<I, import("./typed-kv.js").IndexDef<V>>): TypedKV<V, SubM, I>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=cached-kv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-kv.d.ts","sourceRoot":"","sources":["../src/cached-kv.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAGX,eAAe,EAEf,UAAU,EACV,OAAO,EACP,WAAW,EACX,MAAM,EACN,WAAW,EACX,YAAY,EAEZ,UAAU,EAGV,MAAM,YAAY,CAAC;AA4BpB,qBAAa,GAAG,CAAC,CAAC,GAAG,SAAS,CAAE,YAAW,MAAM,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,GAAE,UAAe;IAgBpC,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,WAAW;YASL,QAAQ;IAkBtB;;;;OAIG;YACW,iBAAiB;YA6EjB,4BAA4B;IA0C1C;;;OAGG;YACW,qCAAqC;IAgDnD,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAUlB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAqH/D;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAwE9B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;YAoJf,YAAY;IA4C1B,OAAO,CAAC,eAAe;IAUjB,GAAG,CAAC,CAAC,GAAG,OAAO,EACpB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,EACrC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,SAAS,SAAS,CAAC,GACxC,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,GACjB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,GACjB,OAAO,CAAC,WAAW,CAAC;IAoIvB,OAAO,CAAC,aAAa;IAiCf,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY;IA+EnC;;;;;;OAMG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACxB,IAAI,EAAE,MAAM,EAAE,EACd,WAAW,SAAK,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAoCtC;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,GAAG,OAAO,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,SAAK,GACd,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;IA6GxB,QAAQ,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC7C,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CAOtB"}
|