Skip to content

Commit b0281f6

Browse files
committed
feat: add ability to retrieve response with toolbox
1 parent 72c580b commit b0281f6

File tree

3 files changed

+171
-10
lines changed

3 files changed

+171
-10
lines changed

crypto.ts

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ export class CryptoKv {
385385
key: Deno.KvKey,
386386
options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},
387387
): Promise<Blob | File | null> {
388+
// TODO: provide the ability to return a Response using the Blob
388389
const meta = await asMeta(this.#kv, key, options);
389390
if (!meta.value?.encrypted) {
390391
return null;

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.19.0-beta.3",
3+
"version": "0.19.0-beta.4",
44
"exports": {
55
".": "./toolbox.ts",
66
"./batched_atomic": "./batched_atomic.ts",

toolbox.ts

+169-9
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,14 @@ import {
7575
batchedAtomic,
7676
type BatchedAtomicOperation,
7777
} from "./batched_atomic.ts";
78-
import { type BlobMeta, get, getAsBlob, getMeta, set } from "./blob.ts";
78+
import {
79+
type BlobMeta,
80+
get,
81+
getAsBlob,
82+
getAsResponse,
83+
getMeta,
84+
set,
85+
} from "./blob.ts";
7986
import { removeBlob } from "./blob_util.ts";
8087
import { CryptoKv, type Encryptor } from "./crypto.ts";
8188
import {
@@ -352,8 +359,73 @@ export class KvToolbox implements Disposable {
352359
}
353360

354361
/**
355-
* Retrieve a binary object from the store as a {@linkcode Blob} or
356-
* {@linkcode File} that has been previously {@linkcode set}.
362+
* Retrieve a binary object from the store as a {@linkcode Response} that has
363+
* been previously {@linkcode set}. This will read the blob out of the KV
364+
* store as a stream and set information in the response based on what is
365+
* available from the source.
366+
*
367+
* If there are other headers required, they can be supplied in the options.
368+
*
369+
* Setting the `contentDisposition` to `true` will cause the function to
370+
* resolve with a {@linkcode Response} which has the `Content-Disposition` set
371+
* as an attachment with an appropriate file name. This is designed to send a
372+
* response that instructs the browser to attempt to download the requested
373+
* entry.
374+
*
375+
* If the blob entry is not present, the response will be set to a
376+
* `404 Not Found` with a `null` body. The not found body and headers can be
377+
* set in the options.
378+
*
379+
* @example Serving static content from Deno KV
380+
*
381+
* Creates a simple web server where the content has already been set in the
382+
* Deno KV datastore as `Blob`s. This is a silly example just to show
383+
* functionality and would be terribly inefficient in production:
384+
*
385+
* ```ts
386+
* import { openKvToolbox } from "jsr:/@kitsonk/kv-toolbox";
387+
*
388+
* const kv = await openKvToolbox();
389+
*
390+
* const server = Deno.serve((req) => {
391+
* const key = new URL(req.url)
392+
* .pathname
393+
* .slice(1)
394+
* .split("/");
395+
* key[key.length - 1] = key[key.length - 1] || "index.html";
396+
* return kv.getAsBlob(key, { response: true });
397+
* });
398+
*
399+
* server.finished.then(() => kv.close());
400+
* ```
401+
*/
402+
getAsBlob(
403+
key: Deno.KvKey,
404+
options: {
405+
consistency?: Deno.KvConsistencyLevel | undefined;
406+
response: true;
407+
/**
408+
* Set an appropriate content disposition header on the response. This will
409+
* cause a browser to usually treat the response as a download.
410+
*
411+
* If a filename is available, it will be used, otherwise a filename and
412+
* extension derived from the key and content type.
413+
*/
414+
contentDisposition?: boolean | undefined;
415+
/** Any headers init to be used in conjunction with creating the request. */
416+
headers?: HeadersInit | undefined;
417+
/** If the blob entry is not present, utilize this body when responding. This
418+
* defaults to `null`. */
419+
notFoundBody?: BodyInit | undefined;
420+
/** If the blob entry is not present, utilize this headers init when
421+
* responding. */
422+
notFoundHeaders?: HeadersInit | undefined;
423+
},
424+
): Promise<Response>;
425+
/**
426+
* Retrieve a binary object from the store as a {@linkcode Blob},
427+
* {@linkcode File} or {@linkcode Response} that has been previously
428+
* {@linkcode set}.
357429
*
358430
* If the object set was originally a {@linkcode Blob} or {@linkcode File} the
359431
* function will resolve with an instance of {@linkcode Blob} or
@@ -377,9 +449,25 @@ export class KvToolbox implements Disposable {
377449
*/
378450
getAsBlob(
379451
key: Deno.KvKey,
380-
options?: { consistency?: Deno.KvConsistencyLevel | undefined },
381-
): Promise<Blob | File | null> {
382-
return getAsBlob(this.#kv, key, options);
452+
options: {
453+
consistency?: Deno.KvConsistencyLevel | undefined;
454+
response?: boolean | undefined;
455+
},
456+
): Promise<Blob | File | null>;
457+
getAsBlob(
458+
key: Deno.KvKey,
459+
options?: {
460+
consistency?: Deno.KvConsistencyLevel | undefined;
461+
response?: boolean | undefined;
462+
contentDisposition?: boolean | undefined;
463+
headers?: HeadersInit | undefined;
464+
notFoundBody?: BodyInit | undefined;
465+
notFoundHeaders?: HeadersInit | undefined;
466+
},
467+
): Promise<Blob | File | Response | null> {
468+
return options?.response
469+
? getAsResponse(this.#kv, key, options)
470+
: getAsBlob(this.#kv, key, options);
383471
}
384472

385473
/**
@@ -999,6 +1087,63 @@ export class CryptoKvToolbox extends KvToolbox {
9991087
: this.#cryptoKv.getBlob(key, options);
10001088
}
10011089

1090+
/**
1091+
* Retrieve a binary object from the store as a {@linkcode Response} that has
1092+
* been previously {@linkcode set}. This will read the blob out of the KV
1093+
* store as a stream and set information in the response based on what is
1094+
* available from the source.
1095+
*
1096+
* > [!WARNING]
1097+
* > Encrypted blobs cannot be retrieved as responses. The `encrypted` option
1098+
* > must be set to `false` to retrieve a blob as a response.
1099+
*
1100+
* If there are other headers required, they can be supplied in the options.
1101+
*
1102+
* Setting the `contentDisposition` to `true` will cause the function to
1103+
* resolve with a {@linkcode Response} which has the `Content-Disposition` set
1104+
* as an attachment with an appropriate file name. This is designed to send a
1105+
* response that instructs the browser to attempt to download the requested
1106+
* entry.
1107+
*
1108+
* If the blob entry is not present, the response will be set to a
1109+
* `404 Not Found` with a `null` body. The not found body and headers can be
1110+
* set in the options.
1111+
*
1112+
* @example Serving static content from Deno KV
1113+
*
1114+
* Creates a simple web server where the content has already been set in the
1115+
* Deno KV datastore as `Blob`s. This is a silly example just to show
1116+
* functionality and would be terribly inefficient in production:
1117+
*
1118+
* ```ts
1119+
* import { generateKey, openKvToolbox } from "jsr:/@kitsonk/kv-toolbox";
1120+
*
1121+
* const kv = await openKvToolbox({ encryptWith: generateKey() });
1122+
*
1123+
* const server = Deno.serve((req) => {
1124+
* const key = new URL(req.url)
1125+
* .pathname
1126+
* .slice(1)
1127+
* .split("/");
1128+
* key[key.length - 1] = key[key.length - 1] || "index.html";
1129+
* return kv.getAsBlob(key, { response: true, encrypted: false });
1130+
* });
1131+
*
1132+
* server.finished.then(() => kv.close());
1133+
* ```
1134+
*/
1135+
getAsBlob(
1136+
key: Deno.KvKey,
1137+
options: {
1138+
consistency?: Deno.KvConsistencyLevel;
1139+
encrypted: false;
1140+
response: true;
1141+
contentDisposition?: boolean | undefined;
1142+
headers?: HeadersInit | undefined;
1143+
notFoundBody?: BodyInit | undefined;
1144+
notFoundHeaders?: HeadersInit | undefined;
1145+
},
1146+
): Promise<Response>;
10021147
/**
10031148
* Retrieve a binary object from the store as a {@linkcode Blob} or
10041149
* {@linkcode File} that has been previously {@linkcode set}.
@@ -1028,13 +1173,28 @@ export class CryptoKvToolbox extends KvToolbox {
10281173
* kv.close();
10291174
* ```
10301175
*/
1176+
getAsBlob(
1177+
key: Deno.KvKey,
1178+
options?: {
1179+
consistency?: Deno.KvConsistencyLevel | undefined;
1180+
encrypted?: boolean | undefined;
1181+
},
1182+
): Promise<Blob | File | null>;
10311183
getAsBlob(
10321184
key: Deno.KvKey,
10331185
options: {
1034-
consistency?: Deno.KvConsistencyLevel;
1035-
encrypted?: boolean;
1186+
consistency?: Deno.KvConsistencyLevel | undefined;
1187+
encrypted?: boolean | undefined;
1188+
response?: boolean | undefined;
1189+
contentDisposition?: boolean | undefined;
1190+
headers?: HeadersInit | undefined;
1191+
notFoundBody?: BodyInit | undefined;
1192+
notFoundHeaders?: HeadersInit | undefined;
10361193
} = {},
1037-
): Promise<Blob | File | null> {
1194+
): Promise<Blob | File | Response | null> {
1195+
if (options.response && options.encrypted !== false) {
1196+
throw new TypeError("Encrypted blobs cannot be retrieved as responses.");
1197+
}
10381198
return options.encrypted === false
10391199
? super.getAsBlob(key, options)
10401200
: this.#cryptoKv.getAsBlob(key, options);

0 commit comments

Comments
 (0)