Хуки в Dexie.js образуют последовательный конвейер перехвата операций
над таблицами, и порядок их выполнения определяет поведение всей системы
реактивной обработки данных. Каждый тип хука — creating,
reading, updating, deleting —
имеет собственную фазу жизненного цикла операции, а внутри каждой фазы
важны правила регистрации, контекста выполнения и вложенности
транзакций.
Dexie.js привязывает хуки либо к экземпляру базы данных, либо к конкретной таблице. При этом фактический порядок определяется комбинацией трёх факторов:
Внутри одной операции 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, при сохранении порядка регистрации внутри каждого уровня.
При выполнении операции над таблицей Dexie формирует цепочку:
Для каждой стадии (creating/updating/deleting) этот порядок сохраняется.
При вставке записи:
creating хукиcreating хукиПри чтении:
reading хукиreading хукиПри обновлении:
updating хукиupdating хукиПри удалении:
deleting хукиdeleting хукиТаким образом, DB-level хуки можно рассматривать как глобальные middleware, а Table-level как специализированные обработчики конкретной сущности.
Хуки creating вызываются до фактической записи объекта в
IndexedDB. Это позволяет модифицировать данные до их сохранения.
Последовательность выглядит следующим образом:
creatingcreatingКаждый хук получает объект, который может быть изменён или заменён.
db.users.hook('creating', (primKey, obj, transaction) => {
obj.createdAt = Date.now();
});
Если один из хуков возвращает изменённый объект, он становится входом для следующего обработчика. Таким образом, формируется цепочка трансформаций.
Важно, что асинхронные хуки также соблюдают порядок: следующий хук не начнёт выполнение, пока предыдущий не завершит Promise.
reading-хуки отличаются тем, что выполняются после
извлечения данных из хранилища, но до возврата результата вызывающему
коду.
Последовательность:
readingreadingОсобенность заключается в том, что reading не влияет на
сохранённые данные, а только на представление объекта на выходе.
db.users.hook('reading', obj => {
obj.fullName = `${obj.firstName} ${obj.lastName}`;
});
При этом порядок регистрации играет критическую роль, так как каждый
последующий reading получает уже модифицированный
объект.
Хуки updating вызываются перед применением изменений к
существующей записи. Они получают не весь объект, а только изменения
(delta).
Последовательность:
updatingupdatingКаждый хук может изменить набор полей, которые будут применены.
db.users.hook('updating', (modifications, primKey, obj) => {
modifications.updatedAt = Date.now();
});
Если хук возвращает новый объект изменений, он полностью заменяет предыдущий набор модификаций.
Порядок выполнения особенно важен, если несколько хуков изменяют одни и те же поля. Последний хук в цепочке фактически определяет итоговое состояние изменений.
Хуки deleting выполняются перед удалением записи и
позволяют отменить или модифицировать операцию.
Последовательность:
deletingdeletingdb.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 (при чтении) →
updating → deletingПри этом reading может срабатывать многократно,
независимо от остальных операций.
Когда зарегистрировано множество хуков одного типа, система превращается в конвейер преобразований. Итоговое значение зависит от последовательности регистрации:
db.users.hook('creating', fn1);
db.users.hook('creating', fn2);
db.users.hook('creating', fn3);
Фактическая цепочка:
Любое изменение порядка регистрации приводит к изменению итогового результата, что делает порядок критически важным элементом архитектуры.
Любой хук может прервать выполнение цепочки:
При этом дальнейшие хуки не выполняются, а операция отменяется.
Для creating, updating и
deleting это приводит к отмене операции, для
reading — к возврату ошибки чтения.
Обобщённая модель выглядит как последовательность слоёв:
reading (если применимо)Каждый слой строго детерминирован, но внутри слоя порядок определяется временем регистрации и асинхронной природой выполнения.
Такая структура делает Dexie.js предсказуемым в простых сценариях и гибким в сложных сценариях обработки данных, где порядок хуков становится частью бизнес-логики приложения.