localForage предоставляет единый асинхронный API поверх различных механизмов хранения данных в браузере, таких как IndexedDB, WebSQL и localStorage. Одной из ключевых архитектурных особенностей библиотеки является возможность замены стандартных драйверов на пользовательские реализации. Это открывает путь к интеграции альтернативных хранилищ, специфических бэкендов или экспериментальных механизмов хранения, сохраняя при этом совместимость с единым интерфейсом.
В основе системы хранения localForage лежит абстракция драйвера. Драйвер — это объект, реализующий минимальный набор методов, необходимых для работы с ключ-значение хранилищем. Все операции библиотеки делегируются текущему активному драйверу, выбранному во время инициализации или динамически в процессе выполнения.
Каждый драйвер обязан соответствовать контракту, который включает базовые операции:
Эти методы формируют универсальный слой, через который localForage взаимодействует с любым типом хранилища.
Кастомный драйвер в localForage представляет собой объект с обязательными методами. Его структура должна соответствовать внутреннему интерфейсу библиотеки, который можно условно представить следующим образом:
_driver — уникальный идентификатор драйвера;_initStorage(options) — инициализация экземпляра
хранилища;getItem(key, callback) — получение значения;setItem(key, value, callback) — сохранение
значения;removeItem(key, callback) — удаление значения;clear(callback) — очистка всех записей;length(callback) — количество элементов;key(n, callback) — получение ключа по индексу;iterate(iterator, callback) — итерация по всем
элементам.Каждый метод должен поддерживать асинхронную модель выполнения. В большинстве реализаций используется callback-стиль, однако localForage оборачивает его в Promise-совместимый интерфейс.
Регистрация кастомного драйвера осуществляется через метод
defineDriver. После регистрации драйвер становится
доступным для выбора так же, как и встроенные реализации.
Процесс можно представить следующим образом:
setDriver.При этом библиотека проверяет наличие всех обязательных методов и
корректность идентификатора _driver.
Ключевым моментом является то, что драйвер не активируется автоматически после регистрации. Он должен быть явно выбран, что позволяет безопасно подключать несколько реализаций одновременно.
Базовая реализация кастомного драйвера может опираться на обычный объект JavaScript в памяти:
Такой драйвер часто используется для тестирования или как fallback в средах без поддержки встроенных хранилищ.
Пример логики хранения:
Map;Promise.resolve.Несмотря на простоту, такой драйвер полностью совместим с API localForage.
Метод _initStorage вызывается при активации драйвера. Он
получает конфигурацию экземпляра и должен подготовить внутренние
структуры хранения. В случае in-memory реализации создаётся новый
контейнер данных, изолированный от других экземпляров.
Метод getItem(key) возвращает значение, связанное с
ключом. Если ключ отсутствует, возвращается null. Важно
соблюдать единообразие поведения с встроенными драйверами, где
отсутствие данных не считается ошибкой.
Метод setItem(key, value) записывает значение в
хранилище. Внутри драйвера может происходить сериализация, если
хранилище требует строкового формата. localForage обычно передаёт уже
сериализуемые данные, но ответственность за совместимость остаётся за
драйвером.
Метод removeItem(key) удаляет запись из хранилища. После
удаления дальнейшие обращения к ключу должны возвращать
null.
Метод clear() полностью очищает внутреннее состояние
драйвера. В случае Map-реализации выполняется вызов clear()
у контейнера.
Метод key(n) возвращает ключ по индексу. Это
предполагает наличие упорядоченного списка ключей, который формируется
из внутреннего состояния хранилища. Порядок может зависеть от
реализации, но должен быть стабильным в рамках одного экземпляра.
Метод iterate(iterator) позволяет последовательно
проходить по всем элементам. Итератор получает значение, ключ и индекс.
Возвращаемое значение может прервать итерацию, если возвращается
ненулевое значение.
После реализации объекта драйвера он регистрируется через механизм расширения. Регистрация добавляет драйвер в глобальный список доступных реализаций. Далее он становится доступен через стандартный API выбора драйверов.
После регистрации возможны следующие сценарии:
Важно учитывать, что порядок регистрации влияет на приоритет выбора
при использовании setDriver с массивом драйверов.
localForage автоматически оборачивает callback-интерфейс драйвера в
Promise API. Это означает, что даже если драйвер реализован в
классическом стиле Node.js callback (err, result),
библиотека преобразует его в цепочку промисов.
Для кастомных драйверов это поведение критично, поскольку позволяет не дублировать логику под разные стили асинхронности.
Однако драйвер должен строго соблюдать сигнатуры методов, иначе обёртка не сможет корректно обработать результат.
Несмотря на гибкость архитектуры, кастомные драйверы имеют ряд ограничений:
Также следует учитывать, что драйверы не должны нарушать изоляцию
экземпляров. Каждый вызов _initStorage должен создавать
независимое пространство данных.
Условная модель кастомного драйвера включает следующие компоненты:
Эта модель обеспечивает совместимость с системой управления драйверами, позволяя библиотеке воспринимать кастомную реализацию как нативную.
При смене драйвера через setDriver происходит повторная
инициализация хранилища. Данные не переносятся автоматически между
драйверами. Это означает, что переключение между реализациями может
привести к смене контекста данных.
Некоторые драйверы могут реализовывать механизм миграции, однако это выходит за рамки базового контракта localForage и требует отдельной логики внутри пользовательской реализации.
Каждый экземпляр localForage может использовать собственный набор настроек и драйверов. Это позволяет создавать изолированные хранилища с разными стратегиями хранения данных.
Кастомный драйвер в этом контексте становится инструментом для:
Такая архитектура позволяет использовать localForage не только как обёртку над браузерным storage, но и как универсальный слой абстракции данных.