Skip to main content
Module

x/kv_toolbox/blob.ts

Utilities for working with Deno KV πŸ¦•πŸ—οΈ
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
/** * A set of APIs for storing arbitrarily sized blobs in Deno KV. Currently Deno * KV has a limit of key values being 64k. * * The {@linkcode set} function breaks down a blob into chunks and manages * sub-keys to store the complete value, including preserving meta data * associated with {@linkcode Blob} and {@linkcode File} instances. * * The {@linkcode get}, {@linkcode getAsBlob}, {@linkcode getAsStream}, and * {@linkcode getAsJSON} functions reverse that process. {@linkcode get} * resolves with a standard {@linkcode Deno.KvEntryMaybe}, while the other * `get*` methods just return or resolve with the value. * * The {@linkcode getAsResponse} will resolve a blob entry as a * {@linkcode Response} using meta information to set appropriate headers. If * the entry is not found, the response will be set to a `404 Not Found`. * * {@linkcode remove} function will delete the key, sub-keys and values. * * {@linkcode getMeta} resolves with the meta data associated with a blob entry * stored in Deno KV. This information is useful for understanding the blob * value without having to read the blob out of the datastore. If the blob does * not exist `null` is resolved. * * {@linkcode toValue} and {@linkcode toJSON} are functions which allow * serializing blob like values to and from JSON. * * {@linkcode toBlob} is a convenience function to convert string values into * {@linkcode Blob}s for storage via {@linkcode set}. * * @example Basic usage * * ```ts * import { get, remove, set } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const data = new TextEncoder().encode("hello deno!"); * await set(kv, ["hello"], data); * const maybeAb = await get(kv, ["hello"]); * // do something with maybeAb * await remove(kv, ["hello"]); * await kv.close(); * ``` * * @example Setting and getting `File`s * * ```ts * import { getAsBlob, remove, set } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * // assume this is form data submitted as a `Request` * const body = new FormData(); * for (const [name, value] of body) { * if (value instanceof File) { * await set(kv, ["files", name], value); * } * } * // and then later * const file = await getAsBlob(kv, ["file", "image"]); * // now the `File` is restored and can be processed * await remove(kv, ["file", "image"]); * await kv.close(); * ``` * * @module */
import { concat } from "jsr:@std/bytes@0.220/concat";import { decodeBase64Url, encodeBase64Url,} from "jsr:@std/encoding@0.220/base64url";import { extension } from "jsr:@std/media-types@0.220/extension";
import { batchedAtomic } from "./batched_atomic.ts";import { BLOB_KEY, BLOB_META_KEY, type BlobMeta, CHUNK_SIZE, setBlob,} from "./blob_util.ts";import { keys } from "./keys.ts";
export { BLOB_KEY, BLOB_META_KEY, type BlobMeta } from "./blob_util.ts";
/** An interface to represent a blob value as JSON. */export type BlobJSON = BlobBlobJSON | BlobBufferJSON | BlobFileJSON;
/** An interface to represent a {@linkcode Blob} value as JSON. */export interface BlobBlobJSON { meta: { kind: "blob"; type: string; size?: number; }; parts: string[];}
/** An interface to represent a array buffer or typed array value as JSON. */export interface BlobBufferJSON { meta: { kind: "buffer"; size?: number }; parts: string[];}
/** An interface to represent a {@linkcode File} value as JSON. */export interface BlobFileJSON { meta: { kind: "file"; type: string; lastModified: number; name: string; size?: number; }; parts: string[];}
const BATCH_SIZE = 10;
async function asBlob( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined }, maybeMeta: Deno.KvEntryMaybe<BlobMeta>,): Promise<File | Blob | null> { const prefix = [...key, BLOB_KEY]; const prefixLength = prefix.length; const list = kv.list<Uint8Array>({ prefix }, { ...options, batchSize: BATCH_SIZE, }); let found = false; const parts: Uint8Array[] = []; let i = 1; for await (const item of list) { if ( item.value && item.key.length === prefixLength + 1 && item.key[prefixLength] === i ) { i++; found = true; if (!(item.value instanceof Uint8Array)) { throw new TypeError("KV value is not a Uint8Array."); } parts.push(item.value); } else { // encountered an unexpected key part, abort break; } } if (!found) { return null; } if (maybeMeta.value) { const { value } = maybeMeta; if (value.kind === "file") { return new File(parts, value.name, { lastModified: value.lastModified, type: value.type, }); } if (value.kind === "blob") { return new Blob(parts, { type: value.type }); } } return new Blob(parts);}
async function asJSON( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined },): Promise<BlobJSON | null> { const prefix = [...key, BLOB_KEY]; const prefixLength = prefix.length; const list = kv.list<Uint8Array>({ prefix }, { ...options, batchSize: BATCH_SIZE, }); let found = false; const parts: Uint8Array[] = []; let i = 1; for await (const item of list) { if ( item.value && item.key.length === prefixLength + 1 && item.key[prefixLength] === i ) { i++; found = true; if (!(item.value instanceof Uint8Array)) { throw new TypeError("KV value is not a Uint8Array"); } parts.push(item.value); } else { break; } } if (!found) { return null; } const json: BlobJSON = { meta: { kind: "buffer" }, parts: parts.map(encodeBase64Url), }; // deno-lint-ignore no-explicit-any const maybeMeta = await kv.get<any>([...key, BLOB_META_KEY], options); if (maybeMeta.value) { json.meta = maybeMeta.value; } return json;}
function asMeta( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined },): Promise<Deno.KvEntryMaybe<BlobMeta>> { return kv.get<BlobMeta>([...key, BLOB_META_KEY], options);}
function asStream( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined },) { const prefix = [...key, BLOB_KEY]; const prefixLength = prefix.length; let i = 1; let list: Deno.KvListIterator<Uint8Array> | null = null; return new ReadableStream({ type: "bytes", autoAllocateChunkSize: CHUNK_SIZE, async pull(controller) { if (!list) { return controller.error(new Error("Internal error - list not set")); } const next = await list.next(); if ( next.value && next.value.value && next.value.key.length === prefixLength + 1 && next.value.key[prefixLength] === i ) { i++; if (next.value.value instanceof Uint8Array) { controller.enqueue(next.value.value); } else { controller.error(new TypeError("KV value is not a Uint8Array.")); } } else { controller.close(); } if (next.done) { controller.close(); } }, start() { list = kv.list<Uint8Array>({ prefix }, { ...options, batchSize: BATCH_SIZE, }); }, });}
async function asUint8Array( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined },): Promise<Uint8Array | null> { const prefix = [...key, BLOB_KEY]; const prefixLength = prefix.length; const list = kv.list<Uint8Array>({ prefix }, { ...options, batchSize: BATCH_SIZE, }); let found = false; let value = new Uint8Array(); let i = 1; for await (const item of list) { if ( item.value && item.key.length === prefixLength + 1 && item.key[prefixLength] === i ) { i++; found = true; if (!(item.value instanceof Uint8Array)) { throw new TypeError("KV value is not a Uint8Array."); } const v = new Uint8Array(value.length + item.value.length); v.set(value, 0); v.set(item.value, value.length); value = v; } else { break; } } return found ? value : null;}
function toParts(blob: ArrayBufferLike): string[] { const buffer = new Uint8Array(blob); const parts: string[] = []; let offset = 0; while (buffer.byteLength > offset) { parts.push(encodeBase64Url(buffer.subarray(offset, offset + CHUNK_SIZE))); offset += CHUNK_SIZE; } return parts;}
/** Remove/delete a binary object from the store with a given key that has been * {@linkcode set}. * * @example * * ```ts * import { remove } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * await remove(kv, ["hello"]); * await kv.close(); * ``` */export async function remove(kv: Deno.Kv, key: Deno.KvKey): Promise<void> { const parts = await keys(kv, { prefix: [...key, BLOB_KEY] }, { batchSize: BATCH_SIZE, }); if (parts.length) { let op = batchedAtomic(kv).delete([...key, BLOB_META_KEY]); for (const key of parts) { op = op.delete(key); } await op.commit(); }}
/** Retrieve a binary object entry from the store with a given key that has been * {@linkcode set}. * * When setting the option `stream` to `true`, a {@linkcode Deno.KvEntryMaybe} * is resolved with a value of {@linkcode ReadableStream} to read the blob in * chunks of {@linkcode Uint8Array}. * * When setting the option `blob` to `true`, the promise resolves with a * {@linkcode Deno.KvEntryMaybe} with a value of {@linkcode Blob} or * {@linkcode File}. If the original file had been a {@linkcode File} or * {@linkcode Blob} it the resolved value will reflect that original value * including its properties. If it was not, it will be a {@linkcode Blob} with a * type of `""`. * * Otherwise the function resolves with a {@linkcode Deno.KvEntryMaybe} with a * value of {@linkcode Uint8Array}. * * @example * * ```ts * import { get } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const stream = await get(kv, ["hello"], { stream: true }); * for await (const chunk of stream) { * // do something with chunk * } * await kv.close(); * ``` */export function get( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined; stream: true },): Promise<Deno.KvEntryMaybe<ReadableStream<Uint8Array>>>;/** Retrieve a binary object from the store with a given key that has been * {@linkcode set}. * * When setting the option `stream` to `true`, a {@linkcode ReadableStream} is * returned to read the blob in chunks of {@linkcode Uint8Array}. * * When setting the option `blob` to `true`, the promise resolves with a * {@linkcode Blob}, {@linkcode File}, or `null`. If the original file had been * a {@linkcode File} or {@linkcode Blob} it the resolved value will reflect * that original value including its properties. If it was not, it will be a * {@linkcode Blob} with a type of `""`. * * Otherwise the function resolves with a single {@linkcode Uint8Array} or * `null`. * * @example * * ```ts * import { get } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const blob = await get(kv, ["hello"], { blob: true }); * // do something with blob * await kv.close(); * ``` */export function get( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined; blob: true },): Promise<Deno.KvEntryMaybe<Blob | File>>;/** Retrieve a binary object from the store with a given key that has been * {@linkcode set}. * * When setting the option `stream` to `true`, a {@linkcode ReadableStream} is * returned to read the blob in chunks of {@linkcode Uint8Array} * * When setting the option `blob` to `true`, the promise resolves with a * {@linkcode Blob}, {@linkcode File}, or `null`. If the original file had been * a {@linkcode File} or {@linkcode Blob} it the resolved value will reflect * that original value including its properties. If it was not, it will be a * {@linkcode Blob} with a type of `""`. * * Otherwise the function resolves with a single {@linkcode Uint8Array} or * `null`. * * @example * * ```ts * import { get } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const ab = await get(kv, ["hello"]); * // do something with ab * await kv.close(); * ``` */export function get( kv: Deno.Kv, key: Deno.KvKey, options?: { consistency?: Deno.KvConsistencyLevel | undefined; blob?: boolean; stream?: boolean; },): Promise<Deno.KvEntryMaybe<Uint8Array>>;export async function get( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined; blob?: boolean; stream?: boolean; } = {},): Promise< Deno.KvEntryMaybe<ReadableStream<Uint8Array> | Uint8Array | File | Blob>> { const meta = await asMeta(kv, key, options); const value = options.stream ? asStream(kv, key, options) : options.blob ? await asBlob(kv, key, options, meta) : await asUint8Array(kv, key, options); if (!value || !meta.value) { return { key: [...key], value: null, versionstamp: null }; } return { key: [...key], value, versionstamp: meta.versionstamp };}
/** * Retrieve a binary object from the store as a {@linkcode Blob} or * {@linkcode File} 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 * {@linkcode File} with the same properties as the original. * * If it was some other form of binary data, it will be an instance of * {@linkcode Blob} with an empty `.type` property. * * If there is no corresponding entry, the function will resolve to `null`. * * @example Getting a value * * ```ts * import { getAsBlob } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const blob = await getAsBlob(kv, ["hello"]); * // do something with blob * await kv.close(); * ``` */export async function getAsBlob( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},): Promise<Blob | File | null> { const maybeMeta = await asMeta(kv, key, options); return asBlob(kv, key, options, maybeMeta);}
/** * Retrieve a binary object from the store as an object which which be safely * converted into a JSON string. * * If there is no corresponding entry, the promise will resolve with a `null`. * * @example Getting a value * * ```ts * import { getAsJSON } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const json = JSON.stringify(await getAsJSON(kv, ["hello"])); * await kv.close(); * ``` */export function getAsJSON( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},): Promise<BlobJSON | null> { return asJSON(kv, key, options);}
/** * Retrieve a binary object's meta data from the store as a * {@linkcode Deno.KvEntryMaybe}. * * @example Getting meta data * * ```ts * import { getMeta } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const maybeMeta = await getMeta(kv, ["hello"])); * await kv.close(); * ``` */export async function getMeta( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},): Promise<Deno.KvEntryMaybe<BlobMeta>> { return await asMeta(kv, key, options);}
/** Options which can be used when calling {@linkcode getAsResponse}. */export interface GetAsResponseOptions { consistency?: Deno.KvConsistencyLevel | undefined; /** * Set an appropriate content disposition header on the response. This will * cause a browser to usually treat the response as a download. * * If a filename is available, it will be used, otherwise a filename and * extension derived from the key and content type. */ contentDisposition?: boolean | undefined; /** Any headers init to be used in conjunction with creating the request. */ headers?: HeadersInit | undefined; /** If the blob entry is not present, utilize this body when responding. This * defaults to `null`. */ notFoundBody?: BodyInit | undefined; /** If the blob entry is not present, utilize this headers init when * responding. */ notFoundHeaders?: HeadersInit | undefined;}
/** * Retrieve a blob value as a {@linkcode Response} which is suitable for sending * as a response to an HTTP request. This will read the blob out of the KV store * as a stream and set information in the response based on what is available * from the source. * * If there are other headers required, they can be supplied in the options. * * Setting the `contentDisposition` to `true` will cause the function to resolve * with a {@linkcode Response} which has the `Content-Disposition` set as an * attachment with an appropriate file name. This is designed to send a response * that instructs the browser to attempt to download the requested entry. * * If the blob entry is not present, the response will be set to a * `404 Not Found` with a `null` body. The not found body and headers can be * set in the options. * * @example Serving static content from Deno KV * * Creates a simple web server where the content has already been set in the * Deno KV datastore as `Blob`s. This is a silly example just to show * functionality and would be terribly inefficient in production: * * ```ts * import { getAsResponse } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * * const server = Deno.serve((req) => { * const key = new URL(req.url) * .pathname * .slice(1) * .split("/"); * key[key.length - 1] = key[key.length - 1] || "index.html"; * return getAsResponse(kv, key); * }); * * server.finished.then(() => kv.close()); * ``` */export async function getAsResponse( kv: Deno.Kv, key: Deno.KvKey, options: GetAsResponseOptions = {},): Promise<Response> { const maybeMeta = await asMeta(kv, key, options); if (!maybeMeta.value) { const { notFoundBody = null, notFoundHeaders: headers } = options; return new Response(notFoundBody, { status: 404, statusText: "Not Found", headers, }); } const headers = new Headers(options.headers); const contentType = (maybeMeta.value.kind !== "buffer" && maybeMeta.value.type) || "application/octet-stream"; headers.set("content-type", contentType); if (maybeMeta.value.size) { headers.set("content-length", String(maybeMeta.value.size)); } if (options.contentDisposition) { const filename = maybeMeta.value.kind === "file" && maybeMeta.value.name || `${ typeof key[key.length - 1] !== "object" && String(key[key.length - 1]) || "file" }.${extension(contentType) ?? "bin"}`; headers.set("content-disposition", `attachment; filename="${filename}"`); } const body = asStream(kv, key, options); return new Response(body, { headers, status: 200, statusText: "OK", });}
/** * Retrieve a binary object from the store as a byte {@linkcode ReadableStream}. * * If there is no corresponding entry, the stream will provide no chunks. * * @example Getting a value * * ```ts * import { getAsStream } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const stream = await getAsStream(kv, ["hello"]); * // do something with stream * await kv.close(); * ``` */export function getAsStream( kv: Deno.Kv, key: Deno.KvKey, options: { consistency?: Deno.KvConsistencyLevel | undefined } = {},): ReadableStream<Uint8Array> { return asStream(kv, key, options);}
/** Set the blob value in the provided {@linkcode Deno.Kv} with the provided * key. The blob can be any array buffer like structure, a byte * {@linkcode ReadableStream}, or a {@linkcode Blob}. * * The function chunks up the blob into parts which deno be stored in Deno KV * and should be retrieved back out using the {@linkcode get} function. * * Optionally an `expireIn` option can be specified to set a time-to-live * (TTL) for the key. The TTL is specified in milliseconds, and the key will * be deleted from the database at earliest after the specified number of * milliseconds have elapsed. Once the specified duration has passed, the * key may still be visible for some additional time. If the `expireIn` * option is not specified, the key will not expire. * * @example Setting a `Uint8Array` * * ```ts * import { set } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const blob = new TextEncoder().encode("hello deno!"); * await set(kv, ["hello"], blob); * await kv.close(); * ``` * * @example Setting a `Blob` * * ```ts * import { set } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const blob = new Blob( * [new TextEncoder().encode("hello deno!")], * { type: "text/plain" }, * ); * await set(kv, ["hello"], blob); * await kv.close(); * ``` */export async function set( kv: Deno.Kv, key: Deno.KvKey, blob: ArrayBufferLike | ReadableStream<Uint8Array> | Blob | File, options?: { expireIn?: number },): Promise<Deno.KvCommitResult> { const items = await keys(kv, { prefix: [...key, BLOB_KEY] }); let operation = batchedAtomic(kv); operation = await setBlob(operation, key, blob, items.length, options); const res = await operation.commit(); if (!res[0].ok) { throw new Error("Unexpected error when setting blob."); } return res[0];}
/** * A convenience function which converts a string value to a {@linkcode Blob} * which can be stored via {@link set}. The function optionally takes a `type` * which represents the media type of the string (e.g. `"text/plain"` or * `"text/html"`). `type` defaults to `"text/plain"`. * * @example Storing and retrieving a blob string * * ```ts * import { getAsBlob, set, toBlob } from "jsr:@kitsonk/kv-toolbox/blob"; * * const kv = await Deno.openKv(); * const blob = toBlob("some big string"); * await set(kv, ["hello"], blob); * const value = await getAsBlob(kv, ["hello"]); * const str = await value.text(); * await kv.close(); * ``` */export function toBlob(value: string, type = "text/plain"): Blob { return new Blob([value], { type });}
/** * Convert a typed array, array buffer, {@linkcode Blob} or {@linkcode File} * into a form that can be converted into a JSON string. * * @example Convert a `Uint8Array` to JSON * * ```ts * import { toJSON } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const u8 = new Uint8Array(); * const json = JSON.stringify(toJSON(u8)); * ``` */export async function toJSON(blob: File): Promise<BlobFileJSON>;/** * Convert a typed array, array buffer, {@linkcode Blob} or {@linkcode File} * into a form that can be converted into a JSON string. * * @example Convert a `Uint8Array` to JSON * * ```ts * import { toJSON } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const u8 = new Uint8Array(); * const json = JSON.stringify(toJSON(u8)); * ``` */export async function toJSON(blob: Blob): Promise<BlobBlobJSON>;/** * Convert a typed array, array buffer, {@linkcode Blob} or {@linkcode File} * into a form that can be converted into a JSON string. * * @example Convert a `Uint8Array` to JSON * * ```ts * import { toJSON } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const u8 = new Uint8Array(); * const json = JSON.stringify(toJSON(u8)); * ``` */export async function toJSON(blob: ArrayBufferLike): Promise<BlobBufferJSON>;export async function toJSON( blob: ArrayBufferLike | Blob | File,): Promise<BlobJSON> { new Uint8Array(); if (blob instanceof File) { return { meta: { kind: "file", type: blob.type, lastModified: blob.lastModified, name: blob.name, }, parts: toParts(await blob.arrayBuffer()), }; } if (blob instanceof Blob) { return { meta: { kind: "blob", type: blob.type }, parts: toParts(await blob.arrayBuffer()), }; } return { meta: { kind: "buffer" }, parts: toParts(blob) };}
/** * Convert a previously encoded object into an instance of {@linkcode File}. * * @example Convert some JSON to a File * * ```ts * import { toValue } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const file = toValue({ * meta: { * type: "file", * lastModified: 1711349710546, * name: "test.bin", * type: "application/octet-stream", * }, * parts: ["AQID"], * }); * ``` */export function toValue(json: BlobFileJSON): File;/** * Convert a previously encoded object into an instance of {@linkcode Blob}. * * @example Convert some JSON to a File * * ```ts * import { toValue } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const blob = toValue({ * meta: { * type: "blob", * type: "application/octet-stream", * }, * parts: ["AQID"], * }); * ``` */export function toValue(json: BlobBlobJSON): Blob;/** * Convert a previously encoded object into an instance of * {@linkcode Uint8Array}. * * @example Convert some JSON to a File * * ```ts * import { toValue } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const u8 = toValue({ parts: ["AQID"] }); * ``` */export function toValue(json: BlobBufferJSON): Uint8Array;/** * Convert a previously encoded object into an instance of a * {@linkcode Uint8Array}, {@linkcode Blob}, or {@linkcode File}. * * @example Convert some JSON to a File * * ```ts * import { toValue } from "jsr:/@kitsonk/kv-toolbox/blob"; * * const u8 = toValue({ parts: ["AQID"] }); * ``` */export function toValue(json: BlobJSON): Uint8Array | Blob | File;export function toValue(json: BlobJSON): Uint8Array | Blob | File { const parts = json.parts.map(decodeBase64Url); if (json.meta.kind === "file") { return new File(parts, json.meta.name, { type: json.meta.type, lastModified: json.meta.lastModified, }); } if (json.meta.kind === "blob") { return new Blob(parts, { type: json.meta.type }); } return concat(parts);}