Объект Table и цепочка вызовов

Объект Table в Dexie.js представляет собой центральную абстракцию для работы с отдельной таблицей в IndexedDB. Именно через него выполняются операции чтения, записи, обновления и удаления данных, а также формируются запросы с использованием цепочек вызовов. Каждая таблица в базе данных Dexie.js соответствует экземпляру Table, который инкапсулирует доступ к конкретному хранилищу объектов и предоставляет богатый API для построения декларативных запросов.

Экземпляр Table создаётся автоматически при объявлении схемы базы данных через Dexie:

const db = new Dexie("AppDB");

db.version(1).stores({
  users: "++id,name,age",
  orders: "++id,userId,total"
});

const usersTable = db.table("users");

Каждая таблица связана с:

  • именем хранилища в IndexedDB;
  • схемой индексов;
  • контекстом базы данных Dexie.

Основная особенность Table заключается в том, что он не просто предоставляет CRUD-операции, а формирует ленивые запросы, которые выполняются только при их материализации (например, через toArray() или each()).

Базовые операции Table

Добавление данных

db.users.add({ name: "Alice", age: 25 });

Метод add() возвращает Promise, который резолвится с ключом добавленной записи. Если используется автоинкрементный ключ, он генерируется автоматически.

Обновление данных

db.users.update(1, { age: 26 });

Обновление возможно как по первичному ключу, так и через модификацию набора записей с использованием modify():

db.users.where("age").below(18).modify({ status: "minor" });

Удаление данных

db.users.delete(1);

Или массовое удаление через запрос:

db.users.where("age").above(60).delete();

Цепочка вызовов в Table

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

Основная структура цепочки

Цепочка обычно начинается с объекта Table и продолжается через методы:

  • where()
  • orderBy()
  • filter()
  • limit()
  • offset()
  • reverse()

Завершается цепочка терминальными операциями:

  • toArray()
  • first()
  • last()
  • each()
  • count()

Метод where() как точка входа в запрос

Метод where() используется для создания индексированного запроса:

db.users.where("age").above(18).toArray();

Здесь формируется диапазонное условие по индексу age. Dexie оптимизирует такие запросы, используя возможности IndexedDB индексов.

Поддерживаемые операторы where

  • equals()
  • above()
  • below()
  • between()
  • anyOf()
  • noneOf()

Пример сложного диапазона:

db.users.where("age").between(18, 30, true, false);

Флаги включают или исключают границы диапазона.

Сортировка через orderBy()

Метод orderBy() задаёт сортировку по индексу:

db.users.orderBy("name").toArray();

Особенность заключается в том, что orderBy() требует индексированного поля. В противном случае операция становится менее эффективной.

Комбинация с reverse() позволяет инвертировать порядок:

db.users.orderBy("age").reverse().toArray();

Фильтрация через filter()

Метод filter() выполняет пост-обработку уже извлечённых данных:

db.users.toCollection().filter(user => user.age > 18).toArray();

Важно понимать, что filter() работает на уровне JavaScript после извлечения данных из IndexedDB, а не на уровне индексов. Это влияет на производительность при больших объёмах данных.

Ограничение выборки: limit() и offset()

Метод limit() ограничивает количество возвращаемых записей:

db.users.orderBy("age").limit(10).toArray();

Метод offset() пропускает указанное количество элементов:

db.users.orderBy("age").offset(20).limit(10).toArray();

Такая комбинация используется для реализации пагинации.

Переход к Collection API

После вызова where() или orderBy() возвращается объект Collection, который расширяет возможности Table. Именно Collection поддерживает цепочки обработки данных.

Пример:

db.users
  .where("age")
  .above(18)
  .filter(user => user.active)
  .limit(5)
  .toArray();

Каждый этап добавляет новый слой обработки, не выполняя запрос немедленно.

Терминальные методы цепочки

toArray()

Материализует коллекцию в массив:

db.users.where("age").above(18).toArray();

each()

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

db.users.where("age").above(18).each(user => {
  console.log(user.name);
});

Этот метод эффективен при обработке больших наборов данных.

count()

Подсчёт количества записей:

db.users.where("age").above(18).count();

first() и last()

Получение первого или последнего элемента:

db.users.orderBy("age").first();
db.users.orderBy("age").last();

Ленивость цепочек вызовов

Цепочки Dexie.js не выполняют запросы немедленно. Вместо этого формируется описание запроса, которое исполняется только при вызове терминального метода. Это позволяет:

  • оптимизировать выполнение;
  • объединять операции;
  • снижать количество обращений к IndexedDB.

Пример:

const query = db.users.where("age").above(18);

query.limit(5); // ещё нет выполнения

query.toArray(); // выполнение запроса

Комбинирование условий

Dexie.js позволяет комбинировать несколько уровней фильтрации:

db.users
  .where("age")
  .between(18, 60)
  .and(user => user.active)
  .toArray();

Метод and() добавляет дополнительное условие, применяемое после первичного индексного отбора.

Преобразование коллекций

Методы map() и modify() позволяют трансформировать данные:

db.users
  .where("age")
  .above(18)
  .modify(user => {
    user.status = "adult";
  });

В отличие от filter(), modify() изменяет данные в базе, а не только в памяти.

Повторное использование цепочек

Цепочки можно сохранять в переменные и переиспользовать:

const adults = db.users.where("age").above(18);

adults.toArray();
adults.count();

Однако важно учитывать, что каждая терминальная операция выполняет отдельный запрос.

Особенности производительности

Эффективность цепочек зависит от:

  • использования индексов в where() и orderBy();
  • минимизации filter() на больших наборах;
  • ограничения выборки через limit();
  • избегания лишних полных сканов таблицы.

IndexedDB работает наиболее эффективно при использовании индексированных полей, тогда как пост-фильтрация снижает производительность.

Внутреннее представление цепочек

Каждый вызов метода добавляет шаг в цепочку обработки. Dexie строит внутренний план выполнения, который затем интерпретируется движком IndexedDB. Это позволяет разделять:

  • индексные операции (выполняются в базе);
  • клиентские операции (выполняются в JavaScript).

Такое разделение критично для оптимизации сложных запросов.

Композиция запросов

Цепочки могут комбинироваться для построения сложной логики выборки:

db.users
  .where("age")
  .above(18)
  .and(user => user.country === "KZ")
  .reverse()
  .limit(20)
  .toArray();

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