Skip to content

Commit

Permalink
feat: add blob as JSON to toolbox
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Sep 15, 2024
1 parent b0281f6 commit 6bf5d7d
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 18 deletions.
18 changes: 18 additions & 0 deletions crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ Deno.test({
},
});

Deno.test({
name: "getAsJSON",
async fn() {
const kv = await setup();
const key = generateKey();
const cryptoKv = new CryptoKv(kv, key);
const part = globalThis.crypto.getRandomValues(new Uint8Array(65_536));
const value = new File([part], "test.bin", { type: "text/plain" });
const res = await cryptoKv.setBlob(["example"], value);
assert(res.ok);
const actual = await cryptoKv.getAsJSON(["example"]);
assert(actual);
assertEquals(actual.meta.kind, "file");
assertEquals(actual.parts.length, 2);
return teardown();
},
});

Deno.test({
name: "getBlobMeta",
async fn() {
Expand Down
32 changes: 31 additions & 1 deletion crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { decodeHex, encodeHex } from "jsr:@std/encoding@~1/hex";
import { concat } from "jsr:@std/bytes@~1/concat";

import { batchedAtomic } from "./batched_atomic.ts";
import { BLOB_KEY, type BlobMeta } from "./blob.ts";
import { BLOB_KEY, type BlobJSON, type BlobMeta, toJSON } from "./blob.ts";
import {
asMeta,
asUint8Array,
Expand Down Expand Up @@ -393,6 +393,36 @@ export class CryptoKv {
return this.#asBlob(key, options, meta.value);
}

/**
* Resolve with just the value of an encrypted blob as a {@linkcode BlobJSON}.
* If there isn't an encrypted blob value associated with the key, `null` will
* be resolved.
*
* @example Retrieve a JSON object from the store
*
* ```ts
* import { generateKey, openCryptoKv } from "jsr:@kitsonk/kv-toolbox/crypto";
*
* const kv = await openCryptoKv(generateKey());
* const value = await kv.getAsJson(["hello"]);
* if (value) {
* // do something with value
* }
* kv.close();
* ```
*/
async getAsJSON(
key: Deno.KvKey,
options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},
): Promise<BlobJSON | null> {
const meta = await asMeta(this.#kv, key, options);
if (!meta.value?.encrypted) {
return null;
}
const blob = await this.#asBlob(key, options, meta.value);
return blob ? toJSON(blob) : null;
}

/**
* Retrieve the meta data associated with a blob value for the provided key.
* If the entry is not is not present, not a blob, or not encrypted `null`
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kitsonk/kv-toolbox",
"version": "0.19.0-beta.4",
"version": "0.19.0-beta.5",
"exports": {
".": "./toolbox.ts",
"./batched_atomic": "./batched_atomic.ts",
Expand Down
93 changes: 77 additions & 16 deletions toolbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ import {
type BatchedAtomicOperation,
} from "./batched_atomic.ts";
import {
type BlobJSON,
type BlobMeta,
get,
getAsBlob,
getAsJSON,
getAsResponse,
getMeta,
set,
Expand All @@ -105,11 +107,6 @@ import {

export { generateKey } from "./crypto.ts";

interface ExportEntriesOptionsResponse {
close?: boolean;
response: true;
}

/**
* A toolbox for interacting with a Deno KV store.
*
Expand Down Expand Up @@ -293,7 +290,7 @@ export class KvToolbox implements Disposable {
*/
export(
selector: Deno.KvListSelector,
options: ExportEntriesOptionsResponse,
options: { close?: boolean; response: true },
): Response;
/**
* Like {@linkcode Deno.Kv} `.list()` method, but returns a
Expand All @@ -319,11 +316,14 @@ export class KvToolbox implements Disposable {
export(
selector: Deno.KvListSelector,
options:
| ExportEntriesOptionsResponse
| ExportEntriesOptionsJSON
| ExportEntriesOptionsBytes = {},
| { close?: boolean; response: true }
| (
| ExportEntriesOptionsJSON
| ExportEntriesOptionsBytes
)
& { response?: boolean | undefined } = {},
): Response | ReadableStream<string | Uint8Array> {
return (options as ExportEntriesOptionsResponse).response
return options.response
? exportToResponse(this.#kv, selector, options)
: exportEntries(this.#kv, selector, options);
}
Expand Down Expand Up @@ -422,10 +422,34 @@ export class KvToolbox implements Disposable {
notFoundHeaders?: HeadersInit | undefined;
},
): Promise<Response>;
/**
* Retrieve a binary object from the store as {@linkcode BlobJSON} that has
* been previously {@linkcode set}.
*
* If there is no corresponding entry, the function will resolve to `null`.
*
* @example Getting a value
*
* ```ts
* import { openKvToolbox } from "jsr:@kitsonk/kv-toolbox";
*
* const kv = await openKvToolbox();
* const json = await kv.getAsBlob(["hello"], { json: true });
* // do something with blob json
* await kv.close();
* ```
*/
getAsBlob(
key: Deno.KvKey,
options: {
consistency?: Deno.KvConsistencyLevel | undefined;
json: true;
},
): Promise<BlobJSON | null>;
/**
* Retrieve a binary object from the store as a {@linkcode Blob},
* {@linkcode File} or {@linkcode Response} that has been previously
* {@linkcode set}.
* {@linkcode File}, {@linkcode BlobJSON} or {@linkcode Response} that has
* been previously {@linkcode set}.
*
* If the object set was originally a {@linkcode Blob} or {@linkcode File} the
* function will resolve with an instance of {@linkcode Blob} or
Expand All @@ -449,9 +473,10 @@ export class KvToolbox implements Disposable {
*/
getAsBlob(
key: Deno.KvKey,
options: {
options?: {
consistency?: Deno.KvConsistencyLevel | undefined;
response?: boolean | undefined;
json?: boolean | undefined;
},
): Promise<Blob | File | null>;
getAsBlob(
Expand All @@ -463,10 +488,13 @@ export class KvToolbox implements Disposable {
headers?: HeadersInit | undefined;
notFoundBody?: BodyInit | undefined;
notFoundHeaders?: HeadersInit | undefined;
json?: boolean | undefined;
},
): Promise<Blob | File | Response | null> {
): Promise<Blob | File | BlobJSON | Response | null> {
return options?.response
? getAsResponse(this.#kv, key, options)
? options?.json
? getAsJSON(this.#kv, key, options)
: getAsResponse(this.#kv, key, options)
: getAsBlob(this.#kv, key, options);
}

Expand Down Expand Up @@ -1144,6 +1172,35 @@ export class CryptoKvToolbox extends KvToolbox {
notFoundHeaders?: HeadersInit | undefined;
},
): Promise<Response>;
/**
* Retrieve a binary object from the store as a {@linkcode BlobJSON}.
*
* By default, the `encrypted` option is true and the `encryptWith` will be
* used to decrypt the value. If the value is not encrypted, then the method
* will return `null`. Explicitly setting the `encrypted` option to `false`
* will bypass the decryption.
*
* @example Retrieving an encrypted blob as JSON
*
* ```ts
* import { generateKey, openKvToolbox } from "jsr:@kitsonk/kv-toolbox";
*
* const kv = await openKvToolbox({ encryptWith: generateKey() });
* const value = await kv.getAsBlob(["hello"], { json: true });
* if (value) {
* // do something with value
* }
* kv.close();
* ```
*/
getAsBlob(
key: Deno.KvKey,
options: {
consistency?: Deno.KvConsistencyLevel;
encrypted?: boolean | undefined;
json: true;
},
): Promise<BlobJSON | null>;
/**
* Retrieve a binary object from the store as a {@linkcode Blob} or
* {@linkcode File} that has been previously {@linkcode set}.
Expand Down Expand Up @@ -1178,6 +1235,7 @@ export class CryptoKvToolbox extends KvToolbox {
options?: {
consistency?: Deno.KvConsistencyLevel | undefined;
encrypted?: boolean | undefined;
json?: boolean | undefined;
},
): Promise<Blob | File | null>;
getAsBlob(
Expand All @@ -1190,13 +1248,16 @@ export class CryptoKvToolbox extends KvToolbox {
headers?: HeadersInit | undefined;
notFoundBody?: BodyInit | undefined;
notFoundHeaders?: HeadersInit | undefined;
json?: boolean | undefined;
} = {},
): Promise<Blob | File | Response | null> {
): Promise<Blob | File | BlobJSON | Response | null> {
if (options.response && options.encrypted !== false) {
throw new TypeError("Encrypted blobs cannot be retrieved as responses.");
}
return options.encrypted === false
? super.getAsBlob(key, options)
: options.json
? this.#cryptoKv.getAsJSON(key, options)
: this.#cryptoKv.getAsBlob(key, options);
}

Expand Down

0 comments on commit 6bf5d7d

Please sign in to comment.