Версионирование сохранённых данных

Idb-keyval — это минималистичная обёртка над IndexedDB, которая позволяет работать с ключ-значение в браузере. Одним из важных аспектов управления данными является версионирование — процесс сохранения нескольких состояний данных, чтобы можно было отслеживать изменения, откатываться к предыдущим версиям или синхронизировать обновления между клиентами.

Версионирование данных в Idb-keyval напрямую не встроено, поэтому для его реализации требуется создание собственной схемы хранения и управления версиями.


Хранение версий

Для реализации версионирования необходимо:

  1. Использовать ключи с версионной меткой. Например, для объекта user можно формировать ключи вида:

    import { set, get } from 'idb-keyval';
    
    const key = 'user:v1';
    await set(key, { name: 'Alice', age: 25 });

    Здесь v1 указывает на версию данных. При обновлении данных создаётся новая версия:

    const newKey = 'user:v2';
    await set(newKey, { name: 'Alice', age: 26 });

    Такой подход позволяет хранить все предыдущие версии без удаления старых данных.

  2. Создать отдельный индекс версий, если планируется частый доступ к последней версии. Для этого можно хранить текущую версию в отдельном ключе:

    await set('user:current', 'v2');

    При чтении последней версии:

    const currentVersion = await get('user:current');
    const userData = await get(`user:${currentVersion}`);

Автоматизация версионирования

Для упрощения работы с версиями полезно создавать вспомогательные функции:

import { get, set } from 'idb-keyval';

async function saveVersionedData(baseKey, data) {
    const currentVersion = await get(`${baseKey}:current`) || 'v0';
    const versionNumber = parseInt(currentVersion.slice(1)) + 1;
    const newVersion = `v${versionNumber}`;
    
    await set(`${baseKey}:${newVersion}`, data);
    await set(`${baseKey}:current`, newVersion);
    
    return newVersion;
}

async function getLatestVersion(baseKey) {
    const currentVersion = await get(`${baseKey}:current`);
    if (!currentVersion) return null;
    return await get(`${baseKey}:${currentVersion}`);
}

Эти функции позволяют централизованно управлять версиями, избегая дублирования логики в разных частях приложения.


Ограничение числа версий

Если приложение хранит большое количество версий, важно контролировать их количество, чтобы не переполнять IndexedDB. Это можно реализовать с помощью функции очистки старых версий:

import { get, keys, del } from 'idb-keyval';

async function pruneOldVersions(baseKey, maxVersions = 5) {
    const allKeys = await keys();
    const versionKeys = allKeys
        .filter(k => typeof k === 'string' && k.startsWith(`${baseKey}:v`))
        .sort((a, b) => {
            const aNum = parseInt(a.split(':v')[1]);
            const bNum = parseInt(b.split(':v')[1]);
            return bNum - aNum;
        });

    const keysToDelete = versionKeys.slice(maxVersions);
    for (const key of keysToDelete) {
        await del(key);
    }
}

Такой механизм позволяет хранить только последние N версий, экономя пространство в IndexedDB.


Работа с откатом

Версионирование также позволяет реализовать откат к предыдущим состояниям. Для этого достаточно изменить текущую версию на одну из старых:

async function rollback(baseKey, targetVersion) {
    const exists = await get(`${baseKey}:${targetVersion}`);
    if (!exists) throw new Error('Версия не найдена');
    
    await set(`${baseKey}:current`, targetVersion);
    return exists;
}

Откат эффективен при необходимости восстановления данных после ошибок или некорректного обновления.


Интеграция с структурами данных

Версионирование удобно использовать не только для простых объектов, но и для массивов, списков задач, конфигураций приложений. Важно соблюдать единый формат ключей и хранить текущую версию отдельно, чтобы обеспечить быстрый доступ к актуальному состоянию.

Пример хранения истории изменений списка:

await saveVersionedData('tasks', [
    { id: 1, title: 'Написать статью', done: false },
]);
await saveVersionedData('tasks', [
    { id: 1, title: 'Написать статью', done: true },
]);

Практические советы

  • Именование версий: используйте последовательную нумерацию (v1, v2) или timestamp (v20260324T1015) для точного отслеживания изменений.
  • Индексация текущей версии: хранение отдельного ключа с последней версией ускоряет доступ и упрощает логику.
  • Контроль объёма данных: регулярное удаление старых версий предотвращает рост базы и снижает нагрузку на IndexedDB.
  • Универсальные функции: централизованные функции сохранения, получения и отката делают код более поддерживаемым и читаемым.

Заключение ключевых принципов

Версионирование данных в Idb-keyval строится на трёх базовых элементах:

  1. Хранение версий с уникальными ключами.
  2. Отдельная запись текущей версии для быстрого доступа.
  3. Управление количеством версий для предотвращения избыточного хранения.

Следуя этим принципам, можно реализовать гибкую систему хранения, способную поддерживать историю изменений, откаты и безопасные обновления данных без сложных внешних библиотек.