В основе работы Dexie.js лежит система версий базы данных, унаследованная от IndexedDB. Каждое изменение структуры хранилища — добавление таблиц, индексов, изменение схемы — требует увеличения версии базы. Версия становится центральной точкой синхронизации между кодом приложения и фактической структурой данных в браузере.
Dexie.js интерпретирует версию как последовательность миграций, каждая из которых описывает переход от одной схемы к другой. Любое несоответствие между ожидаемой версией и текущим состоянием базы приводит к критическим ошибкам и может полностью остановить доступ к данным.
VersionError возникает в момент открытия базы данных,
когда Dexie обнаруживает конфликт версий или некорректную
последовательность миграций.
Типичный сценарий:
Dexie не допускает произвольного отката или пропуска версий, поскольку IndexedDB требует строгой линейной эволюции схемы.
Самая распространённая ситуация:
const db = new Dexie("appDatabase");
db.version(1).stores({
users: "++id,name"
});
db.version(2).stores({
users: "++id,name,email"
});
Если в браузере уже существует база версии 3 или выше, попытка
открыть её с кодом, где максимальная версия равна 2, может вызвать
VersionError.
Причина заключается в том, что Dexie ожидает последовательного определения всех версий вплоть до текущей.
Ошибка часто появляется при неправильной миграции:
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 может выбросить ошибку версии при попытке обновления структуры, если:
Критическая ошибка проектирования:
db.version(1).stores({
users: "++id,name"
});
// попытка изменить структуру без версии
db.users.mapToClass(User);
Любое изменение структуры store или индексов требует увеличения
версии. Игнорирование этого правила приводит к несоответствию метаданных
и внутреннего состояния IndexedDB, что может проявиться как
VersionError или более ранние сбои при открытии базы.
При вызове new Dexie(name) и db.open()
происходит последовательность:
Если текущая версия базы выше, чем последняя объявленная, Dexie
останавливает процесс и выбрасывает VersionError, чтобы
предотвратить потенциальную потерю данных.
Если пользователь уже открыл приложение с новой версией, а затем возвращается к старой (например, через кеш или rollback CI/CD), возникает несоответствие версий.
Старая версия кода не знает о новых структурах таблиц и индексов, но IndexedDB уже их содержит.
Иногда разработчики изменяют структуру кардинально, но не увеличивают версию или не пересоздают базу.
Например:
Если старая база остаётся в браузере, Dexie фиксирует конфликт между
фактической схемой и объявленной, что может проявиться как
VersionError при открытии.
В SPA-приложениях часто встречается ситуация:
При повторной загрузке старого bundle возникает конфликт версий.
Каждая миграция должна быть строго последовательной:
Отсутствие промежуточных версий недопустимо.
upgrade для миграцийDexie позволяет явно управлять переходами между версиями:
db.version(2).upgrade(tx => {
return tx.table("users").toCollection().modify(user => {
user.email = "";
});
});
Такой подход снижает вероятность несогласованности данных при изменении схемы.
Если структура меняется радикально, иногда используется стратегия полной очистки:
Dexie.delete("appDatabase");
После этого база создаётся заново с новой схемой. Этот подход допустим только при отсутствии критически важных пользовательских данных.
Для минимизации проблем с вкладками применяются:
onversionchangedb.on("versionchange", () => {
db.close();
location.reload();
});
Это предотвращает ситуацию, когда старая вкладка блокирует upgrade.
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(...);
}
Такая практика приводит к непредсказуемому состоянию базы, поскольку версия становится зависимой от среды исполнения.
Добавление или удаление индексов всегда требует новой версии:
Игнорирование этого правила почти гарантированно приводит к
VersionError.
После возникновения VersionError база не открывается до
тех пор, пока:
Это делает ошибку “липкой”: один раз возникнув, она сохраняется между перезагрузками приложения.
Правильная стратегия работы с версиями строится вокруг нескольких принципов:
Dexie не поддерживает ветвление версий или параллельные схемы.
В PWA-архитектурах VersionError часто усиливается:
Это создаёт рассинхронизацию трёх уровней:
При анализе проблемы обычно проверяются:
Dexie предоставляет события blocked и
versionchange, которые помогают выявить источник конфликта,
но не устраняют его автоматически.
Если цепочка версий нарушена, Dexie прекращает процесс открытия базы
на этапе open() и не выполняет частичные миграции. Это
предотвращает:
Такое поведение делает VersionError защитным механизмом, а не просто исключением выполнения.