Deno KV
Deno ships with a database, and its model fits in a sentence: a key is an array of typed parts, a value is anything structured cloning can carry, and the database keeps every entry sorted by key with a versionstamp recording when it last changed. There are no tables and no query language, so everything a relational database would answer with WHERE gets answered here by key design, which is why more of this page is about keys than about values. The same API runs against a SQLite file on your machine and against a replicated database on Deno Deploy; this entry stays on your machine.
The API is marked unstable, so it sits behind a flag the first section earns. After that, every test opens its own private in-memory database, the KV equivalent of the scratch directory the understanding the file system page created and removed around each test, so nothing below touches a database you care about.
Create programs/deno-kv.test.ts for this reference and keep it open. Start it with the assertion helpers used on this page:
import {
assert,
assertEquals,
assertFalse,
assertNotStrictEquals,
assertRejects,
} from "@std/assert";
Follow the page as you add and revise the runnable examples below that import. Each operation gets its call shape in miniature at its first use, read the same way as on the file system page.
Deno.openKv is behind a flag
Deno.test("Deno.openKv is behind a flag", async () => {
using kv = await Deno.openKv(":memory:");
const commit = await kv.set(["greeting"], "hello");
assert(commit.ok);
});
Run it the way every other reference file runs:
deno test --no-prompt programs/deno-kv.test.ts
Check programs/deno-kv.test.ts
running 1 test from ./programs/deno-kv.test.ts
Deno.openKv is behind a flag ... FAILED (742µs)
ERRORS
Deno.openKv is behind a flag => ./programs/deno-kv.test.ts:9:6
error: TypeError: Deno.openKv is not a function
using kv = await Deno.openKv(":memory:");
^
at file:///programs/deno-kv.test.ts:10:25
info: Deno.openKv() is an unstable API.
hint: Run again with `--unstable-kv` flag to enable this API.
FAILURES
Deno.openKv is behind a flag => ./programs/deno-kv.test.ts:9:6
FAILED | 0 passed | 1 failed (2ms)
error: Test failed
The checker raised no objection, and the runtime refused anyway. The types for unstable APIs ship with Deno either way; the gate is at run time, where Deno.openKv simply does not exist until the process is started with consent to use an API that may still change shape. If your test passed instead of failing, your deno.json already carries "unstable": ["kv"], the standing consent the Building a CLI installment adds, and the error above is what every project without it shows. The error's hint names the per-command form of the same consent. Give it:
deno test --no-prompt --unstable-kv programs/deno-kv.test.ts
running 1 test from ./programs/deno-kv.test.ts
Deno.openKv is behind a flag ... ok (3ms)
ok | 1 passed | 0 failed (4ms)
Keep this command for the rest of the entry. Deno.openKv(path?: string): Promise<Deno.Kv> opens a database and resolves to the handle every other operation hangs off. With no argument it opens a persistent database that Deno manages on disk for the surrounding project, shared by every script the project's deno.json covers, or private to the script when no project configuration exists; with a path it opens or creates a SQLite file there; with the exact string ":memory:" it creates a private database that lives in the process and dies with it. The page uses the third form throughout, which is why no cleanup code appears anywhere below.
using kv closes the handle when the scope ends, the same Symbol.dispose arrangement that closed the FsFile in using closes an open file at the end of its scope on the file system page. kv.set(key: Deno.KvKey, value: unknown, options?: { expireIn?: number }): Promise<Deno.KvCommitResult> writes one value under one key and resolves to { ok: true, versionstamp }; both fields wait for their own sections.
absence is an answer, not an error
Deno.test("absence is an answer, not an error", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["greeting"], "hello");
const present = await kv.get<string>(["greeting"]);
assertEquals(present.key, ["greeting"]);
assertEquals(present.value, "hello");
assert(present.versionstamp !== null);
await kv.delete(["greeting"]);
const absent = await kv.get<string>(["greeting"]);
assertEquals(absent.value, null);
assertEquals(absent.versionstamp, null);
});
absence is an answer, not an error ... ok (848µs)
kv.get(key: Deno.KvKey, options?): Promise<Deno.KvEntryMaybe<T>> reads one key and always resolves, to an object with key, value, and versionstamp fields. For a present key, value is the stored value and versionstamp is a string. For an absent key, both are null, together, which is this API's sentinel for absence in the sense the sentinels page gave the word. Compare the file system page, where a missing file was a NotFound rejection to catch narrowly; here absence is an ordinary return value, and the catch-shaped fallback code has nothing to do.
kv.delete(key: Deno.KvKey): Promise<void> makes a key absent and resolves without a value, whether or not the key existed, so deleting twice is not an error. The type parameter on kv.get<string> deserves suspicion: the database does not check it, so it is a claim about what was stored, not a guarantee, the same unchecked assertion the any, unknown, never page warned about. Leave the parameter off and value is typed unknown, which is the honest default.
a key is an array of typed parts
Deno.test("a key is an array of typed parts", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["user", 1], "by number");
await kv.set(["user", "1"], "by string");
await kv.set(["user", 1n], "by bigint");
assertEquals((await kv.get(["user", 1])).value, "by number");
assertEquals((await kv.get(["user", "1"])).value, "by string");
assertEquals((await kv.get(["user", 1n])).value, "by bigint");
await assertRejects(
() =>
// @ts-expect-error: Object literal may only specify known properties, and 'id' does not exist in type 'Uint8Array<ArrayBufferLike>'.
kv.set([{ id: 1 }], "by object"),
TypeError,
"expected string, number, bigint, ArrayBufferView, boolean",
);
});
a key is an array of typed parts ... ok (996µs)
A key part can be a string, a number, a bigint, a boolean, or a Uint8Array, and nothing else. The part keeps its type: 1, "1", and 1n name three different entries, and the test reads all three back independently. This is the maps page's lesson wearing a database costume, except that a Map compares keys by SameValueZero and KV compares them by typed contents, so two separately built arrays with the same parts reach the same entry.
The shielded line shows what happens to anything else. An object is not a valid part, so the checker refuses it, with an odd message because the only object-shaped part type is Uint8Array and it tried to make the literal fit that. Run the write anyway, as the shield does, and the runtime rejects with a TypeError listing exactly the five types it accepts. Keys carry structure, not data: identify things with parts, and store the things themselves as values.
a value comes back as a copy
Deno.test("a value comes back as a copy", async () => {
using kv = await Deno.openKv(":memory:");
const original = {
name: "Robin",
roles: new Set(["admin"]),
since: new Date(0),
};
await kv.set(["profile"], original);
original.name = "changed";
const entry = await kv.get<typeof original>(["profile"]);
assert(entry.value !== null);
assertEquals(entry.value.name, "Robin");
assertEquals(entry.value.roles, new Set(["admin"]));
assertEquals(entry.value.since, new Date(0));
assertNotStrictEquals(entry.value, original);
await assertRejects(
() => kv.set(["callback"], () => {}),
TypeError,
"could not be cloned",
);
});
a value comes back as a copy ... ok (799µs)
Walk the mutation through.
kv.setserializes the value with structured cloning, the deep-copy algorithm behindstructuredClonefrom the values and references page, so the database stores a snapshot.original.name = "changed"mutates the live object after the write, and the stored snapshot does not hear about it.kv.getdeserializes a fresh copy, soentry.value.nameis still"Robin", andassertNotStrictEqualsconfirms the copy is not the original object.
Structured cloning carries more than JSON does: the Set and the Date came back as a Set and a Date, and Map, Uint8Array, RegExp, and plain nesting all survive the same way. What it cannot carry is behavior, and the last assertion pins the price: a function rejects with TypeError: ... could not be cloned. A class instance does not throw, but only its own data properties travel, so the practical rule is to store data and rebuild behavior on the way out.
keys and values are bounded
Deno.test("keys and values are bounded", async () => {
using kv = await Deno.openKv(":memory:");
await assertRejects(
() => kv.set(["blob"], new Uint8Array(70_000)),
TypeError,
"Value too large (max 65536 bytes)",
);
await assertRejects(
() => kv.set(["k".repeat(3000)], "anything"),
TypeError,
"Key too large for write (max 2048 bytes)",
);
});
keys and values are bounded ... ok (385µs)
Two limits, pinned by their own error messages: a serialized value tops out at 64 KiB and an encoded key at 2 KiB. Both are measured after encoding, so the usable payload is slightly smaller than the round number. The limits are a statement about what this database is for. An entry is a record, not a file, and anything that feels like a file, an image, an upload, a log, belongs on the file system with KV holding its path or name as a value.
every write advances the versionstamp
Deno.test("every write advances the versionstamp", async () => {
using kv = await Deno.openKv(":memory:");
const first = await kv.set(["counter"], 1);
const second = await kv.set(["counter"], 2);
assertEquals(first.versionstamp.length, 20);
assert(second.versionstamp > first.versionstamp);
const entry = await kv.get<number>(["counter"]);
assertEquals(entry.value, 2);
assertEquals(entry.versionstamp, second.versionstamp);
});
every write advances the versionstamp ... ok (437µs)
A versionstamp is a 20-character hexadecimal string the database mints for every successful write, and it increases across the whole database, not per key. Two facts make it useful. String comparison agrees with commit order, so second.versionstamp > first.versionstamp holds with plain >. And reading a key returns the versionstamp of the write that produced its current value, so the commit result and the later get report the same string. File this fact carefully: it becomes load-bearing three sections down, in check makes a write conditional on what was read.
list walks a prefix in key order
Insert four keys in a deliberately shuffled order, then predict what a walk over the prefix ["users"] yields:
Deno.test("list walks a prefix in key order", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["users", "robin"], 1);
await kv.set(["users"], "the collection itself");
await kv.set(["usersextra"], "a different first part");
await kv.set(["users", "ada"], 2);
const keys: Deno.KvKey[] = [];
for await (const entry of kv.list({ prefix: ["users"] })) {
keys.push(entry.key);
}
assertEquals(keys, [
["users"],
["users", "ada"],
["users", "robin"],
["usersextra"],
]);
});
Check programs/deno-kv.test.ts
running 7 tests from ./programs/deno-kv.test.ts
...
list walks a prefix in key order ... FAILED (9ms)
ERRORS
list walks a prefix in key order => ./programs/deno-kv.test.ts:104:6
error: AssertionError: Values are not equal.
[Diff] Actual / Expected
[
[
"users",
+ ],
+ [
+ "users",
"ada",
],
[
"users",
"robin",
],
+ [
+ "usersextra",
+ ],
]
FAILURES
list walks a prefix in key order => ./programs/deno-kv.test.ts:104:6
FAILED | 6 passed | 1 failed (17ms)
error: Test failed
Two of the four predicted keys did not arrive, and each missing key teaches a rule. ["users"] itself is out because a prefix selects keys that are strictly longer than it, so the parent key and its children can coexist without the parent photobombing every walk. ["usersextra"] is out because parts match whole or not at all; this is array-prefix matching, not string-prefix matching, and "usersextra" is simply a different first part. What did arrive is sorted: "ada" before "robin", whatever order the writes used, because the database keeps entries in key order and list walks that order. Correct the expectation to [["users", "ada"], ["users", "robin"]]:
list walks a prefix in key order ... ok (540µs)
kv.list(selector: Deno.KvListSelector, options?: Deno.KvListOptions): Deno.KvListIterator<T> returns an async iterable of full entries, consumed with for await like every sequence on the async iteration page. The selector is { prefix } as here, or { start, end } for an explicit key range, or a prefix with a start or end bound inside it. Among the options, limit caps the walk, and reverse: true walks backward, so { prefix: ["logs"] } with reverse and limit: 10 is the idiomatic "latest ten" query when the last key part encodes time. The iterator also exposes a cursor string for resuming a walk across requests, which matters for pagination and can wait until a server needs it.
numbers only sort numerically when they are numbers
Key order is the query language, so it pays to know exactly what order the parts sort in:
Deno.test("numbers only sort numerically when they are numbers", async () => {
using kv = await Deno.openKv(":memory:");
for (const id of [10, 9, 2.5]) await kv.set(["by-number", id], id);
for (const id of ["10", "9", "2.5"]) await kv.set(["by-string", id], id);
const numbers: unknown[] = [];
for await (const entry of kv.list({ prefix: ["by-number"] })) {
numbers.push(entry.key[1]);
}
const strings: unknown[] = [];
for await (const entry of kv.list({ prefix: ["by-string"] })) {
strings.push(entry.key[1]);
}
assertEquals(numbers, [2.5, 9, 10]);
assertEquals(strings, ["10", "2.5", "9"]);
await kv.set(["mixed", 0n], "bigint zero");
await kv.set(["mixed", -1], "number minus one");
const mixed: unknown[] = [];
for await (const entry of kv.list({ prefix: ["mixed"] })) {
mixed.push(entry.value);
}
assertEquals(mixed, ["bigint zero", "number minus one"]);
});
numbers only sort numerically when they are numbers ... ok (696µs)
Three orders, one rule each.
- Number parts sort numerically, so
2.5, 9, 10, including negatives and fractions in their natural places. - String parts sort by character, so
"10"comes before"2.5"comes before"9", the same trap string sorting sets everywhere, and the reason zero-padding tricks exist in systems whose keys are only strings. Here the fix is cheaper: store the number as a number. - In the mixed prefix,
0nsorts before-1even though minus one is smaller, because parts group by type before comparing values, and bigints as a group come before numbers as a group.
The cross-type order is fixed but memorizing it is the wrong lesson. A key position that mixes types sorts by a rule no reader will guess, so give each position one type and let point two and point one do your ordering for free.
getMany reads several keys at one moment
Deno.test("getMany reads several keys at one moment", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["config", "retries"], 3);
await kv.set(["config", "verbose"], true);
const [retries, verbose, theme] = await kv.getMany<
[number, boolean, string]
>([
["config", "retries"],
["config", "verbose"],
["config", "theme"],
]);
assertEquals(retries.value, 3);
assertEquals(verbose.value, true);
assertEquals(theme.value, null);
});
getMany reads several keys at one moment ... ok (447µs)
kv.getMany(keys: Deno.KvKey[], options?): Promise<Deno.KvEntryMaybe[]> accepts up to ten keys and resolves to one entry per key, in the same order, with the usual null pair for the absent one. The tuple type parameter assigns each position its own claimed type, which is the get caveat multiplied by three.
The part a single-threaded test cannot make visible is the point: all the answers describe the same moment, where three separate get calls are three separate observations with room for a write to land between them. The file system page made that gap observable with readDir and a deleted file; here the API closes the gap for reads, and the next section closes it for writes.
check makes a write conditional on what was read
Read a value, compute a replacement, write it back: the gap between the read and the write is where another writer's update gets silently overwritten. The versionstamp closes the gap:
Deno.test("check makes a write conditional on what was read", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["balance"], 100);
const read = await kv.get<number>(["balance"]);
assert(read.value !== null);
const commit = await kv.atomic()
.check(read)
.set(["balance"], read.value - 30)
.commit();
assert(commit.ok);
const stale = await kv.atomic()
.check(read)
.set(["balance"], read.value - 30)
.commit();
assertFalse(stale.ok);
assertEquals((await kv.get(["balance"])).value, 70);
});
check makes a write conditional on what was read ... ok (517µs)
Follow the two commits.
kv.getreturns the entry for["balance"]: value100, plus the versionstamp of the write that put it there.kv.atomic().check(read).set(...).commit()asks the database to apply thesetonly if["balance"]still has the versionstamp inread. Nothing has written in between, so the commit lands and returns{ ok: true, versionstamp }.- The second commit presents the same
read, but the first commit already advanced the key's versionstamp, so the check fails, the commit returns{ ok: false }, and itssetnever happens. - The final balance is
70: exactly one deduction, not two.
kv.atomic(): Deno.AtomicOperation starts a builder; check(...checks) adds conditions, each a { key, versionstamp } pair, which is deliberately the shape get returns so entries feed checks directly; commit(): Promise<Deno.KvCommitResult | Deno.KvCommitError> resolves to a union that only has a versionstamp after narrowing on ok, the discriminated-union pattern from the unions and narrowing page. A failed check throws nothing, because losing the race is a normal outcome. Production code wraps steps one and two in a loop: read, compute, attempt, and on ok: false read again, since the right response to a stale observation is a fresh one.
one commit keeps two keys telling one story
There are no queries, so looking users up by email means the email needs a key of its own. Two keys describing one user must never disagree, and atomic is the tool:
Deno.test("one commit keeps two keys telling one story", async () => {
using kv = await Deno.openKv(":memory:");
const user = { id: 1, email: "robin@example.com", name: "Robin" };
const commit = await kv.atomic()
.check({ key: ["users", user.id], versionstamp: null })
.check({ key: ["users_by_email", user.email], versionstamp: null })
.set(["users", user.id], user)
.set(["users_by_email", user.email], user.id)
.commit();
assert(commit.ok);
const byEmail = await kv.get<number>(["users_by_email", user.email]);
assert(byEmail.value !== null);
const found = await kv.get<typeof user>(["users", byEmail.value]);
assertEquals(found.value?.name, "Robin");
const duplicate = await kv.atomic()
.check({ key: ["users_by_email", user.email], versionstamp: null })
.set(["users", 2], { ...user, id: 2 })
.set(["users_by_email", user.email], 2)
.commit();
assertFalse(duplicate.ok);
});
one commit keeps two keys telling one story ... ok (433µs)
Three moves worth naming.
- A check with
versionstamp: nullclaims the key is absent, so it succeeds only if nothing exists there yet. That is the same create-only guaranteecreateNewmade on the file system page, asked of a database. - The two
setcalls ride in one commit, so the user record and its email index appear together or not at all; there is no moment when a reader can see one without the other. - The duplicate registration fails as a unit. Its absence check finds the email key occupied, so neither of its writes happens, and the index cannot be stolen while the record stays intact.
The lookup in the middle is the pattern's read side: index key to id, id to record, two hops. This is the trade KV offers everywhere. The database will not maintain secondary indexes, counts, or "latest" pointers for you, and in exchange every one of them is just another key you update in the same atomic commit as the primary record.
sum counts without a read
Deno.test("sum counts without a read", async () => {
using kv = await Deno.openKv(":memory:");
await kv.atomic().sum(["visits"], 5n).commit();
await kv.atomic().sum(["visits"], 3n).commit();
const entry = await kv.get<Deno.KvU64>(["visits"]);
assertEquals(entry.value, new Deno.KvU64(8n));
assertEquals(entry.value?.value, 8n);
});
sum counts without a read ... ok (487µs)
sum(key: Deno.KvKey, n: bigint) adds to a key without reading it first, treating an absent key as zero, which is why five plus three lands on a key nothing ever set. A counter built from the previous section's check loop works, but under contention it retries, because every increment invalidates every other reader's versionstamp. Additions commute, so sum commits never conflict with each other, and that is the entire reason this mutation exists.
The stored value is a Deno.KvU64, a wrapper marking the entry as an unsigned 64-bit integer rather than an ordinary number; its value property is the bigint inside, and arithmetic wraps at 2n ** 64n. Writing new Deno.KvU64(0n) with a plain set initializes a counter explicitly, and min and max are sum's siblings for high-water and low-water marks, same wrapper, same no-read contract.
expiry is a deadline, not a schedule
Deno.test("expiry is a deadline, not a schedule", async () => {
using kv = await Deno.openKv(":memory:");
await kv.set(["session"], "token", { expireIn: 100 });
assertEquals((await kv.get(["session"])).value, "token");
await new Promise((resolve) => setTimeout(resolve, 250));
assertEquals((await kv.get(["session"])).value, "token");
});
expiry is a deadline, not a schedule ... ok (255ms)
expireIn is a count of milliseconds after which the entry becomes eligible for deletion, and the second assertion is the fine print made visible: 150 milliseconds past its deadline, the value is still there, because deletion happens on the store's own cleanup cadence rather than at the stroke of the deadline. The guarantee runs one way. The entry will be deleted at some point after the deadline, and no promise is made about how soon.
So treat expiry as hygiene, not logic. It keeps sessions, caches, and rate-limit buckets from accumulating forever, which is valuable. If correctness needs "unusable after the deadline," store the deadline inside the value and compare against the clock when reading, so the rule is enforced by your code instead of by a vacuum schedule you do not control.
watch turns a key into a stream
Deno.test("watch turns a key into a stream", async () => {
using kv = await Deno.openKv(":memory:");
const reader = kv.watch<[string]>([["status"]]).getReader();
const initial = await reader.read();
assert(!initial.done);
assertEquals(initial.value[0].versionstamp, null);
await kv.set(["status"], "deploying");
const next = await reader.read();
assert(!next.done);
assertEquals(next.value[0].value, "deploying");
await reader.cancel();
});
watch turns a key into a stream ... ok (2ms)
kv.watch(keys: Deno.KvKey[]): ReadableStream observes up to ten keys and emits an array of their entries, one array element per watched key. The first emission arrives immediately and reports current state, which here is the absence entry with its null versionstamp, so a subscriber does not need a separate get to learn where things stand. Each later emission arrives after a watched key changes, and the set in the middle is what wakes the second read.
The delivery contract is deliberately loose: after a burst of writes the stream guarantees you see the latest state, not every intermediate one, so watch suits live views and cache invalidation rather than audit logs. The ReadableStream machinery, readers, cancellation, and all, belongs to the future Streams and large files entry; the one obligation this test cannot skip is reader.cancel(), which releases the watch instead of leaving it running when the test ends.
a queue delivers to a handler
Deno.test("a queue delivers to a handler", async () => {
const kv = await Deno.openKv(":memory:");
const received = Promise.withResolvers<unknown>();
const listening = kv.listenQueue((message) => {
received.resolve(message);
});
await kv.enqueue({ task: "greet", who: "Robin" });
assertEquals(await received.promise, { task: "greet", who: "Robin" });
kv.close();
await listening;
});
a queue delivers to a handler ... ok (25ms)
The database is also a message queue, and the shutdown choreography is why this test skips using.
kv.listenQueue(handler): Promise<void>registers the process's one queue handler and returns a promise that settles only when the database closes, so the test keeps it inlisteninginstead of awaiting it.kv.enqueue(value, options?): Promise<Deno.KvCommitResult>commits a message, structured-clone rules and all, and the runtime invokes the handler with it shortly after;Promise.withResolversfrom the promises page turns that callback into something the test can await.kv.close()shuts the database explicitly,listeningsettles, and the test ends with nothing still running. Withusing, disposal would wait for the end of the scope whileawait listeningwaits for disposal, a deadlock spelled in one line.
Delivery is at least once, so a handler must tolerate seeing the same message twice, which usually means making its effect idempotent. The options carry the operational features: delay schedules a message for later, backoffSchedule sets the retry cadence when the handler throws, and keysIfUndelivered names keys where a message is atomically parked if every retry fails, so giving up leaves evidence instead of silence. An atomic operation has an enqueue mutation too, which pairs a state change with its follow-up work in one commit; and against a persistent database, undelivered and delayed messages survive process restarts, which a :memory: database by definition cannot offer.
choose the narrowest operation
| Operation | Result | Choose it when |
|---|---|---|
Deno.openKv() | Deno.Kv | the project needs its managed on-disk database, a chosen SQLite path, or ":memory:" for tests |
kv.set() | Deno.KvCommitResult | one value should be written unconditionally |
kv.get() | Deno.KvEntryMaybe | one key's current value and versionstamp are the question |
kv.getMany() | array of entries | up to ten keys must be read from the same moment |
kv.delete() | completion | one key should be absent, no conditions attached |
kv.list() | async iterator of entries | a prefix or range should be walked in key order |
kv.atomic() … commit() | Deno.KvCommitResult or Deno.KvCommitError | several mutations must land together or not at all |
check() on an atomic | conditional commit | a write is only valid if what was read is still current |
sum(), min(), max() | mutation on an atomic | counters and marks that must not conflict under load |
{ expireIn } on set | scheduled cleanup | the entry should eventually vanish without bookkeeping |
kv.watch() | ReadableStream of entries | code should react to changes without polling |
kv.enqueue() / kv.listenQueue() | commit / long-lived promise | work should be delivered to a handler, at least once, possibly later |
kv.close() or using | void | the handle's resources should be released |
"Completion" and the commit results carry the same narrow meaning as on the file system page: the operation happened, and nothing further is implied.
In practice
- Put
"unstable": ["kv"]indeno.jsoninstead of passing the flag on every command. - Design keys for the reads you need: use one type per part position, keep numbers as numbers, and put the sortable part last.
- Use
atomicwhen two keys describe one fact. Addcheckand a retry loop when a write depends on a read. - Count with
suminstead of a check loop. - Treat expiry, coalesced watches, and at-least-once queue delivery as loose contracts. Add exactness in your own code when the database promises only eventual behavior.
- Store records rather than files, and data rather than behavior.
Related
- Understanding the file system covers blobs that will not fit in the 64 KiB value limit.
- Deno Deploy adds replication and a read
consistencyoption; that belongs to a future Deno Deploy entry. - The streams returned by
watchbelong to a future streams entry.