В Dexie.js индексы являются ключевым механизмом ускоренного доступа к данным в IndexedDB. Они определяют, по каким полям можно эффективно выполнять выборки, сортировку и фильтрацию без полного сканирования таблицы. В отличие от обычного перебора записей, индексированные запросы работают поверх внутренней B-tree структуры IndexedDB и обеспечивают предсказуемую производительность даже при больших объёмах данных.
Dexie.js расширяет базовую модель IndexedDB, добавляя декларативное описание схемы и удобный API для работы с индексами. Среди наиболее важных возможностей — уникальные индексы и мультиэнтри (multiEntry) индексы, которые позволяют моделировать как строгие ограничения целостности, так и работу с массивными полями.
Схема базы данных в Dexie.js задаётся через строку описания таблицы, где перечисляются индексы:
const db = new Dexie("AppDatabase");
db.version(1).stores({
users: "++id, email, username, age"
});
В этом примере:
++id — автоинкрементный первичный ключemail, username, age —
обычные индексыИндексы позволяют выполнять запросы вида:
db.users.where("email").equals("test@mail.com").toArray();
Однако базовая схема не накладывает ограничений уникальности и не поддерживает сложные структуры данных. Для этого используются расширенные типы индексов.
Уникальный индекс гарантирует, что значение в указанном поле не будет
повторяться в пределах таблицы. В Dexie.js это задаётся символом
&.
db.version(1).stores({
users: "++id, &email, username"
});
Поле email теперь становится строго уникальным:
await db.users.add({
email: "user@mail.com",
username: "user1"
});
// Ошибка: email уже существует
await db.users.add({
email: "user@mail.com",
username: "user2"
});
Без уникального индекса разработчик вынужден проверять существование значения вручную:
const exists = await db.users.where("email").equals(email).first();
if (!exists) {
await db.users.add({ email, username });
}
Этот подход:
Уникальный индекс решает проблему на уровне движка базы данных.
Dexie.js поддерживает уникальность на основе комбинации полей:
db.version(1).stores({
users: "++id, &[email+tenantId], username, tenantId"
});
Здесь:
email + tenantId должна быть уникальнойПример данных:
await db.users.add({ email: "a@mail.com", tenantId: 1 });
await db.users.add({ email: "a@mail.com", tenantId: 2 }); // допустимо
await db.users.add({ email: "a@mail.com", tenantId: 1 }); // ошибка
Составной уникальный индекс хранится как единое значение ключа, где поля сериализуются в порядке объявления. Это позволяет IndexedDB строить единый B-tree по составному ключу без дополнительных структур.
Мультиэнтри индекс предназначен для индексации массивов. Он позволяет каждому элементу массива стать отдельной индексной записью.
Объявляется символом *.
db.version(1).stores({
posts: "++id, title, *tags"
});
Если поле содержит массив:
{
id: 1,
title: "Dexie guide",
tags: ["js", "indexeddb", "dexie"]
}
то Dexie создаёт три индексные записи:
Таким образом, один объект становится доступен по нескольким ключам индекса.
await db.posts.where("tags").equals("dexie").toArray();
Результат включает все посты, где массив tags содержит
"dexie".
Можно комбинировать multiEntry индексы с другими условиями:
db.posts
.where("tags")
.equals("js")
.and(post => post.title.includes("guide"))
.toArray();
Важно учитывать, что часть условий выполняется уже после выборки из индекса.
MultiEntry индекс:
Это влияет на:
Если поле не массив:
{
tags: "js"
}
multiEntry индекс будет вести себя как обычный индекс с одним значением.
Массив объектов не индексируется рекурсивно:
{
tags: [{ name: "js" }, { name: "db" }]
}
Индекс не извлечёт name автоматически. Только
примитивные значения массива участвуют в индексации.
Dexie.js не поддерживает уникальные multiEntry индексы, поскольку массив создаёт множество значений на один объект, что противоречит модели уникальности.
users: "username"
users: "&email"
posts: "*tags"
Dexie.js позволяет комбинировать составные индексы с уникальностью:
db.version(1).stores({
items: "++id, &[category+name], *tags"
});
Здесь:
[category+name] — уникальный составной индексtags — multiEntry индексОграничения:
При обновлении массива Dexie:
await db.posts.update(1, {
tags: ["new", "updated"]
});
Это важно учитывать при:
При изменении уникального поля:
await db.users.update(1, {
email: "new@mail.com"
});
Dexie проверяет:
Dexie.js опирается на IndexedDB, где:
Это означает, что:
Dexie выбирает индекс автоматически при запросах:
db.posts.where("tags").equals("js")
Если индекс multiEntry:
Если индекс уникальный:
Если индекс отсутствует: