{"id":31720636,"url":"https://github.com/addon-stack/storage","last_synced_at":"2026-01-20T17:53:14.121Z","repository":{"id":315454375,"uuid":"1058982787","full_name":"addon-stack/storage","owner":"addon-stack","description":"Type-safe chrome.storage wrapper for WebExtensions with namespaces, AES‑GCM encryption, MonoStorage, and a React hook.","archived":false,"fork":false,"pushed_at":"2025-10-07T14:42:36.000Z","size":477,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-07T16:35:52.889Z","etag":null,"topics":["browser-extension","chrome","chrome-extension","react","storage"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/addon-stack.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-17T20:24:38.000Z","updated_at":"2025-10-07T14:42:37.000Z","dependencies_parsed_at":"2025-10-07T16:17:53.257Z","dependency_job_id":"151f28c2-a125-4b86-a4e5-b4939eb3f0de","html_url":"https://github.com/addon-stack/storage","commit_stats":null,"previous_names":["addon-stack/storage"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/addon-stack/storage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Fstorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Fstorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Fstorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Fstorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/addon-stack","download_url":"https://codeload.github.com/addon-stack/storage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Fstorage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278972330,"owners_count":26078017,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser-extension","chrome","chrome-extension","react","storage"],"created_at":"2025-10-09T03:17:53.637Z","updated_at":"2025-10-09T03:17:58.780Z","avatar_url":"https://github.com/addon-stack.png","language":"TypeScript","readme":"# @addon-core/storage\n\nType-safe, ergonomic wrapper around chrome.storage for browser extensions (WebExtensions) with namespaces, multiple\nstorage areas, encryption (AES‑GCM), bucket-style storage (MonoStorage), and a React adapter.\n\n[![npm version](https://img.shields.io/npm/v/%40addon-core%2Fstorage.svg?logo=npm)](https://www.npmjs.com/package/@addon-core/storage)\n[![npm downloads](https://img.shields.io/npm/dm/%40addon-core%2Fstorage.svg)](https://www.npmjs.com/package/@addon-core/storage)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md)\n\n- Simple API: set, get, getAll, remove, clear, watch\n- Storage areas: local, session, sync, managed\n- Namespaces to logically separate keys\n- Secure storage: SecureStorage (AES‑GCM, app key)\n- MonoStorage — store multiple values under a single top-level key\n- React hook useStorage for two-way binding between state and storage\n- First-class TypeScript support (strict typing for keys and values)\n\n## Installation\n\n```bash\n# with your preferred package manager\nnpm i @addon-core/storage\n# or\nyarn add @addon-core/storage\n# or\npnpm add @addon-core/storage\n```\n\nRequirements and environment:\n\n- The library targets browser extension environments where chrome.storage is available.\n- For the React adapter, peer dependencies react and react-dom are required (optionally @types/react and @types/react-dom for TypeScript).\n- SecureStorage relies on the Web Crypto API (crypto.subtle, AES‑GCM), available in modern browsers.\n\n## Quick start\n\n```ts\nimport {Storage} from \"@addon-core/storage\";\n\n// Optionally set a namespace to isolate module keys\nconst storage = Storage.Local\u003c{ token?: string; theme?: \"light\" | \"dark\" }\u003e({namespace: \"app\"});\n\nawait storage.set(\"token\", \"abc123\");\nconst token = await storage.get(\"token\"); // \"abc123\"\n\nawait storage.remove(\"token\");\nawait storage.clear(); // clears only keys from the current namespace (if set)\n```\n\n## API overview\n\nThe package exports:\n\n- Provider classes: `Storage`, `SecureStorage`, `MonoStorage`\n- Types: `StorageProvider`, `StorageState`, `StorageWatchOptions`, `StorageWatchCallback`, `StorageWatchKeyCallback`\n- React adapter: `useStorage` from the submodule `@addon-core/storage/react`\n\n### Creating a provider\n\nThere are two ways to create a provider instance.\n\n1) Via constructor with options:\n\n```ts\nimport {Storage} from \"@addon-core/storage\";\n\nconst s1 = new Storage\u003c{ count?: number }\u003e({area: \"local\", namespace: \"counter\"});\n```\n\n2) Via convenient static factories:\n\n```ts\nimport {Storage, SecureStorage} from \"@addon-core/storage\";\n\nconst sLocal = Storage.Local\u003c{ user?: string }\u003e({namespace: \"app\"});\nconst sSession = Storage.Session\u003c{ tmp?: string }\u003e();\nconst sSync = Storage.Sync\u003c{ settings?: any }\u003e({namespace: \"global\"});\nconst sManaged = Storage.Managed\u003c{ policy?: any }\u003e(); // for policy-managed storage\n\n// SecureStorage — values are encrypted (AES‑GCM) under the reserved prefix \"secure:\"\nconst secure = SecureStorage.Local\u003c{ token?: string }\u003e({secureKey: \"MyStrongKey\", namespace: \"auth\"});\n```\n\nProvider options:\n\n- `area?: \"local\" | \"session\" | \"sync\" | \"managed\"` — storage area (defaults to `local`)\n- `namespace?: string` — optional namespace; keys become `namespace:key` (for `SecureStorage`: `secure:namespace:key`)\n- For `SecureStorage`: additionally `secureKey?: string` — a string used to derive the encryption key.\n\n### Provider methods\n\nAll providers (`Storage`, `SecureStorage`, `MonoStorage`) share the `StorageProvider\u003cT\u003e` interface:\n\n```ts\ninterface StorageProvider\u003cT\u003e {\n    set\u003cK extends keyof T\u003e(key: K, value: T[K]): Promise\u003cvoid\u003e;\n\n    get\u003cK extends keyof T\u003e(key: K): Promise\u003cT[K] | undefined\u003e;\n\n    getAll(): Promise\u003cPartial\u003cT\u003e\u003e;\n\n    remove\u003cK extends keyof T\u003e(keys: K | K[]): Promise\u003cvoid\u003e;\n\n    clear(): Promise\u003cvoid\u003e;\n\n    watch(options: StorageWatchOptions\u003cT\u003e): () =\u003e void; // returns an unsubscribe function\n}\n```\n\nWhere `StorageWatchOptions\u003cT\u003e` is either a map of per-key callbacks or a single callback:\n\n```ts\n// Option 1: a single handler for all changes\nconst unsubscribe = storage.watch((next, prev, key) =\u003e {\n    console.log(\"changed\", {key, next, prev});\n});\n\n// Option 2: specific handlers per key\nconst un = storage.watch({\n    token(newVal, oldVal) {\n        console.log(\"token changed\", newVal, oldVal);\n    },\n    theme(newVal, oldVal) {\n        console.log(\"theme changed\", newVal, oldVal);\n    },\n});\n\n// Later\nun(); // unsubscribe\n```\n\nNotes:\n\n- `getAll()` returns entries (key–value pairs) scoped to the current provider (area, namespace, provider kind) and resolves to `Partial\u003cT\u003e`.\n- In the single-callback form of `watch()`, the third argument is the `key` that changed.\n- `SecureStorage` transparently encrypts/decrypts values. They are stored as strings, while you work with original types\n  externally.\n\n### MonoStorage — a “bucket” under one key\n\n`MonoStorage` lets you keep several values under a single top-level key (a bucket). Handy when you need to atomically\nstore and update a set of related values.\n\nYou can create it in two ways:\n\n1) Explicitly:\n\n```ts\nimport {MonoStorage, Storage} from \"@addon-core/storage\";\n\ntype Bucket = { a?: number; b?: string };\nconst base = Storage.Local\u003cRecord\u003c\"bucket\", Partial\u003cBucket\u003e\u003e\u003e();\nconst mono = new MonoStorage\u003cBucket, \"bucket\"\u003e(\"bucket\", base);\n\nawait mono.set(\"a\", 1);\nawait mono.set(\"b\", \"x\");\nconsole.log(await mono.getAll()); // { a: 1, b: \"x\" }\n```\n\n2) Via the factory with the `key` parameter — you’ll get MonoStorage right away:\n\n```ts\nimport {Storage} from \"@addon-core/storage\";\n\nconst mono = Storage.Local\u003c{ a?: number; b?: string }\u003e({key: \"bucket\"});\nawait mono.set(\"a\", 1);\n```\n\nHighlights:\n\n- When the last value in the “bucket” is removed, the top-level key is cleared entirely.\n- `watch()` in MonoStorage invokes callbacks only on actual value changes (deep/structural comparison).\n\n### SecureStorage — value encryption\n\n```ts\nimport {SecureStorage} from \"@addon-core/storage\";\n\ntype Auth = { token?: string; profile?: { id: string } };\nconst secure = SecureStorage.Local\u003cAuth\u003e({secureKey: \"AppSecret\", namespace: \"auth\"});\n\nawait secure.set(\"token\", \"jwt.token.value\");\nconst token = await secure.get(\"token\"); // decrypted\n```\n\nUnder the hood AES‑GCM (Web Crypto API) is used. Don’t keep `secureKey` in public code — obtain it from protected\nsources (e.g., native settings, enterprise policy, remote configuration, etc.).\n\n## React adapter\n\nThe submodule `@addon-core/storage/react` provides the `useStorage` hook to synchronize component state with\nchrome.storage.\n\nSignatures (simplified):\n\n```ts\n// useStorage\u003cT\u003e(options: { key: string; storage?: StorageProvider\u003cRecord\u003cstring, any\u003e\u003e; defaultValue?: T }):\n//   readonly [T | undefined, (v: T) =\u003e void, () =\u003e void]\n// useStorage\u003cT\u003e(key: string, defaultValue?: T):\n//   readonly [T | undefined, (v: T) =\u003e void, () =\u003e void]\n```\n\nBasic example:\n\n```tsx\nimport React from \"react\";\nimport {useStorage} from \"@addon-core/storage/react\";\n\nexport function ThemeSwitch() {\n    const [theme, setTheme] = useStorage\u003c\"light\" | \"dark\"\u003e(\"theme\", \"light\");\n\n    return (\n        \u003cbutton onClick={() =\u003e setTheme(theme === \"light\" ? \"dark\" : \"light\")}\u003eTheme: {theme}\u003c/button\u003e\n    );\n}\n```\n\nUsing a custom provider and default value:\n\n```tsx\nimport React from \"react\";\nimport {Storage} from \"@addon-core/storage\";\nimport {useStorage} from \"@addon-core/storage/react\";\n\nconst storage = Storage.Sync\u003cRecord\u003cstring, any\u003e\u003e({namespace: \"app\"});\n\nexport function Profile() {\n    const [name, setName, removeName] = useStorage\u003cstring\u003e({key: \"name\", storage, defaultValue: \"Anonymous\"});\n\n    return (\n        \u003cdiv\u003e\n            \u003cinput value={name ?? \"\"} onChange={e =\u003e setName(e.target.value)}/\u003e\n            \u003cbutton onClick={removeName}\u003eReset\u003c/button\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\n## Practical tips\n\n- Don’t mix data from different modules — use `namespace`.\n- To sync settings across devices, use the `sync` area.\n- In test environments, use WebExtensions mocks (e.g., `jest-webextension-mock`).\n- Don’t store large amounts of data — `chrome.storage` has quotas. Store settings and lightweight data only.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddon-stack%2Fstorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faddon-stack%2Fstorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddon-stack%2Fstorage/lists"}