Обновление записей: update()

Метод update() в Dexie.js используется для частичного обновления существующих записей в хранилище IndexedDB без необходимости перезаписывать весь объект целиком. Он работает на уровне одной записи и опирается на ключевой идентификатор объекта или индекс, по которому выполняется поиск.

table.update(key, changes)
  • key — первичный ключ записи или значение индексированного поля, по которому происходит поиск.
  • changes — объект с полями, которые необходимо изменить.

Метод возвращает промис, который резолвится в:

  • 1, если запись была успешно найдена и обновлена;
  • 0, если запись с указанным ключом не найдена.

Базовый принцип работы

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

В отличие от put(), который полностью заменяет объект, update() изменяет только перечисленные свойства, что делает его более эффективным при небольших правках.

Простейший пример использования

const db = new Dexie("MyDatabase");

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

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

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

В данном случае изменяется только поле age, остальные данные записи остаются нетронутыми.

Поведение при отсутствии записи

Если запись с указанным ключом не существует, метод не вызывает ошибку, а возвращает 0.

const result = await db.users.update(999, { age: 30 });
// result === 0

Это поведение позволяет безопасно использовать update() без предварительной проверки существования записи.

Частичное обновление объекта

update() не требует передачи полного объекта. Можно обновить только одно поле:

await db.users.update(1, { name: "Bob" });

Если объект содержит вложенные структуры, обновление происходит только на верхнем уровне.

await db.users.update(1, {
  profile: { city: "Almaty" }
});

В этом случае поле profile будет полностью заменено новым объектом.

Ограничения частичного обновления

Метод работает только с верхнеуровневыми свойствами. Глубокое слияние объектов не выполняется автоматически. Это означает, что:

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

Пример поведения с массивами:

await db.users.update(1, {
  tags: ["admin", "editor"]
});

Старое значение массива полностью заменяется новым.

Использование с индексами

Обновление возможно не только по первичному ключу, но и через индекс, если он уникален.

db.version(1).stores({
  users: "id, email, name"
});

await db.users.update("alice@mail.com", {
  name: "Alice Updated"
});

Здесь обновление происходит по полю email, если оно объявлено как индекс.

Возвращаемое значение и обработка результата

Возвращаемое значение позволяет определить, была ли запись изменена:

const updated = await db.users.update(1, { age: 40 });

if (updated) {
  console.log("Обновление выполнено");
} else {
  console.log("Запись не найдена");
}

Это часто используется в логике условного обновления без предварительного get().

Отличие от modify()

В Dexie.js существует альтернативный метод modify(), который часто используется совместно с where() и курсорами.

Основные различия:

  • update() — работает с одной записью по ключу или уникальному индексу;
  • modify() — может обновлять множество записей через запрос.
await db.users.where("age").below(30).modify({ status: "young" });

update() в этом смысле более точечный и прямолинейный инструмент.

Условные обновления через get + update

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

const user = await db.users.get(1);

if (user && user.age < 30) {
  await db.users.update(1, { status: "young" });
}

Хотя update() сам по себе не поддерживает условия, он хорошо сочетается с предварительной выборкой данных.

Поведение при конфликте типов

Если переданные поля не соответствуют схеме объекта, Dexie.js не выполняет строгую валидацию, так как IndexedDB является схемо-независимым хранилищем. Однако это может привести к:

  • появлению неожиданных типов данных;
  • перезаписи структуры объекта;
  • нарушению логики приложения.

Пример:

await db.users.update(1, { age: "twenty five" });

Значение будет сохранено как строка без преобразования.

Обновление с использованием вычисляемых значений

update() не поддерживает функцию в качестве значения поля. Следующий код не будет работать:

// некорректно
await db.users.update(1, {
  age: age => age + 1
});

Для подобных операций используется modify() или предварительное получение значения:

const user = await db.users.get(1);

await db.users.update(1, {
  age: user.age + 1
});

Поведение при отсутствии изменений

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

Использование в транзакциях

Метод update() может использоваться внутри транзакций для обеспечения атомарности операций:

await db.transaction("rw", db.users, async () => {
  await db.users.update(1, { status: "active" });
  await db.users.update(2, { status: "inactive" });
});

В случае ошибки в транзакции все изменения будут отменены.

Работа с большим количеством обновлений

При массовых операциях update() может использоваться в цикле, однако это менее эффективно, чем bulkPut() или modify():

for (const item of updates) {
  await db.users.update(item.id, { age: item.age });
}

Такой подход приводит к последовательным операциям записи, что может снижать производительность при больших объёмах данных.

Особенности внутреннего поведения

На уровне IndexedDB операция update() фактически реализуется как:

  • поиск записи по ключу;
  • частичное объединение объекта;
  • запись обновлённого значения обратно в хранилище.

Dexie.js упрощает этот процесс, скрывая работу с курсорами и транзакциями, но логика остаётся аналогичной.

Обработка ошибок

Типичные сценарии, приводящие к ошибкам:

  • попытка обновления в несуществующей таблице;
  • использование несуществующего индекса;
  • нарушение структуры транзакции;
  • попытка обновления в readonly-транзакции.

Пример:

await db.transaction("r", db.users, async () => {
  await db.users.update(1, { name: "Test" });
});

В режиме только чтения операция завершится с ошибкой.

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

update() чаще всего применяется в ситуациях, где требуется:

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

Типичный сценарий:

await db.orders.update(orderId, {
  status: "shipped",
  shippedAt: Date.now()
});

Такой подход уменьшает количество операций чтения и снижает нагрузку на IndexedDB.