Operations
Deno KV and related cloud primitive APIs like queues and cron are currently experimental and subject to change. While we do our best to ensure data durability, data loss is possible, especially around Deno updates.
Deno programs that use KV require the --unstable
flag when launching the
program, as shown below:
deno run -A --unstable my_kv_code.ts
The Deno KV API provides a set of operations that can be performed on the key space.
There are two operations that read data from the store, and five operations that write data to the store.
Read operations can either be performed in strong or eventual consistency mode. Strong consistency mode guarantees that the read operation will return the most recently written value. Eventual consistency mode may return a stale value, but is faster.
Write operations are always performed in strong consistency mode.
get
The get
operation returns the value and versionstamp associated with a given
key. If a value does not exist, get returns a null
value and versionstamp.
There are two APIs that can be used to perform a get
operation. The
Deno.Kv.prototype.get(key, options?)
API, which can be used to read a
single key, and the Deno.Kv.prototype.getMany(keys, options?)
API,
which can be used to read multiple keys at once.
Get operations are performed as a "snapshot read" in all consistency modes. This means that when retrieving multiple keys at once, the values returned will be consistent with each other.
const res = await kv.get<string>(["config"]);
console.log(res); // { key: ["config"], value: "value", versionstamp: "000002fa526aaccb0000" }
const res = await kv.get<string>(["config"], { consistency: "eventual" });
console.log(res); // { key: ["config"], value: "value", versionstamp: "000002fa526aaccb0000" }
const [res1, res2, res3] = await kv.getMany<[string, string, string]>([
["users", "sam"],
["users", "taylor"],
["users", "alex"],
]);
console.log(res1); // { key: ["users", "sam"], value: "sam", versionstamp: "00e0a2a0f0178b270000" }
console.log(res2); // { key: ["users", "taylor"], value: "taylor", versionstamp: "0059e9035e5e7c5e0000" }
console.log(res3); // { key: ["users", "alex"], value: "alex", versionstamp: "00a44a3c3e53b9750000" }
list
The list
operation returns a list of keys that match a given selector. The
associated values and versionstamps for these keys are also returned. There are
2 different selectors that can be used to filter the keys matched.
The prefix
selector matches all keys that start with the given prefix key
parts, but not inclusive of an exact match of the key. The prefix selector may
optionally be given a start
OR end
key to limit the range of keys returned.
The start
key is inclusive, and the end
key is exclusive.
The range
selector matches all keys that are lexographically between the given
start
and end
keys. The start
key is inclusive, and the end
key is
exclusive.
Note: In the case of the prefix selector, the
prefix
key must consist only of full (not partial) key parts. For example, if the key["foo", "bar"]
exists in the store, then the prefix selector["foo"]
will match it, but the prefix selector["f"]
will not.
The list operation may optionally be given a limit
to limit the number of keys
returned.
List operations can be performed using the
Deno.Kv.prototype.list<string>(selector, options?)
method. This method
returns a Deno.KvListIterator
that can be used to iterate over the keys
returned. This is an async iterator, and can be used with for await
loops.
// Return all users
const iter = kv.list<string>({ prefix: ["users"] });
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "alex"], value: "alex", versionstamp: "00a44a3c3e53b9750000" }
console.log(users[1]); // { key: ["users", "sam"], value: "sam", versionstamp: "00e0a2a0f0178b270000" }
console.log(users[2]); // { key: ["users", "taylor"], value: "taylor", versionstamp: "0059e9035e5e7c5e0000" }
// Return the first 2 users
const iter = kv.list<string>({ prefix: ["users"] }, { limit: 2 });
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "alex"], value: "alex", versionstamp: "00a44a3c3e53b9750000" }
console.log(users[1]); // { key: ["users", "sam"], value: "sam", versionstamp: "00e0a2a0f0178b270000" }
// Return all users lexicographically after "taylor"
const iter = kv.list<string>({ prefix: ["users"], start: ["users", "taylor"] });
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "taylor"], value: "taylor", versionstamp: "0059e9035e5e7c5e0000" }
// Return all users lexicographically before "taylor"
const iter = kv.list<string>({ prefix: ["users"], end: ["users", "taylor"] });
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "alex"], value: "alex", versionstamp: "00a44a3c3e53b9750000" }
console.log(users[1]); // { key: ["users", "sam"], value: "sam", versionstamp: "00e0a2a0f0178b270000" }
// Return all users starting with characters between "a" and "n"
const iter = kv.list<string>({ start: ["users", "a"], end: ["users", "n"] });
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "alex"], value: "alex", versionstamp: "00a44a3c3e53b9750000" }
The list operation reads data from the store in batches. The size of each batch
can be controlled using the batchSize
option. The default batch size is 500
keys. Data within a batch is read in a single snapshot read, so the values are
consistent with each other. Consistency modes apply to each batch of data read.
Across batches, data is not consistent. The borders between batches is not
visible from the API as the iterator returns individual keys.
The list operation can be performed in reverse order by setting the reverse
option to true
. This will return the keys in lexicographically descending
order. The start
and end
keys are still inclusive and exclusive
respectively, and are still interpreted as lexicographically ascending.
// Return all users in reverse order, ending with "sam"
const iter = kv.list<string>({ prefix: ["users"], start: ["users", "sam"] }, {
reverse: true,
});
const users = [];
for await (const res of iter) users.push(res);
console.log(users[0]); // { key: ["users", "taylor"], value: "taylor", versionstamp: "0059e9035e5e7c5e0000" }
console.log(users[1]); // { key: ["users", "sam"], value: "sam", versionstamp: "00e0a2a0f0178b270000" }
Note: in the above example we set the
start
key to["users", "sam"]
, even though the first key returned is["users", "taylor"]
. This is because thestart
andend
keys are always evaluated in lexicographically ascending order, even when the list operation is performed in reverse order (which returns the keys in lexicographically descending order).
set
The set
operation sets the value of a key in the store. If the key does not
exist, it is created. If the key already exists, its value is overwritten.
The set
operation can be performed using the
Deno.Kv.prototype.set(key, value)
method. This method returns a
Promise
that resolves to a Deno.KvCommitResult
object, which contains the
versionstamp
of the commit.
Set operations are always performed in strong consistency mode.
const res = await kv.set(["users", "alex"], "alex");
console.log(res.versionstamp); // "00a44a3c3e53b9750000"
delete
The delete
operation deletes a key from the store. If the key does not exist,
the operation is a no-op.
The delete
operation can be performed using the
Deno.Kv.prototype.delete(key)
method.
Delete operations are always performed in strong consistency mode.
await kv.delete(["users", "alex"]);
sum
The sum
operation atomically adds a value to a key in the store. If the key
does not exist, it is created with the value of the sum. If the key already
exists, its value is added to the sum.
The sum
operation can only be performed as part of an atomic operation. The
Deno.AtomicOperation.prototype.mutate({ type: "sum", value })
method
can be used to add a sum mutation to an atomic operation.
The sum operation can only be performed on values of type Deno.KvU64
. Both the
operand and the value in the store must be of type Deno.KvU64
.
If the new value of the key is greater than 2^64 - 1
or less than 0
, the sum
operation wraps around. For example, if the value in the store is 2^64 - 1
and
the operand is 1
, the new value will be 0
.
Sum operations are always performed in strong consistency mode.
await kv.atomic()
.mutate({
type: "sum",
key: ["accounts", "alex"],
value: new Deno.KvU64(100n),
})
.commit();
min
The min
operation atomically sets a key to the minimum of its current value
and a given value. If the key does not exist, it is created with the given
value. If the key already exists, its value is set to the minimum of its current
value and the given value.
The min
operation can only be performed as part of an atomic operation. The
Deno.AtomicOperation.prototype.mutate({ type: "min", value })
method
can be used to add a min mutation to an atomic operation.
The min operation can only be performed on values of type Deno.KvU64
. Both the
operand and the value in the store must be of type Deno.KvU64
.
Min operations are always performed in strong consistency mode.
await kv.atomic()
.mutate({
type: "min",
key: ["accounts", "alex"],
value: new Deno.KvU64(100n),
})
.commit();
max
The max
operation atomically sets a key to the maximum of its current value
and a given value. If the key does not exist, it is created with the given
value. If the key already exists, its value is set to the maximum of its current
value and the given value.
The max
operation can only be performed as part of an atomic operation. The
Deno.AtomicOperation.prototype.mutate({ type: "max", value })
method
can be used to add a max mutation to an atomic operation.
The max operation can only be performed on values of type Deno.KvU64
. Both the
operand and the value in the store must be of type Deno.KvU64
.
Max operations are always performed in strong consistency mode.
await kv.atomic()
.mutate({
type: "max",
key: ["accounts", "alex"],
value: new Deno.KvU64(100n),
})
.commit();
watch
The watch
operation accepts an array of keys, and returns
a ReadableStream
,
which emits a new value whenever any of the watched keys change
their versionstamp
. The emitted value is an array of
Deno.KvEntryMaybe objects.
Note that the returned stream does not return every single intermediate state of the watched keys, but keeps you up to date with the latest state of keys. This means if a key is modified multiple times quickly, you may not receive a notification for every change, but the latest state of the key.
const db = await Deno.openKv();
const stream = db.watch([["foo"], ["bar"]]);
for await (const entries of stream) {
entries[0].key; // ["foo"]
entries[0].value; // "bar"
entries[0].versionstamp; // "00000000000000010000"
entries[1].key; // ["bar"]
entries[1].value; // null
entries[1].versionstamp; // null
}