Механизм версионирования в Dexie.js строится вокруг декларативного
описания структуры базы данных. Каждый вызов db.version(n)
фиксирует конкретную версию схемы, а метод stores() задаёт
состояние таблиц и индексов для этой версии. Вся эволюция базы данных
выражается цепочкой версий, где каждая последующая описывает изменения
относительно предыдущей.
db.version(1).stores({
users: "++id,name,email",
orders: "++id,userId,date,status"
});
Каждая версия фиксирует:
После объявления версии Dexie автоматически управляет миграциями при открытии базы.
Метод stores() использует компактную DSL-нотацию, где
каждая таблица описывается строкой:
"++id,name,email"
++field — автоинкрементный первичный ключfield — обычный индекс&field — уникальный индекс*field — multiEntry индекс (для массивов)[fieldA+fieldB] — составной индексПервичный ключ задаётся первым параметром и определяет фундаментальную структуру таблицы.
db.version(1).stores({
users: "++id,username"
});
Изменение первичного ключа в новой версии фактически означает пересоздание таблицы, так как IndexedDB не поддерживает прямую модификацию primary key.
Пример изменения:
db.version(2).stores({
users: "username,email"
});
Такое изменение требует миграции через удаление старой структуры или создание новой таблицы.
Версионирование используется для эволюции индексов без изменения логики приложения.
db.version(2).stores({
users: "++id,name,email,age"
});
Поле age становится индексируемым, что позволяет
использовать:
db.users.where("age").above(18)
db.version(3).stores({
users: "++id,&email,name"
});
Символ & гарантирует уникальность значений.
При нарушении уникальности вставка записи вызовет ошибку
ConstraintError.
Составные индексы позволяют ускорять запросы по нескольким полям одновременно.
db.version(4).stores({
orders: "++id,[userId+date],status"
});
Такой индекс оптимизирует запросы вида:
db.orders
.where("[userId+date]")
.between([1, "2024-01-01"], [1, "2024-12-31"]);
Используются для индексации массивов:
db.version(5).stores({
posts: "++id,*tags,title"
});
Если tags = ["js", "indexeddb"], то каждая метка попадёт
в индекс отдельно.
Запрос:
db.posts.where("tags").equals("js")
Dexie.js не предоставляет прямого удаления индекса — изменение выполняется через переопределение схемы.
db.version(6).stores({
users: "++id,name"
});
Если ранее существовал индекс email, он будет удалён при
миграции.
Важно учитывать:
Добавление таблицы — одна из самых безопасных операций.
db.version(7).stores({
users: "++id,name,email",
logs: "++id,level,date"
});
Таблица logs создаётся без влияния на существующие
данные.
Удаление выполняется через исключение таблицы из схемы новой версии:
db.version(8).stores({
users: "++id,name,email"
});
Таблица logs будет удалена при миграции.
Особенность:
Версии должны идти строго по возрастанию:
db.version(1)
db.version(2)
db.version(3)
Dexie применяет миграции последовательно при открытии базы.
Пропуск версии (например, переход с 1 на 3 без 2) требует наличия промежуточного определения, иначе миграция будет некорректной.
При вызове:
db.open()
Dexie выполняет следующие шаги:
version()onupgradeneededstores()Несмотря на гибкость, существует ряд ограничений:
Часто используется стратегия «добавление вместо изменения»:
db.version(9).stores({
users: "++id,name,email,createdAt",
users_v2: "++id,name,email,createdAt,role"
});
Это позволяет:
Миграции часто сопровождаются upgrade-логикой:
db.version(10).stores({
users: "++id,name,email,fullName"
}).upgrade(tx => {
return tx.table("users").toCollection().modify(user => {
user.fullName = user.name;
});
});
Здесь stores() задаёт структуру, а upgrade
отвечает за преобразование данных.
Изменение схемы через stores() влияет на:
Добавление лишних индексов увеличивает стоимость записи, но ускоряет чтение.
Схема Dexie.js фактически становится частью архитектурного контракта данных:
Каждая версия представляет собой снимок состояния базы, а
stores() — его формальное описание.