Открытие соединения и обработка события ready

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

Создание экземпляра базы данных в Dexie.js не приводит к немедленному открытию соединения. Объект создаётся в состоянии отложенной инициализации:

import Dexie from "dexie";

const db = new Dexie("app_database");

На этом этапе определена только сущность базы, но отсутствует активное соединение с IndexedDB. Схема таблиц обычно задаётся до открытия:

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

Важно, что сама по себе конфигурация версии не инициирует I/O-операции. Открытие происходит либо неявно при первом обращении к таблице, либо явно через вызов open().

Явное открытие соединения

Метод open() запускает полный цикл инициализации базы данных:

await db.open();

При вызове open() выполняется последовательность операций:

  1. Проверка существования базы в IndexedDB.
  2. Сравнение версии схемы.
  3. Выполнение миграций (upgrade).
  4. Инициализация внутренних структур Dexie.
  5. Переход в состояние готовности.

Возвращаемый Promise резолвится только после завершения всех шагов, включая выполнение обработчиков ready.

Жизненный цикл открытия

Процесс открытия можно разделить на несколько фаз:

  • создание запроса к IndexedDB
  • загрузка метаданных базы
  • выполнение onupgradeneeded (если требуется)
  • построение транзакций миграции
  • завершение и фиксация схемы
  • вызов событий жизненного цикла
  • возврат управления через Promise

В этой цепочке событие готовности играет роль синхронизирующего барьера между завершением инициализации и началом пользовательских операций.

Событие ready и его семантика

Событие ready в Dexie.js вызывается в момент, когда база данных уже успешно открыта, но до окончательного завершения open() в пользовательском коде. Это создаёт возможность выполнить асинхронные операции, которые должны быть завершены до начала работы приложения с данными.

db.on("ready", async (dbInstance) => {
  const count = await dbInstance.users.count();

  if (count === 0) {
    await dbInstance.users.bulkAdd([
      { name: "Initial User", email: "init@example.com" }
    ]);
  }
});

Ключевое свойство ready заключается в том, что он может задерживать завершение открытия базы. Если обработчик возвращает Promise, выполнение db.open() приостанавливается до его завершения.

Блокирующий и неблокирующий режим ready

Поведение события зависит от возвращаемого значения:

Неблокирующий сценарий

db.on("ready", () => {
  console.log("База открыта");
});

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

Блокирующий сценарий

db.on("ready", async (dbInstance) => {
  await dbInstance.table("settings").toArray();
});

Здесь возвращаемый Promise влияет на момент завершения open(). Это позволяет гарантировать, что база не начнёт обслуживать запросы до завершения подготовки данных.

Отличие ready от open()

Метод open() и событие ready решают разные задачи.

open():

  • управляет запуском соединения
  • возвращает Promise завершения инициализации
  • инициирует весь цикл открытия

ready:

  • встраивается внутрь процесса открытия
  • позволяет выполнить дополнительную асинхронную логику
  • может блокировать завершение open()

Таким образом, ready — это точка расширения внутри процесса открытия, а open() — внешний управляющий механизм.

Поведение при повторном открытии

После закрытия соединения через db.close() повторный вызов open() снова запускает цикл инициализации. При этом обработчики ready вызываются заново:

db.close();
await db.open();

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

Асинхронная подготовка данных

Одна из распространённых моделей использования ready — ленивое заполнение или подготовка кэша:

db.on("ready", async (dbInstance) => {
  const hasConfig = await dbInstance.settings.get("config");

  if (!hasConfig) {
    await dbInstance.settings.put({
      key: "config",
      value: { theme: "dark" }
    });
  }
});

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

Влияние на транзакции

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

Особенность заключается в том, что ready выполняется в контексте уже созданного соединения, но до финального разрешения Promise open().

Ошибки при открытии и их влияние на ready

Если во время открытия происходит ошибка версии, повреждение хранилища или блокировка другой вкладкой, ready не будет вызван. Вместо этого срабатывает обработка ошибок:

db.on("error", (error) => {
  console.error("Ошибка базы:", error);
});

Таким образом, ready гарантирован только при успешном открытии соединения.

Сценарии использования ready

Наиболее характерные сценарии применения включают:

  • первичная инициализация данных
  • миграция пользовательских настроек на уровне данных
  • проверка целостности кэша
  • предварительная загрузка критичных сущностей
  • синхронизация состояния приложения с хранилищем

Каждый из этих сценариев требует выполнения логики строго до начала основного потока операций чтения и записи.

Порядок выполнения относительно других событий

В жизненном цикле Dexie.js событие ready располагается после миграции схемы, но до завершения open():

  • создание экземпляра
  • определение версии
  • выполнение upgrade (если нужно)
  • вызов ready
  • завершение open()

Такое расположение делает его ключевой точкой для синхронизации состояния приложения с физическим хранилищем.

Практика управления состоянием инициализации

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

db.on("ready", async (dbInstance) => {
  await Promise.all([
    dbInstance.users.count(),
    dbInstance.posts.count()
  ]);
});

Такая модель обеспечивает детерминированное состояние базы к моменту начала основной работы с данными.