KV Store
The Key-Value (KV) Store in Anchor provides a simple key-value storage solution backed by IndexedDB. It's designed for scenarios where you need to store simple key-value pairs with automatic persistence and synchronization.
Overview
The KV Store functionality is provided through the createKVStore
function. It offers an optimistic storage model where operations are performed in-memory first and then synchronized with IndexedDB in the background.
API
createKVStore()
Function
Creates a key-value store function that provides reactive state management synchronized with IndexedDB.
type createKVStore = <T extends Storable>(
name: string,
version = 1,
dbName = `${name}.kv`,
seeds?: KVSeed<T>[]
) => KVFn;
Parameters:
name
- The name of the object store in IndexedDBversion
- The version of the database schemadbName
- The name of the databaseseeds
- An initial set of key-value pairs to seed the database
Returns: A KVFn function that can create and manage reactive key-value states
kv
Default Store
A default KV store instance is provided for convenience.
kv: KVFn;
KVFn
Function
Creates a reactive key-value state that is synchronized with IndexedDB.
type KVFn = <T extends Storable>(key: string, init?: T) => KVState<T>;
Parameters:
key
- The unique key to identify the stored valueinit
- The initial value to use if no existing value is found
Returns: A reactive state object containing the data and status
.leave()
Cleans up the subscription for a reactive key-value state.
type leave = <T extends Storable>(state: KVState<T>) => void;
Parameters:
state
- The state object to unsubscribe
.remove()
Removes a key-value pair from the storage.
type remove = (key: string) => void;
Parameters:
key
- The key to remove
.ready()
A helper to wait for the store operations to complete.
type ready = () => Promise<true>;
Returns: A promise that resolves when all operations are completed
Usage Examples
Basic Usage
import { createKVStore } from '@anchorlib/storage/db';
// Create a custom KV store for application data
const appData = createKVStore<{ name: string; age: number }>('app-data');
// Use the custom store
const user1 = appData('user1', { name: 'John', age: 30 });
const user2 = appData('user2', { name: 'Jane', age: 25 });
// Update values
user1.data.name = 'John Doe';
user2.data.age = 26;
Using the Default Store
import { derive } from '@anchorlib/core';
import { kv } from '@anchorlib/storage/db';
// Use the default KV store
const userSettings = kv('user-settings', { theme: 'light' });
// Update settings
userSettings.data.theme = 'dark';
userSettings.data.notifications = true;
// Listen for changes
const unsubscribe = derive(userSettings, (event) => {
if (event.type === 'set') {
console.log('Settings updated:', event.value);
}
});
// Remove a key
kv.remove('user-settings');
// Clean up
unsubscribe();
Subscribing to Changes
import { derive } from '@anchorlib/core';
import { createKVStore } from '@anchorlib/storage/db';
// Create a custom KV store
const users = createKVStore<{ name: string }>('users');
// Use the store
const user1 = users('user1', { name: 'John' });
// Subscribe to all changes
const unsubscribe = derive(user1, (event) => {
console.log(event); // Logs changes
});
// Perform operations
user1.data.name = 'John Doe';
// Clean up
unsubscribe();
Best Practices
1. Error Handling
Handle errors when performing KV store operations:
import { createKVStore } from '@anchorlib/storage/db';
const users = createKVStore<{ name: string }>('users');
// Use the store
const user1 = users('user1', { name: 'John' });
// Errors are available in the state
if (user1.status === 'error') {
console.error('Failed to save user:', user1.error);
}
2. Waiting for Operations
When you need to ensure operations are completed, use the .ready()
method:
import { createKVStore } from '@anchorlib/storage/db';
const users = createKVStore<{ name: string }>('users');
// Use the store
const user1 = users('user1', { name: 'John' });
const user2 = users('user2', { name: 'Jane' });
// Update values
user1.data.name = 'John Doe';
user2.data.name = 'Jane Smith';
// Wait for all operations to complete
await users.ready();
// Now you can be sure all operations are finished
console.log('All operations completed');
3. Data Types
The KV store supports JSON-serializable data types:
import { createKVStore } from '@anchorlib/storage/db';
const dataStore = createKVStore<any>('data');
// Supported types
const stringVal = dataStore('string', 'hello');
const numberVal = dataStore('number', 42);
const booleanVal = dataStore('boolean', true);
const arrayVal = dataStore('array', [1, 2, 3]);
const objectVal = dataStore('object', { a: 1, b: 'test' });
const nullVal = dataStore('null', null);
const dateVal = dataStore('date', new Date());
// Update values
stringVal.data = 'world';
numberVal.data = 100;
booleanVal.data = false;
arrayVal.data.push(4);
objectVal.data.c = 'new value';
nullVal.data = 'not null anymore';
dateVal.data = new Date();
4. Naming Conventions
Use descriptive names for your KV stores and keys:
// Good
const userPreferences = createKVStore('user-preferences-v1');
const theme = userPreferences('user-123-theme', 'dark');
const lang = userPreferences('user-123-lang', 'en');
// Avoid
const data = createKVStore('data');
const val = data('123', 'value');
5. Resource Management
Clean up subscriptions to prevent memory leaks:
import { createKVStore } from '@anchorlib/storage/db';
const users = createKVStore<{ name: string }>('users');
// Use the store
const user = users('user1', { name: 'John' });
// When done with the state, leave it
users.leave(user);
6. Seeding Data
Use the seeds parameter to initialize your store with default data:
import { createKVStore } from '@anchorlib/storage/db';
const appSettings = createKVStore('app-settings', 1, 'myapp', [
['default-theme', 'light'],
['default-lang', 'en'],
['version', '1.0.0'],
]);
// The store will be initialized with these values
const theme = appSettings('default-theme', 'dark'); // Will use 'light' from seeds
console.log(theme.data); // 'light'
Browser Compatibility
IndexedDB is supported in all modern browsers:
- Chrome 24+
- Firefox 16+
- Safari 10+
- Edge 12+
- Internet Explorer 10+ (with prefixes)
In environments where IndexedDB is not available, operations will fail gracefully with appropriate error messages.
Performance Considerations
Batch Operations: The KV store is optimized for multiple operations. It's more efficient to perform several operations and then wait for completion than to wait after each operation.
Memory Usage: Values are stored in-memory for fast access. Be mindful of storing large objects.
Asynchronous Nature: All IndexedDB operations are asynchronous. Use the promise interface to handle completion properly.
Optimistic Updates: The store uses optimistic updates, meaning operations are reflected immediately in-memory while being processed in the background.