v5 to v6 Migration

We are pleased to announce Keyv v6 with major enhancements and some breaking changes. This guide will help you understand how to migrate from v5 to v6. For most users, the transition will be straightforward.

Important: With the release of v6, Keyv v5 will move to maintenance mode. This means v5 will only receive security fixes and minor maintenance updates. We encourage all users to migrate to v6 to take advantage of the latest features and improvements. You can view the v5 branch in the the mono repo.

Keyv v6 Versioning

Starting with v6, all Keyv packages and adapters will use unified versioning. This means every package in the Keyv ecosystem will share the same version number and be released together.

What this means for you:

  • All @keyv/* packages will have the same version (e.g., [email protected], @keyv/[email protected], @keyv/[email protected])
  • When you upgrade Keyv, you can upgrade all adapters to the same version with confidence that they are compatible
  • No more wondering which adapter version works with which Keyv version

Example of unified versions:

keyv: 6.0.0
    @keyv/redis: 6.0.0
    @keyv/sqlite: 6.0.0
    @keyv/postgres: 6.0.0
    @keyv/serialize: 6.0.0
    @keyv/compress-gzip: 6.0.0
    

This approach is used by many popular projects:

  • Vitest - All packages in the Vitest monorepo share the same version
  • Babel - All @babel/* packages are versioned together
  • Jest - All Jest packages use unified versioning
  • Angular - All @angular/* packages share the same version
  • Vue - Vue and its companion packages are versioned together

Unified versioning simplifies dependency management and ensures compatibility across the entire Keyv ecosystem.

Table of Contents

Roadmap & Progress

Task Status
Finalize namespace handling in storage adapters NOT STARTED
Remove opts property NOT STARTED
Integrate Hookified library NOT STARTED
Update deleteMany return type NOT STARTED
Update setMany signature and return type NOT STARTED
Add getRaw and getManyRaw methods NOT STARTED
Refactor iterator implementation NOT STARTED
Implement KeyvGenericStore NOT STARTED
Add serialization adapters NOT STARTED
Add encryption adapters NOT STARTED
Add compression interface standardization NOT STARTED
Browser compatibility NOT STARTED
Documentation updates NOT STARTED
Storage adapter updates NOT STARTED
Release v6.0.0 NOT STARTED

Quick Migration Guide

For most users, migrating from v5 to v6 involves a few key changes:

  1. Update property access - Replace keyv.opts.* with direct property access (keyv.namespace instead of keyv.opts.namespace)

  2. Update serialization - Replace serialize/deserialize options with the new serialization adapter:

    // v5
        const keyv = new Keyv({ serialize: JSON.stringify, deserialize: JSON.parse });
        
        // v6
        import KeyvSerialize from '@keyv/serialize';
        const keyv = new Keyv({ serialization: new KeyvSerialize() });
        
  3. Update raw value access - Replace get(key, { raw: true }) with getRaw(key) and getMany(keys, { raw: true }) with getManyRaw(keys)

  4. Handle new return types - deleteMany and setMany now return boolean[] instead of a single boolean

For detailed information on each change, see the sections below.


Breaking Changes

Namespace Overhaul

We have finalized the transition (started in v5) to move all namespace handling to the storage adapters themselves. When you set the namespace on Keyv, it passes it directly to the storage adapter.

What changed:

  • useKeyPrefix property has been removed
  • keyPrefix property has been removed
  • Key prefixing is no longer done at the Keyv layer

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv({
      namespace: 'myapp',
      useKeyPrefix: true,
      keyPrefix: 'prefix:'
    });
    

v6 (after):

import Keyv from 'keyv';
    
    const keyv = new Keyv({ namespace: 'myapp' });
    // Namespace is handled directly by the storage adapter
    

For legacy storage adapters or Map-compatible stores, we have added KeyvGenericStore which handles advanced features without overloading the main Keyv codebase. See Generic Storage Adapter for more details.


opts Property Removed

In Keyv v5, we began removing opts as a passed-around value. In v6, opts is no longer passed around or provided as a property. All properties are now directly part of the Keyv class.

v5 (before):

const keyv = new Keyv();
    console.log(keyv.opts.namespace);
    

v6 (after):

const keyv = new Keyv();
    console.log(keyv.namespace);
    

Serialization Replaces stringify and parse

The stringify and parse options have been replaced with the new serialization property that accepts a serialization adapter.

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv({
      serialize: JSON.stringify,
      deserialize: JSON.parse
    });
    

v6 (after):

import Keyv from 'keyv';
    import KeyvSerialize from '@keyv/serialize';
    
    const keyv = new Keyv({ serialization: new KeyvSerialize() });
    

See Serialization Adapters for more details.


Hookified for Events and Hooks

We have moved to the standard Hookified library instead of our custom implementation. This introduces some changes to hook function calls and naming conventions.

Available Hooks:

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    
    // Available hooks
    keyv.onHook('preSet', async (key, value, ttl) => {
      console.log(`Setting ${key}`);
    });
    
    keyv.onHook('postSet', async (key, value, ttl) => {
      console.log(`Set ${key}`);
    });
    
    keyv.onHook('preGet', async (key) => {
      console.log(`Getting ${key}`);
    });
    
    keyv.onHook('postGet', async (key, value) => {
      console.log(`Got ${key}: ${value}`);
    });
    
    keyv.onHook('preGetMany', async (keys) => {
      console.log(`Getting keys: ${keys}`);
    });
    
    keyv.onHook('postGetMany', async (keys, values) => {
      console.log(`Got values for keys`);
    });
    
    keyv.onHook('preDelete', async (key) => {
      console.log(`Deleting ${key}`);
    });
    
    keyv.onHook('postDelete', async (key, deleted) => {
      console.log(`Deleted ${key}: ${deleted}`);
    });
    
    keyv.onHook('preClear', async () => {
      console.log('Clearing store');
    });
    
    keyv.onHook('postClear', async () => {
      console.log('Store cleared');
    });
    

Events: Events work the same as before, but now use Hookified internally:

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    
    keyv.on('error', (err) => {
      console.error('Keyv error:', err);
    });
    
    keyv.on('disconnect', () => {
      console.log('Disconnected');
    });
    

Important: By default, throwOnErrors is set to true. If there are no listeners for the error event, it will throw an Error. You can disable this:

const keyv = new Keyv({ throwOnErrors: false });
    

To have errors thrown on hooks, set throwOnHooks to true:

const keyv = new Keyv({ throwOnHooks: true });
    

For more about Hookified, visit https://hookified.org.


deleteMany Returns boolean[]

deleteMany now returns a boolean[] indicating the success of each deletion. The StorageAdapter interface has been updated accordingly.

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key1', 'value1');
    await keyv.set('key2', 'value2');
    
    const result = await keyv.deleteMany(['key1', 'key2']);
    // result was: boolean (true if all deleted)
    

v6 (after):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key1', 'value1');
    await keyv.set('key2', 'value2');
    
    const results = await keyv.deleteMany(['key1', 'key2']);
    // results: [true, true] - boolean for each key
    console.log(results[0]); // true - key1 was deleted
    console.log(results[1]); // true - key2 was deleted
    

setMany Uses KeyvEntry[] and Returns boolean[]

setMany now uses the KeyvEntry[] type for input and returns boolean[] to indicate success for each entry.

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.setMany([
      { key: 'key1', value: 'value1' },
      { key: 'key2', value: 'value2' }
    ]);
    

v6 (after):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    
    // Using KeyvEntry[] type
    const entries = [
      { key: 'key1', value: 'value1', ttl: 1000 },
      { key: 'key2', value: 'value2' }
    ];
    
    const results = await keyv.setMany(entries);
    // results: [true, true] - boolean for each entry
    console.log(results[0]); // true - key1 was set
    console.log(results[1]); // true - key2 was set
    

get and getMany No Longer Support Raw

Since Keyv v5.5, we added getRaw and getManyRaw methods. In v6, raw support has been removed from get and getMany.

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key', 'value', 1000);
    
    const value = await keyv.get('key');
    const rawValue = await keyv.get('key', { raw: true });
    // rawValue: { value: 'value', expires: 1234567890 }
    

v6 (after):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key', 'value', 1000);
    
    // Use get for the value
    const value = await keyv.get('key');
    // value: 'value'
    
    // Use getRaw for the raw format
    const rawValue = await keyv.getRaw('key');
    // rawValue: { value: 'value', expires: 1234567890 }
    
    // For multiple keys
    const values = await keyv.getMany(['key1', 'key2']);
    const rawValues = await keyv.getManyRaw(['key1', 'key2']);
    

Iterator Changes

The iterator no longer requires an argument. We have also improved iteration handling and added an isIterable() function to check if iteration is supported.

v5 (before):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key1', 'value1');
    await keyv.set('key2', 'value2');
    
    for await (const [key, value] of keyv.iterator(keyv.namespace)) {
      console.log(key, value);
    }
    

v6 (after):

import Keyv from 'keyv';
    
    const keyv = new Keyv();
    await keyv.set('key1', 'value1');
    await keyv.set('key2', 'value2');
    
    // Check if iteration is supported
    if (keyv.isIterable()) {
      // No argument required
      for await (const [key, value] of keyv.iterator()) {
        console.log(key, value);
      }
    }
    

Removed .ttlSupport from Storage Adapters

The ttlSupport property has been removed from storage adapters. Keyv now automatically detects the storage adapter type and uses KeyvGenericStore for adapters that don't natively support TTL.

v5 (before):

class MyAdapter {
      ttlSupport = false;
      // ...
    }
    

v6 (after):

// No need to specify ttlSupport
    // Keyv automatically handles TTL through KeyvGenericStore if needed
    class MyAdapter {
      // ...
    }
    

Returns undefined Instead of null

Keyv now consistently returns undefined instead of null for missing values. Previously, some storage adapters returned null, which was passed through. Now we normalize to undefined.

v5 (before):

const value = await keyv.get('nonexistent');
    // value could be null or undefined depending on the adapter
    

v6 (after):

const value = await keyv.get('nonexistent');
    // value is always undefined
    

Compression Adapter Interface Change

Compression adapters now use a simplified interface:

interface KeyvCompression {
      compress: (value: string) => string;
      decompress: (value: string) => T;
    }
    

Important: Compression requires serialization to be enabled (default) or values must be strings.

v6 usage:

import Keyv from 'keyv';
    import KeyvGzip from '@keyv/compress-gzip';
    
    const compression = new KeyvGzip();
    const keyv = new Keyv({ compression });
    
    // Serialization is enabled by default (@keyv/serialize)
    await keyv.set('key', { foo: 'bar' });
    

Note: Encryption and compression require string values. If your values are not strings, you must use serialization.


New Features

Browser Compatibility

Keyv v6 is now fully compatible with browser environments. You can use Keyv in frontend applications with appropriate storage adapters.

import Keyv from 'keyv';
    
    // Works in the browser
    const keyv = new Keyv({ store: new Map() });
    

Serialization Adapters

The default serialization module is @keyv/serialize, which uses the built-in JSON module. The property has been simplified to just serialization.

import Keyv from 'keyv';
    import KeyvSerialize from '@keyv/serialize';
    
    const keyv = new Keyv({ serialization: new KeyvSerialize() });
    
    // You can also set it via the property
    keyv.serialization = new KeyvSerialize();
    

Available Serialization Adapters:

Package Description
@keyv/serialize Default - Based on built-in JSON
@keyv/serialize-superjson Supports BigInt, Date, Map, Set, and more
@keyv/serialize-msgpackr High-performance binary serialization

Disabling Serialization

For in-memory storage or when serialization isn't needed (and you're not using encryption/compression):

import Keyv from 'keyv';
    
    const keyv = new Keyv({ store: new Map(), serialization: false });
    
    // Or set via property
    keyv.serialization = undefined;
    

Note: If you want to use encryption or compression, you must have serialization enabled.

Custom Serialization

Create your own serialization adapter using the KeyvSerialization interface:

interface KeyvSerialization {
      parse: (value: string) => T;
      stringify: (value: unknown) => string;
    }
    
import Keyv from 'keyv';
    
    const customSerializer = {
      stringify: (value) => JSON.stringify(value),
      parse: (value) => JSON.parse(value)
    };
    
    const keyv = new Keyv({ serialization: customSerializer });
    

Encryption Adapters

You can now add encryption to values with the following adapters:

Package Description
@keyv/encryption Node.js built-in encryption (configurable)
@keyv/encryption-browser Browser-compatible encryption using crypto-js
@keyv/encryption-argon Modern, high-performance encryption for Node.js
import Keyv from 'keyv';
    import KeyvEncryption from '@keyv/encryption';
    
    const encryption = new KeyvEncryption({ key: 'your_secret_key_here' });
    const keyv = new Keyv({ encryption });
    
    // Or set via property
    keyv.encryption = encryption;
    
    await keyv.set('sensitive', { password: 'secret' });
    

Custom Encryption

Create your own encryption adapter using the KeyvEncryption interface:

interface KeyvEncryption {
      encrypt: (value: string) => string;
      decrypt: (value: string) => T;
    }
    

Note: Encryption requires string values. Use serialization (enabled by default) if your values are not strings.


New Identification Functions

Keyv v6 provides new functions to help identify adapters and capabilities.

isKeyv

Detects if an object is a Keyv instance by checking for Keyv-specific methods and properties:

import Keyv, { isKeyv } from 'keyv';
    
    const keyv = new Keyv();
    
    isKeyv(new Map());
    // { keyv: false, get: true, set: true, delete: true, clear: true, has: true,
    //   getMany: false, setMany: false, deleteMany: false, hasMany: false,
    //   disconnect: false, getRaw: false, getManyRaw: false, hooks: false,
    //   stats: false, iterator: false }
    
    isKeyv(keyv);
    // { keyv: true, get: true, set: true, delete: true, clear: true, has: true,
    //   getMany: true, setMany: true, deleteMany: true, hasMany: true,
    //   disconnect: true, getRaw: true, getManyRaw: true, hooks: true,
    //   stats: true, iterator: false }
    

The keyv property is true when the object has all core Keyv methods (get, set, delete, clear) plus hooks and stats properties.

isKeyvStorage

Detects if an object is a Keyv storage adapter by checking for required adapter methods:

import { isKeyvStorage } from 'keyv';
    
    isKeyvStorage(new Map());
    // { keyvStorage: false, get: true, set: true, delete: true, clear: true,
    //   has: true, getMany: false, setMany: false, deleteMany: false,
    //   hasMany: false, disconnect: false, iterator: false, namespace: false }
    
    isKeyvStorage(redisAdapter);
    // { keyvStorage: true, get: true, set: true, delete: true, clear: true,
    //   has: true, getMany: true, setMany: true, deleteMany: true,
    //   hasMany: true, disconnect: true, iterator: true, namespace: true }
    

The keyvStorage property is true when the object has all core storage adapter methods (get, set, delete, clear).

Additional Capability Checks

Keyv v6 also provides functions for checking compression, serialization, and encryption adapters:

import { isKeyvCompression, isKeyvSerialization, isKeyvEncryption } from 'keyv';
    
    isKeyvCompression(gzipAdapter);
    // { keyvCompression: true, compress: true, decompress: true }
    
    isKeyvSerialization(customSerializer);
    // { keyvSerialization: true, stringify: true, parse: true }
    
    isKeyvEncryption(aesAdapter);
    // { keyvEncryption: true, encrypt: true, decrypt: true }
    

Generic Storage Adapter

Keyv v6 includes KeyvGenericStore, a wrapper class for storage types that don't conform to v6 storage adapter requirements (such as Map-compatible or legacy adapters).

Features:

  • Handles namespacing using key prefixing
  • Extends the adapter with v6 functions: getMany, setMany, getRaw, getManyRaw
  • Attempts iteration using various strategies
  • Adds TTL support and handles expiration
import Keyv from 'keyv';
    
    // Map-compatible stores are automatically wrapped
    const keyv = new Keyv({ store: new Map() });
    
    // Check if your adapter will use KeyvGenericStore
    const capabilities = keyv.getStoreCapabilities(yourStore);
    if (capabilities.mapCompatible && !capabilities.adapter) {
      console.log('This store will use KeyvGenericStore');
    }
    

Getting Help

If you encounter issues during migration:

  1. Check the Keyv documentation
  2. Search existing issues
  3. Open a new issue with details about your migration problem