Skip to main content
Version: Next

Advanced Features

This guide covers advanced features of the CommandKit KV store, including expiration, transactions, and best practices for complex use cases.

Expiration Support

The KV store supports automatic expiration of data, similar to Redis. This is useful for caching, sessions, and temporary data.

Setting Data with Expiration

// Set data with expiration (1 hour)
kv.setex('session:123', { userId: 123, token: 'abc123' }, 60 * 60 * 1000);

// Set data with 5 minutes expiration
kv.setex('temp:data', { cached: true, timestamp: Date.now() }, 5 * 60 * 1000);

// Set data with 1 day expiration
kv.setex('daily:stats', { count: 100, date: new Date() }, 24 * 60 * 60 * 1000);

// Use dot notation with expiration
kv.setex('user:123.temp_settings', { theme: 'light' }, 30 * 60 * 1000);

Setting Expiration for Existing Keys

// First set the data
kv.set('user:123', { name: 'John', age: 30 });

// Then set expiration (30 minutes)
if (kv.expire('user:123', 30 * 60 * 1000)) {
console.log('Expiration set successfully');
} else {
console.log('Key does not exist');
}

Checking Time to Live

const ttl = kv.ttl('user:123');

if (ttl > 0) {
console.log(`Key expires in ${ttl}ms (${Math.floor(ttl / 1000)}s)`);
} else if (ttl === -2) {
console.log('Key has no expiration');
} else {
console.log('Key does not exist or has expired');
}

Automatic Expiration Handling

// Set data with expiration
kv.setex('temp:key', 'value', 1000); // 1 second

// Immediately check - should exist
console.log(kv.has('temp:key')); // true

// Wait for expiration
setTimeout(() => {
console.log(kv.has('temp:key')); // false
console.log(kv.get('temp:key')); // undefined
}, 1100);

Transactions

Transactions allow you to execute multiple operations atomically. If any operation fails, all changes are rolled back.

Basic Transaction Usage

// Execute multiple operations atomically
kv.transaction(() => {
kv.set('user:123', { name: 'John', balance: 100 });
kv.set('user:456', { name: 'Jane', balance: 200 });

// If any operation fails, all changes are rolled back
});

Async Transactions

// Async transactions are also supported
await kv.transaction(async () => {
kv.set('user:123', { name: 'John', balance: 100 });

// You can perform async operations
await someAsyncOperation();

kv.set('user:456', { name: 'Jane', balance: 200 });

// If any operation fails, all changes are rolled back
});

Transaction with Error Handling

try {
kv.transaction(() => {
kv.set('user:123', { name: 'John', balance: 100 });

// Simulate an error
throw new Error('Database error');

// This won't execute due to the error
kv.set('user:456', { name: 'Jane', balance: 200 });
});
} catch (error) {
console.error('Transaction failed:', error);
// All changes were automatically rolled back
}

Complex Transaction Example

// Transfer money between users
async function transferMoney(
fromUserId: string,
toUserId: string,
amount: number,
) {
return kv.transaction(async () => {
// Get current balances
const fromUser = kv.get(`user:${fromUserId}`) || { balance: 0 };
const toUser = kv.get(`user:${toUserId}`) || { balance: 0 };

// Validate transfer
if (fromUser.balance < amount) {
throw new Error('Insufficient funds');
}

// Update balances
fromUser.balance -= amount;
toUser.balance += amount;

// Save updated users
kv.set(`user:${fromUserId}`, fromUser);
kv.set(`user:${toUserId}`, toUser);

// Log the transaction
kv.set(`transaction:${Date.now()}`, {
from: fromUserId,
to: toUserId,
amount,
timestamp: new Date(),
});

return { success: true, newBalance: fromUser.balance };
});
}

// Usage
try {
const result = await transferMoney('123', '456', 50);
console.log('Transfer successful:', result);
} catch (error) {
console.error('Transfer failed:', error);
}

Resource Management

The KV store implements disposable patterns for automatic resource cleanup.

Using with Statement

// Automatic cleanup with using statement
{
using kv = openKV('data.db');
kv.set('key', 'value');
// kv is automatically closed when the block ends
}

Using Async Disposal

// Async disposal (fake promise wrapper)
await using kv = openKV('data.db');
kv.set('key', 'value');
// kv is automatically closed when the block ends
Async Disposal

The async using statement is just a fake promise wrapper around the synchronous using statement. The disposal is still synchronous.

Manual Resource Management

const kv = openKV('data.db');

try {
kv.set('key', 'value');
// ... other operations
} finally {
kv.close(); // Always close the connection
}

Performance Optimization

Using Write-Ahead Logging (WAL)

// Enable WAL for better performance (enabled by default)
const kv = new KV('data.db', { enableWAL: true });

Batch Operations

// Use transactions for batch operations
kv.transaction(() => {
for (let i = 0; i < 1000; i++) {
kv.set(`item:${i}`, { id: i, data: `data-${i}` });
}
});

Efficient Iteration

// Use iterator for large datasets
for (const [key, value] of kv) {
if (key.startsWith('user:')) {
// Process user data
console.log(`Processing ${key}:`, value);
}
}

// Avoid loading all data into memory
// Instead of: const all = kv.all(); // Loads everything
// Use: for (const [key, value] of kv) { ... } // Processes one at a time

Error Handling Best Practices

Comprehensive Error Handling

class KVStoreManager {
private kv: KV;

constructor(dbPath: string) {
this.kv = new KV(dbPath);
}

async setUser(userId: string, userData: any): Promise<boolean> {
try {
if (!this.kv.isOpen()) {
throw new Error('Database is not open');
}

this.kv.set(`user:${userId}`, userData);
return true;
} catch (error) {
console.error(`Failed to set user ${userId}:`, error);
return false;
}
}

async getUser(userId: string): Promise<any | null> {
try {
if (!this.kv.isOpen()) {
throw new Error('Database is not open');
}

const user = this.kv.get(`user:${userId}`);
return user || null;
} catch (error) {
console.error(`Failed to get user ${userId}:`, error);
return null;
}
}

close(): void {
try {
this.kv.close();
} catch (error) {
console.error('Failed to close database:', error);
}
}
}

Validation and Type Safety

interface User {
id: string;
name: string;
email: string;
settings: {
theme: 'light' | 'dark';
notifications: boolean;
};
}

function validateUser(data: any): data is User {
return (
typeof data === 'object' &&
typeof data.id === 'string' &&
typeof data.name === 'string' &&
typeof data.email === 'string' &&
typeof data.settings === 'object' &&
(data.settings.theme === 'light' || data.settings.theme === 'dark') &&
typeof data.settings.notifications === 'boolean'
);
}

// Usage
const userData = kv.get('user:123');
if (userData && validateUser(userData)) {
console.log('Valid user:', userData.name);
} else {
console.log('Invalid or missing user data');
}

Monitoring and Debugging

Database Health Checks

function checkDatabaseHealth(kv: KV): boolean {
try {
// Check if database is open
if (!kv.isOpen()) {
console.error('Database is not open');
return false;
}

// Test basic operations
const testKey = '__health_check__';
kv.set(testKey, { timestamp: Date.now() });
const result = kv.get(testKey);
kv.delete(testKey);

if (!result) {
console.error('Database read/write test failed');
return false;
}

console.log('Database health check passed');
return true;
} catch (error) {
console.error('Database health check failed:', error);
return false;
}
}

Performance Monitoring

function measureOperationTime<T>(operation: () => T): {
result: T;
duration: number;
} {
const start = performance.now();
const result = operation();
const duration = performance.now() - start;

return { result, duration };
}

// Usage
const { result, duration } = measureOperationTime(() => {
return kv.get('user:123');
});

console.log(`Operation took ${duration.toFixed(2)}ms`);

Best Practices Summary

  1. Use Transactions for Related Operations: Group related operations in transactions for consistency
  2. Handle Expiration Gracefully: Always check TTL and handle expired data appropriately
  3. Validate Data: Implement proper validation for complex data structures
  4. Monitor Performance: Use health checks and performance monitoring
  5. Clean Up Resources: Always close database connections properly
  6. Use Meaningful Keys: Choose descriptive key names and use consistent patterns
  7. Handle Errors: Implement comprehensive error handling for all operations
  8. Optimize for Your Use Case: Use appropriate features like WAL and batch operations

Next Steps