Синтаксис описания хранилищ и индексов

Синтаксис описания схемы хранения в Dexie.js основан на декларативной строковой нотации, где структура таблицы задаётся через строку индексов, а также специальные символы для управления первичными ключами, уникальностью, композитными индексами и многозначными полями. Эта модель построена поверх IndexedDB, но существенно упрощает описание структуры данных, устраняя необходимость ручного создания object stores и индексов через низкоуровневые API.

Описание схемы выполняется внутри версии базы данных:

const db = new Dexie('AppDatabase');

db.version(1).stores({
  users: 'id, name, age'
});

Каждое свойство объекта, переданного в stores, соответствует отдельной таблице (object store). Значение — строка схемы, определяющая:

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

Первичный ключ

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

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

users: '++id, name, age'

++id означает:

  • id — первичный ключ
  • значение автоматически увеличивается при добавлении записи

Простой первичный ключ

users: 'id, name, age'

В этом случае id становится первичным ключом, но без автоинкремента.

Пользовательский ключ

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

Индексы

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

users: '++id, name, age, email'

Каждое поле создаёт индекс, позволяющий выполнять быстрые запросы:

db.users.where('age').above(18)

Индексы в Dexie соответствуют индексам IndexedDB, но создаются декларативно.

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

Для задания уникальности используется символ &:

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

&email означает:

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

Это эквивалентно уникальному constraint в SQL-системах.

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

Составные индексы задаются через квадратные скобки:

users: '++id, [name+age]'

Такой индекс позволяет эффективно выполнять запросы по комбинации полей:

db.users.where('[name+age]').equals(['Ivan', 30])

Особенности:

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

Многозначные индексы (multiEntry)

Многозначные индексы обозначаются символом *:

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

Если поле содержит массив:

{
  id: 1,
  title: 'Post',
  tags: ['js', 'indexeddb', 'dexie']
}

то каждый элемент массива становится отдельной записью в индексе.

Это позволяет выполнять запросы вида:

db.posts.where('tags').equals('dexie')

Комбинирование модификаторов

В Dexie допускается комбинирование индикаторов в рамках одного поля:

posts: '++id, &slug, *tags, [type+created]'

Здесь:

  • ++id — автоинкрементный первичный ключ
  • &slug — уникальный индекс
  • *tags — многозначный индекс
  • [type+created] — составной индекс

Правила порядка полей

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

  1. Первым идёт первичный ключ
  2. Далее уникальные индексы
  3. Затем обычные индексы
  4. После — многозначные и составные индексы

Хотя Dexie допускает гибкость, логически первичный ключ всегда извлекается из первого определённого поля или из ++.

Вложенные поля

Dexie поддерживает индексацию вложенных свойств через точечную нотацию:

users: '++id, address.city, address.country'

Это позволяет индексировать структуры:

{
  id: 1,
  address: {
    city: 'Almaty',
    country: 'Kazakhstan'
  }
}

Запрос:

db.users.where('address.city').equals('Almaty')

Ограничения схемы

Строковая схема Dexie имеет ряд ограничений:

  • нельзя описывать типы данных
  • отсутствует поддержка nullable/optional constraints
  • нет явных foreign key связей
  • схема не валидирует структуру объекта при вставке
  • индексы должны соответствовать реальным полям объектов

Dexie работает на уровне индексации, а не строгой схемы данных.

Версионирование схемы

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

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

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

При обновлении версии:

  • старые индексы удаляются и пересоздаются
  • новые индексы добавляются
  • удалённые поля перестают индексироваться

Миграции при изменении схемы

При изменении структуры данных часто требуется преобразование:

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

Миграции выполняются последовательно, в рамках транзакции версии базы.

Синтаксические особенности строк схемы

Строка схемы разбирается Dexie парсером с учётом специальных символов:

  • ++ — автоинкремент
  • & — уникальность
  • * — multiEntry
  • [] — compound index
  • . — вложенные поля
  • , — разделитель индексов
  • пробелы игнорируются

Пример интерпретации:

'++id, &email, *tags, [a+b], profile.city'

Внутреннее соответствие IndexedDB

Каждое объявление трансформируется в object store IndexedDB:

  • store.name → таблица
  • keyPath → первичный ключ
  • indexes → IDBIndex

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

Особенности работы с ключами

Первичный ключ определяет:

  • физическую организацию данных
  • уникальность записей
  • поведение операций put, add, delete

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

Поведение при конфликте схемы

Если новая версия схемы несовместима со старой:

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

Поэтому изменение схемы требует строгого контроля версий.

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

Схема в Dexie проектируется не как таблица SQL, а как набор индексов под конкретные запросы:

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

Такой подход формирует индексно-ориентированную модель хранения, где структура данных вторична по отношению к паттернам доступа