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

@addon-core/storage

v0.4.0

Published

Type-safe, ergonomic wrapper around chrome.storage for browser extensions (WebExtensions) with namespaces, multiple storage areas, encryption (AES‑GCM), bucket-style storage (MonoStorage), and a React adapter.

Readme

@addon-core/storage

Type-safe, ergonomic wrapper around chrome.storage for browser extensions (WebExtensions) with namespaces, multiple storage areas, encryption (AES‑GCM), bucket-style storage (MonoStorage), and a React adapter.

npm version npm downloads License: MIT

  • Simple API: set, get, getAll, remove, clear, watch
  • Storage areas: local, session, sync, managed
  • Namespaces to logically separate keys
  • Secure storage: SecureStorage (AES‑GCM, app key)
  • MonoStorage — store multiple values under a single top-level key
  • React hook useStorage for two-way binding between state and storage
  • First-class TypeScript support (strict typing for keys and values)

Installation

# with your preferred package manager
npm i @addon-core/storage
# or
yarn add @addon-core/storage
# or
pnpm add @addon-core/storage

Requirements and environment:

  • The library targets browser extension environments where chrome.storage is available.
  • For the React adapter, peer dependencies react and react-dom are required (optionally @types/react and @types/react-dom for TypeScript).
  • SecureStorage relies on the Web Crypto API (crypto.subtle, AES‑GCM), available in modern browsers.

Quick start

import {Storage} from "@addon-core/storage";

// Optionally set a namespace to isolate module keys
const storage = Storage.Local<{ token?: string; theme?: "light" | "dark" }>({namespace: "app"});

await storage.set("token", "abc123");
const token = await storage.get("token"); // "abc123"

await storage.remove("token");
await storage.clear(); // clears only keys from the current namespace (if set)

API overview

The package exports:

  • Provider classes: Storage, SecureStorage, MonoStorage
  • Types: StorageProvider, StorageState, StorageWatchOptions, StorageWatchCallback, StorageWatchKeyCallback
  • React adapter: useStorage from the submodule @addon-core/storage/react

Creating a provider

There are two ways to create a provider instance.

  1. Via constructor with options:
import {Storage} from "@addon-core/storage";

const s1 = new Storage<{ count?: number }>({area: "local", namespace: "counter"});
  1. Via convenient static factories:
import {Storage, SecureStorage} from "@addon-core/storage";

const sLocal = Storage.Local<{ user?: string }>({namespace: "app"});
const sSession = Storage.Session<{ tmp?: string }>();
const sSync = Storage.Sync<{ settings?: any }>({namespace: "global"});
const sManaged = Storage.Managed<{ policy?: any }>(); // for policy-managed storage

// SecureStorage — values are encrypted (AES‑GCM) under the reserved prefix "secure:"
const secure = SecureStorage.Local<{ token?: string }>({secureKey: "MyStrongKey", namespace: "auth"});

Provider options:

  • area?: "local" | "session" | "sync" | "managed" — storage area (defaults to local)
  • namespace?: string — optional namespace; keys become namespace:key (for SecureStorage: secure:namespace:key)
  • For SecureStorage: additionally secureKey?: string — a string used to derive the encryption key.

Provider methods

All providers (Storage, SecureStorage, MonoStorage) share the StorageProvider<T> interface:

interface StorageProvider<T> {
    set<K extends keyof T>(key: K, value: T[K]): Promise<void>;

    get<K extends keyof T>(key: K): Promise<T[K] | undefined>;

    getAll(): Promise<Partial<T>>;

    remove<K extends keyof T>(keys: K | K[]): Promise<void>;

    clear(): Promise<void>;

    watch(options: StorageWatchOptions<T>): () => void; // returns an unsubscribe function
}

Where StorageWatchOptions<T> is either a map of per-key callbacks or a single callback:

// Option 1: a single handler for all changes
const unsubscribe = storage.watch((next, prev, key) => {
    console.log("changed", {key, next, prev});
});

// Option 2: specific handlers per key
const un = storage.watch({
    token(newVal, oldVal) {
        console.log("token changed", newVal, oldVal);
    },
    theme(newVal, oldVal) {
        console.log("theme changed", newVal, oldVal);
    },
});

// Later
un(); // unsubscribe

Notes:

  • getAll() returns entries (key–value pairs) scoped to the current provider (area, namespace, provider kind) and resolves to Partial<T>.
  • In the single-callback form of watch(), the third argument is the key that changed.
  • SecureStorage transparently encrypts/decrypts values. They are stored as strings, while you work with original types externally.

MonoStorage — a “bucket” under one key

MonoStorage lets you keep several values under a single top-level key (a bucket). Handy when you need to atomically store and update a set of related values.

You can create it in two ways:

  1. Explicitly:
import {MonoStorage, Storage} from "@addon-core/storage";

type Bucket = { a?: number; b?: string };
const base = Storage.Local<Record<"bucket", Partial<Bucket>>>();
const mono = new MonoStorage<Bucket, "bucket">("bucket", base);

await mono.set("a", 1);
await mono.set("b", "x");
console.log(await mono.getAll()); // { a: 1, b: "x" }
  1. Via the factory with the key parameter — you’ll get MonoStorage right away:
import {Storage} from "@addon-core/storage";

const mono = Storage.Local<{ a?: number; b?: string }>({key: "bucket"});
await mono.set("a", 1);

Highlights:

  • When the last value in the “bucket” is removed, the top-level key is cleared entirely.
  • watch() in MonoStorage invokes callbacks only on actual value changes (deep/structural comparison).

SecureStorage — value encryption

import {SecureStorage} from "@addon-core/storage";

type Auth = { token?: string; profile?: { id: string } };
const secure = SecureStorage.Local<Auth>({secureKey: "AppSecret", namespace: "auth"});

await secure.set("token", "jwt.token.value");
const token = await secure.get("token"); // decrypted

Under the hood AES‑GCM (Web Crypto API) is used. Don’t keep secureKey in public code — obtain it from protected sources (e.g., native settings, enterprise policy, remote configuration, etc.).

React adapter

The submodule @addon-core/storage/react provides the useStorage hook to synchronize component state with chrome.storage.

Signatures (simplified):

// useStorage<T>(options: { key: string; storage?: StorageProvider<Record<string, any>>; defaultValue?: T }):
//   readonly [T | undefined, (v: T) => void, () => void]
// useStorage<T>(key: string, defaultValue?: T):
//   readonly [T | undefined, (v: T) => void, () => void]

Basic example:

import React from "react";
import {useStorage} from "@addon-core/storage/react";

export function ThemeSwitch() {
    const [theme, setTheme] = useStorage<"light" | "dark">("theme", "light");

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

Using a custom provider and default value:

import React from "react";
import {Storage} from "@addon-core/storage";
import {useStorage} from "@addon-core/storage/react";

const storage = Storage.Sync<Record<string, any>>({namespace: "app"});

export function Profile() {
    const [name, setName, removeName] = useStorage<string>({key: "name", storage, defaultValue: "Anonymous"});

    return (
        <div>
            <input value={name ?? ""} onChange={e => setName(e.target.value)}/>
            <button onClick={removeName}>Reset</button>
        </div>
    );
}

Practical tips

  • Don’t mix data from different modules — use namespace.
  • To sync settings across devices, use the sync area.
  • In test environments, use WebExtensions mocks (e.g., jest-webextension-mock).
  • Don’t store large amounts of data — chrome.storage has quotas. Store settings and lightweight data only.