strata-storage
strata-storage
One storage API across web, iOS and Android — zero runtime dependencies.
[!IMPORTANT] In a Capacitor app, run
npx cap syncafter 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
- ✨ Features
- 📱 Platform Support
- 📋 Requirements
- 📦 Installation
- 🚀 Quick Start
- 🛠️ Usage
- ⚙️ Configuration
- 🔧 API Reference
- 🧩 Types
- 💻 Command Line
- 🧪 Examples
- 🎛️ Advanced Features
- 🚑 Recovery & Troubleshooting
- 🚧 Limitations
- 📚 Documentation
- 🔄 Changelog
- 🤝 Contributing
- 🗂️ Repository
- 💬 Support
- 📄 License
- 👤 Author
- 🔗 Links
- 🏷️ Keywords
💡 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.nodeis>=24.13.0, where Node canrequire()an ESM graph; on older Node arequire('strata-storage')will fail. - No automated test suite. This is a deliberate project decision —
yarn typecheck,yarn lintandyarn buildare 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,cookiesandurl, 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
memoryadapter 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,sessionStorageand cookies are shared with every other script on the origin. An adapter treats a key as its own only when the stored value is aStorageValueenvelope, sokeys(), the TTL sweep andclear()never touch another application's data, and a value we cannot read is skipped atdebugrather 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 tokeys()by design. CallsetLogLevel('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 — setdefineStorage({ 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
StorageTypeunion.'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
expo-sqlite wrapped for SymbioteNative — one framework-agnostic core, built once...
AsyncLocalStorage is an unencrypted, asynchronous, persistent, key-value storage...
Leverage local storage to improve perfomance by caching data and expire cache
Storage is plugin that help to store data in the local storage, or retrieve data
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 | |
| 2.8.5 | >=3.5.26 | MIT | |
| 2.8.4 | >=3.5.26 | MIT | |
| 2.8.3 | >=3.5.26 | MIT | |
| 2.8.2 | >=3.5.26 | MIT | |
| 2.8.1 | >=3.5.26 | MIT | |
| 2.8.0 | >=3.5.26 | MIT | |
| 2.7.1 | >=3.5.26 | Apache-2.0 | |
| 2.7.0 | >=3.5.26 | Apache-2.0 | |
| 2.6.1 | >=3.5.26 | Apache-2.0 | |
| 2.6.0 | >=3.5.26 | Apache-2.0 | |
| 2.5.0 | >=3.5.26 | Apache-2.0 | |
| 2.4.3 | >=3.5.26 | Apache-2.0 | |
| 2.4.2 | >=3.5.26 | Apache-2.0 | |
| 2.4.1 | >=3.5.26 | Apache-2.0 | |
| 2.4.0 | >=3.6.0 | Apache-2.0 | |
| 2.3.0 | >=3.6.0 | Apache-2.0 | |
| 2.2.0 | >=3.6.0 | Apache-2.0 | |
| 2.1.0 | >=3.6.0 | MIT | |
| 2.0.4 | >=3.6.0 | MIT | |
| 2.0.3 | >=3.0.0 | MIT | |
| 2.0.1 | >=3.0.0 | MIT | |
| 2.0.0 | >=3.0.0 | MIT | |
| 1.6.0 | >=3.0.0 | MIT | |
| 1.5.0 | >=3.0.0 | MIT | |
| 1.4.0 | >=3.0.0 | MIT |
Showing the latest 30 of 34. See every release on npm