Third-Party Storage Adapters
Table of Contents
Community-built storage adapters for Keyv
The Keyv community has built storage adapters for many different backends. These adapters allow you to use Keyv with databases and storage systems beyond the officially supported ones.
Any storage adapter that follows the KeyvStoreAdapter interface will work seamlessly with Keyv.
Available Adapters
| Adapter | Description |
|---|---|
| @resolid/keyv-sqlite | SQLite storage adapter for Keyv |
| keyv-arango | ArangoDB storage adapter for Keyv |
| keyv-azuretable | Azure Table Storage/API adapter for Keyv |
| keyv-browser | Browser storage adapter including localStorage and indexedDB |
| keyv-cloudflare | Storage adapter for Cloudflare Workers KV |
| keyv-dynamodb | DynamoDB storage adapter for Keyv |
| keyv-file | File system storage adapter for Keyv |
| keyv-firestore | Firebase Cloud Firestore adapter for Keyv |
| keyv-lru | LRU storage adapter for Keyv |
| keyv-momento | Momento storage adapter for Keyv |
| keyv-mssql | Microsoft SQL Server adapter for Keyv |
| keyv-null | Null storage adapter for Keyv |
| keyv-upstash | Upstash Redis adapter for Keyv |
| quick-lru | Simple "Least Recently Used" (LRU) cache |
How to Contribute
We love the community and the third-party storage adapters they have built. We welcome contributions of new storage adapters!
Steps to Add Your Adapter
- Build your adapter following the
KeyvStoreAdapterinterface (see below) - Test your adapter using the official @keyv/test-suite to ensure API compliance
- Publish to npm with the
keyvkeyword in yourpackage.json - Submit a PR to the Keyv repository adding your adapter to this list
Creating a Pull Request
Once your adapter is published to npm and tested, submit a pull request to add it to this page:
- Fork the Keyv repository
- Edit the file
packages/website/site/docs/third-party-storage-adapters.md - Add your adapter to the "Available Adapters" table in alphabetical order:
| [your-adapter-name](https://github.com/your-username/your-adapter) | Brief description of your adapter | - Create a pull request with:
- Title:
docs: add [your-adapter-name] to third-party storage adapters - Description: Include a link to your npm package and a brief explanation of what backend your adapter supports
- Title:
We review pull requests regularly and appreciate your contributions to the Keyv ecosystem!
Building a Storage Adapter
To build a storage adapter for Keyv, you need to implement the KeyvStoreAdapter interface. Here's the complete type definition:
type StoredDataNoRaw<Value> = Value | undefined;
type StoredDataRaw<Value> = { value?: Value; expires?: number } | undefined;
type StoredData<Value> = StoredDataNoRaw<Value> | StoredDataRaw<Value>;
type IEventEmitter = {
on(event: string, listener: (...args: any[]) => void): IEventEmitter;
};
type KeyvStoreAdapter = {
opts: any;
namespace?: string | undefined;
// Required methods
get<Value>(key: string): Promise<StoredData<Value> | undefined>;
set(key: string, value: any, ttl?: number): any;
delete(key: string): Promise<boolean>;
clear(): Promise<void>;
// Optional methods for better performance
setMany?(values: Array<{ key: string; value: any; ttl?: number }>): Promise<void>;
has?(key: string): Promise<boolean>;
hasMany?(keys: string[]): Promise<boolean[]>;
getMany?<Value>(keys: string[]): Promise<Array<StoredData<Value | undefined>>>;
disconnect?(): Promise<void>;
deleteMany?(key: string[]): Promise<boolean>;
iterator?<Value>(namespace?: string): AsyncGenerator<Array<string | Awaited<Value> | undefined>, void>;
} & IEventEmitter;
Required Methods
| Method | Description |
|---|---|
get(key) |
Retrieve a value by key. Returns undefined if not found or expired. |
set(key, value, ttl?) |
Store a value with an optional TTL (time-to-live) in milliseconds. |
delete(key) |
Delete a key. Returns true if the key existed. |
clear() |
Delete all keys in the current namespace. |
Optional Methods
| Method | Description |
|---|---|
has(key) |
Check if a key exists. |
hasMany(keys) |
Check if multiple keys exist. |
getMany(keys) |
Retrieve multiple values at once. |
setMany(values) |
Store multiple values at once. |
deleteMany(keys) |
Delete multiple keys at once. |
disconnect() |
Close any open connections. |
iterator(namespace?) |
Async iterator over all keys/values in a namespace. |
Example Implementation
Here's a minimal example of a custom storage adapter using an in-memory Map:
import { EventEmitter } from 'events';
import type { KeyvStoreAdapter, StoredData } from 'keyv';
interface CacheItem {
value: any;
expires?: number;
}
class MyCustomStore extends EventEmitter implements KeyvStoreAdapter {
private store: Map<string, CacheItem>;
public opts: any;
public namespace?: string;
constructor(options: any = {}) {
super();
this.store = new Map();
this.opts = options;
this.namespace = options.namespace;
}
async get<Value>(key: string): Promise<StoredData<Value> | undefined> {
const data = this.store.get(key);
if (!data) {
return undefined;
}
// Check if expired
if (data.expires && Date.now() > data.expires) {
this.store.delete(key);
return undefined;
}
return data as StoredData<Value>;
}
async set(key: string, value: any, ttl?: number): Promise<boolean> {
const data: CacheItem = {
value,
expires: ttl ? Date.now() + ttl : undefined,
};
this.store.set(key, data);
return true;
}
async delete(key: string): Promise<boolean> {
return this.store.delete(key);
}
async clear(): Promise<void> {
this.store.clear();
}
// Optional: Implement batch operations for better performance
async getMany<Value>(keys: string[]): Promise<Array<StoredData<Value | undefined>>> {
const values: Array<StoredData<Value | undefined>> = [];
for (const key of keys) {
values.push(await this.get<Value>(key));
}
return values;
}
async deleteMany(keys: string[]): Promise<boolean> {
for (const key of keys) {
this.store.delete(key);
}
return true;
}
async has(key: string): Promise<boolean> {
const data = this.store.get(key);
if (!data) {
return false;
}
if (data.expires && Date.now() > data.expires) {
this.store.delete(key);
return false;
}
return true;
}
async disconnect(): Promise<void> {
this.store.clear();
}
}
export default MyCustomStore;
Using Your Custom Adapter
import Keyv from 'keyv';
import MyCustomStore from './my-custom-store';
const store = new MyCustomStore({ namespace: 'my-app' });
const keyv = new Keyv({ store });
// Use Keyv as normal
await keyv.set('foo', 'bar');
const value = await keyv.get('foo'); // 'bar'
Testing Your Adapter
Use the official @keyv/test-suite to verify your adapter is API-compliant:
npm install --save-dev vitest keyv @keyv/test-suite
Create a test file:
import { describe } from 'vitest';
import keyvTestSuite from '@keyv/test-suite';
import Keyv from 'keyv';
import MyCustomStore from './my-custom-store';
const store = () => new MyCustomStore();
keyvTestSuite(describe, Keyv, store);
Run with:
npx vitest