summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/build.rs5
-rw-r--r--cli/tests/unit/kv_test.ts933
-rw-r--r--cli/tests/unit/test_util.ts1
-rw-r--r--cli/tsc/dts/lib.deno.unstable.d.ts499
4 files changed, 1438 insertions, 0 deletions
diff --git a/cli/build.rs b/cli/build.rs
index ecd7ed1be..a4f8ee92d 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -8,6 +8,7 @@ use deno_core::Extension;
use deno_core::ExtensionFileSource;
use deno_core::ExtensionFileSourceCode;
use deno_runtime::deno_cache::SqliteBackedCache;
+use deno_runtime::deno_kv::sqlite::SqliteDbHandler;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::*;
@@ -353,6 +354,10 @@ fn create_cli_snapshot(snapshot_path: PathBuf) {
None,
),
deno_tls::deno_tls::init_ops(),
+ deno_kv::deno_kv::init_ops(
+ SqliteDbHandler::<PermissionsContainer>::new(None),
+ false, // No --unstable.
+ ),
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Default::default()),
diff --git a/cli/tests/unit/kv_test.ts b/cli/tests/unit/kv_test.ts
new file mode 100644
index 000000000..7bb4656c1
--- /dev/null
+++ b/cli/tests/unit/kv_test.ts
@@ -0,0 +1,933 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ AssertionError,
+ assertRejects,
+ assertThrows,
+} from "./test_util.ts";
+
+function dbTest(name: string, fn: (db: Deno.Kv) => Promise<void>) {
+ Deno.test({
+ name,
+ async fn() {
+ const db: Deno.Kv = await Deno.openKv(
+ ":memory:",
+ );
+ try {
+ await fn(db);
+ } finally {
+ await db.close();
+ }
+ },
+ });
+}
+
+dbTest("basic read-write-delete and versionstamps", async (db) => {
+ const result1 = await db.get(["a"]);
+ assertEquals(result1.key, ["a"]);
+ assertEquals(result1.value, null);
+ assertEquals(result1.versionstamp, null);
+
+ await db.set(["a"], "b");
+ const result2 = await db.get(["a"]);
+ assertEquals(result2.key, ["a"]);
+ assertEquals(result2.value, "b");
+ assertEquals(result2.versionstamp, "00000000000000010000");
+
+ await db.set(["a"], "c");
+ const result3 = await db.get(["a"]);
+ assertEquals(result3.key, ["a"]);
+ assertEquals(result3.value, "c");
+ assertEquals(result3.versionstamp, "00000000000000020000");
+
+ await db.delete(["a"]);
+ const result4 = await db.get(["a"]);
+ assertEquals(result4.key, ["a"]);
+ assertEquals(result4.value, null);
+ assertEquals(result4.versionstamp, null);
+});
+
+const VALUE_CASES = [
+ { name: "string", value: "hello" },
+ { name: "number", value: 42 },
+ { name: "bigint", value: 42n },
+ { name: "boolean", value: true },
+ { name: "null", value: null },
+ { name: "undefined", value: undefined },
+ { name: "Date", value: new Date(0) },
+ { name: "Uint8Array", value: new Uint8Array([1, 2, 3]) },
+ { name: "ArrayBuffer", value: new ArrayBuffer(3) },
+ { name: "array", value: [1, 2, 3] },
+ { name: "object", value: { a: 1, b: 2 } },
+ { name: "nested array", value: [[1, 2], [3, 4]] },
+ { name: "nested object", value: { a: { b: 1 } } },
+];
+
+for (const { name, value } of VALUE_CASES) {
+ dbTest(`set and get ${name} value`, async (db) => {
+ await db.set(["a"], value);
+ const result = await db.get(["a"]);
+ assertEquals(result.key, ["a"]);
+ assertEquals(result.value, value);
+ });
+}
+
+dbTest("set and get recursive object", async (db) => {
+ // deno-lint-ignore no-explicit-any
+ const value: any = { a: undefined };
+ value.a = value;
+ await db.set(["a"], value);
+ const result = await db.get(["a"]);
+ assertEquals(result.key, ["a"]);
+ // deno-lint-ignore no-explicit-any
+ const resultValue: any = result.value;
+ assert(resultValue.a === resultValue);
+});
+
+const keys = [
+ ["a"],
+ ["a", "b"],
+ ["a", "b", "c"],
+ [1],
+ ["a", 1],
+ ["a", 1, "b"],
+ [1n],
+ ["a", 1n],
+ ["a", 1n, "b"],
+ [true],
+ ["a", true],
+ ["a", true, "b"],
+ [new Uint8Array([1, 2, 3])],
+ ["a", new Uint8Array([1, 2, 3])],
+ ["a", new Uint8Array([1, 2, 3]), "b"],
+ [1, 1n, true, new Uint8Array([1, 2, 3]), "a"],
+];
+
+for (const key of keys) {
+ dbTest(`set and get ${Deno.inspect(key)} key`, async (db) => {
+ await db.set(key, "b");
+ const result = await db.get(key);
+ assertEquals(result.key, key);
+ assertEquals(result.value, "b");
+ });
+}
+
+const INVALID_KEYS = [
+ [null],
+ [undefined],
+ [],
+ [{}],
+ [new Date()],
+ [new ArrayBuffer(3)],
+ [new Uint8Array([1, 2, 3]).buffer],
+ [["a", "b"]],
+];
+
+for (const key of INVALID_KEYS) {
+ dbTest(`set and get invalid key ${Deno.inspect(key)}`, async (db) => {
+ await assertRejects(
+ async () => {
+ // @ts-ignore - we are testing invalid keys
+ await db.set(key, "b");
+ },
+ Error,
+ );
+ });
+}
+
+dbTest("compare and mutate", async (db) => {
+ await db.set(["t"], "1");
+
+ const currentValue = await db.get(["t"]);
+ assertEquals(currentValue.versionstamp, "00000000000000010000");
+
+ let ok = await db.atomic()
+ .check({ key: ["t"], versionstamp: currentValue.versionstamp })
+ .set(currentValue.key, "2")
+ .commit();
+ assertEquals(ok, true);
+
+ const newValue = await db.get(["t"]);
+ assertEquals(newValue.versionstamp, "00000000000000020000");
+ assertEquals(newValue.value, "2");
+
+ ok = await db.atomic()
+ .check({ key: ["t"], versionstamp: currentValue.versionstamp })
+ .set(currentValue.key, "3")
+ .commit();
+ assertEquals(ok, false);
+
+ const newValue2 = await db.get(["t"]);
+ assertEquals(newValue2.versionstamp, "00000000000000020000");
+ assertEquals(newValue2.value, "2");
+});
+
+dbTest("compare and mutate not exists", async (db) => {
+ let ok = await db.atomic()
+ .check({ key: ["t"], versionstamp: null })
+ .set(["t"], "1")
+ .commit();
+ assertEquals(ok, true);
+
+ const newValue = await db.get(["t"]);
+ assertEquals(newValue.versionstamp, "00000000000000010000");
+ assertEquals(newValue.value, "1");
+
+ ok = await db.atomic()
+ .check({ key: ["t"], versionstamp: null })
+ .set(["t"], "2")
+ .commit();
+ assertEquals(ok, false);
+});
+
+dbTest("compare multiple and mutate", async (db) => {
+ await db.set(["t1"], "1");
+ await db.set(["t2"], "2");
+
+ const currentValue1 = await db.get(["t1"]);
+ assertEquals(currentValue1.versionstamp, "00000000000000010000");
+ const currentValue2 = await db.get(["t2"]);
+ assertEquals(currentValue2.versionstamp, "00000000000000020000");
+
+ const ok = await db.atomic()
+ .check({ key: ["t1"], versionstamp: currentValue1.versionstamp })
+ .check({ key: ["t2"], versionstamp: currentValue2.versionstamp })
+ .set(currentValue1.key, "3")
+ .set(currentValue2.key, "4")
+ .commit();
+ assertEquals(ok, true);
+
+ const newValue1 = await db.get(["t1"]);
+ assertEquals(newValue1.versionstamp, "00000000000000030000");
+ assertEquals(newValue1.value, "3");
+ const newValue2 = await db.get(["t2"]);
+ assertEquals(newValue2.versionstamp, "00000000000000030000");
+ assertEquals(newValue2.value, "4");
+
+ // just one of the two checks failed
+ const ok2 = await db.atomic()
+ .check({ key: ["t1"], versionstamp: newValue1.versionstamp })
+ .check({ key: ["t2"], versionstamp: null })
+ .set(newValue1.key, "5")
+ .set(newValue2.key, "6")
+ .commit();
+ assertEquals(ok2, false);
+
+ const newValue3 = await db.get(["t1"]);
+ assertEquals(newValue3.versionstamp, "00000000000000030000");
+ assertEquals(newValue3.value, "3");
+ const newValue4 = await db.get(["t2"]);
+ assertEquals(newValue4.versionstamp, "00000000000000030000");
+ assertEquals(newValue4.value, "4");
+});
+
+dbTest("atomic mutation ordering (set before delete)", async (db) => {
+ await db.set(["a"], "1");
+ const ok1 = await db.atomic()
+ .set(["a"], "2")
+ .delete(["a"])
+ .commit();
+ assert(ok1);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, null);
+});
+
+dbTest("atomic mutation ordering (delete before set)", async (db) => {
+ await db.set(["a"], "1");
+ const ok1 = await db.atomic()
+ .delete(["a"])
+ .set(["a"], "2")
+ .commit();
+ assert(ok1);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, "2");
+});
+
+dbTest("atomic mutation type=set", async (db) => {
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: "1", type: "set" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, "1");
+});
+
+dbTest("atomic mutation type=set overwrite", async (db) => {
+ await db.set(["a"], "1");
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: "2", type: "set" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, "2");
+});
+
+dbTest("atomic mutation type=delete", async (db) => {
+ await db.set(["a"], "1");
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], type: "delete" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, null);
+});
+
+dbTest("atomic mutation type=delete no exists", async (db) => {
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], type: "delete" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, null);
+});
+
+dbTest("atomic mutation type=sum", async (db) => {
+ await db.set(["a"], new Deno.KvU64(10n));
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, new Deno.KvU64(11n));
+});
+
+dbTest("atomic mutation type=sum no exists", async (db) => {
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assert(result.value);
+ assertEquals(result.value, new Deno.KvU64(1n));
+});
+
+dbTest("atomic mutation type=sum wrap around", async (db) => {
+ await db.set(["a"], new Deno.KvU64(0xffffffffffffffffn));
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(10n), type: "sum" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, new Deno.KvU64(9n));
+
+ const ok2 = await db.atomic()
+ .mutate({
+ key: ["a"],
+ value: new Deno.KvU64(0xffffffffffffffffn),
+ type: "sum",
+ })
+ .commit();
+ assert(ok2);
+ const result2 = await db.get(["a"]);
+ assertEquals(result2.value, new Deno.KvU64(8n));
+});
+
+dbTest("atomic mutation type=sum wrong type in db", async (db) => {
+ await db.set(["a"], 1);
+ assertRejects(
+ async () => {
+ await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'sum' mutation on a non-U64 value in the database",
+ );
+});
+
+dbTest("atomic mutation type=sum wrong type in mutation", async (db) => {
+ await db.set(["a"], new Deno.KvU64(1n));
+ assertRejects(
+ async () => {
+ await db.atomic()
+ // @ts-expect-error wrong type is intentional
+ .mutate({ key: ["a"], value: 1, type: "sum" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'sum' mutation on a non-U64 operand",
+ );
+});
+
+dbTest("atomic mutation type=min", async (db) => {
+ await db.set(["a"], new Deno.KvU64(10n));
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(5n), type: "min" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, new Deno.KvU64(5n));
+
+ const ok2 = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(15n), type: "min" })
+ .commit();
+ assert(ok2);
+ const result2 = await db.get(["a"]);
+ assertEquals(result2.value, new Deno.KvU64(5n));
+});
+
+dbTest("atomic mutation type=min no exists", async (db) => {
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "min" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assert(result.value);
+ assertEquals(result.value, new Deno.KvU64(1n));
+});
+
+dbTest("atomic mutation type=min wrong type in db", async (db) => {
+ await db.set(["a"], 1);
+ assertRejects(
+ async () => {
+ await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "min" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'min' mutation on a non-U64 value in the database",
+ );
+});
+
+dbTest("atomic mutation type=min wrong type in mutation", async (db) => {
+ await db.set(["a"], new Deno.KvU64(1n));
+ assertRejects(
+ async () => {
+ await db.atomic()
+ // @ts-expect-error wrong type is intentional
+ .mutate({ key: ["a"], value: 1, type: "min" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'min' mutation on a non-U64 operand",
+ );
+});
+
+dbTest("atomic mutation type=max", async (db) => {
+ await db.set(["a"], new Deno.KvU64(10n));
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(5n), type: "max" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assertEquals(result.value, new Deno.KvU64(10n));
+
+ const ok2 = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(15n), type: "max" })
+ .commit();
+ assert(ok2);
+ const result2 = await db.get(["a"]);
+ assertEquals(result2.value, new Deno.KvU64(15n));
+});
+
+dbTest("atomic mutation type=max no exists", async (db) => {
+ const ok = await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "max" })
+ .commit();
+ assert(ok);
+ const result = await db.get(["a"]);
+ assert(result.value);
+ assertEquals(result.value, new Deno.KvU64(1n));
+});
+
+dbTest("atomic mutation type=max wrong type in db", async (db) => {
+ await db.set(["a"], 1);
+ assertRejects(
+ async () => {
+ await db.atomic()
+ .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "max" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'max' mutation on a non-U64 value in the database",
+ );
+});
+
+dbTest("atomic mutation type=max wrong type in mutation", async (db) => {
+ await db.set(["a"], new Deno.KvU64(1n));
+ assertRejects(
+ async () => {
+ await db.atomic()
+ // @ts-expect-error wrong type is intentional
+ .mutate({ key: ["a"], value: 1, type: "max" })
+ .commit();
+ },
+ TypeError,
+ "Failed to perform 'max' mutation on a non-U64 operand",
+ );
+});
+
+Deno.test("KvU64 comparison", () => {
+ const a = new Deno.KvU64(1n);
+ const b = new Deno.KvU64(1n);
+ assertEquals(a, b);
+ assertThrows(() => {
+ assertEquals(a, new Deno.KvU64(2n));
+ }, AssertionError);
+});
+
+Deno.test("KvU64 overflow", () => {
+ assertThrows(() => {
+ new Deno.KvU64(2n ** 64n);
+ }, RangeError);
+});
+
+Deno.test("KvU64 underflow", () => {
+ assertThrows(() => {
+ new Deno.KvU64(-1n);
+ }, RangeError);
+});
+
+Deno.test("KvU64 frozen", () => {
+ const a = new Deno.KvU64(1n);
+ assertThrows(() => {
+ // @ts-expect-error value is readonly
+ a.value = 2n;
+ }, TypeError);
+});
+
+Deno.test("KvU64 unbox", () => {
+ const a = new Deno.KvU64(1n);
+ assertEquals(a.value, 1n);
+});
+
+async function collect(iter: Deno.KvListIterator): Promise<Deno.KvEntry[]> {
+ const entries: Deno.KvEntry[] = [];
+ for await (const entry of iter) {
+ entries.push(entry);
+ }
+ return entries;
+}
+
+async function setupData(db: Deno.Kv) {
+ await db.atomic()
+ .set(["a"], -1)
+ .set(["a", "a"], 0)
+ .set(["a", "b"], 1)
+ .set(["a", "c"], 2)
+ .set(["a", "d"], 3)
+ .set(["a", "e"], 4)
+ .set(["b"], 99)
+ .set(["b", "a"], 100)
+ .commit();
+}
+
+dbTest("list prefix", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"] }));
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix empty", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["c"] }));
+ assertEquals(entries.length, 0);
+
+ const entries2 = await collect(db.list({ prefix: ["a", "f"] }));
+ assertEquals(entries2.length, 0);
+});
+
+dbTest("list prefix with start", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"], start: ["a", "c"] }));
+ assertEquals(entries, [
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with start empty", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"], start: ["a", "f"] }));
+ assertEquals(entries.length, 0);
+});
+
+dbTest("list prefix with end", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"], end: ["a", "c"] }));
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with end empty", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"], end: ["a", "a"] }));
+ assertEquals(entries.length, 0);
+});
+
+dbTest("list prefix reverse", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(db.list({ prefix: ["a"] }, { reverse: true }));
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix reverse with start", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"], start: ["a", "c"] }, { reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix reverse with start empty", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"], start: ["a", "f"] }, { reverse: true }),
+ );
+ assertEquals(entries.length, 0);
+});
+
+dbTest("list prefix reverse with end", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"], end: ["a", "c"] }, { reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix reverse with end empty", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"], end: ["a", "a"] }, { reverse: true }),
+ );
+ assertEquals(entries.length, 0);
+});
+
+dbTest("list prefix limit", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"] }, { limit: 2 }));
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix limit reverse", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"] }, { limit: 2, reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with small batch size", async (db) => {
+ await setupData(db);
+ const entries = await collect(db.list({ prefix: ["a"] }, { batchSize: 2 }));
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with small batch size reverse", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"] }, { batchSize: 2, reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with small batch size and limit", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"] }, { batchSize: 2, limit: 3 }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with small batch size and limit reverse", async (db) => {
+ await setupData(db);
+ const entries = await collect(
+ db.list({ prefix: ["a"] }, { batchSize: 2, limit: 3, reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with manual cursor", async (db) => {
+ await setupData(db);
+
+ const iterator = db.list({ prefix: ["a"] }, { limit: 2 });
+ const values = await collect(iterator);
+ assertEquals(values, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ ]);
+
+ const cursor = iterator.cursor;
+ assertEquals(cursor, "AmIA");
+
+ const iterator2 = db.list({ prefix: ["a"] }, { cursor });
+ const values2 = await collect(iterator2);
+ assertEquals(values2, [
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list prefix with manual cursor reverse", async (db) => {
+ await setupData(db);
+
+ const iterator = db.list({ prefix: ["a"] }, { limit: 2, reverse: true });
+ const values = await collect(iterator);
+ assertEquals(values, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ ]);
+
+ const cursor = iterator.cursor;
+ assertEquals(cursor, "AmQA");
+
+ const iterator2 = db.list({ prefix: ["a"] }, { cursor, reverse: true });
+ const values2 = await collect(iterator2);
+ assertEquals(values2, [
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(
+ db.list({ start: ["a", "a"], end: ["a", "z"] }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range reverse", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(
+ db.list({ start: ["a", "a"], end: ["a", "z"] }, { reverse: true }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range with limit", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(
+ db.list({ start: ["a", "a"], end: ["a", "z"] }, { limit: 3 }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range with limit reverse", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(
+ db.list({ start: ["a", "a"], end: ["a", "z"] }, {
+ limit: 3,
+ reverse: true,
+ }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range nesting", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(db.list({ start: ["a"], end: ["a", "d"] }));
+ assertEquals(entries, [
+ { key: ["a"], value: -1, versionstamp: "00000000000000010000" },
+ { key: ["a", "a"], value: 0, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range short", async (db) => {
+ await setupData(db);
+
+ const entries = await collect(
+ db.list({ start: ["a", "b"], end: ["a", "d"] }),
+ );
+ assertEquals(entries, [
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range with manual cursor", async (db) => {
+ await setupData(db);
+
+ const iterator = db.list({ start: ["a", "b"], end: ["a", "z"] }, {
+ limit: 2,
+ });
+ const entries = await collect(iterator);
+ assertEquals(entries, [
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ ]);
+
+ const cursor = iterator.cursor;
+ const iterator2 = db.list({ start: ["a", "b"], end: ["a", "z"] }, {
+ cursor,
+ });
+ const entries2 = await collect(iterator2);
+ assertEquals(entries2, [
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list range with manual cursor reverse", async (db) => {
+ await setupData(db);
+
+ const iterator = db.list({ start: ["a", "b"], end: ["a", "z"] }, {
+ limit: 2,
+ reverse: true,
+ });
+ const entries = await collect(iterator);
+ assertEquals(entries, [
+ { key: ["a", "e"], value: 4, versionstamp: "00000000000000010000" },
+ { key: ["a", "d"], value: 3, versionstamp: "00000000000000010000" },
+ ]);
+
+ const cursor = iterator.cursor;
+ const iterator2 = db.list({ start: ["a", "b"], end: ["a", "z"] }, {
+ cursor,
+ reverse: true,
+ });
+ const entries2 = await collect(iterator2);
+ assertEquals(entries2, [
+ { key: ["a", "c"], value: 2, versionstamp: "00000000000000010000" },
+ { key: ["a", "b"], value: 1, versionstamp: "00000000000000010000" },
+ ]);
+});
+
+dbTest("list invalid selector", async (db) => {
+ await setupData(db);
+
+ await assertRejects(async () => {
+ await collect(
+ db.list({ prefix: ["a"], start: ["a", "b"], end: ["a", "c"] }),
+ );
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await collect(
+ // @ts-expect-error missing end
+ db.list({ start: ["a", "b"] }),
+ );
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await collect(
+ // @ts-expect-error missing start
+ db.list({ end: ["a", "b"] }),
+ );
+ }, TypeError);
+});
+
+dbTest("invalid versionstamp in atomic check rejects", async (db) => {
+ await assertRejects(async () => {
+ await db.atomic().check({ key: ["a"], versionstamp: "" }).commit();
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await db.atomic().check({ key: ["a"], versionstamp: "xx".repeat(10) })
+ .commit();
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await db.atomic().check({ key: ["a"], versionstamp: "aa".repeat(11) })
+ .commit();
+ }, TypeError);
+});
+
+dbTest("invalid mutation type rejects", async (db) => {
+ await assertRejects(async () => {
+ await db.atomic()
+ // @ts-expect-error invalid type + value combo
+ .mutate({ key: ["a"], type: "set" })
+ .commit();
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await db.atomic()
+ // @ts-expect-error invalid type + value combo
+ .mutate({ key: ["a"], type: "delete", value: "123" })
+ .commit();
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await db.atomic()
+ // @ts-expect-error invalid type
+ .mutate({ key: ["a"], type: "foobar" })
+ .commit();
+ }, TypeError);
+
+ await assertRejects(async () => {
+ await db.atomic()
+ // @ts-expect-error invalid type
+ .mutate({ key: ["a"], type: "foobar", value: "123" })
+ .commit();
+ }, TypeError);
+});
diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts
index 64c399b2d..23713faf4 100644
--- a/cli/tests/unit/test_util.ts
+++ b/cli/tests/unit/test_util.ts
@@ -7,6 +7,7 @@ export {
assert,
assertEquals,
assertFalse,
+ AssertionError,
assertMatch,
assertNotEquals,
assertNotStrictEquals,
diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts
index 198b634fd..b042ceabe 100644
--- a/cli/tsc/dts/lib.deno.unstable.d.ts
+++ b/cli/tsc/dts/lib.deno.unstable.d.ts
@@ -1518,6 +1518,505 @@ declare namespace Deno {
* @category HTTP Server
*/
export function upgradeHttpRaw(request: Request): [Deno.Conn, Uint8Array];
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * Open a new {@linkcode Deno.Kv} connection to persist data.
+ *
+ * When a path is provided, the database will be persisted to disk at that
+ * path. Read and write access to the file is required.
+ *
+ * When no path is provided, the database will be opened in a default path for
+ * the current script. This location is persistent across script runs and is
+ * keyed on the origin storage key (the same key that is used to determine
+ * `localStorage` persistence). More information about the origin storage key
+ * can be found in the Deno Manual.
+ *
+ * @tags allow-read, allow-write
+ * @category KV
+ */
+ export function openKv(path?: string): Promise<Deno.Kv>;
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A key to be persisted in a {@linkcode Deno.Kv}. A key is a sequence
+ * of {@linkcode Deno.KvKeyPart}s.
+ *
+ * Keys are ordered lexicographically by their parts. The first part is the
+ * most significant, and the last part is the least significant. The order of
+ * the parts is determined by both the type and the value of the part. The
+ * relative significance of the types can be found in documentation for the
+ * {@linkcode Deno.KvKeyPart} type.
+ *
+ * @category KV
+ */
+ export type KvKey = readonly KvKeyPart[];
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A single part of a {@linkcode Deno.KvKey}. Parts are ordered
+ * lexicographically, first by their type, and within a given type by their
+ * value.
+ *
+ * The ordering of types is as follows:
+ *
+ * 1. `Uint8Array`
+ * 2. `string`
+ * 3. `number`
+ * 4. `bigint`
+ * 5. `boolean`
+ *
+ * Within a given type, the ordering is as follows:
+ *
+ * - `Uint8Array` is ordered by the byte ordering of the array
+ * - `string` is ordered by the byte ordering of the UTF-8 encoding of the
+ * string
+ * - `number` is ordered following this pattern: `-NaN`
+ * < `-Infinity` < `-100.0` < `-1.0` < -`0.5` < `-0.0` < `0.0` < `0.5`
+ * < `1.0` < `100.0` < `Infinity` < `NaN`
+ * - `bigint` is ordered by mathematical ordering, with the largest negative
+ * number being the least first value, and the largest positive number
+ * being the last value
+ * - `boolean` is ordered by `false` < `true`
+ *
+ * This means that the part `1.0` (a number) is ordered before the part `2.0`
+ * (also a number), but is greater than the part `0n` (a bigint), because
+ * `1.0` is a number and `0n` is a bigint, and type ordering has precedence
+ * over the ordering of values within a type.
+ *
+ * @category KV
+ */
+ export type KvKeyPart = Uint8Array | string | number | bigint | boolean;
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * Consistency level of a KV operation.
+ *
+ * - `strong` - This operation must be strongly-consistent.
+ * - `eventual` - Eventually-consistent behavior is allowed.
+ *
+ * @category KV
+ */
+ export type KvConsistencyLevel = "strong" | "eventual";
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A selector that selects the range of data returned by a list operation on a
+ * {@linkcode Deno.Kv}.
+ *
+ * The selector can either be a prefix selector or a range selector. A prefix
+ * selector selects all keys that start with the given prefix (optionally
+ * starting at a given key). A range selector selects all keys that are
+ * lexicographically between the given start and end keys.
+ *
+ * @category KV
+ */
+ export type KvListSelector =
+ | { prefix: KvKey }
+ | { prefix: KvKey; start: KvKey }
+ | { prefix: KvKey; end: KvKey }
+ | { start: KvKey; end: KvKey };
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A mutation to a key in a {@linkcode Deno.Kv}. A mutation is a
+ * combination of a key, a value, and a type. The type determines how the
+ * mutation is applied to the key.
+ *
+ * - `set` - Sets the value of the key to the given value, overwriting any
+ * existing value.
+ * - `delete` - Deletes the key from the database. The mutation is a no-op if
+ * the key does not exist.
+ * - `sum` - Adds the given value to the existing value of the key. Both the
+ * value specified in the mutation, and any existing value must be of type
+ * `Deno.KvU64`. If the key does not exist, the value is set to the given
+ * value (summed with 0).
+ * - `max` - Sets the value of the key to the maximum of the existing value
+ * and the given value. Both the value specified in the mutation, and any
+ * existing value must be of type `Deno.KvU64`. If the key does not exist,
+ * the value is set to the given value.
+ * - `min` - Sets the value of the key to the minimum of the existing value
+ * and the given value. Both the value specified in the mutation, and any
+ * existing value must be of type `Deno.KvU64`. If the key does not exist,
+ * the value is set to the given value.
+ *
+ * @category KV
+ */
+ export type KvMutation =
+ & { key: KvKey }
+ & (
+ | { type: "set"; value: unknown }
+ | { type: "delete" }
+ | { type: "sum"; value: KvU64 }
+ | { type: "max"; value: KvU64 }
+ | { type: "min"; value: KvU64 }
+ );
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * An iterator over a range of data entries in a {@linkcode Deno.Kv}.
+ *
+ * The cursor getter returns the cursor that can be used to resume the
+ * iteration from the current position in the future.
+ *
+ * @category KV
+ */
+ export class KvListIterator implements AsyncIterableIterator<KvEntry> {
+ /**
+ * Returns the cursor of the current position in the iteration. This cursor
+ * can be used to resume the iteration from the current position in the
+ * future by passing it to the `cursor` option of the `list` method.
+ */
+ get cursor(): string;
+
+ next(): Promise<IteratorResult<KvEntry, any>>;
+ [Symbol.asyncIterator](): AsyncIterableIterator<KvEntry>;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A versioned pair of key and value in a {@linkcode Deno.Kv}.
+ *
+ * The `versionstamp` is a string that represents the current version of the
+ * key-value pair. It can be used to perform atomic operations on the KV store
+ * by passing it to the `check` method of a {@linkcode Deno.AtomicOperation}.
+ * A `null` versionstamp indicates that no value exists for the given key in
+ * the KV store.
+ *
+ * @category KV
+ */
+ export interface KvEntry {
+ key: KvKey;
+ value: unknown;
+ versionstamp: string | null;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * Options for listing key-value pairs in a {@linkcode Deno.Kv}.
+ *
+ * @category KV
+ */
+ export interface KvListOptions {
+ /**
+ * The maximum number of key-value pairs to return. If not specified, all
+ * matching key-value pairs will be returned.
+ */
+ limit?: number;
+ /**
+ * The cursor to resume the iteration from. If not specified, the iteration
+ * will start from the beginning.
+ */
+ cursor?: string;
+ /**
+ * Whether to reverse the order of the returned key-value pairs. If not
+ * specified, the order will be ascending from the start of the range as per
+ * the lexicographical ordering of the keys. If `true`, the order will be
+ * descending from the end of the range.
+ *
+ * The default value is `false`.
+ */
+ reverse?: boolean;
+ /**
+ * The consistency level of the list operation. The default consistency
+ * level is "strong". Some use cases can benefit from using a weaker
+ * consistency level. For more information on consistency levels, see the
+ * documentation for {@linkcode Deno.KvConsistencyLevel}.
+ *
+ * List operations are performed in batches (in sizes specified by the
+ * `batchSize` option). The consistency level of the list operation is
+ * applied to each batch individually. This means that while each batch is
+ * guaranteed to be consistent within itself, the entire list operation may
+ * not be consistent across batches because a mutation may be applied to a
+ * key-value pair between batches, in a batch that has already been returned
+ * by the list operation.
+ */
+ consistency?: KvConsistencyLevel;
+ /**
+ * The size of the batches in which the list operation is performed. Larger
+ * or smaller batch sizes may positively or negatively affect the
+ * performance of a list operation depending on the specific use case and
+ * iteration behavior. Slow iterating queries may benefit from using a
+ * smaller batch size for increased overall consistency, while fast
+ * iterating queries may benefit from using a larger batch size for better
+ * performance.
+ *
+ * The default batch size is equal to the `limit` option, or 100 if this is
+ * unset. The maximum value for this option is 500. Larger values will be
+ * clamped.
+ */
+ batchSize?: number;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A check to perform as part of a {@linkcode Deno.AtomicOperation}. The check
+ * will fail if the versionstamp for the key-value pair in the KV store does
+ * not match the given versionstamp. A check with a `null` versionstamp checks
+ * that the key-value pair does not currently exist in the KV store.
+ *
+ * @category KV
+ */
+ export interface AtomicCheck {
+ key: KvKey;
+ versionstamp: string | null;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * An operation on a {@linkcode Deno.Kv} that can be performed
+ * atomically. Atomic operations do not auto-commit, and must be committed
+ * explicitly by calling the `commit` method.
+ *
+ * Atomic operations can be used to perform multiple mutations on the KV store
+ * in a single atomic transaction. They can also be used to perform
+ * conditional mutations by specifying one or more
+ * {@linkcode Deno.AtomicCheck}s that ensure that a mutation is only performed
+ * if the key-value pair in the KV has a specific versionstamp. If any of the
+ * checks fail, the entire operation will fail and no mutations will be made.
+ *
+ * The ordering of mutations is guaranteed to be the same as the ordering of
+ * the mutations specified in the operation. Checks are performed before any
+ * mutations are performed. The ordering of checks is unobservable.
+ *
+ * Atomic operations can be used to implement optimistic locking, where a
+ * mutation is only performed if the key-value pair in the KV store has not
+ * been modified since the last read. This can be done by specifying a check
+ * that ensures that the versionstamp of the key-value pair matches the
+ * versionstamp that was read. If the check fails, the mutation will not be
+ * performed and the operation will fail. One can then retry the read-modify-
+ * write operation in a loop until it succeeds.
+ *
+ * The `commit` method of an atomic operation returns a boolean indicating
+ * whether checks passed and mutations were performed. If the operation failed
+ * because of a failed check, the return value will be `false`. If the
+ * operation failed for any other reason (storage error, invalid value, etc.),
+ * an exception will be thrown.
+ *
+ * @category KV
+ */
+ export class AtomicOperation {
+ /**
+ * Add to the operation a check that ensures that the versionstamp of the
+ * key-value pair in the KV store matches the given versionstamp. If the
+ * check fails, the entire operation will fail and no mutations will be
+ * performed during the commit.
+ */
+ check(...checks: AtomicCheck[]): this;
+ /**
+ * Add to the operation a mutation that performs the specified mutation on
+ * the specified key if all checks pass during the commit. The types and
+ * semantics of all available mutations are described in the documentation
+ * for {@linkcode Deno.KvMutation}.
+ */
+ mutate(...mutations: KvMutation[]): this;
+ /**
+ * Add to the operation a mutation that sets the value of the specified key
+ * to the specified value if all checks pass during the commit.
+ */
+ set(key: KvKey, value: unknown): this;
+ /**
+ * Add to the operation a mutation that deletes the specified key if all
+ * checks pass during the commit.
+ */
+ delete(key: KvKey): this;
+ /**
+ * Commit the operation to the KV store. Returns a boolean indicating
+ * whether checks passed and mutations were performed. If the operation
+ * failed because of a failed check, the return value will be `false`. If
+ * the operation failed for any other reason (storage error, invalid value,
+ * etc.), an exception will be thrown.
+ *
+ * If the commit returns `false`, one may create a new atomic operation with
+ * updated checks and mutations and attempt to commit it again. See the note
+ * on optimistic locking in the documentation for {@linkcode Deno.AtomicOperation}.
+ */
+ commit(): Promise<boolean>;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * A key-value database that can be used to store and retrieve data.
+ *
+ * Data is stored as key-value pairs, where the key is a {@linkcode Deno.KvKey}
+ * and the value is an arbitrary structured-serializable JavaScript value.
+ * Keys are ordered lexicographically as described in the documentation for
+ * {@linkcode Deno.KvKey}. Keys are unique within a database, and the last
+ * value set for a given key is the one that is returned when reading the
+ * key. Keys can be deleted from the database, in which case they will no
+ * longer be returned when reading keys.
+ *
+ * Values can be any structured-serializable JavaScript value (objects,
+ * arrays, strings, numbers, etc.). The special value {@linkcode Deno.KvU64}
+ * can be used to store 64-bit unsigned integers in the database. This special
+ * value can not be nested within other objects or arrays. In addition to the
+ * regular database mutation operations, the unsigned 64-bit integer value
+ * also supports `sum`, `max`, and `min` mutations.
+ *
+ * Keys are versioned on write by assigning the key an ever-increasing
+ * "versionstamp". The versionstamp represents the version of a key-value pair
+ * in the database at some point in time, and can be used to perform
+ * transactional operations on the database without requiring any locking.
+ * This is enabled by atomic operations, which can have conditions that ensure
+ * that the operation only succeeds if the versionstamp of the key-value pair
+ * matches an expected versionstamp.
+ *
+ * Keys have a maximum length of 2048 bytes after serialization. Values have a
+ * maximum length of 16 KiB after serialization. Serialization of both keys
+ * and values is somewhat opaque, but one can usually assume that the
+ * serialization of any value is about the same length as the resulting string
+ * of a JSON serialization of that same value.
+ *
+ * @category KV
+ */
+ export class Kv {
+ /**
+ * Retrieve the value and versionstamp for the given key from the database
+ * in the form of a {@linkcode Deno.KvEntry}. If no value exists for the key,
+ * the returned entry will have a `null` value and versionstamp.
+ *
+ * ```ts
+ * const db = await Deno.openKv();
+ * const result = await db.get(["foo"]);
+ * result.key; // ["foo"]
+ * result.value; // "bar"
+ * result.versionstamp; // "00000000000000010000"
+ * ```
+ *
+ * The `consistency` option can be used to specify the consistency level
+ * for the read operation. The default consistency level is "strong". Some
+ * use cases can benefit from using a weaker consistency level. For more
+ * information on consistency levels, see the documentation for
+ * {@linkcode Deno.KvConsistencyLevel}.
+ */
+ get(
+ key: KvKey,
+ options?: { consistency?: KvConsistencyLevel },
+ ): Promise<KvEntry>;
+
+ /**
+ * Retrieve multiple values and versionstamps from the database in the form
+ * of an array of {@linkcode Deno.KvEntry} objects. The returned array will
+ * have the same length as the `keys` array, and the entries will be in the
+ * same order as the keys. If no value exists for a given key, the returned
+ * entry will have a `null` value and versionstamp.
+ *
+ * ```ts
+ * const db = await Deno.openKv();
+ * const result = await db.getMany([["foo"], ["baz"]]);
+ * result[0].key; // ["foo"]
+ * result[0].value; // "bar"
+ * result[0].versionstamp; // "00000000000000010000"
+ * result[1].key; // ["baz"]
+ * result[1].value; // null
+ * result[1].versionstamp; // null
+ * ```
+ *
+ * The `consistency` option can be used to specify the consistency level
+ * for the read operation. The default consistency level is "strong". Some
+ * use cases can benefit from using a weaker consistency level. For more
+ * information on consistency levels, see the documentation for
+ * {@linkcode Deno.KvConsistencyLevel}.
+ */
+ getMany(
+ keys: KvKey[],
+ options?: { consistency?: KvConsistencyLevel },
+ ): Promise<KvEntry[]>;
+
+ /**
+ * Set the value for the given key in the database. If a value already
+ * exists for the key, it will be overwritten.
+ *
+ * ```ts
+ * const db = await Deno.openKv();
+ * await db.set(["foo"], "bar");
+ * ```
+ */
+ set(key: KvKey, value: unknown): Promise<void>;
+
+ /**
+ * Delete the value for the given key from the database. If no value exists
+ * for the key, this operation is a no-op.
+ *
+ * ```ts
+ * const db = await Deno.openKv();
+ * await db.delete(["foo"]);
+ * ```
+ */
+ delete(key: KvKey): Promise<void>;
+
+ /**
+ * Retrieve a list of keys in the database. The returned list is an
+ * {@linkcode Deno.KvListIterator} which can be used to iterate over the
+ * entries in the database.
+ *
+ * Each list operation must specify a selector which is used to specify the
+ * range of keys to return. The selector can either be a prefix selector, or
+ * a range selector:
+ *
+ * - A prefix selector selects all keys that start with the given prefix of
+ * key parts. For example, the selector `["users"]` will select all keys
+ * that start with the prefix `["users"]`, such as `["users", "alice"]`
+ * and `["users", "bob"]`. Note that you can not partially match a key
+ * part, so the selector `["users", "a"]` will not match the key
+ * `["users", "alice"]`. A prefix selector may specify a `start` key that
+ * is used to skip over keys that are lexicographically less than the
+ * start key.
+ * - A range selector selects all keys that are lexicographically between
+ * the given start and end keys (including the start, and excluding the
+ * end). For example, the selector `["users", "a"], ["users", "n"]` will
+ * select all keys that start with the prefix `["users"]` and have a
+ * second key part that is lexicographically between `a` and `n`, such as
+ * `["users", "alice"]`, `["users", "bob"]`, and `["users", "mike"]`, but
+ * not `["users", "noa"]` or `["users", "zoe"]`.
+ *
+ * ```ts
+ * const db = await Deno.openKv();
+ * const entries = db.list({ prefix: ["users"] });
+ * for await (const entry of entries) {
+ * entry.key; // ["users", "alice"]
+ * entry.value; // { name: "Alice" }
+ * entry.versionstamp; // "00000000000000010000"
+ * }
+ * ```
+ *
+ * The `options` argument can be used to specify additional options for the
+ * list operation. See the documentation for {@linkcode Deno.KvListOptions}
+ * for more information.
+ */
+ list(selector: KvListSelector, options?: KvListOptions): KvListIterator;
+
+ /**
+ * Create a new {@linkcode Deno.AtomicOperation} object which can be used to
+ * perform an atomic transaction on the database. This does not perform any
+ * operations on the database - the atomic transaction must be committed
+ * explicitly using the {@linkcode Deno.AtomicOperation.commit} method once
+ * all checks and mutations have been added to the operation.
+ */
+ atomic(): AtomicOperation;
+
+ /**
+ * Close the database connection. This will prevent any further operations
+ * from being performed on the database, but will wait for any in-flight
+ * operations to complete before closing the underlying database connection.
+ */
+ close(): Promise<void>;
+ }
+
+ /** **UNSTABLE**: New API, yet to be vetted.
+ *
+ * Wrapper type for 64-bit unsigned integers for use as values in a
+ * {@linkcode Deno.Kv}.
+ *
+ * @category KV
+ */
+ export class KvU64 {
+ /** Create a new `KvU64` instance from the given bigint value. If the value
+ * is signed or greater than 64-bits, an error will be thrown. */
+ constructor(value: bigint);
+ /** The value of this unsigned 64-bit integer, represented as a bigint. */
+ readonly value: bigint;
+ }
}
/** **UNSTABLE**: New API, yet to be vetted.