Как localForage сериализует данные для WebSQL

Внутренняя модель хранения данных в localForage строится вокруг унифицированного слоя сериализации, который позволяет одинаково работать с различными бэкендами (IndexedDB, WebSQL, localStorage). При выборе WebSQL в качестве драйвера ключевую роль играет преобразование пользовательских значений в формат, совместимый с SQL-таблицами, где данные хранятся в виде строк или бинарных представлений.

WebSQL не предоставляет нативной поддержки сложных JavaScript-типов, поэтому localForage вынужден приводить все значения к сериализуемому виду, сохраняя при этом возможность обратного восстановления структуры данных.

Базовый принцип сериализации

Основная задача слоя сериализации — преобразовать произвольное JavaScript-значение в строку, которая может быть безопасно сохранена в таблице WebSQL и затем восстановлена без потери информации.

В основе лежит комбинация двух операций:

  • Сериализация (encode) — преобразование значения в строку
  • Десериализация (decode) — восстановление исходного значения

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

Структура хранения в WebSQL-драйвере

В WebSQL localForage использует таблицу примерно следующей структуры:

  • ключ (primary key)
  • значение (TEXT или BLOB)
  • дополнительные служебные поля (в некоторых реализациях: timestamp, keyspace)

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

Преобразование значений в строку

Базовая сериализация через JSON

Основной механизм — это JSON.stringify. Он применяется для большинства стандартных типов:

  • Object
  • Array
  • Number
  • Boolean
  • null

Пример:

localForage.setItem('user', { name: 'Alex', age: 30 });

В WebSQL это превращается в:

INS ERT INTO store (key, val ue)
VALUES ('user', '{"name":"Alex","age":30}')

Ограничения JSON-сериализации

JSON не поддерживает:

  • функции
  • undefined
  • Symbol
  • циклические структуры
  • Date (теряет тип)
  • Map и Set (теряют структуру)

Поэтому localForage применяет дополнительные механизмы упаковки данных.

Обёртка значений (value wrapper)

Для сохранения типа значения localForage использует внутренний слой упаковки. Значение может быть преобразовано в объект-обёртку, содержащий метаданные:

  • тип значения
  • сериализованное содержимое
  • служебные флаги

Пример внутреннего представления:

{
  "value": "{\"year\":2025}",
  "type": "object",
  "dataType": "json"
}

Такая структура позволяет различать:

  • «обычный JSON»
  • бинарные данные
  • строки
  • специальные типы

Поддержка Date и восстановление типов

Date при сериализации теряет свою сущность и превращается в строку ISO или timestamp. localForage решает эту проблему через явную маркировку типа.

При сохранении:

localForage.setItem('time', new Date());

В WebSQL может храниться:

{
  "value": 1717420000000,
  "type": "date"
}

При извлечении выполняется восстановление:

new Date(1717420000000)

Таким образом достигается частичная типовая сохранность поверх JSON-ограничений.

Обработка бинарных данных

WebSQL способен хранить BLOB, однако localForage чаще приводит бинарные данные к строковому виду.

Для ArrayBuffer и Blob используется один из подходов:

  • преобразование в base64
  • хранение через Blob-представление (если драйвер поддерживает)
  • упаковка в объект с метаданными

Пример:

{
  value: "dat a:application/octet-stream;base64,AAABAAE...",
  type: "arraybuffer"
}

При восстановлении происходит обратное декодирование base64 в бинарный буфер.

Экранование и безопасность строк

Поскольку WebSQL оперирует SQL-запросами, важной частью сериализации является защита от:

  • SQL-инъекций
  • нарушения структуры запроса
  • некорректных символов

localForage не формирует SQL через конкатенацию пользовательских данных напрямую. Вместо этого применяются параметризованные запросы:

db.executeSql(
  'INS ERT INTO store (key, val ue) VALUES (?, ?)',
  [key, serializedValue]
);

Сериализованное значение предварительно проходит экранирование на уровне драйвера.

Десериализация при чтении данных

При вызове getItem происходит обратный процесс:

  1. получение строки из WebSQL
  2. определение типа значения
  3. парсинг JSON
  4. восстановление специальных типов

Алгоритм можно представить следующим образом:

  • если значение — JSON-объект с метаданными → обработать через декодер
  • если обычная строка → вернуть напрямую
  • если число в строковом виде → преобразовать при необходимости
  • если бинарный тип → декодировать base64

Работа с ошибками сериализации

При невозможности сериализации возникает несколько сценариев:

  • JSON.stringify выбрасывает ошибку (циклические ссылки)
  • значение теряет типовую информацию
  • данные становятся частично восстановимыми

localForage обычно:

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

Циклические структуры не поддерживаются и требуют ручного преобразования перед сохранением.

Производительность сериализации в WebSQL

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

Основные факторы влияния:

  • размер объекта
  • глубина вложенности
  • наличие бинарных данных
  • частота операций get/set
  • стоимость JSON.parse / JSON.stringify

Особенно затратными являются:

  • большие массивы объектов
  • частые операции обновления одного ключа
  • хранение больших base64-строк

Отличие от IndexedDB в контексте сериализации

В отличие от IndexedDB, где многие структуры поддерживаются нативно, WebSQL требует полного приведения к строке.

Это приводит к следующим последствиям:

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

IndexedDB может хранить объекты напрямую, тогда как WebSQL всегда работает через строковое представление.

Внутренний слой адаптации localForage

localForage скрывает различия между драйверами через единый интерфейс:

  • setItem
  • getItem
  • removeItem
  • clear

Сериализация является частью драйвер-специфичной реализации, где WebSQL-драйвер содержит собственный слой:

  • encoder
  • decoder
  • wrapper/unwrapper

Этот слой обеспечивает совместимость API независимо от ограничений SQL-хранилища.

Особенности хранения ключей и пространства имён

Ключи в WebSQL хранятся без сериализации, но могут быть дополнены namespace-префиксом:

  • key → строка
  • namespace + key → уникальный идентификатор

Это позволяет разделять несколько инстансов localForage внутри одной базы WebSQL.

Стабильность формата сериализации

Сериализация в WebSQL-драйвере ориентирована на обратную совместимость:

  • старые версии могут читать новые форматы
  • новые версии способны обрабатывать legacy-структуры
  • метаданные расширяются без изменения базового формата хранения

Это достигается за счёт слабосвязанной схемы упаковки данных, где основным является поле value, а дополнительные поля опциональны.

Влияние ограничений WebSQL на модель данных

Поскольку WebSQL:

  • устаревший стандарт
  • не поддерживает сложные типы
  • ориентирован на SQL-строки

localForage вынужден сохранять консервативную стратегию сериализации:

  • минимизация нестандартных форматов
  • приоритет JSON-совместимости
  • строгая обратная совместимость

Это делает сериализацию предсказуемой, но менее эффективной по сравнению с нативными объектными хранилищами.