@adrienlcp/safe-storage
localStorage that never throws: every read, write and removal returns a Result, and typed reads check what they find
Works with
- TypeScript
Install
pnpm add @adrienlcp/safe-storageExports
Grouped by the section of the documentation that explains them. A row leads to its section.
Reading3 exports
readRecognizedTextThe text stored underkey, narrowed byisRecognized— a locale, a theme, any closed set of strings.FunctionreadStoredJsonThe JSON stored underkey, parsed and narrowed byisValue.FunctionreadStoredTextThe text stored underkey, ornullwhen nothing is: an absence, never a failure.Function
Writing and removing3 exports
Documented in the source only5 exports
StorageUnavailablelocalStoragethrew on access: a Safari private window, storage blocked by the user or a policy, or nolocalStorageat all, as on a server.TypeStoredValueUnrecognizedSomething is stored under the key, but not a value the caller recognizes.TypeStorageQuotaExceededThe browser refused the write because the origin's storage is full.TypeStorageReadErrorTypeStorageWriteErrorType
Documentation
The README and the documents beside it, as written in the repository.
OverviewREADME.md
localStorage that never throws. Every read, write and removal returns a
Result, and a typed read checks what it finds instead of
trusting it.
localStorage throws on plain access in a Safari private window, when the
user blocks site data, when the quota is full, and on a server, where it does
not exist. An app that calls it bare turns any of those into a blank page.
pnpm add @adrienlcp/safe-storageReadingREADME.md
import { readRecognizedText, readStoredJson, readStoredText } from '@adrienlcp/safe-storage'
readStoredText('app:volume')
// Result<string | null, 'unavailable'>
readRecognizedText({ isRecognized: isLocale, key: 'app:locale' })
// Result<Locale | null, 'unavailable' | 'unrecognized'>
readStoredJson({ isValue: isTrainingLog, key: 'app:log' })
// Result<TrainingLog | null, 'unavailable' | 'unrecognized'>- A
nullsuccess is nothing stored yet, never a failure. 'unavailable'—localStoragethrew, or there is none.'unrecognized'— something is stored, but the guard refuses it: a value an older version wrote, text that is not JSON, JSON of another shape.
The guard is a plain type predicate, so no schema library is required. One plugs in through its own:
const isTrainingLog = (value: unknown): value is TrainingLog =>
trainingLogSchema.safeParse(value).successWriting and removingREADME.md
import { removeStored, writeStoredJson, writeStoredText } from '@adrienlcp/safe-storage'
writeStoredText({ key: 'app:locale', text: 'fr' }) // Result<void, 'unavailable' | 'quota'>
writeStoredJson({ key: 'app:log', value: log }) // Result<void, 'unavailable' | 'quota'>
removeStored('app:locale') // Result<void, 'unavailable'>'quota' is a full storage. A value JSON.stringify cannot write, like a
cycle, still throws: that is a bug in the caller, not a refusal.
What to do with a failureREADME.md
This package reports; it never decides. What the user gets instead is the app's call, made where the storage is read:
const readTrainingLogOrEmpty = (): TrainingLog => {
const stored = readStoredJson({ isValue: isTrainingLog, key: LOG_KEY })
return stored.status === 'success' ? (stored.data ?? EMPTY_LOG) : EMPTY_LOG
}