Метод version().stores()

Механизм версионирования в Dexie.js строится вокруг декларативного описания структуры базы данных. Каждый вызов db.version(n) фиксирует конкретную версию схемы, а метод stores() задаёт состояние таблиц и индексов для этой версии. Вся эволюция базы данных выражается цепочкой версий, где каждая последующая описывает изменения относительно предыдущей.


Общая форма объявления версии

db.version(1).stores({
  users: "++id,name,email",
  orders: "++id,userId,date,status"
});

Каждая версия фиксирует:

  • набор таблиц
  • первичные ключи
  • индексы (простые, составные, уникальные, multiEntry)
  • структуру хранения данных

После объявления версии 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"]);

MultiEntry индексы

Используются для индексации массивов:

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 выполняет следующие шаги:

  1. Проверка текущей версии IndexedDB
  2. Сравнение со схемой version()
  3. Применение onupgradeneeded
  4. Создание/обновление object stores
  5. Применение индексов из stores()

Ограничения метода stores()

Несмотря на гибкость, существует ряд ограничений:

  • нельзя изменить primary key без пересоздания таблицы
  • нельзя частично модифицировать индекс (только полностью заменить схему)
  • нельзя переименовать поле без миграции данных
  • нельзя избежать полной проверки версии при открытии базы

Практика расширения схемы

Часто используется стратегия «добавление вместо изменения»:

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() влияет на:

  • скорость открытия базы
  • время миграции
  • размер индексов
  • эффективность запросов

Добавление лишних индексов увеличивает стоимость записи, но ускоряет чтение.


Типичные ошибки при работе с version().stores()

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

Связь version().stores() с архитектурой приложения

Схема Dexie.js фактически становится частью архитектурного контракта данных:

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

Каждая версия представляет собой снимок состояния базы, а stores() — его формальное описание.