strata-storage

One storage API across web, iOS and Android — zero runtime dependencies.
4,888
Install
npm install strata-storage
Latest Version:3.1.1
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: aoneahsan
Strata Storage logo

strata-storage

One storage API across web, iOS and Android — zero runtime dependencies.

npm version downloads license types bundle size CI node

Docs · npm · GitHub · Changelog · AI Guide · Support

[!IMPORTANT] In a Capacitor app, run npx cap sync after installing. Without it the native module is never copied into your iOS/Android projects and the native adapters — secure, sqlite, preferences, filesystem — fail silently on device while working fine in the browser.

Strata Storage gives you one get/set/remove/query/subscribe API over every storage backend a cross-platform app touches: localStorage, sessionStorage, IndexedDB, cookies, the Cache API, the page URL, and — through Capacitor — the iOS Keychain, Android EncryptedSharedPreferences, native SQLite and the filesystem. The core is pure TypeScript with no runtime dependencies; React, Vue, Angular, Capacitor and Firebase are optional peers you install only if you use them. Encryption, compression, TTL, cross-tab sync, integrity checksums and mirrored backups are opt-in per call or per instance.

Version 3.1.1
License MIT
Node >=24.13.0
Platforms Web · iOS · Android (via Capacitor)
Module format ESM only — no CommonJS build
Install size 155 kB packed · 660 kB unpacked, of which 130 kB is iOS + Android native source
Types Bundled .d.ts for every entry point
Status Stable · actively maintained

🧭 Table of Contents #

💡 Why strata-storage #

Every cross-platform product re-solves the same problem: pick a storage backend per platform, learn each one's quirks, wrap it for your framework, then bolt on encryption, expiry and cross-tab sync by hand. The result is a per-platform layer nobody wants to own. Strata Storage replaces it with one adapter-based API that behaves identically everywhere, and keeps the runtime package dependency-free so it adds nothing to your dependency tree.

strata-storage Rolling your own per platform
Backends 11 adapters behind one API one integration per backend, written twice
Runtime dependencies none whatever each wrapper pulls in
Framework binding optional sub-path imports, no provider required hand-written hooks per framework
Encryption · TTL · sync · queries opt-in flags built and maintained by you
Native iOS/Android ships as a Capacitor plugin a separate native module to maintain
Automated test suite none — typecheck, lint and build are the gates yours to write

Not the right tool when — you only need localStorage in one web app (use it directly); you need a CommonJS build or Node below 24.13 (this package is ESM-only); you need a server-side or multi-user database (this is client-side key-value storage); or you need cryptographic tamper-proofing from the integrity feature specifically — its checksums are non-cryptographic, so use the encryption feature instead.

✨ Features #

  • One API, every backend — the same calls whether the value lives in localStorage, IndexedDB, the URL or the iOS Keychain.
  • Zero runtime dependencies — the core is pure TypeScript; every framework and platform binding is an optional peer.
  • Provider-free by default — create one instance and import it anywhere. No React context, Vue plugin or Angular module is required, though all three styles are still supported.
  • Synchronous API alongside the async one — for initial render and event handlers, on the backends that are genuinely synchronous.
  • Opt-in encryption and compression — AES-GCM via Web Crypto, size-thresholded compression, both off by default.
  • TTL and expiry — absolute or sliding, with expiry inspection and a cleanup sweep.
  • Queries and tags — MongoDB-style conditions over stored values and their tags.
  • Cross-tab synchronisation — subscribe to changes made in other tabs of the same origin.
  • Recovery features — integrity checksums, durable writes, mirrored read-repair, portable snapshots and scheduled backups.
  • Native adapters with no plugin dependencies — SQLite is hand-rolled and the filesystem adapter uses the platform's own FileManager / java.io.File.
  • Typed end to end — bundled declarations for every entry point, including each framework sub-path.

📱 Platform Support #

Platform Supported Notes
Browsers ✅ All seven web adapters: memory, localStorage, sessionStorage, indexedDB, cookies, cache, url.
iOS ✅ Via Capacitor. Requires npx cap sync. Keychain, UserDefaults, SQLite, FileManager.
Android ✅ Via Capacitor. Requires npx cap sync. EncryptedSharedPreferences, SharedPreferences, SQLite, java.io.File.
Node / SSR ⚠️ Imports and runs, but only the memory adapter is available — there is no DOM storage. Browser-only adapters report isAvailable() === false instead of throwing.
Firebase ✅ Optional cloud sync through the strata-storage/firebase sub-path.

📋 Requirements #

Requirement Version Why
Node >=24.13.0 Matches .nvmrc; the package is ESM-only and relies on modern Node resolution.
TypeScript moduleResolution: "bundler" or "nodenext" Older node/node10 resolution cannot see the typed sub-path exports.
@capacitor/core >=8.0.0 Optional peer — only for the native iOS/Android adapters.
react >=19.2.3 Optional peer — only for strata-storage/react.
vue >=3.5.26 Optional peer — only for strata-storage/vue.
@angular/core, @angular/forms >=21.0.6 Optional peers — only for strata-storage/angular.
firebase >=10.0.0 Optional peer — only for strata-storage/firebase.

📦 Installation #

yarn add strata-storage

Nothing else is needed for web use. Install only the optional peer you actually import:

yarn add react                          # for strata-storage/react
yarn add vue                            # for strata-storage/vue
yarn add @angular/core @angular/forms   # for strata-storage/angular
yarn add @capacitor/core                # for strata-storage/capacitor
yarn add firebase                       # for strata-storage/firebase

Capacitor apps must then sync the native module, or the native adapters will not work on device:

npx cap sync

An optional interactive wizard can scaffold a configuration file for you — see Command Line.

🚀 Quick Start #

The default storage instance registers the standard web adapters and initialises lazily on first use, so importing the package performs no I/O.

import { storage } from 'strata-storage';

await storage.set('user', { id: 123, name: 'Ada Lovelace' });
const user = await storage.get<{ id: number; name: string }>('user');

await storage.remove('user');

🛠️ Usage #

Your own configured instance

defineStorage() is the same factory the default instance is built from. Create one at module scope and import it anywhere — no provider needed.

import { defineStorage } from 'strata-storage';

export const storage = defineStorage({
  defaultStorages: ['indexedDB', 'localStorage'],
  encryption: { enabled: true, password: process.env.STORAGE_KEY! },
});

await storage.set('token', '…', { encrypt: true });

It registers memory, localStorage, sessionStorage, indexedDB, cookies and cache. Register the URL adapter and the native adapters yourself when you need them.

React

Bind the hooks to an instance once, then use them in any component.

// storage.ts
import { defineStorage } from 'strata-storage';
import { createStrataHooks } from 'strata-storage/react';

export const storage = defineStorage();
export const { useStorage, useStorageQuery, useStorageTTL } = createStrataHooks(storage);
// Settings.tsx
import { useStorage } from './storage';

function Settings() {
  const [theme, setTheme, loading] = useStorage<string>('theme', 'light');
  if (loading) return <p>Loading…</p>;
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Theme: {theme}</button>
  );
}

<StrataProvider instance={storage}> still works if you prefer context.

Vue

import { defineStorage } from 'strata-storage';
import { createStrataComposables } from 'strata-storage/vue';

export const storage = defineStorage();
export const { useStorage, useStorageQuery, useStorageTTL } = createStrataComposables(storage);

The classic StrataPlugin remains available, and each built-in composable also accepts an instance as its last argument.

Angular

import { bootstrapApplication } from '@angular/platform-browser';
import { defineStorage } from 'strata-storage';
import { provideStrata } from 'strata-storage/angular';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, { providers: [provideStrata(defineStorage())] });

StrataService is then injectable and returns RxJS Observables. StrataModule.forRoot(config) remains for NgModule applications.

Native adapters (iOS and Android)

import { defineStorage } from 'strata-storage';
import { registerCapacitorAdapters } from 'strata-storage/capacitor';

const storage = defineStorage();
await registerCapacitorAdapters(storage);

await storage.set('token', value, { storage: 'secure' }); // Keychain / EncryptedSharedPreferences

PreferencesAdapter, SecureAdapter, SqliteAdapter and FilesystemAdapter are also exported individually when you need per-adapter configuration. Each SqliteAdapter binds to one (database, table) pair, so separate logical stores map to separate physical files and cannot collide.

The synchronous API

storage.setSync('lastTab', 'inbox');
const tab = storage.getSync<string>('lastTab');
storage.removeSync('lastTab');

Available on memory, localStorage, sessionStorage, cookies and url only, and it cannot encrypt or compress — see Limitations.

The URL adapter

Persists small UI state in the page URL so it survives reloads and shareable links, and emits change events on popstate/hashchange.

import { defineStorage, URLAdapter } from 'strata-storage';

const storage = defineStorage();
storage.registerAdapter(new URLAdapter());

storage.setSync('tab', 'pending', { storage: 'url' });
const tab = storage.getSync<string>('tab', { storage: 'url' });

⚙️ Configuration #

Passed to defineStorage(config) or new Strata(config):

Option Type Default What it does
defaultStorages StorageType[] ['localStorage', 'indexedDB', 'sessionStorage', 'memory'] Preference order for picking the default adapter, and the fallback order when one is unusable. 🔴 Not a registration list — operations with no explicit storage (keys, clear, size, subscribe) span every registered adapter. To leave one out entirely use adapters: { <name>: false }.
keyPrefix string | false 'strata:' Key prefix for localStorage/sessionStorage. false restores pre-3.0 unprefixed keys — take it when anything outside this library reads a physical key directly.
migrateLegacyKeys boolean true Adopt pre-3.0 unprefixed entries on read: never a bulk sweep, never a non-envelope, never overwriting an existing prefixed value.
adapters object {} Per-adapter settings, or false to leave that adapter unregistered entirely. localStorage/sessionStorage take { prefix }; indexedDB takes { dbName, version }; cookies takes { secure, sameSite }; cache takes { cacheName }. Settings apply immediately, so they hold for setSync/getSync issued before initialize() resolves.
encryption { enabled, password } disabled AES-GCM encryption on every write. Async path only.
compression { enabled, threshold } disabled Compress values above threshold bytes. Async path only.
sync { enabled } disabled Cross-tab change notifications.
integrity boolean false Store an FNV-1a checksum with each value and verify it on read.
durableWrites boolean false Read each write back and retry on mismatch. Costs one extra read per write.
mirror StorageType[] [] Copy every write to backup storages, and read-repair the primary from them.
autoBackup { interval, storage } disabled Take periodic snapshots into a durable adapter.
debug boolean false Raise the internal log level from warn to debug.

The URLAdapter takes its own options: mode ('query' | 'hash', default 'query'), prefix (default 'strata.'), history ('push' | 'replace', default 'replace') and maxLength (default 2000).

Full reference: Configuration.

🔧 API Reference #

An index of the main surface. Full signatures and behaviour live on the docs site.

Export Signature Docs
storage the default Strata instance →
defineStorage (config?: StrataConfig) => Strata →
Strata new (config?: StrataConfig) →
.get / .set get<T>(key, opts?) => Promise<T | null> · set<T>(key, value, opts?) => Promise<void> →
.remove / .clear / .has / .keys (key?, opts?) => Promise<…> →
.getSync / .setSync / .removeSync / .hasSync / .keysSync / .clearSync synchronous equivalents, sync-capable adapters only →
.query (condition, opts?) => Promise<Array<{ key, value }>> →
.subscribe (cb, opts?) => UnsubscribeFunction →
.getTTL / .extendTTL / .persist / .getExpiring / .cleanupExpired TTL inspection and maintenance →
.snapshot / .restore / .export / .import portable backup and transfer →
.size / .getCapabilities / .getAvailableStorageTypes introspection →
.registerAdapter / .refreshAdapters / .close lifecycle →
computeChecksum / verifyChecksum (data) => string · (data, sum) => boolean →
URLAdapter and the six web adapters new (config?) →
registerCapacitorAdapters (strata) => Promise<void> — strata-storage/capacitor →
enableFirebaseSync (strata, config) => Promise<void> — strata-storage/firebase →
Error classes StrataError, StorageError, IntegrityError, QuotaExceededError, EncryptionError, CompressionError, SerializationError, ValidationError, NotSupportedError, AdapterNotAvailableError →

🧩 Types #

The types a consumer touches directly. Full definitions: Types.

type StorageType =
  | 'memory' | 'localStorage' | 'sessionStorage' | 'indexedDB' | 'cookies' | 'cache' | 'url'
  | 'preferences' | 'secure' | 'sqlite' | 'filesystem';

interface StorageOptions {
  storage?: StorageType;      // target a specific adapter
  ttl?: number;               // milliseconds
  sliding?: boolean;          // reset the TTL on each read
  tags?: string[];            // for query()
  encrypt?: boolean;          // per-call override (async only)
  compress?: boolean;         // per-call override (async only)
  verify?: boolean;           // integrity check on read
  durable?: boolean;          // read-back-and-retry on write
}

interface StrataConfig {
  defaultStorages?: StorageType[];
  adapters?: { /* per-adapter settings — see Configuration */ };
  encryption?: EncryptionConfig;
  compression?: CompressionConfig;
  sync?: SyncConfig;
  integrity?: boolean;
  durableWrites?: boolean;
  mirror?: StorageType[];
  autoBackup?: { interval: number; storage: StorageType };
  debug?: boolean;
}

💻 Command Line #

npx strata-storage <command>
Command What it does
configure Interactive wizard that writes a Strata configuration for your project.
init Alias for configure.
setup Alias for configure.
--help Print usage.

The CLI is optional — everything it produces can be written by hand.

🧪 Examples #

Goal Example
See every core call in one file docs/examples/basic-usage.ts
Run a real app against a local build example-apps/demo-app
Browse framework and feature recipes Examples on the docs site

🎛️ Advanced Features #

  • Encryption — AES-GCM through Web Crypto, per instance or per call. →
  • Compression — applied above a byte threshold you set. →
  • TTL and expiry — absolute or sliding, with a cleanup sweep. →
  • Queries — MongoDB-style conditions over values and tags. →
  • Cross-tab sync — change notifications across tabs of one origin. →
  • Integrity, durable writes, mirroring, snapshots — the opt-in recovery set, all off by default. →
  • Migrations — an experimental adapter-level utility for reshaping stored data. →
  • Firebase sync — mirror values to Firestore or the Realtime Database. →

🚑 Recovery & Troubleshooting #

Symptom Cause Fix
Native adapters do nothing on device, but web works The native module was never copied into the iOS/Android project Run npx cap sync, then rebuild the app
Cannot find module 'strata-storage/react' or its types TypeScript is on moduleResolution: "node", which cannot read sub-path exports Set "moduleResolution": "bundler" or "nodenext" in tsconfig.json
StorageError telling you to use the async API A sync call targeted an async-only adapter (indexedDB, cache, sqlite, filesystem, secure, preferences) Use await storage.get/set(...), or pass { storage: 'localStorage' }
setSync throws with { encrypt: true } or { compress: true } Web Crypto and compression are asynchronous Use await storage.set(...) for encrypted or compressed values
getSync throws on a value you stored earlier The value was written encrypted or compressed Read it with await storage.get(...)
IntegrityError on read The stored value no longer matches its checksum Configure mirror for read-repair, or pass { ignoreCorruption: true } to get null instead
No available storage adapters Every adapter in defaultStorages reported unavailable — common in Node and SSR Include 'memory' in defaultStorages, or guard the call behind a browser check
Console errors naming keys your app owns, not Strata's Known open defect — see Limitations Set a prefix: defineStorage({ adapters: { localStorage: { prefix: 'myapp:' } } })

More: Troubleshooting · FAQ.

🚧 Limitations #

  • ESM only. There is no CommonJS build. engines.node is >=24.13.0, where Node can require() an ESM graph; on older Node a require('strata-storage') will fail.
  • No automated test suite. This is a deliberate project decision — yarn typecheck, yarn lint and yarn build are the gates, in local development and in CI. There is no unit or integration coverage to point at.
  • Native behaviour cannot be verified from web or Node. The iOS and Android adapters depend on your Capacitor project's own configuration, so verify them on a real device using the device-verification guide.
  • Integrity checksums are not cryptographic. They are FNV-1a: they cheaply catch accidental corruption such as truncated writes, and they do not resist deliberate tampering. Use encryption for that.
  • The synchronous API is genuinely limited. It works only on memory, localStorage, sessionStorage, cookies and url, and it cannot encrypt or compress.
  • The URL adapter is not durable storage. URLs have practical length limits around 2,000 characters, and in 'query' mode the data is sent to the server and appears in its logs. Use 'hash' mode to keep it client-side.
  • Node support is minimal. Only the memory adapter is available outside a browser, so values do not persist across processes.
  • Web adapters share their storage area, and identify their own data by shape. localStorage, sessionStorage and cookies are shared with every other script on the origin. An adapter treats a key as its own only when the stored value is a StorageValue envelope, so keys(), the TTL sweep and clear() never touch another application's data, and a value we cannot read is skipped at debug rather than reported as an error. The consequence to know about: a key written to the same area by something other than this library is invisible to keys() by design. Call setLogLevel('debug') to see what is being skipped and why.
  • Keys are prefixed strata: since 3.0.0, and that is a breaking change. Existing data migrates itself on read, so most consumers do nothing. 🔴 But if anything outside this library reads a physical key directly — a pre-paint theme script, a logger reading its own level — set defineStorage({ keyPrefix: false }). Migration keeps the data reachable through this library; it cannot fix a hard-coded reader. See Changelog.
  • Firebase adapter names are not in the StorageType union. 'firestore' and 'realtime' are runtime names, so strict TypeScript may need a cast on the options object.

📚 Documentation #

Document Read it when
Installation setting the package up for the first time
Quick start you want a working call in two minutes
Configuration you need the full option set
API reference you need an exact signature
Adapters choosing a backend, or writing your own
Platform guides wiring web, iOS, Android, Capacitor or Firebase
Recovery and integrity the data must survive corruption
Migration guide upgrading from an earlier major
Troubleshooting · FAQ something is not behaving
AI integration guide a coding agent is implementing against this package
llms.txt · llms-full.txt you need the docs as machine-readable text

🔄 Changelog #

Latest release: 2.8.5 — documentation only: the at-a-glance table above reported the previous version, because it is a static duplicate of package.json. Full history in the changelog.

Full history: CHANGELOG.md.

🤝 Contributing #

Fork and open a pull request — see CONTRIBUTING.md for setup, standards, and how to request collaborator access. main is protected: every change lands through a reviewed pull request.

🗂️ Repository #

src/            TypeScript source — core, adapters, features, framework integrations
dist/           build output (published)
ios/            native iOS plugin sources (published, for Capacitor)
android/        native Android plugin sources (published, for Capacitor)
scripts/        build script and the npx CLI (published)
docs/           internal project records — NOT the docs site
example-apps/   runnable demo application

The documentation site lives in its own repository: aoneahsan/strata-storage-docs.

💬 Support #

Questions and bugs: open an issue.

If this package saves you time, you can support its maintenance at aoneahsan.com/payment.

📄 License #

MIT © Ahsan Mahmood — see LICENSE.

👤 Author #

Ahsan Mahmood — aoneahsan.com · GitHub · LinkedIn · aoneahsan@gmail.com

🔗 Links #

Documentation https://stratastorage-docs.aoneahsan.com
npm https://www.npmjs.com/package/strata-storage
Repository https://github.com/aoneahsan/strata-storage
Issues https://github.com/aoneahsan/strata-storage/issues
Changelog https://github.com/aoneahsan/strata-storage/blob/main/CHANGELOG.md
Docs site source https://github.com/aoneahsan/strata-storage-docs
Project website https://stratastorage.aoneahsan.com
Support the project https://aoneahsan.com/payment

🏷️ Keywords #

storage · capacitor · localstorage · indexeddb · sqlite · keychain · cross-platform · zero-dependencies · react · vue · angular · typescript

Related Packages

@symbiote-native/sqlite

expo-sqlite wrapped for SymbioteNative — one framework-agnostic core, built once...

870 72
@createnextapp/async-local-storage

AsyncLocalStorage is an unencrypted, asynchronous, persistent, key-value storage...

28,344 29
@tomassjosten/local-storage-cache

Leverage local storage to improve perfomance by caching data and expire cache

166 1
@abarchibody/storage

Storage is plugin that help to store data in the local storage, or retrieve data

935

Version History

Version Released Vue License
3.1.1 >=3.5.26 MIT
3.1.0 >=3.5.26 MIT
3.0.1 >=3.5.26 MIT
3.0.0 >=3.5.26 MIT
2.9.0 >=3.5.26 MIT