diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/build.rs | 5 | ||||
-rw-r--r-- | cli/tests/unit/kv_test.ts | 933 | ||||
-rw-r--r-- | cli/tests/unit/test_util.ts | 1 | ||||
-rw-r--r-- | cli/tsc/dts/lib.deno.unstable.d.ts | 499 |
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. |