Skip to content

Commit

Permalink
Add getDenoKv
Browse files Browse the repository at this point in the history
  • Loading branch information
jollytoad committed Jul 4, 2024
1 parent 562827d commit fc9001b
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 18 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- [store-deno-kv] & [store-deno-kv-fs] renamed `getKv()` to `getDenoKv()`

### Added

- [store-deno-kv/get-deno-kv] utility function `getDenoKv()` to obtain `Deno.Kv`
from a store if it exposes a `getDenoKv()` function

### Fixed

- stricter TS options, add `import type` where necessary

## [0.2.0]

### Changed
Expand Down
4 changes: 4 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"outdated": "deno run --allow-read=. --allow-net=jsr.io,registry.npmjs.org jsr:@check/deps",
"lock": "rm -f deno.lock && deno task check"
},
"compilerOptions": {
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true
},
"workspaces": [
"./store-common",
"./store-deno-fs",
Expand Down
2 changes: 1 addition & 1 deletion store-common/key-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StorageKey } from "./types.ts";
import type { StorageKey } from "./types.ts";

/**
* Convert a {@linkcode StorageKey} to an array of strings
Expand Down
20 changes: 20 additions & 0 deletions store-common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,23 @@ export interface StorageModule<T = unknown> {
*/
url(): Promise<string>;
}

/**
* Additional functions for a store that delegates to another store
*/
export interface DelegatedStore {
/**
* Set the store to which to delegate all function calls
*
* @param storageModule may be the store, promise of the store, or undefined to remove any delegate store
*/
setStore(storageModule?: StorageModule | Promise<StorageModule>): void;

/**
* Get the delegate store previously set via `setStore` or obtained via another mechanism (eg. env vars)
*
* @returns the promise of the store to which all operations are delegated
* @throws if no store has been set or can be loaded
*/
getStore(): Promise<StorageModule>;
}
2 changes: 1 addition & 1 deletion store-deno-fs/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
testUrl,
} from "../store-common/test-storage-module.ts";
import * as store from "./mod.ts";
import { StorageModule } from "./mod.ts";
import type { StorageModule } from "../store-common/types.ts";
import { exists } from "@std/fs/exists";

Deno.test("store-deno-fs", async (t) => {
Expand Down
4 changes: 2 additions & 2 deletions store-deno-kv-fs/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export function close(): Promise<void> {
return kv.close();
}

export function getKv(key: StorageKey): Promise<Deno.Kv> {
return kv.getKv(key);
export function getDenoKv(key: StorageKey): Promise<Deno.Kv> {
return kv.getDenoKv(key);
}

function isFsPrimary(): boolean {
Expand Down
5 changes: 5 additions & 0 deletions store-deno-kv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ This package provides an implementation of the storage module interface.
Uses the [Deno KV](https://deno.land/manual/runtime/kv) API for storage.

Import mapping: `"$store": "jsr:@jollytoad/store-deno-kv"`

## Get out of jail free

This package also supplies a utility function to obtained the actual `Deno.Kv`
of any store backed by it (that exports a `getDenoKv` function).
4 changes: 3 additions & 1 deletion store-deno-kv/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"name": "@jollytoad/store-deno-kv",
"version": "0.2.0",
"exports": {
".": "./mod.ts"
".": "./mod.ts",
"./get-deno-kv": "./get-deno-kv.ts",
"./types": "./types.ts"
}
}
49 changes: 49 additions & 0 deletions store-deno-kv/get-deno-kv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { assertInstanceOf, assertStrictEquals } from "@std/assert";
import { getDenoKv } from "./get-deno-kv.ts";
import * as denoKvStore from "./mod.ts";
import * as denoFsStore from "../store-deno-fs/mod.ts";
import * as store from "../store/mod.ts";

Deno.test("getDenoKv() returns Deno.Kv when given store-deno-kv directly", async () => {
try {
const kv = await getDenoKv(denoKvStore, []);

assertInstanceOf(kv, Deno.Kv);
} finally {
await denoKvStore.close();
}
});

Deno.test("getDenoKv() returns undefined when given store-deno-fs directly", async () => {
try {
const kv = await getDenoKv(denoFsStore, []);

assertStrictEquals(kv, undefined);
} finally {
await denoFsStore.close();
}
});

Deno.test("getDenoKv() returns Deno.Kv when given the delegate store backed by store-deno-kv", async () => {
try {
store.setStore(denoKvStore);

const kv = await getDenoKv(store, []);

assertInstanceOf(kv, Deno.Kv);
} finally {
await store.close();
}
});

Deno.test("getDenoKv() returns Deno.Kv when given the delegate store backed by store-deno-fs", async () => {
try {
store.setStore(denoFsStore);

const kv = await getDenoKv(store, []);

assertStrictEquals(kv, undefined);
} finally {
await store.close();
}
});
40 changes: 40 additions & 0 deletions store-deno-kv/get-deno-kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type {
DelegatedStore,
StorageKey,
StorageModule,
} from "@jollytoad/store-common/types";
import type { ExposeDenoKv } from "./types.ts";

/**
* Utility to attempt to get the underlying `Deno.Kv` if the store is backed by it.
*
* @example
* ```ts
* import { getDenoKv } from "jsr:@jollytoad/store-deno-kv/get-deno-kv";
* import * as store from "jsr:@jollytoad/store";
*
* const kv = await getDenoKv(store, ["foo"]);
*
* const key = ["foo", "counter"];
*
* if (kv) {
* // we can do an atomic increment...
* kv.atomic()
* .mutate({ type: "sum", key, value: new Deno.KvU64(1n) })
* .commit();
* } else {
* // otherwise fallback to a risky, get and set...
* await store.setItem(key, (await store.getItem<number>(key) ?? 0) + 1);
* }
* ```
*/
export async function getDenoKv(
store: StorageModule & Partial<ExposeDenoKv> & Partial<DelegatedStore>,
key: StorageKey,
): Promise<Deno.Kv | undefined> {
if ("getDenoKv" in store && typeof store.getDenoKv === "function") {
return (store.getDenoKv)(key);
} else if ("getStore" in store && typeof store.getStore === "function") {
return getDenoKv(await store.getStore(), key);
}
}
20 changes: 11 additions & 9 deletions store-deno-kv/mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { StorageKey, StorageModule } from "@jollytoad/store-common/types";
import type { ExposeDenoKv } from "./types.ts";

export type { StorageKey, StorageModule };
export type { ExposeDenoKv, StorageKey, StorageModule };

const consistency: Deno.KvConsistencyLevel = "eventual";

Expand All @@ -14,7 +15,8 @@ const consistency: Deno.KvConsistencyLevel = "eventual";
clearItems,
close,
url,
}) satisfies StorageModule;
getDenoKv,
}) satisfies StorageModule & ExposeDenoKv;

export function url(): Promise<string> {
return Promise.resolve(import.meta.url);
Expand All @@ -25,22 +27,22 @@ export function isWritable(_key?: StorageKey): Promise<boolean> {
}

export async function hasItem<T>(key: StorageKey): Promise<boolean> {
return (await (await getKv(key)).get<T>(key, { consistency }))
return (await (await getDenoKv(key)).get<T>(key, { consistency }))
.versionstamp !== null;
}

export async function getItem<T>(key: StorageKey): Promise<T | undefined> {
return (await (await getKv(key)).get<T>(key, { consistency })).value ??
return (await (await getDenoKv(key)).get<T>(key, { consistency })).value ??
undefined;
}

export async function setItem<T>(key: StorageKey, value: T): Promise<void> {
await (await getKv(key)).set(key, value);
await (await getDenoKv(key)).set(key, value);
}

export async function removeItem(key: StorageKey): Promise<void> {
if (key.length) {
await (await getKv(key)).delete(key);
await (await getDenoKv(key)).delete(key);
}
}

Expand All @@ -52,7 +54,7 @@ export async function* listItems<T>(
reverse = false,
): AsyncIterable<[StorageKey, T]> {
for await (
const entry of (await getKv(prefix)).list<T>({ prefix }, {
const entry of (await getDenoKv(prefix)).list<T>({ prefix }, {
consistency,
reverse,
})
Expand All @@ -62,7 +64,7 @@ export async function* listItems<T>(
}

export async function clearItems(prefix: StorageKey): Promise<void> {
const kv = await getKv(prefix);
const kv = await getDenoKv(prefix);
let op = kv.atomic();

if (prefix.length) {
Expand All @@ -89,7 +91,7 @@ const kvCache = new Map<string, Deno.Kv>();
*
* Useful to be able to perform more advanced transactional operations where necessary.
*/
export async function getKv(_key: StorageKey): Promise<Deno.Kv> {
export async function getDenoKv(_key: StorageKey): Promise<Deno.Kv> {
const kvPath = Deno.env.get("STORE_KV_PATH") || undefined;

let kv = kvCache.get(kvPath ?? "");
Expand Down
13 changes: 13 additions & 0 deletions store-deno-kv/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { StorageKey } from "@jollytoad/store-common/types";

/**
* Additional functions for a store that is backed by `Deno.Kv`
*/
export interface ExposeDenoKv {
/**
* Get the underlying `Deno.Kv` connection of the store.
*
* @param key supply the common prefix for any ops you wish to perform
*/
getDenoKv(key: StorageKey): Promise<Deno.Kv>;
}
2 changes: 1 addition & 1 deletion store/_from_env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getEnv } from "@cross/env";
import { StorageModule } from "@jollytoad/store-common/types";
import type { StorageModule } from "@jollytoad/store-common/types";

export function fromEnv(): Promise<StorageModule> {
const moduleSpecifier = getEnv("STORAGE_MODULE");
Expand Down
12 changes: 9 additions & 3 deletions store/mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { StorageKey, StorageModule } from "@jollytoad/store-common/types";
import type {
DelegatedStore,
StorageKey,
StorageModule,
} from "@jollytoad/store-common/types";

export type { StorageKey, StorageModule };
export type { DelegatedStore, StorageKey, StorageModule };

({
isWritable,
Expand All @@ -12,7 +16,9 @@ export type { StorageKey, StorageModule };
clearItems,
close,
url,
}) satisfies StorageModule;
setStore,
getStore,
}) satisfies StorageModule & DelegatedStore;

let store: Promise<StorageModule> | undefined;

Expand Down

0 comments on commit fc9001b

Please sign in to comment.