npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strata-storage

v2.8.2

Published

Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms

Readme

Strata Storage

Zero-dependency universal storage for the web, iOS, and Android. One API for localStorage, IndexedDB, cookies, the URL, native Keychain/Keystore, SQLite, and more — with optional React, Vue, Angular, Capacitor, and Firebase surfaces.

npm version License TypeScript Platform

  • Version: 2.8.2
  • License: MIT
  • Node.js: >= 24.13.0
  • Module format: ESM only

Why Strata Storage

Every product re-solves the same storage problem: pick a backend per platform, learn its quirks, wrap it for your framework, and bolt on encryption, expiry, and cross-tab sync by hand. Strata Storage replaces that with one adapter-based API that runs everywhere and keeps the runtime package free of dependencies.

  • Zero runtime dependencies. The core is pure TypeScript. React, Vue, Angular, and @capacitor/core are optional peer dependencies — install only what you use.
  • One API, every backend. get/set/remove/query/subscribe behave the same whether the value lives in localStorage, IndexedDB, the URL, or the iOS Keychain.
  • Provider-free. defineStorage() returns a ready-to-use instance you create once and import anywhere — no React context, Vue plugin, or Angular module required (the Provider/Plugin/Module styles still work if you prefer them).
  • Opt-in power features. Encryption, compression, TTL, queries, cross-tab sync, integrity checksums, durable writes, mirroring, and snapshots are all off by default and added per call or per instance.

Installation

yarn add strata-storage

Framework adapters import from sub-paths; no extra install beyond the framework itself:

# React / Vue / Angular peers are optional — install the one you use
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

Quick Start

The shortest path is the default storage instance. It registers the standard web adapters and initializes lazily on first use, so importing the package does no I/O.

import { storage } from 'strata-storage';

// No setup, no Provider, no initialize() call — works immediately.
await storage.set('user', { id: 123, name: 'John Doe' });
const user = await storage.get<{ id: number; name: string }>('user');

await storage.remove('user');
await storage.clear();

Need your own configured instance? Use defineStorage() — it is the same factory the default instance is built from.

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 });

defineStorage() registers memory, localStorage, sessionStorage, IndexedDB, cookies, and the Cache API. Add the URL adapter or native adapters yourself when you need them (see below).

Provider-Free Usage

The recommended pattern across all frameworks: create one instance and bind to it. No Provider, plugin, or module is required. The Provider-based styles remain available and are documented in the examples.

Vanilla JavaScript / TypeScript

import { defineStorage } from 'strata-storage';

export const storage = defineStorage();

await storage.set('theme', 'dark');
const theme = await storage.get<string>('theme');

React

Bind the hooks to an instance once at module scope with createStrataHooks, then use them in any component — no <StrataProvider> needed.

// 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() {
  // [value, setValue, loading]
  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>
  );
}

Prefer context? <StrataProvider> still works and now accepts an instance prop so it can wrap an instance you created yourself:

import { StrataProvider, useStorage } from 'strata-storage/react';
import { storage } from './storage';

<StrataProvider instance={storage}>
  <App />
</StrataProvider>;

Vue

createStrataComposables binds the composables to an instance. Each built-in composable also accepts an optional instance as its last argument, and the classic StrataPlugin still works.

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

export const storage = defineStorage();
export const { useStorage, useStorageQuery, useStorageTTL } = createStrataComposables(storage);
<script setup lang="ts">
import { useStorage } from './storage';

const { value: theme, update } = useStorage<string>('theme', 'light');
</script>

<template>
  <button @click="update(theme === 'light' ? 'dark' : 'light')">Theme: {{ theme }}</button>
</template>

Angular

provideStrata accepts either a pre-created instance or a config object, and registers StrataService for injection. It works in bootstrapApplication (standalone) or a component's providers. The STRATA_INSTANCE token holds the instance; StrataModule.forRoot(config) remains for NgModule apps.

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

const storage = defineStorage();

bootstrapApplication(AppComponent, {
  providers: [provideStrata(storage)], // or provideStrata({ defaultStorages: ['indexedDB'] })
});
// any.component.ts
import { Component } from '@angular/core';
import { StrataService } from 'strata-storage/angular';

@Component({ /* ... */ })
export class AnyComponent {
  constructor(private storage: StrataService) {}

  save() {
    // StrataService methods return RxJS Observables
    this.storage.set('theme', 'dark').subscribe();
  }
}

Synchronous API

For UI code that must read or write without await — initial render, event handlers, synchronous state hydration — Strata exposes a synchronous API alongside the async one:

import { defineStorage } from 'strata-storage';

const storage = defineStorage();

storage.setSync('lastTab', 'inbox');
const tab = storage.getSync<string>('lastTab'); // 'inbox'
storage.hasSync('lastTab');                      // true
storage.keysSync();                              // string[]
storage.removeSync('lastTab');
storage.clearSync();

Limitations (read these)

The sync API only works on adapters that are genuinely synchronous: memory, localStorage, sessionStorage, cookies, and url. It does not paper over async backends, and it cannot do work that is inherently asynchronous:

  • Targeting an async-only adapter (indexedDB, cache, sqlite, filesystem, secure, preferences) throws a StorageError telling you to use the async API.
  • setSync with { encrypt: true } or { compress: true } throws — Web Crypto and compression are async, so use await storage.set(...).
  • getSync on a value that was stored encrypted or compressed throws — read it with await storage.get(...).

TTL, tags, and metadata work with the sync API; encryption and compression do not.

// Pick a sync-capable backend explicitly when needed:
storage.setSync('filters', { status: 'open' }, { storage: 'localStorage', ttl: 60_000 });

URL Adapter

The URLAdapter (storage type 'url') persists state in the page URL so it survives reloads and round-trips through shareable/bookmarkable links — filters, the active tab, pagination, and other small UI state. It is inherently synchronous and emits change events on popstate/hashchange, so back/forward navigation and manual URL edits notify subscribers.

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

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

// Write to the URL (no await needed — URL access is synchronous)
storage.setSync('tab', 'pending', { storage: 'url' });
storage.setSync('page', 3, { storage: 'url' });

// Read it back (e.g. on reload)
const tab = storage.getSync<string>('tab', { storage: 'url' });

// React to back/forward navigation or manual edits
storage.subscribe((change) => {
  if (change.key === 'tab') applyTab(change.newValue);
}, { storage: 'url' });

Configuration

Pass URLAdapterConfig when constructing the adapter:

new URLAdapter(); // defaults below

| Option | Type | Default | Meaning | |--------|------|---------|---------| | mode | 'query' | 'hash' | 'query' | Store params in the query string (?strata.tab=...) or the hash fragment (#strata.tab=...). | | prefix | string | 'strata.' | Prefix on every param name to avoid collisions with your own query params. | | history | 'push' | 'replace' | 'replace' | Whether each write adds a browser history entry or replaces the current one. | | maxLength | number | 2000 | Soft warning threshold (chars) for total URL length. |

The adapter is configured through StrataConfig.adapters.url when you let Strata manage it:

const storage = defineStorage({ adapters: { url: { mode: 'hash', history: 'push' } } });
storage.registerAdapter(new URLAdapter());

Limitations (read these)

  • Length limits. URLs have practical limits (~2000 chars in some browsers and servers). This adapter is for small, simple, serializable state — not bulk data. Writes past maxLength are allowed but logged as a warning.
  • Server visibility. In 'query' mode the data is sent to the server on every navigation and appears in server/proxy logs. Use 'hash' mode to keep it client-only (the fragment is never sent to the server).
  • Browser only. Outside a browser (SSR/Node) the adapter reports unavailable; isAvailable() returns false.
  • Not persistent. State lives only as long as the URL does — it is not durable storage.

Disaster Recovery

Strata includes opt-in recovery features for data you cannot afford to silently lose. Everything here is off by default — enable only what you need, since each adds overhead.

Integrity checksums

Set integrity: true (or per call { verify: true }) to compute and store an FNV-1a checksum with each value and verify it on read. On corruption, Strata first attempts mirror read-repair (below); otherwise it honors { ignoreCorruption: true } (returns null) or throws a typed IntegrityError.

import { defineStorage } from 'strata-storage';

const storage = defineStorage({ integrity: true });

await storage.set('config', settings);   // checksum stored
const config = await storage.get('config'); // verified; throws IntegrityError if corrupted

Honest note: checksums are FNV-1a, non-cryptographic. They cheaply detect accidental corruption (truncated writes, bit flips, partial storage). They do not resist tampering — for tamper resistance use the encryption feature.

Durable writes

durableWrites: true (or per call { durable: true }) reads each value back after writing and retries on mismatch, throwing StorageError if it cannot confirm the write after a few attempts. This adds one read per write.

const storage = defineStorage({ durableWrites: true });
await storage.set('order', order); // confirmed written, or throws

Mirroring (read-repair)

mirror: [...] copies every write/remove to backup storage types. On a primary read miss or corruption, Strata recovers the value from a mirror and repairs the primary in place.

const storage = defineStorage({
  defaultStorages: ['localStorage'],
  integrity: true,
  mirror: ['indexedDB'], // localStorage is primary; indexedDB backs it up
});

Snapshots and scheduled backups

snapshot() produces a portable, integrity-verified backup string (embedding a checksum manifest); restore() validates that checksum and throws IntegrityError on a corrupted backup. autoBackup schedules periodic snapshots to a durable adapter.

const backup = await storage.snapshot();      // store/download this string
await storage.restore(backup);                 // validates, then restores

// Scheduled, every 5 minutes, into indexedDB
const storage = defineStorage({
  autoBackup: { interval: 5 * 60_000, storage: 'indexedDB' },
});

Integrity helpers and error classes are exported for direct use:

import { computeChecksum, verifyChecksum, IntegrityError } from 'strata-storage';

Advanced Features

Encryption (async only)

const storage = defineStorage({ encryption: { enabled: true, password: 'secret' } });
await storage.set('secret', { token: 'abc' });        // encrypted with AES-GCM
await storage.set('one-off', data, { encrypt: true }); // per-call override

TTL / expiration

await storage.set('session', data, { ttl: 3_600_000 });          // expires in 1 hour
await storage.set('cache', data, { ttl: 600_000, sliding: true }); // reset on access
const ms = await storage.getTTL('session');
await storage.persist('session');                                  // remove expiry

Compression (async only)

const storage = defineStorage({ compression: { enabled: true, threshold: 1024 } });
await storage.set('largePayload', bigObject); // compressed above 1KB

Cross-tab sync

const storage = defineStorage({ sync: { enabled: true } });
storage.subscribe((change) => {
  console.log(`${change.key} changed`, change.newValue);
});

Queries

await storage.set('user:1', user, { tags: ['users', 'active'] });
const active = await storage.query({
  tags: { $in: ['active'] },
  'value.age': { $gte: 18 },
});

Platform Support

Web (works in any JS environment)

| Adapter | Backend | Use case | |---------|---------|----------| | memory | In-memory Map | Always-available fallback, tests | | localStorage | window.localStorage | Persistent key-value (~5 MB) | | sessionStorage | window.sessionStorage | Session-scoped data | | indexedDB | IndexedDB | Large structured data | | cookies | document.cookie | Small, server-accessible data | | cache | Cache API | Service-worker / HTTP cache | | url | location query/hash | Shareable UI state (see above) |

iOS and Android (via Capacitor)

Register the native adapters you need when running under Capacitor. All four are zero-runtime-dependency: SQLite is hand-rolled (no plugin dependency) and filesystem uses the platform's native FileManager / java.io.File.

import { defineStorage } from 'strata-storage';
import {
  PreferencesAdapter,
  SecureAdapter,
  SqliteAdapter,
  FilesystemAdapter,
} from 'strata-storage/capacitor';

const storage = defineStorage();
storage.registerAdapter(new PreferencesAdapter());   // UserDefaults / SharedPreferences
storage.registerAdapter(new SecureAdapter()); // Keychain / EncryptedSharedPreferences
storage.registerAdapter(new SqliteAdapter());        // native SQLite
storage.registerAdapter(new FilesystemAdapter());    // native files

await storage.set('secret', token, { storage: 'secure' });

| Adapter | iOS backend | Android backend | |---------|-------------|-----------------| | preferences | UserDefaults | SharedPreferences | | secure | Keychain | EncryptedSharedPreferences | | sqlite | SQLite (multi-store) | SQLite (multi-store) | | filesystem | FileManager | java.io.File |

SQLite multi-store (2.6.0+): each SqliteAdapter instance binds to a (database, table) pair, so distinct logical stores map to distinct physical SQLite files / tables and cannot collide.

import { SqliteAdapter } from 'strata-storage/capacitor';

const analytics = defineStorage();
analytics.registerAdapter(new SqliteAdapter({ database: 'analytics', table: 'events' }));

const audit = defineStorage();
audit.registerAdapter(new SqliteAdapter({ database: 'audit', table: 'rows' }));
// → separate physical .db files; writes to `analytics` can never bleed into `audit`.

await storage.size(true) aggregates { total, count, byStorage, ... }; native SQLite and filesystem additionally report a per-column byte breakdown (keys / values / metadata) when called on those adapters directly.

Honest note: the native iOS/Android adapters depend on your downstream Capacitor project setup and platform configuration, and native behavior cannot be exercised in a web/Node environment. Follow the device-verification guide to verify on a real iOS and Android device after integrating.

Firebase (optional cloud sync)

import { defineStorage } from 'strata-storage';
import { enableFirebaseSync } from 'strata-storage/firebase';

const storage = defineStorage();
await enableFirebaseSync(storage, {
  apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
  firestore: true,
});
// 'firestore' / 'realtime' are runtime adapter names (not in the StorageType
// union, so strict TS may need a cast on the options object).
await storage.set('data', value, { storage: 'firestore' });

Storage Types

| Type | Platform | Synchronous | Encrypt/Compress | Notes | |------|----------|-------------|-------------------|-------| | memory | All | ✅ | async only | Always available | | localStorage | Web | ✅ | async only | ~5 MB, persistent | | sessionStorage | Web | ✅ | async only | Session-scoped | | indexedDB | Web | ❌ | async only | Large structured data | | cookies | Web | ✅ | async only | ~4 KB, server-readable | | cache | Web | ❌ | async only | Cache API | | url | Web | ✅ | async only | Shareable UI state, length-limited | | preferences | Mobile | ❌ | async only | UserDefaults / SharedPreferences | | secure | Mobile | ❌ | async only | Keychain / EncryptedSharedPreferences | | sqlite | Mobile | ❌ | async only | Native SQLite — multi-store via (database, table) (2.6.0+) | | filesystem | Mobile | ❌ | async only | Native files — file-per-key with atomic writes (2.6.0+) |

"async only" means encryption and compression require the await storage.set(...) path — the synchronous API cannot encrypt or compress.

Requirements

  • Node.js: >= 24.13.0
  • TypeScript: strict mode supported (optional, recommended)
  • Capacitor: @capacitor/core >= 8.0.0 (for native platforms; optional peer dependency)

Optional peer dependencies (install only the ones you use): react >= 19.2.3, vue >= 3.5.26, @angular/core & @angular/forms >= 21.0.6.

Documentation

📚 Full documentation: stratastorage-docs.aoneahsan.com 🤖 For AI agents: stratastorage-docs.aoneahsan.com/ai (plus /llms.txt and /llms-full.txt)

Getting Started

API

Features

Platforms

Examples & Reference

Contributing

Contributions are welcome — please read the Contributing Guide.

License

MIT License — see LICENSE. Free for commercial and non-commercial use, modification, distribution, and sublicensing; the only condition is keeping the copyright and license notice; provided without warranty.

Author

Ahsan Mahmood

Links

  • NPM Package: https://www.npmjs.com/package/strata-storage
  • Documentation: https://stratastorage-docs.aoneahsan.com
  • Website: https://stratastorage.aoneahsan.com

Support

  1. Check the FAQ and Troubleshooting
  2. Browse the documentation
  3. Contact us / report an issue

Developed with ❤️ by the Strata Storage Team — maintained by Ahsan Mahmood · [email protected].

One API. Every Storage. Everywhere.

Links

  • Live: https://www.npmjs.com/package/strata-storage
  • NPM: https://www.npmjs.com/package/strata-storage

URL source of truth: 01-code/projects/project-live-urls.json (auto-generated — do not hand-edit between these markers).