Skip to content

Core concepts

How Storage thinks — in plain language first, then the precise terms.

Previous: Tutorial · Next: Core

Beginner tip

If you have not run the Tutorial yet, do that first. This page explains why the APIs behave the way they do.

The big idea

Without Storage, apps usually do this:

  1. Pick a string key
  2. JSON.stringify a value
  3. Hope nobody else used the same key
  4. Hand-roll expiry and migrations later

Storage does steps 2–4 for you. You still pick a namespace (like a folder) and an adapter (where the bytes live). On every set, Storage wraps your value in an envelope (value + metadata) and writes that string to the adapter.

mermaid
sequenceDiagram
  participant App
  participant Storage
  participant Adapter
  App->>Storage: set("theme", "dark")
  Storage->>Storage: build envelope
  Storage->>Adapter: setItem("app:theme", json)
  App->>Storage: get("theme")
  Storage->>Adapter: getItem("app:theme")
  Adapter-->>Storage: json
  Storage->>Storage: check expiry / migrate?
  Storage-->>App: "dark" or null

Glossary (quick)

WordMeaningBeginner need?
NamespacePrefix for all keys (app:theme)Yes — pick one per feature
AdapterBackend: memory / local / sessionYes — choose where data lives
EnvelopeStored JSON: your value + savedAt / expiresAt / schemaVersionSkim; use peek when curious
TTLHow long a write should liveOptional at first
PolicyNamed TTL preset (preferences, cache)Optional — convenience
MigrateUpgrade old shapes on getAdvanced / when you ship breaking value changes

Write path (what set does)

ts
storage.set("theme", "dark", { policy: "preferences" });
  1. Resolve TTL: per-write ttl → policy → instance default → none
  2. Build envelope (value, savedAt, optional expiresAt, schemaVersion)
  3. Serialize and adapter.setItem(${namespace}😒{key}, …)

You almost never touch the envelope on write — Storage builds it.

Read path (what get / peek / has do)

CallReturnsMigrates old schemas?Drops expired?When to use
get(key)Your value or nullYesYesNormal app reads
peek(key)Full envelope or nullNoYesDebug / show expiry
has(key)booleanNoYes“Is anything there?”

Soft null is normal

Missing, expired, or “migrate said drop” → null / false. That is not an exception. See Errors for what does throw.

Adapters (where data lives)

AdapterSurvives reload?Typical use
createMemoryAdapter()NoUnit tests, SSR
createLocalStorageAdapter()YesPrefs, cache
createSessionStorageAdapter()Until tab closesWizard / tab-scoped

There is no auto-select — you pass the adapter on purpose.

Policies (optional shortcut)

Instead of repeating { ttl: { days: 365 } } everywhere:

ts
policies: {
  preferences: {
    ttl: {
      days: 365;
    }
  }
}
// …
storage.set("theme", "dark", { policy: "preferences" });

Same as passing that TTL by hand. Unknown policy names throw (programmer error).

Subpaths (advanced tools)

Core stays small. Import extras only when you need them:

NeedImport
Sweep expired keys + report@jayoncode/storage/maintenance
Backup / restore namespace@jayoncode/storage/snapshots
React to changes in-process@jayoncode/storage/observable
DEV size / activity report@jayoncode/storage/diagnostics
Multi-key write with rollback@jayoncode/storage/transactions
IndexedDB / async API@jayoncode/storage/async
Cross-tab notify@jayoncode/storage/cross-tab
Soft quota (approx bytes)@jayoncode/storage/quota
Compress / encrypt hooks@jayoncode/storage/transforms

Next

LevelGo to
Still learningRecipes
Need every optionCore
Handling failuresErrors

An ecosystem of independent, headless TypeScript libraries engineered for modern web development. Every package includes interactive playgrounds and documentation that evolves alongside the code.