Хук updating в Dexie.js перехватывает момент изменения
записи в таблице до фактического применения обновления в IndexedDB. Он
используется для контроля, трансформации или блокировки изменений на
уровне бизнес-логики, позволяя централизованно управлять тем, как именно
модифицируются данные при вызове update() или при частичном
обновлении через put().
Хук вызывается в момент, когда Dexie уже определил:
Сигнатура:
table.hook('updating', function (modifications, primaryKey, obj, transaction) {
// логика хука
});
modifications Объект, содержащий только изменяемые поля. Не является полной записью. Именно эти данные будут наложены поверх существующего объекта.
Пример:
{ name: "New name", age: 30 }
Важно учитывать, что это не клон всей записи, а дифф.
primaryKey Первичный ключ обновляемой записи. Используется для идентификации объекта, но не гарантирует наличие полной структуры записи.
obj Текущая версия объекта, находящегося в базе до применения изменений. Используется для сравнения старых и новых значений, вычисления производных полей, логирования или валидации.
transaction Активная транзакция Dexie. Позволяет выполнять дополнительные операции над другими таблицами в рамках того же атомарного контекста.
Хук может управлять итоговыми изменениями через возвращаемое значение.
Если возвращается объект — он заменяет исходный
modifications.
table.hook('updating', (mods, key, obj) => {
return {
...mods,
updatedAt: Date.now()
};
});
Если возвращается undefined, изменения применяются без
модификации.
table.hook('updating', (mods) => {
if (mods.name === undefined) return;
});
Также возможно полное блокирование изменения через выброс исключения:
table.hook('updating', () => {
throw new Error('Обновление запрещено');
});
Хук часто используется для проверки допустимости изменений до записи в IndexedDB.
db.users.hook('updating', (mods, key, obj) => {
if (mods.age !== undefined && mods.age < 0) {
throw new Error('Возраст не может быть отрицательным');
}
});
Логика выполняется синхронно внутри транзакции, что делает проверку атомарной.
Распространённый паттерн — автоматическое обновление метаданных.
db.posts.hook('updating', (mods) => {
return {
...mods,
updatedAt: new Date().toISOString()
};
});
Таким образом исключается необходимость вручную обновлять временные
метки в каждом update().
Некоторые поля могут быть защищены от изменения.
db.users.hook('updating', (mods, key, obj) => {
if ('role' in mods) {
throw new Error('Поле role нельзя изменять');
}
});
Альтернативный подход — принудительное игнорирование:
db.users.hook('updating', (mods) => {
const { role, ...rest } = mods;
return rest;
});
Хук используется для синхронизации производных данных в связанных таблицах или кэшированных полях.
db.orders.hook('updating', (mods, key, obj, tx) => {
if (mods.status && mods.status !== obj.status) {
tx.table('orderLogs').add({
orderId: key,
from: obj.status,
to: mods.status,
changedAt: Date.now()
});
}
});
Здесь транзакция обеспечивает согласованность между таблицами.
Dexie передаёт в modifications только изменённые поля.
Это создаёт важную особенность: отсутствие поля не означает его
неизменность, а означает, что оно не затрагивается.
db.items.hook('updating', (mods, key, obj) => {
// obj содержит полную запись
// mods содержит только изменённые поля
});
Для вычисления итогового состояния требуется объединение:
const nextState = { ...obj, ...mods };
Dexie допускает возврат Promise из хука, что позволяет выполнять асинхронные преобразования или проверки.
db.users.hook('updating', async (mods, key, obj) => {
if (mods.email) {
const exists = await db.users
.where('email')
.equals(mods.email)
.and(u => u.id !== key)
.count();
if (exists > 0) {
throw new Error('Email уже используется');
}
}
return mods;
});
Асинхронность увеличивает гибкость, но сохраняет транзакционную целостность операции.
Изменения, возвращаемые из хука, напрямую влияют на данные, которые
попадут в IndexedDB. Любая дополнительная логика внутри
updating выполняется в критическом пути записи.
Ключевые аспекты:
Dexie предоставляет несколько хуков уровня таблицы:
creating — перед созданием записи;reading — при чтении данных;updating — перед обновлением;deleting — перед удалением.updating отличается тем, что работает с частичным
набором данных и требует объединения с текущим состоянием записи для
полной картины.
Хук позволяет трансформировать схему без миграции базы.
db.users.hook('updating', (mods, key, obj) => {
if (mods.fullName) {
const [firstName, lastName] = mods.fullName.split(' ');
return {
...mods,
firstName,
lastName
};
}
});
Такой подход часто применяется при постепенной эволюции схемы хранения данных.
primaryKey невозможно через updating-хук для
большинства схем.При наличии нескольких updating-хуков важно учитывать
порядок трансформаций:
db.table.hook('updating', (mods) => {
return { ...mods, step1: true };
});
db.table.hook('updating', (mods) => {
return { ...mods, step2: true };
});
Результат будет зависеть от последовательного применения трансформаций, что требует аккуратного проектирования логики, чтобы избежать конфликтов изменений.
Хук updating часто становится центральной точкой
инкапсуляции бизнес-правил на уровне локальной базы данных. Он
позволяет:
В сложных приложениях он становится аналогом слоя доменных правил, работающего непосредственно поверх IndexedDB, обеспечивая контроль изменений на уровне хранения данных.