Skip to content

Commit 1e0842f

Browse files
committed
feat: add toolbox APIs
1 parent 38a7cbb commit 1e0842f

File tree

5 files changed

+1377
-2
lines changed

5 files changed

+1377
-2
lines changed

.vscode/settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"deno.enable": true,
3-
"deno.unstable": true,
3+
"deno.unstable": ["kv"],
44
"deno.lint": true
55
}

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,43 @@
22

33
A set of tools for working with Deno KV.
44

5+
## Toolbox
6+
7+
The default export of the library is the encapsulation of major functionality of
8+
the library into classes which enhance the capabilities of working with a Deno
9+
KV store, which are also available as individual named exports of the the
10+
library.
11+
12+
There are to variants of the toolbox: `KvToolbox` and `CryptoKvToolbox`. These
13+
provide all the APIs of a `Deno.Kv` and the additional APIs offered by the rest
14+
of the library. The `CryptoKvToolbox` also attempts to encrypt and decrypt blob
15+
values.
16+
17+
Opening a toolbox is similar to opening a `Deno.Kv` store:
18+
19+
```ts
20+
import { openKvToolbox } from "jsr:@kitsonk/kv-toolbox";
21+
22+
const kv = await openKvToolbox();
23+
```
24+
25+
If an encryption key is passed as an option, a `CryptoKvToolbox` instance will
26+
be returned, where when storing and retrieving blobs in the store, they will be
27+
encrypted and decrypted by default:
28+
29+
```ts
30+
import { generateKey, openKvToolbox } from "jsr:@kitsonk/kv-toolbox";
31+
32+
const encryptWith = generateKey();
33+
const kv = await openKvToolbox({ encryptWith });
34+
```
35+
36+
> [!NOTE]
37+
> In practice, encryption keys would need to be persisted from session to
38+
> session. The code above would generate a new key every execution and any
39+
> values stored could not be decrypted. To be practical, generated encryption
40+
> keys need to be stored securely as a secret.
41+
542
## Batched Atomic
643

744
A set of APIs for dealing with the limitation of atomic commit sizes in Deno KV.

deno.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kitsonk/kv-toolbox",
3-
"version": "0.18.0",
3+
"version": "0.19.0-beta.1",
44
"exports": {
55
"./batched_atomic": "./batched_atomic.ts",
66
"./blob": "./blob.ts",

toolbox.test.ts

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import {
2+
assert,
3+
assertEquals,
4+
cleanup,
5+
getPath,
6+
timingSafeEqual,
7+
} from "./_test_util.ts";
8+
import { generateKey } from "./crypto.ts";
9+
10+
import { openKvToolbox } from "./toolbox.ts";
11+
12+
Deno.test({
13+
name: "kvToolbox - open and close",
14+
async fn() {
15+
const path = await getPath();
16+
const kv = await openKvToolbox({ path });
17+
kv.close();
18+
return cleanup();
19+
},
20+
});
21+
22+
Deno.test({
23+
name: "kvToolbox - get and set functionality",
24+
async fn() {
25+
const path = await getPath();
26+
const kv = await openKvToolbox({ path });
27+
const result = await kv.set(["key"], "value");
28+
assert(result.ok);
29+
const maybeEntry = await kv.get(["key"]);
30+
assert(maybeEntry.versionstamp);
31+
assertEquals(maybeEntry.value, "value");
32+
kv.close();
33+
return cleanup();
34+
},
35+
});
36+
37+
Deno.test({
38+
name: "kvToolbox - .keys()",
39+
async fn() {
40+
const path = await getPath();
41+
const kv = await openKvToolbox({ path });
42+
const res = await kv.atomic()
43+
.set(["a"], "a")
44+
.set(["a", "b"], "b")
45+
.set(["a", "b", "c"], "c")
46+
.set(["a", "d", "f", "g"], "g")
47+
.set(["a", "h"], "h")
48+
.set(["e"], "e")
49+
.commit();
50+
assert(res[0].ok);
51+
52+
const actual = await kv.tree();
53+
54+
assertEquals(actual, {
55+
children: [
56+
{
57+
part: "a",
58+
hasValue: true,
59+
children: [
60+
{
61+
part: "b",
62+
hasValue: true,
63+
children: [{ part: "c", hasValue: true }],
64+
},
65+
{
66+
part: "d",
67+
children: [{
68+
part: "f",
69+
children: [{ part: "g", hasValue: true }],
70+
}],
71+
},
72+
{ part: "h", hasValue: true },
73+
],
74+
},
75+
{ part: "e", hasValue: true },
76+
],
77+
});
78+
kv.close();
79+
80+
return cleanup();
81+
},
82+
});
83+
84+
Deno.test({
85+
name: "kvToolbox - open and close with encryption",
86+
async fn() {
87+
const path = await getPath();
88+
const key = generateKey();
89+
const kv = await openKvToolbox({ path, encryptWith: key });
90+
kv.close();
91+
return cleanup();
92+
},
93+
});
94+
95+
Deno.test({
96+
name: "kvToolbox - encrypt/decrypt blob - Uint8Array",
97+
async fn() {
98+
const path = await getPath();
99+
const encryptWith = generateKey();
100+
const kv = await openKvToolbox({ path, encryptWith });
101+
const value = globalThis.crypto.getRandomValues(new Uint8Array(65_536));
102+
const res = await kv.setBlob(["example"], value);
103+
assert(res.ok);
104+
const actual = await kv.getBlob(["example"]);
105+
assert(actual.value);
106+
assertEquals(actual.versionstamp, res.versionstamp);
107+
assert(timingSafeEqual(actual.value, value));
108+
kv.close();
109+
return cleanup();
110+
},
111+
});
112+
113+
Deno.test({
114+
name: "kvToolbox - encrypt/decrypt blob - Uint8Array - bypass encryption set",
115+
async fn() {
116+
const path = await getPath();
117+
const encryptWith = generateKey();
118+
const kv = await openKvToolbox({ path, encryptWith });
119+
const value = globalThis.crypto.getRandomValues(new Uint8Array(65_536));
120+
const res = await kv.setBlob(["example"], value, { encrypted: false });
121+
assert(res.ok);
122+
const actual = await kv.getBlob(["example"]);
123+
assertEquals(actual.value, null);
124+
kv.close();
125+
return cleanup();
126+
},
127+
});
128+
129+
Deno.test({
130+
name:
131+
"kvToolbox - encrypt/decrypt blob - Uint8Array - bypass encryption get and set",
132+
async fn() {
133+
const path = await getPath();
134+
const encryptWith = generateKey();
135+
const kv = await openKvToolbox({ path, encryptWith });
136+
const value = globalThis.crypto.getRandomValues(new Uint8Array(65_536));
137+
const res = await kv.setBlob(["example"], value, { encrypted: false });
138+
assert(res.ok);
139+
const actual = await kv.getBlob(["example"], { encrypted: false });
140+
assert(actual.value);
141+
assertEquals(actual.versionstamp, res.versionstamp);
142+
assert(timingSafeEqual(actual.value, value));
143+
kv.close();
144+
return cleanup();
145+
},
146+
});

0 commit comments

Comments
 (0)