Объявление схемы через Dexie

Схема в Dexie.js определяет структуру IndexedDB-базы: набор таблиц (object stores), их ключи, индексы и правила эволюции данных. Именно через схему задаётся контракт хранения, который сохраняется между версиями приложения и обновлениями структуры базы.

Dexie.js использует декларативный подход: схема описывается строкой или массивом строк внутри версии базы данных, после чего библиотека синхронизирует её с реальным состоянием IndexedDB.


Версионная модель и жизненный цикл схемы

Каждое изменение структуры базы фиксируется через инкремент версии:

  • каждая версия (version(n)) описывает состояние схемы на момент релиза;
  • переход на новую версию запускает миграцию;
  • Dexie.js автоматически сравнивает предыдущую и текущую схему и применяет изменения.

Принцип работы:

  1. создаётся экземпляр базы;
  2. задаётся версия;
  3. объявляется схема таблиц;
  4. при изменении версии выполняется upgrade.

Ключевая особенность: схема не изменяется «на месте», а всегда привязана к версии.


Базовое объявление схемы

Схема задаётся через метод stores:

const db = new Dexie("AppDatabase");

db.version(1).stores({
  users: "++id, name, email, age",
  posts: "++id, title, userId, createdAt"
});

Структура:

  • users, posts — имена таблиц (object stores);
  • строка после двоеточия — описание ключей и индексов.

Первичный ключ и его спецификация

Первичный ключ задаётся первыми символами в строке схемы.

Основные варианты:

Автоинкрементный ключ

++id
  • ++ означает автоувеличение;
  • id — имя поля.

Внешний ключ без автоинкремента

id

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

Композитный ключ

[id+type]

Используется для составных уникальных идентификаторов.

Пример:

orders: "[userId+orderId], userId, orderId, createdAt"

Индексы и их объявление

Dexie.js поддерживает несколько типов индексов:

Обычные индексы

name, email, age

Каждое поле после первичного ключа становится индексируемым.


Уникальные индексы

&email

Оператор & накладывает уникальность:

users: "++id, &email, name"

Это гарантирует отсутствие повторяющихся значений email.


Мультииндексы (multiEntry)

*tags

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

posts: "++id, title, *tags"

Если tags = ["js", "indexeddb"], создаются отдельные записи в индексе для каждого элемента массива.


Составные индексы

userId, createdAt

или более сложный вариант:

[userId+createdAt]

Разница:

  • через запятую — отдельные индексы;
  • через [] — единый композитный индекс.

Полная грамматика схемы

Схема Dexie.js представляет собой строку с синтаксисом:

[primaryKey], index1, index2, ..., indexN

Специальные модификаторы:

  • ++ — автоинкремент;
  • & — уникальный индекс;
  • * — multiEntry индекс;
  • [a+b] — композитный ключ.

Пример комбинированной схемы:

db.version(1).stores({
  products: "++id, &sku, name, category, *tags, [category+name]"
});

Объявление нескольких таблиц

В одной версии можно определить множество stores:

db.version(1).stores({
  users: "++id, &email, name",
  orders: "++id, userId, createdAt",
  orderItems: "++id, orderId, productId"
});

Каждая таблица имеет независимую схему, но разделяет версионный контекст базы.


Эволюция схемы между версиями

Изменение структуры выполняется через новую версию:

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

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

При переходе:

  • добавляются новые индексы;
  • удаляются неописанные индексы;
  • структура синхронизируется с IndexedDB.

Важно: удаление индекса требует его исключения из новой схемы.


Миграции и upgrade-хук

При изменении структуры данных используется upgrade:

db.version(2).stores({
  users: "++id, name, email, phone"
}).upgrade(tx => {
  return tx.table("users").toCollection().modify(user => {
    user.phone = "";
  });
});

Характеристики:

  • выполняется после обновления схемы;
  • работает в транзакции;
  • позволяет трансформировать данные под новую структуру.

Переименование индексов и таблиц

IndexedDB не поддерживает прямое переименование, поэтому Dexie.js требует ручной миграции:

db.version(2).stores({
  customers: "++id, fullName"
}).upgrade(async tx => {
  const oldUsers = tx.table("users");
  const newCustomers = tx.table("customers");

  await oldUsers.each(user => {
    newCustomers.add({
      id: user.id,
      fullName: user.name
    });
  });
});

Удаление таблиц и индексов

Удаление осуществляется через исключение из схемы новой версии:

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

Если ранее существовал индекс email, он будет удалён автоматически.

Для удаления всей таблицы:

  • убрать её из stores;
  • выполнить upgrade при новой версии.

Ограничения схемы IndexedDB в Dexie.js

Схема наследует ограничения IndexedDB:

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

Dexie.js компенсирует это удобной декларацией и API миграций.


Практические паттерны проектирования схемы

Разделение сущностей

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

users
posts
comments

Индексация под запросы

Индексы создаются не по структуре данных, а по паттернам доступа:

  • частые фильтры;
  • сортировки;
  • связи между таблицами.

Минимизация композитных ключей

Композитные ключи используются только при необходимости строгой уникальности.


Версионирование как основа устойчивости схемы

Схема Dexie.js не является статичной структурой. Она представляет последовательность состояний:

  • версия 1 — начальная структура;
  • версия 2 — расширение индексов;
  • версия 3 — миграция данных;
  • версия N — финальная актуальная модель.

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