diff --git a/docs/2.drivers/deno.md b/docs/2.drivers/deno.md index 1ff29b82..1b4f3bca 100644 --- a/docs/2.drivers/deno.md +++ b/docs/2.drivers/deno.md @@ -10,10 +10,10 @@ icon: simple-icons:deno Learn more about Deno KV. :: -## Usage +## Usage (Deno) ::important -`deno-kv` driver requires [Deno deploy](https://docs.deno.com/deploy/kv/manual/on_deploy/) or [Deno runtime](https://docs.deno.com/runtime/) with `--unstable-kv` CLI flag. +`deno-kv` driver requires [Deno deploy](https://docs.deno.com/deploy/kv/manual/on_deploy/) or [Deno runtime](https://docs.deno.com/runtime/) with `--unstable-kv` CLI flag. See [Node.js](#usage-nodejs) section for other runtimes. :: ::note @@ -36,3 +36,41 @@ const storage = createStorage({ - `path`: (optional) File system path to where you'd like to store your database, otherwise one will be created for you based on the current working directory of your script by Deno. You can pass `:memory:` for testing. - `base`: (optional) Prefix key added to all operations. +- `openKV`: (advanced) Custom method that returns a Deno KV instance. + +## Usage (Node.js) + +Deno provides [`@deno/kv`](https://www.npmjs.com/package/@deno/kv) npm package, A Deno KV client library optimized for Node.js. + +- Access [Deno Deploy](https://deno.com/deploy) remote databases (or any + endpoint implementing the open + [KV Connect](https://github.com/denoland/denokv/blob/main/proto/kv-connect.md) + protocol) on Node 18+. +- Create local KV databases backed by + [SQLite](https://www.sqlite.org/index.html), using optimized native + [NAPI](https://nodejs.org/docs/latest-v18.x/api/n-api.html) packages for + Node - compatible with databases created by Deno itself. +- Create ephemeral in-memory KV instances backed by SQLite memory files or by a + lightweight JS-only implementation for testing. + +Install `@deno/kv` peer dependency: + +:pm-install{name="@deno/kv"} + +```js +import { createStorage } from "unstorage"; +import denoKVNodedriver from "unstorage/drivers/deno-kv-node"; + +const storage = createStorage({ + driver: denoKVNodedriver({ + // path: ":memory:", + // base: "", + }), +}); +``` + +**Options:** + +- `path`: (same as `deno-kv`) +- `base`: (same as `deno-kv`) +- `openKvOptions`: Check [docs](https://www.npmjs.com/package/@deno/kv#api) for available options. diff --git a/package.json b/package.json index 9d88d7e2..51d49377 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3", "@cloudflare/workers-types": "^4.20241205.0", + "@deno/kv": "^0.8.4", "@electric-sql/pglite": "^0.2.15", "@libsql/client": "^0.14.0", "@netlify/blobs": "^8.1.0", @@ -114,7 +115,8 @@ "@vercel/kv": "^1.0.1", "db0": ">=0.1", "idb-keyval": "^6.2.1", - "ioredis": "^5.4.1" + "ioredis": "^5.4.1", + "@deno/kv": ">=0.8.4" }, "peerDependenciesMeta": { "@azure/app-configuration": { @@ -161,6 +163,9 @@ }, "ioredis": { "optional": true + }, + "@deno/kv": { + "optional": true } }, "packageManager": "pnpm@9.14.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3245675b..f3760a4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: '@cloudflare/workers-types': specifier: ^4.20241205.0 version: 4.20241205.0 + '@deno/kv': + specifier: ^0.8.4 + version: 0.8.4 '@electric-sql/pglite': specifier: ^0.2.15 version: 0.2.15 @@ -404,6 +407,33 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@deno/kv-darwin-arm64@0.8.4': + resolution: {integrity: sha512-j86nnE1QdLw20OrUs/6Iw6ZYzC8pmfU1+K4hNSVHO9K0bfy3VBd4JSHkHLmYCiHDkgIm+wTxct33thl6HxXz0Q==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] + + '@deno/kv-darwin-x64@0.8.4': + resolution: {integrity: sha512-qdczxcqkN2fbDX/nIzUetI6i8usNu8kpN3sDV0rXcSWlg9E5huWWjGp6PbOS4w1xarUWbqFAZvy4VSmGTVN1Zw==} + engines: {node: '>= 18'} + cpu: [x64] + os: [darwin] + + '@deno/kv-linux-x64-gnu@0.8.4': + resolution: {integrity: sha512-xv2rM6wrVHVOM4Nswl8iyfdZZiEp5r85jwLajj0NGTiLMAQLBtDsBE/kpH4Ap3K6yiqJX3nTb44Z8AJ+IyzO4Q==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@deno/kv-win32-x64-msvc@0.8.4': + resolution: {integrity: sha512-xTEByTpC1DWw4A1F9isD8B16v1+CQFHFvi/Mm2bqlO9iD5HfIGgalWJbL3EvgYeybQ9yA27KGqaGnKxXdaX5Rg==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] + + '@deno/kv@0.8.4': + resolution: {integrity: sha512-5q2izU1tp6wv8rDIwMb6GXe/B+aO/sjAjRAOIigEtX+qOiTLsPE++ibJbfafVb0LmjEdlA18Kpfo23fln73OtQ==} + '@electric-sql/pglite@0.2.15': resolution: {integrity: sha512-Jiq31Dnk+rg8rMhcSxs4lQvHTyizNo5b269c1gCC3ldQ0sCLrNVPGzy+KnmonKy1ZArTUuXZf23/UamzFMKVaA==} @@ -5188,6 +5218,25 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + '@deno/kv-darwin-arm64@0.8.4': + optional: true + + '@deno/kv-darwin-x64@0.8.4': + optional: true + + '@deno/kv-linux-x64-gnu@0.8.4': + optional: true + + '@deno/kv-win32-x64-msvc@0.8.4': + optional: true + + '@deno/kv@0.8.4': + optionalDependencies: + '@deno/kv-darwin-arm64': 0.8.4 + '@deno/kv-darwin-x64': 0.8.4 + '@deno/kv-linux-x64-gnu': 0.8.4 + '@deno/kv-win32-x64-msvc': 0.8.4 + '@electric-sql/pglite@0.2.15': {} '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': diff --git a/src/drivers/deno-kv-node.ts b/src/drivers/deno-kv-node.ts new file mode 100644 index 00000000..5c4d63fd --- /dev/null +++ b/src/drivers/deno-kv-node.ts @@ -0,0 +1,29 @@ +import { openKv, type Kv } from "@deno/kv"; +import { defineDriver } from "./utils/index"; +import denoKV from "./deno-kv"; + +// https://docs.deno.com/deploy/kv/manual/node/ + +export interface DenoKvNodeOptions { + base?: string; + path?: string; + openKvOptions?: Parameters<typeof openKv>[1]; +} + +const DRIVER_NAME = "deno-kv-node"; + +export default defineDriver<DenoKvNodeOptions, Kv | Promise<Kv>>( + (opts: DenoKvNodeOptions = {}) => { + const baseDriver = denoKV({ + ...opts, + openKv: () => openKv(opts.path, opts.openKvOptions), + }); + return { + ...baseDriver, + getInstance() { + return baseDriver.getInstance!() as Promise<Kv>; + }, + name: DRIVER_NAME, + }; + } +); diff --git a/src/drivers/deno-kv.ts b/src/drivers/deno-kv.ts index adfe2629..7830a1f5 100644 --- a/src/drivers/deno-kv.ts +++ b/src/drivers/deno-kv.ts @@ -1,43 +1,49 @@ import { normalizeKey } from "../utils"; import { defineDriver, createError } from "./utils/index"; +import type { Kv, KvKey } from "@deno/kv"; // https://docs.deno.com/deploy/kv/manual/ export interface DenoKvOptions { base?: string; path?: string; + openKv?: () => Promise<Deno.Kv | Kv>; } const DRIVER_NAME = "deno-kv"; -export default defineDriver<DenoKvOptions, Promise<Deno.Kv>>( +export default defineDriver<DenoKvOptions, Promise<Deno.Kv | Kv>>( (opts: DenoKvOptions = {}) => { - const basePrefix: Deno.KvKey = opts.base + const basePrefix: KvKey = opts.base ? normalizeKey(opts.base).split(":") : []; - const r = (key: string = ""): Deno.KvKey => + const r = (key: string = ""): KvKey => [...basePrefix, ...key.split(":")].filter(Boolean); - let _client: Promise<Deno.Kv> | undefined; - const getKv = async () => { - if (_client) { - return _client; + let _kv: Promise<Kv | Deno.Kv> | undefined; + const getKv = () => { + if (_kv) { + return _kv; } - if (!globalThis.Deno) { - throw createError( - DRIVER_NAME, - "Missing global `Deno`. Are you running in Deno?" - ); - } - if (!Deno.openKv) { - throw createError( - DRIVER_NAME, - "Missing `Deno.openKv`. Are you running Deno with --unstable-kv?" - ); + if (opts.openKv) { + _kv = opts.openKv(); + } else { + if (!globalThis.Deno) { + throw createError( + DRIVER_NAME, + "Missing global `Deno`. Are you running in Deno? (hint: use `deno-kv-node` driver for Node.js)" + ); + } + if (!Deno.openKv) { + throw createError( + DRIVER_NAME, + "Missing `Deno.openKv`. Are you running Deno with --unstable-kv?" + ); + } + _kv = Deno.openKv(opts.path); } - _client = Deno.openKv(opts.path); - return _client; + return _kv; }; return { @@ -89,16 +95,15 @@ export default defineDriver<DenoKvOptions, Promise<Deno.Kv>>( const kv = await getKv(); const batch = kv.atomic(); for await (const entry of kv.list({ prefix: r(base) })) { - batch.delete(entry.key); + batch.delete(entry.key as KvKey); } await batch.commit(); }, - dispose() { - if (_client) { - return _client.then((kv) => { - kv.close(); - _client = undefined; - }); + async dispose() { + if (_kv) { + const kv = await _kv; + await kv.close(); + _kv = undefined; } }, }; diff --git a/src/index.ts b/src/index.ts index 9fcd8880..9c772957 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,8 @@ export const builtinDrivers = { cloudflareKVHTTP: "unstorage/drivers/cloudflare-kv-http", cloudflareR2Binding: "unstorage/drivers/cloudflare-r2-binding", db0: "unstorage/drivers/db0", + denoKv: "unstorage/drivers/deno-kv", + denoKvNode: "unstorage/drivers/deno-kv-node", fs: "unstorage/drivers/fs", fsLite: "unstorage/drivers/fs-lite", github: "unstorage/drivers/github", diff --git a/test/drivers/deno-kv-node.test.ts b/test/drivers/deno-kv-node.test.ts new file mode 100644 index 00000000..629f0493 --- /dev/null +++ b/test/drivers/deno-kv-node.test.ts @@ -0,0 +1,12 @@ +import { describe } from "vitest"; +import denoKvNodeDriver from "../../src/drivers/deno-kv-node.ts"; +import { testDriver } from "./utils.ts"; + +describe("drivers: deno-kv-node", async () => { + testDriver({ + driver: denoKvNodeDriver({ + path: ":memory:", + base: Math.round(Math.random() * 1_000_000).toString(16), + }), + }); +});