CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-keyv

Simple key-value storage with support for multiple backends

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

events-hooks.mddocs/

Events and Hooks

Event system for connection monitoring and lifecycle management, plus hooks system for custom functionality on CRUD operations.

Capabilities

Event System

Built-in EventEmitter functionality for monitoring Keyv lifecycle and storage adapter events.

class Keyv extends EventManager {
  /** Emit event with arguments */
  emit(event: string, ...arguments_: any[]): void;
  
  /** Add event listener */
  on(event: string, listener: (...arguments_: any[]) => void): this;
  
  /** Add one-time event listener */
  once(event: string, listener: (...arguments_: any[]) => void): void;
  
  /** Remove event listener */
  off(event: string, listener: (...arguments_: any[]) => void): void;
  
  /** Remove all listeners for event */
  removeAllListeners(event?: string): void;
}

Built-in Events

Keyv emits several lifecycle events automatically.

// Standard events emitted by Keyv
// 'error' - Storage adapter errors
// 'clear' - When clear() method is called  
// 'disconnect' - When disconnect() method is called

Usage Examples:

import Keyv from "keyv";
import KeyvRedis from "@keyv/redis";

const keyv = new Keyv(new KeyvRedis('redis://localhost:6379'));

// Handle connection errors
keyv.on('error', (error) => {
  console.error('Keyv error:', error);
  // Don't let storage errors crash your app
});

// Monitor clear operations
keyv.on('clear', () => {
  console.log('Cache was cleared');
});

// Monitor disconnections
keyv.on('disconnect', () => {
  console.log('Storage adapter disconnected');
});

// One-time listener
keyv.once('error', (error) => {
  console.log('First error occurred:', error);
});

// Remove specific listener
const errorHandler = (error) => console.log(error);
keyv.on('error', errorHandler);
keyv.off('error', errorHandler); // Remove this specific handler

// Remove all error listeners
keyv.removeAllListeners('error');

// Disable error events entirely
const quietKeyv = new Keyv({ emitErrors: false });
// No error events will be emitted

Error Handling Configuration

Control how errors are handled and emitted.

interface KeyvOptions {
  /** Whether to emit error events (default: true) */
  emitErrors?: boolean;
  /** Whether to throw errors in addition to emitting them (default: false) */
  throwOnErrors?: boolean;
}

class Keyv {
  /** Get current throwOnErrors setting */
  get throwOnErrors(): boolean;
  
  /** Set throwOnErrors behavior */
  set throwOnErrors(value: boolean);
}

Usage Examples:

// Silent operation (no error events)
const silentKeyv = new Keyv({ emitErrors: false });

// Throw errors instead of just emitting
const throwingKeyv = new Keyv({ throwOnErrors: true });

try {
  await throwingKeyv.set('key', 'value'); // May throw on storage error
} catch (error) {
  console.error('Operation failed:', error);
}

// Change behavior at runtime
const keyv = new Keyv();
keyv.throwOnErrors = true; // Now operations will throw on error

Hooks System

Pre/post operation hooks for custom functionality on all CRUD operations.

enum KeyvHooks {
  PRE_SET = 'preSet',
  POST_SET = 'postSet',
  PRE_GET = 'preGet',
  POST_GET = 'postGet',
  PRE_GET_MANY = 'preGetMany',
  POST_GET_MANY = 'postGetMany',
  PRE_GET_RAW = 'preGetRaw',
  POST_GET_RAW = 'postGetRaw',
  PRE_GET_MANY_RAW = 'preGetManyRaw',
  POST_GET_MANY_RAW = 'postGetManyRaw',
  PRE_DELETE = 'preDelete',
  POST_DELETE = 'postDelete'
}

class HooksManager {
  /** Add hook handler for specific event */
  addHandler(event: string, handler: (...arguments_: any[]) => void): void;
  
  /** Remove specific hook handler */
  removeHandler(event: string, handler: (...arguments_: any[]) => void): void;
  
  /** Trigger all handlers for event */
  trigger(event: string, data: any): void;
  
  /** Get read-only access to handlers */
  get handlers(): Map<string, Function[]>;
}

class Keyv {
  /** Access to hooks manager */
  hooks: HooksManager;
}

Usage Examples:

import Keyv, { KeyvHooks } from "keyv";

const keyv = new Keyv();

// Pre-set hook for validation
keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
  console.log(`Setting key ${data.key} to ${data.value}`);
  
  // Validation example
  if (data.key.includes('admin') && !isAuthorized()) {
    throw new Error('Unauthorized admin key access');
  }
  
  // Modify data before storing
  if (typeof data.value === 'string') {
    data.value = data.value.toLowerCase();
  }
});

// Post-set hook for logging
keyv.hooks.addHandler(KeyvHooks.POST_SET, (data) => {
  console.log(`Successfully set key ${data.key}`);
  
  // Analytics tracking
  analytics.track('cache_set', {
    key: data.key,
    ttl: data.ttl,
    timestamp: Date.now()
  });
});

// Pre-get hook for access logging
keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data) => {
  console.log(`Accessing key ${data.key}`);
  
  // Rate limiting check
  if (rateLimiter.isExceeded(data.key)) {
    throw new Error('Rate limit exceeded');
  }
});

// Post-get hook for cache hit/miss tracking
keyv.hooks.addHandler(KeyvHooks.POST_GET, (data) => {
  const hit = data.value !== undefined;
  console.log(`Key ${data.key}: ${hit ? 'HIT' : 'MISS'}`);
  
  metrics.increment(hit ? 'cache.hit' : 'cache.miss');
});

// Pre-delete hook for audit logging
keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data) => {
  // data.key can be string or string[] for deleteMany
  const keys = Array.isArray(data.key) ? data.key : [data.key];
  auditLog.log('DELETE_ATTEMPT', { keys, user: getCurrentUser() });
});

// Batch operation hooks
keyv.hooks.addHandler(KeyvHooks.PRE_GET_MANY, (data) => {
  console.log(`Batch get for ${data.keys.length} keys`);
});

keyv.hooks.addHandler(KeyvHooks.POST_GET_MANY, (data) => {
  const hits = data.filter(item => item !== undefined).length;
  console.log(`Batch get: ${hits}/${data.length} hits`);
});

// Raw data access hooks
keyv.hooks.addHandler(KeyvHooks.PRE_GET_RAW, (data) => {
  console.log(`Raw access for key ${data.key}`);
});

keyv.hooks.addHandler(KeyvHooks.POST_GET_RAW, (data) => {
  if (data.value && data.value.expires) {
    const timeLeft = data.value.expires - Date.now();
    console.log(`Key ${data.key} expires in ${timeLeft}ms`);
  }
});

Advanced Hook Patterns

Complex hook implementations for common use cases.

Cache Warming Hook:

const keyv = new Keyv();

// Pre-get hook that implements cache warming
keyv.hooks.addHandler(KeyvHooks.POST_GET, async (data) => {
  // If cache miss, try to warm the cache
  if (data.value === undefined && isWarmable(data.key)) {
    console.log(`Cache miss for ${data.key}, attempting to warm`);
    
    try {
      const freshValue = await fetchFreshData(data.key);
      await keyv.set(data.key, freshValue, 300000); // 5 minute TTL
      console.log(`Warmed cache for ${data.key}`);
    } catch (error) {
      console.error(`Failed to warm cache for ${data.key}:`, error);
    }
  }
});

function isWarmable(key: string): boolean {
  return key.startsWith('user:') || key.startsWith('product:');
}

async function fetchFreshData(key: string) {
  // Implement data fetching logic
  if (key.startsWith('user:')) {
    const userId = key.split(':')[1];
    return await database.getUserById(userId);
  }
  // ... other data sources
}

Encryption Hook:

import crypto from 'crypto';

const keyv = new Keyv();
const secretKey = 'your-secret-key';

// Encrypt data before storing
keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
  if (data.key.startsWith('secure:')) {
    const cipher = crypto.createCipher('aes-256-cbc', secretKey);
    let encrypted = cipher.update(JSON.stringify(data.value), 'utf8', 'hex');
    encrypted += cipher.final('hex');
    data.value = encrypted;
  }
});

// Decrypt data after retrieving
keyv.hooks.addHandler(KeyvHooks.POST_GET, (data) => {
  if (data.key.startsWith('secure:') && data.value) {
    const decipher = crypto.createDecipher('aes-256-cbc', secretKey);
    let decrypted = decipher.update(data.value, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    data.value = JSON.parse(decrypted);
  }
});

Namespace Validation Hook:

const keyv = new Keyv();

// Validate namespace access
keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
  const [namespace] = data.key.split(':');
  
  if (!isAllowedNamespace(namespace)) {
    throw new Error(`Access denied to namespace: ${namespace}`);
  }
});

keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data) => {
  const [namespace] = data.key.split(':');
  
  if (!isAllowedNamespace(namespace)) {
    throw new Error(`Access denied to namespace: ${namespace}`);
  }
});

function isAllowedNamespace(namespace: string): boolean {
  const allowedNamespaces = ['user', 'session', 'cache', 'temp'];
  return allowedNamespaces.includes(namespace);
}

Hook Error Handling

Hook errors are emitted as events and can be handled.

const keyv = new Keyv();

// Handle hook errors
keyv.hooks.on('error', (error) => {
  console.error('Hook error:', error.message);
  // Don't let hook errors break the main operation
});

// Hook that might throw
keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
  if (data.key === 'forbidden') {
    throw new Error('This key is forbidden');
  }
});

try {
  await keyv.set('forbidden', 'value');
} catch (error) {
  // This won't be thrown due to hook error handling
  console.log('This won\'t execute');
}

// Hook errors are emitted instead
keyv.hooks.on('error', (error) => {
  console.log('Hook error caught:', error.message); // "This key is forbidden"
});

docs

batch-operations.md

configuration.md

core-storage.md

events-hooks.md

index.md

raw-data-access.md

storage-adapters.md

tile.json