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

@stackra/storage

v2.0.0

Published

Unified KV storage layer for the Stackra framework — one IStorage contract with pluggable drivers (memory, null, localStorage, sessionStorage, IndexedDB via Dexie, AsyncStorage) and a MultipleInstanceManager for named instances.

Downloads

349

Readme

@stackra/storage

Unified KV storage layer for the Stackra framework.

One IStorage contract. Pluggable drivers per platform. A MultipleInstanceManager for named instances so an app can host several concurrent storage stores (preferences, session, offline, secure) each backed by a different driver.

Why

Before this package, every feature package that needed to persist a small blob of data (consent, scope, state, i18n, auth, …) shipped its own LocalStorageXAdapter and AsyncStorageXAdapter implementations. That's eight variants of the same code — and eight call sites to update when the shape changes.

@stackra/storage replaces every one of them with one contract, one manager, and platform-specific drivers registered per subpath.

Install

pnpm add @stackra/storage

Optional peers (install only what you use):

# IndexedDB driver
pnpm add dexie

# React Native driver
pnpm add @react-native-async-storage/async-storage

Web setup

import { WebStorageModule } from "@stackra/storage/react";

@Module({
  imports: [
    WebStorageModule.forRoot({
      default: "preferences",
      stores: {
        preferences: { driver: "localStorage", prefix: "app:prefs" },
        session: { driver: "sessionStorage", prefix: "app:session" },
        offline: { driver: "indexedDB", database: "app-offline" },
      },
    }),
  ],
})
export class AppModule {}

Native setup

import { NativeStorageModule } from "@stackra/storage/native";

@Module({
  imports: [
    NativeStorageModule.forRoot({
      default: "preferences",
      stores: {
        preferences: { driver: "asyncStorage", prefix: "app:prefs" },
      },
    }),
  ],
})
export class AppModule {}

SecureStore driver (native)

@stackra/storage/native also ships an expoSecureStore driver backed by expo-secure-store — the canonical iOS Keychain / Android Keystore substrate for values that demand encryption-at-rest (consent decisions, auth tokens, minor-data snapshots, anything a GDPR / COPPA / regulated-market posture calls for).

expo-secure-store is declared as an optional peer — install it in the parent app when you want the driver:

pnpm --filter <app> add expo-secure-store

Platform requirements

  • iOS. No Info.plist changes required by default. SecureStore uses the keychain with WHEN_UNLOCKED accessibility, which is the driver's default and does not require biometric prompts. If a caller later opts into biometric-gated reads (per-key requireAuthentication: true), add NSFaceIDUsageDescription to Info.plist — otherwise, no purpose string is needed.
  • Android. Automatic — SecureStore uses AndroidKeyStore under the hood on API 23+.

Wire pattern

Declare a secureStore-named instance in NativeStorageModule.forRoot(...) alongside asyncStorage:

import { NativeStorageModule } from "@stackra/storage/native";

@Module({
  imports: [
    NativeStorageModule.forRoot({
      default: "asyncStorage",
      stores: {
        asyncStorage: { driver: "asyncStorage", prefix: "app:" },
        secureStore: { driver: "expoSecureStore", prefix: "app:secure:" },
      },
    }),
  ],
})
export class AppModule {}

Any consumer of the storage manager can then reach it:

class ConsentStore {
  constructor(
    @Inject(STORAGE_MANAGER) private readonly storage: IStorageManager,
  ) {}

  save(prefs: Record<string, boolean>): Promise<void> {
    return this.storage.instance("secureStore").set("consent.v1", prefs);
  }
}

Fail-soft when the peer is missing

If expo-secure-store is not installed in the parent app the factory falls back to a NullStore and logs a bootstrap warning — every read returns null and every write is dropped. App boot survives; the misconfiguration surfaces in the dev-tools console instead of a crash. Install the peer to turn the driver on.

Downstream consumer — @stackra/consent/native

SecureStoreConsentAdapter in @stackra/consent/native resolves the secureStore-named instance via manager.instance("secureStore") — see packages/frontend/consent/src/native/adapters/secure-store-consent.adapter.ts. Wire the expoSecureStore driver above and set NativeConsentModule.forRoot({ storage: "secureStore" }) to route consent decisions through the encrypted substrate.

Known limitations

  • No enumerate API. SecureStore does not expose "list every key", so the driver maintains an in-memory tracked-keys set populated by every set() and rehydrated on every successful get(). clear() sweeps exactly the keys this driver instance observed during its lifetime — rows written by an earlier session become visible once they've been read.
  • Async-only. Every operation is a bridge call across the RN JS ↔ native boundary; latency is higher than asyncStorage. Prefer asyncStorage for large / hot-path values, and reserve secureStore for the compliance- sensitive subset.

Consume

import { Inject } from "@stackra/container";
import { STORAGE, type IStorage } from "@stackra/contracts";

class PreferencesService {
  constructor(@Inject(STORAGE) private readonly storage: IStorage) {}

  async loadTheme(): Promise<string> {
    return (await this.storage.get<string>("theme")) ?? "light";
  }

  async saveTheme(theme: string): Promise<void> {
    await this.storage.set("theme", theme);
  }
}

Need a different named instance? Inject the manager:

import { STORAGE_MANAGER, type IStorageManager } from "@stackra/contracts";

class OfflineCache {
  constructor(
    @Inject(STORAGE_MANAGER) private readonly storage: IStorageManager,
  ) {}

  save<T>(key: string, value: T): Promise<void> {
    return this.storage
      .instance("offline")
      .set(key, value, { ttlSeconds: 3600 });
  }
}

The IStorage contract

interface IStorage {
  get<T>(key: string): Promise<T | null>;
  set<T>(
    key: string,
    value: T,
    options?: { ttlSeconds?: number },
  ): Promise<void>;
  delete(key: string): Promise<void>;
  clear(): Promise<void>;
  has(key: string): Promise<boolean>;
  keys(): Promise<string[]>;
}

Every method returns a Promise. Sync backing stores (localStorage, sessionStorage) wrap results in Promise.resolve(...) so consumer code never branches on backing store. Missing / expired entries resolve to null.

React hooks

import { useStorage, useStorageValue } from "@stackra/storage/react";

function ThemeToggle() {
  const [theme, setTheme] = useStorageValue<string>("theme", {
    instance: "preferences",
    initialValue: "light",
  });

  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Switch to {theme === "dark" ? "light" : "dark"}
    </button>
  );
}

Testing

import {
  createMockStorageManager,
  MockStorage,
} from "@stackra/storage/testing";

const manager = createMockStorageManager();
await manager.instance().set("key", "value");
expect(await manager.instance().get("key")).toBe("value");

Design decisions

  • Promise-first IStorage — one uniform shape across sync and async backing stores. Consumers never branch.
  • TTL at the driver level — every driver wraps values in a { v, e? } envelope. Manager stays value-agnostic.
  • Drivers register via manager.extend(...) from subpath modules (WebStorageModule, NativeStorageModule). The core package is platform-agnostic; only memory + null live in src/core.
  • Named instances — the manager hosts several IStorage instances side-by-side, each backed by its own driver. This is the same MultipleInstanceManager<T> pattern @stackra/cache, @stackra/http, and @stackra/queue follow.

License

MIT © Figentra L.L.C.