Skip to content
ultrastorageultrastorage
Esc
↑↓navigate↵open⌘Jpreview
On this page

Caveats

Understand storage limits, synchronization boundaries, invalid data, and other tradeoffs.

Adding a storage wrapper does not remove browser storage limits or make existing data automatically compatible.

Before adopting ultrastorage, understand how it handles failed reads, expiration, and notifications. These details help you avoid unexpected behavior. If you are changing storage formats or libraries, see the separate migration guide.

  • Namespaces can overlap: A broad prefix includes matching nested prefixes, while an unprefixed instance can clear recognized entries across the backend; choose non-overlapping prefixes.
  • Namespace changes need migration: Changing prefix or separator does not rename existing entries and can make them inaccessible through the new configuration; keep namespace settings stable or migrate the data explicitly.
  • Reads and writes can throw: Synchronous backend access, quota, serialization, and thrown schema errors propagate without a memory fallback; even expiration cleanup during a read can fail. Opt in to quota recovery to clear expired entries and retry a quota write once.
  • Clearing skips unreadable entries: Corrupted or unsupported envelopes can remain in storage after clear() and require explicit migration or removal.
  • Changing serializers can strand data: No fallback parser runs by default. An explicit fallbackParser runs only when the configured parser throws; use a new prefix and plan format migration.
  • Rich values have limits: Functions, promises, symbols, and arbitrary class instances are rejected by default, while custom serializers may lose other values (serialization limits).
  • Existing raw values need migration: Plain-string and plain-JSON entries are invisible until imported with a legacy read.
  • null has several meanings: Missing, expired, foreign, unparseable, schema-invalid, and stored-null values can all read as null; use has() to distinguish a recognized stored-null entry from an absent one.
  • Presence does not imply validity: Bookkeeping and core helpers do not apply a schema; use explicit read validation.
  • Ordinary read validation does not rewrite storage: Synchronous schemas leave invalid entries untouched and return transformations without saving them, while React setters must write values the schema accepts.
  • Helpers are synchronous and non-atomic: Factories and updaters are not awaited, and read-modify-write operations can race across tabs or callers (helper semantics).
  • Initialization can run again: Stored null causes getOrInit() to call its factory again, and returning an existing value does not refresh its TTL (initialization).
  • Updates preserve expiration: An updateItem() call without expiration options keeps the current unexpired deadline. Pass { expiresAt: null } to remove it (updating values).
  • Expiration is lazy by default: Opt in to reactive expiration for timer notifications. Timers do not delete entries, which can occupy storage until cleanup.
  • Same-page notifications require the same runtime: Direct backend writes do not notify locally, and separately loaded package copies or mixed ESM/CJS runtimes do not share a registry (subscriptions).
  • Use the React adapter for rendering: Ordinary reads return fresh objects and can delete expired entries, while the adapter supplies cached, side-effect-free snapshots.
  • Some string keys alias array keys: Strings matching the reserved array-key encoding address array keys and are returned as arrays during enumeration.

Was this page helpful?