Квоты браузера и экспорт заметок
Локальная запись полезна лишь пока её можно прочитать и сохранить. У браузерного хранилища есть квоты, пользователь может очистить данные сайта, а ноутбук может потеряться. В этой главе «Блокнот» запросит устойчивое хранение и предложит экспорт JSON. Результат — честная граница локальной гарантии и способ перенести доступный текст, не воспроизводя чужие server revision автоматически.
Свойства хранения сверены с MDN о квотах и вытеснении и StorageManager.persist. Автор не измерял квоту, не заполнял диск и не выполнял экспорт в браузере. Все значения интерфейса будут определяться средой читателя; в материалах нет выдуманного размера доступного пространства.
Квота относится к origin и политике браузера
Наличие IndexedDB не означает бесконечное место. Браузер распределяет хранение согласно собственной политике, доступному диску и режиму работы. Origin может использовать не только заметки, но и другие виды хранилища. Поэтому число байтов одной коллекции не обязано совпадать с estimate.usage всего сайта, а одинаковый проект в разных браузерах может получить разные пределы.
StorageManager.estimate возвращает оценку usage и quota. Она помогает показать порядок занятости, но не даёт точного договора о том, сколько следующая транзакция сможет записать. Другие действия могут изменить доступное место после чтения оценки. В конечном интерфейсе эти значения подписаны как оценка и не используются для объявления заранее успешного сохранения.
Readwrite транзакция может завершиться ошибкой квоты. SaveNote вернёт отказ, а поля формы останутся на экране. Это сохраняет ещё не записанный текст доступным для копирования. Программа не чистит outbox или notes автоматически, чтобы освободить место: удаление неотправленной работы ради нового объекта было бы скрытой потерей данных.
Устойчивое хранение требует решения браузера
Кнопка persist вызывает navigator.storage.persist и показывает полученный boolean. Если браузер согласится, данные origin получают другую защиту от автоматического вытеснения по его политике. Однако пользователь по-прежнему может удалить данные сайта, а потеря носителя останется потерей данных. Запрос не создаёт внешнюю резервную копию и не шифрует заметки.
Вызов из app.js отделён от обычного сохранения формы. Здесь показан полный смысл действия без остального обработчика интерфейса.
const granted = await navigator.storage?.persist?.();
const estimate = await navigator.storage?.estimate?.();
$('quota').textContent =
`Устойчивое хранение: ${Boolean(granted)}; ` +
`оценка занято ${estimate?.usage ?? 'неизвестно'}, ` +
`квота ${estimate?.quota ?? 'неизвестно'} байт`;
Если API отсутствует или ответ false, локальное редактирование не становится автоматически невозможным. Меняется доступная гарантия, которую следует объяснить. Повторять запрос непрерывно до согласия неразумно: решение может зависеть от участия пользователя и условий браузера. Учебная кнопка делает попытку явной и не выдаёт отказ за сбой сервера.
Постоянная серверная копия тоже не заменяет весь локальный экспорт. Notes может содержать новую работу, ещё не принятую сервером, и conflict с обеими редакциями. При очистке браузера именно эта часть исчезнет, хотя согласованные server notes останутся. Поэтому возможность экспортировать неотправленное состояние особенно важна для local-first модели.
Экспорт читает связанный снимок
Snapshot читает notes, outbox и meta одной readonly транзакцией. Копирование каждого store отдельным действием могло бы попасть между подтверждением и новой операцией, дав несогласованное описание. Общий scope предоставляет снимок завершённого локального состояния, который можно объяснить вместе: текущие заметки, известные намерения и cursor.
Файл содержит schema один, account, exportedAt и результат snapshot. Время экспорта удобно человеку для различения копий, но не становится серверной revision. Outbox остаётся диагностическим материалом, conflict содержит remote, а deleted записи сохраняют след локального удаления. Поэтому экспорт — больше, чем список только видимых заголовков.
Blob формируется из JSON, object URL назначается временному элементу ссылки с download, затем URL освобождается. Это не серверная загрузка файла: браузер предлагает сохранить данные пользователю. Ожидаемое имя включает аккаунт, чтобы Alice и Bob не смешивались при работе с несколькими копиями. Фактическое успешное сохранение на внешнем носителе зависит от действия читателя.
Файл может содержать личный текст и серверные редакции конфликтов. Пользователь должен выбрать подходящее место хранения и доступ. Наличие download кнопки не обеспечивает шифрование, отдельный диск или проверенное восстановление. Курс не изображает подготовленный JSON как выполненный backup; он даёт путь к копии, которую читатель ещё должен сохранить и проверить по своим условиям.
Импорт создаёт новые прикладные записи
Конечная лаборатория сознательно не вставляет сохранённый outbox обратно в другую базу. Старые opId могли уже быть приняты сервером, cursor относится к определённой истории, revision — к существующим заметкам. Автоматическое воспроизведение такого состояния в новом аккаунте смешало бы идентичность и владельца. Для полноценного аварийного восстановления нужен отдельный согласованный протокол.
Поэтому импорт берёт живые notes и создаёт новые UUID с revision ноль и localVersion один. Каждая получает новый снимок outbox в одной readwrite транзакции. Это перенос текста как новых документов. Если исходная заметка ещё существует на сервере, после синхронизации появится отдельная копия, а не скрытая замена оригинала. Подпись импорта прямо сообщает этот смысл.
Перед чтением проверяются размер файла, schema, массив notes, число записей и длины полей. Deleted объекты не превращаются в новые документы. Ограничения защищают локальную операцию от случайного огромного или неподходящего файла; они не доказывают происхождение данных. Body остаётся текстом и не выполняется как разметка после импорта.
Ожидаемый результат переноса «Плана урока» — новый id, прежнее содержание, подпись ожидания отправки. Если импорт повторить, будут ещё новые копии. Это следствие выбранной операции «создать из текста», а не ошибка ledger. Для устранения повторов нужен отдельный интерфейс сопоставления документов, которого в конечном учебном комплекте нет.
Восстановление после отказа имеет порядок
Если сохранение получило отказ квоты, сначала сохраняют несохранённый текст формы доступным способом и экспортируют уже читаемое состояние. Затем анализируют место и настройки, не уничтожая очередь. Повтор формы после устранения причины снова проходит обычную транзакцию. Не следует менять revision вручную, чтобы обойти локальный отказ: серверная версия к квоте отношения не имеет.
Если server data потеряны, старый cursor может получить 409. Экспорт в таком случае помогает удержать текущие заметки и конфликты до решения о новом серверном поколении. Однако импорт копий не восстанавливает ledger прежнего сервера. Нужно выбрать, восстанавливается ли прежний server state из отдельной копии или начинается новая история с осмысленным переносом документов.
На этом этапе локальное хранение описано без лишней гарантии. Complete подтверждает браузерную операцию, persist меняет политику вытеснения при согласии, estimate показывает оценку, export переносит доступный снимок, import создаёт новые заметки. Следующая глава дополнит эту модель владельцем: переключение аккаунта должно менять область данных и полномочия, сохраняя прежнюю неотправленную работу отдельно.