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.5.0

Published

Typed storage for browser extensions with atomic updates, namespaces, encryption, and React bindings.

Downloads

195

Readme

@addon-core/storage

Typed storage for browser extensions with namespaces, atomic updates, encrypted values, bucket-style storage, and React bindings.

npm version npm downloads CI License: MIT

Why this package

chrome.storage is flexible, but it gets noisy quickly:

  • storage keys are untyped and easy to mistype;
  • namespaces need manual handling;
  • read-modify-write flows are easy to break;
  • encrypted values require extra boilerplate;
  • feature state often ends up scattered across unrelated keys.

@addon-core/storage adds a small typed layer on top of chrome.storage so storage code stays predictable and easy to read.

Features

  • Simple API: set, get, update, getAll, remove, clear, watch
  • Atomic update() for race-safe writes
  • local, session, sync, and managed storage areas
  • Namespaces for isolating module data
  • SecureStorage with AES-GCM encryption
  • MonoStorage for grouping related values under one top-level key
  • React hook via @addon-core/storage/react

Installation

npm

npm i @addon-core/storage

pnpm

pnpm add @addon-core/storage

yarn

yarn add @addon-core/storage

Quick start

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

interface SessionState {
    token?: string;
    theme?: "light" | "dark";
}

const storage = Storage.Local<SessionState>();
await storage.set("token", "abc123");
await storage.set("theme", "dark");

const token = await storage.get("token");
const all = await storage.getAll();

Typed storage without boilerplate

Define your storage shape once:

interface UserSettings {
    theme?: "light" | "dark";
    language?: "en" | "uk";
    shortcutsEnabled?: boolean;
}

Create a typed storage instance for the sync area:

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

const settings = Storage.Sync<UserSettings>({namespace: "settings"});

Now all operations are typed:

await settings.set("theme", "dark");
const theme = await settings.get("theme");
await settings.remove("language");

Atomic updates

If the next value depends on the previous one, use update() instead of get() + set().

interface CounterState {
    installCount?: number;
}

const storage = Storage.Local<CounterState>();

await storage.update("installCount", prev => (prev ?? 0) + 1);

This is useful for:

  • counters;
  • retry state;
  • toggles;
  • queue metadata;
  • any concurrent read-modify-write flow.

With timeout or abort signal

const controller = new AbortController();

await storage.update(
    "installCount",
    prev => (prev ?? 0) + 1,
    {
        signal: controller.signal,
        timeout: 500,
    }
);

Important note

Atomic operations rely on the Web Locks API.

  • update() uses locking for safe writes;
  • remove() and clear() are lock-aware too;
  • set() and get() still work without Web Locks;
  • if Web Locks are unavailable, atomic operations will throw.

Storage areas

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

const local = Storage.Local<{draft?: string}>();
const session = Storage.Session<{popupOpen?: boolean}>();
const sync = Storage.Sync<{theme?: string}>();
const managed = Storage.Managed<{policyEnabled?: boolean}>();

Namespaces

Use namespaces when different modules may use the same key names.

const auth = Storage.Local<{token?: string}>({namespace: "auth"});
const ui = Storage.Local<{token?: string}>({namespace: "ui"});

These storage instances stay isolated even if the key name is the same.

Secure storage

SecureStorage encrypts values before writing them to chrome.storage.

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

interface AuthState {
    accessToken?: string;
    refreshToken?: string;
}

const authStorage = SecureStorage.Local<AuthState>({
    namespace: "auth",
    secureKey: "AppSecret",
});

await authStorage.set("accessToken", "jwt-token");
const token = await authStorage.get("accessToken");

Use it for tokens, sensitive flags, or other small private values.

MonoStorage

MonoStorage is useful when one feature should live under a single top-level storage key.

For example, keeping popup state together:

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

interface PopupState {
    search?: string;
    selectedTab?: "overview" | "history";
    filters?: string[];
}

const popup = Storage.Local<PopupState>({key: "popup"});

Then use it like a regular storage instance:

await popup.set("search", "open tabs");
await popup.update("filters", prev => [...(prev ?? []), "pinned"]);

const state = await popup.getAll();

This keeps related values grouped and easier to manage.

Watching changes

Listen to all keys:

const unsubscribe = settings.watch((next, prev, key) => {
    console.log("changed", key, {prev, next});
});

Or subscribe only to specific keys:

const unsubscribe = settings.watch({
    theme(next, prev) {
        console.log("theme changed", prev, "->", next);
    },
    language(next, prev) {
        console.log("language changed", prev, "->", next);
    },
});

React

The React adapter is available via @addon-core/storage/react.

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

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

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

You can also pass a custom storage instance:

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

const settings = Storage.Sync<{theme?: "light" | "dark"}>({namespace: "settings"});

export function ThemeToggle() {
    const [theme, setTheme] = useStorage({
        key: "theme",
        storage: settings,
        defaultValue: "light",
    });

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

Core methods

Every storage instance exposes the same small API:

  • get(key)
  • set(key, value)
  • update(key, updater, options?)
  • getAll()
  • remove(key | keys, options?)
  • clear(options?)
  • watch(callback | handlers)

Custom locking

If you need custom lock behavior, pass your own locker:

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

const locker: StorageLocker = {
    async request(name, task) {
        return await task();
    },
};

const storage = new Storage<{count?: number}>({
    area: "local",
    locker,
});

Notes

  • Built for browser extensions where chrome.storage is available
  • SecureStorage requires Web Crypto API support
  • chrome.storage quotas still apply, especially for sync