VersionError и конфликты версий

В основе работы Dexie.js лежит система версий базы данных, унаследованная от IndexedDB. Каждое изменение структуры хранилища — добавление таблиц, индексов, изменение схемы — требует увеличения версии базы. Версия становится центральной точкой синхронизации между кодом приложения и фактической структурой данных в браузере.

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


Сущность VersionError в Dexie.js

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

Типичный сценарий:

  • приложение ожидает версию 3
  • в браузере уже существует версия 5
  • или наоборот, код пытается открыть более старую версию

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


Основные причины возникновения VersionError

Несоответствие версии в коде и в хранилище

Самая распространённая ситуация:

const db = new Dexie("appDatabase");

db.version(1).stores({
  users: "++id,name"
});

db.version(2).stores({
  users: "++id,name,email"
});

Если в браузере уже существует база версии 3 или выше, попытка открыть её с кодом, где максимальная версия равна 2, может вызвать VersionError.

Причина заключается в том, что Dexie ожидает последовательного определения всех версий вплоть до текущей.


Пропуск версий при обновлении приложения

Ошибка часто появляется при неправильной миграции:

  • была версия 1
  • затем сразу определена версия 3
  • версия 2 отсутствует
db.version(1).stores({ users: "++id,name" });

db.version(3).stores({ users: "++id,name,email,age" });

Dexie требует, чтобы каждая промежуточная версия была явно описана, даже если она не содержит изменений схемы.

Правильный подход:

db.version(1).stores({ users: "++id,name" });

db.version(2).stores({ users: "++id,name,email" });

db.version(3).stores({ users: "++id,name,email,age" });

Конфликт параллельных вкладок

IndexedDB работает в контексте браузера, где несколько вкладок могут одновременно открывать одну и ту же базу данных. Если одна вкладка инициирует upgrade, а другая удерживает соединение, возникает конфликт блокировок.

Dexie может выбросить ошибку версии при попытке обновления структуры, если:

  • одна вкладка уже использует старую схему
  • другая инициирует upgrade до новой версии
  • происходит рассинхронизация состояния

Изменение схемы без увеличения версии

Критическая ошибка проектирования:

db.version(1).stores({
  users: "++id,name"
});

// попытка изменить структуру без версии
db.users.mapToClass(User);

Любое изменение структуры store или индексов требует увеличения версии. Игнорирование этого правила приводит к несоответствию метаданных и внутреннего состояния IndexedDB, что может проявиться как VersionError или более ранние сбои при открытии базы.


Как Dexie обрабатывает версии внутри

При вызове new Dexie(name) и db.open() происходит последовательность:

  1. чтение текущей версии базы из IndexedDB
  2. сравнение с объявленными версиями в коде
  3. построение цепочки миграций
  4. выполнение upgrade-коллбеков
  5. фиксация новой версии

Если текущая версия базы выше, чем последняя объявленная, Dexie останавливает процесс и выбрасывает VersionError, чтобы предотвратить потенциальную потерю данных.


Сценарии конфликтов версий в реальных приложениях

Частичный деплой новой версии приложения

Если пользователь уже открыл приложение с новой версией, а затем возвращается к старой (например, через кеш или rollback CI/CD), возникает несоответствие версий.

Старая версия кода не знает о новых структурах таблиц и индексов, но IndexedDB уже их содержит.


Очистка схемы без удаления базы

Иногда разработчики изменяют структуру кардинально, но не увеличивают версию или не пересоздают базу.

Например:

  • удаление таблицы
  • изменение ключевого индекса
  • переименование store

Если старая база остаётся в браузере, Dexie фиксирует конфликт между фактической схемой и объявленной, что может проявиться как VersionError при открытии.


Ошибки в build-системах

В SPA-приложениях часто встречается ситуация:

  • часть кода обновилась
  • service worker остался старым
  • IndexedDB уже обновлена новым кодом

При повторной загрузке старого bundle возникает конфликт версий.


Стратегии предотвращения VersionError

Линейная схема версий

Каждая миграция должна быть строго последовательной:

  • версия 1 → базовая схема
  • версия 2 → добавление полей
  • версия 3 → изменение индексов

Отсутствие промежуточных версий недопустимо.


Использование upgrade для миграций

Dexie позволяет явно управлять переходами между версиями:

db.version(2).upgrade(tx => {
  return tx.table("users").toCollection().modify(user => {
    user.email = "";
  });
});

Такой подход снижает вероятность несогласованности данных при изменении схемы.


Очистка базы при критических изменениях

Если структура меняется радикально, иногда используется стратегия полной очистки:

Dexie.delete("appDatabase");

После этого база создаётся заново с новой схемой. Этот подход допустим только при отсутствии критически важных пользовательских данных.


Защита от параллельных конфликтов

Для минимизации проблем с вкладками применяются:

  • onversionchange
  • закрытие старых соединений
  • принудительное обновление страницы
db.on("versionchange", () => {
  db.close();
  location.reload();
});

Это предотвращает ситуацию, когда старая вкладка блокирует upgrade.


Глубокая причина VersionError в архитектуре IndexedDB

IndexedDB требует строгого контроля схемы по следующим причинам:

  • отсутствует централизованный сервер миграций
  • каждая вкладка работает автономно
  • схема хранится внутри самого браузера
  • изменения структуры блокируют доступ к базе

Dexie лишь оборачивает эти ограничения, но не устраняет их.

VersionError — это не сбой библиотеки, а механизм защиты целостности данных.


Особенности поведения при увеличении версии

При вызове db.version(n) происходит регистрация snapshot схемы. Dexie не “обновляет” старые версии автоматически. Это означает:

  • каждая версия хранит собственное описание stores
  • только последняя версия определяет итоговую структуру
  • миграции выполняются строго по цепочке

Любое нарушение этой цепочки приводит к ошибке открытия базы.


Типовые ошибки разработчиков

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

db.version(2).stores({ users: "++id,name" });
db.version(2).stores({ users: "++id,name,email" });

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


Условные версии

Попытки динамически определять версии:

if (featureEnabled) {
  db.version(3).stores(...);
}

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


Изменение индексов без увеличения версии

Добавление или удаление индексов всегда требует новой версии:

  • изменение primary key
  • добавление compound index
  • удаление уникальных ограничений

Игнорирование этого правила почти гарантированно приводит к VersionError.


Поведение при восстановлении после ошибки

После возникновения VersionError база не открывается до тех пор, пока:

  • версия в коде не станет ≥ версии базы
  • или база не будет удалена вручную

Это делает ошибку “липкой”: один раз возникнув, она сохраняется между перезагрузками приложения.


Связь VersionError с миграционной стратегией

Правильная стратегия работы с версиями строится вокруг нескольких принципов:

  • неизменяемость старых версий
  • добавление только новых версий
  • отсутствие пропусков
  • детерминированная последовательность миграций

Dexie не поддерживает ветвление версий или параллельные схемы.


Роль кэша браузера и Service Worker

В PWA-архитектурах VersionError часто усиливается:

  • старый Service Worker возвращает устаревший bundle
  • новый IndexedDB уже создан
  • код пытается работать со старой схемой

Это создаёт рассинхронизацию трёх уровней:

  • код
  • кэш
  • база данных

Диагностика конфликтов версий

При анализе проблемы обычно проверяются:

  • текущая версия базы через DevTools
  • объявленные версии в коде
  • порядок миграций
  • наличие service worker
  • параллельные вкладки

Dexie предоставляет события blocked и versionchange, которые помогают выявить источник конфликта, но не устраняют его автоматически.


Поведение при некорректной последовательности миграций

Если цепочка версий нарушена, Dexie прекращает процесс открытия базы на этапе open() и не выполняет частичные миграции. Это предотвращает:

  • повреждение данных
  • частичное обновление схемы
  • неконсистентные индексы

Такое поведение делает VersionError защитным механизмом, а не просто исключением выполнения.