From 3fcbbbbfa5f2c7f8a1a8a9d7827c8a3f3df45150 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 12 Dec 2024 15:58:05 +0100 Subject: [PATCH 1/4] feat: `deno-kv-node` driver --- docs/2.drivers/deno.md | 1 + package.json | 7 +++- pnpm-lock.yaml | 49 +++++++++++++++++++++++++ src/drivers/deno-kv-node.ts | 29 +++++++++++++++ src/drivers/deno-kv.ts | 59 +++++++++++++++++-------------- test/drivers/deno-kv-node.test.ts | 12 +++++++ 6 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 src/drivers/deno-kv-node.ts create mode 100644 test/drivers/deno-kv-node.test.ts diff --git a/docs/2.drivers/deno.md b/docs/2.drivers/deno.md index 1ff29b82..d0c86180 100644 --- a/docs/2.drivers/deno.md +++ b/docs/2.drivers/deno.md @@ -36,3 +36,4 @@ 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. +- `getKV`: (advanced) Custom method that returns a Deno KV instance. 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[1]; +} + +const DRIVER_NAME = "deno-kv-node"; + +export default defineDriver>( + (opts: DenoKvNodeOptions = {}) => { + const baseDriver = denoKV({ + ...opts, + openKv: () => openKv(opts.path, opts.openKvOptions), + }); + return { + ...baseDriver, + getInstance() { + return baseDriver.getInstance!() as Promise; + }, + 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; } const DRIVER_NAME = "deno-kv"; -export default defineDriver>( +export default defineDriver>( (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 | undefined; - const getKv = async () => { - if (_client) { - return _client; + let _kv: Promise | 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>( 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/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), + }), + }); +}); From a5faa3c6797e9128d7884c7c2d4fb88204e7aed9 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 13 Dec 2024 19:19:35 +0100 Subject: [PATCH 2/4] add index entries --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) 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", From 06fe7a161667519618495e064fe6819144b2158d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 13 Dec 2024 19:41:00 +0100 Subject: [PATCH 3/4] add docs --- docs/2.drivers/deno.md | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/2.drivers/deno.md b/docs/2.drivers/deno.md index d0c86180..c2a3d615 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,4 +36,35 @@ 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. -- `getKV`: (advanced) Custom method that returns a Deno KV instance. +- `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: "", + }), +}); +``` From 06b15787fea6b0b4c52457372015b77689b21b3b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 13 Dec 2024 19:43:56 +0100 Subject: [PATCH 4/4] update docs --- docs/2.drivers/deno.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/2.drivers/deno.md b/docs/2.drivers/deno.md index c2a3d615..1b4f3bca 100644 --- a/docs/2.drivers/deno.md +++ b/docs/2.drivers/deno.md @@ -68,3 +68,9 @@ const storage = createStorage({ }), }); ``` + +**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.