Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ utilities to convert values which can be safely serialized to JSON as well as
deserialize them back. This makes it possible to fully represent entries and
values in a browser, or communicate them between Deno processes.

The JSON utilities are:
The synchronous JSON utilities are:

- `entryMaybeToJSON` - Convert a `Deno.KvEntryMaybe` to JSON.
- `entryToJSON` - Convert a `Deno.KvEntry` to JSON.
Expand All @@ -24,19 +24,36 @@ The JSON utilities are:
- `toKeyPart` - Convert a JSON object to a `Deno.KvKeyPart`.
- `toValue` - Convert a JSON object to a value which can be stored in Deno KV.

Since Deno 2.8.1, Deno KV also supports storing web objects such as `Blob`,
`File`, `CryptoKey`, and `DOMException`. Reading a `Blob`/`File` and exporting a
`CryptoKey` are inherently asynchronous, so the following asynchronous utilities
are provided as counterparts to the synchronous ones above and transparently
handle all value types:

- `entryMaybeToJSONAsync` - Asynchronous version of `entryMaybeToJSON`.
- `entryToJSONAsync` - Asynchronous version of `entryToJSON`.
- `valueToJSONAsync` - Asynchronous version of `valueToJSON`.
- `toEntryAsync` - Asynchronous version of `toEntry`.
- `toEntryMaybeAsync` - Asynchronous version of `toEntryMaybe`.
- `toValueAsync` - Asynchronous version of `toValue`.

The synchronous `valueToJSON` and `toValue` functions throw a `TypeError` when
given a `Blob`, `File`, or `CryptoKey`. Use the `*Async` variants for any value
that may contain these types.

### Examples

Taking a maybe entry from Deno.Kv and converting it to JSON and sending it as a
response:

```ts ignore
import { entryMaybeToJSON } from "@deno/kv-utils";
import { entryMaybeToJSONAsync } from "@deno/kv-utils";

const db = await Deno.openKv();

Deno.serve(async (_req) => {
const maybeEntry = await db.get(["a"]);
const json = entryMaybeToJSON(maybeEntry);
const json = await entryMaybeToJSONAsync(maybeEntry);
return Response.json(json);
});
```
Expand All @@ -45,13 +62,13 @@ Taking a value that was serialized to JSON in a browser and storing it in Deno
KV:

```ts ignore
import { toValue } from "@deno/kv-utils";
import { toValueAsync } from "@deno/kv-utils";

const db = await Deno.openKv();

Deno.serve(async (req) => {
const json = await req.json();
const value = toValue(json);
const value = await toValueAsync(json);
await db.set(["a"], value);
return new Response(null, { status: 204 });
});
Expand Down Expand Up @@ -93,6 +110,10 @@ The import and export utilities are:
response.
- `importEntries` - Import entries into a Deno KV store.

`exportEntries` and `importEntries` use the asynchronous `*Async` JSON utilities
internally, so they transparently support values that contain `Blob`, `File`,
`CryptoKey`, or `DOMException`.

### Examples

Exporting entries from a Deno KV store and saving them to a file:
Expand Down
1 change: 1 addition & 0 deletions _benches/byte_size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @module
*/

// deno-lint-ignore no-import-prefix
import { Serializer } from "jsr:@denostack/superserial@0.3.5";
import { serialize } from "node:v8";
import { estimateSize } from "../estimate_size.ts";
Expand Down
32 changes: 32 additions & 0 deletions import_export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,35 @@ Deno.test({
return teardown();
},
});

Deno.test({
name: "exportEntries / importEntries - Blob round trip",
sanitizeResources: false,
async fn() {
const kv = await setup();
await kv.set(["a"], new Blob([new Uint8Array([1, 2, 3])]));
const bytes: Uint8Array[] = [];
for await (const chunk of exportEntries(kv, { prefix: [] })) {
bytes.push(chunk);
}
const exportData = decoder.decode(concat(bytes));
assert(exportData.includes('"type":"Blob"'));

const target = await Deno.openKv(":memory:");
const result = await importEntries(
target,
new Blob([encoder.encode(exportData)]),
);
assertEquals(result.errors, 0);
const entry = await target.get<Blob>(["a"]);
if (!entry.value) {
throw new Error("expected entry to have a value");
}
assertEquals(entry.value.size, 3);
assert(
new Uint8Array(await entry.value.arrayBuffer())[0] === 1,
);
target.close();
return teardown();
},
});
11 changes: 8 additions & 3 deletions import_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@
* @module
*/

import { entryToJSON, type KvEntryJSON, toKey, toValue } from "./json.ts";
import {
entryToJSONAsync,
type KvEntryJSON,
toKey,
toValueAsync,
} from "./json.ts";
import { LinesTransformStream } from "./line_transform_stream.ts";

/**
Expand Down Expand Up @@ -316,7 +321,7 @@ export function exportEntries(
async start(controller) {
try {
for await (const entry of db.list(selector, options)) {
const chunk = entryToJSON(entry);
const chunk = await entryToJSONAsync(entry);
controller.enqueue(
text
? `${JSON.stringify(chunk)}\n`
Expand Down Expand Up @@ -483,7 +488,7 @@ export async function importEntries(
continue;
}
}
await db.set(entryKey, toValue(value));
await db.set(entryKey, await toValueAsync(value));
onProgress?.(count, skipped, errors);
}
if (result.done) {
Expand Down
Loading
Loading