diff --git a/CHANGELOG.md b/CHANGELOG.md index 8733acb..4fc70ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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] +## [0.3.0] ### Changed diff --git a/deno.json b/deno.json index 3373c71..ddd5d71 100644 --- a/deno.json +++ b/deno.json @@ -6,7 +6,7 @@ "test": "deno test --allow-env --allow-read --allow-write --allow-net", "reload": "deno cache --reload **/*.ts jsr:@check/deps", "check": "deno check **/*.ts", - "lint": "deno lint", + "lint": "deno lint && deno doc --lint **/*.ts", "ok": "deno fmt && deno task lint && deno task check && deno task test && deno publish --dry-run --allow-dirty", "outdated": "deno run --allow-read=. --allow-net=jsr.io,registry.npmjs.org jsr:@check/deps", "lock": "rm -f deno.lock && deno task check" @@ -26,12 +26,13 @@ ], "imports": { "@cross/env": "jsr:@cross/env@^1.0.2", - "@jollytoad/store": "jsr:@jollytoad/store@^0.2.0", - "@jollytoad/store-common": "jsr:@jollytoad/store-common@^0.2.0", - "@jollytoad/store-deno-fs": "jsr:@jollytoad/store-deno-fs@^0.2.0", - "@jollytoad/store-deno-kv": "jsr:@jollytoad/store-deno-kv@^0.2.0", - "@jollytoad/store-deno-kv-fs": "jsr:@jollytoad/store-deno-kv-fs@^0.2.0", - "@jollytoad/store-web-storage": "jsr:@jollytoad/store-web-storage@^0.2.0", + "@jollytoad/store": "jsr:@jollytoad/store@^0.3.0", + "@jollytoad/store-common": "jsr:@jollytoad/store-common@^0.3.0", + "@jollytoad/store-deno-fs": "jsr:@jollytoad/store-deno-fs@^0.3.0", + "@jollytoad/store-deno-kv": "jsr:@jollytoad/store-deno-kv@^0.3.0", + "@jollytoad/store-deno-kv-fs": "jsr:@jollytoad/store-deno-kv-fs@^0.3.0", + "@jollytoad/store-web-storage": "jsr:@jollytoad/store-web-storage@^0.3.0", + "@jollytoad/store-no-op": "jsr:@jollytoad/store-no-op@^0.3.0", "@std/assert": "jsr:@std/assert@^1.0.0-rc.3", "@std/fs": "jsr:@std/fs@^1.0.0-rc.3", "@std/path": "jsr:@std/path@^1.0.0-rc.3" diff --git a/deno.lock b/deno.lock index 854ccc6..15250c7 100644 --- a/deno.lock +++ b/deno.lock @@ -56,12 +56,13 @@ "workspace": { "dependencies": [ "jsr:@cross/env@^1.0.2", - "jsr:@jollytoad/store-common@^0.2.0", - "jsr:@jollytoad/store-deno-fs@^0.2.0", - "jsr:@jollytoad/store-deno-kv-fs@^0.2.0", - "jsr:@jollytoad/store-deno-kv@^0.2.0", - "jsr:@jollytoad/store-web-storage@^0.2.0", - "jsr:@jollytoad/store@^0.2.0", + "jsr:@jollytoad/store-common@^0.3.0", + "jsr:@jollytoad/store-deno-fs@^0.3.0", + "jsr:@jollytoad/store-deno-kv-fs@^0.3.0", + "jsr:@jollytoad/store-deno-kv@^0.3.0", + "jsr:@jollytoad/store-no-op@^0.3.0", + "jsr:@jollytoad/store-web-storage@^0.3.0", + "jsr:@jollytoad/store@^0.3.0", "jsr:@std/assert@^1.0.0-rc.3", "jsr:@std/fs@^1.0.0-rc.3", "jsr:@std/path@^1.0.0-rc.3" diff --git a/store-common/deno.json b/store-common/deno.json index a3dc02d..3be4136 100644 --- a/store-common/deno.json +++ b/store-common/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-common", - "version": "0.2.0", + "version": "0.3.0", "exports": { "./key-utils": "./key-utils.ts", "./test-storage-module": "./test-storage-module.ts", diff --git a/store-common/test-storage-module.ts b/store-common/test-storage-module.ts index 16e75da..0c703a4 100644 --- a/store-common/test-storage-module.ts +++ b/store-common/test-storage-module.ts @@ -6,6 +6,9 @@ import { } from "@std/assert"; import type { StorageModule } from "./types.ts"; +/** + * Test the url() function of the given storage module. + */ export async function testUrl( t: Deno.TestContext, { url }: StorageModule, @@ -18,6 +21,9 @@ export async function testUrl( }); } +/** + * Test the isWriteable() function of the given storage module. + */ export async function testIsWriteable( t: Deno.TestContext, { isWritable }: StorageModule, @@ -28,6 +34,9 @@ export async function testIsWriteable( }); } +/** + * Test the setItem() function of the given storage module. + */ export async function testSetItem( t: Deno.TestContext, { setItem }: StorageModule, @@ -45,6 +54,10 @@ export async function testSetItem( }); } +/** + * Test the hasItem() function of the given storage module, + * this must be called after `testSetItem`. + */ export async function testHasItem( t: Deno.TestContext, { hasItem }: StorageModule, @@ -62,6 +75,10 @@ export async function testHasItem( }); } +/** + * Test the getItem() function of the given storage module, + * this must be called after `testSetItem`. + */ export async function testGetItem( t: Deno.TestContext, { getItem }: StorageModule, @@ -79,6 +96,10 @@ export async function testGetItem( }); } +/** + * Test the listItems() function of the given storage module, + * this must be called after `testSetItem`. + */ export async function testListItems( t: Deno.TestContext, { listItems }: StorageModule, @@ -101,6 +122,10 @@ export async function testListItems( }); } +/** + * Test the removeItem() function of the given storage module, + * this must be called after `testSetItem`. + */ export async function testRemoveItem( t: Deno.TestContext, { removeItem, listItems, setItem, hasItem }: StorageModule, @@ -134,6 +159,9 @@ export async function testRemoveItem( }); } +/** + * Test the clearItems() function of the given storage module. + */ export async function testClearItems( t: Deno.TestContext, { clearItems, setItem, hasItem, listItems }: StorageModule, @@ -163,6 +191,11 @@ export async function testClearItems( }); } +/** + * Test the storage module list items ordered by key, + * and supports listing in reverse. + * This test is optional for a storage module. + */ export async function testOrdering( t: Deno.TestContext, { setItem, listItems, removeItem }: StorageModule, @@ -191,11 +224,13 @@ export async function testOrdering( }); } +/** + * Ensure that the underlying storage has been opened and is empty + */ export async function open( _t: Deno.TestContext, { hasItem, clearItems }: StorageModule, ) { - // Just ensure that the underlying storage has been opened and is empty await hasItem(["store"]); await clearItems([]); } diff --git a/store-common/types.ts b/store-common/types.ts index b7cb081..426c5e5 100644 --- a/store-common/types.ts +++ b/store-common/types.ts @@ -1,3 +1,11 @@ +/** + * A key for an item in a store. + * + * An array of string, number or boolean. + * This may be translated to a format compatible with the underlying storage mechanism, + * often a single string delimited by slashes `/`, and where numbers and booleans are + * converted directly to string format. + */ export type StorageKey = readonly (string | number | boolean)[]; /** @@ -46,6 +54,7 @@ export interface StorageModule { /** * Close all associated resources. + * This isn't generally required in most situations, it's main use is within test cases. */ close(): Promise; @@ -60,7 +69,7 @@ export interface StorageModule { */ export interface DelegatedStore { /** - * Set the store to which to delegate all function calls + * Set the storage module to which all function calls are delegated * * @param storageModule may be the store, promise of the store, or undefined to remove any delegate store */ diff --git a/store-deno-fs/README.md b/store-deno-fs/README.md index 9aca40d..cef9e2a 100644 --- a/store-deno-fs/README.md +++ b/store-deno-fs/README.md @@ -15,3 +15,18 @@ JSON file. eg: `["one", "two", "three"]` -> `.store/one/two/three.json` Import mapping: `"$store": "jsr:@jollytoad/store-deno-fs"` + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-deno-fs"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), true); +assertEquals(await store.getItem(["foo", "hello"]), "world"); + +await store.clearItems(["foo"]); +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` diff --git a/store-deno-fs/deno.json b/store-deno-fs/deno.json index 489e688..545df22 100644 --- a/store-deno-fs/deno.json +++ b/store-deno-fs/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-deno-fs", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts" } diff --git a/store-deno-fs/mod.ts b/store-deno-fs/mod.ts index db20953..398b946 100644 --- a/store-deno-fs/mod.ts +++ b/store-deno-fs/mod.ts @@ -22,10 +22,16 @@ export type { StorageKey, StorageModule }; url, }) satisfies StorageModule; +/** + * Returns the `import.meta.url` of the module. + */ export function url(): Promise { return Promise.resolve(import.meta.url); } +/** + * Check for filesystem write permission at directory for the given key. + */ export async function isWritable(key: StorageKey = []): Promise { if (Deno.env.get("DENO_DEPLOYMENT_ID")) { return false; @@ -36,10 +42,16 @@ export async function isWritable(key: StorageKey = []): Promise { })).state === "granted"; } +/** + * Determine whether a value is set for the given key. + */ export function hasItem(key: StorageKey): Promise { return exists(filepath(key), { isFile: true }); } +/** + * Get a value for the given key. + */ export async function getItem(key: StorageKey): Promise { try { return JSON.parse(await Deno.readTextFile(filepath(key))); @@ -52,12 +64,18 @@ export async function getItem(key: StorageKey): Promise { } } +/** + * Set a value for the given key. + */ export async function setItem(key: StorageKey, value: T): Promise { const path = filepath(key); await ensureDir(dirname(path)); await Deno.writeTextFile(path, JSON.stringify(value)); } +/** + * Remove the value with the given key. + */ export async function removeItem(key: StorageKey): Promise { let path = filepath(key); if (await exists(path, { isFile: true })) { @@ -76,7 +94,10 @@ export async function removeItem(key: StorageKey): Promise { } } -// TODO: Support reverse ordering +/** + * List all items beneath the given key prefix. + * At present ordering is not guaranteed and reverse support is optional. + */ export async function* listItems( keyPrefix: StorageKey = [], _reverse = false, @@ -105,6 +126,9 @@ export async function* listItems( } } +/** + * Delete item and sub items recursively and clean up. + */ export async function clearItems(keyPrefix: StorageKey): Promise { await removeItem(keyPrefix); try { @@ -118,6 +142,10 @@ export async function clearItems(keyPrefix: StorageKey): Promise { } } +/** + * Close all associated resources. + * This isn't generally required in most situations, it's main use is within test cases. + */ export function close(): Promise { return Promise.resolve(); } diff --git a/store-deno-fs/store.test.ts b/store-deno-fs/store.test.ts index 9b405b0..50e9080 100644 --- a/store-deno-fs/store.test.ts +++ b/store-deno-fs/store.test.ts @@ -33,7 +33,7 @@ Deno.test("store-deno-fs", async (t) => { } }); -export async function testDirectoryPurge( +async function testDirectoryPurge( t: Deno.TestContext, { setItem, removeItem }: StorageModule, ) { diff --git a/store-deno-kv-fs/README.md b/store-deno-kv-fs/README.md index b1e2c2c..e2a8efa 100644 --- a/store-deno-kv-fs/README.md +++ b/store-deno-kv-fs/README.md @@ -12,3 +12,18 @@ unless the env var `STORE_PRIMARY` is set to `kv`, in which case the KV always overrides filesystem values. Import mapping: `"$store": "jsr:@jollytoad/store-deno-kv-fs"` + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-deno-kv-fs"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), true); +assertEquals(await store.getItem(["foo", "hello"]), "world"); + +await store.clearItems(["foo"]); +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` diff --git a/store-deno-kv-fs/deno.json b/store-deno-kv-fs/deno.json index d5e4321..563f7bd 100644 --- a/store-deno-kv-fs/deno.json +++ b/store-deno-kv-fs/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-deno-kv-fs", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts" } diff --git a/store-deno-kv-fs/mod.ts b/store-deno-kv-fs/mod.ts index c3acfed..aaf4e05 100644 --- a/store-deno-kv-fs/mod.ts +++ b/store-deno-kv-fs/mod.ts @@ -16,10 +16,17 @@ export type { StorageKey, StorageModule }; url, }) satisfies StorageModule; +/** + * Returns the `import.meta.url` of the module. + */ export function url(): Promise { return Promise.resolve(import.meta.url); } +/** + * Check whether the storage is writable in general, or at or below a particular key. + * There still may be some sub-keys that differ. + */ export async function isWritable(key: StorageKey = []): Promise { if (key.length && isFsPrimary() && await fs.hasItem(key)) { return false; @@ -27,10 +34,16 @@ export async function isWritable(key: StorageKey = []): Promise { return kv.isWritable(key); } +/** + * Determine whether a value is set for the given key. + */ export async function hasItem(key: StorageKey): Promise { return await fs.hasItem(key) || await kv.hasItem(key); } +/** + * Get a value for the given key. + */ export async function getItem(key: StorageKey): Promise { if (isFsPrimary()) { return await fs.getItem(key) ?? await kv.getItem(key); @@ -39,6 +52,9 @@ export async function getItem(key: StorageKey): Promise { } } +/** + * Set a value for the given key. + */ export async function setItem(key: StorageKey, value: T): Promise { if (isFsPrimary() && await fs.hasItem(key)) { // Prevent saving of value that already exists in filesystem @@ -47,10 +63,17 @@ export async function setItem(key: StorageKey, value: T): Promise { await kv.setItem(key, value); } +/** + * Remove the value with the given key. + */ export async function removeItem(key: StorageKey): Promise { await kv.removeItem(key); } +/** + * List all items beneath the given key prefix. + * At present, guaranteed ordering and reverse support is optional. + */ export async function* listItems( prefix: StorageKey = [], reverse = false, @@ -66,14 +89,25 @@ export async function* listItems( } } +/** + * Delete item and sub items recursively and clean up. + */ export async function clearItems(prefix: StorageKey): Promise { await kv.clearItems(prefix); } +/** + * Close all associated resources. + * This isn't generally required in most situations, it's main use is within test cases. + */ export function close(): Promise { return kv.close(); } +/** + * Get the underlying `Deno.Kv` connection, to allow use of more specialized facilities + * such as transactions. + */ export function getDenoKv(key: StorageKey): Promise { return kv.getDenoKv(key); } diff --git a/store-deno-kv/README.md b/store-deno-kv/README.md index 497faf4..c3ce708 100644 --- a/store-deno-kv/README.md +++ b/store-deno-kv/README.md @@ -12,3 +12,18 @@ Import mapping: `"$store": "jsr:@jollytoad/store-deno-kv"` This package also supplies a utility function to obtained the actual `Deno.Kv` of any store backed by it (that exports a `getDenoKv` function). + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-deno-kv"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), true); +assertEquals(await store.getItem(["foo", "hello"]), "world"); + +await store.clearItems(["foo"]); +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` diff --git a/store-deno-kv/deno.json b/store-deno-kv/deno.json index b72868b..45cbc14 100644 --- a/store-deno-kv/deno.json +++ b/store-deno-kv/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-deno-kv", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts", "./get-deno-kv": "./get-deno-kv.ts", diff --git a/store-deno-kv/mod.ts b/store-deno-kv/mod.ts index 3212946..d893188 100644 --- a/store-deno-kv/mod.ts +++ b/store-deno-kv/mod.ts @@ -18,28 +18,47 @@ const consistency: Deno.KvConsistencyLevel = "eventual"; getDenoKv, }) satisfies StorageModule & ExposeDenoKv; +/** + * Returns the `import.meta.url` of the module. + */ export function url(): Promise { return Promise.resolve(import.meta.url); } +/** + * Check whether the storage is writable in general, or at or below a particular key. + * There still may be some sub-keys that differ. + */ export function isWritable(_key?: StorageKey): Promise { return Promise.resolve(true); } +/** + * Determine whether a value is set for the given key. + */ export async function hasItem(key: StorageKey): Promise { return (await (await getDenoKv(key)).get(key, { consistency })) .versionstamp !== null; } +/** + * Get a value for the given key. + */ export async function getItem(key: StorageKey): Promise { return (await (await getDenoKv(key)).get(key, { consistency })).value ?? undefined; } +/** + * Set a value for the given key. + */ export async function setItem(key: StorageKey, value: T): Promise { await (await getDenoKv(key)).set(key, value); } +/** + * Remove the value with the given key. + */ export async function removeItem(key: StorageKey): Promise { if (key.length) { await (await getDenoKv(key)).delete(key); @@ -47,6 +66,7 @@ export async function removeItem(key: StorageKey): Promise { } /** + * List all items beneath the given key prefix. * Supports ordering and reverse based on the KV natural key ordering. */ export async function* listItems( @@ -63,6 +83,9 @@ export async function* listItems( } } +/** + * Delete item and sub items recursively and clean up. + */ export async function clearItems(prefix: StorageKey): Promise { const kv = await getDenoKv(prefix); let op = kv.atomic(); @@ -78,6 +101,10 @@ export async function clearItems(prefix: StorageKey): Promise { await op.commit(); } +/** + * Close all associated resources. + * This isn't generally required in most situations, it's main use is within test cases. + */ export async function close(): Promise { const kvs = [...kvCache.values()]; kvCache.clear(); diff --git a/store-no-op/README.md b/store-no-op/README.md index 57a99a5..12187cf 100644 --- a/store-no-op/README.md +++ b/store-no-op/README.md @@ -7,3 +7,14 @@ This package provides an implementation of the storage module interface. This is a readonly no-op implementation of the storage interface. Import mapping: `"$store": "jsr:@jollytoad/store-no-op"` + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-no-op"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` diff --git a/store-no-op/deno.json b/store-no-op/deno.json index d1d2e52..22cab6e 100644 --- a/store-no-op/deno.json +++ b/store-no-op/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-no-op", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts" } diff --git a/store-no-op/mod.ts b/store-no-op/mod.ts index 3454346..bd61459 100644 --- a/store-no-op/mod.ts +++ b/store-no-op/mod.ts @@ -14,30 +14,51 @@ export type { StorageKey, StorageModule }; url, }) satisfies StorageModule; +/** + * Returns the `import.meta.url` of the module. + */ export function url(): Promise { return Promise.resolve(import.meta.url); } +/** + * Always false, as this is not a real storage mechanism. + */ export function isWritable(_key: StorageKey = []): Promise { return Promise.resolve(false); } +/** + * Always false, as this store holds no data. + */ export function hasItem(_key: StorageKey): Promise { return Promise.resolve(false); } +/** + * Always undefined, as this store holds no data. + */ export function getItem(_key: StorageKey): Promise { return Promise.resolve(undefined); } +/** + * Does nothing, as this is not a real storage mechanism. + */ export function setItem(_key: StorageKey, _value: T): Promise { return Promise.resolve(); } +/** + * Does nothing, as this is not a real storage mechanism. + */ export function removeItem(_key: StorageKey): Promise { return Promise.resolve(); } +/** + * Lists nothing, as this store holds no data. + */ export async function* listItems( _prefix: StorageKey = [], _reverse = false, @@ -45,10 +66,16 @@ export async function* listItems( // do nothing } +/** + * Does nothing, as this is not a real storage mechanism. + */ export function clearItems(_prefix: StorageKey): Promise { return Promise.resolve(); } +/** + * Does nothing, as this is not a real storage mechanism. + */ export function close(): Promise { return Promise.resolve(); } diff --git a/store-web-storage/README.md b/store-web-storage/README.md index 2426e5b..2c09fda 100644 --- a/store-web-storage/README.md +++ b/store-web-storage/README.md @@ -12,3 +12,18 @@ The parts of the key are joined with a `/` to form a single key string for use with the `localStorage` API. Import mapping: `"$store": "jsr:@jollytoad/store-web-storage"` + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-web-storage"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), true); +assertEquals(await store.getItem(["foo", "hello"]), "world"); + +await store.clearItems(["foo"]); +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` diff --git a/store-web-storage/deno.json b/store-web-storage/deno.json index 1046728..067d47e 100644 --- a/store-web-storage/deno.json +++ b/store-web-storage/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store-web-storage", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts" } diff --git a/store-web-storage/mod.ts b/store-web-storage/mod.ts index 84d6148..471ccdf 100644 --- a/store-web-storage/mod.ts +++ b/store-web-storage/mod.ts @@ -17,18 +17,31 @@ export type { StorageKey, StorageModule }; const SEP = "/"; +/** + * Returns the `import.meta.url` of the module. + */ export function url(): Promise { return Promise.resolve(import.meta.url); } +/** + * Check whether the storage is writable in general, or at or below a particular key. + * There still may be some sub-keys that differ. + */ export function isWritable(_key?: StorageKey): Promise { return Promise.resolve(true); } +/** + * Determine whether a value is set for the given key. + */ export function hasItem(key: StorageKey): Promise { return Promise.resolve(localStorage.getItem(storageKey(key)) !== null); } +/** + * Get a value for the given key. + */ export function getItem(key: StorageKey): Promise { const json = localStorage.getItem(storageKey(key)); if (typeof json === "string") { @@ -37,16 +50,26 @@ export function getItem(key: StorageKey): Promise { return Promise.resolve(undefined); } +/** + * Set a value for the given key. + */ export function setItem(key: StorageKey, value: T): Promise { localStorage.setItem(storageKey(key), JSON.stringify(value)); return Promise.resolve(); } +/** + * Remove the value with the given key. + */ export function removeItem(key: StorageKey): Promise { localStorage.removeItem(storageKey(key)); return Promise.resolve(); } +/** + * List all items beneath the given key prefix. + * At present, guaranteed ordering and reverse support is optional. + */ export async function* listItems( keyPrefix: StorageKey = [], reverse = false, @@ -72,6 +95,9 @@ export async function* listItems( } } +/** + * Delete item and sub items recursively and clean up. + */ export function clearItems(keyPrefix: StorageKey): Promise { const queued: string[] = [storageKey(keyPrefix)]; @@ -88,6 +114,10 @@ export function clearItems(keyPrefix: StorageKey): Promise { return Promise.resolve(); } +/** + * Close all associated resources. + * This isn't generally required in most situations, it's main use is within test cases. + */ export function close(): Promise { return Promise.resolve(); } diff --git a/store/README.md b/store/README.md index cfbc390..60d9768 100644 --- a/store/README.md +++ b/store/README.md @@ -70,8 +70,6 @@ await setItem(["store", "hello"], "world"); Set the `STORAGE_MODULE` environment variable to the URL of the preferred storage module. -_(TODO: Test whether `jsr:` specifiers work for the env var)_ - ```sh STORAGE_MODULE=jsr:@jollytoad/store-deno-fs deno run --allow-env --allow-net ... ``` @@ -100,12 +98,44 @@ best to assume the first level key to be a grouping level, eg. a database name. Any JSON serializable value can be stored. -See the [types](./store-common/types.ts) for a description of the module -interface. +Here is a quick overview of the main functions, they deliberately follow a +naming scheme similar to the standard +[Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API): + +- `setItem(key, value)` - set the value for the exact key in the store +- `hasItem(key)` - whether a value at the exact key has been set +- `getItem(key)` - get the value at the exact key +- `listItems(prefix, reverse)` - iterates over the items that match the key + prefix +- `removeItem(key)` - delete the value for the exact key (ie. not any sub-items) +- `clearItems(prefix)` - deletes all items that match the key prefix (ie. an + item and sub-items recursively) + +See the [types](https://jsr.io/@jollytoad/store-common/doc/types/~) for a +description of all the functions in the module interface. + +**Example** + +```ts +import * as store from "jsr:@jollytoad/store-deno-fs"; +import { assertEquals } from "jsr:@std/assert"; + +await store.setItem(["foo", "hello"], "world"); + +assertEquals(await store.hasItem(["foo", "hello"]), true); +assertEquals(await store.getItem(["foo", "hello"]), "world"); + +await store.clearItems(["foo"]); +assertEquals(await store.hasItem(["foo", "hello"]), false); +``` + +You can swap the imported module for any of the alternative implementations, +`"jsr:@jollytoad/store-deno-kv"`, or `"jsr:@jollytoad/store-web-storage"` for +example. ## Modules -### [web-storage](https://jsr.io/@jollytoad/store-web-storage) +### [store-web-storage](https://jsr.io/@jollytoad/store-web-storage) This uses `localStorage` of the standard [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) @@ -116,7 +146,7 @@ with the `localStorage` API. Import mapping: `"$store": "jsr:@jollytoad/store-web-storage"` -### [deno-fs](https://jsr.io/@jollytoad/store-deno-fs) +### [store-deno-fs](https://jsr.io/@jollytoad/store-deno-fs) This stores values in individual files under a directory hierarchy via [Deno fs](https://deno.land/api?s=Deno.readTextFile) calls. By default this is @@ -130,13 +160,13 @@ eg: `["one", "two", "three"]` -> `.store/one/two/three.json` Import mapping: `"$store": "jsr:@jollytoad/store-deno-fs"` -### [deno-kv](https://jsr.io/@jollytoad/store-deno-kv) +### [store-deno-kv](https://jsr.io/@jollytoad/store-deno-kv) -Uses the [Deno KV](https://deno.land/manual/runtime/kv) API for storage. +Uses the [Deno KV](https://docs.deno.com/deploy/kv/manual) API for storage. Import mapping: `"$store": "jsr:@jollytoad/store-deno-kv"` -### [deno-kv-fs](https://jsr.io/@jollytoad/store-deno-kv-fs) +### [store-deno-kv-fs](https://jsr.io/@jollytoad/store-deno-kv-fs) Combination of a readonly `deno-fs` and writeable `deno-kv`, allowing fallback or immutable storage in the filesystem, and mutable storage via the KV store. @@ -147,7 +177,7 @@ overrides filesystem values. Import mapping: `"$store": "jsr:@jollytoad/store-deno-kv-fs"` -### [no-op](https://jsr.io/@jollytoad/store-no-op) +### [store-no-op](https://jsr.io/@jollytoad/store-no-op) For the odd occasion when you want to disable storage entirely but not break your app. This implementation does nothing. @@ -155,12 +185,14 @@ your app. This implementation does nothing. ### Bring your own If these don't fulfil your needs then you can implement your own storage module -based on the [interface](./store-common/types.ts), and switch to it. +based on the +[StorageModule interface](https://jsr.io/@jollytoad/store-common/doc/types/~/StorageModule), +and switch to it. See the existing implementations for inspiration... -- [web-storage](./store-web-storage/mod.ts) -- [deno-fs](./store-deno-fs/mod.ts) -- [deno-kv](./store-deno-kv/mod.ts) -- [deno-kv-fs](./store-deno-kv-fs/mod.ts) -- [no-op](./store-no-op/mod.ts) +- [web-storage](https://github.com/jollytoad/deno_storage_modules/blob/main/store-web-storage/mod.ts) +- [deno-fs](https://github.com/jollytoad/deno_storage_modules/blob/main/store-deno-fs/mod.ts) +- [deno-kv](https://github.com/jollytoad/deno_storage_modules/blob/main/store-deno-kv/mod.ts) +- [deno-kv-fs](https://github.com/jollytoad/deno_storage_modules/blob/main/store-deno-kv-fs/mod.ts) +- [no-op](https://github.com/jollytoad/deno_storage_modules/blob/main/store-no-op/mod.ts) diff --git a/store/_from_env.ts b/store/_from_env.ts index 55474c5..0ad7557 100644 --- a/store/_from_env.ts +++ b/store/_from_env.ts @@ -1,6 +1,9 @@ import { getEnv } from "@cross/env"; import type { StorageModule } from "@jollytoad/store-common/types"; +/** + * Import the storage module declared in the `STORAGE_MODULE` environment variable. + */ export function fromEnv(): Promise { const moduleSpecifier = getEnv("STORAGE_MODULE"); if (moduleSpecifier) { diff --git a/store/deno.json b/store/deno.json index f890b58..b9e8a3c 100644 --- a/store/deno.json +++ b/store/deno.json @@ -1,6 +1,6 @@ { "name": "@jollytoad/store", - "version": "0.2.0", + "version": "0.3.0", "exports": { ".": "./mod.ts" } diff --git a/store/mod.ts b/store/mod.ts index d25f6bf..6eba168 100644 --- a/store/mod.ts +++ b/store/mod.ts @@ -22,12 +22,25 @@ export type { DelegatedStore, StorageKey, StorageModule }; let store: Promise | undefined; +/** + * Set the storage module to which all function calls are delegated + * + * @param storageModule may be the store, promise of the store, or undefined to remove any delegate store + */ export function setStore( storageModule?: StorageModule | Promise, ) { store = storageModule ? Promise.resolve(storageModule) : undefined; } +/** + * Get the delegate storage module previously set via `setStore`, + * if one has not been set it will attempt to dynamically import the module declared in + * the `STORAGE_MODULE` environment variable. + * + * @returns the promise of the store to which all operations are delegated + * @throws if no store or env var has been set, or if dynamic import fails + */ export async function getStore(): Promise { if (!store) { store = (await import("./_from_env.ts")).fromEnv(); @@ -35,30 +48,54 @@ export async function getStore(): Promise { return await store; } +/** + * Returns the `url()` of the delegated storage module. + */ export async function url(): Promise { return (await getStore()).url(); } +/** + * Check whether the delegated storage is writable in general, or at or below a particular key. + * There still may be some sub-keys that differ. + */ export async function isWritable(key: StorageKey = []): Promise { return (await getStore()).isWritable(key); } +/** + * Determine whether a value is set for the given key in the delegated storage. + */ export async function hasItem(key: StorageKey): Promise { return (await getStore()).hasItem(key); } +/** + * Get a value for the given key from the delegated storage. + */ export async function getItem(key: StorageKey): Promise { return (await getStore()).getItem(key) as Promise; } +/** + * Set a value for the given key in the delegated storage. + */ export async function setItem(key: StorageKey, value: T): Promise { return (await getStore()).setItem(key, value); } +/** + * Remove the value with the given key from the delegated storage. + */ export async function removeItem(key: StorageKey): Promise { return (await getStore()).removeItem(key); } +/** + * List all items beneath the given key prefix in the delegated storage. + * At present, guaranteed ordering and reverse support is optional, and + * dependent on the abilities of the delegated storage. + */ export async function* listItems( prefix: StorageKey = [], reverse = false, @@ -68,10 +105,17 @@ export async function* listItems( >; } +/** + * Delete item and sub items recursively from the delegated storage and clean up. + */ export async function clearItems(prefix: StorageKey): Promise { return (await getStore()).clearItems(prefix); } +/** + * Close all associated resources in the delegated storage. + * This isn't generally required in most situations, it's main use is within test cases. + */ export async function close(): Promise { (await store)?.close(); }