Strata Storage โ a project by Ahsan Mahmood
One storage API across localStorage, IndexedDB, cookies, the URL, native Keychain and Keystore, SQLite and the filesystem. Zero dependencies.
Nothing in my own projects calls localStorage directly.
That rule is not fussiness. A value starts in localStorage because it is the shortest thing to type, then it has to survive a reinstall, then something native has to read it, and by the time all three are true the code touching it is spread across a dozen files nobody wants to open.
Storage looks trivial. It is the layer you get to change least often.
Each backend has its own grammar and none of them agree with the others. localStorage is synchronous and holds strings. IndexedDB is asynchronous and holds structures. Cookies carry expiry and scope. The URL is storage that people forget is storage, right up until a filter panel loses its state on a refresh.
Strata Storage puts one grammar over all of it. Get, set, remove, query, subscribe.
Five verbs, and the answer they give does not change depending on where the value happens to sit.
Zero runtime dependencies was the second decision, and it is the one that cost the most to hold.
React, Vue, Angular and the Capacitor core are optional peers. Your project installs them if it already has them, and the package brings none of them along. A library that arrives with its own opinion about your framework is a library you argue with eighteen months later, usually during an upgrade you did not plan.
Everything expensive stays off. Encryption, compression, expiry, queries, integrity checksums, durable writes, mirroring and snapshots are enabled per call or per instance, never by a global switch inherited from somebody else's configuration.
A default that encrypts everything makes every read slower for the sake of the one value that needed it.
Two of the optional pieces earn their weight more often than I expected. A query engine over stored values means a lookup stops being a full read followed by a filter in JavaScript, and cross-tab observers mean two open tabs stop disagreeing about who is signed in.
The native side is where a parity claim has to be earned rather than announced. A key-value store on a phone can be a preferences file, a SQLite table or a file on disk, and those three diverge exactly where a wrapper is tempted to paper over them: what happens to a half-finished write, and what a stored expiry means after a reboot.
I wrote that part twice.
A multi-store SQLite backend and a file-per-key filesystem adapter now answer the same call the same way, which is a smaller sentence than the work behind it.
None of that was asked for. What it cost is a strict TypeScript build, a suite of 140 tests and a documentation site nobody asked for. A storage library whose semantics you cannot read is a storage library nobody should hand a session token to, so the documentation is part of the product rather than an afterthought attached to it.
There is a second cost I did not choose. Every backend belongs to somebody else. A browser tightens its eviction policy, a platform revises what counts as durable, and the abstraction has to absorb that without moving the five verbs, because the whole promise is that the verbs do not move.
Apache-2.0 on npm, source on GitHub, documentation at stratastorage-docs.aoneahsan.com.
It is the storage layer inside my own products, which means it gets used badly and early, by me, in the places where a shortcut would have been easier.
What it does, and what that costs to build
- Unified API across 8+ storage backends
- Zero runtime dependencies
- AES encryption, compression & TTL (opt-in)
- Query engine + cross-tab sync + observers
- Disaster recovery (checksums, durable writes, snapshots)
- Native SQLite multi-store + filesystem adapters
- Provider-free React/Vue/Angular bindings
Built with
- TypeScript
- CapacitorJS
- Swift
- Java
- Vitest
Worth knowing
- npm
- package
- storage
- capacitor
- cross-platform
- zero-dependency
Who built Strata Storage?
Ahsan Mahmood wrote all of it, from the browser tier down to the native adapters. That matters more in a storage library than it does in an application, because the person who decided what an expiry means in localStorage is the same person who implemented it in SQLite, so the two agree rather than drift. It is published on npm as strata-storage under Apache-2.0, the source is at github.com/aoneahsan/strata-storage, and the documentation site is at stratastorage-docs.aoneahsan.com. Every claim here is checkable from one of those.
Why use this instead of calling localStorage directly?
Because localStorage is the decision you are least able to reverse cheaply. It is synchronous, it holds strings, it is per-origin, and every one of those becomes a constraint the day the value has to survive a reinstall or be read from a native shell. Strata Storage gives you one set of verbs over eight or more backends, so changing where a value lives is a configuration change rather than a rewrite of every call site. If a value genuinely never leaves one browser tab, the plain API is fine and this is overhead.
Does Strata Storage work on iPhone?
The browser backends run wherever the browser runs, an iPhone included, because they are web APIs rather than anything I wrote for a platform. What does not exist is an iOS release: there is no Apple Developer account here, so nothing I build has shipped to the App Store, and Android is the native tier I can actually vouch for. Treat the mobile parity claim as proven on Android and unverified anywhere Apple has to approve it.
What does adding it cost a bundle?
The runtime ships zero dependencies, so the baseline cost is the package itself and nothing it drags in. React, Vue, Angular and the Capacitor core are optional peers, installed only if your project already uses them, which means a plain TypeScript project pays for none of that surface. The power features are the same shape: encryption, compression, expiry, queries, checksums, durable writes, mirroring and snapshots are all off until a call or an instance turns them on.
Is stored data encrypted by default?
No. Encryption is opt-in, per call or per instance, and that is a deliberate choice rather than an oversight. A library that encrypts everything by default makes every read slower for the sake of the one value that needed it, and it hides the question of which values those are. Deciding what deserves AES is a decision about your data, so the library asks you to make it. Compression, integrity checksums and durable writes follow the same rule and are switched on the same way.
https://aoneahsan.com/projects/com.aoneahsan.stratastorage