Порядок выполнения хуков

Хуки в Dexie.js образуют последовательный конвейер перехвата операций над таблицами, и порядок их выполнения определяет поведение всей системы реактивной обработки данных. Каждый тип хука — creating, reading, updating, deleting — имеет собственную фазу жизненного цикла операции, а внутри каждой фазы важны правила регистрации, контекста выполнения и вложенности транзакций.

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

  • тип операции (создание, чтение, обновление, удаление)
  • уровень привязки (DB-level или Table-level)
  • порядок регистрации обработчиков
  • контекст транзакции

Внутри одной операции Dexie формирует цепочку обработчиков, которые выполняются последовательно. При этом важно, что каждый хук может модифицировать данные, остановить выполнение или асинхронно продолжить цепочку.

Порядок регистрации и его влияние

Базовое правило: хуки выполняются в том порядке, в котором они были зарегистрированы.

db.table('users').hook('creating', fn1);
db.table('users').hook('creating', fn2);

При добавлении записи fn1 выполнится раньше fn2.

Это правило сохраняется для всех типов хуков и является основой предсказуемости поведения. Однако оно дополняется более сложной структурой, когда используются хуки на уровне базы данных:

db.hook('creating', 'users', fnA);
db.table('users').hook('creating', fnB);

В этом случае порядок становится двухуровневым: сначала DB-level хуки, затем Table-level, при сохранении порядка регистрации внутри каждого уровня.

Иерархия выполнения: DB-level и Table-level

При выполнении операции над таблицей Dexie формирует цепочку:

  1. DB-level хуки
  2. Table-level хуки

Для каждой стадии (creating/updating/deleting) этот порядок сохраняется.

Пример последовательности

При вставке записи:

  • DB-level creating хуки
  • Table-level creating хуки

При чтении:

  • DB-level reading хуки
  • Table-level reading хуки

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

  • DB-level updating хуки
  • Table-level updating хуки

При удалении:

  • DB-level deleting хуки
  • Table-level deleting хуки

Таким образом, DB-level хуки можно рассматривать как глобальные middleware, а Table-level как специализированные обработчики конкретной сущности.

Порядок выполнения creating-хуков

Хуки creating вызываются до фактической записи объекта в IndexedDB. Это позволяет модифицировать данные до их сохранения.

Последовательность выглядит следующим образом:

  1. Запуск транзакции
  2. DB-level creating
  3. Table-level creating
  4. Запись в IndexedDB
  5. Завершение транзакции

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

db.users.hook('creating', (primKey, obj, transaction) => {
  obj.createdAt = Date.now();
});

Если один из хуков возвращает изменённый объект, он становится входом для следующего обработчика. Таким образом, формируется цепочка трансформаций.

Важно, что асинхронные хуки также соблюдают порядок: следующий хук не начнёт выполнение, пока предыдущий не завершит Promise.

Порядок выполнения reading-хуков

reading-хуки отличаются тем, что выполняются после извлечения данных из хранилища, но до возврата результата вызывающему коду.

Последовательность:

  1. Запрос к IndexedDB
  2. Получение сырого объекта
  3. DB-level reading
  4. Table-level reading
  5. Возврат результата

Особенность заключается в том, что reading не влияет на сохранённые данные, а только на представление объекта на выходе.

db.users.hook('reading', obj => {
  obj.fullName = `${obj.firstName} ${obj.lastName}`;
});

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

Порядок выполнения updating-хуков

Хуки updating вызываются перед применением изменений к существующей записи. Они получают не весь объект, а только изменения (delta).

Последовательность:

  1. Загрузка текущей записи (если требуется)
  2. DB-level updating
  3. Table-level updating
  4. Применение изменений
  5. Запись в IndexedDB

Каждый хук может изменить набор полей, которые будут применены.

db.users.hook('updating', (modifications, primKey, obj) => {
  modifications.updatedAt = Date.now();
});

Если хук возвращает новый объект изменений, он полностью заменяет предыдущий набор модификаций.

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

Порядок выполнения deleting-хуков

Хуки deleting выполняются перед удалением записи и позволяют отменить или модифицировать операцию.

Последовательность:

  1. DB-level deleting
  2. Table-level deleting
  3. Удаление записи
  4. Завершение транзакции
db.users.hook('deleting', (primKey, obj, transaction) => {
  if (obj.isProtected) {
    throw new Error('Удаление запрещено');
  }
});

Если любой хук выбрасывает исключение, цепочка прерывается, и удаление не выполняется.

Влияние транзакций на порядок выполнения

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

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

  • если транзакция откатывается, эффекты creating и updating не сохраняются
  • reading хуки не влияют на состояние транзакции
  • асинхронные хуки блокируют завершение транзакции до своего завершения

Внутри транзакции порядок выглядит линейным, даже если внешне операции асинхронны.

Асинхронные хуки и сохранение порядка

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

  • следующий хук не стартует до завершения предыдущего
  • результат предыдущего хука передаётся следующему
db.users.hook('creating', async (primKey, obj) => {
  const settings = await fetchSettings();
  obj.config = settings;
});

Несмотря на асинхронность, цепочка остаётся детерминированной.

Перекрёстное влияние хуков разных типов

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

Для одной операции над записью порядок всегда фиксирован:

  • creating → запись → reading (при чтении) → updatingdeleting

При этом reading может срабатывать многократно, независимо от остальных операций.

Множественные хуки и предсказуемость

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

db.users.hook('creating', fn1);
db.users.hook('creating', fn2);
db.users.hook('creating', fn3);

Фактическая цепочка:

  • fn1 → fn2 → fn3

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

Ошибки и прерывание цепочки

Любой хук может прервать выполнение цепочки:

  • выброс исключения
  • возврат некорректного значения (в зависимости от типа хука)
  • отклонение Promise

При этом дальнейшие хуки не выполняются, а операция отменяется.

Для creating, updating и deleting это приводит к отмене операции, для reading — к возврату ошибки чтения.

Итоговая модель порядка выполнения

Обобщённая модель выглядит как последовательность слоёв:

  1. DB-level хуки (по порядку регистрации)
  2. Table-level хуки (по порядку регистрации)
  3. Фаза IndexedDB операции
  4. Post-processing через reading (если применимо)

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

Такая структура делает Dexie.js предсказуемым в простых сценариях и гибким в сложных сценариях обработки данных, где порядок хуков становится частью бизнес-логики приложения.