@@ -75,7 +75,14 @@ import {
75
75
batchedAtomic ,
76
76
type BatchedAtomicOperation ,
77
77
} 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" ;
79
86
import { removeBlob } from "./blob_util.ts" ;
80
87
import { CryptoKv , type Encryptor } from "./crypto.ts" ;
81
88
import {
@@ -352,8 +359,73 @@ export class KvToolbox implements Disposable {
352
359
}
353
360
354
361
/**
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}.
357
429
*
358
430
* If the object set was originally a {@linkcode Blob} or {@linkcode File} the
359
431
* function will resolve with an instance of {@linkcode Blob} or
@@ -377,9 +449,25 @@ export class KvToolbox implements Disposable {
377
449
*/
378
450
getAsBlob (
379
451
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 ) ;
383
471
}
384
472
385
473
/**
@@ -999,6 +1087,63 @@ export class CryptoKvToolbox extends KvToolbox {
999
1087
: this . #cryptoKv. getBlob ( key , options ) ;
1000
1088
}
1001
1089
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 > ;
1002
1147
/**
1003
1148
* Retrieve a binary object from the store as a {@linkcode Blob} or
1004
1149
* {@linkcode File} that has been previously {@linkcode set}.
@@ -1028,13 +1173,28 @@ export class CryptoKvToolbox extends KvToolbox {
1028
1173
* kv.close();
1029
1174
* ```
1030
1175
*/
1176
+ getAsBlob (
1177
+ key : Deno . KvKey ,
1178
+ options ?: {
1179
+ consistency ?: Deno . KvConsistencyLevel | undefined ;
1180
+ encrypted ?: boolean | undefined ;
1181
+ } ,
1182
+ ) : Promise < Blob | File | null > ;
1031
1183
getAsBlob (
1032
1184
key : Deno . KvKey ,
1033
1185
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 ;
1036
1193
} = { } ,
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
+ }
1038
1198
return options . encrypted === false
1039
1199
? super . getAsBlob ( key , options )
1040
1200
: this . #cryptoKv. getAsBlob ( key , options ) ;
0 commit comments