The KV Connect protocol is the protocol used by the Deno CLI (and other third
party clients) to communicate with remote Deno KV backends. This protocol is not
in use when using the Deno CLI to access a local KV database that is backed by
SQLite. The KV Connect protocol is used when calling
await Deno.openKv("http://<remote>")
.
To connect to a backend that implements the KV Connect protocol, the client just needs to know a single URL and access token.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
The KV Connect protocol uses HTTP, JSON, and ProtoBuf. KV Connect compatible backends MUST support HTTP/1.1 and HTTP/2. It is RECOMMENDED that KV Connect compatible backends that are accessible over the public internet support HTTPS only.
The KV Connect protocol is a request-response protocol. The client sends a request to the server and the server responds with a response. The client and server MAY send multiple requests and responses over the same underlying HTTP/1.1 or HTTP/2 connection, as defined in those specifications.
The KV Connect protocol is stateless. The client and server MAY send requests and responses in any order. The client and server MUST NOT assume that the requests and responses are received in the same order as they were sent. While no state is maintained on either client on server between requests, the client SHOULD maintain a cache of metadata and authentication information received from the server to avoid unnecessary network round trips.
The KV Connect protocol consists of two parts:
- A JSON based protocol for performing metadata exchange and authentication on startup. This is the Metadata Exchange Protocol.
- A ProtoBuf based protocol for performing CRUD operations on the KV database. This is the Data Path Protcol.
Before being able to perform a CRUD operation on the KV database, the client MUST perform a metadata exchange with the server. The metadata exchange is used to inform the client about:
- The version of the KV Connect protocol that the server implements.
- A UUID that uniquely identifies the KV database that the client is connecting to.
- The HTTP endpoints that the Data Path Protocol is available on.
- Authentication information for the client to use when performing CRUD operations.
The metadata exchange is performed by sending a POST request to the
user-specified URL. This request MUST include a Authorization
header with the
value Bearer <token>
, where <token>
is the access token that the user
specified. The request SHOULD include a User-Agent
header with a value
identifying the client library and version that is being used. The request MAY
include a JSON body that adheres to the JSON schema defined in the
schema/kv-metadata-exchange-request.json
file. The supportedVersions
property of the request body MUST be an array of numbers that represents the KV
Connect protocol versions that the client supports (e.g. [1, 2, 3]
). If the
request body is not included, the client is assumed to support only version 1 of
the KV Connect protocol.
Upon receiving the POST request, the server MUST verify that the request body
adheres to the JSON schema defined in the
schema/kv-metadata-exchange-request.json
file. If the request body is not
included the server MUST assume that the client only supports version 1 of the
KV Connect protocol. The server MUST use this information to choose a protocol
version that both the client and server support. The server MUST verify that the
access token provided in the Authorization
header is valid for the KV database
that the client is attempting to connect to.
If the access token is invalid, no overlapping protocol versions were found, the request body is invalid, or another error is encountered that prevent the server from returning database metadata, the server MUST respond with a 4xx class HTTP status response and a plain text body containing a human readable error message. The server MAY return a 3xx class HTTP redirect response. In this case, the client MUST follow the redirect and perform the metadata exchange with the new URL as the base URL.
The server MAY return a 5xx class HTTP status response at any time if the server is unable to process the request right now due to a temporary condition, for example an internal server error or rate limiting. The server SHOULD include a plain text body with a human readable error message in this case.
If the access token is valid and a database is found, the server MUST respond
with a 200 OK response. This response MUST include a Content-Type
header with
the value application/json
. The text encoding of the response body MUST be
UTF-8. The response body MUST be a JSON object that adheres to the JSON schema
defined in the schema/kv-metadata-exchange-response.v<protocolversion>.json
file.
If the client fails to receive a response from the server due to a network error, or a 5xx class HTTP status, the client SHOULD retry the request using an exponential backoff strategy with unlimited retries.
The client MUST verify that the response is a 200 OK, 3xx class redirect
response, or 4xx class HTTP status response. If the response is a 3xx class
redirect response, the client MUST follow the redirect and perform the metadata
exchange with the new URL as the base URL (the client MAY impose a maximum
redirect depth). If the response is a 4xx class HTTP status response, the client
MUST fatally abort the metadata exchange and display the error message to the
user. If the response is a 200 OK response, the client MUST verify that the
response body adheres to the JSON schema defined in the
schema/kv-metadata-exchange-response.v<protocolversion>.json
file. If the
response body is invalid, the client MUST fatally abort the metadata exchange
and display the error message to the user.
If the response body is valid, the client MUST verify that the version
property is a protocol version that the client supports. If the version
is not
a protocol version that the client supports, the client MUST fatally abort the
metadata exchange and display an error message to the user.
If client MUST verify that at least one of the entries in the endpoints
array
has a consistency
property that is equal to strong
. If no such entry is
found, the client MUST fatally abort the metadata exchange and display an error
message to the user.
If the client is using protocol version 2 or higher, the client MUST parse each URL in the endpoints array and resolve it against the base URL of the metadata exchange request. If the client followed a redirect during the metadata exchange the base URL MUST be the URL that the client was last redirected to. If the client did not follow a redirect during the metadata exchange the base URL MUST be the URL that the client originally sent the metadata exchange request to. The client MUST verify that the resolved URL is a valid URL. If the resolved URL is not a valid URL, the client MUST fatally abort the metadata exchange and display an error message to the user.
The token
property of the response body is an opaque string that the client
MUST use as the authorization token when performing CRUD operations using the
Data Path Protocol. The expiresAt
property of the response body is an ISO
8601 formatted string that represents the date and time when the returned
database metadata expires. The client MUST NOT use the returned database
metadata after this date and time. The client SHOULD cache the returned database
metadata and use it until it expires. The client SHOULD perform a metadata
exchange with the server just before the metadata expires to ensure that the
client is using the latest database metadata.
Example metadata exchange:
POST / HTTP/1.1
Authorization: Bearer a1b2c3d4e5f6g7h8i9
Content-Type: application/json
Content-Length: 33
{
"supportedVersions": [1, 2]
}
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 315
{
"version": 2,
"uuid": "a1b2c3d4-e5f6-7g8h-9i1j-2k3l4m5n6o7p",
"endpoints": [
{
"url": "/v2",
"consistency": "strong"
},
{
"url": "https://mirror.example.com/v2",
"consistency": "eventual"
}
],
"token": "123abc456def789ghi",
"expiresAt": "2023-10-01T00:00:00Z"
}
After performing the metadata exchange, the client can perform CRUD operations on the KV database using the Data Path Protocol. The Data Path Protocol is a Protobuf over HTTP protocol.
All requests on this protocol MUST include an Authorization
header with the
value Bearer <token>
, where <token>
is the token returned by the server
during the metadata exchange.
The client MUST include on all requests a User-Agent
header with a value
identifying the client library and version that is being used.
The client MUST include on all requests a Content-Type
header with the value
application/x-protobuf
.
If the protocol version used is 1, the client MUST include on all requests a
x-transaction-domain-id
header with a value of the UUID of the database as
returned by the server during the metadata exchange.
If the protocol version used is 2 or higher, the client MUST include on all
requests a x-denokv-database-id
header with a value of the UUID of the
database as returned by the server during the metadata exchange.
If the protocol version used is 2 or higher, the client MUST include on all
requests a x-denokv-version
header with a value of the protocol version that
the client is using.
The server MUST NOT respond with 3xx class redirect responses to data path requests.
There are two types of requests that the client may send to the server:
- A Snapshot Read Request which is used to read some values from the database.
- An Atomic Write Request which is used to write some values to the database.
A Snapshot Read Request is used to read some values from the database.
A Snapshot Read Request can either be strongly consistent or eventually
consistent. A strongly consistent Snapshot Read Request MUST be sent to an
endpoint that has a consistency
property that is equal to strong
. An
eventually consistent Snapshot Read Request MUST be sent to an endpoint that
has a consistency
property that is either equal to strong
or eventual
.
To determine which endpoint to send a Snapshot Read Request to, the client MUST first narrow down the list of endpoints to only include endpoints that have a valid consistency property for the type of Snapshot Read Request that the client wants to perform. From this list, the client MAY make requests to any endpoint, but the client SHOULD prefer endpoints that have lower latency.
The client sends a Snapshot Read Request to the server by sending a POST
request to the <endpointUrl>/snapshot_read
endpoint, where <endpointUrl>
is
the resolved URL of the selected endpoint. The request body MUST be a Protobuf
message in the format com.deno.kv.datapath.SnapshotRead
(defined in the
schema/datapath.proto
file in this repository).
Upon receiving the POST request, the server MUST verify that the request body
adheres to the Protobuf schema for the com.deno.kv.datapath.SnapshotRead
message. If the request body is invalid, the server MUST respond with a 4xx
response and a plain text body containing a human readable error message.
The server MAY return a 5xx class HTTP status response at any time if the server is unable to process the request right now due to a temporary condition, for example an internal server error or rate limiting. The server SHOULD include a plain text body with a human readable error message in this case.
The server MAY perform quota checks on the request. If the request is rejected due to quota limits, the server MUST respond with a 4xx class response and a plain text body containing a human readable error message.
The server MUST perform the requested snapshot read operation. The server MUST
respond with a 200 OK response. The response MUST include a Content-Type
header with the value application/x-protobuf
. The response body MUST be a
Protobuf message in the format com.deno.kv.datapath.SnapshotReadOutput
. If the
server is unable to perform the read because the database is not available from
this server, the read_disabled
field MUST be set to true
. The server MUST
include the read_is_strongly_consistent
field, setting it to true
if the
server has a strongly consistent view of the database, and false
if the server
has an eventually consistent view. If the request succeeds, the server MUST
include the ranges
field with a list of ranges that were read from the
database. The order MUST be the same as the order of the ranges in the request.
If the client fails to receive a response from the server due to a network error, or a 5xx class HTTP status, the client SHOULD retry the request using an exponential backoff strategy with unlimited retries.
The client MUST verify that the response is a 200 OK response. If the response
is a 4xx class error response, the client MUST fatally abort the request and
display the error message to the user. If the response is a 200 OK response, the
client MUST verify that the response body adheres to the Protobuf schema for the
com.deno.kv.datapath.SnapshotReadOutput
message. If the response body is
invalid, the client MUST fatally abort the request and display the error message
to the user.
If the protocol version is 3 or higher, the client MUST read the status
field
of the response. If the response has a status
field set to SR_UNSPECIFIED
,
the client MUST fatally abort the request and display an error message to the
user.
If the protocol version is 3 or higher and the status
field is set to
SR_READ_DISABLED
, or if the protocol version is 2 or lower and the
read_disabled
field is set to true
, or if the request was strongly
consistent but the read_is_strongly_consistent
field is set to false
, the
client SHOULD perform a metadata exchange with the server to get a new list of
endpoints, and then retry the request.
If the ranges in the response match the ranges in the request, the client SHOULD return the values to the user.
Example snapshot read request:
POST /v2/snapshot_read HTTP/1.1
Authorization: Bearer 123abc456def789ghi
Content-Type: application/x-protobuf
x-denokv-version: 3
x-denokv-database-id: a1b2c3d4-e5f6-7g8h-9i1j-2k3l4m5n6o7p
User-Agent: Deno/1.38.0
Content-Length: 45
<protobuf message>
HTTP/1.1 200 OK
Content-Type: application/x-protobuf
Content-Length: 45
<protobuf message>
An Atomic Write Request is used to write some values to the database.
An Atomic Write Request MUST be sent to an endpoint that has a consistency
property that is equal to strong
.
To determine which endpoint to send an Atomic Write Request to, the client
MUST first narrow down the list of endpoints to only include endpoints that have
a consistency
property that is equal to strong
. From this list, the client
MAY make requests to any endpoint, but the client SHOULD prefer endpoints that
have lower latency.
The client sends an Atomic Write Request to the server by sending a POST
request to the <endpointUrl>/atomic_write
endpoint, where <endpointUrl>
is
the resolved URL of the selected endpoint. The request body MUST be a Protobuf
message in the format com.deno.kv.datapath.AtomicWrite
(defined in the
schema/datapath.proto
file in this repository).
Upon receiving the POST request, the server MUST verify that the request body
adheres to the Protobuf schema for the com.deno.kv.datapath.AtomicWrite
message. If the request body is invalid, the server MUST respond with a 4xx
class HTTP status response and a plain text body containing a human readable
error message.
The server MAY perform quota checks on the request. If the request is rejected due to quota limits, the server MUST respond with a 4xx class HTTP status response and a plain text body containing a human readable error message.
The server MUST perform the requested atomic write operation. The server MUST
respond with a 200 OK response. The response MUST include a Content-Type
header with the value application/x-protobuf
. The response body MUST be a
Protobuf message in the format com.deno.kv.datapath.AtomicWriteResponse
. If
the server is unable to perform the write because the database is not available
from this server, the status
field MUST be set to AW_WRITE_DISABLED
. If a
write operation fails due to a check conflict, the status
field MUST be set to
AW_CHECK_FAILED
. If the write operation succeeds, the status
field MUST be
set to AW_SUCCESS
. If the request succeeds, the server MUST include the
versionstamp
field with the versionstamp of the write operation.
If the client fails to receive a response from the server due to a network error, or a 5xx class HTTP status, the client SHOULD retry the request using an exponential backoff strategy with unlimited retries.
The client MUST verify that the response is a 200 OK response. If the response
is a 4xx class error response, the client MUST fatally abort the request and
display the error message to the user. If the response is a 200 OK response, the
client MUST verify that the response body adheres to the Protobuf schema for the
com.deno.kv.datapath.AtomicWriteResponse
message. If the response body is
invalid, the client MUST fatally abort the request and display the error message
to the user.
If the response has a status
field set to AW_WRITE_DISABLED
, the client
SHOULD perform a metadata exchange with the server to get a new list of
endpoints, and then retry the request.
If the response has a status
field set to AW_CHECK_FAILED
, the client MUST
return an error to the user indicating that the write operation failed due to a
check conflict. The client SHOULD report the checks that failed to the user by
interpereting the check_failures
field of the response.
If the response has a status
field set to AW_UNDEFINED
, the client MUST
return an error to the user indicating that the write operation failed due to an
unknown error.
If the response has a status
field set to AW_SUCCESS
, the client SHOULD
return the versionstamp of the write operation to the user.
Example atomic write request:
POST /v2/atomic_write HTTP/1.1
Authorization: Bearer 123abc456def789ghi
Content-Type: application/x-protobuf
x-denokv-version: 2
x-denokv-database-id: a1b2c3d4-e5f6-7g8h-9i1j-2k3l4m5n6o7p
User-Agent: Deno/1.38.0
Content-Length: 45
<protobuf message>
HTTP/1.1 200 OK
Content-Type: application/x-protobuf
Content-Length: 12
<protobuf message>
A Watch Request is used to watch for changes to keys in the database.
A Watch Request is always eventually consistent, so it MUST be sent to an
endpoint that has a consistency
property that is either equal to strong
or
eventual
.
To determine which endpoint to send a Watch Request to, the client MUST first narrow down the list of endpoints to only include endpoints that have a valid consistency property for the type of Watch Request that the client wants to perform. From this list, the client MAY make requests to any endpoint, but the client SHOULD prefer endpoints that have lower latency.
The client sends a Watch Request to the server by sending a POST request to
the <endpointUrl>/watch
endpoint, where <endpointUrl>
is the resolved URL of
the selected endpoint. The request body MUST be a Protobuf message in the format
com.deno.kv.datapath.Watch
(defined in the schema/datapath.proto
file in
this repository).
Upon receiving the POST request, the server MUST verify that the request body
adheres to the Protobuf schema for the com.deno.kv.datapath.Watch
message. If
the request body is invalid, the server MUST respond with a 4xx class HTTP
status response and a plain text body containing a human readable error message.
The server MAY return a 5xx class HTTP status response at any time if the server is unable to process the request right now due to a temporary condition, for example an internal server error or rate limiting. The server SHOULD include a plain text body with a human readable error message in this case.
The server MAY perform quota checks on the request. If the request is rejected due to quota limits, the server MUST respond with a 4xx class HTTP status response and a plain text body containing a human readable error message.
The server MUST perform a snapshot read operation for the requested keys. The
server MUST respond with a 200 OK response. The response MUST include a
Content-Type
header with the value application/octet-stream
. The response
body MUST be a stream. The first message of this stream MUST be a Protobuf
encoded message in the format com.deno.kv.datapath.WatchOutput
, prefixed with
a 4 byte little endian integer that represents the length of the message.
When any of the watched keys change, the server MUST send a Protobuf encoded
message in the format com.deno.kv.datapath.WatchOutput
, prefixed with a 4 byte
little endian integer that represents the length of the message, to the client
over the same stream. The server MAY collapse multiple changes into a single
notification, as long as each message represents a consistent snapshot of the
keys that the client is watching.
The server MAY send a zero length message to the client over the stream at any time to indicate that the server is still alive. These messages must be structured as a 4 byte little endian integer encoding of 0, followed by no data.
If the client fails to receive a response from the server due to a network error, or a 5xx class HTTP status, the client SHOULD retry the request using an exponential backoff strategy with unlimited retries.
The client MUST verify that the response is a 200 OK response. If the response is a 4xx class error response, the client MUST fatally abort the request and display the error message to the user.
If the response is a 200 OK response, the client MUST parse each message in the
stream by first reading a 4 byte little endian integer that represents the
length of the message, and then reading that many bytes from the stream. If the
length of the message is 0, the client MUST ignore the message and wait for the
next message. If the length of the message is greater than 0, the client MUST
then verify that the message adheres to the Protobuf schema for the
com.deno.kv.datapath.WatchOutput
message. If the message is invalid, the
client MUST fatally abort the request and display the error message to the user.
If the message is valid, the client MUST read the status
field of the message
and verify that it is set to SR_SUCCESS
. If the status
field is set to
SR_READ_DISABLED
, the client SHOULD perform a metadata exchange with the
server to get a new list of endpoints, and then retry the request. If the
status
field is set to any other value, the client MUST fatally abort the
request and display an error message to the user.
If the message is valid, the client MUST yield the message to the user, and then continue reading messages from the stream.
Example watch request:
POST /v3/watch HTTP/1.1
x-denokv-version: 3
x-denokv-database-id: a1b2c3d4-e5f6-7g8h-9i1j-2k3l4m5n6o7p
<protobuf message>
HTTP/1.1 200 OK
Content-Type: application/octet-stream
<u32 little endian><protobuf message><u32 little endian><protobuf message>...
The KV Connect protocol is versioned. The version of the protocol is negotiated during the metadata exchange. Below is a list of protocol versions and how they differ from each other.
This specification defines server and client behavior for version 2, but is entirely backwards compatible with version 1. This means that a client that supports version 2 as specified here can connect to a server that only supports version 1. A client that only supports version 1 can not connect to a server that only supports version 2.
Version 1 is the initial version of the KV Connect protocol. It is the default version that is used if the client does not specify a list of supported versions during the metadata exchange.
Version 2 adds the following features:
-
The
x-denokv-version
header is added to all requests on the Data Path Protocol. This header is used to indicate the protocol version that the client is using. This allows the server to support multiple protocol versions at the same time. -
The
x-denokv-database-id
header is added to all requests on the Data Path Protocol. This header is used to indicate the UUID of the database that the client is accessing. This allows the server to support multiple databases at the same time. -
The
read_is_strongly_consistent
field is added to the response body of Snapshot Read Requests. This field is used to indicate whether the server has a strongly consistent view of the database or not. This allows the client to retry the request if the server does not have a strongly consistent view of the database.
Version 3 adds the following features:
- The
status
field is added to the response body of Snapshot Read Requests and is used instead of theread_disabled
boolean field by the client. - The "Watch" data path operation is added.