CloudflareKvStore — Cloudflare Workers KV
Read this page in the documentation
CloudflareKvStore — Cloudflare Workers KV Overview Cloudflare Workers KV is a globally-distributed, eventually-consistent key-value store exposed over Cloudflare's REST API. It has no query language, no rows, and no SQL-shaped access pattern — data is opaque string values addressed by a flat key within a namespace. Because none of that fits the SQL-shaped Dialect interface, CloudflareKvStore implements the minimal NoSqlStore marker interface (src/nosql/store.ts) — connection lifecycle plus a getClient() escape hatch — and exposes a small edge-KV surface (get/set/del/list/incr) mapped onto the REST endpoints. Identity: Property | Value | --------- | ----------------- | name | 'cloudflare-kv' | library | 'fetch' | No canonical driver — HTTP over fetch Workers KV has no first-party Node client; it is just an HTTP/JSON API. This store talks to it with the global fetch, which is why library is 'fetch' rather than a package name. There is no lazy require() of an SDK — when no client is injected, connect() builds a tiny internal fetch-based client (createFetchClient) from the baseURL/apiToken options. Importing this module therefore never requires any external package. Injected client CloudflareKvStoreOptions accepts a pre-built client implementing the CloudflareKvHttpClient interface (a single request(method, path, init?) method). When provided, connect() uses it verbatim. This is how the test suite injects a mock HTTP client (no network), and how callers can supply their own transport. Values are serialized before writing: strings are stored verbatim; every other value is JSON.stringify'd. Connection Build a store from connection options and call connect(): Connection options: Option | Type | Purpose | ------------- | -------------------------- | ----------------------------------------------------------------------------------------- | accountId | string | Cloudflare account ID (required unless a client is injected). | namespaceId | string | KV namespace ID (required unless a client is injected). | apiToken | string | API token sent as a Bearer credential. | baseURL | string | API base URL. Defaults to https://api.cloudflare.com/client/v4. | client | CloudflareKvHttpClient | Pre-built HTTP client; when set, connect() uses it verbatim (no fetch client is built). | Every option is optional in the constructor (CloudflareKvStoreOptions = {}), but accountId/namespaceId are required to address the namespace when using the built-in fetch client. Injected-client form Supply your own client (or a mock) to bypass the built-in fetch transport: Methods Every operation resolves the namespace path (/accounts/<accountId>/storage/kv/namespaces/<namespaceId>) and issues an HTTP request via the client. SDK/transport failures are wrapped in a DatabaseError; using any method before connect() (or after disconnect()) throws a ConnectionError. Lifecycle Method | Signature | Behavior | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------- | connect | connect(): Promise<void> | Uses an injected client if provided, otherwise builds an internal fetch-based client from the options. Idempotent. Wraps failures in ConnectionError. | disconnect | disconnect(): Promise<void> | Clears the client and connection state. | isConnected | isConnected(): boolean | true only when connected and a client is present. | getClient | getClient(): CloudflareKvHttpClient | Returns the underlying (internal or injected) HTTP client. Throws ConnectionError if not connected. | Key-value operations Method | Signature | REST call | Behavior | ------- | -------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------- | get | get(key: string): Promise<string \| null> | GET .../values/<key> | Reads a key. Returns the response text, or null on a 404 (missing key). | set | set(key: string, value: unknown, options?: CloudflareKvSetOptions): Promise<void> | PUT .../values/<key> | Writes value (serialized). options.ttl maps to expirationttl (seconds), options.expiration to expiration (absolute Unix seconds). | del | del(key: string): Promise<void> | DELETE .../values/<key> | Deletes a key. A 404 is treated as success. | list | list(prefix?: string): Promise<string[]> | GET .../keys?prefix= | Lists key names, optionally filtered by prefix. Returns the name of each entry. | incr | incr(key: string, by?: number): Promise<number> | get + set | Increments the numeric value at key by by (default 1). Non-atomic read-modify-write — Workers KV has no atomic increment. | CloudflareKvSetOptions Example Verification status Unit / mock-verified only. The tests in tests/nosql/cloudflare-kv.test.ts inject a mock CloudflareKvHttpClient via CloudflareKvStoreOptions.client that records each request (method/path/init) and returns canned Response-like objects. There is no network and no Cloudflare account in the test run. What this proves: Each method issues the correct HTTP method and path (e.g. get → GET /accounts/acc1/storage/kv/namespaces/ns1/values/<encoded-key>), with keys URL-encoded. set serializes non-strings ({ a: 1 } → '{"a":1}'), stores strings verbatim, and maps ttl → expirationttl query param. get returns null on a 404; list maps the result[].name array; incr performs a read-modify-write (GET then PUT with the new value). Lifecycle: connect/idempotent-connect/disconnect, getClient returning the injected client, and ConnectionError before connect(). Error handling: transport failures wrapped in DatabaseError. What this does not prove: live execution against the real Cloudflare Workers KV REST API. The request shapes are verified against Cloudflare's documented endpoints, but end-to-end execution over the wire has not been exercised here. Related reading All data stores — the full catalogue, grouped by purpose Database types — where this sits among the 22 categories