Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: deno-kv-node driver #521

Merged
merged 4 commits into from
Dec 13, 2024
Merged
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
42 changes: 40 additions & 2 deletions docs/2.drivers/deno.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -161,6 +163,9 @@
},
"ioredis": {
"optional": true
},
"@deno/kv": {
"optional": true
}
},
"packageManager": "[email protected]"
Expand Down
49 changes: 49 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions src/drivers/deno-kv-node.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
);
59 changes: 32 additions & 27 deletions src/drivers/deno-kv.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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;
}
},
};
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions test/drivers/deno-kv-node.test.ts
Original file line number Diff line number Diff line change
@@ -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),
}),
});
});
Loading