Dexie.js предоставляет мощную систему хуков (hooks), позволяющую вмешиваться в жизненный цикл операций над данными в IndexedDB. Одним из ключевых механизмов является хук удаления (deleting hook), который вызывается перед выполнением операции удаления записи из таблицы. Этот хук используется для контроля, модификации логики удаления, каскадного удаления связанных данных, ведения аудита и реализации сложных бизнес-правил.
Операции с данными в Dexie проходят через несколько этапов:
Хук deleting срабатывает до фактического
удаления записи, но уже внутри транзакционного контекста. Это
означает, что:
Поддерживается поведение:
thisХук регистрируется на уровне таблицы:
db.table('users').hook('deleting', function (primKey, obj, transaction) {
// логика перед удалением
});
Параметры:
primKey — первичный ключ удаляемой записиobj — полная запись, если она доступнаtransaction — текущая транзакция DexieКонтекст this внутри хука указывает на таблицу, из
которой производится удаление.
Объект obj может быть:
undefined, если Dexie не загрузил объект заранее
(например, при delete(key) без предварительного
чтения)Это приводит к важному выводу: нельзя полагаться на наличие
obj без дополнительного запроса. При необходимости
гарантированного доступа к данным используется явное чтение внутри
транзакции:
db.table('users').hook('deleting', async function (primKey, obj, transaction) {
const user = obj || await transaction.table('users').get(primKey);
});
Одной из ключевых возможностей deleting-хука является возможность блокировать операцию удаления.
Для этого используется выброс исключения:
db.table('users').hook('deleting', function (primKey, obj) {
if (obj && obj.role === 'admin') {
throw new Error('Удаление администратора запрещено');
}
});
При выбросе исключения:
Dexie поддерживает асинхронные хуки. Это позволяет выполнять проверки и дополнительные запросы перед удалением:
db.table('orders').hook('deleting', async function (id, order, transaction) {
const payments = await transaction.table('payments')
.where('orderId')
.equals(id)
.toArray();
if (payments.length > 0) {
throw new Error('Нельзя удалить заказ с платежами');
}
});
Асинхронность важна при:
Транзакция, передаваемая в хук, позволяет безопасно выполнять связанные операции без выхода за пределы текущего атомарного контекста.
Пример каскадного удаления:
db.table('users').hook('deleting', async function (userId, user, tx) {
await tx.table('sessions')
.where('userId')
.equals(userId)
.delete();
await tx.table('notifications')
.where('userId')
.equals(userId)
.delete();
});
Такая модель гарантирует:
В IndexedDB отсутствуют встроенные foreign key constraints, поэтому каскады реализуются вручную:
db.table('posts').hook('deleting', async function (postId, post, tx) {
await tx.table('comments')
.where('postId')
.equals(postId)
.delete();
});
Хук позволяет фиксировать историю операций:
db.table('products').hook('deleting', function (id, product, tx) {
return tx.table('audit').add({
type: 'DELETE',
table: 'products',
key: id,
snapshot: product,
timestamp: Date.now()
});
});
db.table('files').hook('deleting', function (id, file) {
if (!file || file.owner !== currentUserId) {
throw new Error('Нет прав на удаление файла');
}
});
db.table('settings').hook('deleting', function (id, setting) {
if (setting && setting.protected) {
throw new Error('Системные настройки нельзя удалять');
}
});
Если зарегистрировано несколько deleting-хуков на одной таблице, они выполняются последовательно в порядке регистрации.
Поведение:
Важно учитывать, что порядок может влиять на результат при модификации связанных данных.
Dexie поддерживает также:
creatingupdatingreadingХук deleting отличается тем, что:
creating и updating
для полной модели контроля данныхНапример, совместное использование:
db.table('logs').hook('deleting', function () {
throw new Error('Логи нельзя удалять');
});
db.table('logs').hook('creating', function (primKey, obj) {
obj.createdAt = Date.now();
});
Несмотря на гибкость, существуют ограничения:
Также важно учитывать, что IndexedDB может отменить транзакцию при любом необработанном исключении, что делает стабильность кода критически важной.
При вызове:
db.table('items').where('type').equals('temp').delete();
хук вызывается для каждой удаляемой записи отдельно. Это означает:
Для оптимизации в таких сценариях используют:
Deleting-хук в Dexie.js фактически формирует слой прикладной бизнес-логики поверх низкоуровневого IndexedDB. Он позволяет эмулировать:
При грамотной архитектуре он становится центральным элементом контроля удаления данных, заменяя отсутствующие в IndexedDB механизмы реляционных баз данных.