Skip to content

Custom Adapters

This guide shows how to implement the StorageAdapter interface for custom storage backends.

Adapter Structure

Every adapter must implement these methods:

import type { StorageAdapter } from "@vp-tw/nanostores-storage";
const myAdapter: StorageAdapter = {
// Single-key operations
get(key: string): string | null {
/* ... */
},
set(key: string, value: string): void {
/* ... */
},
remove(key: string): void {
/* ... */
},
// Bulk operations
getAll(): Record<string, string> {
/* ... */
},
setAll(values: Record<string, string>): void {
/* ... */
},
clear(): void {
/* ... */
},
// Change notifications
subscribe(callback: (key: string | null) => void): () => void {
/* ... */
},
};

Step-by-Step Guide

1. Define Your Storage Backend

First, decide how data will be stored:

// Example: Using a Map for in-memory storage
const storage = new Map<string, string>();

2. Implement Single-Key Operations

get(key: string): string | null {
return storage.get(key) ?? null;
},
set(key: string, value: string): void {
storage.set(key, value);
},
remove(key: string): void {
storage.delete(key);
},

3. Implement Bulk Operations

getAll(): Record<string, string> {
return Object.fromEntries(storage);
},
setAll(values: Record<string, string>): void {
storage.clear();
for (const [key, value] of Object.entries(values)) {
storage.set(key, value);
}
},
clear(): void {
storage.clear();
},

4. Implement Change Notifications

const listeners = new Set<(key: string | null) => void>();
subscribe(callback: (key: string | null) => void): () => void {
listeners.add(callback);
return () => {
listeners.delete(callback);
};
},

5. Notify Listeners on Changes

Update your set, remove, setAll, and clear methods:

set(key: string, value: string): void {
storage.set(key, value);
// Notify listeners
for (const callback of listeners) {
callback(key);
}
},
remove(key: string): void {
storage.delete(key);
for (const callback of listeners) {
callback(key);
}
},
setAll(values: Record<string, string>): void {
storage.clear();
for (const [key, value] of Object.entries(values)) {
storage.set(key, value);
}
// null indicates bulk change
for (const callback of listeners) {
callback(null);
}
},
clear(): void {
storage.clear();
for (const callback of listeners) {
callback(null);
}
},

Complete Example

import type { StorageAdapter } from "@vp-tw/nanostores-storage";
export function createCustomAdapter(): StorageAdapter {
const storage = new Map<string, string>();
const listeners = new Set<(key: string | null) => void>();
const notifyListeners = (key: string | null): void => {
for (const callback of listeners) {
callback(key);
}
};
return {
get(key) {
return storage.get(key) ?? null;
},
set(key, value) {
storage.set(key, value);
notifyListeners(key);
},
remove(key) {
storage.delete(key);
notifyListeners(key);
},
getAll() {
return Object.fromEntries(storage);
},
setAll(values) {
storage.clear();
for (const [key, value] of Object.entries(values)) {
storage.set(key, value);
}
notifyListeners(null);
},
clear() {
storage.clear();
notifyListeners(null);
},
subscribe(callback) {
listeners.add(callback);
return () => {
listeners.delete(callback);
};
},
};
}

Using Your Adapter

import { createStorageStore } from "@vp-tw/nanostores-storage";
import { createCustomAdapter } from "./my-adapter";
const myAdapter = createCustomAdapter();
const store = createStorageStore(myAdapter, "my-key", {
defaultValue: "hello",
listen: true,
});

Best Practices

  1. Return null for missing keys — Never return undefined
  2. Handle edge cases — Empty strings are valid values
  3. Be consistentgetAll() should return the same data as multiple get() calls
  4. Clean up listeners — Return a proper unsubscribe function

See Also